manual redaction flow changes

This commit is contained in:
Timo Bejan 2020-11-11 15:00:28 +02:00
parent 55c8706a9d
commit b128588778
10 changed files with 163 additions and 98 deletions

View File

@ -8,12 +8,18 @@
</div>
{{ manualRedactionEntryWrapper.manualRedactionEntry.value }}
<div
class="red-input-group"
[class.hidden]="manualRedactionEntryWrapper.type === 'DICTIONARY'"
>
<div class="red-input-group" *ngIf="!isDictionaryRequest">
<label translate="manual-redaction.dialog.content.reason"></label>
<input formControlName="reason" name="reason" type="text" rows="2" />
<mat-select formControlName="reason" class="full-width">
<mat-option *ngFor="let option of legalOptions" [value]="option.legalBasis">
{{ option.label }}
</mat-option>
</mat-select>
</div>
<div class="red-input-group" *ngIf="!isDictionaryRequest">
<label translate="manual-redaction.dialog.content.legalBasis"></label>
<input type="text" [value]="redactionForm.get('reason').value" disabled />
</div>
<div class="red-input-group">
@ -21,32 +27,18 @@
<textarea formControlName="comment" name="comment" type="text" rows="4"></textarea>
</div>
<div class="red-input-group">
<div class="red-input-group" *ngIf="isDictionaryRequest">
<label translate="manual-redaction.dialog.content.dictionary"></label>
<mat-select formControlName="dictionary" *ngIf="!isDictionaryRequest">
<mat-option
*ngFor="let dictionary of redactionDictionaries"
[value]="dictionary.type"
>
{{ dictionary.label }}
</mat-option>
</mat-select>
<mat-select formControlName="dictionary" *ngIf="isDictionaryRequest">
<mat-select formControlName="dictionary">
<mat-optgroup [label]="'group.redactions' | translate">
<mat-option
*ngFor="let dictionary of redactionDictionaries"
[value]="dictionary.type"
>
<mat-option *ngFor="let dictionary of redactionDictionaries" [value]="dictionary.type">
{{ dictionary.label }}
</mat-option>
</mat-optgroup>
<mat-optgroup [label]="'group.hints' | translate">
<mat-option
*ngFor="let dictionary of hintDictionaries"
[value]="dictionary.type"
>
<mat-option *ngFor="let dictionary of hintDictionaries" [value]="dictionary.type">
{{ dictionary.label }}
</mat-option>
</mat-optgroup>
@ -55,13 +47,7 @@
</div>
<div class="dialog-actions">
<button
color="primary"
mat-flat-button
[disabled]="!redactionForm.valid"
translate="manual-redaction.dialog.actions.save"
type="submit"
></button>
<button color="primary" mat-flat-button [disabled]="!redactionForm.valid" translate="manual-redaction.dialog.actions.save" type="submit"></button>
</div>
</form>
<button (click)="dialogRef.close()" class="dialog-close" mat-icon-button>

View File

@ -11,6 +11,39 @@ import { ManualAnnotationService } from '../../screens/file/service/manual-annot
import { ManualAnnotationResponse } from '../../screens/file/model/manual-annotation-response';
import { PermissionsService } from '../../common/service/permissions.service';
const LEGAL_OPTIONS = [
{
label: 'the method of manufacture',
legalBasis: 'Reg (EC) No 1107/2009 Art. 63 (2a)'
},
{
label:
'the specification of impurity of the active substance except for the impurities that are considered to be toxicologically, ecotoxicologically or environmentally relevant',
legalBasis: 'Reg (EC) No 1107/2009 Art. 63 (2b)'
},
{
label: 'results of production batches of the active substance including impurities',
legalBasis: 'Reg (EC) No 1107/2009 Art. 63 (2c)'
},
{
label:
'methods of analysis for impurities in the active substance as manufactured except for methods for impurities that are considered to be toxicologically, ecotoxicologically or environmentally relevant',
legalBasis: 'Reg (EC) No 1107/2009 Art. 63 (2d)'
},
{
label: 'links between a producer or importer and the applicant or the authorisation holder',
legalBasis: 'Reg (EC) No 1107/2009 Art. 63 (2e)'
},
{
label: 'information on the complete composition of a plant protection product',
legalBasis: 'Reg (EC) No 1107/2009 Art. 63 (2f)'
},
{
label: 'names and addresses of persons involved in testing on vertebrate animals',
legalBasis: 'Reg (EC) No 1107/2009 Art. 63 (2g)'
}
];
@Component({
selector: 'redaction-manual-annotation-dialog',
templateUrl: './manual-annotation-dialog.component.html',
@ -24,6 +57,8 @@ export class ManualAnnotationDialogComponent implements OnInit {
redactionDictionaries: TypeValue[] = [];
hintDictionaries: TypeValue[] = [];
legalOptions = LEGAL_OPTIONS;
constructor(
private readonly _appStateService: AppStateService,
private readonly _userService: UserService,
@ -38,13 +73,12 @@ export class ManualAnnotationDialogComponent implements OnInit {
async ngOnInit() {
this.isDocumentAdmin = this._permissionsService.isManagerAndOwner();
const commentField = this.isDocumentAdmin ? [null] : [null, Validators.required];
this.isDictionaryRequest = this.manualRedactionEntryWrapper.type === 'DICTIONARY';
this.redactionForm = this._formBuilder.group({
reason: this.isDictionaryRequest ? null : [null, Validators.required],
dictionary: [null, Validators.required],
comment: commentField
reason: this.isDictionaryRequest ? [null] : [null, Validators.required],
dictionary: this.isDictionaryRequest ? [null] : ['manual', Validators.required],
comment: this.isDocumentAdmin ? [null] : [null, Validators.required]
});
for (const key of Object.keys(this._appStateService.dictionaryData)) {
@ -78,8 +112,8 @@ export class ManualAnnotationDialogComponent implements OnInit {
private _enhanceManualRedaction(addRedactionRequest: AddRedactionRequest) {
addRedactionRequest.type = this.redactionForm.get('dictionary').value;
addRedactionRequest.addToDictionary = this.manualRedactionEntryWrapper.type === 'DICTIONARY';
addRedactionRequest.reason = this.redactionForm.get('reason').value;
addRedactionRequest.addToDictionary = this.isDictionaryRequest;
// todo fix this in backend
if (!addRedactionRequest.reason) {
addRedactionRequest.reason = '-';

View File

@ -1,10 +1,10 @@
<div [class.visible]="menuOpen" *ngIf="canPerformAnnotationActions" class="annotation-actions">
<!-- Only owner can accept or reject suggestions-->
<ng-container *ngIf="permissionsService.isManagerAndOwner()">
<button (click)="acceptSuggestion($event, annotation, annotation.modifyDictionary)" *ngIf="annotation.isSuggestion" mat-icon-button>
<ng-container *ngIf="permissionsService.isManagerAndOwner() && annotation.isSuggestion">
<button (click)="acceptSuggestion($event, annotation, annotation.modifyDictionary)" mat-icon-button>
<mat-icon svgIcon="red:check-alt"></mat-icon>
</button>
<button (click)="rejectSuggestion($event, annotation)" *ngIf="annotation.isSuggestion && !canUndoAnnotation()" mat-icon-button>
<button (click)="rejectSuggestion($event, annotation)" *ngIf="!canUndoAnnotation()" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
</ng-container>
@ -17,29 +17,24 @@
<!-- Everyone can suggest to remove annotations, manager will remove directly while user will make a suggestion-->
<!-- For Suggesting Removal, in case of hints, the user can suggest removal from dictionary -->
<button (click)="suggestRemoveAnnotation($event, annotation, true)" *ngIf="annotation.superType === 'hint' && !canUndoAnnotation()" mat-icon-button>
<mat-icon svgIcon="red:trash"></mat-icon>
</button>
<ng-container *ngIf="!annotation.isSuggestion">
<button (click)="suggestRemoveAnnotation($event, annotation, true)" *ngIf="showSimpleSuggestRemove()" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<!-- For Suggesting Removal, in case of redactions, the user can suggest only-here or everywhere-->
<button
(click)="openMenu($event)"
*ngIf="annotation.superType === 'redaction' && !canUndoAnnotation()"
[class.active]="menuOpen"
[matMenuTriggerFor]="menu"
class="confirm"
mat-icon-button
>
<mat-icon svgIcon="red:trash"></mat-icon>
</button>
<mat-menu #menu="matMenu" (closed)="onMenuClosed()" xPosition="before">
<div (click)="suggestRemoveAnnotation($event, annotation, true)" mat-menu-item>
<redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="dictionaryColor"></redaction-annotation-icon>
<div [translate]="'file-preview.tabs.annotations.remove-annotation.remove-from-dict'"></div>
</div>
<div (click)="suggestRemoveAnnotation($event, annotation, false)" mat-menu-item>
<redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="suggestionColor"></redaction-annotation-icon>
<div translate="file-preview.tabs.annotations.remove-annotation.only-here"></div>
</div>
</mat-menu>
<!-- For Suggesting Removal, in case of redactions, the user can suggest only-here or everywhere-->
<button (click)="openMenu($event)" *ngIf="showMenuSuggestRemove()" [class.active]="menuOpen" [matMenuTriggerFor]="menu" class="confirm" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<mat-menu #menu="matMenu" (closed)="onMenuClosed()" xPosition="before">
<div (click)="suggestRemoveAnnotation($event, annotation, true)" mat-menu-item>
<redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="dictionaryColor"></redaction-annotation-icon>
<div [translate]="'file-preview.tabs.annotations.remove-annotation.remove-from-dict'"></div>
</div>
<div (click)="suggestRemoveAnnotation($event, annotation, false)" mat-menu-item>
<redaction-annotation-icon [type]="'rhombus'" [label]="'S'" [color]="suggestionColor"></redaction-annotation-icon>
<div translate="file-preview.tabs.annotations.remove-annotation.only-here"></div>
</div>
</mat-menu>
</ng-container>
</div>

View File

@ -74,13 +74,6 @@ export class AnnotationActionsComponent implements OnInit {
this.menuOpen = false;
}
canUndoAnnotation() {
const isSuggestionOfCurrentUser = this.annotation.isSuggestion && this.annotation.userId === this.permissionsService.currentUserId;
const isManualAnnotationOfCurrentUser =
this.annotation.manual && this.annotation.userId === this.permissionsService.currentUserId && this.permissionsService.isManagerAndOwner();
return isSuggestionOfCurrentUser || isManualAnnotationOfCurrentUser;
}
get suggestionColor() {
return this.appStateService.getDictionaryColor('suggestion');
}
@ -88,4 +81,31 @@ export class AnnotationActionsComponent implements OnInit {
get dictionaryColor() {
return this.appStateService.getDictionaryColor('suggestion-dictionary');
}
showSimpleSuggestRemove() {
return this._canRemove() && !this._showComplexMenu();
}
showMenuSuggestRemove() {
return this._canRemove() && this._showComplexMenu();
}
private _showComplexMenu() {
// we can show the complex dialog only for redactions that are not manual, but this is already checked via canUndo
return this.annotation.superType === 'redaction' && this.annotation.dictionary !== 'manual';
}
canUndoAnnotation() {
// suggestions of current user can be undone
const isSuggestionOfCurrentUser = this.annotation.isSuggestion && this.annotation.userId === this.permissionsService.currentUserId;
// or any performed manual actions and you are the manager, provided that it is not a suggestion
const isActionOfManger = this.permissionsService.isManagerAndOwner() && this.annotation.userId === this.permissionsService.currentUserId;
return isSuggestionOfCurrentUser || isActionOfManger;
}
private _canRemove() {
const canUndo = this.canUndoAnnotation();
// you can do a simple remove for anything you cannot undo, that is not ignored
return !canUndo && !this.annotation.isIgnored;
}
}

View File

@ -56,16 +56,6 @@ export class AnnotationWrapper {
annotationWrapper.userId = manualRedactionEntry?.user || idRemoval?.user;
if (redactionLogEntry) {
if (redactionLogEntry.manual) {
if (!manualRedactionEntry && redactionLogEntry.manualRedactionType === 'ADD') {
// do not draw if it is no longer in the manual redactions
annotationWrapper.shouldDraw = false;
}
if (manualRedactionEntry) {
annotationWrapper.shouldDraw = manualRedactionEntry.status === 'REQUESTED' || manualRedactionEntry.status === 'APPROVED';
}
}
annotationWrapper.id = redactionLogEntry.id;
annotationWrapper.redaction = redactionLogEntry.redacted;
annotationWrapper.hint = redactionLogEntry.hint;
@ -77,7 +67,26 @@ export class AnnotationWrapper {
// either marked as manual or idRemove or manualRedactionEntry exists
annotationWrapper.manual = redactionLogEntry.manual;
annotationWrapper.modifyDictionary = !!manualRedactionEntry?.addToDictionary || !!idRemoval?.removeFromDictionary;
if (redactionLogEntry.manual) {
// marked as add but the entryToAdd has been undone
if (!manualRedactionEntry && redactionLogEntry.manualRedactionType === 'ADD') {
// do not draw if it is no longer in the manual redactions
annotationWrapper.shouldDraw = false;
}
if (manualRedactionEntry) {
annotationWrapper.shouldDraw = manualRedactionEntry.status === 'REQUESTED' || manualRedactionEntry.status === 'APPROVED';
}
// marked as remove but the idRemoval has been undone
if (redactionLogEntry.manualRedactionType === 'REMOVE' && !idRemoval) {
const dictionary = dictionaryData[redactionLogEntry.type];
annotationWrapper.redaction = !dictionary.hint;
annotationWrapper.hint = dictionary.hint;
}
}
} else {
// no redaction log entry - not yet processed
const dictionary = dictionaryData[manualRedactionEntry.type];
annotationWrapper.id = manualRedactionEntry.id;
annotationWrapper.redaction = !dictionary.hint;

View File

@ -13,7 +13,6 @@ export class AnnotationDrawService {
public drawAnnotations(activeViewer: WebViewerInstance, annotationWrappers: AnnotationWrapper[]) {
annotationWrappers.forEach((annotation) => {
console.log('draw', annotation);
this.drawAnnotation(activeViewer, annotation);
});
}

View File

@ -124,24 +124,38 @@ export class ManualAnnotationService {
// /manualRedaction/request/remove/
removeOrSuggestRemoveAnnotation(annotationWrapper: AnnotationWrapper, removeFromDictionary: boolean = false) {
if (this._permissionsService.isManagerAndOwner()) {
return this._manualRedactionControllerService
.removeRedaction(
{
annotationId: annotationWrapper.id,
removeFromDictionary: removeFromDictionary,
comment: 'Auto'
},
this._appStateService.activeProjectId,
this._appStateService.activeFileId
)
.pipe(
tap(
() => this._notify('manual-annotation.remove-redaction-request.success'),
() => {
this._notify('manual-annotation.remove-redaction-request.error', NotificationType.ERROR);
}
// if it was something manual simply decline the existing request
if (annotationWrapper.dictionary === 'manual') {
return this._manualRedactionControllerService
.declineRequest(this._appStateService.activeProjectId, this._appStateService.activeFileId, annotationWrapper.id)
.pipe(
tap(
() => this._notify('manual-annotation.undo-request.success'),
() => {
this._notify('manual-annotation.undo-request.error', NotificationType.ERROR);
}
)
);
} else {
return this._manualRedactionControllerService
.removeRedaction(
{
annotationId: annotationWrapper.id,
removeFromDictionary: removeFromDictionary,
comment: 'Auto'
},
this._appStateService.activeProjectId,
this._appStateService.activeFileId
)
);
.pipe(
tap(
() => this._notify('manual-annotation.remove-redaction-request.success'),
() => {
this._notify('manual-annotation.remove-redaction-request.error', NotificationType.ERROR);
}
)
);
}
} else {
return this._manualRedactionControllerService
.requestRemoveRedaction(

View File

@ -370,7 +370,11 @@ export class AppStateService {
type: 'suggestion',
virtual: true
};
this._dictionaryData['manual'] = {
hexColor: colors.defaultColor,
type: 'manual',
virtual: true
};
this._dictionaryData['suggestion-dictionary'] = {
hexColor: '#5B97DB',
type: 'suggestion-dictionary',

View File

@ -33,6 +33,7 @@
"text": "Selected text:",
"dictionary": "Type",
"reason": "Reason",
"legalBasis": "Legal Basis",
"comment": "Comment"
}
}