use dialog to change dictionary when accepting a recommendation

This commit is contained in:
Dan Percic 2022-01-26 18:37:04 +02:00
parent 65f879af8f
commit a6ca95e7f0
9 changed files with 227 additions and 44 deletions

View File

@ -7,7 +7,7 @@ import { ManualAnnotationService } from '../../services/manual-annotation.servic
import { ManualAnnotationResponse } from '@models/file/manual-annotation-response';
import { PermissionsService } from '@services/permissions.service';
import { JustificationsService } from '@services/entity-services/justifications.service';
import { Dictionary, Dossier, File, IAddRedactionRequest } from '@red/domain';
import { Dictionary, Dossier, File, IAddRedactionRequest, IManualAddResponse } from '@red/domain';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { DictionaryService } from '@shared/services/dictionary.service';
import { BaseDialogComponent } from '@iqser/common-ui';
@ -75,7 +75,10 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
async ngOnInit() {
super.ngOnInit();
this.possibleDictionaries = await this._getPossibleDictionaries();
this.possibleDictionaries = await this._appStateService.getDictionariesOptions(
this._dossier.dossierTemplateId,
this._dossier.dossierId,
);
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this._dossier.dossierTemplateId));
this.legalOptions = data.map(lbm => ({
legalBasis: lbm.reason,
@ -89,7 +92,8 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
save() {
this._enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry);
this._manualAnnotationService.addAnnotation(this.data.manualRedactionEntryWrapper.manualRedactionEntry, this.data.file).subscribe(
response => this._dialogRef.close(new ManualAnnotationResponse(this.data.manualRedactionEntryWrapper, response)),
(response: IManualAddResponse) =>
this._dialogRef.close(new ManualAnnotationResponse(this.data.manualRedactionEntryWrapper, response)),
() => this._dialogRef.close(),
);
}
@ -102,31 +106,6 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
);
}
private async _getPossibleDictionaries(): Promise<Dictionary[]> {
const possibleDictionaries: Dictionary[] = [];
const dossier = this._dossier;
const dossierDictionary = await firstValueFrom(
this._dictionaryService.getForType(dossier.dossierTemplateId, 'dossier_redaction', dossier.dossierId),
);
for (const key of Object.keys(this._appStateService.dictionaryData[dossier.dossierTemplateId])) {
const dictionaryData = this._appStateService.getDictionary(key, dossier.dossierTemplateId);
if (!dictionaryData.virtual && dictionaryData.addToDictionaryAction) {
possibleDictionaries.push(dictionaryData);
}
}
if (dossierDictionary.addToDictionaryAction) {
// TODO fix this in the backend
possibleDictionaries.push(new Dictionary({ ...dossierDictionary, type: 'dossier_redaction' }));
}
possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label));
return possibleDictionaries;
}
private _getForm(): FormGroup {
return this._formBuilder.group({
section: [null],

View File

@ -42,7 +42,7 @@
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.convertRecommendationToAnnotation($event, annotations, file, annotationsChanged)"
(action)="acceptRecommendation($event)"
*ngIf="annotationPermissions.canAcceptRecommendation"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.accept-recommendation.label' | translate"

View File

@ -8,6 +8,14 @@ import { UserService } from '@services/user.service';
import { Dossier, File } from '@red/domain';
import { Required } from '@iqser/common-ui';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { MatDialog } from '@angular/material/dialog';
import {
AcceptRecommendationData,
AcceptRecommendationDialogComponent,
AcceptRecommendationReturnType,
} from '../../dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component';
import { filter } from 'rxjs/operators';
import { ManualAnnotationService } from '../../../../services/manual-annotation.service';
export const AnnotationButtonTypes = {
dark: 'dark',
@ -32,10 +40,12 @@ export class AnnotationActionsComponent implements OnChanges {
annotationPermissions: AnnotationPermissions;
constructor(
private readonly _manualAnnotationService: ManualAnnotationService,
readonly annotationActionsService: AnnotationActionsService,
private readonly _permissionsService: PermissionsService,
private readonly _dossiersService: DossiersService,
private readonly _userService: UserService,
private readonly _dialog: MatDialog,
) {}
private _annotations: AnnotationWrapper[];
@ -77,7 +87,7 @@ export class AnnotationActionsComponent implements OnChanges {
this._setPermissions();
}
removeOrSuggestRemoveAnnotation($event, removeFromDict: boolean) {
removeOrSuggestRemoveAnnotation($event: MouseEvent, removeFromDict: boolean) {
$event.stopPropagation();
this.annotationActionsService.removeOrSuggestRemoveAnnotation(
$event,
@ -88,10 +98,32 @@ export class AnnotationActionsComponent implements OnChanges {
);
}
markAsFalsePositive($event) {
markAsFalsePositive($event: MouseEvent) {
this.annotationActionsService.markAsFalsePositive($event, this.annotations, this.file, this.annotationsChanged);
}
acceptRecommendation($event: MouseEvent) {
if (this.annotations.length > 1) {
this.annotationActionsService.convertRecommendationToAnnotation($event, this.annotations, this.file, this.annotationsChanged);
}
const dialogRef = this._dialog.open<AcceptRecommendationDialogComponent, AcceptRecommendationData, AcceptRecommendationReturnType>(
AcceptRecommendationDialogComponent,
{ autoFocus: true, data: { annotation: this.annotations[0], file: this.file } },
);
const dialogClosed = dialogRef.afterClosed().pipe(filter(value => !!value && !!value.annotation));
dialogClosed.subscribe(({ annotation, comment: commentText }) => {
const comment = commentText ? { text: commentText } : undefined;
this.annotationActionsService.convertRecommendationToAnnotation(
$event,
[annotation],
this.file,
this.annotationsChanged,
comment,
);
});
}
hideAnnotation($event: MouseEvent) {
$event.stopPropagation();
this.viewer.Core.annotationManager.hideAnnotations(this.viewerAnnotations);

View File

@ -0,0 +1,43 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="form">
<div [translate]="'accept-recommendation-dialog.header'" class="dialog-header heading-l"></div>
<div class="dialog-content">
<div class="iqser-input-group">
<label translate="manual-annotation.dialog.content.text"></label>
</div>
{{ format(data.annotation.value) }}
<div class="iqser-input-group required w-400">
<label translate="manual-annotation.dialog.content.dictionary"></label>
<mat-select formControlName="dictionary">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
<mat-option
*ngFor="let dictionary of possibleDictionaries"
[matTooltip]="dictionary.description"
[value]="dictionary.type"
matTooltipPosition="after"
>
<span>
{{ dictionary.label }}
</span>
</mat-option>
</mat-select>
</div>
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
<label translate="manual-annotation.dialog.content.comment"></label>
<textarea formControlName="comment" name="comment" redactionHasScrollbar rows="4" type="text"></textarea>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="form.invalid" color="primary" mat-flat-button type="submit">
{{ 'manual-annotation.dialog.actions.save' | translate }}
</button>
</div>
</form>
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
</section>

View File

@ -0,0 +1,94 @@
import { Component, Inject, Injector, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AppStateService } from '@state/app-state.service';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { PermissionsService } from '@services/permissions.service';
import { Dictionary, Dossier, File } from '@red/domain';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { BaseDialogComponent } from '@iqser/common-ui';
import { DictionaryService } from '@shared/services/dictionary.service';
import { ManualAnnotationService } from '../../../../services/manual-annotation.service';
import { AnnotationWrapper } from '../../../../../../models/file/annotation.wrapper';
export interface AcceptRecommendationData {
readonly annotation: AnnotationWrapper;
readonly file: File;
}
export interface AcceptRecommendationReturnType {
readonly annotation: AnnotationWrapper;
readonly comment: string;
}
@Component({
templateUrl: './accept-recommendation-dialog.component.html',
})
export class AcceptRecommendationDialogComponent extends BaseDialogComponent implements OnInit {
isDocumentAdmin: boolean;
possibleDictionaries: Dictionary[] = [];
private readonly _dossier: Dossier;
constructor(
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder,
private readonly _manualAnnotationService: ManualAnnotationService,
private readonly _permissionsService: PermissionsService,
private readonly _dossiersService: DossiersService,
private readonly _dictionaryService: DictionaryService,
protected readonly _injector: Injector,
protected readonly _dialogRef: MatDialogRef<AcceptRecommendationDialogComponent, AcceptRecommendationReturnType>,
@Inject(MAT_DIALOG_DATA) readonly data: AcceptRecommendationData,
) {
super(_injector, _dialogRef);
this._dossier = this._dossiersService.find(this.data.file.dossierId);
this.isDocumentAdmin = this._permissionsService.isApprover(this._dossier);
this.form = this._getForm();
this.initialFormValue = this.form.getRawValue();
}
get displayedDictionaryLabel() {
const dictType = this.form.get('dictionary').value as string;
if (dictType && this.possibleDictionaries.length) {
return this.possibleDictionaries.find(d => d.type === dictType).label;
}
return null;
}
async ngOnInit() {
super.ngOnInit();
this.possibleDictionaries = await this._appStateService.getDictionariesOptions(
this._dossier.dossierTemplateId,
this._dossier.dossierId,
);
this.form.patchValue({
dictionary: this.possibleDictionaries.find(dict => dict.type === this.data.annotation.recommendationType).type,
});
}
save() {
const recommendationType = this.form.get('dictionary').value;
const annotation = Object.assign({}, this.data.annotation);
annotation.recommendationType = recommendationType;
this._dialogRef.close({
annotation,
comment: this.form.get('comment').value as string,
});
}
format(value: string) {
return value.replace(
// eslint-disable-next-line no-control-regex,max-len
/([^\s\d-]{2,})[-\u00AD]\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]/gi,
'$1',
);
}
private _getForm(): FormGroup {
return this._formBuilder.group({
dictionary: [Validators.required],
comment: [null],
});
}
}

View File

@ -19,6 +19,7 @@ import { TypeAnnotationIconComponent } from './components/type-annotation-icon/t
import { OverlayModule } from '@angular/cdk/overlay';
import { ViewSwitchComponent } from './components/view-switch/view-switch.component';
import { UserManagementComponent } from './components/user-management/user-management.component';
import { AcceptRecommendationDialogComponent } from './dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component';
const routes: Routes = [
{
@ -44,6 +45,7 @@ const routes: Routes = [
TypeAnnotationIconComponent,
ViewSwitchComponent,
UserManagementComponent,
AcceptRecommendationDialogComponent,
],
imports: [
RouterModule.forChild(routes),

View File

@ -184,11 +184,16 @@ export class AnnotationActionsService {
annotations: AnnotationWrapper[],
file: File,
annotationsChanged: EventEmitter<AnnotationWrapper>,
comment?: { text: string },
) {
$event?.stopPropagation();
annotations.forEach(annotation => {
this._processObsAndEmit(this._manualAnnotationService.addRecommendation(annotation, file), annotation, annotationsChanged);
this._processObsAndEmit(
this._manualAnnotationService.addRecommendation(annotation, file, comment),
annotation,
annotationsChanged,
);
});
}
@ -486,15 +491,19 @@ export class AnnotationActionsService {
return this._dossiersService.find(file.dossierId);
}
private _processObsAndEmit(obs: Observable<any>, annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) {
obs.subscribe(
() => {
private _processObsAndEmit(
obs: Observable<unknown>,
annotation: AnnotationWrapper,
annotationsChanged: EventEmitter<AnnotationWrapper>,
) {
obs.subscribe({
next: () => {
annotationsChanged.emit(annotation);
},
() => {
error: () => {
annotationsChanged.emit();
},
);
});
}
private _getFalsePositiveText(annotation: AnnotationWrapper) {

View File

@ -60,9 +60,9 @@ export class ManualAnnotationService extends GenericService<IManualAddResponse>
: this[this.CONFIG[mode]](body, secondParam, file.dossierId, file.id);
return obs.pipe(
tap(
() => this._toaster.success(this._getMessage(mode, modifyDictionary), { positionClass: 'toast-file-preview' }),
(error: HttpErrorResponse) => {
tap({
next: () => this._toaster.success(this._getMessage(mode, modifyDictionary), { positionClass: 'toast-file-preview' }),
error: (error: HttpErrorResponse) => {
const isConflict = error.status === HttpStatusCode.Conflict;
this._toaster.error(this._getMessage(mode, modifyDictionary, true, isConflict), {
error,
@ -73,7 +73,7 @@ export class ManualAnnotationService extends GenericService<IManualAddResponse>
positionClass: 'toast-file-preview',
});
},
),
}),
);
}
@ -89,15 +89,15 @@ export class ManualAnnotationService extends GenericService<IManualAddResponse>
return super.delete({}, url);
}
addRecommendation(annotation: AnnotationWrapper, file: File) {
addRecommendation(annotation: AnnotationWrapper, file: File, comment = { text: 'Accepted Recommendation' }) {
const manualRedactionEntry: IAddRedactionRequest = {};
manualRedactionEntry.addToDictionary = true;
// set the ID as reason, so we can hide the suggestion
manualRedactionEntry.reason = annotation.id;
manualRedactionEntry.reason = annotation.annotationId;
manualRedactionEntry.value = annotation.value;
manualRedactionEntry.positions = annotation.positions;
manualRedactionEntry.type = annotation.recommendationType;
manualRedactionEntry.comment = { text: 'Accepted Recommendation' };
manualRedactionEntry.comment = comment;
return this.addAnnotation(manualRedactionEntry, file);
}

View File

@ -46,6 +46,30 @@ export class AppStateService {
: undefined;
}
async getDictionariesOptions(dossierTemplateId: string, dossierId: string): Promise<Dictionary[]> {
const possibleDictionaries: Dictionary[] = [];
const dossierDictionary = await firstValueFrom(
this._dictionaryService.getForType(dossierTemplateId, 'dossier_redaction', dossierId),
);
for (const key of Object.keys(this.dictionaryData[dossierTemplateId])) {
const dictionaryData = this.getDictionary(key, dossierTemplateId);
if (!dictionaryData.virtual && dictionaryData.addToDictionaryAction) {
possibleDictionaries.push(dictionaryData);
}
}
if (dossierDictionary.addToDictionaryAction) {
// TODO fix this in the backend
possibleDictionaries.push(new Dictionary({ ...dossierDictionary, type: 'dossier_redaction' }));
}
possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label));
return possibleDictionaries;
}
getDictionaryColor(type: string, dossierTemplateId: string) {
return !this._dictionaryData
? '#cccccc'