diff --git a/apps/red-ui/src/app/models/file/file-status.wrapper.ts b/apps/red-ui/src/app/models/file/file-status.wrapper.ts index f05e81f54..5296eee52 100644 --- a/apps/red-ui/src/app/models/file/file-status.wrapper.ts +++ b/apps/red-ui/src/app/models/file/file-status.wrapper.ts @@ -22,6 +22,10 @@ export class FileStatusWrapper { } } + get dossierDictionaryVersion() { + return this.fileStatus.dossierDictionaryVersion; + } + get analysisDuration() { return this.fileStatus.analysisDuration; } diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts index c9997ae8e..e8dc49242 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts @@ -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 !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({}); }) ) ); diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.html index e725780fa..172710bbd 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.html @@ -55,86 +55,11 @@ -
-
-
- - -
-
- -
-
-
- {{ currentMatch + '/' + searchPositions.length }} -
- - - -
-
-
-
-
- {{ 'dictionary-overview.compare.compare' | translate }} -
-
- - - {{ ruleSet === selectRuleSet ? (ruleSet.name | translate) : ruleSet.name }} - - -
-
- - - {{ dictionary === selectDictionary ? (dictionary.label | translate) : dictionary.label }} - - -
-
-
- -
- - -
- - -
- - -
- -
- -
-
-
+
@@ -146,7 +71,7 @@
- {{ initialDictionaryEntries?.length }} + {{ dictionaryManager.initialDictionaryEntries?.length }}
diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.scss b/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.scss index 242fa4bdc..4d87564aa 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.scss +++ b/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.scss @@ -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; - } -} diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts index 5713878c1..0517b0fd1 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts @@ -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; - 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 = []; + } + ); } } diff --git a/apps/red-ui/src/app/modules/projects/components/project-details/project-details.component.html b/apps/red-ui/src/app/modules/projects/components/project-details/project-details.component.html index 406668a84..93688a94f 100644 --- a/apps/red-ui/src/app/modules/projects/components/project-details/project-details.component.html +++ b/apps/red-ui/src/app/modules/projects/components/project-details/project-details.component.html @@ -91,6 +91,10 @@ {{ appStateService.getRuleSetById(appStateService.activeProject.ruleSetId)?.name }}
+
+ + {{ 'project-overview.project-details.dictionary' | translate }} +
diff --git a/apps/red-ui/src/app/modules/projects/components/project-details/project-details.component.ts b/apps/red-ui/src/app/modules/projects/components/project-details/project-details.component.ts index 368cd05aa..04761517b 100644 --- a/apps/red-ui/src/app/modules/projects/components/project-details/project-details.component.ts +++ b/apps/red-ui/src/app/modules/projects/components/project-details/project-details.component.ts @@ -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( diff --git a/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.html b/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.html index b9a770290..e3a038f2a 100644 --- a/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.html +++ b/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.html @@ -56,14 +56,18 @@ >
-
+
- +
diff --git a/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss b/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss index 264daedc3..af18d0899 100644 --- a/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss +++ b/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss @@ -55,15 +55,9 @@ redaction-team-members { } } - &.selected { - .actions { - display: flex; - } - } - + &.selected, &:hover { background-color: $grey-2; - .actions { display: flex; } diff --git a/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts b/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts index e69db6b4c..15b36e701 100644 --- a/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts +++ b/apps/red-ui/src/app/modules/projects/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts @@ -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); } diff --git a/apps/red-ui/src/app/modules/projects/dialogs/dossier-dictionary-dialog/dossier-dictionary-dialog.component.html b/apps/red-ui/src/app/modules/projects/dialogs/dossier-dictionary-dialog/dossier-dictionary-dialog.component.html new file mode 100644 index 000000000..870b6adfa --- /dev/null +++ b/apps/red-ui/src/app/modules/projects/dialogs/dossier-dictionary-dialog/dossier-dictionary-dialog.component.html @@ -0,0 +1,23 @@ +
+
+ +
+
+ +
+ +
+ + +
+
+
+ + +
diff --git a/apps/red-ui/src/app/modules/projects/dialogs/dossier-dictionary-dialog/dossier-dictionary-dialog.component.scss b/apps/red-ui/src/app/modules/projects/dialogs/dossier-dictionary-dialog/dossier-dictionary-dialog.component.scss new file mode 100644 index 000000000..b02797090 --- /dev/null +++ b/apps/red-ui/src/app/modules/projects/dialogs/dossier-dictionary-dialog/dossier-dictionary-dialog.component.scss @@ -0,0 +1,4 @@ +.dialog-content { + height: calc(90vh - 160px); + padding: 12px 12px 0; +} diff --git a/apps/red-ui/src/app/modules/projects/dialogs/dossier-dictionary-dialog/dossier-dictionary-dialog.component.ts b/apps/red-ui/src/app/modules/projects/dialogs/dossier-dictionary-dialog/dossier-dictionary-dialog.component.ts new file mode 100644 index 000000000..21e4cd537 --- /dev/null +++ b/apps/red-ui/src/app/modules/projects/dialogs/dossier-dictionary-dialog/dossier-dictionary-dialog.component.ts @@ -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, + @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(); + }); + } +} diff --git a/apps/red-ui/src/app/modules/projects/projects.module.ts b/apps/red-ui/src/app/modules/projects/projects.module.ts index f18e12ba9..af2361d8d 100644 --- a/apps/red-ui/src/app/modules/projects/projects.module.ts +++ b/apps/red-ui/src/app/modules/projects/projects.module.ts @@ -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] }) diff --git a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html index db0725b1c..2dff5fed0 100644 --- a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html +++ b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html @@ -38,13 +38,14 @@ tooltip="project-overview.header-actions.edit" tooltipPosition="below" > - + + + + + + + + { + this.reloadProjects(); + }); + } + toggleCollapsedDetails() { this.collapsedDetails = !this.collapsedDetails; } diff --git a/apps/red-ui/src/app/modules/projects/services/projects-dialog.service.ts b/apps/red-ui/src/app/modules/projects/services/projects-dialog.service.ts index 1f977cc7f..bc869d051 100644 --- a/apps/red-ui/src/app/modules/projects/services/projects-dialog.service.ts +++ b/apps/red-ui/src/app/modules/projects/services/projects-dialog.service.ts @@ -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 { + $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 { const ref = this._dialog.open(AssignOwnerDialogComponent, { ...dialogConfig, diff --git a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html new file mode 100644 index 000000000..829b735e8 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html @@ -0,0 +1,80 @@ +
+
+
+ + +
+
+ +
+
+
+ {{ currentMatch + '/' + searchPositions.length }} +
+ + + +
+
+
+
+
+ {{ 'dictionary-overview.compare.compare' | translate }} +
+
+ + + {{ ruleSet === selectRuleSet ? (ruleSet.name | translate) : ruleSet.name }} + + +
+
+ + + {{ dictionary === selectDictionary ? (dictionary.label | translate) : dictionary.label }} + + +
+
+
+ +
+ + +
+ + +
+ + +
+ +
+ +
+
+
diff --git a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.scss b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.scss new file mode 100644 index 000000000..7a084f902 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.scss @@ -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; + } +} diff --git a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts new file mode 100644 index 000000000..ff9f5e2cd --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts @@ -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(); + + 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(); + } +} diff --git a/apps/red-ui/src/app/modules/shared/services/dictionary-save.service.ts b/apps/red-ui/src/app/modules/shared/services/dictionary-save.service.ts new file mode 100644 index 000000000..dc72fa7f0 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/services/dictionary-save.service.ts @@ -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 { + 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; + 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'); + } + } +} diff --git a/apps/red-ui/src/app/modules/shared/shared.module.ts b/apps/red-ui/src/app/modules/shared/shared.module.ts index edb7b8ac6..5a570d5f7 100644 --- a/apps/red-ui/src/app/modules/shared/shared.module.ts +++ b/apps/red-ui/src/app/modules/shared/shared.module.ts @@ -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] }, { diff --git a/apps/red-ui/src/app/services/permissions.service.ts b/apps/red-ui/src/app/services/permissions.service.ts index 8d7517faa..ff22c66e8 100644 --- a/apps/red-ui/src/app/services/permissions.service.ts +++ b/apps/red-ui/src/app/services/permissions.service.ts @@ -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) { diff --git a/apps/red-ui/src/app/state/app-state.service.ts b/apps/red-ui/src/app/state/app-state.service.ts index 651296d80..a220dec12 100644 --- a/apps/red-ui/src/app/state/app-state.service.ts +++ b/apps/red-ui/src/app/state/app-state.service.ts @@ -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 : []; diff --git a/apps/red-ui/src/app/state/model/project.wrapper.ts b/apps/red-ui/src/app/state/model/project.wrapper.ts index d1a7913ca..f2827c1d0 100644 --- a/apps/red-ui/src/app/state/model/project.wrapper.ts +++ b/apps/red-ui/src/app/state/model/project.wrapper.ts @@ -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 : []; diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 5906aa7ca..3dac708dc 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -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", diff --git a/apps/red-ui/src/assets/icons/general/ocr.svg b/apps/red-ui/src/assets/icons/general/ocr.svg index dc05b234d..351e8e09c 100644 --- a/apps/red-ui/src/assets/icons/general/ocr.svg +++ b/apps/red-ui/src/assets/icons/general/ocr.svg @@ -1,7 +1,7 @@ - + diff --git a/bamboo-specs/src/main/java/buildjob/PlanSpec.java b/bamboo-specs/src/main/java/buildjob/PlanSpec.java index 6934b555c..6bb744105 100644 --- a/bamboo-specs/src/main/java/buildjob/PlanSpec.java +++ b/bamboo-specs/src/main/java/buildjob/PlanSpec.java @@ -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)); } diff --git a/libs/red-ui-http/src/lib/api/dictionaryController.service.ts b/libs/red-ui-http/src/lib/api/dictionaryController.service.ts index 24800b2c4..8791c682d 100644 --- a/libs/red-ui-http/src/lib/api/dictionaryController.service.ts +++ b/libs/red-ui-http/src/lib/api/dictionaryController.service.ts @@ -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, type: string, ruleSetId: string, removeCurrent?: boolean, observe?: 'body', reportProgress?: boolean): Observable; - public addEntry( body: Array, - type: string, ruleSetId: string, + type: string, + dossierId?: string, + removeCurrent?: boolean, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public addEntry( + body: Array, + ruleSetId: string, + type: string, + dossierId?: string, removeCurrent?: boolean, observe?: 'response', reportProgress?: boolean ): Observable>; - public addEntry( body: Array, - type: string, ruleSetId: string, + type: string, + dossierId?: string, removeCurrent?: boolean, observe?: 'events', reportProgress?: boolean ): Observable>; - public addEntry( body: Array, - 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', dossierId); + } if (removeCurrent !== undefined && removeCurrent !== null) { queryParameters = queryParameters.set('removeCurrent', 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; - - public addType(body: TypeValue, observe?: 'response', reportProgress?: boolean): Observable>; - - public addType(body: TypeValue, observe?: 'events', reportProgress?: boolean): Observable>; - - public addType(body: TypeValue, observe: any = 'body', reportProgress: boolean = false): Observable { + public addType(body: TypeValue, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable; + public addType(body: TypeValue, dossierId?: string, observe?: 'response', reportProgress?: boolean): Observable>; + public addType(body: TypeValue, dossierId?: string, observe?: 'events', reportProgress?: boolean): Observable>; + public addType(body: TypeValue, dossierId?: string, observe: any = 'body', reportProgress: boolean = false): Observable { 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', dossierId); + } + let headers = this.defaultHeaders; // authentication (RED-OAUTH) required @@ -175,6 +203,7 @@ export class DictionaryControllerService { return this.httpClient.request('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, type: string, ruleSetId: string, observe?: 'body', reportProgress?: boolean): Observable; - - public deleteEntries(body: Array, type: string, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable>; - - public deleteEntries(body: Array, type: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable>; - - public deleteEntries(body: Array, type: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable { + public deleteEntries(body: Array, ruleSetId: string, type: string, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable; + public deleteEntries( + body: Array, + ruleSetId: string, + type: string, + dossierId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public deleteEntries( + body: Array, + ruleSetId: string, + type: string, + dossierId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public deleteEntries( + body: Array, + ruleSetId: string, + type: string, + dossierId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { 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', 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; - - public deleteEntry(type: string, ruleSetId: string, entry: string, observe?: 'response', reportProgress?: boolean): Observable>; - - public deleteEntry(type: string, ruleSetId: string, entry: string, observe?: 'events', reportProgress?: boolean): Observable>; - - public deleteEntry(type: string, ruleSetId: string, entry: string, observe: any = 'body', reportProgress: boolean = false): Observable { - 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; + public deleteEntry( + entry: string, + ruleSetId: string, + type: string, + dossierId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public deleteEntry( + entry: string, + ruleSetId: string, + type: string, + dossierId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public deleteEntry( + entry: string, + ruleSetId: string, + type: string, + dossierId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + 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', 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( '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; + public deleteType(ruleSetId: string, type: string, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable; + public deleteType(ruleSetId: string, type: string, dossierId?: string, observe?: 'response', reportProgress?: boolean): Observable>; + public deleteType(ruleSetId: string, type: string, dossierId?: string, observe?: 'events', reportProgress?: boolean): Observable>; + public deleteType(ruleSetId: string, type: string, dossierId?: string, observe: any = 'body', reportProgress: boolean = false): Observable { + 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>; - - public deleteType(type: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable>; - - public deleteType(type: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable { 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', 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( '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; + public downloadDictionaryFile(ruleSetId: string, type: string, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable; + public downloadDictionaryFile( + ruleSetId: string, + type: string, + dossierId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public downloadDictionaryFile( + ruleSetId: string, + type: string, + dossierId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public downloadDictionaryFile( + ruleSetId: string, + type: string, + dossierId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + 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>; - - public downloadDictionaryFile(type: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable>; - - public downloadDictionaryFile(type: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable { 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', 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( '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; - - public getAllTypes(ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable>; - - public getAllTypes(ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable>; - - public getAllTypes(ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable { + public getAllTypes(ruleSetId: string, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable; + public getAllTypes(ruleSetId: string, dossierId?: string, observe?: 'response', reportProgress?: boolean): Observable>; + public getAllTypes(ruleSetId: string, dossierId?: string, observe?: 'events', reportProgress?: boolean): Observable>; + public getAllTypes(ruleSetId: string, dossierId?: string, observe: any = 'body', reportProgress: boolean = false): Observable { 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', 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('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; - public getColors(ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable>; - public getColors(ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable>; - public getColors(ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable { 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('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; + public getDictionaryForType(ruleSetId: string, type: string, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable; + public getDictionaryForType( + ruleSetId: string, + type: string, + dossierId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public getDictionaryForType( + ruleSetId: string, + type: string, + dossierId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public getDictionaryForType(ruleSetId: string, type: string, dossierId?: string, observe: any = 'body', reportProgress: boolean = false): Observable { + 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>; - - public getDictionaryForType(type: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable>; - - public getDictionaryForType(type: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable { 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', 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( '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; - public setColors(body: Colors, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable>; - public setColors(body: Colors, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable>; - public setColors(body: Colors, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable { 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; - - public updateType(body: UpdateTypeValue, type: string, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable>; - - public updateType(body: UpdateTypeValue, type: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable>; - - public updateType(body: UpdateTypeValue, type: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable { + public updateType(body: UpdateTypeValue, ruleSetId: string, type: string, dossierId?: string, observe?: 'body', reportProgress?: boolean): Observable; + public updateType( + body: UpdateTypeValue, + ruleSetId: string, + type: string, + dossierId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public updateType( + body: UpdateTypeValue, + ruleSetId: string, + type: string, + dossierId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public updateType( + body: UpdateTypeValue, + ruleSetId: string, + type: string, + dossierId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { 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', 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; - - public uploadDictionaryFileForm(file: Blob, type: string, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable>; - - public uploadDictionaryFileForm(file: Blob, type: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable>; - - public uploadDictionaryFileForm(file: Blob, type: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable { - 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; + public uploadDictionaryFileForm( + ruleSetId: string, + type: string, + file?: Blob, + dossierId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public uploadDictionaryFileForm( + ruleSetId: string, + type: string, + file?: Blob, + dossierId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public uploadDictionaryFileForm( + ruleSetId: string, + type: string, + file?: Blob, + dossierId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + 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', file) as any) || formParams; } + if (dossierId !== undefined) { + formParams = (formParams.append('dossierId', dossierId) as any) || formParams; + } return this.httpClient.request( '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; - } } diff --git a/libs/red-ui-http/src/lib/api/versionsController.service.ts b/libs/red-ui-http/src/lib/api/versionsController.service.ts index 3276edb08..1416c0a41 100644 --- a/libs/red-ui-http/src/lib/api/versionsController.service.ts +++ b/libs/red-ui-http/src/lib/api/versionsController.service.ts @@ -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; + public getDossierDictionaryVersion(dossierId: string, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable>; + public getDossierDictionaryVersion(dossierId: string, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable>; + public getDossierDictionaryVersion(dossierId: string, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable { + 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( + '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, observe?: 'body', reportProgress?: boolean): Observable<{ [key: string]: VersionsResponse }>; - public getVersions(ruleSetId: Array, observe?: 'response', reportProgress?: boolean): Observable>; - public getVersions(ruleSetId: Array, observe?: 'events', reportProgress?: boolean): Observable>; - public getVersions(ruleSetId: Array, observe: any = 'body', reportProgress: boolean = false): Observable { 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; - } } diff --git a/libs/red-ui-http/src/lib/model/fileStatus.ts b/libs/red-ui-http/src/lib/model/fileStatus.ts index 9e9b88431..65265ec22 100644 --- a/libs/red-ui-http/src/lib/model/fileStatus.ts +++ b/libs/red-ui-http/src/lib/model/fileStatus.ts @@ -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. diff --git a/package.json b/package.json index df840029c..041f1705f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redaction", - "version": "2.29.0", + "version": "2.35.0", "private": true, "license": "MIT", "scripts": {