diff --git a/apps/red-ui/src/app/common/filter/utils/filter-utils.ts b/apps/red-ui/src/app/common/filter/utils/filter-utils.ts index 8942631b6..e5cb6a1dd 100644 --- a/apps/red-ui/src/app/common/filter/utils/filter-utils.ts +++ b/apps/red-ui/src/app/common/filter/utils/filter-utils.ts @@ -1,8 +1,10 @@ import { FilterModel } from '../model/filter.model'; import { FileStatusWrapper } from '../../../screens/file/model/file-status.wrapper'; import { ProjectWrapper } from '../../../state/model/project.wrapper'; +import { PermissionsService } from '../../service/permissions.service'; export const RedactionFilterSorter = { + analysis: 0, hint: 1, redaction: 2, suggestion: 3, @@ -22,9 +24,16 @@ export function handleCheckedValue(filter: FilterModel) { } } -export function checkFilter(entity: any, filters: FilterModel[], validate: Function, matchAll: boolean = false) { +export function checkFilter(entity: any, filters: FilterModel[], validate: Function, validateArgs: any = [], matchAll: boolean = false) { const hasChecked = filters.find((f) => f.checked); + if (validateArgs) { + if (!Array.isArray(validateArgs)) { + validateArgs = [validateArgs]; + } + } else { + validateArgs = []; + } if (!hasChecked) { return true; } @@ -33,9 +42,9 @@ export function checkFilter(entity: any, filters: FilterModel[], validate: Funct for (const filter of filters) { if (filter.checked) { if (matchAll) { - filterMatched = filterMatched && validate(entity, filter); + filterMatched = filterMatched && validate(entity, filter, ...validateArgs); } else { - filterMatched = filterMatched || validate(entity, filter); + filterMatched = filterMatched || validate(entity, filter, ...validateArgs); } } } @@ -45,19 +54,26 @@ export function checkFilter(entity: any, filters: FilterModel[], validate: Funct export const keyChecker = (key: string) => (entity: any, filter: FilterModel) => entity[key] === filter.key; -export const annotationFilterChecker = (f: FileStatusWrapper, filter: FilterModel) => { +export const annotationFilterChecker = (input: FileStatusWrapper | ProjectWrapper, filter: FilterModel, permissionsService: PermissionsService) => { switch (filter.key) { + case 'analysis': { + if (input instanceof ProjectWrapper) { + return permissionsService.projectReanalysisRequired(input); + } else { + return permissionsService.fileRequiresReanalysis(input); + } + } case 'suggestion': { - return f.hasRequests; + return input.hasRequests; } case 'redaction': { - return f.hasRedactions; + return input.hasRedactions; } case 'hint': { - return f.hintsOnly; + return input.hintsOnly; } case 'none': { - return f.hasNone; + return input.hasNone; } } }; @@ -72,12 +88,12 @@ export const dueDateChecker = (pw: ProjectWrapper, filter: FilterModel) => pw.du export const addedDateChecker = (pw: ProjectWrapper, filter: FilterModel) => pw.addedDateMatches(filter.key); -export function getFilteredEntities(entities: any[], filters: { values: FilterModel[]; checker: Function; matchAll?: boolean }[]) { +export function getFilteredEntities(entities: any[], filters: { values: FilterModel[]; checker: Function; matchAll?: boolean; checkerArgs?: any }[]) { const filteredEntities = []; for (const entity of entities) { let add = true; for (const filter of filters) { - add = add && checkFilter(entity, filter.values, filter.checker, filter.matchAll); + add = add && checkFilter(entity, filter.values, filter.checker, filter.checkerArgs, filter.matchAll); } if (add) { filteredEntities.push(entity); diff --git a/apps/red-ui/src/app/common/service/permissions.service.ts b/apps/red-ui/src/app/common/service/permissions.service.ts index 85fece71a..700ecd774 100644 --- a/apps/red-ui/src/app/common/service/permissions.service.ts +++ b/apps/red-ui/src/app/common/service/permissions.service.ts @@ -3,6 +3,7 @@ import { AppStateService } from '../../state/app-state.service'; import { UserService, UserWrapper } from '../../user/user.service'; import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper'; import { Project, User } from '@redaction/red-ui-http'; +import { ProjectWrapper } from '../../state/model/project.wrapper'; @Injectable({ providedIn: 'root' @@ -22,6 +23,15 @@ export class PermissionsService { return this.isFileReviewer(fileStatus) || this.isManagerAndOwner(); } + projectReanalysisRequired(project?: ProjectWrapper) { + for (let file of project.files) { + const fileReanalysisRequired = this.fileRequiresReanalysis(file); + if (fileReanalysisRequired) { + return true; + } + } + } + fileRequiresReanalysis(fileStatus?: FileStatusWrapper) { if (!fileStatus) { fileStatus = this._appStateService.activeFile; diff --git a/apps/red-ui/src/app/screens/common/needs-work-badge/needs-work-badge.component.html b/apps/red-ui/src/app/screens/common/needs-work-badge/needs-work-badge.component.html index 07dccc917..3159e57c2 100644 --- a/apps/red-ui/src/app/screens/common/needs-work-badge/needs-work-badge.component.html +++ b/apps/red-ui/src/app/screens/common/needs-work-badge/needs-work-badge.component.html @@ -1,4 +1,5 @@
+
- +  {{ appStateService.activeFile.filename }} diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts index af966198f..b3533179a 100644 --- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts +++ b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts @@ -167,10 +167,11 @@ export class ProjectListingScreenComponent implements OnInit { // Needs work entry.files.forEach((file) => { - if (file.hintsOnly) allDistinctNeedsWork.add('hint'); - if (file.hasRedactions) allDistinctNeedsWork.add('redaction'); - if (file.hasRequests) allDistinctNeedsWork.add('suggestion'); - if (file.hasNone) allDistinctNeedsWork.add('none'); + if (this.permissionsService.fileRequiresReanalysis(file)) allDistinctNeedsWork.add('analysis'); + if (entry.hintsOnly) allDistinctNeedsWork.add('hint'); + if (entry.hasRedactions) allDistinctNeedsWork.add('redaction'); + if (entry.hasRequests) allDistinctNeedsWork.add('suggestion'); + if (entry.hasNone) allDistinctNeedsWork.add('none'); }); }); @@ -226,7 +227,7 @@ export class ProjectListingScreenComponent implements OnInit { { values: this.statusFilters, checker: projectStatusChecker }, { values: this.peopleFilters, checker: projectMemberChecker }, { values: this.dueDateFilters, checker: dueDateChecker }, - { values: this.needsWorkFilters, checker: annotationFilterChecker, matchAll: true } + { values: this.needsWorkFilters, checker: annotationFilterChecker, matchAll: true, checkerArgs: this.permissionsService } ]; this.detailsContainerFilters = { statusFilters: this.statusFilters.map((f) => ({ ...f })) diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.html b/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.html index 6c96306b0..32776a8f7 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.html +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.html @@ -53,7 +53,7 @@
- {{ 'project-overview.legend.' + filter.key | translate }} + {{ 'filter.' + filter.key | translate }}
diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html index da8548eea..7b6793b1b 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html @@ -122,7 +122,7 @@
{{ fileStatus.filename }}
- +
diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts index eae2c23c0..f103d9f35 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts @@ -191,6 +191,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { // Needs work this.appStateService.activeProject.files.forEach((file) => { + if (this.permissionsService.fileRequiresReanalysis(file)) allDistinctNeedsWork.add('analysis'); if (file.hintsOnly) allDistinctNeedsWork.add('hint'); if (file.hasRedactions) allDistinctNeedsWork.add('redaction'); if (file.hasRequests) allDistinctNeedsWork.add('suggestion'); @@ -250,7 +251,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { const filters = [ { values: this.statusFilters, checker: keyChecker('status') }, { values: this.peopleFilters, checker: keyChecker('currentReviewer') }, - { values: this.needsWorkFilters, checker: annotationFilterChecker, matchAll: true } + { values: this.needsWorkFilters, checker: annotationFilterChecker, matchAll: true, checkerArgs: this.permissionsService } ]; this.displayedFiles = getFilteredEntities(this.appStateService.activeProject.files, filters); this.detailsContainerFilters = { diff --git a/apps/red-ui/src/app/state/app-state.service.ts b/apps/red-ui/src/app/state/app-state.service.ts index 3d71c83ce..f131e5ab0 100644 --- a/apps/red-ui/src/app/state/app-state.service.ts +++ b/apps/red-ui/src/app/state/app-state.service.ts @@ -390,6 +390,11 @@ export class AppStateService { type: 'add', virtual: true }; + this._dictionaryData['analysis'] = { + hexColor: '#dd4d50', + type: 'analysis', + virtual: true + }; }) ); diff --git a/apps/red-ui/src/app/state/model/project.wrapper.ts b/apps/red-ui/src/app/state/model/project.wrapper.ts index 5f8374161..3297d2c0f 100644 --- a/apps/red-ui/src/app/state/model/project.wrapper.ts +++ b/apps/red-ui/src/app/state/model/project.wrapper.ts @@ -8,6 +8,7 @@ export class ProjectWrapper { hintsOnly?: boolean; hasRedactions?: boolean; hasRequests?: boolean; + hasNone?: boolean; allFilesApproved?: boolean; @@ -63,6 +64,7 @@ export class ProjectWrapper { this.hintsOnly = false; this.hasRedactions = false; this.hasRequests = false; + this.hasNone = false; this.allFilesApproved = true; this._files.forEach((f) => { this.hintsOnly = this.hintsOnly || f.hintsOnly; @@ -70,5 +72,6 @@ export class ProjectWrapper { this.hasRequests = this.hasRequests || f.hasRequests; this.allFilesApproved = this.allFilesApproved && f.isApproved; }); + this.hasNone = !this.hasRequests && !this.hasRedactions && !this.hintsOnly; } } diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 68f7191bf..76e93226f 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -239,13 +239,7 @@ }, "header": "Project Overview", "upload-document": "Upload Document", - "no-project": "Requested project: {{projectId}} does not exist! Back to Project Listing. ", - "legend": { - "hint": "Hints only", - "redaction": "Redacted", - "suggestion": "Suggested Redaction", - "none": "No Annotations" - } + "no-project": "Requested project: {{projectId}} does not exist! Back to Project Listing. " }, "file-preview": { "show-redacted-view": "Show Redacted Preview", @@ -317,6 +311,7 @@ "hint": "Hints only", "redaction": "Redacted", "suggestion": "Suggested Redaction", + "analysis": "Re-analysis required", "none": "No Annotations" }, "annotation-filter": {