From 0af9464f23790fc556b22a4de4bb09c178468c4d Mon Sep 17 00:00:00 2001 From: Robert Sun <107655677+rsun19@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:29:44 -0400 Subject: [PATCH] first round of updates to disable type assertions --- frontend/.eslintrc | 12 ++++++ .../src/__tests__/unit/testUtils/hooks.ts | 7 ++-- frontend/src/api/errorUtils.ts | 3 +- frontend/src/api/modelRegistry/errorUtils.ts | 2 +- frontend/src/api/pipelines/errorUtils.ts | 6 ++- frontend/src/api/prometheus/serving.ts | 36 +++++++++++------ frontend/src/api/trustyai/errorUtils.ts | 3 +- frontend/src/app/AppContext.ts | 10 +---- frontend/src/components/DocCardBadges.tsx | 4 +- .../FilterSidePanelCategoryItem.tsx | 12 +++--- frontend/src/components/OdhDocListItem.tsx | 5 ++- frontend/src/components/ToastNotification.tsx | 3 +- .../browserStorage/BrowserStorageContext.tsx | 1 + .../components/table/useTableColumnSort.ts | 2 + frontend/src/concepts/areas/utils.ts | 3 +- .../dashboard/DashboardSearchField.tsx | 6 ++- .../concepts/distributedWorkloads/utils.tsx | 3 +- .../src/concepts/docResources/docUtils.ts | 1 + frontend/src/concepts/metrics/utils.ts | 6 +-- .../context/ModelRegistryContext.tsx | 1 + .../content/PipelineAndVersionContext.tsx | 13 +++--- .../PipelineRunArtifactSelect.tsx | 10 +++-- .../ConfusionMatrixCompare.tsx | 12 +++--- .../metricsSection/confusionMatrix/utils.ts | 9 ++++- .../compareRuns/metricsSection/roc/utils.ts | 17 ++++---- .../compareRuns/metricsSection/utils.ts | 10 ++--- .../ConfigurePipelinesServerModal.tsx | 3 +- .../pipelines/content/createRun/RunPage.tsx | 2 +- .../content/tables/PipelineFilterBar.tsx | 8 ++-- .../content/tables/usePipelineFilter.ts | 8 ++-- .../pipelines/context/MlmdListContext.tsx | 1 + .../pipelines/context/PipelinesContext.tsx | 5 ++- frontend/src/concepts/pipelines/utils.ts | 2 +- .../topology/PipelineDefaultTaskGroup.tsx | 4 +- .../concepts/topology/PipelineTaskEdge.tsx | 7 +++- .../topology/customNodes/ArtifactTaskNode.tsx | 19 ++++----- .../topology/customNodes/StandardTaskNode.tsx | 4 +- .../trustyai/context/TrustyAIContext.tsx | 1 + .../concepts/trustyai/useManageTrustyAICR.ts | 21 +++++----- .../AcceleratorIdentifierMultiselect.tsx | 6 ++- .../pages/exploreApplication/EnableModal.tsx | 6 ++- .../home/resources/useResourcesSection.tsx | 3 +- .../home/resources/useSpecifiedResources.tsx | 4 +- .../learningCenter/ApplicationFilters.tsx | 5 +-- .../pages/learningCenter/DocTypeFilters.tsx | 40 ++++++++----------- .../pages/learningCenter/EnabledFilters.tsx | 14 +------ .../learningCenter/LearningCenterToolbar.tsx | 4 +- .../learningCenter/ProviderTypeFilters.tsx | 5 +-- .../ModelVersionSelector.tsx | 6 ++- .../ModelVersions/ModelVersionListView.tsx | 6 ++- .../ModelVersionsArchiveListView.tsx | 6 ++- .../RegisteredModelListView.tsx | 6 ++- .../RegisteredModelsArchiveListView.tsx | 6 ++- ...ustomServingRuntimeAPIProtocolSelector.tsx | 8 +++- .../customServingRuntimes/utils.ts | 16 ++++++-- .../modelServing/screens/global/utils.ts | 5 ++- .../metrics/EnsureMetricsAvailable.tsx | 5 ++- .../metrics/ModelServingMetricsContext.tsx | 14 +++++-- .../screens/metrics/bias/BiasChart.tsx | 4 +- .../MetricTypeField.tsx | 12 +++--- .../metrics/performance/ModelMeshMetrics.tsx | 9 +---- .../metrics/performance/ServerGraphs.tsx | 23 ++--------- .../modelServing/screens/metrics/utils.tsx | 4 +- .../screens/admin/NotebookControllerTabs.tsx | 6 ++- .../screens/admin/UserTableCellTransform.tsx | 1 + .../server/EnvironmentVariablesField.tsx | 3 +- .../server/EnvironmentVariablesRow.tsx | 4 +- .../executions/details/ExecutionDetails.tsx | 4 +- .../global/runs/GlobalPipelineRunsTabs.tsx | 7 +++- .../pages/projects/ProjectDetailsContext.tsx | 1 + .../deployedModels/DeployedModelsSection.tsx | 13 +++--- .../environmentVariables/EnvConfigMap.tsx | 5 ++- .../environmentVariables/EnvSecret.tsx | 5 ++- .../EnvTypeSelectField.tsx | 12 ++++-- .../spawner/environmentVariables/utils.ts | 8 ++-- .../projects/screens/spawner/spawnerUtils.ts | 5 ++- .../screens/spawner/useBuildStatuses.ts | 4 +- frontend/src/redux/context.ts | 1 + frontend/src/redux/store/store.ts | 4 +- frontend/src/typeHelpers.ts | 4 +- frontend/src/utilities/NavData.tsx | 5 +-- .../src/utilities/__tests__/utils.spec.ts | 20 +++++++++- .../utilities/useAcceleratorProfileState.ts | 5 +-- frontend/src/utilities/useDraggableTable.ts | 4 +- .../utilities/useWatchNotebooksForUsers.tsx | 11 +++-- frontend/src/utilities/utils.ts | 11 ++++- 86 files changed, 376 insertions(+), 266 deletions(-) diff --git a/frontend/.eslintrc b/frontend/.eslintrc index cca83da5be..ec95917b9b 100755 --- a/frontend/.eslintrc +++ b/frontend/.eslintrc @@ -315,6 +315,18 @@ } ] } + }, + { + "files": ["*.ts", "*.tsx"], + "excludedFiles": ["**/__mocks__/**", "**/__tests__/**"], + "rules": { + "@typescript-eslint/consistent-type-assertions": [ + "error", + { + "assertionStyle": "never" + } + ] + } } ] } diff --git a/frontend/src/__tests__/unit/testUtils/hooks.ts b/frontend/src/__tests__/unit/testUtils/hooks.ts index c82a2ff7cd..86c308422f 100644 --- a/frontend/src/__tests__/unit/testUtils/hooks.ts +++ b/frontend/src/__tests__/unit/testUtils/hooks.ts @@ -67,8 +67,8 @@ export const renderHook = < options?: RenderHookOptions, ): RenderHookResultExt => { let updateCount = 0; - let prevResult: Result | undefined; - let currentResult: Result | undefined; + let prevResult: Result; + let currentResult: Result; const renderResult = renderHookRTL((props) => { updateCount++; @@ -80,8 +80,7 @@ export const renderHook = < const renderResultExt: RenderHookResultExt = { ...renderResult, - getPreviousResult: () => - updateCount > 1 ? (prevResult as Result) : renderResult.result.current, + getPreviousResult: () => (updateCount > 1 ? prevResult : renderResult.result.current), getUpdateCount: () => updateCount, diff --git a/frontend/src/api/errorUtils.ts b/frontend/src/api/errorUtils.ts index e972b16c41..1ac6f1c5e9 100644 --- a/frontend/src/api/errorUtils.ts +++ b/frontend/src/api/errorUtils.ts @@ -2,7 +2,7 @@ import { K8sStatus } from '@openshift/dynamic-plugin-sdk-utils'; import { AxiosError } from 'axios'; export const isK8sStatus = (data: unknown): data is K8sStatus => - (data as K8sStatus).kind === 'Status'; + typeof data === 'object' && data !== null && 'kind' in data && data.kind === 'Status'; export class K8sStatusError extends Error { public statusObject: K8sStatus; @@ -18,6 +18,7 @@ const isAxiosErrorWithResponseMessage = ( error?: Error | AxiosError, ): error is AxiosError<{ message: string }> => Boolean( + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions error && typeof (error as AxiosError<{ message: string }>).response?.data.message === 'string', ); diff --git a/frontend/src/api/modelRegistry/errorUtils.ts b/frontend/src/api/modelRegistry/errorUtils.ts index d9a3238d74..1282802199 100644 --- a/frontend/src/api/modelRegistry/errorUtils.ts +++ b/frontend/src/api/modelRegistry/errorUtils.ts @@ -2,7 +2,7 @@ import { ModelRegistryError } from '~/concepts/modelRegistry/types'; import { isCommonStateError } from '~/utilities/useFetchState'; const isError = (e: unknown): e is ModelRegistryError => - ['code', 'message'].every((key) => key in (e as ModelRegistryError)); + typeof e === 'object' && e !== null && ['code', 'message'].every((key) => key in e); export const handleModelRegistryFailures = (promise: Promise): Promise => promise diff --git a/frontend/src/api/pipelines/errorUtils.ts b/frontend/src/api/pipelines/errorUtils.ts index 384e3d6d2f..4efaa5da7c 100644 --- a/frontend/src/api/pipelines/errorUtils.ts +++ b/frontend/src/api/pipelines/errorUtils.ts @@ -14,10 +14,12 @@ type ResultErrorKF = { }; const isErrorKF = (e: unknown): e is ErrorKF => - ['error', 'code', 'message'].every((key) => key in (e as ErrorKF)); + typeof e === 'object' && e !== null && ['error', 'code', 'message'].every((key) => key in e); const isErrorDetailsKF = (result: unknown): result is ResultErrorKF => - ['error_details', 'error_message'].every((key) => key in (result as ResultErrorKF)); + typeof result === 'object' && + result !== null && + ['error_details', 'error_message'].every((key) => key in result); export const handlePipelineFailures = (promise: Promise): Promise => promise diff --git a/frontend/src/api/prometheus/serving.ts b/frontend/src/api/prometheus/serving.ts index 9cf7f35188..102b389c2b 100644 --- a/frontend/src/api/prometheus/serving.ts +++ b/frontend/src/api/prometheus/serving.ts @@ -14,6 +14,7 @@ import { RefreshIntervalValue } from '~/concepts/metrics/const'; import useRefreshInterval from '~/utilities/useRefreshInterval'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; import { PROMETHEUS_BIAS_PATH } from '~/api/prometheus/const'; +import { EitherNotBoth } from '~/typeHelpers'; import useQueryRangeResourceData from './useQueryRangeResourceData'; import { defaultResponsePredicate, @@ -22,17 +23,26 @@ import { export const useModelServingMetrics = ( type: PerformanceMetricType, - queries: { [key in ModelMetricType]: string } | { [key in ServerMetricType]: string }, + queries: EitherNotBoth< + { [key in ModelMetricType]: string }, + { [key in ServerMetricType]: string } + >, timeframe: TimeframeTitle, lastUpdateTime: number, setLastUpdateTime: (time: number) => void, refreshInterval: RefreshIntervalTitle, namespace: string, ): { - data: Record< - ServerMetricType | ModelMetricType, - ContextResourceData - >; + data: { + [ServerMetricType.REQUEST_COUNT]: ContextResourceData; + [ServerMetricType.AVG_RESPONSE_TIME]: ContextResourceData; + [ServerMetricType.CPU_UTILIZATION]: ContextResourceData; + [ServerMetricType.MEMORY_UTILIZATION]: ContextResourceData; + [ModelMetricType.REQUEST_COUNT_FAILED]: ContextResourceData; + [ModelMetricType.REQUEST_COUNT_SUCCESS]: ContextResourceData; + [ModelMetricType.TRUSTY_AI_SPD]: ContextResourceData; + [ModelMetricType.TRUSTY_AI_DIR]: ContextResourceData; + }; refresh: () => void; } => { const [end, setEnd] = React.useState(lastUpdateTime); @@ -43,7 +53,7 @@ export const useModelServingMetrics = ( const serverRequestCount = useQueryRangeResourceData( performanceMetricsAreaAvailable && type === PerformanceMetricType.SERVER, - (queries as { [key in ServerMetricType]: string })[ServerMetricType.REQUEST_COUNT], + queries[ServerMetricType.REQUEST_COUNT] ?? '', end, timeframe, defaultResponsePredicate, @@ -53,7 +63,7 @@ export const useModelServingMetrics = ( const serverAverageResponseTime = useQueryRangeResourceData( performanceMetricsAreaAvailable && type === PerformanceMetricType.SERVER, - (queries as { [key in ServerMetricType]: string })[ServerMetricType.AVG_RESPONSE_TIME], + queries[ServerMetricType.AVG_RESPONSE_TIME] ?? '', end, timeframe, prometheusQueryRangeResponsePredicate, @@ -62,7 +72,7 @@ export const useModelServingMetrics = ( const serverCPUUtilization = useQueryRangeResourceData( performanceMetricsAreaAvailable && type === PerformanceMetricType.SERVER, - (queries as { [key in ServerMetricType]: string })[ServerMetricType.CPU_UTILIZATION], + queries[ServerMetricType.CPU_UTILIZATION] ?? '', end, timeframe, defaultResponsePredicate, @@ -71,7 +81,7 @@ export const useModelServingMetrics = ( const serverMemoryUtilization = useQueryRangeResourceData( performanceMetricsAreaAvailable && type === PerformanceMetricType.SERVER, - (queries as { [key in ServerMetricType]: string })[ServerMetricType.MEMORY_UTILIZATION], + queries[ServerMetricType.MEMORY_UTILIZATION] ?? '', end, timeframe, defaultResponsePredicate, @@ -80,7 +90,7 @@ export const useModelServingMetrics = ( const modelRequestSuccessCount = useQueryRangeResourceData( performanceMetricsAreaAvailable && type === PerformanceMetricType.MODEL, - (queries as { [key in ModelMetricType]: string })[ModelMetricType.REQUEST_COUNT_SUCCESS], + queries[ModelMetricType.REQUEST_COUNT_SUCCESS] ?? '', end, timeframe, defaultResponsePredicate, @@ -89,7 +99,7 @@ export const useModelServingMetrics = ( const modelRequestFailedCount = useQueryRangeResourceData( performanceMetricsAreaAvailable && type === PerformanceMetricType.MODEL, - (queries as { [key in ModelMetricType]: string })[ModelMetricType.REQUEST_COUNT_FAILED], + queries[ModelMetricType.REQUEST_COUNT_FAILED] ?? '', end, timeframe, defaultResponsePredicate, @@ -98,7 +108,7 @@ export const useModelServingMetrics = ( const modelTrustyAISPD = useQueryRangeResourceData( biasMetricsAreaAvailable && type === PerformanceMetricType.MODEL, - (queries as { [key in ModelMetricType]: string })[ModelMetricType.TRUSTY_AI_SPD], + queries[ModelMetricType.TRUSTY_AI_SPD] ?? '', end, timeframe, prometheusQueryRangeResponsePredicate, @@ -108,7 +118,7 @@ export const useModelServingMetrics = ( const modelTrustyAIDIR = useQueryRangeResourceData( biasMetricsAreaAvailable && type === PerformanceMetricType.MODEL, - (queries as { [key in ModelMetricType]: string })[ModelMetricType.TRUSTY_AI_DIR], + queries[ModelMetricType.TRUSTY_AI_DIR] ?? '', end, timeframe, prometheusQueryRangeResponsePredicate, diff --git a/frontend/src/api/trustyai/errorUtils.ts b/frontend/src/api/trustyai/errorUtils.ts index 60fd07a7f6..6f3625f38e 100644 --- a/frontend/src/api/trustyai/errorUtils.ts +++ b/frontend/src/api/trustyai/errorUtils.ts @@ -9,8 +9,7 @@ type TrustyAIClientErrorViolation = { }; const isTrustyAIClientError = (e: unknown): e is TrustyAIClientError => - typeof e === 'object' && - ['title', 'status', 'violations'].every((key) => key in (e as TrustyAIClientError)); + typeof e === 'object' && e !== null && ['title', 'status', 'violations'].every((key) => key in e); export const handleTrustyAIFailures = (promise: Promise): Promise => promise.then((result) => { diff --git a/frontend/src/app/AppContext.ts b/frontend/src/app/AppContext.ts index bdc0141450..2b51e99c15 100644 --- a/frontend/src/app/AppContext.ts +++ b/frontend/src/app/AppContext.ts @@ -8,13 +8,7 @@ type AppContextProps = { storageClasses: StorageClassKind[]; }; -const defaultAppContext: AppContextProps = { - buildStatuses: [], - // At runtime dashboardConfig is never null -- DO NOT DO THIS usually - dashboardConfig: null as unknown as DashboardConfigKind, - storageClasses: [] as StorageClassKind[], -}; - -export const AppContext = React.createContext(defaultAppContext); +// eslint-disable-next-line @typescript-eslint/consistent-type-assertions +export const AppContext = React.createContext({} as AppContextProps); export const useAppContext = (): AppContextProps => React.useContext(AppContext); diff --git a/frontend/src/components/DocCardBadges.tsx b/frontend/src/components/DocCardBadges.tsx index 9a82f54235..e0270402dd 100644 --- a/frontend/src/components/DocCardBadges.tsx +++ b/frontend/src/components/DocCardBadges.tsx @@ -5,7 +5,7 @@ import { QuickStartContext, QuickStartContextValues } from '@patternfly/quicksta import { OdhDocument, OdhDocumentType } from '~/types'; import { getQuickStartCompletionStatus, CompletionStatusEnum } from '~/utilities/quickStartUtils'; import { DOC_TYPE_TOOLTIPS } from '~/utilities/const'; -import { getLabelColorForDocType, getDuration } from '~/utilities/utils'; +import { getLabelColorForDocType, getDuration, asEnumMember } from '~/utilities/utils'; import { DOC_TYPE_LABEL } from '~/pages/learningCenter/const'; import './OdhCard.scss'; @@ -19,7 +19,7 @@ const DocCardBadges: React.FC = ({ odhDoc }) => { const [completionStatus, setCompletionStatus] = React.useState< CompletionStatusEnum | undefined >(); - const docType = odhDoc.spec.type as OdhDocumentType; + const docType = asEnumMember(odhDoc.spec.type, OdhDocumentType) ?? OdhDocumentType.Documentation; const docName = odhDoc.metadata.name; const duration = odhDoc.spec.durationMinutes; diff --git a/frontend/src/components/FilterSidePanelCategoryItem.tsx b/frontend/src/components/FilterSidePanelCategoryItem.tsx index 1c01ec741b..9dcb06ddd8 100644 --- a/frontend/src/components/FilterSidePanelCategoryItem.tsx +++ b/frontend/src/components/FilterSidePanelCategoryItem.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { css } from '@patternfly/react-styles'; -import { Checkbox } from '@patternfly/react-core'; +import { Checkbox, CheckboxProps } from '@patternfly/react-core'; -export interface FilterSidePanelCategoryItemProps extends React.HTMLProps { +export interface FilterSidePanelCategoryItemProps { id: string; /** Children nodes */ children: React.ReactNode; @@ -13,7 +13,7 @@ export interface FilterSidePanelCategoryItemProps extends React.HTMLProps) => void; + onChange?: CheckboxProps['onChange']; /** Flag to show if the Filter Item Checkbox is checked. */ checked?: boolean; /** Title of the checkbox */ @@ -27,7 +27,7 @@ const FilterSidePanelCategoryItem: React.FunctionComponent = ({ odhDoc, favorite, updateFav }; const renderTypeBadge = () => { - const docType = odhDoc.spec.type as OdhDocumentType; + const docType = + asEnumMember(odhDoc.spec.type, OdhDocumentType) ?? OdhDocumentType.Documentation; const typeBadgeClasses = classNames('odh-list-item__partner-badge odh-m-doc', { 'odh-m-documentation': docType === OdhDocumentType.Documentation, 'odh-m-tutorial': docType === OdhDocumentType.Tutorial, diff --git a/frontend/src/components/ToastNotification.tsx b/frontend/src/components/ToastNotification.tsx index 4f2a2299ab..b0a945705b 100644 --- a/frontend/src/components/ToastNotification.tsx +++ b/frontend/src/components/ToastNotification.tsx @@ -3,6 +3,7 @@ import { Alert, AlertActionCloseButton, AlertVariant } from '@patternfly/react-c import { AppNotification } from '~/redux/types'; import { ackNotification, hideNotification } from '~/redux/actions/actions'; import { useAppDispatch } from '~/redux/hooks'; +import { asEnumMember } from '~/utilities/utils'; const TOAST_NOTIFICATION_TIMEOUT = 8 * 1000; @@ -36,7 +37,7 @@ const ToastNotification: React.FC = ({ notification }) = return ( dispatch(ackNotification(notification))} /> diff --git a/frontend/src/components/browserStorage/BrowserStorageContext.tsx b/frontend/src/components/browserStorage/BrowserStorageContext.tsx index f64d105113..ff903a9cfa 100644 --- a/frontend/src/components/browserStorage/BrowserStorageContext.tsx +++ b/frontend/src/components/browserStorage/BrowserStorageContext.tsx @@ -48,6 +48,7 @@ export const useBrowserStorage = ( [isSessionStorage, jsonify, setJSONValue, setStringValue, storageKey], ); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return [(getValue(storageKey, jsonify, isSessionStorage) as T) ?? defaultValue, setValue]; }; diff --git a/frontend/src/components/table/useTableColumnSort.ts b/frontend/src/components/table/useTableColumnSort.ts index ce179df0a0..56c4519d68 100644 --- a/frontend/src/components/table/useTableColumnSort.ts +++ b/frontend/src/components/table/useTableColumnSort.ts @@ -105,7 +105,9 @@ const useTableColumnSort = ( return 0; } + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const dataValueA = a[columnField.field as keyof T]; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const dataValueB = b[columnField.field as keyof T]; if (typeof dataValueA === 'string' && typeof dataValueB === 'string') { return dataValueA.localeCompare(dataValueB); diff --git a/frontend/src/concepts/areas/utils.ts b/frontend/src/concepts/areas/utils.ts index 2957080141..5926dc80a3 100644 --- a/frontend/src/concepts/areas/utils.ts +++ b/frontend/src/concepts/areas/utils.ts @@ -15,8 +15,7 @@ const getFlags = (dashboardConfigSpec: DashboardConfigKind['spec']): FlagState = typeof value === 'boolean'; return { - ...Object.keys(flags).reduce((acc, key) => { - const value = flags[key as FeatureFlag]; + ...Object.entries(flags).reduce((acc, [key, value]) => { if (isFeatureFlag(key, value)) { acc[key] = key.startsWith('disable') ? !value : value; } diff --git a/frontend/src/concepts/dashboard/DashboardSearchField.tsx b/frontend/src/concepts/dashboard/DashboardSearchField.tsx index 18d45e1da6..44d96fa0a0 100644 --- a/frontend/src/concepts/dashboard/DashboardSearchField.tsx +++ b/frontend/src/concepts/dashboard/DashboardSearchField.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { InputGroup, SearchInput, InputGroupItem } from '@patternfly/react-core'; import SimpleDropdownSelect from '~/components/SimpleDropdownSelect'; +import { asEnumMember } from '~/utilities/utils'; // List all the possible search fields here export enum SearchType { @@ -48,7 +49,10 @@ const DashboardSearchField: React.FC = ({ }))} value={searchType} onChange={(key) => { - onSearchTypeChange(key as SearchType); + const enumMember = asEnumMember(key, SearchType); + if (enumMember !== null) { + onSearchTypeChange(enumMember); + } }} icon={icon} /> diff --git a/frontend/src/concepts/distributedWorkloads/utils.tsx b/frontend/src/concepts/distributedWorkloads/utils.tsx index db466618d7..29a4de3785 100644 --- a/frontend/src/concepts/distributedWorkloads/utils.tsx +++ b/frontend/src/concepts/distributedWorkloads/utils.tsx @@ -34,6 +34,7 @@ import { convertToUnit, } from '~/utilities/valueUnits'; import { WorkloadWithUsage } from '~/api'; +import { isEnumMember } from '~/utilities/utils'; export enum WorkloadStatusType { Pending = 'Pending', @@ -166,7 +167,7 @@ export const getWorkloadName = (workload: WorkloadKind): string => workload.metadata?.name || 'Unnamed'; export const isKnownWorkloadOwnerType = (s: string): s is WorkloadOwnerType => - (Object.values(WorkloadOwnerType) as string[]).includes(s); + isEnumMember(s, WorkloadOwnerType); export const getWorkloadOwner = ( workload: WorkloadKind, diff --git a/frontend/src/concepts/docResources/docUtils.ts b/frontend/src/concepts/docResources/docUtils.ts index e66d889d94..1abeb77360 100644 --- a/frontend/src/concepts/docResources/docUtils.ts +++ b/frontend/src/concepts/docResources/docUtils.ts @@ -30,6 +30,7 @@ export const getQuickStartDocs = ( // Get doc cards for the quick starts const docs: OdhDocument[] = quickStarts.map( (quickStart) => + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions _.merge({}, quickStart, { spec: { type: OdhDocumentType.QuickStart, diff --git a/frontend/src/concepts/metrics/utils.ts b/frontend/src/concepts/metrics/utils.ts index 1f44bc91cf..c88e5d2592 100644 --- a/frontend/src/concepts/metrics/utils.ts +++ b/frontend/src/concepts/metrics/utils.ts @@ -1,12 +1,12 @@ import { SelectOptionObject } from '@patternfly/react-core/deprecated'; import { TimeframeTitle, RefreshIntervalTitle } from '~/concepts/metrics/types'; +import { isEnumMember } from '~/utilities/utils'; export const isTimeframeTitle = ( timeframe: string | SelectOptionObject, -): timeframe is TimeframeTitle => - Object.values(TimeframeTitle).includes(timeframe as TimeframeTitle); +): timeframe is TimeframeTitle => isEnumMember(timeframe.toString(), TimeframeTitle); export const isRefreshIntervalTitle = ( refreshInterval: string | SelectOptionObject, ): refreshInterval is RefreshIntervalTitle => - Object.values(RefreshIntervalTitle).includes(refreshInterval as RefreshIntervalTitle); + isEnumMember(refreshInterval.toString(), RefreshIntervalTitle); diff --git a/frontend/src/concepts/modelRegistry/context/ModelRegistryContext.tsx b/frontend/src/concepts/modelRegistry/context/ModelRegistryContext.tsx index eb3bdce52d..dfde7ebcc8 100644 --- a/frontend/src/concepts/modelRegistry/context/ModelRegistryContext.tsx +++ b/frontend/src/concepts/modelRegistry/context/ModelRegistryContext.tsx @@ -28,6 +28,7 @@ export const ModelRegistryContext = React.createContext undefined, refreshState: async () => undefined, diff --git a/frontend/src/concepts/pipelines/content/PipelineAndVersionContext.tsx b/frontend/src/concepts/pipelines/content/PipelineAndVersionContext.tsx index eed1a71d94..858635fbca 100644 --- a/frontend/src/concepts/pipelines/content/PipelineAndVersionContext.tsx +++ b/frontend/src/concepts/pipelines/content/PipelineAndVersionContext.tsx @@ -77,12 +77,13 @@ const PipelineAndVersionContextProvider: React.FC ({ pipelines: selectedPipelines, - versions: (Object.values(selectedVersions) as SelectedVersion[]) - .map((selectedVersion) => - selectedVersion.versions.map((version) => ({ - pipelineName: selectedVersion.pipelineName, - version, - })), + versions: Object.values(selectedVersions) + .map( + (selectedVersion) => + selectedVersion?.versions.map((version) => ({ + pipelineName: selectedVersion.pipelineName, + version, + })) ?? [], ) .flat() .filter((selection) => { diff --git a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/PipelineRunArtifactSelect.tsx b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/PipelineRunArtifactSelect.tsx index 2b15849d79..97c5f0217c 100644 --- a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/PipelineRunArtifactSelect.tsx +++ b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/PipelineRunArtifactSelect.tsx @@ -49,10 +49,12 @@ export const PipelineRunArtifactSelect = ({ isOpen={isOpen} selected={selectedItemTitles} onSelect={(_event, value) => { - if (selectedItemTitles.includes(value as string)) { - setSelectedItemTitles(selectedItemTitles.filter((id) => id !== value)); - } else { - setSelectedItemTitles([...selectedItemTitles, value as string]); + if (typeof value === 'string') { + if (selectedItemTitles.includes(value)) { + setSelectedItemTitles(selectedItemTitles.filter((id) => id !== value)); + } else { + setSelectedItemTitles([...selectedItemTitles, value]); + } } }} onOpenChange={(nextOpen: boolean) => setIsOpen(nextOpen)} diff --git a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/confusionMatrix/ConfusionMatrixCompare.tsx b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/confusionMatrix/ConfusionMatrixCompare.tsx index 60ec951f71..b4c3b7694c 100644 --- a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/confusionMatrix/ConfusionMatrixCompare.tsx +++ b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/confusionMatrix/ConfusionMatrixCompare.tsx @@ -49,13 +49,16 @@ const ConfusionMatrixCompare: React.FC = ({ const { configMap, runMap } = React.useMemo( () => - fullArtifactPaths.reduce( + fullArtifactPaths.reduce<{ + runMap: Record; + configMap: Record; + }>( (acc, fullPath) => { const customProperties = fullPath.linkedArtifact.artifact.getCustomPropertiesMap(); const data = customProperties.get('confusionMatrix')?.getStructValue()?.toJavaScript(); if (data) { - const confusionMatrixData = data.struct as unknown; + const confusionMatrixData = data.struct; if (isConfusionMatrix(confusionMatrixData)) { const runId = fullPath.run.run_id; const title = getFullArtifactPathLabel(fullPath); @@ -78,10 +81,7 @@ const ConfusionMatrixCompare: React.FC = ({ return acc; }, - { - runMap: {} as Record, - configMap: {} as Record, - }, + { runMap: {}, configMap: {} }, ), [fullArtifactPaths], ); diff --git a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/confusionMatrix/utils.ts b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/confusionMatrix/utils.ts index 47fea29c76..2891587bd9 100644 --- a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/confusionMatrix/utils.ts +++ b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/confusionMatrix/utils.ts @@ -1,8 +1,12 @@ import { ConfusionMatrixInput } from '~/concepts/pipelines/content/artifacts/charts/confusionMatrix/types'; export const isConfusionMatrix = (obj: unknown): obj is ConfusionMatrixInput => { - const matrix = obj as ConfusionMatrixInput; + const matrix = obj; return ( + typeof matrix === 'object' && + matrix !== null && + 'annotationSpecs' in matrix && + 'rows' in matrix && Array.isArray(matrix.annotationSpecs) && matrix.annotationSpecs.every( (annotationSpec) => @@ -10,7 +14,8 @@ export const isConfusionMatrix = (obj: unknown): obj is ConfusionMatrixInput => ) && Array.isArray(matrix.rows) && matrix.rows.every( - (row) => Array.isArray(row.row) && row.row.every((value) => typeof value === 'number'), + (row) => + Array.isArray(row.row) && row.row.every((value: unknown) => typeof value === 'number'), ) ); }; diff --git a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/roc/utils.ts b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/roc/utils.ts index c3bf9da5d8..74582cf10e 100644 --- a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/roc/utils.ts +++ b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/roc/utils.ts @@ -2,14 +2,15 @@ import { JavaScriptValue } from 'google-protobuf/google/protobuf/struct_pb'; import { ROCCurveConfig } from '~/concepts/pipelines/content/artifacts/charts/ROCCurve'; import { ConfidenceMetric } from './types'; -export const isConfidenceMetric = (obj: JavaScriptValue): obj is ConfidenceMetric => { - const metric = obj as ConfidenceMetric; - return ( - typeof metric.confidenceThreshold === 'number' && - typeof metric.falsePositiveRate === 'number' && - typeof metric.recall === 'number' - ); -}; +export const isConfidenceMetric = (obj: JavaScriptValue): obj is ConfidenceMetric => + typeof obj === 'object' && + obj !== null && + 'confidenceThreshold' in obj && + 'falsePositiveRate' in obj && + 'recall' in obj && + typeof obj.confidenceThreshold === 'number' && + typeof obj.falsePositiveRate === 'number' && + typeof obj.recall === 'number'; export const buildRocCurveConfig = ( confidenceMetricsArray: ConfidenceMetric[], diff --git a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/utils.ts b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/utils.ts index 38a034f605..b5907e164e 100644 --- a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/utils.ts +++ b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/utils.ts @@ -58,7 +58,7 @@ export const getRunArtifacts = (mlmdPackages: PipelineRunRelatedMlmd[]): RunArti linkedArtifacts.push({ event, artifact, - } as LinkedArtifact); + }); } else { throw new Error(`The artifact with the following ID was not found: ${artifactId}`); } @@ -66,12 +66,12 @@ export const getRunArtifacts = (mlmdPackages: PipelineRunRelatedMlmd[]): RunArti return { execution, linkedArtifacts, - } as ExecutionArtifact; + }; }); return { run: mlmdPackage.run, executionArtifacts, - } as RunArtifact; + }; }); export const filterRunArtifactsByType = ( @@ -102,14 +102,14 @@ export const filterRunArtifactsByType = ( typeExecutions.push({ execution: e.execution, linkedArtifacts: typeArtifacts, - } as ExecutionArtifact); + }); } } if (typeExecutions.length > 0) { typeRuns.push({ run: runArtifact.run, executionArtifacts: typeExecutions, - } as RunArtifact); + }); } } return typeRuns; diff --git a/frontend/src/concepts/pipelines/content/configurePipelinesServer/ConfigurePipelinesServerModal.tsx b/frontend/src/concepts/pipelines/content/configurePipelinesServer/ConfigurePipelinesServerModal.tsx index 252de75602..4a16b8725c 100644 --- a/frontend/src/concepts/pipelines/content/configurePipelinesServer/ConfigurePipelinesServerModal.tsx +++ b/frontend/src/concepts/pipelines/content/configurePipelinesServer/ConfigurePipelinesServerModal.tsx @@ -10,7 +10,6 @@ import { PipelinesDatabaseSection } from './PipelinesDatabaseSection'; import { ObjectStorageSection } from './ObjectStorageSection'; import { DATABASE_CONNECTION_FIELDS, - DatabaseConnectionKeys, EMPTY_DATABASE_CONNECTION, ExternalDatabaseSecret, } from './const'; @@ -48,7 +47,7 @@ export const ConfigurePipelinesServerModal: React.FC DATABASE_CONNECTION_FIELDS.filter((field) => field.isRequired) .map((field) => field.key) - .includes(key as DatabaseConnectionKeys) + .includes(key) ? !!value : true, ); diff --git a/frontend/src/concepts/pipelines/content/createRun/RunPage.tsx b/frontend/src/concepts/pipelines/content/createRun/RunPage.tsx index 8761f6f942..e9b928131c 100644 --- a/frontend/src/concepts/pipelines/content/createRun/RunPage.tsx +++ b/frontend/src/concepts/pipelines/content/createRun/RunPage.tsx @@ -118,7 +118,7 @@ const RunPage: React.FC = ({ cloneRun, contextPath, testId }) => { > diff --git a/frontend/src/concepts/pipelines/content/tables/PipelineFilterBar.tsx b/frontend/src/concepts/pipelines/content/tables/PipelineFilterBar.tsx index 16bdea92fe..81c57a2034 100644 --- a/frontend/src/concepts/pipelines/content/tables/PipelineFilterBar.tsx +++ b/frontend/src/concepts/pipelines/content/tables/PipelineFilterBar.tsx @@ -41,11 +41,13 @@ export function FilterToolbar({ testId = 'filter-toolbar', ...toolbarGroupProps }: ToolbarFilterProps): React.JSX.Element { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const keys = Object.keys(filterOptions) as Array; const [open, setOpen] = React.useState(false); const [currentFilterType, setCurrentFilterType] = React.useState(keys[0]); const isToolbarChip = (v: unknown): v is ToolbarChip & { key: T } => - !!v && Object.keys(v as ToolbarChip).every((k) => ['key', 'node'].includes(k)); + !!v && Object.keys(v).every((k) => ['key', 'node'].includes(k)); + const filterItem = filterData[currentFilterType]; return ( <> @@ -111,9 +113,7 @@ export function FilterToolbar({ {filterOptionRenders[currentFilterType]({ onChange: (value, label) => onFilterUpdate(currentFilterType, label && value ? { label, value } : value), - ...(typeof filterData[currentFilterType] === 'string' - ? { value: filterData[currentFilterType] as string } - : (filterData[currentFilterType] as { label: string; value: string })), + ...(typeof filterItem === 'string' ? { value: filterItem } : filterItem), })} diff --git a/frontend/src/concepts/pipelines/content/tables/usePipelineFilter.ts b/frontend/src/concepts/pipelines/content/tables/usePipelineFilter.ts index 9a1cd1031d..70235f7631 100644 --- a/frontend/src/concepts/pipelines/content/tables/usePipelineFilter.ts +++ b/frontend/src/concepts/pipelines/content/tables/usePipelineFilter.ts @@ -14,13 +14,11 @@ export enum FilterOptions { PIPELINE_VERSION = 'pipeline_version', } -export const getDataValue = ( - data: R, -): string | undefined => { - if (typeof data === 'string') { +export const getDataValue = (data: string | { value: string } | undefined): string | undefined => { + if (typeof data === 'string' || typeof data === 'undefined') { return data; } - return (data as { label: string; value: string } | undefined)?.value; + return data.value; }; const defaultFilterData: FilterProps['filterData'] = { diff --git a/frontend/src/concepts/pipelines/context/MlmdListContext.tsx b/frontend/src/concepts/pipelines/context/MlmdListContext.tsx index 159e7ff51a..4ddeb44524 100644 --- a/frontend/src/concepts/pipelines/context/MlmdListContext.tsx +++ b/frontend/src/concepts/pipelines/context/MlmdListContext.tsx @@ -16,6 +16,7 @@ export interface MlmdListContextProps { setOrderBy: (orderBy: MlmdOrderBy | undefined) => void; } +// eslint-disable-next-line @typescript-eslint/consistent-type-assertions const MlmdListContext = React.createContext({} as MlmdListContextProps); export const MlmdListContextProvider: React.FC = ({ children }) => { diff --git a/frontend/src/concepts/pipelines/context/PipelinesContext.tsx b/frontend/src/concepts/pipelines/context/PipelinesContext.tsx index 8f9689f613..c58d9c74b8 100644 --- a/frontend/src/concepts/pipelines/context/PipelinesContext.tsx +++ b/frontend/src/concepts/pipelines/context/PipelinesContext.tsx @@ -51,6 +51,7 @@ const PipelinesContext = React.createContext({ serverTimedOut: false, ignoreTimedOut: () => undefined, namespace: '', + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions project: null as unknown as ProjectKind, refreshState: async () => undefined, refreshAPIState: () => undefined, @@ -58,7 +59,9 @@ const PipelinesContext = React.createContext({ loading: false, data: null, }), + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions apiState: { apiAvailable: false, api: null as unknown as PipelineAPIState['api'] }, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions metadataStoreServiceClient: null as unknown as MetadataStoreServicePromiseClient, }); @@ -106,7 +109,7 @@ export const PipelineContextProvider = conditionalArea {}); enableDevTools([client]); } diff --git a/frontend/src/concepts/pipelines/utils.ts b/frontend/src/concepts/pipelines/utils.ts index 006bb883f9..e674ecae2d 100644 --- a/frontend/src/concepts/pipelines/utils.ts +++ b/frontend/src/concepts/pipelines/utils.ts @@ -32,4 +32,4 @@ export const isGeneratedDSPAExternalStorageSecret = (name: string): boolean => export const isRunSchedule = ( resource: PipelineRunKFv2 | PipelineRunJobKFv2, -): resource is PipelineRunJobKFv2 => !!(resource as PipelineRunJobKFv2).trigger; +): resource is PipelineRunJobKFv2 => 'trigger' in resource; diff --git a/frontend/src/concepts/topology/PipelineDefaultTaskGroup.tsx b/frontend/src/concepts/topology/PipelineDefaultTaskGroup.tsx index 520ad656da..a48b905eaf 100644 --- a/frontend/src/concepts/topology/PipelineDefaultTaskGroup.tsx +++ b/frontend/src/concepts/topology/PipelineDefaultTaskGroup.tsx @@ -33,7 +33,7 @@ type PipelinesDefaultGroupInnerProps = Omit = observer( ({ element, selected, onSelect }) => { - const [hover, hoverRef] = useHover(); + const [hover, hoverRef] = useHover(); const popoverRef = React.useRef(null); const detailsLevel = element.getGraph().getDetailsLevel(); @@ -80,7 +80,7 @@ const DefaultTaskGroupInner: React.FunctionComponent - }> + {element.isCollapsed() ? ( = ({ element, ...props }) => { - const edge = element as Edge; + if (!isEdge(element)) { + throw new Error('Element is not Edge'); + } + const edge = element; return ( = observer( ({ element, selected, onSelect, ...rest }) => { const bounds = element.getBounds(); - const [isHover, hoverRef] = useHover(); + const [isHover, hoverRef] = useHover(); const detailsLevel = element.getGraph().getDetailsLevel(); const data = element.getData(); const scale = element.getGraph().getScale(); @@ -120,10 +121,7 @@ const ArtifactTaskNodeInner: React.FC = observer( const translateX = bounds.width / 2 - (iconSize / 2) * upScale; const translateY = iconPadding * upScale; return ( - } - > + {isHover || detailsLevel !== ScaleDetailsLevel.high ? ( = ({ element, ...rest }) => ( - -); +const ArtifactTaskNode: React.FC = ({ element, ...rest }) => { + if (!isNode(element)) { + throw new Error('Element is not Node'); + } + return ; +}; export default ArtifactTaskNode; diff --git a/frontend/src/concepts/topology/customNodes/StandardTaskNode.tsx b/frontend/src/concepts/topology/customNodes/StandardTaskNode.tsx index 7bdca3198e..5c32a68623 100644 --- a/frontend/src/concepts/topology/customNodes/StandardTaskNode.tsx +++ b/frontend/src/concepts/topology/customNodes/StandardTaskNode.tsx @@ -28,7 +28,7 @@ const StandardTaskNode: React.FunctionComponent = ({ ...rest }) => { const data = element.getData(); - const [hover, hoverRef] = useHover(); + const [hover, hoverRef] = useHover(); const detailsLevel = element.getGraph().getDetailsLevel(); // Set the cached node status to Succeeded @@ -48,7 +48,7 @@ const StandardTaskNode: React.FunctionComponent = ({ ) : null; return ( - }> + ({ data: DEFAULT_CONTEXT_DATA, refreshState: async () => undefined, refreshAPIState: () => undefined, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions apiState: { apiAvailable: false, api: null as unknown as TrustyAPIState['api'] }, }); diff --git a/frontend/src/concepts/trustyai/useManageTrustyAICR.ts b/frontend/src/concepts/trustyai/useManageTrustyAICR.ts index f75e70e739..e4034d397d 100644 --- a/frontend/src/concepts/trustyai/useManageTrustyAICR.ts +++ b/frontend/src/concepts/trustyai/useManageTrustyAICR.ts @@ -26,20 +26,17 @@ const useManageTrustyAICR = (namespace: string): UseManageTrustyAICRReturnType = showSuccess.current = true; } - const installCR = React.useCallback( - () => - createTrustyAICR(namespace) - .then(refresh) - .catch((e) => setInstallReqError(e)), - [namespace, refresh], - ); + const installCR = React.useCallback(async () => { + await createTrustyAICR(namespace) + .then(refresh) + .catch((e) => setInstallReqError(e)); + }, [namespace, refresh]); - const deleteCR = React.useCallback( - () => deleteTrustyAICR(namespace).then(refresh), - [namespace, refresh], - ); + const deleteCR = React.useCallback(async () => { + await deleteTrustyAICR(namespace).then(refresh); + }, [namespace, refresh]); - return { + return { error, isProgressing, isAvailable, diff --git a/frontend/src/pages/BYONImages/BYONImageModal/AcceleratorIdentifierMultiselect.tsx b/frontend/src/pages/BYONImages/BYONImageModal/AcceleratorIdentifierMultiselect.tsx index a71eb628e4..5e646a6c60 100644 --- a/frontend/src/pages/BYONImages/BYONImageModal/AcceleratorIdentifierMultiselect.tsx +++ b/frontend/src/pages/BYONImages/BYONImageModal/AcceleratorIdentifierMultiselect.tsx @@ -170,7 +170,11 @@ export const AcceleratorIdentifierMultiselect: React.FC onSelect(selection as string)} + onSelect={(ev, selection) => { + if (typeof selection === 'string') { + onSelect(selection); + } + }} onOpenChange={() => setIsOpen(false)} toggle={toggle} > diff --git a/frontend/src/pages/exploreApplication/EnableModal.tsx b/frontend/src/pages/exploreApplication/EnableModal.tsx index 17cc480bb0..39016cdd5c 100644 --- a/frontend/src/pages/exploreApplication/EnableModal.tsx +++ b/frontend/src/pages/exploreApplication/EnableModal.tsx @@ -12,8 +12,8 @@ import { import { ExternalLinkAltIcon } from '@patternfly/react-icons'; import { OdhApplication } from '~/types'; import { EnableApplicationStatus, useEnableApplication } from '~/utilities/useEnableApplication'; +import { asEnumMember } from '~/utilities/utils'; import EnableVariable from './EnableVariable'; - import './EnableModal.scss'; type EnableModalProps = { @@ -158,7 +158,9 @@ const EnableModal: React.FC = ({ selectedApp, shown, onClose } key={key} ref={index === 0 ? focusRef : undefined} label={enable.variableDisplayText?.[key] ?? ''} - inputType={enable.variables?.[key] as TextInputTypes} + inputType={ + asEnumMember(enable.variables?.[key], TextInputTypes) ?? TextInputTypes.text + } helperText={enable.variableHelpText?.[key] ?? ''} validationInProgress={validationInProgress} value={enableValues[key]} diff --git a/frontend/src/pages/home/resources/useResourcesSection.tsx b/frontend/src/pages/home/resources/useResourcesSection.tsx index 4b78d05407..ab66e247a0 100644 --- a/frontend/src/pages/home/resources/useResourcesSection.tsx +++ b/frontend/src/pages/home/resources/useResourcesSection.tsx @@ -12,7 +12,6 @@ import { TextVariants, } from '@patternfly/react-core'; import { ExclamationCircleIcon } from '@patternfly/react-icons'; -import { OdhDocument } from '~/types'; import OdhDocCard from '~/components/OdhDocCard'; import ScrolledGallery from '~/concepts/design/ScrolledGallery'; import CollapsibleSection from '~/concepts/design/CollapsibleSection'; @@ -65,7 +64,7 @@ export const useResourcesSection = (): React.ReactNode => { { + const foundDocs = specifiedDocs.reduce((acc, included) => { const doc = docs.find((d) => d.metadata.name === included.name && d.kind === included.kind); if (doc) { acc.push(doc); } return acc; - }, [] as OdhDocument[]); + }, []); return { docs: foundDocs, loaded, loadError }; }, [docs, specifiedDocs, loadError, loaded]); }; diff --git a/frontend/src/pages/learningCenter/ApplicationFilters.tsx b/frontend/src/pages/learningCenter/ApplicationFilters.tsx index eeb3b7f88f..389eec771b 100644 --- a/frontend/src/pages/learningCenter/ApplicationFilters.tsx +++ b/frontend/src/pages/learningCenter/ApplicationFilters.tsx @@ -34,8 +34,7 @@ const ApplicationFilters: React.FC = ({ docApps, catego return allApplications; }, [categoryApps, docApps]); - const onFilterChange = (docType: string, e: React.SyntheticEvent): void => { - const { checked } = e.target as React.AllHTMLAttributes; + const onFilterChange = (docType: string, checked: boolean): void => { const updatedQuery = [...providerFilters]; const index = updatedQuery.indexOf(docType); if (checked && index === -1) { @@ -71,7 +70,7 @@ const ApplicationFilters: React.FC = ({ docApps, catego id={application} key={application} checked={providerFilters.includes(application)} - onClick={(e) => onFilterChange(application, e)} + onChange={(_, checked) => onFilterChange(application, checked)} title={application} > {`${application} (${applications[application]})`} diff --git a/frontend/src/pages/learningCenter/DocTypeFilters.tsx b/frontend/src/pages/learningCenter/DocTypeFilters.tsx index 7b3d7c94ee..10ba55c421 100644 --- a/frontend/src/pages/learningCenter/DocTypeFilters.tsx +++ b/frontend/src/pages/learningCenter/DocTypeFilters.tsx @@ -4,6 +4,7 @@ import { FilterSidePanelCategory } from '@patternfly/react-catalog-view-extensio import FilterSidePanelCategoryItem from '~/components/FilterSidePanelCategoryItem'; import { OdhDocument, OdhDocumentType } from '~/types'; import { removeQueryArgument, setQueryArgument } from '~/utilities/router'; +import { asEnumMember, enumIterator } from '~/utilities/utils'; import { DOC_TYPE_FILTER_KEY } from './const'; import { useQueryFilters } from './useQueryFilters'; @@ -16,10 +17,11 @@ const DocTypeFilters: React.FC = ({ categoryApps }) => { const docTypeFilters = useQueryFilters(DOC_TYPE_FILTER_KEY); const docCounts = React.useMemo( () => - categoryApps.reduce( + categoryApps.reduce<{ [key in OdhDocumentType]: number }>( (acc, docApp) => { - if (docApp.spec.type in acc) { - acc[docApp.spec.type as keyof typeof acc]++; + const enumMember = asEnumMember(docApp.spec.type, OdhDocumentType); + if (enumMember) { + acc[enumMember]++; } return acc; }, @@ -52,26 +54,18 @@ const DocTypeFilters: React.FC = ({ categoryApps }) => { return ( - {Object.keys(OdhDocumentType).map((docType) => { - const value = OdhDocumentType[docType as keyof typeof OdhDocumentType]; - return ( - - onFilterChange( - value, - (e.target as React.AllHTMLAttributes).checked || false, - ) - } - title={docType} - > - {`${docType} (${docCounts[value]})`} - - ); - })} + {enumIterator(OdhDocumentType).map(([docType, value]) => ( + onFilterChange(value, checked)} + title={docType} + > + {`${docType} (${docCounts[value]})`} + + ))} ); }; diff --git a/frontend/src/pages/learningCenter/EnabledFilters.tsx b/frontend/src/pages/learningCenter/EnabledFilters.tsx index de72e83ea7..6c9b1799d2 100644 --- a/frontend/src/pages/learningCenter/EnabledFilters.tsx +++ b/frontend/src/pages/learningCenter/EnabledFilters.tsx @@ -47,12 +47,7 @@ const EnabledFilter: React.FC = ({ categoryApps }) => { data-id="enabled-filter-checkbox" id="enabled-filter-checkbox" checked={enabledFilters.includes('true')} - onClick={(e) => - onFilterChange( - 'true', - (e.target as React.AllHTMLAttributes).checked || false, - ) - } + onChange={(_, checked) => onFilterChange('true', checked)} title="Enabled" > {`Enabled (${enabledCount})`} @@ -61,12 +56,7 @@ const EnabledFilter: React.FC = ({ categoryApps }) => { data-id="not-enabled-filter-checkbox" id="not-enabled-filter-checkbox" checked={enabledFilters.includes('false')} - onClick={(e) => - onFilterChange( - 'false', - (e.target as React.AllHTMLAttributes).checked || false, - ) - } + onChange={(_, checked) => onFilterChange('false', checked)} title="Not enabled" > {`Not enabled (${notEnabledCount})`} diff --git a/frontend/src/pages/learningCenter/LearningCenterToolbar.tsx b/frontend/src/pages/learningCenter/LearningCenterToolbar.tsx index fda12ae799..bf10b8b391 100644 --- a/frontend/src/pages/learningCenter/LearningCenterToolbar.tsx +++ b/frontend/src/pages/learningCenter/LearningCenterToolbar.tsx @@ -113,7 +113,7 @@ const LearningCenterToolbar: React.FC = ({ const onSortTypeSelect = React.useCallback( (e: React.MouseEvent | React.ChangeEvent) => { setIsSortTypeDropdownOpen(false); - const selection = (e.target as Element).getAttribute('data-key') ?? ''; + const selection = e.target instanceof Element ? e.target.getAttribute('data-key') ?? '' : ''; setQueryArgument(navigate, DOC_SORT_KEY, selection); }, [navigate], @@ -133,7 +133,7 @@ const LearningCenterToolbar: React.FC = ({ const onSortOrderSelect = React.useCallback( (e: React.MouseEvent | React.ChangeEvent) => { setIsSortOrderDropdownOpen(false); - const selection = (e.target as Element).getAttribute('data-key') ?? ''; + const selection = e.target instanceof Element ? e.target.getAttribute('data-key') ?? '' : ''; setQueryArgument(navigate, DOC_SORT_ORDER_KEY, selection); }, [navigate], diff --git a/frontend/src/pages/learningCenter/ProviderTypeFilters.tsx b/frontend/src/pages/learningCenter/ProviderTypeFilters.tsx index ffb216ca36..21eb0e1538 100644 --- a/frontend/src/pages/learningCenter/ProviderTypeFilters.tsx +++ b/frontend/src/pages/learningCenter/ProviderTypeFilters.tsx @@ -39,8 +39,7 @@ const ProviderTypeFilters: React.FC = ({ docApps, cate return allTypes; }, [categoryApps, docApps]); - const onFilterChange = (docType: string, e: React.SyntheticEvent): void => { - const { checked } = e.target as React.AllHTMLAttributes; + const onFilterChange = (docType: string, checked: boolean): void => { const updatedQuery = [...providerTypeFilters]; const index = updatedQuery.indexOf(docType); if (checked && index === -1) { @@ -81,7 +80,7 @@ const ProviderTypeFilters: React.FC = ({ docApps, cate id={providerType} key={providerType} checked={providerTypeFilters.includes(providerType)} - onClick={(e) => onFilterChange(providerType, e)} + onChange={(_, checked) => onFilterChange(providerType, checked)} title={providerType} > {`${providerType} (${providerTypes[providerType]})`} diff --git a/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionSelector.tsx b/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionSelector.tsx index cf00d689ea..80aadb7cca 100644 --- a/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionSelector.tsx +++ b/frontend/src/pages/modelRegistry/screens/ModelVersionDetails/ModelVersionSelector.tsx @@ -53,8 +53,10 @@ const ModelVersionSelector: React.FC = ({ const menu = ( { - onSelect(itemId as string); - setOpen(false); + if (typeof itemId === 'string') { + onSelect(itemId); + setOpen(false); + } }} data-id="model-version-selector-menu" ref={menuRef} diff --git a/frontend/src/pages/modelRegistry/screens/ModelVersions/ModelVersionListView.tsx b/frontend/src/pages/modelRegistry/screens/ModelVersions/ModelVersionListView.tsx index 049454ed25..2bad667c01 100644 --- a/frontend/src/pages/modelRegistry/screens/ModelVersions/ModelVersionListView.tsx +++ b/frontend/src/pages/modelRegistry/screens/ModelVersions/ModelVersionListView.tsx @@ -22,6 +22,7 @@ import EmptyModelRegistryState from '~/pages/modelRegistry/screens/components/Em import { filterModelVersions } from '~/pages/modelRegistry/screens/utils'; import { ModelRegistrySelectorContext } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext'; import { modelVersionArchiveUrl } from '~/pages/modelRegistry/screens/routeUtils'; +import { asEnumMember } from '~/utilities/utils'; import ModelVersionsTable from './ModelVersionsTable'; type ModelVersionListViewProps = { @@ -89,7 +90,10 @@ const ModelVersionListView: React.FC = ({ }))} value={searchType} onChange={(newSearchType) => { - setSearchType(newSearchType as SearchType); + const enumMember = asEnumMember(newSearchType, SearchType); + if (enumMember !== null) { + setSearchType(enumMember); + } }} icon={} /> diff --git a/frontend/src/pages/modelRegistry/screens/ModelVersionsArchive/ModelVersionsArchiveListView.tsx b/frontend/src/pages/modelRegistry/screens/ModelVersionsArchive/ModelVersionsArchiveListView.tsx index e7bf2dcc50..800c4a3e21 100644 --- a/frontend/src/pages/modelRegistry/screens/ModelVersionsArchive/ModelVersionsArchiveListView.tsx +++ b/frontend/src/pages/modelRegistry/screens/ModelVersionsArchive/ModelVersionsArchiveListView.tsx @@ -13,6 +13,7 @@ import { ModelVersion } from '~/concepts/modelRegistry/types'; import SimpleDropdownSelect from '~/components/SimpleDropdownSelect'; import { filterModelVersions } from '~/pages/modelRegistry/screens/utils'; import EmptyModelRegistryState from '~/pages/modelRegistry/screens/components/EmptyModelRegistryState'; +import { asEnumMember } from '~/utilities/utils'; import ModelVersionsArchiveTable from './ModelVersionsArchiveTable'; type ModelVersionsArchiveListViewProps = { @@ -63,7 +64,10 @@ const ModelVersionsArchiveListView: React.FC }))} value={searchType} onChange={(newSearchType) => { - setSearchType(newSearchType as SearchType); + const enumMember = asEnumMember(newSearchType, SearchType); + if (enumMember) { + setSearchType(enumMember); + } }} icon={} /> diff --git a/frontend/src/pages/modelRegistry/screens/RegisteredModels/RegisteredModelListView.tsx b/frontend/src/pages/modelRegistry/screens/RegisteredModels/RegisteredModelListView.tsx index 561cee5da8..3ad9e8be51 100644 --- a/frontend/src/pages/modelRegistry/screens/RegisteredModels/RegisteredModelListView.tsx +++ b/frontend/src/pages/modelRegistry/screens/RegisteredModels/RegisteredModelListView.tsx @@ -9,6 +9,7 @@ import { filterRegisteredModels } from '~/pages/modelRegistry/screens/utils'; import { ModelRegistrySelectorContext } from '~/concepts/modelRegistry/context/ModelRegistrySelectorContext'; import EmptyModelRegistryState from '~/pages/modelRegistry/screens/components/EmptyModelRegistryState'; import { registeredModelArchiveUrl } from '~/pages/modelRegistry/screens/routeUtils'; +import { asEnumMember } from '~/utilities/utils'; import RegisteredModelTable from './RegisteredModelTable'; import RegisteredModelsTableToolbar from './RegisteredModelsTableToolbar'; @@ -71,7 +72,10 @@ const RegisteredModelListView: React.FC = ({ }))} value={searchType} onChange={(newSearchType) => { - setSearchType(newSearchType as SearchType); + const newSearchTypeInput = asEnumMember(newSearchType, SearchType); + if (newSearchTypeInput !== null) { + setSearchType(newSearchTypeInput); + } }} icon={} /> diff --git a/frontend/src/pages/modelRegistry/screens/RegisteredModelsArchive/RegisteredModelsArchiveListView.tsx b/frontend/src/pages/modelRegistry/screens/RegisteredModelsArchive/RegisteredModelsArchiveListView.tsx index 7e5de0392c..86a0f2e9db 100644 --- a/frontend/src/pages/modelRegistry/screens/RegisteredModelsArchive/RegisteredModelsArchiveListView.tsx +++ b/frontend/src/pages/modelRegistry/screens/RegisteredModelsArchive/RegisteredModelsArchiveListView.tsx @@ -13,6 +13,7 @@ import { RegisteredModel } from '~/concepts/modelRegistry/types'; import SimpleDropdownSelect from '~/components/SimpleDropdownSelect'; import { filterRegisteredModels } from '~/pages/modelRegistry/screens/utils'; import EmptyModelRegistryState from '~/pages/modelRegistry/screens/components/EmptyModelRegistryState'; +import { asEnumMember } from '~/utilities/utils'; import RegisteredModelsArchiveTable from './RegisteredModelsArchiveTable'; type RegisteredModelsArchiveListViewProps = { @@ -68,7 +69,10 @@ const RegisteredModelsArchiveListView: React.FC { - setSearchType(newSearchType as SearchType); + const newSearchTypeInput = asEnumMember(newSearchType, SearchType); + if (newSearchTypeInput !== null) { + setSearchType(newSearchTypeInput); + } }} icon={} /> diff --git a/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeAPIProtocolSelector.tsx b/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeAPIProtocolSelector.tsx index 2b9d485279..b22321b07e 100644 --- a/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeAPIProtocolSelector.tsx +++ b/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeAPIProtocolSelector.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { FormGroup } from '@patternfly/react-core'; import { ServingRuntimeAPIProtocol, ServingRuntimePlatform } from '~/types'; import SimpleDropdownSelect from '~/components/SimpleDropdownSelect'; +import { asEnumMember } from '~/utilities/utils'; type CustomServingRuntimeAPIProtocolSelectorProps = { selectedAPIProtocol: ServingRuntimeAPIProtocol | undefined; @@ -50,7 +51,12 @@ const CustomServingRuntimeAPIProtocolSelector: React.FC< isDisabled={isOnlyModelMesh} options={options} value={selectedAPIProtocol || ''} - onChange={(key) => setSelectedAPIProtocol(key as ServingRuntimeAPIProtocol)} + onChange={(key) => { + const enumValue = asEnumMember(key, ServingRuntimeAPIProtocol); + if (enumValue !== null) { + setSelectedAPIProtocol(enumValue); + } + }} /> ); diff --git a/frontend/src/pages/modelServing/customServingRuntimes/utils.ts b/frontend/src/pages/modelServing/customServingRuntimes/utils.ts index 21330644fc..6e0c34f66c 100644 --- a/frontend/src/pages/modelServing/customServingRuntimes/utils.ts +++ b/frontend/src/pages/modelServing/customServingRuntimes/utils.ts @@ -2,6 +2,7 @@ import { K8sResourceCommon } from '@openshift/dynamic-plugin-sdk-utils'; import { ServingRuntimeKind, TemplateKind } from '~/k8sTypes'; import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils'; import { ServingRuntimeAPIProtocol, ServingRuntimePlatform } from '~/types'; +import { asEnumMember } from '~/utilities/utils'; export const getTemplateEnabled = ( template: TemplateKind, @@ -142,8 +143,12 @@ export const getAPIProtocolFromTemplate = ( if (!template.metadata.annotations?.['opendatahub.io/apiProtocol']) { return undefined; } - - return template.metadata.annotations['opendatahub.io/apiProtocol'] as ServingRuntimeAPIProtocol; + return ( + asEnumMember( + template.metadata.annotations['opendatahub.io/apiProtocol'], + ServingRuntimeAPIProtocol, + ) ?? undefined + ); }; export const getAPIProtocolFromServingRuntime = ( @@ -152,5 +157,10 @@ export const getAPIProtocolFromServingRuntime = ( if (!resource.metadata.annotations?.['opendatahub.io/apiProtocol']) { return undefined; } - return resource.metadata.annotations['opendatahub.io/apiProtocol'] as ServingRuntimeAPIProtocol; + return ( + asEnumMember( + resource.metadata.annotations['opendatahub.io/apiProtocol'], + ServingRuntimeAPIProtocol, + ) ?? undefined + ); }; diff --git a/frontend/src/pages/modelServing/screens/global/utils.ts b/frontend/src/pages/modelServing/screens/global/utils.ts index b2728f34a6..d4d4c22111 100644 --- a/frontend/src/pages/modelServing/screens/global/utils.ts +++ b/frontend/src/pages/modelServing/screens/global/utils.ts @@ -2,6 +2,7 @@ import { InferenceServiceKind, ProjectKind, SecretKind, PodKind } from '~/k8sTyp import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils'; import { getProjectDisplayName } from '~/concepts/projects/utils'; import { InferenceServiceModelState, ModelStatus } from '~/pages/modelServing/screens/types'; +import { asEnumMember } from '~/utilities/utils'; export const getInferenceServiceDisplayName = (is: InferenceServiceKind): string => getDisplayNameFromK8sResource(is); @@ -12,8 +13,8 @@ export const getTokenDisplayName = (secret: SecretKind): string => export const getInferenceServiceActiveModelState = ( is: InferenceServiceKind, ): InferenceServiceModelState => - is.status?.modelStatus?.states?.activeModelState || - is.status?.modelStatus?.states?.targetModelState || + asEnumMember(is.status?.modelStatus?.states?.activeModelState, InferenceServiceModelState) || + asEnumMember(is.status?.modelStatus?.states?.targetModelState, InferenceServiceModelState) || InferenceServiceModelState.UNKNOWN; export const getInferenceServiceStatusMessage = (is: InferenceServiceKind): string => { diff --git a/frontend/src/pages/modelServing/screens/metrics/EnsureMetricsAvailable.tsx b/frontend/src/pages/modelServing/screens/metrics/EnsureMetricsAvailable.tsx index 85c1684d85..82a4fd840d 100644 --- a/frontend/src/pages/modelServing/screens/metrics/EnsureMetricsAvailable.tsx +++ b/frontend/src/pages/modelServing/screens/metrics/EnsureMetricsAvailable.tsx @@ -28,8 +28,9 @@ const EnsureMetricsAvailable: React.FC = ({ let readyCount = 0; metrics.forEach((metric) => { - if (data[metric].error) { - error = data[metric].error as AxiosError; + const axiosError = data[metric].error; + if (axiosError && axiosError instanceof AxiosError) { + error = axiosError; } if (data[metric].loaded) { readyCount++; diff --git a/frontend/src/pages/modelServing/screens/metrics/ModelServingMetricsContext.tsx b/frontend/src/pages/modelServing/screens/metrics/ModelServingMetricsContext.tsx index d7981dad08..e757df110e 100644 --- a/frontend/src/pages/modelServing/screens/metrics/ModelServingMetricsContext.tsx +++ b/frontend/src/pages/modelServing/screens/metrics/ModelServingMetricsContext.tsx @@ -24,10 +24,16 @@ export enum ModelMetricType { } type ModelServingMetricsContextType = { - data: Record< - ModelMetricType | ServerMetricType, - ContextResourceData - >; + data: { + [ServerMetricType.REQUEST_COUNT]: ContextResourceData; + [ServerMetricType.AVG_RESPONSE_TIME]: ContextResourceData; + [ServerMetricType.CPU_UTILIZATION]: ContextResourceData; + [ServerMetricType.MEMORY_UTILIZATION]: ContextResourceData; + [ModelMetricType.REQUEST_COUNT_FAILED]: ContextResourceData; + [ModelMetricType.REQUEST_COUNT_SUCCESS]: ContextResourceData; + [ModelMetricType.TRUSTY_AI_SPD]: ContextResourceData; + [ModelMetricType.TRUSTY_AI_DIR]: ContextResourceData; + }; refresh: () => void; namespace: string; }; diff --git a/frontend/src/pages/modelServing/screens/metrics/bias/BiasChart.tsx b/frontend/src/pages/modelServing/screens/metrics/bias/BiasChart.tsx index 89d7018bfe..ade194fdc7 100644 --- a/frontend/src/pages/modelServing/screens/metrics/bias/BiasChart.tsx +++ b/frontend/src/pages/modelServing/screens/metrics/bias/BiasChart.tsx @@ -19,7 +19,9 @@ const BiasChart: React.FC = ({ biasMetricConfig }) => { BIAS_CHART_CONFIGS[metricType]; const metric = React.useMemo(() => { - const metricData = data[modelMetricKey].data as PrometheusQueryRangeResponseDataResult[]; + const metricData = data[modelMetricKey].data.filter( + (x): x is PrometheusQueryRangeResponseDataResult => 'metric' in x, + ); const values = metricData.find((x) => x.metric.request === id)?.values || []; diff --git a/frontend/src/pages/modelServing/screens/metrics/bias/BiasConfigurationPage/BiasConfigurationModal/MetricTypeField.tsx b/frontend/src/pages/modelServing/screens/metrics/bias/BiasConfigurationPage/BiasConfigurationModal/MetricTypeField.tsx index fd7c60a5f1..e50085df55 100644 --- a/frontend/src/pages/modelServing/screens/metrics/bias/BiasConfigurationPage/BiasConfigurationModal/MetricTypeField.tsx +++ b/frontend/src/pages/modelServing/screens/metrics/bias/BiasConfigurationPage/BiasConfigurationModal/MetricTypeField.tsx @@ -7,6 +7,7 @@ import { } from '~/pages/modelServing/screens/metrics/const'; import { BiasMetricType } from '~/api'; import { isMetricType } from '~/pages/modelServing/screens/metrics/utils'; +import { enumIterator } from '~/utilities/utils'; type MetricTypeFieldProps = { fieldId: string; @@ -16,6 +17,7 @@ type MetricTypeFieldProps = { const MetricTypeField: React.FC = ({ fieldId, value, onChange }) => { const [isOpen, setOpen] = React.useState(false); + return ( diff --git a/frontend/src/pages/modelServing/screens/metrics/performance/ModelMeshMetrics.tsx b/frontend/src/pages/modelServing/screens/metrics/performance/ModelMeshMetrics.tsx index 8f9c511223..ed008ecf21 100644 --- a/frontend/src/pages/modelServing/screens/metrics/performance/ModelMeshMetrics.tsx +++ b/frontend/src/pages/modelServing/screens/metrics/performance/ModelMeshMetrics.tsx @@ -5,7 +5,6 @@ import { ModelMetricType, ModelServingMetricsContext, } from '~/pages/modelServing/screens/metrics/ModelServingMetricsContext'; -import { ContextResourceData, PrometheusQueryRangeResultValue } from '~/types'; import EnsureMetricsAvailable from '~/pages/modelServing/screens/metrics/EnsureMetricsAvailable'; const ModelMeshMetrics: React.FC = () => { @@ -22,15 +21,11 @@ const ModelMeshMetrics: React.FC = () => { metrics={[ { name: 'Successful', - metric: data[ - ModelMetricType.REQUEST_COUNT_SUCCESS - ] as ContextResourceData, + metric: data[ModelMetricType.REQUEST_COUNT_SUCCESS], }, { name: 'Failed', - metric: data[ - ModelMetricType.REQUEST_COUNT_FAILED - ] as ContextResourceData, + metric: data[ModelMetricType.REQUEST_COUNT_FAILED], }, ]} color="blue" diff --git a/frontend/src/pages/modelServing/screens/metrics/performance/ServerGraphs.tsx b/frontend/src/pages/modelServing/screens/metrics/performance/ServerGraphs.tsx index bcf76a0b15..d97fc00a2a 100644 --- a/frontend/src/pages/modelServing/screens/metrics/performance/ServerGraphs.tsx +++ b/frontend/src/pages/modelServing/screens/metrics/performance/ServerGraphs.tsx @@ -9,11 +9,6 @@ import { convertPrometheusNaNToZero, toPercentage, } from '~/pages/modelServing/screens/metrics/utils'; -import { - ContextResourceData, - PrometheusQueryRangeResponseDataResult, - PrometheusQueryRangeResultValue, -} from '~/types'; import { NamedMetricChartLine } from '~/pages/modelServing/screens/metrics/types'; const ServerGraphs: React.FC = () => { @@ -24,9 +19,7 @@ const ServerGraphs: React.FC = () => { , + metric: data[ServerMetricType.REQUEST_COUNT], }} color="blue" title="HTTP requests per 5 minutes" @@ -35,11 +28,7 @@ const ServerGraphs: React.FC = () => { - ).data.map( + metrics={data[ServerMetricType.AVG_RESPONSE_TIME].data.map( (line): NamedMetricChartLine => ({ name: line.metric.pod || '', metric: { @@ -56,9 +45,7 @@ const ServerGraphs: React.FC = () => { , + metric: data[ServerMetricType.CPU_UTILIZATION], translatePoint: toPercentage, }} color="purple" @@ -71,9 +58,7 @@ const ServerGraphs: React.FC = () => { , + metric: data[ServerMetricType.MEMORY_UTILIZATION], translatePoint: toPercentage, }} color="orange" diff --git a/frontend/src/pages/modelServing/screens/metrics/utils.tsx b/frontend/src/pages/modelServing/screens/metrics/utils.tsx index f82f06e592..2f878d30cd 100644 --- a/frontend/src/pages/modelServing/screens/metrics/utils.tsx +++ b/frontend/src/pages/modelServing/screens/metrics/utils.tsx @@ -14,6 +14,7 @@ import { BIAS_THRESHOLD_COLOR, } from '~/pages/modelServing/screens/metrics/const'; import { PROMETHEUS_REQUEST_RESOLUTION } from '~/concepts/metrics/const'; +import { isEnumMember } from '~/utilities/utils'; import { BiasSelectOption, DomainCalculator, @@ -219,8 +220,7 @@ export const checkConfigurationFieldsValid = ( export const isMetricType = ( metricType: string | SelectOptionObject, -): metricType is BiasMetricType => - Object.values(BiasMetricType).includes(metricType as BiasMetricType); +): metricType is BiasMetricType => isEnumMember(metricType.toString(), BiasMetricType); export const byId = (arg: U): ((arg: T) => boolean) => diff --git a/frontend/src/pages/notebookController/screens/admin/NotebookControllerTabs.tsx b/frontend/src/pages/notebookController/screens/admin/NotebookControllerTabs.tsx index e85b2193fb..0a76b36591 100644 --- a/frontend/src/pages/notebookController/screens/admin/NotebookControllerTabs.tsx +++ b/frontend/src/pages/notebookController/screens/admin/NotebookControllerTabs.tsx @@ -3,6 +3,7 @@ import { Tab, Tabs, TabTitleText } from '@patternfly/react-core'; import { NotebookControllerTabTypes } from '~/pages/notebookController/const'; import NotebookServerRoutes from '~/pages/notebookController/screens/server/NotebookServerRoutes'; import { NotebookControllerContext } from '~/pages/notebookController/NotebookControllerContext'; +import { asEnumMember } from '~/utilities/utils'; import NotebookAdmin from './NotebookAdmin'; const NotebookControllerTabs: React.FC = () => { @@ -16,7 +17,10 @@ const NotebookControllerTabs: React.FC = () => { unmountOnExit onSelect={(e, eventKey) => { setImpersonating(); - setCurrentAdminTab(eventKey as NotebookControllerTabTypes); + const enumValue = asEnumMember(eventKey, NotebookControllerTabTypes); + if (enumValue !== null) { + setCurrentAdminTab(enumValue); + } }} > = ({ user, userProperty }) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const content = user[userProperty as keyof AdminViewUserData]; if (isField(content, userProperty === 'serverStatus')) { diff --git a/frontend/src/pages/notebookController/screens/server/EnvironmentVariablesField.tsx b/frontend/src/pages/notebookController/screens/server/EnvironmentVariablesField.tsx index 7234cad59f..37fc1ab4e2 100644 --- a/frontend/src/pages/notebookController/screens/server/EnvironmentVariablesField.tsx +++ b/frontend/src/pages/notebookController/screens/server/EnvironmentVariablesField.tsx @@ -17,6 +17,7 @@ import { CUSTOM_VARIABLE, EMPTY_KEY } from '~/pages/notebookController/const'; import { EnvVarType, VariableRow } from '~/types'; import '~/pages/notebookController/NotebookController.scss'; +import { asEnumMember } from '~/utilities/utils'; type EnvironmentVariablesFieldProps = { fieldIndex: string; @@ -88,7 +89,7 @@ const EnvironmentVariablesField: React.FC = ({ type={ showPassword && variableType === 'password' ? TextInputTypes.text - : (variable.type as TextInputTypes) + : asEnumMember(variable.type, TextInputTypes) ?? undefined } value={variable.value} onChange={(e, newValue) => diff --git a/frontend/src/pages/notebookController/screens/server/EnvironmentVariablesRow.tsx b/frontend/src/pages/notebookController/screens/server/EnvironmentVariablesRow.tsx index 84a99a7aca..4b3192712e 100644 --- a/frontend/src/pages/notebookController/screens/server/EnvironmentVariablesRow.tsx +++ b/frontend/src/pages/notebookController/screens/server/EnvironmentVariablesRow.tsx @@ -82,7 +82,9 @@ const EnvironmentVariablesRow: React.FC = ({ onToggle={() => setTypeDropdownOpen(!typeDropdownOpen)} aria-labelledby="container-size" selections={variableRow.variableType} - onSelect={(e, selection) => updateVariableType(selection as string)} + onSelect={(e, selection) => { + updateVariableType(selection.toString()); + }} > {selectOptions} diff --git a/frontend/src/pages/pipelines/global/experiments/executions/details/ExecutionDetails.tsx b/frontend/src/pages/pipelines/global/experiments/executions/details/ExecutionDetails.tsx index 51037b753f..63a3c4533b 100644 --- a/frontend/src/pages/pipelines/global/experiments/executions/details/ExecutionDetails.tsx +++ b/frontend/src/pages/pipelines/global/experiments/executions/details/ExecutionDetails.tsx @@ -44,10 +44,10 @@ const ExecutionDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, co const [artifactTypes, artifactTypesLoaded] = useGetArtifactTypes(); const allEvents = parseEventsByType(events); - const artifactTypeMap = artifactTypes.reduce((acc, artifactType) => { + const artifactTypeMap = artifactTypes.reduce>((acc, artifactType) => { acc[artifactType.getId()] = artifactType.getName(); return acc; - }, {} as Record); + }, {}); const error = executionError || eventsError; diff --git a/frontend/src/pages/pipelines/global/runs/GlobalPipelineRunsTabs.tsx b/frontend/src/pages/pipelines/global/runs/GlobalPipelineRunsTabs.tsx index a2e2e6a45e..62ddbacdfd 100644 --- a/frontend/src/pages/pipelines/global/runs/GlobalPipelineRunsTabs.tsx +++ b/frontend/src/pages/pipelines/global/runs/GlobalPipelineRunsTabs.tsx @@ -30,7 +30,12 @@ const GlobalPipelineRunsTab: React.FC = () => { return ( setSearchParams({ runType: tabId as PipelineRunType })} + onSelect={(_event, tabId) => { + const enumValue = asEnumMember(tabId, PipelineRunType); + if (enumValue !== null) { + setSearchParams({ runType: enumValue }); + } + }} aria-label="Pipeline run page tabs" role="region" className="odh-pipeline-runs-page-tabs" diff --git a/frontend/src/pages/projects/ProjectDetailsContext.tsx b/frontend/src/pages/projects/ProjectDetailsContext.tsx index 825fb866ec..2e7ea07e1d 100644 --- a/frontend/src/pages/projects/ProjectDetailsContext.tsx +++ b/frontend/src/pages/projects/ProjectDetailsContext.tsx @@ -52,6 +52,7 @@ type ProjectDetailsContextType = { export const ProjectDetailsContext = React.createContext({ // We never will get into a case without a project, so fudge the default value + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions currentProject: null as unknown as ProjectKind, refreshAllProjectData: () => undefined, filterTokens: () => [], diff --git a/frontend/src/pages/projects/screens/detail/overview/serverModels/deployedModels/DeployedModelsSection.tsx b/frontend/src/pages/projects/screens/detail/overview/serverModels/deployedModels/DeployedModelsSection.tsx index 6a2b420559..0561d4d81e 100644 --- a/frontend/src/pages/projects/screens/detail/overview/serverModels/deployedModels/DeployedModelsSection.tsx +++ b/frontend/src/pages/projects/screens/detail/overview/serverModels/deployedModels/DeployedModelsSection.tsx @@ -58,11 +58,14 @@ const DeployedModelsSection: React.FC = ({ isMultiPl return; } if (isMultiPlatform) { - const modelInferenceServices = modelServers.reduce((acc, modelServer) => { - const services = getInferenceServiceFromServingRuntime(inferenceServices, modelServer); - acc.push(...services); - return acc; - }, [] as InferenceServiceKind[]); + const modelInferenceServices = modelServers.reduce( + (acc, modelServer) => { + const services = getInferenceServiceFromServingRuntime(inferenceServices, modelServer); + acc.push(...services); + return acc; + }, + [], + ); setDeployedModels(modelInferenceServices); } else { setDeployedModels(inferenceServices); diff --git a/frontend/src/pages/projects/screens/spawner/environmentVariables/EnvConfigMap.tsx b/frontend/src/pages/projects/screens/spawner/environmentVariables/EnvConfigMap.tsx index 087af37f9d..7d876cb12b 100644 --- a/frontend/src/pages/projects/screens/spawner/environmentVariables/EnvConfigMap.tsx +++ b/frontend/src/pages/projects/screens/spawner/environmentVariables/EnvConfigMap.tsx @@ -4,6 +4,7 @@ import { EnvironmentVariableType, EnvVariableData, } from '~/pages/projects/types'; +import { asEnumMember } from '~/utilities/utils'; import EnvDataTypeField from './EnvDataTypeField'; import GenericKeyValuePairField from './GenericKeyValuePairField'; import { EMPTY_KEY_VALUE_PAIR } from './const'; @@ -22,7 +23,9 @@ const DEFAULT_ENV: EnvVariableData = { const EnvConfigMap: React.FC = ({ env = DEFAULT_ENV, onUpdate }) => ( onUpdate({ ...env, category: value as ConfigMapCategory, data: [] })} + onSelection={(value) => + onUpdate({ ...env, category: asEnumMember(value, ConfigMapCategory), data: [] }) + } options={{ [ConfigMapCategory.GENERIC]: { label: 'Key / value', diff --git a/frontend/src/pages/projects/screens/spawner/environmentVariables/EnvSecret.tsx b/frontend/src/pages/projects/screens/spawner/environmentVariables/EnvSecret.tsx index b5abf71d37..aab8fe07c0 100644 --- a/frontend/src/pages/projects/screens/spawner/environmentVariables/EnvSecret.tsx +++ b/frontend/src/pages/projects/screens/spawner/environmentVariables/EnvSecret.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { EnvironmentVariableType, EnvVariableData, SecretCategory } from '~/pages/projects/types'; +import { asEnumMember } from '~/utilities/utils'; import EnvDataTypeField from './EnvDataTypeField'; import GenericKeyValuePairField from './GenericKeyValuePairField'; import { EMPTY_KEY_VALUE_PAIR } from './const'; @@ -18,7 +19,9 @@ const DEFAULT_ENV: EnvVariableData = { const EnvSecret: React.FC = ({ env = DEFAULT_ENV, onUpdate }) => ( onUpdate({ ...env, category: value as SecretCategory, data: [] })} + onSelection={(value) => + onUpdate({ ...env, category: asEnumMember(value, SecretCategory), data: [] }) + } options={{ [SecretCategory.GENERIC]: { label: 'Key / value', diff --git a/frontend/src/pages/projects/screens/spawner/environmentVariables/EnvTypeSelectField.tsx b/frontend/src/pages/projects/screens/spawner/environmentVariables/EnvTypeSelectField.tsx index 259f796cb3..a882c76690 100644 --- a/frontend/src/pages/projects/screens/spawner/environmentVariables/EnvTypeSelectField.tsx +++ b/frontend/src/pages/projects/screens/spawner/environmentVariables/EnvTypeSelectField.tsx @@ -4,6 +4,7 @@ import { Select, SelectOption } from '@patternfly/react-core/deprecated'; import { MinusCircleIcon } from '@patternfly/react-icons'; import { EnvironmentVariableType, EnvVariable } from '~/pages/projects/types'; import IndentSection from '~/pages/projects/components/IndentSection'; +import { asEnumMember } from '~/utilities/utils'; import EnvTypeSwitch from './EnvTypeSwitch'; type EnvTypeSelectFieldProps = { @@ -32,10 +33,13 @@ const EnvTypeSelectField: React.FC = ({ aria-label="Select environment variable type" onSelect={(e, value) => { if (typeof value === 'string') { - onUpdate({ - type: value as EnvironmentVariableType, - }); - setOpen(false); + const enumValue = asEnumMember(value, EnvironmentVariableType); + if (enumValue !== null) { + onUpdate({ + type: enumValue, + }); + setOpen(false); + } } }} > diff --git a/frontend/src/pages/projects/screens/spawner/environmentVariables/utils.ts b/frontend/src/pages/projects/screens/spawner/environmentVariables/utils.ts index d49b49aae5..e73f077feb 100644 --- a/frontend/src/pages/projects/screens/spawner/environmentVariables/utils.ts +++ b/frontend/src/pages/projects/screens/spawner/environmentVariables/utils.ts @@ -7,12 +7,14 @@ export const removeArrayItem = (values: T[], index: number): T[] => values.filter((v, i) => i !== index); export const isConfigMapKind = (object: unknown): object is ConfigMapKind => - (object as ConfigMapKind).kind === 'ConfigMap'; + typeof object === 'object' && object !== null && 'kind' in object && object.kind === 'ConfigMap'; export const isSecretKind = (object: unknown): object is SecretKind => - (object as SecretKind).kind === 'Secret'; + typeof object === 'object' && object !== null && 'kind' in object && object.kind === 'Secret'; export const isStringKeyValuePairObject = (object: unknown): object is Record => - Object.entries(object as Record).every( + typeof object === 'object' && + object !== null && + Object.entries(object).every( ([key, value]) => typeof key === 'string' && typeof value === 'string', ); diff --git a/frontend/src/pages/projects/screens/spawner/spawnerUtils.ts b/frontend/src/pages/projects/screens/spawner/spawnerUtils.ts index 54bbf8397f..b68507988d 100644 --- a/frontend/src/pages/projects/screens/spawner/spawnerUtils.ts +++ b/frontend/src/pages/projects/screens/spawner/spawnerUtils.ts @@ -81,7 +81,10 @@ export const getImageVersionSelectOptionObject = ( export const isImageVersionSelectOptionObject = ( object: unknown, ): object is ImageVersionSelectOptionObjectType => - (object as ImageVersionSelectOptionObjectType | undefined)?.imageVersion !== undefined; + typeof object === 'object' && + object !== null && + 'imageVersion' in object && + object.imageVersion !== undefined; /******************* Compare utils for sorting *******************/ const getBuildNumber = (build: BuildKind): number => { const buildNumber = build.metadata.annotations?.['openshift.io/build.number'] || '-1'; diff --git a/frontend/src/pages/projects/screens/spawner/useBuildStatuses.ts b/frontend/src/pages/projects/screens/spawner/useBuildStatuses.ts index 4c2c3987f3..0e08b4e05f 100644 --- a/frontend/src/pages/projects/screens/spawner/useBuildStatuses.ts +++ b/frontend/src/pages/projects/screens/spawner/useBuildStatuses.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { getNotebookBuildConfigs, getBuildsForBuildConfig } from '~/api'; import useNotification from '~/utilities/useNotification'; -import { BuildConfigKind, BuildKind, BuildPhase } from '~/k8sTypes'; +import { BuildConfigKind, BuildPhase } from '~/k8sTypes'; import { BuildStatus } from './types'; import { compareBuilds } from './spawnerUtils'; @@ -26,7 +26,7 @@ const useBuildStatuses = (namespace?: string): BuildStatus[] => { imageStreamVersion: buildConfig.spec.output.to.name, }; } - const mostRecent = builds.toSorted(compareBuilds).pop() as BuildKind; + const mostRecent = builds.toSorted(compareBuilds)[builds.length - 1]; return { name: buildNotebookName, status: mostRecent.status.phase, diff --git a/frontend/src/redux/context.ts b/frontend/src/redux/context.ts index 82105b8f35..e93bf2751a 100644 --- a/frontend/src/redux/context.ts +++ b/frontend/src/redux/context.ts @@ -2,5 +2,6 @@ import * as React from 'react'; import { ReactReduxContextValue } from 'react-redux'; export const ReduxContext = React.createContext( + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions {} as ReactReduxContextValue, ); diff --git a/frontend/src/redux/store/store.ts b/frontend/src/redux/store/store.ts index 5eb7522efd..aa01b385ce 100644 --- a/frontend/src/redux/store/store.ts +++ b/frontend/src/redux/store/store.ts @@ -5,7 +5,7 @@ import appReducer from '~/redux/reducers/appReducer'; import { ODH_PRODUCT_NAME } from '~/utilities/const'; const composeEnhancers = - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?.({ name: ODH_PRODUCT_NAME, }) || compose; @@ -16,7 +16,7 @@ export const store = configureStore(); // Create a separate for the for the dynamic plugin SDK const sdkComposeEnhancers = - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?.({ name: `${ODH_PRODUCT_NAME} - Dynamic Plugin SDK`, }) || compose; diff --git a/frontend/src/typeHelpers.ts b/frontend/src/typeHelpers.ts index 6df5120c04..2a22a458fe 100644 --- a/frontend/src/typeHelpers.ts +++ b/frontend/src/typeHelpers.ts @@ -1,3 +1,5 @@ +import { isEnumMember } from '~/utilities/utils'; + /** * The type `{}` doesn't mean "any empty object", it means "any non-nullish value". * @@ -162,7 +164,7 @@ export type ExactlyOne = AtMostOne & AtLeastOne; export const isInEnum = (e: T) => (token: unknown): token is T[keyof T] => - Object.values(e).includes(token as T[keyof T]); + isEnumMember(token, e); /** * Pick keys from enum types diff --git a/frontend/src/utilities/NavData.tsx b/frontend/src/utilities/NavData.tsx index 3192d0967c..d8522e0b59 100644 --- a/frontend/src/utilities/NavData.tsx +++ b/frontend/src/utilities/NavData.tsx @@ -28,10 +28,9 @@ export type NavDataGroup = NavDataCommon & { export type NavDataItem = NavDataHref | NavDataGroup; -export const isNavDataHref = (navData: NavDataItem): navData is NavDataHref => - !!(navData as NavDataHref).href; +export const isNavDataHref = (navData: NavDataItem): navData is NavDataHref => 'href' in navData; export const isNavDataGroup = (navData: NavDataItem): navData is NavDataGroup => - !!(navData as NavDataGroup).children; + 'children' in navData; const useAreaCheck = (area: SupportedArea, success: T[]): T[] => useIsAreaAvailable(area).status ? success : []; diff --git a/frontend/src/utilities/__tests__/utils.spec.ts b/frontend/src/utilities/__tests__/utils.spec.ts index 04f4135917..736c302d85 100644 --- a/frontend/src/utilities/__tests__/utils.spec.ts +++ b/frontend/src/utilities/__tests__/utils.spec.ts @@ -1,4 +1,4 @@ -import { asEnumMember, isEnumMember } from '~/utilities/utils'; +import { asEnumMember, enumIterator, isEnumMember } from '~/utilities/utils'; enum Test { first = '1st', @@ -87,3 +87,21 @@ describe('isEnumMember', () => { expect(isEnumMember(null, TestNumeric)).toBe(false); }); }); + +describe('enumIterator', () => { + it('should iterate over enum values', () => { + expect(enumIterator(Test)).toEqual([ + ['first', '1st'], + ['second', '2nd'], + ]); + expect(enumIterator(TestMixed)).toEqual([ + ['first', 1], + ['second', '2nd'], + ['third', '3rd'], + ]); + expect(enumIterator(TestNumeric)).toEqual([ + ['first', 0], + ['second', 1], + ]); + }); +}); diff --git a/frontend/src/utilities/useAcceleratorProfileState.ts b/frontend/src/utilities/useAcceleratorProfileState.ts index f777fc594b..239de80f51 100644 --- a/frontend/src/utilities/useAcceleratorProfileState.ts +++ b/frontend/src/utilities/useAcceleratorProfileState.ts @@ -10,7 +10,7 @@ import { TolerationOperator, } from '~/types'; import useGenericObjectState, { GenericObjectState } from '~/utilities/useGenericObjectState'; -import { getAcceleratorProfileCount } from '~/utilities/utils'; +import { getAcceleratorProfileCount, isEnumMember } from '~/utilities/utils'; export type AcceleratorProfileState = { acceleratorProfile?: AcceleratorProfileKind; @@ -65,9 +65,8 @@ const useAcceleratorProfileState = ( } else { // check if there is accelerator usage in the container // this is to handle the case where the accelerator is disabled, deleted, or empty - const containerResourceAttributes = Object.values(ContainerResourceAttributes) as string[]; const possibleAcceleratorRequests = Object.entries(resources.requests ?? {}) - .filter(([key]) => !containerResourceAttributes.includes(key)) + .filter(([key]) => !isEnumMember(key, ContainerResourceAttributes)) .map(([key, value]) => ({ identifier: key, count: value })); if (possibleAcceleratorRequests.length > 0) { // check if they are just using the nvidia.com/gpu diff --git a/frontend/src/utilities/useDraggableTable.ts b/frontend/src/utilities/useDraggableTable.ts index 6fa8540dcb..9ea56d503f 100644 --- a/frontend/src/utilities/useDraggableTable.ts +++ b/frontend/src/utilities/useDraggableTable.ts @@ -125,7 +125,7 @@ const useDraggableTable = ( return; } - const curListItem = (evt.target as HTMLTableSectionElement).closest('tr'); + const curListItem = evt.target instanceof HTMLElement ? evt.target.closest('tr') : null; if ( !curListItem || !bodyRef.current.contains(curListItem) || @@ -159,7 +159,7 @@ const useDraggableTable = ( ); const onDragEnd = React.useCallback((evt) => { - const target = evt.currentTarget as HTMLTableRowElement; + const target = evt.currentTarget; target.classList.remove(styles.modifiers.ghostRow); target.setAttribute('aria-pressed', 'false'); setDraggedItemId(''); diff --git a/frontend/src/utilities/useWatchNotebooksForUsers.tsx b/frontend/src/utilities/useWatchNotebooksForUsers.tsx index 13a09e4f75..9451a16d0c 100644 --- a/frontend/src/utilities/useWatchNotebooksForUsers.tsx +++ b/frontend/src/utilities/useWatchNotebooksForUsers.tsx @@ -54,10 +54,13 @@ const useWatchNotebooksForUsers = ( setLoadError(undefined); } - const newNotebooks = successes.reduce((acc, { value }) => { - acc[value.name] = value.data; - return acc; - }, {} as UsernameMap); + const newNotebooks = successes.reduce>( + (acc, { value }) => { + acc[value.name] = value.data; + return acc; + }, + {}, + ); setNotebooks((prevState) => ({ ...prevState, ...newNotebooks })); setLoaded(true); }) diff --git a/frontend/src/utilities/utils.ts b/frontend/src/utilities/utils.ts index 2b7def86e2..f29ef82928 100644 --- a/frontend/src/utilities/utils.ts +++ b/frontend/src/utilities/utils.ts @@ -140,6 +140,7 @@ export const getDashboardMainContainer = (): HTMLElement => document.getElementById(DASHBOARD_MAIN_CONTAINER_ID) || document.body; export const isHTMLInputElement = (object: unknown): object is HTMLInputElement => + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions (object as Partial).value !== undefined; export const normalizeBetween = (value: number, min?: number, max?: number): number => { @@ -159,13 +160,19 @@ export const getAcceleratorProfileCount = ( resources: ContainerResources, ): number => Number(resources.requests?.[acceleratorProfile.spec.identifier] ?? 0); +export const enumIterator = (e: T): [keyof T, T[keyof T]][] => + Object.entries(e) + .filter(([key]) => Number.isNaN(Number(key))) + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + .map(([key, value]) => [key as keyof T, value]); + export const asEnumMember = ( - member: T[keyof T] | string | number | null, + member: T[keyof T] | string | number | undefined | null, e: T, ): T[keyof T] | null => (isEnumMember(member, e) ? member : null); export const isEnumMember = ( - member: T[keyof T] | string | number | null, + member: T[keyof T] | string | number | undefined | unknown | null, e: T, ): member is T[keyof T] => { if (member != null) {