From cdc5d2ee6cb6fb021c715d712d472a5c7c80df4c Mon Sep 17 00:00:00 2001 From: Timo Bejan Date: Thu, 7 Nov 2024 15:41:33 +0200 Subject: [PATCH 01/24] 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; From 82e31fffd8c6e8bb9da6a5b2a382f5f1e5d92271 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Fri, 8 Nov 2024 13:27:37 +0200 Subject: [PATCH 02/24] RED-10332 - Dossier Template not changeable when comparing dossier dictionaries in Edit dossier modal --- .../dictionary-manager.component.html | 144 +++++++++--------- 1 file changed, 76 insertions(+), 68 deletions(-) diff --git a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html index c1429100c..e41b354f0 100644 --- a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html +++ b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html @@ -29,63 +29,67 @@ -
- - - - - {{ dossierTemplate.id ? dossierTemplate.name : (dossierTemplate.name | translate) }} - - - - -
+ @if (dossierTemplates) { +
+ + + @for (dossierTemplate of dossierTemplates; track dossierTemplate) { + @if (!initialDossierTemplateId || dossierTemplate?.id !== selectedDossierTemplate.id) { + + {{ dossierTemplate.id ? dossierTemplate.name : (dossierTemplate.name | translate) }} + + } + } + + +
+ }
- - - - - {{ dossier.dossierName }} - - - - - + @if (initialDossierTemplateId) { + + + @for (dossier of dossiers; track dossier; let index = $index) { + @if (dossier.dossierId !== selectedDossier.dossierId) { + + {{ dossier.dossierName }} + + @if (index === dossiers.length - 2 && !selectedDossier.dossierId?.includes('template')) { + + } + } + } + + + } - - - - - {{ dictionary.id ? dictionary.label : (dictionary.label | translate) }} - - - - + @if (!initialDossierTemplateId) { + + + @for (dictionary of dictionaries; track dictionary; let index = $index) { + + {{ dictionary.id ? dictionary.label : (dictionary.label | translate) }} + + } + + + }
@@ -100,20 +104,24 @@ [showDiffEditor]="compare && showDiffEditor" > -
- - -
+ @if (compare && optionNotSelected) { +
+ + +
+ } -
- -
-
+ @if (withFloatingActions && !!editor?.hasChanges && canEdit && !isLeavingPage) { +
+ +
+
+ } From f1aa0e4817ecb431b87e0922cb7db387ae7540ce Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Fri, 8 Nov 2024 13:39:20 +0200 Subject: [PATCH 03/24] RED-9585 - Incorrect capitalization in German translation + missing singular/plural distinction --- apps/red-ui/src/assets/i18n/redact/de.json | 8 ++++---- apps/red-ui/src/assets/i18n/redact/en.json | 10 +++++----- apps/red-ui/src/assets/i18n/scm/de.json | 8 ++++---- apps/red-ui/src/assets/i18n/scm/en.json | 10 +++++----- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index a0c19ea50..523364a19 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -2359,13 +2359,13 @@ }, "roles": { "inactive": "Inaktiv", - "manager-admin": "Manager & Admin", + "manager-admin": "Manager & {length, plural, one{Admin} other{Admins}}", "no-role": "Keine Rolle definiert", - "red-admin": "Anwendungsadmin", + "red-admin": "{length, plural, one{Anwendungsadmin} other{Anwendungsadmins}}", "red-manager": "Manager", "red-user": "Benutzer", - "red-user-admin": "Benutzeradmin", - "regular": "regulärer Benutzer" + "red-user-admin": "{length, plural, one{Benutzeradmin} other{Benutzeradmins}}", + "regular": "{length, plural, one{regulärer} other{reguläre}} Benutzer" }, "search-screen": { "cols": { diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index 443fad141..f3c7d3b6a 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -2359,12 +2359,12 @@ }, "roles": { "inactive": "Inactive", - "manager-admin": "Manager & admin", + "manager-admin": "Manager & {length, plural, one{Admin} other{Admins}}", "no-role": "No role defined", - "red-admin": "Application admin", - "red-manager": "Manager", - "red-user": "User", - "red-user-admin": "Users admin", + "red-admin": "Application {length, plural, one{admin} other{admins}}", + "red-manager": "{length, plural, one{Manager} other{Managers}}", + "red-user": "{length, plural, one{User} other{Users}}", + "red-user-admin": "{length, plural, one{User} other{Users}} admin", "regular": "Regular" }, "search-screen": { diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index e54cbf50a..0b9603749 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -2359,13 +2359,13 @@ }, "roles": { "inactive": "Inaktiv", - "manager-admin": "Manager & Admin", + "manager-admin": "Manager & {length, plural, one{Admin} other{Admins}}", "no-role": "Keine Rolle definiert", - "red-admin": "Anwendungsadmin", + "red-admin": "{length, plural, one{Anwendungsadmin} other{Anwendungsadmins}}", "red-manager": "Manager", "red-user": "Benutzer", - "red-user-admin": "Benutzeradmin", - "regular": "regulärer Benutzer" + "red-user-admin": "{length, plural, one{Benutzeradmin} other{Benutzeradmins}}", + "regular": "{length, plural, one{regulärer} other{reguläre}} Benutzer" }, "search-screen": { "cols": { diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index b2990274c..026460786 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -2359,12 +2359,12 @@ }, "roles": { "inactive": "Inactive", - "manager-admin": "Manager & admin", + "manager-admin": "Manager & {length, plural, one{Admin} other{Admins}}", "no-role": "No role defined", - "red-admin": "Application admin", - "red-manager": "Manager", - "red-user": "User", - "red-user-admin": "Users admin", + "red-admin": "Application {length, plural, one{admin} other{admins}}", + "red-manager": "{length, plural, one{Manager} other{Managers}}", + "red-user": "{length, plural, one{User} other{Users}}", + "red-user-admin": "{length, plural, one{User} other{Users}} admin", "regular": "Regular" }, "search-screen": { From 470685a94f8be16820bfada5578a75847a5ea091 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Fri, 8 Nov 2024 17:01:41 +0200 Subject: [PATCH 04/24] RED-10426: sort annotations according to visual alignment. --- .../utils/sort-by-page-rotation.utils.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/red-ui/src/app/modules/file-preview/utils/sort-by-page-rotation.utils.ts b/apps/red-ui/src/app/modules/file-preview/utils/sort-by-page-rotation.utils.ts index 4ccce0755..6a18812ac 100644 --- a/apps/red-ui/src/app/modules/file-preview/utils/sort-by-page-rotation.utils.ts +++ b/apps/red-ui/src/app/modules/file-preview/utils/sort-by-page-rotation.utils.ts @@ -1,6 +1,12 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper'; export const sortTopLeftToBottomRight = (a1: AnnotationWrapper, a2: AnnotationWrapper): number => { + const A = a1.y; + const B = a1.y - a1.height; + const median = (A + B) / 2; + if (median > a2.y - a2.height && median < a2.y) { + return a1.x < a2.x ? -1 : 1; + } if (a1.y > a2.y) { return -1; } @@ -11,6 +17,12 @@ export const sortTopLeftToBottomRight = (a1: AnnotationWrapper, a2: AnnotationWr }; export const sortBottomLeftToTopRight = (a1: AnnotationWrapper, a2: AnnotationWrapper): number => { + const A = a1.x; + const B = a1.x + a1.width; + const median = (A + B) / 2; + if (median < a2.x + a2.width && median > a2.x) { + return a1.y < a2.y ? -1 : 1; + } if (a1.x < a2.x) { return -1; } @@ -21,6 +33,12 @@ export const sortBottomLeftToTopRight = (a1: AnnotationWrapper, a2: AnnotationWr }; export const sortBottomRightToTopLeft = (a1: AnnotationWrapper, a2: AnnotationWrapper): number => { + const A = a1.y; + const B = a1.y + a1.height; + const median = (A + B) / 2; + if (median < a2.y + a2.height && median > a2.y) { + return a1.x > a2.x ? -1 : 1; + } if (a1.y < a2.y) { return -1; } @@ -31,6 +49,12 @@ export const sortBottomRightToTopLeft = (a1: AnnotationWrapper, a2: AnnotationWr }; export const sortTopRightToBottomLeft = (a1: AnnotationWrapper, a2: AnnotationWrapper): number => { + const A = a1.x; + const B = a1.x - a1.width; + const median = (A + B) / 2; + if (median > a2.x - a2.width && median < a2.x) { + return a1.y > a2.y ? -1 : 1; + } if (a1.x > a2.x) { return -1; } From f0b6cf971234fb352c26330dfaa2cfcf5c2f9367 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Fri, 8 Nov 2024 17:06:29 +0200 Subject: [PATCH 05/24] WIP on VM/RED-10332 RED-10332 - Dossier Template not changeable when comparing dossier dictionaries in Edit dossier modal --- .../edit-dossier-dictionary.component.html | 2 +- .../edit-dossier-dictionary.component.ts | 2 +- .../file-download-btn.component.ts | 4 +- .../dictionary-manager.component.html | 24 +++-- .../dictionary-manager.component.ts | 99 ++++++++++--------- 5 files changed, 73 insertions(+), 58 deletions(-) diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html index 7adbcaea3..7f29746f6 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html @@ -54,8 +54,8 @@ [currentDossierTemplateId]="dossier.dossierTemplateId" [hint]="selectedDictionary.hint" [initialEntries]="entriesToDisplay || []" - [selectedDictionaryTypeLabel]="selectedDictionary.label" [selectedDictionaryType]="selectedDictionary.type" + [activeDictionary]="selectedDictionary" [withFloatingActions]="false" > diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts index 28d5e725f..716d29aca 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts @@ -83,7 +83,7 @@ export class EditDossierDictionaryComponent implements OnInit { try { await this._dictionaryService.saveEntries( this._dictionaryManager.editor.currentEntries, - this._dictionaryManager.initialEntries, + this._dictionaryManager.initialEntries(), this.dossier.dossierTemplateId, this.selectedDictionary.type, this.dossier.id, diff --git a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts index 83b6089e4..1107af8b2 100644 --- a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts @@ -24,8 +24,8 @@ export class FileDownloadBtnComponent implements OnChanges { readonly tooltipPosition = input<'above' | 'below' | 'before' | 'after'>('above'); readonly type = input(CircleButtonTypes.default); readonly tooltipClass = input(); - readonly disabled = input(false); - readonly singleFileDownload = input(false); + readonly disabled = input(false, { transform: booleanAttribute }); + readonly singleFileDownload = input(false, { transform: booleanAttribute }); readonly dossierDownload = input(false, { transform: booleanAttribute }); readonly dropdownButton = computed(() => this.isDocumine && (this.dossierDownload() || this.singleFileDownload())); tooltip: string; diff --git a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html index e41b354f0..a7794e65d 100644 --- a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html +++ b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html @@ -5,7 +5,7 @@ @for (dossier of dossiers; track dossier; let index = $index) { @if (dossier.dossierId !== selectedDossier.dossierId) { - - {{ dossier.dossierName }} - - @if (index === dossiers.length - 2 && !selectedDossier.dossierId?.includes('template')) { - + @if (!activeDictionary().dossierDictionaryOnly) { + + {{ dossier.dossierName }} + + @if (index === dossiers.length - 2 && !selectedDossier.dossierId?.includes('template')) { + + } + } @else if (!dossier.dossierId?.includes('template')) { + + {{ dossier.dossierName }} + } } } @@ -98,9 +104,9 @@
@@ -112,7 +118,7 @@ }
- @if (withFloatingActions && !!editor?.hasChanges && canEdit && !isLeavingPage) { + @if (withFloatingActions() && !!editor?.hasChanges && canEdit() && !isLeavingPage()) {
(); +export class DictionaryManagerComponent implements OnInit { + readonly type = input('dictionary'); + readonly entityType = input(); + readonly currentDossierId = input(); + readonly currentDossierTemplateId = model(); + readonly withFloatingActions = input(true, { transform: booleanAttribute }); + readonly initialEntries = input.required(); + readonly canEdit = input(false, { transform: booleanAttribute }); + readonly canDownload = input(false, { transform: booleanAttribute }); + readonly isLeavingPage = input(false, { transform: booleanAttribute }); + readonly hint = input(false, { transform: booleanAttribute }); + readonly activeDictionary = input(); + readonly selectedDictionaryType = model('dossier_redaction'); + readonly activeEntryType = input(DictionaryEntryTypes.ENTRY); + readonly saveDictionary = output(); @ViewChild(EditorComponent) readonly editor: EditorComponent; readonly iconButtonTypes = IconButtonTypes; dossiers: Dossier[]; @@ -102,7 +107,24 @@ export class DictionaryManagerComponent implements OnChanges, OnInit { private readonly _changeRef: ChangeDetectorRef, private readonly _dossierTemplatesService: DossierTemplatesService, protected readonly _loadingService: LoadingService, - ) {} + ) { + effect(() => { + if (this.activeEntryType() && this.#dossier?.dossierTemplateId && this.selectedDossier?.dossierId) { + this.#onDossierChanged(this.#dossier.dossierTemplateId, this.#dossier.dossierId).then(entries => + this.#updateDiffEditorText(entries), + ); + } + }); + effect( + () => { + if (this.selectedDictionaryType()) { + this.#disableDiffEditor(); + this.#updateDropdownsOptions(); + } + }, + { allowSignalWrites: true }, + ); + } get selectedDossierTemplate() { return this.#dossierTemplate; @@ -115,12 +137,12 @@ export class DictionaryManagerComponent implements OnChanges, OnInit { : this.selectDossierTemplate; } this.#dossierTemplate = value; - this.currentDossierTemplateId = value.dossierTemplateId; + this.currentDossierTemplateId.set(value.dossierTemplateId); this.#dossier = this.selectDossier; this.dictionaries = this.#dictionaries; this.#disableDiffEditor(); - if (!this.initialDossierTemplateId && !this.currentDossierId) { + if (!this.initialDossierTemplateId && !this.currentDossierId()) { this.selectedDictionary = this.selectDictionary; } @@ -148,7 +170,7 @@ export class DictionaryManagerComponent implements OnChanges, OnInit { set selectedDictionary(dictionary: Dictionary) { if (dictionary.type) { - this.selectedDictionaryType = dictionary.type; + this.selectedDictionaryType.set(dictionary.type); this.#dictionary = dictionary; this.#onDossierChanged(this.#dossier.dossierTemplateId).then(entries => this.#updateDiffEditorText(entries)); } @@ -181,7 +203,7 @@ export class DictionaryManagerComponent implements OnChanges, OnInit { get #templatesWithCurrentEntityType() { return this._dossierTemplatesService.all.filter(t => - this._dictionaryService.hasType(t.dossierTemplateId, this.selectedDictionaryType), + this._dictionaryService.hasType(t.dossierTemplateId, this.selectedDictionaryType()), ); } @@ -190,7 +212,7 @@ export class DictionaryManagerComponent implements OnChanges, OnInit { this.dossierTemplates = this._dossierTemplatesService.all; await firstValueFrom(this._dictionaryService.loadDictionaryDataForDossierTemplates(this.dossierTemplates.map(t => t.id))); this.#dossierTemplate = this._dossierTemplatesService.all[0]; - this.initialDossierTemplateId = this.currentDossierTemplateId; + this.initialDossierTemplateId = this.currentDossierTemplateId(); this.#updateDropdownsOptions(); } @@ -199,7 +221,7 @@ export class DictionaryManagerComponent implements OnChanges, OnInit { const blob = new Blob([content], { type: 'text/plain;charset=utf-8', }); - saveAs(blob, `${this.entityType}-${this.type}.txt`); + saveAs(blob, `${this.entityType()}-${this.type()}.txt`); } revert() { @@ -213,43 +235,30 @@ export class DictionaryManagerComponent implements OnChanges, OnInit { } } - ngOnChanges(changes: SimpleChanges): void { - if (changes.activeEntryType && this.#dossier?.dossierTemplateId && this.selectedDossier?.dossierId) { - this.#onDossierChanged(this.#dossier.dossierTemplateId, this.#dossier.dossierId).then(entries => - this.#updateDiffEditorText(entries), - ); - } - - if (changes.selectedDictionaryType) { - this.#disableDiffEditor(); - this.#updateDropdownsOptions(); - } - } - async #onDossierChanged(dossierTemplateId: string, dossierId?: string) { let dictionary: IDictionary; if (dossierId === 'template') { - dictionary = await this._dictionaryService.getForType(dossierTemplateId, this.selectedDictionaryType); + dictionary = await this._dictionaryService.getForType(dossierTemplateId, this.selectedDictionaryType()); } else { if (dossierId) { dictionary = ( await firstValueFrom( - this._dictionaryService.loadDictionaryEntriesByType([this.selectedDictionaryType], dossierTemplateId, dossierId), + this._dictionaryService.loadDictionaryEntriesByType([this.selectedDictionaryType()], dossierTemplateId, dossierId), ).catch(() => { return [{ entries: [COMPARE_ENTRIES_ERROR], type: '' }]; }) )[0]; } else { - dictionary = this.selectedDictionaryType - ? await this._dictionaryService.getForType(this.currentDossierTemplateId, this.selectedDictionaryType) + dictionary = this.selectedDictionaryType() + ? await this._dictionaryService.getForType(this.currentDossierTemplateId(), this.selectedDictionaryType()) : { entries: [COMPARE_ENTRIES_ERROR], type: '' }; } } const activeEntries = - this.activeEntryType === DictionaryEntryTypes.ENTRY || this.hint + this.activeEntryType() === DictionaryEntryTypes.ENTRY || this.hint() ? [...dictionary.entries] - : this.activeEntryType === DictionaryEntryTypes.FALSE_POSITIVE + : this.activeEntryType() === DictionaryEntryTypes.FALSE_POSITIVE ? [...dictionary.falsePositiveEntries] : [...dictionary.falseRecommendationEntries]; return activeEntries.join('\n'); @@ -257,23 +266,23 @@ export class DictionaryManagerComponent implements OnChanges, OnInit { #updateDropdownsOptions(updateSelectedDossierTemplate = true) { if (updateSelectedDossierTemplate) { - this.currentDossierTemplateId = this.initialDossierTemplateId ?? this.currentDossierTemplateId; + this.currentDossierTemplateId.set(this.initialDossierTemplateId ?? this.currentDossierTemplateId()); this.dossierTemplates = this.currentDossierTemplateId ? this.#templatesWithCurrentEntityType : this._dossierTemplatesService.all; if (!this.currentDossierTemplateId) { this.dossierTemplates = [this.selectDossierTemplate, ...this.dossierTemplates]; } - this.selectedDossierTemplate = this.dossierTemplates.find(t => t.id === this.currentDossierTemplateId); + this.selectedDossierTemplate = this.dossierTemplates.find(t => t.id === this.currentDossierTemplateId()); } this.dossiers = this._activeDossiersService.all.filter( - d => d.dossierTemplateId === this.currentDossierTemplateId && d.id !== this.currentDossierId, + d => d.dossierTemplateId === this.currentDossierTemplateId() && d.id !== this.currentDossierId(), ); const templateDictionary = { id: 'template', dossierId: 'template', dossierName: 'Template Dictionary', - dossierTemplateId: this.currentDossierTemplateId, + dossierTemplateId: this.currentDossierTemplateId(), } as Dossier; this.dossiers.push(templateDictionary); } From 8cdb869a103729fb10c2ba6e87e45b18538164be Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Mon, 11 Nov 2024 12:23:47 +0200 Subject: [PATCH 06/24] RED-10363: disable analysis button when rules are locked. --- .../modules/admin/services/rules.service.ts | 24 +++++++++++++++---- ...sier-overview-screen-header.component.html | 17 +++++++------ ...ossier-overview-screen-header.component.ts | 19 +++++++++------ .../file-actions/file-actions.component.ts | 19 +++++++++++++-- libs/red-domain/src/lib/shared/index.ts | 1 + libs/red-domain/src/lib/shared/rules.model.ts | 23 ++++++++++++++++++ libs/red-domain/src/lib/shared/rules.ts | 2 +- 7 files changed, 83 insertions(+), 22 deletions(-) create mode 100644 libs/red-domain/src/lib/shared/rules.model.ts diff --git a/apps/red-ui/src/app/modules/admin/services/rules.service.ts b/apps/red-ui/src/app/modules/admin/services/rules.service.ts index bb89c2ad8..11efa8c81 100644 --- a/apps/red-ui/src/app/modules/admin/services/rules.service.ts +++ b/apps/red-ui/src/app/modules/admin/services/rules.service.ts @@ -1,11 +1,25 @@ import { Injectable } from '@angular/core'; -import { GenericService, QueryParam } from '@iqser/common-ui'; -import { IRules } from '@red/domain'; -import { Observable } from 'rxjs'; +import { EntitiesService, QueryParam } from '@iqser/common-ui'; +import { IRules, Rules } from '@red/domain'; +import { map, Observable, tap } from 'rxjs'; import { List } from '@common-ui/utils'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { distinctUntilChanged, filter } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) -export class RulesService extends GenericService { +export class RulesService extends EntitiesService { + readonly currentTemplateRules = toSignal( + this.all$.pipe( + filter(all => !!all.length), + map(rules => rules[0]), + distinctUntilChanged( + (prev, curr) => + prev.rules !== curr.rules || + prev.timeoutDetected !== curr.timeoutDetected || + prev.dossierTemplateId !== curr.dossierTemplateId, + ), + ), + ); protected readonly _defaultModelPath = 'rules'; download(dossierTemplateId: string, ruleFileType: IRules['ruleFileType'] = 'ENTITY') { @@ -17,6 +31,6 @@ export class RulesService extends GenericService { } getFor(entityId: string, queryParams?: List): Observable { - return super.getFor(entityId, queryParams); + return super.getFor(entityId, queryParams).pipe(tap(rules => this.setEntities([rules as Rules]))); } } diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/screen-header/dossier-overview-screen-header.component.html b/apps/red-ui/src/app/modules/dossier-overview/components/screen-header/dossier-overview-screen-header.component.html index 5e45b10fa..9ffab710e 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/screen-header/dossier-overview-screen-header.component.html +++ b/apps/red-ui/src/app/modules/dossier-overview/components/screen-header/dossier-overview-screen-header.component.html @@ -1,5 +1,5 @@ (); @Output() readonly upload = new EventEmitter(); readonly circleButtonTypes = CircleButtonTypes; readonly roles = Roles; @@ -60,6 +61,9 @@ export class DossierOverviewScreenHeaderComponent implements OnInit { readonly downloadFilesDisabled$: Observable; readonly downloadComponentLogsDisabled$: Observable; readonly isDocumine = getConfig().IS_DOCUMINE; + readonly areRulesLocked = computed(() => { + return this._rulesService.currentTemplateRules().timeoutDetected; + }); constructor( private readonly _toaster: Toaster, @@ -73,6 +77,7 @@ export class DossierOverviewScreenHeaderComponent implements OnInit { private readonly _reanalysisService: ReanalysisService, private readonly _loadingService: LoadingService, private readonly _primaryFileAttributeService: PrimaryFileAttributeService, + private readonly _rulesService: RulesService, ) { const someNotProcessed$ = this.entitiesService.all$.pipe(some(file => !file.lastProcessed)); this.downloadFilesDisabled$ = combineLatest([this.listingService.areSomeSelected$, someNotProcessed$]).pipe( @@ -84,13 +89,13 @@ export class DossierOverviewScreenHeaderComponent implements OnInit { } ngOnInit() { - this.actionConfigs = this.configService.actionConfig(this.dossier.id, this.listingService.areSomeSelected$); + this.actionConfigs = this.configService.actionConfig(this.dossier().id, this.listingService.areSomeSelected$); } async reanalyseDossier() { this._loadingService.start(); try { - await this._reanalysisService.reanalyzeDossier(this.dossier, true); + await this._reanalysisService.reanalyzeDossier(this.dossier(), true); this._toaster.success(_('dossier-overview.reanalyse-dossier.success')); } catch (e) { this._toaster.error(_('dossier-overview.reanalyse-dossier.error')); @@ -101,12 +106,12 @@ export class DossierOverviewScreenHeaderComponent implements OnInit { async downloadDossierAsCSV() { const displayedEntities = await firstValueFrom(this.listingService.displayed$); const entities = this.sortingService.defaultSort(displayedEntities); - const fileName = this.dossier.dossierName + '.export.csv'; + const fileName = this.dossier().dossierName + '.export.csv'; const mapper = (file?: File) => ({ ...file, hasAnnotations: file.hasRedactions, assignee: this._userService.getName(file.assignee) || '-', - primaryAttribute: this._primaryFileAttributeService.getPrimaryFileAttributeValue(file, this.dossier.dossierTemplateId), + primaryAttribute: this._primaryFileAttributeService.getPrimaryFileAttributeValue(file, this.dossier().dossierTemplateId), }); const documineOnlyFields = ['hasAnnotations']; const redactionOnlyFields = ['hasHints', 'hasImages', 'hasUpdates', 'hasRedactions']; diff --git a/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts b/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts index 760c59c66..04375e949 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts @@ -1,4 +1,15 @@ -import { ChangeDetectorRef, Component, HostBinding, Injector, Input, OnChanges, Optional, signal, ViewChild } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + computed, + HostBinding, + Injector, + Input, + OnChanges, + Optional, + signal, + ViewChild, +} from '@angular/core'; import { toObservable } from '@angular/core/rxjs-interop'; import { Router } from '@angular/router'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; @@ -37,6 +48,7 @@ import { ProcessingIndicatorComponent } from '@shared/components/processing-indi import { StatusBarComponent } from '@common-ui/shared'; import { NgIf, NgTemplateOutlet } from '@angular/common'; import { ApproveWarningDetailsComponent } from '@shared/components/approve-warning-details/approve-warning-details.component'; +import { RulesService } from '../../../admin/services/rules.service'; @Component({ selector: 'redaction-file-actions', @@ -86,6 +98,7 @@ export class FileActionsComponent implements OnChanges { @ViewChild(ExpandableFileActionsComponent) private readonly _expandableActionsComponent: ExpandableFileActionsComponent; readonly #isDocumine = getConfig().IS_DOCUMINE; + readonly areRulesLocked = computed(() => this._rulesService.currentTemplateRules().timeoutDetected); constructor( private readonly _injector: Injector, @@ -101,6 +114,7 @@ export class FileActionsComponent implements OnChanges { private readonly _activeDossiersService: ActiveDossiersService, private readonly _fileManagementService: FileManagementService, private readonly _userPreferenceService: UserPreferenceService, + private readonly _rulesService: RulesService, readonly fileAttributesService: FileAttributesService, @Optional() private readonly _documentInfoService: DocumentInfoService, @Optional() private readonly _excludedPagesService: ExcludedPagesService, @@ -277,9 +291,10 @@ export class FileActionsComponent implements OnChanges { id: 'btn-reanalyse_file', type: ActionTypes.circleBtn, action: () => this.#reanalyseFile(), - tooltip: _('dossier-overview.reanalyse.action'), + tooltip: this.areRulesLocked() ? _('dossier-listing.rules.timeoutError') : _('dossier-overview.reanalyse.action'), icon: 'iqser:refresh', show: this.showReanalyseDossierOverview, + disabled: this.areRulesLocked(), helpModeKey: 'stop_analysis', }, { diff --git a/libs/red-domain/src/lib/shared/index.ts b/libs/red-domain/src/lib/shared/index.ts index 8406e4be7..7c5c49665 100644 --- a/libs/red-domain/src/lib/shared/index.ts +++ b/libs/red-domain/src/lib/shared/index.ts @@ -13,3 +13,4 @@ export * from './app-config'; export * from './system-preferences'; export * from './component-rules'; export * from './editor.types'; +export * from './rules.model'; diff --git a/libs/red-domain/src/lib/shared/rules.model.ts b/libs/red-domain/src/lib/shared/rules.model.ts new file mode 100644 index 000000000..85b7c2d05 --- /dev/null +++ b/libs/red-domain/src/lib/shared/rules.model.ts @@ -0,0 +1,23 @@ +import { IRules } from '@red/domain'; +import { Entity } from '@iqser/common-ui'; + +export class Rules extends Entity implements IRules { + readonly id: string; + readonly routerLink: string; + readonly searchKey: string; + readonly dossierTemplateId: string; + readonly ruleFileType?: 'ENTITY' | 'COMPONENT'; + readonly rules?: string; + readonly dryRun?: boolean; + readonly timeoutDetected?: boolean; + + constructor(rules: IRules) { + super(rules); + this.id = rules.dossierTemplateId; + this.dossierTemplateId = rules.dossierTemplateId; + this.ruleFileType = rules.ruleFileType; + this.rules = rules.rules; + this.dryRun = rules.dryRun; + this.timeoutDetected = rules.timeoutDetected; + } +} diff --git a/libs/red-domain/src/lib/shared/rules.ts b/libs/red-domain/src/lib/shared/rules.ts index 5a8316b1b..431bb3427 100644 --- a/libs/red-domain/src/lib/shared/rules.ts +++ b/libs/red-domain/src/lib/shared/rules.ts @@ -5,7 +5,7 @@ export interface IRules { /** * The DossierTemplate ID for these rules */ - dossierTemplateId?: string; + dossierTemplateId: string; /** * The file type to be retrieved/saved under, defaults to ENTITY */ From de1495ee2efc6d1811f96020358620d62e039e2c Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Mon, 11 Nov 2024 14:01:28 +0200 Subject: [PATCH 07/24] RED-10427: send rectangle updated value for edit on multiple pages. --- .../services/annotation-actions.service.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts index 7a91628b3..fdddc5c1c 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { IqserDialog } from '@common-ui/dialog/iqser-dialog.service'; import { getConfig } from '@iqser/common-ui'; -import { List, log } from '@iqser/common-ui/lib/utils'; +import { List } from '@iqser/common-ui/lib/utils'; import { AnnotationPermissions } from '@models/file/annotation.permissions'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { Core } from '@pdftron/webviewer'; @@ -153,7 +153,7 @@ export class AnnotationActionsService { }); } else { recategorizeBody = { - value: annotations[0].value, + value: result.value ?? annotations[0].value, type: result.type, legalBasis: result.legalBasis, section: result.section, @@ -165,15 +165,13 @@ export class AnnotationActionsService { } await this.#processObsAndEmit( - this._manualRedactionService - .recategorizeRedactions( - recategorizeBody, - dossierId, - file().id, - this.#getChangedFields(annotations, result), - result.option === RedactOrHintOptions.IN_DOCUMENT || !!result.pageNumbers.length, - ) - .pipe(log()), + this._manualRedactionService.recategorizeRedactions( + recategorizeBody, + dossierId, + file().id, + this.#getChangedFields(annotations, result), + result.option === RedactOrHintOptions.IN_DOCUMENT || !!result.pageNumbers.length, + ), ); } From 8e2ab6c2c41f3b7d8748b66455087b454d60080c Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Mon, 11 Nov 2024 15:01:52 +0200 Subject: [PATCH 08/24] RED-10420: prevent page refresh by using stop propagation. --- .../entities-listing/entities-listing-screen.component.html | 1 + .../entities-listing/entities-listing-screen.component.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.html index 208e2504f..c6503778c 100644 --- a/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.html @@ -87,6 +87,7 @@ [routerLink]="dict.routerLink" [tooltip]="'entities-listing.action.edit' | translate" icon="iqser:edit" + iqserStopPropagation >
diff --git a/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.ts index 43c425a5a..9ff6a1d6b 100644 --- a/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.ts @@ -11,6 +11,7 @@ import { ListingComponent, listingProvidersFactory, LoadingService, + StopPropagationDirective, TableColumnConfig, } from '@iqser/common-ui'; import { getParam } from '@iqser/common-ui/lib/utils'; @@ -41,6 +42,7 @@ import { AdminDialogService } from '../../services/admin-dialog.service'; AnnotationIconComponent, AsyncPipe, RouterLink, + StopPropagationDirective, ], }) export class EntitiesListingScreenComponent extends ListingComponent implements OnInit { From f31db952418e785061ef56294091cba18273245b Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Mon, 11 Nov 2024 15:44:37 +0200 Subject: [PATCH 09/24] RED-10339 - Secondary Save button in redaction dialogs to store selected option as default --- .../add-hint-dialog.component.html | 8 +++ .../add-hint-dialog.component.ts | 11 +++- .../redact-text-dialog.component.html | 7 +++ .../redact-text-dialog.component.ts | 11 +++- .../remove-redaction-dialog.component.html | 10 ++++ .../remove-redaction-dialog.component.ts | 52 +++++++++++++++++-- apps/red-ui/src/assets/i18n/redact/de.json | 9 ++-- apps/red-ui/src/assets/i18n/redact/en.json | 9 ++-- apps/red-ui/src/assets/i18n/scm/de.json | 9 ++-- apps/red-ui/src/assets/i18n/scm/en.json | 9 ++-- 10 files changed, 117 insertions(+), 18 deletions(-) diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/add-hint-dialog/add-hint-dialog.component.html b/apps/red-ui/src/app/modules/file-preview/dialogs/add-hint-dialog/add-hint-dialog.component.html index 1b8ba9d00..f98e8d31e 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/add-hint-dialog/add-hint-dialog.component.html +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/add-hint-dialog/add-hint-dialog.component.html @@ -104,6 +104,14 @@ > + +
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/add-hint-dialog/add-hint-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/add-hint-dialog/add-hint-dialog.component.ts index 2826b01a4..35c614eb4 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/add-hint-dialog/add-hint-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/add-hint-dialog/add-hint-dialog.component.ts @@ -130,7 +130,7 @@ export class AddHintDialogComponent extends IqserDialogComponent + +
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts index 8dd73df9b..41a32a1fd 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts @@ -138,7 +138,7 @@ export class RedactTextDialogComponent get applyToAll() { return this.isSystemDefault || this._userPreferences.getAddRedactionDefaultExtraOption() === 'undefined' - ? this.data.applyToAllDossiers ?? true + ? (this.data.applyToAllDossiers ?? true) : stringToBoolean(this._userPreferences.getAddRedactionDefaultExtraOption()); } @@ -190,6 +190,15 @@ export class RedactTextDialogComponent }); } + async saveAndRemember() { + const option = this.form.controls.option?.value; + await this._userPreferences.saveAddRedactionDefaultOption(option?.value ?? SystemDefaults.ADD_REDACTION_DEFAULT); + if (option?.additionalCheck) { + await this._userPreferences.saveAddRedactionDefaultExtraOption(option.additionalCheck.checked); + } + this.save(); + } + toggleEditingSelectedText() { this.isEditingSelectedText = !this.isEditingSelectedText; if (this.isEditingSelectedText) { diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.html b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.html index af6631396..cea51e224 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.html +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.html @@ -42,6 +42,16 @@ >
+ @if (!allRectangles) { + + } +
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts index 0ffa843bb..648d4d06e 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts @@ -38,6 +38,12 @@ import { isJustOne } from '@common-ui/utils'; import { validatePageRange } from '../../utils/form-validators'; import { parseRectanglePosition, parseSelectedPageNumbers, prefillPageRange } from '../../utils/enhance-manual-redaction-request.utils'; +const ANNOTATION_TYPES = { + REDACTION: 'redaction', + HINT: 'hint', + RECOMMENDATION: 'recommendation', +}; + @Component({ templateUrl: './remove-redaction-dialog.component.html', styleUrls: ['./remove-redaction-dialog.component.scss'], @@ -65,7 +71,11 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent< readonly iconButtonTypes = IconButtonTypes; readonly recommendation = this.data.redactions.every(redaction => redaction.isRecommendation); readonly hint = this.data.redactions.every(redaction => redaction.isHint); - readonly annotationsType = this.hint ? 'hint' : this.recommendation ? 'recommendation' : 'redaction'; + readonly annotationsType = this.hint + ? ANNOTATION_TYPES.HINT + : this.recommendation + ? ANNOTATION_TYPES.RECOMMENDATION + : ANNOTATION_TYPES.REDACTION; readonly optionByType = { recommendation: { main: this._userPreferences.getRemoveRecommendationDefaultOption(), @@ -94,7 +104,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent< extra: false, }, }; - readonly #allRectangles = this.data.redactions.reduce((acc, a) => acc && a.AREA, true); + readonly allRectangles = this.data.redactions.reduce((acc, a) => acc && a.AREA, true); readonly #applyToAllDossiers = this.systemDefaultByType[this.annotationsType].extra; readonly isSystemDefault = this.optionByType[this.annotationsType].main === SystemDefaultOption.SYSTEM_DEFAULT; readonly isExtraOptionSystemDefault = this.optionByType[this.annotationsType].extra === 'undefined'; @@ -102,7 +112,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent< ? this.systemDefaultByType[this.annotationsType].main : this.optionByType[this.annotationsType].main; readonly extraOptionPreference = stringToBoolean(this.optionByType[this.annotationsType].extra); - readonly options: DetailsRadioOption[] = this.#allRectangles + readonly options: DetailsRadioOption[] = this.allRectangles ? getRectangleRedactOptions('remove') : getRemoveRedactionOptions( this.data, @@ -141,7 +151,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent< ) { super(); - if (this.#allRectangles) { + if (this.allRectangles) { prefillPageRange( this.data.redactions[0], this.data.allFileRedactions, @@ -198,6 +208,40 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent< }); } + async saveAndRemember() { + const option = this.form.controls.option?.value; + + switch (this.annotationsType) { + case ANNOTATION_TYPES.REDACTION: + await this._userPreferences.saveRemoveRedactionDefaultOption(option?.value ?? SystemDefaults.REMOVE_REDACTION_DEFAULT); + break; + case ANNOTATION_TYPES.HINT: + await this._userPreferences.saveRemoveHintDefaultOption(option?.value ?? SystemDefaults.REMOVE_HINT_DEFAULT); + break; + case ANNOTATION_TYPES.RECOMMENDATION: + await this._userPreferences.saveRemoveRecommendationDefaultOption( + option?.value ?? SystemDefaults.REMOVE_RECOMMENDATION_DEFAULT, + ); + break; + } + + if (option?.additionalCheck) { + switch (this.annotationsType) { + case ANNOTATION_TYPES.REDACTION: + await this._userPreferences.saveRemoveRedactionDefaultExtraOption(option.additionalCheck.checked); + break; + case ANNOTATION_TYPES.HINT: + await this._userPreferences.saveRemoveHintDefaultExtraOption(option.additionalCheck.checked); + break; + case ANNOTATION_TYPES.RECOMMENDATION: + await this._userPreferences.saveRemoveRecommendationDefaultExtraOption(option.additionalCheck.checked); + break; + } + } + + this.save(); + } + #getOption(option: RemoveRedactionOption): DetailsRadioOption { return this.options.find(o => o.value === option); } diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index 523364a19..c394633f6 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -224,7 +224,8 @@ "dialog": { "actions": { "cancel": "Abbrechen", - "save": "Speichern" + "save": "Speichern", + "save-and-remember": "" }, "content": { "comment": "Kommentar", @@ -2106,7 +2107,8 @@ "dialog": { "actions": { "cancel": "Abbrechen", - "save": "Speichern" + "save": "Speichern", + "save-and-remember": "" }, "content": { "comment": "Kommentar", @@ -2202,7 +2204,8 @@ "dialog": { "actions": { "cancel": "Abbrechen", - "save": "Speichern" + "save": "Speichern", + "save-and-remember": "" }, "content": { "comment": "Kommentar", diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index f3c7d3b6a..a64aae978 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -224,7 +224,8 @@ "dialog": { "actions": { "cancel": "Cancel", - "save": "Save" + "save": "Save", + "save-and-remember": "Save and remember my choice" }, "content": { "comment": "Comment", @@ -2106,7 +2107,8 @@ "dialog": { "actions": { "cancel": "Cancel", - "save": "Save" + "save": "Save", + "save-and-remember": "Save and remember my choice" }, "content": { "comment": "Comment", @@ -2202,7 +2204,8 @@ "dialog": { "actions": { "cancel": "Cancel", - "save": "Save" + "save": "Save", + "save-and-remember": "Save and remember my choice" }, "content": { "comment": "Comment", diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index 0b9603749..62d882d6e 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -224,7 +224,8 @@ "dialog": { "actions": { "cancel": "Abbrechen", - "save": "Speichern" + "save": "Speichern", + "save-and-remember": "" }, "content": { "comment": "Kommentar", @@ -2106,7 +2107,8 @@ "dialog": { "actions": { "cancel": "Abbrechen", - "save": "Speichern" + "save": "Speichern", + "save-and-remember": "" }, "content": { "comment": "Kommentar", @@ -2202,7 +2204,8 @@ "dialog": { "actions": { "cancel": "Abbrechen", - "save": "Speichern" + "save": "Speichern", + "save-and-remember": "" }, "content": { "comment": "Kommentar", diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index 026460786..61f0d92a6 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -224,7 +224,8 @@ "dialog": { "actions": { "cancel": "Cancel", - "save": "Save" + "save": "Save", + "save-and-remember": "Save and remember my choice" }, "content": { "comment": "Comment", @@ -2106,7 +2107,8 @@ "dialog": { "actions": { "cancel": "Cancel", - "save": "Save" + "save": "Save", + "save-and-remember": "Save and remember my choice" }, "content": { "comment": "Comment", @@ -2202,7 +2204,8 @@ "dialog": { "actions": { "cancel": "Cancel", - "save": "Save" + "save": "Save", + "save-and-remember": "Save and remember my choice" }, "content": { "comment": "Comment", From 01eda984150d0fc55e6eff4191602df57bff0fd7 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Mon, 11 Nov 2024 16:47:08 +0200 Subject: [PATCH 10/24] RED-10440: fixed redacted image hint display color. --- apps/red-ui/src/app/models/file/annotation.wrapper.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/red-ui/src/app/models/file/annotation.wrapper.ts b/apps/red-ui/src/app/models/file/annotation.wrapper.ts index d01ea8fc3..c9e838137 100644 --- a/apps/red-ui/src/app/models/file/annotation.wrapper.ts +++ b/apps/red-ui/src/app/models/file/annotation.wrapper.ts @@ -84,7 +84,10 @@ export class AnnotationWrapper implements IListable { } get isRedactedImageHint() { - return this.IMAGE_HINT && this.superType === SuperTypes.Redaction; + return ( + (this.IMAGE_HINT && this.superType === SuperTypes.Redaction) || + (this.IMAGE_HINT && this.superType === SuperTypes.ManualRedaction) + ); } get isSkippedImageHint() { From ef260037556f83e7ba98c87d91852460bbeea3a7 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Mon, 11 Nov 2024 20:50:04 +0200 Subject: [PATCH 11/24] RED-10396 - fixed current page active annotations re evaluation when they are updated --- .../components/file-workload/file-workload.component.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts b/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts index eae606c2f..626a46c9e 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts @@ -8,6 +8,7 @@ import { HostListener, OnDestroy, OnInit, + signal, TemplateRef, untracked, viewChild, @@ -108,13 +109,17 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On return this.isDocumine && file.excludedFromAutomaticAnalysis && file.workflowStatus !== WorkflowFileStatuses.APPROVED; }); protected displayedAnnotations = new Map(); - readonly activeAnnotations = computed(() => this.displayedAnnotations.get(this.pdf.currentPage()) || []); + readonly activeAnnotations = computed(() => { + this.#displayedAnnotationsUpdated(); + return this.displayedAnnotations.get(this.pdf.currentPage()) || []; + }); protected displayedPages: number[] = []; protected pagesPanelActive = true; protected enabledFilters = []; private readonly _annotationsElement = viewChild('annotationsElement'); private readonly _quickNavigationElement = viewChild('quickNavigation'); readonly #isIqserDevMode = this._userPreferenceService.isIqserDevMode; + readonly #displayedAnnotationsUpdated = signal(false); #displayedPagesChanged = false; constructor( @@ -452,6 +457,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On } this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(annotations, primary, secondary); + this.#displayedAnnotationsUpdated.set(true); const pagesThatDisplayAnnotations = [...this.displayedAnnotations.keys()]; this.enabledFilters = this.filterService.enabledFlatFilters; if (this.enabledFilters.some(f => f.id === 'pages-without-annotations')) { From 31d401869863245c5f56c84c4b7ab8c113bc0466 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Tue, 12 Nov 2024 10:37:13 +0200 Subject: [PATCH 12/24] RED-10396 - fixed current page active annotations re evaluation when they are updated --- .../file-workload.component.html | 2 +- .../file-workload/file-workload.component.ts | 31 ++++++++----------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.html b/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.html index 47d3dc13b..d202d612c 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.html +++ b/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.html @@ -146,7 +146,7 @@ id="annotations-list" tabindex="1" > - + (); - readonly activeAnnotations = computed(() => { - this.#displayedAnnotationsUpdated(); - return this.displayedAnnotations.get(this.pdf.currentPage()) || []; - }); + protected displayedAnnotations = signal(new Map()); + readonly activeAnnotations = computed(() => this.displayedAnnotations().get(this.pdf.currentPage()) || []); protected displayedPages: number[] = []; protected pagesPanelActive = true; protected enabledFilters = []; private readonly _annotationsElement = viewChild('annotationsElement'); private readonly _quickNavigationElement = viewChild('quickNavigation'); readonly #isIqserDevMode = this._userPreferenceService.isIqserDevMode; - readonly #displayedAnnotationsUpdated = signal(false); #displayedPagesChanged = false; constructor( @@ -367,11 +363,11 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On if ($event.key === 'ArrowDown') { const nextPage = this.#nextPageWithAnnotations(); - return this.listingService.selectAnnotations(this.displayedAnnotations.get(nextPage)[0]); + return this.listingService.selectAnnotations(this.displayedAnnotations().get(nextPage)[0]); } const prevPage = this.#prevPageWithAnnotations(); - const prevPageAnnotations = this.displayedAnnotations.get(prevPage); + const prevPageAnnotations = this.displayedAnnotations().get(prevPage); return this.listingService.selectAnnotations(getLast(prevPageAnnotations)); } @@ -380,7 +376,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On const pageIdx = this.displayedPages.indexOf(page); const nextPageIdx = pageIdx + 1; const previousPageIdx = pageIdx - 1; - const annotationsOnPage = this.displayedAnnotations.get(page); + const annotationsOnPage = this.displayedAnnotations().get(page); const idx = annotationsOnPage.findIndex(a => a.id === this._firstSelectedAnnotation.id); if ($event.key === 'ArrowDown') { @@ -390,7 +386,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On } else if (nextPageIdx < this.displayedPages.length) { // If not last page for (let i = nextPageIdx; i < this.displayedPages.length; i++) { - const nextPageAnnotations = this.displayedAnnotations.get(this.displayedPages[i]); + const nextPageAnnotations = this.displayedAnnotations().get(this.displayedPages[i]); if (nextPageAnnotations) { this.listingService.selectAnnotations(nextPageAnnotations[0]); break; @@ -408,7 +404,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On if (pageIdx) { // If not first page for (let i = previousPageIdx; i >= 0; i--) { - const prevPageAnnotations = this.displayedAnnotations.get(this.displayedPages[i]); + const prevPageAnnotations = this.displayedAnnotations().get(this.displayedPages[i]); if (prevPageAnnotations) { this.listingService.selectAnnotations(getLast(prevPageAnnotations)); break; @@ -456,9 +452,8 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On } } - this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(annotations, primary, secondary); - this.#displayedAnnotationsUpdated.set(true); - const pagesThatDisplayAnnotations = [...this.displayedAnnotations.keys()]; + this.displayedAnnotations.set(this._annotationProcessingService.filterAndGroupAnnotations(annotations, primary, secondary)); + const pagesThatDisplayAnnotations = [...this.displayedAnnotations().keys()]; this.enabledFilters = this.filterService.enabledFlatFilters; if (this.enabledFilters.some(f => f.id === 'pages-without-annotations')) { if (this.enabledFilters.length === 1 && !onlyPageWithAnnotations) { @@ -467,7 +462,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On } else { this.#setDisplayedPages([]); } - this.displayedAnnotations.clear(); + this.displayedAnnotations().clear(); } else if (this.enabledFilters.length || onlyPageWithAnnotations || componentReferenceIds) { this.#setDisplayedPages(pagesThatDisplayAnnotations); } else { @@ -475,7 +470,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On } this.displayedPages.sort((a, b) => a - b); - return this.displayedAnnotations; + return this.displayedAnnotations(); } #selectFirstAnnotationOnCurrentPageIfNecessary() { @@ -527,7 +522,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On const currentPage = untracked(this.pdf.currentPage); let idx = 0; for (const page of this.displayedPages) { - if (page > currentPage && this.displayedAnnotations.get(page)) { + if (page > currentPage && this.displayedAnnotations().get(page)) { break; } ++idx; @@ -540,7 +535,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On let idx = this.displayedPages.length - 1; const reverseDisplayedPages = [...this.displayedPages].reverse(); for (const page of reverseDisplayedPages) { - if (page < currentPage && this.displayedAnnotations.get(page)) { + if (page < currentPage && this.displayedAnnotations().get(page)) { break; } --idx; From 19ecd12c5fd78720f2ec22cf6fe52dbbb27e6fc4 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Tue, 12 Nov 2024 12:23:18 +0200 Subject: [PATCH 13/24] RED-3800: fixed generic errors. --- .../services/global-error-handler.service.ts | 4 ++-- .../generic-error-translations.ts | 9 ++++++++ apps/red-ui/src/assets/i18n/redact/de.json | 23 ++++++++++++------- apps/red-ui/src/assets/i18n/redact/en.json | 21 +++++++++++------ apps/red-ui/src/assets/i18n/scm/de.json | 21 +++++++++++------ apps/red-ui/src/assets/i18n/scm/en.json | 17 ++++++++++---- 6 files changed, 66 insertions(+), 29 deletions(-) create mode 100644 apps/red-ui/src/app/translations/generic-error-translations.ts diff --git a/apps/red-ui/src/app/services/global-error-handler.service.ts b/apps/red-ui/src/app/services/global-error-handler.service.ts index 9316c8502..dc55a1302 100644 --- a/apps/red-ui/src/app/services/global-error-handler.service.ts +++ b/apps/red-ui/src/app/services/global-error-handler.service.ts @@ -1,7 +1,7 @@ import { ErrorHandler, Inject, Injectable, Injector } from '@angular/core'; import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; import { Toaster } from '@iqser/common-ui'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { genericErrorTranslations } from '@translations/generic-error-translations'; @Injectable() export class GlobalErrorHandler extends ErrorHandler { @@ -18,7 +18,7 @@ export class GlobalErrorHandler extends ErrorHandler { if (err.error.message) { toaster.rawError(err.error.message); } else if ([400, 403, 404, 409, 500].includes(err.status)) { - toaster.rawError(_(`generic-errors.${err.status}`)); + toaster.error(genericErrorTranslations[err.status]); } } } diff --git a/apps/red-ui/src/app/translations/generic-error-translations.ts b/apps/red-ui/src/app/translations/generic-error-translations.ts new file mode 100644 index 000000000..efa66d811 --- /dev/null +++ b/apps/red-ui/src/app/translations/generic-error-translations.ts @@ -0,0 +1,9 @@ +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; + +export const genericErrorTranslations: { [key: number]: string } = { + 400: _('generic-errors.400'), + 403: _('generic-errors.403'), + 404: _('generic-errors.404'), + 409: _('generic-errors.409'), + 500: _('generic-errors.500'), +}; diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index c394633f6..b169ead13 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -225,7 +225,7 @@ "actions": { "cancel": "Abbrechen", "save": "Speichern", - "save-and-remember": "" + "save-and-remember": "Save and remember my choice" }, "content": { "comment": "Kommentar", @@ -1720,6 +1720,13 @@ }, "title": "SMTP-Konto konfigurieren" }, + "generic-errors": { + "400": "Die gesendete Anfrage ist ungültig.", + "403": "Der Zugriff auf die angeforderte Ressource ist nicht erlaubt.", + "404": "Die angeforderte Ressource konnte nicht gefunden werden.", + "409": "Die Anfrage ist mit dem aktuellen Zustand nicht vereinbar.", + "500": "Der Server ist auf eine unerwartete Bedingung gestoßen, die ihn daran hindert, die Anfrage zu erfüllen." + }, "help-button": { "disable": "Hilfemodus deaktivieren", "enable": "Hilfemodus aktivieren" @@ -2108,7 +2115,7 @@ "actions": { "cancel": "Abbrechen", "save": "Speichern", - "save-and-remember": "" + "save-and-remember": "Save and remember my choice" }, "content": { "comment": "Kommentar", @@ -2205,7 +2212,7 @@ "actions": { "cancel": "Abbrechen", "save": "Speichern", - "save-and-remember": "" + "save-and-remember": "Save and remember my choice" }, "content": { "comment": "Kommentar", @@ -2225,7 +2232,7 @@ "label": "In diesem Kontext aus Dossier entfernen" }, "in-document": { - "description": "{isImage, select, image{Das Bild} other{Der Begriff}} wird auf keiner Seite dieses Dokuments automatisch geschwärzt.", + "description": "{isImage, select, image{{number, plural, one{Das Bild} other{Die Bilder}}} other{{number, plural, one{Der Begriff} other{Die Begriffe}}}} werden auf keiner Seite dieses Dokuments automatisch geschwärzt.\n", "label": "Aus Dokument entfernen" }, "in-dossier": { @@ -2362,13 +2369,13 @@ }, "roles": { "inactive": "Inaktiv", - "manager-admin": "Manager & {length, plural, one{Admin} other{Admins}}", + "manager-admin": "{count, plural, one{Manager & Admin} other{Manager & Admins}}", "no-role": "Keine Rolle definiert", - "red-admin": "{length, plural, one{Anwendungsadmin} other{Anwendungsadmins}}", + "red-admin": "Anwendungsadmin", "red-manager": "Manager", "red-user": "Benutzer", - "red-user-admin": "{length, plural, one{Benutzeradmin} other{Benutzeradmins}}", - "regular": "{length, plural, one{regulärer} other{reguläre}} Benutzer" + "red-user-admin": "{count, plural, one{Benutzeradmin} other{Benutzeradmins}}", + "regular": "{number, plural, one{{regulärer Benutzer}} other{reguläre Benutzer}}" }, "search-screen": { "cols": { diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index a64aae978..e47568ae3 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -1720,6 +1720,13 @@ }, "title": "Configure SMTP account" }, + "generic-errors": { + "400": "The sent request is invalid.", + "403": "Access to the requested resource is not allowed.", + "404": "The requested resource could not be found.", + "409": "The request is incompatible with the current state.", + "500": "The server encountered an unexpected condition that prevented it from fulfilling the request." + }, "help-button": { "disable": "Disable help mode", "enable": "Enable help mode" @@ -2225,7 +2232,7 @@ "label": "Remove from dossier in this context" }, "in-document": { - "description": "Do not auto-redact the selected {isImage, select, image{image} other{term}} on any page of this document.", + "description": "Do not auto-redact the selected {isImage, select, image{{number, plural, one{image} other{images}}} other{{number, plural, one{term} other{terms}}}} on any page of this document.\n", "label": "Remove from document" }, "in-dossier": { @@ -2362,13 +2369,13 @@ }, "roles": { "inactive": "Inactive", - "manager-admin": "Manager & {length, plural, one{Admin} other{Admins}}", + "manager-admin": "{count, plural, one{Manager & admin} other{Manager & admin}}", "no-role": "No role defined", - "red-admin": "Application {length, plural, one{admin} other{admins}}", - "red-manager": "{length, plural, one{Manager} other{Managers}}", - "red-user": "{length, plural, one{User} other{Users}}", - "red-user-admin": "{length, plural, one{User} other{Users}} admin", - "regular": "Regular" + "red-admin": "Application admin", + "red-manager": "{count, plural, one{Manager} other{Managers}}", + "red-user": "User", + "red-user-admin": "{count, plural, one{User admin} other{User admin}}", + "regular": "{count, plural, one{regular} other{regular}}" }, "search-screen": { "cols": { diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index 62d882d6e..8816f8ac5 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -225,7 +225,7 @@ "actions": { "cancel": "Abbrechen", "save": "Speichern", - "save-and-remember": "" + "save-and-remember": "Save and remember my choice" }, "content": { "comment": "Kommentar", @@ -1720,6 +1720,13 @@ }, "title": "SMTP-Konto konfigurieren" }, + "generic-errors": { + "400": "", + "403": "", + "404": "", + "409": "", + "500": "" + }, "help-button": { "disable": "Hilfemodus deaktivieren", "enable": "Hilfemodus aktivieren" @@ -2108,7 +2115,7 @@ "actions": { "cancel": "Abbrechen", "save": "Speichern", - "save-and-remember": "" + "save-and-remember": "Save and remember my choice" }, "content": { "comment": "Kommentar", @@ -2205,7 +2212,7 @@ "actions": { "cancel": "Abbrechen", "save": "Speichern", - "save-and-remember": "" + "save-and-remember": "Save and remember my choice" }, "content": { "comment": "Kommentar", @@ -2362,13 +2369,13 @@ }, "roles": { "inactive": "Inaktiv", - "manager-admin": "Manager & {length, plural, one{Admin} other{Admins}}", + "manager-admin": "Manager & Admin", "no-role": "Keine Rolle definiert", - "red-admin": "{length, plural, one{Anwendungsadmin} other{Anwendungsadmins}}", + "red-admin": "Anwendungsadmin", "red-manager": "Manager", "red-user": "Benutzer", - "red-user-admin": "{length, plural, one{Benutzeradmin} other{Benutzeradmins}}", - "regular": "{length, plural, one{regulärer} other{reguläre}} Benutzer" + "red-user-admin": "Benutzeradmin", + "regular": "regulärer Benutzer" }, "search-screen": { "cols": { diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index 61f0d92a6..b79b765ba 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -1720,6 +1720,13 @@ }, "title": "Configure SMTP Account" }, + "generic-errors": { + "400": "The sent request is not valid.", + "403": "Access to the requested resource is not allowed.", + "404": "The requested resource could not be found.", + "409": "The request is incompatible with the current state.", + "500": "The server encountered an unexpected condition that prevented it from fulfilling the request." + }, "help-button": { "disable": "Disable help mode", "enable": "Enable help mode" @@ -2362,12 +2369,12 @@ }, "roles": { "inactive": "Inactive", - "manager-admin": "Manager & {length, plural, one{Admin} other{Admins}}", + "manager-admin": "Manager & admin", "no-role": "No role defined", - "red-admin": "Application {length, plural, one{admin} other{admins}}", - "red-manager": "{length, plural, one{Manager} other{Managers}}", - "red-user": "{length, plural, one{User} other{Users}}", - "red-user-admin": "{length, plural, one{User} other{Users}} admin", + "red-admin": "Application admin", + "red-manager": "Manager", + "red-user": "User", + "red-user-admin": "Users admin", "regular": "Regular" }, "search-screen": { From 0d4546bb9e25935388f1528c8d1b617666af4d98 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Tue, 12 Nov 2024 12:53:26 +0200 Subject: [PATCH 14/24] RED-10363: fixed distinctUntilChanged comparator. --- apps/red-ui/src/app/modules/admin/services/rules.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/red-ui/src/app/modules/admin/services/rules.service.ts b/apps/red-ui/src/app/modules/admin/services/rules.service.ts index 11efa8c81..09c20cc03 100644 --- a/apps/red-ui/src/app/modules/admin/services/rules.service.ts +++ b/apps/red-ui/src/app/modules/admin/services/rules.service.ts @@ -14,9 +14,9 @@ export class RulesService extends EntitiesService { map(rules => rules[0]), distinctUntilChanged( (prev, curr) => - prev.rules !== curr.rules || - prev.timeoutDetected !== curr.timeoutDetected || - prev.dossierTemplateId !== curr.dossierTemplateId, + prev.rules === curr.rules && + prev.timeoutDetected === curr.timeoutDetected && + prev.dossierTemplateId === curr.dossierTemplateId, ), ), ); From 725c99211bf224bbd7b991b0e0f1941a505204be Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Tue, 12 Nov 2024 13:51:35 +0200 Subject: [PATCH 15/24] RED-10440: map redacted image hints to redaction. --- libs/red-domain/src/lib/files/super-types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/red-domain/src/lib/files/super-types.ts b/libs/red-domain/src/lib/files/super-types.ts index 93ab235a0..9fae4894a 100644 --- a/libs/red-domain/src/lib/files/super-types.ts +++ b/libs/red-domain/src/lib/files/super-types.ts @@ -27,8 +27,8 @@ function resolveRedactionType(entry: IEntityLogEntry, hint = false) { const redaction = hint ? SuperTypes.Hint : SuperTypes.Redaction; const manualRedaction = hint ? SuperTypes.ManualHint : SuperTypes.ManualRedaction; - - if (!entry.engines.length) { + const isRedactedImageHint = entry.state === EntryStates.APPLIED && entry.entryType === EntityTypes.IMAGE_HINT; + if (!entry.engines.length && !isRedactedImageHint) { return entry.state === EntryStates.PENDING && entry.dictionaryEntry ? redaction : manualRedaction; } return redaction; From 43f4cc6c9e3450b5094a1ee39447302c73ce6616 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Tue, 12 Nov 2024 17:25:59 +0200 Subject: [PATCH 16/24] RED-10373 - remove redaction dialog updates to be able to create multiple bulk requests for rectangle annotations --- .../edit-redaction-dialog.component.ts | 6 +--- .../remove-redaction-dialog.component.ts | 11 +++--- .../services/annotation-actions.service.ts | 36 +++++++++++-------- .../file-preview/utils/dialog-options.ts | 8 ++++- .../file-preview/utils/dialog-types.ts | 2 +- .../enhance-manual-redaction-request.utils.ts | 2 +- apps/red-ui/src/assets/i18n/redact/en.json | 4 +-- 7 files changed, 41 insertions(+), 28 deletions(-) diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/edit-redaction-dialog/edit-redaction-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/edit-redaction-dialog/edit-redaction-dialog.component.ts index 645fa6850..b90c2de37 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/edit-redaction-dialog/edit-redaction-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/edit-redaction-dialog/edit-redaction-dialog.component.ts @@ -214,11 +214,7 @@ export class EditRedactionDialogComponent const value = this.form.value; const initialReason: LegalBasisOption = this.initialFormValue.reason; const initialLegalBasis = initialReason?.legalBasis ?? ''; - const pageNumbers = parseSelectedPageNumbers( - this.form.get('option').value?.additionalInput?.value, - this.data.file, - this.data.annotations[0], - ); + const pageNumbers = parseSelectedPageNumbers(this.form.get('option').value?.additionalInput?.value, this.data.file); const position = parseRectanglePosition(this.annotations[0]); this.close({ diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts index 648d4d06e..87a11092e 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts @@ -113,7 +113,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent< : this.optionByType[this.annotationsType].main; readonly extraOptionPreference = stringToBoolean(this.optionByType[this.annotationsType].extra); readonly options: DetailsRadioOption[] = this.allRectangles - ? getRectangleRedactOptions('remove') + ? getRectangleRedactOptions('remove', this.data) : getRemoveRedactionOptions( this.data, this.isSystemDefault || this.isExtraOptionSystemDefault ? this.#applyToAllDossiers : this.extraOptionPreference, @@ -197,14 +197,17 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent< save(): void { const optionValue = this.form.controls.option?.value?.value; const optionInputValue = this.form.controls.option?.value?.additionalInput?.value; - const pageNumbers = parseSelectedPageNumbers(optionInputValue, this.data.file, this.data.redactions[0]); - const position = parseRectanglePosition(this.data.redactions[0]); + const pageNumbers = parseSelectedPageNumbers(optionInputValue, this.data.file); + const positions = []; + for (const redaction of this.data.redactions) { + positions.push(parseRectanglePosition(redaction)); + } this.close({ ...this.form.getRawValue(), bulkLocal: optionValue === ResizeOptions.IN_DOCUMENT || optionValue === RectangleRedactOptions.MULTIPLE_PAGES, pageNumbers, - position, + positions, }); } diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts index fdddc5c1c..8df5c15c7 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts @@ -479,6 +479,7 @@ export class AnnotationActionsService { const isHint = redactions.every(r => r.isHint); const { dossierId, fileId } = this._state; const maximumNumberEntries = 100; + const bulkLocal = dialogResult.bulkLocal || !!dialogResult.pageNumbers.length; if (removeFromDictionary && (body as List).length > maximumNumberEntries) { const requests = body as List; const splitNumber = Math.floor(requests.length / maximumNumberEntries); @@ -493,15 +494,28 @@ export class AnnotationActionsService { const promises = []; for (const split of splitRequests) { + promises.push( + firstValueFrom( + this._manualRedactionService.removeRedaction(split, dossierId, fileId, removeFromDictionary, isHint, bulkLocal), + ), + ); + } + Promise.all(promises).finally(() => this._fileDataService.annotationsChanged()); + return; + } + + if (redactions[0].AREA && bulkLocal) { + const promises = []; + for (const request of body) { promises.push( firstValueFrom( this._manualRedactionService.removeRedaction( - split, + request as IBulkLocalRemoveRequest, dossierId, fileId, removeFromDictionary, isHint, - dialogResult.bulkLocal, + bulkLocal, ), ), ); @@ -509,15 +523,9 @@ export class AnnotationActionsService { Promise.all(promises).finally(() => this._fileDataService.annotationsChanged()); return; } + this.#processObsAndEmit( - this._manualRedactionService.removeRedaction( - body, - dossierId, - fileId, - removeFromDictionary, - isHint, - dialogResult.bulkLocal || !!dialogResult.pageNumbers.length, - ), + this._manualRedactionService.removeRedaction(body, dossierId, fileId, removeFromDictionary, isHint, bulkLocal), ).then(); } @@ -577,16 +585,16 @@ export class AnnotationActionsService { #getRemoveRedactionBody( redactions: AnnotationWrapper[], dialogResult: RemoveRedactionResult, - ): List | IBulkLocalRemoveRequest { + ): List { if (dialogResult.bulkLocal || !!dialogResult.pageNumbers.length) { const redaction = redactions[0]; - return { + return dialogResult.positions.map(position => ({ value: redaction.value, rectangle: redaction.AREA, pageNumbers: dialogResult.pageNumbers, - position: dialogResult.position, + position: position, comment: dialogResult.comment, - }; + })); } return redactions.map(redaction => ({ diff --git a/apps/red-ui/src/app/modules/file-preview/utils/dialog-options.ts b/apps/red-ui/src/app/modules/file-preview/utils/dialog-options.ts index a512cf90a..bd35ccf4c 100644 --- a/apps/red-ui/src/app/modules/file-preview/utils/dialog-options.ts +++ b/apps/red-ui/src/app/modules/file-preview/utils/dialog-options.ts @@ -102,19 +102,25 @@ export const getRedactOrHintOptions = ( return options; }; -export const getRectangleRedactOptions = (action: 'add' | 'edit' | 'remove' = 'add'): DetailsRadioOption[] => { +export const getRectangleRedactOptions = ( + action: 'add' | 'edit' | 'remove' = 'add', + data?: RemoveRedactionData, +): DetailsRadioOption[] => { const translations = action === 'add' ? rectangleRedactTranslations : action === 'edit' ? editRectangleTranslations : removeRectangleTranslations; + const redactions = data?.redactions ?? []; return [ { label: translations.onlyThisPage.label, description: translations.onlyThisPage.description, + descriptionParams: { length: redactions.length }, icon: PIN_ICON, value: RectangleRedactOptions.ONLY_THIS_PAGE, }, { label: translations.multiplePages.label, description: translations.multiplePages.description, + descriptionParams: { length: redactions.length }, icon: DOCUMENT_ICON, value: RectangleRedactOptions.MULTIPLE_PAGES, additionalInput: { diff --git a/apps/red-ui/src/app/modules/file-preview/utils/dialog-types.ts b/apps/red-ui/src/app/modules/file-preview/utils/dialog-types.ts index c1c5ac931..229b0639f 100644 --- a/apps/red-ui/src/app/modules/file-preview/utils/dialog-types.ts +++ b/apps/red-ui/src/app/modules/file-preview/utils/dialog-types.ts @@ -168,7 +168,7 @@ export interface RemoveRedactionResult { applyToAllDossiers?: boolean; bulkLocal?: boolean; pageNumbers?: number[]; - position: IEntityLogEntryPosition; + positions: IEntityLogEntryPosition[]; } export type RemoveAnnotationResult = RemoveRedactionResult; diff --git a/apps/red-ui/src/app/modules/file-preview/utils/enhance-manual-redaction-request.utils.ts b/apps/red-ui/src/app/modules/file-preview/utils/enhance-manual-redaction-request.utils.ts index 17e16a046..4930abc31 100644 --- a/apps/red-ui/src/app/modules/file-preview/utils/enhance-manual-redaction-request.utils.ts +++ b/apps/red-ui/src/app/modules/file-preview/utils/enhance-manual-redaction-request.utils.ts @@ -49,7 +49,7 @@ export const enhanceManualRedactionRequest = (addRedactionRequest: IAddRedaction addRedactionRequest.addToAllDossiers = data.isApprover && data.dictionaryRequest && data.applyToAllDossiers; }; -export const parseSelectedPageNumbers = (inputValue: string, file: File, annotation: AnnotationWrapper) => { +export const parseSelectedPageNumbers = (inputValue: string, file: File) => { if (!inputValue) { return []; } diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index a64aae978..46b0ea6d6 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -2186,14 +2186,14 @@ "content": { "options": { "multiple-pages": { - "description": "Remove redaction on a range of pages", + "description": "Remove {length, plural, one{redaction} other {redactions}} on a range of pages", "extraOptionDescription": "Minus(-) for range and comma(,) for enumeration", "extraOptionLabel": "Pages", "extraOptionPlaceholder": "e.g. 1-20,22,32", "label": "Remove on multiple pages" }, "only-this-page": { - "description": "Remove redaction only at this position in this document", + "description": "Remove {length, plural, one{redaction} other {redactions}} only at this position in this document", "label": "Remove only on this page" } } From 6ac6f0a441cc9558215fcede3f7b59b5f4112004 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Tue, 12 Nov 2024 22:10:05 +0200 Subject: [PATCH 17/24] RED-10332 - fixed dictionary dropdown that did not update when another dossier template was selected --- .../dictionary-manager.component.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts index de73ae43b..b041d7a02 100644 --- a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts @@ -3,16 +3,12 @@ import { ChangeDetectorRef, Component, effect, - EventEmitter, input, - Input, model, - OnChanges, OnInit, output, - Output, signal, - SimpleChanges, + untracked, ViewChild, } from '@angular/core'; import { CircleButtonComponent, IconButtonComponent, IconButtonTypes, LoadingService } from '@iqser/common-ui'; @@ -203,7 +199,7 @@ export class DictionaryManagerComponent implements OnInit { get #templatesWithCurrentEntityType() { return this._dossierTemplatesService.all.filter(t => - this._dictionaryService.hasType(t.dossierTemplateId, this.selectedDictionaryType()), + this._dictionaryService.hasType(t.dossierTemplateId, untracked(this.selectedDictionaryType)), ); } @@ -236,53 +232,57 @@ export class DictionaryManagerComponent implements OnInit { } async #onDossierChanged(dossierTemplateId: string, dossierId?: string) { + const selectedDictionaryByType = untracked(this.selectedDictionaryType); + const activeEntryType = untracked(this.activeEntryType); let dictionary: IDictionary; if (dossierId === 'template') { - dictionary = await this._dictionaryService.getForType(dossierTemplateId, this.selectedDictionaryType()); + dictionary = await this._dictionaryService.getForType(dossierTemplateId, selectedDictionaryByType); } else { if (dossierId) { dictionary = ( await firstValueFrom( - this._dictionaryService.loadDictionaryEntriesByType([this.selectedDictionaryType()], dossierTemplateId, dossierId), + this._dictionaryService.loadDictionaryEntriesByType([selectedDictionaryByType], dossierTemplateId, dossierId), ).catch(() => { return [{ entries: [COMPARE_ENTRIES_ERROR], type: '' }]; }) )[0]; } else { - dictionary = this.selectedDictionaryType() - ? await this._dictionaryService.getForType(this.currentDossierTemplateId(), this.selectedDictionaryType()) + dictionary = selectedDictionaryByType + ? await this._dictionaryService.getForType(this.currentDossierTemplateId(), selectedDictionaryByType) : { entries: [COMPARE_ENTRIES_ERROR], type: '' }; } } const activeEntries = - this.activeEntryType() === DictionaryEntryTypes.ENTRY || this.hint() + activeEntryType === DictionaryEntryTypes.ENTRY || this.hint() ? [...dictionary.entries] - : this.activeEntryType() === DictionaryEntryTypes.FALSE_POSITIVE + : activeEntryType === DictionaryEntryTypes.FALSE_POSITIVE ? [...dictionary.falsePositiveEntries] : [...dictionary.falseRecommendationEntries]; return activeEntries.join('\n'); } #updateDropdownsOptions(updateSelectedDossierTemplate = true) { + const currentDossierTemplateId = untracked(this.currentDossierTemplateId); + const currentDossierId = untracked(this.currentDossierId); if (updateSelectedDossierTemplate) { - this.currentDossierTemplateId.set(this.initialDossierTemplateId ?? this.currentDossierTemplateId()); + this.currentDossierTemplateId.set(this.initialDossierTemplateId ?? currentDossierTemplateId); this.dossierTemplates = this.currentDossierTemplateId ? this.#templatesWithCurrentEntityType : this._dossierTemplatesService.all; if (!this.currentDossierTemplateId) { this.dossierTemplates = [this.selectDossierTemplate, ...this.dossierTemplates]; } - this.selectedDossierTemplate = this.dossierTemplates.find(t => t.id === this.currentDossierTemplateId()); + this.selectedDossierTemplate = this.dossierTemplates.find(t => t.id === currentDossierTemplateId); } this.dossiers = this._activeDossiersService.all.filter( - d => d.dossierTemplateId === this.currentDossierTemplateId() && d.id !== this.currentDossierId(), + d => d.dossierTemplateId === currentDossierTemplateId && d.id !== currentDossierId, ); const templateDictionary = { id: 'template', dossierId: 'template', dossierName: 'Template Dictionary', - dossierTemplateId: this.currentDossierTemplateId(), + dossierTemplateId: currentDossierTemplateId, } as Dossier; this.dossiers.push(templateDictionary); } From 260a53aaf1b478be2e4e5607e99d65403400306a Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Wed, 13 Nov 2024 10:29:34 +0200 Subject: [PATCH 18/24] RED-10405: added labels and filtering for separate file pending types. --- .../dossier-details.component.html | 2 +- .../dossier-details.component.ts | 28 +++++++++++++++++++ .../table-item/table-item.component.html | 7 ++++- .../dossier-overview/config.service.ts | 11 ++++++++ apps/red-ui/src/assets/i18n/redact/de.json | 11 +++++--- apps/red-ui/src/assets/i18n/redact/en.json | 5 +++- apps/red-ui/src/assets/i18n/scm/de.json | 9 ++++-- apps/red-ui/src/assets/i18n/scm/en.json | 5 +++- .../lib/dossier-stats/dossier-stats.model.ts | 20 ++----------- .../red-domain/src/lib/dossier-stats/types.ts | 19 ++++++++++++- libs/red-domain/src/lib/files/file.model.ts | 26 +++++++++++++---- libs/red-domain/src/lib/files/file.ts | 3 +- libs/red-domain/src/lib/files/types.ts | 15 ++++++++++ 13 files changed, 125 insertions(+), 36 deletions(-) diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.html b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.html index 09ba451ce..9f2a66a30 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.html +++ b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.html @@ -61,7 +61,7 @@ *ngFor="let config of statusConfig" [attr.help-mode-key]="'dashboard_in_dossier'" [config]="config" - filterKey="processingTypeFilters" + [filterKey]="PendingTypes[config.id] ? 'pendingTypeFilters' : 'processingTypeFilters'" > diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.ts b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.ts index 75c7cf9d5..860be6f33 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.ts @@ -21,7 +21,9 @@ import { DossierAttributeWithValue, DossierStats, File, + FileErrorCodes, IDossierRequest, + PendingTypes, ProcessingTypes, StatusSorter, User, @@ -74,6 +76,7 @@ export class DossierDetailsComponent extends ContextComponent file.errorCode === FileErrorCodes.LOCKED_RULES).length; + const numberOfTimeoutFiles = files.filter(file => file.errorCode === FileErrorCodes.RULES_EXECUTION_TIMEOUT).length; + const numberOfUnknownErrorFiles = stats.processingStats.pending - numberOfRulesLockedFiles - numberOfTimeoutFiles; return [ { id: ProcessingTypes.pending, @@ -161,6 +168,27 @@ export class DossierDetailsComponent extends ContextComponent
-
+
diff --git a/apps/red-ui/src/app/modules/dossier-overview/config.service.ts b/apps/red-ui/src/app/modules/dossier-overview/config.service.ts index 7ffebb28c..b74de56a6 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/config.service.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/config.service.ts @@ -24,6 +24,7 @@ import { FileAttributeConfigType, FileAttributeConfigTypes, IFileAttributeConfig, + PendingType, ProcessingType, StatusSorter, User, @@ -184,6 +185,7 @@ export class ConfigService { const allDistinctPeople = new Set(); const allDistinctNeedsWork = new Set(); const allDistinctProcessingTypes = new Set(); + const allDistinctPendingTypes = new Set(); const dynamicFilters = new Map }>(); @@ -216,6 +218,7 @@ export class ConfigService { } allDistinctProcessingTypes.add(file.processingType); + allDistinctPendingTypes.add(file.pendingType); // extract values for dynamic filters fileAttributeConfigs.forEach(config => { @@ -317,6 +320,14 @@ export class ConfigService { hide: true, }); + const pendingTypesFilters = [...allDistinctPendingTypes].map(item => new NestedFilter({ id: item, label: item })); + filterGroups.push({ + slug: 'pendingTypeFilters', + filters: pendingTypesFilters, + checker: (file: File, filter: INestedFilter) => file.pendingType === filter.id, + hide: true, + }); + dynamicFilters.forEach((value: { filterValue: Set; type: FileAttributeConfigType }, filterKey: string) => { const id = filterKey.split(':')[0]; const key = filterKey.split(':')[1]; diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index b169ead13..6e622586d 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -987,7 +987,7 @@ "download-file-disabled": "Download: Sie müssen Genehmiger im Dossier sein und die initiale Verarbeitung {count, plural, one{der Datei} other{der Dateien}} muss abgeschlossen sein.", "file-listing": { "file-entry": { - "file-error": "Reanalyse erforderlich", + "file-error": "Reanalyse erforderlich ({errorCode, select, RULES_EXECUTION_TIMEOUT{Zeitlimit für Regeln} LOCKED_RULES{Regeln gesperrt} other{unbekannt}})", "file-pending": "Ausstehend ..." } }, @@ -1463,7 +1463,7 @@ "save": { "error": "Erstellung der Datei-Attribute fehlgeschlagen.", "label": "Attribute speichern", - "success": "{count} Datei-{count, plural, one{Attribut} other{Attribute}} erfolgreich erstellt!" + "success": "{count} Datei-{count, plural, one{Attribut} other{Attribute}} erfolgreich erstellt." }, "search": { "placeholder": "Nach Spaltennamen suchen..." @@ -2101,6 +2101,9 @@ "processing-status": { "ocr": "OCR", "pending": "Ausstehend", + "pending-locked-rules": "Ausstehend (Regeln gesperrt)", + "pending-timeout": "Ausstehend (Zeitlimit für Regeln)", + "pending-unknown": "Ausstehend (unbekannt)", "processed": "Verarbeitet", "processing": "Verarbeitung läuft" }, @@ -2371,8 +2374,8 @@ "inactive": "Inaktiv", "manager-admin": "{count, plural, one{Manager & Admin} other{Manager & Admins}}", "no-role": "Keine Rolle definiert", - "red-admin": "Anwendungsadmin", - "red-manager": "Manager", + "red-admin": "{count, plural, one{Anwendungsadmin} other{Anwendungsadmins}}", + "red-manager": "{count, plural, one{Manager} other{Manager}}", "red-user": "Benutzer", "red-user-admin": "{count, plural, one{Benutzeradmin} other{Benutzeradmins}}", "regular": "{number, plural, one{{regulärer Benutzer}} other{reguläre Benutzer}}" diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index 98ee2dfb8..39ae0b693 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -987,7 +987,7 @@ "download-file-disabled": "To download, ensure you are an approver in the dossier, and the {count, plural, one{file has undergone} other{files have undergone}} initial processing.", "file-listing": { "file-entry": { - "file-error": "Re-processing required", + "file-error": "Re-processing required ({errorCode, select, RULES_EXECUTION_TIMEOUT{Rules timeout} LOCKED_RULES{Rules locked} other{unknown}})", "file-pending": "Pending..." } }, @@ -2101,6 +2101,9 @@ "processing-status": { "ocr": "OCR", "pending": "Pending", + "pending-locked-rules": "Pending (Rules locked)", + "pending-timeout": "Pending (Rules timeout)", + "pending-unknown": "Pending (unknown)", "processed": "Processed", "processing": "Processing" }, diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index 8816f8ac5..18c81a721 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -987,7 +987,7 @@ "download-file-disabled": "Download: Sie müssen Genehmiger im Dossier sein und die initiale Verarbeitung {count, plural, one{der Datei} other{der Dateien}} muss abgeschlossen sein.", "file-listing": { "file-entry": { - "file-error": "Reanalyse erforderlich", + "file-error": "Reanalyse erforderlich ({errorCode, select, RULES_EXECUTION_TIMEOUT{Zeitlimit für Regeln} LOCKED_RULES{Regeln gesperrt} other{unbekannt}})", "file-pending": "Ausstehend ..." } }, @@ -1385,7 +1385,7 @@ }, "file": { "action": "Zurück zum Dossier", - "label": "Diese Datei wurde gelöscht!" + "label": "Diese Datei wurde gelöscht." } }, "file-preview": { @@ -2101,6 +2101,9 @@ "processing-status": { "ocr": "OCR", "pending": "Ausstehend", + "pending-locked-rules": "Ausstehend (Regeln gesperrt)", + "pending-timeout": "Ausstehend (Zeitlimit für Regeln)", + "pending-unknown": "Ausstehend (unbekannt)", "processed": "Verarbeitet", "processing": "Verarbeitung läuft" }, @@ -2294,7 +2297,7 @@ } } }, - "invalid-upload": "Ungültiges Upload-Format ausgewählt! Unterstützt werden Dokumente im .xlsx- und im .docx-Format", + "invalid-upload": "Ungültiges Upload-Format ausgewählt. Unterstützte Formate: .xlsx- und .docx", "multi-file-report": "(Mehrere Dateien)", "report-documents": "Berichtsvorlagen", "setup": "Dieser Platzhalter wird durch die Nummer der Seite ersetzt, auf der sich die Schwärzung befindet.", diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index b79b765ba..19b317511 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -987,7 +987,7 @@ "download-file-disabled": "To download, ensure you are an approver in the dossier, and the {count, plural, one{file has undergone} other{files have undergone}} initial processing.", "file-listing": { "file-entry": { - "file-error": "Re-processing required", + "file-error": "Re-processing required ({errorCode, select, RULES_EXECUTION_TIMEOUT{Rules timeout} LOCKED_RULES{Rules locked} other{unknown}})", "file-pending": "Pending..." } }, @@ -2101,6 +2101,9 @@ "processing-status": { "ocr": "OCR", "pending": "Pending", + "pending-locked-rules": "Pending (Rules locked)", + "pending-timeout": "Pending (Rules timeout)", + "pending-unknown": "Pending (unknown)", "processed": "Processed", "processing": "Processing" }, diff --git a/libs/red-domain/src/lib/dossier-stats/dossier-stats.model.ts b/libs/red-domain/src/lib/dossier-stats/dossier-stats.model.ts index 7a9a2e4ea..61dd2e548 100644 --- a/libs/red-domain/src/lib/dossier-stats/dossier-stats.model.ts +++ b/libs/red-domain/src/lib/dossier-stats/dossier-stats.model.ts @@ -1,22 +1,6 @@ -import { - isProcessingStatuses, - OCR_STATES, - PENDING_STATES, - PROCESSED_STATES, - PROCESSING_STATES, - ProcessingFileStatus, -} from '../files/types'; +import { isProcessingStatuses, OCR_STATES, PENDING_STATES, PROCESSED_STATES, PROCESSING_STATES, ProcessingFileStatus } from '../files'; import { IDossierStats } from './dossier-stats'; -import { FileCountPerProcessingStatus, FileCountPerWorkflowStatus } from './types'; - -export const ProcessingTypes = { - pending: 'pending', - ocr: 'ocr', - processing: 'processing', - processed: 'processed', -} as const; - -export type ProcessingType = keyof typeof ProcessingTypes; +import { FileCountPerProcessingStatus, FileCountPerWorkflowStatus, ProcessingType } from './types'; export type ProcessingStats = Record; diff --git a/libs/red-domain/src/lib/dossier-stats/types.ts b/libs/red-domain/src/lib/dossier-stats/types.ts index b57ca4ecd..04f2e4684 100644 --- a/libs/red-domain/src/lib/dossier-stats/types.ts +++ b/libs/red-domain/src/lib/dossier-stats/types.ts @@ -1,4 +1,21 @@ -import { ProcessingFileStatus, WorkflowFileStatus } from '../files/types'; +import { ProcessingFileStatus, WorkflowFileStatus } from '../files'; export type FileCountPerWorkflowStatus = { [key in WorkflowFileStatus]?: number }; export type FileCountPerProcessingStatus = { [key in ProcessingFileStatus]?: number }; + +export const ProcessingTypes = { + pending: 'pending', + ocr: 'ocr', + processing: 'processing', + processed: 'processed', +} as const; + +export type ProcessingType = keyof typeof ProcessingTypes; + +export const PendingTypes = { + lockedRules: 'lockedRules', + timeout: 'timeout', + unknown: 'unknown', +} as const; + +export type PendingType = keyof typeof PendingTypes; diff --git a/libs/red-domain/src/lib/files/file.model.ts b/libs/red-domain/src/lib/files/file.model.ts index 5146f18fb..181b614d9 100644 --- a/libs/red-domain/src/lib/files/file.model.ts +++ b/libs/red-domain/src/lib/files/file.model.ts @@ -1,10 +1,12 @@ import { Entity } from '@iqser/common-ui'; -import { ProcessingType, ProcessingTypes } from '../dossier-stats/dossier-stats.model'; -import { ARCHIVE_ROUTE, DOSSIERS_ROUTE } from '../dossiers/constants'; -import { FileAttributes } from '../file-attributes/file-attributes'; -import { StatusSorter } from '../shared/sorters/status-sorter'; +import { PendingType, PendingTypes, ProcessingType, ProcessingTypes } from '../dossier-stats'; +import { ARCHIVE_ROUTE, DOSSIERS_ROUTE } from '../dossiers'; +import { FileAttributes } from '../file-attributes'; +import { StatusSorter } from '../shared'; import { IFile } from './file'; import { + FileErrorCode, + FileErrorCodes, isFullProcessingStatuses, isProcessingStatuses, OCR_STATES, @@ -81,6 +83,8 @@ export class File extends Entity implements IFile { readonly canBeOCRed: boolean; readonly processingType: ProcessingType; + readonly errorCode?: FileErrorCode; + readonly pendingType?: PendingType; constructor( file: IFile, @@ -120,7 +124,7 @@ export class File extends Entity implements IFile { this.workflowStatus = file.workflowStatus; this.isError = this.processingStatus === ProcessingFileStatuses.ERROR; this.isUnprocessed = this.processingStatus === ProcessingFileStatuses.UNPROCESSED; - this.numberOfPages = this.isError ? 0 : file.numberOfPages ?? 0; + this.numberOfPages = this.isError ? 0 : (file.numberOfPages ?? 0); this.rulesVersion = file.rulesVersion; this.uploader = file.uploader; this.excludedPages = file.excludedPages || []; @@ -157,6 +161,8 @@ export class File extends Entity implements IFile { file.fileAttributes && file.fileAttributes.attributeIdToValue ? file.fileAttributes : { attributeIdToValue: {} }; this.processingType = this.#processingType; + this.errorCode = file.fileErrorInfo?.errorCode; + this.pendingType = this.processingType === ProcessingTypes.pending ? this.#pendingType : undefined; } get deleted(): boolean { @@ -189,6 +195,16 @@ export class File extends Entity implements IFile { return ProcessingTypes.processed; } + get #pendingType(): PendingType { + if (this.errorCode === FileErrorCodes.LOCKED_RULES) { + return PendingTypes.lockedRules; + } + if (this.errorCode === FileErrorCodes.RULES_EXECUTION_TIMEOUT) { + return PendingTypes.timeout; + } + return PendingTypes.unknown; + } + isPageExcluded(page: number): boolean { return this.excludedPages.includes(page); } diff --git a/libs/red-domain/src/lib/files/file.ts b/libs/red-domain/src/lib/files/file.ts index 1a06145ea..1c0d3c9fa 100644 --- a/libs/red-domain/src/lib/files/file.ts +++ b/libs/red-domain/src/lib/files/file.ts @@ -2,7 +2,7 @@ * Object containing information on a specific file. */ import { FileAttributes } from '../file-attributes'; -import { ProcessingFileStatus, WorkflowFileStatus } from './types'; +import { FileErrorInfo, ProcessingFileStatus, WorkflowFileStatus } from './types'; export interface IFile { /** @@ -147,4 +147,5 @@ export interface IFile { readonly fileManipulationDate: string | null; readonly redactionModificationDate: string | null; readonly lastManualChangeDate?: string; + readonly fileErrorInfo?: FileErrorInfo; } diff --git a/libs/red-domain/src/lib/files/types.ts b/libs/red-domain/src/lib/files/types.ts index 2a3844918..b189186c6 100644 --- a/libs/red-domain/src/lib/files/types.ts +++ b/libs/red-domain/src/lib/files/types.ts @@ -96,3 +96,18 @@ export const PROCESSING_STATES: ProcessingFileStatus[] = [ export const PROCESSED_STATES: ProcessingFileStatus[] = [ProcessingFileStatuses.PROCESSED]; export const OCR_STATES: ProcessingFileStatus[] = [ProcessingFileStatuses.OCR_PROCESSING, ProcessingFileStatuses.OCR_PROCESSING_QUEUED]; + +export const FileErrorCodes = { + RULES_EXECUTION_TIMEOUT: 'RULES_EXECUTION_TIMEOUT', + LOCKED_RULES: 'LOCKED_RULES', +} as const; + +export type FileErrorCode = keyof typeof FileErrorCodes; + +export interface FileErrorInfo { + cause: string; + queue: string; + service: string; + timestamp: string; + errorCode?: FileErrorCode; +} From fb9242f6372e0947293409727268788ea75b4109 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Wed, 13 Nov 2024 11:28:41 +0200 Subject: [PATCH 19/24] RED-10453 - reload file or files (bulk case) after approval request --- .../modules/dossier-overview/services/bulk-actions.service.ts | 4 +++- .../components/file-actions/file-actions.component.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/red-ui/src/app/modules/dossier-overview/services/bulk-actions.service.ts b/apps/red-ui/src/app/modules/dossier-overview/services/bulk-actions.service.ts index d0517aef4..cc289af02 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/services/bulk-actions.service.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/services/bulk-actions.service.ts @@ -113,11 +113,13 @@ export class BulkActionsService { async approve(files: File[]): Promise { this._loadingService.start(); const approvalResponse: ApproveResponse[] = await this._filesService.getApproveWarnings(files); - this._loadingService.stop(); const hasWarnings = approvalResponse.some(response => response.hasWarnings); if (!hasWarnings) { + await firstValueFrom(this._filesService.loadAll(files[0].dossierId)); + this._loadingService.stop(); return; } + this._loadingService.stop(); const fileWarnings = approvalResponse .filter(response => response.hasWarnings) diff --git a/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts b/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts index 04375e949..8a43d507c 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts @@ -320,10 +320,12 @@ export class FileActionsComponent implements OnChanges { async setFileApproved() { this._loadingService.start(); const approvalResponse: ApproveResponse = (await this._filesService.getApproveWarnings([this.file]))[0]; - this._loadingService.stop(); if (!approvalResponse.hasWarnings) { + await this._filesService.reload(this.file.dossierId, this.file); + this._loadingService.stop(); return; } + this._loadingService.stop(); const data: IConfirmationDialogData = { title: _('confirmation-dialog.approve-file.title'), From 7e764cdf62148082ab3aefed179de10b1c882f83 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Wed, 13 Nov 2024 13:35:33 +0200 Subject: [PATCH 20/24] RED-10397: changed the active state for redacted btn; hid rectangle btn. --- .../services/pdf-proxy.service.ts | 2 ++ .../services/readable-redactions.service.ts | 21 ++++++++++++++----- .../pdf-viewer/services/tooltips.service.ts | 1 + .../services/viewer-header.service.ts | 20 +++++++++++------- .../icons/general/redaction-preview.svg | 2 +- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts b/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts index c0804337c..5bad3d053 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts @@ -117,8 +117,10 @@ export class PdfProxyService { effect(() => { if (this._viewModeService.isRedacted()) { + this._viewerHeaderService.disable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]); this._viewerHeaderService.enable([HeaderElements.TOGGLE_READABLE_REDACTIONS]); } else { + this._viewerHeaderService.enable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]); this._viewerHeaderService.disable([HeaderElements.TOGGLE_READABLE_REDACTIONS]); } }); diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/readable-redactions.service.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/readable-redactions.service.ts index 5fa0bcd4f..515c4bf26 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/readable-redactions.service.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/readable-redactions.service.ts @@ -13,8 +13,7 @@ import Annotation = Core.Annotations.Annotation; export class ReadableRedactionsService { readonly active$: Observable; readonly #convertPath = inject(UI_ROOT_PATH_FN); - readonly #enableIcon = this.#convertPath('/assets/icons/general/redaction-preview.svg'); - readonly #disableIcon = this.#convertPath('/assets/icons/general/redaction-final.svg'); + readonly #icon = this.#convertPath('/assets/icons/general/redaction-preview.svg'); readonly #active$ = new BehaviorSubject(true); constructor( @@ -29,8 +28,8 @@ export class ReadableRedactionsService { return this.#active$.getValue(); } - get toggleReadableRedactionsBtnIcon(): string { - return this.active ? this.#enableIcon : this.#disableIcon; + get icon() { + return this.#icon; } toggleReadableRedactions(): void { @@ -79,11 +78,23 @@ export class ReadableRedactionsService { } updateState() { + this.#updateIconState(); this._pdf.instance.UI.updateElement(HeaderElements.TOGGLE_READABLE_REDACTIONS, { title: this._translateService.instant(_('pdf-viewer.header.toggle-readable-redactions'), { active: this.active, }), - img: this.toggleReadableRedactionsBtnIcon, }); } + + #updateIconState() { + const element = this._pdf.instance.UI.iframeWindow.document.querySelector( + `[data-element=${HeaderElements.TOGGLE_READABLE_REDACTIONS}]`, + ); + if (!element) return; + if (!this.active) { + element.classList.add('active'); + } else { + element.classList.remove('active'); + } + } } diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/tooltips.service.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/tooltips.service.ts index e3a17eeb9..d7a3e00bc 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/tooltips.service.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/tooltips.service.ts @@ -24,6 +24,7 @@ export class TooltipsService { updateIconState() { const element = this._pdf.instance.UI.iframeWindow.document.querySelector(`[data-element=${HeaderElements.TOGGLE_TOOLTIPS}]`); + if (!element) return; if (this._userPreferenceService.getFilePreviewTooltipsPreference()) { element.classList.add('active'); } else { diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/viewer-header.service.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/viewer-header.service.ts index 7c7a57eb9..41694ec6f 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/viewer-header.service.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/viewer-header.service.ts @@ -121,7 +121,7 @@ export class ViewerHeaderService { type: 'actionButton', element: HeaderElements.TOGGLE_READABLE_REDACTIONS, dataElement: HeaderElements.TOGGLE_READABLE_REDACTIONS, - img: this._readableRedactionsService.toggleReadableRedactionsBtnIcon, + img: this._readableRedactionsService.icon, onClick: () => this._ngZone.run(() => this._readableRedactionsService.toggleReadableRedactions()), }; } @@ -289,20 +289,26 @@ export class ViewerHeaderService { ], ]; - header.get('selectToolButton').insertAfter(this.#buttons.get(HeaderElements.SHAPE_TOOL_GROUP_BUTTON)); + const shouldHideRectangleButton = this.#isEnabled(HeaderElements.SHAPE_TOOL_GROUP_BUTTON) ? 0 : 1; + if (!shouldHideRectangleButton) { + header.get('selectToolButton').insertAfter(this.#buttons.get(HeaderElements.SHAPE_TOOL_GROUP_BUTTON)); + } else if (header.getItems().includes(this.#buttons.get(HeaderElements.SHAPE_TOOL_GROUP_BUTTON))) { + header.get(HeaderElements.SHAPE_TOOL_GROUP_BUTTON).delete(); + } + groups.forEach(group => this.#pushGroup(enabledItems, group)); const loadAllAnnotationsButton = this.#buttons.get(HeaderElements.LOAD_ALL_ANNOTATIONS); - let startButtons = 11 - documineButtons; - let deleteCount = 15 - documineButtons; + let startButtons = 11 - documineButtons - shouldHideRectangleButton; + let deleteCount = 15 - documineButtons - shouldHideRectangleButton; if (this.#isEnabled(HeaderElements.LOAD_ALL_ANNOTATIONS)) { if (!header.getItems().includes(loadAllAnnotationsButton)) { header.get('leftPanelButton').insertAfter(loadAllAnnotationsButton); } - startButtons = 12 - documineButtons; - deleteCount = 16 - documineButtons; - } else { + startButtons = 12 - documineButtons - shouldHideRectangleButton; + deleteCount = 16 - documineButtons - shouldHideRectangleButton; + } else if (header.getItems().includes(loadAllAnnotationsButton)) { header.delete(HeaderElements.LOAD_ALL_ANNOTATIONS); } diff --git a/apps/red-ui/src/assets/icons/general/redaction-preview.svg b/apps/red-ui/src/assets/icons/general/redaction-preview.svg index e84ae4550..f5afa6030 100644 --- a/apps/red-ui/src/assets/icons/general/redaction-preview.svg +++ b/apps/red-ui/src/assets/icons/general/redaction-preview.svg @@ -6,7 +6,7 @@ #redaction-preview-svg.st0 { fill-rule: evenodd; clip-rule: evenodd; - fill: #868E96; + fill: #000; } From d99f96b0793e1cc89cb7d4f9ce2e7c55edb3daba Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Wed, 13 Nov 2024 15:06:34 +0200 Subject: [PATCH 21/24] RED-10405: check if file isError, indent pending type, remove unknown. --- .../dossier-details/dossier-details.component.html | 1 + .../dossier-details/dossier-details.component.scss | 4 ++++ .../dossier-details/dossier-details.component.ts | 8 -------- apps/red-ui/src/assets/i18n/redact/de.json | 3 +-- apps/red-ui/src/assets/i18n/redact/en.json | 3 +-- apps/red-ui/src/assets/i18n/scm/de.json | 3 +-- apps/red-ui/src/assets/i18n/scm/en.json | 3 +-- libs/red-domain/src/lib/dossier-stats/types.ts | 1 - libs/red-domain/src/lib/files/file.model.ts | 6 +++--- 9 files changed, 12 insertions(+), 20 deletions(-) diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.html b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.html index 9f2a66a30..3670a7aff 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.html +++ b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.html @@ -61,6 +61,7 @@ *ngFor="let config of statusConfig" [attr.help-mode-key]="'dashboard_in_dossier'" [config]="config" + [class.indent]="!!PendingTypes[config.id]" [filterKey]="PendingTypes[config.id] ? 'pendingTypeFilters' : 'processingTypeFilters'" >
diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.scss b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.scss index e415ad1bc..8a1d20f28 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.scss +++ b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.scss @@ -45,3 +45,7 @@ iqser-progress-bar:not(:last-child) { margin-bottom: 10px; } + +.indent { + margin-left: 32px; +} diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.ts b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.ts index 860be6f33..40e99726e 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details/dossier-details.component.ts @@ -159,7 +159,6 @@ export class DossierDetailsComponent extends ContextComponent file.errorCode === FileErrorCodes.LOCKED_RULES).length; const numberOfTimeoutFiles = files.filter(file => file.errorCode === FileErrorCodes.RULES_EXECUTION_TIMEOUT).length; - const numberOfUnknownErrorFiles = stats.processingStats.pending - numberOfRulesLockedFiles - numberOfTimeoutFiles; return [ { id: ProcessingTypes.pending, @@ -182,13 +181,6 @@ export class DossierDetailsComponent extends ContextComponent implements IFile { file.fileAttributes && file.fileAttributes.attributeIdToValue ? file.fileAttributes : { attributeIdToValue: {} }; this.processingType = this.#processingType; - this.errorCode = file.fileErrorInfo?.errorCode; + this.errorCode = this.isError ? file.fileErrorInfo?.errorCode : undefined; this.pendingType = this.processingType === ProcessingTypes.pending ? this.#pendingType : undefined; } @@ -195,14 +195,14 @@ export class File extends Entity implements IFile { return ProcessingTypes.processed; } - get #pendingType(): PendingType { + get #pendingType(): PendingType | undefined { if (this.errorCode === FileErrorCodes.LOCKED_RULES) { return PendingTypes.lockedRules; } if (this.errorCode === FileErrorCodes.RULES_EXECUTION_TIMEOUT) { return PendingTypes.timeout; } - return PendingTypes.unknown; + return undefined; } isPageExcluded(page: number): boolean { From d0940017926a94effd9daca4f5b4cc33bae76d69 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Wed, 13 Nov 2024 20:29:55 +0200 Subject: [PATCH 22/24] RED-10461 - updated message when upload a zip file --- .../modules/upload-download/services/file-upload.service.ts | 5 ++++- apps/red-ui/src/assets/i18n/redact/de.json | 3 ++- apps/red-ui/src/assets/i18n/redact/en.json | 3 ++- apps/red-ui/src/assets/i18n/scm/de.json | 3 ++- apps/red-ui/src/assets/i18n/scm/en.json | 3 ++- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/red-ui/src/app/modules/upload-download/services/file-upload.service.ts b/apps/red-ui/src/app/modules/upload-download/services/file-upload.service.ts index ff626ba01..dca4e4a93 100644 --- a/apps/red-ui/src/app/modules/upload-download/services/file-upload.service.ts +++ b/apps/red-ui/src/app/modules/upload-download/services/file-upload.service.ts @@ -234,9 +234,12 @@ export class FileUploadService extends GenericService impleme if (event.status < 300) { uploadFile.progress = 100; uploadFile.completed = true; - if (isCsv(uploadFile) || isZip(uploadFile)) { + if (isCsv(uploadFile)) { this._toaster.success(_('file-upload.type.csv')); } + if (isZip(uploadFile)) { + this._toaster.success(_('file-upload.type.zip')); + } } else { uploadFile.completed = true; uploadFile.error = { diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index cf881b253..d95b11301 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -1632,7 +1632,8 @@ }, "file-upload": { "type": { - "csv": "Die Datei-Attribute wurden erfolgreich aus der hochgeladenen CSV-Datei importiert." + "csv": "Die Datei-Attribute wurden erfolgreich aus der hochgeladenen CSV-Datei importiert.", + "zip": "" } }, "filter-menu": { diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index 124679db9..70f700417 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -1632,7 +1632,8 @@ }, "file-upload": { "type": { - "csv": "File attributes were imported successfully from uploaded CSV file." + "csv": "File attributes were imported successfully from uploaded CSV file.", + "zip": "The zip file has been uploaded successfully!" } }, "filter-menu": { diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index 3c15d8c81..383dcfc33 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -1632,7 +1632,8 @@ }, "file-upload": { "type": { - "csv": "Die Datei-Attribute wurden erfolgreich aus der hochgeladenen CSV-Datei importiert." + "csv": "Die Datei-Attribute wurden erfolgreich aus der hochgeladenen CSV-Datei importiert.", + "zip": "" } }, "filter-menu": { diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index 4cdc3f840..d781a982a 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -1632,7 +1632,8 @@ }, "file-upload": { "type": { - "csv": "File attributes were imported successfully from uploaded CSV file." + "csv": "File attributes were imported successfully from uploaded CSV file.", + "zip": "The zip file has been uploaded successfully!" } }, "filter-menu": { From abc54fae4f2c0c5ff4774935e5ecb33e8aefbff2 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Wed, 13 Nov 2024 21:51:16 +0200 Subject: [PATCH 23/24] RED-10460 - enable switching the language in Edit Profile page in non-dev mode --- .../base-account-screen-component.html | 9 ++++---- .../user-profile-screen.component.html | 22 +++++++++++-------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/apps/red-ui/src/app/modules/account/base-account-screen/base-account-screen-component.html b/apps/red-ui/src/app/modules/account/base-account-screen/base-account-screen-component.html index 6335575a8..52e91a811 100644 --- a/apps/red-ui/src/app/modules/account/base-account-screen/base-account-screen-component.html +++ b/apps/red-ui/src/app/modules/account/base-account-screen/base-account-screen-component.html @@ -8,10 +8,11 @@
-
-
-
- + @if (!isWarningsScreen) { +
+
+
+ }
diff --git a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.html b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.html index 6c7e7b9aa..bb2764e20 100644 --- a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.html +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.html @@ -16,14 +16,16 @@ -
+
{{ languageSelectLabel() | translate }} - - {{ translations[language] | translate }} - + @for (language of languages; track language) { + + {{ translations[language] | translate }} + + }
@@ -32,11 +34,13 @@ {{ 'user-profile-screen.actions.change-password' | translate }}
-
- - {{ 'user-profile-screen.form.dark-theme' | translate }} - -
+ @if (devMode) { +
+ + {{ 'user-profile-screen.form.dark-theme' | translate }} + +
+ } From 32ef5393e2a90a958c98fa12e748ef84d392fd4b Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Thu, 14 Nov 2024 11:04:10 +0200 Subject: [PATCH 24/24] RED-10363: fixed cosmetical issue, added tooltips to and disabled btns. --- apps/red-ui/src/app/app.module.ts | 1 + .../dossier-overview-bulk-actions.component.ts | 8 ++++++-- .../file-header/file-header.component.ts | 1 - .../components/pages/pages.component.ts | 14 ++++++++------ .../file-actions/file-actions.component.ts | 5 ++--- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index 52a8534ed..82d09377f 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -258,6 +258,7 @@ export const appModuleFactory = (config: AppConfig) => { provide: MAT_TOOLTIP_DEFAULT_OPTIONS, useValue: { disableTooltipInteractivity: true, + showDelay: 1, }, }, BaseDatePipe, diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts b/apps/red-ui/src/app/modules/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts index 52fcb2097..ee0d2ca94 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnChanges } from '@angular/core'; +import { Component, computed, Input, OnChanges } from '@angular/core'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { CircleButtonType, CircleButtonTypes } from '@iqser/common-ui'; import { Action, ActionTypes, Dossier, File, ProcessingFileStatuses } from '@red/domain'; @@ -9,6 +9,7 @@ import { BulkActionsService } from '../../services/bulk-actions.service'; import { ExpandableFileActionsComponent } from '@shared/components/expandable-file-actions/expandable-file-actions.component'; import { IqserTooltipPositions } from '@common-ui/utils'; import { NgIf } from '@angular/common'; +import { RulesService } from '../../../admin/services/rules.service'; @Component({ selector: 'redaction-dossier-overview-bulk-actions [dossier] [selectedFiles]', @@ -43,11 +44,13 @@ export class DossierOverviewBulkActionsComponent implements OnChanges { @Input() maxWidth: number; buttons: Action[]; readonly IqserTooltipPositions = IqserTooltipPositions; + readonly areRulesLocked = computed(() => this._rulesService.currentTemplateRules().timeoutDetected); constructor( private readonly _permissionsService: PermissionsService, private readonly _userPreferenceService: UserPreferenceService, private readonly _bulkActionsService: BulkActionsService, + private readonly _rulesService: RulesService, ) {} private get _buttons(): Action[] { @@ -136,8 +139,9 @@ export class DossierOverviewBulkActionsComponent implements OnChanges { id: 'reanalyse-files-btn', type: ActionTypes.circleBtn, action: () => this._bulkActionsService.reanalyse(this.selectedFiles), - tooltip: _('dossier-overview.bulk.reanalyse'), + tooltip: this.areRulesLocked() ? _('dossier-listing.rules.timeoutError') : _('dossier-overview.bulk.reanalyse'), icon: 'iqser:refresh', + disabled: this.areRulesLocked(), show: this.#canReanalyse && (this.#analysisForced || this.#canEnableAutoAnalysis || this.selectedFiles.every(file => file.isError)), diff --git a/apps/red-ui/src/app/modules/file-preview/components/file-header/file-header.component.ts b/apps/red-ui/src/app/modules/file-preview/components/file-header/file-header.component.ts index c62b29a40..da73d183e 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/file-header/file-header.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/file-header/file-header.component.ts @@ -19,7 +19,6 @@ import { getConfig, HelpModeService, IqserAllowDirective, - IqserDialog, IqserPermissionsService, isIqserDevMode, LoadingService, diff --git a/apps/red-ui/src/app/modules/file-preview/components/pages/pages.component.ts b/apps/red-ui/src/app/modules/file-preview/components/pages/pages.component.ts index 9c8725e46..1a3d4876a 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/pages/pages.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/pages/pages.component.ts @@ -34,12 +34,14 @@ export class PagesComponent implements AfterViewInit { // TODO: looks like this is not working scrollToLastViewedPage() { const currentPdfPage = this._pdf.currentPage(); - scrollIntoView(document.getElementById(`quick-nav-page-${currentPdfPage}`), { - behavior: 'smooth', - scrollMode: 'if-needed', - block: 'start', - inline: 'start', - }); + const currentElement = document.getElementById(`quick-nav-page-${currentPdfPage}`); + if (currentElement) + scrollIntoView(currentElement, { + behavior: 'smooth', + scrollMode: 'if-needed', + block: 'start', + inline: 'start', + }); } pageSelectedByClick($event: number): void { diff --git a/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts b/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts index 8a43d507c..9a0eec5a6 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/components/file-actions/file-actions.component.ts @@ -252,11 +252,10 @@ export class FileActionsComponent implements OnChanges { id: 'btn-reanalyse_file_preview', type: ActionTypes.circleBtn, action: () => this.#reanalyseFile(), - tooltip: _('file-preview.reanalyse-notification'), - tooltipClass: 'small', + tooltip: this.areRulesLocked() ? _('dossier-listing.rules.timeoutError') : _('file-preview.reanalyse-notification'), icon: 'iqser:refresh', show: this.showReanalyseFilePreview, - disabled: this.file.isProcessing, + disabled: this.file.isProcessing || this.areRulesLocked(), helpModeKey: 'stop_analysis', }, {