diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-card/annotation-card.component.html b/apps/red-ui/src/app/modules/file-preview/components/annotation-card/annotation-card.component.html index a9c7db561..5fbccad00 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-card/annotation-card.component.html +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-card/annotation-card.component.html @@ -21,7 +21,7 @@ {{ annotation.entity.label }} -
+
: {{ annotation.shortContent }}
diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-card/annotation-card.component.ts b/apps/red-ui/src/app/modules/file-preview/components/annotation-card/annotation-card.component.ts index 24bc532c4..b085567d5 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-card/annotation-card.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-card/annotation-card.component.ts @@ -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) {} } diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html b/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html index 48aef3433..c5a49d8a1 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html @@ -46,14 +46,12 @@ [value]="dictionary.type" matTooltipPosition="after" > - - {{ dictionary.label }} - + {{ dictionary.label }}
-
+
-
+
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts index 7f3292dc0..6f158fd0c 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts @@ -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]); + } + } } diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.html b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.html index 5e98ca16f..88ec0641b 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.html +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.html @@ -1,28 +1,76 @@
-
+

-
-
-
{{ entry.key }}:
-
{{ entry.value }}
-
+
+
Component
+
Value
+
Transformation
+
Annotations
+ + +
{{ entry.key }}
+
+ + + + + +
+
{{ entry.value.transformation }}
+ +
+
    +
  • +
+ + - +
+
- - -
- +
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.scss b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.scss index fa01814c2..cf04f8aa6 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.scss +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.scss @@ -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); +} diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.ts index 2b8eb4b21..62a8119ab 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.ts @@ -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; +export class RssDialogComponent extends BaseDialogComponent implements OnInit { + readonly iconButtonTypes = IconButtonTypes; - originalOrder = (a: KeyValue, b: KeyValue): number => 0; + rssData$ = new BehaviorSubject(null); constructor( protected readonly _dialogRef: MatDialogRef, @@ -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 { + await this.#loadData(); + } + + originalOrder = (a: KeyValue, b: KeyValue): 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) { + 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) { + 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 { + 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'), ' '); + (entry.result[key]).originalKey = key; + mapped[newKey] = entry.result[key]; + } + return { + filaName: entry.filaName, + result: mapped, + }; + }), + ), + ); + this.rssData$.next(rssData); + this._loadingService.stop(); + } } diff --git a/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts b/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts index f3fa8143c..8c2391e64 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/pdf-proxy.service.ts @@ -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); diff --git a/apps/red-ui/src/app/services/files/rss.service.ts b/apps/red-ui/src/app/services/files/rss.service.ts index 71f639f6d..25f60ea1d 100644 --- a/apps/red-ui/src/app/services/files/rss.service.ts +++ b/apps/red-ui/src/app/services/files/rss.service.ts @@ -15,7 +15,7 @@ export class RssService extends GenericService { const queryParams: QueryParam[] = []; queryParams.push({ key: 'fileId', value: fileId }); - const rssData$ = this._getOne([dossierId], 'rss', queryParams); + const rssData$ = this._getOne([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 { }); } + @Validate() + override( + @RequiredParam() dossierId: string, + @RequiredParam() fileId: string, + @RequiredParam() componentOverrides: Record, + ) { + 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 => { diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index f4b69417a..ef98789d9 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -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/" } diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index 6fbb77a45..1045a6ad2 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -1953,9 +1953,13 @@ }, "rss-dialog": { "actions": { + "cancel-edit": "", "close": "", + "edit": "", "export-json": "", - "export-xml": "" + "export-xml": "", + "save": "", + "undo": "" }, "title": "" }, diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index c08cefd6c..4ba768210 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -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" }, diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index 7cf98b4e0..2e29848a1 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -1953,9 +1953,13 @@ }, "rss-dialog": { "actions": { + "cancel-edit": "", "close": "", + "edit": "", "export-json": "", - "export-xml": "" + "export-xml": "", + "save": "", + "undo": "" }, "title": "" }, diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index 097c86b11..634c5d846 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -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": "{type} found on {pageCount, plural, one{page} other{pages}} {pages} by rule #{ruleNumber}", "title": "Structured Component Management" }, "rules-screen": {