From 058e27e3ef8f75b19be2c5bc65f2a7483b748e1a Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sat, 14 Nov 2026 11:30:35 -0800 Subject: [PATCH] Reprot more accurate notification for tasks with follow-ups --- .../admin/tasks/tasks.component.spec.ts | 15 ++++++++++++++- .../components/admin/tasks/tasks.component.ts | 9 +++++++-- .../document-detail/document-detail.component.ts | 8 +++++--- .../bulk-editor/bulk-editor.component.spec.ts | 9 +++++++++ .../bulk-editor/bulk-editor.component.ts | 15 +++++++++++---- .../services/task-notification.service.spec.ts | 16 ++++++++++++++++ .../app/services/task-notification.service.ts | 13 +++++++++---- src-ui/src/app/utils/document-operation.ts | 13 +++++++++++++ 8 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src-ui/src/app/components/admin/tasks/tasks.component.spec.ts b/src-ui/src/app/components/admin/tasks/tasks.component.spec.ts index 906ce93f28..694b729b2e 100644 --- a/src-ui/src/app/components/admin/tasks/tasks.component.spec.ts +++ b/src-ui/src/app/components/admin/tasks/tasks.component.spec.ts @@ -899,6 +899,7 @@ describe('TasksComponent', () => { ['modify_tags', 12, 'Bulk operation: Update tags', '(12 documents)'], ['run_workflow', 1, 'Run workflow', null], ['delete', 3, 'Bulk operation: Move to trash', '(3 documents)'], + ['reprocess', 2, 'Bulk operation: Document reprocessing', '(2 documents)'], ])( 'shows the operation, document count and outcome for %s', (operation, count, label, summary) => { @@ -933,7 +934,19 @@ describe('TasksComponent', () => { expect(name.textContent).not.toContain(task.task_type_display) expect( name.closest('tr').querySelector('.results-col').textContent - ).toContain('Completed') + ).toContain( + [ + 'reprocess', + 'rotate', + 'merge', + 'split', + 'delete_pages', + 'edit_pdf', + 'remove_password', + ].includes(operation) + ? 'Submitted for processing' + : 'Completed' + ) } ) diff --git a/src-ui/src/app/components/admin/tasks/tasks.component.ts b/src-ui/src/app/components/admin/tasks/tasks.component.ts index 80268a80ae..87ec037785 100644 --- a/src-ui/src/app/components/admin/tasks/tasks.component.ts +++ b/src-ui/src/app/components/admin/tasks/tasks.component.ts @@ -33,7 +33,10 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { TasksService } from 'src/app/services/tasks.service' import { ToastService } from 'src/app/services/toast.service' -import { getDocumentOperationLabel } from 'src/app/utils/document-operation' +import { + documentOperationHasFollowUpTasks, + getDocumentOperationLabel, +} from 'src/app/utils/document-operation' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component' import { LoadingComponentWithPermissions } from '../../loading-component/loading.component' @@ -470,7 +473,9 @@ export class TasksComponent task.status === PaperlessTaskStatus.Success && this.taskOperationLabel(task) ) { - return $localize`Completed` + return documentOperationHasFollowUpTasks(task.input_data?.operation) + ? $localize`Submitted for processing` + : $localize`Completed` } return null 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 f6aa0052d8..6f71887bcb 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 @@ -1564,7 +1564,7 @@ export class DocumentDetailComponent this.taskNotifications.showQueued( task_id, $localize`Document reprocessing queued.`, - { content: $localize`Document reprocessing completed.` } + { content: $localize`Document submitted for reprocessing.` } ) if (modal) { modal.close() @@ -2002,7 +2002,7 @@ export class DocumentDetailComponent task_id, $localize`PDF edit queued.`, { - content: $localize`PDF edit completed.`, + content: $localize`PDF edit submitted for processing.`, } ) modal.close() @@ -2059,7 +2059,9 @@ export class DocumentDetailComponent this.taskNotifications.showQueued( task_id, $localize`Password removal queued.`, - { content: $localize`Password removal completed.` } + { + content: $localize`Password removal submitted for processing.`, + } ) this.networkActive.set(false) modal.close() 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 782ed0b786..ba82aea301 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 @@ -1388,6 +1388,7 @@ describe('BulkEditorComponent', () => { }) it('should support rotate', () => { + const toast = jest.spyOn(toastService, 'show') let modal: NgbModalRef modalService.activeInstances.subscribe((m) => (modal = m[0])) jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) @@ -1409,6 +1410,14 @@ describe('BulkEditorComponent', () => { `${environment.apiBaseUrl}documents/rotate/` ) req.flush({ result: 'OK', task_id: 'rotate-1' }) + TestBed.inject(TaskNotificationService).complete({ + task_id: 'rotate-1', + operation: 'rotate', + status: PaperlessTaskStatus.Success, + }) + expect(toast.mock.calls.at(-1)[0].content).toBe( + "Bulk operation 'Rotate documents' submitted for processing." + ) expect(req.request.body).toEqual({ documents: [3, 4], degrees: 90, 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 60bba04d28..1869723505 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 @@ -53,7 +53,10 @@ import { SettingsService } from 'src/app/services/settings.service' import { TaskNotificationService } from 'src/app/services/task-notification.service' import { ToastService } from 'src/app/services/toast.service' import { TrashService } from 'src/app/services/trash.service' -import { getDocumentOperationLabel } from 'src/app/utils/document-operation' +import { + documentOperationHasFollowUpTasks, + getDocumentOperationLabel, +} from 'src/app/utils/document-operation' 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' @@ -374,9 +377,13 @@ export class BulkEditorComponent ? $localize`Bulk operation '${label}' queued.` : $localize`Operation '${label}' queued.`, { - content: bulk - ? $localize`Bulk operation '${label}' completed.` - : $localize`Operation '${label}' completed.`, + content: documentOperationHasFollowUpTasks(operation) + ? bulk + ? $localize`Bulk operation '${label}' submitted for processing.` + : $localize`Operation '${label}' submitted for processing.` + : bulk + ? $localize`Bulk operation '${label}' completed.` + : $localize`Operation '${label}' completed.`, } ) } diff --git a/src-ui/src/app/services/task-notification.service.spec.ts b/src-ui/src/app/services/task-notification.service.spec.ts index d193849b98..440acf919c 100644 --- a/src-ui/src/app/services/task-notification.service.spec.ts +++ b/src-ui/src/app/services/task-notification.service.spec.ts @@ -81,6 +81,22 @@ describe('TaskNotificationService', () => { ]) }) + it.each([ + 'reprocess', + 'rotate', + 'merge', + 'split', + 'delete_pages', + 'edit_pdf', + 'remove_password', + ])('reports %s as submitted rather than completed', (operation) => { + service.showQueued('task-1', 'Queued') + service.complete({ ...result(), operation }) + expect(notifications[0].content).toContain('submitted for processing.') + expect(notifications[0].content).not.toContain('completed') + expect(notifications[0].action).toBeUndefined() + }) + it('does not show a stale queued state when completion beats the HTTP response', () => { service.complete(result()) service.showQueued('task-1', 'Workflow queued.', { diff --git a/src-ui/src/app/services/task-notification.service.ts b/src-ui/src/app/services/task-notification.service.ts index c52699bba2..d426ce91aa 100644 --- a/src-ui/src/app/services/task-notification.service.ts +++ b/src-ui/src/app/services/task-notification.service.ts @@ -2,7 +2,10 @@ import { inject, Injectable, OnDestroy } from '@angular/core' import { Router } from '@angular/router' import { BulkOperationStatus } from '../data/bulk-operation' import { PaperlessTaskStatus } from '../data/paperless-task' -import { getDocumentOperationLabel } from '../utils/document-operation' +import { + documentOperationHasFollowUpTasks, + getDocumentOperationLabel, +} from '../utils/document-operation' import { PermissionAction, PermissionsService, @@ -80,9 +83,11 @@ export class TaskNotificationService implements OnDestroy { const operation = getDocumentOperationLabel(result.operation) let content: string if (succeeded) { - content = operation - ? $localize`Operation '${operation}' completed.` - : $localize`Operation completed.` + content = documentOperationHasFollowUpTasks(result.operation) + ? $localize`Operation '${operation}' submitted for processing.` + : operation + ? $localize`Operation '${operation}' completed.` + : $localize`Operation completed.` } else if (result.status === PaperlessTaskStatus.Revoked) { content = operation ? $localize`Operation '${operation}' revoked.` diff --git a/src-ui/src/app/utils/document-operation.ts b/src-ui/src/app/utils/document-operation.ts index 4bffe51aaf..639bae7638 100644 --- a/src-ui/src/app/utils/document-operation.ts +++ b/src-ui/src/app/utils/document-operation.ts @@ -23,3 +23,16 @@ export function getDocumentOperationLabel(operation?: string): string | null { const label = operation ? OPERATION_LABELS[operation] : null return label ?? null } + +// These operations queue processing in separate tasks +export function documentOperationHasFollowUpTasks(operation?: string): boolean { + return [ + 'reprocess', + 'rotate', + 'merge', + 'split', + 'delete_pages', + 'edit_pdf', + 'remove_password', + ].includes(operation ?? '') +}