From 6e82676fedbb50e2df017f7de10bed46e048e892 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 8 Mar 2026 20:59:21 -0700 Subject: [PATCH] Frontend use the endpoints --- .../document-detail.component.spec.ts | 38 ++++---- .../document-detail.component.ts | 4 +- .../bulk-editor/bulk-editor.component.spec.ts | 23 +++-- .../bulk-editor/bulk-editor.component.ts | 91 +++++++++++-------- .../services/rest/document.service.spec.ts | 58 ++++++++++++ .../src/app/services/rest/document.service.ts | 33 +++++++ 6 files changed, 174 insertions(+), 73 deletions(-) diff --git a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts index 990c6b4d0..6fcb8a051 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts @@ -1669,18 +1669,15 @@ describe('DocumentDetailComponent', () => { modal.componentInstance.pages = [{ page: 1, rotate: 0, splitAfter: false }] modal.componentInstance.confirm() let req = httpTestingController.expectOne( - `${environment.apiBaseUrl}documents/bulk_edit/` + `${environment.apiBaseUrl}documents/edit_pdf/` ) expect(req.request.body).toEqual({ documents: [10], - method: 'edit_pdf', - parameters: { - operations: [{ page: 1, rotate: 0, doc: 0 }], - delete_original: false, - update_document: false, - include_metadata: true, - source_mode: 'explicit_selection', - }, + operations: [{ page: 1, rotate: 0, doc: 0 }], + delete_original: false, + update_document: false, + include_metadata: true, + source_mode: 'explicit_selection', }) req.error(new ErrorEvent('failed')) expect(errorSpy).toHaveBeenCalled() @@ -1691,7 +1688,7 @@ describe('DocumentDetailComponent', () => { modal.componentInstance.deleteOriginal = true modal.componentInstance.confirm() req = httpTestingController.expectOne( - `${environment.apiBaseUrl}documents/bulk_edit/` + `${environment.apiBaseUrl}documents/edit_pdf/` ) req.flush(true) expect(closeSpy).toHaveBeenCalled() @@ -1711,18 +1708,15 @@ describe('DocumentDetailComponent', () => { dialog.deleteOriginal = true dialog.confirm() const req = httpTestingController.expectOne( - `${environment.apiBaseUrl}documents/bulk_edit/` + `${environment.apiBaseUrl}documents/remove_password/` ) expect(req.request.body).toEqual({ documents: [10], - method: 'remove_password', - parameters: { - password: 'secret', - update_document: false, - include_metadata: false, - delete_original: true, - source_mode: 'explicit_selection', - }, + password: 'secret', + update_document: false, + include_metadata: false, + delete_original: true, + source_mode: 'explicit_selection', }) req.flush(true) }) @@ -1737,7 +1731,7 @@ describe('DocumentDetailComponent', () => { expect(errorSpy).toHaveBeenCalled() httpTestingController.expectNone( - `${environment.apiBaseUrl}documents/bulk_edit/` + `${environment.apiBaseUrl}documents/remove_password/` ) }) @@ -1753,7 +1747,7 @@ describe('DocumentDetailComponent', () => { modal.componentInstance as PasswordRemovalConfirmDialogComponent dialog.confirm() const req = httpTestingController.expectOne( - `${environment.apiBaseUrl}documents/bulk_edit/` + `${environment.apiBaseUrl}documents/remove_password/` ) req.error(new ErrorEvent('failed')) @@ -1774,7 +1768,7 @@ describe('DocumentDetailComponent', () => { modal.componentInstance as PasswordRemovalConfirmDialogComponent dialog.confirm() const req = httpTestingController.expectOne( - `${environment.apiBaseUrl}documents/bulk_edit/` + `${environment.apiBaseUrl}documents/remove_password/` ) req.flush(true) diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts index a2c193770..16d0018e1 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.ts @@ -1766,7 +1766,7 @@ export class DocumentDetailComponent .subscribe(() => { modal.componentInstance.buttonsEnabled = false this.documentsService - .bulkEdit([sourceDocumentId], 'edit_pdf', { + .editPdfDocuments([sourceDocumentId], { operations: modal.componentInstance.getOperations(), delete_original: modal.componentInstance.deleteOriginal, update_document: @@ -1824,7 +1824,7 @@ export class DocumentDetailComponent dialog.buttonsEnabled = false this.networkActive = true this.documentsService - .bulkEdit([sourceDocumentId], 'remove_password', { + .removePasswordDocuments([sourceDocumentId], { password: this.password, update_document: dialog.updateDocument, include_metadata: dialog.includeMetadata, diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts index 694899d3d..d3ad1a841 100644 --- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts +++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts @@ -979,13 +979,13 @@ describe('BulkEditorComponent', () => { modal.componentInstance.rotate() modal.componentInstance.confirm() let req = httpTestingController.expectOne( - `${environment.apiBaseUrl}documents/bulk_edit/` + `${environment.apiBaseUrl}documents/rotate/` ) req.flush(true) expect(req.request.body).toEqual({ documents: [3, 4], - method: 'rotate', - parameters: { degrees: 90 }, + degrees: 90, + source_mode: 'latest_version', }) httpTestingController.match( `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true` @@ -1021,13 +1021,12 @@ describe('BulkEditorComponent', () => { modal.componentInstance.metadataDocumentID = 3 modal.componentInstance.confirm() let req = httpTestingController.expectOne( - `${environment.apiBaseUrl}documents/bulk_edit/` + `${environment.apiBaseUrl}documents/merge/` ) req.flush(true) expect(req.request.body).toEqual({ documents: [3, 4], - method: 'merge', - parameters: { metadata_document_id: 3 }, + metadata_document_id: 3, }) httpTestingController.match( `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true` @@ -1040,13 +1039,13 @@ describe('BulkEditorComponent', () => { modal.componentInstance.deleteOriginals = true modal.componentInstance.confirm() req = httpTestingController.expectOne( - `${environment.apiBaseUrl}documents/bulk_edit/` + `${environment.apiBaseUrl}documents/merge/` ) req.flush(true) expect(req.request.body).toEqual({ documents: [3, 4], - method: 'merge', - parameters: { metadata_document_id: 3, delete_originals: true }, + metadata_document_id: 3, + delete_originals: true, }) httpTestingController.match( `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true` @@ -1061,13 +1060,13 @@ describe('BulkEditorComponent', () => { modal.componentInstance.archiveFallback = true modal.componentInstance.confirm() req = httpTestingController.expectOne( - `${environment.apiBaseUrl}documents/bulk_edit/` + `${environment.apiBaseUrl}documents/merge/` ) req.flush(true) expect(req.request.body).toEqual({ documents: [3, 4], - method: 'merge', - parameters: { metadata_document_id: 3, archive_fallback: true }, + metadata_document_id: 3, + archive_fallback: true, }) httpTestingController.match( `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true` diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts index 8e97b5f9e..7ab3ec042 100644 --- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts +++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -12,7 +12,7 @@ import { } from '@ng-bootstrap/ng-bootstrap' import { saveAs } from 'file-saver' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' -import { first, map, Subject, switchMap, takeUntil } from 'rxjs' +import { first, map, Observable, Subject, switchMap, takeUntil } from 'rxjs' import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component' import { CustomField } from 'src/app/data/custom-field' import { MatchingModel } from 'src/app/data/matching-model' @@ -261,41 +261,50 @@ export class BulkEditorComponent args: any, overrideDocumentIDs?: number[] ) { - if (modal) { - modal.componentInstance.buttonsEnabled = false - } - this.documentService - .bulkEdit( + this.executeOperation( + modal, + this.documentService.bulkEdit( overrideDocumentIDs ?? Array.from(this.list.selected), method, args - ) - .pipe(first()) - .subscribe({ - next: () => { - if (args['delete_originals']) { - this.list.selected.clear() - } - this.list.reload() - this.list.reduceSelectionToFilter() - this.list.selected.forEach((id) => { - this.openDocumentService.refreshDocument(id) - }) - this.savedViewService.maybeRefreshDocumentCounts() - if (modal) { - modal.close() - } - }, - error: (error) => { - if (modal) { - modal.componentInstance.buttonsEnabled = true - } - this.toastService.showError( - $localize`Error executing bulk operation`, - error - ) - }, - }) + ), + args + ) + } + + private executeOperation( + modal: NgbModalRef, + request: Observable, + args: any = {} + ) { + if (modal) { + modal.componentInstance.buttonsEnabled = false + } + request.pipe(first()).subscribe({ + next: () => { + if (args['delete_originals']) { + this.list.selected.clear() + } + this.list.reload() + this.list.reduceSelectionToFilter() + this.list.selected.forEach((id) => { + this.openDocumentService.refreshDocument(id) + }) + this.savedViewService.maybeRefreshDocumentCounts() + if (modal) { + modal.close() + } + }, + error: (error) => { + if (modal) { + modal.componentInstance.buttonsEnabled = true + } + this.toastService.showError( + $localize`Error executing bulk operation`, + error + ) + }, + }) } private applySelectionData( @@ -838,9 +847,13 @@ export class BulkEditorComponent .pipe(takeUntil(this.unsubscribeNotifier)) .subscribe(() => { rotateDialog.buttonsEnabled = false - this.executeBulkOperation(modal, 'rotate', { - degrees: rotateDialog.degrees, - }) + this.executeOperation( + modal, + this.documentService.rotateDocuments( + Array.from(this.list.selected), + rotateDialog.degrees + ) + ) }) } @@ -867,7 +880,11 @@ export class BulkEditorComponent args['archive_fallback'] = true } mergeDialog.buttonsEnabled = false - this.executeBulkOperation(modal, 'merge', args, mergeDialog.documentIDs) + this.executeOperation( + modal, + this.documentService.mergeDocuments(mergeDialog.documentIDs, args), + args + ) this.toastService.showInfo( $localize`Merged document will be queued for consumption.` ) diff --git a/src-ui/src/app/services/rest/document.service.spec.ts b/src-ui/src/app/services/rest/document.service.spec.ts index 78cef69cb..8dc13e214 100644 --- a/src-ui/src/app/services/rest/document.service.spec.ts +++ b/src-ui/src/app/services/rest/document.service.spec.ts @@ -230,6 +230,64 @@ describe(`DocumentService`, () => { }) }) + it('should call appropriate api endpoint for rotate documents', () => { + const ids = [1, 2, 3] + subscription = service.rotateDocuments(ids, 90).subscribe() + const req = httpTestingController.expectOne( + `${environment.apiBaseUrl}${endpoint}/rotate/` + ) + expect(req.request.method).toEqual('POST') + expect(req.request.body).toEqual({ + documents: ids, + degrees: 90, + source_mode: 'latest_version', + }) + }) + + it('should call appropriate api endpoint for merge documents', () => { + const ids = [1, 2, 3] + const args = { metadata_document_id: 1, delete_originals: true } + subscription = service.mergeDocuments(ids, args).subscribe() + const req = httpTestingController.expectOne( + `${environment.apiBaseUrl}${endpoint}/merge/` + ) + expect(req.request.method).toEqual('POST') + expect(req.request.body).toEqual({ + documents: ids, + metadata_document_id: 1, + delete_originals: true, + }) + }) + + it('should call appropriate api endpoint for edit pdf', () => { + const ids = [1] + const args = { operations: [{ page: 1, rotate: 90, doc: 0 }] } + subscription = service.editPdfDocuments(ids, args).subscribe() + const req = httpTestingController.expectOne( + `${environment.apiBaseUrl}${endpoint}/edit_pdf/` + ) + expect(req.request.method).toEqual('POST') + expect(req.request.body).toEqual({ + documents: ids, + operations: [{ page: 1, rotate: 90, doc: 0 }], + }) + }) + + it('should call appropriate api endpoint for remove password', () => { + const ids = [1] + const args = { password: 'secret', update_document: true } + subscription = service.removePasswordDocuments(ids, args).subscribe() + const req = httpTestingController.expectOne( + `${environment.apiBaseUrl}${endpoint}/remove_password/` + ) + expect(req.request.method).toEqual('POST') + expect(req.request.body).toEqual({ + documents: ids, + password: 'secret', + update_document: true, + }) + }) + it('should return the correct preview URL for a single document', () => { let url = service.getPreviewUrl(documents[0].id) expect(url).toEqual( diff --git a/src-ui/src/app/services/rest/document.service.ts b/src-ui/src/app/services/rest/document.service.ts index bc06d571c..18bfe8f67 100644 --- a/src-ui/src/app/services/rest/document.service.ts +++ b/src-ui/src/app/services/rest/document.service.ts @@ -307,6 +307,39 @@ export class DocumentService extends AbstractPaperlessService { }) } + rotateDocuments( + ids: number[], + degrees: number, + sourceMode: BulkEditSourceMode = BulkEditSourceMode.LATEST_VERSION + ) { + return this.http.post(this.getResourceUrl(null, 'rotate'), { + documents: ids, + degrees, + source_mode: sourceMode, + }) + } + + mergeDocuments(ids: number[], args: any = {}) { + return this.http.post(this.getResourceUrl(null, 'merge'), { + documents: ids, + ...args, + }) + } + + editPdfDocuments(ids: number[], args: any) { + return this.http.post(this.getResourceUrl(null, 'edit_pdf'), { + documents: ids, + ...args, + }) + } + + removePasswordDocuments(ids: number[], args: any) { + return this.http.post(this.getResourceUrl(null, 'remove_password'), { + documents: ids, + ...args, + }) + } + getSelectionData(ids: number[]): Observable { return this.http.post( this.getResourceUrl(null, 'selection_data'),