diff --git a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx index 83524d102a7..d18dd193be8 100644 --- a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx +++ b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx @@ -700,6 +700,7 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => { title={props.errorMessage} > diff --git a/superset-frontend/src/explore/components/ExploreViewContainer/ExploreViewContainer.test.tsx b/superset-frontend/src/explore/components/ExploreViewContainer/ExploreViewContainer.test.tsx index aecb3c775da..8670ac27c4b 100644 --- a/superset-frontend/src/explore/components/ExploreViewContainer/ExploreViewContainer.test.tsx +++ b/superset-frontend/src/explore/components/ExploreViewContainer/ExploreViewContainer.test.tsx @@ -299,3 +299,170 @@ test('does omit hiddenFormData when query_mode is not enabled', async () => { expect(formData[key]).toBeUndefined(); }); }); + +// Component tests for the errorMessage behavior +test('does not show error indicator when no controls have validation errors', async () => { + getChartControlPanelRegistry().registerValue('table', { + controlPanelSections: [], + }); + const customState = { + ...reduxState, + explore: { + ...reduxState.explore, + controls: { + ...reduxState.explore.controls, + metric: { value: 'count', label: 'Metric' }, + groupby: { value: ['category'], label: 'Group by' }, + }, + }, + }; + + renderWithRouter({ initialState: customState }); + + await waitFor(() => { + const errorIndicator = screen.queryByTestId('query-error-tooltip-trigger'); + expect(errorIndicator).not.toBeInTheDocument(); + }); +}); + +test('shows error indicator when controls have validation errors', async () => { + getChartControlPanelRegistry().registerValue('table', { + controlPanelSections: [], + }); + const customState = { + ...reduxState, + explore: { + ...reduxState.explore, + controls: { + ...reduxState.explore.controls, + metric: { + value: '', + label: 'Metric', + validationErrors: ['Metric is required'], + }, + }, + }, + }; + + renderWithRouter({ initialState: customState }); + + const errorIndicator = await screen.findByTestId( + 'query-error-tooltip-trigger', + ); + + userEvent.hover(errorIndicator); + + const tooltip = await screen.findByRole('tooltip'); + expect(tooltip).toBeInTheDocument(); + + const errorMessage = await screen.findByText(/Metric is required/); + expect(errorMessage).toBeInTheDocument(); +}); + +test('shows error indicator for multiple controls with validation errors', async () => { + getChartControlPanelRegistry().registerValue('table', { + controlPanelSections: [], + }); + const customState = { + ...reduxState, + explore: { + ...reduxState.explore, + controls: { + ...reduxState.explore.controls, + metric: { + value: '', + label: 'Metric', + validationErrors: ['Field is required'], + }, + groupby: { + value: [], + label: 'Group by', + validationErrors: ['Field is required'], + }, + }, + }, + }; + + renderWithRouter({ initialState: customState }); + + const errorIndicator = await screen.findByTestId( + 'query-error-tooltip-trigger', + ); + + userEvent.hover(errorIndicator); + + const tooltip = await screen.findByRole('tooltip'); + expect(tooltip).toBeInTheDocument(); + + expect(await screen.findByText(/Field is required/)).toBeInTheDocument(); +}); + +test('shows error indicator for control with multiple validation errors', async () => { + getChartControlPanelRegistry().registerValue('table', { + controlPanelSections: [], + }); + const customState = { + ...reduxState, + explore: { + ...reduxState.explore, + controls: { + ...reduxState.explore.controls, + metric: { + value: '', + label: 'Metric', + validationErrors: ['Field is required', 'Invalid format'], + }, + }, + }, + }; + + renderWithRouter({ initialState: customState }); + + const errorIndicator = await screen.findByTestId( + 'query-error-tooltip-trigger', + ); + + userEvent.hover(errorIndicator); + + const tooltip = await screen.findByRole('tooltip'); + expect(tooltip).toBeInTheDocument(); + + expect(await screen.findByText(/Field is required/)).toBeInTheDocument(); + expect(await screen.findByText(/Invalid format/)).toBeInTheDocument(); +}); + +test('shows error indicator with function labels', async () => { + getChartControlPanelRegistry().registerValue('table', { + controlPanelSections: [], + }); + // Ensure the explore state passed to the label function contains the expected property + const customState = { + ...reduxState, + explore: { + ...reduxState.explore, + someState: 'test', + controls: { + ...reduxState.explore.controls, + metric: { + value: '', + label: (exploreState: { someState: string }) => + `Dynamic Metric (${exploreState.someState})`, + validationErrors: ['Metric is required'], + }, + }, + }, + }; + + renderWithRouter({ initialState: customState }); + + const errorIndicator = await screen.findByTestId( + 'query-error-tooltip-trigger', + ); + + userEvent.hover(errorIndicator); + + const tooltip = await screen.findByRole('tooltip'); + expect(tooltip).toBeInTheDocument(); + + expect(await screen.findByText(/Metric is required/)).toBeInTheDocument(); +}); diff --git a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx index 13b67c5b997..40caa9decc9 100644 --- a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx +++ b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx @@ -541,7 +541,11 @@ function ExploreViewContainer(props) { .map(message => { const matchingLabels = controlsWithErrors .filter(control => control.validationErrors?.includes(message)) - .map(control => control.label); + .map(control => + typeof control.label === 'function' + ? control.label(props.exploreState) + : control.label, + ); return [matchingLabels, message]; }) .map(([labels, message]) => ( diff --git a/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.tsx b/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.tsx index ad80f98205c..b370333d838 100644 --- a/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.tsx +++ b/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.tsx @@ -87,17 +87,6 @@ export const useHeaderReportMenuItems = ({ const resourceTypeReports = reportsState[resourceType] || {}; const reportData = resourceTypeReports[resourceId]; - // Debug logging to understand what's happening - console.log('Report selector called:', { - resourceId, - resourceType, - reportsState: Object.keys(reportsState), - resourceTypeReports: Object.keys(resourceTypeReports), - reportData: reportData - ? { id: reportData.id, name: reportData.name } - : null, - }); - return reportData || null; });