false positive impl
This commit is contained in:
parent
f871bc22b9
commit
49635013c9
@ -5,6 +5,8 @@ import { DialogService } from '../../dialogs/dialog.service';
|
||||
import { AnnotationWrapper } from '../../screens/file/model/annotation.wrapper';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { AddRedactionRequest } from '@redaction/red-ui-http';
|
||||
import { getFirstRelevantTextPart } from '../../utils/functions';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -54,12 +56,12 @@ export class AnnotationActionsService {
|
||||
}
|
||||
|
||||
public acceptSuggestion($event: MouseEvent, annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) {
|
||||
if ($event) $event.stopPropagation();
|
||||
$event?.stopPropagation();
|
||||
this._processObsAndEmit(this._manualAnnotationService.approveRequest(annotation.id, annotation.isModifyDictionary), annotation, annotationsChanged);
|
||||
}
|
||||
|
||||
public rejectSuggestion($event: MouseEvent, annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) {
|
||||
if ($event) $event.stopPropagation();
|
||||
$event?.stopPropagation();
|
||||
this._processObsAndEmit(this._manualAnnotationService.declineOrRemoveRequest(annotation), annotation, annotationsChanged);
|
||||
}
|
||||
|
||||
@ -78,19 +80,33 @@ export class AnnotationActionsService {
|
||||
});
|
||||
}
|
||||
|
||||
public markAsFalsePositive($event: MouseEvent, annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) {
|
||||
$event?.stopPropagation();
|
||||
|
||||
const falsePositiveRequest: AddRedactionRequest = {};
|
||||
falsePositiveRequest.reason = annotation.id;
|
||||
falsePositiveRequest.value = this._getFalsePositiveText(annotation);
|
||||
falsePositiveRequest.type = 'false_positive';
|
||||
falsePositiveRequest.positions = annotation.positions;
|
||||
falsePositiveRequest.addToDictionary = true;
|
||||
falsePositiveRequest.comment = { text: 'False Positive' };
|
||||
|
||||
this._processObsAndEmit(this._manualAnnotationService.addAnnotation(falsePositiveRequest), annotation, annotationsChanged);
|
||||
}
|
||||
|
||||
public undoDirectAction($event: MouseEvent, annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) {
|
||||
if ($event) $event.stopPropagation();
|
||||
$event?.stopPropagation();
|
||||
this._processObsAndEmit(this._manualAnnotationService.undoRequest(annotation), annotation, annotationsChanged);
|
||||
}
|
||||
|
||||
public convertRecommendationToAnnotation($event: any, annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) {
|
||||
if ($event) $event.stopPropagation();
|
||||
$event?.stopPropagation();
|
||||
this._processObsAndEmit(this._manualAnnotationService.addRecommendation(annotation), annotation, annotationsChanged);
|
||||
}
|
||||
|
||||
private _processObsAndEmit(obs: Observable<any>, annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) {
|
||||
obs.subscribe(
|
||||
(data) => {
|
||||
() => {
|
||||
annotationsChanged.emit(annotation);
|
||||
},
|
||||
() => {
|
||||
@ -109,7 +125,7 @@ export class AnnotationActionsService {
|
||||
title: this._translateService.instant('annotation-actions.accept-recommendation.label'),
|
||||
onClick: () => {
|
||||
this._ngZone.run(() => {
|
||||
this.undoDirectAction(null, annotation, annotationsChanged);
|
||||
this.convertRecommendationToAnnotation(null, annotation, annotationsChanged);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -167,20 +183,7 @@ export class AnnotationActionsService {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: probably need icons for these?
|
||||
|
||||
if (this.requiresSuggestionRemoveMenu(annotation)) {
|
||||
availableActions.push({
|
||||
type: 'actionButton',
|
||||
img: '/assets/icons/general/trash.svg',
|
||||
title: this._translateService.instant('annotation-actions.remove-annotation.remove-from-dict'),
|
||||
onClick: () => {
|
||||
this._ngZone.run(() => {
|
||||
this.suggestRemoveAnnotation(null, annotation, true, annotationsChanged);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!annotation.isIgnored) {
|
||||
availableActions.push({
|
||||
type: 'actionButton',
|
||||
@ -193,8 +196,45 @@ export class AnnotationActionsService {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
availableActions.push({
|
||||
type: 'actionButton',
|
||||
img: '/assets/icons/general/trash.svg',
|
||||
title: this._translateService.instant('annotation-actions.remove-annotation.remove-from-dict'),
|
||||
onClick: () => {
|
||||
this._ngZone.run(() => {
|
||||
this.suggestRemoveAnnotation(null, annotation, true, annotationsChanged);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (annotation.canBeMarkedAsFalsePositive) {
|
||||
availableActions.push({
|
||||
type: 'actionButton',
|
||||
img: '/assets/icons/general/thumb-down.svg',
|
||||
title: this._translateService.instant('annotation-actions.remove-annotation.false-positive'),
|
||||
onClick: () => {
|
||||
this._ngZone.run(() => {
|
||||
this.markAsFalsePositive(null, annotation, annotationsChanged);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return availableActions;
|
||||
}
|
||||
|
||||
private _getFalsePositiveText(annotation: AnnotationWrapper) {
|
||||
if (annotation.canBeMarkedAsFalsePositive) {
|
||||
let text;
|
||||
if (annotation.hasTextAfter) {
|
||||
text = getFirstRelevantTextPart(annotation.textAfter, 'FORWARD');
|
||||
return annotation.value + text;
|
||||
} else {
|
||||
text = getFirstRelevantTextPart(annotation.textBefore, 'BACKWARD');
|
||||
return text + annotation.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,6 +59,7 @@ export class IconsModule {
|
||||
'sort-desc',
|
||||
'status',
|
||||
'trash',
|
||||
'thumb-down',
|
||||
'template',
|
||||
'user',
|
||||
'check-alt',
|
||||
|
||||
@ -74,5 +74,14 @@
|
||||
<redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="suggestionColor"></redaction-annotation-icon>
|
||||
<div translate="annotation-actions.remove-annotation.only-here"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
(click)="annotationActionsService.markAsFalsePositive($event, annotation, annotationsChanged)"
|
||||
mat-menu-item
|
||||
*ngIf="annotation.canBeMarkedAsFalsePositive"
|
||||
>
|
||||
<mat-icon svgIcon="red:thumb-down" class="false-positive-icon"></mat-icon>
|
||||
<div translate="annotation-actions.remove-annotation.false-positive"></div>
|
||||
</div>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
||||
@ -23,3 +23,8 @@
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.false-positive-icon {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
@ -33,6 +33,20 @@ export class AnnotationWrapper {
|
||||
recommendation: boolean;
|
||||
positions: Rectangle[];
|
||||
recommendationType: string;
|
||||
textAfter?: string;
|
||||
textBefore?: string;
|
||||
|
||||
get hasTextAfter() {
|
||||
return this.textAfter && this.textAfter.trim().length > 0;
|
||||
}
|
||||
|
||||
get hasTextBefore() {
|
||||
return this.textBefore && this.textBefore.trim().length > 0;
|
||||
}
|
||||
|
||||
get canBeMarkedAsFalsePositive() {
|
||||
return this.isRedacted && (this.hasTextAfter || this.hasTextBefore);
|
||||
}
|
||||
|
||||
get isIgnored() {
|
||||
return this.superType === 'ignore';
|
||||
@ -138,6 +152,8 @@ export class AnnotationWrapper {
|
||||
annotationWrapper.positions = redactionLogEntry.positions;
|
||||
annotationWrapper.content = AnnotationWrapper.createContent(redactionLogEntry);
|
||||
annotationWrapper.status = redactionLogEntry.status;
|
||||
annotationWrapper.textBefore = redactionLogEntry.textBefore;
|
||||
annotationWrapper.textAfter = redactionLogEntry.textAfter;
|
||||
} else {
|
||||
// no redaction log entry - not yet processed
|
||||
const dictionary = dictionaryData[manualRedactionEntry.type];
|
||||
|
||||
@ -41,6 +41,11 @@ export class FileDataModel {
|
||||
pair.comments
|
||||
);
|
||||
if (annotation) {
|
||||
// skip annotations that were marked as false positive
|
||||
if (pair.manualRedactionEntry?.type === 'false_positive' && pair.redactionLogEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (annotation.isIgnored && !areDevFeaturesEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -257,18 +257,19 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
}
|
||||
});
|
||||
|
||||
this.instance.textPopup.add(<any>{
|
||||
type: 'actionButton',
|
||||
dataElement: 'add-false-positive',
|
||||
img: '/assets/icons/general/pdftron-action-false-positive.svg',
|
||||
title: this._translateService.instant(this._manualAnnotationService.getTitle('FALSE_POSITIVE')),
|
||||
onClick: () => {
|
||||
const selectedQuads = this.instance.docViewer.getSelectedTextQuads();
|
||||
const text = this.instance.docViewer.getSelectedText();
|
||||
const mre = this._getManualRedactionEntry(selectedQuads, text);
|
||||
this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(this.instance.docViewer.getSelectedTextQuads(), mre, 'FALSE_POSITIVE'));
|
||||
}
|
||||
});
|
||||
// Temporary removed false positive action
|
||||
// this.instance.textPopup.add(<any>{
|
||||
// type: 'actionButton',
|
||||
// dataElement: 'add-false-positive',
|
||||
// img: '/assets/icons/general/pdftron-action-false-positive.svg',
|
||||
// title: this._translateService.instant(this._manualAnnotationService.getTitle('FALSE_POSITIVE')),
|
||||
// onClick: () => {
|
||||
// const selectedQuads = this.instance.docViewer.getSelectedTextQuads();
|
||||
// const text = this.instance.docViewer.getSelectedText();
|
||||
// const mre = this._getManualRedactionEntry(selectedQuads, text);
|
||||
// this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(this.instance.docViewer.getSelectedTextQuads(), mre, 'FALSE_POSITIVE'));
|
||||
// }
|
||||
// });
|
||||
|
||||
this.instance.textPopup.add(<any>{
|
||||
type: 'actionButton',
|
||||
|
||||
@ -4,6 +4,7 @@ import { AnnotationWrapper } from '../model/annotation.wrapper';
|
||||
import { FilterModel } from '../../../common/filter/model/filter.model';
|
||||
import { handleCheckedValue } from '../../../common/filter/utils/filter-utils';
|
||||
import { SuperTypeSorter } from '../../../common/sorters/super-type-sorter';
|
||||
import { getFirstRelevantTextPart } from '../../../utils/functions';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
||||
@ -58,7 +58,7 @@ export class ManualAnnotationService {
|
||||
// this wraps
|
||||
// /manualRedaction/redaction/add
|
||||
// /manualRedaction/request/add
|
||||
addAnnotation(manualRedactionEntry: ManualRedactionEntry) {
|
||||
addAnnotation(manualRedactionEntry: AddRedactionRequest) {
|
||||
if (this._permissionsService.isManagerAndOwner()) {
|
||||
return this._makeRedaction(manualRedactionEntry);
|
||||
} else {
|
||||
@ -174,7 +174,7 @@ export class ManualAnnotationService {
|
||||
}
|
||||
}
|
||||
|
||||
private _makeRedactionRequest(manualRedactionEntry: ManualRedactionEntry) {
|
||||
private _makeRedactionRequest(manualRedactionEntry: AddRedactionRequest) {
|
||||
return this._manualRedactionControllerService
|
||||
.requestAddRedaction(manualRedactionEntry, this._appStateService.activeProject.project.projectId, this._appStateService.activeFile.fileId)
|
||||
.pipe(
|
||||
@ -185,7 +185,7 @@ export class ManualAnnotationService {
|
||||
);
|
||||
}
|
||||
|
||||
private _makeRedaction(manualRedactionEntry: ManualRedactionEntry) {
|
||||
private _makeRedaction(manualRedactionEntry: AddRedactionRequest) {
|
||||
return this._manualRedactionControllerService
|
||||
.addRedaction(manualRedactionEntry, this._appStateService.activeProject.project.projectId, this._appStateService.activeFile.fileId)
|
||||
.pipe(
|
||||
|
||||
@ -43,11 +43,11 @@ export class ProjectListingActionsComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
async reanalyseProject($event: MouseEvent, project: ProjectWrapper) {
|
||||
reanalyseProject($event: MouseEvent, project: ProjectWrapper) {
|
||||
$event.stopPropagation();
|
||||
await this.appStateService.reanalyzeProject(project);
|
||||
await this.appStateService.loadAllProjects();
|
||||
this.actionPerformed.emit();
|
||||
this.appStateService.reanalyzeProject(project).then(() => {
|
||||
this.appStateService.loadAllProjects().then(() => this.actionPerformed.emit());
|
||||
});
|
||||
}
|
||||
|
||||
// Download Files
|
||||
|
||||
@ -42,3 +42,42 @@ export function keypress(key: string) {
|
||||
export function reference(x: any) {
|
||||
return x;
|
||||
}
|
||||
|
||||
export function getFirstRelevantTextPart(text, direction: 'FORWARD' | 'BACKWARD') {
|
||||
let spaceCount = 0;
|
||||
let accumulator = '';
|
||||
const breakChars = ['/', ':'];
|
||||
|
||||
const handle = (i) => {
|
||||
const char = text[i];
|
||||
if (char === ' ') {
|
||||
spaceCount += 1;
|
||||
}
|
||||
if (spaceCount >= 2) {
|
||||
return true;
|
||||
}
|
||||
accumulator += char;
|
||||
|
||||
return breakChars.indexOf(char) >= 0;
|
||||
};
|
||||
|
||||
if (direction === 'FORWARD') {
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const shouldBreak = handle(i);
|
||||
if (shouldBreak) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = text.length - 1; i >= 0; i--) {
|
||||
const shouldBreak = handle(i);
|
||||
if (shouldBreak) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (direction === 'BACKWARD') {
|
||||
accumulator = accumulator.split('').reverse().join('');
|
||||
}
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
@ -365,7 +365,8 @@
|
||||
"suggest-remove-from-dict": "Suggest to remove from dictionary",
|
||||
"suggest-only-here": "Suggest to remove only here",
|
||||
"remove-from-dict": "Remove from dictionary",
|
||||
"only-here": "Remove only here"
|
||||
"only-here": "Remove only here",
|
||||
"false-positive": "False Positive"
|
||||
},
|
||||
"remove": "Remove",
|
||||
"undo": "Undo",
|
||||
|
||||
32
apps/red-ui/src/assets/icons/general/thumb-down.svg
Normal file
32
apps/red-ui/src/assets/icons/general/thumb-down.svg
Normal file
@ -0,0 +1,32 @@
|
||||
<svg id="Capa_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
width="475.092px" height="475.092px" viewBox="0 0 475.092 475.092"
|
||||
style="enable-background:new 0 0 475.092 475.092;"
|
||||
xml:space="preserve">
|
||||
<g fill="currentColor">
|
||||
<path d="M442.822,209.562c1.715-6.283,2.57-12.847,2.57-19.702c0-14.655-3.621-28.361-10.852-41.112
|
||||
c0.567-3.995,0.855-8.088,0.855-12.275c0-19.223-5.716-36.162-17.132-50.819v-1.427c0.191-26.075-7.946-46.632-24.414-61.669
|
||||
C377.387,7.521,355.831,0,329.186,0h-31.977c-19.985,0-39.02,2.093-57.102,6.28c-18.086,4.189-39.304,10.468-63.666,18.842
|
||||
c-22.08,7.616-35.211,11.422-39.399,11.422H54.821c-10.088,0-18.702,3.567-25.84,10.704C21.845,54.387,18.276,63,18.276,73.085
|
||||
v182.728c0,10.089,3.566,18.698,10.705,25.837c7.142,7.139,15.752,10.705,25.84,10.705h78.228
|
||||
c6.849,4.572,19.889,19.324,39.113,44.255c11.231,14.661,21.416,26.741,30.551,36.265c3.612,3.997,6.564,10.089,8.848,18.271
|
||||
c2.284,8.186,3.949,16.228,4.998,24.126c1.047,7.898,3.475,16.516,7.281,25.837c3.806,9.329,8.944,17.139,15.415,23.422
|
||||
c7.423,7.043,15.985,10.561,25.697,10.561c15.988,0,30.361-3.087,43.112-9.274c12.754-6.184,22.463-15.845,29.126-28.981
|
||||
c6.663-12.943,9.996-30.646,9.996-53.103c0-17.702-4.568-35.974-13.702-54.819h50.244c19.801,0,36.925-7.23,51.394-21.7
|
||||
c14.469-14.462,21.693-31.497,21.693-51.103C456.809,239.165,452.15,223.652,442.822,209.562z M85.942,104.219
|
||||
c-3.616,3.615-7.898,5.424-12.847,5.424c-4.95,0-9.233-1.805-12.85-5.424c-3.615-3.621-5.424-7.898-5.424-12.851
|
||||
c0-4.948,1.809-9.231,5.424-12.847c3.621-3.617,7.9-5.424,12.85-5.424c4.949,0,9.231,1.807,12.847,5.424
|
||||
c3.617,3.616,5.426,7.898,5.426,12.847C91.368,96.317,89.56,100.598,85.942,104.219z M409.135,281.377
|
||||
c-7.42,7.33-15.886,10.992-25.413,10.992H283.227c0,11.04,4.564,26.217,13.698,45.535c9.138,19.321,13.71,34.598,13.71,45.829
|
||||
c0,18.647-3.046,32.449-9.134,41.395c-6.092,8.949-18.274,13.422-36.546,13.422c-4.951-4.948-8.572-13.045-10.854-24.276
|
||||
c-2.276-11.225-5.185-23.168-8.706-35.83c-3.519-12.655-9.18-23.079-16.984-31.266c-4.184-4.373-11.516-13.038-21.982-25.98
|
||||
c-0.761-0.951-2.952-3.806-6.567-8.562c-3.614-4.757-6.613-8.658-8.992-11.703c-2.38-3.046-5.664-7.091-9.851-12.136
|
||||
c-4.189-5.044-7.995-9.232-11.422-12.565c-3.427-3.327-7.089-6.708-10.992-10.137c-3.901-3.426-7.71-5.996-11.421-7.707
|
||||
c-3.711-1.711-7.089-2.566-10.135-2.566h-9.136V73.092h9.136c2.474,0,5.47-0.282,8.993-0.854c3.518-0.571,6.658-1.192,9.419-1.858
|
||||
c2.76-0.666,6.377-1.713,10.849-3.14c4.476-1.425,7.804-2.522,9.994-3.283c2.19-0.763,5.568-1.951,10.138-3.571
|
||||
c4.57-1.615,7.33-2.613,8.28-2.996c40.159-13.894,72.708-20.839,97.648-20.839h36.542c16.563,0,29.506,3.899,38.828,11.704
|
||||
c9.328,7.804,13.989,19.795,13.989,35.975c0,4.949-0.479,10.279-1.423,15.987c5.708,3.046,10.231,8.042,13.559,14.987
|
||||
c3.333,6.945,4.996,13.944,4.996,20.985c0,7.039-1.711,13.61-5.141,19.701c10.089,9.517,15.126,20.839,15.126,33.974
|
||||
c0,4.759-0.948,10.039-2.847,15.846c-1.899,5.808-4.285,10.327-7.139,13.562c6.091,0.192,11.184,4.665,15.276,13.422
|
||||
c4.093,8.754,6.14,16.468,6.14,23.127C420.277,265.525,416.561,274.043,409.135,281.377z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@ -27,9 +27,12 @@ export interface RedactionLogEntry {
|
||||
section?: string;
|
||||
sectionNumber?: number;
|
||||
status?: RedactionLogEntry.StatusEnum;
|
||||
textAfter?: string;
|
||||
textBefore?: string;
|
||||
type?: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export namespace RedactionLogEntry {
|
||||
export type ManualRedactionTypeEnum = 'ADD' | 'REMOVE';
|
||||
export const ManualRedactionTypeEnum = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user