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(