refactor file status

This commit is contained in:
Dan Percic 2021-08-11 19:54:16 +03:00
parent 86a84393b5
commit d8e758ff14
20 changed files with 142 additions and 297 deletions

View File

@ -1,23 +1,55 @@
import { FileAttributesConfig, FileStatus } from '@redaction/red-ui-http'; import { FileAttributesConfig, FileStatus } from '@redaction/red-ui-http';
import { StatusSorter } from '@utils/sorters/status-sorter'; import { StatusSorter } from '@utils/sorters/status-sorter';
export class FileStatusWrapper { const processingStatuses = [
FileStatus.StatusEnum.REPROCESS,
FileStatus.StatusEnum.FULLREPROCESS,
FileStatus.StatusEnum.OCRPROCESSING,
FileStatus.StatusEnum.INDEXING,
FileStatus.StatusEnum.PROCESSING
] as const;
export class FileStatusWrapper implements FileStatus {
readonly added = this.fileStatus.added;
readonly allManualRedactionsApplied = this.fileStatus.allManualRedactionsApplied;
readonly analysisDuration = this.fileStatus.analysisDuration;
readonly analysisRequired = this.fileStatus.analysisRequired && !this.fileStatus.excluded;
readonly approvalDate = this.fileStatus.approvalDate;
currentReviewer = this.fileStatus.currentReviewer;
readonly dictionaryVersion = this.fileStatus.dictionaryVersion;
readonly dossierDictionaryVersion = this.fileStatus.dossierDictionaryVersion;
readonly dossierId = this.fileStatus.dossierId;
readonly excluded = this.fileStatus.excluded;
readonly fileAttributes = this.fileStatus.fileAttributes;
readonly fileId = this.fileStatus.fileId;
readonly filename = this.fileStatus.filename;
readonly hasAnnotationComments = this.fileStatus.hasAnnotationComments;
readonly hasHints = this.fileStatus.hasHints;
readonly hasImages = this.fileStatus.hasImages;
readonly hasRedactions = this.fileStatus.hasRedactions;
readonly hasRequests = this.fileStatus.hasRequests;
readonly hasUpdates = this.fileStatus.hasUpdates;
readonly lastOCRTime = this.fileStatus.lastOCRTime;
readonly lastProcessed = this.fileStatus.lastProcessed;
readonly lastReviewer = this.fileStatus.lastReviewer;
readonly lastUpdated = this.fileStatus.lastUpdated;
readonly lastUploaded = this.fileStatus.lastUploaded;
readonly legalBasisVersion = this.fileStatus.legalBasisVersion;
readonly numberOfAnalyses = this.fileStatus.numberOfAnalyses;
readonly numberOfPages = this.fileStatus.numberOfPages;
readonly rulesVersion = this.fileStatus.rulesVersion;
readonly status = this._status;
readonly uploader = this.fileStatus.uploader;
readonly excludedPages = this.fileStatus.excludedPages;
primaryAttribute: string; primaryAttribute: string;
searchField: string;
constructor(
public fileStatus: FileStatus,
public reviewerName: string,
public dossierTemplateId: string,
fileAttributesConfig?: FileAttributesConfig
) {
this.searchField = fileStatus.filename;
constructor(readonly fileStatus: FileStatus, public reviewerName: string, fileAttributesConfig?: FileAttributesConfig) {
if (fileAttributesConfig) { if (fileAttributesConfig) {
const primary = fileAttributesConfig.fileAttributeConfigs?.find(c => c.primaryAttribute); const primary = fileAttributesConfig.fileAttributeConfigs?.find(c => c.primaryAttribute);
if (primary && fileStatus.fileAttributes?.attributeIdToValue) { if (primary && fileStatus.fileAttributes?.attributeIdToValue) {
this.primaryAttribute = fileStatus.fileAttributes?.attributeIdToValue[primary.id]; this.primaryAttribute = fileStatus.fileAttributes?.attributeIdToValue[primary.id];
this.searchField += ' ' + this.primaryAttribute; this.filename += ' ' + this.primaryAttribute;
} }
if (!this.primaryAttribute) { if (!this.primaryAttribute) {
@ -27,178 +59,40 @@ export class FileStatusWrapper {
} }
} }
get analysisDuration() { readonly excludedPagesCount = this.excludedPages?.length ?? 0;
return this.fileStatus.analysisDuration; readonly statusSort = StatusSorter[this.status];
readonly pages = this._pages;
readonly cacheIdentifier = btoa(this.lastUploaded + this.lastOCRTime);
readonly hasUnappliedSuggestions = !this.allManualRedactionsApplied;
readonly hintsOnly = this.hasHints && !this.hasRedactions;
readonly hasNone = !this.hasRedactions && !this.hasHints && !this.hasRequests;
readonly isError = this.status === FileStatus.StatusEnum.ERROR;
readonly isProcessing = processingStatuses.includes(this.status);
readonly isApproved = this.status === FileStatus.StatusEnum.APPROVED;
readonly isPending = this.status === FileStatus.StatusEnum.UNPROCESSED;
readonly isUnderReview = this.status === FileStatus.StatusEnum.UNDERREVIEW;
readonly isUnderApproval = this.status === FileStatus.StatusEnum.UNDERAPPROVAL;
readonly canBeApproved = !this.analysisRequired && !this.hasRequests;
readonly canBeOpened = !this.isError && !this.isPending;
readonly isWorkable = !this.isProcessing && this.canBeOpened;
readonly canBeOCRed = !this.excluded && !this.lastOCRTime && ['UNASSIGNED', 'UNDER_REVIEW', 'UNDER_APPROVAL'].includes(this.status);
get isUnassigned() {
return !this.currentReviewer;
} }
get lastProcessed() { private get _pages() {
return this.fileStatus.lastProcessed;
}
get added() {
return this.fileStatus.added;
}
get lastUploaded() {
return this.fileStatus.lastUploaded;
}
get hasImages() {
return this.fileStatus.hasImages;
}
get hasUpdates() {
return this.fileStatus.hasUpdates && !this.hasRequests;
}
get hasUnappliedSuggestions() {
return !this.fileStatus.allManualRedactionsApplied;
}
get currentReviewer() {
return this.fileStatus.currentReviewer;
}
set currentReviewer(value: string) {
this.fileStatus.currentReviewer = value;
}
get fileId() {
return this.fileStatus.fileId;
}
get filename() {
return this.fileStatus.filename;
}
get hasAnnotationComments() {
// return this.fileStatus.hasAnnotationComments;
// TODO remove this once backend works properly
return false;
}
get ocrTime() {
return this.fileStatus.lastOCRTime;
}
get hasHints() {
return this.fileStatus.hasHints;
}
get hintsOnly() {
return this.fileStatus.hasHints && !this.fileStatus.hasRedactions;
}
get hasRedactions() {
return this.fileStatus.hasRedactions;
}
get hasRequests() {
return this.fileStatus.hasRequests || this.hasUnappliedSuggestions;
}
get hasNone() {
return !this.hasRedactions && !this.hasHints && !this.hasRequests;
}
get lastUpdated() {
return this.fileStatus.lastUpdated;
}
get numberOfAnalyses() {
return this.fileStatus.numberOfAnalyses;
}
get dossierId() {
return this.fileStatus.dossierId;
}
get isExcluded() {
return this.fileStatus.excluded;
}
get status() {
return this.fileStatus.status === 'REPROCESS' || this.fileStatus.status === 'FULLREPROCESS' ? 'PROCESSING' : this.fileStatus.status;
}
get numberOfPages() {
return this.fileStatus.numberOfPages;
}
get numberOfExcludedPages() {
return this.fileStatus.excludedPages?.length || 0;
}
get uploader() {
return this.fileStatus.uploader;
}
get isPending() {
return this.status === FileStatus.StatusEnum.UNPROCESSED;
}
get isProcessing() {
return [
FileStatus.StatusEnum.REPROCESS,
FileStatus.StatusEnum.FULLREPROCESS,
FileStatus.StatusEnum.OCRPROCESSING,
FileStatus.StatusEnum.INDEXING,
FileStatus.StatusEnum.PROCESSING
].includes(this.status);
}
get analysisRequired() {
return this.fileStatus.analysisRequired && !this.fileStatus.excluded;
}
get statusSort() {
return StatusSorter[this.status];
}
get isWorkable() {
return !this.isProcessing && !this.isPending && !this.isError;
}
get isApproved() {
return this.fileStatus.status === 'APPROVED';
}
get isError() {
return this.fileStatus.status === 'ERROR';
}
get pages() {
if (this.fileStatus.status === 'ERROR') { if (this.fileStatus.status === 'ERROR') {
return -1; return -1;
} }
return this.fileStatus.numberOfPages ? this.fileStatus.numberOfPages : 0; return this.fileStatus.numberOfPages ? this.fileStatus.numberOfPages : 0;
} }
get isApprovedOrUnderApproval() { private get _status(): FileStatus.StatusEnum {
return this.status === 'APPROVED' || this.status === 'UNDER_APPROVAL'; return this.fileStatus.status === FileStatus.StatusEnum.REPROCESS || this.fileStatus.status === FileStatus.StatusEnum.FULLREPROCESS
} ? FileStatus.StatusEnum.PROCESSING
: this.fileStatus.status;
get isUnassigned() {
return !this.currentReviewer;
}
get isUnderReview() {
return this.fileStatus.status === 'UNDER_REVIEW';
}
get isUnderApproval() {
return this.fileStatus.status === 'UNDER_APPROVAL';
}
get canApprove() {
return this.status === 'UNDER_REVIEW' || this.status === 'UNDER_APPROVAL';
}
get cacheIdentifier() {
return btoa(this.fileStatus.lastUploaded + this.fileStatus.lastOCRTime);
}
get excludedPages(): number[] {
return this.fileStatus.excludedPages;
} }
} }

View File

@ -1,7 +1,7 @@
<div class="action-buttons"> <div class="action-buttons">
<iqser-circle-button <iqser-circle-button
(action)="openDeleteDossierTemplateDialog($event)" (action)="openDeleteDossierTemplateDialog($event)"
*ngIf="permissionsService.isAdmin()" *ngIf="currentUser.isAdmin"
[tooltip]="'dossier-templates-listing.action.delete' | translate" [tooltip]="'dossier-templates-listing.action.delete' | translate"
icon="red:trash" icon="red:trash"
[type]="circleButtonTypes.dark" [type]="circleButtonTypes.dark"
@ -9,7 +9,7 @@
<iqser-circle-button <iqser-circle-button
(action)="openEditDossierTemplateDialog($event)" (action)="openEditDossierTemplateDialog($event)"
*ngIf="permissionsService.isAdmin()" *ngIf="currentUser.isAdmin"
[tooltip]="'dossier-templates-listing.action.edit' | translate" [tooltip]="'dossier-templates-listing.action.edit' | translate"
icon="red:edit" icon="red:edit"
[type]="circleButtonTypes.dark" [type]="circleButtonTypes.dark"

View File

@ -1,34 +1,35 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { AdminDialogService } from '../../services/admin-dialog.service'; import { AdminDialogService } from '../../services/admin-dialog.service';
import { DossierTemplateControllerService } from '@redaction/red-ui-http'; import { DossierTemplateControllerService } from '@redaction/red-ui-http';
import { LoadingService } from '@services/loading.service'; import { LoadingService } from '@services/loading.service';
import { CircleButtonTypes } from '@iqser/common-ui'; import { CircleButtonTypes } from '@iqser/common-ui';
import { UserService } from '@services/user.service';
@Component({ @Component({
selector: 'redaction-dossier-template-actions', selector: 'redaction-dossier-template-actions',
templateUrl: './dossier-template-actions.component.html', templateUrl: './dossier-template-actions.component.html',
styleUrls: ['./dossier-template-actions.component.scss'] styleUrls: ['./dossier-template-actions.component.scss']
}) })
export class DossierTemplateActionsComponent { export class DossierTemplateActionsComponent implements OnInit {
readonly circleButtonTypes = CircleButtonTypes; readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
@Input() dossierTemplateId: string; @Input() dossierTemplateId: string;
@Output() loadDossierTemplatesData = new EventEmitter<any>(); @Output() loadDossierTemplatesData = new EventEmitter<any>();
constructor( constructor(
private readonly _dialogService: AdminDialogService,
private readonly _appStateService: AppStateService,
private readonly _dossierTemplateControllerService: DossierTemplateControllerService,
private readonly _loadingService: LoadingService,
private readonly _router: Router, private readonly _router: Router,
readonly permissionsService: PermissionsService private readonly _userService: UserService,
) { private readonly _loadingService: LoadingService,
if (!this.dossierTemplateId) { private readonly _appStateService: AppStateService,
this.dossierTemplateId = this._appStateService.activeDossierTemplateId; private readonly _dialogService: AdminDialogService,
} private readonly _dossierTemplateControllerService: DossierTemplateControllerService
) {}
ngOnInit() {
this.dossierTemplateId ??= this._appStateService.activeDossierTemplateId;
} }
get dossierTemplate() { get dossierTemplate() {
@ -48,9 +49,7 @@ export class DossierTemplateActionsComponent {
await this._appStateService.loadAllDossierTemplates(); await this._appStateService.loadAllDossierTemplates();
await this._appStateService.loadDictionaryData(); await this._appStateService.loadDictionaryData();
await this._router.navigate(['main', 'admin']); await this._router.navigate(['main', 'admin']);
if (this.loadDossierTemplatesData) { this.loadDossierTemplatesData?.emit();
this.loadDossierTemplatesData.emit();
}
}); });
} }
} }

View File

@ -5,7 +5,7 @@
<div class="actions"> <div class="actions">
<iqser-circle-button <iqser-circle-button
(action)="openDeleteDictionaryDialog($event)" (action)="openDeleteDictionaryDialog($event)"
*ngIf="permissionsService.isAdmin()" *ngIf="currentUser.isAdmin"
[tooltip]="'dictionary-overview.action.delete' | translate" [tooltip]="'dictionary-overview.action.delete' | translate"
icon="red:trash" icon="red:trash"
tooltipPosition="below" tooltipPosition="below"
@ -14,7 +14,7 @@
<iqser-circle-button <iqser-circle-button
(action)="openEditDictionaryDialog($event)" (action)="openEditDictionaryDialog($event)"
*ngIf="permissionsService.isAdmin()" *ngIf="currentUser.isAdmin"
[tooltip]="'dictionary-overview.action.edit' | translate" [tooltip]="'dictionary-overview.action.edit' | translate"
icon="red:edit" icon="red:edit"
tooltipPosition="below" tooltipPosition="below"
@ -30,7 +30,7 @@
<iqser-circle-button <iqser-circle-button
(action)="fileInput.click()" (action)="fileInput.click()"
*ngIf="permissionsService.isAdmin()" *ngIf="currentUser.isAdmin"
[tooltip]="'dictionary-overview.action.upload' | translate" [tooltip]="'dictionary-overview.action.upload' | translate"
icon="red:upload" icon="red:upload"
tooltipPosition="below" tooltipPosition="below"
@ -56,7 +56,7 @@
<redaction-dictionary-manager <redaction-dictionary-manager
#dictionaryManager #dictionaryManager
(saveDictionary)="saveEntries($event)" (saveDictionary)="saveEntries($event)"
[canEdit]="permissionsService.isAdmin()" [canEdit]="currentUser.isAdmin"
[initialEntries]="entries" [initialEntries]="entries"
></redaction-dictionary-manager> ></redaction-dictionary-manager>

View File

@ -1,7 +1,6 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { DictionaryControllerService } from '@redaction/red-ui-http'; import { DictionaryControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
import { PermissionsService } from '@services/permissions.service';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
@ -12,6 +11,7 @@ import { DictionarySaveService } from '@shared/services/dictionary-save.service'
import { TypeValueWrapper } from '@models/file/type-value.wrapper'; import { TypeValueWrapper } from '@models/file/type-value.wrapper';
import { LoadingService } from '@services/loading.service'; import { LoadingService } from '@services/loading.service';
import { CircleButtonTypes } from '@iqser/common-ui'; import { CircleButtonTypes } from '@iqser/common-ui';
import { UserService } from '@services/user.service';
@Component({ @Component({
templateUrl: './dictionary-overview-screen.component.html', templateUrl: './dictionary-overview-screen.component.html',
@ -19,33 +19,32 @@ import { CircleButtonTypes } from '@iqser/common-ui';
}) })
export class DictionaryOverviewScreenComponent extends ComponentHasChanges implements OnInit { export class DictionaryOverviewScreenComponent extends ComponentHasChanges implements OnInit {
readonly circleButtonTypes = CircleButtonTypes; readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
entries: string[] = []; entries: string[] = [];
dictionary: TypeValueWrapper;
@ViewChild('dictionaryManager', { static: false }) @ViewChild('dictionaryManager', { static: false })
private readonly _dictionaryManager: DictionaryManagerComponent; private readonly _dictionaryManager: DictionaryManagerComponent;
@ViewChild('fileInput') private readonly _fileInput: ElementRef; @ViewChild('fileInput') private readonly _fileInput: ElementRef;
constructor( constructor(
readonly permissionsService: PermissionsService, private readonly _router: Router,
private readonly _userService: UserService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _loadingService: LoadingService,
private readonly _appStateService: AppStateService,
private readonly _dialogService: AdminDialogService,
protected readonly _translateService: TranslateService, protected readonly _translateService: TranslateService,
private readonly _dictionarySaveService: DictionarySaveService, private readonly _dictionarySaveService: DictionarySaveService,
private readonly _dictionaryControllerService: DictionaryControllerService, private readonly _dictionaryControllerService: DictionaryControllerService
private readonly _dialogService: AdminDialogService,
private readonly _router: Router,
private readonly _activatedRoute: ActivatedRoute,
private readonly _appStateService: AppStateService,
private readonly _loadingService: LoadingService
) { ) {
super(_translateService); super(_translateService);
this._appStateService.activateDictionary( this._appStateService.activateDictionary(
this._activatedRoute.snapshot.params.type, this._activatedRoute.snapshot.params.type,
this._activatedRoute.snapshot.params.dossierTemplateId this._activatedRoute.snapshot.params.dossierTemplateId
); );
} this.dictionary = this._appStateService.activeDictionary;
get dictionary(): TypeValueWrapper {
return this._appStateService.activeDictionary;
} }
get hasChanges() { get hasChanges() {
@ -67,6 +66,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
async () => { async () => {
this._loadingService.start(); this._loadingService.start();
await this._appStateService.loadDictionaryData(); await this._appStateService.loadDictionaryData();
this.dictionary = this._appStateService.activeDictionary;
this._loadingService.stop(); this._loadingService.stop();
} }
); );

View File

@ -89,7 +89,7 @@ export class DossierOverviewBulkActionsComponent {
} }
get canOcr() { get canOcr() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canOcrFile(file), true); return this.selectedFiles.reduce((acc, file) => acc && file.canBeOCRed, true);
} }
get fileStatuses() { get fileStatuses() {
@ -112,7 +112,7 @@ export class DossierOverviewBulkActionsComponent {
} }
get canApprove() { get canApprove() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canApprove(file), true); return this.selectedFiles.reduce((acc, file) => acc && file.canBeApproved, true);
} }
// Undo approval // Undo approval
@ -163,7 +163,7 @@ export class DossierOverviewBulkActionsComponent {
} }
async reanalyse() { async reanalyse() {
const fileIds = this.selectedFiles.filter(file => this._permissionsService.fileRequiresReanalysis(file)).map(file => file.fileId); const fileIds = this.selectedFiles.filter(file => file.analysisRequired).map(file => file.fileId);
this._performBulkAction( this._performBulkAction(
this._reanalysisControllerService.reanalyzeFilesForDossier(fileIds, this._appStateService.activeDossier.dossierId) this._reanalysisControllerService.reanalyzeFilesForDossier(fileIds, this._appStateService.activeDossier.dossierId)
); );

View File

@ -88,9 +88,11 @@
<iqser-circle-button <iqser-circle-button
(action)="setFileApproved($event)" (action)="setFileApproved($event)"
*ngIf="readyForApproval" *ngIf="readyForApproval"
[disabled]="!canApprove" [disabled]="!fileStatus.canBeApproved"
[tooltipPosition]="tooltipPosition" [tooltipPosition]="tooltipPosition"
[tooltip]="canApprove ? ('dossier-overview.approve' | translate) : ('dossier-overview.approve-disabled' | translate)" [tooltip]="
fileStatus.canBeApproved ? ('dossier-overview.approve' | translate) : ('dossier-overview.approve-disabled' | translate)
"
[type]="buttonType" [type]="buttonType"
icon="red:approved" icon="red:approved"
></iqser-circle-button> ></iqser-circle-button>
@ -107,7 +109,7 @@
<iqser-circle-button <iqser-circle-button
(action)="ocrFile($event)" (action)="ocrFile($event)"
*ngIf="canOcr" *ngIf="fileStatus.canBeOCRed"
[tooltipPosition]="tooltipPosition" [tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.ocr-file' | translate" [tooltip]="'dossier-overview.ocr-file' | translate"
[type]="buttonType" [type]="buttonType"
@ -140,7 +142,7 @@
<mat-slide-toggle <mat-slide-toggle
(change)="toggleAnalysis()" (change)="toggleAnalysis()"
(click)="$event.stopPropagation()" (click)="$event.stopPropagation()"
[checked]="!fileStatus?.isExcluded" [checked]="!fileStatus?.excluded"
[class.mr-24]="screen === 'dossier-overview'" [class.mr-24]="screen === 'dossier-overview'"
[disabled]="!canToggleAnalysis" [disabled]="!canToggleAnalysis"
[matTooltipPosition]="tooltipPosition" [matTooltipPosition]="tooltipPosition"

View File

@ -39,10 +39,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
canAssign: boolean; canAssign: boolean;
canDelete: boolean; canDelete: boolean;
canReanalyse: boolean; canReanalyse: boolean;
canOcr: boolean;
canSetToUnderReview: boolean; canSetToUnderReview: boolean;
canSetToUnderApproval: boolean; canSetToUnderApproval: boolean;
canApprove: boolean;
readyForApproval: boolean; readyForApproval: boolean;
canToggleAnalysis: boolean; canToggleAnalysis: boolean;
@ -63,7 +61,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
return _('file-preview.toggle-analysis.only-managers'); return _('file-preview.toggle-analysis.only-managers');
} }
return this.fileStatus?.isExcluded ? _('file-preview.toggle-analysis.enable') : _('file-preview.toggle-analysis.disable'); return this.fileStatus?.excluded ? _('file-preview.toggle-analysis.enable') : _('file-preview.toggle-analysis.disable');
} }
private _setup() { private _setup() {
@ -81,10 +79,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
this.canAssign = this.permissionsService.canAssignUser(this.fileStatus); this.canAssign = this.permissionsService.canAssignUser(this.fileStatus);
this.canDelete = this.permissionsService.canDeleteFile(this.fileStatus); this.canDelete = this.permissionsService.canDeleteFile(this.fileStatus);
this.canReanalyse = this.permissionsService.canReanalyseFile(this.fileStatus); this.canReanalyse = this.permissionsService.canReanalyseFile(this.fileStatus);
this.canOcr = this.permissionsService.canOcrFile(this.fileStatus);
this.canSetToUnderReview = this.permissionsService.canSetUnderReview(this.fileStatus); this.canSetToUnderReview = this.permissionsService.canSetUnderReview(this.fileStatus);
this.canSetToUnderApproval = this.permissionsService.canSetUnderApproval(this.fileStatus); this.canSetToUnderApproval = this.permissionsService.canSetUnderApproval(this.fileStatus);
this.canApprove = this.permissionsService.canApprove(this.fileStatus);
this.readyForApproval = this.permissionsService.isReadyForApproval(this.fileStatus); this.readyForApproval = this.permissionsService.isReadyForApproval(this.fileStatus);
this.canToggleAnalysis = this.permissionsService.canToggleAnalysis(this.fileStatus); this.canToggleAnalysis = this.permissionsService.canToggleAnalysis(this.fileStatus);
} }
@ -206,6 +202,6 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
async toggleAnalysis() { async toggleAnalysis() {
await this._fileActionService.toggleAnalysis(this.fileStatus).toPromise(); await this._fileActionService.toggleAnalysis(this.fileStatus).toPromise();
await this.appStateService.getFiles(); await this.appStateService.getFiles();
this.actionPerformed.emit(this.fileStatus?.isExcluded ? 'enable-analysis' : 'disable-analysis'); this.actionPerformed.emit(this.fileStatus?.excluded ? 'enable-analysis' : 'disable-analysis');
} }
} }

View File

@ -1,6 +1,5 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
import { PermissionsService } from '@services/permissions.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper'; import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { DossierWrapper } from '@state/model/dossier.wrapper'; import { DossierWrapper } from '@state/model/dossier.wrapper';
@ -12,7 +11,7 @@ import { DossierWrapper } from '@state/model/dossier.wrapper';
export class NeedsWorkBadgeComponent { export class NeedsWorkBadgeComponent {
@Input() needsWorkInput: FileStatusWrapper | DossierWrapper; @Input() needsWorkInput: FileStatusWrapper | DossierWrapper;
constructor(private readonly _appStateService: AppStateService, private readonly _permissionsService: PermissionsService) {} constructor(private readonly _appStateService: AppStateService) {}
get suggestionColor() { get suggestionColor() {
return this._getDictionaryColor('suggestion'); return this._getDictionaryColor('suggestion');
@ -52,9 +51,9 @@ export class NeedsWorkBadgeComponent {
reanalysisRequired() { reanalysisRequired() {
if (this.needsWorkInput instanceof DossierWrapper) { if (this.needsWorkInput instanceof DossierWrapper) {
return this._permissionsService.dossierReanalysisRequired(this.needsWorkInput); return this.needsWorkInput.reanalysisRequired;
} else { } else {
return this._permissionsService.fileRequiresReanalysis(this.needsWorkInput); return this.needsWorkInput.analysisRequired;
} }
} }

View File

@ -251,8 +251,7 @@ export class DossierListingScreenComponent
filterTemplate: this._needsWorkTemplate, filterTemplate: this._needsWorkTemplate,
filters: needsWorkFilters.sort(RedactionFilterSorter.byKey), filters: needsWorkFilters.sort(RedactionFilterSorter.byKey),
checker: annotationFilterChecker, checker: annotationFilterChecker,
matchAll: true, matchAll: true
checkerArgs: [this.permissionsService]
}); });
const dossierTemplateFilters = [...allDistinctDossierTemplates].map<NestedFilter>(id => ({ const dossierTemplateFilters = [...allDistinctDossierTemplates].map<NestedFilter>(id => ({

View File

@ -57,9 +57,9 @@
<cdk-virtual-scroll-viewport #scrollViewport [itemSize]="itemSize" redactionHasScrollbar> <cdk-virtual-scroll-viewport #scrollViewport [itemSize]="itemSize" redactionHasScrollbar>
<div <div
*cdkVirtualFor="let fileStatus of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" *cdkVirtualFor="let fileStatus of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
[class.disabled]="fileStatus.isExcluded" [class.disabled]="fileStatus.excluded"
[class.last-opened]="isLastOpenedFile(fileStatus.fileId)" [class.last-opened]="isLastOpenedFile(fileStatus.fileId)"
[class.pointer]="permissionsService.canOpenFile(fileStatus)" [class.pointer]="fileStatus.canBeOpened"
[routerLink]="fileLink(fileStatus)" [routerLink]="fileLink(fileStatus)"
class="table-item" class="table-item"
> >
@ -93,15 +93,15 @@
</div> </div>
<div> <div>
<mat-icon svgIcon="red:exclude-pages"></mat-icon> <mat-icon svgIcon="red:exclude-pages"></mat-icon>
{{ fileStatus.numberOfExcludedPages }} {{ fileStatus.excludedPagesCount }}
</div> </div>
<div <div
*ngIf="fileStatus.ocrTime" *ngIf="fileStatus.lastOCRTime"
[matTooltipPosition]="'above'" [matTooltipPosition]="'above'"
[matTooltip]="'dossier-overview.ocr-performed' | translate" [matTooltip]="'dossier-overview.ocr-performed' | translate"
> >
<mat-icon svgIcon="red:ocr"></mat-icon> <mat-icon svgIcon="red:ocr"></mat-icon>
{{ fileStatus.ocrTime | date: 'mediumDate' }} {{ fileStatus.lastOCRTime | date: 'mediumDate' }}
</div> </div>
</div> </div>
</div> </div>

View File

@ -144,7 +144,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
.subscribe(); .subscribe();
this.dossierAttributes = await this._dossierAttributesService.getValues(this.currentDossier); this.dossierAttributes = await this._dossierAttributesService.getValues(this.currentDossier);
this.searchService.setSearchKey('searchField'); this.searchService.setSearchKey('filename');
} catch (e) { } catch (e) {
} finally { } finally {
this._loadingService.stop(); this._loadingService.stop();
@ -210,9 +210,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
} }
fileLink(fileStatus: FileStatusWrapper) { fileLink(fileStatus: FileStatusWrapper) {
return this.permissionsService.canOpenFile(fileStatus) return fileStatus.canBeOpened ? [`/main/dossiers/${this.currentDossier.dossierId}/file/${fileStatus.fileId}`] : [];
? [`/main/dossiers/${this.currentDossier.dossierId}/file/${fileStatus.fileId}`]
: [];
} }
bulkActionPerformed() { bulkActionPerformed() {
@ -274,7 +272,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
allDistinctFileStatusWrapper.add(file.status); allDistinctFileStatusWrapper.add(file.status);
allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY')); allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY'));
if (this.permissionsService.fileRequiresReanalysis(file)) allDistinctNeedsWork.add('analysis'); if (file.analysisRequired) allDistinctNeedsWork.add('analysis');
if (file.hintsOnly) allDistinctNeedsWork.add('hint'); if (file.hintsOnly) allDistinctNeedsWork.add('hint');
if (file.hasRedactions) allDistinctNeedsWork.add('redaction'); if (file.hasRedactions) allDistinctNeedsWork.add('redaction');
if (file.hasRequests) allDistinctNeedsWork.add('suggestion'); if (file.hasRequests) allDistinctNeedsWork.add('suggestion');
@ -331,8 +329,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
filterTemplate: this._needsWorkTemplate, filterTemplate: this._needsWorkTemplate,
filters: needsWorkFilters.sort(RedactionFilterSorter.byKey), filters: needsWorkFilters.sort(RedactionFilterSorter.byKey),
checker: annotationFilterChecker, checker: annotationFilterChecker,
matchAll: true, matchAll: true
checkerArgs: [this.permissionsService]
}); });
this.filterService.addFilterGroup({ this.filterService.addFilterGroup({

View File

@ -30,7 +30,7 @@
</div> </div>
<div class="flex-1 actions-container"> <div class="flex-1 actions-container">
<ng-container *ngIf="!appStateService.activeFile.isExcluded"> <ng-container *ngIf="!appStateService.activeFile.excluded">
<ng-container *ngIf="!appStateService.activeFile.isProcessing"> <ng-container *ngIf="!appStateService.activeFile.isProcessing">
<iqser-status-bar [configs]="statusBarConfig" [small]="true"></iqser-status-bar> <iqser-status-bar [configs]="statusBarConfig" [small]="true"></iqser-status-bar>
@ -148,7 +148,7 @@
<div class="right-container"> <div class="right-container">
<redaction-empty-state <redaction-empty-state
*ngIf="appStateService.activeFile.isExcluded && !viewDocumentInfo && !excludePages" *ngIf="appStateService.activeFile.excluded && !viewDocumentInfo && !excludePages"
[horizontalPadding]="40" [horizontalPadding]="40"
[text]="'file-preview.tabs.is-excluded' | translate" [text]="'file-preview.tabs.is-excluded' | translate"
icon="red:needs-work" icon="red:needs-work"
@ -168,7 +168,7 @@
(selectAnnotations)="selectAnnotations($event)" (selectAnnotations)="selectAnnotations($event)"
(selectPage)="selectPage($event)" (selectPage)="selectPage($event)"
(toggleSkipped)="toggleSkipped($event)" (toggleSkipped)="toggleSkipped($event)"
*ngIf="!appStateService.activeFile.isExcluded" *ngIf="!appStateService.activeFile.excluded"
[(shouldDeselectAnnotationsOnPageChange)]="shouldDeselectAnnotationsOnPageChange" [(shouldDeselectAnnotationsOnPageChange)]="shouldDeselectAnnotationsOnPageChange"
[activeViewerPage]="activeViewerPage" [activeViewerPage]="activeViewerPage"
[annotationActionsTemplate]="annotationActionsTemplate" [annotationActionsTemplate]="annotationActionsTemplate"

View File

@ -131,11 +131,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
} }
get canSwitchToRedactedView() { get canSwitchToRedactedView() {
return this.fileData && !this.permissionsService.fileRequiresReanalysis() && !this.fileData.fileStatus.isExcluded; return this.fileData && !this.fileData.fileStatus.analysisRequired && !this.fileData.fileStatus.excluded;
} }
get canSwitchToDeltaView() { get canSwitchToDeltaView() {
return this.fileData?.redactionChangeLog?.redactionLogEntry?.length > 0 && !this.fileData.fileStatus.isExcluded; return this.fileData?.redactionChangeLog?.redactionLogEntry?.length > 0 && !this.fileData.fileStatus.excluded;
} }
get canAssign(): boolean { get canAssign(): boolean {
@ -231,7 +231,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
} }
async ngOnAttach(previousRoute: ActivatedRouteSnapshot) { async ngOnAttach(previousRoute: ActivatedRouteSnapshot) {
if (!this.permissionsService.canOpenFile(this.appStateService.activeFile)) { if (!this.appStateService.activeFile.canBeOpened) {
await this._router.navigate(['/main/dossiers/' + this.dossierId]); await this._router.navigate(['/main/dossiers/' + this.dossierId]);
return; return;
} }

View File

@ -38,7 +38,7 @@ export class FileActionService {
return this._reanalysisControllerService.toggleAnalysis( return this._reanalysisControllerService.toggleAnalysis(
fileStatusWrapper.dossierId, fileStatusWrapper.dossierId,
fileStatusWrapper.fileId, fileStatusWrapper.fileId,
!fileStatusWrapper.isExcluded !fileStatusWrapper.excluded
); );
} }

View File

@ -1,6 +1,5 @@
import { FileStatusWrapper } from '@models/file/file-status.wrapper'; import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { DossierWrapper } from '@state/model/dossier.wrapper'; import { DossierWrapper } from '@state/model/dossier.wrapper';
import { PermissionsService } from '@services/permissions.service';
import { handleCheckedValue, NestedFilter } from '@iqser/common-ui'; import { handleCheckedValue, NestedFilter } from '@iqser/common-ui';
export function handleFilterDelta(oldFilters: NestedFilter[], newFilters: NestedFilter[], allFilters: NestedFilter[]) { export function handleFilterDelta(oldFilters: NestedFilter[], newFilters: NestedFilter[], allFilters: NestedFilter[]) {
@ -46,17 +45,13 @@ export function handleFilterDelta(oldFilters: NestedFilter[], newFilters: Nested
}); });
} }
export const annotationFilterChecker = ( export const annotationFilterChecker = (input: FileStatusWrapper | DossierWrapper, filter: NestedFilter) => {
input: FileStatusWrapper | DossierWrapper,
filter: NestedFilter,
permissionsService: PermissionsService
) => {
switch (filter.key) { switch (filter.key) {
case 'analysis': { case 'analysis': {
if (input instanceof DossierWrapper) { if (input instanceof DossierWrapper) {
return permissionsService.dossierReanalysisRequired(input); return input.reanalysisRequired;
} else { } else {
return permissionsService.fileRequiresReanalysis(input); return input.analysisRequired;
} }
} }
case 'suggestion': { case 'suggestion': {

View File

@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
import { UserService, UserWrapper } from './user.service'; import { UserService } from './user.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper'; import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { Comment } from '@redaction/red-ui-http'; import { Comment } from '@redaction/red-ui-http';
import { DossierWrapper } from '@state/model/dossier.wrapper'; import { DossierWrapper } from '@state/model/dossier.wrapper';
@ -23,16 +23,8 @@ export class PermissionsService {
return this.isFileReviewer(fileStatus) || this.isApprover(); return this.isFileReviewer(fileStatus) || this.isApprover();
} }
dossierReanalysisRequired(dossier: DossierWrapper): boolean {
for (const file of dossier.files) if (this.fileRequiresReanalysis(file)) return true;
}
fileRequiresReanalysis(fileStatus = this._activeFile): boolean {
return fileStatus.analysisRequired;
}
displayReanalyseBtn(dossier = this._activeDossier): boolean { displayReanalyseBtn(dossier = this._activeDossier): boolean {
return this.isApprover(dossier) && dossier.files.filter(file => this.fileRequiresReanalysis(file)).length > 0; return this.isApprover(dossier) && dossier.files.filter(file => file.analysisRequired).length > 0;
} }
canToggleAnalysis(fileStatus: FileStatusWrapper): boolean { canToggleAnalysis(fileStatus: FileStatusWrapper): boolean {
@ -41,7 +33,7 @@ export class PermissionsService {
canReanalyseFile(fileStatus = this._activeFile): boolean { canReanalyseFile(fileStatus = this._activeFile): boolean {
return ( return (
(this.fileRequiresReanalysis(fileStatus) && (this.isReviewerOrApprover(fileStatus) || fileStatus.isUnassigned)) || (fileStatus.analysisRequired && (this.isReviewerOrApprover(fileStatus) || fileStatus.isUnassigned)) ||
(fileStatus.isError && fileStatus.isUnassigned) (fileStatus.isError && fileStatus.isUnassigned)
); );
} }
@ -54,10 +46,6 @@ export class PermissionsService {
return this.isOwner(dossier) || fileStatus.isUnassigned; return this.isOwner(dossier) || fileStatus.isUnassigned;
} }
isApprovedOrUnderApproval(fileStatus = this._activeFile): boolean {
return fileStatus.isApprovedOrUnderApproval;
}
canAssignToSelf(fileStatus = this._activeFile): boolean { canAssignToSelf(fileStatus = this._activeFile): boolean {
const precondition = this.isDossierMember() && !fileStatus.isProcessing && !fileStatus.isError && !fileStatus.isApproved; const precondition = this.isDossierMember() && !fileStatus.isProcessing && !fileStatus.isError && !fileStatus.isApproved;
@ -96,10 +84,6 @@ export class PermissionsService {
return this.canSetUnderReview(fileStatus); return this.canSetUnderReview(fileStatus);
} }
canApprove(fileStatus = this._activeFile): boolean {
return !fileStatus?.analysisRequired && !fileStatus.hasRequests;
}
canSetUnderApproval(fileStatus = this._activeFile): boolean { canSetUnderApproval(fileStatus = this._activeFile): boolean {
return fileStatus?.isUnderReview && this.isReviewerOrApprover(fileStatus); return fileStatus?.isUnderReview && this.isReviewerOrApprover(fileStatus);
} }
@ -124,18 +108,10 @@ export class PermissionsService {
return ['UNDER_REVIEW', 'UNDER_APPROVAL'].includes(fileStatus?.status) && this.isFileReviewer(fileStatus); return ['UNDER_REVIEW', 'UNDER_APPROVAL'].includes(fileStatus?.status) && this.isFileReviewer(fileStatus);
} }
canOpenFile(fileStatus = this._activeFile): boolean {
return !fileStatus?.isError && !fileStatus?.isPending;
}
canUndoApproval(fileStatus = this._activeFile): boolean { canUndoApproval(fileStatus = this._activeFile): boolean {
return fileStatus?.isApproved && this.isApprover(); return fileStatus?.isApproved && this.isApprover();
} }
canUndoUnderApproval(fileStatus = this._activeFile): boolean {
return fileStatus?.isUnderApproval && this.isDossierMember();
}
canMarkPagesAsViewed(fileStatus = this._activeFile): boolean { canMarkPagesAsViewed(fileStatus = this._activeFile): boolean {
return ['UNDER_REVIEW', 'UNDER_APPROVAL'].includes(fileStatus?.status) && this.isFileReviewer(fileStatus); return ['UNDER_REVIEW', 'UNDER_APPROVAL'].includes(fileStatus?.status) && this.isFileReviewer(fileStatus);
} }
@ -155,16 +131,6 @@ export class PermissionsService {
return user.isAdmin; return user.isAdmin;
} }
canOcrFile(fileStatus = this._activeFile): boolean {
return (
!fileStatus.isExcluded && !fileStatus.ocrTime && ['UNASSIGNED', 'UNDER_REVIEW', 'UNDER_APPROVAL'].includes(fileStatus.status)
);
}
canManageUsers(user: UserWrapper = this._userService.currentUser): boolean {
return user.isUserAdmin;
}
canAddComment(fileStatus = this._activeFile): boolean { canAddComment(fileStatus = this._activeFile): boolean {
return (this.isFileReviewer(fileStatus) || this.isApprover()) && !fileStatus.isApproved; return (this.isFileReviewer(fileStatus) || this.isApprover()) && !fileStatus.isApproved;
} }

View File

@ -243,7 +243,6 @@ export class AppStateService {
const activeFileWrapper = new FileStatusWrapper( const activeFileWrapper = new FileStatusWrapper(
activeFile, activeFile,
this._userService.getNameForId(activeFile.currentReviewer), this._userService.getNameForId(activeFile.currentReviewer),
this.activeDossierTemplateId,
this._appState.fileAttributesConfig[this.activeDossierTemplateId] this._appState.fileAttributesConfig[this.activeDossierTemplateId]
); );
this.activeDossier.files = this.activeDossier?.files.map(file => this.activeDossier.files = this.activeDossier?.files.map(file =>
@ -669,7 +668,6 @@ export class AppStateService {
const fileStatusWrapper = new FileStatusWrapper( const fileStatusWrapper = new FileStatusWrapper(
file, file,
this._userService.getNameForId(file.currentReviewer), this._userService.getNameForId(file.currentReviewer),
dossier.dossierTemplateId,
this._appState.fileAttributesConfig[dossier.dossierTemplateId] this._appState.fileAttributesConfig[dossier.dossierTemplateId]
); );
if (JSON.stringify(oldFile) !== JSON.stringify(fileStatusWrapper)) { if (JSON.stringify(oldFile) !== JSON.stringify(fileStatusWrapper)) {
@ -687,7 +685,6 @@ export class AppStateService {
const fsw = new FileStatusWrapper( const fsw = new FileStatusWrapper(
file, file,
this._userService.getNameForId(file.currentReviewer), this._userService.getNameForId(file.currentReviewer),
dossier.dossierTemplateId,
this._appState.fileAttributesConfig[dossier.dossierTemplateId] this._appState.fileAttributesConfig[dossier.dossierTemplateId]
); );
fileStatusChangedEvent.push(fsw); fileStatusChangedEvent.push(fsw);
@ -699,7 +696,6 @@ export class AppStateService {
new FileStatusWrapper( new FileStatusWrapper(
f, f,
this._userService.getNameForId(f.currentReviewer), this._userService.getNameForId(f.currentReviewer),
dossier.dossierTemplateId,
this._appState.fileAttributesConfig[dossier.dossierTemplateId] this._appState.fileAttributesConfig[dossier.dossierTemplateId]
) )
); );

View File

@ -24,6 +24,7 @@ export class DossierWrapper implements Dossier {
readonly hasMoreThanOneReviewer = this.memberIds.length > 1; readonly hasMoreThanOneReviewer = this.memberIds.length > 1;
readonly memberCount = this.memberIds.length; readonly memberCount = this.memberIds.length;
reanalysisRequired = this._files.some(file => file.analysisRequired);
hasFiles = this._files.length > 0; hasFiles = this._files.length > 0;
filesLength = this._files.length; filesLength = this._files.length;
@ -81,5 +82,6 @@ export class DossierWrapper implements Dossier {
this.hasNone = !this.hasRequests && !this.hasRedactions && !this.hintsOnly; this.hasNone = !this.hasRequests && !this.hasRedactions && !this.hintsOnly;
this.hasFiles = this._files.length > 0; this.hasFiles = this._files.length > 0;
this.filesLength = this._files.length; this.filesLength = this._files.length;
this.reanalysisRequired = this._files.some(file => file.analysisRequired);
} }
} }

View File

@ -169,5 +169,5 @@ export namespace FileStatus {
UNDERREVIEW: 'UNDER_REVIEW' as StatusEnum, UNDERREVIEW: 'UNDER_REVIEW' as StatusEnum,
UNPROCESSED: 'UNPROCESSED' as StatusEnum, UNPROCESSED: 'UNPROCESSED' as StatusEnum,
INDEXING: 'INDEXING' as StatusEnum INDEXING: 'INDEXING' as StatusEnum
}; } as const;
} }