Compare commits

...

68 Commits

Author SHA1 Message Date
Claude
3e3466862a test(sqla): remove duplicate test function causing mypy no-redef error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 16:07:48 -07:00
Superset Dev
7704d4c811 test: add unit tests verifying values_for_column passes catalog/schema to get_sqla_engine
Addresses review feedback requesting regression coverage to assert that
the dataset's catalog and schema are forwarded to get_sqla_engine,
including the None/empty-schema case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 13:32:29 -07:00
Claude
82087d2fb5 test(sqla): add unit test verifying catalog/schema forwarded in values_for_column
Adds `test_values_for_column_passes_catalog_and_schema` to assert that
`values_for_column` passes the dataset's catalog and schema to
`get_sqla_engine`, covering both the explicit-schema and None/no-schema cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 13:10:29 -07:00
Superset Dev
b2c10f909b fix(sqla): pass catalog and schema to get_sqla_engine in values_for_column
The `values_for_column()` method, which powers the native filter value
dropdown, was calling `self.database.get_sqla_engine()` without passing
`catalog` or `schema`. This meant `adjust_engine_params()` was invoked
without schema context, so databases that encode the schema in the
connection URL (MySQL, Snowflake, Presto, etc.) would not apply the
correct schema for filter dropdown queries.

Every other query path in `ExploreMixin` already passes `self.catalog`
and `self.schema` — this change makes `values_for_column()` consistent.

Closes #32498

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 16:27:10 -07:00
amaannawab923
9215eb5e45 fix(ag-grid): persist AG Grid column filters in explore permalinks (#38393) 2026-03-11 01:56:24 +05:30
Amin Ghadersohi
fe7f220c21 fix(charts): set reasonable default y-axis title margin to prevent label overlap (#38389)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:09:09 +01:00
Amin Ghadersohi
3bb9704cd5 fix(mcp): honor target_tab parameter when adding charts to tabbed dashboards (#38409)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:57:15 -07:00
Amin Ghadersohi
eb77452857 feat(mcp): auto-generate dashboard title from chart names when omitted (#38410)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:56:58 -07:00
Amin Ghadersohi
6d7cfac8b2 fix(mcp): wrap LoggingMiddleware.on_message event_logger in try/except (#38560) 2026-03-10 17:48:08 +01:00
Đỗ Trọng Hải
31754a39c9 fix(i18n): correct variable name for translated SQL Lab query message (#38494)
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-03-10 21:50:14 +07:00
Michael S. Molina
bde48e563e fix: SQL Lab tab content padding (#38561) 2026-03-10 11:44:31 -03:00
Amin Ghadersohi
0cfd760a36 fix(mcp): improve default chart names with descriptive format (#38406)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:53:05 +01:00
dependabot[bot]
13fe88000a chore(deps-dev): bump lightningcss from 1.31.1 to 1.32.0 in /superset-frontend (#38511)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-03-10 16:51:58 +07:00
dependabot[bot]
cc8ad23d6f chore(deps): bump react-diff-viewer-continued from 3.4.0 to 4.2.0 in /superset-frontend (#38552)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2026-03-10 15:57:32 +07:00
Amin Ghadersohi
5c2cbb58bc fix(mcp): add missing __init__.py for chart, dashboard, dataset packages (#38400) 2026-03-10 09:52:48 +01:00
Amin Ghadersohi
6342c4f338 feat(mcp): add horizontal bar chart orientation support to generate_chart (#38390)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:52:12 +01:00
Amin Ghadersohi
5fa70bdbd8 fix(mcp): add guardrails to prevent LLM artifact generation (#38391)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:51:23 +01:00
Amin Ghadersohi
2a876e8b86 fix(mcp): add missing command.validate() to MCP chart data tools (#38521)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:50:41 +01:00
Amin Ghadersohi
0533ca9941 feat(mcp): register GlobalErrorHandlerMiddleware and LoggingMiddleware (#38523)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:48:38 +01:00
dependabot[bot]
5f20d2e15a chore(deps): bump react-syntax-highlighter from 16.1.0 to 16.1.1 in /superset-frontend (#38548)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 15:21:18 +07:00
dependabot[bot]
6d1d5d64d1 chore(deps): bump antd from 6.3.1 to 6.3.2 in /docs (#38547)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 15:21:00 +07:00
dependabot[bot]
06d6b513cd chore(deps-dev): bump jest from 30.2.0 to 30.3.0 in /superset-frontend (#38549)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 14:39:05 +07:00
dependabot[bot]
afa51125de chore(deps): bump the storybook group in /docs with 11 updates (#38501)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-03-10 10:09:40 +07:00
dependabot[bot]
26c07b1ffb chore(deps-dev): bump eslint-plugin-react-you-might-not-need-an-effect from 0.9.1 to 0.9.2 in /superset-frontend (#38509)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-03-10 08:54:43 +07:00
Đỗ Trọng Hải
9ecca47e69 feat(ci): only run precommit on changed files (#38155)
Signed-off-by: hainenber <dotronghai96@gmail.com>
2026-03-10 08:49:38 +07:00
dependabot[bot]
6c1df93215 chore(deps): bump aquasecurity/trivy-action from 0.34.2 to 0.35.0 (#38502)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-03-10 08:46:19 +07:00
dependabot[bot]
06fd0658ae chore(deps-dev): bump prettier-plugin-packagejson from 3.0.0 to 3.0.2 in /superset-frontend (#38508)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-03-10 08:46:01 +07:00
Enzo Martellucci
a17f38a4e2 fix(embedded): add CurrentUserRestApi read permission to Public role defaults (#38474) 2026-03-10 00:08:37 +01:00
Amin Ghadersohi
6ef4794778 fix(mcp): resolve chatbot tool call flakiness with URL and instruction fixes (#38532) 2026-03-09 23:35:50 +01:00
Amin Ghadersohi
4cd3ce164d fix(mcp): make fastmcp truly optional during Superset startup (#38534) 2026-03-09 15:32:27 -07:00
Evan Rusackas
8e3e57c1c8 fix(docs): swizzle MethodEndpoint to fix SSG crash on all API pages (#38533)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 14:18:59 -07:00
Hugh A. Miles II
61fbfda501 feat(security): add granular export controls (Phase 1) (#38361) 2026-03-09 16:44:56 -04:00
Evan Rusackas
9017b9a74f chore: enable allow_update_branch in .asf.yaml (#38530)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 13:42:09 -07:00
Gabriel Torres Ruiz
bc99b710bd fix(dashboard): ensure clear all respects required filter validation (#37681) 2026-03-09 13:02:57 -07:00
Michael S. Molina
bf55f1e438 chore(extensions): bump superset-core and superset-extensions-cli to 0.1.0rc1 (#38516) 2026-03-09 14:40:08 -03:00
Gabriel Torres Ruiz
dca41f9a7b fix(theme): prevent background color flash on page load (#38399) 2026-03-09 09:53:38 -07:00
yousoph
62cebc8a0e fix(dashboard): prevent Apply button from disabling when required filters are auto-applied (#38479)
Co-authored-by: Kamil Gabryjelski <kamil.gabryjelski@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 09:29:43 -07:00
Michael S. Molina
e70c7944b7 fix(tests): achieve 100% TypeScript coverage for core packages (#38518) 2026-03-09 13:25:39 -03:00
yousoph
577654cd02 fix(heatmap): correct tooltip display to show axis values instead of indices (#38487) 2026-03-09 08:54:47 -07:00
Enzo Martellucci
c7a1f57487 fix(sqla): parenthesize extras where/having clauses in query generation (#38183)
Co-authored-by: Diego Pucci <diegopucci.me@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:05:55 +01:00
yousoph
9983e255f8 fix(charts): revert: improve negative stacked bar label positioning and accessibility (#37405) (#38484)
Co-authored-by: Kamil Gabryjelski <kamil.gabryjelski@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 00:09:09 -07:00
Vitor Avila
d9a91f99db feat: support for import/export masked_encrypted_extra (frontend) (#38078) 2026-03-09 01:59:54 -03:00
dependabot[bot]
60577bcd97 chore(deps-dev): bump webpack from 5.105.3 to 5.105.4 in /docs (#38380)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 16:54:50 -08:00
HY Chang
3cb00bf116 feat(database): add Google Cloud Datastore db engine spec (#37677)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-03-06 16:40:17 -08:00
Michael S. Molina
a6c0d6321f chore(extensions): simplify backend package structure by removing superset_extensions namespace (#38476) 2026-03-06 14:49:49 -03:00
Michael S. Molina
5fb9e17721 refactor(extensions): align editors API naming with commands/views, add description to all contribution types (#38475) 2026-03-06 14:18:49 -03:00
Beto Dealmeida
03ad1789f0 feat(alerts/reports): external URL warning (#35021) 2026-03-06 11:57:03 -05:00
Michael S. Molina
296bd7e56b docs(extensions): fix extension developer documentation and CLI scaffolding (#38472) 2026-03-06 13:10:41 -03:00
Daniel Vaz Gaspar
5c4bf0f6ea fix(deps): bump Python dependencies to fix 7 security vulnerabilities (#38447)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 09:44:13 +00:00
Amin Ghadersohi
db7665c0bc feat(mcp): add user roles to MCP response and permission-aware instructions (#38367) 2026-03-06 08:16:51 +01:00
Amin Ghadersohi
84a53eab31 feat(mcp): add pie, pivot table, and mixed timeseries chart creation support (#38375) 2026-03-06 08:13:47 +01:00
Amin Ghadersohi
3609cd9544 feat(mcp): add compile check to validate chart queries before returning (#38408) 2026-03-06 08:10:58 +01:00
Amin Ghadersohi
7d2efd8c1a fix(mcp): suppress third-party deprecation warnings from client responses (#38401) 2026-03-06 08:02:25 +01:00
Evan Rusackas
0d5ade6dd3 fix(ColorPicker): restore alpha input visibility hidden by geostyler CSS (#38169)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 17:45:11 -08:00
Joe Li
17df85b5ed fix(roles): convert permissions/groups dropdowns to AsyncSelect with server-side search (#38387)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:54:16 -08:00
Joe Li
664c465d80 fix(test): use correct @apache-superset/core/theme import in Menu test (#38457)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:50:35 -08:00
Jonas Scholz
884db9347d feat(docs): Adding VTG GmbH in INTHEWILD.yaml (#38359)
Co-authored-by: Joe Li <joe@preset.io>
2026-03-05 14:41:17 -08:00
Joe Li
6c359733e1 fix(frontend): preserve absolute and protocol-relative URLs in ensureAppRoot (#38316)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 14:06:16 -08:00
Michael S. Molina
357e35dc62 refactor(core): reorganize superset-core packages into feature-based structure (#38448) 2026-03-05 17:41:15 -03:00
Joe Li
5f0efd2be9 test: fix CI OOM crashes in DatasourceControl test and flaky FileHandleer test (#38430)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 12:05:58 -08:00
Amin Ghadersohi
0dbd4c5b90 docs(mcp): add MCP server deployment and authentication guide (#38415) 2026-03-05 17:52:04 +01:00
Enzo Martellucci
f0416eff78 fix(dashboard): fix multiple drag-and-drop and edit mode issues (#37891) 2026-03-05 16:47:11 +01:00
Đỗ Trọng Hải
a513406239 feat!: upgrade project's Node version to v22 (#37223)
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-03-05 22:20:18 +07:00
dependabot[bot]
f6f734f0d1 chore(deps): bump google-auth-library from 10.5.0 to 10.6.1 in /superset-frontend (#38436)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 21:25:04 +07:00
dependabot[bot]
a2c23a2a58 chore(deps-dev): bump css-minimizer-webpack-plugin from 7.0.4 to 8.0.0 in /superset-frontend (#38434)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 21:17:41 +07:00
Antonio Rivero
20cc3345d8 chore(playwright): Using warning for timeouts (#38441) 2026-03-05 14:15:10 +01:00
Alexandru Soare
880cab58c3 fix(bug): Error when adding a filter using custom sql (#38246) 2026-03-05 11:39:21 +02:00
dependabot[bot]
4dfb0e66cb chore(deps-dev): bump webpack from 5.105.3 to 5.105.4 in /superset-frontend (#38385)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 09:26:50 +07:00
1337 changed files with 16366 additions and 5724 deletions

View File

@@ -24,7 +24,9 @@ notifications:
discussions: notifications@superset.apache.org
github:
del_branch_on_merge: true
pull_requests:
del_branch_on_merge: true
allow_update_branch: true
description: "Apache Superset is a Data Visualization and Data Exploration Platform"
homepage: https://superset.apache.org/
labels:

View File

@@ -104,7 +104,7 @@ jobs:
# Scan for vulnerabilities in built container image after pushes to mainline branch.
- name: Run Trivy container image vulnerabity scan
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker) && matrix.build_preset == 'lean'
uses: aquasecurity/trivy-action@97e0b3872f55f89b95b2f65b3dbab56962816478 # v0.34.2
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
with:
image-ref: ${{ env.IMAGE_TAG }}
format: 'sarif'

View File

@@ -64,11 +64,17 @@ jobs:
restore-keys: |
pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-
- name: Get changed files
id: changed_files
uses: ./.github/actions/file-changes-action
with:
output: ' '
- name: pre-commit
run: |
set +e # Don't exit immediately on failure
export SKIP=eslint-frontend,type-checking-frontend
pre-commit run --all-files
export SKIP=type-checking-frontend
pre-commit run --files ${{ steps.changed_files.outputs.files }}
PRE_COMMIT_EXIT_CODE=$?
git diff --quiet --exit-code
GIT_DIFF_EXIT_CODE=$?

View File

@@ -625,6 +625,9 @@ categories:
- name: Stockarea
url: https://stockarea.io
- name: VTG
url: https://www.vtg.de
Sports:
- name: Club 25 de Agosto (Femenino / Women's Team)
url: https://www.instagram.com/25deagosto.basketfemenino/

View File

@@ -1 +1 @@
v20.20.0
v22.22.0

View File

@@ -233,6 +233,20 @@ def alert_dynamic_minimal_interval(**kwargs) -> int:
ALERT_MINIMUM_INTERVAL = alert_dynamic_minimal_interval
```
## External Link Redirection
For security, Superset rewrites external links in alert/report email HTML so
they go through a warning page before the user is navigated to the external
site. Internal links (matching your configured base URL) are not affected.
```python
# Disable external link redirection entirely (default: True)
ALERT_REPORTS_ENABLE_LINK_REDIRECT = False
```
The feature uses `WEBDRIVER_BASEURL_USER_FRIENDLY` (or `WEBDRIVER_BASEURL`)
to determine which hosts are internal.
## Troubleshooting
There are many reasons that reports might not be working. Try these steps to check for specific issues.

View File

@@ -33,13 +33,15 @@ The extension architecture is built on six core principles that guide all techni
### 1. Lean Core
Superset's core should remain minimal, with many features delegated to extensions. Built-in features use the same APIs and extension mechanisms available to external developers. This approach:
- Reduces maintenance burden and complexity
- Encourages modularity
- Allows the community to innovate independently of the main codebase
### 2. Explicit Contribution Points
All extension points are clearly defined and documented. Extension authors know exactly where and how they can interact with the host system. Backend contributions are declared in `extension.json`. Frontend contributions are registered directly in code at module load time, giving the host clear visibility into what each extension provides:
All extension points are clearly defined and documented. Extension authors know exactly where and how they can interact with the host system. Both backend and frontend contributions are registered directly in code — backend contributions via classes decorated with `@api` (and other decorators) imported from the auto-discovered entrypoint, frontend contributions via calls like `views.registerView` and `commands.registerCommand` executed at module load time in `index.tsx`. This gives the host clear visibility into what each extension provides:
- Manage the extension lifecycle
- Provide a consistent user experience
- Validate extension compatibility
@@ -47,6 +49,7 @@ All extension points are clearly defined and documented. Extension authors know
### 3. Versioned and Stable APIs
Public interfaces for extensions follow semantic versioning, allowing for:
- Safe evolution of the platform
- Backward compatibility
- Clear upgrade paths for extension authors
@@ -54,6 +57,7 @@ Public interfaces for extensions follow semantic versioning, allowing for:
### 4. Lazy Loading and Activation
Extensions are loaded and activated only when needed, which:
- Minimizes performance overhead
- Reduces resource consumption
- Improves startup time
@@ -61,6 +65,7 @@ Extensions are loaded and activated only when needed, which:
### 5. Composability and Reuse
The architecture encourages reusing extension points and patterns across different modules, promoting:
- Consistency across extensions
- Reduced duplication
- Shared best practices
@@ -80,6 +85,7 @@ Two core packages provide the foundation for extension development:
**Frontend: `@apache-superset/core`**
This package provides essential building blocks for frontend extensions and the host application:
- Shared UI components
- Utility functions
- APIs and hooks
@@ -90,6 +96,7 @@ By centralizing these resources, both extensions and built-in features use the s
**Backend: `apache-superset-core`**
This package exposes key classes and APIs for backend extensions:
- Database connectors
- API extensions
- Security manager customization
@@ -102,6 +109,7 @@ It includes dependencies on critical libraries like Flask-AppBuilder and SQLAlch
**`apache-superset-extensions-cli`**
The CLI provides comprehensive commands for extension development:
- Project scaffolding
- Code generation
- Building and bundling
@@ -114,6 +122,7 @@ By standardizing these processes, the CLI ensures extensions are built consisten
The Superset host application serves as the runtime environment for extensions:
**Extension Management**
- Exposes `/api/v1/extensions` endpoint for registration and management
- Provides a dedicated UI for managing extensions
- Stores extension metadata in the `extensions` database table
@@ -121,6 +130,7 @@ The Superset host application serves as the runtime environment for extensions:
**Extension Storage**
The extensions table contains:
- Extension name, version, and author
- Metadata and configuration
- Built frontend and/or backend code
@@ -132,6 +142,7 @@ The following diagram illustrates how these components work together:
<img width="955" height="586" alt="Extension System Architecture" src="https://github.com/user-attachments/assets/cc2a41df-55a4-48c8-b056-35f7a1e567c6" />
The diagram shows:
1. **Extension projects** depend on core packages for development
2. **Core packages** provide APIs and type definitions
3. **The host application** implements the APIs and manages extensions
@@ -151,23 +162,25 @@ The architecture leverages Webpack's Module Federation to enable dynamic loading
Extensions configure Webpack to expose their entry points:
``` typescript
new ModuleFederationPlugin({
name: 'my_extension',
filename: 'remoteEntry.[contenthash].js',
exposes: {
'./index': './src/index.tsx',
},
externalsType: 'window',
externals: {
'@apache-superset/core': 'superset',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
'antd-v5': { singleton: true }
}
})
```javascript
externalsType: 'window',
externals: {
'@apache-superset/core': 'superset',
},
plugins: [
new ModuleFederationPlugin({
name: 'my_extension',
filename: 'remoteEntry.[contenthash].js',
exposes: {
'./index': './src/index.tsx',
},
shared: {
react: { singleton: true, import: false },
'react-dom': { singleton: true, import: false },
antd: { singleton: true, import: false },
},
}),
]
```
This configuration does several important things:
@@ -195,24 +208,12 @@ Here's what happens at runtime:
On the Superset side, the APIs are mapped to `window.superset` during application bootstrap:
``` typescript
```typescript
import * as supersetCore from '@apache-superset/core';
import {
authentication,
core,
commands,
extensions,
sqlLab,
} from 'src/extensions';
export default function setupExtensionsAPI() {
window.superset = {
...supersetCore,
authentication,
core,
commands,
extensions,
sqlLab,
};
}
```

View File

@@ -23,7 +23,7 @@ sidebar_label: Alert
-->
import { StoryWithControls } from '../../../src/components/StorybookWrapper';
import { Alert } from '@apache-superset/core/ui';
import { Alert } from '@apache-superset/core/components';
# Alert
@@ -105,10 +105,10 @@ function Demo() {
## Usage in Extensions
This component is available in the `@apache-superset/core/ui` package, which is automatically available to Superset extensions.
This component is available in the `@apache-superset/core/components` package, which is automatically available to Superset extensions.
```tsx
import { Alert } from '@apache-superset/core/ui';
import { Alert } from '@apache-superset/core/components';
function MyExtension() {
return (

View File

@@ -25,7 +25,7 @@ sidebar_position: 1
# Extension Components
These UI components are available to Superset extension developers through the `@apache-superset/core/ui` package. They provide a consistent look and feel with the rest of Superset and are designed to be used in extension panels, views, and other UI elements.
These UI components are available to Superset extension developers through the `@apache-superset/core/components` package. They provide a consistent look and feel with the rest of Superset and are designed to be used in extension panels, views, and other UI elements.
## Available Components
@@ -33,10 +33,10 @@ These UI components are available to Superset extension developers through the `
## Usage
All components are exported from the `@apache-superset/core/ui` package:
All components are exported from the `@apache-superset/core/components` package:
```tsx
import { Alert } from '@apache-superset/core/ui';
import { Alert } from '@apache-superset/core/components';
export function MyExtensionPanel() {
return (
@@ -49,7 +49,7 @@ export function MyExtensionPanel() {
## Adding New Components
Components in `@apache-superset/core/ui` are automatically documented here. To add a new extension component:
Components in `@apache-superset/core/components` are automatically documented here. To add a new extension component:
1. Add the component to `superset-frontend/packages/superset-core/src/ui/components/`
2. Export it from `superset-frontend/packages/superset-core/src/ui/components/index.ts`

View File

@@ -28,7 +28,7 @@ To facilitate the development of extensions, we define a set of well-defined con
## Frontend
Frontend contribution types allow extensions to extend Superset's user interface with new views, commands, and menu items. Frontend contributions are registered directly in code from your extension's `index.tsx` entry point — they do not need to be declared in `extension.json`.
Frontend contribution types allow extensions to extend Superset's user interface with new views, commands, and menu items. Frontend contributions are registered directly in code from your extension's `index.tsx` entry point.
### Views
@@ -68,25 +68,28 @@ commands.registerCommand(
### Menus
Extensions can contribute new menu items or context menus to the host application, providing users with additional actions and options. Each menu item specifies the target area, the command to execute, and its placement (primary, secondary, or context). Menu contribution areas are uniquely identified (e.g., `sqllab.editor` for the SQL Lab editor).
Extensions can contribute new menu items or context menus to the host application, providing users with additional actions and options. Each menu item specifies the view and command to execute, the target area, and the location (`primary`, `secondary`, or `context`). Menu contribution areas are uniquely identified (e.g., `sqllab.editor` for the SQL Lab editor).
```typescript
import { menus } from '@apache-superset/core';
menus.addMenuItem('sqllab.editor', {
placement: 'primary',
command: 'my-extension.copy-query',
});
menus.registerMenuItem(
{ view: 'sqllab.editor', command: 'my-extension.copy-query' },
'sqllab.editor',
'primary',
);
menus.addMenuItem('sqllab.editor', {
placement: 'secondary',
command: 'my-extension.prettify',
});
menus.registerMenuItem(
{ view: 'sqllab.editor', command: 'my-extension.prettify' },
'sqllab.editor',
'secondary',
);
menus.addMenuItem('sqllab.editor', {
placement: 'context',
command: 'my-extension.clear',
});
menus.registerMenuItem(
{ view: 'sqllab.editor', command: 'my-extension.clear' },
'sqllab.editor',
'context',
);
```
### Editors
@@ -111,24 +114,31 @@ See [Editors Extension Point](./extension-points/editors) for implementation det
## Backend
Backend contribution types allow extensions to extend Superset's server-side capabilities with new API endpoints, MCP tools, and MCP prompts.
Backend contribution types allow extensions to extend Superset's server-side capabilities. Backend contributions are registered at startup via classes and functions imported from the auto-discovered `entrypoint.py` file.
### REST API Endpoints
Extensions can register custom REST API endpoints under the `/extensions/` namespace. This dedicated namespace prevents conflicts with built-in endpoints and provides a clear separation between core and extension functionality.
```python
from superset_core.api.rest_api import RestApi, api
from flask_appbuilder.api import expose, protect
from flask import Response
from flask_appbuilder.api import expose, permission_name, protect, safe
from superset_core.rest_api.api import RestApi
from superset_core.rest_api.decorators import api
@api(
id="my_extension_api",
name="My Extension API",
description="Custom API endpoints for my extension"
description="Custom API endpoints for my extension",
)
class MyExtensionAPI(RestApi):
openapi_spec_tag = "My Extension"
class_permission_name = "my_extension_api"
@expose("/hello", methods=("GET",))
@protect()
@safe
@permission_name("read")
def hello(self) -> Response:
return self.response(200, result={"message": "Hello from extension!"})
@@ -136,7 +146,7 @@ class MyExtensionAPI(RestApi):
from .api import MyExtensionAPI
```
**Note**: The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator automatically detects context and generates appropriate paths:
**Note**: The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator automatically detects context and generates appropriate paths:
- **Extension context**: `/extensions/{publisher}/{name}/` with ID prefixed as `extensions.{publisher}.{name}.{id}`
- **Host context**: `/api/v1/` with original ID
@@ -152,16 +162,65 @@ You can also specify a `resource_name` parameter to add an additional path segme
@api(
id="analytics_api",
name="Analytics API",
resource_name="analytics" # Adds /analytics to the path
resource_name="analytics", # Adds /analytics to the path
)
class AnalyticsAPI(RestApi):
@expose("/insights", methods=("GET",))
def insights(self):
@protect()
@safe
@permission_name("read")
def insights(self) -> Response:
# This endpoint will be available at:
# /extensions/my-org/dataset-tools/analytics/insights
return self.response(200, result={"insights": []})
```
### MCP Tools and Prompts
### MCP Tools
Extensions can contribute Model Context Protocol (MCP) tools and prompts that AI agents can discover and use. See [MCP Integration](./mcp) for detailed documentation.
Extensions can register Python functions as MCP tools that AI agents can discover and call. Tools provide executable functionality such as data processing, custom analytics, or integration with external services. Each tool specifies a unique name and an optional description that helps AI agents decide when to use it.
```python
from superset_core.mcp.decorators import tool
@tool(
name="my-extension.get_summary",
description="Get a summary of recent query activity",
tags=["analytics", "queries"],
)
def get_summary() -> dict:
"""Returns a summary of recent query activity."""
return {"status": "success", "result": {"queries_today": 42}}
```
See [MCP Integration](./mcp) for implementation details.
### MCP Prompts
Extensions can register MCP prompts that provide interactive guidance and context to AI agents. Prompts help agents understand domain-specific workflows, best practices, or troubleshooting steps for your extension's use cases.
```python
from superset_core.mcp.decorators import prompt
from fastmcp import Context
@prompt(
"my-extension.analysis_guide",
title="Analysis Guide",
description="Step-by-step guidance for data analysis workflows",
)
async def analysis_guide(ctx: Context) -> str:
"""Provides guidance for data analysis workflows."""
return """
# Data Analysis Guide
Follow these steps for effective analysis:
1. **Explore your data** - Review available datasets and schema
2. **Build your query** - Use SQL Lab to craft and test queries
3. **Visualize results** - Choose the right chart type for your data
What would you like to analyze today?
"""
```
See [MCP Integration](./mcp) for implementation details.

View File

@@ -70,8 +70,8 @@ import { someInternalFunction } from 'src/explore/components/SomeComponent';
```python
# ✅ Public API - stable
from superset_core.api.models import Database
from superset_core.api.daos import DatabaseDAO
from superset_core.common.models import Database
from superset_core.common.daos import DatabaseDAO
# ❌ Internal code - may break without notice
from superset.views.core import SomeInternalClass
@@ -117,7 +117,7 @@ Extension developers should depend on and use core libraries directly:
**Frontend (examples):**
- [React](https://react.dev/) - UI framework
- [Ant Design](https://ant.design/) - UI component library (prefer Superset components from `@apache-superset/core/ui` when available to preserve visual consistency)
- [Ant Design](https://ant.design/) - UI component library (prefer Superset components from `@apache-superset/core/components` when available to preserve visual consistency)
- [Emotion](https://emotion.sh/) - CSS-in-JS styling
- ...

View File

@@ -38,12 +38,14 @@ superset-extensions build: Builds extension assets.
superset-extensions bundle: Packages the extension into a .supx file.
superset-extensions dev: Automatically rebuilds the extension as files change.
superset-extensions validate: Validates the extension structure and metadata.
```
When creating a new extension with `superset-extensions init`, the CLI generates a standardized folder structure:
```
dataset-references/
my-org.dataset-references/
├── extension.json
├── frontend/
│ ├── src/
@@ -52,9 +54,10 @@ dataset-references/
│ └── package.json
├── backend/
│ ├── src/
│ │ └── superset_extensions/
│ │ └── my_org/
│ │ └── dataset_references/
├── tests/
│ ├── api.py
│ │ └── entrypoint.py
│ ├── pyproject.toml
│ └── requirements.txt
├── dist/
@@ -64,20 +67,20 @@ dataset-references/
│ │ ├── remoteEntry.d7a9225d042e4ccb6354.js
│ │ └── 900.038b20cdff6d49cfa8d9.js
│ └── backend
│ └── superset_extensions/
│ └── my_org/
│ └── dataset_references/
│ ├── __init__.py
│ ├── api.py
│ └── entrypoint.py
├── dataset-references-1.0.0.supx
├── my-org.dataset-references-1.0.0.supx
└── README.md
```
**Note**: The extension ID (`dataset-references`) serves as the basis for all technical names:
- Directory name: `dataset-references` (kebab-case)
- Backend Python package: `dataset_references` (snake_case)
- Frontend package name: `dataset-references` (kebab-case)
- Module Federation name: `datasetReferences` (camelCase)
**Note**: With publisher `my-org` and name `dataset-references`, the technical names are:
- Directory name: `my-org.dataset-references` (kebab-case)
- Backend Python namespace: `my_org.dataset_references`
- Backend distribution package: `my_org-dataset_references`
- Frontend package name: `@my-org/dataset-references` (scoped)
- Module Federation name: `myOrg_datasetReferences` (camelCase)
The `extension.json` file serves as the declared metadata for the extension, containing the extension's name, version, author, description, and a list of capabilities. This file is essential for the host application to understand how to load and manage the extension.
@@ -108,7 +111,7 @@ The `extension.json` file contains the metadata necessary for the host applicati
Extensions use standardized entry point locations:
- **Backend**: `backend/src/superset_extensions/{publisher}/{name}/entrypoint.py`
- **Backend**: `backend/src/{publisher}/{name}/entrypoint.py`
- **Frontend**: `frontend/src/index.tsx`
### Build Configuration
@@ -124,7 +127,7 @@ license = "Apache-2.0"
[tool.apache_superset_extensions.build]
# Files to include in the extension build/bundle
include = [
"src/superset_extensions/my_org/dataset_references/**/*.py",
"src/my_org/dataset_references/**/*.py",
]
exclude = []
```
@@ -201,9 +204,10 @@ Backend APIs (via `apache-superset-core`) follow a similar pattern, providing ac
Extension endpoints are registered under a dedicated `/extensions` namespace to avoid conflicting with built-in endpoints and also because they don't share the same version constraints. By grouping all extension endpoints under `/extensions`, Superset establishes a clear boundary between core and extension functionality, making it easier to manage, document, and secure both types of APIs.
```python
from superset_core.api.models import Database, get_session
from superset_core.api.daos import DatabaseDAO
from superset_core.api.rest_api import RestApi, api
from superset_core.common.models import Database, get_session
from superset_core.common.daos import DatabaseDAO
from superset_core.rest_api.api import RestApi
from superset_core.rest_api.decorators import api
from flask_appbuilder.api import expose, protect
@api(
@@ -244,7 +248,7 @@ class DatasetReferencesAPI(RestApi):
### Automatic Context Detection
The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator automatically detects whether it's being used in host or extension code:
The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator automatically detects whether it's being used in host or extension code:
- **Extension APIs**: Registered under `/extensions/{publisher}/{name}/` with IDs prefixed as `extensions.{publisher}.{name}.{id}`
- **Host APIs**: Registered under `/api/v1/` with original IDs
@@ -262,7 +266,7 @@ LOCAL_EXTENSIONS = [
]
```
This instructs Superset to load and serve extensions directly from disk, so you can iterate quickly. Running `superset-extensions dev` watches for file changes and rebuilds assets automatically, while the Webpack development server (started separately with `npm run dev-server`) serves updated files as soon as they're modified. This enables immediate feedback for React components, styles, and other frontend code. Changes to backend files are also detected automatically and immediately synced, ensuring that both frontend and backend updates are reflected in your development environment.
This instructs Superset to load and serve extensions directly from disk, so you can iterate quickly. Running `superset-extensions dev` watches for file changes and rebuilds assets automatically, while the Webpack development server (started separately with `npm run start`) serves updated files as soon as they're modified. This enables immediate feedback for React components, styles, and other frontend code. Changes to backend files are also detected automatically and immediately synced, ensuring that both frontend and backend updates are reflected in your development environment.
Example output when running in development mode:

View File

@@ -37,31 +37,11 @@ Superset uses text editors in various places throughout the application:
| `css` | Dashboard Properties, CSS Template Modal |
| `markdown` | Dashboard Markdown component |
| `yaml` | Template Params Editor |
| `javascript` | Custom JavaScript editor contexts |
| `python` | Custom Python editor contexts |
| `text` | Plain text editor contexts |
By registering an editor provider for a language, your extension replaces the default Ace editor in **all** locations that use that language.
## Manifest Configuration
Declare editor contributions in your `extension.json` manifest:
```json
{
"name": "monaco-editor",
"version": "1.0.0",
"frontend": {
"contributions": {
"editors": [
{
"id": "monaco-editor.sql",
"name": "Monaco SQL Editor",
"languages": ["sql"],
"description": "Monaco-based SQL editor with IntelliSense"
}
]
}
}
}
```
By registering an editor for a language, your extension replaces the default Ace editor in **all** locations that use that language.
## Implementing an Editor
@@ -165,21 +145,22 @@ const MonacoSQLEditor = forwardRef<editors.EditorHandle, editors.EditorProps>(
export default MonacoSQLEditor;
```
### activate.ts
### index.tsx
Register the editor at module load time from your extension's entry point:
```typescript
import { editors } from '@apache-superset/core';
import MonacoSQLEditor from './MonacoSQLEditor';
export function activate(context) {
// Register the Monaco editor for SQL using the contribution ID from extension.json
const disposable = editors.registerEditorProvider(
'monaco-sql-editor.sql',
MonacoSQLEditor,
);
context.subscriptions.push(disposable);
}
editors.registerEditor(
{
id: 'my-extension.monaco-sql',
name: 'Monaco SQL Editor',
languages: ['sql'],
},
MonacoSQLEditor,
);
```
## Handling Hotkeys

View File

@@ -86,132 +86,73 @@ Extensions can replace the default SQL editor with custom implementations (Monac
This example adds a "Data Profiler" panel to SQL Lab:
```json
{
"name": "data_profiler",
"version": "1.0.0",
"frontend": {
"contributions": {
"views": {
"sqllab": {
"panels": [
{
"id": "data_profiler.main",
"name": "Data Profiler"
}
]
}
}
}
}
}
```
```typescript
import { core } from '@apache-superset/core';
import React from 'react';
import { views } from '@apache-superset/core';
import DataProfilerPanel from './DataProfilerPanel';
export function activate(context) {
// Register the panel view with the ID declared in extension.json
const disposable = core.registerView('data_profiler.main', <DataProfilerPanel />);
context.subscriptions.push(disposable);
}
views.registerView(
{ id: 'my-extension.data-profiler', name: 'Data Profiler' },
'sqllab.panels',
() => <DataProfilerPanel />,
);
```
### Adding Actions to the Editor
This example adds primary, secondary, and context actions to the editor:
```json
{
"name": "query_tools",
"version": "1.0.0",
"frontend": {
"contributions": {
"commands": [
{
"command": "query_tools.format",
"title": "Format Query",
"icon": "FormatPainterOutlined"
},
{
"command": "query_tools.explain",
"title": "Explain Query"
},
{
"command": "query_tools.copy_as_cte",
"title": "Copy as CTE"
}
],
"menus": {
"sqllab": {
"editor": {
"primary": [
{
"view": "builtin.editor",
"command": "query_tools.format"
}
],
"secondary": [
{
"view": "builtin.editor",
"command": "query_tools.explain"
}
],
"context": [
{
"view": "builtin.editor",
"command": "query_tools.copy_as_cte"
}
]
}
}
}
}
}
}
```
```typescript
import { commands, sqlLab } from '@apache-superset/core';
import { commands, menus, sqlLab } from '@apache-superset/core';
export function activate(context) {
// Register the commands declared in extension.json
const formatCommand = commands.registerCommand(
'query_tools.format',
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Format the SQL query
}
},
);
commands.registerCommand(
{ id: 'my-extension.format', title: 'Format Query', icon: 'FormatPainterOutlined' },
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Format the SQL query
}
},
);
const explainCommand = commands.registerCommand(
'query_tools.explain',
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Show query explanation
}
},
);
commands.registerCommand(
{ id: 'my-extension.explain', title: 'Explain Query' },
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Show query explanation
}
},
);
const copyAsCteCommand = commands.registerCommand(
'query_tools.copy_as_cte',
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Copy selected text as CTE
}
},
);
commands.registerCommand(
{ id: 'my-extension.copy-as-cte', title: 'Copy as CTE' },
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Copy selected text as CTE
}
},
);
context.subscriptions.push(formatCommand, explainCommand, copyAsCteCommand);
}
menus.registerMenuItem(
{ view: 'builtin.editor', command: 'my-extension.format' },
'sqllab.editor',
'primary',
);
menus.registerMenuItem(
{ view: 'builtin.editor', command: 'my-extension.explain' },
'sqllab.editor',
'secondary',
);
menus.registerMenuItem(
{ view: 'builtin.editor', command: 'my-extension.copy-as-cte' },
'sqllab.editor',
'context',
);
```
## Next Steps

View File

@@ -0,0 +1,679 @@
---
title: MCP Server Deployment & Authentication
hide_title: true
sidebar_position: 9
version: 1
---
<!--
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.
-->
# MCP Server Deployment & Authentication
Superset includes a built-in [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that lets AI assistants -- Claude, ChatGPT, and other MCP-compatible clients -- interact with your Superset instance. Through MCP, clients can list dashboards, query datasets, execute SQL, create charts, and more.
This guide covers how to run, secure, and deploy the MCP server.
```mermaid
flowchart LR
A["AI Client<br/>(Claude, ChatGPT, etc.)"] -- "MCP protocol<br/>(HTTP + JSON-RPC)" --> B["MCP Server<br/>(:5008/mcp)"]
B -- "Superset context<br/>(app, db, RBAC)" --> C["Superset<br/>(:8088)"]
C --> D[("Database<br/>(Postgres)")]
```
---
## Quick Start
Get the MCP server running locally and connect an AI client in three steps.
### 1. Start the MCP server
The MCP server runs as a separate process alongside Superset:
```bash
superset mcp run --host 127.0.0.1 --port 5008
```
| Flag | Default | Description |
|------|---------|-------------|
| `--host` | `127.0.0.1` | Host to bind to |
| `--port` | `5008` | Port to bind to |
| `--debug` | off | Enable debug logging |
The endpoint is available at `http://<host>:<port>/mcp`.
### 2. Set a development user
For local development, tell the MCP server which Superset user to impersonate (the user must already exist in your database):
```python
# superset_config.py
MCP_DEV_USERNAME = "admin"
```
### 3. Connect an AI client
Point your MCP client at the server. For **Claude Desktop**, edit the config file:
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"superset": {
"url": "http://localhost:5008/mcp"
}
}
}
```
Restart Claude Desktop. The hammer icon in the chat bar confirms the connection.
See [Connecting AI Clients](#connecting-ai-clients) for Claude Code, Claude Web, ChatGPT, and raw HTTP examples.
---
## Prerequisites
- Apache Superset 5.0+ running and accessible
- Python 3.10+
- The `fastmcp` package (`pip install fastmcp`)
---
## Authentication
The MCP server supports multiple authentication methods depending on your deployment scenario.
```mermaid
flowchart TD
R["Incoming MCP Request"] --> F{"MCP_AUTH_FACTORY<br/>set?"}
F -- Yes --> CF["Custom Auth Provider"]
F -- No --> AE{"MCP_AUTH_ENABLED?"}
AE -- "True" --> JWT["JWT Validation"]
AE -- "False" --> DU["Dev Mode<br/>(MCP_DEV_USERNAME)"]
JWT --> ALG{"MCP_JWT_ALGORITHM"}
ALG -- "RS256 + JWKS" --> JWKS["Fetch keys from<br/>MCP_JWKS_URI"]
ALG -- "RS256 + static" --> PK["Use<br/>MCP_JWT_PUBLIC_KEY"]
ALG -- "HS256" --> SEC["Use<br/>MCP_JWT_SECRET"]
JWKS --> V["Validate token<br/>(exp, iss, aud, scopes)"]
PK --> V
SEC --> V
V --> UR["Resolve Superset user<br/>from token claims"]
UR --> OK["Authenticated request"]
CF --> OK
DU --> OK
```
### Development Mode (No Auth)
Disable authentication and use a fixed user:
```python
# superset_config.py
MCP_AUTH_ENABLED = False
MCP_DEV_USERNAME = "admin"
```
All operations run as the configured user.
:::warning
Never use development mode in production. Always enable authentication for any internet-facing deployment.
:::
### JWT Authentication
For production, enable JWT-based authentication. The MCP server validates a Bearer token on every request.
#### Option A: RS256 with JWKS endpoint
The most common setup for OAuth 2.0 / OIDC providers that publish a JWKS (JSON Web Key Set) endpoint:
```python
# superset_config.py
MCP_AUTH_ENABLED = True
MCP_JWT_ALGORITHM = "RS256"
MCP_JWKS_URI = "https://your-identity-provider.com/.well-known/jwks.json"
MCP_JWT_ISSUER = "https://your-identity-provider.com/"
MCP_JWT_AUDIENCE = "your-superset-instance"
```
#### Option B: RS256 with static public key
Use this when you have a fixed RSA key pair (e.g., self-signed tokens):
```python
# superset_config.py
MCP_AUTH_ENABLED = True
MCP_JWT_ALGORITHM = "RS256"
MCP_JWT_PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----"""
MCP_JWT_ISSUER = "your-issuer"
MCP_JWT_AUDIENCE = "your-audience"
```
#### Option C: HS256 with shared secret
Use this when both the token issuer and the MCP server share a symmetric secret:
```python
# superset_config.py
MCP_AUTH_ENABLED = True
MCP_JWT_ALGORITHM = "HS256"
MCP_JWT_SECRET = "your-shared-secret-key"
MCP_JWT_ISSUER = "your-issuer"
MCP_JWT_AUDIENCE = "your-audience"
```
:::warning
Store `MCP_JWT_SECRET` securely. Never commit it to version control. Use environment variables:
```python
import os
MCP_JWT_SECRET = os.environ.get("MCP_JWT_SECRET")
```
:::
#### JWT claims
The MCP server validates these standard claims:
| Claim | Config Key | Description |
|-------|-----------|-------------|
| `exp` | -- | Expiration time (always validated) |
| `iss` | `MCP_JWT_ISSUER` | Token issuer (optional but recommended) |
| `aud` | `MCP_JWT_AUDIENCE` | Token audience (optional but recommended) |
| `sub` | -- | Subject -- primary claim used to resolve the Superset user |
#### User resolution
After validating the token, the MCP server resolves a Superset username from the claims. It checks these in order, using the first non-empty value:
1. `subject` -- the standard `sub` claim (via the access token object)
2. `client_id` -- for machine-to-machine tokens
3. `payload["sub"]` -- fallback to raw payload
4. `payload["email"]` -- email-based lookup
5. `payload["username"]` -- explicit username claim
The resolved value must match a `username` in the Superset `ab_user` table.
#### Scoped access
Require specific scopes in the JWT to limit what MCP operations a token can perform:
```python
# superset_config.py
MCP_REQUIRED_SCOPES = ["mcp:read", "mcp:write"]
```
Only tokens that include **all** required scopes are accepted.
### Custom Auth Provider
For advanced scenarios (e.g., a proprietary auth system), provide a factory function. This takes precedence over all built-in JWT configuration:
```python
# superset_config.py
def my_custom_auth_factory(app):
"""Return a FastMCP auth provider instance."""
from fastmcp.server.auth.providers.jwt import JWTVerifier
return JWTVerifier(
jwks_uri="https://my-auth.example.com/.well-known/jwks.json",
issuer="https://my-auth.example.com/",
audience="superset-mcp",
)
MCP_AUTH_FACTORY = my_custom_auth_factory
```
---
## Connecting AI Clients
### Claude Desktop
**Local development (no auth):**
```json
{
"mcpServers": {
"superset": {
"url": "http://localhost:5008/mcp"
}
}
}
```
**With JWT authentication:**
```json
{
"mcpServers": {
"superset": {
"command": "npx",
"args": [
"-y",
"mcp-remote@latest",
"http://your-superset-host:5008/mcp",
"--header",
"Authorization: Bearer YOUR_TOKEN"
]
}
}
}
```
### Claude Code (CLI)
Add to your project's `.mcp.json`:
```json
{
"mcpServers": {
"superset": {
"type": "url",
"url": "http://localhost:5008/mcp"
}
}
}
```
With authentication:
```json
{
"mcpServers": {
"superset": {
"type": "url",
"url": "http://localhost:5008/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}
```
### Claude Web (claude.ai)
1. Open [claude.ai](https://claude.ai)
2. Click the **+** button (or your profile icon)
3. Select **Connectors**
4. Click **Manage Connectors** > **Add custom connector**
5. Enter a name and your MCP URL (e.g., `https://your-superset-host/mcp`)
6. Click **Add**
:::info
Custom connectors on Claude Web require a Pro, Max, Team, or Enterprise plan.
:::
### ChatGPT
1. Click your profile icon > **Settings** > **Apps and Connectors**
2. Enable **Developer Mode** in Advanced Settings
3. In the chat composer, press **+** > **Add sources** > **App** > **Connect more** > **Create app**
4. Enter a name and your MCP server URL
5. Click **I understand and continue**
:::info
ChatGPT MCP connectors require a Pro, Team, Enterprise, or Edu plan.
:::
### Direct HTTP requests
Call the MCP server directly with any HTTP client:
```bash
curl -X POST http://localhost:5008/mcp \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
-d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
```
---
## Deployment
### Single Process
The simplest setup: run the MCP server alongside Superset on the same host.
```mermaid
flowchart TD
subgraph host["Host / VM"]
direction TB
S["Superset<br/>:8088"] --> DB[("Postgres")]
M["MCP Server<br/>:5008"] --> DB
end
C["AI Client"] -- "HTTPS" --> P["Reverse Proxy<br/>(Nginx / Caddy)"]
U["Browser"] -- "HTTPS" --> P
P -- ":8088" --> S
P -- ":5008/mcp" --> M
```
**superset_config.py:**
```python
MCP_SERVICE_HOST = "0.0.0.0"
MCP_SERVICE_PORT = 5008
MCP_DEV_USERNAME = "admin" # or enable JWT auth
# If behind a reverse proxy, set the public-facing URL so
# MCP-generated links (chart previews, SQL Lab URLs) resolve correctly:
MCP_SERVICE_URL = "https://superset.example.com"
```
**Start both processes:**
```bash
# Terminal 1 -- Superset web server
superset run -h 0.0.0.0 -p 8088
# Terminal 2 -- MCP server
superset mcp run --host 0.0.0.0 --port 5008
```
**Nginx reverse proxy with TLS:**
```nginx
server {
listen 443 ssl;
server_name superset.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# Superset web UI
location / {
proxy_pass http://127.0.0.1:8088;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# MCP endpoint
location /mcp {
proxy_pass http://127.0.0.1:5008/mcp;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Authorization $http_authorization;
}
}
```
### Docker Compose
Run Superset and the MCP server as separate containers sharing the same config:
```yaml
# docker-compose.yml
services:
superset:
image: apache/superset:latest
ports:
- "8088:8088"
volumes:
- ./superset_config.py:/app/superset_config.py
environment:
- SUPERSET_CONFIG_PATH=/app/superset_config.py
mcp:
image: apache/superset:latest
command: ["superset", "mcp", "run", "--host", "0.0.0.0", "--port", "5008"]
ports:
- "5008:5008"
volumes:
- ./superset_config.py:/app/superset_config.py
environment:
- SUPERSET_CONFIG_PATH=/app/superset_config.py
depends_on:
- superset
```
Both containers share the same `superset_config.py`, so authentication settings, database connections, and feature flags stay in sync.
### Multi-Pod (Kubernetes)
For high-availability deployments, configure Redis so that replicas share session state:
```mermaid
flowchart TD
LB["Load Balancer"] --> M1["MCP Pod 1"]
LB --> M2["MCP Pod 2"]
LB --> M3["MCP Pod 3"]
M1 --> R[("Redis<br/>(session store)")]
M2 --> R
M3 --> R
M1 --> DB[("Postgres")]
M2 --> DB
M3 --> DB
```
**superset_config.py:**
```python
MCP_STORE_CONFIG = {
"enabled": True,
"CACHE_REDIS_URL": "redis://redis-host:6379/0",
"event_store_max_events": 100,
"event_store_ttl": 3600,
}
```
When `CACHE_REDIS_URL` is set, the MCP server uses a Redis-backed EventStore for session management, allowing replicas to share state. Without Redis, each pod manages its own in-memory sessions and stateful MCP interactions may fail when requests hit different replicas.
---
## Configuration Reference
All MCP settings go in `superset_config.py`. Defaults are defined in `superset/mcp_service/mcp_config.py`.
### Core
| Setting | Default | Description |
|---------|---------|-------------|
| `MCP_SERVICE_HOST` | `"localhost"` | Host the MCP server binds to |
| `MCP_SERVICE_PORT` | `5008` | Port the MCP server binds to |
| `MCP_SERVICE_URL` | `None` | Public base URL for MCP-generated links (set this when behind a reverse proxy) |
| `MCP_DEBUG` | `False` | Enable debug logging |
| `MCP_DEV_USERNAME` | -- | Superset username for development mode (no auth) |
### Authentication
| Setting | Default | Description |
|---------|---------|-------------|
| `MCP_AUTH_ENABLED` | `False` | Enable JWT authentication |
| `MCP_JWT_ALGORITHM` | `"RS256"` | JWT signing algorithm (`RS256` or `HS256`) |
| `MCP_JWKS_URI` | `None` | JWKS endpoint URL (RS256) |
| `MCP_JWT_PUBLIC_KEY` | `None` | Static RSA public key string (RS256) |
| `MCP_JWT_SECRET` | `None` | Shared secret string (HS256) |
| `MCP_JWT_ISSUER` | `None` | Expected `iss` claim |
| `MCP_JWT_AUDIENCE` | `None` | Expected `aud` claim |
| `MCP_REQUIRED_SCOPES` | `[]` | Required JWT scopes |
| `MCP_JWT_DEBUG_ERRORS` | `False` | Log detailed JWT errors server-side (never exposed in HTTP responses per RFC 6750) |
| `MCP_AUTH_FACTORY` | `None` | Custom auth provider factory `(flask_app) -> auth_provider`. Takes precedence over built-in JWT |
### Response Size Guard
Limits response sizes to prevent exceeding LLM context windows:
```python
MCP_RESPONSE_SIZE_CONFIG = {
"enabled": True,
"token_limit": 25000,
"warn_threshold_pct": 80,
"excluded_tools": [
"health_check",
"get_chart_preview",
"generate_explore_link",
"open_sql_lab_with_context",
],
}
```
| Key | Default | Description |
|-----|---------|-------------|
| `enabled` | `True` | Enable response size checking |
| `token_limit` | `25000` | Maximum estimated token count per response |
| `warn_threshold_pct` | `80` | Warn when response exceeds this percentage of the limit |
| `excluded_tools` | See above | Tools exempt from size checking (e.g., tools that return URLs, not data) |
### Caching
Optional response caching for read-heavy workloads. Requires Redis when used with multiple replicas.
```python
MCP_CACHE_CONFIG = {
"enabled": False,
"CACHE_KEY_PREFIX": None,
"list_tools_ttl": 300, # 5 min
"list_resources_ttl": 300,
"list_prompts_ttl": 300,
"read_resource_ttl": 3600, # 1 hour
"get_prompt_ttl": 3600,
"call_tool_ttl": 3600,
"max_item_size": 1048576, # 1 MB
"excluded_tools": [
"execute_sql",
"generate_dashboard",
"generate_chart",
"update_chart",
],
}
```
| Key | Default | Description |
|-----|---------|-------------|
| `enabled` | `False` | Enable response caching |
| `CACHE_KEY_PREFIX` | `None` | Optional prefix for cache keys (useful for shared Redis) |
| `list_tools_ttl` | `300` | Cache TTL in seconds for `tools/list` |
| `list_resources_ttl` | `300` | Cache TTL for `resources/list` |
| `list_prompts_ttl` | `300` | Cache TTL for `prompts/list` |
| `read_resource_ttl` | `3600` | Cache TTL for `resources/read` |
| `get_prompt_ttl` | `3600` | Cache TTL for `prompts/get` |
| `call_tool_ttl` | `3600` | Cache TTL for `tools/call` |
| `max_item_size` | `1048576` | Maximum cached item size in bytes (1 MB) |
| `excluded_tools` | See above | Tools that are never cached (mutating or non-deterministic) |
### Redis Store (Multi-Pod)
Enables Redis-backed session and event storage for multi-replica deployments:
```python
MCP_STORE_CONFIG = {
"enabled": False,
"CACHE_REDIS_URL": None,
"event_store_max_events": 100,
"event_store_ttl": 3600,
}
```
| Key | Default | Description |
|-----|---------|-------------|
| `enabled` | `False` | Enable Redis-backed store |
| `CACHE_REDIS_URL` | `None` | Redis connection URL (e.g., `redis://redis-host:6379/0`) |
| `event_store_max_events` | `100` | Maximum events retained per session |
| `event_store_ttl` | `3600` | Event TTL in seconds |
### Session & CSRF
These values are flat-merged into the Flask app config used by the MCP server process:
```python
MCP_SESSION_CONFIG = {
"SESSION_COOKIE_HTTPONLY": True,
"SESSION_COOKIE_SECURE": False,
"SESSION_COOKIE_SAMESITE": "Lax",
"SESSION_COOKIE_NAME": "superset_session",
"PERMANENT_SESSION_LIFETIME": 86400,
}
MCP_CSRF_CONFIG = {
"WTF_CSRF_ENABLED": True,
"WTF_CSRF_TIME_LIMIT": None,
}
```
---
## Troubleshooting
### Server won't start
- Verify `fastmcp` is installed: `pip install fastmcp`
- Check that `MCP_DEV_USERNAME` is set if auth is disabled -- the server requires a user identity
- Confirm the port is not already in use: `lsof -i :5008`
### 401 Unauthorized
- Verify your JWT token has not expired (`exp` claim)
- Check that `MCP_JWT_ISSUER` and `MCP_JWT_AUDIENCE` match the token's `iss` and `aud` claims exactly
- For RS256 with JWKS: confirm the JWKS URI is reachable from the MCP server
- For RS256 with static key: confirm the public key string includes the `BEGIN`/`END` markers
- For HS256: confirm the secret matches between the token issuer and `MCP_JWT_SECRET`
- Enable `MCP_JWT_DEBUG_ERRORS = True` for detailed server-side logging (errors are never leaked to the client)
### Tool not found
- Ensure the MCP server and Superset share the same `superset_config.py`
- Check server logs at startup -- tool registration errors are logged with the tool name and reason
### Client can't connect
- Verify the MCP server URL is reachable from the client machine
- For Claude Desktop: fully quit the app (not just close the window) and restart after config changes
- For remote access: ensure your firewall and reverse proxy allow traffic to the MCP port
- Confirm the URL path ends with `/mcp` (e.g., `http://localhost:5008/mcp`)
### Permission errors on tool calls
- The MCP server enforces Superset's RBAC permissions -- the authenticated user must have the required roles
- In development mode, ensure `MCP_DEV_USERNAME` maps to a user with appropriate roles (e.g., Admin)
- Check `superset/security/manager.py` for the specific permission tuples required by each tool domain (e.g., `("can_execute_sql_query", "SQLLab")`)
### Response too large
- If a tool call returns an error about exceeding token limits, the response size guard is blocking an oversized result
- Reduce `page_size` or `limit` parameters, use `select_columns` to exclude large fields, or add filters to narrow results
- To adjust the threshold, change `token_limit` in `MCP_RESPONSE_SIZE_CONFIG`
- To disable the guard entirely, set `MCP_RESPONSE_SIZE_CONFIG = {"enabled": False}`
---
## Security Best Practices
- **Use TLS** for all production MCP endpoints -- place the server behind a reverse proxy with HTTPS
- **Enable JWT authentication** for any internet-facing deployment
- **RBAC enforcement** -- The MCP server respects Superset's role-based access control. Users can only access data their roles permit
- **Secrets management** -- Store `MCP_JWT_SECRET`, database credentials, and API keys in environment variables or a secrets manager, never in config files committed to version control
- **Scoped tokens** -- Use `MCP_REQUIRED_SCOPES` to limit what operations a token can perform
- **Network isolation** -- In Kubernetes, restrict MCP pod network policies to only allow traffic from your AI client endpoints
- Review the **[Security documentation](./security)** for additional extension security guidance
---
## Next Steps
- **[MCP Integration](./mcp)** -- Build custom MCP tools and prompts via Superset extensions
- **[Security](./security)** -- Security best practices for extensions
- **[Deployment](./deployment)** -- Package and deploy Superset extensions

View File

@@ -61,7 +61,7 @@ Prompts provide interactive guidance and context to AI agents. They help agents
The simplest way to create an MCP tool is using the `@tool` decorator:
```python
from superset_core.api.mcp import tool
from superset_core.mcp.decorators import tool
@tool
def hello_world() -> dict:
@@ -94,7 +94,7 @@ Here's a more comprehensive example showing best practices:
import random
from datetime import datetime, timezone
from pydantic import BaseModel, Field
from superset_core.api.mcp import tool
from superset_core.mcp.decorators import tool
class RandomNumberRequest(BaseModel):
"""Request schema for random number generation."""
@@ -253,7 +253,7 @@ The AI agent sees your tool's:
Create interactive prompts using the `@prompt` decorator:
```python
from superset_core.api.mcp import prompt
from superset_core.mcp.decorators import prompt
from fastmcp import Context
@prompt("my_extension.workflow_guide")

View File

@@ -43,7 +43,7 @@ Extensions can provide:
## UI Components for Extensions
Extension developers have access to pre-built UI components via `@apache-superset/core/ui`. Browse all available components on the [UI Components](/docs/components/) page and filter by **Extension Compatible** to see components available to extensions.
Extension developers have access to pre-built UI components via `@apache-superset/core/components`. Browse all available components on the [UI Components](/docs/components/) page and filter by **Extension Compatible** to see components available to extensions.
## Next Steps

View File

@@ -64,10 +64,11 @@ Include backend? [Y/n]: Y
```
**Publisher Namespaces**: Extensions use organizational namespaces similar to VS Code extensions, providing collision-safe naming across organizations:
- **NPM package**: `@my-org/hello-world` (scoped package for frontend distribution)
- **Module Federation name**: `myOrg_helloWorld` (collision-safe JavaScript identifier)
- **Backend package**: `my_org-hello_world` (collision-safe Python distribution name)
- **Python namespace**: `superset_extensions.my_org.hello_world`
- **Python namespace**: `my_org.hello_world`
This approach ensures that extensions from different organizations cannot conflict, even if they use the same technical name (e.g., both `acme.dashboard-widgets` and `corp.dashboard-widgets` can coexist).
@@ -78,12 +79,9 @@ my-org.hello-world/
├── extension.json # Extension metadata and configuration
├── backend/ # Backend Python code
│ ├── src/
│ │ └── superset_extensions/
│ │ └── my_org/
│ │ ── __init__.py
│ │ └── hello_world/
│ │ ├── __init__.py
│ │ └── entrypoint.py # Backend registration
│ │ └── my_org/
│ │ └── hello_world/
│ │ ── entrypoint.py # Backend registration
│ └── pyproject.toml
└── frontend/ # Frontend TypeScript/React code
├── src/
@@ -95,7 +93,7 @@ my-org.hello-world/
## Step 3: Configure Extension Metadata
The generated `extension.json` contains the extension's metadata. It is used to identify the extension and declare its backend entry points. Frontend contributions are registered directly in code (see Step 5).
The generated `extension.json` contains the extension's metadata.
```json
{
@@ -104,10 +102,6 @@ The generated `extension.json` contains the extension's metadata. It is used to
"displayName": "Hello World",
"version": "0.1.0",
"license": "Apache-2.0",
"backend": {
"entryPoints": ["superset_extensions.my_org.hello_world.entrypoint"],
"files": ["backend/src/superset_extensions/my_org/hello_world/**/*.py"]
},
"permissions": ["can_read"]
}
```
@@ -117,19 +111,19 @@ The generated `extension.json` contains the extension's metadata. It is used to
- `publisher`: Organizational namespace for the extension
- `name`: Technical identifier (kebab-case)
- `displayName`: Human-readable name shown to users
- `backend.entryPoints`: Python modules to load eagerly when the extension starts
- `backend.files`: Glob patterns for Python source files to include in the bundle
- `permissions`: List of permissions the extension requires
## Step 4: Create Backend API
The CLI generated a basic `backend/src/superset_extensions/my_org/hello_world/entrypoint.py`. We'll create an API endpoint.
The CLI generated a basic `backend/src/my_org/hello_world/entrypoint.py`. We'll create an API endpoint.
**Create `backend/src/superset_extensions/my_org/hello_world/api.py`**
**Create `backend/src/my_org/hello_world/api.py`**
```python
from flask import Response
from flask_appbuilder.api import expose, protect, safe
from superset_core.api.rest_api import RestApi, api
from superset_core.rest_api.api import RestApi
from superset_core.rest_api.decorators import api
@api(
@@ -174,25 +168,23 @@ class HelloWorldAPI(RestApi):
**Key points:**
- Uses [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator with automatic context detection
- Extends `RestApi` from `superset_core.api.rest_api`
- Uses [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator with automatic context detection
- Extends `RestApi` from `superset_core.rest_api.api`
- Uses Flask-AppBuilder decorators (`@expose`, `@protect`, `@safe`)
- Returns responses using `self.response(status_code, result=data)`
- The endpoint will be accessible at `/extensions/my-org/hello-world/message` (automatic extension context)
- OpenAPI docstrings are crucial - Flask-AppBuilder uses them to automatically generate interactive API documentation at `/swagger/v1`, allowing developers to explore endpoints, understand schemas, and test the API directly from the browser
**Update `backend/src/superset_extensions/my_org/hello_world/entrypoint.py`**
**Update `backend/src/my_org/hello_world/entrypoint.py`**
Replace the generated print statement with API import to trigger registration:
```python
# Importing the API class triggers the @api decorator registration
from .api import HelloWorldAPI
print("Hello World extension loaded successfully!")
from .api import HelloWorldAPI # noqa: F401
```
The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator automatically detects extension context and registers your API with proper namespacing.
The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator automatically detects extension context and registers your API with proper namespacing.
## Step 5: Create Frontend Component
@@ -236,52 +228,53 @@ The webpack configuration requires specific settings for Module Federation. Key
**Convention**: Superset always loads extensions by requesting the `./index` module from the Module Federation container. The `exposes` entry must be exactly `'./index': './src/index.tsx'` — do not rename or add additional entries. All API registrations must be reachable from that file. See [Architecture](./architecture#module-federation) for a full explanation.
```javascript
const path = require("path");
const { ModuleFederationPlugin } = require("webpack").container;
const packageConfig = require("./package.json");
const path = require('path');
const { ModuleFederationPlugin } = require('webpack').container;
const packageConfig = require('./package');
const extensionConfig = require('../extension.json');
module.exports = (env, argv) => {
const isProd = argv.mode === "production";
const isProd = argv.mode === 'production';
return {
entry: isProd ? {} : "./src/index.tsx",
mode: isProd ? "production" : "development",
entry: isProd ? {} : './src/index.tsx',
mode: isProd ? 'production' : 'development',
devServer: {
port: 3001,
port: 3000,
headers: {
"Access-Control-Allow-Origin": "*",
'Access-Control-Allow-Origin': '*',
},
},
output: {
filename: isProd ? undefined : "[name].[contenthash].js",
chunkFilename: "[name].[contenthash].js",
clean: true,
path: path.resolve(__dirname, "dist"),
publicPath: `/api/v1/extensions/my-org/hello-world/`,
filename: isProd ? undefined : '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
publicPath: `/api/v1/extensions/${extensionConfig.publisher}/${extensionConfig.name}/`,
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx"],
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
// Map @apache-superset/core imports to window.superset at runtime
externalsType: "window",
externalsType: 'window',
externals: {
"@apache-superset/core": "superset",
'@apache-superset/core': 'superset',
},
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new ModuleFederationPlugin({
name: "myOrg_helloWorld",
filename: "remoteEntry.[contenthash].js",
name: 'myOrg_helloWorld',
filename: 'remoteEntry.[contenthash].js',
exposes: {
"./index": "./src/index.tsx",
'./index': './src/index.tsx',
},
shared: {
react: {
@@ -289,9 +282,14 @@ module.exports = (env, argv) => {
requiredVersion: packageConfig.peerDependencies.react,
import: false, // Use host's React, don't bundle
},
"react-dom": {
'react-dom': {
singleton: true,
requiredVersion: packageConfig.peerDependencies["react-dom"],
requiredVersion: packageConfig.peerDependencies['react-dom'],
import: false,
},
antd: {
singleton: true,
requiredVersion: packageConfig.peerDependencies['antd'],
import: false,
},
},
@@ -306,8 +304,9 @@ module.exports = (env, argv) => {
```json
{
"compilerOptions": {
"baseUrl": ".",
"moduleResolution": "node",
"target": "es5",
"module": "esnext",
"moduleResolution": "node10",
"jsx": "react",
"strict": true,
"esModuleInterop": true,
@@ -332,16 +331,16 @@ const HelloWorldPanel: React.FC = () => {
const [error, setError] = useState<string>('');
useEffect(() => {
const fetchMessage = async () => {
try {
const csrfToken = await authentication.getCSRFToken();
const response = await fetch('/extensions/my-org/hello-world/message', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken!,
},
});
const fetchMessage = async () => {
try {
const csrfToken = await authentication.getCSRFToken();
const response = await fetch('/extensions/my-org/hello-world/message', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken!,
},
});
if (!response.ok) {
throw new Error(`Server returned ${response.status}`);
@@ -496,8 +495,8 @@ Superset will extract and validate the extension metadata, load the assets, regi
Here's what happens when your extension loads:
1. **Superset starts**: Reads `extension.json` and loads the backend entrypoint
2. **Backend registration**: `entrypoint.py` imports your API class, triggering the [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator to register it automatically
1. **Superset starts**: Reads `manifest.json` from the `.supx` bundle and loads the backend entrypoint
2. **Backend registration**: `entrypoint.py` imports your API class, triggering the [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator to register it automatically
3. **Frontend loads**: When SQL Lab opens, Superset fetches the remote entry file
4. **Module Federation**: Webpack loads your extension module and resolves `@apache-superset/core` to `window.superset`
5. **Registration**: The module executes at load time, calling `views.registerView` to register your panel

View File

@@ -30,15 +30,15 @@ This page serves as a registry of community-created Superset extensions. These e
| Name | Description | Author | Preview |
| ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Extensions API Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api_explorer) | A SQL Lab panel that demonstrates the Extensions API by providing an interactive explorer for testing commands like getTabs, getCurrentTab, and getDatabases. Useful for extension developers to understand and experiment with the available APIs. | Michael S. Molina | <a href="/img/extensions/api-explorer.png" target="_blank"><img src="/img/extensions/api-explorer.png" alt="Extensions API Explorer" width="120" /></a> |
| [Extensions API Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api-explorer) | A SQL Lab panel that demonstrates the Extensions API by providing an interactive explorer for testing commands like getTabs, getCurrentTab, and getDatabases. Useful for extension developers to understand and experiment with the available APIs. | Michael S. Molina | <a href="/img/extensions/api-explorer.png" target="_blank"><img src="/img/extensions/api-explorer.png" alt="Extensions API Explorer" width="120" /></a> |
| [SQL Query Flow Visualizer](https://github.com/msyavuz/superset-sql-visualizer) | A SQL Lab panel that transforms SQL queries into interactive flow diagrams, helping developers and analysts understand query execution paths and data relationships. | Mehmet Salih Yavuz | <a href="/img/extensions/sql-flow-visualizer.png" target="_blank"><img src="/img/extensions/sql-flow-visualizer.png" alt="SQL Flow Visualizer" width="120" /></a> |
| [SQL Lab Export to Google Sheets](https://github.com/michael-s-molina/superset-extensions/tree/main/sqllab_gsheets) | A Superset extension that allows users to export SQL Lab query results directly to Google Sheets. | Michael S. Molina | <a href="/img/extensions/gsheets-export.png" target="_blank"><img src="/img/extensions/gsheets-export.png" alt="SQL Lab Export to Google Sheets" width="120" /></a> |
| [SQL Lab Export to Google Sheets](https://github.com/michael-s-molina/superset-extensions/tree/main/sqllab-gsheets) | A Superset extension that allows users to export SQL Lab query results directly to Google Sheets. | Michael S. Molina | <a href="/img/extensions/gsheets-export.png" target="_blank"><img src="/img/extensions/gsheets-export.png" alt="SQL Lab Export to Google Sheets" width="120" /></a> |
| [SQL Lab Export to Parquet](https://github.com/rusackas/superset-extensions/tree/main/sqllab_parquet) | Export SQL Lab query results directly to Apache Parquet format with Snappy compression. | Evan Rusackas | <a href="/img/extensions/parquet-export.png" target="_blank"><img src="/img/extensions/parquet-export.png" alt="SQL Lab Export to Parquet" width="120" /></a> |
| [SQL Lab Query Comparison](https://github.com/michael-s-molina/superset-extensions/tree/main/query_comparison) | A SQL Lab extension that enables side-by-side comparison of query results across different tabs, with GitHub-style diff visualization showing added/removed rows and columns. | Michael S. Molina | <a href="/img/extensions/query-comparison.png" target="_blank"><img src="/img/extensions/query-comparison.png" alt="Query Comparison" width="120" /></a> |
| [SQL Lab Result Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result_stats) | A SQL Lab extension that automatically computes statistics for query results, providing type-aware analysis including numeric metrics (min, max, mean, median, std dev), string analysis (length, empty counts), and date range information. | Michael S. Molina | <a href="/img/extensions/result-stats.png" target="_blank"><img src="/img/extensions/result-stats.png" alt="Result Stats" width="120" /></a> |
| [SQL Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/sql_snippets) | A SQL Lab extension that provides reusable SQL code snippets, enabling quick insertion of commonly used code blocks such as license headers, author information, and frequently used SQL patterns. | Michael S. Molina | <a href="/img/extensions/sql-snippets.png" target="_blank"><img src="/img/extensions/sql-snippets.png" alt="SQL Snippets" width="120" /></a> |
| [SQL Lab Query Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query_estimator) | A SQL Lab panel that analyzes query execution plans to estimate resource impact, detect performance issues like Cartesian products and high-cost operations, and visualize the query plan tree. | Michael S. Molina | <a href="/img/extensions/query-estimator.png" target="_blank"><img src="/img/extensions/query-estimator.png" alt="Query Estimator" width="120" /></a> |
| [Editors Bundle](https://github.com/michael-s-molina/superset-extensions/tree/main/editors_bundle) | A Superset extension that demonstrates how to provide custom code editors for different languages. This extension showcases the editor contribution system by registering alternative editors that can replace Superset's default Ace editor. | Michael S. Molina | <a href="/img/extensions/editors-bundle.png" target="_blank"><img src="/img/extensions/editors-bundle.png" alt="Editors Bundle" width="120" /></a> |
| [SQL Lab Query Comparison](https://github.com/michael-s-molina/superset-extensions/tree/main/query-comparison) | A SQL Lab extension that enables side-by-side comparison of query results across different tabs, with GitHub-style diff visualization showing added/removed rows and columns. | Michael S. Molina | <a href="/img/extensions/query-comparison.png" target="_blank"><img src="/img/extensions/query-comparison.png" alt="Query Comparison" width="120" /></a> |
| [SQL Lab Result Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result-stats) | A SQL Lab extension that automatically computes statistics for query results, providing type-aware analysis including numeric metrics (min, max, mean, median, std dev), string analysis (length, empty counts), and date range information. | Michael S. Molina | <a href="/img/extensions/result-stats.png" target="_blank"><img src="/img/extensions/result-stats.png" alt="Result Stats" width="120" /></a> |
| [Editor Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/editor-snippets) | A SQL Lab extension for managing and inserting reusable code snippets into the editor, with server-side persistence per user. | Michael S. Molina | <a href="/img/extensions/editor-snippets.png" target="_blank"><img src="/img/extensions/editor-snippets.png" alt="Editor Snippets" width="120" /></a> |
| [SQL Lab Query Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query-estimator) | A SQL Lab panel that analyzes query execution plans to estimate resource impact, detect performance issues like Cartesian products and high-cost operations, and visualize the query plan tree. | Michael S. Molina | <a href="/img/extensions/query-estimator.png" target="_blank"><img src="/img/extensions/query-estimator.png" alt="Query Estimator" width="120" /></a> |
| [Editors Bundle](https://github.com/michael-s-molina/superset-extensions/tree/main/editors-bundle) | A Superset extension that demonstrates how to provide custom code editors for different languages. This extension showcases the editor contribution system by registering alternative editors that can replace Superset's default Ace editor. | Michael S. Molina | <a href="/img/extensions/editors-bundle.png" target="_blank"><img src="/img/extensions/editors-bundle.png" alt="Editors Bundle" width="120" /></a> |
## How to Add Your Extension

View File

@@ -50,7 +50,7 @@ When GTF is considered stable, it will replace legacy Celery tasks for built-in
### Define a Task
```python
from superset_core.api.tasks import task, get_context
from superset_core.tasks.decorators import task, get_context
@task
def process_data(dataset_id: int) -> None:
@@ -245,7 +245,8 @@ Always implement an abort handler for long-running tasks. This allows users to c
Set a timeout to automatically abort tasks that run too long:
```python
from superset_core.api.tasks import task, get_context, TaskOptions
from superset_core.tasks.decorators import task, get_context
from superset_core.tasks.types import TaskOptions
# Set default timeout in decorator
@task(timeout=300) # 5 minutes
@@ -299,7 +300,7 @@ Timeouts require an abort handler to be effective. Without one, the timeout trig
Use `task_key` to prevent duplicate task execution:
```python
from superset_core.api.tasks import TaskOptions
from superset_core.tasks.types import TaskOptions
# Without key - creates new task each time (random UUID)
task1 = my_task.schedule(x=1)
@@ -331,7 +332,8 @@ print(task2.status) # "success" (terminal status)
## Task Scopes
```python
from superset_core.api.tasks import task, TaskScope
from superset_core.tasks.decorators import task
from superset_core.tasks.types import TaskScope
@task # Private by default
def private_task(): ...

View File

@@ -52,6 +52,7 @@ module.exports = {
'extensions/development',
'extensions/deployment',
'extensions/mcp',
'extensions/mcp-server',
'extensions/security',
'extensions/tasks',
'extensions/registry',

View File

@@ -55,20 +55,20 @@
"@fontsource/inter": "^5.2.8",
"@mdx-js/react": "^3.1.1",
"@saucelabs/theme-github-codeblock": "^0.3.0",
"@storybook/addon-docs": "^8.6.17",
"@storybook/addon-docs": "^8.6.18",
"@storybook/blocks": "^8.6.15",
"@storybook/channels": "^8.6.17",
"@storybook/client-logger": "^8.6.17",
"@storybook/components": "^8.6.17",
"@storybook/core": "^8.6.17",
"@storybook/core-events": "^8.6.17",
"@storybook/channels": "^8.6.18",
"@storybook/client-logger": "^8.6.18",
"@storybook/components": "^8.6.18",
"@storybook/core": "^8.6.18",
"@storybook/core-events": "^8.6.18",
"@storybook/csf": "^0.1.13",
"@storybook/docs-tools": "^8.6.17",
"@storybook/preview-api": "^8.6.17",
"@storybook/docs-tools": "^8.6.18",
"@storybook/preview-api": "^8.6.18",
"@storybook/theming": "^8.6.15",
"@superset-ui/core": "^0.20.4",
"@swc/core": "^1.15.17",
"antd": "^6.3.1",
"antd": "^6.3.2",
"baseline-browser-mapping": "^2.10.0",
"caniuse-lite": "^1.0.30001775",
"docusaurus-plugin-openapi-docs": "^4.6.0",
@@ -85,7 +85,7 @@
"react-table": "^7.8.0",
"remark-import-partial": "^0.0.2",
"reselect": "^5.1.1",
"storybook": "^8.6.17",
"storybook": "^8.6.18",
"swagger-ui-react": "^5.32.0",
"swc-loader": "^0.2.7",
"tinycolor2": "^1.4.2",
@@ -107,7 +107,7 @@
"prettier": "^3.8.1",
"typescript": "~5.9.3",
"typescript-eslint": "^8.56.1",
"webpack": "^5.105.3"
"webpack": "^5.105.4"
},
"browserslist": {
"production": [

View File

@@ -152,8 +152,8 @@ const SOURCES = [
{
name: 'Extension Components',
path: 'packages/superset-core/src',
importPrefix: '@apache-superset/core/ui',
docImportPrefix: '@apache-superset/core/ui',
importPrefix: '@apache-superset/core/components',
docImportPrefix: '@apache-superset/core/components',
category: 'extension',
enabled: true,
extensionCompatible: true,
@@ -1155,7 +1155,7 @@ Help improve it by [editing the story file](https://github.com/apache/superset/e
const CATEGORY_LABELS = {
ui: { title: 'Core Components', sidebarLabel: 'Core Components', description: 'Buttons, inputs, modals, selects, and other fundamental UI elements.' },
'design-system': { title: 'Layout Components', sidebarLabel: 'Layout Components', description: 'Grid, Layout, Table, Flex, Space, and container components for page structure.' },
extension: { title: 'Extension Components', sidebarLabel: 'Extension Components', description: 'Components available to extension developers via @apache-superset/core/ui.' },
extension: { title: 'Extension Components', sidebarLabel: 'Extension Components', description: 'Components available to extension developers via @apache-superset/core/components.' },
};
/**
@@ -1463,7 +1463,7 @@ function generateExtensionTypeDeclarations(extensionComponents) {
*/
/**
* Type declarations for @apache-superset/core/ui
* Type declarations for @apache-superset/core/components
*
* AUTO-GENERATED by scripts/generate-superset-components.mjs
* Do not edit manually - regenerate by running: yarn generate:superset-components

View File

@@ -39,7 +39,7 @@ function getComponentRegistry() {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const SupersetComponents = require('@superset/components');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const CoreUI = require('@apache-superset/core/ui');
const CoreUI = require('@apache-superset/core/components');
// Build component registry with antd as base fallback layer.
// Some Superset components (e.g., Typography) use styled-components that may
@@ -65,7 +65,7 @@ function getProviders() {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { themeObject } = require('@apache-superset/core/ui');
const { themeObject } = require('@apache-superset/core/theme');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { App, ConfigProvider } = require('antd');

View File

@@ -0,0 +1,120 @@
/**
* 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.
*
* Swizzled from docusaurus-theme-openapi-docs to fix SSG crash.
*
* The original component calls useTypedSelector (Redux) at the top level,
* which fails during static site generation because no Redux store is
* available. This version moves the hook into a browser-only child component
* so SSG can render the page without a store context.
*/
import React from "react";
import BrowserOnly from "@docusaurus/BrowserOnly";
import { useSelector } from "react-redux";
interface ServerVariable {
default?: string;
}
interface ServerValue {
url: string;
variables?: Record<string, ServerVariable>;
}
interface StoreState {
server: { value: ServerValue | null };
}
function colorForMethod(method: string) {
switch (method.toLowerCase()) {
case "get":
return "primary";
case "post":
return "success";
case "delete":
return "danger";
case "put":
return "info";
case "patch":
return "warning";
case "head":
return "secondary";
case "event":
return "secondary";
default:
return undefined;
}
}
export interface Props {
method: string;
path: string;
context?: "endpoint" | "callback";
}
// Inner component rendered only in the browser, where the Redux store exists.
function ServerUrl() {
const serverValue = useSelector((state: StoreState) => state.server.value);
if (serverValue && serverValue.variables) {
let serverUrlWithVariables = serverValue.url.replace(/\/$/, "");
Object.keys(serverValue.variables).forEach((variable) => {
serverUrlWithVariables = serverUrlWithVariables.replace(
`{${variable}}`,
serverValue.variables?.[variable].default ?? ""
);
});
return <>{serverUrlWithVariables}</>;
}
if (serverValue && serverValue.url) {
return <>{serverValue.url}</>;
}
return null;
}
function MethodEndpoint({ method, path, context }: Props) {
const renderServerUrl = () => {
if (context === "callback") {
return "";
}
return <BrowserOnly>{() => <ServerUrl />}</BrowserOnly>;
};
return (
<>
<pre className="openapi__method-endpoint">
<span className={"badge badge--" + colorForMethod(method)}>
{method === "event" ? "Webhook" : method.toUpperCase()}
</span>{" "}
{method !== "event" && (
<h2 className="openapi__method-endpoint-path">
{renderServerUrl()}
{`${path.replace(/{([a-z0-9-_]+)}/gi, ":$1")}`}
</h2>
)}
</pre>
<div className="openapi__divider" />
</>
);
}
export default MethodEndpoint;

View File

@@ -35,7 +35,7 @@ function getThemeWrapper() {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { themeObject } = require('@apache-superset/core/ui');
const { themeObject } = require('@apache-superset/core/theme');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { App } = require('antd');

View File

@@ -50,7 +50,7 @@ if (isBrowser) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const SupersetComponents = require('@superset/components');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { Alert } = require('@apache-superset/core/ui');
const { Alert } = require('@apache-superset/core/components');
console.log('[ReactLiveScope] SupersetComponents keys:', Object.keys(SupersetComponents || {}).slice(0, 10));
console.log('[ReactLiveScope] Has Button?', 'Button' in (SupersetComponents || {}));

View File

@@ -18,7 +18,7 @@
*/
/**
* Type declarations for @apache-superset/core/ui
* Type declarations for @apache-superset/core/components
*
* AUTO-GENERATED by scripts/generate-superset-components.mjs
* Do not edit manually - regenerate by running: yarn generate:superset-components

View File

@@ -156,9 +156,9 @@ export default function webpackExtendPlugin(): Plugin<void> {
// to source so the docs build doesn't depend on pre-built lib/ artifacts.
// More specific sub-path aliases must come first; webpack matches the
// longest prefix.
'@apache-superset/core/ui': path.resolve(
'@apache-superset/core/components': path.resolve(
__dirname,
'../../superset-frontend/packages/superset-core/src/ui',
'../../superset-frontend/packages/superset-core/src/components',
),
'@apache-superset/core/api/core': path.resolve(
__dirname,

View File

@@ -51,6 +51,12 @@
"lifecycle": "development",
"description": "Enable Superset extensions for custom functionality without modifying core"
},
{
"name": "GRANULAR_EXPORT_CONTROLS",
"default": false,
"lifecycle": "development",
"description": "Enable granular export controls (can_export_data, can_export_image, can_copy_clipboard) instead of the single can_csv permission"
},
{
"name": "MATRIXIFY",
"default": false,

BIN
docs/static/img/databases/datastore.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 358 KiB

After

Width:  |  Height:  |  Size: 358 KiB

View File

@@ -14,10 +14,10 @@
"paths": {
"@superset-ui/core": ["../superset-frontend/packages/superset-ui-core/src"],
"@superset-ui/core/*": ["../superset-frontend/packages/superset-ui-core/src/*"],
// Types for @apache-superset/core/ui are auto-generated by scripts/generate-superset-components.mjs
// Types for @apache-superset/core/components are auto-generated by scripts/generate-superset-components.mjs
// Runtime resolution uses webpack alias pointing to actual source (see src/webpack.extend.ts)
// Using /ui path matches the established pattern used throughout the Superset codebase
"@apache-superset/core/ui": ["./src/types/apache-superset-core"],
"@apache-superset/core/components": ["./src/types/apache-superset-core"],
"*": ["src/*", "node_modules/*"]
}
},

View File

@@ -195,19 +195,19 @@
dependencies:
"@ant-design/fast-color" "^3.0.0"
"@ant-design/cssinjs-utils@^2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.1.tgz#c70d86206204e882073a0fe4969a5ddf154c6915"
integrity sha512-RKxkj5pGFB+FkPJ5NGhoX3DK3xsv0pMltha7Ei1AnY3tILeq38L7tuhaWDPQI/5nlPxOog44wvqpNyyGcUsNMg==
"@ant-design/cssinjs-utils@^2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.2.tgz#a4a57e02dd7e7c3732ab7f1df406df98b5542d12"
integrity sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==
dependencies:
"@ant-design/cssinjs" "^2.1.0"
"@ant-design/cssinjs" "^2.1.2"
"@babel/runtime" "^7.23.2"
"@rc-component/util" "^1.4.0"
"@ant-design/cssinjs@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@ant-design/cssinjs/-/cssinjs-2.1.0.tgz#081394937f86aefe55e35198019d0483f405a484"
integrity sha512-eZFrPCnrYrF3XtL7qA4L75P0qA3TtZta8H3Yggy7UYFh8gZgu5bSMNF+v4UVCzGxzYmx8ZvPdgOce0BJ6PsW9g==
"@ant-design/cssinjs@^2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz#0219e37afdd957248b10da366febae1e4001c952"
integrity sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==
dependencies:
"@babel/runtime" "^7.11.1"
"@emotion/hash" "^0.8.0"
@@ -2964,12 +2964,12 @@
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
"@rc-component/color-picker@~3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@rc-component/color-picker/-/color-picker-3.1.0.tgz#437586ea2fc27862e7429a754cf85e519e05f461"
integrity sha512-o7Vavj7yyfVxFmeynXf0fCHVlC0UTE9al74c6nYuLck+gjuVdQNWSVXR8Efq/mmWFy7891SCOsfaPq6Eqe1s/g==
"@rc-component/color-picker@~3.1.1":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@rc-component/color-picker/-/color-picker-3.1.1.tgz#0a00411457e697cf9320e945762a4b08f71938f9"
integrity sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==
dependencies:
"@ant-design/fast-color" "^3.0.0"
"@ant-design/fast-color" "^3.0.1"
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
@@ -3009,10 +3009,10 @@
"@rc-component/util" "^1.2.1"
clsx "^2.1.1"
"@rc-component/form@~1.6.2":
version "1.6.2"
resolved "https://registry.npmjs.org/@rc-component/form/-/form-1.6.2.tgz"
integrity sha512-OgIn2RAoaSBqaIgzJf/X6iflIa9LpTozci1lagLBdURDFhGA370v0+T0tXxOi8YShMjTha531sFhwtnrv+EJaQ==
"@rc-component/form@~1.7.1":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@rc-component/form/-/form-1.7.1.tgz#baf18de01e649415c39e895a2c2fc9c61e1f2e23"
integrity sha512-Uhw0FPvJ+Ko4xBxhvziqmqzIuO0YvVBzVyFGNAI9fMCz4r4DfrYK6PRIN6CkFqM0vdAX9sr4JGA1/h/VzpA1cA==
dependencies:
"@rc-component/async-validator" "^5.1.0"
"@rc-component/util" "^1.6.2"
@@ -3075,15 +3075,7 @@
dependencies:
"@babel/runtime" "^7.18.0"
"@rc-component/motion@^1.0.0", "@rc-component/motion@^1.1.3", "@rc-component/motion@^1.1.4":
version "1.1.6"
resolved "https://registry.npmjs.org/@rc-component/motion/-/motion-1.1.6.tgz"
integrity sha512-aEQobs/YA0kqRvHIPjQvOytdtdRVyhf/uXAal4chBjxDu6odHckExJzjn2D+Ju1aKK6hx3pAs6BXdV9+86xkgQ==
dependencies:
"@rc-component/util" "^1.2.0"
clsx "^2.1.1"
"@rc-component/motion@^1.3.1":
"@rc-component/motion@^1.0.0", "@rc-component/motion@^1.1.3", "@rc-component/motion@^1.1.4", "@rc-component/motion@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@rc-component/motion/-/motion-1.3.1.tgz#1e56b06841ee677261251e6e69fedc8d73e65b22"
integrity sha512-Wo1mkd0tCcHtvYvpPOmlYJz546z16qlsiwaygmW7NPJpOZOF9GBjhGzdzZSsC2lEJ1IUkWLF4gMHlRA1aSA+Yw==
@@ -3184,21 +3176,10 @@
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
"@rc-component/select@~1.6.0":
version "1.6.5"
resolved "https://registry.yarnpkg.com/@rc-component/select/-/select-1.6.5.tgz#69276239c6ac0884a67597961b0224c4ad0bc4ca"
integrity sha512-Cx+/OYEorXlPQ6ZFDro3HbalPZLlJWagvGtl8DGYO4losXM6gw43qbsxWqU1c3XOQVIOUDBlr7dSksSNMj8kXg==
dependencies:
"@rc-component/overflow" "^1.0.0"
"@rc-component/trigger" "^3.0.0"
"@rc-component/util" "^1.3.0"
"@rc-component/virtual-list" "^1.0.1"
clsx "^2.1.1"
"@rc-component/select@~1.6.12":
version "1.6.12"
resolved "https://registry.yarnpkg.com/@rc-component/select/-/select-1.6.12.tgz#24312b31aad2a78ce1ec0062b15f56428bddab8f"
integrity sha512-jYXAglYdOb54BrpWAcjjhdBP16NyCv/HbEaWFMbEHZQAJVmGHPAtmBqbFuPPuvInAVsIwLbCj4Agag9udOamiQ==
"@rc-component/select@~1.6.0", "@rc-component/select@~1.6.14":
version "1.6.14"
resolved "https://registry.yarnpkg.com/@rc-component/select/-/select-1.6.14.tgz#61028c0abe02d2a909935b5cb586374968196c96"
integrity sha512-T1IWeLlSas7Z/igZtPtJ/bweCxMMkXIGKQBtnigK+I/n1AVNjCs+ZdL3Fj42mq3uqm4sd1uzeQLZkdCqR26ADw==
dependencies:
"@rc-component/overflow" "^1.0.0"
"@rc-component/trigger" "^3.0.0"
@@ -3524,53 +3505,53 @@
resolved "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz"
integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==
"@storybook/addon-docs@^8.6.17":
version "8.6.17"
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-8.6.17.tgz#3b7fdccebb60bcde62241a2ef2c9e493003003d5"
integrity sha512-zvcSzoYvaZO4l9NxsviDr5vmuq8GVnH4Ap0v+5sSTq192yevm/iQcRnkWYBD9E/Lg5GBeyE+Ml2vjEOK+EPBEg==
"@storybook/addon-docs@^8.6.18":
version "8.6.18"
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-8.6.18.tgz#1910942ecdff4e5cda6352d22bc483f0c2058f61"
integrity sha512-55ADer0yNmmeR928Y3UAv3r4i7bJSd9LwywsQ+lRol/FNe0ZcwLEz31xL+jVsqQFNnDh/imsDIp8aYapGMtfEQ==
dependencies:
"@mdx-js/react" "^3.0.0"
"@storybook/blocks" "8.6.17"
"@storybook/csf-plugin" "8.6.17"
"@storybook/react-dom-shim" "8.6.17"
"@storybook/blocks" "8.6.18"
"@storybook/csf-plugin" "8.6.18"
"@storybook/react-dom-shim" "8.6.18"
react "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
react-dom "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
ts-dedent "^2.0.0"
"@storybook/blocks@8.6.17", "@storybook/blocks@^8.6.15":
version "8.6.17"
resolved "https://registry.yarnpkg.com/@storybook/blocks/-/blocks-8.6.17.tgz#153a9e5ce2b1f2e769f2d095208a303266a85823"
integrity sha512-zuYHH+0egovMrjWRKwOtgVGbz6KALGowPSWBzQ8deTBu6IXfkz6Ce1hRLJPn5S6/jDqqr9xx8vuAiypnRQ98tA==
"@storybook/blocks@8.6.18", "@storybook/blocks@^8.6.15":
version "8.6.18"
resolved "https://registry.yarnpkg.com/@storybook/blocks/-/blocks-8.6.18.tgz#d1bf7e9639a86cdf690bea1c53028be725afb1e8"
integrity sha512-esZv4msPQ9LxgTb8YUIZhhxVMuI6BPi5bkXtk8c7w7sWuAsqsCe/RnVInn7ooUry2gjnD4hd9+8Eqj0b8oTVoA==
dependencies:
"@storybook/icons" "^1.2.12"
ts-dedent "^2.0.0"
"@storybook/channels@^8.6.17":
version "8.6.17"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-8.6.17.tgz#074930ccfc9ce4a6d798f274819b70d2852f0fbe"
integrity sha512-3uwPYVia6MdyeTI2oq46ybpFIZCCjohvzI7zn6NmnRqC8WvZapngLY6OT590eFCrFdgxMszKORUvSsPgtjpnuA==
"@storybook/channels@^8.6.18":
version "8.6.18"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-8.6.18.tgz#21bf4624badc41f343ac7e182ba7a88c5d682bff"
integrity sha512-J/xabOEHfMYEWpdm4gR6HD5IdC0e7OsNvgUEspQjcUMhjMwtGm/EaahwNpRUIxO2tgzKj4zHnflGfPCfTd4PgQ==
"@storybook/client-logger@^8.6.17":
version "8.6.17"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-8.6.17.tgz#43decc0f507dd7daf9310994fd612b25fc6915a5"
integrity sha512-l8vbDNyyR9YfWZzlsupxEeekA/eq4iibBo3gWwr+2G5QfNTGveTQdpgr2m5IL5k+Xjnii22AepmQ4NdjPbJXwA==
"@storybook/client-logger@^8.6.18":
version "8.6.18"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-8.6.18.tgz#21b95c5ecb30475ad5a1fa68c0af603a4199c01b"
integrity sha512-l7x3KkumMcTN+R1ozAqEyAkHpNBonIvicYoTgha/3Dh/tKiBYLLum2AEXbiu0TBJ7EEUfi4AG7eOBBfVdfWqvQ==
"@storybook/components@^8.6.17":
version "8.6.17"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-8.6.17.tgz#67c87f4a5b98999c81f17418cbe2396e6dd216f1"
integrity sha512-0b8xkkuPCNbM8LTOzyfxuo2KdJCHIfu3+QxWBFllXap0eYNHwVeSxE5KERQ/bk2GDCiRzaUbwH9PeLorxOzJJQ==
"@storybook/components@^8.6.18":
version "8.6.18"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-8.6.18.tgz#0e5431f9d84cae29a8b8a406c9ad99406bf2ccb4"
integrity sha512-55yViiZzPS/cPBuOeW4QGxGqrusjXVyxuknmbYCIwDtFyyvI/CgbjXRHdxNBaIjz+IlftxvBmmSaOqFG5+/dkA==
"@storybook/core-events@^8.6.17":
version "8.6.17"
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-8.6.17.tgz#e67d6308e61cd6d7be574c40c605eafe7bb04c74"
integrity sha512-HiKVE2sSbJF6PVFt2DfJtLef1Mc35cN+sf2f8Ay2ibHy2gY1t3/7W1PhYVGt7UpJNOnVZfsmcE3yqGNojct3mw==
"@storybook/core-events@^8.6.18":
version "8.6.18"
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-8.6.18.tgz#aaaf2a544fdb07036a08200692bb88a96d9df651"
integrity sha512-eUVwrcppny/ZYyke/SPVZVuco8wxkQ/0K20nlevSiDkgWZSELii5Ju0/l9Ubnopr9dshoFCYbC7q6liTSpok7A==
"@storybook/core@8.6.17", "@storybook/core@^8.6.17":
version "8.6.17"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-8.6.17.tgz#73af480521333e421413ffdda7a992b3c96b1afb"
integrity sha512-lndZDYIvUddWk54HmgYwE4h2B0JtWt8ztIRAzHRt6ReZZ9QQbmM5b85Qpa+ng4dyQEKc2JAtYD3Du7RRFcpHlw==
"@storybook/core@8.6.18", "@storybook/core@^8.6.18":
version "8.6.18"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-8.6.18.tgz#0ddbec8421715b372419ae5dfefef3df5848386c"
integrity sha512-dRBP2TnX6fGdS0T2mXBHjkS/3Nlu1ra1huovZVFuM67CYMzrhM/3hX/zru1vWSC5rqY93ZaAhjMciPW4pK5mMQ==
dependencies:
"@storybook/theming" "8.6.17"
"@storybook/theming" "8.6.18"
better-opn "^3.0.2"
browser-assert "^1.2.1"
esbuild "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0"
@@ -3582,10 +3563,10 @@
util "^0.12.5"
ws "^8.2.3"
"@storybook/csf-plugin@8.6.17":
version "8.6.17"
resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-8.6.17.tgz#004e25cd408d98a1514d0bf83e02f270c87a2091"
integrity sha512-ouvF/izbKclZxpfnRUkyC5ZVDU7QA0cHhjQnXTDT4F8b0uciQUDw1LosDZy5MXf03BeIDdyBAtzd/ym3wzd+kw==
"@storybook/csf-plugin@8.6.18":
version "8.6.18"
resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-8.6.18.tgz#f92cede49c71d4381187884d72e41ee44d324d3b"
integrity sha512-x1ioz/L0CwaelCkHci3P31YtvwayN3FBftvwQOPbvRh9qeb4Cpz5IdVDmyvSxxYwXN66uAORNoqgjTi7B4/y5Q==
dependencies:
unplugin "^1.3.1"
@@ -3596,30 +3577,30 @@
dependencies:
type-fest "^2.19.0"
"@storybook/docs-tools@^8.6.17":
version "8.6.17"
resolved "https://registry.yarnpkg.com/@storybook/docs-tools/-/docs-tools-8.6.17.tgz#e0beafce8ad36dbadf2e0c3a6bb39ee50ead8c03"
integrity sha512-lnGPEecD2nNrByIGhlJOJEi4/3PM+P5DElsFdJ9EhQwO0rwQhTL+4sdBMOXgwsJj4WrQTBXQ1jr/x0UYrl7Qzg==
"@storybook/docs-tools@^8.6.18":
version "8.6.18"
resolved "https://registry.yarnpkg.com/@storybook/docs-tools/-/docs-tools-8.6.18.tgz#ba79b08a41131f97d9c6970c48651552763acbcf"
integrity sha512-43ggjDA1ZV0FWjMlNBkKC1VWQ6zDQmSj0WWWqivGQdnBt4dufYQFXnbQeFr9Og+3OjZYmr3KTrLCjDiyCGOgjg==
"@storybook/icons@^1.2.12":
version "1.4.0"
resolved "https://registry.npmjs.org/@storybook/icons/-/icons-1.4.0.tgz"
integrity sha512-Td73IeJxOyalzvjQL+JXx72jlIYHgs+REaHiREOqfpo3A2AYYG71AUbcv+lg7mEDIweKVCxsMQ0UKo634c8XeA==
"@storybook/preview-api@^8.6.17":
version "8.6.17"
resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-8.6.17.tgz#92fa66f495c520074c88c3373be73e57f2803a5c"
integrity sha512-vpTCTkw11wXerYnlG5Q0y4SbFqG9O6GhR0hlYgCn3Z9kcHlNjK/xuwd3h4CvwNXxRNWZGT8qYYCLn5gSSrX6fA==
"@storybook/preview-api@^8.6.18":
version "8.6.18"
resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-8.6.18.tgz#2f5eb75c7587035a07670457c09b67208aa16735"
integrity sha512-joXRXh3GdVvzhbfIgmix1xs90p8Q/nja7AhEAC2egn5Pl7SKsIYZUCYI6UdrQANb2myg9P552LKXfPect8llKg==
"@storybook/react-dom-shim@8.6.17":
version "8.6.17"
resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-8.6.17.tgz#68a2e279ac2ce2e37d3f7331a16c5b46cc1c5659"
integrity sha512-bHLsR9b/tiwm9lXbN8kp9XlOgkRXeg84UFwXaWBPu3pOO7vRXukk23SQUpLW+HhjKtCJ3xClSi5uMpse5MpkVQ==
"@storybook/react-dom-shim@8.6.18":
version "8.6.18"
resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-8.6.18.tgz#34bdc010d3c3572fc74fa149f754d185df85044e"
integrity sha512-N4xULcAWZQTUv4jy1/d346Tyb4gufuC3UaLCuU/iVSZ1brYF4OW3ANr+096btbMxY8pR/65lmtoqr5CTGwnBvA==
"@storybook/theming@8.6.17", "@storybook/theming@^8.6.15":
version "8.6.17"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-8.6.17.tgz#0175bbc22cdc262d171168af67fce6a5e3d76a7f"
integrity sha512-IttFvRqozpuzN5MlQEWGOzUA2rZg86688Dyv1d+bjpYcFHtY1X4XyTCGwv1BPTaTsB959oM8R2yoNYWQkABbBA==
"@storybook/theming@8.6.18", "@storybook/theming@^8.6.15":
version "8.6.18"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-8.6.18.tgz#18c66263868bfb00a419772b5460a5714c5e1181"
integrity sha512-n6OEjEtHupa2PdTwWzRepr7cO8NkDd4rgF6BKLitRbujOspLxzMBEqdphs+QLcuiCIgf33SqmEA64QWnbSMhPw==
"@superset-ui/core@^0.20.4":
version "0.20.4"
@@ -5509,12 +5490,7 @@ acorn@^6.1.1:
resolved "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz"
integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==
acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0:
version "8.15.0"
resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz"
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
acorn@^8.16.0:
acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0, acorn@^8.16.0:
version "8.16.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a"
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
@@ -5673,14 +5649,14 @@ ansi-styles@^6.1.0:
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
antd@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/antd/-/antd-6.3.1.tgz#ea035d7b0f836a20938945d5a0eaef172537d89b"
integrity sha512-8pRjvxitZFyrYAtgwml93Km7fCXjw9IeqlmzpIsusRsmO3eWFVrOMum6+0TsGCtR/WrXVnPwfsgrFg3ChzGCeA==
antd@^6.3.2:
version "6.3.2"
resolved "https://registry.yarnpkg.com/antd/-/antd-6.3.2.tgz#ce1a33783d495fcfc77b58b73156ac6249e4fc0a"
integrity sha512-IlMoqaXlq5Bgxi0ANERhAzmDREYyGwr/U7MCVihaUQbE/ZOB3r4ArakUxjA1ULYNDA6K00dawSrB8aalGnZlLA==
dependencies:
"@ant-design/colors" "^8.0.1"
"@ant-design/cssinjs" "^2.1.0"
"@ant-design/cssinjs-utils" "^2.1.1"
"@ant-design/cssinjs" "^2.1.2"
"@ant-design/cssinjs-utils" "^2.1.2"
"@ant-design/fast-color" "^3.0.1"
"@ant-design/icons" "^6.1.0"
"@ant-design/react-slick" "~2.0.0"
@@ -5688,11 +5664,11 @@ antd@^6.3.1:
"@rc-component/cascader" "~1.14.0"
"@rc-component/checkbox" "~2.0.0"
"@rc-component/collapse" "~1.2.0"
"@rc-component/color-picker" "~3.1.0"
"@rc-component/color-picker" "~3.1.1"
"@rc-component/dialog" "~1.8.4"
"@rc-component/drawer" "~1.4.2"
"@rc-component/dropdown" "~1.0.2"
"@rc-component/form" "~1.6.2"
"@rc-component/form" "~1.7.1"
"@rc-component/image" "~1.6.0"
"@rc-component/input" "~1.1.2"
"@rc-component/input-number" "~1.6.2"
@@ -5708,7 +5684,7 @@ antd@^6.3.1:
"@rc-component/rate" "~1.0.1"
"@rc-component/resize-observer" "^1.1.1"
"@rc-component/segmented" "~1.3.0"
"@rc-component/select" "~1.6.12"
"@rc-component/select" "~1.6.14"
"@rc-component/slider" "~1.0.1"
"@rc-component/steps" "~1.2.2"
"@rc-component/switch" "~1.0.3"
@@ -7710,10 +7686,10 @@ encodeurl@~2.0.0:
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
enhanced-resolve@^5.19.0:
version "5.19.0"
resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz"
integrity sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==
enhanced-resolve@^5.20.0:
version "5.20.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz#323c2a70d2aa7fb4bdfd6d3c24dfc705c581295d"
integrity sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.3.0"
@@ -13940,7 +13916,7 @@ serialize-error@^8.1.0:
dependencies:
type-fest "^0.20.2"
serialize-javascript@^6.0.0, serialize-javascript@^6.0.1, serialize-javascript@^6.0.2:
serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
version "6.0.2"
resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz"
integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==
@@ -14332,12 +14308,12 @@ stop-iteration-iterator@^1.1.0:
es-errors "^1.3.0"
internal-slot "^1.1.0"
storybook@^8.6.17:
version "8.6.17"
resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.6.17.tgz#56299bf9e58622bb834fb100eac89c15f7d0de98"
integrity sha512-krR/l680A6qVnkGiK9p8jY0ucX3+kFCs2f4zw+S3w2Cdq8EiM/tFebPcX2V4S3z2UsO0v0dwAJOJNpzbFPdmVg==
storybook@^8.6.18:
version "8.6.18"
resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.6.18.tgz#2a635a4b0c99693f43ba21b8eb511c5cc513a807"
integrity sha512-p8seiSI6FiVY6P3V0pG+5v7c8pDMehMAFRWEhG5XqIBSQszzOjDnW2rNvm3odoLKfo3V3P6Cs6Hv9ILzymULyQ==
dependencies:
"@storybook/core" "8.6.17"
"@storybook/core" "8.6.18"
string-convert@^0.2.0:
version "0.2.1"
@@ -14691,15 +14667,14 @@ tapable@^2.0.0, tapable@^2.2.1, tapable@^2.3.0:
resolved "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz"
integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==
terser-webpack-plugin@^5.3.16, terser-webpack-plugin@^5.3.9:
version "5.3.16"
resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz"
integrity sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==
terser-webpack-plugin@^5.3.17, terser-webpack-plugin@^5.3.9:
version "5.3.17"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.17.tgz#75ea98876297fbb190d2fbb395e982582b859a67"
integrity sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==
dependencies:
"@jridgewell/trace-mapping" "^0.3.25"
jest-worker "^27.4.5"
schema-utils "^4.3.0"
serialize-javascript "^6.0.2"
terser "^5.31.1"
terser@^5.10.0, terser@^5.15.1, terser@^5.31.1:
@@ -15591,11 +15566,6 @@ webpack-merge@^6.0.1:
flat "^5.0.2"
wildcard "^2.0.1"
webpack-sources@^3.3.3:
version "3.3.3"
resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz"
integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==
webpack-sources@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.4.tgz#a338b95eb484ecc75fbb196cbe8a2890618b4891"
@@ -15606,10 +15576,10 @@ webpack-virtual-modules@^0.6.2:
resolved "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz"
integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==
webpack@^5.105.3:
version "5.105.3"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.105.3.tgz#307ad95bafffd08bc81049d6519477b16e42e7ba"
integrity sha512-LLBBA4oLmT7sZdHiYE/PeVuifOxYyE2uL/V+9VQP7YSYdJU7bSf7H8bZRRxW8kEPMkmVjnrXmoR3oejIdX0xbg==
webpack@^5.105.4, webpack@^5.88.1, webpack@^5.95.0:
version "5.105.4"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.105.4.tgz#1b77fcd55a985ac7ca9de80a746caffa38220169"
integrity sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==
dependencies:
"@types/eslint-scope" "^3.7.7"
"@types/estree" "^1.0.8"
@@ -15621,7 +15591,7 @@ webpack@^5.105.3:
acorn-import-phases "^1.0.3"
browserslist "^4.28.1"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.19.0"
enhanced-resolve "^5.20.0"
es-module-lexer "^2.0.0"
eslint-scope "5.1.1"
events "^3.2.0"
@@ -15633,41 +15603,10 @@ webpack@^5.105.3:
neo-async "^2.6.2"
schema-utils "^4.3.3"
tapable "^2.3.0"
terser-webpack-plugin "^5.3.16"
terser-webpack-plugin "^5.3.17"
watchpack "^2.5.1"
webpack-sources "^3.3.4"
webpack@^5.88.1, webpack@^5.95.0:
version "5.105.2"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.105.2.tgz#f3b76f9fc36f1152e156e63ffda3bbb82e6739ea"
integrity sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==
dependencies:
"@types/eslint-scope" "^3.7.7"
"@types/estree" "^1.0.8"
"@types/json-schema" "^7.0.15"
"@webassemblyjs/ast" "^1.14.1"
"@webassemblyjs/wasm-edit" "^1.14.1"
"@webassemblyjs/wasm-parser" "^1.14.1"
acorn "^8.15.0"
acorn-import-phases "^1.0.3"
browserslist "^4.28.1"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.19.0"
es-module-lexer "^2.0.0"
eslint-scope "5.1.1"
events "^3.2.0"
glob-to-regexp "^0.4.1"
graceful-fs "^4.2.11"
json-parse-even-better-errors "^2.3.1"
loader-runner "^4.3.1"
mime-types "^2.1.27"
neo-async "^2.6.2"
schema-utils "^4.3.3"
tapable "^2.3.0"
terser-webpack-plugin "^5.3.16"
watchpack "^2.5.1"
webpack-sources "^3.3.3"
webpackbar@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz"

View File

@@ -18,8 +18,8 @@
#
# Security: CVE-2026-21441 - decompression bomb bypass on redirects
urllib3>=2.6.3,<3.0.0
# Security: GHSA-87hc-h4r5-73f7 - Windows path traversal fix
werkzeug>=3.1.5,<4.0.0
# Security: CVE-2026-27199 - Windows device name handling in safe_join
werkzeug>=3.1.6,<4.0.0
# Security: CVE-2025-68146 - TOCTOU symlink vulnerability
filelock>=3.20.3,<4.0.0
# Security: decompression bomb fix (required by aiohttp 3.13.3)

View File

@@ -54,7 +54,7 @@ certifi==2025.6.15
# via
# requests
# selenium
cffi==1.17.1
cffi==2.0.0
# via
# cryptography
# pynacl
@@ -86,7 +86,7 @@ cron-descriptor==1.4.5
# via apache-superset (pyproject.toml)
croniter==6.0.0
# via apache-superset (pyproject.toml)
cryptography==44.0.3
cryptography==46.0.5
# via
# apache-superset (pyproject.toml)
# paramiko
@@ -219,7 +219,7 @@ markupsafe==3.0.2
# mako
# werkzeug
# wtforms
marshmallow==3.26.1
marshmallow==3.26.2
# via
# apache-superset (pyproject.toml)
# flask-appbuilder
@@ -317,9 +317,9 @@ pyjwt==2.10.1
# flask-appbuilder
# flask-jwt-extended
# redis
pynacl==1.5.0
pynacl==1.6.2
# via paramiko
pyopenssl==25.1.0
pyopenssl==25.3.0
# via shillelagh
pyparsing==3.2.3
# via apache-superset (pyproject.toml)
@@ -457,7 +457,7 @@ wcwidth==0.2.13
# via prompt-toolkit
websocket-client==1.8.0
# via selenium
werkzeug==3.1.5
werkzeug==3.1.6
# via
# -r requirements/base.in
# flask

View File

@@ -48,7 +48,7 @@ attrs==25.3.0
# referencing
# requests-cache
# trio
authlib==1.6.5
authlib==1.6.7
# via fastmcp
babel==2.17.0
# via
@@ -115,7 +115,7 @@ certifi==2025.6.15
# httpx
# requests
# selenium
cffi==1.17.1
cffi==2.0.0
# via
# -c requirements/base-constraint.txt
# cryptography
@@ -177,7 +177,7 @@ croniter==6.0.0
# via
# -c requirements/base-constraint.txt
# apache-superset
cryptography==44.0.3
cryptography==46.0.5
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -526,7 +526,7 @@ markupsafe==3.0.2
# mako
# werkzeug
# wtforms
marshmallow==3.26.1
marshmallow==3.26.2
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -703,7 +703,7 @@ proto-plus==1.25.0
# via
# google-api-core
# google-cloud-bigquery-storage
protobuf==4.25.5
protobuf==4.25.8
# via
# google-api-core
# google-cloud-bigquery-storage
@@ -786,11 +786,11 @@ pyjwt==2.10.1
# redis
pylint==3.3.7
# via apache-superset
pynacl==1.5.0
pynacl==1.6.2
# via
# -c requirements/base-constraint.txt
# paramiko
pyopenssl==25.1.0
pyopenssl==25.3.0
# via
# -c requirements/base-constraint.txt
# shillelagh
@@ -1009,7 +1009,7 @@ sshtunnel==0.4.0
# via
# -c requirements/base-constraint.txt
# apache-superset
starlette==0.48.0
starlette==0.49.1
# via mcp
statsd==4.0.1
# via apache-superset
@@ -1111,7 +1111,7 @@ websocket-client==1.8.0
# selenium
websockets==15.0.1
# via fastmcp
werkzeug==3.1.5
werkzeug==3.1.6
# via
# -c requirements/base-constraint.txt
# flask

View File

@@ -31,70 +31,70 @@ The official core package for building Apache Superset backend extensions and in
pip install apache-superset-core
```
## 🏗️ Architecture
## 🏗️ Package Structure
The package is organized into logical modules, each providing specific functionality:
- **`api`** - REST API base classes, models access, query utilities, and registration
- **`api.models`** - Access to Superset's database models (datasets, databases, etc.)
- **`api.query`** - Database query utilities and SQL dialect handling
- **`api.rest_api`** - Extension API registration and management
- **`api.types.rest_api`** - REST API base classes and type definitions
```
src/superset_core/
├── common/
├── extensions/
├── mcp/
├── queries/
├── rest_api/
├── tasks/
└── __init__.py
```
## 🚀 Quick Start
### Basic Extension Structure
### Basic Extension API
```python
from flask import request, Response
from flask_appbuilder.api import expose, permission_name, protect, safe
from superset_core.api import models, query, rest_api
from superset_core.api.rest_api import RestApi
from superset_core.rest_api.api import RestApi
from superset_core.rest_api.decorators import api
@api(id="dataset_references", name="Dataset References API")
class DatasetReferencesAPI(RestApi):
"""Example extension API demonstrating core functionality."""
resource_name = "dataset_references"
openapi_spec_tag = "Dataset references"
class_permission_name = "dataset_references"
@expose("/metadata", methods=("POST",))
@protect()
@safe
@permission_name("read")
def metadata(self) -> Response:
"""Get dataset metadata for tables referenced in SQL."""
sql: str = request.json.get("sql")
database_id: int = request.json.get("databaseId")
# Access Superset's models using core APIs
databases = models.get_databases(id=database_id)
if not databases:
return self.response_404()
database = databases[0]
dialect = query.get_sqlglot_dialect(database)
# Access datasets to get owner information
datasets = models.get_datasets()
owners_map = {
dataset.table_name: [
f"{owner.first_name} {owner.last_name}"
for owner in dataset.owners
]
for dataset in datasets
}
# Process SQL and return dataset metadata
return self.response(200, result=owners_map)
# Register the extension API
rest_api.add_extension_api(DatasetReferencesAPI)
# ... endpoint implementation
```
## 🤝 Contributing
### Background Tasks
We welcome contributions! Please see the [Developer Portal](https://superset.apache.org/developer_portal/) for details.
```python
from superset_core.tasks.decorators import task
from superset_core.tasks.types import TaskScope
@task(name="generate_report", scope=TaskScope.SHARED)
def generate_report(chart_id: int) -> None:
# ... task implementation
```
### MCP Tools
```python
from superset_core.mcp.decorators import tool
@tool(name="my_tool", description="Custom business logic", tags=["extension"])
def my_extension_tool(param: str) -> dict:
# ... tool implementation
```
### MCP Prompts
```python
from superset_core.mcp.decorators import prompt
@prompt(name="my_prompt", title="My Prompt", description="Interactive prompt", tags={"extension"})
async def my_prompt_handler(ctx: Context) -> str:
# ... prompt implementation
```
## 📄 License
@@ -102,12 +102,6 @@ Licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com
## 🔗 Links
- [Apache Superset](https://superset.apache.org/)
- [Documentation](https://superset.apache.org/docs/)
- [Community](https://superset.apache.org/community/)
- [GitHub Repository](https://github.com/apache/superset)
- [Extension Development Guide](https://superset.apache.org/docs/extensions/)
---
**Note**: This package is currently in release candidate status. APIs may change before the 1.0.0 release. Please check the [changelog](CHANGELOG.md) for breaking changes between versions.
- [Extensions Documentation](https://superset.apache.org/developer-docs/extensions/overview)

View File

@@ -18,7 +18,7 @@
[project]
name = "apache-superset-core"
version = "0.0.1rc4"
version = "0.1.0rc1"
description = "Core Python package for building Apache Superset backend extensions and integrations"
readme = "README.md"
authors = [

View File

@@ -16,13 +16,13 @@
# under the License.
"""
Data Access Object API for superset-core.
Common Data Access Object API for superset-core.
Provides dependency-injected DAO classes that will be replaced by
host implementations during initialization.
Usage:
from superset_core.api.daos import DatasetDAO, DatabaseDAO
from superset_core.common.daos import DatasetDAO, DatabaseDAO
# Use standard BaseDAO methods
datasets = DatasetDAO.find_all()
@@ -36,17 +36,14 @@ from typing import Any, ClassVar, Generic, TypeVar
from flask_appbuilder.models.filters import BaseFilter
from sqlalchemy.orm import Query as SQLAQuery
from superset_core.api.models import (
from superset_core.common.models import (
Chart,
CoreModel,
Dashboard,
Database,
Dataset,
KeyValue,
Query,
SavedQuery,
Tag,
Task,
User,
)
@@ -193,34 +190,6 @@ class UserDAO(BaseDAO[User]):
id_column_name = "id"
class QueryDAO(BaseDAO[Query]):
"""
Abstract Query DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
class SavedQueryDAO(BaseDAO[SavedQuery]):
"""
Abstract SavedQuery DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
class TagDAO(BaseDAO[Tag]):
"""
Abstract Tag DAO interface.
@@ -249,48 +218,6 @@ class KeyValueDAO(BaseDAO[KeyValue]):
id_column_name = "id"
class TaskDAO(BaseDAO[Task]):
"""
Abstract Task DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
uuid_column_name = "uuid"
@classmethod
@abstractmethod
def find_by_task_key(
cls,
task_type: str,
task_key: str,
scope: str = "private",
user_id: int | None = None,
) -> Task | None:
"""
Find active task by type, key, scope, and user.
Uses dedup_key internally for efficient querying with a unique index.
Only returns tasks that are active (pending or in progress).
Uniqueness logic by scope:
- private: scope + task_type + task_key + user_id
- shared/system: scope + task_type + task_key (user-agnostic)
:param task_type: Task type to filter by
:param task_key: Task identifier for deduplication
:param scope: Task scope (private/shared/system)
:param user_id: User ID (required for private tasks)
:returns: Task instance or None if not found or not active
"""
...
__all__ = [
"BaseDAO",
"DatasetDAO",
@@ -298,9 +225,6 @@ __all__ = [
"ChartDAO",
"DashboardDAO",
"UserDAO",
"QueryDAO",
"SavedQueryDAO",
"TagDAO",
"KeyValueDAO",
"TaskDAO",
]

View File

@@ -16,13 +16,13 @@
# under the License.
"""
Model API for superset-core.
Common model API for superset-core.
Provides model classes that will be replaced by host implementations
Provides core model classes that will be replaced by host implementations
during initialization for extension developers to use.
Usage:
from superset_core.api.models import Dataset, Database, get_session
from superset_core.common.models import Dataset, Database, get_session
# Use as regular model classes
dataset = Dataset(name="My Dataset")
@@ -40,8 +40,7 @@ from flask_appbuilder import Model
from sqlalchemy.orm import scoped_session
if TYPE_CHECKING:
from superset_core.api.tasks import TaskProperties
from superset_core.api.types import (
from superset_core.queries.types import (
AsyncQueryHandle,
QueryOptions,
QueryResult,
@@ -88,8 +87,8 @@ class Database(CoreModel):
def execute(
self,
sql: str,
options: QueryOptions | None = None,
) -> QueryResult:
options: "QueryOptions | None" = None,
) -> "QueryResult":
"""
Execute SQL synchronously.
@@ -103,8 +102,8 @@ class Database(CoreModel):
:returns: QueryResult with status, data (DataFrame), and metadata
Example:
from superset_core.api.daos import DatabaseDAO
from superset_core.api.types import QueryOptions, QueryStatus
from superset_core.common.daos import DatabaseDAO
from superset_core.queries.types import QueryOptions, QueryStatus
db = DatabaseDAO.find_one_or_none(id=1)
result = db.execute(
@@ -136,8 +135,8 @@ class Database(CoreModel):
def execute_async(
self,
sql: str,
options: QueryOptions | None = None,
) -> AsyncQueryHandle:
options: "QueryOptions | None" = None,
) -> "AsyncQueryHandle":
"""
Execute SQL asynchronously.
@@ -286,47 +285,6 @@ class User(CoreModel):
active: bool
class Query(CoreModel):
"""
Abstract Query model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
client_id: str | None
database_id: int | None
sql: str | None
status: str | None
user_id: int | None
progress: int
error_message: str | None
class SavedQuery(CoreModel):
"""
Abstract SavedQuery model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
uuid: UUID | None
label: str | None
sql: str | None
database_id: int | None
description: str | None
user_id: int | None
class Tag(CoreModel):
"""
Abstract Tag model interface.
@@ -362,132 +320,6 @@ class KeyValue(CoreModel):
changed_by_fk: int | None
class Task(CoreModel):
"""
Abstract Task model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
This model represents async tasks in the Global Task Framework (GTF).
Non-filterable fields (progress, error info, execution config) are stored
in a `properties` JSON blob for schema flexibility.
"""
__abstract__ = True
# Type hints for expected column attributes
id: int
uuid: UUID
task_key: str # For deduplication
task_type: str # e.g., 'sql_execution'
task_name: str | None # Human readable name
scope: str # private/shared/system
status: str
dedup_key: str # Computed deduplication key
# Timestamps (from AuditMixinNullable)
created_on: datetime | None
changed_on: datetime | None
started_at: datetime | None
ended_at: datetime | None
# User context
created_by_fk: int | None
user_id: int | None
# Task output data
payload: str # JSON serialized task output data
def get_payload(self) -> dict[str, Any]:
"""
Get payload as parsed JSON.
Payload contains task-specific output data set by task code.
Host implementations will replace this method during initialization
with concrete implementation providing actual functionality.
:returns: Dictionary containing payload data
"""
raise NotImplementedError("Method will be replaced during initialization")
def set_payload(self, data: dict[str, Any]) -> None:
"""
Update payload with new data (merges with existing).
Host implementations will replace this method during initialization
with concrete implementation providing actual functionality.
:param data: Dictionary of data to merge into payload
"""
raise NotImplementedError("Method will be replaced during initialization")
@property
def properties(self) -> Any:
"""
Get typed properties (runtime state and execution config).
Properties contain:
- is_abortable: bool | None - has abort handler registered
- progress_percent: float | None - progress 0.0-1.0
- progress_current: int | None - current iteration count
- progress_total: int | None - total iterations
- error_message: str | None - human-readable error message
- exception_type: str | None - exception class name
- stack_trace: str | None - full formatted traceback
- timeout: int | None - timeout in seconds
Host implementations will replace this property during initialization.
:returns: TaskProperties dataclass instance
"""
raise NotImplementedError("Property will be replaced during initialization")
def update_properties(self, updates: "TaskProperties") -> None:
"""
Update specific properties fields (merge semantics).
Only updates fields present in the updates dict.
Host implementations will replace this method during initialization.
:param updates: TaskProperties dict with fields to update
Example:
task.update_properties({"is_abortable": True})
"""
raise NotImplementedError("Method will be replaced during initialization")
class TaskSubscriber(CoreModel):
"""
Abstract TaskSubscriber model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
This model tracks task subscriptions for multi-user shared tasks. When a user
schedules a shared task with the same parameters as an existing task,
they are subscribed to that task instead of creating a duplicate.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
task_id: int
user_id: int
subscribed_at: datetime
# Audit fields from AuditMixinNullable
created_on: datetime | None
changed_on: datetime | None
created_by_fk: int | None
changed_by_fk: int | None
def get_session() -> scoped_session:
"""
Retrieve the SQLAlchemy session to directly interface with the
@@ -507,12 +339,8 @@ __all__ = [
"Chart",
"Dashboard",
"User",
"Query",
"SavedQuery",
"Tag",
"KeyValue",
"Task",
"TaskSubscriber",
"CoreModel",
"get_session",
]

View File

@@ -0,0 +1,16 @@
# 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.

View File

@@ -22,7 +22,7 @@ This module provides a decorator interface to register MCP tools with the
host application.
Usage:
from superset_core.api.mcp import tool
from superset_core.mcp.decorators import tool
@tool(name="my_tool", description="Custom business logic", tags=["extension"])
def my_extension_tool(param: str) -> dict:

View File

@@ -0,0 +1,16 @@
# 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.

View File

@@ -0,0 +1,63 @@
# 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.
"""
Query Data Access Object API for superset-core.
Provides query-related DAO classes that will be replaced by host implementations
during initialization.
Usage:
from superset_core.queries.daos import QueryDAO, SavedQueryDAO
"""
from superset_core.common.daos import BaseDAO
from superset_core.queries.models import Query, SavedQuery
class QueryDAO(BaseDAO[Query]):
"""
Abstract Query DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
class SavedQueryDAO(BaseDAO[SavedQuery]):
"""
Abstract SavedQuery DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
__all__ = [
"QueryDAO",
"SavedQueryDAO",
]

View File

@@ -0,0 +1,79 @@
# 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.
"""
Query model API for superset-core.
Provides query-related model classes that will be replaced by host implementations
during initialization for extension developers to use.
Usage:
from superset_core.queries.models import Query, SavedQuery
"""
from __future__ import annotations
from uuid import UUID
from superset_core.common.models import CoreModel
class Query(CoreModel):
"""
Abstract Query model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
client_id: str | None
database_id: int | None
sql: str | None
status: str | None
user_id: int | None
progress: int
error_message: str | None
class SavedQuery(CoreModel):
"""
Abstract SavedQuery model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
uuid: UUID | None
label: str | None
sql: str | None
database_id: int | None
description: str | None
user_id: int | None
__all__ = [
"Query",
"SavedQuery",
]

View File

@@ -22,7 +22,7 @@ Provides dependency-injected query utility functions that will be replaced by
host implementations during initialization.
Usage:
from superset_core.api.query import get_sqlglot_dialect
from superset_core.queries.query import get_sqlglot_dialect
dialect = get_sqlglot_dialect(database)
"""
@@ -32,7 +32,7 @@ from typing import TYPE_CHECKING
from sqlglot import Dialects
if TYPE_CHECKING:
from superset_core.api.models import Database
from superset_core.common.models import Database
def get_sqlglot_dialect(database: "Database") -> Dialects:

View File

@@ -0,0 +1,16 @@
# 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.

View File

@@ -0,0 +1,32 @@
# 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.
from flask_appbuilder.api import BaseApi
class RestApi(BaseApi):
"""
Base REST API class for Superset with browser login support.
This class extends Flask-AppBuilder's BaseApi and enables browser-based
authentication by default.
"""
allow_browser_login = True
__all__ = ["RestApi"]

View File

@@ -16,15 +16,11 @@
# under the License.
"""
REST API functions and decorators for superset-core.
Provides dependency-injected REST API utility functions and decorators that will be
replaced by host implementations during initialization.
REST API decorator for superset-core.
Usage:
from superset_core.api.rest_api import api
from superset_core.rest_api.decorators import api
# Unified decorator for both host and extension APIs
@api(
id="main_api",
name="Main API",
@@ -34,25 +30,15 @@ Usage:
pass
"""
from typing import Callable, TypeVar
from typing import Callable, TYPE_CHECKING, TypeVar
from flask_appbuilder.api import BaseApi
if TYPE_CHECKING:
from superset_core.rest_api.api import RestApi
# Type variable for decorated API classes
T = TypeVar("T", bound=type["RestApi"])
class RestApi(BaseApi):
"""
Base REST API class for Superset with browser login support.
This class extends Flask-AppBuilder's BaseApi and enables browser-based
authentication by default.
"""
allow_browser_login = True
def api(
id: str,
name: str,
@@ -114,4 +100,4 @@ def api(
)
__all__ = ["RestApi", "api"]
__all__ = ["api"]

View File

@@ -0,0 +1,16 @@
# 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.

View File

@@ -0,0 +1,76 @@
# 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.
"""
Task Data Access Object API for superset-core.
Provides task-related DAO classes that will be replaced by host implementations
during initialization.
Usage:
from superset_core.tasks.daos import TaskDAO
"""
from abc import abstractmethod
from superset_core.common.daos import BaseDAO
from superset_core.tasks.models import Task
class TaskDAO(BaseDAO[Task]):
"""
Abstract Task DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
uuid_column_name = "uuid"
@classmethod
@abstractmethod
def find_by_task_key(
cls,
task_type: str,
task_key: str,
scope: str = "private",
user_id: int | None = None,
) -> Task | None:
"""
Find active task by type, key, scope, and user.
Uses dedup_key internally for efficient querying with a unique index.
Only returns tasks that are active (pending or in progress).
Uniqueness logic by scope:
- private: scope + task_type + task_key + user_id
- shared/system: scope + task_type + task_key (user-agnostic)
:param task_type: Task type to filter by
:param task_key: Task identifier for deduplication
:param scope: Task scope (private/shared/system)
:param user_id: User ID (required for private tasks)
:returns: Task instance or None if not found or not active
"""
...
__all__ = ["TaskDAO"]

View File

@@ -0,0 +1,152 @@
# 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.
from __future__ import annotations
from typing import Callable, Generic, ParamSpec, TYPE_CHECKING, TypeVar
from superset_core.tasks.types import TaskContext, TaskScope
if TYPE_CHECKING:
from superset_core.tasks.models import Task
P = ParamSpec("P")
R = TypeVar("R")
def task(
name: str | None = None,
scope: TaskScope = TaskScope.PRIVATE,
timeout: int | None = None,
) -> Callable[[Callable[P, R]], "TaskWrapper[P]"]:
"""
Decorator to register a task.
Host implementations will replace this function during initialization
with a concrete implementation providing actual functionality.
:param name: Optional unique task name (e.g., "superset.generate_thumbnail").
If not provided, uses the function name as the task name.
:param scope: Task scope (TaskScope.PRIVATE, SHARED, or SYSTEM).
Defaults to TaskScope.PRIVATE.
:param timeout: Optional timeout in seconds. When the timeout is reached,
abort handlers are triggered if registered. Can be overridden
at call time via TaskOptions(timeout=...).
:returns: TaskWrapper with .schedule() method
Note:
Both direct calls and .schedule() return Task, regardless of the
original function's return type. The decorated function's return value
is discarded; only side effects and context updates matter.
Example:
from superset_core.tasks.decorators import task, get_context
from superset_core.tasks.types import TaskScope
# Private task (default scope)
@task
def generate_thumbnail(chart_id: int) -> None:
ctx = get_context()
# ... task implementation
# Named task with shared scope
@task(name="generate_report", scope=TaskScope.SHARED)
def generate_chart_thumbnail(chart_id: int) -> None:
ctx = get_context()
# Update progress and payload atomically
ctx.update_task(
progress=0.5,
payload={"chart_id": chart_id, "status": "processing"}
)
# ... task implementation
ctx.update_task(progress=1.0)
# System task (admin-only)
@task(scope=TaskScope.SYSTEM)
def cleanup_old_data() -> None:
ctx = get_context()
# ... cleanup implementation
# Task with timeout
@task(timeout=300) # 5-minute timeout
def long_running_task() -> None:
ctx = get_context()
@ctx.on_abort
def handle_abort():
# Called when timeout or manual abort
pass
# Schedule async execution
task = generate_chart_thumbnail.schedule(chart_id=123) # Returns Task
# Direct call for sync execution (blocks until task is complete)
task = generate_chart_thumbnail(chart_id=123) # Also returns Task
"""
raise NotImplementedError("Function will be replaced during initialization")
class TaskWrapper(Generic[P]):
"""
Type stub for task wrapper returned by @task decorator.
Both __call__ and .schedule() return Task.
"""
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> "Task":
"""Execute the task synchronously."""
raise NotImplementedError("Will be replaced during initialization")
def schedule(self, *args: P.args, **kwargs: P.kwargs) -> "Task":
"""Schedule the task for async execution."""
raise NotImplementedError("Will be replaced during initialization")
def get_context() -> TaskContext:
"""
Get the current task context from ambient context.
Host implementations will replace this function during initialization
with a concrete implementation providing actual functionality.
This function provides ambient access to the task context without
requiring it to be passed as a parameter. It can only be called
from within an async task execution.
:returns: The current TaskContext
:raises RuntimeError: If called outside a task execution context
Example:
@task("thumbnail_generation")
def generate_chart_thumbnail(chart_id: int):
ctx = get_context() # Access ambient context
# Update task state - no need to fetch task object
ctx.update_task(
progress=0.5,
payload={"chart_id": chart_id}
)
"""
raise NotImplementedError("Function will be replaced during initialization")
__all__ = [
"task",
"get_context",
]

View File

@@ -0,0 +1,169 @@
# 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.
"""
Task model API for superset-core.
Provides task-related model classes that will be replaced by host implementations
during initialization for extension developers to use.
Usage:
from superset_core.tasks.models import Task, TaskSubscriber
"""
from __future__ import annotations
from datetime import datetime
from typing import Any, TYPE_CHECKING
from uuid import UUID
from superset_core.common.models import CoreModel
if TYPE_CHECKING:
from superset_core.tasks.types import TaskProperties
class Task(CoreModel):
"""
Abstract Task model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
This model represents async tasks in the Global Task Framework (GTF).
Non-filterable fields (progress, error info, execution config) are stored
in a `properties` JSON blob for schema flexibility.
"""
__abstract__ = True
# Type hints for expected column attributes
id: int
uuid: UUID
task_key: str # For deduplication
task_type: str # e.g., 'sql_execution'
task_name: str | None # Human readable name
scope: str # private/shared/system
status: str
dedup_key: str # Computed deduplication key
# Timestamps (from AuditMixinNullable)
created_on: datetime | None
changed_on: datetime | None
started_at: datetime | None
ended_at: datetime | None
# User context
created_by_fk: int | None
user_id: int | None
# Task output data
payload: str # JSON serialized task output data
def get_payload(self) -> dict[str, Any]:
"""
Get payload as parsed JSON.
Payload contains task-specific output data set by task code.
Host implementations will replace this method during initialization
with concrete implementation providing actual functionality.
:returns: Dictionary containing payload data
"""
raise NotImplementedError("Method will be replaced during initialization")
def set_payload(self, data: dict[str, Any]) -> None:
"""
Update payload with new data (merges with existing).
Host implementations will replace this method during initialization
with concrete implementation providing actual functionality.
:param data: Dictionary of data to merge into payload
"""
raise NotImplementedError("Method will be replaced during initialization")
@property
def properties(self) -> Any:
"""
Get typed properties (runtime state and execution config).
Properties contain:
- is_abortable: bool | None - has abort handler registered
- progress_percent: float | None - progress 0.0-1.0
- progress_current: int | None - current iteration count
- progress_total: int | None - total iterations
- error_message: str | None - human-readable error message
- exception_type: str | None - exception class name
- stack_trace: str | None - full formatted traceback
- timeout: int | None - timeout in seconds
Host implementations will replace this property during initialization.
:returns: TaskProperties dataclass instance
"""
raise NotImplementedError("Property will be replaced during initialization")
def update_properties(self, updates: "TaskProperties") -> None:
"""
Update specific properties fields (merge semantics).
Only updates fields present in the updates dict.
Host implementations will replace this method during initialization.
:param updates: TaskProperties dict with fields to update
Example:
task.update_properties({"is_abortable": True})
"""
raise NotImplementedError("Method will be replaced during initialization")
class TaskSubscriber(CoreModel):
"""
Abstract TaskSubscriber model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
This model tracks task subscriptions for multi-user shared tasks. When a user
schedules a shared task with the same parameters as an existing task,
they are subscribed to that task instead of creating a duplicate.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
task_id: int
user_id: int
subscribed_at: datetime
# Audit fields from AuditMixinNullable
created_on: datetime | None
changed_on: datetime | None
created_by_fk: int | None
changed_by_fk: int | None
__all__ = [
"Task",
"TaskSubscriber",
]

View File

@@ -20,12 +20,7 @@ from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum
from typing import Any, Callable, Generic, Literal, ParamSpec, TypedDict, TypeVar
from superset_core.api.models import Task
P = ParamSpec("P")
R = TypeVar("R")
from typing import Any, Callable, Literal, TypedDict
class TaskStatus(str, Enum):
@@ -104,7 +99,7 @@ class TaskOptions:
- Retry policies and backoff strategies
Example:
from superset_core.api.tasks import TaskOptions, TaskScope
from superset_core.tasks.types import TaskOptions, TaskScope
# Private task (default)
task = my_task.schedule(arg1)
@@ -233,129 +228,10 @@ class TaskContext(ABC):
...
def task(
name: str | None = None,
scope: TaskScope = TaskScope.PRIVATE,
timeout: int | None = None,
) -> Callable[[Callable[P, R]], "TaskWrapper[P]"]:
"""
Decorator to register a task.
Host implementations will replace this function during initialization
with a concrete implementation providing actual functionality.
:param name: Optional unique task name (e.g., "superset.generate_thumbnail").
If not provided, uses the function name as the task name.
:param scope: Task scope (TaskScope.PRIVATE, SHARED, or SYSTEM).
Defaults to TaskScope.PRIVATE.
:param timeout: Optional timeout in seconds. When the timeout is reached,
abort handlers are triggered if registered. Can be overridden
at call time via TaskOptions(timeout=...).
:returns: TaskWrapper with .schedule() method
Note:
Both direct calls and .schedule() return Task, regardless of the
original function's return type. The decorated function's return value
is discarded; only side effects and context updates matter.
Example:
from superset_core.api.tasks import task, get_context, TaskScope
# Private task (default scope)
@task
def generate_thumbnail(chart_id: int) -> None:
ctx = get_context()
# ... task implementation
# Named task with shared scope
@task(name="generate_report", scope=TaskScope.SHARED)
def generate_chart_thumbnail(chart_id: int) -> None:
ctx = get_context()
# Update progress and payload atomically
ctx.update_task(
progress=0.5,
payload={"chart_id": chart_id, "status": "processing"}
)
# ... task implementation
ctx.update_task(progress=1.0)
# System task (admin-only)
@task(scope=TaskScope.SYSTEM)
def cleanup_old_data() -> None:
ctx = get_context()
# ... cleanup implementation
# Task with timeout
@task(timeout=300) # 5-minute timeout
def long_running_task() -> None:
ctx = get_context()
@ctx.on_abort
def handle_abort():
# Called when timeout or manual abort
pass
# Schedule async execution
task = generate_chart_thumbnail.schedule(chart_id=123) # Returns Task
# Direct call for sync execution (blocks until task is complete)
task = generate_chart_thumbnail(chart_id=123) # Also returns Task
"""
raise NotImplementedError("Function will be replaced during initialization")
class TaskWrapper(Generic[P]):
"""
Type stub for task wrapper returned by @task decorator.
Both __call__ and .schedule() return Task.
"""
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Task:
"""Execute the task synchronously."""
raise NotImplementedError("Will be replaced during initialization")
def schedule(self, *args: P.args, **kwargs: P.kwargs) -> Task:
"""Schedule the task for async execution."""
raise NotImplementedError("Will be replaced during initialization")
def get_context() -> TaskContext:
"""
Get the current task context from ambient context.
Host implementations will replace this function during initialization
with a concrete implementation providing actual functionality.
This function provides ambient access to the task context without
requiring it to be passed as a parameter. It can only be called
from within an async task execution.
:returns: The current TaskContext
:raises RuntimeError: If called outside a task execution context
Example:
@task("thumbnail_generation")
def generate_chart_thumbnail(chart_id: int):
ctx = get_context() # Access ambient context
# Update task state - no need to fetch task object
ctx.update_task(
progress=0.5,
payload={"chart_id": chart_id}
)
"""
raise NotImplementedError("Function will be replaced during initialization")
__all__ = [
"TaskStatus",
"TaskScope",
"TaskProperties",
"TaskContext",
"TaskOptions",
"task",
"get_context",
]

View File

@@ -1 +1 @@
v20.18.3
v22.22.0

View File

@@ -232,6 +232,7 @@ export async function embedDashboard({
});
iframe.src = `${supersetDomain}/embedded/${id}${urlParamsString}`;
iframe.title = iframeTitle;
iframe.style.background = 'transparent';
if (iframeAllowExtras.length > 0) {
iframe.setAttribute('allow', iframeAllowExtras.join('; '));
}

View File

@@ -28,6 +28,7 @@ Official command-line interface for building, bundling, and managing Apache Supe
## 🚀 Features
- **Extension Scaffolding** - Generate initial folder structure and scaffold new extension projects
- **Validation** - Validate extension structure and configuration before building
- **Development Server** - Automatically rebuild extensions as files change during development
- **Build System** - Build extension assets for production deployment
- **Bundle Packaging** - Package extensions into distributable .supx files
@@ -43,68 +44,52 @@ pip install apache-superset-extensions-cli
### Available Commands
```bash
# Generate initial folder structure and scaffold a new extension project
superset-extensions init <extension-name>
# Scaffold a new extension project (interactive prompts, or pass options directly)
superset-extensions init [--publisher <publisher>] [--name <name>] [--display-name <name>]
[--version <version>] [--license <license>]
[--frontend/--no-frontend] [--backend/--no-backend]
# Validate extension structure and configuration
superset-extensions validate
# Build extension assets for production (runs validate first)
superset-extensions build
# Package extension into a distributable .supx file (runs build first)
superset-extensions bundle [--output/-o <path>]
# Automatically rebuild extension as files change during development
superset-extensions dev
# Build extension assets for production
superset-extensions build
# Package extension into a distributable .supx file
superset-extensions bundle
```
## 📋 Extension Structure
The CLI generates extensions with the following structure:
The CLI scaffolds extensions with the following structure:
```
extension_name/
{publisher}.{name}/ # e.g., my-org.dashboard-widgets/
├── extension.json # Extension configuration and metadata
├── frontend/ # Frontend code
│ ├── src/ # TypeScript/React source files
│ ├── webpack.config.js # Frontend build configuration
│ ├── tsconfig.json # TypeScript configuration
│ └── package.json # Frontend dependencies
├── backend/ # Backend code
├── .gitignore
├── frontend/ # Optional frontend code
│ ├── src/
│ │ └── dataset_references/ # Python package source
│ ├── tests/ # Backend tests
│ ├── pyproject.toml # Python package configuration
│ └── requirements.txt # Python dependencies
── dist/ # Built extension files (generated)
├── manifest.json # Generated extension manifest
── frontend/
└── dist/ # Built frontend assets
├── remoteEntry.*.js # Module federation entry
│ │ └── *.js # Additional frontend bundles
│ └── backend/
│ └── dataset_references/ # Built backend package
│ ├── __init__.py
│ ├── api.py
│ └── entrypoint.py
├── dataset_references-1.0.0.supx # Packaged extension file (generated)
└── README.md # Extension documentation
│ │ └── index.tsx # Frontend entry point
│ ├── package.json
│ ├── webpack.config.js
│ └── tsconfig.json
── backend/ # Optional backend code
├── src/
── {publisher}/ # e.g., my_org/
└── {name}/ # e.g., dashboard_widgets/
└── entrypoint.py
└── pyproject.toml
```
## 🤝 Contributing
We welcome contributions! Please see the [Developer Portal](https://superset.apache.org/developer_portal/) for details.
## 📄 License
Licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/apache/superset/blob/master/LICENSE.txt) for details.
## 🔗 Links
- [Apache Superset](https://superset.apache.org/)
- [Extension Development Guide](https://superset.apache.org/docs/extensions/)
- [API Documentation](https://superset.apache.org/docs/api/)
- [GitHub Repository](https://github.com/apache/superset)
- [Community](https://superset.apache.org/community/)
---
**Note**: This package is currently in early development. APIs and commands may change before the 1.0.0 release. Please check the [changelog](CHANGELOG.md) for breaking changes between versions.
- [GitHub Repository](https://github.com/apache/superset)
- [Extensions Documentation](https://superset.apache.org/developer-docs/extensions/overview)

View File

@@ -17,7 +17,7 @@
[project]
name = "apache-superset-extensions-cli"
version = "0.0.1rc2"
version = "0.1.0rc1"
description = "Official command-line interface for building, bundling, and managing Apache Superset extensions"
readme = "README.md"
authors = [

View File

@@ -167,7 +167,7 @@ def build_manifest(cwd: Path, remote_entry: str | None) -> Manifest:
# Generate conventional entry point
publisher_snake = kebab_to_snake_case(extension.publisher)
name_snake = kebab_to_snake_case(extension.name)
entrypoint = f"superset_extensions.{publisher_snake}.{name_snake}.entrypoint"
entrypoint = f"{publisher_snake}.{name_snake}.entrypoint"
backend = ManifestBackend(entrypoint=entrypoint)
return Manifest(
@@ -344,12 +344,7 @@ def validate() -> None:
publisher_snake = kebab_to_snake_case(extension.publisher)
name_snake = kebab_to_snake_case(extension.name)
expected_entry_file = (
backend_dir
/ "src"
/ "superset_extensions"
/ publisher_snake
/ name_snake
/ "entrypoint.py"
backend_dir / "src" / publisher_snake / name_snake / "entrypoint.py"
)
if not expected_entry_file.exists():
@@ -359,7 +354,7 @@ def validate() -> None:
fg="red",
)
click.secho(
f" Convention requires: backend/src/superset_extensions/{publisher_snake}/{name_snake}/entrypoint.py",
f" Convention requires: backend/src/{publisher_snake}/{name_snake}/entrypoint.py",
fg="yellow",
)
sys.exit(1)
@@ -713,23 +708,19 @@ def init(
(frontend_src_dir / "index.tsx").write_text(index_tsx)
click.secho("✅ Created frontend folder structure", fg="green")
# Initialize backend files with superset_extensions.publisher.name structure
# Initialize backend files with publisher.name structure
if include_backend:
backend_dir = target_dir / "backend"
backend_dir.mkdir()
backend_src_dir = backend_dir / "src"
backend_src_dir.mkdir()
# Create superset_extensions namespace directory
namespace_dir = backend_src_dir / "superset_extensions"
namespace_dir.mkdir()
# Create publisher directory (e.g., superset_extensions/my_org)
# Create publisher directory (e.g., my_org)
publisher_snake = kebab_to_snake_case(names["publisher"])
publisher_dir = namespace_dir / publisher_snake
publisher_dir = backend_src_dir / publisher_snake
publisher_dir.mkdir()
# Create extension package directory (e.g., superset_extensions/my_org/dashboard_widgets)
# Create extension package directory (e.g., my_org/dashboard_widgets)
name_snake = kebab_to_snake_case(names["name"])
extension_package_dir = publisher_dir / name_snake
extension_package_dir.mkdir()
@@ -738,13 +729,7 @@ def init(
pyproject_toml = env.get_template("backend/pyproject.toml.j2").render(ctx)
(backend_dir / "pyproject.toml").write_text(pyproject_toml)
# Namespace package __init__.py (empty for namespace)
(namespace_dir / "__init__.py").write_text("")
(publisher_dir / "__init__.py").write_text("")
# Extension package files
init_py = env.get_template("backend/src/package/__init__.py.j2").render(ctx)
(extension_package_dir / "__init__.py").write_text(init_py)
entrypoint_py = env.get_template("backend/src/package/entrypoint.py.j2").render(
ctx
)

View File

@@ -42,8 +42,8 @@ class ExtensionNames(TypedDict):
# Backend package name with hyphens for distribution (e.g., "my_org-dashboard_widgets")
backend_package: str
# Full backend import path (e.g., "superset_extensions.my_org.dashboard_widgets")
# Full backend import path (e.g., "my_org.dashboard_widgets")
backend_path: str
# Backend entry point (e.g., "superset_extensions.my_org.dashboard_widgets.entrypoint")
# Backend entry point (e.g., "my_org.dashboard_widgets.entrypoint")
backend_entry: str

View File

@@ -361,7 +361,7 @@ def generate_extension_names(
publisher_snake = kebab_to_snake_case(publisher)
name_snake = kebab_to_snake_case(technical_name)
backend_package = f"{publisher_snake}-{name_snake}"
backend_path = f"superset_extensions.{publisher_snake}.{name_snake}"
backend_path = f"{publisher_snake}.{name_snake}"
backend_entry = f"{backend_path}.entrypoint"
# Validate the generated names

View File

@@ -133,14 +133,7 @@ def extension_setup_for_bundling():
(frontend_dir / "main.js").write_text("// main js")
# Create some backend files - updated path structure
backend_dir = (
dist_dir
/ "backend"
/ "src"
/ "superset_extensions"
/ "test_org"
/ "test_extension"
)
backend_dir = dist_dir / "backend" / "src" / "test_org" / "test_extension"
backend_dir.mkdir(parents=True)
(backend_dir / "__init__.py").write_text("# init")

View File

@@ -56,13 +56,7 @@ def extension_with_build_structure():
backend_dir.mkdir()
# Create conventional backend structure
backend_src_dir = (
backend_dir
/ "src"
/ "superset_extensions"
/ "test_org"
/ "test_extension"
)
backend_src_dir = backend_dir / "src" / "test_org" / "test_extension"
backend_src_dir.mkdir(parents=True)
# Create conventional entry point file
@@ -70,10 +64,7 @@ def extension_with_build_structure():
(backend_src_dir / "__init__.py").write_text("")
# Create parent __init__.py files for namespace packages
(backend_dir / "src" / "superset_extensions" / "__init__.py").write_text("")
(
backend_dir / "src" / "superset_extensions" / "test_org" / "__init__.py"
).write_text("")
(backend_dir / "src" / "test_org" / "__init__.py").write_text("")
# Create pyproject.toml matching the template structure
pyproject_content = """[project]
@@ -84,7 +75,7 @@ license = "Apache-2.0"
[tool.apache_superset_extensions.build]
# Files to include in the extension build/bundle
include = [
"src/superset_extensions/test_org/test_extension/**/*.py",
"src/test_org/test_extension/**/*.py",
]
exclude = []
"""
@@ -133,11 +124,7 @@ def test_build_command_success_flow(
"project": {"name": "test"},
"tool": {
"apache_superset_extensions": {
"build": {
"include": [
"src/superset_extensions/test_org/test_extension/**/*.py"
]
}
"build": {"include": ["src/test_org/test_extension/**/*.py"]}
}
},
}
@@ -178,11 +165,7 @@ def test_build_command_handles_frontend_build_failure(
"project": {"name": "test"},
"tool": {
"apache_superset_extensions": {
"build": {
"include": [
"src/superset_extensions/test_org/test_extension/**/*.py"
]
}
"build": {"include": ["src/test_org/test_extension/**/*.py"]}
}
},
}
@@ -322,10 +305,7 @@ def test_build_manifest_creates_correct_manifest_structure(
# Verify backend section and conventional entrypoint
assert manifest.backend is not None
assert (
manifest.backend.entrypoint
== "superset_extensions.test_org.test_extension.entrypoint"
)
assert manifest.backend.entrypoint == "test_org.test_extension.entrypoint"
@pytest.mark.unit
@@ -477,7 +457,7 @@ def test_copy_backend_files_skips_non_files(isolated_filesystem):
"""Test copy_backend_files skips directories and non-files."""
# Create backend structure with directory
backend_dir = isolated_filesystem / "backend"
backend_src = backend_dir / "src" / "superset_extensions" / "test_org" / "test_ext"
backend_src = backend_dir / "src" / "test_org" / "test_ext"
backend_src.mkdir(parents=True)
(backend_src / "__init__.py").write_text("# init")
@@ -493,7 +473,7 @@ license = "Apache-2.0"
[tool.apache_superset_extensions.build]
include = [
"src/superset_extensions/test_org/test_ext/**/*",
"src/test_org/test_ext/**/*",
]
exclude = []
"""
@@ -517,25 +497,11 @@ exclude = []
# Verify only files were copied, not directories
dist_dir = isolated_filesystem / "dist"
assert_file_exists(
dist_dir
/ "backend"
/ "src"
/ "superset_extensions"
/ "test_org"
/ "test_ext"
/ "__init__.py"
dist_dir / "backend" / "src" / "test_org" / "test_ext" / "__init__.py"
)
# Directory should not be copied as a file
copied_subdir = (
dist_dir
/ "backend"
/ "src"
/ "superset_extensions"
/ "test_org"
/ "test_ext"
/ "subdir"
)
copied_subdir = dist_dir / "backend" / "src" / "test_org" / "test_ext" / "subdir"
# The directory might exist but should be empty since we skip non-files
if copied_subdir.exists():
assert list(copied_subdir.iterdir()) == []
@@ -546,7 +512,7 @@ def test_copy_backend_files_copies_matched_files(isolated_filesystem):
"""Test copy_backend_files copies files matching patterns from pyproject.toml."""
# Create backend source files
backend_dir = isolated_filesystem / "backend"
backend_src = backend_dir / "src" / "superset_extensions" / "test_org" / "test_ext"
backend_src = backend_dir / "src" / "test_org" / "test_ext"
backend_src.mkdir(parents=True)
(backend_src / "__init__.py").write_text("# init")
(backend_src / "main.py").write_text("# main")
@@ -559,7 +525,7 @@ license = "Apache-2.0"
[tool.apache_superset_extensions.build]
include = [
"src/superset_extensions/test_org/test_ext/**/*.py",
"src/test_org/test_ext/**/*.py",
]
exclude = []
"""
@@ -583,22 +549,10 @@ exclude = []
# Verify files were copied
dist_dir = isolated_filesystem / "dist"
assert_file_exists(
dist_dir
/ "backend"
/ "src"
/ "superset_extensions"
/ "test_org"
/ "test_ext"
/ "__init__.py"
dist_dir / "backend" / "src" / "test_org" / "test_ext" / "__init__.py"
)
assert_file_exists(
dist_dir
/ "backend"
/ "src"
/ "superset_extensions"
/ "test_org"
/ "test_ext"
/ "main.py"
dist_dir / "backend" / "src" / "test_org" / "test_ext" / "main.py"
)
@@ -607,7 +561,7 @@ def test_copy_backend_files_handles_various_glob_patterns(isolated_filesystem):
"""Test copy_backend_files correctly handles different glob pattern formats."""
# Create backend structure with files in different locations
backend_dir = isolated_filesystem / "backend"
backend_src = backend_dir / "src" / "superset_extensions" / "test_org" / "test_ext"
backend_src = backend_dir / "src" / "test_org" / "test_ext"
backend_src.mkdir(parents=True)
# Create files that should match different pattern types
@@ -628,9 +582,9 @@ license = "Apache-2.0"
[tool.apache_superset_extensions.build]
include = [
"config.py", # No '/' - would break old logic
"**/*.py", # Starts with '**' - would break old logic
"src/superset_extensions/test_org/test_ext/main.py", # Specific file
"config.py", # No '/' - would break old logic
"**/*.py", # Starts with '**' - would break old logic
"src/test_org/test_ext/main.py", # Specific file
]
exclude = []
"""
@@ -659,34 +613,15 @@ exclude = []
# All .py files should be included (pattern: "**/*.py")
assert_file_exists(
dist_dir
/ "backend"
/ "src"
/ "superset_extensions"
/ "test_org"
/ "test_ext"
/ "__init__.py"
dist_dir / "backend" / "src" / "test_org" / "test_ext" / "__init__.py"
)
assert_file_exists(
dist_dir
/ "backend"
/ "src"
/ "superset_extensions"
/ "test_org"
/ "test_ext"
/ "utils"
/ "helper.py"
dist_dir / "backend" / "src" / "test_org" / "test_ext" / "utils" / "helper.py"
)
# Specific file (pattern: "src/superset_extensions/test_org/test_ext/main.py")
# Specific file (pattern: "src/test_org/test_ext/main.py")
assert_file_exists(
dist_dir
/ "backend"
/ "src"
/ "superset_extensions"
/ "test_org"
/ "test_ext"
/ "main.py"
dist_dir / "backend" / "src" / "test_org" / "test_ext" / "main.py"
)

View File

@@ -55,10 +55,7 @@ def test_bundle_command_creates_zip_with_default_name(
assert "manifest.json" in file_list
assert "frontend/dist/remoteEntry.abc123.js" in file_list
assert "frontend/dist/main.js" in file_list
assert (
"backend/src/superset_extensions/test_org/test_extension/__init__.py"
in file_list
)
assert "backend/src/test_org/test_extension/__init__.py" in file_list
@pytest.mark.cli

View File

@@ -376,13 +376,10 @@ def test_generate_extension_names_complete_flow(
assert (
names["backend_package"] == f"{publisher.replace('-', '_')}-{expected_snake}"
) # Collision-safe
assert (
names["backend_path"]
== f"superset_extensions.{publisher.replace('-', '_')}.{expected_snake}"
)
assert names["backend_path"] == f"{publisher.replace('-', '_')}.{expected_snake}"
assert (
names["backend_entry"]
== f"superset_extensions.{publisher.replace('-', '_')}.{expected_snake}.entrypoint"
== f"{publisher.replace('-', '_')}.{expected_snake}.entrypoint"
)
@@ -476,8 +473,8 @@ def test_manual_technical_name_override():
assert names["id"] == "acme.chart-builder" # Composite ID
assert names["mf_name"] == "acme_chartBuilder" # Module Federation format
assert names["backend_package"] == "acme-chart_builder" # Collision-safe
assert names["backend_path"] == "superset_extensions.acme.chart_builder"
assert names["backend_entry"] == "superset_extensions.acme.chart_builder.entrypoint"
assert names["backend_path"] == "acme.chart_builder"
assert names["backend_entry"] == "acme.chart_builder.entrypoint"
def test_generate_names_uses_suggested_technical_names():

View File

@@ -49,8 +49,8 @@ def template_context():
"npm_name": "@test-org/test-extension",
"mf_name": "testOrg_testExtension",
"backend_package": "test_org-test_extension",
"backend_path": "superset_extensions.test_org.test_extension",
"backend_entry": "superset_extensions.test_org.test_extension.entrypoint",
"backend_path": "test_org.test_extension",
"backend_entry": "test_org.test_extension.entrypoint",
"version": "0.1.0",
"license": "Apache-2.0",
"include_frontend": True,
@@ -197,8 +197,8 @@ def test_template_rendering_with_different_ids(
"npm_name": f"@{publisher}/{technical_name}",
"mf_name": get_module_federation_name(publisher, technical_name),
"backend_package": f"{publisher_snake}-{name_snake}",
"backend_path": f"superset_extensions.{publisher_snake}.{name_snake}",
"backend_entry": f"superset_extensions.{publisher_snake}.{name_snake}.entrypoint",
"backend_path": f"{publisher_snake}.{name_snake}",
"backend_entry": f"{publisher_snake}.{name_snake}.entrypoint",
"version": "1.0.0",
"license": "MIT",
"include_frontend": True,
@@ -274,8 +274,8 @@ def test_template_rendering_with_different_licenses(jinja_env, license_type):
"npm_name": "@test-pub/test-ext",
"mf_name": "testPub_testExt",
"backend_package": "test_pub-test_ext",
"backend_path": "superset_extensions.test_pub.test_ext",
"backend_entry": "superset_extensions.test_pub.test_ext.entrypoint",
"backend_path": "test_pub.test_ext",
"backend_entry": "test_pub.test_ext.entrypoint",
"version": "1.0.0",
"license": license_type,
"include_frontend": True,
@@ -347,8 +347,8 @@ def test_template_context_edge_cases(jinja_env):
"npm_name": "@min/minimal",
"mf_name": "min_minimal",
"backend_package": "min-minimal",
"backend_path": "superset_extensions.min.minimal",
"backend_entry": "superset_extensions.min.minimal.entrypoint",
"backend_path": "min.minimal",
"backend_entry": "min.minimal.entrypoint",
"version": "1.0.0",
"license": "MIT",
"include_frontend": False,

View File

@@ -1 +1 @@
v20.18.3
v22.22.0

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import { withJsx } from '@mihkeleidast/storybook-addon-source';
import { themeObject, css, exampleThemes } from '@apache-superset/core/ui';
import { themeObject, css, exampleThemes } from '@apache-superset/core/theme';
import { combineReducers, createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';

View File

@@ -18,7 +18,7 @@
*/
import { useState, ReactNode, SyntheticEvent } from 'react';
import { styled } from '@apache-superset/core/ui';
import { styled } from '@apache-superset/core/theme';
import type { Decorator } from '@storybook/react';
import { ResizeCallbackData } from 'react-resizable';
import ResizablePanel, { Size } from './ResizablePanel';

View File

@@ -23,7 +23,7 @@ import {
ResizableBoxProps,
ResizeCallbackData,
} from 'react-resizable';
import { styled } from '@apache-superset/core/ui';
import { styled } from '@apache-superset/core/theme';
import 'react-resizable/css/styles.css';

View File

@@ -18,7 +18,7 @@
*/
import { Component, ReactNode } from 'react';
import { t } from '@apache-superset/core';
import { t } from '@apache-superset/core/translation';
import {
SupersetClient,
Method,

View File

@@ -69,7 +69,7 @@ module.exports = {
],
coverageReporters: ['lcov', 'json-summary', 'html', 'text'],
transformIgnorePatterns: [
'node_modules/(?!d3-(array|interpolate|color|time|scale|time-format|format)|internmap|@mapbox/tiny-sdf|remark-gfm|(?!@ngrx|(?!deck.gl)|d3-scale)|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|uuid|@rjsf/*.|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|geostyler|geostyler-.*|react-error-boundary|react-json-tree|react-base16-styling|lodash-es|rbush|quickselect)',
'node_modules/(?!d3-(array|interpolate|color|time|scale|time-format|format)|internmap|@mapbox/tiny-sdf|remark-gfm|(?!@ngrx|(?!deck.gl)|d3-scale)|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|uuid|@rjsf/*.|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|geostyler|geostyler-.*|react-error-boundary|react-json-tree|react-base16-styling|lodash-es|rbush|quickselect|react-diff-viewer-continued)',
],
preset: 'ts-jest',
transform: {

View File

@@ -237,8 +237,7 @@
"jsx-a11y/no-noninteractive-tabindex": "error",
"jsx-a11y/no-redundant-roles": "error",
"jsx-a11y/no-static-element-interactions": "off",
// TODO: Fix missing aria-selected on tab roles
"jsx-a11y/role-has-required-aria-props": "warn",
"jsx-a11y/role-has-required-aria-props": "error",
"jsx-a11y/role-supports-aria-props": "error",
"jsx-a11y/scope": "error",
"jsx-a11y/tabindex-no-positive": "error",

File diff suppressed because it is too large Load Diff

View File

@@ -175,7 +175,7 @@
"geostyler-openlayers-parser": "^4.3.0",
"geostyler-style": "7.5.0",
"geostyler-wfs-parser": "^2.0.3",
"google-auth-library": "^10.5.0",
"google-auth-library": "^10.6.1",
"immer": "^11.1.4",
"interweave": "^13.1.1",
"jquery": "^4.0.0",
@@ -197,7 +197,7 @@
"react": "^17.0.2",
"react-arborist": "^3.4.3",
"react-checkbox-tree": "^1.8.0",
"react-diff-viewer-continued": "^3.4.0",
"react-diff-viewer-continued": "^4.2.0",
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^17.0.2",
@@ -311,7 +311,7 @@
"copy-webpack-plugin": "^13.0.1",
"cross-env": "^10.1.0",
"css-loader": "^7.1.4",
"css-minimizer-webpack-plugin": "^7.0.4",
"css-minimizer-webpack-plugin": "^8.0.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^7.2.0",
"eslint-import-resolver-alias": "^1.1.2",
@@ -325,7 +325,7 @@
"eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-react-prefer-function-component": "^5.0.0",
"eslint-plugin-react-you-might-not-need-an-effect": "^0.9.1",
"eslint-plugin-react-you-might-not-need-an-effect": "^0.9.2",
"eslint-plugin-storybook": "^0.8.0",
"eslint-plugin-testing-library": "^7.16.0",
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
@@ -335,20 +335,20 @@
"html-webpack-plugin": "^5.6.6",
"http-server": "^14.1.1",
"imports-loader": "^5.0.0",
"jest": "^30.2.0",
"jest": "^30.3.0",
"jest-environment-jsdom": "^29.7.0",
"jest-html-reporter": "^4.3.0",
"jest-websocket-mock": "^2.5.0",
"js-yaml-loader": "^1.2.2",
"jsdom": "^28.1.0",
"lerna": "^8.2.3",
"lightningcss": "^1.31.1",
"lightningcss": "^1.32.0",
"mini-css-extract-plugin": "^2.10.0",
"open-cli": "^8.0.0",
"oxlint": "^1.51.0",
"po2json": "^0.4.5",
"prettier": "3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"prettier-plugin-packagejson": "^3.0.2",
"process": "^0.11.10",
"react-refresh": "^0.18.0",
"react-resizable": "^3.1.3",
@@ -368,7 +368,7 @@
"unzipper": "^0.12.3",
"vm-browserify": "^1.1.2",
"wait-on": "^9.0.4",
"webpack": "^5.105.3",
"webpack": "^5.105.4",
"webpack-bundle-analyzer": "^5.2.0",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.3",
@@ -384,7 +384,7 @@
"regenerator-runtime": "^0.14.1"
},
"engines": {
"node": "^20.18.1",
"node": "^22.22.0",
"npm": "^10.8.1"
},
"overrides": {

View File

@@ -36,7 +36,7 @@
"devDependencies": {
"cross-env": "^10.1.0",
"fs-extra": "^11.3.3",
"jest": "^30.2.0",
"jest": "^30.3.0",
"yeoman-test": "^11.3.1"
},
"engines": {

View File

@@ -30,74 +30,98 @@ The official core package for building Apache Superset extensions and integratio
npm install @apache-superset/core
```
## 🏗️ Architecture
## 🏗️ Package Structure
The package is organized into logical namespaces, each providing specific functionality:
The source is organized into focused namespaces, each in its own directory:
- **`authentication`** - User authentication and authorization APIs
- **`commands`** - Command registration and execution system
- **`contributions`** - UI contribution points and customization APIs
- **`core`** - Fundamental types, utilities, and lifecycle management
- **`environment`** - Environment detection and configuration APIs
- **`extensions`** - Extension management and metadata APIs
- **`sqlLab`** - SQL Lab integration and event handling
```
src/
├── authentication/
├── commands/
├── common/
├── components/
├── contributions/
├── editors/
├── extensions/
├── menus/
├── sqlLab/
├── theme/
├── translation/
├── utils/
├── views/
└── index.ts
```
## 🚀 Quick Start
### Basic Extension Structure
Frontend contributions are registered as module-level side effects from your extension's entry point.
```typescript
import {
core,
commands,
sqlLab,
authentication,
} from '@apache-superset/core';
### Views
export function activate(context: core.ExtensionContext) {
// Register a command to save current query
const commandDisposable = commands.registerCommand(
'my_extension.save_query',
async () => {
const currentTab = sqlLab.getCurrentTab();
if (currentTab?.editor.content) {
const token = await authentication.getCSRFToken();
// Use token for secure API calls
console.log('Saving query with CSRF token:', token);
}
},
);
Add custom panels or UI components at specific locations in the application:
// Listen for query execution events
const eventDisposable = sqlLab.onDidQueryRun(editor => {
console.log('Query executed:', editor.content.substring(0, 50) + '...');
});
```tsx
import { views } from '@apache-superset/core';
import MyPanel from './MyPanel';
// Register a simple view
const viewDisposable = core.registerViewProvider(
'my_extension.panel',
() => (
<div>
<h3>My Extension</h3>
<button onClick={() => commands.executeCommand('my_extension.save_query')}>
Save Query
</button>
</div>
)
);
// Cleanup registration
context.subscriptions.push(commandDisposable, eventDisposable, viewDisposable);
}
export function deactivate() {
// Cleanup handled automatically via disposables
}
views.registerView(
{ id: 'my-extension.main', name: 'My Panel Name' },
'sqllab.panels',
() => <MyPanel />,
);
```
## 🤝 Contributing
### Commands
We welcome contributions! Please see the [Developer Portal](https://superset.apache.org/developer_portal/) for details.
Define named actions that can be triggered from menus, keyboard shortcuts, or code:
```typescript
import { commands } from '@apache-superset/core';
commands.registerCommand(
{
id: 'my-extension.copy-query',
title: 'Copy Query',
icon: 'CopyOutlined',
description: 'Copy the current query to clipboard',
},
() => {
/* implementation */
},
);
```
### Menus
Attach commands to primary, secondary, or context menus at a given location:
```typescript
import { menus } from '@apache-superset/core';
menus.registerMenuItem(
{ view: 'sqllab.editor', command: 'my-extension.copy-query' },
'sqllab.editor',
'primary',
);
```
### Editors
Replace the default text editor for one or more languages:
```typescript
import { editors } from '@apache-superset/core';
import MonacoSQLEditor from './MonacoSQLEditor';
editors.registerEditor(
{
id: 'my-extension.monaco-sql',
name: 'Monaco SQL Editor',
languages: ['sql'],
},
MonacoSQLEditor,
);
```
## 📄 License
@@ -105,12 +129,6 @@ Licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com
## 🔗 Links
- [Apache Superset](https://superset.apache.org/)
- [Documentation](https://superset.apache.org/docs/)
- [Community](https://superset.apache.org/community/)
- [GitHub Repository](https://github.com/apache/superset)
- [Extension Development Guide](https://superset.apache.org/docs/extensions/)
---
**Note**: This package is currently in release candidate status. APIs may change before the 1.0.0 release. Please check the [changelog](CHANGELOG.md) for breaking changes between versions.
- [Extensions Documentation](https://superset.apache.org/developer-docs/extensions/overview)

View File

@@ -1,10 +1,72 @@
{
"name": "@apache-superset/core",
"version": "0.0.1-rc11",
"version": "0.1.0-rc1",
"description": "This package contains UI elements, APIs, and utility functions used by Superset.",
"sideEffects": false,
"main": "lib/index.js",
"types": "lib/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
},
"./common": {
"types": "./lib/common/index.d.ts",
"default": "./lib/common/index.js"
},
"./authentication": {
"types": "./lib/authentication/index.d.ts",
"default": "./lib/authentication/index.js"
},
"./commands": {
"types": "./lib/commands/index.d.ts",
"default": "./lib/commands/index.js"
},
"./editors": {
"types": "./lib/editors/index.d.ts",
"default": "./lib/editors/index.js"
},
"./extensions": {
"types": "./lib/extensions/index.d.ts",
"default": "./lib/extensions/index.js"
},
"./menus": {
"types": "./lib/menus/index.d.ts",
"default": "./lib/menus/index.js"
},
"./sqlLab": {
"types": "./lib/sqlLab/index.d.ts",
"default": "./lib/sqlLab/index.js"
},
"./views": {
"types": "./lib/views/index.d.ts",
"default": "./lib/views/index.js"
},
"./contributions": {
"types": "./lib/contributions/index.d.ts",
"default": "./lib/contributions/index.js"
},
"./theme": {
"types": "./lib/theme/index.d.ts",
"default": "./lib/theme/index.js"
},
"./translation": {
"types": "./lib/translation/index.d.ts",
"default": "./lib/translation/index.js"
},
"./components": {
"types": "./lib/components/index.d.ts",
"default": "./lib/components/index.js"
},
"./utils": {
"types": "./lib/utils/index.d.ts",
"default": "./lib/utils/index.js"
},
"./testing": {
"types": "./lib/testing.d.ts",
"default": "./lib/testing.js"
}
},
"files": [
"lib"
],

View File

@@ -1,44 +0,0 @@
/**
* 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.
*/
/**
* @fileoverview Main entry point for the Superset Extension API.
*
* This module exports all public APIs for Superset extensions, providing
* a unified interface for extension developers to interact with the Superset
* platform. The API includes:
*
* - `authentication`: Handle user authentication and authorization
* - `commands`: Execute Superset commands and operations
* - `contributions`: Register UI contributions and customizations
* - `core`: Access fundamental Superset types and utilities
* - `editors`: Register custom text editor implementations
* - `extensions`: Manage extension lifecycle and metadata
* - `sqlLab`: Integrate with SQL Lab functionality
*/
export * as authentication from './authentication';
export * as commands from './commands';
export * as contributions from './contributions';
export * as core from './core';
export * as editors from './editors';
export * as extensions from './extensions';
export * as menus from './menus';
export * as sqlLab from './sqlLab';
export * as views from './views';

View File

@@ -25,7 +25,7 @@
* via keyboard shortcuts, menu items, programmatic calls, or other user interactions.
*/
import { Disposable } from './core';
import { Disposable } from '../common';
/**
* Describes a command that can be contributed to the application.
@@ -38,7 +38,7 @@ export interface Command {
/** The icon associated with the command. */
icon?: string;
/** A description of what the command does. */
description: string;
description?: string;
}
/**

View File

@@ -26,10 +26,10 @@
* menus, editors) and re-exported here for the manifest schema.
*/
import { Command } from './commands';
import { View } from './views';
import { Menu } from './menus';
import { Editor } from './editors';
import { Command } from '../commands';
import { View } from '../views';
import { Menu } from '../menus';
import { Editor } from '../editors';
/**
* Valid locations within SQL Lab.

View File

@@ -34,8 +34,8 @@
*/
import { ForwardRefExoticComponent, RefAttributes } from 'react';
import { Disposable, Event } from './core';
import type { SupersetTheme } from '../ui';
import { Disposable, Event } from '../common';
import type { SupersetTheme } from '../theme';
/**
* Supported editor languages.
@@ -480,6 +480,18 @@ export interface EditorHandle {
* @returns A Disposable that removes the provider when disposed
*/
registerCompletionProvider(provider: CompletionProvider): Disposable;
/**
* Force the editor to recalculate its dimensions.
* Called when the container size changes or when the editor becomes
* visible after being hidden (e.g., in a tab).
*
* Each editor implementation maps this to their equivalent:
* - Ace: editor.resize()
* - Monaco: editor.layout()
* - CodeMirror: editor.requestMeasure()
*/
resize(): void;
}
/**
@@ -501,17 +513,17 @@ export interface EditorProvider {
}
/**
* Event fired when an editor provider is registered.
* Event fired when an editor is registered.
*/
export interface EditorProviderRegisteredEvent {
/** The registered provider */
provider: EditorProvider;
export interface EditorRegisteredEvent {
/** The descriptor of the editor that was registered */
editor: Editor;
}
/**
* Event fired when an editor provider is unregistered.
* Event fired when an editor is unregistered.
*/
export interface EditorProviderUnregisteredEvent {
export interface EditorUnregisteredEvent {
/** The descriptor of the editor that was unregistered */
editor: Editor;
}
@@ -545,7 +557,7 @@ export declare function registerEditor(
* @param language The language to get an editor for
* @returns The editor provider or undefined if no extension provides one
*/
export declare function getEditorProvider(
export declare function getEditor(
language: EditorLanguage,
): EditorProvider | undefined;
@@ -555,21 +567,21 @@ export declare function getEditorProvider(
* @param language The language to check
* @returns True if an extension provides an editor for this language
*/
export declare function hasEditorProvider(language: EditorLanguage): boolean;
export declare function hasEditor(language: EditorLanguage): boolean;
/**
* Get all registered editor providers.
*
* @returns Array of all registered editor providers
*/
export declare function getAllEditorProviders(): EditorProvider[];
export declare function getAllEditors(): EditorProvider[];
/**
* Event fired when an editor provider is registered.
* Event fired when an editor is registered.
*/
export declare const onDidRegisterEditorProvider: Event<EditorProviderRegisteredEvent>;
export declare const onDidRegisterEditor: Event<EditorRegisteredEvent>;
/**
* Event fired when an editor provider is unregistered.
* Event fired when an editor is unregistered.
*/
export declare const onDidUnregisterEditorProvider: Event<EditorProviderUnregisteredEvent>;
export declare const onDidUnregisterEditor: Event<EditorUnregisteredEvent>;

View File

@@ -26,7 +26,7 @@
* in the extension ecosystem.
*/
import { Extension } from './core';
import { Extension } from '../common';
/**
* Get an extension by its full identifier in the form of: `publisher.name`.

View File

@@ -16,6 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
export * from './api';
export * from './ui';
export * from './utils';
export * as common from './common';
export * as authentication from './authentication';
export * as commands from './commands';
export * as editors from './editors';
export * as extensions from './extensions';
export * as menus from './menus';
export * as sqlLab from './sqlLab';
export * as views from './views';
export * as contributions from './contributions';
export * as theme from './theme';
export * as translation from './translation';
export * as components from './components';
export * as utils from './utils';

View File

@@ -37,7 +37,7 @@
* ```
*/
import { Disposable } from './core';
import { Disposable } from '../common';
/**
* Represents a menu item that links a view to a command.
@@ -47,6 +47,8 @@ export interface MenuItem {
view: string;
/** The command to execute when this menu item is selected. */
command: string;
/** Optional description of the menu item, for display in contribution manifests. */
description?: string;
}
/**

View File

@@ -29,8 +29,8 @@
* - Global APIs: Functions and events available across the entire SQL Lab interface
*/
import { Event, Database, SupersetError, Column } from './core';
import { EditorHandle } from './editors';
import { Event, Database, SupersetError, Column } from '../common';
import { EditorHandle } from '../editors';
/**
* Provides imperative control over the code editor component.

View File

@@ -98,6 +98,17 @@ export const GlobalStyles = () => {
[role='button'] {
cursor: pointer;
}
// Override geostyler CSS that hides AntD ColorPicker alpha input
// See: https://github.com/apache/superset/issues/34721
.ant-color-picker .ant-color-picker-alpha-input {
display: block;
}
.ant-color-picker .ant-color-picker-slider-alpha {
display: flex;
margin-top: ${theme.marginXS}px;
}
`}
/>
);

Some files were not shown because too many files have changed in this diff Show More