From da8e077a4445f536e5aec894c45c539f936ec5ca Mon Sep 17 00:00:00 2001 From: Evan Rusackas Date: Thu, 18 Dec 2025 13:54:40 -0800 Subject: [PATCH] chore(frontend): migrate utility JS files to TypeScript (#36721) Co-authored-by: Claude Opus 4.5 --- ...kMessageToasts.js => mockMessageToasts.ts} | 6 +- .../dnd/{handleHover.js => handleHover.ts} | 13 +++- .../components/dnd/handleScroll/index.ts | 2 +- ...odesTree.js => getFilterFieldNodesTree.ts} | 25 ++++++- ...> getSelectedChartIdForFilterScopeTree.ts} | 7 +- ...ChartIds.js => findNonTabChildChartIds.ts} | 26 +++++-- ...nentIds.js => findTopLevelComponentIds.ts} | 33 +++++++-- ...js => getLoadStatsPerTopLevelComponent.ts} | 21 +++++- ...sList.js => updateComponentParentsList.ts} | 14 +++- ...aveModalReducer.js => saveModalReducer.ts} | 29 ++++++-- .../src/utils/getControlsForVizType.js | 52 -------------- superset-frontend/src/utils/reducerUtils.js | 71 ------------------- 12 files changed, 146 insertions(+), 153 deletions(-) rename superset-frontend/src/components/MessageToasts/{mockMessageToasts.js => mockMessageToasts.ts} (85%) rename superset-frontend/src/dashboard/components/dnd/{handleHover.js => handleHover.ts} (84%) rename superset-frontend/src/dashboard/util/{getFilterFieldNodesTree.js => getFilterFieldNodesTree.ts} (72%) rename superset-frontend/src/dashboard/util/{getSelectedChartIdForFilterScopeTree.js => getSelectedChartIdForFilterScopeTree.ts} (91%) rename superset-frontend/src/dashboard/util/logging/{findNonTabChildChartIds.js => findNonTabChildChartIds.ts} (75%) rename superset-frontend/src/dashboard/util/logging/{findTopLevelComponentIds.js => findTopLevelComponentIds.ts} (77%) rename superset-frontend/src/dashboard/util/logging/{getLoadStatsPerTopLevelComponent.js => getLoadStatsPerTopLevelComponent.ts} (71%) rename superset-frontend/src/dashboard/util/{updateComponentParentsList.js => updateComponentParentsList.ts} (87%) rename superset-frontend/src/explore/reducers/{saveModalReducer.js => saveModalReducer.ts} (71%) delete mode 100644 superset-frontend/src/utils/getControlsForVizType.js delete mode 100644 superset-frontend/src/utils/reducerUtils.js diff --git a/superset-frontend/src/components/MessageToasts/mockMessageToasts.js b/superset-frontend/src/components/MessageToasts/mockMessageToasts.ts similarity index 85% rename from superset-frontend/src/components/MessageToasts/mockMessageToasts.js rename to superset-frontend/src/components/MessageToasts/mockMessageToasts.ts index b06fd0c49fa..f51adc01eb6 100644 --- a/superset-frontend/src/components/MessageToasts/mockMessageToasts.js +++ b/superset-frontend/src/components/MessageToasts/mockMessageToasts.ts @@ -16,9 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -import { ToastType } from 'src/components/MessageToasts/types'; +import { ToastType, ToastMeta } from 'src/components/MessageToasts/types'; -export default [ +const mockMessageToasts: Partial[] = [ { id: 'info_id', toastType: ToastType.Info, text: 'info toast' }, { id: 'danger_id', toastType: ToastType.Danger, text: 'danger toast' }, ]; + +export default mockMessageToasts; diff --git a/superset-frontend/src/dashboard/components/dnd/handleHover.js b/superset-frontend/src/dashboard/components/dnd/handleHover.ts similarity index 84% rename from superset-frontend/src/dashboard/components/dnd/handleHover.js rename to superset-frontend/src/dashboard/components/dnd/handleHover.ts index e709b6e3e54..7e25246e03d 100644 --- a/superset-frontend/src/dashboard/components/dnd/handleHover.js +++ b/superset-frontend/src/dashboard/components/dnd/handleHover.ts @@ -17,13 +17,22 @@ * under the License. */ import { throttle } from 'lodash'; +import { DropTargetMonitor } from 'react-dnd'; import { DASHBOARD_ROOT_TYPE } from 'src/dashboard/util/componentTypes'; import getDropPosition from 'src/dashboard/util/getDropPosition'; +import type { + DragDroppableProps, + DragDroppableComponent, +} from './dragDroppableConfig'; import handleScroll from './handleScroll'; const HOVER_THROTTLE_MS = 100; -function handleHover(props, monitor, Component) { +function handleHover( + props: DragDroppableProps, + monitor: DropTargetMonitor, + Component: DragDroppableComponent, +): void { // this may happen due to throttling if (!Component.mounted) return; @@ -40,7 +49,7 @@ function handleHover(props, monitor, Component) { return; } - Component?.props?.onHover(); + Component?.props?.onHover?.(); Component.setState(() => ({ dropIndicator: dropPosition, diff --git a/superset-frontend/src/dashboard/components/dnd/handleScroll/index.ts b/superset-frontend/src/dashboard/components/dnd/handleScroll/index.ts index 9609edb011b..d072498d0b3 100644 --- a/superset-frontend/src/dashboard/components/dnd/handleScroll/index.ts +++ b/superset-frontend/src/dashboard/components/dnd/handleScroll/index.ts @@ -20,7 +20,7 @@ let scrollTopDashboardInterval: any; const SCROLL_STEP = 120; const INTERVAL_DELAY = 50; -export default function handleScroll(scroll: string) { +export default function handleScroll(scroll: string | null) { const setupScroll = scroll === 'SCROLL_TOP' && !scrollTopDashboardInterval && diff --git a/superset-frontend/src/dashboard/util/getFilterFieldNodesTree.js b/superset-frontend/src/dashboard/util/getFilterFieldNodesTree.ts similarity index 72% rename from superset-frontend/src/dashboard/util/getFilterFieldNodesTree.js rename to superset-frontend/src/dashboard/util/getFilterFieldNodesTree.ts index 727fc3e279a..e1ec1ade7c8 100644 --- a/superset-frontend/src/dashboard/util/getFilterFieldNodesTree.js +++ b/superset-frontend/src/dashboard/util/getFilterFieldNodesTree.ts @@ -22,11 +22,32 @@ import { getDashboardFilterKey } from './getDashboardFilterKey'; import { ALL_FILTERS_ROOT } from './constants'; import { DASHBOARD_ROOT_TYPE } from './componentTypes'; -export default function getFilterFieldNodesTree({ dashboardFilters = {} }) { +interface DashboardFilter { + chartId: number; + filterName: string; + columns: Record; + labels: Record; +} + +interface FilterFieldNode { + value: string | number; + label: string; + type?: string; + children?: FilterFieldNode[]; + showCheckbox?: boolean; +} + +interface GetFilterFieldNodesTreeParams { + dashboardFilters?: Record; +} + +export default function getFilterFieldNodesTree({ + dashboardFilters = {}, +}: GetFilterFieldNodesTreeParams): FilterFieldNode[] { const allFilters = Object.values(dashboardFilters).map(dashboardFilter => { const { chartId, filterName, columns, labels } = dashboardFilter; const children = Object.keys(columns).map(column => ({ - value: getDashboardFilterKey({ chartId, column }), + value: getDashboardFilterKey({ chartId: String(chartId), column }), label: labels[column] || column, })); return { diff --git a/superset-frontend/src/dashboard/util/getSelectedChartIdForFilterScopeTree.js b/superset-frontend/src/dashboard/util/getSelectedChartIdForFilterScopeTree.ts similarity index 91% rename from superset-frontend/src/dashboard/util/getSelectedChartIdForFilterScopeTree.js rename to superset-frontend/src/dashboard/util/getSelectedChartIdForFilterScopeTree.ts index ac9bc065017..0003e8bcbf0 100644 --- a/superset-frontend/src/dashboard/util/getSelectedChartIdForFilterScopeTree.js +++ b/superset-frontend/src/dashboard/util/getSelectedChartIdForFilterScopeTree.ts @@ -18,10 +18,15 @@ */ import { getChartIdAndColumnFromFilterKey } from './getDashboardFilterKey'; +interface GetSelectedChartIdParams { + activeFilterField?: string | null; + checkedFilterFields: string[]; +} + export default function getSelectedChartIdForFilterScopeTree({ activeFilterField, checkedFilterFields, -}) { +}: GetSelectedChartIdParams): number | null { // this function returns chart id based on current filter scope selector local state: // 1. if in single-edit mode, return the chart id for selected filter field. // 2. if in multi-edit mode, if all filter fields are from same chart id, diff --git a/superset-frontend/src/dashboard/util/logging/findNonTabChildChartIds.js b/superset-frontend/src/dashboard/util/logging/findNonTabChildChartIds.ts similarity index 75% rename from superset-frontend/src/dashboard/util/logging/findNonTabChildChartIds.js rename to superset-frontend/src/dashboard/util/logging/findNonTabChildChartIds.ts index 8785ebbfe1a..dd13a21df8a 100644 --- a/superset-frontend/src/dashboard/util/logging/findNonTabChildChartIds.js +++ b/superset-frontend/src/dashboard/util/logging/findNonTabChildChartIds.ts @@ -16,14 +16,23 @@ * specific language governing permissions and limitations * under the License. */ +import { Layout, LayoutItem } from 'src/dashboard/types'; import { TABS_TYPE, CHART_TYPE } from '../componentTypes'; +interface FindNonTabChildChartIdsParams { + id: string; + layout: Layout; +} + // This function traverses the layout from the passed id, returning an array // of any child chartIds NOT nested within a Tabs component. These helps us identify // if the charts at a given "Tabs" level are loaded -function findNonTabChildChartIds({ id, layout }) { - const chartIds = []; - function recurseFromNode(node) { +function findNonTabChildChartIds({ + id, + layout, +}: FindNonTabChildChartIdsParams): number[] { + const chartIds: number[] = []; + function recurseFromNode(node: LayoutItem | undefined): void { if (node && node.type === CHART_TYPE) { if (node.meta && node.meta.chartId) { chartIds.push(node.meta.chartId); @@ -49,10 +58,13 @@ function findNonTabChildChartIds({ id, layout }) { } // This method is called frequently, so cache results -let cachedLayout; -let cachedIdsLookup = {}; -export default function findNonTabChildChartIdsWithCache({ id, layout }) { - if (cachedLayout === layout && cachedIdsLookup[id]) { +let cachedLayout: Layout | undefined; +let cachedIdsLookup: Record = {}; +export default function findNonTabChildChartIdsWithCache({ + id, + layout, +}: FindNonTabChildChartIdsParams): number[] { + if (cachedLayout === layout && id in cachedIdsLookup) { return cachedIdsLookup[id]; } if (layout !== cachedLayout) { diff --git a/superset-frontend/src/dashboard/util/logging/findTopLevelComponentIds.js b/superset-frontend/src/dashboard/util/logging/findTopLevelComponentIds.ts similarity index 77% rename from superset-frontend/src/dashboard/util/logging/findTopLevelComponentIds.js rename to superset-frontend/src/dashboard/util/logging/findTopLevelComponentIds.ts index 98061d1aae5..d1ccb71067c 100644 --- a/superset-frontend/src/dashboard/util/logging/findTopLevelComponentIds.js +++ b/superset-frontend/src/dashboard/util/logging/findTopLevelComponentIds.ts @@ -16,14 +16,33 @@ * specific language governing permissions and limitations * under the License. */ +import { Layout, LayoutItem } from 'src/dashboard/types'; import { TAB_TYPE, DASHBOARD_GRID_TYPE } from '../componentTypes'; import { DASHBOARD_ROOT_ID } from '../constants'; import findNonTabChildChartIds from './findNonTabChildChartIds'; +interface TopLevelNode { + id: string; + type: string; + parent_type: string | null; + parent_id: string | null; + index: number | null; + depth: number; + slice_ids: number[]; +} + +interface RecurseParams { + node: LayoutItem | undefined; + index?: number | null; + depth: number; + parentType?: string | null; + parentId?: string | null; +} + // This function traverses the layout to identify top grid + tab level components // for which we track load times -function findTopLevelComponentIds(layout) { - const topLevelNodes = []; +function findTopLevelComponentIds(layout: Layout): TopLevelNode[] { + const topLevelNodes: TopLevelNode[] = []; function recurseFromNode({ node, @@ -31,7 +50,7 @@ function findTopLevelComponentIds(layout) { depth, parentType = null, parentId = null, - }) { + }: RecurseParams): void { if (!node) return; let nextParentType = parentType; @@ -79,9 +98,11 @@ function findTopLevelComponentIds(layout) { } // This method is called frequently, so cache results -let cachedLayout; -let cachedTopLevelNodes; -export default function findTopLevelComponentIdsWithCache(layout) { +let cachedLayout: Layout | undefined; +let cachedTopLevelNodes: TopLevelNode[] = []; +export default function findTopLevelComponentIdsWithCache( + layout: Layout, +): TopLevelNode[] { if (layout === cachedLayout) { return cachedTopLevelNodes; } diff --git a/superset-frontend/src/dashboard/util/logging/getLoadStatsPerTopLevelComponent.js b/superset-frontend/src/dashboard/util/logging/getLoadStatsPerTopLevelComponent.ts similarity index 71% rename from superset-frontend/src/dashboard/util/logging/getLoadStatsPerTopLevelComponent.js rename to superset-frontend/src/dashboard/util/logging/getLoadStatsPerTopLevelComponent.ts index 3f771af912e..f5bb6451067 100644 --- a/superset-frontend/src/dashboard/util/logging/getLoadStatsPerTopLevelComponent.js +++ b/superset-frontend/src/dashboard/util/logging/getLoadStatsPerTopLevelComponent.ts @@ -16,16 +16,31 @@ * specific language governing permissions and limitations * under the License. */ +import { ChartState } from 'src/explore/types'; +import { Layout } from 'src/dashboard/types'; import findTopLevelComponentIds from './findTopLevelComponentIds'; import childChartsDidLoad from './childChartsDidLoad'; +interface GetLoadStatsParams { + layout: Layout; + chartQueries: Record>; +} + +interface LoadStats { + didLoad: boolean; + id: string; + minQueryStartTime: number | null; + [key: string]: unknown; +} + export default function getLoadStatsPerTopLevelComponent({ layout, chartQueries, -}) { +}: GetLoadStatsParams): Record { const topLevelComponents = findTopLevelComponentIds(layout); - const stats = {}; - topLevelComponents.forEach(({ id, ...restStats }) => { + const stats: Record = {}; + topLevelComponents.forEach(topLevelComponent => { + const { id, ...restStats } = topLevelComponent; const { didLoad, minQueryStartTime } = childChartsDidLoad({ id, layout, diff --git a/superset-frontend/src/dashboard/util/updateComponentParentsList.js b/superset-frontend/src/dashboard/util/updateComponentParentsList.ts similarity index 87% rename from superset-frontend/src/dashboard/util/updateComponentParentsList.js rename to superset-frontend/src/dashboard/util/updateComponentParentsList.ts index 5a3d7d78f65..72000f1ae3e 100644 --- a/superset-frontend/src/dashboard/util/updateComponentParentsList.js +++ b/superset-frontend/src/dashboard/util/updateComponentParentsList.ts @@ -18,10 +18,22 @@ */ import { logging } from '@superset-ui/core'; +interface LayoutComponent { + id: string; + parents?: string[]; + children?: string[]; + [key: string]: unknown; +} + +interface UpdateComponentParentsListParams { + currentComponent?: LayoutComponent | null; + layout?: Record; +} + export default function updateComponentParentsList({ currentComponent, layout = {}, -}) { +}: UpdateComponentParentsListParams): void { if (currentComponent && layout) { if (layout[currentComponent.id]) { const parentsList = Array.isArray(currentComponent.parents) diff --git a/superset-frontend/src/explore/reducers/saveModalReducer.js b/superset-frontend/src/explore/reducers/saveModalReducer.ts similarity index 71% rename from superset-frontend/src/explore/reducers/saveModalReducer.js rename to superset-frontend/src/explore/reducers/saveModalReducer.ts index 572cabde39d..c4c619e7b46 100644 --- a/superset-frontend/src/explore/reducers/saveModalReducer.js +++ b/superset-frontend/src/explore/reducers/saveModalReducer.ts @@ -20,8 +20,26 @@ import * as actions from '../actions/saveModalActions'; import { HYDRATE_EXPLORE } from '../actions/hydrateExplore'; -export default function saveModalReducer(state = {}, action) { - const actionHandlers = { +interface SaveModalState { + isVisible?: boolean; + dashboards?: unknown[]; + saveModalAlert?: string; + data?: unknown; +} + +interface SaveModalAction { + type: string; + isVisible?: boolean; + choices?: unknown[]; + userId?: string; + data?: unknown; +} + +export default function saveModalReducer( + state: SaveModalState = {}, + action: SaveModalAction, +): SaveModalState { + const actionHandlers: Record SaveModalState> = { [actions.SET_SAVE_CHART_MODAL_VISIBILITY]() { return { ...state, isVisible: action.isVisible }; }, @@ -37,11 +55,12 @@ export default function saveModalReducer(state = {}, action) { [actions.SAVE_SLICE_FAILED]() { return { ...state, saveModalAlert: 'Failed to save slice' }; }, - [actions.SAVE_SLICE_SUCCESS](data) { - return { ...state, data }; + [actions.SAVE_SLICE_SUCCESS]() { + return { ...state, data: action.data }; }, [HYDRATE_EXPLORE]() { - return { ...action.data.saveModal }; + const payload = action.data as { saveModal?: SaveModalState } | undefined; + return { ...payload?.saveModal }; }, }; diff --git a/superset-frontend/src/utils/getControlsForVizType.js b/superset-frontend/src/utils/getControlsForVizType.js deleted file mode 100644 index 8771d91dc7b..00000000000 --- a/superset-frontend/src/utils/getControlsForVizType.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * 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 memoizeOne from 'memoize-one'; -import { isControlPanelSectionConfig } from '@superset-ui/chart-controls'; -import { getChartControlPanelRegistry } from '@superset-ui/core'; -import { controls } from '../explore/controls'; - -const memoizedControls = memoizeOne((vizType, controlPanel) => { - const controlsMap = {}; - (controlPanel?.controlPanelSections || []) - .filter(isControlPanelSectionConfig) - .forEach(section => { - section.controlSetRows.forEach(row => { - row.forEach(control => { - if (!control) return; - if (typeof control === 'string') { - // For now, we have to look in controls.jsx to get the config for some controls. - // Once everything is migrated out, delete this if statement. - controlsMap[control] = controls[control]; - } else if (control.name && control.config) { - // condition needed because there are elements, e.g.
in some control configs (I'm looking at you, FilterBox!) - controlsMap[control.name] = control.config; - } - }); - }); - }); - return controlsMap; -}); - -const getControlsForVizType = vizType => { - const controlPanel = getChartControlPanelRegistry().get(vizType); - return memoizedControls(vizType, controlPanel); -}; - -export default getControlsForVizType; diff --git a/superset-frontend/src/utils/reducerUtils.js b/superset-frontend/src/utils/reducerUtils.js deleted file mode 100644 index 199c8aa292d..00000000000 --- a/superset-frontend/src/utils/reducerUtils.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * 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 { nanoid } from 'nanoid'; - -export function addToObject(state, arrKey, obj) { - const newObject = { ...state[arrKey] }; - const copiedObject = { ...obj }; - - if (!copiedObject.id) { - copiedObject.id = nanoid(); - } - newObject[copiedObject.id] = copiedObject; - return { ...state, [arrKey]: newObject }; -} - -export function alterInObject(state, arrKey, obj, alterations) { - const newObject = { ...state[arrKey] }; - newObject[obj.id] = { ...newObject[obj.id], ...alterations }; - return { ...state, [arrKey]: newObject }; -} - -export function alterInArr(state, arrKey, obj, alterations) { - // Finds an item in an array in the state and replaces it with a - // new object with an altered property - const idKey = 'id'; - const newArr = []; - state[arrKey].forEach(arrItem => { - if (obj[idKey] === arrItem[idKey]) { - newArr.push({ ...arrItem, ...alterations }); - } else { - newArr.push(arrItem); - } - }); - return { ...state, [arrKey]: newArr }; -} - -export function removeFromArr(state, arrKey, obj, idKey = 'id') { - const newArr = []; - state[arrKey].forEach(arrItem => { - if (!(obj[idKey] === arrItem[idKey])) { - newArr.push(arrItem); - } - }); - return { ...state, [arrKey]: newArr }; -} - -export function addToArr(state, arrKey, obj) { - const newObj = { ...obj }; - if (!newObj.id) { - newObj.id = nanoid(); - } - const newState = {}; - newState[arrKey] = [...state[arrKey], newObj]; - return { ...state, ...newState }; -}