Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,45 @@ assists people when migrating to a new version.

## Next

### Customizable Empty State Messages for BigNumber Charts

BigNumber charts now support customizable empty state messages at the dashboard level. This allows dashboard owners to provide context-specific messages when charts display no data or no results.

#### New Features
- Dashboard-level configuration for BigNumber chart empty states
- Four customizable message fields:
- `no_data_message`: Primary message when query returns no data
- `no_data_subtitle`: Additional context for no data state
- `no_results_message`: Primary message when filters yield no results
- `no_results_subtitle`: Additional context for no results state
- Available in Dashboard Properties modal under "Empty State Configuration" section
- Applies to all BigNumber chart variants (Total, With Trendline, Period over Period)
- Internationalization (i18n) support with fallback to default translated messages

#### Usage
1. Edit a dashboard containing BigNumber charts
2. Open Dashboard Properties (three dots menu → Edit properties)
3. Navigate to "Empty State Configuration" section
4. Enter custom messages for each empty state scenario
5. Save dashboard

#### Database Migration
The `empty_state_config` column has been added to the `dashboards` table to store the configuration as JSON. Run database migrations to apply:
```bash
superset db upgrade
```

#### API Changes
The Dashboard API now accepts `empty_state_config` field in POST/PUT requests:
```json
{
"dashboard_title": "My Dashboard",
"empty_state_config": "{\"no_data_message\": \"Custom message\", \"no_data_subtitle\": \"Custom subtitle\"}"
}
```

The field is also returned in GET responses when present.

### MCP Service

The MCP (Model Context Protocol) service enables AI assistants and automation tools to interact programmatically with Superset.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export default function transformProps(
formData,
rawFormData,
hooks,
dashboardEmptyStateConfig,
datasource: { currencyFormats = {}, columnFormats = {} },
theme,
} = chartProps;
Expand Down Expand Up @@ -123,5 +124,6 @@ export default function transformProps(
metricName: originalLabel,
showMetricName,
metricNameFontSize,
dashboardEmptyStateConfig,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,23 @@ function BigNumberVis({
};

const renderHeader = (maxHeight: number) => {
const { bigNumber, width, colorThresholdFormatters, onContextMenu } = props;
// @ts-ignore
const text = bigNumber === null ? t('No data') : headerFormatter(bigNumber);
const {
bigNumber,
width,
colorThresholdFormatters,
onContextMenu,
dashboardEmptyStateConfig,
} = props;

let text: string;
if (bigNumber === null) {
// Priority: custom dashboard message > default i18n message
text = dashboardEmptyStateConfig?.no_data_message || t('No data');
} else {
// headerFormatter expects the raw bigNumber value (number, string, etc.)
const formattedValue = headerFormatter(bigNumber as number);
text = String(formattedValue);
}

const hasThresholdColorFormatter =
Array.isArray(colorThresholdFormatters) &&
Expand Down Expand Up @@ -286,20 +300,34 @@ function BigNumberVis({
};

const renderSubtitle = (maxHeight: number) => {
const { subtitle, width, bigNumber, bigNumberFallback } = props;
const {
subtitle,
width,
bigNumber,
bigNumberFallback,
dashboardEmptyStateConfig,
} = props;
let fontSize = 0;

const NO_DATA_OR_HASNT_LANDED = t(
'No data after filtering or data is NULL for the latest time record',
);
const NO_DATA = t(
'Try applying different filters or ensuring your datasource has data',
);

let text = subtitle;
let text: string | undefined;
if (bigNumber === null) {
text =
subtitle || (bigNumberFallback ? NO_DATA : NO_DATA_OR_HASNT_LANDED);
// Priority: custom dashboard subtitle > chart subtitle > default i18n message
if (dashboardEmptyStateConfig?.no_data_subtitle) {
text = dashboardEmptyStateConfig.no_data_subtitle;
} else if (subtitle) {
text = subtitle;
} else {
// Default i18n messages
text = bigNumberFallback
? t(
'Try applying different filters or ensuring your datasource has data',
)
: t(
'No data after filtering or data is NULL for the latest time record',
);
}
} else {
text = subtitle;
}

if (text) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export default function transformProps(
hooks,
inContextMenu,
theme,
dashboardEmptyStateConfig,
datasource: { currencyFormats = {}, columnFormats = {} },
} = chartProps;
const {
Expand Down Expand Up @@ -394,5 +395,6 @@ export default function transformProps(
onContextMenu,
xValueFormatter: formatTime,
refs,
dashboardEmptyStateConfig,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,10 @@ export type BigNumberVizProps = {
formData?: BigNumberWithTrendlineFormData;
refs: Refs;
colorThresholdFormatters?: ColorFormatters;
dashboardEmptyStateConfig?: {
no_data_message?: string;
no_data_subtitle?: string;
no_results_message?: string;
no_results_subtitle?: string;
};
};
12 changes: 9 additions & 3 deletions superset-frontend/src/components/Chart/ChartRenderer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const propTypes = {
source: PropTypes.oneOf([ChartSource.Dashboard, ChartSource.Explore]),
emitCrossFilters: PropTypes.bool,
onChartStateChange: PropTypes.func,
dashboardEmptyStateConfig: PropTypes.object,
};

const BLANK = {};
Expand Down Expand Up @@ -327,13 +328,17 @@ class ChartRenderer extends Component {
: '';

let noResultsComponent;
const noResultTitle = t('No results were returned for this query');
const { dashboardEmptyStateConfig } = this.props;
const noResultTitle =
dashboardEmptyStateConfig?.no_results_message ||
t('No results were returned for this query');
const noResultDescription =
this.props.source === ChartSource.Explore
dashboardEmptyStateConfig?.no_results_subtitle ||
(this.props.source === ChartSource.Explore
? t(
'Make sure that the controls are configured properly and the datasource contains data for the selected time range',
)
: undefined;
: undefined);
const noResultImage = 'chart.svg';
if (width > BIG_NO_RESULT_MIN_WIDTH && height > BIG_NO_RESULT_MIN_HEIGHT) {
noResultsComponent = (
Expand Down Expand Up @@ -411,6 +416,7 @@ class ChartRenderer extends Component {
legendState={this.state.legendState}
enableNoResults={bypassNoResult}
legendIndex={this.state.legendIndex}
dashboardEmptyStateConfig={dashboardEmptyStateConfig}
{...drillToDetailProps}
/>
</div>
Expand Down
26 changes: 22 additions & 4 deletions superset-frontend/src/dashboard/actions/dashboardState.js
Original file line number Diff line number Diff line change
Expand Up @@ -454,9 +454,22 @@ export function saveDashboardRequest(data, id, saveType) {
dashboard_title: cleanedData.dashboard_title,
slug: cleanedData.slug,
owners: cleanedData.owners,
roles: cleanedData.roles,
tags: cleanedData.tags || [],
...(cleanedData.roles !== undefined && {
roles: cleanedData.roles,
}),
...(cleanedData.tags !== undefined && {
tags: cleanedData.tags || [],
}),
theme_id: cleanedData.theme_id,
// Only include empty_state_config if it has actual values
...(cleanedData.empty_state_config &&
Object.values(cleanedData.empty_state_config).some(val => val)
? {
empty_state_config: safeStringify(
cleanedData.empty_state_config,
),
}
: {}),
json_metadata: safeStringify({
...cleanedData?.metadata,
default_filters: safeStringify(serializedFilters),
Expand All @@ -466,14 +479,19 @@ export function saveDashboardRequest(data, id, saveType) {
}),
};

const updateDashboard = () =>
SupersetClient.put({
const updateDashboard = () => {
console.log(
'Dashboard PUT payload:',
JSON.stringify(updatedDashboard, null, 2),
);
return SupersetClient.put({
endpoint: `/api/v1/dashboard/${id}`,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedDashboard),
})
.then(response => onUpdateSuccess(response))
.catch(response => onError(response));
};
return new Promise((resolve, reject) => {
if (
!isFeatureEnabled(FeatureFlag.ConfirmDashboardDiff) ||
Expand Down
9 changes: 9 additions & 0 deletions superset-frontend/src/dashboard/actions/hydrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,15 @@ export const hydrateDashboard =
metadata.chart_configuration = chartConfiguration;
metadata.global_chart_configuration = globalChartConfiguration;

// Parse and add empty_state_config to metadata if it exists
if (dashboard.empty_state_config) {
try {
metadata.empty_state_config = JSON.parse(dashboard.empty_state_config);
} catch (e) {
console.error('Failed to parse empty_state_config:', e);
}
}

const { roles } = user;
const canEdit = canUserEditDashboard(dashboard, user);
const crossFiltersEnabled = isCrossFiltersEnabled(
Expand Down
12 changes: 7 additions & 5 deletions superset-frontend/src/dashboard/components/Header/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -420,13 +420,15 @@ const Header = () => {
css: customCss,
dashboard_title: dashboardTitle,
last_modified_time: actualLastModifiedTime,
owners: dashboardInfo.owners,
roles: dashboardInfo.roles,
owners: (dashboardInfo.owners || []).map(o => o.id || o),
roles: (dashboardInfo.roles || []).map(r => r.id || r),
slug,
tags: (dashboardInfo.tags || []).filter(
item => item.type === TagTypeEnum.Custom || !item.type,
),
tags: (dashboardInfo.tags || [])
.filter(item => item.type === TagTypeEnum.Custom || !item.type)
.map(tag => tag.id)
.filter(id => typeof id === 'number'),
theme_id: dashboardInfo.theme ? dashboardInfo.theme.id : null,
empty_state_config: dashboardInfo.metadata?.empty_state_config,
metadata: {
...dashboardInfo?.metadata,
color_namespace: currentColorNamespace,
Expand Down
Loading
Loading