From 97d956c11cf35a456ef19a5963834627afca4922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Tue, 1 Mar 2022 13:08:49 +0200 Subject: [PATCH] View archived files --- .../src/app/guards/dossier-files-guard.ts | 2 +- .../src/app/guards/file-preview.guard.ts | 11 ++-- .../modules/archive/archive-routing.module.ts | 13 +++- ...sign-reviewer-approver-dialog.component.ts | 23 +------ .../document-info-dialog.component.ts | 12 ++-- ...dit-dossier-deleted-documents.component.ts | 12 ++-- ...dossier-overview-bulk-actions.component.ts | 2 +- .../dossier-details-stats.component.ts | 2 +- ...sier-overview-screen-header.component.html | 1 + ...ossier-overview-screen-header.component.ts | 2 +- .../dossier-overview-screen.component.ts | 2 +- .../services/bulk-actions.service.ts | 54 +++------------- .../dossiers-listing-actions.component.html | 8 +-- .../dossiers-listing-actions.component.ts | 4 +- .../annotations-list.component.ts | 2 +- .../file-workload.component.html | 2 +- .../page-exclusion.component.ts | 7 ++- .../user-management.component.ts | 8 +-- .../file-preview-screen.component.ts | 10 ++- .../services/file-preview-state.service.ts | 29 +++++---- .../file-actions/file-actions.component.ts | 42 +++++++------ .../shared/services/file-assign.service.ts | 29 ++------- .../file-download-btn.component.ts | 2 +- .../expandable-file-actions.component.ts | 10 +-- .../services/file-upload.service.ts | 2 +- .../entity-services/dossiers.service.ts | 3 +- .../file-management.service.ts | 14 +++-- .../entity-services/files-map.service.ts | 6 +- .../services/entity-services/files.service.ts | 56 ++++++++++------- .../platform-search.service.ts | 10 +-- .../src/app/services/permissions.service.ts | 61 +++++++++++++------ .../src/app/services/reanalysis.service.ts | 41 ++++++++----- apps/red-ui/src/assets/config/config.json | 4 +- apps/red-ui/src/assets/i18n/en.json | 4 +- libs/common-ui | 2 +- .../src/lib/dossiers/dossier.model.ts | 4 +- libs/red-domain/src/lib/files/file.model.ts | 8 +-- 37 files changed, 260 insertions(+), 244 deletions(-) diff --git a/apps/red-ui/src/app/guards/dossier-files-guard.ts b/apps/red-ui/src/app/guards/dossier-files-guard.ts index 3594a6897..a438ea900 100644 --- a/apps/red-ui/src/app/guards/dossier-files-guard.ts +++ b/apps/red-ui/src/app/guards/dossier-files-guard.ts @@ -26,7 +26,7 @@ export class DossierFilesGuard implements CanActivate { } if (!this._filesMapService.has(dossierId)) { - await firstValueFrom(this._filesService.loadAll(dossierId)); + await firstValueFrom(this._filesService.loadAll(dossierId, dossiersService.routerPath)); } return true; } diff --git a/apps/red-ui/src/app/guards/file-preview.guard.ts b/apps/red-ui/src/app/guards/file-preview.guard.ts index 43feda4ae..9ad5cc95b 100644 --- a/apps/red-ui/src/app/guards/file-preview.guard.ts +++ b/apps/red-ui/src/app/guards/file-preview.guard.ts @@ -1,22 +1,25 @@ -import { Injectable } from '@angular/core'; +import { Injectable, Injector, ProviderToken } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; import { FilesMapService } from '@services/entity-services/files-map.service'; -import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service'; import { DOSSIER_ID, FILE_ID } from '@utils/constants'; +import { DossiersService } from '../services/entity-services/dossiers.service'; @Injectable({ providedIn: 'root' }) export class FilePreviewGuard implements CanActivate { constructor( private readonly _filesMapService: FilesMapService, - private readonly _activeDossiersService: ActiveDossiersService, private readonly _router: Router, + private readonly _injector: Injector, ) {} async canActivate(route: ActivatedRouteSnapshot): Promise { + const token: ProviderToken = route.data.dossiersService; + const dossiersService: DossiersService = this._injector.get(token); + const dossierId = route.paramMap.get(DOSSIER_ID); const fileId = route.paramMap.get(FILE_ID); - const dossier = this._activeDossiersService.find(dossierId); + const dossier = dossiersService.find(dossierId); if (!this._filesMapService.get(dossierId, fileId)) { await this._router.navigate([dossier.routerLink]); diff --git a/apps/red-ui/src/app/modules/archive/archive-routing.module.ts b/apps/red-ui/src/app/modules/archive/archive-routing.module.ts index 905da9c09..6c31c1026 100644 --- a/apps/red-ui/src/app/modules/archive/archive-routing.module.ts +++ b/apps/red-ui/src/app/modules/archive/archive-routing.module.ts @@ -2,10 +2,11 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { BreadcrumbTypes } from '@red/domain'; import { ArchivedDossiersScreenComponent } from './screens/archived-dossiers-screen/archived-dossiers-screen.component'; -import { DOSSIER_ID } from '@utils/constants'; +import { DOSSIER_ID, FILE_ID } from '@utils/constants'; import { CompositeRouteGuard } from '@iqser/common-ui'; import { ARCHIVED_DOSSIERS_SERVICE } from '../../tokens'; import { DossierFilesGuard } from '@guards/dossier-files-guard'; +import { FilePreviewGuard } from '../../guards/file-preview.guard'; const routes: Routes = [ { @@ -24,6 +25,16 @@ const routes: Routes = [ }, loadChildren: () => import('../dossier/screens/dossier-overview/dossier-overview.module').then(m => m.DossierOverviewModule), }, + { + path: `:${DOSSIER_ID}/file/:${FILE_ID}`, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [DossierFilesGuard, FilePreviewGuard], + breadcrumbs: [BreadcrumbTypes.archive, BreadcrumbTypes.dossier, BreadcrumbTypes.file], + dossiersService: ARCHIVED_DOSSIERS_SERVICE, + }, + loadChildren: () => import('../dossier/screens/file-preview-screen/file-preview.module').then(m => m.FilePreviewModule), + }, ]; @NgModule({ diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts index 7c7cab056..0bf590180 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts @@ -102,28 +102,11 @@ export class AssignReviewerApproverDialogComponent { this._loadingService.start(); try { if (!this.selectedUser) { - await firstValueFrom( - this._filesService.setUnassigned( - this.data.files.map(f => f.fileId), - this.dossier.id, - ), - ); + await firstValueFrom(this._filesService.setUnassigned(this.data.files, this.dossier.id)); } else if (this.data.mode === 'reviewer') { - await firstValueFrom( - this._filesService.setReviewerFor( - this.data.files.map(f => f.fileId), - this.dossier.id, - this.selectedUser, - ), - ); + await firstValueFrom(this._filesService.setReviewerFor(this.data.files, this.dossier.id, this.selectedUser)); } else { - await firstValueFrom( - this._filesService.setUnderApprovalFor( - this.data.files.map(f => f.fileId), - this.dossier.id, - this.selectedUser, - ), - ); + await firstValueFrom(this._filesService.setUnderApprovalFor(this.data.files, this.dossier.id, this.selectedUser)); } } catch (error) { this._toaster.error(_('error.http.generic'), { params: error }); diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.ts index 6c035d370..a51c29780 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.ts @@ -25,10 +25,10 @@ export class DocumentInfoDialogComponent extends BaseDialogComponent implements private readonly _filesService: FilesService, protected readonly _injector: Injector, protected readonly _dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) readonly data: File, + @Inject(MAT_DIALOG_DATA) readonly file: File, ) { super(_injector, _dialogRef); - this._dossier = this._activeDossiersService.find(this.data.dossierId); + this._dossier = this._activeDossiersService.find(this.file.dossierId); } async ngOnInit() { @@ -42,11 +42,11 @@ export class DocumentInfoDialogComponent extends BaseDialogComponent implements async save() { const attributeIdToValue = { - ...this.data.fileAttributes?.attributeIdToValue, + ...this.file.fileAttributes?.attributeIdToValue, ...this.form.getRawValue(), }; - await firstValueFrom(this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.data.dossierId, this.data.fileId)); - await firstValueFrom(this._filesService.reload(this.data.dossierId, this.data.fileId)); + await firstValueFrom(this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.file.dossierId, this.file.fileId)); + await firstValueFrom(this._filesService.reload(this.file.dossierId, this.file)); this._dialogRef.close(true); } @@ -55,7 +55,7 @@ export class DocumentInfoDialogComponent extends BaseDialogComponent implements this.attributes.reduce( (acc, attr) => ({ ...acc, - [attr.id]: [this.data.fileAttributes?.attributeIdToValue[attr.id]], + [attr.id]: [this.file.fileAttributes?.attributeIdToValue[attr.id]], }), {}, ), diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.ts index a9fd70641..6d1bb87d9 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.ts @@ -6,6 +6,7 @@ import { ConfirmationDialogInput, DefaultListingServices, IListable, + IRouterPath, ListingComponent, LoadingService, SortingOrders, @@ -25,7 +26,7 @@ import { workflowFileStatusTranslations } from '../../../translations/file-statu import { PermissionsService } from '@services/permissions.service'; import { UserService } from '@services/user.service'; -interface FileListItem extends IFile, IListable { +interface FileListItem extends IFile, IListable, IRouterPath { readonly canHardDelete: boolean; readonly canRestore: boolean; readonly restoreDate: string; @@ -129,14 +130,13 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent !file.canRestore; private async _restore(files: FileListItem[]): Promise { - const fileIds = files.map(f => f.fileId); - await firstValueFrom(this._fileManagementService.restore(fileIds, this.dossier.id)); + const fileIds = files.map(f => f.id); + await firstValueFrom(this._fileManagementService.restore(files, this.dossier.id)); this._removeFromList(fileIds); - await firstValueFrom(this._filesService.loadAll(files[0].dossierId)); } private async _hardDelete(files: FileListItem[]) { - const fileIds = files.map(f => f.fileId); + const fileIds = files.map(f => f.id); await firstValueFrom(this._fileManagementService.hardDelete(this.dossier.id, fileIds)); this._removeFromList(fileIds); } @@ -151,7 +151,7 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent acc && file.canBeOCRed, true); + this.#canOcr = this._permissionsService.canOcrFile(this.selectedFiles); this.#canSetToUnderReview = this._permissionsService.canSetUnderReview(this.selectedFiles) && !isWorkflow; diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.ts index bc8ef922a..8861fe27a 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.ts @@ -43,7 +43,7 @@ export class DossierDetailsStatsComponent implements OnInit { openEditDossierDialog(section: string): void { const data = { dossierId: this.dossier.dossierId, section }; this._dialogService.openDialog('editDossier', null, data, async () => { - await firstValueFrom(this._filesService.loadAll(this.dossier.dossierId)); + await firstValueFrom(this._filesService.loadAll(this.dossier.dossierId, this.dossier.routerPath)); }); } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/screen-header/dossier-overview-screen-header.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/screen-header/dossier-overview-screen-header.component.html index 57de5f509..2edb909ac 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/screen-header/dossier-overview-screen-header.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/screen-header/dossier-overview-screen-header.component.html @@ -14,6 +14,7 @@ imple this.addSubscription = this._dossiersService.dossierFileChanges$ .pipe( filter(dossierId => dossierId === this.dossierId), - switchMap(dossierId => this._filesService.loadAll(dossierId)), + switchMap(dossierId => this._filesService.loadAll(dossierId, this._dossiersService.routerPath)), ) .subscribe(); diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/services/bulk-actions.service.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/services/bulk-actions.service.ts index aeeaf658a..71ebccdd9 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/services/bulk-actions.service.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/services/bulk-actions.service.ts @@ -29,13 +29,7 @@ export class BulkActionsService { this._assignFiles(files, 'approver', true); } else { this._loadingService.start(); - await firstValueFrom( - this._filesService.setUnderApprovalFor( - files.map(f => f.id), - dossier.id, - dossier.approverIds[0], - ), - ); + await firstValueFrom(this._filesService.setUnderApprovalFor(files, dossier.id, dossier.approverIds[0])); this._loadingService.stop(); } } @@ -46,12 +40,7 @@ export class BulkActionsService { async ocr(files: File[]) { this._loadingService.start(); - await firstValueFrom( - this._reanalysisService.ocrFiles( - files.map(f => f.fileId), - files[0].dossierId, - ), - ); + await firstValueFrom(this._reanalysisService.ocrFiles(files, files[0].dossierId)); this._loadingService.stop(); } @@ -65,12 +54,7 @@ export class BulkActionsService { }), async () => { this._loadingService.start(); - await firstValueFrom( - this._fileManagementService.delete( - files.map(item => item.fileId), - files[0].dossierId, - ), - ); + await firstValueFrom(this._fileManagementService.delete(files, files[0].dossierId)); this._loadingService.stop(); }, ); @@ -78,9 +62,8 @@ export class BulkActionsService { async reanalyse(files: File[]) { this._loadingService.start(); - const fileIds = files.map(file => file.fileId); await firstValueFrom( - this._reanalysisService.reanalyzeFilesForDossier(fileIds, files[0].dossierId, { force: true, triggeredByUser: true }), + this._reanalysisService.reanalyzeFilesForDossier(files, files[0].dossierId, { force: true, triggeredByUser: true }), ); this._loadingService.stop(); } @@ -93,24 +76,13 @@ export class BulkActionsService { async toggleAnalysis(files: File[], excluded: boolean) { this._loadingService.start(); - await firstValueFrom( - this._reanalysisService.toggleAnalysis( - files[0].dossierId, - files.map(f => f.id), - excluded, - ), - ); + await firstValueFrom(this._reanalysisService.toggleAnalysis(files[0].dossierId, files, excluded)); this._loadingService.stop(); } async backToUnderReview(files: File[]): Promise { this._loadingService.start(); - await firstValueFrom( - this._filesService.setUnderReviewFor( - files.map(f => f.id), - files[0].dossierId, - ), - ); + await firstValueFrom(this._filesService.setUnderReviewFor(files, files[0].dossierId)); this._loadingService.stop(); } @@ -135,23 +107,13 @@ export class BulkActionsService { }), async () => { this._loadingService.start(); - await firstValueFrom( - this._filesService.setApprovedFor( - files.map(f => f.id), - files[0].dossierId, - ), - ); + await firstValueFrom(this._filesService.setApprovedFor(files, files[0].dossierId)); this._loadingService.stop(); }, ); } else { this._loadingService.start(); - await firstValueFrom( - this._filesService.setApprovedFor( - files.map(f => f.id), - files[0].dossierId, - ), - ); + await firstValueFrom(this._filesService.setApprovedFor(files, files[0].dossierId)); this._loadingService.stop(); } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.html index 4001358a5..b58f96883 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.html @@ -2,15 +2,15 @@ diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.ts index 4dae58539..512dfdc9d 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.ts @@ -56,8 +56,8 @@ export class DossiersListingActionsComponent implements OnChanges { this._dialogService.openDialog('editDossier', $event, { dossierId }); } - async reanalyseDossier($event: MouseEvent, id: string): Promise { + async reanalyseDossier($event: MouseEvent, dossier: Dossier): Promise { $event.stopPropagation(); - await firstValueFrom(this._reanalysisService.reanalyzeDossier(id)); + await firstValueFrom(this._reanalysisService.reanalyzeDossier(dossier)); } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.ts index cb29ddd3a..a1ef76c91 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotations-list/annotations-list.component.ts @@ -5,7 +5,7 @@ import { MultiSelectService } from '../../services/multi-select.service'; import { AnnotationReferencesService } from '../../services/annotation-references.service'; import { ViewModeService } from '../../services/view-mode.service'; import { FilePreviewStateService } from '../../services/file-preview-state.service'; -import { UserPreferenceService } from '../../../../../../services/user-preference.service'; +import { UserPreferenceService } from '@services/user-preference.service'; @Component({ selector: 'redaction-annotations-list', diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html index cc48bfa09..f2c3f2066 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html @@ -34,7 +34,7 @@ diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-exclusion/page-exclusion.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-exclusion/page-exclusion.component.ts index ef40421ca..c0d1a0e23 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-exclusion/page-exclusion.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-exclusion/page-exclusion.component.ts @@ -67,7 +67,7 @@ export class PageExclusionComponent { endPage, }; }); - const excludePages$ = this._reanalysisService.excludePages({ pageRanges }, this._state.dossierId, this._state.fileId); + const excludePages$ = this._reanalysisService.excludePages({ pageRanges }, file.dossierId, file); await firstValueFrom(excludePages$); this._inputComponent.reset(); } catch (e) { @@ -78,12 +78,13 @@ export class PageExclusionComponent { async includePagesRange(range: IPageRange): Promise { this._loadingService.start(); + const file = await this._state.file; const includePages$ = this._reanalysisService.includePages( { pageRanges: [range], }, - this._state.dossierId, - this._state.fileId, + file.dossierId, + file, ); await firstValueFrom(includePages$); this._inputComponent.reset(); diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.ts index 91e2c5eee..97df238a6 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.ts @@ -96,15 +96,15 @@ export class UserManagementComponent { const assigneeId = typeof user === 'string' ? user : user?.id; const reviewerName = this.userService.getNameForId(assigneeId); - const { dossierId, fileId, filename } = file; + const { dossierId, filename } = file; this.loadingService.start(); if (!assigneeId) { - await firstValueFrom(this.filesService.setUnassigned([fileId], dossierId)); + await firstValueFrom(this.filesService.setUnassigned([file], dossierId)); } else if (file.isNew || file.isUnderReview) { - await firstValueFrom(this.filesService.setReviewerFor([fileId], dossierId, assigneeId)); + await firstValueFrom(this.filesService.setReviewerFor([file], dossierId, assigneeId)); } else if (file.isUnderApproval) { - await firstValueFrom(this.filesService.setUnderApprovalFor([fileId], dossierId, assigneeId)); + await firstValueFrom(this.filesService.setUnderApprovalFor([file], dossierId, assigneeId)); } this.loadingService.stop(); diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts index 2df092289..5f6b2ba18 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts @@ -219,7 +219,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni const file = await this.stateService.file; if (file?.analysisRequired && !file.excludedFromAutomaticAnalysis) { - const reanalyzeFiles = this._reanalysisService.reanalyzeFilesForDossier([this.fileId], this.dossierId, { force: true }); + const reanalyzeFiles = this._reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true }); await firstValueFrom(reanalyzeFiles); } @@ -416,7 +416,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni async annotationsChangedByReviewAction(annotation?: AnnotationWrapper) { this.multiSelectService.deactivate(); - const fileReloaded = await firstValueFrom(this._filesService.reload(this.dossierId, this.fileId)); + const file = await this.stateService.file; + const fileReloaded = await firstValueFrom(this._filesService.reload(this.dossierId, file)); if (!fileReloaded) { await this._reloadAnnotationsForPage(annotation?.pageNumber ?? this.activeViewerPage); } @@ -541,7 +542,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni private _subscribeToFileUpdates(): void { this.addActiveScreenSubscription = timer(0, 5000) - .pipe(switchMap(() => this._filesService.reload(this.dossierId, this.fileId))) + .pipe( + switchMap(() => this.stateService.file$), + switchMap(file => this._filesService.reload(this.dossierId, file)), + ) .subscribe(); this.addActiveScreenSubscription = this._activeDossiersService diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/file-preview-state.service.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/file-preview-state.service.ts index d36846a7f..f6053b336 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/file-preview-state.service.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/file-preview-state.service.ts @@ -1,8 +1,7 @@ -import { Injectable } from '@angular/core'; +import { Injectable, Injector } from '@angular/core'; import { BehaviorSubject, firstValueFrom, Observable, pairwise, switchMap } from 'rxjs'; import { FileDataModel } from '@models/file/file-data.model'; import { Dossier, File } from '@red/domain'; -import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service'; import { ActivatedRoute } from '@angular/router'; import { FilesMapService } from '@services/entity-services/files-map.service'; import { PermissionsService } from '@services/permissions.service'; @@ -10,6 +9,8 @@ import { boolFactory, shareLast } from '@iqser/common-ui'; import { filter, startWith } from 'rxjs/operators'; import { FileManagementService } from '@services/entity-services/file-management.service'; import { DOSSIER_ID, FILE_ID } from '@utils/constants'; +import { DossiersService } from '@services/entity-services/dossiers.service'; +import { dossiersServiceResolver } from '@services/entity-services/dossiers.service.provider'; @Injectable() export class FilePreviewStateService { @@ -27,19 +28,19 @@ export class FilePreviewStateService { readonly #fileData$ = new BehaviorSubject(undefined); constructor( - activeDossiersService: ActiveDossiersService, - filesMapService: FilesMapService, - permissionsService: PermissionsService, - activatedRoute: ActivatedRoute, private readonly _fileManagementService: FileManagementService, + private readonly _injector: Injector, + private readonly _route: ActivatedRoute, + private readonly _filesMapService: FilesMapService, + private readonly _permissionsService: PermissionsService, ) { - this.fileId = activatedRoute.snapshot.paramMap.get(FILE_ID); - this.dossierId = activatedRoute.snapshot.paramMap.get(DOSSIER_ID); - this.dossierTemplateId = activeDossiersService.find(this.dossierId).dossierTemplateId; + this.fileId = _route.snapshot.paramMap.get(FILE_ID); + this.dossierId = _route.snapshot.paramMap.get(DOSSIER_ID); + this.dossierTemplateId = this._dossiersService.find(this.dossierId).dossierTemplateId; - this.dossier$ = activeDossiersService.getEntityChanged$(this.dossierId); - this.file$ = filesMapService.watch$(this.dossierId, this.fileId); - [this.isReadonly$, this.isWritable$] = boolFactory(this.file$, file => !permissionsService.canPerformAnnotationActions(file)); + this.dossier$ = this._dossiersService.getEntityChanged$(this.dossierId); + this.file$ = _filesMapService.watch$(this.dossierId, this.fileId); + [this.isReadonly$, this.isWritable$] = boolFactory(this.file$, file => !_permissionsService.canPerformAnnotationActions(file)); this.fileData$ = this.#fileData$.asObservable().pipe(filter(value => !!value)); this.blob$ = this.#blob$; @@ -65,6 +66,10 @@ export class FilePreviewStateService { return firstValueFrom(this.blob$); } + private get _dossiersService(): DossiersService { + return dossiersServiceResolver(this._injector); + } + get #blob$() { return this.file$.pipe( startWith(undefined), diff --git a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts index d1f53443b..3c1dcde3c 100644 --- a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts @@ -74,6 +74,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, showUnderApproval = false; showApprove = false; canToggleAnalysis = false; + showToggleAnalysis = false; showStatusBar = false; showOpenDocument = false; showReanalyseFilePreview = false; @@ -127,28 +128,28 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, return [ { type: ActionTypes.circleBtn, - action: $event => this._openDeleteFileDialog($event), + action: ($event: MouseEvent) => this._openDeleteFileDialog($event), tooltip: _('dossier-overview.delete.action'), icon: 'iqser:trash', show: this.showDelete, }, { type: ActionTypes.circleBtn, - action: $event => this._assign($event), + action: ($event: MouseEvent) => this._assign($event), tooltip: this.assignTooltip, icon: 'red:assign', show: this.showAssign, }, { type: ActionTypes.circleBtn, - action: $event => this._assignToMe($event), + action: ($event: MouseEvent) => this._assignToMe($event), tooltip: _('dossier-overview.assign-me'), icon: 'red:assign-me', show: this.showAssignToSelf, }, { type: ActionTypes.circleBtn, - action: $event => this._triggerImportRedactions($event), + action: ($event: MouseEvent) => this._triggerImportRedactions($event), tooltip: _('dossier-overview.import-redactions'), icon: 'iqser:upload', show: this.showImportRedactions, @@ -178,21 +179,21 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, }, { type: ActionTypes.circleBtn, - action: $event => this._setFileUnderApproval($event), + action: ($event: MouseEvent) => this._setFileUnderApproval($event), tooltip: _('dossier-overview.under-approval'), icon: 'red:ready-for-approval', show: this.showUnderApproval, }, { type: ActionTypes.circleBtn, - action: $event => this._setFileUnderReview($event), + action: ($event: MouseEvent) => this._setFileUnderReview($event), tooltip: _('dossier-overview.under-review'), icon: 'red:undo', show: this.showUnderReview, }, { type: ActionTypes.circleBtn, - action: $event => this.setFileApproved($event), + action: ($event: MouseEvent) => this.setFileApproved($event), tooltip: this.file.canBeApproved ? _('dossier-overview.approve') : _('dossier-overview.approve-disabled'), icon: 'red:approved', disabled: !this.file.canBeApproved, @@ -200,14 +201,14 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, }, { type: ActionTypes.circleBtn, - action: $event => this.toggleAutomaticAnalysis($event), + action: ($event: MouseEvent) => this.toggleAutomaticAnalysis($event), tooltip: _('dossier-overview.disable-auto-analysis'), icon: 'red:stop', show: this.canDisableAutoAnalysis, }, { type: ActionTypes.circleBtn, - action: $event => this._reanalyseFile($event), + action: ($event: MouseEvent) => this._reanalyseFile($event), tooltip: _('file-preview.reanalyse-notification'), tooltipClass: 'warn small', icon: 'iqser:refresh', @@ -215,7 +216,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, }, { type: ActionTypes.circleBtn, - action: $event => this.toggleAutomaticAnalysis($event), + action: ($event: MouseEvent) => this.toggleAutomaticAnalysis($event), tooltip: _('dossier-overview.enable-auto-analysis'), buttonType: this.isFilePreview ? CircleButtonTypes.warn : CircleButtonTypes.default, icon: 'red:play', @@ -223,21 +224,21 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, }, { type: ActionTypes.circleBtn, - action: $event => this._setFileUnderApproval($event), + action: ($event: MouseEvent) => this._setFileUnderApproval($event), tooltip: _('dossier-overview.under-approval'), icon: 'red:undo', show: this.showUndoApproval, }, { type: ActionTypes.circleBtn, - action: $event => this._ocrFile($event), + action: ($event: MouseEvent) => this._ocrFile($event), tooltip: _('dossier-overview.ocr-file'), icon: 'iqser:ocr', show: this.showOCR, }, { type: ActionTypes.circleBtn, - action: $event => this._reanalyseFile($event), + action: ($event: MouseEvent) => this._reanalyseFile($event), tooltip: _('dossier-overview.reanalyse.action'), icon: 'iqser:refresh', show: this.showReanalyseDossierOverview, @@ -249,7 +250,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, tooltip: this.toggleTooltip, class: { 'mr-24': this.isDossierOverviewList }, checked: !this.file.excluded, - show: true, + show: this.showToggleAnalysis, }, ].filter(btn => btn.show); } @@ -331,7 +332,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, this._loadingService.start(); try { const dossier = this._activeDossiersService.find(this.file.dossierId); - await firstValueFrom(this._fileManagementService.delete([this.file.fileId], this.file.dossierId)); + await firstValueFrom(this._fileManagementService.delete([this.file], this.file.dossierId)); await this._router.navigate([dossier.routerLink]); } catch (error) { this._toaster.error(_('error.http.generic'), { params: error }); @@ -360,7 +361,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, force: true, triggeredByUser: true, }; - await firstValueFrom(this._reanalysisService.reanalyzeFilesForDossier([this.file.fileId], this.file.dossierId, params)); + await firstValueFrom(this._reanalysisService.reanalyzeFilesForDossier([this.file], this.file.dossierId, params)); } private async toggleAutomaticAnalysis($event: MouseEvent) { @@ -378,7 +379,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, private async _ocrFile($event: MouseEvent) { $event.stopPropagation(); this._loadingService.start(); - await firstValueFrom(this._reanalysisService.ocrFiles([this.file.fileId], this.file.dossierId)); + await firstValueFrom(this._reanalysisService.ocrFiles([this.file], this.file.dossierId)); this._loadingService.stop(); } @@ -388,7 +389,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, private async _toggleAnalysis() { this._loadingService.start(); - await firstValueFrom(this._reanalysisService.toggleAnalysis(this.file.dossierId, [this.file.fileId], !this.file.excluded)); + await firstValueFrom(this._reanalysisService.toggleAnalysis(this.file.dossierId, [this.file], !this.file.excluded)); this._loadingService.stop(); } @@ -410,8 +411,9 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, this.showApprove = this._permissionsService.isReadyForApproval(this.file) && !this.isDossierOverviewWorkflow; this.canToggleAnalysis = this._permissionsService.canToggleAnalysis(this.file); + this.showToggleAnalysis = this._permissionsService.showToggleAnalysis(this.file); this.showDelete = this._permissionsService.canDeleteFile(this.file); - this.showOCR = this.file.canBeOCRed; + this.showOCR = this._permissionsService.canOcrFile(this.file); this.canReanalyse = this._permissionsService.canReanalyseFile(this.file); this.canDisableAutoAnalysis = this._permissionsService.canDisableAutoAnalysis([this.file]); this.canEnableAutoAnalysis = this._permissionsService.canEnableAutoAnalysis([this.file]); @@ -437,7 +439,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, private async _setFileApproved() { this._loadingService.start(); - await firstValueFrom(this._filesService.setApprovedFor([this.file.id], this.file.dossierId)); + await firstValueFrom(this._filesService.setApprovedFor([this.file], this.file.dossierId)); this._loadingService.stop(); } } diff --git a/apps/red-ui/src/app/modules/dossier/shared/services/file-assign.service.ts b/apps/red-ui/src/app/modules/dossier/shared/services/file-assign.service.ts index eac7696a8..bd4672134 100644 --- a/apps/red-ui/src/app/modules/dossier/shared/services/file-assign.service.ts +++ b/apps/red-ui/src/app/modules/dossier/shared/services/file-assign.service.ts @@ -81,28 +81,11 @@ export class FileAssignService { this._loadingService.start(); try { if (!userId) { - await firstValueFrom( - this._filesService.setUnassigned( - files.map(f => f.fileId), - files[0].dossierId, - ), - ); + await firstValueFrom(this._filesService.setUnassigned(files, files[0].dossierId)); } else if (mode === 'reviewer') { - await firstValueFrom( - this._filesService.setReviewerFor( - files.map(f => f.fileId), - files[0].dossierId, - userId, - ), - ); + await firstValueFrom(this._filesService.setReviewerFor(files, files[0].dossierId, userId)); } else { - await firstValueFrom( - this._filesService.setUnderApprovalFor( - files.map(f => f.fileId), - files[0].dossierId, - userId, - ), - ); + await firstValueFrom(this._filesService.setUnderApprovalFor(files, files[0].dossierId, userId)); } } catch (error) { this._toaster.error(_('error.http.generic'), { params: error }); @@ -117,11 +100,7 @@ export class FileAssignService { private _assignReviewerToCurrentUser(files: File[]): Observable { this._loadingService.start(); return this._filesService - .setReviewerFor( - files.map(f => f.fileId), - files[0].dossierId, - this._userService.currentUser.id, - ) + .setReviewerFor(files, files[0].dossierId, this._userService.currentUser.id) .pipe(tap(() => this._loadingService.stop())); } } diff --git a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts index 8009960da..0b595cdf7 100644 --- a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts @@ -38,7 +38,7 @@ export class FileDownloadBtnComponent implements OnChanges { async downloadFiles($event: MouseEvent) { $event.stopPropagation(); const dossierId = this.files[0].dossierId; - const filesIds = this.files.map(f => f.fileId); + const filesIds = this.files.map(f => f.id); await firstValueFrom(this._fileDownloadService.downloadFiles(filesIds, dossierId)); this._toaster.info(_('download-status.queued')); } diff --git a/apps/red-ui/src/app/modules/shared/components/expandable-file-actions/expandable-file-actions.component.ts b/apps/red-ui/src/app/modules/shared/components/expandable-file-actions/expandable-file-actions.component.ts index df9d6396a..5c57435a8 100644 --- a/apps/red-ui/src/app/modules/shared/components/expandable-file-actions/expandable-file-actions.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/expandable-file-actions/expandable-file-actions.component.ts @@ -29,6 +29,10 @@ export class ExpandableFileActionsComponent implements OnChanges { private readonly _permissionsService: PermissionsService, ) {} + get scrollableParentView(): ScrollableParentView { + return this.helpModeKey === 'document_features' ? ScrollableParentViews.VIRTUAL_SCROLL : undefined; + } + ngOnChanges(changes: SimpleChanges) { if (changes.actions || changes.maxWidth) { if (this.maxWidth) { @@ -59,12 +63,8 @@ export class ExpandableFileActionsComponent implements OnChanges { private async _downloadFiles($event: MouseEvent, files: File[]) { $event.stopPropagation(); const dossierId = files[0].dossierId; - const filesIds = files.map(f => f.fileId); + const filesIds = files.map(f => f.id); await firstValueFrom(this._fileDownloadService.downloadFiles(filesIds, dossierId)); this._toaster.info(_('download-status.queued')); } - - get scrollableParentView(): ScrollableParentView { - return this.helpModeKey === 'document_features' ? ScrollableParentViews.VIRTUAL_SCROLL : undefined; - } } diff --git a/apps/red-ui/src/app/modules/upload-download/services/file-upload.service.ts b/apps/red-ui/src/app/modules/upload-download/services/file-upload.service.ts index 5494c0e81..674e28b8a 100644 --- a/apps/red-ui/src/app/modules/upload-download/services/file-upload.service.ts +++ b/apps/red-ui/src/app/modules/upload-download/services/file-upload.service.ts @@ -42,7 +42,7 @@ export class FileUploadService extends GenericService impleme super(_injector, 'upload'); const fileFetch$ = this._fetchFiles$.pipe( throttleTime(250), - switchMap(dossierId => this._filesService.loadAll(dossierId)), + switchMap(dossierId => this._filesService.loadAll(dossierId, 'dossiers')), ); this._subscriptions.add(fileFetch$.subscribe()); const interval$ = interval(2500).pipe(tap(() => this._handleUploads())); diff --git a/apps/red-ui/src/app/services/entity-services/dossiers.service.ts b/apps/red-ui/src/app/services/entity-services/dossiers.service.ts index cf383c3dc..16b9cb57e 100644 --- a/apps/red-ui/src/app/services/entity-services/dossiers.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dossiers.service.ts @@ -90,7 +90,8 @@ export abstract class DossiersService extends EntitiesService dossierChanges.filter(change => change.fileChanges).forEach(change => this.dossierFileChanges$.next(change.dossierId)); } - private _load(id: string, queryParams?: List): Observable { + private _load(id: string): Observable { + const queryParams: List = [{ key: 'includeArchived', value: this._path === 'archived-dossiers' }]; return super._getOne([id], this._defaultModelPath, queryParams).pipe( map(entity => new Dossier(entity, 'dossiers')), tap(dossier => this.replace(dossier)), diff --git a/apps/red-ui/src/app/services/entity-services/file-management.service.ts b/apps/red-ui/src/app/services/entity-services/file-management.service.ts index a2187df9c..300ec08b1 100644 --- a/apps/red-ui/src/app/services/entity-services/file-management.service.ts +++ b/apps/red-ui/src/app/services/entity-services/file-management.service.ts @@ -1,4 +1,4 @@ -import { GenericService, HeadersConfiguration, List, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; +import { GenericService, HeadersConfiguration, IRouterPath, List, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; import { Injectable, Injector } from '@angular/core'; import { HttpHeaders, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; @@ -19,8 +19,10 @@ export class FileManagementService extends GenericService { } @Validate() - delete(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string) { - return super._post(fileIds, `delete/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId))); + delete(@RequiredParam() files: List, @RequiredParam() dossierId: string) { + const fileIds = files.map(f => f.id); + const routerPath: string = files[0].routerPath; + return super._post(fileIds, `delete/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId, routerPath))); } @Validate() @@ -32,8 +34,10 @@ export class FileManagementService extends GenericService { } @Validate() - restore(@RequiredParam() body: List, @RequiredParam() dossierId: string) { - return this._post(body, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId))); + restore(@RequiredParam() files: List, @RequiredParam() dossierId: string) { + const fileIds = files.map(f => f.id); + const routerPath: string = files[0].routerPath; + return this._post(fileIds, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId, routerPath))); } downloadOriginalFile(dossierId: string, fileId: string, observe?: 'body', indicator?: string): Observable; diff --git a/apps/red-ui/src/app/services/entity-services/files-map.service.ts b/apps/red-ui/src/app/services/entity-services/files-map.service.ts index d71f717d1..bfe6587aa 100644 --- a/apps/red-ui/src/app/services/entity-services/files-map.service.ts +++ b/apps/red-ui/src/app/services/entity-services/files-map.service.ts @@ -12,7 +12,11 @@ export class FilesMapService extends EntitiesMapService { replaceFiles(files: File[], property: keyof IFile, generateValue: Function) { const newFiles = files.map( file => - new File({ ...file, [property]: generateValue(file[property]), lastUpdated: new Date().toISOString() }, file.reviewerName), + new File( + { ...file, [property]: generateValue(file[property]), lastUpdated: new Date().toISOString() }, + file.reviewerName, + file.routerPath, + ), ); this.replace(newFiles); } diff --git a/apps/red-ui/src/app/services/entity-services/files.service.ts b/apps/red-ui/src/app/services/entity-services/files.service.ts index 8a8109f46..f67a969e7 100644 --- a/apps/red-ui/src/app/services/entity-services/files.service.ts +++ b/apps/red-ui/src/app/services/entity-services/files.service.ts @@ -1,5 +1,5 @@ import { Injectable, Injector } from '@angular/core'; -import { EntitiesService, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui'; +import { EntitiesService, IRouterPath, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui'; import { File, IFile } from '@red/domain'; import { Observable } from 'rxjs'; import { UserService } from '../user.service'; @@ -21,40 +21,50 @@ export class FilesService extends EntitiesService { } /** Reload dossier files + stats. */ - loadAll(dossierId: string) { - const files$ = this.getFor(dossierId).pipe(mapEach(file => new File(file, this._userService.getNameForId(file.assignee)))); + loadAll(dossierId: string, routerPath: string) { + const files$ = this.getFor(dossierId).pipe( + mapEach(file => new File(file, this._userService.getNameForId(file.assignee), routerPath)), + ); const loadStats$ = files$.pipe(switchMap(files => this._dossierStatsService.getFor([dossierId]).pipe(mapTo(files)))); return loadStats$.pipe(tap(files => this._filesMapService.set(dossierId, files))); } - reload(dossierId: string, fileId: string): Observable { - return super._getOne([dossierId, fileId]).pipe( - map(file => new File(file, this._userService.getNameForId(file.assignee))), - switchMap(file => this._dossierStatsService.getFor([dossierId]).pipe(mapTo(file))), - map(file => this._filesMapService.replace([file])), + reload(dossierId: string, file: File): Observable { + return super._getOne([dossierId, file.id]).pipe( + map(_file => new File(_file, this._userService.getNameForId(_file.assignee), file.routerPath)), + switchMap(_file => this._dossierStatsService.getFor([dossierId]).pipe(mapTo(_file))), + map(_file => this._filesMapService.replace([_file])), ); } @Validate() - setUnassigned(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string) { + setUnassigned(@RequiredParam() files: List, @RequiredParam() dossierId: string) { const url = `${this._defaultModelPath}/set-assignee/${dossierId}/bulk`; - return this._post(fileIds, url).pipe(switchMap(() => this.loadAll(dossierId))); + const fileIds = files.map(f => f.id); + const routerPath: string = files[0].routerPath; + return this._post(fileIds, url).pipe(switchMap(() => this.loadAll(dossierId, routerPath))); } @Validate() - setUnderApprovalFor(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, assigneeId: string) { + setUnderApprovalFor(@RequiredParam() files: List, @RequiredParam() dossierId: string, assigneeId: string) { const url = `${this._defaultModelPath}/under-approval/${dossierId}/bulk`; - return this._post(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(switchMap(() => this.loadAll(dossierId))); + const fileIds = files.map(f => f.id); + const routerPath: string = files[0].routerPath; + return this._post(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe( + switchMap(() => this.loadAll(dossierId, routerPath)), + ); } /** * Assigns a reviewer for a list of files. */ @Validate() - setReviewerFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string, assigneeId: string) { + setReviewerFor(@RequiredParam() files: List, @RequiredParam() dossierId: string, assigneeId: string) { const url = `${this._defaultModelPath}/under-review/${dossierId}/bulk`; - return this._post(filesIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe( - switchMap(() => this.loadAll(dossierId)), + const fileIds = files.map(f => f.id); + const routerPath: string = files[0].routerPath; + return this._post(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe( + switchMap(() => this.loadAll(dossierId, routerPath)), ); } @@ -62,9 +72,11 @@ export class FilesService extends EntitiesService { * Sets the status APPROVED for a list of files. */ @Validate() - setApprovedFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string) { - return this._post(filesIds, `${this._defaultModelPath}/approved/${dossierId}/bulk`).pipe( - switchMap(() => this.loadAll(dossierId)), + setApprovedFor(@RequiredParam() files: List, @RequiredParam() dossierId: string) { + const fileIds = files.map(f => f.id); + const routerPath: string = files[0].routerPath; + return this._post(fileIds, `${this._defaultModelPath}/approved/${dossierId}/bulk`).pipe( + switchMap(() => this.loadAll(dossierId, routerPath)), ); } @@ -72,9 +84,11 @@ export class FilesService extends EntitiesService { * Sets the status UNDER_REVIEW for a list of files. */ @Validate() - setUnderReviewFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string) { - return this._post(filesIds, `${this._defaultModelPath}/under-review/${dossierId}/bulk`).pipe( - switchMap(() => this.loadAll(dossierId)), + setUnderReviewFor(@RequiredParam() files: List, @RequiredParam() dossierId: string) { + const fileIds = files.map(f => f.id); + const routerPath: string = files[0].routerPath; + return this._post(fileIds, `${this._defaultModelPath}/under-review/${dossierId}/bulk`).pipe( + switchMap(() => this.loadAll(dossierId, routerPath)), ); } diff --git a/apps/red-ui/src/app/services/entity-services/platform-search.service.ts b/apps/red-ui/src/app/services/entity-services/platform-search.service.ts index c96e31909..920b89753 100644 --- a/apps/red-ui/src/app/services/entity-services/platform-search.service.ts +++ b/apps/red-ui/src/app/services/entity-services/platform-search.service.ts @@ -1,6 +1,6 @@ import { Injectable, Injector } from '@angular/core'; import { GenericService } from '@iqser/common-ui'; -import { IMatchedDocument, ISearchInput, ISearchRequest, ISearchResponse } from '@red/domain'; +import { Dossier, IMatchedDocument, ISearchInput, ISearchRequest, ISearchResponse } from '@red/domain'; import { Observable, of, zip } from 'rxjs'; import { mapTo, switchMap } from 'rxjs/operators'; import { ActiveDossiersService } from './active-dossiers.service'; @@ -44,11 +44,11 @@ export class PlatformSearchService extends GenericService { const fileNotLoaded = ({ dossierId, fileId }: IMatchedDocument) => !this._filesMapService.get(dossierId, fileId); const dossiersWithNotLoadedFiles = documentsOfActiveDossiers.filter(fileNotLoaded).map(document => document.dossierId); - const dossierIds = Array.from(new Set(dossiersWithNotLoadedFiles)); - return dossierIds.length ? this._loadFilesFor$(dossierIds).pipe(mapTo(searchResponse)) : of(searchResponse); + const dossiers = Array.from(new Set(dossiersWithNotLoadedFiles)).map(dossierId => this._activeDossiersService.find(dossierId)); + return dossiers.length ? this._loadFilesFor$(dossiers).pipe(mapTo(searchResponse)) : of(searchResponse); } - private _loadFilesFor$(dossierIds: string[]) { - return zip(...dossierIds.map(dossierId => this._filesService.loadAll(dossierId))); + private _loadFilesFor$(dossiers: Dossier[]) { + return zip(...dossiers.map(dossier => this._filesService.loadAll(dossier.id, dossier.routerPath))); } } diff --git a/apps/red-ui/src/app/services/permissions.service.ts b/apps/red-ui/src/app/services/permissions.service.ts index 21dbf29e6..75c9f0798 100644 --- a/apps/red-ui/src/app/services/permissions.service.ts +++ b/apps/red-ui/src/app/services/permissions.service.ts @@ -30,6 +30,10 @@ export class PermissionsService { return dossier.isActive; } + canDownloadCsvReport(dossier: Dossier): boolean { + return dossier.isActive; + } + canEditFileAttributes(file: File): boolean { const dossier = this._getDossier(file); return ((file.isUnderReview || file.isNew) && this.isDossierMember(dossier)) || (file.isUnderApproval && this.isApprover(dossier)); @@ -41,6 +45,11 @@ export class PermissionsService { return sameState && files.reduce((acc, _file) => this._canToggleAnalysis(_file) && acc, true); } + showToggleAnalysis(file: File | File[]): boolean { + const files = file instanceof File ? [file] : file; + return this._isActive(files[0]); + } + canReanalyseFile(file: File | File[]): boolean { const files = file instanceof File ? [file] : file; return files.reduce((acc, _file) => this._canReanalyseFile(_file) && acc, true); @@ -64,6 +73,12 @@ export class PermissionsService { return files.reduce((acc, _file) => this._canDeleteFile(_file, dossier) && acc, true); } + canOcrFile(file: File | File[]): boolean { + const files = file instanceof File ? [file] : file; + const dossier = this._getDossier(files[0]); + return files.reduce((acc, _file) => this._canOcrFile(_file, dossier) && acc, true); + } + canAssignToSelf(file: File | File[]): boolean { const files = file instanceof File ? [file] : file; const dossier = this._getDossier(files[0]); @@ -120,7 +135,7 @@ export class PermissionsService { // TODO: Remove '?', after we make sure file is loaded before page canPerformAnnotationActions(file: File): boolean { - return !file.excluded && (file?.isUnderReview || file?.isUnderApproval) && this.isFileAssignee(file); + return this._isActive(file) && !file.excluded && (file?.isUnderReview || file?.isUnderApproval) && this.isFileAssignee(file); } canUndoApproval(file: File | File[]): boolean { @@ -171,54 +186,60 @@ export class PermissionsService { } canImportRedactions(file: File) { - return (this.isFileAssignee(file) || this.isApprover(this._getDossier(file))) && !file.isApproved; + return this._isActive(file) && (this.isFileAssignee(file) || this.isApprover(this._getDossier(file))) && !file.isApproved; + } + + private _canOcrFile(file: File, dossier: Dossier): boolean { + return dossier.isActive && file.canBeOCRed; } private _canToggleAnalysis(file: File): boolean { - return this.isFileAssignee(file) && (file.isNew || file.isUnderReview || file.isUnderApproval); + return this._isActive(file) && this.isFileAssignee(file) && (file.isNew || file.isUnderReview || file.isUnderApproval); } // https://jira.iqser.com/browse/RED-2787 private _canDeleteFile(file: File, dossier: Dossier): boolean { return ( - file.isNew || - (file.isUnderReview && !file.assignee && this.isDossierMember(dossier)) || - (file.isUnderApproval && !file.assignee && this.isApprover(dossier)) || - (file.assignee && !file.isApproved && (this.isFileAssignee(file) || this.isOwner(dossier))) + dossier.isActive && + (file.isNew || + (file.isUnderReview && !file.assignee && this.isDossierMember(dossier)) || + (file.isUnderApproval && !file.assignee && this.isApprover(dossier)) || + (file.assignee && !file.isApproved && (this.isFileAssignee(file) || this.isOwner(dossier)))) ); } private _canReanalyseFile(file: File): boolean { - return this.isReviewerOrApprover(file) && file.analysisRequired; + return this._isActive(file) && this.isReviewerOrApprover(file) && file.analysisRequired; } private _canEnableAutoAnalysis(file: File): boolean { - return file.excludedFromAutomaticAnalysis && file.assignee === this._userService.currentUser.id; + return this._isActive(file) && file.excludedFromAutomaticAnalysis && file.assignee === this._userService.currentUser.id; } private _canDisableAutoAnalysis(file: File): boolean { - return !file.excludedFromAutomaticAnalysis && file.assignee === this._userService.currentUser.id; + return this._isActive(file) && !file.excludedFromAutomaticAnalysis && file.assignee === this._userService.currentUser.id; } private _canAssignToSelf(file: File, dossier: Dossier): boolean { - const precondition = this.isDossierMember(dossier) && !this.isFileAssignee(file) && !file.isError && !file.isProcessing; + const precondition = + this._isActive(file) && this.isDossierMember(dossier) && !this.isFileAssignee(file) && !file.isError && !file.isProcessing; return precondition && (file.isNew || file.isUnderReview || (file.isUnderApproval && this.isApprover(dossier))); } private _canSetUnderApproval(file: File): boolean { - return file.isUnderReview && this.isReviewerOrApprover(file); + return this._isActive(file) && file.isUnderReview && this.isReviewerOrApprover(file); } private _canUndoApproval(file: File, dossier: Dossier): boolean { - return file.isApproved && this.isApprover(dossier); + return this._isActive(file) && file.isApproved && this.isApprover(dossier); } private _canBeApproved(file: File): boolean { - return file.canBeApproved; + return this._isActive(file) && file.canBeApproved; } private _canAssignUser(file: File, dossier: Dossier) { - const precondition = !file.isProcessing && !file.isError && !file.isApproved && this.isApprover(dossier); + const precondition = this._isActive(file) && !file.isProcessing && !file.isError && !file.isApproved && this.isApprover(dossier); if (precondition) { if ((file.isNew || file.isUnderReview) && dossier.hasReviewers) { @@ -232,14 +253,20 @@ export class PermissionsService { } private _canUnassignUser(file: File, dossier: Dossier) { - return (file.isUnderReview || file.isUnderApproval) && (this.isFileAssignee(file) || this.isApprover(dossier)); + return ( + this._isActive(file) && (file.isUnderReview || file.isUnderApproval) && (this.isFileAssignee(file) || this.isApprover(dossier)) + ); } private _canSetUnderReview(file: File): boolean { - return file.isUnderApproval; + return this._isActive(file) && file.isUnderApproval; } private _getDossier(file: File): Dossier { return this._dossiersService.find(file.dossierId); } + + private _isActive(file: File): boolean { + return this._getDossier(file).isActive; + } } diff --git a/apps/red-ui/src/app/services/reanalysis.service.ts b/apps/red-ui/src/app/services/reanalysis.service.ts index 03d002626..d58a2e406 100644 --- a/apps/red-ui/src/app/services/reanalysis.service.ts +++ b/apps/red-ui/src/app/services/reanalysis.service.ts @@ -1,6 +1,6 @@ import { Injectable, Injector } from '@angular/core'; -import { GenericService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; -import { File, IPageExclusionRequest } from '@red/domain'; +import { GenericService, IRouterPath, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; +import { Dossier, File, IPageExclusionRequest } from '@red/domain'; import { catchError, switchMap, tap } from 'rxjs/operators'; import { FilesService } from './entity-services/files.service'; import { FilesMapService } from './entity-services/files-map.service'; @@ -26,17 +26,19 @@ export class ReanalysisService extends GenericService { } @Validate() - excludePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) { - return this._post(body, `exclude-pages/${dossierId}/${fileId}`).pipe(switchMap(() => this._filesService.reload(dossierId, fileId))); + excludePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() file: File) { + return this._post(body, `exclude-pages/${dossierId}/${file.id}`).pipe(switchMap(() => this._filesService.reload(dossierId, file))); } @Validate() - includePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) { - return this._post(body, `include-pages/${dossierId}/${fileId}`).pipe(switchMap(() => this._filesService.reload(dossierId, fileId))); + includePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() file: File) { + return this._post(body, `include-pages/${dossierId}/${file.id}`).pipe(switchMap(() => this._filesService.reload(dossierId, file))); } @Validate() - reanalyzeFilesForDossier(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, params?: ReanalyzeQueryParams) { + reanalyzeFilesForDossier(@RequiredParam() files: List, @RequiredParam() dossierId: string, params?: ReanalyzeQueryParams) { + const fileIds = files.map(f => f.id); + const routerPath: string = files[0].routerPath; const queryParams: QueryParam[] = []; if (params?.force) { queryParams.push({ key: 'force', value: true }); @@ -45,18 +47,22 @@ export class ReanalysisService extends GenericService { queryParams.push({ key: 'triggeredByUser', value: true }); } - return this._post(fileIds, `reanalyze/${dossierId}/bulk`, queryParams).pipe(switchMap(() => this._filesService.loadAll(dossierId))); + return this._post(fileIds, `reanalyze/${dossierId}/bulk`, queryParams).pipe( + switchMap(() => this._filesService.loadAll(dossierId, routerPath)), + ); } @Validate() - toggleAnalysis(@RequiredParam() dossierId: string, @RequiredParam() fileIds: string[], excluded?: boolean) { + toggleAnalysis(@RequiredParam() dossierId: string, @RequiredParam() files: List, excluded?: boolean) { + const fileIds = files.map(f => f.id); + const routerPath: string = files[0].routerPath; const queryParams: QueryParam[] = []; if (excluded) { queryParams.push({ key: 'excluded', value: excluded }); } return this._post(fileIds, `toggle-analysis/${dossierId}/bulk`, queryParams).pipe( - switchMap(() => this._filesService.loadAll(dossierId)), + switchMap(() => this._filesService.loadAll(dossierId, routerPath)), ); } @@ -80,17 +86,24 @@ export class ReanalysisService extends GenericService { } @Validate() - ocrFiles(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string) { - return this._post(fileIds, `ocr/reanalyze/${dossierId}/bulk`).pipe(switchMap(() => this._filesService.loadAll(dossierId))); + ocrFiles(@RequiredParam() files: List, @RequiredParam() dossierId: string) { + const fileIds = files.map(f => f.id); + const routerPath: string = files[0].routerPath; + return this._post(fileIds, `ocr/reanalyze/${dossierId}/bulk`).pipe( + switchMap(() => this._filesService.loadAll(dossierId, routerPath)), + ); } @Validate() - reanalyzeDossier(@RequiredParam() dossierId: string, force?: boolean) { + reanalyzeDossier(@RequiredParam() dossier: Dossier, force?: boolean) { + const { dossierId, routerPath } = dossier; const queryParams: QueryParam[] = []; if (force) { queryParams.push({ key: 'force', value: force }); } - return this._post({}, `reanalyze/${dossierId}`, queryParams).pipe(switchMap(() => this._filesService.loadAll(dossierId))); + return this._post({}, `reanalyze/${dossierId}`, queryParams).pipe( + switchMap(() => this._filesService.loadAll(dossierId, routerPath)), + ); } } diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 78d1498b2..9ee85116b 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -1,7 +1,7 @@ { "ADMIN_CONTACT_NAME": null, "ADMIN_CONTACT_URL": null, - "API_URL": "https://dev-04.iqser.cloud/redaction-gateway-v1", + "API_URL": "https://dev-05.iqser.cloud/redaction-gateway-v1", "APP_NAME": "RedactManager", "AUTO_READ_TIME": 3, "BACKEND_APP_VERSION": "4.4.40", @@ -17,7 +17,7 @@ "MAX_RETRIES_ON_SERVER_ERROR": 3, "OAUTH_CLIENT_ID": "redaction", "OAUTH_IDP_HINT": null, - "OAUTH_URL": "https://dev-04.iqser.cloud/auth/realms/redaction", + "OAUTH_URL": "https://dev-05.iqser.cloud/auth/realms/redaction", "RECENT_PERIOD_IN_HOURS": 24, "SELECTION_MODE": "structural", "MANUAL_BASE_URL": "https://docs.redactmanager.com/preview" diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 1d0e5658b..1cce3154e 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -322,7 +322,7 @@ }, "table-col-names": { "dossier-status": "Dossier Status", - "last-modified": "Last Modified", + "last-modified": "Archived Time", "name": "Name", "owner": "Owner" }, @@ -1251,6 +1251,7 @@ "reanalyse-notification": "This document was not processed with the latest rule/dictionary set. Analyze now to get updated annotations.", "redacted": "Preview", "redacted-tooltip": "Redaction preview shows only redactions. Consider this a preview for the final redacted version. This view is only available if the file has no pending changes & doesn't require a reanalysis", + "reset-filters": "", "standard": "Standard", "standard-tooltip": "Standard Workload view shows all hints, redactions, recommendations & suggestions. This view allows editing.", "tabs": { @@ -1587,6 +1588,7 @@ "processing": "Processing" }, "readonly": "Read only", + "readonly-archived": "Read only (archived)", "recategorize-image-dialog": { "actions": { "cancel": "Cancel", diff --git a/libs/common-ui b/libs/common-ui index f480a52cc..242789330 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit f480a52cc384eea5ab54fb3b7bbc5db431c1506c +Subproject commit 242789330301767bc38fe62651ea777297159bb3 diff --git a/libs/red-domain/src/lib/dossiers/dossier.model.ts b/libs/red-domain/src/lib/dossiers/dossier.model.ts index 9acde0d21..fba166c8a 100644 --- a/libs/red-domain/src/lib/dossiers/dossier.model.ts +++ b/libs/red-domain/src/lib/dossiers/dossier.model.ts @@ -1,9 +1,9 @@ -import { IListable, List } from '@iqser/common-ui'; +import { IListable, IRouterPath, List } from '@iqser/common-ui'; import { IDossier } from './dossier'; import { DossierStatus, DossierStatuses } from './types'; import { DownloadFileType } from '../shared'; -export class Dossier implements IDossier, IListable { +export class Dossier implements IDossier, IListable, IRouterPath { readonly dossierId: string; readonly dossierTemplateId: string; readonly ownerId: string; diff --git a/libs/red-domain/src/lib/files/file.model.ts b/libs/red-domain/src/lib/files/file.model.ts index b5930769c..56e4c1458 100644 --- a/libs/red-domain/src/lib/files/file.model.ts +++ b/libs/red-domain/src/lib/files/file.model.ts @@ -1,10 +1,10 @@ -import { Entity } from '@iqser/common-ui'; +import { Entity, IRouterPath } from '@iqser/common-ui'; import { StatusSorter } from '../shared'; import { isProcessingStatuses, ProcessingFileStatus, ProcessingFileStatuses, WorkflowFileStatus, WorkflowFileStatuses } from './types'; import { IFile } from './file'; import { FileAttributes } from '../file-attributes'; -export class File extends Entity implements IFile { +export class File extends Entity implements IFile, IRouterPath { readonly added?: string; readonly allManualRedactionsApplied: boolean; readonly analysisDuration?: number; @@ -57,7 +57,7 @@ export class File extends Entity implements IFile { readonly canBeOpened: boolean; readonly canBeOCRed: boolean; - constructor(file: IFile, readonly reviewerName: string) { + constructor(file: IFile, readonly reviewerName: string, readonly routerPath: string) { super(file); this.added = file.added; this.allManualRedactionsApplied = !!file.allManualRedactionsApplied; @@ -125,6 +125,6 @@ export class File extends Entity implements IFile { } get routerLink(): string | undefined { - return this.canBeOpened ? `/main/dossiers/${this.dossierId}/file/${this.fileId}` : undefined; + return this.canBeOpened ? `/main/${this.routerPath}/${this.dossierId}/file/${this.fileId}` : undefined; } }