This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new e357014559b fix(dashboard): initialize label colors before mounting
charts (#44557)
e357014559b is described below
commit e357014559b8b353e8240d971b126d31b9911f31
Author: Deepak Jain (DJ) <[email protected]>
AuthorDate: Thu Sep 24 13:30:03 2026 -0700
fix(dashboard): initialize label colors before mounting charts (#44557)
Signed-off-by: Deepak Jain <[email protected]>
---
.../DashboardBuilder/DashboardContainer.test.tsx | 145 ++++++++++++++++++++-
.../DashboardBuilder/DashboardContainer.tsx | 12 +-
2 files changed, 150 insertions(+), 7 deletions(-)
diff --git
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.test.tsx
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.test.tsx
index d941b9f2324..caa490512d0 100644
---
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.test.tsx
+++
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.test.tsx
@@ -16,12 +16,20 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { render, screen, waitFor } from 'spec/helpers/testing-library';
+import { StrictMode, useState } from 'react';
+import { act, render, screen, waitFor } from 'spec/helpers/testing-library';
import fetchMock from 'fetch-mock';
import { storeWithState } from 'spec/fixtures/mockStore';
import mockState from 'spec/fixtures/mockState';
import { sliceId } from 'spec/fixtures/mockChartQueries';
-import { ChartCustomizationType, NativeFilterType } from '@superset-ui/core';
+import {
+ ChartCustomizationType,
+ NativeFilterType,
+ CategoricalColorNamespace,
+} from '@superset-ui/core';
+import DashboardGrid from 'src/dashboard/containers/DashboardGrid';
+import { applyDashboardLabelsColorOnLoad } from
'src/dashboard/actions/dashboardState';
+import { dashboardInfoChanged } from 'src/dashboard/actions/dashboardInfo';
import { CHART_TYPE } from '../../util/componentTypes';
import DashboardContainer from './DashboardContainer';
import * as nativeFiltersActions from '../../actions/nativeFilters';
@@ -37,7 +45,7 @@ jest.mock('@visx/responsive', () => ({
jest.mock('src/dashboard/containers/DashboardGrid', () => ({
__esModule: true,
- default: () => <div data-test="mock-dashboard-grid" />,
+ default: jest.fn(() => <div data-test="mock-dashboard-grid" />),
}));
// DashboardContainer dispatches these on mount, so unit tests stub them.
@@ -154,6 +162,12 @@ beforeEach(() => {
afterEach(() => {
setInScopeStatusMock.mockRestore();
+ jest
+ .mocked(applyDashboardLabelsColorOnLoad)
+ .mockImplementation(() => async () => {});
+ jest
+ .mocked(DashboardGrid)
+ .mockImplementation(() => <div data-test="mock-dashboard-grid" />);
});
test('calculates chartsInScope correctly for filters', async () => {
@@ -853,3 +867,128 @@ test('does not dispatch setInScopeStatusOfCustomizations
when chart_customizatio
spy.mockRestore();
}
});
+
+test.each([false, true])(
+ 'applies custom label colors before a cached chart consumes its first color
scale (StrictMode=%s)',
+ strict => {
+ const namespace = 'initial-label-colors';
+ CategoricalColorNamespace.getNamespace(namespace).resetColors();
+ jest
+ .mocked(applyDashboardLabelsColorOnLoad)
+ .mockImplementation(
+ jest.requireActual('src/dashboard/actions/dashboardState')
+ .applyDashboardLabelsColorOnLoad,
+ );
+ jest.mocked(DashboardGrid).mockImplementation(function CachedChart() {
+ const [color] = useState(() =>
+ CategoricalColorNamespace.getScale(undefined, namespace).getColor(
+ '20_Passed',
+ ),
+ );
+ return <div data-test="cached-chart" data-color={color} />;
+ });
+
+ const initialState = createTestState({
+ dashboardInfo: {
+ ...mockState.dashboardInfo,
+ metadata: {
+ ...mockState.dashboardInfo.metadata,
+ color_namespace: namespace,
+ color_scheme: '',
+ label_colors: { '20_Passed': '#008000' },
+ },
+ },
+ });
+
+ const { unmount } = render(
+ strict ? (
+ <StrictMode>
+ <DashboardContainer />
+ </StrictMode>
+ ) : (
+ <DashboardContainer />
+ ),
+ { useRedux: true, store: storeWithState(initialState) },
+ );
+
+ expect(screen.getByTestId('cached-chart')).toHaveAttribute(
+ 'data-color',
+ '#008000',
+ );
+ unmount();
+ CategoricalColorNamespace.getNamespace(namespace).resetColors();
+ },
+);
+
+test.each([false, true])(
+ 'initializes the next dashboard colors before mounting its cached charts
(new namespace=%s)',
+ newNamespace => {
+ const namespace = 'navigation-label-colors';
+ let chartNamespace = namespace;
+ CategoricalColorNamespace.getNamespace(namespace).resetColors();
+ jest
+ .mocked(applyDashboardLabelsColorOnLoad)
+ .mockImplementation(
+ jest.requireActual('src/dashboard/actions/dashboardState')
+ .applyDashboardLabelsColorOnLoad,
+ );
+ jest.mocked(DashboardGrid).mockImplementation(function CachedChart() {
+ const [color] = useState(() =>
+ CategoricalColorNamespace.getScale(undefined, chartNamespace).getColor(
+ '20_Passed',
+ ),
+ );
+ return <div data-test="cached-chart" data-color={color} />;
+ });
+ const metadata = {
+ ...mockState.dashboardInfo.metadata,
+ color_namespace: namespace,
+ color_scheme: '',
+ label_colors: { '20_Passed': '#008000' },
+ };
+ const { store, unmount } = setupWithStore({
+ dashboardInfo: { ...mockState.dashboardInfo, metadata },
+ });
+ expect(screen.getByTestId('cached-chart')).toHaveAttribute(
+ 'data-color',
+ '#008000',
+ );
+
+ chartNamespace = newNamespace ? 'next-dashboard-colors' : namespace;
+ act(() => {
+ store.dispatch(
+ dashboardInfoChanged({
+ id: mockState.dashboardInfo.id + 1,
+ metadata: {
+ ...store.getState().dashboardInfo.metadata,
+ ...metadata,
+ color_namespace: chartNamespace,
+ label_colors: { '20_Passed': '#ff0000' },
+ },
+ }),
+ );
+ });
+
+ expect(screen.getByTestId('cached-chart')).toHaveAttribute(
+ 'data-color',
+ '#ff0000',
+ );
+ unmount();
+ CategoricalColorNamespace.getNamespace(namespace).resetColors();
+ CategoricalColorNamespace.getNamespace(chartNamespace).resetColors();
+ },
+);
+
+test('waits for dashboard hydration before mounting cached charts', () => {
+ jest.mocked(applyDashboardLabelsColorOnLoad).mockClear();
+ const { store } = setupWithStore({ dashboardInfo: {} });
+ expect(screen.queryByTestId('mock-dashboard-grid')).not.toBeInTheDocument();
+ expect(applyDashboardLabelsColorOnLoad).not.toHaveBeenCalled();
+
+ act(() => {
+ store.dispatch(dashboardInfoChanged({ id: mockState.dashboardInfo.id }));
+ });
+
+ expect(applyDashboardLabelsColorOnLoad).toHaveBeenCalled();
+ expect(screen.getByTestId('mock-dashboard-grid')).toBeInTheDocument();
+});
diff --git
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
index 97f3549c160..335c2d391b0 100644
---
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
+++
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
@@ -164,8 +164,10 @@ const DashboardContainer: FC<DashboardContainerProps> = ({
topLevelTabs }) => {
const renderedChartIds = useRenderedChartIds();
- const [dashboardLabelsColorInitiated, setDashboardLabelsColorInitiated] =
- useState(false);
+ const [colorInitializedDashboardId, setColorInitializedDashboardId] =
+ useState<number | null>(null);
+ const dashboardLabelsColorInitiated =
+ colorInitializedDashboardId === dashboardInfo?.id;
const prevRenderedChartIds = useRef<number[]>([]);
const prevTabIndexRef = useRef<number>();
const prevFilterScopesRef = useRef<FilterScopeData[]>([]);
@@ -304,7 +306,7 @@ const DashboardContainer: FC<DashboardContainerProps> = ({
topLevelTabs }) => {
if (dashboardInfo?.id && !dashboardLabelsColorInitiated) {
dispatch(applyDashboardLabelsColorOnLoad(dashboardInfo.metadata));
// apply labels color as dictated by stored metadata (if any)
- setDashboardLabelsColorInitiated(true);
+ setColorInitializedDashboardId(dashboardInfo.id);
}
return () => {
@@ -379,7 +381,9 @@ const DashboardContainer: FC<DashboardContainerProps> = ({
topLevelTabs }) => {
return (
<div className="grid-container" data-test="grid-container" ref={parentRef}>
- {renderParentSizeChildren({ width })}
+ {/* Defer the grid until hydration and color initialization complete,
+ before cached charts consume their color scales on first render. */}
+ {dashboardLabelsColorInitiated && renderParentSizeChildren({ width })}
</div>
);
};