mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Merge pull request #4055 from paperless-ngx/feature-frontend-handle-slowness
Enhancement: frontend better handle slow backend requests
This commit is contained in:
commit
e3352ea426
@ -1,5 +1,5 @@
|
||||
<div class="btn-group w-100" ngbDropdown role="group">
|
||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="dateBefore || dateAfter ? 'btn-primary' : 'btn-outline-primary'">
|
||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="dateBefore || dateAfter ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
|
||||
{{title}}
|
||||
<app-clearable-badge [selected]="isActive" (cleared)="reset()"></app-clearable-badge><span class="visually-hidden">selected</span>
|
||||
</button>
|
||||
|
@ -85,6 +85,9 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
|
||||
@Output()
|
||||
datesSet = new EventEmitter<DateSelection>()
|
||||
|
||||
@Input()
|
||||
disabled: boolean = false
|
||||
|
||||
get isActive(): boolean {
|
||||
return (
|
||||
this.relativeDate !== null ||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div class="btn-group w-100" ngbDropdown role="group">
|
||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="isActive ? 'btn-primary' : 'btn-outline-primary'">
|
||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="isActive ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock" />
|
||||
</svg>
|
||||
|
@ -4,11 +4,10 @@ import {
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
QueryList,
|
||||
ViewChild,
|
||||
ViewChildren,
|
||||
} from '@angular/core'
|
||||
import { Router } from '@angular/router'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { Subject, takeUntil } from 'rxjs'
|
||||
import { PaperlessDocument } from 'src/app/data/paperless-document'
|
||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
|
||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
|
||||
@ -49,7 +48,7 @@ export class SavedViewWidgetComponent
|
||||
|
||||
documents: PaperlessDocument[] = []
|
||||
|
||||
subscription: Subscription
|
||||
unsubscribeNotifier: Subject<any> = new Subject()
|
||||
|
||||
@ViewChildren('popover') popovers: QueryList<NgbPopover>
|
||||
popover: NgbPopover
|
||||
@ -59,15 +58,17 @@ export class SavedViewWidgetComponent
|
||||
|
||||
ngOnInit(): void {
|
||||
this.reload()
|
||||
this.subscription = this.consumerStatusService
|
||||
this.consumerStatusService
|
||||
.onDocumentConsumptionFinished()
|
||||
.subscribe((status) => {
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe(() => {
|
||||
this.reload()
|
||||
})
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscription.unsubscribe()
|
||||
this.unsubscribeNotifier.next(true)
|
||||
this.unsubscribeNotifier.complete()
|
||||
}
|
||||
|
||||
reload() {
|
||||
@ -81,6 +82,7 @@ export class SavedViewWidgetComponent
|
||||
this.savedView.filter_rules,
|
||||
{ truncate_content: true }
|
||||
)
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe((result) => {
|
||||
this.loading = false
|
||||
this.documents = result.results
|
||||
|
@ -185,7 +185,7 @@ export class DocumentListComponent
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
// unsubscribes all
|
||||
this.list.cancelPending()
|
||||
this.unsubscribeNotifier.next(this)
|
||||
this.unsubscribeNotifier.complete()
|
||||
}
|
||||
|
@ -2,16 +2,23 @@
|
||||
|
||||
</app-page-header>
|
||||
|
||||
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeLog" (activeIdChange)="reloadLogs()" class="nav-tabs">
|
||||
<li *ngFor="let logFile of logFiles" [ngbNavItem]="logFile">
|
||||
<a ngbNavLink>{{logFile}}.log</a>
|
||||
</li>
|
||||
<div *ngIf="isLoading && !logFiles.length" class="pb-2">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
||||
|
||||
<div class="bg-dark p-3 text-light font-monospace log-container" #logContainer>
|
||||
<div *ngIf="isLoading && logFiles.length">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</div>
|
||||
<p
|
||||
class="m-0 p-0 log-entry-{{getLogLevel(log)}}"
|
||||
*ngFor="let log of logs">{{log}}</p>
|
||||
|
@ -4,7 +4,9 @@ import {
|
||||
OnInit,
|
||||
AfterViewChecked,
|
||||
ViewChild,
|
||||
OnDestroy,
|
||||
} from '@angular/core'
|
||||
import { Subject, takeUntil } from 'rxjs'
|
||||
import { LogService } from 'src/app/services/rest/log.service'
|
||||
|
||||
@Component({
|
||||
@ -12,40 +14,60 @@ import { LogService } from 'src/app/services/rest/log.service'
|
||||
templateUrl: './logs.component.html',
|
||||
styleUrls: ['./logs.component.scss'],
|
||||
})
|
||||
export class LogsComponent implements OnInit, AfterViewChecked {
|
||||
export class LogsComponent implements OnInit, AfterViewChecked, OnDestroy {
|
||||
constructor(private logService: LogService) {}
|
||||
|
||||
logs: string[] = []
|
||||
public logs: string[] = []
|
||||
|
||||
logFiles: string[] = []
|
||||
public logFiles: string[] = []
|
||||
|
||||
activeLog: string
|
||||
public activeLog: string
|
||||
|
||||
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||
|
||||
public isLoading: boolean = false
|
||||
|
||||
@ViewChild('logContainer') logContainer: ElementRef
|
||||
|
||||
ngOnInit(): void {
|
||||
this.logService.list().subscribe((result) => {
|
||||
this.logFiles = result
|
||||
if (this.logFiles.length > 0) {
|
||||
this.activeLog = this.logFiles[0]
|
||||
this.reloadLogs()
|
||||
}
|
||||
})
|
||||
this.isLoading = true
|
||||
this.logService
|
||||
.list()
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe((result) => {
|
||||
this.logFiles = result
|
||||
this.isLoading = false
|
||||
if (this.logFiles.length > 0) {
|
||||
this.activeLog = this.logFiles[0]
|
||||
this.reloadLogs()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ngAfterViewChecked() {
|
||||
this.scrollToBottom()
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.unsubscribeNotifier.next(true)
|
||||
this.unsubscribeNotifier.complete()
|
||||
}
|
||||
|
||||
reloadLogs() {
|
||||
this.logService.get(this.activeLog).subscribe({
|
||||
next: (result) => {
|
||||
this.logs = result
|
||||
},
|
||||
error: () => {
|
||||
this.logs = []
|
||||
},
|
||||
})
|
||||
this.isLoading = true
|
||||
this.logService
|
||||
.get(this.activeLog)
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.logs = result
|
||||
this.isLoading = false
|
||||
},
|
||||
error: () => {
|
||||
this.logs = []
|
||||
this.isLoading = false
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
getLogLevel(log: string) {
|
||||
|
@ -24,6 +24,12 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngIf="isLoading">
|
||||
<td colspan="5">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngFor="let object of data">
|
||||
<td scope="row">{{ object.name }}</td>
|
||||
<td scope="row" class="d-none d-sm-table-cell">{{ getMatching(object) }}</td>
|
||||
@ -69,7 +75,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="d-flex">
|
||||
<div class="d-flex" *ngIf="!isLoading">
|
||||
<div i18n *ngIf="collectionSize > 0">{collectionSize, plural, =1 {One {{typeName}}} other {{{collectionSize || 0}} total {{typeNamePlural}}}}</div>
|
||||
<ngb-pagination *ngIf="collectionSize > 20" class="ms-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
} from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { Subject, Subscription } from 'rxjs'
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
|
||||
import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators'
|
||||
import {
|
||||
MatchingModel,
|
||||
MATCHING_ALGORITHMS,
|
||||
@ -76,8 +76,10 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
||||
public sortField: string
|
||||
public sortReverse: boolean
|
||||
|
||||
public isLoading: boolean = false
|
||||
|
||||
private nameFilterDebounce: Subject<string>
|
||||
private subscription: Subscription
|
||||
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||
private _nameFilter: string
|
||||
|
||||
ngOnInit(): void {
|
||||
@ -85,8 +87,12 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
||||
|
||||
this.nameFilterDebounce = new Subject<string>()
|
||||
|
||||
this.subscription = this.nameFilterDebounce
|
||||
.pipe(debounceTime(400), distinctUntilChanged())
|
||||
this.nameFilterDebounce
|
||||
.pipe(
|
||||
takeUntil(this.unsubscribeNotifier),
|
||||
debounceTime(400),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
.subscribe((title) => {
|
||||
this._nameFilter = title
|
||||
this.page = 1
|
||||
@ -95,7 +101,8 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscription.unsubscribe()
|
||||
this.unsubscribeNotifier.next(true)
|
||||
this.unsubscribeNotifier.complete()
|
||||
}
|
||||
|
||||
getMatching(o: MatchingModel) {
|
||||
@ -119,6 +126,7 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
||||
}
|
||||
|
||||
reloadData() {
|
||||
this.isLoading = true
|
||||
this.service
|
||||
.listFiltered(
|
||||
this.page,
|
||||
@ -128,9 +136,11 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
||||
this._nameFilter,
|
||||
true
|
||||
)
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe((c) => {
|
||||
this.data = c.results
|
||||
this.collectionSize = c.count
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
|
||||
@ -192,19 +202,22 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
||||
activeModal.componentInstance.btnCaption = $localize`Delete`
|
||||
activeModal.componentInstance.confirmClicked.subscribe(() => {
|
||||
activeModal.componentInstance.buttonsEnabled = false
|
||||
this.service.delete(object).subscribe({
|
||||
next: () => {
|
||||
activeModal.close()
|
||||
this.reloadData()
|
||||
},
|
||||
error: (error) => {
|
||||
activeModal.componentInstance.buttonsEnabled = true
|
||||
this.toastService.showError(
|
||||
$localize`Error while deleting element`,
|
||||
error
|
||||
)
|
||||
},
|
||||
})
|
||||
this.service
|
||||
.delete(object)
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
activeModal.close()
|
||||
this.reloadData()
|
||||
},
|
||||
error: (error) => {
|
||||
activeModal.componentInstance.buttonsEnabled = true
|
||||
this.toastService.showError(
|
||||
$localize`Error while deleting element`,
|
||||
error
|
||||
)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core'
|
||||
import { Router } from '@angular/router'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { Subject, first } from 'rxjs'
|
||||
import { first } from 'rxjs'
|
||||
import { PaperlessTask } from 'src/app/data/paperless-task'
|
||||
import { TasksService } from 'src/app/services/tasks.service'
|
||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||
@ -18,7 +18,6 @@ export class TasksComponent
|
||||
{
|
||||
public activeTab: string
|
||||
public selectedTasks: Set<number> = new Set()
|
||||
private unsubscribeNotifer = new Subject()
|
||||
public expandedTask: number
|
||||
|
||||
public pageSize: number = 25
|
||||
@ -43,7 +42,7 @@ export class TasksComponent
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsubscribeNotifer.next(true)
|
||||
this.tasksService.cancelPending()
|
||||
}
|
||||
|
||||
dismissTask(task: PaperlessTask) {
|
||||
|
@ -103,6 +103,7 @@ describe('DocumentListViewService', () => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
documentListViewService.cancelPending()
|
||||
httpTestingController.verify()
|
||||
sessionStorage.clear()
|
||||
})
|
||||
@ -425,4 +426,13 @@ describe('DocumentListViewService', () => {
|
||||
})
|
||||
expect(documentListViewService.selected.size).toEqual(3)
|
||||
})
|
||||
|
||||
it('should cancel on reload the list', () => {
|
||||
const cancelSpy = jest.spyOn(documentListViewService, 'cancelPending')
|
||||
documentListViewService.reload()
|
||||
httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__all=9`
|
||||
)
|
||||
expect(cancelSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ParamMap, Router } from '@angular/router'
|
||||
import { Observable, first } from 'rxjs'
|
||||
import { Observable, Subject, first, takeUntil } from 'rxjs'
|
||||
import { FilterRule } from '../data/filter-rule'
|
||||
import {
|
||||
filterRulesDiffer,
|
||||
@ -82,6 +82,8 @@ export class DocumentListViewService {
|
||||
|
||||
currentPageSize: number = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
|
||||
|
||||
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||
|
||||
private listViewStates: Map<number, ListViewState> = new Map()
|
||||
|
||||
private _activeSavedViewId: number = null
|
||||
@ -143,6 +145,10 @@ export class DocumentListViewService {
|
||||
return this.listViewStates.get(this._activeSavedViewId)
|
||||
}
|
||||
|
||||
public cancelPending(): void {
|
||||
this.unsubscribeNotifier.next(true)
|
||||
}
|
||||
|
||||
activateSavedView(view: PaperlessSavedView) {
|
||||
this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null
|
||||
if (view) {
|
||||
@ -210,6 +216,7 @@ export class DocumentListViewService {
|
||||
}
|
||||
|
||||
reload(onFinish?, updateQueryParams: boolean = true) {
|
||||
this.cancelPending()
|
||||
this.isReloading = true
|
||||
this.error = null
|
||||
let activeListViewState = this.activeListViewState
|
||||
@ -222,6 +229,7 @@ export class DocumentListViewService {
|
||||
activeListViewState.filterRules,
|
||||
{ truncate_content: true }
|
||||
)
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.initialized = true
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { first } from 'rxjs/operators'
|
||||
import { Subject } from 'rxjs'
|
||||
import { first, takeUntil } from 'rxjs/operators'
|
||||
import {
|
||||
PaperlessTask,
|
||||
PaperlessTaskStatus,
|
||||
@ -14,10 +15,12 @@ import { environment } from 'src/environments/environment'
|
||||
export class TasksService {
|
||||
private baseUrl: string = environment.apiBaseUrl
|
||||
|
||||
loading: boolean
|
||||
public loading: boolean
|
||||
|
||||
private fileTasks: PaperlessTask[] = []
|
||||
|
||||
private unsubscribeNotifer: Subject<any> = new Subject()
|
||||
|
||||
public get total(): number {
|
||||
return this.fileTasks.length
|
||||
}
|
||||
@ -51,7 +54,7 @@ export class TasksService {
|
||||
|
||||
this.http
|
||||
.get<PaperlessTask[]>(`${this.baseUrl}tasks/`)
|
||||
.pipe(first())
|
||||
.pipe(takeUntil(this.unsubscribeNotifer), first())
|
||||
.subscribe((r) => {
|
||||
this.fileTasks = r.filter((t) => t.type == PaperlessTaskType.File) // they're all File tasks, for now
|
||||
this.loading = false
|
||||
@ -63,9 +66,13 @@ export class TasksService {
|
||||
.post(`${this.baseUrl}acknowledge_tasks/`, {
|
||||
tasks: [...task_ids],
|
||||
})
|
||||
.pipe(first())
|
||||
.pipe(takeUntil(this.unsubscribeNotifer), first())
|
||||
.subscribe((r) => {
|
||||
this.reload()
|
||||
})
|
||||
}
|
||||
|
||||
public cancelPending(): void {
|
||||
this.unsubscribeNotifer.next(true)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user