Enhancement: merge documents as versions (#13515)

This commit is contained in:
shamoon
2026-08-19 07:20:14 -07:00
committed by GitHub
parent c5c5cc0b1d
commit e389298aab
30 changed files with 1504 additions and 53 deletions
@@ -95,6 +95,9 @@
<button ngbDropdownItem (click)="mergeSelected()" [disabled]="!userCanAdd || list.allSelected || list.selectedCount < 2">
<i-bs name="journals" class="me-1"></i-bs><ng-container i18n>Merge</ng-container>
</button>
<button ngbDropdownItem (click)="mergeSelectedAsVersions()" [disabled]="!userOwnsAll || !userCanEditAll || !userCanDelete || list.allSelected || list.selectedCount < 2">
<i-bs name="journal-bookmark-fill" class="me-1"></i-bs><ng-container i18n>Merge as versions</ng-container>
</button>
</div>
</div>
</div>
@@ -1248,6 +1248,89 @@ describe('BulkEditorComponent', () => {
expect(documentListViewService.selected.size).toEqual(0)
})
it('should support merging documents as versions', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[0]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest.spyOn(documentService, 'getFew').mockReturnValue(
of({
all: [3, 4],
count: 2,
results: [
{ id: 3, title: 'Document 3' },
{ id: 4, title: 'Document 4' },
],
})
)
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
jest
.spyOn(permissionsService, 'currentUserOwnsObject')
.mockReturnValue(true)
const mergeAsVersionsSpy = jest
.spyOn(documentService, 'mergeDocumentsAsVersions')
.mockReturnValue(of(true))
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
fixture.detectChanges()
component.mergeSelectedAsVersions()
expect(modal).not.toBeUndefined()
modal.componentInstance.rootDocumentID.set(4)
modal.componentInstance.confirm()
expect(mergeAsVersionsSpy).toHaveBeenCalledWith([3, 4], 4)
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&include_selection_data=true`
)
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
)
expect(documentListViewService.selected.size).toEqual(0)
expect(toastInfoSpy).toHaveBeenCalledWith('Documents merged as versions.')
})
it('should not report success when merging documents as versions fails', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[0]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest.spyOn(documentService, 'getFew').mockReturnValue(
of({
all: [3, 4],
count: 2,
results: [
{ id: 3, title: 'Document 3' },
{ id: 4, title: 'Document 4' },
],
})
)
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(documentService, 'mergeDocumentsAsVersions')
.mockReturnValue(throwError(() => new Error('failed')))
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
const toastErrorSpy = jest.spyOn(toastService, 'showError')
fixture.detectChanges()
component.mergeSelectedAsVersions()
modal.componentInstance.rootDocumentID.set(4)
modal.componentInstance.confirm()
expect(toastErrorSpy).toHaveBeenCalled()
expect(toastInfoSpy).not.toHaveBeenCalled()
})
it('should support bulk download with archive, originals or both and file formatting', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
@@ -50,6 +50,7 @@ import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { flattenTags } from 'src/app/utils/flatten-tags'
import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
import { MergeAsVersionsConfirmDialogComponent } from '../../common/confirm-dialog/merge-as-versions-confirm-dialog/merge-as-versions-confirm-dialog.component'
import { MergeConfirmDialogComponent } from '../../common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component'
import { RotateConfirmDialogComponent } from '../../common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component'
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
@@ -171,6 +172,13 @@ export class BulkEditorComponent
)
}
get userCanDelete(): boolean {
return this.permissionService.currentUserCan(
PermissionAction.Delete,
PermissionType.Document
)
}
ngOnInit() {
if (
this.permissionService.currentUserCan(
@@ -287,14 +295,17 @@ export class BulkEditorComponent
private executeDocumentAction(
modal: NgbModalRef,
request: Observable<any>,
options: { deleteOriginals?: boolean } = {}
options: { clearSelection?: boolean; successMessage?: string } = {}
) {
if (modal) {
modal.componentInstance.buttonsEnabled.set(false)
}
request.pipe(first()).subscribe({
next: () => {
this.handleOperationSuccess(modal, options.deleteOriginals ?? false)
this.handleOperationSuccess(modal, options.clearSelection ?? false)
if (options.successMessage) {
this.toastService.showInfo(options.successMessage)
}
},
error: (error) => this.handleOperationError(modal, error),
})
@@ -990,7 +1001,7 @@ export class BulkEditorComponent
this.executeDocumentAction(
modal,
this.documentService.mergeDocuments(mergeDialog.documentIDs(), args),
{ deleteOriginals: !!args.delete_originals }
{ clearSelection: !!args.delete_originals }
)
this.toastService.showInfo(
$localize`Merged document will be queued for consumption.`
@@ -998,6 +1009,35 @@ export class BulkEditorComponent
})
}
mergeSelectedAsVersions() {
let modal = this.modalService.open(MergeAsVersionsConfirmDialogComponent, {
backdrop: 'static',
})
const mergeDialog =
modal.componentInstance as MergeAsVersionsConfirmDialogComponent
const documentIDs = Array.from(this.list.selected)
mergeDialog.title = $localize`Merge as versions`
mergeDialog.message = $localize`The selected documents will become versions of the root document.`
mergeDialog.btnCaption = $localize`Proceed`
mergeDialog.documentIDs.set(documentIDs)
mergeDialog.rootDocumentID.set(documentIDs[0])
mergeDialog.confirmClicked
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
this.executeDocumentAction(
modal,
this.documentService.mergeDocumentsAsVersions(
mergeDialog.documentIDs(),
mergeDialog.rootDocumentID()
),
{
clearSelection: true,
successMessage: $localize`Documents merged as versions.`,
}
)
})
}
public setCustomFieldValues(changedCustomFields: ChangedItems) {
const modal = this.modalService.open(CustomFieldsBulkEditDialogComponent, {
backdrop: 'static',