Skip to content

Commit 4527e25

Browse files
committed
fix(dashboard): refresh tabs as they load when dashboard is refreshed
1 parent cb88d88 commit 4527e25

File tree

4 files changed

+138
-3
lines changed

4 files changed

+138
-3
lines changed

superset-frontend/src/dashboard/components/gridComponents/Tab/Tab.jsx

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
import { Fragment, useCallback, memo } from 'react';
19+
import { Fragment, useCallback, memo, useEffect } from 'react';
2020
import PropTypes from 'prop-types';
2121
import classNames from 'classnames';
2222
import { useDispatch, useSelector } from 'react-redux';
2323
import { styled, t } from '@superset-ui/core';
2424

2525
import { EditableTitle, EmptyState } from '@superset-ui/core/components';
26-
import { setEditMode } from 'src/dashboard/actions/dashboardState';
26+
import { setEditMode, onRefresh } from 'src/dashboard/actions/dashboardState';
27+
import getChartIdsFromComponent from 'src/dashboard/util/getChartIdsFromComponent';
2728
import DashboardComponent from 'src/dashboard/containers/DashboardComponent';
2829
import AnchorLink from 'src/dashboard/components/AnchorLink';
2930
import {
@@ -113,6 +114,49 @@ const renderDraggableContent = dropProps =>
113114
const Tab = props => {
114115
const dispatch = useDispatch();
115116
const canEdit = useSelector(state => state.dashboardInfo.dash_edit_perm);
117+
const dashboardLayout = useSelector(state => state.dashboardLayout.present);
118+
const lastRefreshTime = useSelector(
119+
state => state.dashboardState.lastRefreshTime,
120+
);
121+
const tabActivationTimes = useSelector(
122+
state => state.dashboardState.tabActivationTimes || {},
123+
);
124+
const dashboardInfo = useSelector(state => state.dashboardInfo);
125+
126+
// Check if tab needs refresh when it becomes visible
127+
useEffect(() => {
128+
if (props.renderType === RENDER_TAB_CONTENT && props.isComponentVisible) {
129+
const tabId = props.id;
130+
const tabActivationTime = tabActivationTimes[tabId] || 0;
131+
132+
// If a refresh occurred while this tab was inactive,
133+
// refresh the charts in this tab now that it's visible
134+
if (
135+
lastRefreshTime &&
136+
tabActivationTime &&
137+
lastRefreshTime > tabActivationTime
138+
) {
139+
const chartIds = getChartIdsFromComponent(tabId, dashboardLayout);
140+
if (chartIds.length > 0) {
141+
// Small delay to ensure charts are fully mounted
142+
setTimeout(() => {
143+
// Refresh charts in this tab
144+
dispatch(onRefresh(chartIds, true, 0, dashboardInfo.id));
145+
}, 100);
146+
}
147+
}
148+
}
149+
}, [
150+
props.isComponentVisible,
151+
props.renderType,
152+
props.id,
153+
lastRefreshTime,
154+
tabActivationTimes,
155+
dashboardLayout,
156+
dashboardInfo.id,
157+
dispatch,
158+
]);
159+
116160
const handleChangeTab = useCallback(
117161
({ pathToTabIndex }) => {
118162
props.setDirectPathToChild(pathToTabIndex);

superset-frontend/src/dashboard/reducers/dashboardState.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,17 @@ import { HYDRATE_DASHBOARD } from '../actions/hydrate';
5656
export default function dashboardStateReducer(state = {}, action) {
5757
const actionHandlers = {
5858
[HYDRATE_DASHBOARD]() {
59-
return { ...state, ...action.data.dashboardState };
59+
const hydratedState = { ...state, ...action.data.dashboardState };
60+
// Initialize tab activation times for initially active tabs
61+
if (hydratedState.activeTabs && hydratedState.activeTabs.length > 0) {
62+
const now = Date.now();
63+
hydratedState.tabActivationTimes =
64+
hydratedState.tabActivationTimes || {};
65+
hydratedState.activeTabs.forEach(tabId => {
66+
hydratedState.tabActivationTimes[tabId] = now;
67+
});
68+
}
69+
return hydratedState;
6070
},
6171
[ADD_SLICE]() {
6272
const updatedSliceIds = new Set(state.sliceIds);
@@ -178,6 +188,7 @@ export default function dashboardStateReducer(state = {}, action) {
178188
return {
179189
...state,
180190
isRefreshing: true,
191+
lastRefreshTime: Date.now(),
181192
};
182193
},
183194
[ON_FILTERS_REFRESH]() {
@@ -213,10 +224,17 @@ export default function dashboardStateReducer(state = {}, action) {
213224
.difference(new Set(action.activeTabs))
214225
.union(new Set(action.inactiveTabs));
215226

227+
// Track when each tab was last activated
228+
const tabActivationTimes = { ...(state.tabActivationTimes || {}) };
229+
action.activeTabs.forEach(tabId => {
230+
tabActivationTimes[tabId] = Date.now();
231+
});
232+
216233
return {
217234
...state,
218235
inactiveTabs: Array.from(newInactiveTabs),
219236
activeTabs: Array.from(newActiveTabs.union(new Set(action.activeTabs))),
237+
tabActivationTimes,
220238
};
221239
},
222240
[SET_ACTIVE_TABS]() {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
import { useSelector } from 'react-redux';
20+
import { useMemo } from 'react';
21+
import { RootState } from 'src/dashboard/types';
22+
import getChartIdsFromLayout from '../getChartIdsFromLayout';
23+
24+
export const useAllChartIds = () => {
25+
const layout = useSelector(
26+
(state: RootState) => state.dashboardLayout.present,
27+
);
28+
return useMemo(() => getChartIdsFromLayout(layout), [layout]);
29+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
import { CHART_TYPE } from './componentTypes';
20+
import type { DashboardLayout } from '../types';
21+
22+
export default function getChartIdsFromComponent(
23+
componentId: string,
24+
layout: DashboardLayout,
25+
): number[] {
26+
const chartIds: number[] = [];
27+
const component = layout[componentId];
28+
29+
if (!component) return chartIds;
30+
31+
// If this component is a chart, add its ID
32+
if (component.type === CHART_TYPE && component.meta?.chartId) {
33+
chartIds.push(component.meta.chartId);
34+
}
35+
36+
// Recursively check children
37+
if (component.children) {
38+
component.children.forEach((childId: string) => {
39+
chartIds.push(...getChartIdsFromComponent(childId, layout));
40+
});
41+
}
42+
43+
return chartIds;
44+
}

0 commit comments

Comments
 (0)