move to bulk endpoints, add works

This commit is contained in:
Dan Percic 2022-03-29 17:32:30 +03:00
parent 7b9d5ac405
commit 7fdfa5d308
9 changed files with 324 additions and 499 deletions

View File

@ -29,7 +29,7 @@ export class AnnotationActionsComponent implements OnChanges {
@Input() tooltipPosition: 'before' | 'above' = 'before';
@Input() canPerformAnnotationActions: boolean;
@Input() alwaysVisible: boolean;
@Output() readonly annotationsChanged = new EventEmitter<AnnotationWrapper>();
@Output() readonly annotationsChanged = new EventEmitter<AnnotationWrapper[]>();
annotationPermissions: AnnotationPermissions;
constructor(

View File

@ -58,7 +58,7 @@ export class FileWorkloadComponent {
@Input() annotationActionsTemplate: TemplateRef<unknown>;
@Output() readonly selectAnnotations = new EventEmitter<AnnotationWrapper[]>();
@Output() readonly selectPage = new EventEmitter<number>();
@Output() readonly annotationsChanged = new EventEmitter<AnnotationWrapper>();
@Output() readonly annotationsChanged = new EventEmitter<AnnotationWrapper[]>();
displayedPages: number[] = [];
pagesPanelActive = true;
readonly displayedAnnotations$: Observable<Map<number, AnnotationWrapper[]>>;

View File

@ -60,7 +60,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
@Output() readonly manualAnnotationRequested = new EventEmitter<ManualRedactionEntryWrapper>();
@Output() readonly pageChanged = new EventEmitter<number>();
@Output() readonly keyUp = new EventEmitter<KeyboardEvent>();
@Output() readonly annotationsChanged = new EventEmitter<AnnotationWrapper>();
@Output() readonly annotationsChanged = new EventEmitter<AnnotationWrapper[]>();
@ViewChild('viewer', { static: true }) viewer: ElementRef;
@ViewChild('compareFileInput', { static: true }) compareFileInput: ElementRef;
instance: WebViewerInstance;

View File

@ -9,7 +9,15 @@ import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { BASE_HREF } from '../../../tokens';
import { UserService } from '@services/user.service';
import { Core } from '@pdftron/webviewer';
import { DictionaryEntryTypes, Dossier, IAddRedactionRequest, ILegalBasisChangeRequest, IRectangle, IResizeRequest } from '@red/domain';
import {
DictionaryEntryTypes,
Dossier,
IAddRedactionRequest,
ILegalBasisChangeRequest,
IRecategorizationRequest,
IRectangle,
IResizeRequest,
} from '@red/domain';
import { toPosition } from '../../dossier/utils/pdf-calculation.utils';
import { AnnotationDrawService } from './annotation-draw.service';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
@ -18,7 +26,7 @@ import {
AcceptRecommendationDialogComponent,
AcceptRecommendationReturnType,
} from '../dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component';
import { defaultDialogConfig } from '@iqser/common-ui';
import { defaultDialogConfig, List } from '@iqser/common-ui';
import { filter } from 'rxjs/operators';
import { MatDialog } from '@angular/material/dialog';
import { FilePreviewStateService } from './file-preview-state.service';
@ -41,86 +49,85 @@ export class AnnotationActionsService {
private readonly _pdf: PdfViewer,
private readonly _annotationDrawService: AnnotationDrawService,
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _screenStateService: FilePreviewStateService,
private readonly _dictionariesMapService: DictionariesMapService,
private readonly _state: FilePreviewStateService,
) {}
private get _dossier(): Dossier {
return this._activeDossiersService.find(this._screenStateService.dossierId);
return this._activeDossiersService.find(this._state.dossierId);
}
acceptSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper>) {
acceptSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper[]>) {
$event?.stopPropagation();
const { dossierId, fileId } = this._screenStateService;
annotations.forEach(annotation => {
this._processObsAndEmit(
this._manualRedactionService.approve(annotation.id, dossierId, fileId, annotation.isModifyDictionary),
annotation,
annotationsChanged,
);
});
const { dossierId, fileId } = this._state;
this._processObsAndEmit(
this._manualRedactionService.approve(
annotations.map(a => a.id),
dossierId,
fileId,
),
annotations,
annotationsChanged,
);
}
rejectSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper>) {
rejectSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper[]>) {
$event?.stopPropagation();
const { dossierId, fileId } = this._screenStateService;
annotations.forEach(annotation => {
this._processObsAndEmit(
this._manualRedactionService.declineOrRemoveRequest(annotation, dossierId, fileId),
annotation,
annotationsChanged,
);
});
const { dossierId, fileId } = this._state;
const modifyDictionary = annotations[0].isModifyDictionary;
this._processObsAndEmit(
this._manualRedactionService.declineOrRemoveRequest(
annotations.map(a => a.id),
dossierId,
fileId,
modifyDictionary,
),
annotations,
annotationsChanged,
);
}
forceAnnotation(
$event: MouseEvent,
annotations: AnnotationWrapper[],
annotationsChanged: EventEmitter<AnnotationWrapper>,
annotationsChanged: EventEmitter<AnnotationWrapper[]>,
hint: boolean = false,
) {
const { dossierId, fileId } = this._screenStateService;
const { dossierId, fileId } = this._state;
const data = { dossier: this._dossier, annotations, hint };
this._dialogService.openDialog('forceAnnotation', $event, data, (request: ILegalBasisChangeRequest) => {
annotations.forEach(annotation => {
this._processObsAndEmit(
this._manualRedactionService.force(
{
...request,
annotationId: annotation.id,
},
dossierId,
fileId,
),
annotation,
annotationsChanged,
);
});
this._processObsAndEmit(
this._manualRedactionService.bulkForce(
annotations.map(a => ({ ...request, annotationId: a.id })),
dossierId,
fileId,
),
annotations,
annotationsChanged,
);
});
}
changeLegalBasis($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper>) {
const { dossierId, fileId } = this._screenStateService;
changeLegalBasis($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper[]>) {
const { dossierId, fileId } = this._state;
this._dialogService.openDialog(
'changeLegalBasis',
$event,
{ annotations, dossier: this._dossier },
(data: { comment: string; legalBasis: string; section: string; value: string }) => {
annotations.forEach(annotation => {
this._processObsAndEmit(
this._manualRedactionService.changeLegalBasis(
annotation.annotationId,
dossierId,
fileId,
data.section,
data.value,
data.legalBasis,
data.comment,
),
annotation,
annotationsChanged,
);
});
const body = annotations.map(annotation => ({
annotationId: annotation.id,
comment: data.comment,
legalBasis: data.legalBasis,
section: data.section,
value: data.value,
}));
this._processObsAndEmit(
this._manualRedactionService.changeLegalBasis(body, dossierId, fileId),
annotations,
annotationsChanged,
);
},
);
}
@ -129,7 +136,7 @@ export class AnnotationActionsService {
$event: MouseEvent,
annotations: AnnotationWrapper[],
removeFromDictionary: boolean,
annotationsChanged: EventEmitter<AnnotationWrapper>,
annotationsChanged: EventEmitter<AnnotationWrapper[]>,
) {
const data = {
annotationsToRemove: annotations,
@ -137,65 +144,63 @@ export class AnnotationActionsService {
dossier: this._dossier,
hint: annotations[0].hintDictionary,
};
const { dossierId, fileId } = this._screenStateService;
const { dossierId, fileId } = this._state;
this._dialogService.openDialog('removeAnnotations', $event, data, (result: { comment: string }) => {
annotations.forEach(annotation => {
this._processObsAndEmit(
this._manualRedactionService.removeOrSuggestRemoveAnnotation(
annotation,
dossierId,
fileId,
result.comment,
removeFromDictionary,
),
annotation,
annotationsChanged,
);
});
});
}
markAsFalsePositive($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper>) {
annotations.forEach(annotation => {
this._markAsFalsePositive($event, annotation, this._getFalsePositiveText(annotation), annotationsChanged);
});
}
recategorizeImages($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper>) {
const data = { annotations, dossier: this._dossier };
const { dossierId, fileId } = this._screenStateService;
this._dialogService.openDialog('recategorizeImage', $event, data, (res: { type: string; comment: string }) => {
annotations.forEach(annotation => {
this._processObsAndEmit(
this._manualRedactionService.recategorizeImg(annotation.annotationId, dossierId, fileId, res.type, res.comment),
annotation,
annotationsChanged,
);
});
});
}
undoDirectAction($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper>) {
$event?.stopPropagation();
const { dossierId, fileId } = this._screenStateService;
annotations.forEach(annotation => {
const body = annotations.map(annotation => ({
annotationId: annotation.id,
removeFromDictionary,
comment: result.comment,
}));
this._processObsAndEmit(
this._manualRedactionService.undoRequest(annotation, dossierId, fileId),
annotation,
this._manualRedactionService.removeOrSuggestRemoveAnnotation(body, dossierId, fileId, removeFromDictionary),
annotations,
annotationsChanged,
);
});
}
recategorizeImages($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper[]>) {
const data = { annotations, dossier: this._dossier };
const { dossierId, fileId } = this._state;
this._dialogService.openDialog('recategorizeImage', $event, data, ({ comment, type }: { type: string; comment: string }) => {
const body: List<IRecategorizationRequest> = annotations.map(({ annotationId }) => ({
annotationId,
type,
comment,
}));
this._processObsAndEmit(
this._manualRedactionService.recategorizeImage(body, dossierId, fileId),
annotations,
annotationsChanged,
);
});
}
undoDirectAction($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper[]>) {
$event?.stopPropagation();
const { dossierId, fileId } = this._state;
const modifyDictionary = annotations[0].isModifyDictionary;
this._processObsAndEmit(
this._manualRedactionService.undoRequest(
annotations.map(a => a.id),
dossierId,
fileId,
modifyDictionary,
),
annotations,
annotationsChanged,
);
}
convertRecommendationToAnnotation(
$event: any,
recommendations: AnnotationWrapper[],
annotationsChanged: EventEmitter<AnnotationWrapper>,
annotationsChanged: EventEmitter<AnnotationWrapper[]>,
) {
$event?.stopPropagation();
const { dossierId, fileId } = this._screenStateService;
const { dossierId, fileId } = this._state;
const dialogRef = this._dialog.open<AcceptRecommendationDialogComponent, AcceptRecommendationData, AcceptRecommendationReturnType>(
AcceptRecommendationDialogComponent,
{ ...defaultDialogConfig, autoFocus: true, data: { annotations: recommendations, dossierId } },
@ -203,20 +208,18 @@ export class AnnotationActionsService {
const dialogClosed = dialogRef.afterClosed().pipe(filter(value => !!value && !!value.annotations));
dialogClosed.subscribe(({ annotations, comment: commentText }) => {
const comment = commentText ? { text: commentText } : undefined;
annotations.forEach(annotation => {
this._processObsAndEmit(
this._manualRedactionService.addRecommendation(annotation, dossierId, fileId, comment),
annotation,
annotationsChanged,
);
});
this._processObsAndEmit(
this._manualRedactionService.addRecommendation(annotations, dossierId, fileId, comment),
annotations,
annotationsChanged,
);
});
}
getViewerAvailableActions(
dossier: Dossier,
annotations: AnnotationWrapper[],
annotationsChanged: EventEmitter<AnnotationWrapper>,
annotationsChanged: EventEmitter<AnnotationWrapper[]>,
): Record<string, unknown>[] {
const availableActions = [];
const annotationPermissions = annotations.map(annotation => ({
@ -428,9 +431,9 @@ export class AnnotationActionsService {
this._annotationDrawService.annotationToQuads(viewerAnnotation);
}
acceptResize($event: MouseEvent, annotationWrapper: AnnotationWrapper, annotationsChanged?: EventEmitter<AnnotationWrapper>) {
acceptResize($event: MouseEvent, annotationWrapper: AnnotationWrapper, annotationsChanged?: EventEmitter<AnnotationWrapper[]>) {
const data = { dossier: this._dossier };
const fileId = this._screenStateService.fileId;
const fileId = this._state.fileId;
this._dialogService.openDialog('resizeAnnotation', $event, data, async (result: { comment: string }) => {
const textAndPositions = await this._extractTextAndPositions(annotationWrapper.id);
const text =
@ -444,14 +447,14 @@ export class AnnotationActionsService {
};
this._processObsAndEmit(
this._manualRedactionService.resizeOrSuggestToResize(annotationWrapper, data.dossier.dossierId, fileId, resizeRequest),
annotationWrapper,
this._manualRedactionService.resizeOrSuggestToResize([resizeRequest], data.dossier.dossierId, fileId),
[annotationWrapper],
annotationsChanged,
);
});
}
async cancelResize($event: MouseEvent, annotationWrapper: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) {
async cancelResize($event: MouseEvent, annotationWrapper: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper[]>) {
$event?.stopPropagation();
annotationWrapper.resizing = false;
@ -461,17 +464,36 @@ export class AnnotationActionsService {
this._pdf.deleteAnnotations([viewerAnnotation.Id]);
await this._annotationDrawService.drawAnnotations([annotationWrapper]);
this._pdf.annotationManager.deselectAllAnnotations();
annotationsChanged.emit(annotationWrapper);
annotationsChanged.emit([annotationWrapper]);
}
markAsFalsePositive($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter<AnnotationWrapper[]>) {
$event?.stopPropagation();
const requests: List<IAddRedactionRequest> = annotations.map(annotation => ({
reason: annotation.id,
value: this._getFalsePositiveText(annotation),
type: annotation.type,
positions: annotation.positions,
addToDictionary: true,
comment: { text: 'False Positive' },
dictionaryEntryType: annotation.isRecommendation
? DictionaryEntryTypes.FALSE_RECOMMENDATION
: DictionaryEntryTypes.FALSE_POSITIVE,
}));
const { dossierId, fileId } = this._state;
this._processObsAndEmit(this._manualRedactionService.addAnnotation(requests, dossierId, fileId), annotations, annotationsChanged);
}
private _processObsAndEmit(
obs: Observable<unknown>,
annotation: AnnotationWrapper,
annotationsChanged: EventEmitter<AnnotationWrapper>,
annotations: AnnotationWrapper[],
annotationsChanged: EventEmitter<AnnotationWrapper[]>,
) {
obs.subscribe({
next: () => {
annotationsChanged.emit(annotation);
annotationsChanged.emit(annotations);
},
error: () => {
annotationsChanged.emit();
@ -495,34 +517,6 @@ export class AnnotationActionsService {
}
}
private _markAsFalsePositive(
$event: MouseEvent,
annotation: AnnotationWrapper,
text: string,
annotationsChanged: EventEmitter<AnnotationWrapper>,
) {
$event?.stopPropagation();
const falsePositiveRequest: IAddRedactionRequest = {
reason: annotation.id,
value: text,
type: annotation.type,
positions: annotation.positions,
addToDictionary: true,
comment: { text: 'False Positive' },
dictionaryEntryType: annotation.isRecommendation
? DictionaryEntryTypes.FALSE_RECOMMENDATION
: DictionaryEntryTypes.FALSE_POSITIVE,
};
const { dossierId, fileId } = this._screenStateService;
this._processObsAndEmit(
this._manualRedactionService.addAnnotation(falsePositiveRequest, dossierId, fileId),
annotation,
annotationsChanged,
);
}
private _convertPath(path: string): string {
return this._baseHref + path;
}

View File

@ -1,44 +1,32 @@
import { Injectable, Injector } from '@angular/core';
import type {
AnnotationActionMode,
DictionaryActions,
Dossier,
IAddRedactionRequest,
IApproveRequest,
IImageRecategorizationRequest,
ILegalBasisChangeRequest,
IManualAddResponse,
IRecategorizationRequest,
IRemoveRedactionRequest,
IResizeRequest,
ManualRedactionActions,
} from '@red/domain';
import { type AnnotationWrapper } from '../../../models/file/annotation.wrapper';
import { GenericService, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
import { GenericService, List, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
import { map, tap } from 'rxjs/operators';
import { PermissionsService } from '../../../services/permissions.service';
import {
annotationActionsTranslations,
dictionaryActionsTranslations,
manualRedactionActionsTranslations,
} from '../../../translations/annotation-actions-translations';
import { dictionaryActionsTranslations, manualRedactionActionsTranslations } from '../../../translations/annotation-actions-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { ActiveDossiersService } from '../../../services/dossiers/active-dossiers.service';
import { type Observable } from 'rxjs';
import { DictionariesMapService } from '../../../services/entity-services/dictionaries-map.service';
import { type ManualRedactionEntryType } from '../../../models/file/manual-redaction-entry.wrapper';
function getMessage(mode: AnnotationActionMode, modifyDictionary?: boolean, error = false, isConflict = false) {
const type = modifyDictionary ? 'dictionary' : 'manual-redaction';
const resultType = error ? (isConflict ? 'conflictError' : 'error') : 'success';
return annotationActionsTranslations[type][mode][resultType];
}
import { NGXLogger } from 'ngx-logger';
function getResponseType(error: boolean, isConflict: boolean) {
return error ? (isConflict ? 'conflictError' : 'error') : 'success';
}
function getDictionaryMessage(action: DictionaryActions, error = false, isConflict = false) {
function getDictionaryMessage(action: DictionaryActions, error = false, isConflict = false): string {
return dictionaryActionsTranslations[action][getResponseType(error, isConflict)];
}
@ -48,70 +36,18 @@ function getManualRedactionMessage(action: ManualRedactionActions, error = false
@Injectable()
export class ManualRedactionService extends GenericService<IManualAddResponse> {
readonly request = `${this._defaultModelPath}/request`;
readonly redaction = `${this._defaultModelPath}/redaction`;
CONFIG: {
[key in AnnotationActionMode]: string;
};
readonly bulkRequest = `${this._defaultModelPath}/bulk/request`;
readonly bulkRedaction = `${this._defaultModelPath}/bulk/redaction`;
constructor(
private readonly _dictionariesMapService: DictionariesMapService,
private readonly _toaster: Toaster,
private readonly _logger: NGXLogger,
private readonly _permissionsService: PermissionsService,
private readonly _activeDossiersService: ActiveDossiersService,
protected readonly _injector: Injector,
) {
super(_injector, 'manualRedaction');
this.CONFIG = {
add: 'addRedaction',
'recategorize-image': 'recategorizeImage',
'request-image-recategorization': 'requestImageRecategorization',
'change-legal-basis': 'legalBasisChange',
'request-change-legal-basis': 'requestLegalBasisChange',
'request-remove': 'requestRemoveRedaction',
approve: 'approveRequest',
decline: 'declineRequest',
undo: 'undo',
remove: 'removeRedaction',
suggest: 'requestAddRedaction',
'force-redaction': 'forceRedaction',
'request-force-redaction': 'requestForceRedaction',
resize: 'resize',
'request-resize': 'requestResize',
};
}
_makeRequest(
mode: AnnotationActionMode,
dossierId: string,
fileId: string,
body: any,
secondParam: any = null,
modifyDictionary = false,
): Observable<unknown> {
const obs = !secondParam
? this[this.CONFIG[mode]](body, dossierId, fileId)
: this[this.CONFIG[mode]](body, secondParam, dossierId, fileId);
return obs.pipe(
tap({
next: () => this._toaster.success(getMessage(mode, modifyDictionary), { positionClass: 'toast-file-preview' }),
error: (error: HttpErrorResponse) => {
const isConflict = error.status === HttpStatusCode.Conflict;
this._toaster.error(getMessage(mode, modifyDictionary, true, isConflict), {
error,
params: {
dictionaryName: this._dictionariesMapService.getDictionary(
body.type as string,
this.#dossier(dossierId).dossierTemplateId,
).label,
content: body.value,
},
positionClass: 'toast-file-preview',
});
},
}),
);
}
@Validate()
@ -126,124 +62,93 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
return super.delete({}, url);
}
addRecommendation(annotation: AnnotationWrapper, dossierId: string, fileId: string, comment = { text: 'Accepted Recommendation' }) {
const manualRedactionEntry: IAddRedactionRequest = {
addToDictionary: true,
// set the ID as reason, so we can hide the suggestion
reason: annotation.annotationId,
value: annotation.value,
positions: annotation.positions,
type: annotation.recommendationType,
comment: comment,
};
return this.addAnnotation(manualRedactionEntry, dossierId, fileId);
}
changeLegalBasis(
annotationId: string,
dossierId: string,
fileId: string,
section: string,
value: string,
legalBasis: string,
comment?: string,
) {
const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId))
? 'change-legal-basis'
: 'request-change-legal-basis';
return this._makeRequest(mode, dossierId, fileId, { annotationId, legalBasis, comment, section, value });
}
recategorizeImg(annotationId: string, dossierId: string, fileId: string, type: string, comment: string) {
const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId))
? 'recategorize-image'
: 'request-image-recategorization';
return this._makeRequest(mode, dossierId, fileId, { annotationId, type, comment });
}
addAnnotation(manualRedactionEntry: IAddRedactionRequest, dossierId: string, fileId: string) {
const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId)) ? 'add' : 'suggest';
return this._makeRequest(mode, dossierId, fileId, manualRedactionEntry, null, manualRedactionEntry.addToDictionary);
}
force(request: ILegalBasisChangeRequest, dossierId: string, fileId: string) {
const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId))
? 'force-redaction'
: 'request-force-redaction';
return this._makeRequest(mode, dossierId, fileId, request);
}
_force(request: ILegalBasisChangeRequest, dossierId: string, fileId: string) {
const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId))
? 'force-redaction'
: 'request-force-redaction';
return this._makeRequest(mode, dossierId, fileId, request);
}
_requestForce(request: ILegalBasisChangeRequest, dossierId: string, fileId: string) {
const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId))
? 'force-redaction'
: 'request-force-redaction';
return this._makeRequest(mode, dossierId, fileId, request);
}
approve(annotationId: string, dossierId: string, fileId: string, addToDictionary: boolean = false) {
// for only here - approve the request
return this._makeRequest(
'approve',
dossierId,
fileId,
{ addOrRemoveFromDictionary: addToDictionary },
annotationId,
addToDictionary,
addRecommendation(annotations: AnnotationWrapper[], dossierId: string, fileId: string, comment = { text: 'Accepted Recommendation' }) {
const recommendations = annotations.map(
annotation =>
({
addToDictionary: true,
// set the ID as reason, so we can hide the suggestion
reason: annotation.annotationId,
value: annotation.value,
positions: annotation.positions,
type: annotation.recommendationType,
comment: comment,
} as IAddRedactionRequest),
);
return this.addAnnotation(recommendations, dossierId, fileId);
}
undoRequest(annotationWrapper: AnnotationWrapper, dossierId: string, fileId: string) {
return this._makeRequest('undo', dossierId, fileId, annotationWrapper.id, null, annotationWrapper.isModifyDictionary);
changeLegalBasis(body: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string) {
if (this._permissionsService.isApprover(this.#dossier(dossierId))) {
return this.bulkLegalBasisChange(body, dossierId, fileId).pipe(this.#showToast('change-legal-basis'));
}
return this.bulkRequestLegalBasisChange(body, dossierId, fileId).pipe(this.#showToast('request-change-legal-basis'));
}
declineOrRemoveRequest(annotationWrapper: AnnotationWrapper, dossierId: string, fileId: string) {
const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId)) ? 'decline' : 'undo';
return this._makeRequest(mode, dossierId, fileId, annotationWrapper.id, null, annotationWrapper.isModifyDictionary);
recategorizeImage(body: List<IRecategorizationRequest>, dossierId: string, fileId: string) {
if (this._permissionsService.isApprover(this.#dossier(dossierId))) {
return this.recategorize(body, dossierId, fileId).pipe(this.#showToast('recategorize-image'));
}
return this.bulkRequestImageRecategorization(body, dossierId, fileId).pipe(this.#showToast('request-image-recategorization'));
}
resizeOrSuggestToResize(annotationWrapper: AnnotationWrapper, dossierId: string, fileId: string, resizeRequest: IResizeRequest) {
const mode: AnnotationActionMode = this._permissionsService.isApprover(this.#dossier(dossierId)) ? 'resize' : 'request-resize';
return this._makeRequest(mode, dossierId, fileId, resizeRequest);
addAnnotation(requests: List<IAddRedactionRequest>, dossierId: string, fileId: string) {
const toast = requests[0].addToDictionary ? this.#showAddToDictionaryToast(requests, dossierId) : this.#showToast('add');
if (this._permissionsService.isApprover(this.#dossier(dossierId))) {
return this.add(requests, dossierId, fileId).pipe(toast);
}
return this.bulkRequestAddRedaction(requests, dossierId, fileId).pipe(toast);
}
bulkForce(requests: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string) {
if (this._permissionsService.isApprover(this.#dossier(dossierId))) {
return this.bulkForceRedaction(requests, dossierId, fileId).pipe(this.#showToast('force-redaction'));
}
return this.bulkForceRequest(requests, dossierId, fileId).pipe(this.#showToast('request-force-redaction'));
}
approve(annotationIds: List, dossierId: string, fileId: string) {
return this.bulkApprove(annotationIds, dossierId, fileId).pipe(this.#showToast('approve'));
}
undoRequest(annotationIds: List, dossierId: string, fileId: string, modifyDictionary = false) {
const toast = modifyDictionary ? this.#showDictionaryToast : this.#showToast;
return this.bulkUndo(annotationIds, dossierId, fileId).pipe(toast('undo'));
}
declineOrRemoveRequest(annotationIds: List, dossierId: string, fileId: string, modifyDictionary = false) {
const toast = modifyDictionary ? this.#showDictionaryToast : this.#showToast;
if (this._permissionsService.isApprover(this.#dossier(dossierId))) {
return this.bulkDecline(annotationIds, dossierId, fileId).pipe(toast('decline'));
}
return this.bulkUndo(annotationIds, dossierId, fileId).pipe(toast('undo'));
}
resizeOrSuggestToResize(requests: List<IResizeRequest>, dossierId: string, fileId: string) {
if (this._permissionsService.isApprover(this.#dossier(dossierId))) {
return this.bulkResize(requests, dossierId, fileId);
}
return this.bulkRequestResize(requests, dossierId, fileId);
}
removeOrSuggestRemoveAnnotation(
annotationWrapper: AnnotationWrapper,
body: List<IRemoveRedactionRequest>,
dossierId: string,
fileId: string,
comment: string,
removeFromDictionary: boolean = false,
) {
let mode: AnnotationActionMode,
body: any,
removeDict = false;
const toast = removeFromDictionary ? this.#showDictionaryToast : this.#showToast;
if (this._permissionsService.isApprover(this.#dossier(dossierId))) {
// if it was something manual simply decline the existing request
mode = 'remove';
body = {
annotationId: annotationWrapper.id,
removeFromDictionary,
comment: comment,
};
removeDict = removeFromDictionary;
} else {
mode = 'request-remove';
body = {
annotationId: annotationWrapper.id,
removeFromDictionary,
comment: comment,
};
removeDict = removeFromDictionary;
return this.bulkRemoveRedaction(body, dossierId, fileId).pipe(toast('remove'));
}
return this._makeRequest(mode, dossierId, fileId, body, null, removeDict);
return this.bulkRequestRemoveRedaction(body, dossierId, fileId).pipe(toast('request-remove'));
}
getTitle(type: ManualRedactionEntryType, dossier: Dossier) {
@ -269,127 +174,162 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
}
@Validate()
addRedaction(@RequiredParam() body: IAddRedactionRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
const url = `${this.redaction}/add/${dossierId}/${fileId}`;
return this._post(body, url);
add(@RequiredParam() body: List<IAddRedactionRequest>, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `${this.bulkRedaction}/add/${dossierId}/${fileId}`).pipe(
tap(response => {
this._logger.info('[MANUAL-REDACTIONS] Add ', body, response);
}),
);
}
@Validate()
recategorizeImage(
@RequiredParam() body: IImageRecategorizationRequest,
recategorize(
@RequiredParam() body: List<IRecategorizationRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
const url = `${this.redaction}/recategorize/${dossierId}/${fileId}`;
return this._post(body, url);
return this._post(body, `${this.bulkRedaction}/recategorize/${dossierId}/${fileId}`);
// .pipe(
// tap(response => {
// this._logger.info('[MANUAL-REDACTIONS] Recategorize', body, response);
// }),
// );
}
@Validate()
requestImageRecategorization(
@RequiredParam() body: IImageRecategorizationRequest,
bulkRequestImageRecategorization(
@RequiredParam() body: List<IRecategorizationRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
const url = `${this.request}/recategorize/${dossierId}/${fileId}`;
return this._post(body, url);
return this._post(body, `${this.bulkRequest}/recategorize/${dossierId}/${fileId}`);
}
@Validate()
legalBasisChange(@RequiredParam() body: ILegalBasisChangeRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
const url = `${this.redaction}/legalBasisChange/${dossierId}/${fileId}`;
return this._post(body, url);
}
@Validate()
requestLegalBasisChange(
@RequiredParam() body: ILegalBasisChangeRequest,
bulkLegalBasisChange(
@RequiredParam() body: List<ILegalBasisChangeRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
const url = `${this.request}/legalBasis/${dossierId}/${fileId}`;
return this._post(body, url);
return this._post(body, `${this.bulkRedaction}/legalBasisChange/${dossierId}/${fileId}`);
}
@Validate()
requestRemoveRedaction(
@RequiredParam() body: IRemoveRedactionRequest,
bulkRequestLegalBasisChange(
@RequiredParam() body: List<ILegalBasisChangeRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
const url = `${this.request}/remove/${dossierId}/${fileId}`;
return this._post(body, url);
return this._post(body, `${this.bulkRequest}/legalBasis/${dossierId}/${fileId}`);
}
@Validate()
approveRequest(
@RequiredParam() body: IApproveRequest,
@RequiredParam() annotationId: string,
bulkRequestRemoveRedaction(
@RequiredParam() body: List<IRemoveRedactionRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
const url = `${this._defaultModelPath}/approve/${dossierId}/${fileId}/${annotationId}`;
return this._post<unknown>(body, url);
return this._post(body, `${this.bulkRequest}/remove/${dossierId}/${fileId}`);
}
@Validate()
declineRequest(@RequiredParam() annotationId: string, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
const url = `${this._defaultModelPath}/decline/${dossierId}/${fileId}/${annotationId}`;
return this._post<unknown>({}, url);
bulkApprove(@RequiredParam() annotationIds: List, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(annotationIds, `${this._defaultModelPath}/bulk/approve/${dossierId}/${fileId}`);
}
@Validate()
undo(@RequiredParam() annotationId: string, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
const url = `${this._defaultModelPath}/undo/${dossierId}/${fileId}/${annotationId}`;
return super.delete({}, url);
bulkDecline(@RequiredParam() annotationIds: List, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(annotationIds, `${this._defaultModelPath}/bulk/decline/${dossierId}/${fileId}`);
}
@Validate()
removeRedaction(@RequiredParam() body: IRemoveRedactionRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `${this.redaction}/remove/${dossierId}/${fileId}`);
bulkUndo(@RequiredParam() annotationIds: List, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return super.delete(annotationIds, `${this._defaultModelPath}/bulk/undo/${dossierId}/${fileId}`);
}
@Validate()
requestAddRedaction(@RequiredParam() body: IAddRedactionRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `${this.request}/add/${dossierId}/${fileId}`);
}
@Validate()
forceRedaction(@RequiredParam() body: ILegalBasisChangeRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `${this.redaction}/force/${dossierId}/${fileId}`);
}
@Validate()
requestForceRedaction(
@RequiredParam() body: ILegalBasisChangeRequest,
bulkRemoveRedaction(
@RequiredParam() body: List<IRemoveRedactionRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
return this._post(body, `${this.request}/force/${dossierId}/${fileId}`);
return this._post(body, `${this.bulkRedaction}/remove/${dossierId}/${fileId}`);
}
@Validate()
resize(@RequiredParam() body: IResizeRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `${this.redaction}/resize/${dossierId}/${fileId}`);
bulkRequestAddRedaction(
@RequiredParam() body: List<IAddRedactionRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
return this._post(body, `${this.bulkRequest}/add/${dossierId}/${fileId}`);
}
@Validate()
requestResize(@RequiredParam() body: IResizeRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `${this.request}/resize/${dossierId}/${fileId}`);
bulkForceRedaction(
@RequiredParam() body: List<ILegalBasisChangeRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
return this._post(body, `${this.bulkRedaction}/force/${dossierId}/${fileId}`);
}
#showToast(mode: AnnotationActionMode, body, dossierId: string, modifyDictionary = false) {
@Validate()
bulkForceRequest(
@RequiredParam() body: List<ILegalBasisChangeRequest>,
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
) {
return this._post(body, `${this.bulkRequest}/force/${dossierId}/${fileId}`);
}
@Validate()
bulkResize(@RequiredParam() body: List<IResizeRequest>, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `${this.bulkRedaction}/resize/${dossierId}/${fileId}`);
}
@Validate()
bulkRequestResize(@RequiredParam() body: List<IResizeRequest>, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
return this._post(body, `${this.bulkRequest}/resize/${dossierId}/${fileId}`);
}
#showToast(action: ManualRedactionActions) {
return tap({
next: () => this._toaster.success(getMessage(mode, modifyDictionary), { positionClass: 'toast-file-preview' }),
next: () => this._toaster.success(getManualRedactionMessage(action), { positionClass: 'toast-file-preview' }),
error: (error: HttpErrorResponse) => {
const isConflict = error.status === HttpStatusCode.Conflict;
this._toaster.error(getMessage(mode, modifyDictionary, true, isConflict), {
this._toaster.error(getManualRedactionMessage(action, true, isConflict), {
error,
positionClass: 'toast-file-preview',
});
},
});
}
#showDictionaryToast(action: DictionaryActions) {
return tap({
next: () => this._toaster.success(getDictionaryMessage(action), { positionClass: 'toast-file-preview' }),
error: (error: HttpErrorResponse) => {
const isConflict = error.status === HttpStatusCode.Conflict;
this._toaster.error(getDictionaryMessage(action, true, isConflict), {
error,
positionClass: 'toast-file-preview',
});
},
});
}
#showAddToDictionaryToast(body: List<IAddRedactionRequest>, dossierId: string) {
return tap({
next: () => this._toaster.success(getDictionaryMessage('add'), { positionClass: 'toast-file-preview' }),
error: (error: HttpErrorResponse) => {
const isConflict = error.status === HttpStatusCode.Conflict;
this._toaster.error(getDictionaryMessage('add', true, isConflict), {
error,
params: {
dictionaryName: this._dictionariesMapService.getDictionary(
body.type as string,
this.#dossier(dossierId).dossierTemplateId,
).label,
content: body.value,
dictionaryName: this._dictionariesMapService.getDictionary(body[0].type, this.#dossier(dossierId).dossierTemplateId)
.label,
content: body[0].value,
},
positionClass: 'toast-file-preview',
});

View File

@ -1,4 +1,4 @@
import { AnnotationActionMode, DictionaryActions, ManualRedactionActions } from '@red/domain';
import { DictionaryActions, ManualRedactionActions } from '@red/domain';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
interface AnnotationActionResponses {
@ -7,99 +7,6 @@ interface AnnotationActionResponses {
conflictError?: string;
}
type ActionType = { [key in AnnotationActionMode]?: AnnotationActionResponses };
export const annotationActionsTranslations: {
dictionary: ActionType;
'manual-redaction': ActionType;
} = {
dictionary: {
add: {
error: _('annotation-actions.message.dictionary.add.error'),
conflictError: _('annotation-actions.message.dictionary.add.conflict-error'),
success: _('annotation-actions.message.dictionary.add.success'),
},
approve: {
error: _('annotation-actions.message.dictionary.approve.error'),
success: _('annotation-actions.message.dictionary.approve.success'),
},
decline: {
error: _('annotation-actions.message.dictionary.decline.error'),
success: _('annotation-actions.message.dictionary.decline.success'),
},
remove: {
error: _('annotation-actions.message.dictionary.remove.error'),
success: _('annotation-actions.message.dictionary.remove.success'),
},
'request-remove': {
error: _('annotation-actions.message.dictionary.request-remove.error'),
success: _('annotation-actions.message.dictionary.request-remove.success'),
},
suggest: {
error: _('annotation-actions.message.dictionary.suggest.error'),
success: _('annotation-actions.message.dictionary.suggest.success'),
},
undo: {
error: _('annotation-actions.message.dictionary.undo.error'),
success: _('annotation-actions.message.dictionary.undo.success'),
},
},
'manual-redaction': {
add: {
error: _('annotation-actions.message.manual-redaction.add.error'),
success: _('annotation-actions.message.manual-redaction.add.success'),
},
approve: {
error: _('annotation-actions.message.manual-redaction.approve.error'),
success: _('annotation-actions.message.manual-redaction.approve.success'),
},
'change-legal-basis': {
error: _('annotation-actions.message.manual-redaction.change-legal-basis.error'),
success: _('annotation-actions.message.manual-redaction.change-legal-basis.success'),
},
decline: {
error: _('annotation-actions.message.manual-redaction.decline.error'),
success: _('annotation-actions.message.manual-redaction.decline.success'),
},
'force-redaction': {
error: _('annotation-actions.message.manual-redaction.force-redaction.error'),
success: _('annotation-actions.message.manual-redaction.force-redaction.success'),
},
'recategorize-image': {
error: _('annotation-actions.message.manual-redaction.recategorize-image.error'),
success: _('annotation-actions.message.manual-redaction.recategorize-image.success'),
},
'request-change-legal-basis': {
error: _('annotation-actions.message.manual-redaction.request-change-legal-basis.error'),
success: _('annotation-actions.message.manual-redaction.request-change-legal-basis.success'),
},
'request-force-redaction': {
error: _('annotation-actions.message.manual-redaction.request-force-redaction.error'),
success: _('annotation-actions.message.manual-redaction.request-force-redaction.success'),
},
'request-image-recategorization': {
error: _('annotation-actions.message.manual-redaction.request-image-recategorization.error'),
success: _('annotation-actions.message.manual-redaction.request-image-recategorization.success'),
},
suggest: {
error: _('annotation-actions.message.manual-redaction.suggest.error'),
success: _('annotation-actions.message.manual-redaction.suggest.success'),
},
undo: {
error: _('annotation-actions.message.manual-redaction.undo.error'),
success: _('annotation-actions.message.manual-redaction.undo.success'),
},
remove: {
error: _('annotation-actions.message.manual-redaction.remove.error'),
success: _('annotation-actions.message.manual-redaction.remove.success'),
},
'request-remove': {
error: _('annotation-actions.message.manual-redaction.request-remove.error'),
success: _('annotation-actions.message.manual-redaction.request-remove.success'),
},
},
};
export const dictionaryActionsTranslations: Record<DictionaryActions, AnnotationActionResponses> = {
add: {
error: _('annotation-actions.message.dictionary.add.error'),

View File

@ -1,23 +1,7 @@
export type ImageCategory = 'signature' | 'logo' | 'formula' | 'image';
export type AnnotationActionMode =
| 'add'
| 'approve'
| 'remove'
| 'change-legal-basis'
| 'decline'
| 'request-remove'
| 'request-change-legal-basis'
| 'recategorize-image'
| 'request-image-recategorization'
| 'suggest'
| 'undo'
| 'force-redaction'
| 'request-force-redaction'
| 'resize'
| 'request-resize';
export type DictionaryActions = 'add' | 'approve' | 'remove' | 'decline' | 'request-remove' | 'suggest' | 'undo';
export type ManualRedactionActions =
| 'add'
| 'approve'

View File

@ -8,7 +8,7 @@ export * from './redaction-log';
export * from './remove-redaction.request';
export * from './manual-add.response';
export * from './approve-request';
export * from './image-recategorization.request';
export * from './recategorization.request';
export * from './resize.request';
export * from './manual-change';
export * from './dictionary-entry-types';

View File

@ -1,4 +1,4 @@
export interface IImageRecategorizationRequest {
export interface IRecategorizationRequest {
readonly annotationId?: string;
readonly comment?: string;
readonly type?: string;