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;

Reply via email to