False positive & recommendations

This commit is contained in:
Adina Țeudan 2022-03-23 15:58:00 +02:00
parent e5686441cf
commit 52681330f5
23 changed files with 192 additions and 101 deletions

View File

@ -1,5 +1,5 @@
import { AnnotationWrapper } from './annotation.wrapper'; import { AnnotationWrapper } from './annotation.wrapper';
import { User } from '@red/domain'; import { Dictionary, User } from '@red/domain';
import { isArray } from 'lodash-es'; import { isArray } from 'lodash-es';
export class AnnotationPermissions { export class AnnotationPermissions {
@ -16,7 +16,7 @@ export class AnnotationPermissions {
canRecategorizeImage = true; canRecategorizeImage = true;
canForceHint = 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)) { if (!isArray(annotations)) {
annotations = [annotations]; annotations = [annotations];
} }
@ -35,7 +35,8 @@ export class AnnotationPermissions {
permissions.canForceRedaction = annotation.isSkipped && !annotation.isFalsePositive && !annotation.pending; permissions.canForceRedaction = annotation.isSkipped && !annotation.isFalsePositive && !annotation.pending;
permissions.canAcceptRecommendation = annotation.isRecommendation && !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.canRemoveOrSuggestToRemoveOnlyHere = (annotation.isRedacted || annotation.isHint) && !annotation.pending;
permissions.canRemoveOrSuggestToRemoveFromDictionary = permissions.canRemoveOrSuggestToRemoveFromDictionary =

View File

@ -79,7 +79,7 @@ export class AnnotationWrapper implements Record<string, unknown> {
} }
get canBeMarkedAsFalsePositive() { 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() { get isSuperTypeBasedColor() {
@ -294,7 +294,7 @@ export class AnnotationWrapper implements Record<string, unknown> {
private static _handleRecommendations(annotationWrapper: AnnotationWrapper, redactionLogEntry: RedactionLogEntry) { private static _handleRecommendations(annotationWrapper: AnnotationWrapper, redactionLogEntry: RedactionLogEntry) {
if (annotationWrapper.superType === SuperTypes.Recommendation) { if (annotationWrapper.superType === SuperTypes.Recommendation) {
annotationWrapper.recommendationType = redactionLogEntry.type.substr('recommendation_'.length); annotationWrapper.recommendationType = redactionLogEntry.type;
} }
} }

View File

@ -19,6 +19,16 @@ const routes = [
component: DictionaryScreenComponent, component: DictionaryScreenComponent,
canDeactivate: [PendingChangesGuard], canDeactivate: [PendingChangesGuard],
}, },
{
path: 'false-positive',
component: DictionaryScreenComponent,
canDeactivate: [PendingChangesGuard],
},
{
path: 'false-recommendations',
component: DictionaryScreenComponent,
canDeactivate: [PendingChangesGuard],
},
]; ];
@NgModule({ @NgModule({

View File

@ -6,6 +6,7 @@ import { LoadingService } from '@iqser/common-ui';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { BehaviorSubject, firstValueFrom } from 'rxjs'; import { BehaviorSubject, firstValueFrom } from 'rxjs';
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants'; import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants';
import { DICTIONARY_TO_ENTRY_TYPE_MAP, DICTIONARY_TYPE_KEY_MAP, DictionaryType } from '@red/domain';
@Component({ @Component({
templateUrl: './dictionary-screen.component.html', templateUrl: './dictionary-screen.component.html',
@ -17,6 +18,7 @@ export class DictionaryScreenComponent implements OnInit {
isLeavingPage = false; isLeavingPage = false;
readonly #dossierTemplateId: string; readonly #dossierTemplateId: string;
readonly #entityType: string; readonly #entityType: string;
readonly #type: DictionaryType;
@ViewChild('dictionaryManager', { static: false }) @ViewChild('dictionaryManager', { static: false })
private readonly _dictionaryManager: DictionaryManagerComponent; private readonly _dictionaryManager: DictionaryManagerComponent;
@ViewChild('fileInput') private readonly _fileInput: ElementRef; @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.#dossierTemplateId = _route.parent.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
this.#entityType = _route.parent.snapshot.paramMap.get(ENTITY_TYPE); this.#entityType = _route.parent.snapshot.paramMap.get(ENTITY_TYPE);
this.#type = this._route.snapshot.routeConfig.path as DictionaryType;
} }
get changed() { get changed() {
@ -45,7 +48,15 @@ export class DictionaryScreenComponent implements OnInit {
this._loadingService.start(); this._loadingService.start();
try { try {
await firstValueFrom( 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(); await this._loadEntries();
} catch (e) { } catch (e) {
@ -57,9 +68,8 @@ export class DictionaryScreenComponent implements OnInit {
this._loadingService.start(); this._loadingService.start();
try { try {
const data = await firstValueFrom(this._dictionaryService.getForType(this.#dossierTemplateId, this.#entityType)); const data = await firstValueFrom(this._dictionaryService.getForType(this.#dossierTemplateId, this.#entityType));
this.initialEntries$.next( const entries: string[] = data[DICTIONARY_TYPE_KEY_MAP[this.#type]];
[...data.entries].sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' })), this.initialEntries$.next([...entries].sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' })));
);
this._loadingService.stop(); this._loadingService.stop();
} catch (e) { } catch (e) {
this._loadingService.stop(); this._loadingService.stop();

View File

@ -72,7 +72,7 @@
<div class="iqser-input-group mb-14"> <div class="iqser-input-group mb-14">
<label translate="entity.info.form.technical-name"></label> <label translate="entity.info.form.technical-name"></label>
<div class="technical-name">{{ dictionary.type }}</div> <div class="technical-name">{{ entity.type }}</div>
<span class="hint" translate="entity.info.form.technical-name-hint"></span> <span class="hint" translate="entity.info.form.technical-name-hint"></span>
</div> </div>

View File

@ -18,7 +18,7 @@ import { DictionaryService } from '@services/entity-services/dictionary.service'
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class InfoComponent extends BaseFormComponent { export class InfoComponent extends BaseFormComponent {
dictionary: Dictionary; entity: Dictionary;
readonly hasHexColor$: Observable<boolean>; readonly hasHexColor$: Observable<boolean>;
readonly hasRecommendationHexColor$: Observable<boolean>; readonly hasRecommendationHexColor$: Observable<boolean>;
readonly currentUser = this._userService.currentUser; readonly currentUser = this._userService.currentUser;
@ -37,7 +37,7 @@ export class InfoComponent extends BaseFormComponent {
super(); super();
this.#dossierTemplateId = this._route.parent.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID); this.#dossierTemplateId = this._route.parent.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
const entityType = this._route.parent.snapshot.paramMap.get(ENTITY_TYPE); 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.form = this._getForm();
this.initialFormValue = this.form.getRawValue(); this.initialFormValue = this.form.getRawValue();
this.hasHexColor$ = this._colorEmpty$('hexColor'); this.hasHexColor$ = this._colorEmpty$('hexColor');
@ -71,7 +71,7 @@ export class InfoComponent extends BaseFormComponent {
private _formToObject(): IDictionary { private _formToObject(): IDictionary {
return { return {
type: this.dictionary.type, type: this.entity.type,
label: this.form.get('label').value, label: this.form.get('label').value,
caseInsensitive: !this.form.get('caseSensitive').value, caseInsensitive: !this.form.get('caseSensitive').value,
description: this.form.get('description').value, description: this.form.get('description').value,
@ -81,21 +81,22 @@ export class InfoComponent extends BaseFormComponent {
rank: this.form.get('rank').value, rank: this.form.get('rank').value,
addToDictionaryAction: this.form.get('addToDictionaryAction').value, addToDictionaryAction: this.form.get('addToDictionaryAction').value,
dossierTemplateId: this.#dossierTemplateId, dossierTemplateId: this.#dossierTemplateId,
hasDictionary: this.form.get('hasDictionary').value,
}; };
} }
private _getForm(): FormGroup { private _getForm(): FormGroup {
return this._formBuilder.group({ return this._formBuilder.group({
label: [{ value: this.dictionary.label, disabled: !this.currentUser.isAdmin }, [Validators.required, Validators.minLength(3)]], label: [{ value: this.entity.label, disabled: !this.currentUser.isAdmin }, [Validators.required, Validators.minLength(3)]],
description: [this.dictionary.description], description: [this.entity.description],
rank: [this.dictionary.rank, Validators.required], rank: [this.entity.rank, Validators.required],
hexColor: [this.dictionary.hexColor, [Validators.required, Validators.minLength(7)]], hexColor: [this.entity.hexColor, [Validators.required, Validators.minLength(7)]],
recommendationHexColor: [this.dictionary.recommendationHexColor, [Validators.required, Validators.minLength(7)]], recommendationHexColor: [this.entity.recommendationHexColor, [Validators.required, Validators.minLength(7)]],
hint: [this.dictionary.hint], hint: [this.entity.hint],
addToDictionaryAction: [this.dictionary.addToDictionaryAction], addToDictionaryAction: [this.entity.addToDictionaryAction],
caseSensitive: [!this.dictionary.caseInsensitive], caseSensitive: [!this.entity.caseInsensitive],
defaultReason: [{ value: null, disabled: true }], defaultReason: [{ value: null, disabled: true }],
hasDictionary: [false], hasDictionary: [this.entity.hasDictionary],
}); });
} }
} }

View File

@ -28,7 +28,3 @@
iqser-status-bar { iqser-status-bar {
margin-left: 2px; margin-left: 2px;
} }
.spinning-icon {
margin: 0 12px 0 11px;
}

View File

@ -10,6 +10,7 @@ import { FilePreviewStateService } from '../../services/file-preview-state.servi
import { HelpModeService, ScrollableParentView, ScrollableParentViews } from '@iqser/common-ui'; import { HelpModeService, ScrollableParentView, ScrollableParentViews } from '@iqser/common-ui';
import { PdfViewer } from '../../services/pdf-viewer.service'; import { PdfViewer } from '../../services/pdf-viewer.service';
import { FileDataService } from '../../services/file-data.service'; import { FileDataService } from '../../services/file-data.service';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
export const AnnotationButtonTypes = { export const AnnotationButtonTypes = {
dark: 'dark', dark: 'dark',
@ -41,6 +42,7 @@ export class AnnotationActionsComponent implements OnChanges {
private readonly _state: FilePreviewStateService, private readonly _state: FilePreviewStateService,
private readonly _permissionsService: PermissionsService, private readonly _permissionsService: PermissionsService,
private readonly _fileDataService: FileDataService, private readonly _fileDataService: FileDataService,
private readonly _dictionariesMapService: DictionariesMapService,
) {} ) {}
private _annotations: AnnotationWrapper[]; private _annotations: AnnotationWrapper[];
@ -131,6 +133,7 @@ export class AnnotationActionsComponent implements OnChanges {
this._permissionsService.isApprover(dossier), this._permissionsService.isApprover(dossier),
this._userService.currentUser, this._userService.currentUser,
this.annotations, this.annotations,
this._dictionariesMapService.get(dossier.dossierTemplateId),
); );
} }
} }

View File

@ -30,10 +30,9 @@ export class TypeAnnotationIconComponent implements OnChanges {
if (isHighlight) { if (isHighlight) {
this.color = this.annotation.color; this.color = this.annotation.color;
} else if (this.annotation.isSuperTypeBasedColor) {
this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.superType, this._dossierTemplateId);
} else { } 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 = this.type =

View File

@ -9,7 +9,7 @@ import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { BASE_HREF } from '../../../tokens'; import { BASE_HREF } from '../../../tokens';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { Core } from '@pdftron/webviewer'; 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 { toPosition } from '../../dossier/utils/pdf-calculation.utils';
import { AnnotationDrawService } from './annotation-draw.service'; import { AnnotationDrawService } from './annotation-draw.service';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.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 { FilePreviewStateService } from './file-preview-state.service';
import { PdfViewer } from './pdf-viewer.service'; import { PdfViewer } from './pdf-viewer.service';
import { FilePreviewDialogService } from './file-preview-dialog.service'; import { FilePreviewDialogService } from './file-preview-dialog.service';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import Quad = Core.Math.Quad; import Quad = Core.Math.Quad;
@Injectable() @Injectable()
@ -41,6 +42,7 @@ export class AnnotationActionsService {
private readonly _annotationDrawService: AnnotationDrawService, private readonly _annotationDrawService: AnnotationDrawService,
private readonly _activeDossiersService: ActiveDossiersService, private readonly _activeDossiersService: ActiveDossiersService,
private readonly _screenStateService: FilePreviewStateService, private readonly _screenStateService: FilePreviewStateService,
private readonly _dictionariesMapService: DictionariesMapService,
) {} ) {}
private get _dossier(): Dossier { private get _dossier(): Dossier {
@ -223,6 +225,7 @@ export class AnnotationActionsService {
this._permissionsService.isApprover(dossier), this._permissionsService.isApprover(dossier),
this._userService.currentUser, this._userService.currentUser,
annotation, annotation,
this._dictionariesMapService.get(dossier.dossierTemplateId),
), ),
})); }));
@ -500,13 +503,17 @@ export class AnnotationActionsService {
) { ) {
$event?.stopPropagation(); $event?.stopPropagation();
const falsePositiveRequest: IAddRedactionRequest = {}; const falsePositiveRequest: IAddRedactionRequest = {
falsePositiveRequest.reason = annotation.id; reason: annotation.id,
falsePositiveRequest.value = text; value: text,
falsePositiveRequest.type = 'false_positive'; type: annotation.type,
falsePositiveRequest.positions = annotation.positions; positions: annotation.positions,
falsePositiveRequest.addToDictionary = true; addToDictionary: true,
falsePositiveRequest.comment = { text: 'False Positive' }; comment: { text: 'False Positive' },
dictionaryEntryType: annotation.isRecommendation
? DictionaryEntryTypes.FALSE_RECOMMENDATION
: DictionaryEntryTypes.FALSE_POSITIVE,
};
const { dossierId, fileId } = this._screenStateService; const { dossierId, fileId } = this._screenStateService;
this._processObsAndEmit( this._processObsAndEmit(

View File

@ -46,9 +46,11 @@ export class AnnotationDrawService {
switch (superType) { switch (superType) {
case SuperTypes.Hint: case SuperTypes.Hint:
case SuperTypes.Redaction: case SuperTypes.Redaction:
case SuperTypes.Recommendation:
color = this._dictionariesMapService.getDictionaryColor(dictionary, this._state.dossierTemplateId); color = this._dictionariesMapService.getDictionaryColor(dictionary, this._state.dossierTemplateId);
break; break;
case SuperTypes.Recommendation:
color = this._dictionariesMapService.getDictionaryColor(dictionary, this._state.dossierTemplateId, true);
break;
case SuperTypes.Skipped: case SuperTypes.Skipped:
color = this._dictionariesMapService.getDictionaryColor(superType, this._state.dossierTemplateId); color = this._dictionariesMapService.getDictionaryColor(superType, this._state.dossierTemplateId);
break; break;

View File

@ -42,7 +42,7 @@
</div> </div>
<div class="iqser-input-group w-200 mt-0"> <div class="iqser-input-group w-200 mt-0">
<mat-select [(ngModel)]="dictionary" [disabled]="!compare || dossierTemplateIsNotSelected"> <mat-select [(ngModel)]="compareDictionary" [disabled]="!compare || dossierTemplateIsNotSelected">
<mat-option [value]="selectDictionary">{{ selectDictionary.label | translate }}</mat-option> <mat-option [value]="selectDictionary">{{ selectDictionary.label | translate }}</mat-option>
<mat-option *ngFor="let dictionary of dictionaries" [value]="dictionary"> <mat-option *ngFor="let dictionary of dictionaries" [value]="dictionary">
{{ dictionary.label }} {{ dictionary.label }}

View File

@ -1,5 +1,5 @@
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core'; import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
import { Debounce, IconButtonTypes, List } from '@iqser/common-ui'; import { Debounce, IconButtonTypes, List, LoadingService } from '@iqser/common-ui';
import { firstValueFrom, Observable, of } from 'rxjs'; import { firstValueFrom, Observable, of } from 'rxjs';
import { catchError, map, take, tap } from 'rxjs/operators'; import { catchError, map, take, tap } from 'rxjs/operators';
import { Dictionary, Dossier, DossierTemplate } from '@red/domain'; import { Dictionary, Dossier, DossierTemplate } from '@red/domain';
@ -47,6 +47,8 @@ export class DictionaryManagerComponent implements OnChanges {
constructor( constructor(
private readonly _dictionaryService: DictionaryService, private readonly _dictionaryService: DictionaryService,
private readonly _dictionariesMapService: DictionariesMapService, private readonly _dictionariesMapService: DictionariesMapService,
private readonly _loadingService: LoadingService,
private readonly _changeRef: ChangeDetectorRef,
readonly activeDossiersService: ActiveDossiersService, readonly activeDossiersService: ActiveDossiersService,
readonly dossierTemplatesService: DossierTemplatesService, readonly dossierTemplatesService: DossierTemplatesService,
) {} ) {}
@ -60,7 +62,7 @@ export class DictionaryManagerComponent implements OnChanges {
set dossierTemplate(value) { set dossierTemplate(value) {
this._dossierTemplate = value; this._dossierTemplate = value;
this.dictionaries = this._dictionaries; this.dictionaries = this._dictionaries;
this._dictionary = this.selectDictionary; this._compareDictionary = this.selectDictionary;
this.showDiffEditor = false; this.showDiffEditor = false;
} }
@ -87,14 +89,14 @@ export class DictionaryManagerComponent implements OnChanges {
}); });
} }
private _dictionary = this.selectDictionary; private _compareDictionary = this.selectDictionary;
get dictionary() { get compareDictionary() {
return this._dictionary; return this._compareDictionary;
} }
set dictionary(dictionary: Dictionary) { set compareDictionary(dictionary: Dictionary) {
this._dictionary = dictionary; this._compareDictionary = dictionary;
if (dictionary.label === this.selectDictionary.label) { if (dictionary.label === this.selectDictionary.label) {
this.showDiffEditor = false; this.showDiffEditor = false;
@ -102,7 +104,8 @@ export class DictionaryManagerComponent implements OnChanges {
return; return;
} }
const entries: List = 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) { if (entries.length) {
this.diffEditorText = this._toString([...entries]); this.diffEditorText = this._toString([...entries]);
@ -110,17 +113,20 @@ export class DictionaryManagerComponent implements OnChanges {
return; return;
} }
this._loadingService.start();
firstValueFrom( firstValueFrom(
this._dictionaryService.getForType(this._dictionary.dossierTemplateId, this._dictionary.type).pipe( this._dictionaryService.getForType(this._compareDictionary.dossierTemplateId, this._compareDictionary.type).pipe(
tap(values => (this._dictionary.entries = [...values.entries] ?? [])), tap(values => (this._compareDictionary.entries = [...values.entries] ?? [])),
catchError(() => { catchError(() => {
this._dictionary.entries = []; this._compareDictionary.entries = [];
return of({}); return of({});
}), }),
), ),
).then(() => { ).then(() => {
this.diffEditorText = this._toString([...this._dictionary.entries]); this.diffEditorText = this._toString([...this._compareDictionary.entries]);
this.showDiffEditor = true; this.showDiffEditor = true;
this._changeRef.markForCheck();
this._loadingService.stop();
}); });
} }
@ -137,7 +143,7 @@ export class DictionaryManagerComponent implements OnChanges {
get optionNotSelected() { get optionNotSelected() {
if (this.filterByDossierTemplate) { if (this.filterByDossierTemplate) {
return this.selectDictionary.label === this._dictionary.label; return this.selectDictionary.label === this._compareDictionary.label;
} }
return this.dossier.dossierName === this.selectDossier.dossierName; return this.dossier.dossierName === this.selectDossier.dossierName;
} }

View File

@ -14,7 +14,18 @@ export class DictionariesMapService extends EntitiesMapService<Dictionary, IDict
return this.get(dossierTemplateId, type) || this.get(dossierTemplateId, 'default'); return this.get(dossierTemplateId, type) || this.get(dossierTemplateId, 'default');
} }
getDictionaryColor(type: string, dossierTemplateId: string) { getDictionaryColor(type: string, dossierTemplateId: string, isRecommendation = false) {
return !this.get(dossierTemplateId) ? '#cccccc' : this.getDictionary(type, dossierTemplateId)?.hexColor || '#cccccc'; const defaultColor = '#CCCCCC';
if (!this.get(dossierTemplateId)) {
return defaultColor;
}
const dictionary = this.getDictionary(type, dossierTemplateId);
const colorKey = isRecommendation ? 'recommendationHexColor' : 'hexColor';
if (dictionary && dictionary[colorKey]) {
return dictionary[colorKey];
}
return defaultColor;
} }
} }

View File

@ -1,7 +1,7 @@
import { Injectable, Injector } from '@angular/core'; import { Injectable, Injector } from '@angular/core';
import { firstValueFrom, forkJoin, Observable, of, throwError } from 'rxjs'; import { firstValueFrom, forkJoin, Observable, of, throwError } from 'rxjs';
import { EntitiesService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { EntitiesService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
import { Dictionary, IColors, IDictionary, IUpdateDictionary } from '@red/domain'; import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, IColors, IDictionary, IUpdateDictionary } from '@red/domain';
import { catchError, map, mapTo, switchMap, tap } from 'rxjs/operators'; import { catchError, map, mapTo, switchMap, tap } from 'rxjs/operators';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DossierTemplateStatsService } from './dossier-template-stats.service'; import { DossierTemplateStatsService } from './dossier-template-stats.service';
@ -144,6 +144,7 @@ export class DictionaryService extends EntitiesService<Dictionary, IDictionary>
type: string, type: string,
dossierId: string, dossierId: string,
showToast = true, showToast = true,
dictionaryEntryType = DictionaryEntryTypes.ENTRY,
): Observable<unknown> { ): Observable<unknown> {
let entriesToAdd = []; let entriesToAdd = [];
entries.forEach(currentEntry => { entries.forEach(currentEntry => {
@ -156,13 +157,13 @@ export class DictionaryService extends EntitiesService<Dictionary, IDictionary>
// can add at least 1 - block UI // can add at least 1 - block UI
let obs: Observable<IDictionary>; let obs: Observable<IDictionary>;
if (entriesToAdd.length > 0) { if (entriesToAdd.length > 0) {
obs = this._addEntry(entriesToAdd, dossierTemplateId, type, dossierId, true); obs = this._addEntries(entriesToAdd, dossierTemplateId, type, dictionaryEntryType, dossierId);
} else { } else {
obs = this._deleteEntries(initialEntries, dossierTemplateId, type, dossierId); obs = this._deleteEntries(initialEntries, dossierTemplateId, type, dictionaryEntryType, dossierId);
} }
return obs.pipe( return obs.pipe(
switchMap(dictionary => this._dossierTemplateStatsService.getFor([dossierTemplateId]).pipe(mapTo(dictionary))), switchMap(dictionary => this._dossierTemplateStatsService.getFor([dossierTemplateId]).pipe(map(() => dictionary))),
tap( tap(
() => { () => {
if (showToast) { if (showToast) {
@ -216,7 +217,7 @@ export class DictionaryService extends EntitiesService<Dictionary, IDictionary>
const virtualTypes$: Observable<Dictionary[]> = this.getColors(dossierTemplateId).pipe( const virtualTypes$: Observable<Dictionary[]> = this.getColors(dossierTemplateId).pipe(
tap(colors => { tap(colors => {
for (const key of Object.keys(colors)) { for (const key of Object.keys(colors)) {
const color = colors[key]; const color: string = colors[key];
try { try {
const rgbValue = hexToRgb(color); const rgbValue = hexToRgb(color);
if (!rgbValue) { if (!rgbValue) {
@ -286,19 +287,20 @@ export class DictionaryService extends EntitiesService<Dictionary, IDictionary>
* Add dictionary entries with entry type. * Add dictionary entries with entry type.
*/ */
@Validate() @Validate()
private _addEntry( private _addEntries(
@RequiredParam() body: List, entries: List,
@RequiredParam() dossierTemplateId: string, dossierTemplateId: string,
@RequiredParam() type: string, type: string,
dossierId?: string, dictionaryEntryType: DictionaryEntryType,
removeCurrent?: boolean, dossierId: string,
) { ) {
const queryParams: List<QueryParam> = [ const queryParams: List<QueryParam> = [
{ key: 'dossierId', value: dossierId }, { key: 'dossierId', value: dossierId },
{ key: 'removeCurrent', value: removeCurrent }, { key: 'dictionaryEntryType', value: dictionaryEntryType },
{ key: 'removeCurrent', value: true },
]; ];
const url = `${this._defaultModelPath}/${type}/${dossierTemplateId}`; 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<Dictionary, IDictionary>
*/ */
@Validate() @Validate()
private _deleteEntries( private _deleteEntries(
@RequiredParam() body: List, entries: List,
@RequiredParam() dossierTemplateId: string, dossierTemplateId: string,
@RequiredParam() type: string, type: string,
@RequiredParam() dossierId?: 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}`; const url = `${this._defaultModelPath}/delete/${type}/${dossierTemplateId}`;
return this._post(body, url, queryParams); return this._post(entries, url, queryParams);
} }
} }

View File

@ -101,14 +101,15 @@ export class ManualAnnotationService extends GenericService<IManualAddResponse>
} }
addRecommendation(annotation: AnnotationWrapper, dossierId: string, fileId: string, comment = { text: 'Accepted Recommendation' }) { addRecommendation(annotation: AnnotationWrapper, dossierId: string, fileId: string, comment = { text: 'Accepted Recommendation' }) {
const manualRedactionEntry: IAddRedactionRequest = {}; const manualRedactionEntry: IAddRedactionRequest = {
manualRedactionEntry.addToDictionary = true; addToDictionary: true,
// set the ID as reason, so we can hide the suggestion // set the ID as reason, so we can hide the suggestion
manualRedactionEntry.reason = annotation.annotationId; reason: annotation.annotationId,
manualRedactionEntry.value = annotation.value; value: annotation.value,
manualRedactionEntry.positions = annotation.positions; positions: annotation.positions,
manualRedactionEntry.type = annotation.recommendationType; type: annotation.recommendationType,
manualRedactionEntry.comment = comment; comment: comment,
};
return this.addAnnotation(manualRedactionEntry, dossierId, fileId); return this.addAnnotation(manualRedactionEntry, dossierId, fileId);
} }

View File

@ -1,7 +1,7 @@
{ {
"ADMIN_CONTACT_NAME": null, "ADMIN_CONTACT_NAME": null,
"ADMIN_CONTACT_URL": 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", "APP_NAME": "RedactManager",
"AUTO_READ_TIME": 3, "AUTO_READ_TIME": 3,
"BACKEND_APP_VERSION": "4.4.40", "BACKEND_APP_VERSION": "4.4.40",
@ -17,7 +17,7 @@
"MAX_RETRIES_ON_SERVER_ERROR": 3, "MAX_RETRIES_ON_SERVER_ERROR": 3,
"OAUTH_CLIENT_ID": "redaction", "OAUTH_CLIENT_ID": "redaction",
"OAUTH_IDP_HINT": null, "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, "RECENT_PERIOD_IN_HOURS": 24,
"SELECTION_MODE": "structural", "SELECTION_MODE": "structural",
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview" "MANUAL_BASE_URL": "https://docs.redactmanager.com/preview"

View File

@ -6,7 +6,6 @@ export class Dictionary extends Entity<IDictionary> implements IDictionary {
readonly caseInsensitive: boolean; readonly caseInsensitive: boolean;
readonly description: string; readonly description: string;
readonly dossierTemplateId?: string; readonly dossierTemplateId?: string;
entries: List;
readonly hexColor?: string; readonly hexColor?: string;
readonly recommendationHexColor?: string; readonly recommendationHexColor?: string;
readonly hint: boolean; readonly hint: boolean;
@ -14,23 +13,31 @@ export class Dictionary extends Entity<IDictionary> implements IDictionary {
readonly rank?: number; readonly rank?: number;
readonly recommendation: boolean; readonly recommendation: boolean;
readonly type: string; readonly type: string;
readonly typeId: string; readonly typeId?: string;
readonly hasDictionary?: boolean;
constructor(dictionary: IDictionary, readonly virtual = false) { entries: List;
super(dictionary); falsePositiveEntries: List;
this.addToDictionaryAction = !!dictionary.addToDictionaryAction; falseRecommendationEntries: List;
this.caseInsensitive = !!dictionary.caseInsensitive;
this.description = dictionary.description ?? ''; constructor(entity: IDictionary, readonly virtual = false) {
this.dossierTemplateId = dictionary.dossierTemplateId; super(entity);
this.entries = dictionary.entries ?? []; this.addToDictionaryAction = !!entity.addToDictionaryAction;
this.hexColor = dictionary.hexColor; this.caseInsensitive = !!entity.caseInsensitive;
this.recommendationHexColor = dictionary.recommendationHexColor; this.description = entity.description ?? '';
this.hint = !!dictionary.hint; this.dossierTemplateId = entity.dossierTemplateId;
this.label = dictionary.label ?? dictionary.type; this.entries = entity.entries ?? [];
this.rank = dictionary.rank; this.falsePositiveEntries = entity.falsePositiveEntries ?? [];
this.recommendation = !!dictionary.recommendation; this.falseRecommendationEntries = entity.falseRecommendationEntries ?? [];
this.type = dictionary.type; this.hexColor = entity.hexColor;
this.typeId = dictionary.typeId; 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 { get id(): string {

View File

@ -29,6 +29,8 @@ export interface IDictionary {
* The list of dictionary entries of an entry type. * The list of dictionary entries of an entry type.
*/ */
readonly entries?: List; readonly entries?: List;
readonly falsePositiveEntries?: List;
readonly falseRecommendationEntries?: List;
/** /**
* The value of color must be a correct hex color * The value of color must be a correct hex color
*/ */
@ -51,4 +53,6 @@ export interface IDictionary {
readonly recommendation?: boolean; readonly recommendation?: boolean;
readonly recommendationHexColor?: string; readonly recommendationHexColor?: string;
readonly hasDictionary?: boolean;
} }

View File

@ -39,6 +39,7 @@ export const isProcessingStatuses: List<ProcessingFileStatus> = [
ProcessingFileStatuses.INDEXING, ProcessingFileStatuses.INDEXING,
ProcessingFileStatuses.PROCESSING, ProcessingFileStatuses.PROCESSING,
ProcessingFileStatuses.ANALYSE, ProcessingFileStatuses.ANALYSE,
ProcessingFileStatuses.FULL_PROCESSING,
] as const; ] as const;
export const isFullProcessingStatuses: List<ProcessingFileStatus> = [ export const isFullProcessingStatuses: List<ProcessingFileStatus> = [

View File

@ -1,9 +1,11 @@
import { IRectangle } from '../geometry'; import { IRectangle } from '../geometry';
import { List } from '@iqser/common-ui'; import { List } from '@iqser/common-ui';
import { DictionaryEntryType } from './dictionary-entry-types';
export interface IAddRedactionRequest { export interface IAddRedactionRequest {
addToDictionary?: boolean; addToDictionary?: boolean;
addToDossierDictionary?: boolean; addToDossierDictionary?: boolean;
dictionaryEntryType?: DictionaryEntryType;
comment?: { text: string }; comment?: { text: string };
legalBasis?: string; legalBasis?: string;
positions?: List<IRectangle>; positions?: List<IRectangle>;

View File

@ -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> } = {
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,
};

View File

@ -11,3 +11,4 @@ export * from './approve-request';
export * from './image-recategorization.request'; export * from './image-recategorization.request';
export * from './resize.request'; export * from './resize.request';
export * from './manual-change'; export * from './manual-change';
export * from './dictionary-entry-types';