WIP on VM/RED-10256
RED-10256 - Bulk-local: Changes should not be filtered + Remove for image-based redactions
This commit is contained in:
parent
d8b20774f4
commit
7dd24698b1
@ -46,12 +46,13 @@ export class RemoveAnnotationDialogComponent extends IqserDialogComponent<
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly options: DetailsRadioOption<RemoveAnnotationOption>[];
|
||||
readonly redactedTexts: string[];
|
||||
readonly isImage = this.data.redactions.reduce((acc, next) => acc && next.isImage, true);
|
||||
|
||||
form!: UntypedFormGroup;
|
||||
|
||||
constructor(private readonly _formBuilder: FormBuilder) {
|
||||
super();
|
||||
this.options = getRemoveRedactionOptions(this.data, this.data.applyToAllDossiers, true);
|
||||
this.options = getRemoveRedactionOptions(this.data, this.data.applyToAllDossiers, this.isImage, true);
|
||||
this.redactedTexts = this.data.redactions.map(annotation => annotation.value);
|
||||
this.form = this.#getForm();
|
||||
}
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { ReactiveFormsModule, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import {
|
||||
BaseDialogComponent,
|
||||
CircleButtonComponent,
|
||||
getConfig,
|
||||
HasScrollbarDirective,
|
||||
HelpButtonComponent,
|
||||
IconButtonComponent,
|
||||
IqserDenyDirective,
|
||||
IqserDialogComponent,
|
||||
} from '@iqser/common-ui';
|
||||
import { JustificationsService } from '@services/entity-services/justifications.service';
|
||||
import { Dossier, ILegalBasisChangeRequest } from '@red/domain';
|
||||
@ -21,20 +20,19 @@ import {
|
||||
ValueColumn,
|
||||
} from '../../components/selected-annotations-table/selected-annotations-table.component';
|
||||
import { NgForOf, NgIf } from '@angular/common';
|
||||
import { MatFormField } from '@angular/material/form-field';
|
||||
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
|
||||
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
|
||||
import { ForceAnnotationData, ForceAnnotationOption, ForceAnnotationResult, LegalBasisOption } from '../../utils/dialog-types';
|
||||
import { getForceAnnotationOptions } from '../../utils/dialog-options';
|
||||
import { SystemDefaults } from '../../../account/utils/dialog-defaults';
|
||||
import { MatFormField } from '@angular/material/form-field';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
|
||||
import { ForceAnnotationOption, LegalBasisOption } from '../../utils/dialog-types';
|
||||
import { getForceAnnotationOptions } from '../../utils/dialog-options';
|
||||
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
|
||||
import { SystemDefaults } from '../../../account/utils/dialog-defaults';
|
||||
|
||||
const DOCUMINE_LEGAL_BASIS = 'n-a.';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-force-annotation-dialog',
|
||||
templateUrl: './force-annotation-dialog.component.html',
|
||||
styleUrls: ['./force-annotation-dialog.component.scss'],
|
||||
standalone: true,
|
||||
@ -57,12 +55,16 @@ const DOCUMINE_LEGAL_BASIS = 'n-a.';
|
||||
DetailsRadioComponent,
|
||||
],
|
||||
})
|
||||
export class ForceAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
export class ForceAnnotationDialogComponent
|
||||
extends IqserDialogComponent<ForceAnnotationDialogComponent, ForceAnnotationData, ForceAnnotationResult>
|
||||
implements OnInit
|
||||
{
|
||||
readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
readonly options: DetailsRadioOption<ForceAnnotationOption>[];
|
||||
|
||||
readonly form: FormGroup;
|
||||
readonly tableColumns: ValueColumn[] = [{ label: 'Value' }, { label: 'Type' }];
|
||||
readonly tableData: ValueColumn[][] = this._data.annotations.map(redaction => [
|
||||
readonly tableData: ValueColumn[][] = this.data.annotations.map(redaction => [
|
||||
{ label: redaction.value, bold: true },
|
||||
{ label: redaction.typeLabel },
|
||||
]);
|
||||
@ -72,21 +74,23 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
|
||||
|
||||
constructor(
|
||||
private readonly _justificationsService: JustificationsService,
|
||||
protected readonly _dialogRef: MatDialogRef<ForceAnnotationDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
private readonly _data: { readonly dossier: Dossier; readonly hint: boolean; annotations: AnnotationWrapper[] },
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
) {
|
||||
super(_dialogRef);
|
||||
this.options = getForceAnnotationOptions(this.isDocumine, this.isHintDialog);
|
||||
super();
|
||||
this.options = getForceAnnotationOptions(this.isDocumine, this.isHintDialog, this.isImageDialog);
|
||||
this.form = this.#getForm();
|
||||
}
|
||||
|
||||
get isImageHint() {
|
||||
return this._data.annotations.every(annotation => annotation.IMAGE_HINT);
|
||||
return this.data.annotations.every(annotation => annotation.IMAGE_HINT);
|
||||
}
|
||||
|
||||
get isHintDialog() {
|
||||
return this._data.hint;
|
||||
return this.data.hint;
|
||||
}
|
||||
|
||||
get isImageDialog() {
|
||||
return this.data.image;
|
||||
}
|
||||
|
||||
get disabled(): boolean {
|
||||
@ -103,7 +107,7 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
|
||||
|
||||
async ngOnInit() {
|
||||
if (!this.isDocumine) {
|
||||
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this._data.dossier.dossierTemplateId));
|
||||
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.data.dossier.dossierTemplateId));
|
||||
|
||||
this.legalOptions = data.map(lbm => ({
|
||||
legalBasis: lbm.reason,
|
||||
@ -114,8 +118,8 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
|
||||
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
// Set pre-existing reason if it exists
|
||||
const existingReason = this.legalOptions.find(option => option.legalBasis === this._data.annotations[0].legalBasis);
|
||||
if (!this._data.hint && existingReason) {
|
||||
const existingReason = this.legalOptions.find(option => option.legalBasis === this.data.annotations[0].legalBasis);
|
||||
if (!this.data.hint && existingReason) {
|
||||
this.form.patchValue({ reason: existingReason }, { emitEvent: false });
|
||||
}
|
||||
}
|
||||
@ -123,12 +127,12 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
|
||||
}
|
||||
|
||||
save() {
|
||||
this._dialogRef.close(this.#createForceRedactionRequest());
|
||||
this.close(this.#createForceRedactionRequest());
|
||||
}
|
||||
|
||||
#getForm(): UntypedFormGroup {
|
||||
return this._formBuilder.group({
|
||||
reason: this._data.hint ? ['Forced Hint'] : [null, !this.isDocumine ? Validators.required : null],
|
||||
reason: this.data.hint ? ['Forced Hint'] : [null, !this.isDocumine ? Validators.required : null],
|
||||
comment: [null],
|
||||
option: this.options.find(o => o.value === SystemDefaults.FORCE_REDACTION_DEFAULT),
|
||||
});
|
||||
|
||||
@ -66,6 +66,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
|
||||
readonly recommendation = this.data.redactions.every(redaction => redaction.isRecommendation);
|
||||
readonly hint = this.data.redactions.every(redaction => redaction.isHint);
|
||||
readonly annotationsType = this.hint ? 'hint' : this.recommendation ? 'recommendation' : 'redaction';
|
||||
readonly isImage = this.data.redactions.reduce((acc, next) => acc && next.isImage, true);
|
||||
readonly optionByType = {
|
||||
recommendation: {
|
||||
main: this._userPreferences.getRemoveRecommendationDefaultOption(),
|
||||
@ -107,6 +108,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
|
||||
: getRemoveRedactionOptions(
|
||||
this.data,
|
||||
this.isSystemDefault || this.isExtraOptionSystemDefault ? this.#applyToAllDossiers : this.extraOptionPreference,
|
||||
this.isImage,
|
||||
);
|
||||
readonly skipped = this.data.redactions.some(annotation => annotation.isSkipped);
|
||||
readonly redactedTexts = this.data.redactions.map(annotation => annotation.value);
|
||||
@ -185,12 +187,9 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
|
||||
}
|
||||
|
||||
save(): void {
|
||||
const optionValue = this.form.controls.option.value.value;
|
||||
const pageNumbers = parseSelectedPageNumbers(
|
||||
this.form.get('option').value.additionalInput?.value,
|
||||
this.data.file,
|
||||
this.data.redactions[0],
|
||||
);
|
||||
const optionValue = this.form.controls.option?.value?.value;
|
||||
const optionInputValue = this.form.controls.option?.value?.additionalInput?.value;
|
||||
const pageNumbers = parseSelectedPageNumbers(optionInputValue, this.data.file, this.data.redactions[0]);
|
||||
const position = parseRectanglePosition(this.data.redactions[0]);
|
||||
|
||||
this.close({
|
||||
|
||||
@ -7,6 +7,7 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { Core } from '@pdftron/webviewer';
|
||||
import {
|
||||
DictionaryEntryTypes,
|
||||
DownloadFileTypes,
|
||||
EarmarkOperation,
|
||||
type IBulkLocalRemoveRequest,
|
||||
IBulkRecategorizationRequest,
|
||||
@ -51,6 +52,7 @@ import { FilePreviewStateService } from './file-preview-state.service';
|
||||
import { ManualRedactionService } from './manual-redaction.service';
|
||||
import { SkippedService } from './skipped.service';
|
||||
import { NON_READABLE_CONTENT } from '../dialogs/rectangle-annotation-dialog/rectangle-annotation-dialog.component';
|
||||
import { ForceAnnotationDialogComponent } from '../dialogs/force-redaction-dialog/force-annotation-dialog.component';
|
||||
|
||||
@Injectable()
|
||||
export class AnnotationActionsService {
|
||||
@ -81,34 +83,41 @@ export class AnnotationActionsService {
|
||||
this._dialogService.openDialog('highlightAction', data);
|
||||
}
|
||||
|
||||
forceAnnotation(annotations: AnnotationWrapper[], hint: boolean = false) {
|
||||
async forceAnnotation(annotations: AnnotationWrapper[], hint: boolean = false) {
|
||||
const { dossierId, fileId } = this._state;
|
||||
const data = { dossier: this._state.dossier(), annotations, hint };
|
||||
this._dialogService.openDialog('forceAnnotation', data, (request: ILegalBasisChangeRequest) => {
|
||||
let obs$: Observable<unknown>;
|
||||
if (request.option === ForceAnnotationOptions.ONLY_HERE || hint) {
|
||||
obs$ = this._manualRedactionService.bulkForce(
|
||||
annotations.map(a => ({ ...request, annotationId: a.id })),
|
||||
dossierId,
|
||||
fileId,
|
||||
annotations[0].isIgnoredHint,
|
||||
);
|
||||
} else {
|
||||
const addAnnotationRequest = annotations.map(a => ({
|
||||
comment: request.comment,
|
||||
legalBasis: request.legalBasis,
|
||||
reason: request.reason,
|
||||
positions: a.positions,
|
||||
type: a.type,
|
||||
value: a.value,
|
||||
}));
|
||||
obs$ = this._manualRedactionService.addAnnotation(addAnnotationRequest, dossierId, fileId, {
|
||||
hint,
|
||||
bulkLocal: true,
|
||||
});
|
||||
}
|
||||
this.#processObsAndEmit(obs$).then();
|
||||
});
|
||||
const image = annotations.every(a => a.isImage);
|
||||
const data = { dossier: this._state.dossier(), annotations, hint, image };
|
||||
|
||||
const dialogRef = this._iqserDialog.openDefault(ForceAnnotationDialogComponent, { data });
|
||||
const result = await dialogRef.result();
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
let obs$: Observable<unknown>;
|
||||
if (result.option === ForceAnnotationOptions.ONLY_HERE || hint || image) {
|
||||
obs$ = this._manualRedactionService.bulkForce(
|
||||
annotations.map(a => ({ ...result, annotationId: a.id })),
|
||||
dossierId,
|
||||
fileId,
|
||||
annotations[0].isIgnoredHint,
|
||||
);
|
||||
} else {
|
||||
const addAnnotationRequest = annotations.map(a => ({
|
||||
comment: result.comment,
|
||||
legalBasis: result.legalBasis,
|
||||
reason: result.reason,
|
||||
positions: a.positions,
|
||||
type: a.type,
|
||||
value: a.value,
|
||||
}));
|
||||
obs$ = this._manualRedactionService.addAnnotation(addAnnotationRequest, dossierId, fileId, {
|
||||
hint,
|
||||
bulkLocal: true,
|
||||
});
|
||||
}
|
||||
this.#processObsAndEmit(obs$).then();
|
||||
}
|
||||
|
||||
async editRedaction(annotations: AnnotationWrapper[]) {
|
||||
@ -147,15 +156,11 @@ export class AnnotationActionsService {
|
||||
return body;
|
||||
});
|
||||
} else {
|
||||
const originTypes = annotations.map(a => a.type);
|
||||
const originLegalBases = annotations.map(a => a.legalBasis);
|
||||
recategorizeBody = {
|
||||
value: annotations[0].value,
|
||||
type: result.type,
|
||||
legalBasis: result.legalBasis,
|
||||
section: result.section,
|
||||
originTypes,
|
||||
originLegalBases,
|
||||
rectangle: annotations[0].AREA,
|
||||
pageNumbers: result.pageNumbers,
|
||||
position: result.position,
|
||||
@ -206,8 +211,8 @@ export class AnnotationActionsService {
|
||||
}
|
||||
|
||||
if (
|
||||
result.option.value === RemoveRedactionOptions.FALSE_POSITIVE ||
|
||||
result.option.value === RemoveRedactionOptions.DO_NOT_RECOMMEND
|
||||
result.option?.value === RemoveRedactionOptions.FALSE_POSITIVE ||
|
||||
result.option?.value === RemoveRedactionOptions.DO_NOT_RECOMMEND
|
||||
) {
|
||||
this.#setAsFalsePositive(redactions, result);
|
||||
} else {
|
||||
@ -476,7 +481,7 @@ export class AnnotationActionsService {
|
||||
}
|
||||
|
||||
#removeRedaction(redactions: AnnotationWrapper[], dialogResult: RemoveRedactionResult) {
|
||||
const removeFromDictionary = dialogResult.option.value === RemoveRedactionOptions.IN_DOSSIER;
|
||||
const removeFromDictionary = dialogResult.option?.value === RemoveRedactionOptions.IN_DOSSIER;
|
||||
const includeUnprocessed = redactions.every(redaction => this.#includeUnprocessed(redaction, true));
|
||||
const body = this.#getRemoveRedactionBody(redactions, dialogResult);
|
||||
// todo: might not be correct, probably shouldn't get to this point if they are not all the same
|
||||
@ -615,8 +620,8 @@ export class AnnotationActionsService {
|
||||
annotationId: redaction.id,
|
||||
value: redaction.value,
|
||||
comment: dialogResult.comment,
|
||||
removeFromDictionary: dialogResult.option.value === RemoveRedactionOptions.IN_DOSSIER,
|
||||
removeFromAllDossiers: !!dialogResult.option.additionalCheck?.checked || !!dialogResult.applyToAllDossiers,
|
||||
removeFromDictionary: dialogResult.option?.value === RemoveRedactionOptions.IN_DOSSIER,
|
||||
removeFromAllDossiers: !!dialogResult.option?.additionalCheck?.checked || !!dialogResult.applyToAllDossiers,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,10 +3,9 @@ import { MatDialog } from '@angular/material/dialog';
|
||||
import { ConfirmationDialogComponent, DialogConfig, DialogService } from '@iqser/common-ui';
|
||||
import { ChangeLegalBasisDialogComponent } from '../dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component';
|
||||
import { DocumentInfoDialogComponent } from '../dialogs/document-info-dialog/document-info-dialog.component';
|
||||
import { ForceAnnotationDialogComponent } from '../dialogs/force-redaction-dialog/force-annotation-dialog.component';
|
||||
import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dialog/highlight-action-dialog.component';
|
||||
|
||||
type DialogType = 'confirm' | 'documentInfo' | 'changeLegalBasis' | 'forceAnnotation' | 'highlightAction';
|
||||
type DialogType = 'confirm' | 'documentInfo' | 'changeLegalBasis' | 'highlightAction';
|
||||
|
||||
@Injectable()
|
||||
export class FilePreviewDialogService extends DialogService<DialogType> {
|
||||
@ -22,9 +21,6 @@ export class FilePreviewDialogService extends DialogService<DialogType> {
|
||||
changeLegalBasis: {
|
||||
component: ChangeLegalBasisDialogComponent,
|
||||
},
|
||||
forceAnnotation: {
|
||||
component: ForceAnnotationDialogComponent,
|
||||
},
|
||||
highlightAction: {
|
||||
component: HighlightActionDialogComponent,
|
||||
},
|
||||
|
||||
@ -132,22 +132,21 @@ export const getResizeRedactionOptions = (
|
||||
isApprover: boolean,
|
||||
canResizeInDictionary: boolean,
|
||||
): DetailsRadioOption<ResizeRedactionOption>[] => {
|
||||
if (isRss || !canResizeInDictionary) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const translations = resizeRedactionTranslations;
|
||||
const options: DetailsRadioOption<ResizeRedactionOption>[] = [
|
||||
const dictBasedType = redaction.isModifyDictionary;
|
||||
|
||||
return [
|
||||
{
|
||||
label: translations.onlyHere.label,
|
||||
description: translations.onlyHere.description,
|
||||
icon: PIN_ICON,
|
||||
value: ResizeOptions.ONLY_HERE,
|
||||
},
|
||||
];
|
||||
|
||||
if (isRss) {
|
||||
return options;
|
||||
}
|
||||
if (canResizeInDictionary) {
|
||||
const dictBasedType = redaction.isModifyDictionary;
|
||||
options.push({
|
||||
{
|
||||
label: translations.inDossier.label,
|
||||
description: translations.inDossier.description,
|
||||
descriptionParams: { dossierName: dossier.dossierName },
|
||||
@ -160,14 +159,14 @@ export const getResizeRedactionOptions = (
|
||||
checked: applyToAllDossiers,
|
||||
hidden: !isApprover,
|
||||
},
|
||||
});
|
||||
}
|
||||
return options;
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const getRemoveRedactionOptions = (
|
||||
data: RemoveRedactionData,
|
||||
applyToAllDossiers: boolean,
|
||||
isImage: boolean,
|
||||
isDocumine: boolean = false,
|
||||
): DetailsRadioOption<RemoveRedactionOption>[] => {
|
||||
const translations = isDocumine ? removeAnnotationTranslations : removeRedactionTranslations;
|
||||
@ -175,7 +174,7 @@ export const getRemoveRedactionOptions = (
|
||||
const isBulk = redactions.length > 1;
|
||||
|
||||
const options: DetailsRadioOption<RemoveRedactionOption>[] = [];
|
||||
if (permissions.canRemoveOnlyHere) {
|
||||
if (permissions.canRemoveOnlyHere && !isImage) {
|
||||
options.push({
|
||||
label: translations.ONLY_HERE.label,
|
||||
description: isBulk ? translations.ONLY_HERE.descriptionBulk : translations.ONLY_HERE.description,
|
||||
@ -191,9 +190,6 @@ export const getRemoveRedactionOptions = (
|
||||
options.push({
|
||||
label: removeRedactionTranslations.IN_DOCUMENT.label,
|
||||
description: removeRedactionTranslations.IN_DOCUMENT.description,
|
||||
descriptionParams: {
|
||||
isImage: redactions[0].isImage ? 'image' : redactions[0].typeLabel,
|
||||
},
|
||||
icon: DOCUMENT_ICON,
|
||||
value: RemoveRedactionOptions.IN_DOCUMENT,
|
||||
});
|
||||
@ -265,8 +261,12 @@ export const getRemoveRedactionOptions = (
|
||||
return options;
|
||||
};
|
||||
|
||||
export const getForceAnnotationOptions = (isDocumine: boolean, isHint: boolean): DetailsRadioOption<ForceAnnotationOption>[] => {
|
||||
if (isDocumine || isHint) {
|
||||
export const getForceAnnotationOptions = (
|
||||
isDocumine: boolean,
|
||||
isHint: boolean,
|
||||
isImage: boolean,
|
||||
): DetailsRadioOption<ForceAnnotationOption>[] => {
|
||||
if (isDocumine || isHint || isImage) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
@ -73,6 +73,21 @@ export interface EditRedactionData {
|
||||
export type AddAnnotationData = RedactTextData;
|
||||
export type AddHintData = RedactTextData;
|
||||
|
||||
export interface ForceAnnotationData {
|
||||
readonly dossier: Dossier;
|
||||
readonly annotations: AnnotationWrapper[];
|
||||
readonly hint: boolean;
|
||||
readonly image: boolean;
|
||||
}
|
||||
|
||||
export interface ForceAnnotationResult {
|
||||
readonly annotationId?: string;
|
||||
readonly comment?: string;
|
||||
readonly legalBasis?: string;
|
||||
readonly reason?: string;
|
||||
readonly option?: ForceAnnotationOption;
|
||||
}
|
||||
|
||||
export interface RedactTextResult {
|
||||
redaction: IManualRedactionEntry;
|
||||
dictionary: Dictionary;
|
||||
|
||||
@ -2224,7 +2224,7 @@
|
||||
"label": "Remove from dossier in this context"
|
||||
},
|
||||
"in-document": {
|
||||
"description": "Do not auto-redact the selected {isImage, select, image{image} other{term}} on any page of this document.",
|
||||
"description": "Do not auto-redact the selected term on any page of this document.",
|
||||
"label": "Remove from document"
|
||||
},
|
||||
"in-dossier": {
|
||||
|
||||
@ -14,8 +14,8 @@ export interface IBulkRecategorizationRequest {
|
||||
readonly type: string;
|
||||
readonly legalBasis: string;
|
||||
readonly section: string;
|
||||
readonly originTypes: string[];
|
||||
readonly originLegalBases: string[];
|
||||
readonly originTypes?: string[];
|
||||
readonly originLegalBases?: string[];
|
||||
readonly rectangle: boolean;
|
||||
readonly position?: IEntityLogEntryPosition;
|
||||
readonly pageNumbers?: number[];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user