From 32a2607e23d8b8953df179355a6dbaedab38c0da Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Sat, 11 Feb 2023 20:27:17 +0200 Subject: [PATCH] RED-5546: fix escaped html --- ...one-dossier-template-dialog.component.html | 9 +- ...clone-dossier-template-dialog.component.ts | 87 +++++++------ .../annotation-details.component.html | 48 +++----- .../annotation-details.component.ts | 115 +++++++++--------- .../edit-dossier-dialog.component.html | 8 +- .../dossier-templates.service.ts | 12 +- 6 files changed, 134 insertions(+), 145 deletions(-) diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-clone-dossier-template-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-clone-dossier-template-dialog.component.html index 34c415d0d..701c80841 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-clone-dossier-template-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-clone-dossier-template-dialog.component.html @@ -1,12 +1,5 @@
-
+
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-clone-dossier-template-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-clone-dossier-template-dialog.component.ts index e8bacff1f..934a2d978 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-clone-dossier-template-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-clone-dossier-template-dialog.component.ts @@ -1,14 +1,13 @@ import { Component, Inject } from '@angular/core'; -import { AbstractControl, UntypedFormGroup, Validators } from '@angular/forms'; +import { AbstractControl, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { applyIntervalConstraints } from '@utils/date-inputs-utils'; import { downloadTypesTranslations } from '@translations/download-types-translations'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; import { BaseDialogComponent } from '@iqser/common-ui'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { DossierTemplate, DownloadFileType, IDossierTemplate } from '@red/domain'; +import { DossierTemplate, IDossierTemplate } from '@red/domain'; import { HttpStatusCode } from '@angular/common/http'; -import { firstValueFrom } from 'rxjs'; import dayjs, { Dayjs } from 'dayjs'; import { ROLES } from '@users/roles'; @@ -17,6 +16,11 @@ interface EditCloneTemplateData { clone?: boolean; } +const downloadTypes = ['ORIGINAL', 'PREVIEW', 'DELTA_PREVIEW', 'REDACTED'].map(type => ({ + key: type, + label: downloadTypesTranslations[type], +})); + @Component({ templateUrl: './add-edit-clone-dossier-template-dialog.component.html', styleUrls: ['./add-edit-clone-dossier-template-dialog.component.scss'], @@ -25,11 +29,7 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon readonly roles = ROLES; hasValidFrom: boolean; hasValidTo: boolean; - downloadTypesEnum: DownloadFileType[] = ['ORIGINAL', 'PREVIEW', 'DELTA_PREVIEW', 'REDACTED']; - downloadTypes: { key: DownloadFileType; label: string }[] = this.downloadTypesEnum.map(type => ({ - key: type, - label: downloadTypesTranslations[type], - })); + readonly downloadTypes = downloadTypes; readonly dossierTemplate: DossierTemplate; private _previousValidFrom: Dayjs; private _previousValidTo: Dayjs; @@ -59,6 +59,13 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon return !this.valid; } + get translateParams() { + return { + type: this.dossierTemplate ? (this.data.clone ? 'clone' : 'edit') : 'create', + name: this.dossierTemplate?.name, + }; + } + toggleHasValid(extremity: string) { if (extremity === 'from') { this.hasValidFrom = !this.hasValidFrom; @@ -72,20 +79,21 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon async save() { this._loadingService.start(); + const dossierTemplate = { + dossierTemplateId: this.dossierTemplate?.dossierTemplateId, + ...this.form.getRawValue(), + validFrom: this.hasValidFrom ? this.form.get('validFrom').value : null, + validTo: this.hasValidTo ? this.form.get('validTo').value : null, + } as IDossierTemplate; + try { - const dossierTemplate = { - dossierTemplateId: this.dossierTemplate?.dossierTemplateId, - ...this.form.getRawValue(), - validFrom: this.hasValidFrom ? this.form.get('validFrom').value : null, - validTo: this.hasValidTo ? this.form.get('validTo').value : null, - } as IDossierTemplate; if (this.data?.clone) { - await firstValueFrom(this._dossierTemplatesService.clone(this.dossierTemplate.id, dossierTemplate)); + await this._dossierTemplatesService.clone(this.dossierTemplate.id, dossierTemplate); } else { - await firstValueFrom(this._dossierTemplatesService.createOrUpdate(dossierTemplate)); + await this._dossierTemplatesService.createOrUpdate(dossierTemplate); } this._dialogRef.close(true); - } catch (error: any) { + } catch (error) { const message = error.status === HttpStatusCode.Conflict ? _('add-edit-clone-dossier-template.error.conflict') @@ -105,7 +113,7 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon this._lastValidTo = this._previousValidTo || this._lastValidTo; } - private _getForm(): UntypedFormGroup { + private _getForm() { return this._formBuilder.group({ name: [this._getCloneName(), Validators.required], description: [this.dossierTemplate?.description], @@ -122,29 +130,28 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon } private _getCloneName(): string { - if (this.data?.clone) { - const templateName = this.dossierTemplate.name.trim(); - - let nameOfClonedTemplate: string = templateName.split('Copy of ').filter(n => n)[0]; - nameOfClonedTemplate = nameOfClonedTemplate.split(/\(\s*\d+\s*\)$/)[0].trim(); - - const allTemplatesNames = this._dossierTemplatesService.all.map(t => t.name); - - let clonesCount = 0; - for (const name of allTemplatesNames) { - const splitName = name.split(nameOfClonedTemplate); - const suffixRegExp = new RegExp(/^\(\s*\d+\s*\)$/); - if (splitName[0] === 'Copy of ' && (splitName[1].trim().match(suffixRegExp) || splitName[1] === '')) { - clonesCount++; - } - } - - if (clonesCount >= 1) { - return `Copy of ${nameOfClonedTemplate} (${clonesCount})`; - } - return `Copy of ${nameOfClonedTemplate}`; + if (!this.data?.clone) { + return this.dossierTemplate?.name; } - return this.dossierTemplate?.name; + + const templateName = this.dossierTemplate.name.trim(); + let nameOfClonedTemplate: string = templateName.split('Copy of ').filter(n => n)[0]; + nameOfClonedTemplate = nameOfClonedTemplate.split(/\(\s*\d+\s*\)$/)[0].trim(); + const allTemplatesNames = this._dossierTemplatesService.all.map(t => t.name); + + let clonesCount = 0; + for (const name of allTemplatesNames) { + const splitName = name.split(nameOfClonedTemplate); + const suffixRegExp = new RegExp(/^\(\s*\d+\s*\)$/); + if (splitName[0] === 'Copy of ' && (splitName[1].trim().match(suffixRegExp) || splitName[1] === '')) { + clonesCount++; + } + } + + if (clonesCount >= 1) { + return `Copy of ${nameOfClonedTemplate} (${clonesCount})`; + } + return `Copy of ${nameOfClonedTemplate}`; } private _requiredIfValidator(predicate) { diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.html b/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.html index a95603df5..317fe83b7 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.html +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.html @@ -1,6 +1,6 @@
- - -
- - - -
+ +
+ +
- -
- -
- - {{ engine.description | translate: engine.translateParams }} -
-
+ +
+
+ +
- - +
+
diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.ts b/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.ts index 290a60570..57777f99c 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.ts @@ -1,12 +1,11 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { Component, Input, OnChanges } from '@angular/core'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { TranslateService } from '@ngx-translate/core'; import { annotationChangesTranslations } from '@translations/annotation-changes-translations'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { MultiSelectService } from '../../services/multi-select.service'; -import { KeysOf } from '@iqser/common-ui'; -import { BehaviorSubject, Observable } from 'rxjs'; -import { filter, map, switchMap } from 'rxjs/operators'; +import { KeysOf, shareDistinctLast } from '@iqser/common-ui'; +import { BehaviorSubject, combineLatest, filter, map, Observable, switchMap } from 'rxjs'; import { AnnotationsListingService } from '../../services/annotations-listing.service'; interface Engine { @@ -24,94 +23,92 @@ const Engines = { type EngineName = keyof typeof Engines; +function isBasedOn(annotation: AnnotationWrapper, engineName: EngineName) { + return !!annotation.engines?.includes(engineName); +} + +const changesProperties: KeysOf[] = [ + 'hasBeenResized', + 'hasBeenRecategorized', + 'hasLegalBasisChanged', + 'hasBeenRemovedByManualOverride', + 'hasBeenForcedRedaction', + 'hasBeenForcedHint', +]; + @Component({ selector: 'redaction-annotation-details [annotation]', templateUrl: './annotation-details.component.html', styleUrls: ['./annotation-details.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class AnnotationDetailsComponent implements OnChanges { @Input() annotation: AnnotationWrapper; - readonly isSelected$: Observable; + readonly noSelection$: Observable; isPopoverOpen = false; readonly engines$: Observable; - readonly changes$: Observable; readonly changesTooltip$: Observable; - private readonly _annotationChanged$ = new BehaviorSubject(undefined); + readonly #annotationChanged$ = new BehaviorSubject(undefined); constructor( private readonly _translateService: TranslateService, private readonly _listingService: AnnotationsListingService, readonly multiSelectService: MultiSelectService, ) { - this.isSelected$ = this._annotationChanged$.pipe(switchMap(annotation => this._listingService.isSelected$(annotation))); + const isSelected$ = this.#annotationChanged$.pipe(switchMap(annotation => this._listingService.isSelected$(annotation))); + this.noSelection$ = combineLatest([isSelected$, multiSelectService.inactive$]).pipe( + map(([isSelected, inactive]) => !isSelected || inactive), + shareDistinctLast(), + ); this.engines$ = this.#engines$; - this.changes$ = this.#changes$; this.changesTooltip$ = this.#changesTooltip; } get #engines$(): Observable { - return this._annotationChanged$.pipe( + return this.#annotationChanged$.pipe( filter(annotation => !!annotation), - map(annotation => - [ - { - icon: 'red:dictionary', - description: _('annotation-engines.dictionary'), - show: AnnotationDetailsComponent._isBasedOn(annotation, Engines.DICTIONARY), - translateParams: { isHint: this.annotation.hint }, - }, - { - icon: 'red:ai', - description: _('annotation-engines.ner'), - show: AnnotationDetailsComponent._isBasedOn(annotation, Engines.NER), - }, - { - icon: 'red:rule', - description: _('annotation-engines.rule'), - show: AnnotationDetailsComponent._isBasedOn(annotation, Engines.RULE), - translateParams: { rule: this.annotation.legalBasisValue || '' }, - }, - ].filter(engine => engine.show), - ), + map(annotation => this.#extractEngines(annotation).filter(engine => engine.show)), ); } - get #changes$(): Observable { - return this._annotationChanged$.pipe( + get #changesTooltip(): Observable { + return this.#annotationChanged$.pipe( filter(annotation => !!annotation), - map(annotation => { - const changesProperties: KeysOf[] = [ - 'hasBeenResized', - 'hasBeenRecategorized', - 'hasLegalBasisChanged', - 'hasBeenRemovedByManualOverride', - 'hasBeenForcedRedaction', - 'hasBeenForcedHint', - ]; - return changesProperties.filter(key => annotation[key]); - }), - ); - } - - get #changesTooltip(): Observable { - return this.changes$.pipe( + map(annotation => changesProperties.filter(key => annotation[key])), map(changes => { + if (!changes.length) { + return; + } const header = this._translateService.instant(_('annotation-changes.header')); - const details = changes - .map(change => this._translateService.instant(annotationChangesTranslations[change])) - .map(change => `• ${change}`); - return [header, ...details].join('\n'); + const details = changes.map(change => this._translateService.instant(annotationChangesTranslations[change])); + return [header, ...details.map(change => `• ${change}`)].join('\n'); }), ); } - private static _isBasedOn(annotation: AnnotationWrapper, engineName: EngineName) { - return !!annotation.engines?.includes(engineName); - } - ngOnChanges() { - this._annotationChanged$.next(this.annotation); + this.#annotationChanged$.next(this.annotation); + } + + #extractEngines(annotation: AnnotationWrapper): Engine[] { + return [ + { + icon: 'red:dictionary', + description: _('annotation-engines.dictionary'), + show: isBasedOn(annotation, Engines.DICTIONARY), + translateParams: { isHint: annotation.hint }, + }, + { + icon: 'red:ai', + description: _('annotation-engines.ner'), + show: isBasedOn(annotation, Engines.NER), + }, + { + icon: 'red:rule', + description: _('annotation-engines.rule'), + show: isBasedOn(annotation, Engines.RULE), + translateParams: { rule: annotation.legalBasisValue || '' }, + }, + ]; } } diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html index b4f5db284..f9a401d86 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html @@ -1,7 +1,9 @@
-
- {{ 'edit-dossier-dialog.header' | translate : { dossierName: dossier.dossierName } }} -
+
diff --git a/apps/red-ui/src/app/services/dossier-templates/dossier-templates.service.ts b/apps/red-ui/src/app/services/dossier-templates/dossier-templates.service.ts index 2922875c4..d30033833 100644 --- a/apps/red-ui/src/app/services/dossier-templates/dossier-templates.service.ts +++ b/apps/red-ui/src/app/services/dossier-templates/dossier-templates.service.ts @@ -1,7 +1,7 @@ import { EntitiesService, List, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { DossierTemplate, IDossierTemplate } from '@red/domain'; import { Injectable } from '@angular/core'; -import { forkJoin, Observable, of } from 'rxjs'; +import { firstValueFrom, forkJoin, Observable, of } from 'rxjs'; import { FileAttributesService } from '../entity-services/file-attributes.service'; import { catchError, map, mapTo, switchMap, tap } from 'rxjs/operators'; import { DossierTemplateStatsService } from '../entity-services/dossier-template-stats.service'; @@ -73,12 +73,14 @@ export class DossierTemplatesService extends EntitiesService this.loadAll())); + async createOrUpdate(@RequiredParam() body: IDossierTemplate) { + await firstValueFrom(this._post(body)); + return await firstValueFrom(this.loadAll()); } - clone(dossierTemplateId: string, body: IDossierTemplate) { - return this._post(body, `${this._defaultModelPath}/${dossierTemplateId}/clone`).pipe(switchMap(() => this.loadAll())); + async clone(dossierTemplateId: string, body: IDossierTemplate) { + await firstValueFrom(this._post(body, `${this._defaultModelPath}/${dossierTemplateId}/clone`)); + return await firstValueFrom(this.loadAll()); } refreshDossierTemplate(dossierTemplateId: string): Observable {