Update frontend bulk operatoins to pass from_webui, get the task id

This commit is contained in:
shamoon committed 2026-11-03 09:57:22 -08:00
1 parent 736983b7e1
commit e06e1ad3b0
6 files changed
+90 -35

No files matched your search

@@ -561,6 +561,7 @@ describe('BulkEditorComponent', () => {
)
req.flush(true)
expect(req.request.body).toEqual({
from_webui: true,
documents: [3, 4],
method: 'modify_tags',
parameters: { add_tags: [101], remove_tags: [] },
@@ -609,6 +610,7 @@ describe('BulkEditorComponent', () => {
)
req.flush(true)
expect(req.request.body).toEqual({
from_webui: true,
all: true,
filters: { title_search: 'apple' },
excluded_documents: [4],
@@ -731,6 +733,7 @@ describe('BulkEditorComponent', () => {
)
req.flush(true)
expect(req.request.body).toEqual({
from_webui: true,
documents: [3, 4],
method: 'set_correspondent',
parameters: { correspondent: 101 },
@@ -829,6 +832,7 @@ describe('BulkEditorComponent', () => {
)
req.flush(true)
expect(req.request.body).toEqual({
from_webui: true,
documents: [3, 4],
method: 'set_document_type',
parameters: { document_type: 101 },
@@ -927,6 +931,7 @@ describe('BulkEditorComponent', () => {
)
req.flush(true)
expect(req.request.body).toEqual({
from_webui: true,
documents: [3, 4],
method: 'set_storage_path',
parameters: { storage_path: 101 },
@@ -1025,6 +1030,7 @@ describe('BulkEditorComponent', () => {
)
req.flush(true)
expect(req.request.body).toEqual({
from_webui: true,
documents: [3, 4],
method: 'modify_custom_fields',
parameters: { add_custom_fields: [101], remove_custom_fields: [102] },
@@ -1171,6 +1177,7 @@ describe('BulkEditorComponent', () => {
.mockImplementation(() => {})
req.flush(true)
expect(req.request.body).toEqual({
from_webui: true,
documents: [3, 4],
})
expect(notifySpy).toHaveBeenCalledWith(expect.any(String), [3, 4])
@@ -1540,6 +1547,28 @@ describe('BulkEditorComponent', () => {
)
})
it('closes a queued permissions dialog without refreshing before completion', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((instances) => {
if (instances.length) modal = instances[0]
})
const reload = jest.spyOn(documentListViewService, 'reload')
component.setPermissions()
const close = jest.spyOn(modal, 'close')
modal.componentInstance.confirmClicked.emit({
permissions: {},
merge: true,
})
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/bulk_edit/`
)
req.flush({ result: 'OK', task_id: 'queued-operation' })
expect(close).toHaveBeenCalledTimes(1)
expect(reload).not.toHaveBeenCalled()
expect(documentListViewService.selected.size).toBe(0)
expect(documentListViewService.allSelected).toBe(false)
})
it('should support bulk permissions update', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[0]))
@@ -1574,6 +1603,7 @@ describe('BulkEditorComponent', () => {
)
req.flush(true)
expect(req.request.body).toEqual({
from_webui: true,
documents: [3, 4],
method: 'set_permissions',
parameters: {
@@ -19,15 +19,7 @@ import {
NgbModalRef,
} from '@ng-bootstrap/ng-bootstrap'
import { saveAs } from 'file-saver'
import {
first,
map,
Observable,
Subject,
switchMap,
takeUntil,
tap,
} from 'rxjs'
import { first, map, Observable, Subject, switchMap, takeUntil } from 'rxjs'
import { BootstrapIconComponent } from 'src/app/components/common/bootstrap-icon/bootstrap-icon.component'
import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component'
import { CustomField } from 'src/app/data/custom-field'
@@ -299,7 +291,14 @@ export class BulkEditorComponent
.bulkEdit(overrideSelection ?? this.getSelectionQuery(), method, args)
.pipe(first())
.subscribe({
next: () => this.handleOperationSuccess(modal),
next: (response) => {
if (response.task_id) {
modal?.close()
this.list.selectNone()
} else {
this.handleOperationSuccess(modal)
}
},
error: (error) => this.handleOperationError(modal, error),
})
}
@@ -906,19 +905,26 @@ export class BulkEditorComponent
? null
: Array.from(this.list.selected)
const count = this.getSelectionSize()
this.executeDocumentAction(
modal,
this.documentService
.deleteDocuments(this.getSelectionQuery())
.pipe(
tap(() =>
this.documentService
.deleteDocuments(this.getSelectionQuery())
.pipe(first())
.subscribe({
next: (response) => {
const completed = () =>
this.trashService.notifyMovedToTrash(
$localize`Moved ${count} document(s) to the trash`,
documents
)
)
)
)
if (response.task_id) {
modal?.close()
this.list.selectNone()
} else {
this.handleOperationSuccess(modal)
completed()
}
},
error: (error) => this.handleOperationError(modal, error),
})
}
downloadSelected() {
@@ -1089,12 +1095,12 @@ export class BulkEditorComponent
dialog.selection = this.getSelectionQuery()
dialog.selectionCount = this.getSelectionSize()
dialog.succeeded.subscribe((result) => {
this.toastService.showSuccess($localize`Custom fields updated.`)
this.list.reload()
this.list.reduceSelectionToFilter()
this.list.selected.forEach((id) => {
this.openDocumentService.refreshDocument(id)
})
if (result?.task_id) {
this.list.selectNone()
} else {
this.toastService.showSuccess($localize`Custom fields updated.`)
this.handleOperationSuccess(null)
}
})
dialog.failed.subscribe((error) => {
this.toastService.showError(
@@ -103,6 +103,8 @@ export class CustomFieldsBulkEditDialogComponent {
}
public save() {
if (this.networkActive) return
this.networkActive = true
this.documentService
.bulkEdit(this.selection, 'modify_custom_fields', {
add_custom_fields: this.form.value,
@@ -110,11 +112,12 @@ export class CustomFieldsBulkEditDialogComponent {
})
.pipe(first())
.subscribe({
next: () => {
next: (response) => {
this.activeModal.close()
this.succeeded.emit()
this.succeeded.emit(response)
},
error: (error) => {
this.networkActive = false
this.failed.emit(error)
},
})
+4
View File
@@ -0,0 +1,4 @@
export interface BulkOperationResponse {
result: string
task_id?: string
}
@@ -248,6 +248,7 @@ describe(`DocumentService`, () => {
)
expect(req.request.method).toEqual('POST')
expect(req.request.body).toEqual({
from_webui: true,
documents: ids,
method,
parameters,
@@ -271,6 +272,7 @@ describe(`DocumentService`, () => {
)
expect(req.request.method).toEqual('POST')
expect(req.request.body).toEqual({
from_webui: true,
all: true,
filters: { title__icontains: 'apple' },
excluded_documents: [2, 3],
@@ -287,6 +289,7 @@ describe(`DocumentService`, () => {
)
expect(req.request.method).toEqual('POST')
expect(req.request.body).toEqual({
from_webui: true,
documents: ids,
})
})
@@ -14,6 +14,7 @@ import { DocumentSuggestions } from 'src/app/data/document-suggestions'
import { FilterRule } from 'src/app/data/filter-rule'
import { Results, SelectionData } from 'src/app/data/results'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { BulkOperationResponse } from '../../data/bulk-operation'
import { queryParamsFromFilterRules } from '../../utils/query-params'
import {
PermissionAction,
@@ -337,17 +338,25 @@ export class DocumentService extends AbstractPaperlessService<Document> {
method: DocumentBulkEditMethod,
args: any
) {
return this.http.post(this.getResourceUrl(null, 'bulk_edit'), {
...selection,
method: method,
parameters: args,
})
return this.http.post<BulkOperationResponse>(
this.getResourceUrl(null, 'bulk_edit'),
{
...selection,
method: method,
from_webui: true,
parameters: args,
}
)
}
deleteDocuments(selection: DocumentSelectionQuery) {
return this.http.post(this.getResourceUrl(null, 'delete'), {
...selection,
})
return this.http.post<BulkOperationResponse>(
this.getResourceUrl(null, 'delete'),
{
...selection,
from_webui: true,
}
)
}
reprocessDocuments(