mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-21 07:18:13 +00:00
Frontend use the bulk edit
This commit is contained in:
4 files changed
+35
-121
No files matched your search
@@ -98,7 +98,7 @@
|
||||
<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>
|
||||
<button ngbDropdownItem (click)="runZoneOcrSelected()" [disabled]="!userCanEditAll || list.allSelected">
|
||||
<button ngbDropdownItem (click)="runZoneOcrSelected()" [disabled]="!userCanEditAll">
|
||||
<i-bs name="file-earmark-ruled" class="me-1"></i-bs><ng-container i18n>Run Zone OCR</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1286,91 +1286,36 @@ describe('BulkEditorComponent', () => {
|
||||
) // listAllFilteredIds
|
||||
})
|
||||
|
||||
it('finishes bulk zone OCR despite skipped and failed documents and refreshes the list', () => {
|
||||
it('should support run zone ocr', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((m) => (modal = m[0]))
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
jest
|
||||
.spyOn(documentListViewService, 'selected', 'get')
|
||||
.mockReturnValue(new Set([3, 4, 5, 6]))
|
||||
const modalSpy = jest.spyOn(modalService, 'open')
|
||||
const refreshSpy = jest
|
||||
.spyOn(component as any, 'handleOperationSuccess')
|
||||
.mockImplementation(() => {})
|
||||
const errorSpy = jest.spyOn(toastService, 'showError')
|
||||
|
||||
component.runZoneOcrSelected()
|
||||
const modal = modalSpy.mock.results[0].value
|
||||
modal.componentInstance.confirm()
|
||||
const requests = [3, 4, 5, 6].map((id) =>
|
||||
httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/${id}/run-zone-ocr/`
|
||||
)
|
||||
)
|
||||
requests[1].flush({
|
||||
status: 'skipped',
|
||||
reason: 'no_document_type',
|
||||
results: [],
|
||||
})
|
||||
requests[2].flush({ status: 'skipped', reason: 'no_template', results: [] })
|
||||
requests[3].flush(
|
||||
{ error: 'OCR failed' },
|
||||
{ status: 500, statusText: 'Server Error' }
|
||||
)
|
||||
|
||||
expect(requests[0].cancelled).toBe(false)
|
||||
expect(refreshSpy).not.toHaveBeenCalled()
|
||||
requests[0].flush({ status: 'completed', results: [] })
|
||||
|
||||
expect(refreshSpy).toHaveBeenCalledWith(modal, false)
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
'Zone OCR: 1 completed, 2 skipped, 1 failed. Failed documents: 6',
|
||||
[{ document: 6, error: { error: 'OCR failed' } }]
|
||||
)
|
||||
})
|
||||
|
||||
it.each([
|
||||
[403, 'Permission denied'],
|
||||
[404, 'Document file not found'],
|
||||
[500, 'OCR failed'],
|
||||
])('reports a %s OCR error as a failure, not a skip', (status, message) => {
|
||||
jest
|
||||
.spyOn(documentListViewService, 'selected', 'get')
|
||||
.mockReturnValue(new Set([3]))
|
||||
const modalSpy = jest.spyOn(modalService, 'open')
|
||||
jest
|
||||
.spyOn(component as any, 'handleOperationSuccess')
|
||||
.mockImplementation(() => {})
|
||||
const errorSpy = jest.spyOn(toastService, 'showError')
|
||||
|
||||
component.runZoneOcrSelected()
|
||||
modalSpy.mock.results[0].value.componentInstance.confirm()
|
||||
httpTestingController
|
||||
.expectOne(`${environment.apiBaseUrl}documents/3/run-zone-ocr/`)
|
||||
.flush({ error: message }, { status, statusText: 'Error' })
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
'Zone OCR: 0 completed, 0 skipped, 1 failed. Failed documents: 3',
|
||||
[{ document: 3, error: { error: message } }]
|
||||
)
|
||||
})
|
||||
|
||||
it('reports a successful bulk zone OCR run', () => {
|
||||
.spyOn(documentListViewService, 'documents', 'get')
|
||||
.mockReturnValue([{ id: 3 }, { id: 4 }])
|
||||
jest
|
||||
.spyOn(documentListViewService, 'selected', 'get')
|
||||
.mockReturnValue(new Set([3, 4]))
|
||||
jest
|
||||
.spyOn(documentService, 'runZoneOcr')
|
||||
.mockReturnValue(of({ status: 'completed', results: [] }))
|
||||
const modalSpy = jest.spyOn(modalService, 'open')
|
||||
jest
|
||||
.spyOn(component as any, 'handleOperationSuccess')
|
||||
.mockImplementation(() => {})
|
||||
const infoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
|
||||
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
|
||||
.mockReturnValue(true)
|
||||
fixture.detectChanges()
|
||||
component.runZoneOcrSelected()
|
||||
modalSpy.mock.results[0].value.componentInstance.confirm()
|
||||
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
'Zone OCR: 2 completed, 0 skipped, 0 failed.'
|
||||
expect(modal).not.toBeUndefined()
|
||||
modal.componentInstance.confirm()
|
||||
let req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/run_zone_ocr/`
|
||||
)
|
||||
req.flush(true)
|
||||
expect(req.request.body).toEqual({
|
||||
documents: [3, 4],
|
||||
})
|
||||
httpTestingController.match(
|
||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&include_selection_data=true`
|
||||
) // list reload
|
||||
httpTestingController.match(
|
||||
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
|
||||
) // listAllFilteredIds
|
||||
})
|
||||
|
||||
it('should support rotate', () => {
|
||||
|
||||
@@ -21,12 +21,9 @@ import {
|
||||
import { saveAs } from 'file-saver'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import {
|
||||
catchError,
|
||||
first,
|
||||
forkJoin,
|
||||
map,
|
||||
Observable,
|
||||
of,
|
||||
Subject,
|
||||
switchMap,
|
||||
takeUntil,
|
||||
@@ -970,54 +967,20 @@ export class BulkEditorComponent
|
||||
}
|
||||
|
||||
runZoneOcrSelected() {
|
||||
const ids = Array.from(this.list.selected)
|
||||
if (!ids.length) return
|
||||
const modal = this.modalService.open(ConfirmDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
modal.componentInstance.title = $localize`Run Zone OCR`
|
||||
modal.componentInstance.messageBold = $localize`Run zone OCR on ${this.getSelectionSize()} selected document(s)?`
|
||||
modal.componentInstance.message = $localize`Each document's type template (if it has one) is applied, overwriting the mapped fields.`
|
||||
modal.componentInstance.title = $localize`Run zone OCR confirm`
|
||||
modal.componentInstance.messageBold = $localize`This operation will run zone OCR on ${this.getSelectionSize()} selected document(s).`
|
||||
modal.componentInstance.message = $localize`Fields mapped by each document type's OCR template will be overwritten.`
|
||||
modal.componentInstance.btnClass = 'btn-warning'
|
||||
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||
modal.componentInstance.confirmClicked
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe(() => {
|
||||
modal.componentInstance.buttonsEnabled.set(false)
|
||||
this.executeDocumentAction(
|
||||
modal,
|
||||
forkJoin(
|
||||
ids.map((id) =>
|
||||
this.documentService.runZoneOcr(id).pipe(
|
||||
map(({ status }) => ({ id, status })),
|
||||
catchError((error) =>
|
||||
of({ id, status: 'failed' as const, error })
|
||||
)
|
||||
)
|
||||
)
|
||||
).pipe(
|
||||
tap((results) => {
|
||||
const completed = results.filter(
|
||||
(r) => r.status === 'completed'
|
||||
).length
|
||||
const skipped = results.filter(
|
||||
(r) => r.status === 'skipped'
|
||||
).length
|
||||
const failed = results.filter((r) => r.status === 'failed')
|
||||
const summary = $localize`Zone OCR: ${completed} completed, ${skipped} skipped, ${failed.length} failed.`
|
||||
if (failed.length) {
|
||||
const failedIds = failed.map((r) => r.id).join(', ')
|
||||
this.toastService.showError(
|
||||
`${summary} ${$localize`Failed documents: ${failedIds}`}`,
|
||||
failed.map((r) => ({
|
||||
document: r.id,
|
||||
error: r.error.error ?? r.error.message,
|
||||
}))
|
||||
)
|
||||
} else {
|
||||
this.toastService.showInfo(summary)
|
||||
}
|
||||
})
|
||||
)
|
||||
this.documentService.runZoneOcrDocuments(this.getSelectionQuery())
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -361,6 +361,12 @@ export class DocumentService extends AbstractPaperlessService<Document> {
|
||||
})
|
||||
}
|
||||
|
||||
runZoneOcrDocuments(selection: DocumentSelectionQuery) {
|
||||
return this.http.post(this.getResourceUrl(null, 'run_zone_ocr'), {
|
||||
...selection,
|
||||
})
|
||||
}
|
||||
|
||||
runZoneOcr(id: number): Observable<OcrZoneRunResponse> {
|
||||
return this.http.post<OcrZoneRunResponse>(
|
||||
this.getResourceUrl(id, 'run-zone-ocr'),
|
||||
|
||||
Reference in new issue
Block a user