RED-6829: remove redactionLogEntry class
This commit is contained in:
parent
2601822a32
commit
e083fa5eb4
@ -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<string, unknown> {
|
||||
export class AnnotationWrapper implements IListable {
|
||||
[x: string]: unknown;
|
||||
|
||||
superType: SuperType;
|
||||
@ -37,7 +39,7 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
||||
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<string, unknown> {
|
||||
hasBeenForcedHint: boolean;
|
||||
hasBeenForcedRedaction: boolean;
|
||||
hasBeenRemovedByManualOverride: boolean;
|
||||
legalBasisList: ILegalBasis[] = [];
|
||||
|
||||
get searchKey(): string {
|
||||
return this.id;
|
||||
@ -240,10 +243,6 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
||||
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<string, unknown> {
|
||||
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<string, unknown> {
|
||||
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<string, unknown> {
|
||||
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<string, unknown> {
|
||||
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<string, unknown> {
|
||||
}
|
||||
}
|
||||
|
||||
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<string, unknown> {
|
||||
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<string, unknown> {
|
||||
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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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<void> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<IRecategorizationRequest> = annotations.map(({ annotationId }) => ({
|
||||
annotationId,
|
||||
const body: List<IRecategorizationRequest> = annotations.map(({ id }) => ({
|
||||
id: id,
|
||||
type,
|
||||
comment,
|
||||
}));
|
||||
|
||||
@ -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)))),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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<T>(property: (x: T) => string) {
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class FileDataService extends EntitiesService<AnnotationWrapper, AnnotationWrapper> implements OnDestroy {
|
||||
export class FileDataService extends EntitiesService<AnnotationWrapper, AnnotationWrapper> {
|
||||
missingTypes = new Set<string>();
|
||||
readonly annotations$: Observable<AnnotationWrapper[]>;
|
||||
readonly earmarks$: Observable<Map<number, AnnotationWrapper[]>>;
|
||||
protected readonly _entityClass = AnnotationWrapper;
|
||||
readonly #redactionLog$ = new Subject<IRedactionLog>();
|
||||
readonly #earmarks$ = new BehaviorSubject<Map<number, AnnotationWrapper[]>>(new Map());
|
||||
readonly #earmarks = signal<Map<number, AnnotationWrapper[]>>(new Map());
|
||||
#originalViewedPages: ViewedPage[] = [];
|
||||
readonly #subscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private readonly _state: FilePreviewStateService,
|
||||
@ -72,15 +71,16 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
||||
) {
|
||||
super();
|
||||
this.annotations$ = this.#annotations$;
|
||||
this.earmarks$ = this.#earmarks$.asObservable();
|
||||
this.#subscription = this._viewModeService.viewMode$
|
||||
this.earmarks$ = toObservable(this.#earmarks);
|
||||
this._viewModeService.viewMode$
|
||||
.pipe(
|
||||
switchMap(viewMode =>
|
||||
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<AnnotationWrapper, Annotati
|
||||
tap(() => 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<AnnotationWrapper, Annotati
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.#subscription.unsubscribe();
|
||||
}
|
||||
|
||||
setEntities(entities: AnnotationWrapper[]): void {
|
||||
// this is a light version of setEntities to skip looping too much
|
||||
// used mostly for earmarks (which are usually a lot)
|
||||
@ -149,9 +145,9 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
||||
}
|
||||
|
||||
async loadEarmarks() {
|
||||
const rawHighlights = await firstValueFrom(this._earmarksService.getEarmarks(this._state.dossierId, this._state.fileId));
|
||||
const rawHighlights = await this._earmarksService.getEarmarks(this._state.dossierId, this._state.fileId);
|
||||
const earmarks = rawHighlights.groupBy(h => h.pageNumber);
|
||||
this.#earmarks$.next(earmarks);
|
||||
this.#earmarks.set(earmarks);
|
||||
|
||||
return earmarks;
|
||||
}
|
||||
@ -203,29 +199,11 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
||||
});
|
||||
}
|
||||
|
||||
async #buildAnnotations(redactionLog: IRedactionLog, file: File) {
|
||||
const entries = await this.#convertData(redactionLog, file);
|
||||
const dictionaries = this._state.dictionaries;
|
||||
const defaultColors = this._defaultColorsService.find(this._state.dossierTemplateId);
|
||||
const annotations: AnnotationWrapper[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const pageNumber = entry.positions[0]?.page;
|
||||
const manual = entry.manualChanges?.length > 0;
|
||||
if (!manual && file.excludedPages.includes(pageNumber)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
annotations.push(AnnotationWrapper.fromData(entry, dictionaries, defaultColors));
|
||||
}
|
||||
|
||||
return annotations;
|
||||
}
|
||||
|
||||
async #buildRemovedRedactions(redactionLog: IRedactionLog, file: File): Promise<void> {
|
||||
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<AnnotationWrapper, Annotati
|
||||
) {
|
||||
entry.manualChanges.pop();
|
||||
entry.reason = null;
|
||||
filtered.push(entry);
|
||||
redactionLogEntries.push(entry);
|
||||
}
|
||||
return filtered;
|
||||
}, []);
|
||||
}
|
||||
|
||||
const annotations = await this.#buildAnnotations(redactionLogCopy, file);
|
||||
redactionLogCopy.redactionLogEntry = redactionLogEntries;
|
||||
const annotations = await this.#convertData(redactionLogCopy, file);
|
||||
this._suggestionsService.removedRedactions = annotations.filter(a => !a.isSkipped);
|
||||
}
|
||||
|
||||
@ -248,49 +226,58 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
||||
return [];
|
||||
}
|
||||
|
||||
const result: RedactionLogEntry[] = [];
|
||||
const sourceIdAnnotationIds: { [key: string]: RedactionLogEntry[] } = {};
|
||||
const annotations: AnnotationWrapper[] = [];
|
||||
const sourceIds: { [key: string]: AnnotationWrapper[] } = {};
|
||||
const dictionaries = this._state.dictionaries;
|
||||
const defaultColors = this._defaultColorsService.find(this._state.dossierTemplateId);
|
||||
let checkDictionary = true;
|
||||
|
||||
for (const redactionLogEntry of redactionLog.redactionLogEntry) {
|
||||
const changeLogValues = this.#getChangeLogValues(redactionLogEntry, file);
|
||||
if (changeLogValues.hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dictionary = dictionaries.find(dict => 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<AnnotationWrapper, Annotati
|
||||
}
|
||||
|
||||
const viableChanges = redactionLogEntry.changes?.filter(c => 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);
|
||||
|
||||
@ -67,7 +67,7 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
||||
addRecommendation(annotations: AnnotationWrapper[], dossierId: string, fileId: string, comment = { text: 'Accepted Recommendation' }) {
|
||||
const recommendations: List<IAddRedactionRequest> = annotations.map(annotation => ({
|
||||
addToDictionary: true,
|
||||
sourceId: annotation.annotationId,
|
||||
sourceId: annotation.id,
|
||||
value: annotation.value,
|
||||
reason: annotation.legalBasis ?? 'Dictionary Request',
|
||||
positions: annotation.positions,
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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<AnnotationWrapper>): List | undefined {
|
||||
|
||||
@ -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<Dictionary> = this._dictionaries;
|
||||
dictionaries: List<Dictionary> = 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<string> {
|
||||
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');
|
||||
}
|
||||
|
||||
@ -32,9 +32,9 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
* Retrieves all dictionary entries of an entry type
|
||||
*/
|
||||
|
||||
getForType(dossierTemplateId: string, type: string, dossierId?: string): Observable<IDictionary> {
|
||||
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<IDictionary, Dictionary>
|
||||
}
|
||||
|
||||
async loadDossierDictionary(dossierTemplateId: string, dossierId: string): Promise<Dictionary> {
|
||||
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({
|
||||
|
||||
@ -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<EarmarkResponse> {
|
||||
protected readonly _defaultModelPath = '';
|
||||
readonly #toaster = inject(Toaster);
|
||||
|
||||
getEarmarks(dossierId: string, fileId: string): Observable<AnnotationWrapper[]> {
|
||||
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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user