Merge branch 'master' into RED-3796

This commit is contained in:
Adina Țeudan 2022-05-02 16:48:24 +03:00
commit 0ab20a608a
35 changed files with 703 additions and 567 deletions

View File

@ -25,16 +25,17 @@
<iqser-help-button [dialogButton]="false"></iqser-help-button>
<redaction-notifications iqserHelpMode="open_notifications"></redaction-notifications>
</div>
<redaction-user-button [matMenuTriggerFor]="userMenu" [userId]="currentUser.id"></redaction-user-button>
<redaction-user-button [matMenuTriggerFor]="userMenu" [userId]="currentUser.id" id="userMenu"></redaction-user-button>
<mat-menu #userMenu="matMenu" xPosition="before">
<ng-container *ngFor="let item of userMenuItems; trackBy: trackByName">
<a (click)="(item.action)" *ngIf="item.show" [routerLink]="item.routerLink" mat-menu-item>
<a (click)="(item.action)" *ngIf="item.show" [id]="item.id" [routerLink]="item.routerLink" mat-menu-item>
{{ item.name | translate }}
</a>
</ng-container>
<button (click)="userService.logout()" mat-menu-item>
<button (click)="userService.logout()" id="logout" mat-menu-item>
<mat-icon svgIcon="iqser:logout"></mat-icon>
<span translate="top-bar.navigation-items.my-account.children.logout"> </span>
</button>

View File

@ -13,6 +13,7 @@ import { FeaturesService } from '@services/features.service';
import { DOSSIERS_ARCHIVE } from '@utils/constants';
interface MenuItem {
readonly id: string;
readonly name: string;
readonly routerLink?: string;
readonly show: boolean;
@ -31,21 +32,25 @@ export class BaseScreenComponent {
readonly currentUser = this.userService.currentUser;
readonly userMenuItems: readonly MenuItem[] = [
{
id: 'account',
name: _('top-bar.navigation-items.my-account.children.account'),
routerLink: '/main/account',
show: true,
},
{
id: 'admin',
name: _('top-bar.navigation-items.my-account.children.admin'),
routerLink: '/main/admin',
show: this.currentUser.isManager || this.currentUser.isUserAdmin,
},
{
id: 'downloads',
name: _('top-bar.navigation-items.my-account.children.downloads'),
routerLink: '/main/downloads',
show: this.currentUser.isUser,
},
{
id: 'trash',
name: _('top-bar.navigation-items.my-account.children.trash'),
routerLink: '/main/admin/trash',
show: this.currentUser.isManager,

View File

@ -37,7 +37,8 @@ export class AnnotationPermissions {
const annotationEntity = entities.find(entity => entity.type === annotation.type);
permissions.canMarkAsFalsePositive = annotation.canBeMarkedAsFalsePositive && annotationEntity.hasDictionary;
permissions.canRemoveOrSuggestToRemoveOnlyHere = (annotation.isRedacted || annotation.isHint) && !annotation.pending;
permissions.canRemoveOrSuggestToRemoveOnlyHere =
(annotation.isRedacted || annotation.isHint) && !annotation.pending && !annotation.isImage;
permissions.canRemoveOrSuggestToRemoveFromDictionary =
annotation.isModifyDictionary &&
(annotation.isRedacted || annotation.isSkipped || annotation.isHint) &&

View File

@ -303,20 +303,24 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
}
}
private static _getLastRelevantManualChange(manualChanges: IManualChange[]) {
return manualChanges[manualChanges.length - 1];
}
private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntry) {
if (redactionLogEntryWrapper.manualChanges?.length) {
const lastManualChange = redactionLogEntryWrapper.manualChanges[redactionLogEntryWrapper.manualChanges.length - 1];
const lastRelevantManualChange = this._getLastRelevantManualChange(redactionLogEntryWrapper.manualChanges);
annotationWrapper.pending = !lastManualChange.processed;
annotationWrapper.pending = !lastRelevantManualChange.processed;
annotationWrapper.superType = AnnotationWrapper._selectSuperType(
redactionLogEntryWrapper,
lastManualChange,
lastRelevantManualChange,
annotationWrapper.hintDictionary,
);
if (lastManualChange.annotationStatus === LogEntryStatus.REQUESTED) {
annotationWrapper.recategorizationType = lastManualChange.propertyChanges.type;
if (lastRelevantManualChange.annotationStatus === LogEntryStatus.REQUESTED) {
annotationWrapper.recategorizationType = lastRelevantManualChange.propertyChanges.type;
}
} else {
if (redactionLogEntryWrapper.recommendation) {
@ -418,7 +422,7 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
case LogEntryStatus.APPROVED:
return SuperTypes.Redaction;
case LogEntryStatus.DECLINED:
return SuperTypes.Skipped;
return isHintDictionary ? SuperTypes.IgnoredHint : SuperTypes.Skipped;
case LogEntryStatus.REQUESTED:
return SuperTypes.SuggestionForceRedaction;
}
@ -444,7 +448,7 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
} else if (redactionLogEntry.hint) {
return SuperTypes.Hint;
} else {
return SuperTypes.Skipped;
return isHintDictionary ? SuperTypes.IgnoredHint : SuperTypes.Skipped;
}
}
case LogEntryStatus.REQUESTED:
@ -471,7 +475,7 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
} else if (redactionLogEntry.hint) {
return SuperTypes.Hint;
} else {
return SuperTypes.Skipped;
return isHintDictionary ? SuperTypes.IgnoredHint : SuperTypes.Skipped;
}
case LogEntryStatus.REQUESTED:
return SuperTypes.SuggestionResize;

View File

@ -29,6 +29,7 @@ export class RedactionLogEntry implements IRedactionLogEntry {
readonly textBefore?: string;
readonly type?: string;
readonly value?: string;
readonly sourceId?: string;
reason?: string;
@ -69,5 +70,6 @@ export class RedactionLogEntry implements IRedactionLogEntry {
this.type = redactionLogEntry.type;
this.value = redactionLogEntry.value;
this.imported = redactionLogEntry.imported;
this.sourceId = redactionLogEntry.sourceId;
}
}

View File

@ -12,7 +12,7 @@ import { UserListingScreenComponent } from './screens/user-listing/user-listing-
import { DossierTemplateBreadcrumbsComponent } from './components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component';
import { ColorPickerModule } from 'ngx-color-picker';
import { AddEditFileAttributeDialogComponent } from './dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component';
import { AddEditCloneDossierTemplateDialogComponent } from './dialogs/add-edit-clone-dossier-template-dialog/add-edit-clone-dossier-template-dialog.component';
import { AddEditDossierTemplateDialogComponent } from './dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component';
import { AddEntityDialogComponent } from './dialogs/add-entity-dialog/add-entity-dialog.component';
import { EditColorDialogComponent } from './dialogs/edit-color-dialog/edit-color-dialog.component';
import { ComboChartComponent, ComboSeriesVerticalComponent } from './components/combo-chart';
@ -48,11 +48,13 @@ import { A11yModule } from '@angular/cdk/a11y';
import { ConfirmDeleteDossierStateDialogComponent } from './dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component';
import { TrashTableItemComponent } from './screens/trash/trash-table-item/trash-table-item.component';
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
import { CloneDossierTemplateDialogComponent } from './dialogs/clone-dossier-template-dialog/clone-dossier-template-dialog.component';
const dialogs = [
AddEditCloneDossierTemplateDialogComponent,
AddEditDossierTemplateDialogComponent,
AddEntityDialogComponent,
AddEditFileAttributeDialogComponent,
CloneDossierTemplateDialogComponent,
EditColorDialogComponent,
SmtpAuthDialogComponent,
AddEditUserDialogComponent,

View File

@ -1,19 +1,19 @@
<section class="dialog">
<div
[translateParams]="{
type: dossierTemplate ? (data.clone ? 'clone' : 'edit') : 'create',
type: dossierTemplate ? 'edit' : 'create',
name: dossierTemplate?.name
}"
[translate]="'add-edit-clone-dossier-template.title'"
[translate]="'add-edit-dossier-template.title'"
class="dialog-header heading-l"
></div>
<form [formGroup]="form">
<div class="dialog-content">
<div class="iqser-input-group required w-300">
<label translate="add-edit-clone-dossier-template.form.name"></label>
<label translate="add-edit-dossier-template.form.name"></label>
<input
[placeholder]="'add-edit-clone-dossier-template.form.name-placeholder' | translate"
[placeholder]="'add-edit-dossier-template.form.name-placeholder' | translate"
formControlName="name"
name="name"
type="text"
@ -21,9 +21,9 @@
</div>
<div class="iqser-input-group w-400">
<label translate="add-edit-clone-dossier-template.form.description"></label>
<label translate="add-edit-dossier-template.form.description"></label>
<textarea
[placeholder]="'add-edit-clone-dossier-template.form.description-placeholder' | translate"
[placeholder]="'add-edit-dossier-template.form.description-placeholder' | translate"
formControlName="description"
name="description"
rows="4"
@ -34,11 +34,11 @@
<div class="validity">
<div>
<mat-checkbox (change)="toggleHasValid('from')" [checked]="hasValidFrom" class="filter-menu-checkbox" color="primary">
{{ 'add-edit-clone-dossier-template.form.valid-from' | translate }}
{{ 'add-edit-dossier-template.form.valid-from' | translate }}
</mat-checkbox>
<mat-checkbox (change)="toggleHasValid('to')" [checked]="hasValidTo" class="filter-menu-checkbox" color="primary">
{{ 'add-edit-clone-dossier-template.form.valid-to' | translate }}
{{ 'add-edit-dossier-template.form.valid-to' | translate }}
</mat-checkbox>
</div>
@ -83,7 +83,7 @@
<div class="dialog-actions">
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
{{ 'add-edit-clone-dossier-template.save' | translate }}
{{ 'add-edit-dossier-template.save' | translate }}
</button>
</div>
</form>

View File

@ -12,16 +12,11 @@ import { DictionaryService } from '@services/entity-services/dictionary.service'
import { firstValueFrom } from 'rxjs';
import dayjs, { Dayjs } from 'dayjs';
interface EditCloneTemplateData {
dossierTemplateId: string;
clone?: boolean;
}
@Component({
templateUrl: './add-edit-clone-dossier-template-dialog.component.html',
styleUrls: ['./add-edit-clone-dossier-template-dialog.component.scss'],
templateUrl: './add-edit-dossier-template-dialog.component.html',
styleUrls: ['./add-edit-dossier-template-dialog.component.scss'],
})
export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogComponent {
export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
hasValidFrom: boolean;
hasValidTo: boolean;
downloadTypesEnum: DownloadFileType[] = ['ORIGINAL', 'PREVIEW', 'DELTA_PREVIEW', 'REDACTED'];
@ -41,12 +36,12 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon
private readonly _dictionaryService: DictionaryService,
private readonly _formBuilder: FormBuilder,
protected readonly _injector: Injector,
protected readonly _dialogRef: MatDialogRef<AddEditCloneDossierTemplateDialogComponent>,
protected readonly _dialogRef: MatDialogRef<AddEditDossierTemplateDialogComponent>,
private readonly _loadingService: LoadingService,
@Inject(MAT_DIALOG_DATA) readonly data: EditCloneTemplateData,
@Inject(MAT_DIALOG_DATA) readonly dossierTemplateId: string,
) {
super(_injector, _dialogRef, !!data && !data.clone);
this.dossierTemplate = this._dossierTemplatesService.find(this.data?.dossierTemplateId);
super(_injector, _dialogRef, !!dossierTemplateId);
this.dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
this.form = this._getForm();
this.initialFormValue = this.form.getRawValue();
this.hasValidFrom = !!this.dossierTemplate?.validFrom;
@ -81,24 +76,18 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon
this._loadingService.start();
try {
const dossierTemplate = {
dossierTemplateId: !this.data?.clone ? this.dossierTemplate?.dossierTemplateId : null,
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) {
const dossierTemplateId = this.dossierTemplate?.dossierTemplateId;
const nameOfClonedTemplate = this._getNameOfClonedTemplate(this.dossierTemplate.name);
await firstValueFrom(this._dossierTemplatesService.clone(dossierTemplate, dossierTemplateId, nameOfClonedTemplate));
} else {
await firstValueFrom(this._dossierTemplatesService.createOrUpdate(dossierTemplate));
}
await firstValueFrom(this._dossierTemplatesService.createOrUpdate(dossierTemplate));
this._dialogRef.close(true);
} catch (error: any) {
const message =
error.status === HttpStatusCode.Conflict
? _('add-edit-clone-dossier-template.error.conflict')
: _('add-edit-clone-dossier-template.error.generic');
? _('add-edit-dossier-template.error.conflict')
: _('add-edit-dossier-template.error.generic');
this._toaster.error(message, { error });
}
this._loadingService.stop();
@ -106,7 +95,7 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon
private _getForm(): FormGroup {
return this._formBuilder.group({
name: [this._getTemplateName(), Validators.required],
name: [this.dossierTemplate?.name, Validators.required],
description: [this.dossierTemplate?.description],
validFrom: [
this.dossierTemplate?.validFrom ? dayjs(this.dossierTemplate?.validFrom) : null,
@ -120,37 +109,6 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon
});
}
private _getTemplateName(): string | null {
if (this.dossierTemplate) {
const templateName = this.dossierTemplate.name.trim();
if (this.data.clone) {
const nameOfClonedTemplate = this._getNameOfClonedTemplate(templateName);
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] === 'Clone of ' && (splitName[1].trim().match(suffixRegExp) || splitName[1] === '')) {
clonesCount++;
}
}
if (clonesCount >= 1) {
return `Clone of ${nameOfClonedTemplate} ${clonesCount === 1 ? '(1)' : `(${clonesCount})`}`;
}
return `Clone of ${nameOfClonedTemplate}`;
}
return templateName;
}
return null;
}
private _getNameOfClonedTemplate(templateName: string): string {
const nameOfClonedTemplate = templateName.split('Clone of ').filter(n => n)[0];
return nameOfClonedTemplate.split(/\(\s*\d+\s*\)$/)[0].trim();
}
private _applyValidityIntervalConstraints(value): boolean {
if (applyIntervalConstraints(value, this._previousValidFrom, this._previousValidTo, this.form, 'validFrom', 'validTo')) {
return true;
@ -172,11 +130,4 @@ export class AddEditCloneDossierTemplateDialogComponent extends BaseDialogCompon
return null;
};
}
get disabled(): boolean {
if (!this.data?.clone) {
return super.disabled;
}
return !this.valid;
}
}

View File

@ -0,0 +1,23 @@
<section class="dialog">
<div translate="clone-dossier-template.title" class="dialog-header heading-l"></div>
<div class="dialog-content">
<div class="iqser-input-group required w-full">
<label translate="clone-dossier-template.content.name"></label>
<input
[placeholder]="'clone-dossier-template.content.name-placeholder' | translate"
[(ngModel)]="nameOfClonedDossierTemplate"
name="name"
type="text"
/>
</div>
</div>
<div class="dialog-actions">
<button color="primary" mat-flat-button type="submit" (click)="save()" [disabled]="!nameOfClonedDossierTemplate">
{{ 'clone-dossier-template.actions.save' | translate }}
</button>
</div>
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
</section>

View File

@ -0,0 +1,61 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { DossierTemplatesService } from '../../../../services/entity-services/dossier-templates.service';
import { DossierTemplate, IDossierTemplate } from '../../../../../../../../libs/red-domain/src';
import { LoadingService, Toaster } from '../../../../../../../../libs/common-ui/src';
import { firstValueFrom } from 'rxjs';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@Component({
templateUrl: './clone-dossier-template-dialog.component.html',
styleUrls: ['./clone-dossier-template-dialog.component.scss'],
})
export class CloneDossierTemplateDialogComponent {
private readonly _dossierTemplate: DossierTemplate;
nameOfClonedDossierTemplate: string;
constructor(
private readonly _toaster: Toaster,
private readonly _loadingService: LoadingService,
private readonly _dossierTemplatesService: DossierTemplatesService,
protected readonly _dialogRef: MatDialogRef<CloneDossierTemplateDialogComponent>,
@Inject(MAT_DIALOG_DATA) readonly dossierTemplateId: string,
) {
this._dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
this.nameOfClonedDossierTemplate = this._getCloneName();
}
private _getCloneName(): string | null {
const templateName = this._dossierTemplate.name.trim();
let nameOfClonedTemplate: string = templateName.split('Clone 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] === 'Clone of ' && (splitName[1].trim().match(suffixRegExp) || splitName[1] === '')) {
clonesCount++;
}
}
if (clonesCount >= 1) {
return `Clone of ${nameOfClonedTemplate} ${clonesCount === 1 ? '(1)' : `(${clonesCount})`}`;
}
return `Clone of ${nameOfClonedTemplate}`;
}
async save() {
this._loadingService.start();
try {
await firstValueFrom(this._dossierTemplatesService.clone(this.dossierTemplateId, this.nameOfClonedDossierTemplate));
this._dialogRef.close(true);
} catch (error: any) {
this._toaster.error(_('clone-dossier-template.error.generic'), { error });
}
this._loadingService.stop();
}
}

View File

@ -63,7 +63,7 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
}
openAddDossierTemplateDialog() {
this._dialogService.openDialog('addEditCloneDossierTemplate', null, null);
this._dialogService.openDialog('addEditDossierTemplate', null, null);
}
private async _deleteTemplates(templateIds = this.listingService.selected.map(d => d.dossierTemplateId)) {

View File

@ -32,6 +32,6 @@ export class DossierTemplateInfoScreenComponent {
}
openEditDossierTemplateDialog($event: MouseEvent, dossierTemplate: DossierTemplate) {
this._dialogService.openDialog('addEditCloneDossierTemplate', $event, { dossierTemplateId: dossierTemplate.id });
this._dialogService.openDialog('addEditDossierTemplate', $event, { dossierTemplateId: dossierTemplate.id });
}
}

View File

@ -2,7 +2,7 @@ import { Injectable, TemplateRef } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { AddEditFileAttributeDialogComponent } from '../dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component';
import { AddEntityDialogComponent } from '../dialogs/add-entity-dialog/add-entity-dialog.component';
import { AddEditCloneDossierTemplateDialogComponent } from '../dialogs/add-edit-clone-dossier-template-dialog/add-edit-clone-dossier-template-dialog.component';
import { AddEditDossierTemplateDialogComponent } from '../dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component';
import { EditColorDialogComponent } from '../dialogs/edit-color-dialog/edit-color-dialog.component';
import { SmtpAuthDialogComponent } from '../dialogs/smtp-auth-dialog/smtp-auth-dialog.component';
import { AddEditUserDialogComponent } from '../dialogs/add-edit-user-dialog/add-edit-user-dialog.component';
@ -28,6 +28,7 @@ import { ActiveDossiersService } from '@services/dossiers/active-dossiers.servic
import { UserService } from '@services/user.service';
import { IDossierAttributeConfig, IFileAttributeConfig, IReportTemplate } from '@red/domain';
import { ReportTemplateService } from '@services/report-template.service';
import { CloneDossierTemplateDialogComponent } from '../dialogs/clone-dossier-template-dialog/clone-dossier-template-dialog.component';
type DialogType =
| 'confirm'
@ -38,7 +39,8 @@ type DialogType =
| 'fileAttributesConfigurations'
| 'addEditUser'
| 'smtpAuthConfig'
| 'addEditCloneDossierTemplate'
| 'addEditDossierTemplate'
| 'cloneDossierTemplate'
| 'addEditDossierAttribute'
| 'uploadDictionary'
| 'addEditDossierState'
@ -78,10 +80,14 @@ export class AdminDialogService extends DialogService<DialogType> {
component: SmtpAuthDialogComponent,
dialogConfig: { autoFocus: true },
},
addEditCloneDossierTemplate: {
component: AddEditCloneDossierTemplateDialogComponent,
addEditDossierTemplate: {
component: AddEditDossierTemplateDialogComponent,
dialogConfig: { width: '900px', autoFocus: true },
},
cloneDossierTemplate: {
component: CloneDossierTemplateDialogComponent,
dialogConfig: { disableClose: false },
},
addEditDossierAttribute: {
component: AddEditDossierAttributeDialogComponent,
dialogConfig: { autoFocus: true },

View File

@ -7,14 +7,14 @@
></iqser-circle-button>
<iqser-circle-button
(action)="openEditCloneDossierTemplateDialog($event, true)"
(action)="openCloneDossierTemplateDialog($event)"
[tooltip]="'dossier-templates-listing.action.clone' | translate"
icon="iqser:copy"
[type]="circleButtonTypes.dark"
></iqser-circle-button>
<iqser-circle-button
(action)="openEditCloneDossierTemplateDialog($event)"
(action)="openEditDossierTemplateDialog($event)"
[tooltip]="'dossier-templates-listing.action.edit' | translate"
icon="iqser:edit"
[type]="circleButtonTypes.dark"

View File

@ -34,8 +34,11 @@ export class DossierTemplateActionsComponent implements OnInit {
this.dossierTemplateId ??= this._route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
}
openEditCloneDossierTemplateDialog($event: MouseEvent, clone: boolean = false) {
this._dialogService.openDialog('addEditCloneDossierTemplate', $event, { dossierTemplateId: this.dossierTemplateId, clone });
openEditDossierTemplateDialog($event: MouseEvent) {
this._dialogService.openDialog('addEditDossierTemplate', $event, this.dossierTemplateId);
}
openCloneDossierTemplateDialog($event: MouseEvent) {
this._dialogService.openDialog('cloneDossierTemplate', $event, this.dossierTemplateId);
}
openDeleteDossierTemplateDialog($event?: MouseEvent) {

View File

@ -215,7 +215,11 @@ export class ConfigService {
filters = dynamicFilters.get(filterKey);
}
const filterValue = file.fileAttributes?.attributeIdToValue[config.id];
filters.add(filterValue);
if (filterValue === undefined || filterValue === null) {
filters.add(undefined);
} else {
filters.add(filterValue);
}
}
});
});

View File

@ -83,7 +83,15 @@ export class AnnotationActionsComponent implements OnChanges {
}
get helpModeKey() {
return this.annotations[0]?.typeLabel?.split('.')[1];
const type = this.annotations[0]?.typeLabel?.split('.')[1];
const typeValue = this.annotations[0]?.typeValue;
if (type === 'hint' && (typeValue === 'formula' || typeValue === 'image')) {
return 'image';
}
if (type === 'redaction' || type === 'manual-redaction') {
return 'redaction';
}
return type;
}
ngOnChanges(): void {

View File

@ -26,7 +26,7 @@ import { AnnotationActionsService } from '../../services/annotation-actions.serv
import { UserPreferenceService } from '@services/user-preference.service';
import { BASE_HREF } from '../../../../tokens';
import { ConfigService } from '@services/config.service';
import { AutoUnsubscribe, ConfirmationDialogInput, LoadingService } from '@iqser/common-ui';
import { AutoUnsubscribe, ConfirmationDialogInput, CustomError, ErrorService, LoadingService } from '@iqser/common-ui';
import { PdfViewer } from '../../services/pdf-viewer.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { toPosition } from '../../../dossier/utils/pdf-calculation.utils';
@ -47,6 +47,8 @@ import Tools = Core.Tools;
import TextTool = Tools.TextTool;
import Annotation = Core.Annotations.Annotation;
const DocLoadingError = new CustomError(_('error.file-preview.label'), _('error.file-preview.action'), 'iqser:refresh');
@Component({
selector: 'redaction-pdf-viewer',
templateUrl: './pdf-viewer.component.html',
@ -81,6 +83,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
private readonly _fileDataService: FileDataService,
private readonly _headerConfigService: ViewerHeaderConfigService,
private readonly _tooltipsService: TooltipsService,
private readonly _errorService: ErrorService,
readonly stateService: FilePreviewStateService,
readonly viewModeService: ViewModeService,
readonly multiSelectService: MultiSelectService,
@ -97,6 +100,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
.pipe(
switchMap(blob => from(this.pdfViewer.lockDocument()).pipe(map(() => blob))),
withLatestFrom(this.stateService.file$),
tap(() => this._errorService.clear()),
tap(([blob, file]) => this._loadDocument(blob, file)),
)
.subscribe();
@ -484,7 +488,6 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
}
private _handleCustomActions() {
this.instance.UI.setToolMode('AnnotationEdit');
const textPopupsToToggle = [TextPopups.ADD_REDACTION, TextPopups.ADD_RECTANGLE, TextPopups.ADD_FALSE_POSITIVE];
const headerItemsToToggle = [
HeaderElements.SHAPE_TOOL_GROUP_BUTTON,
@ -550,7 +553,12 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
}
private _loadDocument(blob: Blob, file: File) {
this.instance.UI.loadDocument(blob, { filename: file?.filename + '.pdf' ?? 'document.pdf' });
const onError = () => {
this._loadingService.stop();
this._errorService.set(DocLoadingError);
this.stateService.reloadBlob();
};
this.instance.UI.loadDocument(blob, { filename: file?.filename + '.pdf' ?? 'document.pdf', onError });
this._pageRotationService.clearRotationsHideActions();
}

View File

@ -396,7 +396,7 @@ export class AnnotationActionsService {
annotationWrapper.resizing = true;
const viewerAnnotation = this._pdf.annotationManager.getAnnotationById(annotationWrapper.id);
if (annotationWrapper.rectangle || annotationWrapper.imported) {
if (annotationWrapper.rectangle || annotationWrapper.imported || annotationWrapper.isImage) {
this._pdf.deleteAnnotations([annotationWrapper.id]);
const rectangleAnnotation = this.#generateRectangle(annotationWrapper);
this._pdf.annotationManager.addAnnotation(rectangleAnnotation, { imported: true });
@ -446,12 +446,12 @@ export class AnnotationActionsService {
$event?.stopPropagation();
const requests: List<IAddRedactionRequest> = annotations.map(annotation => ({
reason: annotation.id,
sourceId: annotation.id,
value: this._getFalsePositiveText(annotation),
type: annotation.type,
positions: annotation.positions,
addToDictionary: true,
comment: { text: 'False Positive' },
reason: 'False Positive',
dictionaryEntryType: annotation.isRecommendation
? DictionaryEntryTypes.FALSE_RECOMMENDATION
: DictionaryEntryTypes.FALSE_POSITIVE,

View File

@ -1,14 +1,4 @@
import {
ChangeType,
File,
IRedactionLog,
IRedactionLogEntry,
IViewedPage,
LogEntryStatus,
ManualRedactionType,
ViewMode,
ViewModes,
} from '@red/domain';
import { ChangeType, File, IRedactionLog, IRedactionLogEntry, IViewedPage, ViewMode, ViewModes } from '@red/domain';
import { AnnotationWrapper } from '../../../models/file/annotation.wrapper';
import { BehaviorSubject, firstValueFrom, iif, Observable, Subject } from 'rxjs';
import { RedactionLogEntry } from '../../../models/file/redaction-log.entry';
@ -179,7 +169,7 @@ export class FileDataService extends EntitiesService<AnnotationWrapper> {
#convertData(redactionLog: IRedactionLog, file: File): RedactionLogEntry[] {
let result: RedactionLogEntry[] = [];
const reasonAnnotationIds: { [key: string]: RedactionLogEntry[] } = {};
const sourceIdAnnotationIds: { [key: string]: RedactionLogEntry[] } = {};
const dictionaries = this._dictionariesMapService.get(this._state.dossierTemplateId);
redactionLog.redactionLogEntry?.forEach(redactionLogEntry => {
@ -199,36 +189,18 @@ export class FileDataService extends EntitiesService<AnnotationWrapper> {
!!dictionary?.hint,
);
if (
redactionLogEntry.manualChanges?.find(
mc =>
mc.manualRedactionType === ManualRedactionType.ADD_TO_DICTIONARY &&
(mc.annotationStatus === LogEntryStatus.APPROVED || mc.annotationStatus === LogEntryStatus.REQUESTED),
)
) {
// for dictionary entries -> I.E accepted recommendations or false positives,
// check reason
if (!reasonAnnotationIds[redactionLogEntry.reason]) {
reasonAnnotationIds[redactionLogEntry.reason] = [redactionLogEntryWrapper];
} else {
reasonAnnotationIds[redactionLogEntry.reason].push(redactionLogEntryWrapper);
if (redactionLogEntry.sourceId) {
if (!sourceIdAnnotationIds[redactionLogEntry.sourceId]) {
sourceIdAnnotationIds[redactionLogEntry.sourceId] = [];
}
sourceIdAnnotationIds[redactionLogEntry.sourceId].push(redactionLogEntryWrapper);
}
result.push(redactionLogEntryWrapper);
});
const reasonKeys = Object.keys(reasonAnnotationIds);
result = result.filter(r => {
const matched = reasonKeys.indexOf(r.id) >= 0;
if (matched) {
reasonAnnotationIds[r.id].forEach(value => {
value.reason = null;
});
}
return !matched;
});
const sourceKeys = Object.keys(sourceIdAnnotationIds);
result = result.filter(r => !sourceKeys.includes(r.id));
result = result.filter(r => !r.hidden);
return result;

View File

@ -1,11 +1,11 @@
import { Injectable, Injector } from '@angular/core';
import { combineLatest, firstValueFrom, from, Observable, of, pairwise, switchMap } from 'rxjs';
import { combineLatest, firstValueFrom, from, merge, Observable, of, pairwise, Subject, switchMap } from 'rxjs';
import { Dossier, File } from '@red/domain';
import { ActivatedRoute } from '@angular/router';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { PermissionsService } from '@services/permissions.service';
import { boolFactory } from '@iqser/common-ui';
import { filter, startWith, tap } from 'rxjs/operators';
import { filter, map, startWith, tap, withLatestFrom } from 'rxjs/operators';
import { FileManagementService } from '@services/entity-services/file-management.service';
import { DOSSIER_ID, FILE_ID } from '@utils/constants';
import { dossiersServiceResolver } from '@services/entity-services/dossiers.service.provider';
@ -29,26 +29,28 @@ export class FilePreviewStateService {
dossier: Dossier;
file: File;
constructor(
private readonly _fileManagementService: FileManagementService,
private readonly _injector: Injector,
private readonly _route: ActivatedRoute,
private readonly _filesMapService: FilesMapService,
private readonly _dossiersService: DossiersService,
private readonly _filesService: FilesService,
private readonly _permissionsService: PermissionsService,
) {
const dossiersService = dossiersServiceResolver(this._injector);
readonly #reloadBlob$ = new Subject();
this.fileId = _route.snapshot.paramMap.get(FILE_ID);
this.dossierId = _route.snapshot.paramMap.get(DOSSIER_ID);
constructor(
route: ActivatedRoute,
filesMapService: FilesMapService,
private readonly _injector: Injector,
permissionsService: PermissionsService,
private readonly _filesService: FilesService,
private readonly _dossiersService: DossiersService,
private readonly _fileManagementService: FileManagementService,
) {
const dossiersService = dossiersServiceResolver(_injector);
this.fileId = route.snapshot.paramMap.get(FILE_ID);
this.dossierId = route.snapshot.paramMap.get(DOSSIER_ID);
this.dossierTemplateId = dossiersService.find(this.dossierId).dossierTemplateId;
this.dossier$ = dossiersService.getEntityChanged$(this.dossierId).pipe(tap(dossier => (this.dossier = dossier)));
this.file$ = _filesMapService.watch$(this.dossierId, this.fileId).pipe(tap(file => (this.file = file)));
this.file$ = filesMapService.watch$(this.dossierId, this.fileId).pipe(tap(file => (this.file = file)));
[this.isReadonly$, this.isWritable$] = boolFactory(
combineLatest([this.file$, this.dossier$]),
([file, dossier]) => !_permissionsService.canPerformAnnotationActions(file, dossier),
([file, dossier]) => !permissionsService.canPerformAnnotationActions(file, dossier),
);
this.blob$ = this.#blob$;
@ -60,7 +62,11 @@ export class FilePreviewStateService {
}
get #blob$() {
return this.file$.pipe(
const reloadBlob$ = this.#reloadBlob$.pipe(
withLatestFrom(this.file$),
map(([, file]) => file),
);
return merge(this.file$, reloadBlob$).pipe(
startWith(undefined),
pairwise(),
filter(([oldFile, newFile]) => oldFile?.cacheIdentifier !== newFile.cacheIdentifier),
@ -68,6 +74,10 @@ export class FilePreviewStateService {
);
}
reloadBlob(): void {
this.#reloadBlob$.next(true);
}
#dossierFilesChange$() {
return this._dossiersService.dossierFileChanges$.pipe(
filter(dossierId => dossierId === this.dossierId),

View File

@ -66,8 +66,8 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
addRecommendation(annotations: AnnotationWrapper[], dossierId: string, fileId: string, comment = { text: 'Accepted Recommendation' }) {
const recommendations: List<IAddRedactionRequest> = annotations.map(annotation => ({
addToDictionary: true,
// set the ID as reason, so we can hide the suggestion
reason: annotation.annotationId,
sourceId: annotation.annotationId,
reason: 'False Positive',
value: annotation.value,
positions: annotation.positions,
type: annotation.recommendationType,

View File

@ -70,8 +70,8 @@ export class DossierTemplatesService extends EntitiesService<DossierTemplate, ID
return this._post(body).pipe(switchMap(() => this.loadAll()));
}
clone(@RequiredParam() body: any, dossierTemplateId: string, name: string) {
return this._post(body, `${this._defaultModelPath}/${dossierTemplateId}/clone?nameOfClonedDossierTemplate=${name}`).pipe(
clone(dossierTemplateId: string, name: string) {
return this._post(null, `${this._defaultModelPath}/${dossierTemplateId}/clone?nameOfClonedDossierTemplate=${name}`).pipe(
switchMap(() => this.loadAll()),
);
}

View File

@ -6,10 +6,12 @@ import { ILoggerConfig } from '@red/domain';
export class LoggerRulesService extends NGXLoggerRulesService {
shouldCallWriter(level: NgxLoggerLevel, config: ILoggerConfig, message?: unknown, additional?: unknown[]): boolean {
if (message && typeof message === 'string') {
const matches = message.match('(?<=\\[)(.*?)(?=\\])');
const matches = message.match('\\[(.*?)\\]');
if (matches && matches.length > 0 && config.features[matches[0]]) {
const featureConfig = config.features[matches[0]];
const firstMatch = matches[1]?.toUpperCase();
if (matches && matches.length > 0 && config.features[firstMatch]) {
const featureConfig = config.features[firstMatch];
if (!featureConfig.enabled || (featureConfig.level && featureConfig?.level < config.level)) {
return false;

View File

@ -150,7 +150,7 @@
"fr": ""
},
"hint_remove_from_dictionary": {
"en": "/en/index-en.html?contextId=hints_remove_from_dictionary",
"en": "/en/index-en.html?contextId=hint_remove_from_dictionary",
"de": "",
"it": "",
"fr": ""
@ -252,7 +252,7 @@
"fr": ""
},
"redaction_resize": {
"en": "/en/index-en.html?contextId=redaction_resize_redaction",
"en": "/en/index-en.html?contextId=redaction_resize",
"de": "",
"it": "",
"fr": ""
@ -263,6 +263,12 @@
"it": "",
"fr": ""
},
"hint_resize": {
"en": "/en/index-en.html?contextId=hint_resize",
"de": "",
"it": "",
"fr": ""
},
"skipped_force_redaction": {
"en": "/en/index-en.html?contextId=skipped_force_redaction",
"de": "",
@ -358,5 +364,53 @@
"de": "",
"it": "",
"fr": ""
},
"image_resize": {
"en": "/en/index-en.html?contextId=image_resize",
"de": "",
"it": "",
"fr": ""
},
"image_recategorize": {
"en": "/en/index-en.html?contextId=image_recategorize",
"de": "",
"it": "",
"fr": ""
},
"image_hide": {
"en": "/en/index-en.html?contextId=image_hide",
"de": "",
"it": "",
"fr": ""
},
"image_remove_only_here": {
"en": "/en/index-en.html?contextId=image_remove_only_here",
"de": "",
"it": "",
"fr": ""
},
"redaction_remove_from_dictionary": {
"en": "/en/index-en.html?contextId=redaction_remove_from_dictionary",
"de": "",
"it": "",
"fr": ""
},
"skipped_resize": {
"en": "/en/index-en.html?contextId=skipped_resize_redaction",
"de": "",
"it": "",
"fr": ""
},
"skipped_recategorize": {
"en": "/en/index-en.html?contextId=skipped_recategorize_redaction",
"de": "",
"it": "",
"fr": ""
},
"skipped_hide": {
"en": "/en/index-en.html?contextId=skipped_hide",
"de": "",
"it": "",
"fr": ""
}
}

View File

@ -36,22 +36,6 @@
},
"header-new": "Dossier erstellen"
},
"add-edit-clone-dossier-template": {
"error": {
"conflict": "Dossiervorlage konnte nicht erstellt werden: Es existiert bereits eine Dossiervorlage mit demselben Namen.",
"generic": "Fehler beim Erstellen der Dossiervorlage."
},
"form": {
"description": "Beschreibung",
"description-placeholder": "Beschreibung eingeben",
"name": "Name der Dossier-Vorlage",
"name-placeholder": "Namen eingeben",
"valid-from": "Gültig ab",
"valid-to": "Gültig bis"
},
"save": "Dossier-Vorlage speichern",
"title": "{type, select, edit{Dossier-Vorlage {name} bearbeiten} create{Dossier-Vorlage erstellen} clone{} other{}}"
},
"add-edit-dossier-attribute": {
"error": {
"generic": "Attribut konnte nicht gespeichert werden!"
@ -77,6 +61,22 @@
"success": "",
"title": ""
},
"add-edit-dossier-template": {
"error": {
"conflict": "Dossiervorlage konnte nicht erstellt werden: Es existiert bereits eine Dossiervorlage mit demselben Namen.",
"generic": "Fehler beim Erstellen der Dossiervorlage."
},
"form": {
"description": "Beschreibung",
"description-placeholder": "Beschreibung eingeben",
"name": "Name der Dossier-Vorlage",
"name-placeholder": "Namen eingeben",
"valid-from": "Gültig ab",
"valid-to": "Gültig bis"
},
"save": "Dossier-Vorlage speichern",
"title": "{type, select, edit{Dossier-Vorlage {name} bearbeiten} create{Dossier-Vorlage erstellen} other{}}"
},
"add-edit-entity": {
"error": {
"entity-already-exists": "",
@ -454,6 +454,19 @@
},
"header": "Begründung für die Schwärzung bearbeiten"
},
"clone-dossier-template": {
"actions": {
"save": ""
},
"content": {
"name": "",
"name-placeholder": ""
},
"error": {
"generic": ""
},
"title": ""
},
"color": "",
"comments": {
"add-comment": "Kommentar eingeben",
@ -1124,6 +1137,10 @@
"label": "Diese Datei wurde gelöscht!"
}
},
"file-preview": {
"action": "",
"label": ""
},
"http": {
"generic": "Aktion mit Code {status} fehlgeschlagen"
},

View File

@ -36,22 +36,6 @@
},
"header-new": "Create Dossier"
},
"add-edit-clone-dossier-template": {
"error": {
"conflict": "Failed to create dossier template: a dossier template with the same name already exists.",
"generic": "Failed to create dossier template."
},
"form": {
"description": "Description",
"description-placeholder": "Enter Description",
"name": "Dossier Template Name",
"name-placeholder": "Enter Name",
"valid-from": "Valid from",
"valid-to": "Valid to"
},
"save": "Save Dossier Template",
"title": "{type, select, edit{Edit {name}} create{Create} clone{Clone} other{}} Dossier Template"
},
"add-edit-dossier-attribute": {
"error": {
"generic": "Failed to save attribute!"
@ -77,6 +61,22 @@
"success": "Successfully {type, select, edit{updated} create{created} other{}} the dossier state!",
"title": "{type, select, edit{Edit {name}} create{Create} other{}} Dossier State"
},
"add-edit-dossier-template": {
"error": {
"conflict": "Failed to create dossier template: a dossier template with the same name already exists.",
"generic": "Failed to create dossier template."
},
"form": {
"description": "Description",
"description-placeholder": "Enter Description",
"name": "Dossier Template Name",
"name-placeholder": "Enter Name",
"valid-from": "Valid from",
"valid-to": "Valid to"
},
"save": "Save Dossier Template",
"title": "{type, select, edit{Edit {name}} create{Create} other{}} Dossier Template"
},
"add-edit-entity": {
"error": {
"entity-already-exists": "Entity with this name already exists!",
@ -454,6 +454,19 @@
},
"header": "Edit Redaction Reason"
},
"clone-dossier-template": {
"actions": {
"save": "Save Dossier Template"
},
"content": {
"name": "Dossier Template Name",
"name-placeholder": "Enter Name"
},
"error": {
"generic": "Failed to clone dossier template."
},
"title": "Clone Dossier Template"
},
"color": "Color",
"comments": {
"add-comment": "Enter comment",
@ -1026,7 +1039,7 @@
"change-successful": "Dossier {dossierName} was updated.",
"delete-successful": "Dossier {dossierName} was deleted.",
"dictionary": {
"add-to-dictionary-action": "Available for add to dictionary",
"add-to-dictionary-action": "Enable 'Add to dictionary'",
"display-name": {
"cancel": "Cancel",
"edit": "Edit Display Name",
@ -1124,6 +1137,10 @@
"label": "This file has been deleted!"
}
},
"file-preview": {
"action": "Refresh",
"label": "An unknown error occurred. Please refresh the page"
},
"http": {
"generic": "Action failed with code {status}"
},

View File

@ -34,4 +34,5 @@ export interface IRedactionLogEntry {
textBefore?: string;
type?: string;
value?: string;
sourceId?: string;
}

View File

@ -1,6 +1,6 @@
{
"name": "redaction",
"version": "3.440.0",
"version": "3.451.0",
"private": true,
"license": "MIT",
"scripts": {

View File

@ -1,14 +1,6 @@
@use '../apps/red-ui/src/assets/styles/variables';
@use 'mixin';
.featured-content-label {
display: none;
}
.featured-content {
display: none;
}
.portal-single-publication {
background-color: transparent;
@ -22,7 +14,7 @@
}
.portal-contents {
margin-top: 100px;
margin-top: 24px;
margin-bottom: 0;
.inner {
@ -45,10 +37,6 @@
.portal-contents .inner {
grid-template-columns: 1fr;
}
.publication-contents:first-child {
grid-column: auto;
}
}
// Icon Overwrites for sections - change these if sections change!!!

View File

@ -86,13 +86,8 @@
background-color: variables.$white;
border-radius: 4px;
&:first-child {
grid-column: 1 / span 2;
}
h4.featured-title,
.section-toc-title {
@include heading-4;
margin: 0;
a {
@ -105,6 +100,10 @@
}
}
.section-toc-title {
@include heading-4;
}
ul {
margin: 0;
padding: 0;

Binary file not shown.

File diff suppressed because it is too large Load Diff