Pull request #391: Rss editing
Merge in RED/ui from rss-editing to master * commit '545303b6d5a85d4b80a6f4ed7216fd921d6c6b4a': RED-3800 rss editing RED-3800: RSS editing RED-3800 rss editing RED-3800: RSS editing RED-3800: RSS editing RED-3800 rss editing RED-3800 rss editing
This commit is contained in:
commit
5277d4384e
@ -21,7 +21,7 @@
|
||||
</strong>
|
||||
{{ annotation.entity.label }}
|
||||
</div>
|
||||
<div *ngIf="annotation.shortContent && !annotation.isHint">
|
||||
<div *ngIf="annotation.shortContent && !annotation.isHint && !iqserPermissionsService.has(roles.getRss)">
|
||||
<strong><span translate="content"></span>: </strong>{{ annotation.shortContent }}
|
||||
</div>
|
||||
<div *ngIf="annotation.isEarmark">
|
||||
|
||||
@ -2,6 +2,8 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { annotationTypesTranslations } from '@translations/annotation-types-translations';
|
||||
import { ROLES } from '@users/roles';
|
||||
import { IqserPermissionsService } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-annotation-card',
|
||||
@ -10,9 +12,10 @@ import { annotationTypesTranslations } from '@translations/annotation-types-tran
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AnnotationCardComponent {
|
||||
readonly roles = ROLES;
|
||||
annotationTypesTranslations = annotationTypesTranslations;
|
||||
@Input() annotation: AnnotationWrapper;
|
||||
@Input() isSelected = false;
|
||||
|
||||
constructor(readonly multiSelectService: MultiSelectService) {}
|
||||
constructor(readonly iqserPermissionsService: IqserPermissionsService, readonly multiSelectService: MultiSelectService) {}
|
||||
}
|
||||
|
||||
@ -46,14 +46,12 @@
|
||||
[value]="dictionary.type"
|
||||
matTooltipPosition="after"
|
||||
>
|
||||
<span>
|
||||
{{ dictionary.label }}
|
||||
</span>
|
||||
<span> {{ dictionary.label }} </span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!isDictionaryRequest" class="iqser-input-group required w-450">
|
||||
<div *ngIf="!isDictionaryRequest && !iqserPermissionsService.has(roles.getRss)" class="iqser-input-group required w-450">
|
||||
<label translate="manual-annotation.dialog.content.reason"></label>
|
||||
<mat-select
|
||||
[placeholder]="'manual-annotation.dialog.content.reason-placeholder' | translate"
|
||||
@ -71,7 +69,7 @@
|
||||
</mat-select>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!isDictionaryRequest" class="iqser-input-group w-450">
|
||||
<div *ngIf="!isDictionaryRequest && !iqserPermissionsService.has(roles.getRss)" class="iqser-input-group w-450">
|
||||
<label translate="manual-annotation.dialog.content.legalBasis"></label>
|
||||
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
|
||||
</div>
|
||||
|
||||
@ -6,10 +6,11 @@ import { JustificationsService } from '@services/entity-services/justifications.
|
||||
import { Dictionary, Dossier, File, IAddRedactionRequest, SuperTypes } from '@red/domain';
|
||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||
import { BaseDialogComponent, CircleButtonTypes } from '@iqser/common-ui';
|
||||
import { BaseDialogComponent, CircleButtonTypes, IqserPermissionsService } from '@iqser/common-ui';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ManualRedactionService } from '../../services/manual-redaction.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { ROLES } from '@users/roles';
|
||||
|
||||
export interface LegalBasisOption {
|
||||
label?: string;
|
||||
@ -22,6 +23,7 @@ export interface LegalBasisOption {
|
||||
styleUrls: ['./manual-annotation-dialog.component.scss'],
|
||||
})
|
||||
export class ManualAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
readonly roles = ROLES;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
isDictionaryRequest: boolean;
|
||||
isFalsePositiveRequest: boolean;
|
||||
@ -35,6 +37,7 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
|
||||
private readonly _dossier: Dossier;
|
||||
|
||||
constructor(
|
||||
readonly iqserPermissionsService: IqserPermissionsService,
|
||||
private readonly _justificationsService: JustificationsService,
|
||||
private readonly _manualRedactionService: ManualRedactionService,
|
||||
activeDossiersService: ActiveDossiersService,
|
||||
@ -87,6 +90,9 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
|
||||
}));
|
||||
|
||||
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
this._selectReason();
|
||||
|
||||
if (!this.isRectangle) {
|
||||
this._formatSelectedTextValue();
|
||||
}
|
||||
@ -165,9 +171,15 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
|
||||
addRedactionRequest.reason = legalOption.description;
|
||||
addRedactionRequest.legalBasis = legalOption.legalBasis;
|
||||
}
|
||||
// todo fix this in backend
|
||||
addRedactionRequest.addToDictionary = this.isDictionaryRequest && addRedactionRequest.type !== 'dossier_redaction';
|
||||
addRedactionRequest.addToDossierDictionary = this.isDictionaryRequest && addRedactionRequest.type === 'dossier_redaction';
|
||||
|
||||
if (this.iqserPermissionsService.has(ROLES.getRss)) {
|
||||
const selectedType = this.possibleDictionaries.find(d => d.type === addRedactionRequest.type);
|
||||
console.log(selectedType.hasDictionary);
|
||||
addRedactionRequest.addToDictionary = selectedType.hasDictionary;
|
||||
} else {
|
||||
addRedactionRequest.addToDictionary = this.isDictionaryRequest && addRedactionRequest.type !== 'dossier_redaction';
|
||||
addRedactionRequest.addToDossierDictionary = this.isDictionaryRequest && addRedactionRequest.type === 'dossier_redaction';
|
||||
}
|
||||
|
||||
if (!addRedactionRequest.reason) {
|
||||
addRedactionRequest.reason = 'Dictionary Request';
|
||||
@ -179,4 +191,10 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
|
||||
? this.form.get('classification').value
|
||||
: this.form.get('selectedText').value;
|
||||
}
|
||||
|
||||
private _selectReason() {
|
||||
if (this.legalOptions.length === 1) {
|
||||
this.form.get('reason').setValue(this.legalOptions[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,28 +1,76 @@
|
||||
<section class="dialog">
|
||||
<div translate="rss-dialog.title" class="dialog-header heading-l"></div>
|
||||
<div class="dialog-header heading-l" translate="rss-dialog.title"></div>
|
||||
|
||||
<hr />
|
||||
<div class="dialog-content">
|
||||
<div *ngIf="rssData$ | async as rssEntry">
|
||||
<div *ngFor="let entry of rssEntry.result | keyvalue: originalOrder" class="rss-row">
|
||||
<div class="rss-key">{{ entry.key }}:</div>
|
||||
<div class="rss-value">{{ entry.value }}</div>
|
||||
</div>
|
||||
<div *ngIf="rssData$ | async as rssEntry" class="table output-data">
|
||||
<div class="table-header">Component</div>
|
||||
<div class="table-header">Value</div>
|
||||
<div class="table-header">Transformation</div>
|
||||
<div class="table-header">Annotations</div>
|
||||
|
||||
<ng-container *ngFor="let entry of rssEntry.result | keyvalue: originalOrder">
|
||||
<div class="bold">{{ entry.key }}</div>
|
||||
<div>
|
||||
<iqser-editable-input
|
||||
(save)="saveEdit($event, entry)"
|
||||
[buttonsType]="iconButtonTypes.dark"
|
||||
[cancelTooltip]="'rss-dialog.actions.cancel-edit' | translate"
|
||||
[editTooltip]="'rss-dialog.actions.edit' | translate"
|
||||
[saveTooltip]="'rss-dialog.actions.save' | translate"
|
||||
[value]="entry.value.value ?? entry.value.originalValue"
|
||||
>
|
||||
<ng-container slot="editing">
|
||||
<iqser-circle-button
|
||||
(action)="undo(entry)"
|
||||
*ngIf="entry.value.value"
|
||||
[showDot]="true"
|
||||
[tooltip]="'rss-dialog.actions.undo' | translate: { value: entry.value.originalValue }"
|
||||
[type]="iconButtonTypes.dark"
|
||||
class="ml-2"
|
||||
icon="red:undo"
|
||||
></iqser-circle-button>
|
||||
</ng-container>
|
||||
</iqser-editable-input>
|
||||
</div>
|
||||
<div>{{ entry.value.transformation }}</div>
|
||||
<!-- <div>{{ entry.value.scmAnnotations || '-' }}</div>-->
|
||||
<div>
|
||||
<ul *ngIf="entry.value.scmAnnotations; else noAnnotations" class="pl-0">
|
||||
<li
|
||||
*ngFor="let annotation of entry.value.scmAnnotations"
|
||||
class="mb-8"
|
||||
[innerHTML]="
|
||||
'rss-dialog.annotations'
|
||||
| translate
|
||||
: {
|
||||
ruleNumber: annotation.ruleNumber,
|
||||
pageCount: annotation.pages.length,
|
||||
pages: annotation.pages.join(','),
|
||||
type: annotation.type
|
||||
}
|
||||
"
|
||||
></li>
|
||||
</ul>
|
||||
|
||||
<ng-template #noAnnotations>-</ng-template>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button color="primary" mat-flat-button type="submit" (click)="exportJSON()">
|
||||
<button (click)="exportJSON()" color="primary" mat-flat-button type="submit">
|
||||
{{ 'rss-dialog.actions.export-json' | translate }}
|
||||
</button>
|
||||
<button color="primary" mat-flat-button type="button" (click)="exportXML()">
|
||||
<button (click)="exportXML()" color="primary" mat-flat-button type="button">
|
||||
{{ 'rss-dialog.actions.export-xml' | translate }}
|
||||
</button>
|
||||
<button color="primary" mat-flat-button type="button" (click)="exportAllInDossier()" *ngIf="userPreferences.areDevFeaturesEnabled">
|
||||
<button (click)="exportAllInDossier()" *ngIf="userPreferences.areDevFeaturesEnabled" color="primary" mat-flat-button type="button">
|
||||
{{ 'Export All' }}
|
||||
</button>
|
||||
<div class="all-caps-label cancel" mat-dialog-close translate="rss-dialog.actions.close"></div>
|
||||
</div>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -19,3 +19,47 @@
|
||||
.dialog-content {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.table {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
|
||||
> div {
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.value-content {
|
||||
.value {
|
||||
}
|
||||
|
||||
.actions {
|
||||
}
|
||||
}
|
||||
|
||||
.table-header {
|
||||
margin: 10px 0;
|
||||
border-bottom: 1px solid var(--iqser-separator);
|
||||
background-color: var(--iqser-grey-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.annotation-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 1fr 1fr 5fr;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.output-data > div:nth-child(8n + 9),
|
||||
.output-data > div:nth-child(8n + 10),
|
||||
.output-data > div:nth-child(8n + 11),
|
||||
.output-data > div:nth-child(8n + 12) {
|
||||
background: var(--iqser-grey-8);
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { BaseDialogComponent, IconButtonTypes } from '@iqser/common-ui';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { RssService } from '@services/files/rss.service';
|
||||
import { IFile, IRssEntry } from '@red/domain';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { FilesMapService } from '@services/files/files-map.service';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
@ -17,10 +17,10 @@ interface RssData {
|
||||
templateUrl: './rss-dialog.component.html',
|
||||
styleUrls: ['./rss-dialog.component.scss'],
|
||||
})
|
||||
export class RssDialogComponent extends BaseDialogComponent {
|
||||
rssData$: Observable<IRssEntry>;
|
||||
export class RssDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
|
||||
originalOrder = (a: KeyValue<string, string>, b: KeyValue<string, string>): number => 0;
|
||||
rssData$ = new BehaviorSubject<IRssEntry>(null);
|
||||
|
||||
constructor(
|
||||
protected readonly _dialogRef: MatDialogRef<RssDialogComponent>,
|
||||
@ -30,21 +30,14 @@ export class RssDialogComponent extends BaseDialogComponent {
|
||||
@Inject(MAT_DIALOG_DATA) readonly data: RssData,
|
||||
) {
|
||||
super(_dialogRef);
|
||||
this.rssData$ = this._rssService.getRSSData(this.data.file.dossierId, this.data.file.fileId).pipe(
|
||||
map(entry => {
|
||||
const mapped = {};
|
||||
for (const key of Object.keys(entry.result)) {
|
||||
const newKey = key.replace(new RegExp('_', 'g'), ' ');
|
||||
mapped[newKey] = entry.result[key];
|
||||
}
|
||||
return {
|
||||
filaName: entry.filaName,
|
||||
result: mapped,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.#loadData();
|
||||
}
|
||||
|
||||
originalOrder = (a: KeyValue<string, any>, b: KeyValue<string, any>): number => 0;
|
||||
|
||||
exportJSON() {
|
||||
this._rssService.exportJSON(this.data.file.dossierId, this.data.file.fileId, this.data.file.filename).subscribe();
|
||||
}
|
||||
@ -64,4 +57,40 @@ export class RssDialogComponent extends BaseDialogComponent {
|
||||
save(): void {
|
||||
this.exportJSON();
|
||||
}
|
||||
|
||||
async undo(entry: KeyValue<string, any>) {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._rssService.revertOverride(this.data.file.dossierId, this.data.file.fileId, [entry.value.originalKey]));
|
||||
await this.#loadData();
|
||||
}
|
||||
|
||||
async saveEdit(event: string, entry: KeyValue<string, any>) {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(
|
||||
this._rssService.override(this.data.file.dossierId, this.data.file.fileId, { [entry.value.originalKey]: event }),
|
||||
);
|
||||
await this.#loadData();
|
||||
}
|
||||
|
||||
async #loadData(): Promise<void> {
|
||||
this._loadingService.start();
|
||||
const rssData = await firstValueFrom(
|
||||
this._rssService.getRSSData(this.data.file.dossierId, this.data.file.fileId).pipe(
|
||||
map(entry => {
|
||||
const mapped = {};
|
||||
for (const key of Object.keys(entry.result)) {
|
||||
const newKey = key.replace(new RegExp('_', 'g'), ' ');
|
||||
(<any>entry.result[key]).originalKey = key;
|
||||
mapped[newKey] = entry.result[key];
|
||||
}
|
||||
return {
|
||||
filaName: entry.filaName,
|
||||
result: mapped,
|
||||
};
|
||||
}),
|
||||
),
|
||||
);
|
||||
this.rssData$.next(rssData);
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,13 +253,15 @@ export class PdfProxyService {
|
||||
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION),
|
||||
});
|
||||
|
||||
popups.push({
|
||||
type: 'actionButton',
|
||||
dataElement: TextPopups.ADD_DICTIONARY,
|
||||
img: this.#addDictIcon,
|
||||
title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY),
|
||||
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY),
|
||||
});
|
||||
if (!this._iqserPermissionsService.has(ROLES.getRss)) {
|
||||
popups.push({
|
||||
type: 'actionButton',
|
||||
dataElement: TextPopups.ADD_DICTIONARY,
|
||||
img: this.#addDictIcon,
|
||||
title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY),
|
||||
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._pdf.configureTextPopups(popups);
|
||||
|
||||
@ -15,7 +15,7 @@ export class RssService extends GenericService<void> {
|
||||
const queryParams: QueryParam[] = [];
|
||||
queryParams.push({ key: 'fileId', value: fileId });
|
||||
|
||||
const rssData$ = this._getOne<IRssData>([dossierId], 'rss', queryParams);
|
||||
const rssData$ = this._getOne<IRssData>([dossierId], 'rss/detailed', queryParams);
|
||||
return rssData$.pipe(
|
||||
map(data => data.files[0]),
|
||||
catchError(() => of({} as IRssEntry)),
|
||||
@ -40,6 +40,20 @@ export class RssService extends GenericService<void> {
|
||||
});
|
||||
}
|
||||
|
||||
@Validate()
|
||||
override(
|
||||
@RequiredParam() dossierId: string,
|
||||
@RequiredParam() fileId: string,
|
||||
@RequiredParam() componentOverrides: Record<string, string>,
|
||||
) {
|
||||
return this._post({ componentOverrides }, `rss/override/${dossierId}/${fileId}`);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
revertOverride(@RequiredParam() dossierId: string, @RequiredParam() fileId: string, @RequiredParam() components: string[]) {
|
||||
return this._post({ components }, `rss/override/revert/${dossierId}/${fileId}`);
|
||||
}
|
||||
|
||||
exportJSON(dossierId: string, fileId: string, name: string) {
|
||||
return this.getRSSData(dossierId, fileId).pipe(
|
||||
tap(data => {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"ADMIN_CONTACT_NAME": null,
|
||||
"ADMIN_CONTACT_URL": null,
|
||||
"API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1",
|
||||
"API_URL": "https://syngenta-scm.iqser.cloud/redaction-gateway-v1",
|
||||
"APP_NAME": "RedactManager",
|
||||
"AUTO_READ_TIME": 3,
|
||||
"BACKEND_APP_VERSION": "4.4.40",
|
||||
@ -11,11 +11,11 @@
|
||||
"MAX_RETRIES_ON_SERVER_ERROR": 3,
|
||||
"OAUTH_CLIENT_ID": "redaction",
|
||||
"OAUTH_IDP_HINT": null,
|
||||
"OAUTH_URL": "https://dev-08.iqser.cloud/auth/realms/redaction",
|
||||
"OAUTH_URL": "https://syngenta-scm.iqser.cloud/auth/realms/redaction",
|
||||
"RECENT_PERIOD_IN_HOURS": 24,
|
||||
"SELECTION_MODE": "structural",
|
||||
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",
|
||||
"ANNOTATIONS_THRESHOLD": 1000,
|
||||
"THEME": "redact",
|
||||
"BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/redact/"
|
||||
"THEME": "scm",
|
||||
"BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/scm/"
|
||||
}
|
||||
|
||||
@ -1953,9 +1953,13 @@
|
||||
},
|
||||
"rss-dialog": {
|
||||
"actions": {
|
||||
"cancel-edit": "",
|
||||
"close": "",
|
||||
"edit": "",
|
||||
"export-json": "",
|
||||
"export-xml": ""
|
||||
"export-xml": "",
|
||||
"save": "",
|
||||
"undo": ""
|
||||
},
|
||||
"title": ""
|
||||
},
|
||||
|
||||
@ -1953,9 +1953,13 @@
|
||||
},
|
||||
"rss-dialog": {
|
||||
"actions": {
|
||||
"cancel-edit": "Cancel",
|
||||
"close": "Close",
|
||||
"edit": "Edit",
|
||||
"export-json": "Export JSON",
|
||||
"export-xml": "Export XML"
|
||||
"export-xml": "Export XML",
|
||||
"save": "Save",
|
||||
"undo": "Undo"
|
||||
},
|
||||
"title": "Structured Component Management"
|
||||
},
|
||||
|
||||
@ -1953,9 +1953,13 @@
|
||||
},
|
||||
"rss-dialog": {
|
||||
"actions": {
|
||||
"cancel-edit": "",
|
||||
"close": "",
|
||||
"edit": "",
|
||||
"export-json": "",
|
||||
"export-xml": ""
|
||||
"export-xml": "",
|
||||
"save": "",
|
||||
"undo": ""
|
||||
},
|
||||
"title": ""
|
||||
},
|
||||
|
||||
@ -1953,10 +1953,15 @@
|
||||
},
|
||||
"rss-dialog": {
|
||||
"actions": {
|
||||
"cancel-edit": "Cancel",
|
||||
"close": "Close",
|
||||
"edit": "Edit",
|
||||
"export-json": "Export JSON",
|
||||
"export-xml": "Export XML"
|
||||
"export-xml": "Export XML",
|
||||
"save": "Save",
|
||||
"undo": "Undo to: {value}"
|
||||
},
|
||||
"annotations": "<strong>{type}</strong> found on {pageCount, plural, one{page} other{pages}} {pages} by rule #{ruleNumber}",
|
||||
"title": "Structured Component Management"
|
||||
},
|
||||
"rules-screen": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user