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 d20d9045e..0026d8575 100644 --- a/src-ui/src/app/components/admin/tasks/tasks.component.html +++ b/src-ui/src/app/components/admin/tasks/tasks.component.html @@ -113,9 +113,7 @@
@if (task.status === PaperlessTaskStatus.Failed) { - + } +
+
+
    +
  • + +
  • +
+
+ +
+
+
+
+ diff --git a/src-ui/src/app/components/admin/tasks/tasks.component.scss b/src-ui/src/app/components/admin/tasks/tasks.component.scss index 325fd2c02..8387597f6 100644 --- a/src-ui/src/app/components/admin/tasks/tasks.component.scss +++ b/src-ui/src/app/components/admin/tasks/tasks.component.scss @@ -37,3 +37,7 @@ pre { .z-10 { z-index: 10; } + +.retry-dropdown { + width: 300px; +} 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 6846e0f7c..5cf062208 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 @@ -32,6 +32,7 @@ import { TasksService } from 'src/app/services/tasks.service' import { ToastService } from 'src/app/services/toast.service' import { environment } from 'src/environments/environment' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' +import { CheckComponent } from '../../common/input/check/check.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component' import { TasksComponent, TaskTab } from './tasks.component' @@ -138,6 +139,7 @@ describe('TasksComponent', () => { PageHeaderComponent, IfPermissionsDirective, CustomDatePipe, + CheckComponent, ConfirmDialogComponent, ], providers: [ @@ -184,8 +186,10 @@ describe('TasksComponent', () => { `Failed${currentTasksLength}` ) expect( - fixture.debugElement.queryAll(By.css('table input[type="checkbox"]')) - ).toHaveLength(currentTasksLength + 1) + fixture.debugElement.queryAll( + By.css('table td > .form-check input[type="checkbox"]') + ) + ).toHaveLength(currentTasksLength) currentTasksLength = tasks.filter( (t) => t.status === PaperlessTaskStatus.Complete @@ -396,7 +400,7 @@ describe('TasksComponent', () => { const toastErrorSpy = jest.spyOn(toastService, 'showError') retrySpy.mockReturnValueOnce(of({ task_id: '123' })) component.retryTask(tasks[0]) - expect(retrySpy).toHaveBeenCalledWith(tasks[0]) + expect(retrySpy).toHaveBeenCalledWith(tasks[0], false) expect(toastInfoSpy).toHaveBeenCalledWith('Retrying task...') retrySpy.mockReturnValueOnce(throwError(() => new Error('test'))) component.retryTask(tasks[0]) 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 a3fe50447..159e403f9 100644 --- a/src-ui/src/app/components/admin/tasks/tasks.component.ts +++ b/src-ui/src/app/components/admin/tasks/tasks.component.ts @@ -26,6 +26,7 @@ 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 { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' +import { CheckComponent } from '../../common/input/check/check.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component' import { LoadingComponentWithPermissions } from '../../loading-component/loading.component' @@ -54,6 +55,7 @@ const FILTER_TARGETS = [ PageHeaderComponent, IfPermissionsDirective, CustomDatePipe, + CheckComponent, SlicePipe, FormsModule, ReactiveFormsModule, @@ -106,6 +108,8 @@ export class TasksComponent : FILTER_TARGETS.slice(0, 1) } + public retryClean: boolean = false + get dismissButtonText(): string { return this.selectedTasks.size > 0 ? $localize`Dismiss selected` @@ -180,7 +184,7 @@ export class TasksComponent } retryTask(task: PaperlessTask) { - this.tasksService.retryTask(task).subscribe({ + this.tasksService.retryTask(task, this.retryClean).subscribe({ next: () => { this.toastService.showInfo($localize`Retrying task...`) }, diff --git a/src-ui/src/app/services/tasks.service.spec.ts b/src-ui/src/app/services/tasks.service.spec.ts index ae066cc7d..ce3ed9416 100644 --- a/src-ui/src/app/services/tasks.service.spec.ts +++ b/src-ui/src/app/services/tasks.service.spec.ts @@ -159,14 +159,14 @@ describe('TasksService', () => { date_created: new Date(), } - tasksService.retryTask(task).subscribe() + tasksService.retryTask(task, true).subscribe() const reloadSpy = jest.spyOn(tasksService, 'reload') const req = httpTestingController.expectOne( `${environment.apiBaseUrl}tasks/${task.id}/retry/` ) expect(req.request.method).toEqual('POST') expect(req.request.body).toEqual({ - task_id: task.id, + clean: true, }) req.flush({ task_id: 12345 }) expect(reloadSpy).toHaveBeenCalled() diff --git a/src-ui/src/app/services/tasks.service.ts b/src-ui/src/app/services/tasks.service.ts index bdcd00d1c..7f64432ae 100644 --- a/src-ui/src/app/services/tasks.service.ts +++ b/src-ui/src/app/services/tasks.service.ts @@ -81,10 +81,10 @@ export class TasksService { ) } - public retryTask(task: PaperlessTask): Observable { + public retryTask(task: PaperlessTask, clean: boolean): Observable { return this.http .post(`${this.baseUrl}tasks/${task.id}/retry/`, { - task_id: task.id, + clean, }) .pipe( takeUntil(this.unsubscribeNotifer), diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index 7361ca70b..8ff13ae54 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -2410,6 +2410,13 @@ class TasksViewSerializer(OwnedObjectSerializer): duplicates = _get_viewable_duplicates(document, user) return list(duplicates.values("id", "title", "deleted_at")) +class RetryTaskSerializer(serializers.Serializer): + clean = serializers.BooleanField( + default=False, + write_only=True, + required=False, + ) + class RunTaskViewSerializer(serializers.Serializer[dict[str, Any]]): task_name = serializers.ChoiceField( diff --git a/src/documents/views.py b/src/documents/views.py index 9c1743b61..c667223a7 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -189,6 +189,7 @@ from documents.serialisers import NotesSerializer from documents.serialisers import PostDocumentSerializer from documents.serialisers import RemovePasswordDocumentsSerializer from documents.serialisers import ReprocessDocumentsSerializer +from documents.serialisers import RetryTaskSerializer from documents.serialisers import RotateDocumentsSerializer from documents.serialisers import RunTaskViewSerializer from documents.serialisers import SavedViewSerializer @@ -3471,9 +3472,16 @@ class TasksViewSet(ReadOnlyModelViewSet): @action(methods=["post"], detail=True) def retry(self, request, pk=None): task = self.get_object() + + serializer = RetryTaskSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + clean = serializer.validated_data.get("clean") + try: - new_task_id = retry_failed_file(task.task_id, True) + new_task_id = retry_failed_file(task.task_id, clean) return Response({"task_id": new_task_id}) + except FileNotFoundError: + return HttpResponseBadRequest("Original file not found") except Exception as e: logger.warning(f"An error occurred retrying task: {e!s}") return HttpResponseBadRequest(