From cdc5d2ee6cb6fb021c715d712d472a5c7c80df4c Mon Sep 17 00:00:00 2001 From: Timo Bejan Date: Thu, 7 Nov 2024 15:41:33 +0200 Subject: [PATCH] RED-10422 - improved number of requests with help of new changes endpoint --- .../dossier-overview-screen.component.ts | 7 +- .../services/file-preview-state.service.ts | 3 +- .../dossiers/dossier-changes.service.ts | 74 ++++++++++--------- .../app/services/dossiers/dossiers.service.ts | 19 ++++- .../src/app/services/files/files.service.ts | 29 +++++++- .../src/lib/dossiers/dossier-changes.ts | 10 ++- 6 files changed, 99 insertions(+), 43 deletions(-) diff --git a/apps/red-ui/src/app/modules/dossier-overview/screen/dossier-overview-screen.component.ts b/apps/red-ui/src/app/modules/dossier-overview/screen/dossier-overview-screen.component.ts index 9e6bfb412..8f00fd038 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/screen/dossier-overview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/screen/dossier-overview-screen.component.ts @@ -41,7 +41,7 @@ import { Roles } from '@users/roles'; import { UserPreferenceService } from '@users/user-preference.service'; import { convertFiles, Files, handleFileDrop } from '@utils/index'; import { merge, Observable } from 'rxjs'; -import { filter, skip, switchMap, tap } from 'rxjs/operators'; +import { filter, map, skip, switchMap, tap } from 'rxjs/operators'; import { ConfigService } from '../config.service'; import { BulkActionsService } from '../services/bulk-actions.service'; import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service'; @@ -145,8 +145,9 @@ export default class DossierOverviewScreenComponent extends ListingComponent dossierId === this.dossierId && !!this._dossiersCacheService.get(dossierId)), - switchMap(dossierId => this._filesService.loadAll(dossierId)), + map(changes => changes[this.dossierId]), + filter(changes => !!changes && !!this._dossiersCacheService.get(this.dossierId)), + switchMap(changes => this._filesService.loadByIds({ [this.dossierId]: changes }).pipe(map(files => files[this.dossierId]))), ); } diff --git a/apps/red-ui/src/app/modules/file-preview/services/file-preview-state.service.ts b/apps/red-ui/src/app/modules/file-preview/services/file-preview-state.service.ts index 50b8f579e..ff821733f 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/file-preview-state.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/file-preview-state.service.ts @@ -128,7 +128,8 @@ export class FilePreviewStateService { get #dossierFilesChange$() { return this._dossiersService.dossierFileChanges$.pipe( - filter(dossierId => dossierId === this.dossierId), + map(changes => changes[this.dossierId]), + filter(fileIds => fileIds && fileIds.length > 0), map(() => true), ); } diff --git a/apps/red-ui/src/app/services/dossiers/dossier-changes.service.ts b/apps/red-ui/src/app/services/dossiers/dossier-changes.service.ts index 00a784754..5ba7e9f9b 100644 --- a/apps/red-ui/src/app/services/dossiers/dossier-changes.service.ts +++ b/apps/red-ui/src/app/services/dossiers/dossier-changes.service.ts @@ -1,17 +1,16 @@ -import { GenericService, QueryParam, ROOT_CHANGES_KEY } from '@iqser/common-ui'; -import { Dossier, DossierStats, IDossierChanges } from '@red/domain'; -import { forkJoin, Observable, of, Subscription, throwError, timer } from 'rxjs'; -import { catchError, filter, map, switchMap, take, tap } from 'rxjs/operators'; -import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; +import { GenericService, ROOT_CHANGES_KEY } from '@iqser/common-ui'; +import { Dossier, DossierStats, IChangesDetails } from '@red/domain'; +import { forkJoin, Observable, Subscription, timer } from 'rxjs'; +import { filter, map, switchMap, take, tap } from 'rxjs/operators'; import { NGXLogger } from 'ngx-logger'; import { ActiveDossiersService } from './active-dossiers.service'; import { ArchivedDossiersService } from './archived-dossiers.service'; import { inject, Injectable, OnDestroy } from '@angular/core'; import { DashboardStatsService } from '../dossier-templates/dashboard-stats.service'; import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; -import { List } from '@iqser/common-ui/lib/utils'; import { Router } from '@angular/router'; import { filterEventsOnPages } from '@utils/operators'; +import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; @Injectable({ providedIn: 'root' }) export class DossiersChangesService extends GenericService implements OnDestroy { @@ -19,40 +18,39 @@ export class DossiersChangesService extends GenericService implements O readonly #activeDossiersService = inject(ActiveDossiersService); readonly #archivedDossiersService = inject(ArchivedDossiersService); readonly #dashboardStatsService = inject(DashboardStatsService); + readonly #dossierStatsService = inject(DossierStatsService); readonly #logger = inject(NGXLogger); readonly #router = inject(Router); protected readonly _defaultModelPath = 'dossier'; - loadOnlyChanged(): Observable { - const removeIfNotFound = (id: string) => - catchError((error: unknown) => { - if (error instanceof HttpErrorResponse && error.status === HttpStatusCode.NotFound) { - this.#activeDossiersService.remove(id); - this.#archivedDossiersService.remove(id); - return of([]); - } - return throwError(() => error); - }); + loadOnlyChanged(): Observable { + const load = (changes: IChangesDetails) => this.#load(changes.dossierChanges.map(d => d.dossierId)); - const load = (changes: IDossierChanges) => - changes.map(change => this.#load(change.dossierId).pipe(removeIfNotFound(change.dossierId))); + const loadStats = (change: IChangesDetails) => { + const dossierStatsToLoad = new Set(); + change.dossierChanges.forEach(dossierChange => dossierStatsToLoad.add(dossierChange.dossierId)); + change.fileChanges.forEach(fileChange => dossierStatsToLoad.add(fileChange.dossierId)); + return this.#dossierStatsService.getFor(Array.from(dossierStatsToLoad)); + }; return this.hasChangesDetails$().pipe( tap(changes => this.#logger.info('[DOSSIERS_CHANGES] Found changes', changes)), switchMap(dossierChanges => - forkJoin([...load(dossierChanges), this.#dashboardStatsService.loadAll().pipe(take(1))]).pipe(map(() => dossierChanges)), + forkJoin([load(dossierChanges), loadStats(dossierChanges), this.#dashboardStatsService.loadAll().pipe(take(1))]).pipe( + map(() => dossierChanges), + ), ), ); } - hasChangesDetails$(): Observable { + hasChangesDetails$(): Observable { const body = { value: this._lastCheckedForChanges.get(ROOT_CHANGES_KEY) }; const dateBeforeRequest = new Date().toISOString(); this.#logger.info('[DOSSIERS_CHANGES] Check with Last Checked Date', body.value); - return this._post(body, `${this._defaultModelPath}/changes/details`).pipe( - filter(changes => changes.length > 0), + return this._post(body, `${this._defaultModelPath}/changes/details/v2`).pipe( + filter(changes => changes.dossierChanges.length > 0 || changes.fileChanges.length > 0), tap(() => this._lastCheckedForChanges.set(ROOT_CHANGES_KEY, dateBeforeRequest)), tap(() => this.#logger.info('[DOSSIERS_CHANGES] Save Last Checked Date value', dateBeforeRequest)), ); @@ -75,17 +73,27 @@ export class DossiersChangesService extends GenericService implements O this.#subscription.unsubscribe(); } - #load(id: string): Observable { - const queryParams: List = [{ key: 'includeArchived', value: true }]; - return super._getOne([id], this._defaultModelPath, queryParams).pipe( - map(entity => new Dossier(entity)), - switchMap((dossier: Dossier) => { - if (dossier.isArchived) { - this.#activeDossiersService.remove(dossier.id); - return this.#archivedDossiersService.updateDossier(dossier); - } - this.#archivedDossiersService.remove(dossier.id); - return this.#activeDossiersService.updateDossier(dossier); + getByIds(ids: string[]) { + return super._post>({ value: ids }, `${this._defaultModelPath}/by-id`); + } + + #load(ids: string[]): Observable { + return this.getByIds(ids).pipe( + map(entity => { + return Object.values(entity).map(dossier => new Dossier(dossier)); + }), + map((dossiers: Dossier[]) => { + const archivedDossiers = dossiers.filter(dossier => dossier.isArchived); + const deletedDossiers = dossiers.filter(dossier => dossier.isSoftDeleted); + const activeDossiers = dossiers.filter(dossier => !dossier.isArchived && !dossier.isSoftDeleted); + + archivedDossiers.forEach(dossier => this.#activeDossiersService.remove(dossier.id)); + activeDossiers.forEach(dossier => this.#archivedDossiersService.remove(dossier.id)); + deletedDossiers.forEach(dossier => this.#activeDossiersService.remove(dossier.id)); + + this.#activeDossiersService.updateDossiers(activeDossiers); + this.#archivedDossiersService.updateDossiers(archivedDossiers); + return dossiers; }), ); } diff --git a/apps/red-ui/src/app/services/dossiers/dossiers.service.ts b/apps/red-ui/src/app/services/dossiers/dossiers.service.ts index aea9fda45..bca71afb3 100644 --- a/apps/red-ui/src/app/services/dossiers/dossiers.service.ts +++ b/apps/red-ui/src/app/services/dossiers/dossiers.service.ts @@ -1,5 +1,5 @@ import { EntitiesService, Toaster } from '@iqser/common-ui'; -import { Dossier, DossierStats, IDossier, IDossierChanges, IDossierRequest } from '@red/domain'; +import { Dossier, DossierFileChanges, DossierStats, IChangesDetails, IDossier, IDossierRequest } from '@red/domain'; import { Observable, of, Subject } from 'rxjs'; import { catchError, map, switchMap, tap } from 'rxjs/operators'; import { inject } from '@angular/core'; @@ -17,7 +17,7 @@ export abstract class DossiersService extends EntitiesService protected readonly _toaster = inject(Toaster); protected readonly _entityClass = Dossier; protected abstract readonly _defaultModelPath: string; - readonly dossierFileChanges$ = new Subject(); + readonly dossierFileChanges$ = new Subject(); abstract readonly routerPath: string; createOrUpdate(dossier: IDossierRequest): Observable { @@ -52,7 +52,18 @@ export abstract class DossiersService extends EntitiesService return this._dossierStatsService.getFor([dossier.id]); } - emitFileChanges(dossierChanges: IDossierChanges): void { - dossierChanges.filter(change => change.fileChanges).forEach(change => this.dossierFileChanges$.next(change.dossierId)); + updateDossiers(dossier: Dossier[]): void { + dossier.forEach(d => this.replace(d)); + } + + emitFileChanges(changes: IChangesDetails): void { + const changeModel: DossierFileChanges = {}; + changes.fileChanges.forEach(change => { + if (!changeModel[change.dossierId]) { + changeModel[change.dossierId] = []; + } + changeModel[change.dossierId].push(change.fileId); + }); + this.dossierFileChanges$.next(changeModel); } } diff --git a/apps/red-ui/src/app/services/files/files.service.ts b/apps/red-ui/src/app/services/files/files.service.ts index 59db98b39..87e8047cc 100644 --- a/apps/red-ui/src/app/services/files/files.service.ts +++ b/apps/red-ui/src/app/services/files/files.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { EntitiesService, isArray, QueryParam } from '@iqser/common-ui'; import { List, mapEach } from '@iqser/common-ui/lib/utils'; -import { ApproveResponse, File, IFile } from '@red/domain'; +import { ApproveResponse, Dossier, DossierFileChanges, File, IFile } from '@red/domain'; import { UserService } from '@users/user.service'; import { NGXLogger } from 'ngx-logger'; import { firstValueFrom } from 'rxjs'; @@ -27,8 +27,35 @@ export class FilesService extends EntitiesService { super(); } + loadByIds(dossierFileChanges: DossierFileChanges) { + const filesByDossier$ = super + ._post<{ value: Record }>({ value: dossierFileChanges }, `${this._defaultModelPath}/by-id`) + .pipe( + map(response => { + const filesByDossier = response.value; + const result: Record = {}; + for (const key of Object.keys(filesByDossier)) { + result[key] = filesByDossier[key].map(file => new File(file, this._userService.getName(file.assignee))); + result[key].forEach(file => this._logger.info('[FILE] Loaded', file)); + } + return result; + }), + ); + return filesByDossier$.pipe( + tap(files => { + for (const key of Object.keys(files)) { + const notDeletedFiles = files[key].filter(file => !file.deleted); + const deletedFiles = files[key].filter(file => file.deleted); + this._filesMapService.replace(key, notDeletedFiles); + deletedFiles.map(file => file.id).forEach(id => this._filesMapService.delete(key, id)); + } + }), + ); + } + /** Reload dossier files + stats. */ loadAll(dossierId: string) { + console.log('loadAll'); const files$ = this.getFor(dossierId).pipe( mapEach(file => new File(file, this._userService.getName(file.assignee))), tap(file => this._logger.info('[FILE] Loaded', file)), diff --git a/libs/red-domain/src/lib/dossiers/dossier-changes.ts b/libs/red-domain/src/lib/dossiers/dossier-changes.ts index a63406f4c..625671215 100644 --- a/libs/red-domain/src/lib/dossiers/dossier-changes.ts +++ b/libs/red-domain/src/lib/dossiers/dossier-changes.ts @@ -1,11 +1,19 @@ export interface IDossierChange { readonly dossierChanges: boolean; readonly dossierId: string; - readonly fileChanges: boolean; + readonly lastUpdated: string; +} + +export interface IFileChange extends IDossierChange { + readonly fileId: string; } export type IDossierChanges = readonly IDossierChange[]; +export type IFileChanges = readonly IFileChange[]; export interface IChangesDetails { readonly dossierChanges: IDossierChanges; + readonly fileChanges: IFileChanges; } + +export type DossierFileChanges = Record;