Merge remote-tracking branch 'origin/master' into RED-1332

This commit is contained in:
Dan Percic 2021-05-12 11:56:30 +03:00
commit 8a9436b376
32 changed files with 1092 additions and 651 deletions

View File

@ -22,6 +22,10 @@ export class FileStatusWrapper {
}
}
get dossierDictionaryVersion() {
return this.fileStatus.dossierDictionaryVersion;
}
get analysisDuration() {
return this.fileStatus.analysisDuration;
}

View File

@ -2,8 +2,8 @@ import { Component, Injector, OnInit } from '@angular/core';
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { defaultIfEmpty, tap } from 'rxjs/operators';
import { forkJoin } from 'rxjs';
import { catchError, defaultIfEmpty, tap } from 'rxjs/operators';
import { forkJoin, of } from 'rxjs';
import { PermissionsService } from '@services/permissions.service';
import { ActivatedRoute } from '@angular/router';
import { AdminDialogService } from '../../services/admin-dialog.service';
@ -61,9 +61,14 @@ export class DictionaryListingScreenComponent extends BaseListingComponent<TypeV
.filter((d) => !d.virtual);
this.displayedEntities = [...this.allEntities];
const dataObs = this.allEntities.map((dict) =>
this._dictionaryControllerService.getDictionaryForType(dict.type, this._appStateService.activeRuleSetId).pipe(
this._dictionaryControllerService.getDictionaryForType(this._appStateService.activeRuleSetId, dict.type).pipe(
tap((values) => {
dict.entries = values.entries ? values.entries : [];
}),
catchError(() => {
console.log('error');
dict.entries = [];
return of({});
})
)
);

View File

@ -55,86 +55,11 @@
<redaction-side-nav type="project-templates"></redaction-side-nav>
<div class="content-container">
<div class="actions-bar">
<div class="red-input-group w-450 mr-32">
<input
#inputElement
(keyup)="searchChanged(searchText)"
[(ngModel)]="searchText"
[class.with-matches]="searchText.length > 0"
placeholder="{{ 'dictionary-overview.search' | translate }}"
type="text"
/>
<div class="input-icons">
<div *ngIf="searchText.length === 0" class="no-input">
<mat-icon svgIcon="red:search"></mat-icon>
</div>
<div *ngIf="searchText.length > 0" class="with-input">
<div class="search-match-text">
{{ currentMatch + '/' + searchPositions.length }}
</div>
<mat-icon (click)="previousSearchMatch()" class="pointer" svgIcon="red:arrow-up"></mat-icon>
<mat-icon (click)="nextSearchMatch()" class="pointer" svgIcon="red:arrow-down"></mat-icon>
<mat-icon (click)="searchChanged(''); inputElement.focus()" class="pointer" svgIcon="red:close"></mat-icon>
</div>
</div>
</div>
<form [formGroup]="compareForm" class="compare-form">
<div class="red-input-group mr-16">
<mat-checkbox color="primary" formControlName="active"> {{ 'dictionary-overview.compare.compare' | translate }} </mat-checkbox>
</div>
<div class="red-input-group w-200 mr-8">
<mat-select formControlName="ruleSet">
<mat-option *ngFor="let ruleSet of ruleSets" [value]="ruleSet">
{{ ruleSet === selectRuleSet ? (ruleSet.name | translate) : ruleSet.name }}
</mat-option>
</mat-select>
</div>
<div class="red-input-group w-200">
<mat-select formControlName="dictionary">
<mat-option *ngFor="let dictionary of dictionaries" [value]="dictionary">
{{ dictionary === selectDictionary ? (dictionary.label | translate) : dictionary.label }}
</mat-option>
</mat-select>
</div>
</form>
</div>
<div class="editor-container">
<ace-editor
#editorComponent
(textChanged)="textChanged($event)"
[autoUpdateContent]="true"
[mode]="'text'"
[options]="aceOptions"
[readOnly]="!permissionsService.isAdmin()"
[theme]="'eclipse'"
class="ace-redaction"
>
</ace-editor>
<div *ngIf="compareForm.get('active').value && compareForm.get('dictionary').value === selectDictionary" class="no-dictionary-selected">
<mat-icon svgIcon="red:dictionary"></mat-icon>
<span class="heading-l" translate="dictionary-overview.select-dictionary"></span>
</div>
<ace-editor
#compareEditorComponent
*ngIf="compareForm.get('active').value && compareForm.get('dictionary').value !== selectDictionary"
[mode]="'text'"
[options]="aceOptions"
[readOnly]="true"
[theme]="'eclipse'"
class="ace-redaction"
>
</ace-editor>
</div>
<div *ngIf="hasChanges && permissionsService.isAdmin()" [class.offset]="compareForm.get('active').value" class="changes-box">
<redaction-icon-button (action)="saveEntries()" icon="red:check" text="dictionary-overview.save-changes" type="primary"></redaction-icon-button>
<div (click)="revert()" class="all-caps-label cancel" translate="dictionary-overview.revert-changes"></div>
</div>
</div>
<redaction-dictionary-manager
[initialDictionaryEntries]="entries"
(saveDictionary)="saveEntries($event)"
#dictionaryManager
></redaction-dictionary-manager>
<div class="right-container">
<div class="dictionary-header">
@ -146,7 +71,7 @@
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:entries"></mat-icon>
{{ initialDictionaryEntries?.length }}
{{ dictionaryManager.initialDictionaryEntries?.length }}
</div>
</div>
<div class="small-label stats-subtitle">

View File

@ -1,74 +1,6 @@
@import '../../../../../assets/styles/red-variables';
@import '../../../../../assets/styles/red-mixins';
.editor-container {
height: calc(100% - 50px);
display: flex;
> *:not(:first-child:last-child) {
width: 50%;
}
> *:not(:last-child) {
border-radius: 8px 0 0 8px;
}
> *:not(:first-child) {
border-radius: 0 8px 8px 0;
border-left: none;
}
}
.content-container {
padding: 15px;
.actions-bar {
display: flex;
align-items: center;
margin-bottom: 16px;
.mr-32 {
margin-right: 32px;
}
.mr-16 {
margin-right: 16px;
//opacity: 0; // TODO: Hidden for now
}
.red-input-group {
input {
padding-right: 32px;
&.with-matches {
padding-right: 108px;
}
}
.input-icons {
position: absolute;
right: 12px;
top: 8px;
color: $grey-1;
.with-input {
display: flex;
justify-content: center;
align-items: center;
.search-match-text {
}
}
mat-icon {
max-width: 14px;
margin-left: 8px;
}
}
}
}
}
.right-container {
width: 353px;
min-width: 353px;
@ -96,36 +28,3 @@
margin-right: 8px;
}
}
.compare-form {
display: flex;
flex: 1;
justify-content: flex-end;
align-items: center;
.red-input-group {
margin-top: 0;
}
}
.no-dictionary-selected {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 0 100px;
box-sizing: border-box;
text-align: center;
border: 1px solid $grey-5;
> mat-icon {
height: 60px;
width: 60px;
opacity: 0.1;
margin-bottom: 24px;
}
.heading-l {
color: $grey-7;
}
}

View File

@ -1,20 +1,16 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { DictionaryControllerService, RuleSetModel, TypeValue } from '@redaction/red-ui-http';
import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { PermissionsService } from '@services/permissions.service';
import { ActivatedRoute, Router } from '@angular/router';
import { AceEditorComponent } from 'ng2-ace-editor';
import { debounce } from '@utils/debounce';
import { NotificationService, NotificationType } from '@services/notification.service';
import { NotificationService } from '@services/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { saveAs } from 'file-saver';
import { ComponentHasChanges } from '@guards/can-deactivate.guard';
import { FormBuilder, FormGroup } from '@angular/forms';
import { FormBuilder } from '@angular/forms';
import { AdminDialogService } from '../../services/admin-dialog.service';
declare let ace;
const MIN_WORD_LENGTH = 2;
import { DictionaryManagerComponent } from '../../../shared/components/dictionary-manager/dictionary-manager.component';
import { DictionarySaveService } from '../../../shared/services/dictionary-save.service';
@Component({
selector: 'redaction-dictionary-overview-screen',
@ -22,32 +18,17 @@ const MIN_WORD_LENGTH = 2;
styleUrls: ['./dictionary-overview-screen.component.scss']
})
export class DictionaryOverviewScreenComponent extends ComponentHasChanges implements OnInit {
activeEditMarkers: any[] = [];
activeSearchMarkers: any[] = [];
searchPositions: any[] = [];
currentMatch = 1;
initialDictionaryEntries: string[] = [];
currentDictionaryEntries: string[] = [];
compareDictionaryEntries: string[] = [];
changedLines: number[] = [];
aceOptions = { showPrintMargin: false };
searchText = '';
processing = true;
processing = false;
entries: string[] = [];
selectRuleSet = { name: 'dictionary-overview.compare.select-ruleset' };
selectDictionary = { label: 'dictionary-overview.compare.select-dictionary' };
ruleSets: RuleSetModel[];
dictionaries: TypeValue[] = [this.selectDictionary];
compareForm: FormGroup;
@ViewChild('editorComponent', { static: true }) private _editorComponent: AceEditorComponent;
@ViewChild('compareEditorComponent') private _compareEditorComponent: AceEditorComponent;
@ViewChild('dictionaryManager', { static: false }) private _dictionaryManager: DictionaryManagerComponent;
@ViewChild('fileInput') private _fileInput: ElementRef;
constructor(
readonly permissionsService: PermissionsService,
private readonly _notificationService: NotificationService,
protected readonly _translateService: TranslateService,
private readonly _dictionarySaveService: DictionarySaveService,
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _dialogService: AdminDialogService,
private readonly _router: Router,
@ -57,30 +38,10 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
) {
super(_translateService);
this._appStateService.activateDictionary(this._activatedRoute.snapshot.params.type, this._activatedRoute.snapshot.params.ruleSetId);
}
this.compareForm = this._formBuilder.group({
active: [false],
ruleSet: [{ value: this.selectRuleSet, disabled: true }],
dictionary: [{ value: this.selectDictionary, disabled: true }]
});
this.compareForm.valueChanges.subscribe((value) => {
this._setFieldStatus('ruleSet', value.active);
this._setFieldStatus('dictionary', value.active && this.compareForm.get('ruleSet').value !== this.selectRuleSet);
this._loadDictionaries();
});
this.ruleSets = [this.selectRuleSet, ...this._appStateService.ruleSets];
this._initializeEditor();
this.compareForm.controls.ruleSet.valueChanges.subscribe(() => {
this._onRuleSetChanged();
});
this.compareForm.controls.dictionary.valueChanges.subscribe((dictionary) => {
this._onDictionaryChanged(dictionary);
});
ngOnInit(): void {
this._loadEntries();
}
get dictionary(): TypeValue {
@ -88,27 +49,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
}
get hasChanges() {
return (
this.currentDictionaryEntries.length &&
(this.activeEditMarkers.length > 0 ||
this.currentDictionaryEntries.filter((e) => e && e.trim().length > 0).length < this.initialDictionaryEntries.length)
);
}
private get _activeRow(): number {
return this._editorComponent.getEditor().selection.getCursor().row + 1;
}
private static _setEditorValue(editor: AceEditorComponent, entries: string[]) {
const dictionaryEntriesAsText = entries.join('\n');
editor.getEditor().setValue(dictionaryEntriesAsText);
editor.getEditor().gotoLine(1);
}
ngOnInit(): void {
this._editorComponent.getEditor().selection.on('changeCursor', () => {
this._syncActiveLines();
});
return this._dictionaryManager.hasChanges;
}
openEditDictionaryDialog($event: any) {
@ -125,113 +66,8 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
});
}
@debounce()
searchChanged(text: string) {
this.searchText = text.toLowerCase();
this._applySearchMarkers();
this.currentMatch = 0;
this.nextSearchMatch();
}
@debounce(500)
textChanged($event: any) {
this._applySearchMarkers();
this.currentDictionaryEntries = $event.split('\n');
this.changedLines = [];
this.activeEditMarkers.forEach((am) => {
this._editorComponent.getEditor().getSession().removeMarker(am);
});
this.activeEditMarkers = [];
for (let i = 0; i < this.currentDictionaryEntries.length; i++) {
const currentEntry = this.currentDictionaryEntries[i];
if (this.initialDictionaryEntries.indexOf(currentEntry) < 0) {
this.changedLines.push(i);
}
}
const range = ace.require('ace/range').Range;
for (const i of this.changedLines) {
const entry = this.currentDictionaryEntries[i];
if (entry?.trim().length > 0) {
// only mark non-empty lines
this.activeEditMarkers.push(this._editorComponent.getEditor().getSession().addMarker(new range(i, 0, i, 1), 'changed-row-marker', 'fullLine'));
}
if (entry?.trim().length > 0 && entry.trim().length < MIN_WORD_LENGTH) {
// show lines that are too short
this.activeEditMarkers.push(this._editorComponent.getEditor().getSession().addMarker(new range(i, 0, i, 1), 'too-short-marker', 'fullLine'));
}
}
}
async saveEntries() {
let entriesToAdd = [];
this.currentDictionaryEntries.forEach((currentEntry) => {
entriesToAdd.push(currentEntry);
});
// remove empty lines
entriesToAdd = entriesToAdd.filter((e) => e && e.trim().length > 0).map((e) => e.trim());
const invalidRowsExist = entriesToAdd.filter((e) => e.length < MIN_WORD_LENGTH);
if (invalidRowsExist.length === 0) {
// can add at least 1 - block UI
this.processing = true;
let obs: Observable<any>;
if (entriesToAdd.length > 0) {
obs = this._dictionaryControllerService.addEntry(entriesToAdd, this.dictionary.type, this.dictionary.ruleSetId, true);
} else {
obs = this._dictionaryControllerService.deleteEntries(this.initialDictionaryEntries, this.dictionary.type, this.dictionary.ruleSetId);
}
obs.subscribe(
() => {
this._initializeEditor();
this._notificationService.showToastNotification(
this._translateService.instant('dictionary-overview.success.generic'),
null,
NotificationType.SUCCESS
);
},
() => {
this.processing = false;
this._notificationService.showToastNotification(
this._translateService.instant('dictionary-overview.error.generic'),
null,
NotificationType.ERROR
);
}
);
} else {
this._notificationService.showToastNotification(
this._translateService.instant('dictionary-overview.error.entries-too-short'),
null,
NotificationType.ERROR
);
}
}
revert() {
DictionaryOverviewScreenComponent._setEditorValue(this._editorComponent, this.initialDictionaryEntries);
this.searchChanged('');
this.processing = false;
}
nextSearchMatch() {
// length = 3
if (this.searchPositions.length > 0) {
this.currentMatch = this.currentMatch < this.searchPositions.length ? this.currentMatch + 1 : 1;
this._gotoLine();
}
}
previousSearchMatch() {
if (this.searchPositions.length > 0) {
this.currentMatch = this.currentMatch > 1 ? this.currentMatch - 1 : this.searchPositions.length;
this._gotoLine();
}
}
download(): void {
const content = this._editorComponent.getEditor().getValue();
const content = this._dictionaryManager.editorValue;
const blob = new Blob([content], {
type: 'text/plain;charset=utf-8'
});
@ -244,104 +80,37 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
if (file) {
fileReader.onload = () => {
this._editorComponent.getEditor().setValue(fileReader.result);
this._dictionaryManager.editorValue = fileReader.result;
this._fileInput.nativeElement.value = null;
};
fileReader.readAsText(file);
}
}
private _syncActiveLines() {
if (this._compareEditorComponent) {
this._compareEditorComponent.getEditor().gotoLine(this._activeRow);
}
saveEntries(entries: string[]) {
this.processing = true;
this._dictionarySaveService.saveEntries(entries, this.entries, this.dictionary.ruleSetId, this.dictionary.type, null).subscribe(
() => {
this.processing = false;
this._loadEntries();
},
() => {
this.processing = false;
}
);
}
private _onRuleSetChanged() {
this._loadDictionaries();
this.compareForm.patchValue({ dictionary: this.selectDictionary });
}
private _onDictionaryChanged(dictionary: TypeValue) {
if (dictionary !== this.selectDictionary) {
this._dictionaryControllerService.getDictionaryForType(dictionary.type, dictionary.ruleSetId).subscribe(
(data) => {
this.compareDictionaryEntries = data.entries.sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' }));
DictionaryOverviewScreenComponent._setEditorValue(this._compareEditorComponent, this.compareDictionaryEntries);
this._syncActiveLines();
},
() => {
this.processing = false;
}
);
}
}
private _setFieldStatus(field: 'ruleSet' | 'dictionary', enabled: boolean) {
this.compareForm.get(field)[enabled ? 'enable' : 'disable']({ emitEvent: false });
}
private _loadDictionaries() {
const ruleSetId = this.compareForm.get('ruleSet').value.ruleSetId;
if (!ruleSetId) {
this.dictionaries = [this.selectDictionary];
return;
}
const appStateDictionaryData = this._appStateService.dictionaryData[ruleSetId];
this.dictionaries = [
this.selectDictionary,
...Object.keys(appStateDictionaryData)
.map((key) => appStateDictionaryData[key])
.filter((d) => !d.virtual || d.type === 'false_positive')
];
}
private _initializeEditor() {
if (this.dictionary.type)
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();
},
() => {
this.processing = false;
}
);
}
private _applySearchMarkers() {
this.searchPositions = this._getSearchPositions();
this.activeSearchMarkers.forEach((am) => {
this._editorComponent.getEditor().getSession().removeMarker(am);
});
this.activeSearchMarkers = [];
const range = ace.require('ace/range').Range;
for (const position of this.searchPositions) {
this.activeSearchMarkers.push(
this._editorComponent
.getEditor()
.getSession()
.addMarker(new range(position.row, position.column, position.row, position.column + position.length), 'search-marker', 'text')
);
}
}
private _getSearchPositions() {
const lowerCaseSearchText = this.searchText.toLowerCase();
return this.currentDictionaryEntries
.map((val, index) => {
const columnIndex = val.toLowerCase().indexOf(lowerCaseSearchText);
if (columnIndex >= 0) {
return { row: index, column: columnIndex, length: lowerCaseSearchText.length };
}
})
.filter((entry) => !!entry);
}
private _gotoLine() {
const position = this.searchPositions[this.currentMatch - 1];
this._editorComponent.getEditor().scrollToLine(position.row, true, true, () => {});
this._editorComponent.getEditor().gotoLine(position.row + 1, position.column, true);
private _loadEntries() {
this.processing = true;
this._dictionaryControllerService.getDictionaryForType(this.dictionary.ruleSetId, this.dictionary.type).subscribe(
(data) => {
this.processing = false;
this.entries = data.entries.sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' }));
},
() => {
this.processing = false;
this.entries = [];
}
);
}
}

View File

@ -91,6 +91,10 @@
<mat-icon svgIcon="red:template"></mat-icon>
<span>{{ appStateService.getRuleSetById(appStateService.activeProject.ruleSetId)?.name }} </span>
</div>
<div *ngIf="appStateService.activeProject.type" class="pointer" (click)="openDossierDictionaryDialog.emit()">
<mat-icon svgIcon="red:dictionary"></mat-icon>
<span>{{ 'project-overview.project-details.dictionary' | translate }} </span>
</div>
</div>
<div *ngIf="!!appStateService.activeProject.project.description" class="pb-32">

View File

@ -19,6 +19,7 @@ export class ProjectDetailsComponent implements OnInit {
@Input() filters: { needsWorkFilters: FilterModel[]; statusFilters: FilterModel[] };
@Output() filtersChanged = new EventEmitter();
@Output() openAssignProjectMembersDialog = new EventEmitter();
@Output() openDossierDictionaryDialog = new EventEmitter();
@Output() toggleCollapse = new EventEmitter();
constructor(

View File

@ -56,14 +56,18 @@
></redaction-search-input>
<div class="members-list">
<div (click)="toggleSelected(userId)" *ngFor="let userId of multiUsersSelectOptions" [class.selected]="isMemberSelected(userId)">
<div
(click)="!isOwner(userId) && toggleSelected(userId)"
*ngFor="let userId of multiUsersSelectOptions"
[class.selected]="isMemberSelected(userId)"
>
<redaction-initials-avatar [userId]="userId" [withName]="true" size="large"></redaction-initials-avatar>
<div class="actions">
<div *ngIf="!isOwner(userId)" (click)="toggleApprover(userId, $event)" class="make-approver">
<redaction-round-checkbox [active]="isApprover(userId)" class="mr-8"></redaction-round-checkbox>
<span [translate]="'assign-' + data.type + '-owner.dialog.make-approver'"></span>
</div>
<mat-icon *ngIf="!isOwner(userId)" [svgIcon]="'red:' + (isMemberSelected(userId) ? 'close' : 'check')"></mat-icon>
<mat-icon *ngIf="!isOwner(userId)" svgIcon="red:check"></mat-icon>
</div>
</div>
</div>

View File

@ -55,15 +55,9 @@ redaction-team-members {
}
}
&.selected {
.actions {
display: flex;
}
}
&.selected,
&:hover {
background-color: $grey-2;
.actions {
display: flex;
}

View File

@ -61,6 +61,7 @@ export class AssignOwnerDialogComponent {
const searchQuery = this.searchForm.get('query').value;
return this.userService.eligibleUsers
.filter((user) => this.userService.getNameForId(user.userId).toLowerCase().includes(searchQuery.toLowerCase()))
.filter((user) => this.selectedSingleUser !== user.userId)
.map((user) => user.userId);
}

View File

@ -0,0 +1,23 @@
<section class="dialog">
<div [translate]="'dossier-dictionary-dialog.title'" class="dialog-header heading-l"></div>
<form (submit)="saveDossierDictionary()">
<div class="dialog-content">
<redaction-dictionary-manager
#dictionaryManager
[withFloatingActions]="false"
[initialDictionaryEntries]="project.type?.entries"
></redaction-dictionary-manager>
</div>
<div class="dialog-actions">
<button [disabled]="!dictionaryManager.hasChanges" color="primary" mat-flat-button type="submit">
{{ 'dossier-dictionary-dialog.save-changes' | translate }}
</button>
<div [translate]="'dossier-dictionary-dialog.cancel'" class="all-caps-label pointer cancel" mat-dialog-close></div>
</div>
</form>
<redaction-circle-button class="dialog-close" icon="red:close" mat-dialog-close></redaction-circle-button>
</section>

View File

@ -0,0 +1,4 @@
.dialog-content {
height: calc(90vh - 160px);
padding: 12px 12px 0;
}

View File

@ -0,0 +1,38 @@
import { Component, Inject, ViewChild } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ProjectWrapper } from '../../../../state/model/project.wrapper';
import { DictionaryManagerComponent } from '../../../shared/components/dictionary-manager/dictionary-manager.component';
import { DictionarySaveService } from '../../../shared/services/dictionary-save.service';
import { AppStateService } from '../../../../state/app-state.service';
@Component({
selector: 'redaction-dossier-dictionary-dialog',
templateUrl: './dossier-dictionary-dialog.component.html',
styleUrls: ['./dossier-dictionary-dialog.component.scss']
})
export class DossierDictionaryDialogComponent {
@ViewChild('dictionaryManager', { static: false }) private _dictionaryManager: DictionaryManagerComponent;
constructor(
public dialogRef: MatDialogRef<DossierDictionaryDialogComponent>,
@Inject(MAT_DIALOG_DATA) public project: ProjectWrapper,
private readonly _appStateService: AppStateService,
private readonly _dictionarySaveService: DictionarySaveService
) {}
saveDossierDictionary() {
this._dictionarySaveService
.saveEntries(
this._dictionaryManager.currentDictionaryEntries,
this._dictionaryManager.initialDictionaryEntries,
this.project.ruleSetId,
'dossier_redaction',
this.project.projectId
)
.subscribe(async () => {
await this._appStateService.updateProjectDictionaryVersion(this.project);
this._appStateService.updateProjectDictionary(this.project.ruleSetId, this.project.projectId);
this.dialogRef.close();
});
}
}

View File

@ -35,6 +35,7 @@ import { ManualAnnotationService } from './services/manual-annotation.service';
import { AnnotationDrawService } from './services/annotation-draw.service';
import { AnnotationProcessingService } from './services/annotation-processing.service';
import { AnnotationRemoveActionsComponent } from './components/annotation-remove-actions/annotation-remove-actions.component';
import { DossierDictionaryDialogComponent } from './dialogs/dossier-dictionary-dialog/dossier-dictionary-dialog.component';
const screens = [ProjectListingScreenComponent, ProjectOverviewScreenComponent, FilePreviewScreenComponent];
@ -80,7 +81,7 @@ const services = [
];
@NgModule({
declarations: [...components],
declarations: [...components, DossierDictionaryDialogComponent],
providers: [...services],
imports: [CommonModule, SharedModule, FileUploadDownloadModule, ProjectsRoutingModule]
})

View File

@ -38,13 +38,14 @@
tooltip="project-overview.header-actions.edit"
tooltipPosition="below"
></redaction-circle-button>
<redaction-circle-button
(action)="openDeleteProjectDialog($event)"
*ngIf="permissionsService.canDeleteProject()"
icon="red:trash"
tooltip="project-overview.header-actions.delete"
tooltipPosition="below"
></redaction-circle-button>
<!-- Commented because users miss-click this shit !-->
<!-- <redaction-circle-button-->
<!-- (action)="openDeleteProjectDialog($event)"-->
<!-- *ngIf="permissionsService.canDeleteProject()"-->
<!-- icon="red:trash"-->
<!-- tooltip="project-overview.header-actions.delete"-->
<!-- tooltipPosition="below"-->
<!-- ></redaction-circle-button>-->
<redaction-circle-button
(action)="openAssignProjectMembersDialog()"
*ngIf="permissionsService.isManager()"
@ -264,6 +265,7 @@
<redaction-project-details
#projectDetailsComponent
(filtersChanged)="filtersChanged($event)"
(openDossierDictionaryDialog)="openDossierDictionaryDialog()"
(openAssignProjectMembersDialog)="openAssignProjectMembersDialog()"
(toggleCollapse)="toggleCollapsedDetails()"
[filters]="detailsContainerFilters"

View File

@ -238,6 +238,12 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
});
}
openDossierDictionaryDialog() {
this._dialogService.openDossierDictionaryDialog(null, this.activeProject, () => {
this.reloadProjects();
});
}
toggleCollapsedDetails() {
this.collapsedDetails = !this.collapsedDetails;
}

View File

@ -21,6 +21,7 @@ import { ManualAnnotationService } from './manual-annotation.service';
import { TranslateService } from '@ngx-translate/core';
import { ManualAnnotationDialogComponent } from '../dialogs/manual-redaction-dialog/manual-annotation-dialog.component';
import { AssignOwnerDialogComponent } from '../dialogs/assign-owner-dialog/assign-owner-dialog.component';
import { DossierDictionaryDialogComponent } from '../dialogs/dossier-dictionary-dialog/dossier-dictionary-dialog.component';
const dialogConfig = {
width: '662px',
@ -201,6 +202,23 @@ export class ProjectsDialogService {
return ref;
}
openDossierDictionaryDialog($event: MouseEvent, project: ProjectWrapper, cb?: Function): MatDialogRef<DossierDictionaryDialogComponent> {
$event?.stopPropagation();
const ref = this._dialog.open(DossierDictionaryDialogComponent, {
...dialogConfig,
width: '90vw',
height: '90vh',
autoFocus: false,
data: project
});
ref.afterClosed().subscribe((result) => {
if (cb) {
cb(result);
}
});
return ref;
}
openAssignFileReviewerDialog(file: FileStatus, cb?: Function, ignoreDialogChanges = false): MatDialogRef<AssignOwnerDialogComponent> {
const ref = this._dialog.open(AssignOwnerDialogComponent, {
...dialogConfig,

View File

@ -0,0 +1,80 @@
<div class="content-container">
<div class="actions-bar">
<div class="red-input-group w-450 mr-32">
<input
#inputElement
(keyup)="searchChanged(searchText)"
[(ngModel)]="searchText"
[class.with-matches]="searchText.length > 0"
placeholder="{{ 'dictionary-overview.search' | translate }}"
type="text"
/>
<div class="input-icons">
<div *ngIf="searchText.length === 0" class="no-input">
<mat-icon svgIcon="red:search"></mat-icon>
</div>
<div *ngIf="searchText.length > 0" class="with-input">
<div class="search-match-text">
{{ currentMatch + '/' + searchPositions.length }}
</div>
<mat-icon (click)="previousSearchMatch()" class="pointer" svgIcon="red:arrow-up"></mat-icon>
<mat-icon (click)="nextSearchMatch()" class="pointer" svgIcon="red:arrow-down"></mat-icon>
<mat-icon (click)="searchChanged(''); inputElement.focus()" class="pointer" svgIcon="red:close"></mat-icon>
</div>
</div>
</div>
<form [formGroup]="compareForm" class="compare-form">
<div class="red-input-group mr-16">
<mat-checkbox color="primary" formControlName="active"> {{ 'dictionary-overview.compare.compare' | translate }} </mat-checkbox>
</div>
<div class="red-input-group w-200 mr-8">
<mat-select formControlName="ruleSet">
<mat-option *ngFor="let ruleSet of ruleSets" [value]="ruleSet">
{{ ruleSet === selectRuleSet ? (ruleSet.name | translate) : ruleSet.name }}
</mat-option>
</mat-select>
</div>
<div class="red-input-group w-200">
<mat-select formControlName="dictionary">
<mat-option *ngFor="let dictionary of dictionaries" [value]="dictionary">
{{ dictionary === selectDictionary ? (dictionary.label | translate) : dictionary.label }}
</mat-option>
</mat-select>
</div>
</form>
</div>
<div class="editor-container">
<ace-editor
#editorComponent
(textChanged)="textChanged($event)"
[autoUpdateContent]="true"
[mode]="'text'"
[options]="aceOptions"
[readOnly]="!permissionsService.isAdmin()"
[theme]="'eclipse'"
class="ace-redaction"
>
</ace-editor>
<div *ngIf="compareForm.get('active').value && compareForm.get('dictionary').value === selectDictionary" class="no-dictionary-selected">
<mat-icon svgIcon="red:dictionary"></mat-icon>
<span class="heading-l" translate="dictionary-overview.select-dictionary"></span>
</div>
<ace-editor
#compareEditorComponent
*ngIf="compareForm.get('active').value && compareForm.get('dictionary').value !== selectDictionary"
[mode]="'text'"
[options]="aceOptions"
[readOnly]="true"
[theme]="'eclipse'"
class="ace-redaction"
>
</ace-editor>
</div>
<div *ngIf="withFloatingActions && hasChanges && permissionsService.isAdmin()" [class.offset]="compareForm.get('active').value" class="changes-box">
<redaction-icon-button (action)="saveEntries()" icon="red:check" text="dictionary-overview.save-changes" type="primary"></redaction-icon-button>
<div (click)="revert()" class="all-caps-label cancel" translate="dictionary-overview.revert-changes"></div>
</div>
</div>

View File

@ -0,0 +1,110 @@
@import '../../../../../assets/styles/red-variables';
@import '../../../../../assets/styles/red-mixins';
:host {
width: 100%;
height: 100%;
}
.compare-form {
display: flex;
flex: 1;
justify-content: flex-end;
align-items: center;
.red-input-group {
margin-top: 0;
}
}
.editor-container {
height: calc(100% - 50px);
display: flex;
> *:not(:first-child:last-child) {
width: 50%;
}
> *:not(:last-child) {
border-radius: 8px 0 0 8px;
}
> *:not(:first-child) {
border-radius: 0 8px 8px 0;
border-left: none;
}
}
.content-container {
padding: 15px;
height: calc(100% - 30px);
width: calc(100% - 30px);
.actions-bar {
display: flex;
align-items: center;
margin-bottom: 16px;
.mr-32 {
margin-right: 32px;
}
.mr-16 {
margin-right: 16px;
//opacity: 0; // TODO: Hidden for now
}
.red-input-group {
input {
padding-right: 32px;
&.with-matches {
padding-right: 108px;
}
}
.input-icons {
position: absolute;
right: 12px;
top: 8px;
color: $grey-1;
.with-input {
display: flex;
justify-content: center;
align-items: center;
.search-match-text {
}
}
mat-icon {
max-width: 14px;
margin-left: 8px;
}
}
}
}
}
.no-dictionary-selected {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 0 100px;
box-sizing: border-box;
text-align: center;
border: 1px solid $grey-5;
> mat-icon {
height: 60px;
width: 60px;
opacity: 0.1;
margin-bottom: 24px;
}
.heading-l {
color: $grey-7;
}
}

View File

@ -0,0 +1,259 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core';
import { DictionaryControllerService, RuleSetModel, TypeValue } from '@redaction/red-ui-http';
import { FormBuilder, FormGroup } from '@angular/forms';
import { AceEditorComponent } from 'ng2-ace-editor';
import { PermissionsService } from '@services/permissions.service';
import { NotificationService } from '@services/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { ActivatedRoute, Router } from '@angular/router';
import { AppStateService } from '@state/app-state.service';
import { debounce } from '@utils/debounce';
declare let ace;
const MIN_WORD_LENGTH = 2;
@Component({
selector: 'redaction-dictionary-manager',
templateUrl: './dictionary-manager.component.html',
styleUrls: ['./dictionary-manager.component.scss']
})
export class DictionaryManagerComponent implements OnInit, OnChanges {
@Input()
withFloatingActions = true;
@Input()
initialDictionaryEntries: string[];
@Output()
saveDictionary = new EventEmitter<string[]>();
activeEditMarkers: any[] = [];
activeSearchMarkers: any[] = [];
searchPositions: any[] = [];
currentMatch = 1;
currentDictionaryEntries: string[] = [];
compareDictionaryEntries: string[] = [];
changedLines: number[] = [];
aceOptions = { showPrintMargin: false };
searchText = '';
selectRuleSet = { name: 'dictionary-overview.compare.select-ruleset' };
selectDictionary = { label: 'dictionary-overview.compare.select-dictionary' };
ruleSets: RuleSetModel[];
dictionaries: TypeValue[] = [this.selectDictionary];
compareForm: FormGroup;
@ViewChild('editorComponent', { static: true }) private _editorComponent: AceEditorComponent;
@ViewChild('compareEditorComponent') private _compareEditorComponent: AceEditorComponent;
constructor(
readonly permissionsService: PermissionsService,
private readonly _notificationService: NotificationService,
protected readonly _translateService: TranslateService,
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _router: Router,
private readonly _activatedRoute: ActivatedRoute,
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder
) {
this.compareForm = this._formBuilder.group({
active: [false],
ruleSet: [{ value: this.selectRuleSet, disabled: true }],
dictionary: [{ value: this.selectDictionary, disabled: true }]
});
this.compareForm.valueChanges.subscribe((value) => {
this._setFieldStatus('ruleSet', value.active);
this._setFieldStatus('dictionary', value.active && this.compareForm.get('ruleSet').value !== this.selectRuleSet);
this._loadDictionaries();
});
this.ruleSets = [this.selectRuleSet, ...this._appStateService.ruleSets];
this.compareForm.controls.ruleSet.valueChanges.subscribe(() => {
this._onRuleSetChanged();
});
this.compareForm.controls.dictionary.valueChanges.subscribe((dictionary) => {
this._onDictionaryChanged(dictionary);
});
}
private static _setEditorValue(editor: AceEditorComponent, entries: string[]) {
const dictionaryEntriesAsText = entries.join('\n');
editor.getEditor().setValue(dictionaryEntriesAsText);
editor.getEditor().gotoLine(1);
}
get editorValue() {
return this._editorComponent.getEditor().getValue();
}
set editorValue(value: any) {
this._editorComponent.getEditor().setValue(value);
}
ngOnInit(): void {
this._editorComponent.getEditor().selection.on('changeCursor', () => {
this._syncActiveLines();
});
}
revert() {
DictionaryManagerComponent._setEditorValue(this._editorComponent, this.initialDictionaryEntries);
this.searchChanged('');
}
@debounce()
searchChanged(text: string) {
this.searchText = text.toLowerCase();
this._applySearchMarkers();
this.currentMatch = 0;
this.nextSearchMatch();
}
@debounce(500)
textChanged($event: any) {
this._applySearchMarkers();
this.currentDictionaryEntries = $event.split('\n');
this.changedLines = [];
this.activeEditMarkers.forEach((am) => {
this._editorComponent.getEditor().getSession().removeMarker(am);
});
this.activeEditMarkers = [];
for (let i = 0; i < this.currentDictionaryEntries.length; i++) {
const currentEntry = this.currentDictionaryEntries[i];
if (this.initialDictionaryEntries.indexOf(currentEntry) < 0) {
this.changedLines.push(i);
}
}
const range = ace.require('ace/range').Range;
for (const i of this.changedLines) {
const entry = this.currentDictionaryEntries[i];
if (entry?.trim().length > 0) {
// only mark non-empty lines
this.activeEditMarkers.push(this._editorComponent.getEditor().getSession().addMarker(new range(i, 0, i, 1), 'changed-row-marker', 'fullLine'));
}
if (entry?.trim().length > 0 && entry.trim().length < MIN_WORD_LENGTH) {
// show lines that are too short
this.activeEditMarkers.push(this._editorComponent.getEditor().getSession().addMarker(new range(i, 0, i, 1), 'too-short-marker', 'fullLine'));
}
}
}
get hasChanges() {
return (
this.currentDictionaryEntries.length &&
(this.activeEditMarkers.length > 0 ||
this.currentDictionaryEntries.filter((e) => e && e.trim().length > 0).length < this.initialDictionaryEntries.length)
);
}
nextSearchMatch() {
// length = 3
if (this.searchPositions.length > 0) {
this.currentMatch = this.currentMatch < this.searchPositions.length ? this.currentMatch + 1 : 1;
this._gotoLine();
}
}
previousSearchMatch() {
if (this.searchPositions.length > 0) {
this.currentMatch = this.currentMatch > 1 ? this.currentMatch - 1 : this.searchPositions.length;
this._gotoLine();
}
}
private _setFieldStatus(field: 'ruleSet' | 'dictionary', enabled: boolean) {
this.compareForm.get(field)[enabled ? 'enable' : 'disable']({ emitEvent: false });
}
private _loadDictionaries() {
const ruleSetId = this.compareForm.get('ruleSet').value.ruleSetId;
if (!ruleSetId) {
this.dictionaries = [this.selectDictionary];
return;
}
const appStateDictionaryData = this._appStateService.dictionaryData[ruleSetId];
this.dictionaries = [
this.selectDictionary,
...Object.keys(appStateDictionaryData)
.map((key) => appStateDictionaryData[key])
.filter((d) => !d.virtual || d.type === 'false_positive')
];
}
private _applySearchMarkers() {
this.searchPositions = this._getSearchPositions();
this.activeSearchMarkers.forEach((am) => {
this._editorComponent.getEditor().getSession().removeMarker(am);
});
this.activeSearchMarkers = [];
const range = ace.require('ace/range').Range;
for (const position of this.searchPositions) {
this.activeSearchMarkers.push(
this._editorComponent
.getEditor()
.getSession()
.addMarker(new range(position.row, position.column, position.row, position.column + position.length), 'search-marker', 'text')
);
}
}
private _getSearchPositions() {
const lowerCaseSearchText = this.searchText.toLowerCase();
return this.currentDictionaryEntries
.map((val, index) => {
const columnIndex = val.toLowerCase().indexOf(lowerCaseSearchText);
if (columnIndex >= 0) {
return { row: index, column: columnIndex, length: lowerCaseSearchText.length };
}
})
.filter((entry) => !!entry);
}
private _gotoLine() {
const position = this.searchPositions[this.currentMatch - 1];
this._editorComponent.getEditor().scrollToLine(position.row, true, true, () => {});
this._editorComponent.getEditor().gotoLine(position.row + 1, position.column, true);
}
private _onRuleSetChanged() {
this._loadDictionaries();
this.compareForm.patchValue({ dictionary: this.selectDictionary });
}
private _onDictionaryChanged(dictionary: TypeValue) {
if (dictionary !== this.selectDictionary) {
this._dictionaryControllerService.getDictionaryForType(dictionary.ruleSetId, dictionary.type).subscribe(
(data) => {
this.compareDictionaryEntries = data.entries.sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' }));
DictionaryManagerComponent._setEditorValue(this._compareEditorComponent, this.compareDictionaryEntries);
this._syncActiveLines();
},
() => {}
);
}
}
private _syncActiveLines() {
if (this._compareEditorComponent) {
this._compareEditorComponent.getEditor().gotoLine(this._activeRow);
}
}
private get _activeRow(): number {
return this._editorComponent.getEditor().selection.getCursor().row + 1;
}
saveEntries() {
this.saveDictionary.emit(this.currentDictionaryEntries);
}
ngOnChanges(): void {
this.revert();
}
}

View File

@ -0,0 +1,65 @@
import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { NotificationService, NotificationType } from '../../../services/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { DictionaryControllerService } from '@redaction/red-ui-http';
import { tap } from 'rxjs/operators';
const MIN_WORD_LENGTH = 2;
@Injectable({
providedIn: 'root'
})
export class DictionarySaveService {
constructor(
private readonly _notificationService: NotificationService,
private readonly _translateService: TranslateService,
private readonly _dictionaryControllerService: DictionaryControllerService
) {}
saveEntries(entries: string[], initialEntries: string[], ruleSetId: string, type: string, dossierId: string): Observable<any> {
let entriesToAdd = [];
entries.forEach((currentEntry) => {
entriesToAdd.push(currentEntry);
});
// remove empty lines
entriesToAdd = entriesToAdd.filter((e) => e && e.trim().length > 0).map((e) => e.trim());
const invalidRowsExist = entriesToAdd.filter((e) => e.length < MIN_WORD_LENGTH);
if (invalidRowsExist.length === 0) {
// can add at least 1 - block UI
let obs: Observable<any>;
if (entriesToAdd.length > 0) {
obs = this._dictionaryControllerService.addEntry(entriesToAdd, ruleSetId, type, dossierId, true);
} else {
obs = this._dictionaryControllerService.deleteEntries(initialEntries, ruleSetId, type, dossierId);
}
return obs.pipe(
tap(
() => {
this._notificationService.showToastNotification(
this._translateService.instant('dictionary-overview.success.generic'),
null,
NotificationType.SUCCESS
);
},
() => {
this._notificationService.showToastNotification(
this._translateService.instant('dictionary-overview.error.generic'),
null,
NotificationType.ERROR
);
}
)
);
} else {
this._notificationService.showToastNotification(
this._translateService.instant('dictionary-overview.error.entries-too-short'),
null,
NotificationType.ERROR
);
return throwError('Entries to short');
}
}
}

View File

@ -32,6 +32,8 @@ import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/materia
import { MomentDateAdapter } from '@angular/material-moment-adapter';
import { SelectComponent } from './components/select/select.component';
import { NavigateLastProjectsScreenDirective } from './directives/navigate-last-projects-screen.directive';
import { DictionaryManagerComponent } from './components/dictionary-manager/dictionary-manager.component';
import { AceEditorModule } from 'ng2-ace-editor';
const buttons = [ChevronButtonComponent, CircleButtonComponent, FileDownloadBtnComponent, IconButtonComponent, UserButtonComponent];
@ -61,9 +63,9 @@ const utils = [HumanizePipe, SyncWidthDirective, HasScrollbarDirective, Navigate
const modules = [MatConfigModule, TranslateModule, ScrollingModule, IconsModule, FormsModule, ReactiveFormsModule];
@NgModule({
declarations: [...components, ...utils],
imports: [CommonModule, ...modules],
exports: [...modules, ...components, ...utils],
declarations: [...components, ...utils, DictionaryManagerComponent],
imports: [CommonModule, ...modules, AceEditorModule],
exports: [...modules, ...components, ...utils, DictionaryManagerComponent],
providers: [
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
{

View File

@ -43,12 +43,9 @@ 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(project.ruleSetId) ||
fileStatus.rulesVersion !== this._appStateService.rulesVersion(project.ruleSetId) ||
fileStatus.hasUnappliedSuggestions)) ||
(!this._appStateService.fileIsUpToDateWithLatestVersions(fileStatus) || fileStatus.hasUnappliedSuggestions)) ||
fileStatus.isError
);
}
@ -137,7 +134,7 @@ export class PermissionsService {
if (!fileStatus) {
return false;
}
return !fileStatus.hasRequests && !fileStatus.hasUnappliedSuggestions && this._appStateService.isFileUpToDateWithDictionaryAndRules(fileStatus);
return !fileStatus.hasRequests && !fileStatus.hasUnappliedSuggestions && this._appStateService.fileIsUpToDateWithLatestVersions(fileStatus);
}
canSetUnderApproval(fileStatus?: FileStatusWrapper) {

View File

@ -177,24 +177,33 @@ export class AppStateService {
if (this.activeProject?.hasPendingOrProcessing) {
await this.reloadActiveProjectFiles();
await this.updateDictionaryVersion();
await this.updateProjectDictionaryVersion(this.activeProject);
}
}
isFileUpToDateWithDictionaryAndRules(fileStatus?: FileStatusWrapper) {
fileIsUpToDateWithLatestVersions(fileStatus?: FileStatusWrapper) {
if (!fileStatus) {
fileStatus = this.activeFile;
}
return fileStatus.dictionaryVersion === this.dictionaryVersion() && fileStatus.rulesVersion === this.rulesVersion();
const project = this.getProjectById(fileStatus.projectId);
const ruleSetId = project.ruleSetId;
return (
fileStatus.dictionaryVersion === this._dictionaryVersion(ruleSetId) &&
fileStatus.rulesVersion === this._rulesVersion(ruleSetId) &&
(!project.dictionaryVersion || fileStatus.dossierDictionaryVersion === project.dictionaryVersion)
);
}
dictionaryVersion(ruleSetId?: string) {
private _dictionaryVersion(ruleSetId?: string) {
if (!ruleSetId) {
ruleSetId = this.activeProject.ruleSetId;
}
return this._appState.versions[ruleSetId].dictionaryVersion;
}
rulesVersion(ruleSetId?: string) {
private _rulesVersion(ruleSetId?: string) {
if (!ruleSetId) {
ruleSetId = this.activeProject.ruleSetId;
}
@ -270,6 +279,8 @@ export class AppStateService {
this._appState.projects = mappedProjects;
this._computeStats();
this.updateProjectDictionaryVersions();
}
}
@ -320,9 +331,23 @@ export class AppStateService {
this._appState.activeProjectId = null;
this._router.navigate(['/main/projects']);
return;
} else {
this.updateProjectDictionary(this.activeProject.ruleSetId, projectId);
}
}
updateProjectDictionary(ruleSetId: string, projectId: string) {
// project exists, load it's dictionary
this._dictionaryControllerService.getDictionaryForType(ruleSetId, 'dossier_redaction', projectId).subscribe(
(typeData) => {
this.activeProject.type = typeData;
},
() => {
this.activeProject.type = null;
}
);
}
activateFile(projectId: string, fileId: string) {
if (this._appState.activeProjectId === projectId && this._appState.activeFileId === fileId) return;
this.activateProject(projectId);
@ -597,6 +622,17 @@ export class AppStateService {
this._appState.versions = await this._versionsControllerService.getVersions(ruleSetIds).toPromise();
}
updateProjectDictionaryVersions() {
for (const project of this.allProjects) {
// don't wait / block
this.updateProjectDictionaryVersion(project);
}
}
async updateProjectDictionaryVersion(project: ProjectWrapper) {
project.dictionaryVersion = await this._versionsControllerService.getDossierDictionaryVersion(project.projectId, project.ruleSetId).toPromise();
}
private _getExistingFiles(projectId: string) {
const found = this._appState.projects.find((p) => p.project.projectId === projectId);
return found ? found.files : [];

View File

@ -1,6 +1,6 @@
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import * as moment from 'moment';
import { Project } from '@redaction/red-ui-http';
import { Dictionary, Project } from '@redaction/red-ui-http';
export class ProjectWrapper {
totalNumberOfPages?: number;
@ -12,6 +12,8 @@ export class ProjectWrapper {
hasPendingOrProcessing?: boolean;
allFilesApproved?: boolean;
type: Dictionary;
dictionaryVersion?: number;
constructor(public project: Project, files: FileStatusWrapper[]) {
this._files = files ? files : [];

View File

@ -185,6 +185,11 @@
"expand": "Show Details",
"collapse": "Hide Details"
},
"dossier-dictionary-dialog": {
"title": "Dossier Dictionary",
"save-changes": "Save Changes",
"cancel": "cancel"
},
"project-overview": {
"no-ocr": "No OCR",
"ocr-performed": "OCR was performed for this file.",
@ -272,6 +277,7 @@
"created-on": "Created on {{date}}",
"due-date": "Due {{date}}"
},
"dictionary": "Dossier Dictionary",
"description": "Description"
},
"header": "Dossier Overview",

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="100px" version="1.1" viewBox="0 0 100 100" width="100px" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd" id="Symbols" stroke="none" stroke-width="1">
<g fill="#283241" fill-rule="nonzero" id="OCR">
<g fill="currentColor" fill-rule="nonzero" id="OCR">
<path
d="M10,70 L10,90 L30,90 L30,100 L0,100 L0,70 L10,70 Z M100,70 L100,100 L70,100 L70,90 L90,90 L90,70 L100,70 Z M54,63 L54,73 L24,73 L24,63 L54,63 Z M76,45 L76,55 L24,55 L24,45 L76,45 Z M76,27 L76,37 L24,37 L24,27 L76,27 Z M30,0 L30,10 L10,10 L10,30 L0,30 L0,0 L30,0 Z M100,0 L100,30 L90,30 L90,10 L70,10 L70,0 L100,0 Z"></path>
</g>

Before

Width:  |  Height:  |  Size: 675 B

After

Width:  |  Height:  |  Size: 680 B

View File

@ -86,6 +86,7 @@ public class PlanSpec {
new VcsTagTask().tagName("${bamboo.inject.APP_VERSION}").repository("RED / ui")
).dockerConfiguration(
new DockerConfiguration().image("nexus.iqser.com:5001/infra/release_build:2.9.1")
.volume("/var/lib/docker", "/var/lib/docker")
.volume("/var/run/docker.sock", "/var/run/docker.sock"))
.artifacts(new Artifact("version").location(".").copyPattern("**/version.properties").shared(true));
}

View File

@ -8,7 +8,7 @@
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/ /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/member-ordering */
*/ /* tslint:disable:no-unused-variable member-ordering */
import { Inject, Injectable, Optional } from '@angular/core';
import { HttpClient, HttpEvent, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http';
@ -27,9 +27,9 @@ import { Configuration } from '../configuration';
@Injectable()
export class DictionaryControllerService {
protected basePath = '';
public defaultHeaders = new HttpHeaders();
public configuration = new Configuration();
protected basePath = '';
constructor(protected httpClient: HttpClient, @Optional() @Inject(BASE_PATH) basePath: string, @Optional() configuration: Configuration) {
if (basePath) {
@ -41,40 +41,63 @@ export class DictionaryControllerService {
}
}
/**
* @param consumes string[] mime-types
* @return true: consumes contains 'multipart/form-data', false: otherwise
*/
private canConsumeForm(consumes: string[]): boolean {
const form = 'multipart/form-data';
for (const consume of consumes) {
if (form === consume) {
return true;
}
}
return false;
}
/**
* Add dictionary entries with entry type.
* None
* @param body entries
* @param type type
* @param ruleSetId ruleSetId
* @param type type
* @param dossierId dossierId
* @param removeCurrent removeCurrent
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public addEntry(body: Array<string>, type: string, ruleSetId: string, removeCurrent?: boolean, observe?: 'body', reportProgress?: boolean): Observable<any>;
public addEntry(
body: Array<string>,
type: string,
ruleSetId: string,
type: string,
dossierId?: string,
removeCurrent?: boolean,
observe?: 'body',
reportProgress?: boolean
): Observable<any>;
public addEntry(
body: Array<string>,
ruleSetId: string,
type: string,
dossierId?: string,
removeCurrent?: boolean,
observe?: 'response',
reportProgress?: boolean
): Observable<HttpResponse<any>>;
public addEntry(
body: Array<string>,
type: string,
ruleSetId: string,
type: string,
dossierId?: string,
removeCurrent?: boolean,
observe?: 'events',
reportProgress?: boolean
): Observable<HttpEvent<any>>;
public addEntry(
body: Array<string>,
type: string,
ruleSetId: string,
type: string,
dossierId?: string,
removeCurrent?: boolean,
observe: any = 'body',
reportProgress: boolean = false
@ -83,15 +106,18 @@ export class DictionaryControllerService {
throw new Error('Required parameter body was null or undefined when calling addEntry.');
}
if (type === null || type === undefined) {
throw new Error('Required parameter type was null or undefined when calling addEntry.');
}
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling addEntry.');
}
if (type === null || type === undefined) {
throw new Error('Required parameter type was null or undefined when calling addEntry.');
}
let queryParameters = new HttpParams({ encoder: new CustomHttpUrlEncodingCodec() });
if (dossierId !== undefined && dossierId !== null) {
queryParameters = queryParameters.set('dossierId', <any>dossierId);
}
if (removeCurrent !== undefined && removeCurrent !== null) {
queryParameters = queryParameters.set('removeCurrent', <any>removeCurrent);
}
@ -136,21 +162,23 @@ export class DictionaryControllerService {
* Creates entry type with colors, hint and caseInsensitive
* None
* @param body typeValue
* @param ruleSetId ruleSetId
* @param dossierId dossierId
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public addType(body: TypeValue, observe?: 'body', reportProgress?: boolean): Observable<any>;
public addType(body: TypeValue, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public addType(body: TypeValue, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public addType(body: TypeValue, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
public addType(body: TypeValue, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public addType(body: TypeValue, dossierId?: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public addType(body: TypeValue, dossierId?: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public addType(body: TypeValue, dossierId?: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (body === null || body === undefined) {
throw new Error('Required parameter body was null or undefined when calling addType.');
}
let queryParameters = new HttpParams({ encoder: new CustomHttpUrlEncodingCodec() });
if (dossierId !== undefined && dossierId !== null) {
queryParameters = queryParameters.set('dossierId', <any>dossierId);
}
let headers = this.defaultHeaders;
// authentication (RED-OAUTH) required
@ -175,6 +203,7 @@ export class DictionaryControllerService {
return this.httpClient.request<any>('post', `${this.basePath}/dictionary/type`, {
body: body,
params: queryParameters,
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
@ -186,28 +215,52 @@ export class DictionaryControllerService {
* Delete dictionary entries with entry type.
* None
* @param body entries
* @param type type
* @param ruleSetId ruleSetId
* @param type type
* @param dossierId dossierId
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public deleteEntries(body: Array<string>, type: string, ruleSetId: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public deleteEntries(body: Array<string>, type: string, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public deleteEntries(body: Array<string>, type: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public deleteEntries(body: Array<string>, type: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
public deleteEntries(body: Array<string>, ruleSetId: string, type: string, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public deleteEntries(
body: Array<string>,
ruleSetId: string,
type: string,
dossierId?: string,
observe?: 'response',
reportProgress?: boolean
): Observable<HttpResponse<any>>;
public deleteEntries(
body: Array<string>,
ruleSetId: string,
type: string,
dossierId?: string,
observe?: 'events',
reportProgress?: boolean
): Observable<HttpEvent<any>>;
public deleteEntries(
body: Array<string>,
ruleSetId: string,
type: string,
dossierId?: string,
observe: any = 'body',
reportProgress: boolean = false
): Observable<any> {
if (body === null || body === undefined) {
throw new Error('Required parameter body was null or undefined when calling deleteEntries.');
}
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling deleteEntries.');
}
if (type === null || type === undefined) {
throw new Error('Required parameter type was null or undefined when calling deleteEntries.');
}
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling deleteEntries.');
let queryParameters = new HttpParams({ encoder: new CustomHttpUrlEncodingCodec() });
if (dossierId !== undefined && dossierId !== null) {
queryParameters = queryParameters.set('dossierId', <any>dossierId);
}
let headers = this.defaultHeaders;
@ -237,6 +290,7 @@ export class DictionaryControllerService {
`${this.basePath}/dictionary/delete/${encodeURIComponent(String(type))}/${encodeURIComponent(String(ruleSetId))}`,
{
body: body,
params: queryParameters,
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
@ -248,29 +302,53 @@ export class DictionaryControllerService {
/**
* Delete dictionary entry with entry type.
* None
* @param type type
* @param ruleSetId ruleSetId
* @param entry entry
* @param ruleSetId ruleSetId
* @param type type
* @param dossierId dossierId
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public deleteEntry(type: string, ruleSetId: string, entry: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public deleteEntry(type: string, ruleSetId: string, entry: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public deleteEntry(type: string, ruleSetId: string, entry: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public deleteEntry(type: string, ruleSetId: string, entry: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (type === null || type === undefined) {
throw new Error('Required parameter type was null or undefined when calling deleteEntry.');
public deleteEntry(entry: string, ruleSetId: string, type: string, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public deleteEntry(
entry: string,
ruleSetId: string,
type: string,
dossierId?: string,
observe?: 'response',
reportProgress?: boolean
): Observable<HttpResponse<any>>;
public deleteEntry(
entry: string,
ruleSetId: string,
type: string,
dossierId?: string,
observe?: 'events',
reportProgress?: boolean
): Observable<HttpEvent<any>>;
public deleteEntry(
entry: string,
ruleSetId: string,
type: string,
dossierId?: string,
observe: any = 'body',
reportProgress: boolean = false
): Observable<any> {
if (entry === null || entry === undefined) {
throw new Error('Required parameter entry was null or undefined when calling deleteEntry.');
}
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling deleteEntry.');
}
if (entry === null || entry === undefined) {
throw new Error('Required parameter entry was null or undefined when calling deleteEntry.');
if (type === null || type === undefined) {
throw new Error('Required parameter type was null or undefined when calling deleteEntry.');
}
let queryParameters = new HttpParams({ encoder: new CustomHttpUrlEncodingCodec() });
if (dossierId !== undefined && dossierId !== null) {
queryParameters = queryParameters.set('dossierId', <any>dossierId);
}
let headers = this.defaultHeaders;
@ -288,13 +366,11 @@ export class DictionaryControllerService {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
// to determine the Content-Type header
const consumes: string[] = [];
return this.httpClient.request<any>(
'delete',
`${this.basePath}/dictionary/${encodeURIComponent(String(type))}/${encodeURIComponent(String(ruleSetId))}/${encodeURIComponent(String(entry))}`,
{
params: queryParameters,
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
@ -306,24 +382,27 @@ export class DictionaryControllerService {
/**
* Deletes entry type
* None
* @param type type
* @param ruleSetId ruleSetId
* @param type type
* @param dossierId dossierId
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public deleteType(type: string, ruleSetId: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public deleteType(ruleSetId: string, type: string, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public deleteType(ruleSetId: string, type: string, dossierId?: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public deleteType(ruleSetId: string, type: string, dossierId?: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public deleteType(ruleSetId: string, type: string, dossierId?: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling deleteType.');
}
public deleteType(type: string, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public deleteType(type: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public deleteType(type: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (type === null || type === undefined) {
throw new Error('Required parameter type was null or undefined when calling deleteType.');
}
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling deleteType.');
let queryParameters = new HttpParams({ encoder: new CustomHttpUrlEncodingCodec() });
if (dossierId !== undefined && dossierId !== null) {
queryParameters = queryParameters.set('dossierId', <any>dossierId);
}
let headers = this.defaultHeaders;
@ -341,13 +420,11 @@ export class DictionaryControllerService {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
// to determine the Content-Type header
const consumes: string[] = [];
return this.httpClient.request<any>(
'delete',
`${this.basePath}/dictionary/type/${encodeURIComponent(String(type))}/${encodeURIComponent(String(ruleSetId))}`,
{
params: queryParameters,
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
@ -359,24 +436,45 @@ export class DictionaryControllerService {
/**
* Returns file containing the the dictionary entries for given type..
*
* @param type type
* @param ruleSetId ruleSetId
* @param type type
* @param dossierId dossierId
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public downloadDictionaryFile(type: string, ruleSetId: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public downloadDictionaryFile(ruleSetId: string, type: string, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public downloadDictionaryFile(
ruleSetId: string,
type: string,
dossierId?: string,
observe?: 'response',
reportProgress?: boolean
): Observable<HttpResponse<any>>;
public downloadDictionaryFile(
ruleSetId: string,
type: string,
dossierId?: string,
observe?: 'events',
reportProgress?: boolean
): Observable<HttpEvent<any>>;
public downloadDictionaryFile(
ruleSetId: string,
type: string,
dossierId?: string,
observe: any = 'body',
reportProgress: boolean = false
): Observable<any> {
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling downloadDictionaryFile.');
}
public downloadDictionaryFile(type: string, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public downloadDictionaryFile(type: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public downloadDictionaryFile(type: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (type === null || type === undefined) {
throw new Error('Required parameter type was null or undefined when calling downloadDictionaryFile.');
}
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling downloadDictionaryFile.');
let queryParameters = new HttpParams({ encoder: new CustomHttpUrlEncodingCodec() });
if (dossierId !== undefined && dossierId !== null) {
queryParameters = queryParameters.set('dossierId', <any>dossierId);
}
let headers = this.defaultHeaders;
@ -394,13 +492,11 @@ export class DictionaryControllerService {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
// to determine the Content-Type header
const consumes: string[] = [];
return this.httpClient.request<any>(
'get',
`${this.basePath}/dictionary/download/${encodeURIComponent(String(type))}/${encodeURIComponent(String(ruleSetId))}`,
{
params: queryParameters,
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
@ -413,20 +509,23 @@ export class DictionaryControllerService {
* Retrieve all entry types
* None
* @param ruleSetId ruleSetId
* @param dossierId dossierId
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public getAllTypes(ruleSetId: string, observe?: 'body', reportProgress?: boolean): Observable<TypeResponse>;
public getAllTypes(ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<TypeResponse>>;
public getAllTypes(ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<TypeResponse>>;
public getAllTypes(ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
public getAllTypes(ruleSetId: string, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable<TypeResponse>;
public getAllTypes(ruleSetId: string, dossierId?: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<TypeResponse>>;
public getAllTypes(ruleSetId: string, dossierId?: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<TypeResponse>>;
public getAllTypes(ruleSetId: string, dossierId?: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling getAllTypes.');
}
let queryParameters = new HttpParams({ encoder: new CustomHttpUrlEncodingCodec() });
if (dossierId !== undefined && dossierId !== null) {
queryParameters = queryParameters.set('dossierId', <any>dossierId);
}
let headers = this.defaultHeaders;
// authentication (RED-OAUTH) required
@ -442,10 +541,8 @@ export class DictionaryControllerService {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
// to determine the Content-Type header
const consumes: string[] = [];
return this.httpClient.request<TypeResponse>('get', `${this.basePath}/dictionary/type/${encodeURIComponent(String(ruleSetId))}`, {
params: queryParameters,
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
@ -461,11 +558,8 @@ export class DictionaryControllerService {
* @param reportProgress flag to report request and response progress.
*/
public getColors(ruleSetId: string, observe?: 'body', reportProgress?: boolean): Observable<Colors>;
public getColors(ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<Colors>>;
public getColors(ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<Colors>>;
public getColors(ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling getColors.');
@ -486,9 +580,6 @@ export class DictionaryControllerService {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
// to determine the Content-Type header
const consumes: string[] = [];
return this.httpClient.request<Colors>('get', `${this.basePath}/color/${encodeURIComponent(String(ruleSetId))}`, {
withCredentials: this.configuration.withCredentials,
headers: headers,
@ -500,24 +591,39 @@ export class DictionaryControllerService {
/**
* Retrieves all dictionary entries of an entry type
* None
* @param type type
* @param ruleSetId ruleSetId
* @param type type
* @param dossierId dossierId
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public getDictionaryForType(type: string, ruleSetId: string, observe?: 'body', reportProgress?: boolean): Observable<Dictionary>;
public getDictionaryForType(ruleSetId: string, type: string, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable<Dictionary>;
public getDictionaryForType(
ruleSetId: string,
type: string,
dossierId?: string,
observe?: 'response',
reportProgress?: boolean
): Observable<HttpResponse<Dictionary>>;
public getDictionaryForType(
ruleSetId: string,
type: string,
dossierId?: string,
observe?: 'events',
reportProgress?: boolean
): Observable<HttpEvent<Dictionary>>;
public getDictionaryForType(ruleSetId: string, type: string, dossierId?: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling getDictionaryForType.');
}
public getDictionaryForType(type: string, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<Dictionary>>;
public getDictionaryForType(type: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<Dictionary>>;
public getDictionaryForType(type: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (type === null || type === undefined) {
throw new Error('Required parameter type was null or undefined when calling getDictionaryForType.');
}
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling getDictionaryForType.');
let queryParameters = new HttpParams({ encoder: new CustomHttpUrlEncodingCodec() });
if (dossierId !== undefined && dossierId !== null) {
queryParameters = queryParameters.set('dossierId', <any>dossierId);
}
let headers = this.defaultHeaders;
@ -535,13 +641,11 @@ export class DictionaryControllerService {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
// to determine the Content-Type header
const consumes: string[] = [];
return this.httpClient.request<Dictionary>(
'get',
`${this.basePath}/dictionary/${encodeURIComponent(String(type))}/${encodeURIComponent(String(ruleSetId))}`,
{
params: queryParameters,
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
@ -559,11 +663,8 @@ export class DictionaryControllerService {
* @param reportProgress flag to report request and response progress.
*/
public setColors(body: Colors, ruleSetId: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public setColors(body: Colors, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public setColors(body: Colors, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public setColors(body: Colors, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (body === null || body === undefined) {
throw new Error('Required parameter body was null or undefined when calling setColors.');
@ -608,28 +709,52 @@ export class DictionaryControllerService {
* Updates colors, hint and caseInsensitive of an entry type.
* None
* @param body typeValue
* @param type type
* @param ruleSetId ruleSetId
* @param type type
* @param dossierId dossierId
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public updateType(body: UpdateTypeValue, type: string, ruleSetId: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public updateType(body: UpdateTypeValue, type: string, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public updateType(body: UpdateTypeValue, type: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public updateType(body: UpdateTypeValue, type: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
public updateType(body: UpdateTypeValue, ruleSetId: string, type: string, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public updateType(
body: UpdateTypeValue,
ruleSetId: string,
type: string,
dossierId?: string,
observe?: 'response',
reportProgress?: boolean
): Observable<HttpResponse<any>>;
public updateType(
body: UpdateTypeValue,
ruleSetId: string,
type: string,
dossierId?: string,
observe?: 'events',
reportProgress?: boolean
): Observable<HttpEvent<any>>;
public updateType(
body: UpdateTypeValue,
ruleSetId: string,
type: string,
dossierId?: string,
observe: any = 'body',
reportProgress: boolean = false
): Observable<any> {
if (body === null || body === undefined) {
throw new Error('Required parameter body was null or undefined when calling updateType.');
}
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling updateType.');
}
if (type === null || type === undefined) {
throw new Error('Required parameter type was null or undefined when calling updateType.');
}
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling updateType.');
let queryParameters = new HttpParams({ encoder: new CustomHttpUrlEncodingCodec() });
if (dossierId !== undefined && dossierId !== null) {
queryParameters = queryParameters.set('dossierId', <any>dossierId);
}
let headers = this.defaultHeaders;
@ -659,6 +784,7 @@ export class DictionaryControllerService {
`${this.basePath}/dictionary/type/${encodeURIComponent(String(type))}/${encodeURIComponent(String(ruleSetId))}`,
{
body: body,
params: queryParameters,
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
@ -668,33 +794,55 @@ export class DictionaryControllerService {
}
/**
* Takes object containing string or rules as argument, which will be used by the redaction service.
* Upload a text-file with 1 entry per line and add each line as an entry to a dictionary for a specific type
*
* @param file
* @param type type
* @param ruleSetId ruleSetId
* @param type type
* @param file
* @param dossierId
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public uploadDictionaryFileForm(file: Blob, type: string, ruleSetId: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public uploadDictionaryFileForm(file: Blob, type: string, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public uploadDictionaryFileForm(file: Blob, type: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public uploadDictionaryFileForm(file: Blob, type: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (file === null || file === undefined) {
throw new Error('Required parameter file was null or undefined when calling uploadDictionaryFile.');
public uploadDictionaryFileForm(
ruleSetId: string,
type: string,
file?: Blob,
dossierId?: string,
observe?: 'body',
reportProgress?: boolean
): Observable<any>;
public uploadDictionaryFileForm(
ruleSetId: string,
type: string,
file?: Blob,
dossierId?: string,
observe?: 'response',
reportProgress?: boolean
): Observable<HttpResponse<any>>;
public uploadDictionaryFileForm(
ruleSetId: string,
type: string,
file?: Blob,
dossierId?: string,
observe?: 'events',
reportProgress?: boolean
): Observable<HttpEvent<any>>;
public uploadDictionaryFileForm(
ruleSetId: string,
type: string,
file?: Blob,
dossierId?: string,
observe: any = 'body',
reportProgress: boolean = false
): Observable<any> {
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling uploadDictionaryFile.');
}
if (type === null || type === undefined) {
throw new Error('Required parameter type was null or undefined when calling uploadDictionaryFile.');
}
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling uploadDictionaryFile.');
}
let headers = this.defaultHeaders;
// authentication (RED-OAUTH) required
@ -730,6 +878,9 @@ export class DictionaryControllerService {
if (file !== undefined) {
formParams = (formParams.append('file', <any>file) as any) || formParams;
}
if (dossierId !== undefined) {
formParams = (formParams.append('dossierId', <any>dossierId) as any) || formParams;
}
return this.httpClient.request<any>(
'post',
@ -743,18 +894,4 @@ export class DictionaryControllerService {
}
);
}
/**
* @param consumes string[] mime-types
* @return true: consumes contains 'multipart/form-data', false: otherwise
*/
private canConsumeForm(consumes: string[]): boolean {
const form = 'multipart/form-data';
for (const consume of consumes) {
if (form === consume) {
return true;
}
}
return false;
}
}

View File

@ -8,7 +8,7 @@
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/ /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/member-ordering */
*/ /* tslint:disable:no-unused-variable member-ordering */
import { Inject, Injectable, Optional } from '@angular/core';
import { HttpClient, HttpEvent, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http';
@ -23,9 +23,9 @@ import { Configuration } from '../configuration';
@Injectable()
export class VersionsControllerService {
protected basePath = '';
public defaultHeaders = new HttpHeaders();
public configuration = new Configuration();
protected basePath = '';
constructor(protected httpClient: HttpClient, @Optional() @Inject(BASE_PATH) basePath: string, @Optional() configuration: Configuration) {
if (basePath) {
@ -37,6 +37,67 @@ export class VersionsControllerService {
}
}
/**
* @param consumes string[] mime-types
* @return true: consumes contains 'multipart/form-data', false: otherwise
*/
private canConsumeForm(consumes: string[]): boolean {
const form = 'multipart/form-data';
for (const consume of consumes) {
if (form === consume) {
return true;
}
}
return false;
}
/**
* Retrieves current version for dossier dictionary.
* None
* @param dossierId dossierId
* @param ruleSetId ruleSetId
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public getDossierDictionaryVersion(dossierId: string, ruleSetId: string, observe?: 'body', reportProgress?: boolean): Observable<number>;
public getDossierDictionaryVersion(dossierId: string, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<number>>;
public getDossierDictionaryVersion(dossierId: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<number>>;
public getDossierDictionaryVersion(dossierId: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (dossierId === null || dossierId === undefined) {
throw new Error('Required parameter dossierId was null or undefined when calling getDossierDictionaryVersion.');
}
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling getDossierDictionaryVersion.');
}
let headers = this.defaultHeaders;
// authentication (RED-OAUTH) required
if (this.configuration.accessToken) {
const accessToken = typeof this.configuration.accessToken === 'function' ? this.configuration.accessToken() : this.configuration.accessToken;
headers = headers.set('Authorization', 'Bearer ' + accessToken);
}
// to determine the Accept header
const httpHeaderAccepts: string[] = ['*/*'];
const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(httpHeaderAccepts);
if (httpHeaderAcceptSelected !== undefined) {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
return this.httpClient.request<number>(
'get',
`${this.basePath}/version/dossier/${encodeURIComponent(String(ruleSetId))}/${encodeURIComponent(String(dossierId))}`,
{
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
* Retrieves current versions.
* None
@ -45,11 +106,8 @@ export class VersionsControllerService {
* @param reportProgress flag to report request and response progress.
*/
public getVersions(ruleSetId: Array<string>, observe?: 'body', reportProgress?: boolean): Observable<{ [key: string]: VersionsResponse }>;
public getVersions(ruleSetId: Array<string>, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<{ [key: string]: VersionsResponse }>>;
public getVersions(ruleSetId: Array<string>, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<{ [key: string]: VersionsResponse }>>;
public getVersions(ruleSetId: Array<string>, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling getVersions.');
@ -77,9 +135,6 @@ export class VersionsControllerService {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
// to determine the Content-Type header
const consumes: string[] = [];
return this.httpClient.request<{ [key: string]: VersionsResponse }>('get', `${this.basePath}/version`, {
params: queryParameters,
withCredentials: this.configuration.withCredentials,
@ -88,18 +143,4 @@ export class VersionsControllerService {
reportProgress: reportProgress
});
}
/**
* @param consumes string[] mime-types
* @return true: consumes contains 'multipart/form-data', false: otherwise
*/
private canConsumeForm(consumes: string[]): boolean {
const form = 'multipart/form-data';
for (const consume of consumes) {
if (form === consume) {
return true;
}
}
return false;
}
}

View File

@ -39,6 +39,13 @@ export interface FileStatus {
* Shows which dictionary versions was used during the analysis.
*/
dictionaryVersion?: number;
/**
* dossier dictionary version
*/
dossierDictionaryVersion?: number;
/**
* File Attributes;
*/
fileAttributes?: FileAttributes;
/**
* The ID of the file.

View File

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