This is an automated email from the ASF dual-hosted git repository. maximebeauchemin pushed a commit to branch js-to-ts in repository https://gitbox.apache.org/repos/asf/superset.git
commit 6b65ab7a29140c1e9a894f6d9581fa0f7ba0031a Author: Maxime Beauchemin <[email protected]> AuthorDate: Sun Sep 7 16:03:46 2025 -0700 feat(js-to-ts): migrate 3 utility files to TypeScript with comprehensive tests Migrate small leaf node utility files to TypeScript: - getDirectPathToTabIndex.js → .ts (35 lines) - Dashboard tab path utility - isDashboardLoading.js → .ts (34 lines) - Chart loading state checker - Separator.js → .ts (85 lines) - Control panel configuration for separator widget Key improvements: - Proper TypeScript interfaces (TabsComponentLike, ChartLoadTimestamps) - Zero `any` types throughout all migrations - Comprehensive test coverage with 8 total test cases - Type-safe control panel configuration with ControlPanelState - All TypeScript compilation and ESLint validation passes Technical notes: - Used proper optional property handling with nullish coalescing (??) - Created focused interfaces avoiding over-broad typing - Test files include edge cases and error scenarios Progress: 12/219 files migrated (5.5%) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> --- ...TabIndex.js => getDirectPathToTabIndex.test.ts} | 30 +++++++-- ...athToTabIndex.js => getDirectPathToTabIndex.ts} | 12 +++- .../src/dashboard/util/isDashboardLoading.test.ts | 48 ++++++++++++++ ...isDashboardLoading.js => isDashboardLoading.ts} | 19 ++++-- .../src/explore/controlPanels/Separator.test.ts | 76 ++++++++++++++++++++++ .../controlPanels/{Separator.js => Separator.ts} | 23 +++++-- 6 files changed, 190 insertions(+), 18 deletions(-) diff --git a/superset-frontend/src/dashboard/util/getDirectPathToTabIndex.js b/superset-frontend/src/dashboard/util/getDirectPathToTabIndex.test.ts similarity index 56% copy from superset-frontend/src/dashboard/util/getDirectPathToTabIndex.js copy to superset-frontend/src/dashboard/util/getDirectPathToTabIndex.test.ts index df77c321c0..c9a0c769cd 100644 --- a/superset-frontend/src/dashboard/util/getDirectPathToTabIndex.js +++ b/superset-frontend/src/dashboard/util/getDirectPathToTabIndex.test.ts @@ -16,10 +16,28 @@ * specific language governing permissions and limitations * under the License. */ -export default function getDirectPathToTabIndex(tabsComponent, tabIndex) { - const directPathToFilter = (tabsComponent.parents || []).slice(); - directPathToFilter.push(tabsComponent.id); - directPathToFilter.push(tabsComponent.children[tabIndex]); +import getDirectPathToTabIndex from './getDirectPathToTabIndex'; - return directPathToFilter; -} +describe('getDirectPathToTabIndex', () => { + it('builds path using parents, id, and child at index', () => { + const tabs = { + id: 'TABS_ID', + parents: ['ROOT', 'ROW_1'], + children: ['TAB_A', 'TAB_B', 'TAB_C'], + }; + expect(getDirectPathToTabIndex(tabs, 1)).toEqual([ + 'ROOT', + 'ROW_1', + 'TABS_ID', + 'TAB_B', + ]); + }); + + it('handles missing parents', () => { + const tabs = { + id: 'TABS_ID', + children: ['TAB_A'], + }; + expect(getDirectPathToTabIndex(tabs, 0)).toEqual(['TABS_ID', 'TAB_A']); + }); +}); diff --git a/superset-frontend/src/dashboard/util/getDirectPathToTabIndex.js b/superset-frontend/src/dashboard/util/getDirectPathToTabIndex.ts similarity index 80% rename from superset-frontend/src/dashboard/util/getDirectPathToTabIndex.js rename to superset-frontend/src/dashboard/util/getDirectPathToTabIndex.ts index df77c321c0..61d4a105ed 100644 --- a/superset-frontend/src/dashboard/util/getDirectPathToTabIndex.js +++ b/superset-frontend/src/dashboard/util/getDirectPathToTabIndex.ts @@ -16,7 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -export default function getDirectPathToTabIndex(tabsComponent, tabIndex) { +export interface TabsComponentLike { + id: string; + parents?: string[]; + children: string[]; + [key: string]: unknown; +} + +export default function getDirectPathToTabIndex( + tabsComponent: TabsComponentLike, + tabIndex: number, +): string[] { const directPathToFilter = (tabsComponent.parents || []).slice(); directPathToFilter.push(tabsComponent.id); directPathToFilter.push(tabsComponent.children[tabIndex]); diff --git a/superset-frontend/src/dashboard/util/isDashboardLoading.test.ts b/superset-frontend/src/dashboard/util/isDashboardLoading.test.ts new file mode 100644 index 0000000000..ee6aa85915 --- /dev/null +++ b/superset-frontend/src/dashboard/util/isDashboardLoading.test.ts @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import isDashboardLoading, { ChartLoadTimestamps } from './isDashboardLoading'; + +describe('isDashboardLoading', () => { + it('returns false when no charts are loading', () => { + const charts: Record<string, ChartLoadTimestamps> = { + a: { chartUpdateStartTime: 1, chartUpdateEndTime: 2 }, + b: { chartUpdateStartTime: 5, chartUpdateEndTime: 5 }, + }; + expect(isDashboardLoading(charts)).toBe(false); + }); + + it('returns true when any chart has start > end', () => { + const charts: Record<string, ChartLoadTimestamps> = { + a: { chartUpdateStartTime: 10, chartUpdateEndTime: 5 }, + b: { chartUpdateStartTime: 1, chartUpdateEndTime: 2 }, + }; + expect(isDashboardLoading(charts)).toBe(true); + }); + + it('treats missing end as 0', () => { + const charts: Record<string, ChartLoadTimestamps> = { + a: { chartUpdateStartTime: 1 }, + }; + expect(isDashboardLoading(charts)).toBe(true); + }); + + it('handles empty charts object', () => { + expect(isDashboardLoading({})).toBe(false); + }); +}); diff --git a/superset-frontend/src/dashboard/util/isDashboardLoading.js b/superset-frontend/src/dashboard/util/isDashboardLoading.ts similarity index 63% rename from superset-frontend/src/dashboard/util/isDashboardLoading.js rename to superset-frontend/src/dashboard/util/isDashboardLoading.ts index c970fc8978..ce5ffdc612 100644 --- a/superset-frontend/src/dashboard/util/isDashboardLoading.js +++ b/superset-frontend/src/dashboard/util/isDashboardLoading.ts @@ -16,8 +16,19 @@ * specific language governing permissions and limitations * under the License. */ -export default function isDashboardLoading(charts) { - return Object.values(charts).some( - chart => chart.chartUpdateStartTime > (chart.chartUpdateEndTime || 0), - ); +export interface ChartLoadTimestamps { + chartUpdateStartTime?: number; + chartUpdateEndTime?: number | null; + // allow extra fields without narrowing + [key: string]: unknown; +} + +export default function isDashboardLoading( + charts: Record<string, ChartLoadTimestamps>, +): boolean { + return Object.values(charts).some(chart => { + const start = chart.chartUpdateStartTime ?? 0; + const end = chart.chartUpdateEndTime ?? 0; + return start > end; + }); } diff --git a/superset-frontend/src/explore/controlPanels/Separator.test.ts b/superset-frontend/src/explore/controlPanels/Separator.test.ts new file mode 100644 index 0000000000..42212c4927 --- /dev/null +++ b/superset-frontend/src/explore/controlPanels/Separator.test.ts @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import type { + ControlPanelState, + ControlState, +} from '@superset-ui/chart-controls'; +import Separator from './Separator'; + +function getCodeControlMapStateToProps() { + const sections = + (Separator.controlPanelSections as unknown as Array<{ + controlSetRows?: Array< + Array<{ + name?: string; + config?: { + mapStateToProps?: (s: Partial<ControlPanelState>) => { + language: string; + }; + }; + }> + >; + }>) || []; + + const codeControl = sections + .flatMap(s => s.controlSetRows || []) + .flatMap(r => r) + .find(i => i?.name === 'code') as unknown as { + config: { + mapStateToProps: (s: Partial<ControlPanelState>) => { language: string }; + }; + }; + + if (!codeControl || !codeControl.config?.mapStateToProps) { + throw new Error('Code control configuration not found'); + } + return codeControl.config.mapStateToProps; +} + +describe('Separator control panel config', () => { + it('defaults language to markdown when markup_type is missing', () => { + const mapStateToProps = getCodeControlMapStateToProps(); + const state: Partial<ControlPanelState> = {}; + const result = mapStateToProps(state); + expect(result.language).toBe('markdown'); + }); + + it('uses markup_type value when provided', () => { + const mapStateToProps = getCodeControlMapStateToProps(); + const state: Partial<ControlPanelState> = { + controls: { + // minimal mock for the control used in mapStateToProps + markup_type: { value: 'html' } as Partial< + ControlState<'SelectControl'> + > as ControlState<'SelectControl'>, + }, + }; + const result = mapStateToProps(state); + expect(result.language).toBe('html'); + }); +}); diff --git a/superset-frontend/src/explore/controlPanels/Separator.js b/superset-frontend/src/explore/controlPanels/Separator.ts similarity index 79% rename from superset-frontend/src/explore/controlPanels/Separator.js rename to superset-frontend/src/explore/controlPanels/Separator.ts index d49d389b90..170d09c997 100644 --- a/superset-frontend/src/explore/controlPanels/Separator.js +++ b/superset-frontend/src/explore/controlPanels/Separator.ts @@ -17,9 +17,13 @@ * under the License. */ import { t, validateNonEmpty } from '@superset-ui/core'; +import type { + ControlPanelConfig, + ControlPanelState, +} from '@superset-ui/chart-controls'; import { formatSelectOptions } from 'src/explore/exploreUtils'; -export default { +const config: ControlPanelConfig = { controlPanelSections: [ { label: t('Code'), @@ -45,12 +49,15 @@ export default { type: 'TextAreaControl', label: t('Code'), description: t('Put your code here'), - mapStateToProps: state => ({ - language: - state.controls && state.controls.markup_type - ? state.controls.markup_type.value - : 'markdown', - }), + mapStateToProps: (state: Partial<ControlPanelState>) => { + const languageValue = state.controls?.markup_type?.value; + return { + language: + typeof languageValue === 'string' + ? languageValue + : 'markdown', + }; + }, default: '', }, }, @@ -74,3 +81,5 @@ export default { }, }, }; + +export default config;
