+ }: MockSuperChartProps & { isRefreshing?: boolean }) => (
+
{JSON.stringify(postTransformProps(props).formData)}
),
@@ -373,3 +377,33 @@ test('should detect nested matrixify property changes', () => {
JSON.stringify(updatedProps.formData),
);
});
+
+test('renders chart during loading when suppressLoadingSpinner has valid data', () => {
+ const props = {
+ ...requiredProps,
+ chartStatus: 'loading' as const,
+ chartAlert: undefined,
+ suppressLoadingSpinner: true,
+ queriesResponse: [{ data: [{ value: 1 }] }],
+ };
+
+ const { getByTestId } = render(
);
+ expect(getByTestId('mock-super-chart')).toBeInTheDocument();
+ expect(getByTestId('mock-super-chart')).toHaveAttribute(
+ 'data-is-refreshing',
+ 'true',
+ );
+});
+
+test('does not render chart during loading when last data has errors', () => {
+ const props = {
+ ...requiredProps,
+ chartStatus: 'loading' as const,
+ chartAlert: undefined,
+ suppressLoadingSpinner: true,
+ queriesResponse: [{ error: 'bad' }],
+ };
+
+ const { queryByTestId } = render(
);
+ expect(queryByTestId('mock-super-chart')).not.toBeInTheDocument();
+});
diff --git a/superset-frontend/src/components/Chart/ChartRenderer.tsx b/superset-frontend/src/components/Chart/ChartRenderer.tsx
index 2b6016c1532f..995df527d64e 100644
--- a/superset-frontend/src/components/Chart/ChartRenderer.tsx
+++ b/superset-frontend/src/components/Chart/ChartRenderer.tsx
@@ -134,6 +134,7 @@ export interface ChartRendererProps {
emitCrossFilters?: boolean;
cacheBusterProp?: string;
onChartStateChange?: (chartState: AgGridChartState) => void;
+ suppressLoadingSpinner?: boolean;
}
// State interface
@@ -411,11 +412,20 @@ class ChartRenderer extends Component
{
render(): ReactNode {
const { chartAlert, chartStatus, chartId, emitCrossFilters } = this.props;
- // Skip chart rendering
- if (chartStatus === 'loading' || !!chartAlert || chartStatus === null) {
+ const hasAnyErrors = this.props.queriesResponse?.some(item => item?.error);
+ const hasValidPreviousData =
+ (this.props.queriesResponse?.length ?? 0) > 0 && !hasAnyErrors;
+
+ if (!!chartAlert || chartStatus === null) {
return null;
}
+ if (chartStatus === 'loading') {
+ if (!this.props.suppressLoadingSpinner || !hasValidPreviousData) {
+ return null;
+ }
+ }
+
this.renderStartTime = Logger.getTimestamp();
const {
@@ -548,6 +558,7 @@ class ChartRenderer extends Component {
legendState={this.state.legendState}
enableNoResults={bypassNoResult}
legendIndex={this.state.legendIndex}
+ isRefreshing={this.props.suppressLoadingSpinner}
{...drillToDetailProps}
/>
diff --git a/superset-frontend/src/components/Chart/chartAction.ts b/superset-frontend/src/components/Chart/chartAction.ts
index 63fa72b5ad20..2ed8bba83b77 100644
--- a/superset-frontend/src/components/Chart/chartAction.ts
+++ b/superset-frontend/src/components/Chart/chartAction.ts
@@ -129,6 +129,7 @@ export interface ChartUpdateSucceededAction {
export interface ChartUpdateStoppedAction {
type: typeof CHART_UPDATE_STOPPED;
key: string | number;
+ queryController?: AbortController;
}
export interface ChartUpdateFailedAction {
@@ -327,8 +328,9 @@ export function chartUpdateSucceeded(
export function chartUpdateStopped(
key: string | number,
+ queryController?: AbortController,
): ChartUpdateStoppedAction {
- return { type: CHART_UPDATE_STOPPED, key };
+ return { type: CHART_UPDATE_STOPPED, key, queryController };
}
export function chartUpdateFailed(
@@ -819,7 +821,9 @@ export function exploreJSON(
response?.name === 'AbortError' || response?.statusText === 'abort';
if (isAbort) {
// Abort is expected: filters changed, chart unmounted, etc.
- return dispatch(chartUpdateStopped(key as string | number));
+ return dispatch(
+ chartUpdateStopped(key as string | number, controller),
+ );
}
if (isFeatureEnabled(FeatureFlag.GlobalAsyncQueries)) {
@@ -945,9 +949,15 @@ export function refreshChart(
chartKey: string | number,
force: boolean,
dashboardId?: number,
-): ChartThunkAction {
- return (dispatch: ChartThunkDispatch, getState: () => RootState): void => {
+): ChartThunkAction