diff --git a/src-ui/src/app/components/admin/tasks/tasks.component.html b/src-ui/src/app/components/admin/tasks/tasks.component.html
index 7424668d21..cd2089c88b 100644
--- a/src-ui/src/app/components/admin/tasks/tasks.component.html
+++ b/src-ui/src/app/components/admin/tasks/tasks.component.html
@@ -180,6 +180,11 @@
@if (taskShowsSeparateTypeLabel(task)) {
{{ task.task_type_display }}
}
+ @let documentCount = taskDocumentCount(task);
+ @if (documentCount !== null) {
+ {documentCount, plural, =1 {1 document} other {{{ documentCount }} documents}}
+ ·
+ }
{{ task.trigger_source_display }}
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 887e690982..fd4ede26c5 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
@@ -895,6 +895,96 @@ describe('TasksComponent', () => {
expect(sanityTaskRow.nativeElement.textContent).toContain('System')
})
+ it.each([
+ ['modify_tags', 12, 'Update tags', '12 documents'],
+ ['run_workflow', 1, 'Run workflow', '1 document'],
+ ['delete', 3, 'Move to trash', '3 documents'],
+ ])(
+ 'shows the operation, document count and outcome for %s',
+ (operation, count, label, summary) => {
+ const task: PaperlessTask = {
+ ...tasks[4],
+ task_type:
+ operation === 'delete'
+ ? PaperlessTaskType.BulkDelete
+ : PaperlessTaskType.BulkUpdate,
+ task_type_display:
+ operation === 'delete' ? 'Bulk Delete' : 'Bulk Update',
+ trigger_source: PaperlessTaskTriggerSource.WebUI,
+ trigger_source_display: 'Web UI',
+ input_data: { operation, document_count: count },
+ result_data: { operation, document_count: count },
+ }
+ component.setSection(TaskSection.Completed)
+ flushTaskLists(25, 1, [task])
+ fixture.detectChanges()
+
+ const name = fixture.nativeElement.querySelector('tbody .name-col')
+ expect(name.textContent).toContain(label)
+ expect(name.textContent).toContain(summary)
+ expect(name.textContent).toContain('Web UI')
+ expect(name.textContent).not.toContain(task.task_type_display)
+ expect(
+ name.closest('tr').querySelector('.results-col').textContent
+ ).toContain('Completed')
+ }
+ )
+
+ it('shows operation context while work is pending without reporting completion', () => {
+ const task = {
+ ...tasks[2],
+ task_type: PaperlessTaskType.BulkUpdate,
+ task_type_display: 'Bulk Update',
+ input_data: { operation: 'reprocess', document_count: 1 },
+ }
+ component.setSection(TaskSection.InProgress)
+ flushTaskLists(25, 1, [task])
+ fixture.detectChanges()
+
+ const name = fixture.nativeElement.querySelector('tbody .name-col')
+ expect(name.textContent).toContain('Document reprocessing')
+ expect(name.textContent).toContain('1 document')
+ expect(component.taskResultMessage(task)).toBeNull()
+ })
+
+ it.each([{}, { operation: 'future_operation' }])(
+ 'preserves the fallback for legacy or unknown operation data: %j',
+ (input_data) => {
+ const task = {
+ ...tasks[4],
+ task_type: PaperlessTaskType.BulkUpdate,
+ task_type_display: 'Bulk Update',
+ input_data,
+ result_data: {},
+ }
+ expect(component.taskDisplayName(task)).toBe('Bulk Update')
+ expect(component.taskDocumentCount(task)).toBeNull()
+ expect(component.taskResultMessage(task)).toBeNull()
+ }
+ )
+
+ it.each([-1, 1.5, '2', null, undefined])(
+ 'does not display an invalid document count: %s',
+ (document_count) => {
+ expect(
+ component.taskDocumentCount({
+ ...tasks[4],
+ input_data: { operation: 'modify_tags', document_count },
+ })
+ ).toBeNull()
+ }
+ )
+
+ it('retains error details for failed operation tasks', () => {
+ expect(
+ component.taskResultMessage({
+ ...tasks[0],
+ input_data: { operation: 'modify_tags', document_count: 12 },
+ result_data: { error_message: 'Insufficient permissions' },
+ })
+ ).toBe('Insufficient permissions')
+ })
+
it('should filter tasks by result', () => {
component.setSection(TaskSection.NeedsAttention)
component.filterTargetID = 1
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 ad8793ef85..568ce5daef 100644
--- a/src-ui/src/app/components/admin/tasks/tasks.component.ts
+++ b/src-ui/src/app/components/admin/tasks/tasks.component.ts
@@ -56,6 +56,27 @@ const FILTER_TARGETS = [
{ id: TaskFilterTargetID.TaskId, name: $localize`Task ID` },
]
+const OPERATION_LABELS: Record = {
+ set_correspondent: $localize`Set correspondent`,
+ set_document_type: $localize`Set document type`,
+ set_storage_path: $localize`Set storage path`,
+ add_tag: $localize`Add tag`,
+ remove_tag: $localize`Remove tag`,
+ modify_tags: $localize`Update tags`,
+ modify_custom_fields: $localize`Update custom fields`,
+ set_permissions: $localize`Set permissions`,
+ delete: $localize`Move to trash`,
+ reprocess: $localize`Document reprocessing`,
+ run_workflow: $localize`Run workflow`,
+ rotate: $localize`Rotate documents`,
+ merge: $localize`Merge documents`,
+ merge_as_versions: $localize`Merge as versions`,
+ split: $localize`Split documents`,
+ delete_pages: $localize`Delete pages`,
+ edit_pdf: $localize`Edit PDF`,
+ remove_password: $localize`Remove password`,
+}
+
const SECTION_LABELS = {
[TaskSection.All]: $localize`All`,
[TaskSection.NeedsAttention]: $localize`Needs attention`,
@@ -414,11 +435,28 @@ export class TasksComponent
}
taskDisplayName(task: PaperlessTask): string {
- return task.input_data?.filename?.toString() || task.task_type_display
+ return (
+ task.input_data?.filename?.toString() ||
+ this.taskOperationLabel(task) ||
+ task.task_type_display
+ )
}
taskShowsSeparateTypeLabel(task: PaperlessTask): boolean {
- return this.taskDisplayName(task) !== task.task_type_display
+ return (
+ !this.taskOperationLabel(task) &&
+ this.taskDisplayName(task) !== task.task_type_display
+ )
+ }
+
+ private taskOperationLabel(task: PaperlessTask): string | null {
+ const label = OPERATION_LABELS[task.input_data?.operation]
+ return typeof label === 'string' ? label : null
+ }
+
+ taskDocumentCount(task: PaperlessTask): number | null {
+ const count = task.input_data?.document_count
+ return Number.isInteger(count) && count >= 0 ? count : null
}
taskResultMessage(task: PaperlessTask): string | null {
@@ -446,6 +484,13 @@ export class TasksComponent
return errorMessage
}
+ if (
+ task.status === PaperlessTaskStatus.Success &&
+ this.taskOperationLabel(task)
+ ) {
+ return $localize`Completed`
+ }
+
return null
}