AAE-21047 Get rid of enums (#11643)

* Refactor enums to const objects and update ESLint rules

- Converted several TypeScript enums to const objects for better type inference and immutability.
- Updated ESLint configuration to disable 'no-redeclare' rule and added new restrictions on schema usage.
- Adjusted package-lock.json to mark several dependencies as peer dependencies.

* Refactor enums to const objects in site-dropdown and new-version-uploader models

- Converted TypeScript enums to const objects for improved type safety and immutability in `sites-dropdown.component.ts` and `new-version-uploader.model.ts`.
- Updated related types to reflect the changes in both files.
- Enhanced error handling in the `DropdownSitesComponent` by using the `subscribe` method with an object for better readability.

* Refactor TypeScript types and improve error handling in component tests

- Updated type annotations in `upload.service.ts` and `node-actions.service.ts` for better type safety.
- Enhanced error handling in various component tests by using more descriptive error messages in `task-attachment-list.component.spec.ts`, `attach-file-widget-dialog.component.spec.ts`, and `task-form.component.spec.ts`.
- Removed unnecessary schemas from test configurations in several component spec files to streamline the testing setup.

* Refactor TypeScript enums to const objects for improved type safety

- Converted multiple TypeScript enums to const objects across various models, including `AppConfigValues`, `Status`, `ShowHeaderMode`, `WidgetTypeEnum`, and others.
- Updated related type definitions to enhance type inference and immutability.
- Adjusted ESLint configurations by removing the 'no-redeclare' rule to streamline code quality checks.

* Refactor TypeScript types for improved type safety and consistency

- Updated type annotations in `document-list.component.ts`, `document-actions.service.ts`, and `node-actions.service.ts` to use `Observable` instead of `Subject` for better reactive programming practices.
- Enhanced type definitions in `search-date-range.component.ts` and related spec files to allow `inLastValue` to be either a string or a number, improving flexibility in handling date range inputs.
- Adjusted test cases to reflect these type changes, ensuring consistency across the application.

* Enhance type safety in ViewerComponent by specifying type for closeButtonPosition

- Updated the type annotation for `closeButtonPosition` in `viewer.component.ts` to explicitly define it as `CloseButtonPosition`, improving type safety and clarity.

* Enhance type safety in DataTableComponent by specifying type for showHeader

- Updated the type annotation for `showHeader` in `datatable.component.ts` to explicitly define it as `ShowHeaderMode`, improving type safety and clarity.

* Update PDF viewer test to accommodate varying date formats

- Modified the test for the annotation popup in `pdf-viewer.component.spec.ts` to check for the presence of date components instead of a specific date format, enhancing test robustness across different locales.
This commit is contained in:
Denys Vuika
2026-02-12 16:28:45 +00:00
committed by GitHub
parent d274d62d73
commit 40b15689d5
54 changed files with 503 additions and 415 deletions
@@ -15,7 +15,9 @@
* limitations under the License.
*/
export enum CategoriesManagementMode {
CRUD,
ASSIGN
}
export const CategoriesManagementMode = {
CRUD: 'CRUD',
ASSIGN: 'ASSIGN'
} as const;
export type CategoriesManagementMode = (typeof CategoriesManagementMode)[keyof typeof CategoriesManagementMode];
@@ -74,17 +74,18 @@ export class FileUploadOptions {
versioningEnabled?: boolean;
}
// eslint-disable-next-line no-shadow
export enum FileUploadStatus {
Pending = 0,
Complete = 1,
Starting = 2,
Progress = 3,
Cancelled = 4,
Aborted = 5,
Error = 6,
Deleted = 7
}
export const FileUploadStatus = {
Pending: 0,
Complete: 1,
Starting: 2,
Progress: 3,
Cancelled: 4,
Aborted: 5,
Error: 6,
Deleted: 7
} as const;
export type FileUploadStatus = (typeof FileUploadStatus)[keyof typeof FileUploadStatus];
export class FileModel {
readonly name: string;
@@ -103,7 +103,7 @@ export class UploadService {
* @returns True if files in the queue are still uploading, false otherwise
*/
isUploading(): boolean {
const finishedFileStates = [
const finishedFileStates: FileUploadStatus[] = [
FileUploadStatus.Complete,
FileUploadStatus.Cancelled,
FileUploadStatus.Aborted,
@@ -55,11 +55,13 @@ import { CategoriesManagementComponent } from '../../../category/categories-mana
const DEFAULT_SEPARATOR = ', ';
enum DefaultPanels {
PROPERTIES = 'Properties',
TAGS = 'Tags',
CATEGORIES = 'Categories'
}
const DefaultPanels = {
PROPERTIES: 'Properties',
TAGS: 'Tags',
CATEGORIES: 'Categories'
} as const;
export type DefaultPanels = (typeof DefaultPanels)[keyof typeof DefaultPanels];
@Component({
selector: 'adf-content-metadata',
@@ -28,10 +28,12 @@ import { MatFormFieldModule } from '@angular/material/form-field';
/* eslint-disable no-shadow */
/* eslint-disable @typescript-eslint/naming-convention */
export enum Relations {
Members = 'members',
Containers = 'containers'
}
export const Relations = {
Members: 'members',
Containers: 'containers'
} as const;
export type Relations = (typeof Relations)[keyof typeof Relations];
@Component({
selector: 'adf-sites-dropdown',
@@ -124,7 +126,7 @@ export class DropdownSitesComponent implements OnInit {
}
private loadSiteList() {
const extendedOptions: any = {
const extendedOptions: { skipCount: number; maxItems: number; relations?: string[] } = {
skipCount: this.skipCount,
maxItems: InfiniteSelectScrollDirective.MAX_ITEMS
};
@@ -135,8 +137,8 @@ export class DropdownSitesComponent implements OnInit {
extendedOptions.relations = [this.relations];
}
this.sitesService.getSites(extendedOptions).subscribe(
(sitePaging: SitePaging) => {
this.sitesService.getSites(extendedOptions).subscribe({
next: (sitePaging: SitePaging) => {
if (!this.siteList) {
this.siteList = this.relations === Relations.Members ? this.filteredResultsByMember(sitePaging) : sitePaging;
@@ -163,13 +165,15 @@ export class DropdownSitesComponent implements OnInit {
if (this.value && !this.selected && this.siteListHasMoreItems()) {
this.loadSiteList();
}
this.loading = false;
},
(error) => {
error: (error) => {
this.loading = false;
this.error.emit(error);
},
complete: () => {
this.loading = false;
}
);
});
}
showLoading(): boolean {
@@ -171,7 +171,7 @@ export class DocumentListComponent extends DataTableSchema implements OnInit, On
/** Toggles the header */
@Input()
showHeader = ShowHeaderMode.Data;
showHeader: ShowHeaderMode = ShowHeaderMode.Data;
/**
* User interaction for folder navigation or file preview.
@@ -50,10 +50,12 @@ export class ContentActionModel {
}
}
export enum ContentActionTarget {
Document = 'document',
Folder = 'folder',
All = 'all'
}
export const ContentActionTarget = {
Document: 'document',
Folder: 'folder',
All: 'all'
} as const;
export type ContentActionTarget = (typeof ContentActionTarget)[keyof typeof ContentActionTarget];
export type ContentActionHandler = (obj: any, target?: any, permission?: string) => any;
@@ -18,10 +18,12 @@
/* eslint-disable no-shadow */
/* eslint-disable @typescript-eslint/naming-convention */
export enum NodeAction {
ATTACH = 'ATTACH',
CHOOSE = 'CHOOSE',
COPY = 'COPY',
MOVE = 'MOVE',
NEXT = 'NEXT'
}
export const NodeAction = {
ATTACH: 'ATTACH',
CHOOSE: 'CHOOSE',
COPY: 'COPY',
MOVE: 'MOVE',
NEXT: 'NEXT'
} as const;
export type NodeAction = (typeof NodeAction)[keyof typeof NodeAction];
@@ -114,7 +114,7 @@ export class DocumentActionsService {
return actionObservable;
}
private prepareHandlers(actionObservable: Subject<string>): void {
private prepareHandlers(actionObservable: Observable<string>): void {
actionObservable.subscribe((fileOperationMessage) => {
this.success.next(fileOperationMessage);
}, this.error.next.bind(this.error));
@@ -17,7 +17,8 @@
import { Injectable, Output, EventEmitter } from '@angular/core';
import { Node, NodeEntry } from '@alfresco/js-api';
import { Subject } from 'rxjs';
import { Observable } from 'rxjs';
import { switchMap, map } from 'rxjs/operators';
import { DownloadService } from '@alfresco/adf-core';
import { MatDialog } from '@angular/material/dialog';
import { ContentService } from '../../common/services/content.service';
@@ -57,7 +58,7 @@ export class NodeActionsService {
* @param permission permission which is needed to apply the action
* @returns operation result
*/
copyContent(contentEntry: Node, permission?: string): Subject<string> {
copyContent(contentEntry: Node, permission?: string): Observable<string> {
return this.doFileOperation(NodeAction.COPY, 'content', contentEntry, permission);
}
@@ -68,7 +69,7 @@ export class NodeActionsService {
* @param permission permission which is needed to apply the action
* @returns operation result
*/
copyFolder(contentEntry: Node, permission?: string): Subject<string> {
copyFolder(contentEntry: Node, permission?: string): Observable<string> {
return this.doFileOperation(NodeAction.COPY, 'folder', contentEntry, permission);
}
@@ -79,7 +80,7 @@ export class NodeActionsService {
* @param permission permission which is needed to apply the action
* @returns operation result
*/
moveContent(contentEntry: Node, permission?: string): Subject<string> {
moveContent(contentEntry: Node, permission?: string): Observable<string> {
return this.doFileOperation(NodeAction.MOVE, 'content', contentEntry, permission);
}
@@ -90,7 +91,7 @@ export class NodeActionsService {
* @param permission permission which is needed to apply the action
* @returns operation result
*/
moveFolder(contentEntry: Node, permission?: string): Subject<string> {
moveFolder(contentEntry: Node, permission?: string): Observable<string> {
return this.doFileOperation(NodeAction.MOVE, 'folder', contentEntry, permission);
}
@@ -103,29 +104,14 @@ export class NodeActionsService {
* @param permission permission which is needed to apply the action
* @returns operation result
*/
private doFileOperation(
action: NodeAction.COPY | NodeAction.MOVE,
type: 'content' | 'folder',
contentEntry: Node,
permission?: string
): Subject<string> {
const observable = new Subject<string>();
this.contentDialogService.openCopyMoveDialog(action, contentEntry, permission).subscribe(
(selections: Node[]) => {
private doFileOperation(action: 'COPY' | 'MOVE', type: 'content' | 'folder', contentEntry: Node, permission?: string): Observable<string> {
return this.contentDialogService.openCopyMoveDialog(action, contentEntry, permission).pipe(
switchMap((selections) => {
const selection = selections[0];
this.documentListService[`${action.toLowerCase()}Node`]
return this.documentListService[`${action.toLowerCase()}Node`]
.call(this.documentListService, contentEntry.id, selection.id)
.subscribe(
observable.next.bind(observable, `OPERATION.SUCCESS.${type.toUpperCase()}.${action}`),
observable.error.bind(observable)
);
},
(error) => {
observable.error(error);
return observable;
}
.pipe(map(() => `OPERATION.SUCCESS.${type.toUpperCase()}.${action}`));
})
);
return observable;
}
}
@@ -33,29 +33,30 @@ export interface NewVersionUploaderDialogData {
export type NewVersionUploaderData = VersionManagerUploadData | ViewVersion | RefreshData;
// eslint-disable-next-line no-shadow
export enum NewVersionUploaderDataAction {
refresh = 'refresh',
upload = 'upload',
view = 'view'
}
export const NewVersionUploaderDataAction = {
refresh: 'refresh',
upload: 'upload',
view: 'view'
} as const;
export type NewVersionUploaderDataAction = (typeof NewVersionUploaderDataAction)[keyof typeof NewVersionUploaderDataAction];
interface BaseData {
action: NewVersionUploaderDataAction;
}
export interface VersionManagerUploadData extends BaseData {
action: NewVersionUploaderDataAction.upload;
action: 'upload';
newVersion: NodeEntityEvent;
currentVersion: NodeChildAssociation;
}
export interface ViewVersion extends BaseData {
action: NewVersionUploaderDataAction.view;
action: 'view';
versionId: string;
}
export interface RefreshData extends BaseData {
action: NewVersionUploaderDataAction.refresh;
action: 'refresh';
node: Node;
}
@@ -106,7 +106,7 @@ describe('SearchDateRangeTabbedComponent', () => {
inLastMockData = {
dateRangeType: DateRangeType.IN_LAST,
inLastValueType: InLastDateType.WEEKS,
inLastValue: '5',
inLastValue: 5,
betweenStartDate: undefined,
betweenEndDate: undefined
};
@@ -173,7 +173,7 @@ describe('SearchDateRangeTabbedComponent', () => {
inLastMockData = {
dateRangeType: DateRangeType.IN_LAST,
inLastValueType: InLastDateType.DAYS,
inLastValue: '9',
inLastValue: 9,
betweenStartDate: null,
betweenEndDate: null
};
@@ -189,7 +189,7 @@ describe('SearchDateRangeTabbedComponent', () => {
inLastMockData = {
dateRangeType: DateRangeType.IN_LAST,
inLastValueType: InLastDateType.MONTHS,
inLastValue: '7',
inLastValue: 7,
betweenStartDate: null,
betweenEndDate: null
};
@@ -155,15 +155,16 @@ export class SearchDateRangeTabbedComponent implements SearchWidget, OnInit {
let endDate: Date;
if (value.dateRangeType === DateRangeType.IN_LAST) {
if (value.inLastValue) {
const numValue = typeof value.inLastValue === 'number' ? value.inLastValue : parseInt(value.inLastValue, 10);
switch (value.inLastValueType) {
case InLastDateType.DAYS:
startDate = startOfDay(subDays(new Date(), parseInt(value.inLastValue, 10)));
startDate = startOfDay(subDays(new Date(), numValue));
break;
case InLastDateType.WEEKS:
startDate = startOfWeek(subWeeks(new Date(), parseInt(value.inLastValue, 10)));
startDate = startOfWeek(subWeeks(new Date(), numValue));
break;
case InLastDateType.MONTHS:
startDate = startOfMonth(subMonths(new Date(), parseInt(value.inLastValue, 10)));
startDate = startOfMonth(subMonths(new Date(), numValue));
break;
default:
break;
@@ -15,8 +15,10 @@
* limitations under the License.
*/
export enum DateRangeType {
ANY = 'ANY',
IN_LAST = 'IN_LAST',
BETWEEN = 'BETWEEN',
}
export const DateRangeType = {
ANY: 'ANY',
IN_LAST: 'IN_LAST',
BETWEEN: 'BETWEEN'
} as const;
export type DateRangeType = (typeof DateRangeType)[keyof typeof DateRangeType];
@@ -15,8 +15,10 @@
* limitations under the License.
*/
export enum InLastDateType {
DAYS = 'DAYS',
WEEKS = 'WEEKS',
MONTHS = 'MONTHS'
}
export const InLastDateType = {
DAYS: 'DAYS',
WEEKS: 'WEEKS',
MONTHS: 'MONTHS'
} as const;
export type InLastDateType = (typeof InLastDateType)[keyof typeof InLastDateType];
@@ -18,6 +18,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { SearchDateRangeComponent } from './search-date-range.component';
import { SearchDateRange } from './search-date-range';
import { addDays, endOfToday, format, parse, startOfYesterday, subDays } from 'date-fns';
import { Validators } from '@angular/forms';
import { HarnessLoader } from '@angular/cdk/testing';
@@ -226,7 +227,7 @@ describe('SearchDateRangeComponent', () => {
it('should not emit values when form is invalid', async () => {
spyOn(component.changed, 'emit');
let value = {
let value: Partial<SearchDateRange> = {
dateRangeType: component.DateRangeType.IN_LAST,
inLastValueType: component.InLastDateType.WEEKS,
inLastValue: '',
@@ -251,8 +252,8 @@ describe('SearchDateRangeComponent', () => {
dateRangeType: component.DateRangeType.BETWEEN,
inLastValueType: component.InLastDateType.DAYS,
inLastValue: undefined,
betweenStartDate: '',
betweenEndDate: ''
betweenStartDate: undefined,
betweenEndDate: undefined
};
dateRangeTypeRadioButton = await loader.getHarness(MatRadioButtonHarness.with({ selector: '[data-automation-id="date-range-between"]' }));
await dateRangeTypeRadioButton.check();
@@ -262,7 +263,7 @@ describe('SearchDateRangeComponent', () => {
it('should emit values when form is valid', async () => {
spyOn(component.changed, 'emit');
let value = {
let value: Partial<SearchDateRange> = {
dateRangeType: component.DateRangeType.IN_LAST,
inLastValueType: component.InLastDateType.WEEKS,
inLastValue: 5,
@@ -21,7 +21,7 @@ import { InLastDateType } from './in-last-date-type';
export interface SearchDateRange {
dateRangeType: DateRangeType;
inLastValueType?: InLastDateType;
inLastValue?: string;
inLastValue?: string | number;
betweenStartDate?: Date;
betweenEndDate?: Date;
}
@@ -136,7 +136,7 @@ export class SearchFilterAutocompleteChipsComponent implements SearchWidget, OnI
private updateQuery(updateContext = true) {
this.context.filterRawParams[this.id] = this.selectedOptions.length > 0 ? this.selectedOptions : undefined;
this.displayValue$.next(this.selectedOptions.map((option) => option.value).join(', '));
if (this.context && this.settings && this.settings.field) {
if (this.context && this.settings?.field) {
let queryFragments;
switch (this.settings.field) {
case AutocompleteField.CATEGORIES:
@@ -28,12 +28,14 @@ import { TranslatePipe } from '@ngx-translate/core';
import { FormsModule } from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export enum LogicalSearchFields {
MATCH_ALL = 'matchAll',
MATCH_ANY = 'matchAny',
EXCLUDE = 'exclude',
MATCH_EXACT = 'matchExact'
}
export const LogicalSearchFields = {
MATCH_ALL: 'matchAll',
MATCH_ANY: 'matchAny',
EXCLUDE: 'exclude',
MATCH_EXACT: 'matchExact'
} as const;
export type LogicalSearchFields = (typeof LogicalSearchFields)[keyof typeof LogicalSearchFields];
export type LogicalSearchConditionEnumValuedKeys = { [T in LogicalSearchFields]: string };
// eslint-disable-next-line @typescript-eslint/no-empty-interface
@@ -15,8 +15,10 @@
* limitations under the License.
*/
export enum FileSizeOperator {
AT_LEAST = 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST',
AT_MOST = 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST',
EXACTLY = 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.EXACTLY'
}
export const FileSizeOperator = {
AT_LEAST: 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST',
AT_MOST: 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST',
EXACTLY: 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.EXACTLY'
} as const;
export type FileSizeOperator = (typeof FileSizeOperator)[keyof typeof FileSizeOperator];
@@ -22,8 +22,10 @@ export interface AutocompleteOption {
query?: string;
}
export enum AutocompleteField {
TAG = 'TAG',
CATEGORIES = 'cm:categories',
LOCATION = 'SITE'
}
export const AutocompleteField = {
TAG: 'TAG',
CATEGORIES: 'cm:categories',
LOCATION: 'SITE'
} as const;
export type AutocompleteField = (typeof AutocompleteField)[keyof typeof AutocompleteField];
@@ -48,14 +48,16 @@ export interface FacetFieldSettings {
bucketSortDirection?: FacetBucketSortDirection;
}
// eslint-disable-next-line no-shadow
export enum FacetBucketSortBy {
LABEL = 'LABEL',
COUNT = 'COUNT'
}
export const FacetBucketSortBy = {
LABEL: 'LABEL',
COUNT: 'COUNT'
} as const;
// eslint-disable-next-line no-shadow
export enum FacetBucketSortDirection {
ASCENDING = 'ASCENDING',
DESCENDING = 'DESCENDING'
}
export type FacetBucketSortBy = (typeof FacetBucketSortBy)[keyof typeof FacetBucketSortBy];
export const FacetBucketSortDirection = {
ASCENDING: 'ASCENDING',
DESCENDING: 'DESCENDING'
} as const;
export type FacetBucketSortDirection = (typeof FacetBucketSortDirection)[keyof typeof FacetBucketSortDirection];
@@ -20,7 +20,9 @@
* Create mode allows only for creating completely new tags.
* Create and Assign mode allows for both - creation of new tags and selection of existing tags.
*/
export enum TagsCreatorMode {
CREATE,
CREATE_AND_ASSIGN
}
export const TagsCreatorMode = {
CREATE: 'CREATE',
CREATE_AND_ASSIGN: 'CREATE_AND_ASSIGN'
} as const;
export type TagsCreatorMode = (typeof TagsCreatorMode)[keyof typeof TagsCreatorMode];
@@ -15,10 +15,12 @@
* limitations under the License.
*/
export enum TreeNodeType {
RegularNode,
LoadMoreNode
}
export const TreeNodeType = {
RegularNode: 'RegularNode',
LoadMoreNode: 'LoadMoreNode'
} as const;
export type TreeNodeType = (typeof TreeNodeType)[keyof typeof TreeNodeType];
export interface TreeNode {
id: string;