From 73ece11cc72a8a5f6a745525e5c9591af5c09eb8 Mon Sep 17 00:00:00 2001 From: Shailesh Parmar Date: Mon, 24 Aug 2026 16:42:04 +0530 Subject: [PATCH] style(ui): fix @typescript-eslint/no-use-before-define + promote to error Clear all 15 no-use-before-define violations: 6 safe reorders (hoist pure helpers above first use) + 9 documented disables for mutual-recursion / derived-below cases that can't be reordered without moving the error. Promote the rule warn->error so CI blocks regressions. Chunk 2a of the type-safety split (was bundled PR #30990). 0 new errors, 240 tests pass. Part of epic #30977. --- .../src/main/resources/ui/eslint.config.mjs | 5 +- .../components/TestCaseFormDrawer.tsx | 4 +- .../DomainDetails/DomainDetails.component.tsx | 3 + .../components/DomainTreeView.tsx | 1 + .../src/pages/AuditLogsPage/AuditLogsPage.tsx | 1 + .../ui/src/utils/ApplicationUtils.tsx | 60 +++++++++---------- .../ui/src/utils/CronExpressionUtils.ts | 32 +++++----- .../ui/src/utils/CustomProperty.utils.ts | 10 ++-- .../GlossaryTerm/GlossaryTermWidgetUtils.tsx | 22 +++---- .../utils/ServiceConnectionDetailsUtils.tsx | 2 + 10 files changed, 75 insertions(+), 65 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/eslint.config.mjs b/openmetadata-ui/src/main/resources/ui/eslint.config.mjs index bbfc3b52ba28..706f9dc0fbed 100644 --- a/openmetadata-ui/src/main/resources/ui/eslint.config.mjs +++ b/openmetadata-ui/src/main/resources/ui/eslint.config.mjs @@ -176,7 +176,10 @@ export default [ // TypeScript rules '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-use-before-define': 'warn', + // Cleared to zero and locked by the ESLint-cleanup stack — safe reorders + // where possible, documented disables for mutual-recursion / derived-below + // cases. Promoted to error so CI blocks any regression. + '@typescript-eslint/no-use-before-define': 'error', 'no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': [ 'error', diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormDrawer.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormDrawer.tsx index e533da469fab..ae20e819182a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormDrawer.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormDrawer.tsx @@ -369,6 +369,8 @@ const TestCaseFormDrawer: FC = ({ testCaseFormBody ); + const closeDrawerRef = useRef<() => void>(() => undefined); + const formBody = ( = ({ ); - const closeDrawerRef = useRef<() => void>(() => undefined); - // Every dismissal path (cancel, X, Escape, backdrop, programmatic close) // funnels through the base drawer's onClose, so the parent is notified // exactly once and the form resets. The backdrop stays dismissable to match diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetails/DomainDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetails/DomainDetails.component.tsx index 49e3a3db8a58..620e4ce5eb7e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetails/DomainDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetails/DomainDetails.component.tsx @@ -326,6 +326,7 @@ const DomainDetails = ({ // refresh domain count when assets tab is selected fetchDomainAssets(); } + // eslint-disable-next-line @typescript-eslint/no-use-before-define -- derived below from `tabs` if (activeKey !== activeTab) { if (onActiveTabChange) { onActiveTabChange(activeKey as EntityTabs); @@ -765,6 +766,7 @@ const DomainDetails = ({ const newFqn = domain.parent ? `${domain.parent.fullyQualifiedName}.${newName.trim()}` : newName.trim(); + // eslint-disable-next-line @typescript-eslint/no-use-before-define -- derived below from `tabs` navigate(getDomainDetailsPath(newFqn, activeTab)); } } catch { @@ -908,6 +910,7 @@ const DomainDetails = ({ handleAssetSave: () => { fetchDomainAssets(); assetTabRef.current?.refreshAssets(); + // eslint-disable-next-line @typescript-eslint/no-use-before-define -- derived below from `tabs` activeTab !== EntityTabs.ASSETS && handleTabChange(EntityTabs.ASSETS); }, setShowAddSubDomainModal: openSubDomainDrawer, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DomainListing/components/DomainTreeView.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DomainListing/components/DomainTreeView.tsx index b669423bf46f..06c7c0ec3121 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DomainListing/components/DomainTreeView.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DomainListing/components/DomainTreeView.tsx @@ -197,6 +197,7 @@ const DomainTreeView = ({ const firstDomain = selectDomain(domains, resetExpandedItems, domainFqn); if ((firstDomain?.childrenCount || 0) > 0 && shouldLoadChildren) { + // eslint-disable-next-line @typescript-eslint/no-use-before-define -- useCallback defined below loadDomains(firstDomain.fullyQualifiedName as string); } }, diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AuditLogsPage/AuditLogsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AuditLogsPage/AuditLogsPage.tsx index 0059657974f2..a512aca96645 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/AuditLogsPage/AuditLogsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AuditLogsPage/AuditLogsPage.tsx @@ -336,6 +336,7 @@ const AuditLogsPage = () => { } if (!cancelled) { + // eslint-disable-next-line @typescript-eslint/no-use-before-define -- mutually recursive with pollOnce scheduleNextPoll(); } }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationUtils.tsx index d86cf157a7fc..aae50e447242 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationUtils.tsx @@ -79,6 +79,36 @@ const VECTOR_INDEXABLE_ENTITIES = new Set([ 'topic', ]); +/** + * Format avg stage latency as a short human string. Returns "—" when no records or no time + * has been recorded yet (e.g. fresh job, or stages that haven't reported timing because the + * legacy non-distributed path is in use). Below 1 ms shows "<1 ms" rather than rounding to 0. + */ +export const formatLatencyAverage = ( + totalTimeMs?: number, + successRecords?: number +): string => { + // No timing recorded yet (legacy non-distributed path) or no records to divide by. + // totalTimeMs === 0 with successRecords > 0 is a valid sub-millisecond batch and + // falls through to the "<1 ms" branch. + if ( + totalTimeMs === undefined || + successRecords === undefined || + successRecords <= 0 + ) { + return '—'; + } + const avgMs = totalTimeMs / successRecords; + if (avgMs < 1) { + return '<1 ms'; + } + if (avgMs < 1000) { + return `${avgMs.toFixed(1)} ms`; + } + + return `${(avgMs / 1000).toFixed(2)} s`; +}; + export const getEntityStatsData = (data: { [key: string]: StepStats; }): EntityStatsData[] => { @@ -144,36 +174,6 @@ export const getEntityStatsData = (data: { ); }; -/** - * Format avg stage latency as a short human string. Returns "—" when no records or no time - * has been recorded yet (e.g. fresh job, or stages that haven't reported timing because the - * legacy non-distributed path is in use). Below 1 ms shows "<1 ms" rather than rounding to 0. - */ -export const formatLatencyAverage = ( - totalTimeMs?: number, - successRecords?: number -): string => { - // No timing recorded yet (legacy non-distributed path) or no records to divide by. - // totalTimeMs === 0 with successRecords > 0 is a valid sub-millisecond batch and - // falls through to the "<1 ms" branch. - if ( - totalTimeMs === undefined || - successRecords === undefined || - successRecords <= 0 - ) { - return '—'; - } - const avgMs = totalTimeMs / successRecords; - if (avgMs < 1) { - return '<1 ms'; - } - if (avgMs < 1000) { - return `${avgMs.toFixed(1)} ms`; - } - - return `${(avgMs / 1000).toFixed(2)} s`; -}; - /** * Format throughput in records per second derived from the same total-time / success-records * pair. Useful as a secondary signal next to avg latency when comparing entities or runs. diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CronExpressionUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/CronExpressionUtils.ts index 741d24769415..9d0d43bbedb8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CronExpressionUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CronExpressionUtils.ts @@ -160,6 +160,22 @@ export const getCronDefaultValue = (appName: string) => { return initialValue; }; +export const getDefaultScheduleFromPeriod = ( + includePeriodOptions: string[] +) => { + if (includePeriodOptions.includes('day')) { + return DEFAULT_SCHEDULE_CRON_DAILY; + } else if (includePeriodOptions.includes('week')) { + return DEFAULT_SCHEDULE_CRON_WEEKLY; + } else if (includePeriodOptions.includes('month')) { + return DEFAULT_SCHEDULE_CRON_MONTHLY; + } else if (includePeriodOptions.includes('hour')) { + return DEFAULT_SCHEDULE_CRON_HOURLY; + } + + return DEFAULT_SCHEDULE_CRON_DAILY; +}; + export const getDefaultScheduleValue = ({ defaultSchedule, includePeriodOptions, @@ -182,22 +198,6 @@ export const getDefaultScheduleValue = ({ return getDefaultScheduleFromPeriod(includePeriodOptions); }; -export const getDefaultScheduleFromPeriod = ( - includePeriodOptions: string[] -) => { - if (includePeriodOptions.includes('day')) { - return DEFAULT_SCHEDULE_CRON_DAILY; - } else if (includePeriodOptions.includes('week')) { - return DEFAULT_SCHEDULE_CRON_WEEKLY; - } else if (includePeriodOptions.includes('month')) { - return DEFAULT_SCHEDULE_CRON_MONTHLY; - } else if (includePeriodOptions.includes('hour')) { - return DEFAULT_SCHEDULE_CRON_HOURLY; - } - - return DEFAULT_SCHEDULE_CRON_DAILY; -}; - export const getUpdatedStateFromFormState = ( currentState: StateValue, formValues: StateValue & WorkflowExtraConfig & T diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CustomProperty.utils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/CustomProperty.utils.ts index 9cbf98e2599d..fd10e887a4bd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CustomProperty.utils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CustomProperty.utils.ts @@ -87,6 +87,11 @@ const serializeTimeInterval = ( return isEmptyExtensionValue(interval) ? undefined : interval; }; +export const hasPopulatedTableRows = (value: unknown) => + isRecord(value) && + Array.isArray(value.rows) && + value.rows.some((row) => isRecord(row) && Object.values(row).some(Boolean)); + const serializeTableValue = (raw: unknown): unknown => hasPopulatedTableRows(raw) ? raw : undefined; @@ -217,11 +222,6 @@ export const filterPopulatedTableRows = >( rows: T[] ) => rows.filter((row) => Object.values(row).some(Boolean)); -export const hasPopulatedTableRows = (value: unknown) => - isRecord(value) && - Array.isArray(value.rows) && - value.rows.some((row) => isRecord(row) && Object.values(row).some(Boolean)); - export const getCustomPropertyEntityPathname = (entityType: string) => { const entityPathEntries = Object.entries(ENTITY_PATH); const entityPath = entityPathEntries.find(([, path]) => path === entityType); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryTerm/GlossaryTermWidgetUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryTerm/GlossaryTermWidgetUtils.tsx index 681641fda57a..817b89984095 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryTerm/GlossaryTermWidgetUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryTerm/GlossaryTermWidgetUtils.tsx @@ -83,6 +83,17 @@ const WorkflowHistory = withSuspenseFallback( ) ); +const GlossaryTermDomainWidget = () => { + const { entityRules } = useGenericContext(); + + return ( + + ); +}; + export const getGlossaryTermWidgetFromKey = (widgetConfig: WidgetConfig) => { if ( widgetConfig.i.startsWith(GlossaryTermDetailPageWidgetKeys.WORKFLOW_HISTORY) @@ -121,14 +132,3 @@ export const getGlossaryTermWidgetFromKey = (widgetConfig: WidgetConfig) => { /> ); }; - -const GlossaryTermDomainWidget = () => { - const { entityRules } = useGenericContext(); - - return ( - - ); -}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceConnectionDetailsUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceConnectionDetailsUtils.tsx index a313f04c25d8..b55762313975 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceConnectionDetailsUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceConnectionDetailsUtils.tsx @@ -166,6 +166,7 @@ export const getKeyValues = ({ } // Handle special service configurations + // eslint-disable-next-line @typescript-eslint/no-use-before-define -- mutually recursive with getKeyValues const specialConfig = handleSpecialServiceConfig( serviceType, key, @@ -183,6 +184,7 @@ export const getKeyValues = ({ serviceType === EntityType.DATABASE_SERVICE && key === 'configSource' ) { + // eslint-disable-next-line @typescript-eslint/no-use-before-define -- mutually recursive with getKeyValues const configSource = handleDatabaseConfigSource( key, value,