further fixes for state handling of annotations

This commit is contained in:
Timo 2021-01-08 18:15:21 +02:00
parent 57a9f86fd0
commit 92d3b866ce
9 changed files with 227 additions and 215 deletions

View File

@ -7,6 +7,7 @@ import { Observable } from 'rxjs';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { AddRedactionRequest } from '@redaction/red-ui-http'; import { AddRedactionRequest } from '@redaction/red-ui-http';
import { getFirstRelevantTextPart } from '../../utils/functions'; import { getFirstRelevantTextPart } from '../../utils/functions';
import { AnnotationPermissions } from '../../screens/file/model/annotation.permissions';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -20,39 +21,6 @@ export class AnnotationActionsService {
private readonly _dialogService: DialogService private readonly _dialogService: DialogService
) {} ) {}
public canAcceptSuggestion(annotation: AnnotationWrapper): boolean {
return this._permissionsService.isManagerAndOwner() && (annotation.isSuggestion || annotation.isDeclinedSuggestion);
}
public canRejectSuggestion(annotation: AnnotationWrapper): boolean {
// i can reject whatever i may not undo
return !this.canUndoAnnotation(annotation) && this.canAcceptSuggestion(annotation) && !annotation.isDeclinedSuggestion;
}
public canDirectlySuggestToRemoveAnnotation(annotation: AnnotationWrapper): boolean {
return (
// annotation.isHint || // HINTS CAN NO LONGER BE REMOVED DIRECTLY ONLY VIA DICTIONARY ACTION
annotation.isManualRedaction && this._permissionsService.isManagerAndOwner() && !this.canUndoAnnotation(annotation) && !annotation.isRecommendation
);
}
public requiresSuggestionRemoveMenu(annotation: AnnotationWrapper): boolean {
return (annotation.isRedacted || annotation.isIgnored) && !annotation.isRecommendation;
}
public canConvertRecommendationToAnnotation(annotation: AnnotationWrapper): boolean {
// recommendations that have not already been turned into a suggestion
return annotation.isRecommendation && !annotation.isConvertedRecommendation;
}
public canUndoAnnotation(annotation: AnnotationWrapper): boolean {
// suggestions of current user can be undone
const isSuggestionOfCurrentUser = annotation.isSuggestion && annotation.userId === this._permissionsService.currentUserId;
// or any performed manual actions and you are the manager, provided that it is not a suggestion
const isActionOfManger = this._permissionsService.isManagerAndOwner() && annotation.userId === this._permissionsService.currentUserId;
return isSuggestionOfCurrentUser || isActionOfManger;
}
public acceptSuggestion($event: MouseEvent, annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) { public acceptSuggestion($event: MouseEvent, annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) {
$event?.stopPropagation(); $event?.stopPropagation();
console.log(annotation.isModifyDictionary); console.log(annotation.isModifyDictionary);
@ -117,7 +85,9 @@ export class AnnotationActionsService {
public getViewerAvailableActions(annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>): {}[] { public getViewerAvailableActions(annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>): {}[] {
const availableActions = []; const availableActions = [];
if (this.canConvertRecommendationToAnnotation(annotation)) { const annotationPermissions = AnnotationPermissions.forUser(this._permissionsService.currentUser, annotation);
if (annotationPermissions.canAcceptRecommendation) {
availableActions.push({ availableActions.push({
type: 'actionButton', type: 'actionButton',
img: '/assets/icons/general/check-alt.svg', img: '/assets/icons/general/check-alt.svg',
@ -130,7 +100,7 @@ export class AnnotationActionsService {
}); });
} }
if (this.canAcceptSuggestion(annotation)) { if (annotationPermissions.canAcceptSuggestion) {
availableActions.push({ availableActions.push({
type: 'actionButton', type: 'actionButton',
img: '/assets/icons/general/check-alt.svg', img: '/assets/icons/general/check-alt.svg',
@ -143,7 +113,7 @@ export class AnnotationActionsService {
}); });
} }
if (this.canUndoAnnotation(annotation)) { if (annotationPermissions.canUndo) {
availableActions.push({ availableActions.push({
type: 'actionButton', type: 'actionButton',
img: '/assets/icons/general/undo.svg', img: '/assets/icons/general/undo.svg',
@ -156,7 +126,7 @@ export class AnnotationActionsService {
}); });
} }
if (this.canRejectSuggestion(annotation)) { if (annotationPermissions.canRejectSuggestion) {
availableActions.push({ availableActions.push({
type: 'actionButton', type: 'actionButton',
img: '/assets/icons/general/close.svg', img: '/assets/icons/general/close.svg',
@ -169,33 +139,20 @@ export class AnnotationActionsService {
}); });
} }
if (this.canDirectlySuggestToRemoveAnnotation(annotation)) { if (annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere) {
availableActions.push({ availableActions.push({
type: 'actionButton', type: 'actionButton',
img: '/assets/icons/general/trash.svg', img: '/assets/icons/general/close.svg',
title: this._translateService.instant('annotation-actions.suggest-remove-annotation'), title: this._translateService.instant('annotation-actions.suggest-remove-annotation'),
onClick: () => { onClick: () => {
this._ngZone.run(() => { this._ngZone.run(() => {
this.suggestRemoveAnnotation(null, annotation, true, annotationsChanged); this.suggestRemoveAnnotation(null, annotation, false, annotationsChanged);
}); });
} }
}); });
} }
if (this.requiresSuggestionRemoveMenu(annotation)) { if (annotationPermissions.canRemoveOrSuggestToRemoveFromDictionary) {
if (!annotation.isIgnored) {
availableActions.push({
type: 'actionButton',
img: '/assets/icons/general/close.svg',
title: this._translateService.instant('annotation-actions.remove-annotation.only-here'),
onClick: () => {
this._ngZone.run(() => {
this.suggestRemoveAnnotation(null, annotation, false, annotationsChanged);
});
}
});
}
availableActions.push({ availableActions.push({
type: 'actionButton', type: 'actionButton',
img: '/assets/icons/general/trash.svg', img: '/assets/icons/general/trash.svg',
@ -206,19 +163,19 @@ export class AnnotationActionsService {
}); });
} }
}); });
}
if (annotation.canBeMarkedAsFalsePositive) { if (annotationPermissions.canMarkAsFalsePositive) {
availableActions.push({ availableActions.push({
type: 'actionButton', type: 'actionButton',
img: '/assets/icons/general/thumb-down.svg', img: '/assets/icons/general/thumb-down.svg',
title: this._translateService.instant('annotation-actions.remove-annotation.false-positive'), title: this._translateService.instant('annotation-actions.remove-annotation.false-positive'),
onClick: () => { onClick: () => {
this._ngZone.run(() => { this._ngZone.run(() => {
this.markAsFalsePositive(null, annotation, annotationsChanged); this.markAsFalsePositive(null, annotation, annotationsChanged);
}); });
} }
}); });
}
} }
return availableActions; return availableActions;

View File

@ -2,7 +2,7 @@
<redaction-circle-button <redaction-circle-button
(action)="annotationActionsService.convertRecommendationToAnnotation($event, annotation, annotationsChanged)" (action)="annotationActionsService.convertRecommendationToAnnotation($event, annotation, annotationsChanged)"
type="dark-bg" type="dark-bg"
*ngIf="annotationActionsService.canConvertRecommendationToAnnotation(annotation)" *ngIf="annotationPermissions.canAcceptRecommendation"
tooltipPosition="before" tooltipPosition="before"
tooltip="annotation-actions.accept-recommendation.label" tooltip="annotation-actions.accept-recommendation.label"
icon="red:check-alt" icon="red:check-alt"
@ -12,7 +12,7 @@
<redaction-circle-button <redaction-circle-button
(action)="annotationActionsService.acceptSuggestion($event, annotation, annotationsChanged)" (action)="annotationActionsService.acceptSuggestion($event, annotation, annotationsChanged)"
type="dark-bg" type="dark-bg"
*ngIf="annotationActionsService.canAcceptSuggestion(annotation)" *ngIf="annotationPermissions.canAcceptSuggestion"
tooltipPosition="before" tooltipPosition="before"
tooltip="annotation-actions.accept-suggestion.label" tooltip="annotation-actions.accept-suggestion.label"
icon="red:check-alt" icon="red:check-alt"
@ -21,7 +21,7 @@
<redaction-circle-button <redaction-circle-button
(action)="annotationActionsService.undoDirectAction($event, annotation, annotationsChanged)" (action)="annotationActionsService.undoDirectAction($event, annotation, annotationsChanged)"
*ngIf="annotationActionsService.canUndoAnnotation(annotation)" *ngIf="annotationPermissions.canUndo"
type="dark-bg" type="dark-bg"
icon="red:undo" icon="red:undo"
tooltipPosition="before" tooltipPosition="before"
@ -33,24 +33,24 @@
(action)="annotationActionsService.rejectSuggestion($event, annotation, annotationsChanged)" (action)="annotationActionsService.rejectSuggestion($event, annotation, annotationsChanged)"
type="dark-bg" type="dark-bg"
icon="red:close" icon="red:close"
*ngIf="annotationActionsService.canRejectSuggestion(annotation)" *ngIf="annotationPermissions.canRejectSuggestion"
tooltipPosition="before" tooltipPosition="before"
tooltip="annotation-actions.reject-suggestion" tooltip="annotation-actions.reject-suggestion"
> >
</redaction-circle-button> </redaction-circle-button>
<redaction-circle-button <redaction-circle-button
(action)="annotationActionsService.suggestRemoveAnnotation($event, annotation, true, annotationsChanged)" (action)="annotationActionsService.suggestRemoveAnnotation($event, annotation, false, annotationsChanged)"
type="dark-bg" type="dark-bg"
icon="red:trash" icon="red:trash"
*ngIf="annotationActionsService.canDirectlySuggestToRemoveAnnotation(annotation)" *ngIf="annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere && !annotationPermissions.canPerformMultipleRemoveActions"
tooltipPosition="before" tooltipPosition="before"
tooltip="annotation-actions.suggest-remove-annotation" tooltip="annotation-actions.suggest-remove-annotation"
> >
</redaction-circle-button> </redaction-circle-button>
<redaction-circle-button <redaction-circle-button
*ngIf="annotationActionsService.requiresSuggestionRemoveMenu(annotation)" *ngIf="annotationPermissions.canPerformMultipleRemoveActions"
(action)="openMenu($event)" (action)="openMenu($event)"
[class.active]="menuOpen" [class.active]="menuOpen"
[matMenuTriggerFor]="menu" [matMenuTriggerFor]="menu"
@ -62,14 +62,18 @@
</redaction-circle-button> </redaction-circle-button>
<mat-menu #menu="matMenu" (closed)="onMenuClosed()" xPosition="before"> <mat-menu #menu="matMenu" (closed)="onMenuClosed()" xPosition="before">
<div (click)="annotationActionsService.suggestRemoveAnnotation($event, annotation, true, annotationsChanged)" mat-menu-item> <div
(click)="annotationActionsService.suggestRemoveAnnotation($event, annotation, true, annotationsChanged)"
mat-menu-item
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveFromDictionary"
>
<redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="dictionaryColor"></redaction-annotation-icon> <redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="dictionaryColor"></redaction-annotation-icon>
<div [translate]="'annotation-actions.remove-annotation.remove-from-dict'"></div> <div [translate]="'annotation-actions.remove-annotation.remove-from-dict'"></div>
</div> </div>
<div <div
(click)="annotationActionsService.suggestRemoveAnnotation($event, annotation, false, annotationsChanged)" (click)="annotationActionsService.suggestRemoveAnnotation($event, annotation, false, annotationsChanged)"
mat-menu-item mat-menu-item
*ngIf="!annotation.isIgnored" *ngIf="annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere"
> >
<redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="suggestionColor"></redaction-annotation-icon> <redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="suggestionColor"></redaction-annotation-icon>
<div translate="annotation-actions.remove-annotation.only-here"></div> <div translate="annotation-actions.remove-annotation.only-here"></div>
@ -78,7 +82,7 @@
<div <div
(click)="annotationActionsService.markAsFalsePositive($event, annotation, annotationsChanged)" (click)="annotationActionsService.markAsFalsePositive($event, annotation, annotationsChanged)"
mat-menu-item mat-menu-item
*ngIf="annotation.canBeMarkedAsFalsePositive" *ngIf="annotationPermissions.canMarkAsFalsePositive"
> >
<mat-icon svgIcon="red:thumb-down" class="false-positive-icon"></mat-icon> <mat-icon svgIcon="red:thumb-down" class="false-positive-icon"></mat-icon>
<div translate="annotation-actions.remove-annotation.false-positive"></div> <div translate="annotation-actions.remove-annotation.false-positive"></div>

View File

@ -1,7 +1,8 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { AnnotationWrapper } from '../model/annotation.wrapper'; import { AnnotationWrapper } from '../model/annotation.wrapper';
import { AppStateService } from '../../../state/app-state.service'; import { AppStateService } from '../../../state/app-state.service';
import { TypeValue } from '@redaction/red-ui-http'; import { PermissionsService } from '../../../common/service/permissions.service';
import { AnnotationPermissions } from '../model/annotation.permissions';
import { AnnotationActionsService } from '../../../common/service/annotation-actions.service'; import { AnnotationActionsService } from '../../../common/service/annotation-actions.service';
@Component({ @Component({
@ -15,13 +16,17 @@ export class AnnotationActionsComponent implements OnInit {
@Output() annotationsChanged = new EventEmitter<AnnotationWrapper>(); @Output() annotationsChanged = new EventEmitter<AnnotationWrapper>();
suggestionType: TypeValue;
menuOpen: boolean; menuOpen: boolean;
annotationPermissions: AnnotationPermissions;
constructor(public appStateService: AppStateService, public annotationActionsService: AnnotationActionsService) {} constructor(
public appStateService: AppStateService,
private _permissionsService: PermissionsService,
public annotationActionsService: AnnotationActionsService
) {}
ngOnInit(): void { ngOnInit(): void {
this.suggestionType = this.appStateService.getDictionaryTypeValue('suggestion'); this.annotationPermissions = AnnotationPermissions.forUser(this._permissionsService.currentUser, this.annotation);
} }
public openMenu($event: MouseEvent) { public openMenu($event: MouseEvent) {

View File

@ -0,0 +1,36 @@
import { UserWrapper } from '../../../user/user.service';
import { AnnotationWrapper } from './annotation.wrapper';
export class AnnotationPermissions {
canUndo: boolean;
canAcceptRecommendation: boolean;
canMarkAsFalsePositive: boolean;
canRemoveOrSuggestToRemoveOnlyHere: boolean;
canRemoveOrSuggestToRemoveFromDictionary: boolean;
canAcceptSuggestion: boolean;
canRejectSuggestion: boolean;
public static forUser(user: UserWrapper, annotation: AnnotationWrapper) {
const permissions: AnnotationPermissions = new AnnotationPermissions();
permissions.canUndo = annotation.userId === user.id && annotation.isUndoableSuperType;
permissions.canAcceptRecommendation = annotation.isRecommendation;
permissions.canMarkAsFalsePositive = annotation.canBeMarkedAsFalsePositive;
permissions.canRemoveOrSuggestToRemoveOnlyHere = annotation.isRedacted;
permissions.canRemoveOrSuggestToRemoveFromDictionary = annotation.isRedacted && !annotation.isManualRedaction;
permissions.canAcceptSuggestion = user.isManager && (annotation.isSuggestion || annotation.isDeclinedSuggestion);
permissions.canRejectSuggestion = user.isManager && (annotation.isSuggestion || (annotation.isReadyForAnalysis && !permissions.canUndo));
return permissions;
}
public get canPerformMultipleRemoveActions() {
return <any>this.canMarkAsFalsePositive + <any>this.canRemoveOrSuggestToRemoveFromDictionary + <any>this.canRemoveOrSuggestToRemoveOnlyHere >= 2;
}
}

View File

@ -5,6 +5,7 @@ export class AnnotationWrapper {
superType: superType:
| 'add-dictionary' | 'add-dictionary'
| 'remove-dictionary' | 'remove-dictionary'
| 'remove-only-here'
| 'suggestion-add-dictionary' | 'suggestion-add-dictionary'
| 'suggestion-remove-dictionary' | 'suggestion-remove-dictionary'
| 'suggestion-add' | 'suggestion-add'
@ -12,8 +13,10 @@ export class AnnotationWrapper {
| 'ignore' | 'ignore'
| 'redaction' | 'redaction'
| 'manual-redaction' | 'manual-redaction'
| 'recommendation'
| 'hint' | 'hint'
| 'declined-suggestion'; | 'declined-suggestion';
dictionary: string; dictionary: string;
color: string; color: string;
comments: Comment[] = []; comments: Comment[] = [];
@ -29,18 +32,26 @@ export class AnnotationWrapper {
status: string; status: string;
dictionaryOperation: boolean; dictionaryOperation: boolean;
positions: Rectangle[]; positions: Rectangle[];
recommendation: boolean;
recommendationType: string; recommendationType: string;
pendingRecommendationAnnotationId: string;
textAfter?: string; textAfter?: string;
textBefore?: string; textBefore?: string;
constructor() {} constructor() {}
get isUndoableSuperType() {
return (
this.superType === 'add-dictionary' ||
this.superType === 'remove-dictionary' ||
this.superType === 'suggestion-add-dictionary' ||
this.superType === 'suggestion-remove-dictionary' ||
this.superType === 'suggestion-add' ||
this.superType === 'suggestion-remove'
);
}
get isDictionaryBased() { get isDictionaryBased() {
return (this.isHint || this.isRedacted) && !this.isManualRedaction; return this.isHint || this.superType === 'redaction';
} }
get descriptor() { get descriptor() {
@ -58,7 +69,7 @@ export class AnnotationWrapper {
} }
get canBeMarkedAsFalsePositive() { get canBeMarkedAsFalsePositive() {
return this.isRedacted && (this.hasTextAfter || this.hasTextBefore); return this.superType === 'redaction' && (this.hasTextAfter || this.hasTextBefore);
} }
get isSuperTypeBasedColor() { get isSuperTypeBasedColor() {
@ -86,7 +97,7 @@ export class AnnotationWrapper {
} }
get isReadyForAnalysis() { get isReadyForAnalysis() {
return this.superType === 'add-dictionary' || this.superType === 'remove-dictionary'; return this.superType === 'add-dictionary' || this.superType === 'remove-dictionary' || this.superType === 'remove-only-here';
} }
get isApproved() { get isApproved() {
@ -98,7 +109,7 @@ export class AnnotationWrapper {
} }
get isRedacted() { get isRedacted() {
return this.superType === 'redaction'; return this.superType === 'redaction' || this.superType === 'manual-redaction';
} }
get isSuggestion() { get isSuggestion() {
@ -122,11 +133,11 @@ export class AnnotationWrapper {
} }
get isRecommendation() { get isRecommendation() {
return this.recommendation; return this.superType === 'recommendation';
} }
get id() { get id() {
return this.isConvertedRecommendation ? this.pendingRecommendationAnnotationId : this.annotationId; return this.annotationId;
} }
get x() { get x() {
@ -153,61 +164,68 @@ export class AnnotationWrapper {
annotationWrapper.textAfter = redactionLogEntry.textAfter; annotationWrapper.textAfter = redactionLogEntry.textAfter;
annotationWrapper.dictionaryOperation = redactionLogEntry.dictionaryEntry; annotationWrapper.dictionaryOperation = redactionLogEntry.dictionaryEntry;
annotationWrapper.userId = redactionLogEntry.userId; annotationWrapper.userId = redactionLogEntry.userId;
annotationWrapper.content = AnnotationWrapper.createContent(redactionLogEntry); annotationWrapper.content = AnnotationWrapper._createContent(redactionLogEntry);
AnnotationWrapper._setSuperType(annotationWrapper, redactionLogEntry); AnnotationWrapper._setSuperType(annotationWrapper, redactionLogEntry);
AnnotationWrapper._handleRecommendations(annotationWrapper, redactionLogEntry); AnnotationWrapper._handleRecommendations(annotationWrapper, redactionLogEntry);
annotationWrapper.typeLabel = 'annotation-type.' + annotationWrapper.superType;
annotationWrapper.content = annotationWrapper.id;
if (annotationWrapper.dictionary === 'PII') {
annotationWrapper.content = annotationWrapper.id;
console.log(annotationWrapper, redactionLogEntry, annotationWrapper.id);
}
return annotationWrapper; return annotationWrapper;
} }
private static _handleRecommendations(annotationWrapper: AnnotationWrapper, redactionLogEntry: RedactionLogEntryWrapper) { private static _handleRecommendations(annotationWrapper: AnnotationWrapper, redactionLogEntry: RedactionLogEntryWrapper) {
annotationWrapper.recommendation = !!redactionLogEntry?.recommendation; if (annotationWrapper.superType === 'recommendation') {
if (annotationWrapper.recommendation) {
annotationWrapper.recommendationType = redactionLogEntry.type.substr('recommendation_'.length); annotationWrapper.recommendationType = redactionLogEntry.type.substr('recommendation_'.length);
if (annotationWrapper.isConvertedRecommendation) {
annotationWrapper.dictionary = annotationWrapper.recommendationType;
annotationWrapper.pendingRecommendationAnnotationId = redactionLogEntry.pendingRecommendationAnnotationId;
}
} }
} }
private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntryWrapper) { private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntryWrapper) {
if (redactionLogEntryWrapper.manual && redactionLogEntryWrapper.status === 'DECLINED') { if (redactionLogEntryWrapper.recommendation) {
annotationWrapper.superType = 'recommendation';
return;
}
if (redactionLogEntryWrapper.status === 'DECLINED') {
annotationWrapper.superType = 'declined-suggestion'; annotationWrapper.superType = 'declined-suggestion';
return; return;
} }
if (redactionLogEntryWrapper.type === 'manual') { if (annotationWrapper.dictionary === 'false_positive') {
annotationWrapper.superType = redactionLogEntryWrapper.status === 'REQUESTED' ? 'suggestion-add' : 'manual-redaction';
} else {
if (redactionLogEntryWrapper.status === 'REQUESTED') { if (redactionLogEntryWrapper.status === 'REQUESTED') {
if (redactionLogEntryWrapper.dictionaryEntry) { annotationWrapper.superType = 'suggestion-add-dictionary';
annotationWrapper.superType =
redactionLogEntryWrapper.manualRedactionType === 'ADD' ? 'suggestion-add-dictionary' : 'suggestion-remove-dictionary';
} else {
annotationWrapper.superType = redactionLogEntryWrapper.manualRedactionType === 'ADD' ? 'suggestion-add' : 'suggestion-remove';
}
} }
if (redactionLogEntryWrapper.status === 'APPROVED') { if (redactionLogEntryWrapper.status === 'APPROVED') {
annotationWrapper.superType = redactionLogEntryWrapper.manualRedactionType === 'ADD' ? 'add-dictionary' : 'remove-dictionary'; annotationWrapper.superType = 'add-dictionary';
}
} else {
if (redactionLogEntryWrapper.redacted) {
if (redactionLogEntryWrapper.type === 'manual') {
annotationWrapper.superType = redactionLogEntryWrapper.status === 'REQUESTED' ? 'suggestion-add' : 'manual-redaction';
} else {
if (redactionLogEntryWrapper.status === 'REQUESTED') {
if (redactionLogEntryWrapper.dictionaryEntry) {
annotationWrapper.superType =
redactionLogEntryWrapper.manualRedactionType === 'ADD' ? 'suggestion-add-dictionary' : 'suggestion-remove-dictionary';
} else {
annotationWrapper.superType = redactionLogEntryWrapper.manualRedactionType === 'ADD' ? 'suggestion-add' : 'suggestion-remove';
}
}
if (redactionLogEntryWrapper.status === 'APPROVED') {
if (redactionLogEntryWrapper.dictionaryEntry) {
annotationWrapper.superType = redactionLogEntryWrapper.manualRedactionType === 'ADD' ? 'add-dictionary' : 'remove-dictionary';
} else {
annotationWrapper.superType = redactionLogEntryWrapper.manualRedactionType === 'ADD' ? 'manual-redaction' : 'remove-only-here';
}
}
}
} }
} }
if (!annotationWrapper.superType) { if (!annotationWrapper.superType) {
annotationWrapper.superType = annotationWrapper.redaction ? 'redaction' : annotationWrapper.hint ? 'hint' : 'ignore'; annotationWrapper.superType = annotationWrapper.redaction ? 'redaction' : annotationWrapper.hint ? 'hint' : 'ignore';
} }
annotationWrapper.typeLabel = 'annotation-type.' + annotationWrapper.superType;
} }
private static createContent(entry: any) { private static _createContent(entry: any) {
let content = ''; let content = '';
if (entry.matchedRule) { if (entry.matchedRule) {
content += 'Rule ' + entry.matchedRule + ' matched \n\n'; content += 'Rule ' + entry.matchedRule + ' matched \n\n';

View File

@ -1,16 +1,9 @@
import { Comment, IdRemoval, ManualRedactionEntry, ManualRedactions, RedactionLog, RedactionLogEntry, TypeValue, ViewedPages } from '@redaction/red-ui-http'; import { IdRemoval, ManualRedactionEntry, ManualRedactions, RedactionLog, RedactionLogEntry, TypeValue, ViewedPages } from '@redaction/red-ui-http';
import { FileStatusWrapper } from './file-status.wrapper'; import { FileStatusWrapper } from './file-status.wrapper';
import { UserWrapper } from '../../../user/user.service'; import { UserWrapper } from '../../../user/user.service';
import { AnnotationWrapper } from './annotation.wrapper'; import { AnnotationWrapper } from './annotation.wrapper';
import { RedactionLogEntryWrapper } from './redaction-log-entry.wrapper'; import { RedactionLogEntryWrapper } from './redaction-log-entry.wrapper';
export interface AnnotationPair {
redactionLogEntry?: RedactionLogEntry;
manualRedactionEntry?: ManualRedactionEntry;
idRemoval?: IdRemoval;
comments?: Comment[];
}
export class FileDataModel { export class FileDataModel {
redactedFileData: Blob; redactedFileData: Blob;
@ -47,113 +40,102 @@ export class FileDataModel {
let result: RedactionLogEntryWrapper[] = []; let result: RedactionLogEntryWrapper[] = [];
this.redactionLog.redactionLogEntry.forEach((redactionLogEntry) => { this.redactionLog.redactionLogEntry.forEach((redactionLogEntry) => {
// copy the redactionLog Entry // false positive entries from the redaction-log need to be skipped
const wrapper: RedactionLogEntryWrapper = {}; if (redactionLogEntry.type !== 'false_positive') {
Object.assign(wrapper, redactionLogEntry); // copy the redactionLog Entry
wrapper.comments = this.manualRedactions.comments[wrapper.id]; const redactionLogEntryWrapper: RedactionLogEntryWrapper = { actionPendingReanalysis: false };
result.push(wrapper); Object.assign(redactionLogEntryWrapper, redactionLogEntry);
redactionLogEntryWrapper.comments = this.manualRedactions.comments[redactionLogEntryWrapper.id];
result.push(redactionLogEntryWrapper);
}
}); });
this.manualRedactions.entriesToAdd.forEach((manual) => { this.manualRedactions.entriesToAdd.forEach((manual) => {
let wrapper; const markedAsReasonRedactionLogEntry = result.find((r) => r.id === manual.reason);
if (this._hasAlreadyBeenProcessed(manual) && this._isAcceptedOrRejected(manual)) {
wrapper = result.find((r) => r.id === manual.id); const relevantRedactionLogEntry = result.find((r) => r.id === manual.id);
if (wrapper) {
wrapper.userId = manual.user; // a redaction-log entry is marked as a reason for another entry - hide it
} else { if (!!markedAsReasonRedactionLogEntry) {
wrapper = result.find((r) => r.id === manual.reason); markedAsReasonRedactionLogEntry.hidden = true;
// if we found it
if (wrapper) {
wrapper.userId = manual.user;
}
}
return;
} }
// normal case // an entry for this request already exists in the redactionLog
wrapper = result.find((r) => r.id === manual.id); if (!!relevantRedactionLogEntry) {
if (!wrapper) { relevantRedactionLogEntry.userId = manual.user;
// false positive and recommendation case relevantRedactionLogEntry.dictionaryEntry = manual.addToDictionary;
// if we mark Annotation N as false positive -> it's reason is the original annotations Id
// if we confirm a recommendation, it's reason is the original annotations Id
wrapper = result.find((r) => r.id === manual.reason);
// if we found it
if (wrapper) {
// it's a recommendation if it's not a false positive
wrapper.recommendation = manual.type !== 'false_positive'; // if statuses differ
if (wrapper.recommendation) { if (relevantRedactionLogEntry.status !== manual.status) {
wrapper.pendingRecommendationAnnotationId = manual.id; relevantRedactionLogEntry.actionPendingReanalysis = true;
} relevantRedactionLogEntry.status = manual.status;
wrapper.manual = true;
wrapper.manualRedactionType = 'ADD';
wrapper.status = manual.status;
} else {
const dictionary = dictionaryData[manual.type];
wrapper = {};
wrapper.id = manual.id;
wrapper.dictionaryEntry = manual.addToDictionary;
wrapper.legalBasis = manual.legalBasis;
wrapper.positions = manual.positions;
wrapper.reason = manual.reason;
wrapper.status = manual.status;
wrapper.type = manual.type;
wrapper.userId = manual.user;
wrapper.value = manual.value;
wrapper.redacted = !dictionary.hint;
wrapper.hint = dictionary.hint;
wrapper.manualRedactionType = 'ADD';
wrapper.manual = true;
wrapper.comments = this.manualRedactions.comments[wrapper.id];
result.push(wrapper);
} }
} else { } else {
wrapper.confirmed = true; // dictionary modifying requests that have been processed already updated the dictionary and should not be drawn
if (manual.addToDictionary && this._hasAlreadyBeenProcessed(manual)) {
return;
}
// no entry exists in the redaction log - create it
const dictionary = dictionaryData[manual.type];
const redactionLogEntryWrapper: RedactionLogEntryWrapper = {};
redactionLogEntryWrapper.id = manual.id;
redactionLogEntryWrapper.dictionaryEntry = manual.addToDictionary;
redactionLogEntryWrapper.legalBasis = manual.legalBasis;
redactionLogEntryWrapper.positions = manual.positions;
redactionLogEntryWrapper.reason = manual.reason;
redactionLogEntryWrapper.status = manual.status;
redactionLogEntryWrapper.type = manual.type;
redactionLogEntryWrapper.userId = manual.user;
redactionLogEntryWrapper.value = manual.value;
redactionLogEntryWrapper.redacted = !dictionary.hint;
redactionLogEntryWrapper.hint = dictionary.hint;
redactionLogEntryWrapper.manualRedactionType = 'ADD';
redactionLogEntryWrapper.manual = true;
redactionLogEntryWrapper.comments = this.manualRedactions.comments[redactionLogEntryWrapper.id];
result.push(redactionLogEntryWrapper);
} }
}); });
this.manualRedactions.idsToRemove.forEach((idToRemove) => { this.manualRedactions.idsToRemove.forEach((idToRemove) => {
let wrapper; const relevantRedactionLogEntry = result.find((r) => r.id === idToRemove.id);
if (this._hasAlreadyBeenProcessed(idToRemove) && this._isAcceptedOrRejected(idToRemove)) {
wrapper = result.find((r) => r.id === idToRemove.id); if (!relevantRedactionLogEntry) {
if (wrapper && wrapper.dictionaryEntry) { // idRemove for something that doesn't exist - skip
wrapper.manual = false;
wrapper.manualRedactionType = null;
wrapper.status = null;
}
return; return;
} } else {
relevantRedactionLogEntry.userId = idToRemove.user;
relevantRedactionLogEntry.dictionaryEntry = idToRemove.removeFromDictionary;
wrapper = result.find((r) => r.id === idToRemove.id); // if statuses differ
if (wrapper) { if (relevantRedactionLogEntry.status !== idToRemove.status) {
wrapper.manual = true; relevantRedactionLogEntry.actionPendingReanalysis = true;
wrapper.dictionaryEntry = idToRemove.removeFromDictionary; relevantRedactionLogEntry.status = idToRemove.status;
wrapper.userId = idToRemove.user; }
wrapper.status = idToRemove.status;
wrapper.manualRedactionType = 'REMOVE'; if (this._hasAlreadyBeenProcessed(idToRemove)) {
if (idToRemove.status === 'DECLINED') {
relevantRedactionLogEntry.status = null;
}
}
} }
}); });
result.forEach((id) => { result.forEach((redactionLogEntry) => {
if (id.id === '57205cfd183653d4d159dc8d07f86a4c') {
console.log(' ========= ', id);
}
});
// remove undone entriesToAdd and idsToRemove
result = result.filter((redactionLogEntry) => {
if (redactionLogEntry.manual) { if (redactionLogEntry.manual) {
if (redactionLogEntry.manualRedactionType === 'ADD') { if (redactionLogEntry.manualRedactionType === 'ADD') {
const foundManualEntry = this.manualRedactions.entriesToAdd.find((me) => me.id === redactionLogEntry.id); const foundManualEntry = this.manualRedactions.entriesToAdd.find((me) => me.id === redactionLogEntry.id);
// ADD has been undone - not yet processed
if (!foundManualEntry) { if (!foundManualEntry) {
return false; redactionLogEntry.hidden = true;
} }
} }
if (redactionLogEntry.manualRedactionType === 'REMOVE') { if (redactionLogEntry.manualRedactionType === 'REMOVE') {
const foundManualEntry = this.manualRedactions.idsToRemove.find((me) => me.id === redactionLogEntry.id); const foundManualEntry = this.manualRedactions.idsToRemove.find((me) => me.id === redactionLogEntry.id);
// REMOVE has been undone - not yet processed
if (!foundManualEntry) { if (!foundManualEntry) {
redactionLogEntry.manual = false; redactionLogEntry.manual = false;
redactionLogEntry.manualRedactionType = null; redactionLogEntry.manualRedactionType = null;
@ -161,14 +143,12 @@ export class FileDataModel {
} }
} }
} }
return true;
}); });
return result; // remove undone entriesToAdd and idsToRemove
} result = result.filter((redactionLogEntry) => !redactionLogEntry.hidden);
private _isAcceptedOrRejected(entry: ManualRedactionEntry | IdRemoval): boolean { return result;
return entry.status === 'APPROVED' || entry.status === 'DECLINED';
} }
private _hasAlreadyBeenProcessed(entry: ManualRedactionEntry | IdRemoval): boolean { private _hasAlreadyBeenProcessed(entry: ManualRedactionEntry | IdRemoval): boolean {

View File

@ -11,8 +11,6 @@ export interface RedactionLogEntryWrapper {
matchedRule?: number; matchedRule?: number;
positions?: Array<Rectangle>; positions?: Array<Rectangle>;
reason?: string; reason?: string;
recommendation?: boolean;
pendingRecommendationAnnotationId?: string;
redacted?: boolean; redacted?: boolean;
section?: string; section?: string;
sectionNumber?: number; sectionNumber?: number;
@ -21,7 +19,14 @@ export interface RedactionLogEntryWrapper {
textBefore?: string; textBefore?: string;
type?: string; type?: string;
value?: string; value?: string;
recommendation?: boolean;
recommendationAnnotationId?: string;
actionPendingReanalysis?: boolean;
hidden?: boolean;
userId?: string; userId?: string;
comments?: Comment[]; comments?: Comment[];
confirmed?: boolean;
} }

View File

@ -498,6 +498,11 @@ export class AppStateService {
type: 'remove-dictionary', type: 'remove-dictionary',
virtual: true virtual: true
}; };
dictionaryData['remove-only-here'] = {
hexColor: '#dd4d50',
type: 'remove-only-here',
virtual: true
};
// generic suggestions // generic suggestions
dictionaryData['suggestion'] = { dictionaryData['suggestion'] = {
hexColor: colors.requestAdd, hexColor: colors.requestAdd,

View File

@ -454,6 +454,8 @@
"hints": "Hint Dictionaries" "hints": "Hint Dictionaries"
}, },
"annotation-type": { "annotation-type": {
"recommendation": "Recommendation",
"remove-only-here": "Pending removal ( only here )",
"add-dictionary": "Pending add to dictionary", "add-dictionary": "Pending add to dictionary",
"remove-dictionary": "Pending remove from dictionary", "remove-dictionary": "Pending remove from dictionary",
"suggestion-add-dictionary": "Suggested dictionary add", "suggestion-add-dictionary": "Suggested dictionary add",