Pull request #84: Rule sets

Merge in RED/ui from rule-sets to master

* commit '615a45d91d78959d02f22baa61171885c4105f1c':
  Rule sets
This commit is contained in:
Timo Bejan 2021-01-08 10:01:45 +01:00
commit 4e4f99f0fb
31 changed files with 288 additions and 226 deletions

View File

@ -218,6 +218,15 @@ const routes = [
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
path: 'watermark',
component: WatermarkScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
requiredRoles: ['RED_ADMIN']
}
},
{ path: '', redirectTo: 'dictionaries', pathMatch: 'full' }
]
}
@ -230,15 +239,6 @@ const routes = [
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
path: 'watermark',
component: WatermarkScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
requiredRoles: ['RED_ADMIN']
}
}
]
}

View File

@ -16,3 +16,7 @@
margin-right: 5px;
font-weight: bold;
}
redaction-status-bar {
margin-left: 2px;
}

View File

@ -43,10 +43,11 @@ export class PermissionsService {
if (!fileStatus) {
return false;
}
const project = this._appStateService.getProjectById(fileStatus.projectId);
return (
((fileStatus.status === 'UNASSIGNED' || fileStatus.status === 'UNDER_REVIEW' || fileStatus.status === 'UNDER_APPROVAL') &&
(fileStatus.dictionaryVersion !== this._appStateService.dictionaryVersion ||
fileStatus.rulesVersion !== this._appStateService.rulesVersion ||
(fileStatus.dictionaryVersion !== this._appStateService.dictionaryVersion(project.ruleSetId) ||
fileStatus.rulesVersion !== this._appStateService.rulesVersion(project.ruleSetId) ||
fileStatus.hasUnappliedSuggestions)) ||
fileStatus.isError
);

View File

@ -5,7 +5,7 @@
[routerLinkActiveOptions]="{ exact: true }"
routerLinkActive="active"
translate="project-templates"
*ngIf="root || !!ruleSet"
*ngIf="root || !!appStateService.activeRuleSetId"
></a>
<a
@ -17,30 +17,25 @@
*ngIf="root && userPreferenceService.areDevFeaturesEnabled"
></a>
<a
class="breadcrumb"
[routerLink]="'/ui/admin/watermark'"
[routerLinkActiveOptions]="{ exact: true }"
routerLinkActive="active"
translate="watermark"
*ngIf="root && permissionService.isAdmin()"
></a>
<ng-container *ngIf="ruleSet">
<mat-icon svgIcon="red:arrow-right"></mat-icon>
<a class="breadcrumb ml-0" [routerLink]="'/ui/admin/project-templates/' + ruleSet.ruleSetId" [class.active]="!dictionary">
{{ ruleSet.name }}
</a>
</ng-container>
<ng-container *ngIf="dictionary">
<ng-container *ngIf="appStateService.activeRuleSetId">
<mat-icon svgIcon="red:arrow-right"></mat-icon>
<a
class="breadcrumb ml-0"
[routerLink]="'/ui/admin/project-templates/' + ruleSet.ruleSetId + '/dictionaries/' + dictionary.type"
[routerLink]="'/ui/admin/project-templates/' + appStateService.activeRuleSetId"
[class.active]="!appStateService.activeDictionaryType"
>
{{ appStateService.activeRuleSet.name }}
</a>
</ng-container>
<ng-container *ngIf="appStateService.activeDictionaryType">
<mat-icon svgIcon="red:arrow-right"></mat-icon>
<a
class="breadcrumb ml-0"
[routerLink]="'/ui/admin/project-templates/' + appStateService.activeRuleSetId + '/dictionaries/' + appStateService.activeDictionaryType"
routerLinkActive="active"
>
{{ dictionary.label }}
{{ appStateService.activeDictionary.label }}
</a>
</ng-container>
</div>

View File

@ -1,6 +1,4 @@
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { RuleSetModel, TypeValue } from '@redaction/red-ui-http';
import { Component, Input } from '@angular/core';
import { AppStateService } from '../../state/app-state.service';
import { UserPreferenceService } from '../../common/service/user-preference.service';
import { PermissionsService } from '../../common/service/permissions.service';
@ -10,27 +8,12 @@ import { PermissionsService } from '../../common/service/permissions.service';
templateUrl: './admin-breadcrumbs.component.html',
styleUrls: ['./admin-breadcrumbs.component.scss']
})
export class AdminBreadcrumbsComponent implements OnInit {
public dictionary: TypeValue;
public ruleSet: RuleSetModel;
@Input()
public root = false;
export class AdminBreadcrumbsComponent {
@Input() public root = false;
constructor(
public readonly userPreferenceService: UserPreferenceService,
public readonly permissionService: PermissionsService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _appStateService: AppStateService
public readonly appStateService: AppStateService
) {}
ngOnInit(): void {
this._activatedRoute.params.subscribe((params) => {
if (params.ruleSetId) {
this.ruleSet = this._appStateService.getRuleSetById(params.ruleSetId);
}
if (params.type) {
this.dictionary = this._appStateService.getDictionaryTypeValue(params.type);
}
});
}
}

View File

@ -9,6 +9,7 @@ import { TypeValue } from '@redaction/red-ui-http';
})
export class DictionaryAnnotationIconComponent implements OnChanges {
@Input() dictionaryKey: string;
@Input() ruleSetId: string;
color: string;
label: string;
@ -18,8 +19,8 @@ export class DictionaryAnnotationIconComponent implements OnChanges {
ngOnChanges(): void {
if (this.dictionaryKey) {
const typeValue: TypeValue = this._appStateService.getDictionaryTypeValue(this.dictionaryKey);
this.color = this._appStateService.getDictionaryColor(this.dictionaryKey);
const typeValue: TypeValue = this._appStateService.getDictionaryTypeValue(this.dictionaryKey, this.ruleSetId);
this.color = this._appStateService.getDictionaryColor(this.dictionaryKey, this.ruleSetId);
this.type = typeValue.hint ? 'circle' : 'square';
this.label = this.dictionaryKey[0].toUpperCase();
}

View File

@ -25,6 +25,11 @@ export class NeedsWorkBadgeComponent implements OnInit {
}
get suggestionColor() {
return this.appStateService.getDictionaryColor('suggestion');
let ruleSetId = null;
if (this.needsWorkInput instanceof ProjectWrapper) {
ruleSetId = this.needsWorkInput.ruleSetId;
}
return this.appStateService.getDictionaryColor('suggestion', ruleSetId);
}
}

View File

@ -2,6 +2,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { DialogService } from '../../dialogs/dialog.service';
import { PermissionsService } from '../../common/service/permissions.service';
import { RuleSetModel } from '@redaction/red-ui-http';
import { AppStateService } from '../../state/app-state.service';
@Component({
selector: 'redaction-rule-set-actions',
@ -12,7 +13,15 @@ export class RuleSetActionsComponent implements OnInit {
@Input() ruleSet: RuleSetModel;
@Output() loadRuleSetsData = new EventEmitter<any>();
constructor(private readonly _dialogService: DialogService, public readonly permissionsService: PermissionsService) {}
constructor(
private readonly _dialogService: DialogService,
private readonly _appStateService: AppStateService,
public readonly permissionsService: PermissionsService
) {
if (!this.ruleSet) {
this.ruleSet = this._appStateService.activeRuleSet;
}
}
ngOnInit(): void {}

View File

@ -3,6 +3,7 @@
<mat-button-toggle-group [value]="screen" (change)="switchView($event)" appearance="legacy">
<mat-button-toggle [value]="'dictionaries'"> {{ 'dictionaries' | translate }}</mat-button-toggle>
<mat-button-toggle [value]="'rules'"> {{ 'rule-editor' | translate }}</mat-button-toggle>
<mat-button-toggle [value]="'watermark'"> {{ 'watermark' | translate }}</mat-button-toggle>
</mat-button-toggle-group>
</div>
</div>

View File

@ -1,5 +1,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Router } from '@angular/router';
import { AppStateService } from '../../state/app-state.service';
@Component({
selector: 'redaction-rule-set-view-switch',
@ -7,21 +8,13 @@ import { ActivatedRoute, Router } from '@angular/router';
styleUrls: ['./rule-set-view-switch.component.scss']
})
export class RuleSetViewSwitchComponent implements OnInit {
@Input() public screen: 'rules' | 'dictionaries';
@Input() public screen: 'rules' | 'dictionaries' | 'watermark';
private readonly _ruleSetId: string;
constructor(private readonly _actr: ActivatedRoute, private _router: Router) {
this._ruleSetId = this._actr.snapshot.params.ruleSetId;
}
constructor(private readonly _router: Router, private readonly _appStateService: AppStateService) {}
ngOnInit(): void {}
public switchView($event) {
if ($event.value === 'dictionaries') {
this._router.navigate(['ui/admin/project-templates/' + this._ruleSetId + '/dictionaries']);
} else {
this._router.navigate(['ui/admin/project-templates/' + this._ruleSetId + '/rules']);
}
this._router.navigate(['ui/admin/project-templates/' + this._appStateService.activeRuleSetId + '/' + $event.value]);
}
}

View File

@ -1,6 +1,7 @@
import { Component, Input, OnInit } from '@angular/core';
import { FilterModel } from '../../common/filter/model/filter.model';
import { AppStateService } from '../../state/app-state.service';
import { DEFAULT_RUL_SET_UUID } from '../../utils/rule-set-default';
@Component({
selector: 'redaction-type-filter',
@ -15,6 +16,6 @@ export class TypeFilterComponent implements OnInit {
constructor(public appStateService: AppStateService) {}
ngOnInit(): void {
this.dictionaryColor = this.appStateService.getDictionaryColor(this.filter.key);
this.dictionaryColor = this.appStateService.getDictionaryColor(this.filter.key, this.appStateService.activeProject?.ruleSetId || DEFAULT_RUL_SET_UUID);
}
}

View File

@ -5,6 +5,7 @@ import {
FileManagementControllerService,
FileStatus,
ManualRedactionControllerService,
RuleSetControllerService,
RuleSetModel,
TypeValue
} from '@redaction/red-ui-http';
@ -21,7 +22,6 @@ import { ManualAnnotationService } from '../screens/file/service/manual-annotati
import { ProjectWrapper } from '../state/model/project.wrapper';
import { AddEditDictionaryDialogComponent } from '../screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component';
import { AddEditRuleSetDialogComponent } from '../screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component';
import { DEFAULT_RUL_SET_UUID } from '../utils/rule-set-default';
const dialogConfig = {
width: '662px',
@ -37,6 +37,7 @@ export class DialogService {
private readonly _dialog: MatDialog,
private readonly _translateService: TranslateService,
private readonly _appStateService: AppStateService,
private readonly _ruleSetControllerService: RuleSetControllerService,
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _fileManagementControllerService: FileManagementControllerService,
private readonly _notificationService: NotificationService,
@ -170,12 +171,12 @@ export class DialogService {
return ref;
}
public openDeleteDictionaryDialog($event: MouseEvent, dictionary: TypeValue, cb?: Function): MatDialogRef<ConfirmationDialogComponent> {
public openDeleteDictionaryDialog($event: MouseEvent, dictionary: TypeValue, ruleSetId: string, cb?: Function): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation();
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
ref.afterClosed().subscribe(async (result) => {
if (result) {
await this._dictionaryControllerService.deleteType(dictionary.type, DEFAULT_RUL_SET_UUID).toPromise();
await this._dictionaryControllerService.deleteType(dictionary.type, ruleSetId).toPromise();
if (cb) cb();
}
});
@ -187,6 +188,8 @@ export class DialogService {
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
ref.afterClosed().subscribe(async (result) => {
if (result) {
await this._ruleSetControllerService.getAllRuleSets(ruleSet.ruleSetId).toPromise();
await this._appStateService.loadAllRuleSets();
if (cb) cb();
}
});
@ -276,10 +279,10 @@ export class DialogService {
return ref;
}
public openAddEditDictionaryDialog(dictionary: TypeValue, cb?: Function): MatDialogRef<AddEditDictionaryDialogComponent> {
public openAddEditDictionaryDialog(dictionary: TypeValue, ruleSetId: string, cb?: Function): MatDialogRef<AddEditDictionaryDialogComponent> {
const ref = this._dialog.open(AddEditDictionaryDialogComponent, {
...dialogConfig,
data: dictionary,
data: { dictionary, ruleSetId },
autoFocus: true
});

View File

@ -1,4 +1,4 @@
import { Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
import { Component, Inject, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AppStateService } from '../../state/app-state.service';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@ -10,7 +10,6 @@ import { ManualRedactionEntryWrapper } from '../../screens/file/model/manual-red
import { ManualAnnotationService } from '../../screens/file/service/manual-annotation.service';
import { ManualAnnotationResponse } from '../../screens/file/model/manual-annotation-response';
import { PermissionsService } from '../../common/service/permissions.service';
import { DEFAULT_RUL_SET_UUID } from '../../utils/rule-set-default';
export interface LegalBasisOption {
label?: string;
@ -57,7 +56,7 @@ export class ManualAnnotationDialogComponent implements OnInit {
) {}
async ngOnInit() {
this._legalBasisMappingControllerService.getLegalBasisMapping(DEFAULT_RUL_SET_UUID).subscribe((data) => {
this._legalBasisMappingControllerService.getLegalBasisMapping(this._appStateService.activeProject.ruleSetId).subscribe((data) => {
for (const key of Object.keys(data.reasonByLegalBasis)) {
this.legalOptions.push({
legalBasis: key,
@ -80,8 +79,8 @@ export class ManualAnnotationDialogComponent implements OnInit {
comment: this.isDocumentAdmin ? [null] : [null, Validators.required]
});
for (const key of Object.keys(this._appStateService.dictionaryData)) {
const dictionaryData = this._appStateService.dictionaryData[key];
for (const key of Object.keys(this._appStateService.dictionaryData[this._appStateService.activeProject.ruleSetId])) {
const dictionaryData = this._appStateService.getDictionaryTypeValue(key);
if (!dictionaryData.virtual && !(dictionaryData.type.indexOf('recommendation_') === 0)) {
if (!dictionaryData.hint && dictionaryData.type !== 'manual') {
this.redactionDictionaries.push(dictionaryData);

View File

@ -1,4 +1,4 @@
import { Component, Inject, OnInit } from '@angular/core';
import { Component, Inject } from '@angular/core';
import { AppStateService } from '../../../../state/app-state.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@ -6,7 +6,6 @@ import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http';
import { Observable } from 'rxjs';
import { NotificationService, NotificationType } from '../../../../notification/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { DEFAULT_RUL_SET_UUID } from '../../../../utils/rule-set-default';
@Component({
selector: 'redaction-add-edit-dictionary-dialog',
@ -15,6 +14,8 @@ import { DEFAULT_RUL_SET_UUID } from '../../../../utils/rule-set-default';
})
export class AddEditDictionaryDialogComponent {
dictionaryForm: FormGroup;
public readonly dictionary: TypeValue;
private readonly _ruleSetId: string;
constructor(
private readonly _dictionaryControllerService: DictionaryControllerService,
@ -23,8 +24,10 @@ export class AddEditDictionaryDialogComponent {
private readonly _notificationService: NotificationService,
private readonly _translateService: TranslateService,
public dialogRef: MatDialogRef<AddEditDictionaryDialogComponent>,
@Inject(MAT_DIALOG_DATA) public dictionary: TypeValue
@Inject(MAT_DIALOG_DATA) public data: { dictionary: TypeValue; ruleSetId: string }
) {
this.dictionary = data.dictionary;
this._ruleSetId = data.ruleSetId;
this.dictionaryForm = this._formBuilder.group({
type: [this.dictionary?.type, Validators.required],
rank: [this.dictionary?.rank, Validators.required],
@ -60,14 +63,13 @@ export class AddEditDictionaryDialogComponent {
let observable: Observable<any>;
if (this.dictionary) {
// edit mode
observable = this._dictionaryControllerService.updateType(typeValue, typeValue.type, DEFAULT_RUL_SET_UUID);
observable = this._dictionaryControllerService.updateType(typeValue, typeValue.type, this._ruleSetId);
} else {
// create mode
typeValue.ruleSetId = DEFAULT_RUL_SET_UUID;
typeValue.ruleSetId = this._ruleSetId;
observable = this._dictionaryControllerService.addType(typeValue);
}
//
observable.subscribe(
() => {
this.dialogRef.close({ dictionary: typeValue });
@ -94,7 +96,8 @@ export class AddEditDictionaryDialogComponent {
hexColor: this.dictionaryForm.get('hexColor').value,
hint: this.dictionaryForm.get('hint').value,
type: this.dictionaryForm.get('type').value,
rank: this.dictionaryForm.get('rank').value
rank: this.dictionaryForm.get('rank').value,
ruleSetId: this._ruleSetId
};
}
}

View File

@ -5,7 +5,7 @@
<redaction-rule-set-view-switch [screen]="'dictionaries'"></redaction-rule-set-view-switch>
<div class="flex-1 actions">
<redaction-rule-set-actions (loadRuleSetsData)="loadRuleSetsData()" [ruleSet]="ruleSet"> </redaction-rule-set-actions>
<redaction-rule-set-actions (loadRuleSetsData)="loadRuleSetsData()"> </redaction-rule-set-actions>
<redaction-circle-button [routerLink]="['../..']" tooltip="common.close" tooltipPosition="before" icon="red:close"></redaction-circle-button>
</div>

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { DoughnutChartConfig } from '../../../components/simple-doughnut-chart/simple-doughnut-chart.component';
import { DictionaryControllerService, RuleSetModel, TypeValue } from '@redaction/red-ui-http';
import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http';
import { SortingOption, SortingService } from '../../../utils/sorting.service';
import { DialogService } from '../../../dialogs/dialog.service';
import { AppStateService } from '../../../state/app-state.service';
@ -9,7 +9,6 @@ import { forkJoin } from 'rxjs';
import { PermissionsService } from '../../../common/service/permissions.service';
import { FormBuilder, FormGroup } from '@angular/forms';
import { debounce } from '../../../utils/debounce';
import { DEFAULT_RUL_SET_UUID } from '../../../utils/rule-set-default';
import { ActivatedRoute } from '@angular/router';
@Component({
@ -23,24 +22,23 @@ export class DictionaryListingScreenComponent implements OnInit {
public displayedDictionaries: TypeValue[];
public selectedDictKeys: string[] = [];
public searchForm: FormGroup;
public ruleSet: RuleSetModel;
constructor(
private readonly _dialogService: DialogService,
private readonly _sortingService: SortingService,
private readonly _formBuilder: FormBuilder,
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _appStateService: AppStateService,
private readonly _actr: ActivatedRoute,
public readonly permissionsService: PermissionsService
) {
this._appStateService.activateRuleSet(_activatedRoute.snapshot.params.ruleSetId);
this.searchForm = this._formBuilder.group({
query: ['']
});
this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value));
this.ruleSet = this._appStateService.getRuleSetById(this._actr.snapshot.params.ruleSetId);
}
ngOnInit(): void {
@ -54,15 +52,14 @@ export class DictionaryListingScreenComponent implements OnInit {
}
private _loadDictionaryData() {
this._appStateService.reset();
const appStateDictionaryData = this._appStateService.dictionaryData;
const appStateDictionaryData = this._appStateService.dictionaryData[this._appStateService.activeRuleSetId];
this.dictionaries = Object.keys(appStateDictionaryData)
.map((key) => appStateDictionaryData[key])
.filter((d) => !d.virtual || d.type === 'false_positive');
this.displayedDictionaries = [...this.dictionaries];
const dataObs = [];
this.dictionaries.forEach((item) => {
const observable = this._dictionaryControllerService.getDictionaryForType(item.type, DEFAULT_RUL_SET_UUID).pipe(
const observable = this._dictionaryControllerService.getDictionaryForType(item.type, this._appStateService.activeRuleSetId).pipe(
tap((values) => {
item.entries = values.entries ? values.entries : [];
})
@ -126,7 +123,7 @@ export class DictionaryListingScreenComponent implements OnInit {
}
openAddEditDictionaryDialog(dict?: TypeValue) {
this._dialogService.openAddEditDictionaryDialog(dict, async (newDictionary) => {
this._dialogService.openAddEditDictionaryDialog(dict, this._appStateService.activeRuleSetId, async (newDictionary) => {
if (newDictionary) {
await this._appStateService.loadDictionaryData();
this._loadDictionaryData();
@ -140,13 +137,13 @@ export class DictionaryListingScreenComponent implements OnInit {
}
openDeleteDictionaryDialog($event: any, dict: TypeValue) {
this._dialogService.openDeleteDictionaryDialog($event, dict, async () => {
this._dialogService.openDeleteDictionaryDialog($event, dict, this._appStateService.activeRuleSetId, async () => {
await this._appStateService.loadDictionaryData();
this._loadDictionaryData();
});
}
public loadRuleSetsData(): void {
console.log('load rule sets data');
public async loadRuleSetsData(): Promise<void> {
await this._appStateService.loadAllRuleSets();
}
}

View File

@ -132,7 +132,10 @@
</div>
<div class="indicator">
<redaction-dictionary-annotation-icon [dictionaryKey]="dictionary.hint ? 'hint' : 'redaction'"></redaction-dictionary-annotation-icon>
<redaction-dictionary-annotation-icon
[dictionaryKey]="dictionary.hint ? 'hint' : 'redaction'"
[ruleSetId]="dictionary.ruleSetId"
></redaction-dictionary-annotation-icon>
<div class="large-label">
{{ (dictionary.hint ? 'hint' : 'redaction') | translate }}
</div>

View File

@ -5,10 +5,6 @@
height: calc(100% - 50px);
}
.changes-box {
right: 403px;
}
.left-container {
width: calc(100vw - 353px);
padding: 15px;

View File

@ -10,7 +10,6 @@ import { NotificationService, NotificationType } from '../../../notification/not
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { saveAs } from 'file-saver';
import { DEFAULT_RUL_SET_UUID } from '../../../utils/rule-set-default';
import { ComponentHasChanges } from '../../../utils/can-deactivate.guard';
declare var ace;
@ -59,18 +58,13 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges {
private readonly _appStateService: AppStateService
) {
super(_translateService);
this._activatedRoute.params.subscribe((params) => {
this.dictionary = this._appStateService.dictionaryData[params.type];
if (!this.dictionary) {
this._router.navigate(['/ui/admin/dictionaries']);
} else {
this._initialize();
}
});
this._appStateService.activateDictionary(this._activatedRoute.snapshot.params.type, this._activatedRoute.snapshot.params.ruleSetId);
this.dictionary = this._appStateService.activeDictionary;
this._initialize();
}
private _initialize() {
this._dictionaryControllerService.getDictionaryForType(this.dictionary.type, DEFAULT_RUL_SET_UUID).subscribe(
this._dictionaryControllerService.getDictionaryForType(this.dictionary.type, this.dictionary.ruleSetId).subscribe(
(data) => {
this.initialDictionaryEntries = data.entries.sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' }));
this.revert();
@ -83,7 +77,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges {
openEditDictionaryDialog($event: any) {
$event.stopPropagation();
this._dialogService.openAddEditDictionaryDialog(this.dictionary, async (newDictionary) => {
this._dialogService.openAddEditDictionaryDialog(this.dictionary, this.dictionary.ruleSetId, async (newDictionary) => {
if (newDictionary) {
await this._appStateService.loadDictionaryData();
this.dictionary = this._appStateService.dictionaryData[this.dictionary.type];
@ -92,7 +86,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges {
}
openDeleteDictionaryDialog($event: any) {
this._dialogService.openDeleteDictionaryDialog($event, this.dictionary, async () => {
this._dialogService.openDeleteDictionaryDialog($event, this.dictionary, this.dictionary.ruleSetId, async () => {
await this._appStateService.loadDictionaryData();
this._router.navigate(['/ui/admin/dictionaries']);
});
@ -186,9 +180,9 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges {
this.processing = true;
let obs: Observable<any>;
if (entriesToAdd.length > 0) {
obs = this._dictionaryControllerService.addEntry(entriesToAdd, this.dictionary.type, DEFAULT_RUL_SET_UUID, true);
obs = this._dictionaryControllerService.addEntry(entriesToAdd, this.dictionary.type, this.dictionary.ruleSetId, true);
} else {
obs = this._dictionaryControllerService.deleteEntries(this.initialDictionaryEntries, this.dictionary.type, DEFAULT_RUL_SET_UUID);
obs = this._dictionaryControllerService.deleteEntries(this.initialDictionaryEntries, this.dictionary.type, this.dictionary.ruleSetId);
}
obs.subscribe(

View File

@ -54,7 +54,8 @@ export class AddEditRuleSetDialogComponent {
ruleSetId: this.ruleSet?.ruleSetId,
...this.ruleSetForm.getRawValue()
};
const res = await this._ruleSetController.createOrUpdateRuleSet(ruleSet).toPromise();
await this._ruleSetController.createOrUpdateRuleSet(ruleSet).toPromise();
await this._appStateService.loadAllRuleSets();
this.dialogRef.close({ ruleSet });
}
}

View File

@ -5,7 +5,7 @@
<redaction-rule-set-view-switch [screen]="'rules'"></redaction-rule-set-view-switch>
<div class="flex-1 actions">
<redaction-rule-set-actions (loadRuleSetsData)="loadRuleSetsData()" [ruleSet]="ruleSet"> </redaction-rule-set-actions>
<redaction-rule-set-actions (loadRuleSetsData)="loadRuleSetsData()"> </redaction-rule-set-actions>
<redaction-circle-button [routerLink]="['../..']" tooltip="common.close" tooltipPosition="before" icon="red:close"></redaction-circle-button>
</div>

View File

@ -6,10 +6,6 @@
@include inset-shadow;
}
.changes-box {
right: 40px;
}
.page-header .actions {
display: flex;
justify-content: flex-end;

View File

@ -5,7 +5,6 @@ import { RulesControllerService, RuleSetModel } from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../../../notification/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { saveAs } from 'file-saver';
import { DEFAULT_RUL_SET_UUID } from '../../../utils/rule-set-default';
import { ComponentHasChanges } from '../../../utils/can-deactivate.guard';
import { ActivatedRoute } from '@angular/router';
import { AppStateService } from '../../../state/app-state.service';
@ -26,7 +25,6 @@ export class RulesScreenComponent extends ComponentHasChanges {
public currentLines: string[] = [];
public changedLines: number[] = [];
public activeEditMarkers: any[] = [];
public ruleSet: RuleSetModel;
@ViewChild('editorComponent', { static: true })
editorComponent: AceEditorComponent;
@ -40,15 +38,15 @@ export class RulesScreenComponent extends ComponentHasChanges {
private readonly _appStateService: AppStateService,
private readonly _notificationService: NotificationService,
protected readonly _translateService: TranslateService,
private readonly _actr: ActivatedRoute
private readonly _activatedRoute: ActivatedRoute
) {
super(_translateService);
this._appStateService.activateRuleSet(_activatedRoute.snapshot.params.ruleSetId);
this._initialize();
this.ruleSet = this._appStateService.getRuleSetById(this._actr.snapshot.params.ruleSetId);
}
private _initialize() {
this._rulesControllerService.downloadRules(DEFAULT_RUL_SET_UUID).subscribe(
this._rulesControllerService.downloadRules(this._appStateService.activeRuleSetId).subscribe(
(rules) => {
this.rules = rules.rules;
this.revert();
@ -90,16 +88,22 @@ export class RulesScreenComponent extends ComponentHasChanges {
public async save(): Promise<void> {
this.processing = true;
this._rulesControllerService.uploadRules({ rules: this.editorComponent.getEditor().getValue(), ruleSetId: DEFAULT_RUL_SET_UUID }).subscribe(
() => {
this._initialize();
this._notificationService.showToastNotification(this._translateService.instant('rules-screen.success.generic'), null, NotificationType.SUCCESS);
},
() => {
this.processing = false;
this._notificationService.showToastNotification(this._translateService.instant('rules-screen.error.generic'), null, NotificationType.ERROR);
}
);
this._rulesControllerService
.uploadRules({ rules: this.editorComponent.getEditor().getValue(), ruleSetId: this._appStateService.activeRuleSetId })
.subscribe(
() => {
this._initialize();
this._notificationService.showToastNotification(
this._translateService.instant('rules-screen.success.generic'),
null,
NotificationType.SUCCESS
);
},
() => {
this.processing = false;
this._notificationService.showToastNotification(this._translateService.instant('rules-screen.error.generic'), null, NotificationType.ERROR);
}
);
}
public revert(): void {

View File

@ -1,8 +1,10 @@
<section>
<div class="page-header">
<redaction-admin-breadcrumbs class="flex-1" [root]="true"></redaction-admin-breadcrumbs>
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<div class="actions">
<redaction-rule-set-view-switch [screen]="'watermark'"></redaction-rule-set-view-switch>
<div class="actions flex-1">
<redaction-circle-button
class="ml-6"
*ngIf="permissionsService.isUser()"

View File

@ -10,10 +10,6 @@
.viewer {
height: 100%;
}
.changes-box {
right: 40px;
}
}
.right-container {

View File

@ -10,7 +10,7 @@ import { debounce } from '../../../utils/debounce';
import { WatermarkControllerService, WatermarkModel } from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../../../notification/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { DEFAULT_RUL_SET_UUID } from '../../../utils/rule-set-default';
import { ActivatedRoute } from '@angular/router';
export const DEFAULT_WATERMARK: WatermarkModel = {
text:
@ -32,7 +32,6 @@ export const DEFAULT_WATERMARK: WatermarkModel = {
})
export class WatermarkScreenComponent implements OnInit {
private _instance: WebViewerInstance;
private _watermark: WatermarkModel = {};
@ViewChild('viewer', { static: true })
@ -62,8 +61,10 @@ export class WatermarkScreenComponent implements OnInit {
private readonly _fileDownloadService: FileDownloadService,
private readonly _http: HttpClient,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _formBuilder: FormBuilder
private readonly _formBuilder: FormBuilder,
private readonly _activatedRoute: ActivatedRoute
) {
this.appStateService.activateRuleSet(_activatedRoute.snapshot.params.ruleSetId);
this._initForm();
}
@ -72,7 +73,7 @@ export class WatermarkScreenComponent implements OnInit {
}
private _loadWatermark() {
this._watermarkControllerService.getWatermark(DEFAULT_RUL_SET_UUID).subscribe(
this._watermarkControllerService.getWatermark(this.appStateService.activeRuleSetId).subscribe(
(watermark) => {
this._watermark = watermark;
this.configForm.setValue({ ...this._watermark, fontType: 'sans-serif', orientation: 'diagonal' });
@ -95,7 +96,7 @@ export class WatermarkScreenComponent implements OnInit {
const watermark = {
...this.configForm.getRawValue()
};
this._watermarkControllerService.saveWatermark(watermark, DEFAULT_RUL_SET_UUID).subscribe(
this._watermarkControllerService.saveWatermark(watermark, this.appStateService.activeRuleSetId).subscribe(
() => {
this._loadWatermark();
this._notificationService.showToastNotification(

View File

@ -150,6 +150,10 @@
}
}
redaction-dictionary-annotation-icon {
margin-right: 8px;
}
.mr-16 {
margin-right: 16px;
}

View File

@ -182,7 +182,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
this.activeViewer.annotManager.deleteAnnotations(existingAnnotations, true, true);
}
this.annotations = this.fileData.getAnnotations(
this.appStateService.dictionaryData,
this.appStateService.dictionaryData[this.appStateService.activeProject.ruleSetId],
this.permissionsService.currentUser,
this.userPreferenceService.areDevFeaturesEnabled
);

View File

@ -6,7 +6,6 @@ import {
Project,
ProjectControllerService,
ReanalysisControllerService,
RulesControllerService,
RuleSetControllerService,
RuleSetModel,
StatusControllerService,
@ -17,33 +16,32 @@ import { NotificationService, NotificationType } from '../notification/notificat
import { TranslateService } from '@ngx-translate/core';
import { Router } from '@angular/router';
import { UserService } from '../user/user.service';
import { forkJoin } from 'rxjs';
import { forkJoin, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { humanize } from '../utils/functions';
import { FileStatusWrapper } from '../screens/file/model/file-status.wrapper';
import { ProjectWrapper } from './model/project.wrapper';
import { saveAs } from 'file-saver';
import { DEFAULT_RUL_SET_UUID } from '../utils/rule-set-default';
export interface AppState {
projects: ProjectWrapper[];
ruleSets: RuleSetModel[];
activeProjectId: string;
activeFileId: string;
activeRuleSetId: string;
activeDictionaryType: string;
totalAnalysedPages?: number;
totalDocuments?: number;
totalPeople?: number;
dictionaryVersion?: number;
ruleVersion?: number;
versions: { [key: string]: { dictionaryVersion?: number; rulesVersion?: number } };
}
@Injectable({
providedIn: 'root'
})
// TODO everything needs to be stored ruleSetBased in either an Object ( Dict ) or Map Structure.
export class AppStateService {
private _appState: AppState;
private _dictionaryData: { [key: string]: TypeValue } = null;
private _dictionaryData: { [key: string]: { [key: string]: TypeValue } } = null;
public fileChanged = new EventEmitter<FileStatusWrapper>();
public fileReanalysed = new EventEmitter<FileStatusWrapper>();
@ -64,7 +62,10 @@ export class AppStateService {
projects: [],
ruleSets: [],
activeProjectId: null,
activeFileId: null
activeFileId: null,
activeRuleSetId: null,
activeDictionaryType: null,
versions: {}
};
}
@ -79,28 +80,41 @@ export class AppStateService {
if (!fileStatus) {
fileStatus = this.activeFile;
}
return fileStatus.dictionaryVersion === this.dictionaryVersion && fileStatus.rulesVersion === this.rulesVersion;
return fileStatus.dictionaryVersion === this.dictionaryVersion() && fileStatus.rulesVersion === this.rulesVersion();
}
get dictionaryVersion() {
return this._appState.dictionaryVersion;
public dictionaryVersion(ruleSetId?: string) {
if (!ruleSetId) {
ruleSetId = this.activeProject.ruleSetId;
}
return this._appState.versions[ruleSetId].dictionaryVersion;
}
get rulesVersion() {
return this._appState.ruleVersion;
public rulesVersion(ruleSetId?: string) {
if (!ruleSetId) {
ruleSetId = this.activeProject.ruleSetId;
}
return this._appState.versions[ruleSetId].rulesVersion;
}
get dictionaryData(): { [key: string]: TypeValue } {
get dictionaryData(): { [key: string]: { [key: string]: TypeValue } } {
return this._dictionaryData;
}
getDictionaryColor(type?: string) {
const color = this._dictionaryData[type]?.hexColor;
return color ? color : this._dictionaryData['default'].hexColor;
getDictionaryColor(type?: string, ruleSetId?: string) {
if (!ruleSetId && this.activeProject) {
ruleSetId = this.activeProject.ruleSetId;
}
const color = this._dictionaryData[ruleSetId][type]?.hexColor;
return color ? color : this._dictionaryData[ruleSetId]['default'].hexColor;
}
getDictionaryLabel(type: string) {
return this._dictionaryData[type].label;
getDictionaryLabel(type: string, ruleSetId?: string) {
if (!ruleSetId && this.activeProject) {
ruleSetId = this.activeProject.ruleSetId;
}
return this._dictionaryData[ruleSetId][type].label;
}
get aggregatedFiles(): FileStatusWrapper[] {
@ -111,12 +125,45 @@ export class AppStateService {
return result;
}
get activeRuleSetId(): string {
return this._appState.activeRuleSetId;
}
get activeRuleSet(): RuleSetModel {
return this.getRuleSetById(this.activeRuleSetId);
}
public get ruleSets(): RuleSetModel[] {
return this._appState.ruleSets;
}
public getRuleSetById(id: string): RuleSetModel {
return this.ruleSets.find((rs) => rs.ruleSetId === id);
}
get activeDictionaryType(): string {
return this._appState.activeDictionaryType;
}
get activeDictionary(): TypeValue {
return this.dictionaryData[this.activeRuleSetId][this.activeDictionaryType];
}
getDictionaryTypeValue(key: string, ruleSetId?: string) {
if (!ruleSetId && this.activeProject) {
ruleSetId = this.activeProject.ruleSetId;
}
const data = this._dictionaryData[ruleSetId][key];
return data ? data : this._dictionaryData[ruleSetId]['default'];
}
get activeProjectId(): string {
return this._appState.activeProjectId;
}
get activeProject(): ProjectWrapper {
return this._appState.projects.find((p) => p.projectId === this._appState.activeProjectId);
return this._appState.projects.find((p) => p.projectId === this.activeProjectId);
}
get allProjects(): ProjectWrapper[] {
@ -155,14 +202,6 @@ export class AppStateService {
return this.getProjectById(projectId).files.find((file) => file.fileId === fileId);
}
public get ruleSets(): RuleSetModel[] {
return this._appState.ruleSets;
}
public getRuleSetById(id: string): RuleSetModel {
return this.ruleSets.find((pt) => pt.ruleSetId === id);
}
async loadAllProjects() {
const projects = await this._projectControllerService.getProjects().toPromise();
if (projects) {
@ -274,27 +313,45 @@ export class AppStateService {
if (!this.activeProject) {
this._appState.activeProjectId = null;
this._router.navigate(['/ui/projects']);
return;
}
return this.activeProject;
}
activateFile(projectId: string, fileId: string) {
const activeProject = this.activateProject(projectId);
if (activeProject) {
this.activateProject(projectId);
if (this.activeProject) {
this._appState.activeFileId = fileId;
if (!this.activeFile) {
this._appState.activeFileId = null;
this._router.navigate(['/ui/projects/' + projectId]);
return;
}
return this.activateFile;
}
}
activateRuleSet(ruleSetId: string) {
this._appState.activeRuleSetId = ruleSetId;
this._appState.activeDictionaryType = null;
if (!this.activeRuleSet) {
this._appState.activeRuleSetId = null;
this._router.navigate(['/ui/admin/project-templates']);
}
}
activateDictionary(dictionaryType: string, ruleSetId: string) {
this.activateRuleSet(ruleSetId);
if (this.activeRuleSet) {
this._appState.activeDictionaryType = dictionaryType;
if (!this.activeDictionary) {
this._appState.activeDictionaryType = null;
this._router.navigate(['/ui/admin/project-templates/' + this.activeRuleSetId]);
}
}
}
reset() {
this._appState.activeFileId = null;
this._appState.activeProjectId = null;
this._appState.activeRuleSetId = null;
this._appState.activeDictionaryType = null;
}
deleteProject(project: ProjectWrapper) {
@ -319,8 +376,6 @@ export class AppStateService {
async addOrUpdateProject(project: Project) {
try {
// TODO fix after adding this to the Dialog
project.ruleSetId = DEFAULT_RUL_SET_UUID;
const updatedProject = await this._projectControllerService.createOrUpdateProject(project).toPromise();
let foundProject = this._appState.projects.find((p) => p.project.projectId === updatedProject.projectId);
if (foundProject) {
@ -407,120 +462,128 @@ export class AppStateService {
}
}
async loadDictionaryData() {
this._dictionaryData = {};
const typeObs = this._dictionaryControllerService.getAllTypes(DEFAULT_RUL_SET_UUID).pipe(
getDictionaryDataForRuleSetObservables(ruleSetId: string, dictionaryData: {}): Observable<any>[] {
const typeObs = this._dictionaryControllerService.getAllTypes(ruleSetId).pipe(
tap((typesResponse) => {
for (const type of typesResponse.types) {
this._dictionaryData[type.type] = type;
this._dictionaryData[type.type].virtual = type.type === 'false_positive';
dictionaryData[type.type] = type;
dictionaryData[type.type].virtual = type.type === 'false_positive';
dictionaryData[type.type].label = humanize(type.type, false);
}
})
);
const colorsObs = this._dictionaryControllerService.getColors(DEFAULT_RUL_SET_UUID).pipe(
const colorsObs = this._dictionaryControllerService.getColors(ruleSetId).pipe(
tap((colors) => {
// declined
this._dictionaryData['declined-suggestion'] = {
dictionaryData['declined-suggestion'] = {
hexColor: colors.notRedacted,
type: 'declined-suggestion',
virtual: true
};
// manual
this._dictionaryData['manual'] = {
dictionaryData['manual'] = {
hexColor: colors.defaultColor,
type: 'manual',
virtual: true
};
// dictionary actions
this._dictionaryData['add-dictionary'] = {
dictionaryData['add-dictionary'] = {
hexColor: '#dd4d50',
type: 'add-dictionary',
virtual: true
};
this._dictionaryData['remove-dictionary'] = {
dictionaryData['remove-dictionary'] = {
hexColor: '#dd4d50',
type: 'remove-dictionary',
virtual: true
};
// generic suggestions
this._dictionaryData['suggestion'] = {
dictionaryData['suggestion'] = {
hexColor: colors.requestAdd,
type: 'suggestion',
virtual: true
};
// add suggestions
this._dictionaryData['suggestion-add'] = {
dictionaryData['suggestion-add'] = {
hexColor: colors.requestAdd,
type: 'suggestion-add',
virtual: true
};
this._dictionaryData['suggestion-add-dictionary'] = {
dictionaryData['suggestion-add-dictionary'] = {
hexColor: '#5B97DB',
type: 'suggestion-add',
virtual: true
};
// suggestion remove
this._dictionaryData['suggestion-remove'] = {
dictionaryData['suggestion-remove'] = {
hexColor: colors.requestRemove,
type: 'suggestion-add',
virtual: true
};
this._dictionaryData['suggestion-remove-dictionary'] = {
dictionaryData['suggestion-remove-dictionary'] = {
hexColor: '#5B97DB',
type: 'suggestion-add',
virtual: true
};
this._dictionaryData['manual'] = {
dictionaryData['manual'] = {
hexColor: colors.defaultColor,
type: 'manual',
virtual: true
};
this._dictionaryData['ignore'] = {
dictionaryData['ignore'] = {
hexColor: colors.notRedacted,
type: 'ignore',
virtual: true
};
this._dictionaryData['default'] = {
dictionaryData['default'] = {
hexColor: colors.defaultColor,
type: 'default',
virtual: true
};
this._dictionaryData['add'] = {
dictionaryData['add'] = {
hexColor: colors.requestAdd,
type: 'add',
virtual: true
};
this._dictionaryData['analysis'] = {
dictionaryData['analysis'] = {
hexColor: '#dd4d50',
type: 'analysis',
virtual: true
};
dictionaryData['hint'] = {
hexColor: '#9398a0',
type: 'hint',
virtual: true,
hint: true
};
dictionaryData['redaction'] = {
hexColor: '#283241',
type: 'redaction',
virtual: true
};
})
);
await forkJoin([typeObs, colorsObs]).toPromise();
this._dictionaryData['hint'] = { hexColor: '#9398a0', type: 'hint', virtual: true, hint: true };
this._dictionaryData['redaction'] = {
hexColor: '#283241',
type: 'redaction',
virtual: true
};
for (const key of Object.keys(this._dictionaryData)) {
this._dictionaryData[key].label = humanize(key, false);
}
return [typeObs, colorsObs];
}
getDictionaryTypeValue(key: string) {
const data = this._dictionaryData[key];
return data ? data : this._dictionaryData['default'];
async loadDictionaryData() {
this._dictionaryData = {};
const observables = [];
for (const ruleSet of this.ruleSets) {
this.dictionaryData[ruleSet.ruleSetId] = {};
observables.push(...this.getDictionaryDataForRuleSetObservables(ruleSet.ruleSetId, this.dictionaryData[ruleSet.ruleSetId]));
}
await forkJoin(observables).toPromise();
}
async updateDictionaryVersion() {
const result = await this._versionsControllerService.getVersions([DEFAULT_RUL_SET_UUID]).toPromise();
this._appState.dictionaryVersion = result[DEFAULT_RUL_SET_UUID].dictionaryVersion;
this._appState.ruleVersion = result[DEFAULT_RUL_SET_UUID].rulesVersion;
const ruleSetIds = this.ruleSets.map((rs) => rs.ruleSetId);
this._appState.versions = await this._versionsControllerService.getVersions(ruleSetIds).toPromise();
}
}

View File

@ -82,6 +82,7 @@
align-items: center;
justify-content: space-between;
bottom: 40px;
right: 40px;
border-radius: 8px;
padding: 16px 32px 16px 16px;
background-color: $white;

View File

@ -2,8 +2,9 @@
.mat-button-toggle-standalone,
.mat-button-toggle-group {
border-radius: 100px !important;
box-shadow: none;
border-radius: 100px !important;
width: fit-content;
.mat-button-toggle:not(.mat-button-toggle-checked) {
.mat-button-toggle-button {
@ -11,4 +12,9 @@
color: $grey-7;
}
}
.mat-button-toggle:not(:first-of-type):not(:last-of-type) {
border-left: 1px solid $white;
border-right: 1px solid $white;
}
}