From 52681330f594880c985800652c87f4d94ab6565c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 23 Mar 2022 15:58:00 +0200 Subject: [PATCH] False positive & recommendations --- .../app/models/file/annotation.permissions.ts | 7 +-- .../src/app/models/file/annotation.wrapper.ts | 4 +- .../admin/screens/entities/entities.module.ts | 10 +++++ .../dictionary/dictionary-screen.component.ts | 18 ++++++-- .../entities/screens/info/info.component.html | 2 +- .../entities/screens/info/info.component.ts | 25 +++++------ .../file-actions/file-actions.component.scss | 4 -- .../annotation-actions.component.ts | 3 ++ .../type-annotation-icon.component.ts | 5 +-- .../services/annotation-actions.service.ts | 23 ++++++---- .../services/annotation-draw.service.ts | 4 +- .../dictionary-manager.component.html | 2 +- .../dictionary-manager.component.ts | 34 +++++++++------ .../dictionaries-map.service.ts | 15 ++++++- .../entity-services/dictionary.service.ts | 43 +++++++++++-------- .../app/services/manual-annotation.service.ts | 17 ++++---- apps/red-ui/src/assets/config/config.json | 4 +- .../src/lib/dictionaries/dictionary.model.ts | 41 ++++++++++-------- .../src/lib/dictionaries/dictionary.ts | 4 ++ libs/red-domain/src/lib/files/types.ts | 1 + .../redaction-log/add-redaction.request.ts | 2 + .../redaction-log/dictionary-entry-types.ts | 24 +++++++++++ .../red-domain/src/lib/redaction-log/index.ts | 1 + 23 files changed, 192 insertions(+), 101 deletions(-) create mode 100644 libs/red-domain/src/lib/redaction-log/dictionary-entry-types.ts diff --git a/apps/red-ui/src/app/models/file/annotation.permissions.ts b/apps/red-ui/src/app/models/file/annotation.permissions.ts index 751ec622a..b19e11dad 100644 --- a/apps/red-ui/src/app/models/file/annotation.permissions.ts +++ b/apps/red-ui/src/app/models/file/annotation.permissions.ts @@ -1,5 +1,5 @@ import { AnnotationWrapper } from './annotation.wrapper'; -import { User } from '@red/domain'; +import { Dictionary, User } from '@red/domain'; import { isArray } from 'lodash-es'; export class AnnotationPermissions { @@ -16,7 +16,7 @@ export class AnnotationPermissions { canRecategorizeImage = true; canForceHint = true; - static forUser(isApprover: boolean, user: User, annotations: AnnotationWrapper | AnnotationWrapper[]) { + static forUser(isApprover: boolean, user: User, annotations: AnnotationWrapper | AnnotationWrapper[], entities: Dictionary[]) { if (!isArray(annotations)) { annotations = [annotations]; } @@ -35,7 +35,8 @@ export class AnnotationPermissions { permissions.canForceRedaction = annotation.isSkipped && !annotation.isFalsePositive && !annotation.pending; permissions.canAcceptRecommendation = annotation.isRecommendation && !annotation.pending; - permissions.canMarkAsFalsePositive = annotation.canBeMarkedAsFalsePositive && !annotation.imported && !annotation.pending; + const annotationEntity = entities.find(entity => entity.type === annotation.type); + permissions.canMarkAsFalsePositive = annotation.canBeMarkedAsFalsePositive && annotationEntity.hasDictionary; permissions.canRemoveOrSuggestToRemoveOnlyHere = (annotation.isRedacted || annotation.isHint) && !annotation.pending; permissions.canRemoveOrSuggestToRemoveFromDictionary = 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 0113c05bb..9d5a99980 100644 --- a/apps/red-ui/src/app/models/file/annotation.wrapper.ts +++ b/apps/red-ui/src/app/models/file/annotation.wrapper.ts @@ -79,7 +79,7 @@ export class AnnotationWrapper implements Record { } get canBeMarkedAsFalsePositive() { - return (this.isRecommendation || this.superType === SuperTypes.Redaction) && !this.isImage; + return (this.isRecommendation || this.superType === SuperTypes.Redaction) && !this.isImage && !this.imported && !this.pending; } get isSuperTypeBasedColor() { @@ -294,7 +294,7 @@ export class AnnotationWrapper implements Record { private static _handleRecommendations(annotationWrapper: AnnotationWrapper, redactionLogEntry: RedactionLogEntry) { if (annotationWrapper.superType === SuperTypes.Recommendation) { - annotationWrapper.recommendationType = redactionLogEntry.type.substr('recommendation_'.length); + annotationWrapper.recommendationType = redactionLogEntry.type; } } diff --git a/apps/red-ui/src/app/modules/admin/screens/entities/entities.module.ts b/apps/red-ui/src/app/modules/admin/screens/entities/entities.module.ts index 24be5a2fb..10dbe0d71 100644 --- a/apps/red-ui/src/app/modules/admin/screens/entities/entities.module.ts +++ b/apps/red-ui/src/app/modules/admin/screens/entities/entities.module.ts @@ -19,6 +19,16 @@ const routes = [ component: DictionaryScreenComponent, canDeactivate: [PendingChangesGuard], }, + { + path: 'false-positive', + component: DictionaryScreenComponent, + canDeactivate: [PendingChangesGuard], + }, + { + path: 'false-recommendations', + component: DictionaryScreenComponent, + canDeactivate: [PendingChangesGuard], + }, ]; @NgModule({ 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 643ba18ec..9e3038b3b 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 @@ -6,6 +6,7 @@ import { LoadingService } from '@iqser/common-ui'; import { UserService } from '@services/user.service'; import { BehaviorSubject, firstValueFrom } from 'rxjs'; import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants'; +import { DICTIONARY_TO_ENTRY_TYPE_MAP, DICTIONARY_TYPE_KEY_MAP, DictionaryType } from '@red/domain'; @Component({ templateUrl: './dictionary-screen.component.html', @@ -17,6 +18,7 @@ export class DictionaryScreenComponent implements OnInit { isLeavingPage = false; readonly #dossierTemplateId: string; readonly #entityType: string; + readonly #type: DictionaryType; @ViewChild('dictionaryManager', { static: false }) private readonly _dictionaryManager: DictionaryManagerComponent; @ViewChild('fileInput') private readonly _fileInput: ElementRef; @@ -29,6 +31,7 @@ export class DictionaryScreenComponent implements OnInit { ) { this.#dossierTemplateId = _route.parent.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID); this.#entityType = _route.parent.snapshot.paramMap.get(ENTITY_TYPE); + this.#type = this._route.snapshot.routeConfig.path as DictionaryType; } get changed() { @@ -45,7 +48,15 @@ export class DictionaryScreenComponent implements OnInit { this._loadingService.start(); try { await firstValueFrom( - this._dictionaryService.saveEntries(entries, this.initialEntries$.value, this.#dossierTemplateId, this.#entityType, null), + this._dictionaryService.saveEntries( + entries, + this.initialEntries$.value, + this.#dossierTemplateId, + this.#entityType, + null, + true, + DICTIONARY_TO_ENTRY_TYPE_MAP[this.#type], + ), ); await this._loadEntries(); } catch (e) { @@ -57,9 +68,8 @@ export class DictionaryScreenComponent implements OnInit { this._loadingService.start(); try { const data = await firstValueFrom(this._dictionaryService.getForType(this.#dossierTemplateId, this.#entityType)); - this.initialEntries$.next( - [...data.entries].sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' })), - ); + const entries: string[] = 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(); diff --git a/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.html b/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.html index 2295e52af..90a85b622 100644 --- a/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.html @@ -72,7 +72,7 @@
-
{{ dictionary.type }}
+
{{ entity.type }}
diff --git a/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.ts b/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.ts index 2e1ad6976..13b10a598 100644 --- a/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.ts @@ -18,7 +18,7 @@ import { DictionaryService } from '@services/entity-services/dictionary.service' changeDetection: ChangeDetectionStrategy.OnPush, }) export class InfoComponent extends BaseFormComponent { - dictionary: Dictionary; + entity: Dictionary; readonly hasHexColor$: Observable; readonly hasRecommendationHexColor$: Observable; readonly currentUser = this._userService.currentUser; @@ -37,7 +37,7 @@ export class InfoComponent extends BaseFormComponent { super(); this.#dossierTemplateId = this._route.parent.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID); const entityType = this._route.parent.snapshot.paramMap.get(ENTITY_TYPE); - this.dictionary = this._dictionariesMapService.getDictionary(entityType, this.#dossierTemplateId); + this.entity = this._dictionariesMapService.getDictionary(entityType, this.#dossierTemplateId); this.form = this._getForm(); this.initialFormValue = this.form.getRawValue(); this.hasHexColor$ = this._colorEmpty$('hexColor'); @@ -71,7 +71,7 @@ export class InfoComponent extends BaseFormComponent { private _formToObject(): IDictionary { return { - type: this.dictionary.type, + type: this.entity.type, label: this.form.get('label').value, caseInsensitive: !this.form.get('caseSensitive').value, description: this.form.get('description').value, @@ -81,21 +81,22 @@ export class InfoComponent extends BaseFormComponent { rank: this.form.get('rank').value, addToDictionaryAction: this.form.get('addToDictionaryAction').value, dossierTemplateId: this.#dossierTemplateId, + hasDictionary: this.form.get('hasDictionary').value, }; } private _getForm(): FormGroup { return this._formBuilder.group({ - label: [{ value: this.dictionary.label, disabled: !this.currentUser.isAdmin }, [Validators.required, Validators.minLength(3)]], - description: [this.dictionary.description], - rank: [this.dictionary.rank, Validators.required], - hexColor: [this.dictionary.hexColor, [Validators.required, Validators.minLength(7)]], - recommendationHexColor: [this.dictionary.recommendationHexColor, [Validators.required, Validators.minLength(7)]], - hint: [this.dictionary.hint], - addToDictionaryAction: [this.dictionary.addToDictionaryAction], - caseSensitive: [!this.dictionary.caseInsensitive], + label: [{ value: this.entity.label, disabled: !this.currentUser.isAdmin }, [Validators.required, Validators.minLength(3)]], + description: [this.entity.description], + rank: [this.entity.rank, Validators.required], + hexColor: [this.entity.hexColor, [Validators.required, Validators.minLength(7)]], + recommendationHexColor: [this.entity.recommendationHexColor, [Validators.required, Validators.minLength(7)]], + hint: [this.entity.hint], + addToDictionaryAction: [this.entity.addToDictionaryAction], + caseSensitive: [!this.entity.caseInsensitive], defaultReason: [{ value: null, disabled: true }], - hasDictionary: [false], + hasDictionary: [this.entity.hasDictionary], }); } } diff --git a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.scss b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.scss index 68682cd6a..759479c3d 100644 --- a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.scss +++ b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.scss @@ -28,7 +28,3 @@ iqser-status-bar { margin-left: 2px; } - -.spinning-icon { - margin: 0 12px 0 11px; -} diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts index f6359d91f..eb5b07569 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts @@ -10,6 +10,7 @@ import { FilePreviewStateService } from '../../services/file-preview-state.servi import { HelpModeService, ScrollableParentView, ScrollableParentViews } from '@iqser/common-ui'; import { PdfViewer } from '../../services/pdf-viewer.service'; import { FileDataService } from '../../services/file-data.service'; +import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; export const AnnotationButtonTypes = { dark: 'dark', @@ -41,6 +42,7 @@ export class AnnotationActionsComponent implements OnChanges { private readonly _state: FilePreviewStateService, private readonly _permissionsService: PermissionsService, private readonly _fileDataService: FileDataService, + private readonly _dictionariesMapService: DictionariesMapService, ) {} private _annotations: AnnotationWrapper[]; @@ -131,6 +133,7 @@ export class AnnotationActionsComponent implements OnChanges { this._permissionsService.isApprover(dossier), this._userService.currentUser, this.annotations, + this._dictionariesMapService.get(dossier.dossierTemplateId), ); } } diff --git a/apps/red-ui/src/app/modules/file-preview/components/type-annotation-icon/type-annotation-icon.component.ts b/apps/red-ui/src/app/modules/file-preview/components/type-annotation-icon/type-annotation-icon.component.ts index 2e5f280b1..0bc6e7af0 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/type-annotation-icon/type-annotation-icon.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/type-annotation-icon/type-annotation-icon.component.ts @@ -30,10 +30,9 @@ export class TypeAnnotationIconComponent implements OnChanges { if (isHighlight) { this.color = this.annotation.color; - } else if (this.annotation.isSuperTypeBasedColor) { - this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.superType, this._dossierTemplateId); } else { - this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.type, this._dossierTemplateId); + const type = this.annotation.isSuperTypeBasedColor ? this.annotation.superType : this.annotation.type; + this.color = this._dictionariesMapService.getDictionaryColor(type, this._dossierTemplateId, this.annotation.isRecommendation); } this.type = 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 e941e0603..843e3ecf5 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 @@ -9,7 +9,7 @@ import { AnnotationPermissions } from '@models/file/annotation.permissions'; import { BASE_HREF } from '../../../tokens'; import { UserService } from '@services/user.service'; import { Core } from '@pdftron/webviewer'; -import { Dossier, IAddRedactionRequest, ILegalBasisChangeRequest, IRectangle, IResizeRequest } from '@red/domain'; +import { DictionaryEntryTypes, Dossier, IAddRedactionRequest, ILegalBasisChangeRequest, IRectangle, IResizeRequest } from '@red/domain'; import { toPosition } from '../../dossier/utils/pdf-calculation.utils'; import { AnnotationDrawService } from './annotation-draw.service'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; @@ -24,6 +24,7 @@ import { MatDialog } from '@angular/material/dialog'; import { FilePreviewStateService } from './file-preview-state.service'; import { PdfViewer } from './pdf-viewer.service'; import { FilePreviewDialogService } from './file-preview-dialog.service'; +import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; import Quad = Core.Math.Quad; @Injectable() @@ -41,6 +42,7 @@ export class AnnotationActionsService { private readonly _annotationDrawService: AnnotationDrawService, private readonly _activeDossiersService: ActiveDossiersService, private readonly _screenStateService: FilePreviewStateService, + private readonly _dictionariesMapService: DictionariesMapService, ) {} private get _dossier(): Dossier { @@ -223,6 +225,7 @@ export class AnnotationActionsService { this._permissionsService.isApprover(dossier), this._userService.currentUser, annotation, + this._dictionariesMapService.get(dossier.dossierTemplateId), ), })); @@ -500,13 +503,17 @@ export class AnnotationActionsService { ) { $event?.stopPropagation(); - const falsePositiveRequest: IAddRedactionRequest = {}; - falsePositiveRequest.reason = annotation.id; - falsePositiveRequest.value = text; - falsePositiveRequest.type = 'false_positive'; - falsePositiveRequest.positions = annotation.positions; - falsePositiveRequest.addToDictionary = true; - falsePositiveRequest.comment = { text: 'False Positive' }; + const falsePositiveRequest: IAddRedactionRequest = { + reason: annotation.id, + value: text, + type: annotation.type, + positions: annotation.positions, + addToDictionary: true, + comment: { text: 'False Positive' }, + dictionaryEntryType: annotation.isRecommendation + ? DictionaryEntryTypes.FALSE_RECOMMENDATION + : DictionaryEntryTypes.FALSE_POSITIVE, + }; const { dossierId, fileId } = this._screenStateService; this._processObsAndEmit( diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotation-draw.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotation-draw.service.ts index 37a7dc010..a73ef55e3 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotation-draw.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotation-draw.service.ts @@ -46,9 +46,11 @@ export class AnnotationDrawService { switch (superType) { case SuperTypes.Hint: case SuperTypes.Redaction: - case SuperTypes.Recommendation: color = this._dictionariesMapService.getDictionaryColor(dictionary, this._state.dossierTemplateId); break; + case SuperTypes.Recommendation: + color = this._dictionariesMapService.getDictionaryColor(dictionary, this._state.dossierTemplateId, true); + break; case SuperTypes.Skipped: color = this._dictionariesMapService.getDictionaryColor(superType, this._state.dossierTemplateId); break; 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 8050b4264..4dfa09fe4 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 @@ -42,7 +42,7 @@
- + {{ selectDictionary.label | translate }} {{ dictionary.label }} 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 08af3a52f..4744e5dc0 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,5 +1,5 @@ -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core'; -import { Debounce, IconButtonTypes, List } from '@iqser/common-ui'; +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, Dossier, DossierTemplate } from '@red/domain'; @@ -47,6 +47,8 @@ export class DictionaryManagerComponent implements OnChanges { constructor( private readonly _dictionaryService: DictionaryService, private readonly _dictionariesMapService: DictionariesMapService, + private readonly _loadingService: LoadingService, + private readonly _changeRef: ChangeDetectorRef, readonly activeDossiersService: ActiveDossiersService, readonly dossierTemplatesService: DossierTemplatesService, ) {} @@ -60,7 +62,7 @@ export class DictionaryManagerComponent implements OnChanges { set dossierTemplate(value) { this._dossierTemplate = value; this.dictionaries = this._dictionaries; - this._dictionary = this.selectDictionary; + this._compareDictionary = this.selectDictionary; this.showDiffEditor = false; } @@ -87,14 +89,14 @@ export class DictionaryManagerComponent implements OnChanges { }); } - private _dictionary = this.selectDictionary; + private _compareDictionary = this.selectDictionary; - get dictionary() { - return this._dictionary; + get compareDictionary() { + return this._compareDictionary; } - set dictionary(dictionary: Dictionary) { - this._dictionary = dictionary; + set compareDictionary(dictionary: Dictionary) { + this._compareDictionary = dictionary; if (dictionary.label === this.selectDictionary.label) { this.showDiffEditor = false; @@ -102,7 +104,8 @@ export class DictionaryManagerComponent implements OnChanges { return; } const entries: List = - this._dictionary.entries ?? this._dictionariesMapService.get(this._dictionary.dossierTemplateId, this._dictionary.type).entries; + this._compareDictionary.entries ?? + this._dictionariesMapService.get(this._compareDictionary.dossierTemplateId, this._compareDictionary.type).entries; if (entries.length) { this.diffEditorText = this._toString([...entries]); @@ -110,17 +113,20 @@ export class DictionaryManagerComponent implements OnChanges { return; } + this._loadingService.start(); firstValueFrom( - this._dictionaryService.getForType(this._dictionary.dossierTemplateId, this._dictionary.type).pipe( - tap(values => (this._dictionary.entries = [...values.entries] ?? [])), + this._dictionaryService.getForType(this._compareDictionary.dossierTemplateId, this._compareDictionary.type).pipe( + tap(values => (this._compareDictionary.entries = [...values.entries] ?? [])), catchError(() => { - this._dictionary.entries = []; + this._compareDictionary.entries = []; return of({}); }), ), ).then(() => { - this.diffEditorText = this._toString([...this._dictionary.entries]); + this.diffEditorText = this._toString([...this._compareDictionary.entries]); this.showDiffEditor = true; + this._changeRef.markForCheck(); + this._loadingService.stop(); }); } @@ -137,7 +143,7 @@ export class DictionaryManagerComponent implements OnChanges { get optionNotSelected() { if (this.filterByDossierTemplate) { - return this.selectDictionary.label === this._dictionary.label; + return this.selectDictionary.label === this._compareDictionary.label; } return this.dossier.dossierName === this.selectDossier.dossierName; } diff --git a/apps/red-ui/src/app/services/entity-services/dictionaries-map.service.ts b/apps/red-ui/src/app/services/entity-services/dictionaries-map.service.ts index 7f09cbf9f..f828d0326 100644 --- a/apps/red-ui/src/app/services/entity-services/dictionaries-map.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dictionaries-map.service.ts @@ -14,7 +14,18 @@ export class DictionariesMapService extends EntitiesMapService type: string, dossierId: string, showToast = true, + dictionaryEntryType = DictionaryEntryTypes.ENTRY, ): Observable { let entriesToAdd = []; entries.forEach(currentEntry => { @@ -156,13 +157,13 @@ export class DictionaryService extends EntitiesService // can add at least 1 - block UI let obs: Observable; if (entriesToAdd.length > 0) { - obs = this._addEntry(entriesToAdd, dossierTemplateId, type, dossierId, true); + obs = this._addEntries(entriesToAdd, dossierTemplateId, type, dictionaryEntryType, dossierId); } else { - obs = this._deleteEntries(initialEntries, dossierTemplateId, type, dossierId); + obs = this._deleteEntries(initialEntries, dossierTemplateId, type, dictionaryEntryType, dossierId); } return obs.pipe( - switchMap(dictionary => this._dossierTemplateStatsService.getFor([dossierTemplateId]).pipe(mapTo(dictionary))), + switchMap(dictionary => this._dossierTemplateStatsService.getFor([dossierTemplateId]).pipe(map(() => dictionary))), tap( () => { if (showToast) { @@ -216,7 +217,7 @@ export class DictionaryService extends EntitiesService const virtualTypes$: Observable = this.getColors(dossierTemplateId).pipe( tap(colors => { for (const key of Object.keys(colors)) { - const color = colors[key]; + const color: string = colors[key]; try { const rgbValue = hexToRgb(color); if (!rgbValue) { @@ -286,19 +287,20 @@ export class DictionaryService extends EntitiesService * Add dictionary entries with entry type. */ @Validate() - private _addEntry( - @RequiredParam() body: List, - @RequiredParam() dossierTemplateId: string, - @RequiredParam() type: string, - dossierId?: string, - removeCurrent?: boolean, + private _addEntries( + entries: List, + dossierTemplateId: string, + type: string, + dictionaryEntryType: DictionaryEntryType, + dossierId: string, ) { const queryParams: List = [ { key: 'dossierId', value: dossierId }, - { key: 'removeCurrent', value: removeCurrent }, + { key: 'dictionaryEntryType', value: dictionaryEntryType }, + { key: 'removeCurrent', value: true }, ]; const url = `${this._defaultModelPath}/${type}/${dossierTemplateId}`; - return this._post(body, url, queryParams); + return this._post(entries, url, queryParams); } /** @@ -306,13 +308,16 @@ export class DictionaryService extends EntitiesService */ @Validate() private _deleteEntries( - @RequiredParam() body: List, - @RequiredParam() dossierTemplateId: string, - @RequiredParam() type: string, - @RequiredParam() dossierId?: string, + entries: List, + dossierTemplateId: string, + type: string, + dictionaryEntryType: DictionaryEntryType, + dossierId: string, ) { - const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined; + const queryParams = dossierId + ? [{ key: 'dossierId', value: dossierId }] + : [{ key: 'dictionaryEntryType', value: dictionaryEntryType }]; const url = `${this._defaultModelPath}/delete/${type}/${dossierTemplateId}`; - return this._post(body, url, queryParams); + return this._post(entries, url, queryParams); } } diff --git a/apps/red-ui/src/app/services/manual-annotation.service.ts b/apps/red-ui/src/app/services/manual-annotation.service.ts index fece80bf6..f3294ed5c 100644 --- a/apps/red-ui/src/app/services/manual-annotation.service.ts +++ b/apps/red-ui/src/app/services/manual-annotation.service.ts @@ -101,14 +101,15 @@ export class ManualAnnotationService extends GenericService } addRecommendation(annotation: AnnotationWrapper, dossierId: string, fileId: string, comment = { text: 'Accepted Recommendation' }) { - const manualRedactionEntry: IAddRedactionRequest = {}; - manualRedactionEntry.addToDictionary = true; - // set the ID as reason, so we can hide the suggestion - manualRedactionEntry.reason = annotation.annotationId; - manualRedactionEntry.value = annotation.value; - manualRedactionEntry.positions = annotation.positions; - manualRedactionEntry.type = annotation.recommendationType; - manualRedactionEntry.comment = comment; + const manualRedactionEntry: IAddRedactionRequest = { + addToDictionary: true, + // set the ID as reason, so we can hide the suggestion + reason: annotation.annotationId, + value: annotation.value, + positions: annotation.positions, + type: annotation.recommendationType, + comment: comment, + }; return this.addAnnotation(manualRedactionEntry, dossierId, fileId); } diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 684210fa7..d8966dc0b 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -1,7 +1,7 @@ { "ADMIN_CONTACT_NAME": null, "ADMIN_CONTACT_URL": null, - "API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1", + "API_URL": "https://dev-05.iqser.cloud/redaction-gateway-v1", "APP_NAME": "RedactManager", "AUTO_READ_TIME": 3, "BACKEND_APP_VERSION": "4.4.40", @@ -17,7 +17,7 @@ "MAX_RETRIES_ON_SERVER_ERROR": 3, "OAUTH_CLIENT_ID": "redaction", "OAUTH_IDP_HINT": null, - "OAUTH_URL": "https://dev-08.iqser.cloud/auth/realms/redaction", + "OAUTH_URL": "https://dev-05.iqser.cloud/auth/realms/redaction", "RECENT_PERIOD_IN_HOURS": 24, "SELECTION_MODE": "structural", "MANUAL_BASE_URL": "https://docs.redactmanager.com/preview" diff --git a/libs/red-domain/src/lib/dictionaries/dictionary.model.ts b/libs/red-domain/src/lib/dictionaries/dictionary.model.ts index 665bad84c..f61935dfb 100644 --- a/libs/red-domain/src/lib/dictionaries/dictionary.model.ts +++ b/libs/red-domain/src/lib/dictionaries/dictionary.model.ts @@ -6,7 +6,6 @@ export class Dictionary extends Entity implements IDictionary { readonly caseInsensitive: boolean; readonly description: string; readonly dossierTemplateId?: string; - entries: List; readonly hexColor?: string; readonly recommendationHexColor?: string; readonly hint: boolean; @@ -14,23 +13,31 @@ export class Dictionary extends Entity implements IDictionary { readonly rank?: number; readonly recommendation: boolean; readonly type: string; - readonly typeId: string; + readonly typeId?: string; + readonly hasDictionary?: boolean; - constructor(dictionary: IDictionary, readonly virtual = false) { - super(dictionary); - this.addToDictionaryAction = !!dictionary.addToDictionaryAction; - this.caseInsensitive = !!dictionary.caseInsensitive; - this.description = dictionary.description ?? ''; - this.dossierTemplateId = dictionary.dossierTemplateId; - this.entries = dictionary.entries ?? []; - this.hexColor = dictionary.hexColor; - this.recommendationHexColor = dictionary.recommendationHexColor; - this.hint = !!dictionary.hint; - this.label = dictionary.label ?? dictionary.type; - this.rank = dictionary.rank; - this.recommendation = !!dictionary.recommendation; - this.type = dictionary.type; - this.typeId = dictionary.typeId; + entries: List; + falsePositiveEntries: List; + falseRecommendationEntries: List; + + constructor(entity: IDictionary, readonly virtual = false) { + super(entity); + this.addToDictionaryAction = !!entity.addToDictionaryAction; + this.caseInsensitive = !!entity.caseInsensitive; + this.description = entity.description ?? ''; + this.dossierTemplateId = entity.dossierTemplateId; + this.entries = entity.entries ?? []; + this.falsePositiveEntries = entity.falsePositiveEntries ?? []; + this.falseRecommendationEntries = entity.falseRecommendationEntries ?? []; + this.hexColor = entity.hexColor; + this.recommendationHexColor = entity.recommendationHexColor; + this.hint = !!entity.hint; + this.label = entity.label ?? entity.type; + this.rank = entity.rank; + this.recommendation = !!entity.recommendation; + this.type = entity.type; + this.typeId = entity.typeId; + this.hasDictionary = entity.hasDictionary; } get id(): string { diff --git a/libs/red-domain/src/lib/dictionaries/dictionary.ts b/libs/red-domain/src/lib/dictionaries/dictionary.ts index f5827eb91..5f0e6b205 100644 --- a/libs/red-domain/src/lib/dictionaries/dictionary.ts +++ b/libs/red-domain/src/lib/dictionaries/dictionary.ts @@ -29,6 +29,8 @@ export interface IDictionary { * The list of dictionary entries of an entry type. */ readonly entries?: List; + readonly falsePositiveEntries?: List; + readonly falseRecommendationEntries?: List; /** * The value of color must be a correct hex color */ @@ -51,4 +53,6 @@ export interface IDictionary { readonly recommendation?: boolean; readonly recommendationHexColor?: string; + + readonly hasDictionary?: boolean; } diff --git a/libs/red-domain/src/lib/files/types.ts b/libs/red-domain/src/lib/files/types.ts index dae596fff..9ce6398e3 100644 --- a/libs/red-domain/src/lib/files/types.ts +++ b/libs/red-domain/src/lib/files/types.ts @@ -39,6 +39,7 @@ export const isProcessingStatuses: List = [ ProcessingFileStatuses.INDEXING, ProcessingFileStatuses.PROCESSING, ProcessingFileStatuses.ANALYSE, + ProcessingFileStatuses.FULL_PROCESSING, ] as const; export const isFullProcessingStatuses: List = [ diff --git a/libs/red-domain/src/lib/redaction-log/add-redaction.request.ts b/libs/red-domain/src/lib/redaction-log/add-redaction.request.ts index 40040a583..da708bad8 100644 --- a/libs/red-domain/src/lib/redaction-log/add-redaction.request.ts +++ b/libs/red-domain/src/lib/redaction-log/add-redaction.request.ts @@ -1,9 +1,11 @@ import { IRectangle } from '../geometry'; import { List } from '@iqser/common-ui'; +import { DictionaryEntryType } from './dictionary-entry-types'; export interface IAddRedactionRequest { addToDictionary?: boolean; addToDossierDictionary?: boolean; + dictionaryEntryType?: DictionaryEntryType; comment?: { text: string }; legalBasis?: string; positions?: List; diff --git a/libs/red-domain/src/lib/redaction-log/dictionary-entry-types.ts b/libs/red-domain/src/lib/redaction-log/dictionary-entry-types.ts new file mode 100644 index 000000000..ca59c6ce8 --- /dev/null +++ b/libs/red-domain/src/lib/redaction-log/dictionary-entry-types.ts @@ -0,0 +1,24 @@ +import { KeysOf } from '@iqser/common-ui'; +import { Dictionary } from '../dictionaries'; + +export type DictionaryEntryType = 'ENTRY' | 'FALSE_POSITIVE' | 'FALSE_RECOMMENDATION'; + +export const DictionaryEntryTypes = { + ENTRY: 'ENTRY' as DictionaryEntryType, + FALSE_POSITIVE: 'FALSE_POSITIVE' as DictionaryEntryType, + FALSE_RECOMMENDATION: 'FALSE_RECOMMENDATION' as DictionaryEntryType, +}; + +export type DictionaryType = 'dictionary' | 'false-positive' | 'false-recommendations'; + +export const DICTIONARY_TYPE_KEY_MAP: { [key in DictionaryType]: KeysOf } = { + dictionary: 'entries', + 'false-positive': 'falsePositiveEntries', + 'false-recommendations': 'falseRecommendationEntries', +}; + +export const DICTIONARY_TO_ENTRY_TYPE_MAP: { [key in DictionaryType]: DictionaryEntryType } = { + dictionary: DictionaryEntryTypes.ENTRY, + 'false-positive': DictionaryEntryTypes.FALSE_POSITIVE, + 'false-recommendations': DictionaryEntryTypes.FALSE_RECOMMENDATION, +}; diff --git a/libs/red-domain/src/lib/redaction-log/index.ts b/libs/red-domain/src/lib/redaction-log/index.ts index 04deded25..746bdbb73 100644 --- a/libs/red-domain/src/lib/redaction-log/index.ts +++ b/libs/red-domain/src/lib/redaction-log/index.ts @@ -11,3 +11,4 @@ export * from './approve-request'; export * from './image-recategorization.request'; export * from './resize.request'; export * from './manual-change'; +export * from './dictionary-entry-types';