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 7fae03a7f..61517c1e8 100644 --- a/src-ui/src/app/components/admin/tasks/tasks.component.html +++ b/src-ui/src/app/components/admin/tasks/tasks.component.html @@ -21,7 +21,7 @@ -@if (!tasksService.completedFileTasks && tasksService.loading) { +@if (!tasksService.completedTasks && tasksService.loading) {
Loading...
} diff --git a/src-ui/src/app/services/tasks.service.spec.ts b/src-ui/src/app/services/tasks.service.spec.ts index 3412ae2ce..336635a85 100644 --- a/src-ui/src/app/services/tasks.service.spec.ts +++ b/src-ui/src/app/services/tasks.service.spec.ts @@ -50,13 +50,66 @@ describe('TasksService', () => { req.flush({ count: 0, results: [] }) }) - it('does not call tasks api endpoint on reload if already loading', () => { - tasksService.loading = true + it('cancels an in-progress reload when reloading again', () => { tasksService.reload() - httpTestingController.expectNone( + const staleReload = httpTestingController.expectOne( (req: HttpRequest) => req.url === `${environment.apiBaseUrl}tasks/` ) + tasksService.reload() + + expect(staleReload.cancelled).toBe(true) + httpTestingController + .expectOne( + (req: HttpRequest) => + req.url === `${environment.apiBaseUrl}tasks/` + ) + .flush({ count: 0, results: [] }) + }) + + it('continues reloading after a reload request fails', () => { + tasksService.reload() + httpTestingController + .expectOne( + (req: HttpRequest) => + req.url === `${environment.apiBaseUrl}tasks/` + ) + .flush('error', { status: 500, statusText: 'error' }) + + expect(tasksService.loading).toBe(false) + + tasksService.reload() + httpTestingController + .expectOne( + (req: HttpRequest) => + req.url === `${environment.apiBaseUrl}tasks/` + ) + .flush({ count: 0, results: [] }) + }) + + it('reloads after dismissing a task while a reload is already in progress', () => { + tasksService.reload() + const staleReload = httpTestingController.expectOne( + (req: HttpRequest) => + req.url === `${environment.apiBaseUrl}tasks/` && + req.params.get('acknowledged') === 'false' + ) + + tasksService.dismissTasks(new Set([1])).subscribe() + httpTestingController + .expectOne(`${environment.apiBaseUrl}tasks/acknowledge/`) + .flush([]) + + expect(staleReload.cancelled).toBe(true) + httpTestingController + .expectOne( + (req: HttpRequest) => + req.url === `${environment.apiBaseUrl}tasks/` && + req.params.get('acknowledged') === 'false' + ) + .flush({ count: 0, results: [] }) + + expect(tasksService.needsAttentionTasks).toHaveLength(0) }) it('calls acknowledge_tasks api endpoint on dismiss and reloads', () => { @@ -166,12 +219,6 @@ describe('TasksService', () => { ) req.flush({ count: mockTasks.length, results: mockTasks }) - - expect(tasksService.allFileTasks).toHaveLength(5) - expect(tasksService.completedFileTasks).toHaveLength(2) - expect(tasksService.failedFileTasks).toHaveLength(1) - expect(tasksService.queuedFileTasks).toHaveLength(1) - expect(tasksService.startedFileTasks).toHaveLength(1) }) it('includes revoked tasks in needs attention', () => { diff --git a/src-ui/src/app/services/tasks.service.ts b/src-ui/src/app/services/tasks.service.ts index 8b71c6923..41ef5bc80 100644 --- a/src-ui/src/app/services/tasks.service.ts +++ b/src-ui/src/app/services/tasks.service.ts @@ -1,7 +1,15 @@ import { HttpClient } from '@angular/common/http' import { Injectable, inject, signal } from '@angular/core' -import { Observable, Subject } from 'rxjs' -import { first, map, takeUntil, tap } from 'rxjs/operators' +import { EMPTY, Observable, Subject } from 'rxjs' +import { + catchError, + finalize, + first, + map, + switchMap, + takeUntil, + tap, +} from 'rxjs/operators' import { PaperlessTask, PaperlessTaskStatus, @@ -23,44 +31,48 @@ export class TasksService { public loading: boolean = false - private readonly fileTasks = signal([]) + private readonly tasks = signal([]) + private readonly reloadNotifier = new Subject() private unsubscribeNotifer: Subject = new Subject() + constructor() { + this.reloadNotifier + .pipe( + switchMap(() => { + this.loading = true + return this.http + .get>(`${this.baseUrl}${this.endpoint}/`, { + params: { + acknowledged: 'false', + page_size: this.defaultReloadPageSize, + }, + }) + .pipe( + map((response) => response.results), + takeUntil(this.unsubscribeNotifer), + catchError(() => EMPTY), + finalize(() => { + this.loading = false + }) + ) + }) + ) + .subscribe((tasks) => { + this.tasks.set(tasks) + }) + } + public get total(): number { - return this.fileTasks().length + return this.tasks().length } - public get allFileTasks(): PaperlessTask[] { - return this.fileTasks().slice(0) - } - - public get queuedFileTasks(): PaperlessTask[] { - return this.fileTasks().filter( - (t) => t.status === PaperlessTaskStatus.Pending - ) - } - - public get startedFileTasks(): PaperlessTask[] { - return this.fileTasks().filter( - (t) => t.status === PaperlessTaskStatus.Started - ) - } - - public get completedFileTasks(): PaperlessTask[] { - return this.fileTasks().filter( - (t) => t.status === PaperlessTaskStatus.Success - ) - } - - public get failedFileTasks(): PaperlessTask[] { - return this.fileTasks().filter( - (t) => t.status === PaperlessTaskStatus.Failure - ) + public get completedTasks(): PaperlessTask[] { + return this.tasks().filter((t) => t.status === PaperlessTaskStatus.Success) } public get needsAttentionTasks(): PaperlessTask[] { - return this.fileTasks().filter((t) => + return this.tasks().filter((t) => [PaperlessTaskStatus.Failure, PaperlessTaskStatus.Revoked].includes( t.status ) @@ -68,22 +80,7 @@ export class TasksService { } public reload() { - if (this.loading) return - this.loading = true - - this.http - .get>(`${this.baseUrl}${this.endpoint}/`, { - params: { - acknowledged: 'false', - page_size: this.defaultReloadPageSize, - }, - }) - .pipe(map((r) => r.results)) - .pipe(takeUntil(this.unsubscribeNotifer), first()) - .subscribe((r) => { - this.fileTasks.set(r) - this.loading = false - }) + this.reloadNotifier.next() } public list(