diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts index c9730e14b..90d3354dd 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.ts @@ -25,6 +25,8 @@ export class DossierOverviewBulkActionsComponent implements OnChanges { canAssign: boolean; canDelete: boolean; canReanalyse: boolean; + canDisableAutoAnalysis: boolean; + canEnableAutoAnalysis: boolean; canOcr: boolean; canSetToUnderReview: boolean; canSetToUnderApproval: boolean; @@ -112,7 +114,21 @@ export class DossierOverviewBulkActionsComponent implements OnChanges { action: () => this._bulkActionsService.reanalyse(this.selectedFiles), tooltip: _('dossier-overview.bulk.reanalyse'), icon: 'iqser:refresh', - show: this.canReanalyse && this.analysisForced, + show: this.canReanalyse && (this.analysisForced || this.canEnableAutoAnalysis), + }, + { + type: ActionTypes.circleBtn, + action: $event => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles), + tooltip: _('dossier-overview.disable-auto-analysis'), + icon: 'red:stop', + show: this.canDisableAutoAnalysis, + }, + { + type: ActionTypes.circleBtn, + action: $event => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles), + tooltip: _('dossier-overview.enable-auto-analysis'), + icon: 'red:play', + show: this.canEnableAutoAnalysis, }, ].filter(btn => btn.show); } @@ -149,6 +165,10 @@ export class DossierOverviewBulkActionsComponent implements OnChanges { this.canReanalyse = this._permissionsService.canReanalyseFile(this.selectedFiles); + this.canDisableAutoAnalysis = this._permissionsService.canDisableAutoAnalysis(this.selectedFiles); + + this.canEnableAutoAnalysis = this._permissionsService.canEnableAutoAnalysis(this.selectedFiles); + this.canOcr = this.selectedFiles.reduce((acc, file) => acc && file.canBeOCRed, true); this.canSetToUnderReview = this._permissionsService.canSetUnderReview(this.selectedFiles) && !isWorkflow; 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 a0b27b673..b72dbe887 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 @@ -78,8 +78,16 @@ export class BulkActionsService { async reanalyse(files: File[]) { this._loadingService.start(); - const fileIds = files.filter(file => file.analysisRequired).map(file => file.fileId); - await firstValueFrom(this._reanalysisService.reanalyzeFilesForDossier(fileIds, files[0].dossierId)); + const fileIds = files.map(file => file.fileId); + await firstValueFrom( + this._reanalysisService.reanalyzeFilesForDossier(fileIds, files[0].dossierId, { force: true, triggeredByUser: true }), + ); + this._loadingService.stop(); + } + + async toggleAutomaticAnalysis(files: File[]) { + this._loadingService.start(); + await firstValueFrom(this._reanalysisService.toggleAutomaticAnalysis(files[0].dossierId, files)); this._loadingService.stop(); } 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 80384739b..86877bcc9 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 @@ -31,12 +31,18 @@
-
+ +
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.scss b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.scss index a3ced2806..95e122287 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.scss +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.scss @@ -1,13 +1,13 @@ @use 'variables'; @use 'common-mixins'; -.read-only { +.banner { padding: 13px 16px; - background-color: variables.$primary; color: variables.$white; justify-content: space-between; - .read-only-text { + .read-only-text, + .disabled-auto-analysis-text { font-size: 11px; font-weight: 600; line-height: 14px; @@ -33,8 +33,17 @@ } } +.read-only { + background-color: variables.$primary; +} + +.disabled-auto-analysis { + background-color: variables.$yellow-2; +} + .right-content { flex-direction: column; + overflow: hidden; .no-annotations-buttons-container { display: flex; 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 cdc3da485..c01c905ed 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 @@ -209,7 +209,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni const file = await this.stateService.file; if (file?.analysisRequired) { - const reanalyzeFiles = this._reanalysisService.reanalyzeFilesForDossier([this.fileId], this.dossierId, true); + const reanalyzeFiles = this._reanalysisService.reanalyzeFilesForDossier([this.fileId], this.dossierId, { force: true }); await firstValueFrom(reanalyzeFiles); } 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 ae697a33e..e084f178c 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 @@ -30,7 +30,7 @@ import { FileAssignService } from '../../services/file-assign.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { FileManagementService } from '@services/entity-services/file-management.service'; import { FilesService } from '@services/entity-services/files.service'; -import { ReanalysisService } from '@services/reanalysis.service'; +import { ReanalysisService, ReanalyzeQueryParams } from '@services/reanalysis.service'; import { Router } from '@angular/router'; import { ExcludedPagesService } from '../../../screens/file-preview-screen/services/excluded-pages.service'; import { tap } from 'rxjs/operators'; @@ -62,6 +62,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, showDelete = false; showOCR = false; canReanalyse = false; + canDisableAutoAnalysis = false; + canEnableAutoAnalysis = false; showUnderReview = false; showUnderApproval = false; showApprove = false; @@ -182,6 +184,20 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, disabled: !this.file.canBeApproved, show: this.showApprove, }, + { + type: ActionTypes.circleBtn, + action: $event => this.toggleAutomaticAnalysis($event), + tooltip: _('dossier-overview.disable-auto-analysis'), + icon: 'red:stop', + show: this.canDisableAutoAnalysis, + }, + { + type: ActionTypes.circleBtn, + action: $event => this.toggleAutomaticAnalysis($event), + tooltip: _('dossier-overview.enable-auto-analysis'), + icon: 'red:play', + show: this.canEnableAutoAnalysis, + }, { type: ActionTypes.circleBtn, action: $event => this._setFileUnderApproval($event), @@ -296,10 +312,19 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, } private async _reanalyseFile($event?: MouseEvent) { - if ($event) { - $event.stopPropagation(); - } - await firstValueFrom(this._reanalysisService.reanalyzeFilesForDossier([this.file.fileId], this.file.dossierId, true)); + $event?.stopPropagation(); + const params: ReanalyzeQueryParams = { + force: true, + triggeredByUser: true, + }; + await firstValueFrom(this._reanalysisService.reanalyzeFilesForDossier([this.file.fileId], this.file.dossierId, params)); + } + + private async toggleAutomaticAnalysis($event: MouseEvent) { + $event.stopPropagation(); + this._loadingService.start(); + await firstValueFrom(this._reanalysisService.toggleAutomaticAnalysis(this.file.dossierId, [this.file])); + this._loadingService.stop(); } private async _setFileUnderApproval($event: MouseEvent) { @@ -345,6 +370,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, this.showDelete = this._permissionsService.canDeleteFile(this.file); this.showOCR = this.file.canBeOCRed; this.canReanalyse = this._permissionsService.canReanalyseFile(this.file); + this.canDisableAutoAnalysis = this._permissionsService.canDisableAutoAnalysis([this.file]); + this.canEnableAutoAnalysis = this._permissionsService.canEnableAutoAnalysis([this.file]); this.showStatusBar = !this.file.isError && !this.file.isPending && this.isDossierOverviewList; @@ -353,8 +380,9 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, (this._permissionsService.canAssignUser(this.file) || this._permissionsService.canUnassignUser(this.file)) && this.isDossierOverview; - this.showReanalyseFilePreview = this.canReanalyse && this.isFilePreview && this.analysisForced; - this.showReanalyseDossierOverview = this.canReanalyse && this.isDossierOverview && this.analysisForced; + this.showReanalyseFilePreview = this.canReanalyse && this.isFilePreview && (this.analysisForced || this.canEnableAutoAnalysis); + this.showReanalyseDossierOverview = + this.canReanalyse && this.isDossierOverview && (this.analysisForced || this.canEnableAutoAnalysis); this.buttons = this._buttons; diff --git a/apps/red-ui/src/app/modules/icons/icons.module.ts b/apps/red-ui/src/app/modules/icons/icons.module.ts index cf4598843..7d808ccd7 100644 --- a/apps/red-ui/src/app/modules/icons/icons.module.ts +++ b/apps/red-ui/src/app/modules/icons/icons.module.ts @@ -24,6 +24,7 @@ export class IconsModule { 'comment-fill', 'csv', 'dictionary', + 'denied', 'double-chevron-right', 'enter', 'entries', @@ -44,6 +45,7 @@ export class IconsModule { 'new-tab', 'notification', 'page', + 'play', 'preview', 'put-back', 'read-only', @@ -59,6 +61,7 @@ export class IconsModule { 'secret', 'status', 'status-info', + 'stop', 'template', 'thumb-down', 'thumb-up', 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 07936d006..372fadc42 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 @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; -import { File } from '@red/domain'; +import { File, IFile } from '@red/domain'; import { filter, startWith } from 'rxjs/operators'; import { RequiredParam, shareLast, Validate } from '@iqser/common-ui'; @@ -66,14 +66,28 @@ export class FilesMapService { } } - replace(entity: File) { - const existingFile = this.get(entity.dossierId).find(file => file.fileId === entity.fileId); - if (existingFile.lastUpdated !== entity.lastUpdated) { - const all = this.get(entity.dossierId).filter(file => file.fileId !== entity.fileId); - this.set(entity.dossierId, [...all, entity]); + replace(entities: File[]) { + const dossierId = entities[0].dossierId; + const entityIds = entities.map(entity => entity.id); + let existingFiles = this.get(dossierId).filter(file => entityIds.includes(file.fileId)); + entities = entities.filter(entity => { + const existingFile = existingFiles.find(existingFile => existingFile.id === entity.id); + return existingFile.lastUpdated !== entity.lastUpdated; + }); + if (entities.length) { + const all = this.get(dossierId).filter(file => !entities.map(entity => entity.id).includes(file.id)); + this.set(dossierId, [...all, ...entities]); } } + 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), + ); + this.replace(newFiles); + } + @Validate() watch$(@RequiredParam() key: string, @RequiredParam() entityId: string): Observable { return this._entityChanged$.pipe( 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 258c93870..f28eeb296 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 @@ -31,7 +31,7 @@ export class FilesService extends EntitiesService { 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))), - tap(file => this._filesMapService.replace(file)), + tap(file => this._filesMapService.replace([file])), ); } diff --git a/apps/red-ui/src/app/services/permissions.service.ts b/apps/red-ui/src/app/services/permissions.service.ts index 4ad6f6d6b..b476bd189 100644 --- a/apps/red-ui/src/app/services/permissions.service.ts +++ b/apps/red-ui/src/app/services/permissions.service.ts @@ -32,6 +32,14 @@ export class PermissionsService { return files.reduce((acc, _file) => this._canReanalyseFile(_file) && acc, true); } + canEnableAutoAnalysis(files: File[]): boolean { + return files.reduce((acc, _file) => this._canEnableAutoAnalysis(_file) && acc, true); + } + + canDisableAutoAnalysis(files: File[]): boolean { + return files.reduce((acc, _file) => this._canDisableAutoAnalysis(_file) && acc, true); + } + isFileAssignee(file: File): boolean { return file.assignee === this._userService.currentUser.id; } @@ -158,6 +166,14 @@ export class PermissionsService { return this.isReviewerOrApprover(file) || file.isNew || (file.isError && file.isNew); } + private _canEnableAutoAnalysis(file: File): boolean { + return file.excludedFromAutomaticAnalysis && file.assignee === this._userService.currentUser.id; + } + + private _canDisableAutoAnalysis(file: File): boolean { + return !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; return precondition && (file.isNew || file.isUnderReview || (file.isUnderApproval && this.isApprover(dossier))); diff --git a/apps/red-ui/src/app/services/reanalysis.service.ts b/apps/red-ui/src/app/services/reanalysis.service.ts index 1707aca5a..a636a7b60 100644 --- a/apps/red-ui/src/app/services/reanalysis.service.ts +++ b/apps/red-ui/src/app/services/reanalysis.service.ts @@ -1,14 +1,27 @@ import { Injectable, Injector } from '@angular/core'; -import { GenericService, List, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; -import { IPageExclusionRequest } from '@red/domain'; -import { switchMap } from 'rxjs/operators'; +import { GenericService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; +import { 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'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { of } from 'rxjs'; + +export interface ReanalyzeQueryParams { + force?: boolean; + triggeredByUser?: boolean; +} @Injectable({ providedIn: 'root', }) export class ReanalysisService extends GenericService { - constructor(protected readonly _injector: Injector, private readonly _filesService: FilesService) { + constructor( + protected readonly _injector: Injector, + private readonly _toaster: Toaster, + private readonly _filesService: FilesService, + private readonly _filesMapService: FilesMapService, + ) { super(_injector, ''); } @@ -23,10 +36,13 @@ export class ReanalysisService extends GenericService { } @Validate() - reanalyzeFilesForDossier(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, force?: boolean) { + reanalyzeFilesForDossier(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, params?: ReanalyzeQueryParams) { const queryParams: QueryParam[] = []; - if (force) { - queryParams.push({ key: 'force', value: force }); + if (params?.force) { + queryParams.push({ key: 'force', value: true }); + } + if (params?.triggeredByUser) { + queryParams.push({ key: 'triggeredByUser', value: true }); } return this._post(fileIds, `reanalyze/${dossierId}/bulk`, queryParams).pipe(switchMap(() => this._filesService.loadAll(dossierId))); @@ -44,6 +60,25 @@ export class ReanalysisService extends GenericService { ); } + @Validate() + toggleAutomaticAnalysis(@RequiredParam() dossierId: string, @RequiredParam() files: File[]) { + const fileIds = files.map(file => file.id); + const excluded = !files[0].excludedFromAutomaticAnalysis; + const queryParams: QueryParam[] = [{ key: 'excluded', value: excluded }]; + return this._post(fileIds, `toggle-automatic-analysis/${dossierId}/bulk`, queryParams).pipe( + tap(() => this._filesMapService.replaceFiles(files, 'excludedFromAutomaticAnalysis', value => !value)), + tap(() => + this._toaster.success(_('toggle-auto-analysis-message.success'), { + params: { toggleOperation: excluded ? 'Deactivated' : 'Activated' }, + }), + ), + catchError(() => { + this._toaster.error(_('toggle-auto-analysis-message.error')); + return of({}); + }), + ); + } + @Validate() ocrFiles(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string) { return this._post(fileIds, `ocr/reanalyze/${dossierId}/bulk`).pipe(switchMap(() => this._filesService.loadAll(dossierId))); diff --git a/apps/red-ui/src/assets/i18n/de.json b/apps/red-ui/src/assets/i18n/de.json index 07269c939..21819e941 100644 --- a/apps/red-ui/src/assets/i18n/de.json +++ b/apps/red-ui/src/assets/i18n/de.json @@ -599,6 +599,7 @@ "placeholder": "Begründung" } }, + "disabled-auto-analysis": "", "document-info": { "save": "Dokumenteninformation speichern", "title": "Datei-Attribute anlegen" @@ -710,6 +711,7 @@ "delete": { "action": "Datei löschen" }, + "disable-auto-analysis": "", "dossier-details": { "attributes": { "expand": "{count} {count, plural, one{benutzerdefiniertes Attribut} other{benutzerdefinierte Attribute}}", @@ -733,6 +735,7 @@ }, "download-file": "Herunterladen", "download-file-disabled": "Nur genehmigte Dateien können heruntergeladen werden", + "enable-auto-analysis": "", "file-listing": { "file-entry": { "file-error": "Reanalyse erforderlich", @@ -1594,6 +1597,10 @@ "less-than-an-hour": "< 1 Stunde", "no-time-left": "Frist für Wiederherstellung verstrichen" }, + "toggle-auto-analysis-message": { + "success": "", + "error": "" + }, "top-bar": { "navigation-items": { "back": "Zurück", diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 4cfab2283..642abf35c 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -607,6 +607,7 @@ "placeholder": "Reason" } }, + "disabled-auto-analysis": "Automatic analysys is disabled", "document-info": { "save": "Save Document Info", "title": "Introduce File Attributes" @@ -718,6 +719,7 @@ "delete": { "action": "Delete File" }, + "disable-auto-analysis": "Disable auto-analysis", "dossier-details": { "attributes": { "expand": "{count} custom {count, plural, one{attribute} other{attributes}}", @@ -741,6 +743,7 @@ }, "download-file": "Download", "download-file-disabled": "You need to be approver in the dossier and the {count, plural, one{file needs} other{files need}} to be approved in order to download.", + "enable-auto-analysis": "Enable auto-analysis", "file-listing": { "file-entry": { "file-error": "Re-processing required", @@ -1633,6 +1636,10 @@ "less-than-an-hour": "< 1 hour", "no-time-left": "Time to restore already passed" }, + "toggle-auto-analysis-message": { + "success": "{toggleOperation} automatic processing.", + "error": "Something went wrong." + }, "top-bar": { "navigation-items": { "back": "Back", diff --git a/apps/red-ui/src/assets/icons/general/denied.svg b/apps/red-ui/src/assets/icons/general/denied.svg new file mode 100644 index 000000000..6530d96ff --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/denied.svg @@ -0,0 +1,7 @@ + + + + + Svg Vector Icons : http://www.onlinewebfonts.com/icon + + \ No newline at end of file diff --git a/apps/red-ui/src/assets/icons/general/play.svg b/apps/red-ui/src/assets/icons/general/play.svg new file mode 100644 index 000000000..1b37e3874 --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/play.svg @@ -0,0 +1,7 @@ + + + + + Svg Vector Icons : http://www.onlinewebfonts.com/icon + + \ No newline at end of file diff --git a/apps/red-ui/src/assets/icons/general/stop.svg b/apps/red-ui/src/assets/icons/general/stop.svg new file mode 100644 index 000000000..db10a7bb9 --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/stop.svg @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/libs/red-domain/src/lib/files/file.model.ts b/libs/red-domain/src/lib/files/file.model.ts index f333045c8..d1168ffa2 100644 --- a/libs/red-domain/src/lib/files/file.model.ts +++ b/libs/red-domain/src/lib/files/file.model.ts @@ -16,6 +16,7 @@ export class File extends Entity implements IFile { readonly dossierDictionaryVersion?: number; readonly dossierId: string; readonly excluded: boolean; + readonly excludedFromAutomaticAnalysis: boolean; readonly fileAttributes?: FileAttributes; readonly fileId: string; readonly filename: string; @@ -68,6 +69,7 @@ export class File extends Entity implements IFile { this.dossierDictionaryVersion = file.dossierDictionaryVersion; this.dossierId = file.dossierId; this.excluded = !!file.excluded; + this.excludedFromAutomaticAnalysis = !!file.excludedFromAutomaticAnalysis; this.fileAttributes = file.fileAttributes; this.fileId = file.fileId; this.filename = file.filename; diff --git a/libs/red-domain/src/lib/files/file.ts b/libs/red-domain/src/lib/files/file.ts index a93082aa8..6208d4fab 100644 --- a/libs/red-domain/src/lib/files/file.ts +++ b/libs/red-domain/src/lib/files/file.ts @@ -46,6 +46,10 @@ export interface IFile { * Shows if the file was excluded from analysis. */ readonly excluded?: boolean; + /** + * Shows if the file was excluded from automatic analysis. + */ + readonly excludedFromAutomaticAnalysis?: boolean; /** * Set of excluded pages for this file. */