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 3b394cf67..2fb56a236 100644 --- a/apps/red-ui/src/app/models/file/annotation.wrapper.ts +++ b/apps/red-ui/src/app/models/file/annotation.wrapper.ts @@ -4,15 +4,18 @@ import { annotationDefaultColorConfig, annotationEntityColorConfig, AnnotationIconType, + ChangeType, ChangeTypes, DefaultColors, Dictionary, Earmark, FalsePositiveSuperTypes, IComment, + ILegalBasis, IManualChange, IPoint, IRectangle, + IRedactionLogEntry, LogEntryEngine, LogEntryStatuses, LowLevelFilterTypes, @@ -23,11 +26,10 @@ import { SuperType, SuperTypes, } from '@red/domain'; -import { RedactionLogEntry } from '@models/file/redaction-log.entry'; import { IListable } from '@iqser/common-ui'; import { chronologicallyBy, timestampOf } from '../../modules/file-preview/services/file-data.service'; -export class AnnotationWrapper implements IListable, Record { +export class AnnotationWrapper implements IListable { [x: string]: unknown; superType: SuperType; @@ -37,7 +39,7 @@ export class AnnotationWrapper implements IListable, Record { entity: Dictionary; comments: IComment[] = []; firstTopLeftPoint: IPoint; - annotationId: string; + id: string; shortContent: string; content: string; value: string; @@ -70,6 +72,7 @@ export class AnnotationWrapper implements IListable, Record { hasBeenForcedHint: boolean; hasBeenForcedRedaction: boolean; hasBeenRemovedByManualOverride: boolean; + legalBasisList: ILegalBasis[] = []; get searchKey(): string { return this.id; @@ -240,10 +243,6 @@ export class AnnotationWrapper implements IListable, Record { return this.superType === SuperTypes.Recommendation; } - get id() { - return this.annotationId; - } - get x() { return this.firstTopLeftPoint.x; } @@ -278,7 +277,7 @@ export class AnnotationWrapper implements IListable, Record { static fromEarmark(earmark: Earmark) { const annotationWrapper = new AnnotationWrapper(); - annotationWrapper.annotationId = earmark.id; + annotationWrapper.id = earmark.id; annotationWrapper.pageNumber = earmark.positions[0].page; annotationWrapper.superType = SuperTypes.TextHighlight; annotationWrapper.typeValue = SuperTypes.TextHighlight; @@ -291,12 +290,20 @@ export class AnnotationWrapper implements IListable, Record { return annotationWrapper; } - static fromData(redactionLogEntry: RedactionLogEntry, dictionaries: Dictionary[], defaultColors: DefaultColors) { + static fromData( + redactionLogEntry: IRedactionLogEntry, + dictionaries: Dictionary[], + defaultColors: DefaultColors, + changeLogType: ChangeType, + legalBasisList: ILegalBasis[], + hintDictionary: boolean, + ) { const annotationWrapper = new AnnotationWrapper(); - annotationWrapper.annotationId = redactionLogEntry.id; - annotationWrapper.isChangeLogEntry = redactionLogEntry.isChangeLogEntry; - annotationWrapper.changeLogType = redactionLogEntry.changeLogType; + annotationWrapper.id = redactionLogEntry.id; + annotationWrapper.isChangeLogEntry = !!changeLogType; + annotationWrapper.changeLogType = changeLogType; + annotationWrapper.legalBasisList = legalBasisList; annotationWrapper.redaction = redactionLogEntry.redacted; annotationWrapper.hint = redactionLogEntry.hint; annotationWrapper.typeValue = redactionLogEntry.type; @@ -316,7 +323,7 @@ export class AnnotationWrapper implements IListable, Record { annotationWrapper.section = redactionLogEntry.section; annotationWrapper.reference = redactionLogEntry.reference || []; annotationWrapper.rectangle = redactionLogEntry.rectangle; - annotationWrapper.hintDictionary = redactionLogEntry.hintDictionary; + annotationWrapper.hintDictionary = hintDictionary; annotationWrapper.hasBeenResized = !!redactionLogEntry.manualChanges?.find( c => c.manualRedactionType === ManualRedactionTypes.RESIZE && c.annotationStatus === LogEntryStatuses.APPROVED, ); @@ -360,53 +367,45 @@ export class AnnotationWrapper implements IListable, Record { return annotationWrapper; } - static #getTypeLabel(redactionLogEntry: RedactionLogEntry, annotation: AnnotationWrapper): string { + static #getTypeLabel(redactionLogEntry: IRedactionLogEntry, annotation: AnnotationWrapper): string { if (redactionLogEntry.reason?.toLowerCase() === 'false positive') { return annotationTypesTranslations[SuggestionAddFalsePositive]; } return annotationTypesTranslations[annotation.superType]; } - static #handleRecommendations(annotationWrapper: AnnotationWrapper, redactionLogEntry: RedactionLogEntry) { + static #handleRecommendations(annotationWrapper: AnnotationWrapper, redactionLogEntry: IRedactionLogEntry) { if (annotationWrapper.superType === SuperTypes.Recommendation) { annotationWrapper.recommendationType = redactionLogEntry.type; } } - static #getLastRelevantManualChange(manualChanges: IManualChange[]) { - return manualChanges[manualChanges.length - 1]; - } - - static #setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntry) { - if (redactionLogEntryWrapper.manualChanges?.length) { - const lastRelevantManualChange = this.#getLastRelevantManualChange(redactionLogEntryWrapper.manualChanges); - const viableChanges = redactionLogEntryWrapper.changes.filter(c => c.analysisNumber > 1); + static #setSuperType(annotationWrapper: AnnotationWrapper, entry: IRedactionLogEntry) { + if (entry.manualChanges?.length) { + const lastRelevantManualChange = entry.manualChanges?.at(-1); + const viableChanges = entry.changes.filter(c => c.analysisNumber > 1); const lastChange = viableChanges.sort(chronologicallyBy(x => x.dateTime)).at(-1); const lastChangeOccurredAfterLastManualChange = lastChange && timestampOf(lastChange.dateTime) > timestampOf(lastRelevantManualChange.processedDate); - if (lastChangeOccurredAfterLastManualChange && lastChange.type === ChangeTypes.ADDED && redactionLogEntryWrapper.redacted) { + if (lastChangeOccurredAfterLastManualChange && lastChange.type === ChangeTypes.ADDED && entry.redacted) { annotationWrapper.superType = SuperTypes.Redaction; return; } annotationWrapper.pending = !lastRelevantManualChange.processed; - annotationWrapper.superType = AnnotationWrapper.#selectSuperType( - redactionLogEntryWrapper, - lastRelevantManualChange, - annotationWrapper.hintDictionary, - ); + annotationWrapper.superType = this.#selectSuperType(entry, lastRelevantManualChange, annotationWrapper.hintDictionary); if (lastRelevantManualChange.annotationStatus === LogEntryStatuses.REQUESTED) { annotationWrapper.recategorizationType = lastRelevantManualChange.propertyChanges.type; } } else { - if (redactionLogEntryWrapper.recommendation) { + if (entry.recommendation) { annotationWrapper.superType = SuperTypes.Recommendation; - } else if (redactionLogEntryWrapper.redacted) { + } else if (entry.redacted) { annotationWrapper.superType = SuperTypes.Redaction; - } else if (redactionLogEntryWrapper.hint) { + } else if (entry.hint) { annotationWrapper.superType = SuperTypes.Hint; } else { annotationWrapper.superType = SuperTypes.Skipped; @@ -414,7 +413,7 @@ export class AnnotationWrapper implements IListable, Record { } } - static #createContent(annotationWrapper: AnnotationWrapper, entry: RedactionLogEntry) { + static #createContent(annotationWrapper: AnnotationWrapper, entry: IRedactionLogEntry) { let content = ''; if (entry.matchedRule) { content += `Rule ${entry.matchedRule} matched \n\n`; @@ -443,13 +442,15 @@ export class AnnotationWrapper implements IListable, Record { content += `${prefix} "${entry.section}"`; } - annotationWrapper.shortContent = this.#getShortContent(annotationWrapper, entry) || content; + annotationWrapper.shortContent = this.#getShortContent(annotationWrapper) || content; annotationWrapper.content = content; } - static #getShortContent(annotationWrapper: AnnotationWrapper, entry: RedactionLogEntry) { + static #getShortContent(annotationWrapper: AnnotationWrapper) { if (annotationWrapper.legalBasis) { - const lb = entry.legalBasisList?.find(lbm => lbm.reason?.toLowerCase().includes(annotationWrapper.legalBasis.toLowerCase())); + const lb = annotationWrapper.legalBasisList.find(lbm => + lbm.reason?.toLowerCase().includes(annotationWrapper.legalBasis.toLowerCase()), + ); if (lb) { return lb.name; } @@ -458,7 +459,7 @@ export class AnnotationWrapper implements IListable, Record { return annotationWrapper.legalBasis; } - static #selectSuperType(redactionLogEntry: RedactionLogEntry, lastManualChange: IManualChange, isHintDictionary: boolean): SuperType { + static #selectSuperType(redactionLogEntry: IRedactionLogEntry, lastManualChange: IManualChange, isHintDictionary: boolean): SuperType { switch (lastManualChange.manualRedactionType) { case ManualRedactionTypes.ADD_LOCALLY: switch (lastManualChange.annotationStatus) { diff --git a/apps/red-ui/src/app/models/file/redaction-log.entry.ts b/apps/red-ui/src/app/models/file/redaction-log.entry.ts deleted file mode 100644 index 5cfa5f91f..000000000 --- a/apps/red-ui/src/app/models/file/redaction-log.entry.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { IChange, IComment, ILegalBasis, IManualChange, IRectangle, IRedactionLogEntry, LogEntryEngine } from '@red/domain'; - -// TODO: this should be removed. Use only AnnotationWrapper class -export class RedactionLogEntry implements IRedactionLogEntry { - readonly changes: IChange[]; - readonly imported: boolean; - readonly manualChanges: IManualChange[]; - readonly color: number[]; - readonly comments: IComment[]; - readonly dictionaryEntry: boolean; - readonly dossierDictionaryEntry: boolean; - readonly endOffset?: number; - readonly engines: LogEntryEngine[]; - readonly excluded: boolean; - readonly hint: boolean; - readonly rectangle: boolean; - readonly id: string; - readonly image: boolean; - readonly imageHasTransparency: boolean; - readonly legalBasis?: string; - readonly matchedRule?: number; - readonly positions: IRectangle[]; - readonly recommendation: boolean; - readonly redacted: boolean; - readonly reference: string[]; - readonly section?: string; - readonly sectionNumber?: number; - readonly startOffset?: number; - readonly textAfter?: string; - readonly textBefore?: string; - readonly type?: string; - readonly value?: string; - readonly sourceId?: string; - readonly isChangeLogEntry: boolean; - - reason?: string; - - constructor( - redactionLogEntry: IRedactionLogEntry, - readonly changeLogType: 'ADDED' | 'REMOVED' | 'CHANGED' | undefined, - readonly legalBasisList: ILegalBasis[], - readonly hintDictionary: boolean, - ) { - this.changes = redactionLogEntry.changes ?? []; - this.manualChanges = redactionLogEntry.manualChanges ?? []; - this.color = redactionLogEntry.color ?? []; - this.comments = redactionLogEntry.comments ?? []; - this.dictionaryEntry = !!redactionLogEntry.dictionaryEntry; - this.dossierDictionaryEntry = !!redactionLogEntry.dossierDictionaryEntry; - this.endOffset = redactionLogEntry.endOffset; - this.engines = redactionLogEntry.engines ?? []; - this.excluded = !!redactionLogEntry.excluded; - this.hint = !!redactionLogEntry.hint; - this.rectangle = !!redactionLogEntry.rectangle; - this.id = redactionLogEntry.id; - this.image = !!redactionLogEntry.image; - this.imageHasTransparency = !!redactionLogEntry.imageHasTransparency; - this.legalBasis = redactionLogEntry.legalBasis; - this.matchedRule = redactionLogEntry.matchedRule; - this.positions = redactionLogEntry.positions ?? []; - this.reason = redactionLogEntry.reason; - this.recommendation = !!redactionLogEntry.recommendation; - this.redacted = !!redactionLogEntry.redacted; - this.reference = redactionLogEntry.reference ?? []; - this.section = redactionLogEntry.section; - this.sectionNumber = redactionLogEntry.sectionNumber; - this.startOffset = redactionLogEntry.startOffset; - this.textAfter = redactionLogEntry.textAfter; - this.textBefore = redactionLogEntry.textBefore; - this.type = redactionLogEntry.type; - this.value = redactionLogEntry.value; - this.imported = !!redactionLogEntry.imported; - this.sourceId = redactionLogEntry.sourceId; - this.isChangeLogEntry = !!this.changeLogType; - } -} diff --git a/apps/red-ui/src/app/modules/admin/screens/entities/screens/dictionary/dictionary-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/entities/screens/dictionary/dictionary-screen.component.ts index 0364da8ed..4b4441d82 100644 --- a/apps/red-ui/src/app/modules/admin/screens/entities/screens/dictionary/dictionary-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/entities/screens/dictionary/dictionary-screen.component.ts @@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router'; import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component'; import { DictionaryService } from '@services/entity-services/dictionary.service'; import { getCurrentUser, getParam, IqserPermissionsService, List, LoadingService } from '@iqser/common-ui'; -import { BehaviorSubject, firstValueFrom } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { DICTIONARY_TO_ENTRY_TYPE_MAP, DICTIONARY_TYPE_KEY_MAP, DictionaryType, DOSSIER_TEMPLATE_ID, ENTITY_TYPE, User } from '@red/domain'; import { PermissionsService } from '@services/permissions.service'; import { ROLES } from '@users/roles'; @@ -26,7 +26,7 @@ export class DictionaryScreenComponent implements OnInit { private readonly _dictionaryManager: DictionaryManagerComponent; constructor( - readonly route: ActivatedRoute, + route: ActivatedRoute, readonly permissionsService: PermissionsService, readonly iqserPermissionsService: IqserPermissionsService, private readonly _loadingService: LoadingService, @@ -40,8 +40,8 @@ export class DictionaryScreenComponent implements OnInit { return this._dictionaryManager.editor.hasChanges; } - async ngOnInit(): Promise { - await this._loadEntries(); + async ngOnInit() { + await this.#loadEntries(); } async save() { @@ -60,22 +60,22 @@ export class DictionaryScreenComponent implements OnInit { DICTIONARY_TO_ENTRY_TYPE_MAP[this.type], false, ); - await this._loadEntries(); + await this.#loadEntries(); } catch (e) { this._loadingService.stop(); } } - private async _loadEntries() { + async #loadEntries() { this._loadingService.start(); try { - const data = await firstValueFrom(this._dictionaryService.getForType(this.#dossierTemplateId, this.entityType)); + const data = await this._dictionaryService.getForType(this.#dossierTemplateId, this.entityType); const entries: List = data[DICTIONARY_TYPE_KEY_MAP[this.type]]; this.initialEntries$.next([...entries].sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' }))); - this._loadingService.stop(); } catch (e) { - this._loadingService.stop(); this.initialEntries$.next([]); + } finally { + this._loadingService.stop(); } } } 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 311dde880..d864870c6 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 @@ -133,8 +133,8 @@ export class AnnotationActionsService { const data = { annotations, dossier: this._state.dossier }; const { dossierId, fileId } = this._state; this._dialogService.openDialog('recategorizeImage', data, ({ comment, type }: { type: string; comment: string }) => { - const body: List = annotations.map(({ annotationId }) => ({ - annotationId, + const body: List = annotations.map(({ id }) => ({ + id: id, type, comment, })); diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotation-references.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotation-references.service.ts index 79a603d67..b51a47c16 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotation-references.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotation-references.service.ts @@ -20,7 +20,7 @@ export class AnnotationReferencesService { const annotations$ = this._fileDataService.annotations$.pipe(map(dict => Object.values(dict))); return this.annotation$.pipe( filter(annotation => !!annotation), - switchMap(({ reference }) => annotations$.pipe(filterEach(a => reference.includes(a.annotationId)))), + switchMap(({ reference }) => annotations$.pipe(filterEach(a => reference.includes(a.id)))), ); } diff --git a/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts b/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts index ae9005f35..38bbc5896 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts @@ -10,9 +10,8 @@ import { ViewModes, } from '@red/domain'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; -import { BehaviorSubject, firstValueFrom, Observable, Subject, Subscription } from 'rxjs'; -import { RedactionLogEntry } from '@models/file/redaction-log.entry'; -import { Injectable, OnDestroy } from '@angular/core'; +import { firstValueFrom, Observable, Subject } from 'rxjs'; +import { Injectable, signal } from '@angular/core'; import { FilePreviewStateService } from './file-preview-state.service'; import { ViewedPagesService } from '@services/files/viewed-pages.service'; import { UserPreferenceService } from '@users/user-preference.service'; @@ -31,6 +30,7 @@ import { DefaultColorsService } from '@services/entity-services/default-colors.s import { DictionaryService } from '@services/entity-services/dictionary.service'; import { SuggestionsService } from './suggestions.service'; import { ViewedPagesMapService } from '@services/files/viewed-pages-map.service'; +import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'; const DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes; @@ -43,15 +43,14 @@ export function chronologicallyBy(property: (x: T) => string) { } @Injectable() -export class FileDataService extends EntitiesService implements OnDestroy { +export class FileDataService extends EntitiesService { missingTypes = new Set(); readonly annotations$: Observable; readonly earmarks$: Observable>; protected readonly _entityClass = AnnotationWrapper; readonly #redactionLog$ = new Subject(); - readonly #earmarks$ = new BehaviorSubject>(new Map()); + readonly #earmarks = signal>(new Map()); #originalViewedPages: ViewedPage[] = []; - readonly #subscription: Subscription; constructor( private readonly _state: FilePreviewStateService, @@ -72,15 +71,16 @@ export class FileDataService extends EntitiesService viewMode === ViewModes.TEXT_HIGHLIGHTS - ? this.#earmarks$.pipe(map(textHighlights => ([] as AnnotationWrapper[]).concat(...textHighlights.values()))) + ? this.earmarks$.pipe(map(textHighlights => ([] as AnnotationWrapper[]).concat(...textHighlights.values()))) : this.annotations$.pipe(map(annotations => this.#getVisibleAnnotations(annotations, viewMode))), ), tap(annotations => this.setEntities(annotations)), + takeUntilDestroyed(), ) .subscribe(); } @@ -94,7 +94,7 @@ export class FileDataService extends EntitiesService console.time('buildAnnotations')), withLatestFrom(this._state.file$), tap(([redactionLog, file]) => this.#buildRemovedRedactions(redactionLog, file)), - switchMap(([redactionLog, file]) => this.#buildAnnotations(redactionLog, file)), + switchMap(([redactionLog, file]) => this.#convertData(redactionLog, file)), tap(() => this.#checkMissingTypes()), map(annotations => this._userPreferenceService.areDevFeaturesEnabled ? annotations : annotations.filter(a => !a.isFalsePositive), @@ -104,10 +104,6 @@ export class FileDataService extends EntitiesService h.pageNumber); - this.#earmarks$.next(earmarks); + this.#earmarks.set(earmarks); return earmarks; } @@ -203,29 +199,11 @@ export class FileDataService extends EntitiesService 0; - if (!manual && file.excludedPages.includes(pageNumber)) { - continue; - } - - annotations.push(AnnotationWrapper.fromData(entry, dictionaries, defaultColors)); - } - - return annotations; - } - async #buildRemovedRedactions(redactionLog: IRedactionLog, file: File): Promise { - const redactionLogCopy = JSON.parse(JSON.stringify(redactionLog)); + const redactionLogCopy: IRedactionLog = JSON.parse(JSON.stringify(redactionLog)); + const redactionLogEntries: IRedactionLogEntry[] = []; - redactionLogCopy.redactionLogEntry = redactionLogCopy.redactionLogEntry?.reduce((filtered, entry) => { + for (const entry of redactionLogCopy.redactionLogEntry) { const lastChange = entry.manualChanges.at(-1); if ( lastChange?.annotationStatus === LogEntryStatuses.REQUESTED && @@ -234,12 +212,12 @@ export class FileDataService extends EntitiesService !a.isSkipped); } @@ -248,49 +226,58 @@ export class FileDataService extends EntitiesService dict.type === redactionLogEntry.type); + for (const entry of redactionLog.redactionLogEntry) { + let dictionary = dictionaries.find(dict => dict.type === entry.type); if (!dictionary && checkDictionary) { const dictionaryRequest = this._dictionaryService.loadDictionaryDataForDossierTemplate(this._state.dossierTemplateId); await firstValueFrom(dictionaryRequest); checkDictionary = false; - dictionary = dictionaries.find(dict => dict.type === redactionLogEntry.type); + dictionary = dictionaries.find(dict => dict.type === entry.type); } if (!dictionary) { - this.missingTypes.add(redactionLogEntry.type); + this.missingTypes.add(entry.type); continue; } - const redactionLogEntryWrapper: RedactionLogEntry = new RedactionLogEntry( - redactionLogEntry, + const pageNumber = entry.positions[0]?.page; + const manual = entry.manualChanges?.length > 0; + if (!manual && file.excludedPages.includes(pageNumber)) { + continue; + } + + const changeLogValues = this.#getChangeLogValues(entry, file); + if (changeLogValues.hidden) { + continue; + } + + const annotation = AnnotationWrapper.fromData( + entry, + dictionaries, + defaultColors, changeLogValues.changeLogType, redactionLog.legalBasis ?? [], !!dictionary?.hint, ); - if (redactionLogEntry.sourceId) { - if (!sourceIdAnnotationIds[redactionLogEntry.sourceId]) { - sourceIdAnnotationIds[redactionLogEntry.sourceId] = []; + if (entry.sourceId) { + if (!sourceIds[entry.sourceId]) { + sourceIds[entry.sourceId] = []; } - sourceIdAnnotationIds[redactionLogEntry.sourceId].push(redactionLogEntryWrapper); + sourceIds[entry.sourceId].push(annotation); } - result.push(redactionLogEntryWrapper); + annotations.push(annotation); } - const sourceKeys = Object.keys(sourceIdAnnotationIds); - return result.filter(r => !sourceKeys.includes(r.id)); + const sourceKeys = Object.keys(sourceIds); + return annotations.filter(r => !sourceKeys.includes(r.id)); } #getChangeLogValues( @@ -309,7 +296,7 @@ export class FileDataService extends EntitiesService c.analysisNumber > 1); - const lastChange = viableChanges.sort(chronologicallyBy(x => x.dateTime)).at(-1); + const lastChange = viableChanges?.sort(chronologicallyBy(x => x.dateTime)).at(-1); const page = redactionLogEntry.positions?.[0].page; const viewedPage = this.#originalViewedPages.find(p => p.page === page); diff --git a/apps/red-ui/src/app/modules/file-preview/services/manual-redaction.service.ts b/apps/red-ui/src/app/modules/file-preview/services/manual-redaction.service.ts index bae39ff8c..39ad69d26 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/manual-redaction.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/manual-redaction.service.ts @@ -67,7 +67,7 @@ export class ManualRedactionService extends GenericService { addRecommendation(annotations: AnnotationWrapper[], dossierId: string, fileId: string, comment = { text: 'Accepted Recommendation' }) { const recommendations: List = annotations.map(annotation => ({ addToDictionary: true, - sourceId: annotation.annotationId, + sourceId: annotation.id, value: annotation.value, reason: annotation.legalBasis ?? 'Dictionary Request', positions: annotation.positions, diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-draw.service.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-draw.service.ts index b1f388abb..de46e0371 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-draw.service.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-draw.service.ts @@ -146,13 +146,13 @@ export class AnnotationDrawService { annotation.ReadOnly = true; const isOCR = annotationWrapper.isOCR && !annotationWrapper.isSuggestionResize; - if (isOCR && !this._annotationManager.isHidden(annotationWrapper.annotationId)) { - this._annotationManager.addToHidden(annotationWrapper.annotationId); + if (isOCR && !this._annotationManager.isHidden(annotationWrapper.id)) { + this._annotationManager.addToHidden(annotationWrapper.id); } annotation.Hidden = annotationWrapper.isChangeLogRemoved || (hideSkipped && annotationWrapper.isSkipped) || - this._annotationManager.isHidden(annotationWrapper.annotationId); + this._annotationManager.isHidden(annotationWrapper.id); annotation.setCustomData('redact-manager', 'true'); annotation.setCustomData('redaction', String(annotationWrapper.previewAnnotation)); annotation.setCustomData('suggestion', String(annotationWrapper.isSuggestion)); diff --git a/apps/red-ui/src/app/modules/pdf-viewer/utils/functions.ts b/apps/red-ui/src/app/modules/pdf-viewer/utils/functions.ts index 12091decb..74239b114 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/utils/functions.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/utils/functions.ts @@ -14,7 +14,7 @@ export function stopAndPreventIfNotAllowed($event: KeyboardEvent) { } export function getId(item: string | AnnotationWrapper) { - return typeof item === 'string' ? item : item.annotationId; + return typeof item === 'string' ? item : item.id; } export function getIds(items?: List | List): List | undefined { 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 90e22c1a4..9ea29d8c1 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 @@ -1,7 +1,5 @@ import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core'; import { Debounce, IconButtonTypes, List, LoadingService } from '@iqser/common-ui'; -import { firstValueFrom, Observable, of } from 'rxjs'; -import { catchError, map, take, tap } from 'rxjs/operators'; import { Dictionary, DICTIONARY_TYPE_KEY_MAP, DictionaryType, Dossier, DossierTemplate } from '@red/domain'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { DictionaryService } from '@services/entity-services/dictionary.service'; @@ -50,9 +48,10 @@ export class DictionaryManagerComponent implements OnChanges { } as Dictionary; selectDossierTemplate = { name: _('dictionary-overview.compare.select-dossier-template') } as DossierTemplate; compare: false; - dictionaries: List = this._dictionaries; + dictionaries: List = this.#dictionaries; private _searchDecorations: string[] = []; - readonly #currentTab: string = window.location.href.split('/').pop(); + readonly #currentTab = window.location.href.split('/').pop(); + #compareDictionary = this.selectDictionary; constructor( private readonly _dictionaryService: DictionaryService, @@ -71,8 +70,8 @@ export class DictionaryManagerComponent implements OnChanges { set dossierTemplate(value: DossierTemplate) { this._dossierTemplate = value; - this.dictionaries = this._dictionaries; - this._compareDictionary = this.selectDictionary; + this.dictionaries = this.#dictionaries; + this.#compareDictionary = this.selectDictionary; this.showDiffEditor = false; } @@ -91,24 +90,19 @@ export class DictionaryManagerComponent implements OnChanges { return; } - this._onDossierChanged(dossier.dossierTemplateId, dossier.id) - .pipe(take(1)) - // eslint-disable-next-line rxjs/no-ignored-subscription - .subscribe(entries => { - this.diffEditorText = entries; - this.showDiffEditor = true; - }); + this.#onDossierChanged(dossier.dossierTemplateId, dossier.id).then(entries => { + this.diffEditorText = entries; + this.showDiffEditor = true; + }); } - private _compareDictionary = this.selectDictionary; - get compareDictionary() { - return this._compareDictionary; + return this.#compareDictionary; } set compareDictionary(dictionary: Dictionary) { this._loadingService.start(); - this._compareDictionary = dictionary; + this.#compareDictionary = dictionary; if (dictionary.label === this.selectDictionary.label) { this.showDiffEditor = false; @@ -116,38 +110,25 @@ export class DictionaryManagerComponent implements OnChanges { return; } const entries: List = - this._compareDictionary.getEntries(this.type) ?? - this._dictionariesMapService.get(this._compareDictionary.dossierTemplateId, this._compareDictionary.type).getEntries(this.type); + this.#compareDictionary.getEntries(this.type) ?? + this._dictionariesMapService.get(this.#compareDictionary.dossierTemplateId, this.#compareDictionary.type).getEntries(this.type); if (entries.length) { - this.diffEditorText = this._toString([...entries]); + this.diffEditorText = this.#toString([...entries]); this.showDiffEditor = true; return; } - firstValueFrom( - this._dictionaryService.getForType(this._compareDictionary.dossierTemplateId, this._compareDictionary.type).pipe( - tap(values => { - this._compareDictionary.setEntries([...values[DICTIONARY_TYPE_KEY_MAP[this.type]]] ?? [], this.type); - }), - catchError(() => { - this._compareDictionary.setEntries([], this.type); - return of({}); - }), - ), - ).then(() => { - this.diffEditorText = this._toString([...(this._compareDictionary.getEntries(this.type) as string[])]); - this.showDiffEditor = true; - this._changeRef.markForCheck(); - this._loadingService.stop(); - }); - } - - get _dictionaries(): Dictionary[] { - if (!this._dossierTemplate || this._dossierTemplate.name === this.selectDossierTemplate.name) { - return; - } - return this._dictionariesMapService.get(this.dossierTemplate?.dossierTemplateId).filter(dict => !dict.virtual); + this._dictionaryService + .getForType(this.#compareDictionary.dossierTemplateId, this.#compareDictionary.type) + .then(values => { + this.#compareDictionary.setEntries([...values[DICTIONARY_TYPE_KEY_MAP[this.type]]] ?? [], this.type); + this.diffEditorText = this.#toString([...(this.#compareDictionary.getEntries(this.type) as string[])]); + this.showDiffEditor = true; + this._changeRef.markForCheck(); + this._loadingService.stop(); + }) + .catch(() => this.#compareDictionary.setEntries([], this.type)); } get dossierTemplateIsNotSelected() { @@ -156,7 +137,7 @@ export class DictionaryManagerComponent implements OnChanges { get optionNotSelected() { if (this.filterByDossierTemplate) { - return this.selectDictionary.label === this._compareDictionary.label; + return this.selectDictionary.label === this.#compareDictionary.label; } return this.dossier.dossierName === this.selectDossier.dossierName; } @@ -165,6 +146,13 @@ export class DictionaryManagerComponent implements OnChanges { return HELP_MODE_KEYS[this.#currentTab]; } + get #dictionaries(): Dictionary[] { + if (!this._dossierTemplate || this._dossierTemplate.name === this.selectDossierTemplate.name) { + return; + } + return this._dictionariesMapService.get(this.dossierTemplate?.dossierTemplateId).filter(dict => !dict.virtual); + } + download(): void { const content = this.editor.currentEntries.join('\n'); const blob = new Blob([content], { @@ -236,12 +224,12 @@ export class DictionaryManagerComponent implements OnChanges { this.editor.codeEditor.revealLineInCenter(range.startLineNumber, SMOOTH_SCROLL); } - private _onDossierChanged(dossierTemplateId: string, dossierId?: string, type = 'dossier_redaction'): Observable { - const dictionary$ = this._dictionaryService.getForType(dossierTemplateId, type, dossierId); - return dictionary$.pipe(map(data => this._toString([...data.entries]))); + async #onDossierChanged(dossierTemplateId: string, dossierId?: string, type = 'dossier_redaction') { + const dictionary = await this._dictionaryService.getForType(dossierTemplateId, type, dossierId); + return this.#toString([...dictionary.entries]); } - private _toString(entries: string[]): string { + #toString(entries: string[]): string { entries.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'accent' })); return entries.join('\n'); } diff --git a/apps/red-ui/src/app/services/entity-services/dictionary.service.ts b/apps/red-ui/src/app/services/entity-services/dictionary.service.ts index 11615cffa..63375cf4c 100644 --- a/apps/red-ui/src/app/services/entity-services/dictionary.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dictionary.service.ts @@ -32,9 +32,9 @@ export class DictionaryService extends EntitiesService * Retrieves all dictionary entries of an entry type */ - getForType(dossierTemplateId: string, type: string, dossierId?: string): Observable { + getForType(dossierTemplateId: string, type: string, dossierId?: string) { const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined; - return this._getOne([type, dossierTemplateId], this._defaultModelPath, queryParams); + return firstValueFrom(this._getOne([type, dossierTemplateId], this._defaultModelPath, queryParams)); } /** @@ -181,7 +181,7 @@ export class DictionaryService extends EntitiesService } async loadDossierDictionary(dossierTemplateId: string, dossierId: string): Promise { - const promise = firstValueFrom(this.getForType(dossierTemplateId, 'dossier_redaction', dossierId)); + const promise = this.getForType(dossierTemplateId, 'dossier_redaction', dossierId); const dict = await promise.catch(() => undefined); if (dict) { const dictionary = new Dictionary({ diff --git a/apps/red-ui/src/app/services/files/earmarks.service.ts b/apps/red-ui/src/app/services/files/earmarks.service.ts index 966248629..6e27ee7d2 100644 --- a/apps/red-ui/src/app/services/files/earmarks.service.ts +++ b/apps/red-ui/src/app/services/files/earmarks.service.ts @@ -2,7 +2,7 @@ import { inject, Injectable } from '@angular/core'; import { GenericService, Toaster } from '@iqser/common-ui'; import { Earmark, EarmarkOperation, EarmarkResponse } from '@red/domain'; import { catchError, map, tap } from 'rxjs/operators'; -import { Observable, of } from 'rxjs'; +import { firstValueFrom, of } from 'rxjs'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; @@ -13,13 +13,15 @@ export class EarmarksService extends GenericService { protected readonly _defaultModelPath = ''; readonly #toaster = inject(Toaster); - getEarmarks(dossierId: string, fileId: string): Observable { - return this._http.get<{ highlights: Earmark[] }>(`/${this.#getPath(dossierId, fileId)}`).pipe( + getEarmarks(dossierId: string, fileId: string) { + const request = this._http.get<{ highlights: Earmark[] }>(`/${this.#getPath(dossierId, fileId)}`).pipe( map(response => response.highlights), map(highlights => highlights.map(highlight => AnnotationWrapper.fromEarmark(highlight))), map(highlights => highlights.sort((h1, h2) => h1.color.localeCompare(h2.color))), catchError(() => of([])), ); + + return firstValueFrom(request); } performHighlightsAction(ids: string[], dossierId: string, fileId: string, operation: EarmarkOperation) {