From 1c5f8fd0068324137af9f17b43f59535851e5ba5 Mon Sep 17 00:00:00 2001 From: Timo Bejan Date: Mon, 9 Nov 2020 19:33:50 +0200 Subject: [PATCH] refactor permissions --- .prettierrc | 2 +- apps/red-ui/src/app/app.module.ts | 13 +- .../file-actions/file-actions.component.html | 80 ++++++ .../file-actions/file-actions.component.scss} | 0 .../file-actions/file-actions.component.ts | 75 ++++++ .../src/app/common/filter/filter.component.ts | 17 +- .../app/common/filter/utils/filter-utils.ts | 32 +-- .../app/common/service/permissions.service.ts | 120 +++++++++ .../needs-work-badge.component.html | 5 +- .../needs-work-badge.component.ts | 2 +- .../annotation-actions.component.ts | 16 +- .../file-not-available-overlay.component.html | 1 - .../file-not-available-overlay.component.ts | 12 - .../file-preview-screen.component.html | 154 ++--------- .../file-preview-screen.component.ts | 253 +++++------------- .../screens/file/model/file-status.wrapper.ts | 19 +- .../file/service/file-action.service.ts | 78 ++---- .../project-listing-screen.component.html | 99 ++----- .../project-listing-screen.component.ts | 31 +-- .../project-details.component.html | 72 ++--- .../project-details.component.ts | 36 +-- .../project-overview-screen.component.html | 229 ++-------------- .../project-overview-screen.component.ts | 196 ++++---------- .../red-ui/src/app/state/app-state.service.ts | 246 +++-------------- .../src/app/state/model/project.wrapper.ts | 74 +++++ apps/red-ui/src/assets/i18n/en.json | 2 +- 26 files changed, 667 insertions(+), 1197 deletions(-) create mode 100644 apps/red-ui/src/app/common/file-actions/file-actions.component.html rename apps/red-ui/src/app/{screens/file/file-not-available-overlay/file-not-available-overlay.component.scss => common/file-actions/file-actions.component.scss} (100%) create mode 100644 apps/red-ui/src/app/common/file-actions/file-actions.component.ts create mode 100644 apps/red-ui/src/app/common/service/permissions.service.ts delete mode 100644 apps/red-ui/src/app/screens/file/file-not-available-overlay/file-not-available-overlay.component.html delete mode 100644 apps/red-ui/src/app/screens/file/file-not-available-overlay/file-not-available-overlay.component.ts create mode 100644 apps/red-ui/src/app/state/model/project.wrapper.ts diff --git a/.prettierrc b/.prettierrc index c9f126c1d..d23118991 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,6 @@ { "useTabs": false, - "printWidth": 100, + "printWidth": 160, "tabWidth": 4, "singleQuote": true, "trailingComma": "none", diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index bbbd42661..d2e7663d3 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -58,11 +58,9 @@ import { AssignOwnerDialogComponent } from './dialogs/assign-owner-dialog/assign import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatNativeDateModule } from '@angular/material/core'; import { MatInputModule } from '@angular/material/input'; -import { ProjectMemberGuard } from './auth/project-member-guard.service'; import { HumanizePipe } from './utils/humanize.pipe'; import { CommentsComponent } from './components/comments/comments.component'; import { ManualAnnotationDialogComponent } from './dialogs/manual-redaction-dialog/manual-annotation-dialog.component'; -import { FileNotAvailableOverlayComponent } from './screens/file/file-not-available-overlay/file-not-available-overlay.component'; import { ToastComponent } from './components/toast/toast.component'; import { FilterComponent } from './common/filter/filter.component'; import { AppInfoComponent } from './screens/app-info/app-info.component'; @@ -75,6 +73,7 @@ import { ProjectOverviewEmptyComponent } from './screens/empty-states/project-ov import { ProjectListingEmptyComponent } from './screens/empty-states/project-listing-empty/project-listing-empty.component'; import { AnnotationActionsComponent } from './screens/file/annotation-actions/annotation-actions.component'; import { ProjectListingDetailsComponent } from './screens/project-listing-screen/project-listing-details/project-listing-details.component'; +import { FileActionsComponent } from './common/file-actions/file-actions.component'; export function HttpLoaderFactory(httpClient: HttpClient) { return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json'); @@ -104,7 +103,6 @@ export function HttpLoaderFactory(httpClient: HttpClient) { CommentsComponent, HumanizePipe, ToastComponent, - FileNotAvailableOverlayComponent, FilterComponent, AppInfoComponent, SortingComponent, @@ -116,7 +114,8 @@ export function HttpLoaderFactory(httpClient: HttpClient) { ProjectListingEmptyComponent, AnnotationActionsComponent, ProjectListingEmptyComponent, - ProjectListingDetailsComponent + ProjectListingDetailsComponent, + FileActionsComponent ], imports: [ BrowserModule, @@ -226,11 +225,7 @@ export function HttpLoaderFactory(httpClient: HttpClient) { export class AppModule { constructor(private router: Router, private route: ActivatedRoute) { route.queryParamMap.subscribe((queryParams) => { - if ( - queryParams.has('code') || - queryParams.has('state') || - queryParams.has('session_state') - ) { + if (queryParams.has('code') || queryParams.has('state') || queryParams.has('session_state')) { this.router.navigate([], { queryParams: { state: null, diff --git a/apps/red-ui/src/app/common/file-actions/file-actions.component.html b/apps/red-ui/src/app/common/file-actions/file-actions.component.html new file mode 100644 index 000000000..09531137e --- /dev/null +++ b/apps/red-ui/src/app/common/file-actions/file-actions.component.html @@ -0,0 +1,80 @@ + + +
+ +
+ + + + + + diff --git a/apps/red-ui/src/app/screens/file/file-not-available-overlay/file-not-available-overlay.component.scss b/apps/red-ui/src/app/common/file-actions/file-actions.component.scss similarity index 100% rename from apps/red-ui/src/app/screens/file/file-not-available-overlay/file-not-available-overlay.component.scss rename to apps/red-ui/src/app/common/file-actions/file-actions.component.scss diff --git a/apps/red-ui/src/app/common/file-actions/file-actions.component.ts b/apps/red-ui/src/app/common/file-actions/file-actions.component.ts new file mode 100644 index 000000000..ff6c849e0 --- /dev/null +++ b/apps/red-ui/src/app/common/file-actions/file-actions.component.ts @@ -0,0 +1,75 @@ +import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'; +import { PermissionsService } from '../service/permissions.service'; +import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper'; +import { DialogService } from '../../dialogs/dialog.service'; +import { AppStateService } from '../../state/app-state.service'; +import { FileActionService } from '../../screens/file/service/file-action.service'; + +@Component({ + selector: 'redaction-file-actions', + templateUrl: './file-actions.component.html', + styleUrls: ['./file-actions.component.scss'] +}) +export class FileActionsComponent implements OnInit { + @Input() fileStatus: FileStatusWrapper; + @Output() actionPerformed = new EventEmitter(); + + constructor( + public readonly permissionsService: PermissionsService, + private readonly _dialogService: DialogService, + private readonly _appStateService: AppStateService, + private readonly _fileActionService: FileActionService + ) {} + + ngOnInit(): void {} + + openDeleteFileDialog($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) { + this._dialogService.openDeleteFileDialog($event, fileStatusWrapper.projectId, fileStatusWrapper.fileId, () => { + this.actionPerformed.emit('delete'); + }); + } + + downloadFileRedactionReport($event: MouseEvent, file: FileStatusWrapper) { + $event.stopPropagation(); + this._appStateService.downloadFileRedactionReport(file); + } + + assignReviewer($event: MouseEvent, file: FileStatusWrapper) { + $event.stopPropagation(); + this._fileActionService.assignProjectReviewer(file, () => this.actionPerformed.emit('assign-reviewer')); + } + + reanalyseFile($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) { + $event.stopPropagation(); + this._fileActionService.reanalyseFile(fileStatusWrapper).subscribe(() => { + this.reloadProjects('reanalyse'); + }); + } + + setFileUnderApproval($event: MouseEvent, fileStatus: FileStatusWrapper) { + $event.stopPropagation(); + this._fileActionService.setFileUnderApproval(fileStatus).subscribe(() => { + this.reloadProjects('set-under-approval'); + }); + } + + setFileApproved($event: MouseEvent, fileStatus: FileStatusWrapper) { + $event.stopPropagation(); + this._fileActionService.setFileApproved(fileStatus).subscribe(() => { + this.reloadProjects('set-approved'); + }); + } + + setFileUnderReview($event: MouseEvent, fileStatus: FileStatusWrapper) { + $event.stopPropagation(); + this._fileActionService.setFileUnderReview(fileStatus).subscribe(() => { + this.reloadProjects('set-review'); + }); + } + + public reloadProjects(action: string) { + this._appStateService.getFiles().then(() => { + this.actionPerformed.emit(action); + }); + } +} diff --git a/apps/red-ui/src/app/common/filter/filter.component.ts b/apps/red-ui/src/app/common/filter/filter.component.ts index b6a35f169..13a1df555 100644 --- a/apps/red-ui/src/app/common/filter/filter.component.ts +++ b/apps/red-ui/src/app/common/filter/filter.component.ts @@ -1,16 +1,8 @@ -import { - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnChanges, - Output, - SimpleChanges, - TemplateRef -} from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef } from '@angular/core'; import { AppStateService } from '../../state/app-state.service'; import { FilterModel } from './model/filter.model'; import { handleCheckedValue } from './utils/filter-utils'; +import { PermissionsService } from '../service/permissions.service'; @Component({ selector: 'redaction-filter', @@ -25,10 +17,7 @@ export class FilterComponent implements OnChanges { @Input() hasArrow = true; @Input() icon: string; - constructor( - public readonly appStateService: AppStateService, - private readonly _changeDetectorRef: ChangeDetectorRef - ) {} + constructor(public readonly appStateService: AppStateService, private readonly _changeDetectorRef: ChangeDetectorRef) {} ngOnChanges(changes: SimpleChanges): void { if (changes.filters) { diff --git a/apps/red-ui/src/app/common/filter/utils/filter-utils.ts b/apps/red-ui/src/app/common/filter/utils/filter-utils.ts index 36740ae65..7cb7d2d02 100644 --- a/apps/red-ui/src/app/common/filter/utils/filter-utils.ts +++ b/apps/red-ui/src/app/common/filter/utils/filter-utils.ts @@ -3,9 +3,9 @@ import { FileStatusWrapper } from '../../../screens/file/model/file-status.wrapp import { ProjectWrapper } from '../../../state/app-state.service'; export const RedactionFilterSorter = { - hints: 1, - redactions: 2, - requests: 3, + hint: 1, + redaction: 2, + suggestion: 3, none: 4 }; @@ -22,12 +22,7 @@ export function handleCheckedValue(filter: FilterModel) { } } -export function checkFilter( - entity: any, - filters: FilterModel[], - validate: Function, - matchAll: boolean = false -) { +export function checkFilter(entity: any, filters: FilterModel[], validate: Function, matchAll: boolean = false) { const hasChecked = filters.find((f) => f.checked); if (!hasChecked) { @@ -48,8 +43,7 @@ export function checkFilter( return filterMatched; } -export const keyChecker = (key: string) => (entity: any, filter: FilterModel) => - entity[key] === filter.key; +export const keyChecker = (key: string) => (entity: any, filter: FilterModel) => entity[key] === filter.key; export const annotationFilterChecker = (f: FileStatusWrapper, filter: FilterModel) => { switch (filter.key) { @@ -60,7 +54,7 @@ export const annotationFilterChecker = (f: FileStatusWrapper, filter: FilterMode return f.hasRedactions; } case 'hint': { - return f.hasHints; + return f.hintsOnly; } case 'none': { return f.hasNone; @@ -68,23 +62,17 @@ export const annotationFilterChecker = (f: FileStatusWrapper, filter: FilterMode } }; -export const projectStatusChecker = (pw: ProjectWrapper, filter: FilterModel) => - pw.hasStatus(filter.key); +export const projectStatusChecker = (pw: ProjectWrapper, filter: FilterModel) => pw.hasStatus(filter.key); export const projectMemberChecker = (pw: ProjectWrapper, filter: FilterModel) => { return pw.hasMember(filter.key); }; -export const dueDateChecker = (pw: ProjectWrapper, filter: FilterModel) => - pw.dueDateMatches(filter.key); +export const dueDateChecker = (pw: ProjectWrapper, filter: FilterModel) => pw.dueDateMatches(filter.key); -export const addedDateChecker = (pw: ProjectWrapper, filter: FilterModel) => - pw.addedDateMatches(filter.key); +export const addedDateChecker = (pw: ProjectWrapper, filter: FilterModel) => pw.addedDateMatches(filter.key); -export function getFilteredEntities( - entities: any[], - filters: { values: FilterModel[]; checker: Function; matchAll?: boolean }[] -) { +export function getFilteredEntities(entities: any[], filters: { values: FilterModel[]; checker: Function; matchAll?: boolean }[]) { const filteredEntities = []; for (const entity of entities) { let add = true; diff --git a/apps/red-ui/src/app/common/service/permissions.service.ts b/apps/red-ui/src/app/common/service/permissions.service.ts new file mode 100644 index 000000000..829dd6207 --- /dev/null +++ b/apps/red-ui/src/app/common/service/permissions.service.ts @@ -0,0 +1,120 @@ +import { Injectable } from '@angular/core'; +import { AppStateService } from '../../state/app-state.service'; +import { UserService, UserWrapper } from '../../user/user.service'; +import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper'; +import { Project, User } from '@redaction/red-ui-http'; + +@Injectable({ + providedIn: 'root' +}) +export class PermissionsService { + constructor(private readonly _appStateService: AppStateService, private _userService: UserService) {} + + get currentUser() { + return this._userService.user; + } + + public isManager(user?: User) { + return this._userService.isManager(); + } + + isReviewerOrOwner(fileStatus?: FileStatusWrapper, user?: User) { + return this.isActiveFileDocumentReviewer() || this.isManagerAndOwner(); + } + + canReanalyseFile(fileStatus?: FileStatusWrapper) { + if (!fileStatus) { + fileStatus = this._appStateService.activeFile; + } + // can reanalyse file if error, has requests not up to date with dictionary and is owner or reviewer + return ( + ((!fileStatus.isApproved && this._appStateService.fileNotUpToDateWithDictionary(fileStatus)) || fileStatus.isError || fileStatus.hasRequests) && + (this.isManagerAndOwner() || this.isActiveFileDocumentReviewer()) + ); + } + + isActiveFileDocumentReviewer() { + return this._appStateService.activeFile?.currentReviewer === this._userService.userId; + } + + canDeleteFile(fileStatus?: FileStatusWrapper) { + return this.isManagerAndOwner() || fileStatus.isUnassigned; + } + + isApprovedOrUnderApproval(fileStatus?: FileStatusWrapper) { + if (!fileStatus) { + fileStatus = this._appStateService.activeFile; + } + return fileStatus.status === 'APPROVED' || fileStatus.status === 'UNDER_APPROVAL'; + } + + isApproved(fileStatus?: FileStatusWrapper) { + if (!fileStatus) { + fileStatus = this._appStateService.activeFile; + } + return fileStatus.status === 'APPROVED'; + } + + canApprove(fileStatus?: FileStatusWrapper) { + if (!fileStatus) { + fileStatus = this._appStateService.activeFile; + } + return fileStatus.status === 'UNDER_APPROVAL'; + } + + canSetUnderApproval(fileStatus?: FileStatusWrapper) { + if (!fileStatus) { + fileStatus = this._appStateService.activeFile; + } + return fileStatus.status === 'UNDER_REVIEW'; + } + + isManagerAndOwner(project?: Project, user?: UserWrapper) { + if (!user) { + user = this._userService.user; + } + if (!project) { + project = this._appStateService.activeProject; + } + return user.isManager && project.ownerId === user.id; + } + + isProjectMember(project?: Project, user?: UserWrapper) { + if (!user) { + user = this._userService.user; + } + if (!project) { + project = this._appStateService.activeProject; + } + return project.memberIds?.includes(user.id); + } + + canPerformAnnotationActions(fileStatus?: FileStatusWrapper) { + if (!fileStatus) { + fileStatus = this._appStateService.activeFile; + } + return (fileStatus.status === 'UNDER_APPROVAL' || fileStatus.status === 'UNDER_REVIEW') && this._userService.userId === fileStatus.currentReviewer; + } + + public canOpenFile(fileStatus: FileStatusWrapper) { + if (!fileStatus) { + fileStatus = this._appStateService.activeFile; + } + return !fileStatus.isError && !fileStatus.isProcessing; + } + + canShowRedactionReportDownloadBtn(fileStatus?: FileStatusWrapper) { + return this.isManagerAndOwner() && !fileStatus.isError; + } + + canAssignReviewer(fileStatus?: FileStatusWrapper) { + if (!fileStatus) { + fileStatus = this._appStateService.activeFile; + } + return this.isProjectMember() && !fileStatus.isError && !fileStatus.isApprovedOrUnderApproval; + } + + canUndoApproval(fileStatus: any) {} + + canUndoUnderApproval(fileStatus: any) {} +} diff --git a/apps/red-ui/src/app/screens/common/needs-work-badge/needs-work-badge.component.html b/apps/red-ui/src/app/screens/common/needs-work-badge/needs-work-badge.component.html index 84815eee0..07dccc917 100644 --- a/apps/red-ui/src/app/screens/common/needs-work-badge/needs-work-badge.component.html +++ b/apps/red-ui/src/app/screens/common/needs-work-badge/needs-work-badge.component.html @@ -3,10 +3,7 @@ *ngIf="needsWorkInput.hasRedactions" [typeValue]="appStateService.getDictionaryTypeValue('redaction')" > - + file-not-available-overlay works!

diff --git a/apps/red-ui/src/app/screens/file/file-not-available-overlay/file-not-available-overlay.component.ts b/apps/red-ui/src/app/screens/file/file-not-available-overlay/file-not-available-overlay.component.ts deleted file mode 100644 index ff096a3d1..000000000 --- a/apps/red-ui/src/app/screens/file/file-not-available-overlay/file-not-available-overlay.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'redaction-file-not-available-overlay', - templateUrl: './file-not-available-overlay.component.html', - styleUrls: ['./file-not-available-overlay.component.scss'] -}) -export class FileNotAvailableOverlayComponent implements OnInit { - constructor() {} - - ngOnInit(): void {} -} diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html index a96ee07f2..a72e578af 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html @@ -3,131 +3,24 @@
- + {{ 'file-preview.view-toggle' | translate }}
- -  {{ appStateService.activeFile.filename }} + +  {{ + appStateService.activeFile.filename + }}
- -
- -
- - - - - - - -
@@ -150,11 +43,7 @@
- +
@@ -186,9 +75,7 @@ >
- {{ page }} + {{ page }}
{{ annotation.superType | humanize }}
- : {{ annotation.dictionary | humanize }} + : {{ annotation.dictionary | humanize }}
- : {{ annotation.content }} + : {{ annotation.content }}
@@ -235,16 +116,11 @@
- + - + {{ filter.label ? (filter.label | translate) : (filter.key | humanize) }} diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts index 58328cfd7..380a988b3 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts @@ -1,23 +1,11 @@ -import { - ChangeDetectorRef, - Component, - ElementRef, - HostListener, - NgZone, - OnInit, - ViewChild -} from '@angular/core'; +import { ChangeDetectorRef, Component, ElementRef, HostListener, NgZone, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { ReanalysisControllerService } from '@redaction/red-ui-http'; import { AppStateService } from '../../../state/app-state.service'; import { WebViewerInstance } from '@pdftron/webviewer'; import { PdfViewerComponent } from '../pdf-viewer/pdf-viewer.component'; -import { UserService } from '../../../user/user.service'; import { debounce } from '../../../utils/debounce'; import scrollIntoView from 'scroll-into-view-if-needed'; import { FileDownloadService } from '../service/file-download.service'; -import { saveAs } from 'file-saver'; -import { FileType } from '../model/file-type'; import { DialogService } from '../../../dialogs/dialog.service'; import { MatDialogRef, MatDialogState } from '@angular/material/dialog'; import { ManualRedactionEntryWrapper } from '../model/manual-redaction-entry.wrapper'; @@ -34,6 +22,7 @@ import { NotificationService } from '../../../notification/notification.service' import { TranslateService } from '@ngx-translate/core'; import { FileStatusWrapper } from '../model/file-status.wrapper'; import { MatTooltip } from '@angular/material/tooltip'; +import { PermissionsService } from '../../../common/service/permissions.service'; const KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']; @@ -53,21 +42,21 @@ export class FilePreviewScreenComponent implements OnInit { @ViewChild('quickNavigation') private _quickNavigationElement: ElementRef; @ViewChild('reanalyseTooltip') private _reanalyseTooltip: MatTooltip; - public fileData: FileDataModel; - public fileId: string; - public annotations: AnnotationWrapper[] = []; - public displayedAnnotations: { [key: number]: { annotations: AnnotationWrapper[] } } = {}; - public selectedAnnotation: AnnotationWrapper; - public pagesPanelActive = true; - public viewReady = false; - public filters: FilterModel[]; + fileData: FileDataModel; + fileId: string; + annotations: AnnotationWrapper[] = []; + displayedAnnotations: { [key: number]: { annotations: AnnotationWrapper[] } } = {}; + selectedAnnotation: AnnotationWrapper; + pagesPanelActive = true; + viewReady = false; + filters: FilterModel[]; loadingMessage: string; canPerformAnnotationActions: boolean; constructor( public readonly appStateService: AppStateService, - public readonly userService: UserService, + public readonly permissionsService: PermissionsService, private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _activatedRoute: ActivatedRoute, private readonly _dialogService: DialogService, @@ -79,7 +68,6 @@ export class FilePreviewScreenComponent implements OnInit { private readonly _fileActionService: FileActionService, private readonly _manualAnnotationService: ManualAnnotationService, private readonly _fileDownloadService: FileDownloadService, - private readonly _reanalysisControllerService: ReanalysisControllerService, private ngZone: NgZone ) { this._activatedRoute.params.subscribe((params) => { @@ -89,23 +77,19 @@ export class FilePreviewScreenComponent implements OnInit { }); } - public get user() { - return this.userService.user; - } - - public get redactedView() { + get redactedView() { return this._activeViewer === 'REDACTED'; } - public set redactedView(value: boolean) { + set redactedView(value: boolean) { this._activeViewer = value ? 'REDACTED' : 'ANNOTATED'; } - public get activeViewer() { + get activeViewer() { return this.instance; } - public get displayedPages(): number[] { + get displayedPages(): number[] { return Object.keys(this.displayedAnnotations).map((key) => Number(key)); } @@ -114,14 +98,11 @@ export class FilePreviewScreenComponent implements OnInit { } get canNotSwitchToRedactedView() { - return ( - this.appStateService.fileNotUpToDateWithDictionary() || - this.fileData?.entriesToAdd?.length > 0 - ); + return this.appStateService.fileNotUpToDateWithDictionary() || this.fileData?.entriesToAdd?.length > 0; } - public ngOnInit(): void { - this.canPerformAnnotationActions = this.appStateService.canPerformAnnotationActionsOnCurrentFile(); + ngOnInit(): void { + this.canPerformAnnotationActions = this.permissionsService.canPerformAnnotationActions(); this._loadFileData().subscribe(() => {}); this.appStateService.fileReanalysed.subscribe((fileStatus: FileStatusWrapper) => { if (fileStatus.fileId === this.fileId) { @@ -145,18 +126,12 @@ export class FilePreviewScreenComponent implements OnInit { private _rebuildFilters() { const manualRedactionAnnotations = this.fileData.entriesToAdd.map((mr) => - AnnotationWrapper.fromManualRedaction( - mr, - this.fileData.manualRedactions, - this.appStateService.dictionaryData, - this.user - ) + AnnotationWrapper.fromManualRedaction(mr, this.fileData.manualRedactions, this.appStateService.dictionaryData, this.permissionsService.currentUser) ); const redactionLogAnnotations = this.fileData.redactionLog.redactionLogEntry.map((rde) => - AnnotationWrapper.fromRedactionLog(rde, this.fileData.manualRedactions, this.user) + AnnotationWrapper.fromRedactionLog(rde, this.fileData.manualRedactions, this.permissionsService.currentUser) ); - //this.annotations.splice(0, this.annotations.length); this.annotations = []; this.annotations.push(...manualRedactionAnnotations); this.annotations.push(...redactionLogAnnotations); @@ -164,48 +139,13 @@ export class FilePreviewScreenComponent implements OnInit { this.filtersChanged(this.filters); } - public openFileDetailsDialog($event: MouseEvent) { - this._dialogRef = this._dialogService.openFileDetailsDialog( - $event, - this.appStateService.activeFile - ); - } - - public reanalyseFile($event?: MouseEvent) { - $event?.stopPropagation(); - this.viewReady = false; - this.loadingMessage = 'file-preview.reanalyse-file'; - this._reanalysisControllerService - .reanalyzeFile(this.appStateService.activeProject.project.projectId, this.fileId) - .subscribe(async () => { - await this.appStateService.reloadActiveProjectFiles(); - }); - } - - public openDeleteFileDialog($event: MouseEvent) { - this._dialogRef = this._dialogService.openDeleteFileDialog( - $event, - this.projectId, - this.fileId, - () => { - this._router.navigate([`/ui/projects/${this.projectId}`]); - } - ); - } - - public assignReviewer() { - this._fileActionService.assignProjectReviewer(null, () => { - this.canPerformAnnotationActions = this.appStateService.canPerformAnnotationActionsOnCurrentFile(); - }); - } - - public handleAnnotationSelected(annotationId: string) { + handleAnnotationSelected(annotationId: string) { this.selectedAnnotation = this.annotations.find((a) => a.id === annotationId); this.scrollToSelectedAnnotation(); this._changeDetectorRef.detectChanges(); } - public selectAnnotation(annotation: AnnotationWrapper) { + selectAnnotation(annotation: AnnotationWrapper) { this._viewerComponent.selectAnnotation(annotation); } @@ -214,25 +154,20 @@ export class FilePreviewScreenComponent implements OnInit { if (!this.selectedAnnotation) { return; } - const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll( - `div[annotation-id="${this.selectedAnnotation.id}"].active` - ); + const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[annotation-id="${this.selectedAnnotation.id}"].active`); this._scrollToFirstElement(elements); } - public selectPage(pageNumber: number) { + selectPage(pageNumber: number) { this._viewerComponent.navigateToPage(pageNumber); this._scrollAnnotationsToPage(pageNumber, 'always'); } - public openManualRedactionDialog($event: ManualRedactionEntryWrapper) { + openManualRedactionDialog($event: ManualRedactionEntryWrapper) { this.ngZone.run(() => { - this._dialogRef = this._dialogService.openManualRedactionDialog( - $event, - (response: ManualAnnotationResponse) => { - this._cleanupAndRedrawManualAnnotations(); - } - ); + this._dialogRef = this._dialogService.openManualRedactionDialog($event, (response: ManualAnnotationResponse) => { + this._cleanupAndRedrawManualAnnotations(); + }); }); } @@ -243,9 +178,7 @@ export class FilePreviewScreenComponent implements OnInit { } private _scrollQuickNavigation() { - const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll( - `#quick-nav-page-${this.activeViewerPage}` - ); + const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${this.activeViewerPage}`); this._scrollToFirstElement(elements); } @@ -257,16 +190,11 @@ export class FilePreviewScreenComponent implements OnInit { } private _scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed') { - const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll( - `div[anotation-page-header="${page}"]` - ); + const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`); this._scrollToFirstElement(elements, mode); } - private _scrollToFirstElement( - elements: HTMLElement[], - mode: 'always' | 'if-needed' = 'if-needed' - ) { + private _scrollToFirstElement(elements: HTMLElement[], mode: 'always' | 'if-needed' = 'if-needed') { if (elements.length > 0) { scrollIntoView(elements[0], { behavior: 'smooth', @@ -277,18 +205,9 @@ export class FilePreviewScreenComponent implements OnInit { } } - public downloadFile(type: FileType | string) { - this._fileDownloadService.loadFile(type, this.fileId).subscribe((data) => { - saveAs(data, this.appStateService.activeFile.filename); - }); - } - @HostListener('window:keyup', ['$event']) - public handleKeyEvent($event: KeyboardEvent) { - if ( - !KEY_ARRAY.includes($event.key) || - this._dialogRef?.getState() === MatDialogState.OPEN - ) { + handleKeyEvent($event: KeyboardEvent) { + if (!KEY_ARRAY.includes($event.key) || this._dialogRef?.getState() === MatDialogState.OPEN) { return; } @@ -318,8 +237,7 @@ export class FilePreviewScreenComponent implements OnInit { private _selectFirstAnnotationOnCurrentPageIfNecessary() { if ( - (!this.selectedAnnotation || - this.activeViewerPage !== this.selectedAnnotation.pageNumber) && + (!this.selectedAnnotation || this.activeViewerPage !== this.selectedAnnotation.pageNumber) && this.displayedPages.indexOf(this.activeViewerPage) >= 0 ) { this.selectAnnotation(this.displayedAnnotations[this.activeViewerPage].annotations[0]); @@ -327,16 +245,11 @@ export class FilePreviewScreenComponent implements OnInit { } private _navigateAnnotations($event: KeyboardEvent) { - if ( - !this.selectedAnnotation || - this.activeViewerPage !== this.selectedAnnotation.pageNumber - ) { + if (!this.selectedAnnotation || this.activeViewerPage !== this.selectedAnnotation.pageNumber) { const pageIdx = this.displayedPages.indexOf(this.activeViewerPage); if (pageIdx !== -1) { // Displayed page has annotations - this.selectAnnotation( - this.displayedAnnotations[this.activeViewerPage].annotations[0] - ); + this.selectAnnotation(this.displayedAnnotations[this.activeViewerPage].annotations[0]); } else { // Displayed page doesn't have annotations if ($event.key === 'ArrowDown') { @@ -360,9 +273,7 @@ export class FilePreviewScreenComponent implements OnInit { this.selectAnnotation(annotationsOnPage[idx + 1]); } else if (pageIdx + 1 < this.displayedPages.length) { // If not last page - const nextPageAnnotations = this.displayedAnnotations[ - this.displayedPages[pageIdx + 1] - ].annotations; + const nextPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx + 1]].annotations; this.selectAnnotation(nextPageAnnotations[0]); } } else { @@ -371,9 +282,7 @@ export class FilePreviewScreenComponent implements OnInit { this.selectAnnotation(annotationsOnPage[idx - 1]); } else if (pageIdx) { // If not first page - const prevPageAnnotations = this.displayedAnnotations[ - this.displayedPages[pageIdx - 1] - ].annotations; + const prevPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx - 1]].annotations; this.selectAnnotation(prevPageAnnotations[prevPageAnnotations.length - 1]); } } @@ -451,10 +360,7 @@ export class FilePreviewScreenComponent implements OnInit { } filtersChanged(filters: FilterModel[]) { - this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations( - this.annotations, - filters - ); + this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(this.annotations, filters); this._changeDetectorRef.markForCheck(); } @@ -465,62 +371,41 @@ export class FilePreviewScreenComponent implements OnInit { } private _cleanupAndRedrawManualAnnotations() { - this._fileDownloadService - .loadActiveFileManualAnnotations() - .subscribe((manualRedactions) => { - const annotationsToRemove = []; - this.fileData.entriesToAdd.forEach((manuallyAddedEntry) => { - const annotation = this.activeViewer.annotManager.getAnnotationById( - manuallyAddedEntry.id - ); - if (annotation) { - annotationsToRemove.push(annotation); - } - }); - this.activeViewer.annotManager.deleteAnnotations(annotationsToRemove, false, true); - - this.fileData.manualRedactions = manualRedactions; - this._annotationDrawService.drawAnnotations( - this.instance, - this.fileData.entriesToAdd - ); - this._rebuildFilters(); + this._fileDownloadService.loadActiveFileManualAnnotations().subscribe((manualRedactions) => { + const annotationsToRemove = []; + this.fileData.entriesToAdd.forEach((manuallyAddedEntry) => { + const annotation = this.activeViewer.annotManager.getAnnotationById(manuallyAddedEntry.id); + if (annotation) { + annotationsToRemove.push(annotation); + } }); - } + this.activeViewer.annotManager.deleteAnnotations(annotationsToRemove, false, true); - get fileReadyForDownload() { - return this.appStateService.activeFile.status === 'APPROVED'; - } - - isApprovedOrUnderApproval() { - return ( - this.appStateService.activeFile.status === 'APPROVED' || - this.appStateService.activeFile.status === 'UNDER_APPROVAL' - ); - } - - isApproved() { - return this.appStateService.activeFile.status === 'APPROVED'; - } - - canApprove() { - return ( - this.appStateService.activeFile.status === 'UNDER_REVIEW' || - this.appStateService.activeFile.status === 'UNDER_APPROVAL' - ); - } - - requestApprovalOrApproveFile($event: MouseEvent) { - $event.stopPropagation(); - this._fileActionService.requestApprovalOrApproveFile().subscribe(() => {}); - } - - undoApproveOrUnderApproval($event: MouseEvent) { - $event.stopPropagation(); - this._fileActionService.undoApproveOrUnderApproval().subscribe(() => {}); + this.fileData.manualRedactions = manualRedactions; + this._annotationDrawService.drawAnnotations(this.instance, this.fileData.entriesToAdd); + this._rebuildFilters(); + }); } annotationsChangedByReviewAction() { this._cleanupAndRedrawManualAnnotations(); } + + async fileActionPerformed(action: string) { + switch (action) { + case 'delete': + await this._router.navigate([`/ui/projects/${this.projectId}`]); + break; + + case 'reanalyse': + this.viewReady = false; + this.loadingMessage = 'file-preview.reanalyse-file'; + break; + } + + this.canPerformAnnotationActions = this.permissionsService.canPerformAnnotationActions(); + await this.appStateService.reloadActiveProjectFiles(); + } + + // allManualRedactionsApplied } diff --git a/apps/red-ui/src/app/screens/file/model/file-status.wrapper.ts b/apps/red-ui/src/app/screens/file/model/file-status.wrapper.ts index a487932f8..9789d529d 100644 --- a/apps/red-ui/src/app/screens/file/model/file-status.wrapper.ts +++ b/apps/red-ui/src/app/screens/file/model/file-status.wrapper.ts @@ -11,6 +11,7 @@ export class FileStatusWrapper { return this.fileStatus.added; } + // TODO use this for suggestions get allManualRedactionsApplied() { return this.fileStatus.allManualRedactionsApplied; } @@ -39,6 +40,10 @@ export class FileStatusWrapper { return this.fileStatus.hasHints; } + get hintsOnly() { + return this.fileStatus.hasHints && !this.fileStatus.hasRedactions; + } + get hasRedactions() { return this.fileStatus.hasRedactions; } @@ -79,6 +84,18 @@ export class FileStatusWrapper { return this.fileStatus.uploader; } + get isPending() { + return this.status === FileStatus.StatusEnum.UNPROCESSED; + } + + get isProcessing() { + return [FileStatus.StatusEnum.REPROCESS, FileStatus.StatusEnum.PROCESSING].includes(this.status); + } + + get isWorkable() { + return !this.isProcessing && !this.isPending && !this.isError; + } + get isApproved() { return this.fileStatus.status === 'APPROVED'; } @@ -99,7 +116,7 @@ export class FileStatusWrapper { } get isUnassigned() { - return this.status === 'UNASSIGNED'; + return !this.currentReviewer; } get canApprove() { diff --git a/apps/red-ui/src/app/screens/file/service/file-action.service.ts b/apps/red-ui/src/app/screens/file/service/file-action.service.ts index 9f8f4e2c6..6036be20c 100644 --- a/apps/red-ui/src/app/screens/file/service/file-action.service.ts +++ b/apps/red-ui/src/app/screens/file/service/file-action.service.ts @@ -2,9 +2,10 @@ import { Injectable } from '@angular/core'; import { DialogService } from '../../../dialogs/dialog.service'; import { AppStateService } from '../../../state/app-state.service'; import { UserService } from '../../../user/user.service'; -import { StatusControllerService } from '@redaction/red-ui-http'; +import { ReanalysisControllerService, StatusControllerService } from '@redaction/red-ui-http'; import { FileStatus } from '@redaction/red-ui-http'; import { FileStatusWrapper } from '../model/file-status.wrapper'; +import { PermissionsService } from '../../../common/service/permissions.service'; @Injectable({ providedIn: 'root' @@ -12,29 +13,31 @@ import { FileStatusWrapper } from '../model/file-status.wrapper'; export class FileActionService { constructor( private readonly _dialogService: DialogService, + private readonly _permissionsService: PermissionsService, private readonly _userService: UserService, private readonly _statusControllerService: StatusControllerService, - private _appStateService: AppStateService + private readonly _reanalysisControllerService: ReanalysisControllerService, + private readonly _appStateService: AppStateService ) {} + public reanalyseFile(fileStatusWrapper?: FileStatusWrapper) { + if (!fileStatusWrapper) { + fileStatusWrapper = this._appStateService.activeFile; + } + return this._reanalysisControllerService.reanalyzeFile(this._appStateService.activeProject.project.projectId, fileStatusWrapper.fileId); + } + public assignProjectReviewer(file?: FileStatus, callback?: Function) { - if (this._appStateService.isActiveProjectOwnerAndManager) { - this._dialogService.openAssignFileReviewerDialog( - file ? file : this._appStateService.activeFile, - async () => { - await this._appStateService.reloadActiveProjectFiles(); - if (callback) { - callback(); - } + if (this._permissionsService.isManagerAndOwner()) { + this._dialogService.openAssignFileReviewerDialog(file ? file : this._appStateService.activeFile, async () => { + await this._appStateService.reloadActiveProjectFiles(); + if (callback) { + callback(); } - ); + }); } else { this._statusControllerService - .assignProjectOwner( - this._appStateService.activeProjectId, - file ? file.fileId : this._appStateService.activeFileId, - this._userService.userId - ) + .assignProjectOwner(this._appStateService.activeProjectId, file ? file.fileId : this._appStateService.activeFileId, this._userService.userId) .subscribe(async () => { await this._appStateService.reloadActiveProjectFiles(); if (callback) { @@ -44,46 +47,15 @@ export class FileActionService { } } - setUnderApproval(fileStatus: FileStatusWrapper) { - return this._statusControllerService.setStatusUnderApproval( - this._appStateService.activeProjectId, - fileStatus.fileId - ); + setFileUnderApproval(fileStatus: FileStatusWrapper) { + return this._statusControllerService.setStatusUnderApproval(this._appStateService.activeProjectId, fileStatus.fileId); } - setApproved(fileStatus: FileStatusWrapper) { - return this._statusControllerService.setStatusApproved( - this._appStateService.activeProjectId, - fileStatus.fileId - ); + setFileApproved(fileStatus: FileStatusWrapper) { + return this._statusControllerService.setStatusApproved(this._appStateService.activeProjectId, fileStatus.fileId); } - setReview(fileStatus: FileStatusWrapper) { - return this._statusControllerService.setStatusUnderReview( - this._appStateService.activeProjectId, - fileStatus.fileId - ); - } - - requestApprovalOrApproveFile(fileStatusWrapper?: FileStatusWrapper) { - if (!fileStatusWrapper) { - fileStatusWrapper = this._appStateService.activeFile; - } - if (fileStatusWrapper.status === 'UNDER_REVIEW') { - return this.setUnderApproval(fileStatusWrapper); - } else { - return this.setApproved(fileStatusWrapper); - } - } - - undoApproveOrUnderApproval(fileStatusWrapper?: FileStatusWrapper) { - if (!fileStatusWrapper) { - fileStatusWrapper = this._appStateService.activeFile; - } - if (fileStatusWrapper.status === 'APPROVED') { - return this.setUnderApproval(fileStatusWrapper); - } else { - return this.setReview(fileStatusWrapper); - } + setFileUnderReview(fileStatus: FileStatusWrapper) { + return this._statusControllerService.setStatusUnderReview(this._appStateService.activeProjectId, fileStatus.fileId); } } diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html index b1eeb4ef4..9fc24d639 100644 --- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html +++ b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html @@ -32,13 +32,7 @@ [icon]="'red:needs-work'" >
- @@ -49,16 +43,9 @@
-
+
- {{ - 'project-listing.table-header.title' - | translate: { length: displayedProjects.length || 0 } - }} + {{ 'project-listing.table-header.title' | translate: { length: displayedProjects.length || 0 } }}
@@ -82,42 +69,22 @@ (toggleSort)="sortingComponent.toggleSort($event)" > - + - + - + + +
- -
-
+
@@ -148,27 +115,19 @@
- +
- +
- + -
+
-
@@ -207,7 +157,6 @@ (click)="openAssignProjectOwnerDialog($event, pw.project)" [matTooltip]="'project-listing.assign.action' | translate" matTooltipPosition="above" - *ngIf="userService.isManager(user)" color="accent" mat-icon-button > @@ -215,7 +164,7 @@ -
-
@@ -34,11 +21,7 @@
- +
@@ -49,11 +32,7 @@
+{{ overflowCount }}
-
+
+
@@ -72,14 +51,8 @@
-
- +
+ {{ 'project-overview.legend.' + filter.key | translate }}
@@ -87,41 +60,26 @@
- {{ - 'project-overview.project-details.stats.documents' - | translate: { count: appStateService.activeProject.files.length } - }} + {{ 'project-overview.project-details.stats.documents' | translate: { count: appStateService.activeProject.files.length } }}
- {{ - 'project-overview.project-details.stats.people' - | translate: { count: appStateService.activeProject.project.memberIds.length } - }} + {{ 'project-overview.project-details.stats.people' | translate: { count: appStateService.activeProject.project.memberIds.length } }}
- {{ - 'project-overview.project-details.stats.analysed-pages' - | translate: { count: appStateService.activeProject.totalNumberOfPages } - }} + {{ 'project-overview.project-details.stats.analysed-pages' | translate: { count: appStateService.activeProject.totalNumberOfPages } }}
{{ - 'project-overview.project-details.stats.created-on' - | translate - : { date: appStateService.activeProject.project.date | date: 'd MMM. yyyy' } - }} + >{{ 'project-overview.project-details.stats.created-on' | translate: { date: appStateService.activeProject.project.date | date: 'd MMM. yyyy' } }}
{{ - 'project-overview.project-details.stats.due-date' - | translate - : { date: appStateService.activeProject.project.dueDate | date: 'd MMM. yyyy' } + 'project-overview.project-details.stats.due-date' | translate: { date: appStateService.activeProject.project.dueDate | date: 'd MMM. yyyy' } }}
diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts b/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts index 921d4f22d..95a3c318a 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts @@ -6,6 +6,7 @@ import { DoughnutChartConfig } from '../../../components/simple-doughnut-chart/s import { DialogService } from '../../../dialogs/dialog.service'; import { Router } from '@angular/router'; import { FilterModel } from '../../../common/filter/model/filter.model'; +import { PermissionsService } from '../../../common/service/permissions.service'; @Component({ selector: 'redaction-project-details', @@ -20,7 +21,7 @@ export class ProjectDetailsComponent implements OnInit { constructor( public readonly appStateService: AppStateService, - public readonly userService: UserService, + public readonly permissionsService: PermissionsService, private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _dialogService: DialogService, private readonly _router: Router @@ -33,45 +34,28 @@ export class ProjectDetailsComponent implements OnInit { }); } - public get user() { - return this.userService.user; - } - public get displayMembers() { return this.appStateService.activeProject.project.memberIds.slice(0, 6); } public get overflowCount() { - return this.appStateService.activeProject.project.memberIds.length > 6 - ? this.appStateService.activeProject.project.memberIds.length - 6 - : 0; + return this.appStateService.activeProject.project.memberIds.length > 6 ? this.appStateService.activeProject.project.memberIds.length - 6 : 0; } public openEditProjectDialog($event: MouseEvent) { - this._dialogService.openEditProjectDialog( - $event, - this.appStateService.activeProject.project - ); + this._dialogService.openEditProjectDialog($event, this.appStateService.activeProject.project); } public openDeleteProjectDialog($event: MouseEvent) { - this._dialogService.openDeleteProjectDialog( - $event, - this.appStateService.activeProject.project, - () => { - this._router.navigate(['/ui/projects']); - } - ); + this._dialogService.openDeleteProjectDialog($event, this.appStateService.activeProject.project, () => { + this._router.navigate(['/ui/projects']); + }); } public openAssignProjectMembersDialog(): void { - this._dialogService.openAssignProjectMembersAndOwnerDialog( - null, - this.appStateService.activeProject.project, - () => { - this.reloadProjects.emit(); - } - ); + this._dialogService.openAssignProjectMembersAndOwnerDialog(null, this.appStateService.activeProject.project, () => { + this.reloadProjects.emit(); + }); } public downloadRedactionReport($event: MouseEvent): void { diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html index 45d685900..10a22c9eb 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html @@ -1,7 +1,4 @@ - +
@@ -58,16 +44,9 @@
-
+
- {{ - 'project-overview.table-header.title' - | translate: { length: displayedFiles.length || 0 } - }} + {{ 'project-overview.table-header.title' | translate: { length: displayedFiles.length || 0 } }}
@@ -99,9 +78,7 @@ label="project-overview.table-col-names.added-on" > - + -
+
-
+
-
+
-
+
{{ fileStatus.filename }}
-
+
{{ fileStatus.added | date: 'd MMM. yyyy, hh:mm a' }}
-
- +
+
-
- +
+
-
+
{{ fileStatus.numberOfPages }}
-
-
-
-
+
+
+
+
- - -
- -
- - - - + - + {{ filter.label | translate }} diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts index 31d161333..1c529ca7d 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts @@ -1,18 +1,12 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { - FileStatus, - FileUploadControllerService, - ReanalysisControllerService, - StatusControllerService -} from '@redaction/red-ui-http'; +import { FileUploadControllerService, ReanalysisControllerService, StatusControllerService } from '@redaction/red-ui-http'; import { NotificationService, NotificationType } from '../../notification/notification.service'; import { AppStateService } from '../../state/app-state.service'; import { FileDropOverlayService } from '../../upload/file-drop/service/file-drop-overlay.service'; import { FileUploadModel } from '../../upload/model/file-upload.model'; import { FileUploadService } from '../../upload/file-upload.service'; import { UploadStatusOverlayService } from '../../upload/upload-status-dialog/service/upload-status-overlay.service'; -import { UserService } from '../../user/user.service'; import { humanize } from '../../utils/functions'; import { DialogService } from '../../dialogs/dialog.service'; import { TranslateService } from '@ngx-translate/core'; @@ -22,12 +16,9 @@ import * as moment from 'moment'; import { SortingOption } from '../../components/sorting/sorting.component'; import { ProjectDetailsComponent } from './project-details/project-details.component'; import { FileStatusWrapper } from '../file/model/file-status.wrapper'; -import { - annotationFilterChecker, - getFilteredEntities, - keyChecker, - RedactionFilterSorter -} from '../../common/filter/utils/filter-utils'; +import { annotationFilterChecker, getFilteredEntities, keyChecker, RedactionFilterSorter } from '../../common/filter/utils/filter-utils'; +import { PermissionsService } from '../../common/service/permissions.service'; +import { UserService } from '../../user/user.service'; @Component({ selector: 'redaction-project-overview-screen', @@ -37,13 +28,13 @@ import { export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { private _selectedFileIds: string[] = []; - public statusFilters: FilterModel[]; - public peopleFilters: FilterModel[]; - public needsWorkFilters: FilterModel[]; + statusFilters: FilterModel[]; + peopleFilters: FilterModel[]; + needsWorkFilters: FilterModel[]; - public displayedFiles: FileStatusWrapper[] = []; + displayedFiles: FileStatusWrapper[] = []; - public detailsContainerFilters: { + detailsContainerFilters: { needsWorkFilters: FilterModel[]; statusFilters: FilterModel[]; }; @@ -51,20 +42,18 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { @ViewChild('projectDetailsComponent', { static: false }) private _projectDetailsComponent: ProjectDetailsComponent; - public sortingOption: SortingOption = { column: 'added', order: 'desc' }; + sortingOption: SortingOption = { column: 'added', order: 'desc' }; constructor( public readonly appStateService: AppStateService, - public readonly userService: UserService, + public readonly permissionsService: PermissionsService, + private readonly _userService: UserService, private readonly _activatedRoute: ActivatedRoute, - private readonly _fileUploadControllerService: FileUploadControllerService, - private readonly _statusControllerService: StatusControllerService, private readonly _notificationService: NotificationService, private readonly _dialogService: DialogService, private readonly _fileActionService: FileActionService, private readonly _fileUploadService: FileUploadService, private readonly _uploadStatusOverlayService: UploadStatusOverlayService, - private readonly _reanalysisControllerService: ReanalysisControllerService, private readonly _router: Router, private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _translateService: TranslateService, @@ -75,13 +64,13 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { }); this.appStateService.fileStatusChanged.subscribe(() => { - this._calculateData(); + this.calculateData(); }); } ngOnInit(): void { this._fileDropOverlayService.initFileDropHandling(); - this._calculateData(); + this.calculateData(); this._displayNewRuleToast(); } @@ -89,38 +78,14 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { this._fileDropOverlayService.cleanupFileDropHandling(); } - public get user() { - return this.userService.user; - } - - public isPending(fileStatusWrapper: FileStatusWrapper) { - return fileStatusWrapper.status === FileStatus.StatusEnum.UNPROCESSED; - } - - public isError(fileStatusWrapper: FileStatusWrapper) { - return fileStatusWrapper.status === FileStatus.StatusEnum.ERROR; - } - - public isProcessing(fileStatusWrapper: FileStatusWrapper) { - return [FileStatus.StatusEnum.REPROCESS, FileStatus.StatusEnum.PROCESSING].includes( - fileStatusWrapper.status - ); - } - private _displayNewRuleToast() { // @ts-ignore - if ( - !this.appStateService.activeProject.files.filter((file) => - this.appStateService.fileNotUpToDateWithDictionary(file) - ).length - ) { + if (!this.appStateService.activeProject.files.filter((file) => this.appStateService.fileNotUpToDateWithDictionary(file)).length) { return; } this._notificationService.showToastNotification( - `${this._translateService.instant( - 'project-overview.new-rule.toast.message-project' - )} ${this._translateService.instant( + `${this._translateService.instant('project-overview.new-rule.toast.message-project')} ${this._translateService.instant( 'project-overview.new-rule.label' )}`, null, @@ -130,41 +95,35 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { positionClass: 'toast-top-left', actions: [ { - title: this._translateService.instant( - 'project-overview.new-rule.toast.actions.reanalyse-all' - ), + title: this._translateService.instant('project-overview.new-rule.toast.actions.reanalyse-all'), action: () => - this._reanalysisControllerService - .reanalyzeProject( - this.appStateService.activeProject.project.projectId - ) + this.appStateService + .reanalyzeProject() .toPromise() .then(() => this.reloadProjects()) }, { - title: this._translateService.instant( - 'project-overview.new-rule.toast.actions.later' - ) + title: this._translateService.instant('project-overview.new-rule.toast.actions.later') } ] } ); } - public reloadProjects() { + reloadProjects() { this.appStateService.getFiles().then(() => { - this._calculateData(); + this.calculateData(); }); } - private _calculateData(): void { + calculateData(): void { this._computeAllFilters(); this._filterFiles(); this._projectDetailsComponent?.calculateChartConfig(); this._changeDetectorRef.detectChanges(); } - public toggleFileSelected($event: MouseEvent, file: FileStatusWrapper) { + toggleFileSelected($event: MouseEvent, file: FileStatusWrapper) { $event.stopPropagation(); const idx = this._selectedFileIds.indexOf(file.fileId); if (idx === -1) { @@ -174,65 +133,27 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { } } - public toggleSelectAll() { + toggleSelectAll() { if (this.areAllFilesSelected()) { this._selectedFileIds = []; } else { - this._selectedFileIds = this.appStateService.activeProject.files.map( - (file) => file.fileId - ); + this._selectedFileIds = this.appStateService.activeProject.files.map((file) => file.fileId); } } - public areAllFilesSelected() { - return ( - this.appStateService.activeProject.files.length !== 0 && - this._selectedFileIds.length === this.appStateService.activeProject.files.length - ); + areAllFilesSelected() { + return this.appStateService.activeProject.files.length !== 0 && this._selectedFileIds.length === this.appStateService.activeProject.files.length; } - public isFileSelected(file: FileStatusWrapper) { + isFileSelected(file: FileStatusWrapper) { return this._selectedFileIds.indexOf(file.fileId) !== -1; } - public openDeleteFileDialog($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) { - this._dialogService.openDeleteFileDialog( - $event, - fileStatusWrapper.projectId, - fileStatusWrapper.fileId, - () => { - this._calculateData(); - } - ); - } - - downloadFileRedactionReport($event: MouseEvent, file: FileStatusWrapper) { - $event.stopPropagation(); - this.appStateService.downloadFileRedactionReport(file); - } - - public assignReviewer($event: MouseEvent, file: FileStatusWrapper) { - $event.stopPropagation(); - this._fileActionService.assignProjectReviewer(file, () => this._calculateData()); - } - - public reanalyseFile($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) { - $event.stopPropagation(); - this._reanalysisControllerService - .reanalyzeFile( - this.appStateService.activeProject.project.projectId, - fileStatusWrapper.fileId - ) - .subscribe(() => { - this.reloadProjects(); - }); - } - - public fileId(index, item) { + fileId(index, item) { return item.fileId; } - public uploadFiles(files: FileList | File[]) { + uploadFiles(files: FileList | File[]) { const uploadFiles: FileUploadModel[] = []; for (let i = 0; i < files.length; i++) { const file = files[i]; @@ -248,15 +169,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { this._uploadStatusOverlayService.openStatusOverlay(); } - public canOpenFile(fileStatusWrapper: FileStatusWrapper): boolean { - return ( - !this.isError(fileStatusWrapper) && - !this.isProcessing(fileStatusWrapper) && - this.appStateService.isActiveProjectMember - ); - } - - public sortingOptionChanged(option: SortingOption) { + sortingOptionChanged(option: SortingOption) { this.sortingOption = option; } @@ -267,23 +180,17 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { const allDistinctNeedsWork = new Set(); // All people - this.appStateService.activeProject.project.memberIds.forEach((memberId) => - allDistinctPeople.add(memberId) - ); + this.appStateService.activeProject.project.memberIds.forEach((memberId) => allDistinctPeople.add(memberId)); // File statuses - this.appStateService.activeProject.files.forEach((file) => - allDistinctFileStatusWrapper.add(file.status) - ); + this.appStateService.activeProject.files.forEach((file) => allDistinctFileStatusWrapper.add(file.status)); // Added dates - this.appStateService.activeProject.files.forEach((file) => - allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY')) - ); + this.appStateService.activeProject.files.forEach((file) => allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY'))); // Needs work this.appStateService.activeProject.files.forEach((file) => { - if (file.hasHints) allDistinctNeedsWork.add('hint'); + if (file.hintsOnly) allDistinctNeedsWork.add('hint'); if (file.hasRedactions) allDistinctNeedsWork.add('redaction'); if (file.hasRequests) allDistinctNeedsWork.add('suggestion'); if (file.hasNone) allDistinctNeedsWork.add('none'); @@ -301,7 +208,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { allDistinctPeople.forEach((userId) => { this.peopleFilters.push({ key: userId, - label: this.userService.getNameForId(userId) + label: this._userService.getNameForId(userId) }); }); @@ -312,9 +219,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { label: this._translateService.instant('filter.' + type) }); }); - needsWorkFilters.sort( - (a, b) => RedactionFilterSorter[a.key] - RedactionFilterSorter[b.key] - ); + needsWorkFilters.sort((a, b) => RedactionFilterSorter[a.key] - RedactionFilterSorter[b.key]); this.needsWorkFilters = needsWorkFilters; } @@ -329,34 +234,23 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { this._filterFiles(); } + fileLink(fileStatus: FileStatusWrapper) { + return this.permissionsService.canOpenFile(fileStatus) + ? ['/ui/projects/' + this.appStateService.activeProject.project.projectId + '/file/' + fileStatus.fileId] + : []; + } + private _filterFiles() { const filters = [ { values: this.statusFilters, checker: keyChecker('status') }, { values: this.peopleFilters, checker: keyChecker('currentReviewer') }, { values: this.needsWorkFilters, checker: annotationFilterChecker, matchAll: true } ]; - this.displayedFiles = getFilteredEntities( - this.appStateService.activeProject.files, - filters - ); + this.displayedFiles = getFilteredEntities(this.appStateService.activeProject.files, filters); this.detailsContainerFilters = { needsWorkFilters: this.needsWorkFilters.map((f) => ({ ...f })), statusFilters: this.statusFilters.map((f) => ({ ...f })) }; this._changeDetectorRef.detectChanges(); } - - requestApprovalOrApproveFile($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) { - $event.stopPropagation(); - this._fileActionService.requestApprovalOrApproveFile(fileStatusWrapper).subscribe(() => { - this.reloadProjects(); - }); - } - - undoApproveOrUnderApproval($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) { - $event.stopPropagation(); - this._fileActionService.undoApproveOrUnderApproval(fileStatusWrapper).subscribe(() => { - this.reloadProjects(); - }); - } } 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 6a69cd0de..d4fa48a1d 100644 --- a/apps/red-ui/src/app/state/app-state.service.ts +++ b/apps/red-ui/src/app/state/app-state.service.ts @@ -13,14 +13,14 @@ import { import { NotificationService, NotificationType } from '../notification/notification.service'; import { TranslateService } from '@ngx-translate/core'; import { Router } from '@angular/router'; -import { UserService, UserWrapper } from '../user/user.service'; -import { forkJoin, interval, of, timer } from 'rxjs'; +import { UserService } from '../user/user.service'; +import { forkJoin, of, timer } from 'rxjs'; import { tap } from 'rxjs/operators'; import { download } from '../utils/file-download-utils'; import { humanize } from '../utils/functions'; import { AnnotationWrapper } from '../screens/file/model/annotation.wrapper'; -import * as moment from 'moment'; import { FileStatusWrapper } from '../screens/file/model/file-status.wrapper'; +import { ProjectWrapper } from './model/project.wrapper'; export interface AppState { projects: ProjectWrapper[]; @@ -33,73 +33,6 @@ export interface AppState { ruleVersion?: number; } -export class ProjectWrapper { - totalNumberOfPages?: number; - - hasHints?: boolean; - hasRedactions?: boolean; - hasRequests?: boolean; - - allFilesApproved?: boolean; - - private _files: FileStatusWrapper[]; - - constructor(public project: Project, files: FileStatusWrapper[]) { - this._files = files ? files : []; - this._recomputeFileStatus(); - } - - set files(files: FileStatusWrapper[]) { - this._files = files ? files : []; - this._recomputeFileStatus(); - } - - get files() { - return this._files; - } - - get projectDate() { - return this.project.date; - } - - get dueDate() { - return this.project.dueDate; - } - - get hasFiles() { - return this._files.length > 0; - } - - hasStatus(status: string) { - return this._files.find((f) => f.status === status); - } - - hasMember(key: string) { - return this.project.memberIds.indexOf(key) >= 0; - } - - dueDateMatches(key: string) { - return moment(this.dueDate).format('DD/MM/YYYY') === key; - } - - addedDateMatches(key: string) { - return moment(this.projectDate).format('DD/MM/YYYY') === key; - } - - private _recomputeFileStatus() { - this.hasHints = false; - this.hasRedactions = false; - this.hasRequests = false; - this.allFilesApproved = true; - this._files.forEach((f) => { - this.hasHints = this.hasHints || f.hasHints; - this.hasRedactions = this.hasRedactions || f.hasRedactions; - this.hasRequests = this.hasRequests || f.hasRequests; - this.allFilesApproved = this.allFilesApproved && f.isApproved; - }); - } -} - @Injectable({ providedIn: 'root' }) @@ -161,34 +94,19 @@ export class AppStateService { return this._appState.ruleVersion; } - get isActiveProjectOwner() { - return this._appState.activeProject?.project?.ownerId === this._userService.userId; - } - - get isActiveProjectOwnerAndManager() { - return ( - this._appState.activeProject?.project?.ownerId === this._userService.userId && - this._userService.isManager(this._userService.user) - ); - } - - get isActiveProjectMember() { - return this._appState.activeProject?.project?.memberIds?.includes(this._userService.userId); - } - get dictionaryData() { return this._dictionaryData; } getViewedPagesForActiveFile() { - if (this.canMarkPagesAsViewedForActiveFile) { - return this._viewedPagesControllerService.getViewedPages( - this.activeProjectId, - this.activeFileId - ); - } else { - return of({ pages: [] }); + return this._viewedPagesControllerService.getViewedPages(this.activeProjectId, this.activeFileId); + } + + reanalyzeProject(project?: Project) { + if (!project) { + project = this.activeProject.project; } + return this._reanalysisControllerService.reanalyzeProject(project.projectId); } getDictionaryColor(type: string) { @@ -200,10 +118,6 @@ export class AppStateService { return this._dictionaryData[type].label; } - get isActiveFileDocumentReviewer() { - return this._appState.activeFile?.currentReviewer === this._userService.userId; - } - get aggregatedFiles(): FileStatusWrapper[] { const result: FileStatusWrapper[] = []; this._appState.projects.forEach((p) => { @@ -248,10 +162,6 @@ export class AppStateService { return this._appState.totalDocuments; } - get canMarkPagesAsViewedForActiveFile() { - return this.canPerformAnnotationActionsOnCurrentFile(); - } - public getProjectById(id: string) { return this.allProjects.find((project) => project.project.projectId === id); } @@ -277,9 +187,7 @@ export class AppStateService { } private _getExistingFiles(project: Project) { - const found = this._appState.projects.find( - (p) => p.project.projectId === project.projectId - ); + const found = this._appState.projects.find((p) => p.project.projectId === project.projectId); return found ? found.files : []; } @@ -287,9 +195,7 @@ export class AppStateService { if (!project) { project = this.activeProject; } - const files = await this._statusControllerService - .getProjectStatus(project.project.projectId) - .toPromise(); + const files = await this._statusControllerService.getProjectStatus(project.project.projectId).toPromise(); const oldFiles = [...project.files]; const fileStatusChangedEvent = []; @@ -301,10 +207,7 @@ export class AppStateService { if (oldFile.fileId === file.fileId) { // emit when analysis count changed if (oldFile.lastUpdated !== file.lastUpdated) { - const fileStatusWrapper = new FileStatusWrapper( - file, - this._userService.getNameForId(file.currentReviewer) - ); + const fileStatusWrapper = new FileStatusWrapper(file, this._userService.getNameForId(file.currentReviewer)); fileStatusChangedEvent.push(fileStatusWrapper); if (oldFile.lastProcessed !== file.lastProcessed) { @@ -317,17 +220,12 @@ export class AppStateService { } // emit for new file if (!found) { - const fsw = new FileStatusWrapper( - file, - this._userService.getNameForId(file.currentReviewer) - ); + const fsw = new FileStatusWrapper(file, this._userService.getNameForId(file.currentReviewer)); fileStatusChangedEvent.push(fsw); } } - project.files = files.map( - (f) => new FileStatusWrapper(f, this._userService.getNameForId(f.currentReviewer)) - ); + project.files = files.map((f) => new FileStatusWrapper(f, this._userService.getNameForId(f.currentReviewer))); this._computeStats(); @@ -348,18 +246,14 @@ export class AppStateService { if (!project) { project = this.activeProject.project; } - this._fileUploadControllerService - .downloadRedactionReportForProject(project.projectId, true, 'response') - .subscribe((data) => { - download(data, 'redaction-report-' + project.projectName + '.docx'); - }); + this._fileUploadControllerService.downloadRedactionReportForProject(project.projectId, true, 'response').subscribe((data) => { + download(data, 'redaction-report-' + project.projectName + '.docx'); + }); } activateProject(projectId: string) { this._appState.activeFile = null; - this._appState.activeProject = this._appState.projects.find( - (p) => p.project.projectId === projectId - ); + this._appState.activeProject = this._appState.projects.find((p) => p.project.projectId === projectId); if (!this._appState.activeProject) { this._router.navigate(['/ui/projects']); } @@ -368,12 +262,8 @@ export class AppStateService { activateFile(projectId: string, fileId: string) { this._appState.activeFile = null; - this._appState.activeProject = this._appState.projects.find( - (p) => p.project.projectId === projectId - ); - this._appState.activeFile = this._appState.activeProject.files.find( - (f) => f.fileId === fileId - ); + this._appState.activeProject = this._appState.projects.find((p) => p.project.projectId === projectId); + this._appState.activeFile = this._appState.activeProject.files.find((f) => f.fileId === fileId); } reset() { @@ -387,9 +277,7 @@ export class AppStateService { .toPromise() .then( () => { - const index = this._appState.projects.findIndex( - (p) => p.project.projectId === project.projectId - ); + const index = this._appState.projects.findIndex((p) => p.project.projectId === project.projectId); this._appState.projects.splice(index, 1); this._appState.projects = [...this._appState.projects]; }, @@ -405,12 +293,8 @@ export class AppStateService { async addOrUpdateProject(project: Project) { try { - const updatedProject = await this._projectControllerService - .createProjectOrUpdateProject(project) - .toPromise(); - let foundProject = this._appState.projects.find( - (p) => p.project.projectId === updatedProject.projectId - ); + const updatedProject = await this._projectControllerService.createProjectOrUpdateProject(project).toPromise(); + let foundProject = this._appState.projects.find((p) => p.project.projectId === updatedProject.projectId); if (foundProject) { Object.assign(foundProject.project, updatedProject); } else { @@ -466,25 +350,13 @@ export class AppStateService { } } - async reanalyseActiveFile() { - await this._reanalysisControllerService - .reanalyzeFile( - this._appState.activeProject.project.projectId, - this._appState.activeFile.fileId - ) - .toPromise(); - await this.reloadActiveProjectFiles(); - } - downloadFileRedactionReport(file?: FileStatusWrapper) { if (!file) { file = this.activeFile; } - this._fileUploadControllerService - .downloadRedactionReport({ fileIds: [file.fileId] }, true, 'response') - .subscribe((data) => { - download(data, 'redaction-report-' + file.filename + '.docx'); - }); + this._fileUploadControllerService.downloadRedactionReport({ fileIds: [file.fileId] }, true, 'response').subscribe((data) => { + download(data, 'redaction-report-' + file.filename + '.docx'); + }); } async loadDictionaryDataIfNecessary() { @@ -527,7 +399,7 @@ export class AppStateService { }) ); - const result = await forkJoin([typeObs, colorsObs]).toPromise(); + await forkJoin([typeObs, colorsObs]).toPromise(); this._dictionaryData['hint'] = { hexColor: '#283241', type: 'hint', virtual: true }; this._dictionaryData['redaction'] = { @@ -548,19 +420,8 @@ export class AppStateService { return data ? data : this._dictionaryData['default']; } - isManagerAndOwner(project: Project, user?: UserWrapper) { - if (!user) { - user = this._userService.user; - } - return user.isManager && project.ownerId === user.id; - } - getDictionaryTypeValueForAnnotation(annotation: AnnotationWrapper) { - if ( - annotation.superType === 'suggestion' || - annotation.superType === 'ignore' || - annotation.superType === 'suggestion-remove' - ) { + if (annotation.superType === 'suggestion' || annotation.superType === 'ignore' || annotation.superType === 'suggestion-remove') { return this._dictionaryData[annotation.superType]; } if (annotation.superType === 'redaction' || annotation.superType === 'hint') { @@ -568,56 +429,19 @@ export class AppStateService { } } - async updateDictionaryVersion() { - const result = await this._versionsControllerService.getVersions().toPromise(); - this._appState.dictionaryVersion = result.dictionaryVersion; - this._appState.ruleVersion = result.rulesVersion; - } - - isReviewerOrOwner(fileStatus: FileStatusWrapper) { - return ( - fileStatus.currentReviewer === this._userService.userId || - this.isActiveProjectOwnerAndManager - ); - } - - canReanalyseFile(fileStatus?: FileStatusWrapper) { - if (!fileStatus) { - fileStatus = this.activeFile; - } - return ( - (!fileStatus.isApproved && this.fileNotUpToDateWithDictionary(fileStatus)) || - fileStatus.isError || - fileStatus.hasRequests - ); - } - fileNotUpToDateWithDictionary(fileStatus?: FileStatusWrapper) { if (!fileStatus) { fileStatus = this.activeFile; } return ( - (fileStatus.status === 'UNASSIGNED' || - fileStatus.status === 'UNDER_REVIEW' || - fileStatus.status === 'UNDER_APPROVAL') && - (fileStatus.dictionaryVersion !== this.dictionaryVersion || - fileStatus.rulesVersion !== this.rulesVersion) + (fileStatus.status === 'UNASSIGNED' || fileStatus.status === 'UNDER_REVIEW' || fileStatus.status === 'UNDER_APPROVAL') && + (fileStatus.dictionaryVersion !== this.dictionaryVersion || fileStatus.rulesVersion !== this.rulesVersion) ); } - canPerformAnnotationActionsOnCurrentFile() { - // const status = this.activeFile.status; - // if (status === 'UNDER_REVIEW') { - // return this.isActiveProjectOwnerAndManager || this.isActiveFileDocumentReviewer; - // } - // if (status === 'UNDER_APPROVAL') { - // return this.isActiveProjectOwnerAndManager; - // } - // return false; - return ( - (this.activeFile.status === 'UNDER_APPROVAL' || - this.activeFile.status === 'UNDER_REVIEW') && - this._userService.userId === this.activeFile.currentReviewer - ); + async updateDictionaryVersion() { + const result = await this._versionsControllerService.getVersions().toPromise(); + this._appState.dictionaryVersion = result.dictionaryVersion; + this._appState.ruleVersion = result.rulesVersion; } } diff --git a/apps/red-ui/src/app/state/model/project.wrapper.ts b/apps/red-ui/src/app/state/model/project.wrapper.ts new file mode 100644 index 000000000..5f8374161 --- /dev/null +++ b/apps/red-ui/src/app/state/model/project.wrapper.ts @@ -0,0 +1,74 @@ +import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper'; +import * as moment from 'moment'; +import { Project } from '@redaction/red-ui-http'; + +export class ProjectWrapper { + totalNumberOfPages?: number; + + hintsOnly?: boolean; + hasRedactions?: boolean; + hasRequests?: boolean; + + allFilesApproved?: boolean; + + private _files: FileStatusWrapper[]; + + constructor(public project: Project, files: FileStatusWrapper[]) { + this._files = files ? files : []; + this._recomputeFileStatus(); + } + + set files(files: FileStatusWrapper[]) { + this._files = files ? files : []; + this._recomputeFileStatus(); + } + + get files() { + return this._files; + } + + get projectDate() { + return this.project.date; + } + + get numberOfMembers() { + return this.project.memberIds.length; + } + + get dueDate() { + return this.project.dueDate; + } + + get hasFiles() { + return this._files.length > 0; + } + + hasStatus(status: string) { + return this._files.find((f) => f.status === status); + } + + hasMember(key: string) { + return this.project.memberIds.indexOf(key) >= 0; + } + + dueDateMatches(key: string) { + return moment(this.dueDate).format('DD/MM/YYYY') === key; + } + + addedDateMatches(key: string) { + return moment(this.projectDate).format('DD/MM/YYYY') === key; + } + + private _recomputeFileStatus() { + this.hintsOnly = false; + this.hasRedactions = false; + this.hasRequests = false; + this.allFilesApproved = true; + this._files.forEach((f) => { + this.hintsOnly = this.hintsOnly || f.hintsOnly; + this.hasRedactions = this.hasRedactions || f.hasRedactions; + this.hasRequests = this.hasRequests || f.hasRequests; + this.allFilesApproved = this.allFilesApproved && f.isApproved; + }); + } +} diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index ae9f74891..4204989e2 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -337,7 +337,7 @@ "number-of-analyses": "Number of analyses", "custom": "Custom" }, - "readonly-pill": "Readonly", + "readonly-pill": "Read-only", "group": { "redactions": "Redaction Dictionaries", "hints": "Hint Dictionaries"