Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d00d658
feat(extensions): expose toasts to extensions through @apache-superse…
orion222 Nov 3, 2026
ef5612c
feat(extensions): add download menu locations and useExtensionMenuItems
orion222 Nov 3, 2026
052cf7c
style(extensions): format download menu locations and hook with oxfmt
orion222 Nov 3, 2026
2c3f753
feat(explore): expose chart download menus to extensions
orion222 Nov 3, 2026
75acd3c
fix(explore): hide extension download items for image-only charts
orion222 Nov 3, 2026
9c34b52
feat(dashboard): expose dashboard chart download menu to extensions
orion222 Nov 3, 2026
731f994
test(dashboard): pin click-time export payload freshness
orion222 Nov 3, 2026
daf8369
feat(charts): expose drill download menus to extensions
orion222 Nov 3, 2026
7425927
feat(dashboard): expose dashboard download menu to extensions
orion222 Nov 3, 2026
6c392c8
docs(extensions): document download menu locations and toasts
orion222 Nov 3, 2026
c97415e
fix(extensions): address final review of download menus and toasts
orion222 Nov 3, 2026
b552621
fix(extensions): open toast links through openInNewTab
orion222 Nov 3, 2026
6a3b160
fix(extensions): tighten toast link test and docs
orion222 Nov 3, 2026
db72f24
fix(charts): pass the chart name to Drill By download items
orion222 Nov 4, 2026
8cff1e1
chore(translations): add the chart export error string to messages.pot
orion222 Nov 4, 2026
600bf36
fix(extensions): type download payloads and pass displayed chart titles
orion222 Nov 4, 2026
2e4c108
test(extensions): cover unregistered commands and unbuildable drill p…
orion222 Nov 4, 2026
be42927
test(charts): pass onHideModal in the Drill to Detail title test
orion222 Nov 4, 2026
2711669
test(dashboard): cover chart state in the extension export payload
orion222 Nov 4, 2026
f635dbd
ci: re-run checks after a flaky frontend type-check build
orion222 Nov 4, 2026
e4c8c37
fix(extensions): preserve full table export queries
Grimshock1015 Nov 5, 2026
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
2 changes: 2 additions & 0 deletions docs/developer_docs/extensions/contribution-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ menus.registerMenuItem(
);
```

Charts and dashboards also have download-menu locations, such as `explore.chart.download` and `dashboard.download`. See [Download Menus](./extension-points/download-menus).

### Editors

Extensions can replace Superset's default text editors with custom implementations. This enables enhanced editing experiences using alternative editor frameworks like Monaco, CodeMirror, or custom solutions. When an extension registers an editor for a language, it replaces the default editor in all locations that use that language (SQL Lab, Dashboard Properties, CSS editors, etc.).
Expand Down
119 changes: 119 additions & 0 deletions docs/developer_docs/extensions/extension-points/download-menus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
title: Download Menus
sidebar_position: 5
---

<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

# Download Menu Extension Points

Extensions can add items to the download menus for charts and dashboards. An item runs a command and passes it a context object that describes the chart or dashboard, including a function that returns the query behind the export.

## Locations

Register a menu item at one of these locations with `menus.registerMenuItem`, using the `primary` group.

| Location | Surface | Where the item appears |
| --- | --- | --- |
| `explore.chart.download` | Explore, chart actions menu | After "Export to Excel" |
| `explore.chart.download.pivoted` | Explore, chart actions menu | After "Export to Pivoted Excel" |
| `explore.standalone.download` | Explore, standalone download control | After "Export to Excel" |
| `dashboard.chart.download` | Dashboard, chart menu | After "Export to Excel" |
| `dashboard.chart.download.pivoted` | Dashboard, chart menu | After "Export to Pivoted Excel" |
| `chart.drillDetail.download` | Drill to detail pane | After "Export to Excel" |
| `chart.drillBy.download` | Drill by modal | After "Export to Excel" |
| `dashboard.download` | Dashboard download menu | After the Excel export entries |

The pivoted locations appear only where the chart offers "Export to Pivoted Excel". For those locations, `getPayload()` returns the query for the post-processed (pivoted) result.

## Context

The seven chart locations pass a `ChartDownloadContext` to the command:

```typescript
interface ChartDownloadContext {
getPayload: () => Promise<ChartDataRequest>;
chartName: string;
canDownload: boolean;
}

interface ChartDataRequest {
datasource: { id: number; type: string };
queries: object[];
result_type: string;
result_format: string;
force?: boolean;
form_data?: object;
}
```

- `getPayload()` is evaluated when called, so call it from the command. It resolves to the `/api/v1/chart/data` request body that the CSV export sends, with `result_format: 'json'`. Chart query builders run in CSV export mode before the response format is changed to JSON, preserving export row limits, filters and column order. Paginated tables export from the first row up to the configured row limit, rather than only the visible page.
- `chartName` is the chart's title as shown, including a name set on the dashboard.
- `canDownload` is `false` when the current user lacks the permission to export the chart's data.

The `dashboard.download` location passes a `DashboardDownloadContext`:

```typescript
interface DashboardDownloadContext {
dashboardId: number;
dashboardTitle: string;
activeDataMask: Record<
string,
{
extraFormData: object;
filterState: { value?: unknown; label?: string };
}
>;
canDownload: boolean;
}
```

`activeDataMask` lists the applied native filters only, keyed by filter ID. Each entry carries `extraFormData` and `filterState` with `value` and `label`. Cross-filters and `ownState` are not included.

## Rules

- Chart items are disabled when `canDownload` is `false`.
- The dashboard item appears only where "Export Data to Excel" does, so it is hidden from users who cannot export.
- Image-only charts get no items in the Explore and dashboard chart menus. The standalone control keeps them, because it offers CSV and Excel for every chart.
- The context is read when the item is clicked, so `getPayload()` and `canDownload` reflect the state at that moment.
- Register the command **before** its menu item. An item whose command is not registered yet is skipped, and it appears only after the menu next re-renders.
- A command that throws or rejects is logged and does not break the menu.

## Example

```typescript
import { commands, menus } from '@apache-superset/core';

commands.registerCommand(
{ id: 'my-extension.export-rows', title: 'Export rows to My Service' },
async ctx => {
const payload = await ctx.getPayload();
// Send the payload to the service, for example with `fetch`.
},
);

menus.registerMenuItem(
{ view: 'explore.chart.download', command: 'my-extension.export-rows' },
'explore.chart.download',
'primary',
);
```

To report progress and the result to the user, see [Toasts](../toasts).
81 changes: 81 additions & 0 deletions docs/developer_docs/extensions/toasts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
title: Toasts
sidebar_position: 12
---

<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

# Toasts

Extensions can show toasts in Superset's notification area with the `toasts` API.

```typescript
import { toasts } from '@apache-superset/core';

toasts.addSuccessToast('Exported to Google Sheets', {
link: { href: 'https://docs.google.com/spreadsheets/d/abc', label: 'Open' },
});
```

## Functions

| Function | Description |
| --- | --- |
| `addInfoToast(text, options?)` | Shows an informational toast |
| `addSuccessToast(text, options?)` | Shows a success toast |
| `addWarningToast(text, options?)` | Shows a warning toast |
| `addDangerToast(text, options?)` | Shows an error toast |
| `removeToast(id)` | Closes a toast |

Each `add*Toast` function returns the ID of the toast, which you can pass to `removeToast`.

## Options

```typescript
interface ToastOptions {
link?: { href: string; label: string };
duration?: number;
}
```

- `text` is always rendered as plain text, never as HTML.
- `link` adds a button with the given `label`. Clicking it opens `href` in a new tab with `noopener` and `noreferrer`. The URL goes through Superset's navigation guard: absolute URLs must use `http(s)`, `ftp:`, `mailto:` or `tel:`, other values are treated as paths within Superset, and URLs the guard rejects (for example ones with embedded credentials) are logged instead of opened.
- `duration` is the time in milliseconds before the toast closes. The default is 8000. `0` keeps the toast until the user closes it.
- A toast with a `link` never closes on its own, so that keyboard and assistive-technology users are not raced. `duration` is ignored when `link` is set.

## Example

Show a toast while an export runs, then replace it with the result:

```typescript
import { toasts } from '@apache-superset/core';

const id = toasts.addInfoToast('Sending to Google Sheets…', { duration: 0 });
try {
const url = await sendToSheets();
toasts.removeToast(id);
toasts.addSuccessToast('Sent to Google Sheets', {
link: { href: url, label: 'Open' },
});
} catch {
toasts.removeToast(id);
toasts.addDangerToast('Sending to Google Sheets failed');
}
```
2 changes: 2 additions & 0 deletions docs/developer_docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,15 @@ module.exports = {
'extensions/extension-points/editors',
'extensions/extension-points/chat',
'extensions/extension-points/semantic-layers',
'extensions/extension-points/download-menus',
],
},
'extensions/development',
'extensions/deployment',
'extensions/mcp',
'extensions/security',
'extensions/tasks',
'extensions/toasts',
'extensions/registry',
],
},
Expand Down
1 change: 1 addition & 0 deletions superset-frontend/packages/superset-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export * as extensions from './extensions';
export * as menus from './menus';
export * as navigation from './navigation';
export * as sqlLab from './sqlLab';
export * as toasts from './toasts';
export * as views from './views';
export * as contributions from './contributions';
export * as theme from './theme';
Expand Down
2 changes: 2 additions & 0 deletions superset-frontend/packages/superset-core/src/menus/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@

import { Disposable, Event } from '../common';

export * from './locations';

/**
* Represents a menu item that links a view to a command.
*/
Expand Down
83 changes: 83 additions & 0 deletions superset-frontend/packages/superset-core/src/menus/locations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

/**
* Chart download menus that accept extension items. Each command receives a
* {@link ChartDownloadContext}.
*/
export type ChartDownloadLocation =
| 'explore.chart.download'
| 'explore.chart.download.pivoted'
| 'explore.standalone.download'
| 'dashboard.chart.download'
| 'dashboard.chart.download.pivoted'
| 'chart.drillDetail.download'
| 'chart.drillBy.download';

/**
* Dashboard download menus that accept extension items. Each command receives
* a {@link DashboardDownloadContext}.
*/
export type DashboardDownloadLocation = 'dashboard.download';

/** Request body for `POST /api/v1/chart/data`. */
export interface ChartDataRequest {
datasource: { id: number; type: string };
queries: object[];
result_type: string;
result_format: string;
force?: boolean;
form_data?: object;
}

/**
* Argument passed to commands contributed to a {@link ChartDownloadLocation}.
*/
export interface ChartDownloadContext {
/**
* Resolves to the chart-data request the CSV export would send, with
* `result_format: 'json'`. Evaluated when called, so invoke it from the
* command rather than ahead of time.
*/
getPayload: () => Promise<ChartDataRequest>;
/** Display name of the chart. */
chartName: string;
/** Whether the current user may download this chart's data. */
canDownload: boolean;
}

/**
* Argument passed to commands contributed to a {@link DashboardDownloadLocation}.
*/
export interface DashboardDownloadContext {
/** Numeric id of the dashboard. */
dashboardId: number;
/** Title of the dashboard. */
dashboardTitle: string;
/** Currently applied native filters, keyed by filter id. */
activeDataMask: Record<
string,
{
extraFormData: object;
filterState: { value?: unknown; label?: string };
}
>;
/** Whether the current user may download this dashboard's data. */
canDownload: boolean;
}
Loading
Loading