Compare commits

..

85 Commits

Author SHA1 Message Date
Claude
d59840db73 fix(dropdown-container): prevent empty popover from opening when alwaysShowDropdownButton is set
Gates popover open state and onOpenChange on popoverContent existence,
addressing review feedback that the button could trigger an empty popover
during layout recalculation. Adds a test covering this behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 10:00:51 -07:00
Evan Rusackas
d0c4a0aff0 fix(dashboard): prevent filter dropdown button from disappearing during layout recalculations
Add `alwaysShowDropdownButton` prop to DropdownContainer to keep the dropdown
button visible even when no items are overflowing. This prevents the button from
flickering/disappearing when the filter bar recalculates layout, which caused
a confusing UX where users would lose access to overflowed filters momentarily.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 10:00:51 -07:00
mcdogg17
5865176f36 fix(dashboard): overload issue in dashboard export to excel (#29418)
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 09:29:32 -07:00
dependabot[bot]
461037f645 chore(deps-dev): bump typescript-eslint from 8.56.1 to 8.57.1 in /docs (#38684)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-17 21:54:27 +07:00
dependabot[bot]
c980f39aab chore(deps): bump caniuse-lite from 1.0.30001778 to 1.0.30001780 in /docs (#38688)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-17 21:51:39 +07:00
Beto Dealmeida
a854fa60a2 feat: apply RLS conservatively (#38683) 2026-03-17 10:20:09 -04:00
Amin Ghadersohi
1c8224f4c6 feat(mcp): Add tool annotations for MCP directory compliance (#38641) 2026-03-16 19:09:25 -07:00
Đỗ Trọng Hải
ca403dc45d fix(ci): allow docs testing to run despite absence of db diagnostics data (#38655)
Signed-off-by: hainenber <dotronghai96@gmail.com>
2026-03-17 08:01:43 +07:00
João Pedro Alves Barbosa
96705c156a fix(map-box): make opacity, lon, lat, and zoom controls functional (#38374)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: codeant-ai-for-open-source[bot] <244253245+codeant-ai-for-open-source[bot]@users.noreply.github.com>
2026-03-16 21:55:49 -03:00
endimonan
7909095ff3 feat(native-filters): add configurable LIKE/ILIKE operators to Select filter (#38470)
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Richard Fogaca Nienkotter <63572350+richardfogaca@users.noreply.github.com>
2026-03-16 21:11:53 -03:00
Joe Li
aa5adb0fce fix(embedded): default to light theme instead of system preference (#38644)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 09:33:33 -07:00
Ville Brofeldt
dcb414aa06 feat(extensions): add update command to extensions cli (#38651) 2026-03-16 07:02:42 -07:00
Đỗ Trọng Hải
afe093f1ca fix(FilterBar): reduce padded space between filter header and first filter (#38646)
Signed-off-by: hainenber <dotronghai96@gmail.com>
2026-03-16 10:00:52 -03:00
Mayank Aggarwal
cc066b3576 fix(docs): use absolute API doc links in developer docs (#38649) 2026-03-14 22:50:14 +07:00
dependabot[bot]
39cd1cdd43 chore(deps): bump dawidd6/action-download-artifact from 16 to 17 (#38620)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-14 12:40:25 +07:00
dependabot[bot]
176bf00c16 chore(deps-dev): bump baseline-browser-mapping from 2.10.0 to 2.10.7 + sync lockfile + run npm audit fix in /superset-frontend (#38621)
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: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2026-03-14 12:28:47 +07:00
dependabot[bot]
68e38c8893 chore(deps): bump undici from 7.22.0 to 7.24.1 in /superset-frontend (#38642)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-14 11:13:50 +07:00
Amin Ghadersohi
48220fb33f feat(mcp): add save_sql_query tool for SQL Lab saved queries (#38414)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 14:02:04 -07:00
Mehmet Salih Yavuz
ed622e254a feat(matrixify): Revamp control panel (#38519) 2026-03-13 21:51:53 +03:00
amaannawab923
6e7d6a85b4 fix(ag-grid-table): fix failing buildQuery test expectation (#38636) 2026-03-14 00:59:52 +07:00
Luiz Otavio
e8061a9c2b style(metadata-bar): use bold font weight for metadata bar title (#38608) 2026-03-13 14:24:14 -03:00
Amin Ghadersohi
97a66f7a64 feat(mcp): add BM25 tool search transform to reduce initial context size (#38562)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 18:06:11 +01:00
Amin Ghadersohi
b6c3b3ef46 fix(mcp): return all statement results for multi-statement SQL queries (#38388)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 16:53:52 +01:00
dependabot[bot]
f4a57a13bc chore(deps): bump dompurify from 3.3.2 to 3.3.3 in /superset-frontend (#38592)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com>
2026-03-13 22:07:09 +07:00
dependabot[bot]
242636b36b chore(deps): bump baseline-browser-mapping from 2.10.0 to 2.10.7 in /docs (#38622)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-13 22:06:43 +07:00
dependabot[bot]
ba7d7dcec0 chore(deps): bump react-syntax-highlighter from 16.1.0 to 16.1.1 in /superset-frontend (#38619)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-13 22:06:21 +07:00
Rafael Benitez
ba7271b4d8 fix(world-map): add fallback fill color when colorFn returns null (#38602)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:02:03 -03:00
amaannawab923
ca2d26a1e2 fix(ag-grid-table): fix AND filter conditions not applied (#38369) 2026-03-13 19:42:14 +05:30
Alexandru Soare
f6106cd26f fix(timeshiftcolor): Time shift color to match the original color (#38473) 2026-03-13 15:24:56 +02:00
Michael S. Molina
1867336907 fix(editor): implement missing methods, fix cursor position clearing (#38603) 2026-03-13 09:06:55 -03:00
Ville Brofeldt
f5383263bc fix(extensions): fix gitignore template and bump version (#38614) 2026-03-13 08:50:10 -03:00
Amin Ghadersohi
d5cf77cd60 fix(mcp): fix crashes in list tools, dataset info, chart preview, and add owner/favorite filters (#38277)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:46:52 +01:00
Amin Ghadersohi
f458e2d484 feat(mcp): add extra_form_data param to get_chart_data for dashboard filters (#38531)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:13:47 +01:00
Kamil Gabryjelski
af5e05db2e fix(mcp): Support form_data_key without chart identifier for unsaved charts (#38628) 2026-03-13 11:58:12 +01:00
Enzo Martellucci
32a64d02c7 fix(deckgl): polygon chart not rendering when boundary column contains nested geometry JSON (#38595) 2026-03-13 11:54:05 +01:00
Enzo Martellucci
9516d1a306 fix(explore/dashboard): fix CSV/Excel downloads for legacy chart types (#38513)
Co-authored-by: Diego Pucci <diegopucci.me@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: codeant-ai-for-open-source[bot] <244253245+codeant-ai-for-open-source[bot]@users.noreply.github.com>
2026-03-13 11:49:34 +01:00
Kamil Gabryjelski
d91b96814e fix(mcp): Improve validation errors and field aliases to reduce failed LLM tool calls (#38625) 2026-03-13 11:16:50 +01:00
Daniel Vaz Gaspar
56d6bb1913 feat(auth): add SAML login support to frontend (#38606) 2026-03-13 09:00:07 +00:00
Amin Ghadersohi
fc156d0014 fix(mcp): replace uuid with url and changed_on_humanized in default list columns (#38566)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 09:53:30 +01:00
Đỗ Trọng Hải
0b8df8d3f2 build(deps): update geostyler-* deps to latest major versions (#38151)
Signed-off-by: hainenber <dotronghai96@gmail.com>
2026-03-13 01:18:59 -07:00
Amin Ghadersohi
83955e87ac refactor(mcp): use serialize_user_object in get_instance_info (#38613) 2026-03-13 08:59:21 +01:00
dependabot[bot]
4a9db243a1 chore(deps): bump caniuse-lite from 1.0.30001777 to 1.0.30001778 in /docs (#38593)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-13 09:35:13 +07:00
Amin Ghadersohi
d4f1f8db00 fix(mcp): extract role names as strings in UserInfo serialization (#38612) 2026-03-12 16:20:53 -07:00
Mehmet Salih Yavuz
95f61bd223 fix: add parent_slice_id for multilayer charts to embed (#38243) 2026-03-12 21:21:43 +03:00
Mehmet Salih Yavuz
7f476a79b3 fix: add embedded box sizing rule for layout (#38351)
Co-authored-by: Joe Li <joe@preset.io>
2026-03-12 21:20:14 +03:00
Amin Ghadersohi
65e21cf13c docs: move MCP deployment guide to admin docs, add user-facing AI guide (#38585) 2026-03-12 10:30:51 -07:00
Amin Ghadersohi
7943af359c feat(mcp): implement RBAC permission checking for MCP tools (#38407)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:35:07 +01:00
Yuriy Krasilnikov
09e9c6a522 fix(embedded): prevent double RLS application in virtual datasets (#37395) 2026-03-12 14:12:59 +01:00
Ville Brofeldt
a9def2fc15 fix: support nested function calls in cache_key_wrapper (#38569) 2026-03-12 08:08:58 -03:00
Alexandru Soare
27197faba9 fix(matrixify): Matrixify to not override slice id (#38515)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-03-12 11:39:29 +02:00
dependabot[bot]
ffe60bd960 chore(deps-dev): bump oxlint from 1.51.0 to 1.53.0 in /superset-frontend (#38571)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-11 23:41:41 +07:00
dependabot[bot]
d752be5f74 chore(deps): bump dompurify from 3.3.1 to 3.3.2 in /superset-frontend (#38455)
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-11 08:51:40 -07:00
dependabot[bot]
3056c41507 chore(deps): bump caniuse-lite from 1.0.30001775 to 1.0.30001777 in /docs (#38463)
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-11 08:51:21 -07:00
dependabot[bot]
d42e9c4d1b chore(deps): bump acorn from 8.9.0 to 8.16.0 in /superset-frontend (#38466)
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-11 08:51:10 -07:00
dependabot[bot]
5912941942 chore(deps-dev): bump @typescript-eslint/parser from 8.56.1 to 8.57.0 in /superset-websocket (#38570)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-11 22:50:11 +07:00
dependabot[bot]
9b8106b382 chore(deps-dev): bump mini-css-extract-plugin from 2.10.0 to 2.10.1 in /superset-frontend (#38573)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-11 22:23:08 +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
540 changed files with 7589 additions and 34796 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

@@ -26,16 +26,16 @@ runs:
- name: Set up QEMU
if: ${{ inputs.build == 'true' }}
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
if: ${{ inputs.build == 'true' }}
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
uses: docker/setup-buildx-action@v3
- name: Try to login to DockerHub
if: ${{ inputs.login-to-dockerhub == 'true' }}
continue-on-error: true
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
uses: docker/login-action@v3
with:
username: ${{ inputs.dockerhub-user }}
password: ${{ inputs.dockerhub-token }}

View File

@@ -32,7 +32,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: true
ref: master
@@ -41,7 +41,7 @@ jobs:
uses: ./.github/actions/setup-supersetbot/
- name: Set up Python ${{ inputs.python-version }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
uses: actions/setup-python@v6
with:
python-version: "3.10"

View File

@@ -31,7 +31,7 @@ jobs:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
if: steps.check_queued.outputs.count >= 20
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: Cancel duplicate workflow runs
if: steps.check_queued.outputs.count >= 20

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive

View File

@@ -25,9 +25,9 @@ jobs:
pull-requests: write
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: Check and notify
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{ github.token }}
script: |

View File

@@ -44,7 +44,7 @@ jobs:
pull-requests: write
steps:
- name: Comment access denied
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
script: |
const message = `👋 Hi @${{ github.event.comment.user.login || github.event.review.user.login || github.event.issue.user.login }}!
@@ -71,12 +71,12 @@ jobs:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Run Claude PR Action
uses: anthropics/claude-code-action@28f83620103c48a57093dcc2837eec89e036bb9f # beta
uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
timeout_minutes: "60"

View File

@@ -31,7 +31,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: Check for file changes
id: check

View File

@@ -27,9 +27,9 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout Repository"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: "Dependency Review"
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
uses: actions/dependency-review-action@v4
continue-on-error: true
with:
fail-on-severity: critical
@@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: "Checkout Repository"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: Setup Python
uses: ./.github/actions/setup-backend/

View File

@@ -14,6 +14,7 @@ concurrency:
cancel-in-progress: true
jobs:
setup_matrix:
runs-on: ubuntu-24.04
outputs:
@@ -39,8 +40,9 @@ jobs:
IMAGE_TAG: apache/superset:GHA-${{ matrix.build_preset }}-${{ github.run_id }}
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
@@ -89,7 +91,7 @@ jobs:
# in the context of push (using multi-platform build), we need to pull the image locally
- name: Docker pull
if: github.event_name == 'push' && (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker)
run: docker pull $IMAGE_TAG
run: docker pull $IMAGE_TAG
- name: Print docker stats
if: steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker
@@ -99,6 +101,23 @@ jobs:
docker images $IMAGE_TAG
docker history $IMAGE_TAG
# 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@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
with:
image-ref: ${{ env.IMAGE_TAG }}
format: 'sarif'
output: 'trivy-results.sarif'
vuln-type: 'os'
severity: 'CRITICAL,HIGH'
ignore-unfixed: true
- name: Upload Trivy scan results to GitHub Security tab
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: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: 'trivy-results.sarif'
- name: docker-compose sanity check
if: (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker) && matrix.build_preset == 'dev'
shell: bash
@@ -115,7 +134,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Check for file changes

View File

@@ -28,8 +28,8 @@ jobs:
run:
working-directory: superset-embedded-sdk
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: './superset-embedded-sdk/.nvmrc'
registry-url: 'https://registry.npmjs.org'

View File

@@ -18,8 +18,8 @@ jobs:
run:
working-directory: superset-embedded-sdk
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: './superset-embedded-sdk/.nvmrc'
registry-url: 'https://registry.npmjs.org'

View File

@@ -69,7 +69,7 @@ jobs:
- name: Comment (success)
if: steps.describe-services.outputs.active == 'true'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{github.token}}
script: |

View File

@@ -63,7 +63,7 @@ jobs:
- name: Get event SHA
id: get-sha
if: steps.eval-label.outputs.result == 'up'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
@@ -94,7 +94,7 @@ jobs:
core.setOutput("sha", prSha);
- name: Looking for feature flags in PR description
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
id: eval-feature-flags
if: steps.eval-label.outputs.result == 'up'
with:
@@ -116,7 +116,7 @@ jobs:
return results;
- name: Reply with confirmation comment
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
if: steps.eval-label.outputs.result == 'up'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -160,7 +160,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ needs.ephemeral-env-label.outputs.sha }} : ${{steps.get-sha.outputs.sha}} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
ref: ${{ needs.ephemeral-env-label.outputs.sha }}
persist-credentials: false
@@ -189,7 +189,7 @@ jobs:
--extra-flags "--build-arg INCLUDE_CHROMIUM=false"
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
@@ -197,7 +197,7 @@ jobs:
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@c962da2960ed15f492addc26fffa274485265950 # v2
uses: aws-actions/amazon-ecr-login@v2
- name: Load, tag and push image to ECR
id: push-image
@@ -220,12 +220,12 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
@@ -233,7 +233,7 @@ jobs:
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@c962da2960ed15f492addc26fffa274485265950 # v2
uses: aws-actions/amazon-ecr-login@v2
- name: Check target image exists in ECR
id: check-image
@@ -248,7 +248,7 @@ jobs:
- name: Fail on missing container image
if: steps.check-image.outcome == 'failure'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{ github.token }}
script: |
@@ -263,7 +263,7 @@ jobs:
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@77954e213ba1f9f9cb016b86a1d4f6fcdea0d57e # v1
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: .github/workflows/ecs-task-definition.json
container-name: superset-ci
@@ -296,7 +296,7 @@ jobs:
--tags key=pr,value=$PR_NUMBER key=github_user,value=${{ github.actor }}
- name: Deploy Amazon ECS task definition
id: deploy-task
uses: aws-actions/amazon-ecs-deploy-task-definition@cbf54ec46642b86ff78c2f5793da6746954cf8ff # v2
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service
@@ -318,7 +318,7 @@ jobs:
echo "ip=$(aws ec2 describe-network-interfaces --network-interface-ids ${{ steps.get-eni.outputs.eni }} | jq -r '.NetworkInterfaces | first | .Association.PublicIp')" >> $GITHUB_OUTPUT
- name: Comment (success)
if: ${{ success() }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{github.token}}
script: |
@@ -331,7 +331,7 @@ jobs:
});
- name: Comment (failure)
if: ${{ failure() }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{github.token}}
script: |

View File

@@ -27,12 +27,12 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "11"

View File

@@ -14,10 +14,10 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version: '20'

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive

View File

@@ -15,12 +15,12 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '11'

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Check for 'hold' label
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |

View File

@@ -16,7 +16,7 @@ jobs:
pull-requests: write
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive

View File

@@ -24,7 +24,7 @@ jobs:
python-version: ["current", "previous", "next"]
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -42,7 +42,7 @@ jobs:
echo "HOMEBREW_REPOSITORY=$HOMEBREW_REPOSITORY" >>"${GITHUB_ENV}"
brew install norwoodj/tap/helm-docs
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version: '20'
@@ -57,18 +57,24 @@ jobs:
yarn install --immutable
- name: Cache pre-commit environments
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache@v5
with:
path: ~/.cache/pre-commit
key: pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
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

@@ -26,7 +26,7 @@ jobs:
name: Bump version and publish package(s)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v6
with:
# pulls all commits (needed for lerna / semantic release to correctly version)
fetch-depth: 0
@@ -42,13 +42,13 @@ jobs:
- name: Install Node.js
if: env.HAS_TAGS
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: './superset-frontend/.nvmrc'
- name: Cache npm
if: env.HAS_TAGS
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache@v5
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
@@ -62,7 +62,7 @@ jobs:
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- name: Cache npm
if: env.HAS_TAGS
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache@v5
id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}

View File

@@ -37,7 +37,7 @@ jobs:
steps:
- name: Security Check - Authorize Maintainers Only
id: auth
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -147,7 +147,7 @@ jobs:
- name: Checkout PR code (only if build needed)
if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.build_needed == 'true'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
ref: ${{ steps.check.outputs.target_sha }}
persist-credentials: false

View File

@@ -37,7 +37,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive

View File

@@ -38,21 +38,21 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.event.workflow_run.head_sha || github.sha }}"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: "./docs/.nvmrc"
node-version-file: './docs/.nvmrc'
- name: Setup Python
uses: ./.github/actions/setup-backend/
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
- uses: actions/setup-java@v5
with:
distribution: "zulu"
java-version: "21"
distribution: 'zulu'
java-version: '21'
- name: Install Graphviz
run: sudo apt-get install -y graphviz
- name: Compute Entity Relationship diagram (ERD)
@@ -68,7 +68,7 @@ jobs:
yarn install --check-cache
- name: Download database diagnostics (if triggered by integration tests)
if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success'
uses: dawidd6/action-download-artifact@8a338493df3d275e4a7a63bcff3b8fe97e51a927 # v19
uses: dawidd6/action-download-artifact@v17
continue-on-error: true
with:
workflow: superset-python-integrationtest.yml
@@ -77,7 +77,7 @@ jobs:
path: docs/src/data/
- name: Try to download latest diagnostics (for push/dispatch triggers)
if: github.event_name != 'workflow_run'
uses: dawidd6/action-download-artifact@8a338493df3d275e4a7a63bcff3b8fe97e51a927 # v19
uses: dawidd6/action-download-artifact@v17
continue-on-error: true
with:
workflow: superset-python-integrationtest.yml

View File

@@ -24,10 +24,10 @@ jobs:
name: Link Checking
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v6
# Do not bump this linkinator-action version without opening
# an ASF Infra ticket to allow the new version first!
- uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
- uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
continue-on-error: true # This will make the job advisory (non-blocking, no red X)
with:
paths: "**/*.md, **/*.mdx"
@@ -67,14 +67,14 @@ jobs:
working-directory: docs
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: "./docs/.nvmrc"
node-version-file: './docs/.nvmrc'
- name: yarn install
run: |
yarn install --check-cache
@@ -98,26 +98,26 @@ jobs:
working-directory: docs
steps:
- name: "Checkout PR head: ${{ github.event.workflow_run.head_sha }}"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
ref: ${{ github.event.workflow_run.head_sha }}
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: "./docs/.nvmrc"
node-version-file: './docs/.nvmrc'
- name: yarn install
run: |
yarn install --check-cache
- name: Download database diagnostics from integration tests
uses: dawidd6/action-download-artifact@8a338493df3d275e4a7a63bcff3b8fe97e51a927 # v19
uses: dawidd6/action-download-artifact@v17
with:
workflow: superset-python-integrationtest.yml
run_id: ${{ github.event.workflow_run.id }}
name: database-diagnostics
path: docs/src/data/
if_no_artifact_found: "warning"
if_no_artifact_found: 'warning'
- name: Use fresh diagnostics
run: |
if [ -f "src/data/databases-diagnostics.json" ]; then

View File

@@ -69,21 +69,21 @@ jobs:
# Conditional checkout based on context
- name: Checkout for push or pull_request event
if: github.event_name == 'push' || github.event_name == 'pull_request'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Checkout using ref (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ github.event.inputs.ref }}
submodules: recursive
- name: Checkout using PR ID (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
@@ -109,7 +109,7 @@ jobs:
run: testdata
- name: Setup Node.js
if: steps.check.outputs.python || steps.check.outputs.frontend
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: './superset-frontend/.nvmrc'
- name: Install npm dependencies
@@ -146,7 +146,7 @@ jobs:
SAFE_APP_ROOT=${APP_ROOT//\//_}
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
- name: Upload Artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
if: failure()
with:
path: ${{ github.workspace }}/superset-frontend/cypress-base/cypress/screenshots
@@ -186,21 +186,21 @@ jobs:
# Conditional checkout based on context (same as Cypress workflow)
- name: Checkout for push or pull_request event
if: github.event_name == 'push' || github.event_name == 'pull_request'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Checkout using ref (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ github.event.inputs.ref }}
submodules: recursive
- name: Checkout using PR ID (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
@@ -226,7 +226,7 @@ jobs:
run: playwright_testdata
- name: Setup Node.js
if: steps.check.outputs.python || steps.check.outputs.frontend
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: './superset-frontend/.nvmrc'
- name: Install npm dependencies
@@ -259,7 +259,7 @@ jobs:
SAFE_APP_ROOT=${APP_ROOT//\//_}
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
- name: Upload Playwright Artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
if: failure()
with:
path: |

View File

@@ -24,7 +24,7 @@ jobs:
working-directory: superset-extensions-cli
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -49,7 +49,7 @@ jobs:
- name: Upload coverage reports to Codecov
if: steps.check.outputs.superset-extensions-cli
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
file: ./coverage.xml
flags: superset-extensions-cli
@@ -58,7 +58,7 @@ jobs:
- name: Upload HTML coverage report
if: steps.check.outputs.superset-extensions-cli
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
with:
name: superset-extensions-cli-coverage-html
path: htmlcov/

View File

@@ -23,7 +23,7 @@ jobs:
should-run: ${{ steps.check.outputs.frontend }}
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 0
@@ -58,7 +58,7 @@ jobs:
- name: Upload Docker Image Artifact
if: steps.check.outputs.frontend
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
with:
name: docker-image
path: docker-image.tar.gz
@@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
uses: actions/download-artifact@v8
with:
name: docker-image
@@ -90,7 +90,7 @@ jobs:
"npm run test -- --coverage --shard=${{ matrix.shard }}/8 --coverageReporters=json"
- name: Upload Coverage Artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
with:
name: coverage-artifacts-${{ matrix.shard }}
path: superset-frontend/coverage
@@ -103,14 +103,14 @@ jobs:
id-token: write
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 0
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Download Coverage Artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
uses: actions/download-artifact@v8
with:
pattern: coverage-artifacts-*
path: coverage/
@@ -127,7 +127,7 @@ jobs:
run: npx nyc merge coverage/ merged-output/coverage-summary.json
- name: Upload Code Coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: javascript
use_oidc: true
@@ -142,7 +142,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
uses: actions/download-artifact@v8
with:
name: docker-image
@@ -166,7 +166,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
uses: actions/download-artifact@v8
with:
name: docker-image
@@ -184,7 +184,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
uses: actions/download-artifact@v8
with:
name: docker-image

View File

@@ -16,14 +16,14 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
uses: azure/setup-helm@v4
with:
version: v3.16.4

View File

@@ -29,7 +29,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref_name }}
persist-credentials: true
@@ -42,7 +42,7 @@ jobs:
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
uses: azure/setup-helm@v4
with:
version: v3.5.4
@@ -101,7 +101,7 @@ jobs:
CR_RELEASE_NAME_TEMPLATE: "superset-helm-chart-{{ .Version }}"
- name: Open Pull Request
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
script: |
const branchName = '${{ env.branch_name }}';

View File

@@ -60,21 +60,21 @@ jobs:
# Conditional checkout based on context (same as Cypress workflow)
- name: Checkout for push or pull_request event
if: github.event_name == 'push' || github.event_name == 'pull_request'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Checkout using ref (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ github.event.inputs.ref }}
submodules: recursive
- name: Checkout using PR ID (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
@@ -100,7 +100,7 @@ jobs:
run: playwright_testdata
- name: Setup Node.js
if: steps.check.outputs.python || steps.check.outputs.frontend
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: './superset-frontend/.nvmrc'
- name: Install npm dependencies
@@ -133,7 +133,7 @@ jobs:
SAFE_APP_ROOT=${APP_ROOT//\//_}
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
- name: Upload Playwright Artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
if: failure()
with:
path: |

View File

@@ -41,7 +41,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -68,7 +68,7 @@ jobs:
run: |
./scripts/python_tests.sh
- name: Upload code coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: python,mysql
token: ${{ secrets.CODECOV_TOKEN }}
@@ -98,7 +98,7 @@ jobs:
"
- name: Upload database diagnostics artifact
if: steps.check.outputs.python
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
with:
name: database-diagnostics
path: databases-diagnostics.json
@@ -129,7 +129,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -159,7 +159,7 @@ jobs:
run: |
./scripts/python_tests.sh
- name: Upload code coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: python,postgres
token: ${{ secrets.CODECOV_TOKEN }}
@@ -182,7 +182,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -211,7 +211,7 @@ jobs:
run: |
./scripts/python_tests.sh
- name: Upload code coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: python,sqlite
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -48,7 +48,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -77,7 +77,7 @@ jobs:
run: |
./scripts/python_tests.sh -m 'chart_data_flow or sql_json_flow'
- name: Upload code coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: python,presto
token: ${{ secrets.CODECOV_TOKEN }}
@@ -108,7 +108,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -145,7 +145,7 @@ jobs:
pip install -e .[hive]
./scripts/python_tests.sh -m 'chart_data_flow or sql_json_flow'
- name: Upload code coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: python,hive
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -24,7 +24,7 @@ jobs:
PYTHONPATH: ${{ github.workspace }}
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -53,7 +53,7 @@ jobs:
run: |
pytest --durations-min=0.5 --cov=superset/sql/ ./tests/unit_tests/sql/ --cache-clear --cov-fail-under=100
- name: Upload code coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: python,unit
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -31,7 +31,7 @@ jobs:
- name: Setup Node.js
if: steps.check.outputs.frontend
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: './superset-frontend/.nvmrc'
- name: Install dependencies
@@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -62,10 +62,6 @@ jobs:
- name: Setup Python
if: steps.check.outputs.python
uses: ./.github/actions/setup-backend/
- name: Install msgcat
run: sudo apt update && sudo apt install gettext
- name: Test babel extraction
if: steps.check.outputs.python
run: ./scripts/translations/babel_update.sh

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Install dependencies

View File

@@ -26,7 +26,7 @@ jobs:
steps:
- name: Quickly add thumbs up!
if: github.event_name == 'issue_comment' && contains(github.event.comment.body, '@supersetbot')
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
script: |
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/')
@@ -38,7 +38,7 @@ jobs:
});
- name: "Checkout ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false

View File

@@ -47,7 +47,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -60,7 +60,7 @@ jobs:
build: "true"
- name: Use Node.js 20
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version: 20
@@ -107,12 +107,12 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Use Node.js 20
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version: 20

View File

@@ -30,10 +30,10 @@ jobs:
name: Generate Reports
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: './superset-frontend/.nvmrc'

1
.gitignore vendored
View File

@@ -133,7 +133,6 @@ CLAUDE.local.md
PROJECT.md
.aider*
.claude_rc*
.claude/settings.local.json
.env.local
oxc-custom-build/
*.code-workspace

View File

@@ -50,4 +50,3 @@ under the License.
- [4.1.4](./CHANGELOG/4.1.4.md)
- [5.0.0](./CHANGELOG/5.0.0.md)
- [6.0.0](./CHANGELOG/6.0.0.md)
- [6.1.0](./CHANGELOG/6.1.0.md)

File diff suppressed because it is too large Load Diff

View File

@@ -24,12 +24,54 @@ assists people when migrating to a new version.
## Next
## 6.1.0
### Deck.gl MapBox viewport and opacity controls are functional
The Deck.gl MapBox chart's **Opacity**, **Default longitude**, **Default latitude**, and **Zoom** controls were previously non-functional — changing them had no effect on the rendered map. These controls are now wired up correctly.
**Behavior change for existing charts:** Previously, the viewport controls had hard-coded default values (`-122.405293`, `37.772123`, zoom `11` — San Francisco) that were stored in each chart's `form_data` but never applied. The map always used `fitBounds` to center on the data. With this fix, those stored values are now respected, which means existing MapBox charts may open centered on the old default coordinates instead of fitting to data bounds.
**To restore fit-to-data behavior:** Open the chart in Explore, clear the **Default longitude**, **Default latitude**, and **Zoom** fields in the Viewport section, and re-save the chart.
### ClickHouse minimum driver version bump
The minimum required version of `clickhouse-connect` has been raised to `>=0.13.0`. If you are using the ClickHouse connector, please upgrade your `clickhouse-connect` package. The `_mutate_label` workaround that appended hash suffixes to column aliases has also been removed, as it is no longer needed with modern versions of the driver.
### MCP Tool Observability
MCP (Model Context Protocol) tools now include enhanced observability instrumentation for monitoring and debugging:
**Two-layer instrumentation:**
1. **Middleware layer** (`LoggingMiddleware`): Automatically logs all MCP tool calls with `duration_ms` and `success` status in the audit log (Action Log UI, logs table)
2. **Sub-operation tracking**: All 19 MCP tools include granular `event_logger.log_context()` blocks for tracking individual operations like validation, database writes, and query execution
**Action naming convention:**
- Tool-level logs: `mcp_tool_call` (via middleware)
- Sub-operation logs: `mcp.{tool_name}.{operation}` (e.g., `mcp.generate_chart.validation`, `mcp.execute_sql.query_execution`)
**Querying MCP logs:**
```sql
-- Top slowest MCP operations
SELECT action, COUNT(*) as calls, AVG(duration_ms) as avg_ms
FROM logs
WHERE action LIKE 'mcp.%'
GROUP BY action
ORDER BY avg_ms DESC
LIMIT 20;
-- MCP tool success rate
SELECT
json_extract(curated_payload, '$.tool') as tool,
COUNT(*) as total_calls,
SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) as successful,
ROUND(100.0 * SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) / COUNT(*), 2) as success_rate
FROM logs
WHERE action = 'mcp_tool_call'
GROUP BY tool
ORDER BY total_calls DESC;
```
**Security note:** Sensitive parameters (passwords, API keys, tokens) are automatically redacted in logs as `[REDACTED]`.
### Distributed Coordination Backend
A new `DISTRIBUTED_COORDINATION_CONFIG` configuration provides a unified Redis-based backend for real-time coordination features in Superset. This backend enables:
@@ -41,7 +83,6 @@ A new `DISTRIBUTED_COORDINATION_CONFIG` configuration provides a unified Redis-b
The distributed coordination is used by the Global Task Framework (GTF) for abort notifications and task completion signaling, and will eventually replace `GLOBAL_ASYNC_QUERIES_CACHE_BACKEND` as the standard signaling backend. Configuring this is recommended for Redis enabled production deployments.
Example configuration in `superset_config.py`:
```python
DISTRIBUTED_COORDINATION_CONFIG = {
"CACHE_TYPE": "RedisCache",
@@ -56,11 +97,9 @@ See `superset/config.py` for complete configuration options.
### WebSocket config for GAQ with Docker
[35896](https://github.com/apache/superset/pull/35896) and [37624](https://github.com/apache/superset/pull/37624) updated documentation on how to run and configure Superset with Docker. Specifically for the WebSocket configuration, a new `docker/superset-websocket/config.example.json` was added to the repo, so that users could copy it to create a `docker/superset-websocket/config.json` file. The existing `docker/superset-websocket/config.json` was removed and git-ignored, so if you're using GAQ / WebSocket make sure to:
- Stash/backup your existing `config.json` file, to re-apply it after (will get git-ignored going forward)
- Update the `volumes` configuration for the `superset-websocket` service in your `docker-compose.override.yml` file, to include the `docker/superset-websocket/config.json` file. For example:
```yaml
``` yaml
services:
superset-websocket:
volumes:
@@ -73,9 +112,7 @@ services:
### Example Data Loading Improvements
#### New Directory Structure
Examples are now organized by name with data and configs co-located:
```
superset/examples/
├── _shared/ # Shared database & metadata configs
@@ -87,12 +124,31 @@ superset/examples/
└── ...
```
#### Simplified Parquet-based Loading
- Auto-discovery: create `superset/examples/my_dataset/data.parquet` to add a new example
- Parquet is an Apache project format: compressed (~27% smaller), self-describing schema
- YAML configs define datasets, charts, and dashboards declaratively
- Removed Python-based data generation from individual example files
#### Test Data Reorganization
- Moved `big_data.py` to `superset/cli/test_loaders.py` - better reflects its purpose as a test utility
- Fixed inverted logic for `--load-test-data` flag (now correctly includes .test.yaml files when flag is set)
- Clarified CLI flags:
- `--force` / `-f`: Force reload even if tables exist
- `--only-metadata` / `-m`: Create table metadata without loading data
- `--load-test-data` / `-t`: Include test dashboards and .test.yaml configs
- `--load-big-data` / `-b`: Generate synthetic stress-test data
#### Bug Fixes
- Fixed numpy array serialization for PostgreSQL (converts complex types to JSON strings)
- Fixed KeyError for `allow_csv_upload` field in database configs (now optional with default)
- Fixed test data loading logic that was incorrectly filtering files
### MCP Service
The MCP (Model Context Protocol) service enables AI assistants and automation tools to interact programmatically with Superset.
#### New Features
- MCP service infrastructure with FastMCP framework
- Tools for dashboards, charts, datasets, SQL Lab, and instance metadata
- Optional dependency: install with `pip install apache-superset[fastmcp]`
@@ -102,7 +158,6 @@ The MCP (Model Context Protocol) service enables AI assistants and automation to
#### New Configuration Options
**Development** (single-user, local testing):
```python
# superset_config.py
MCP_DEV_USERNAME = "admin" # User for MCP authentication
@@ -111,7 +166,6 @@ MCP_SERVICE_PORT = 5008
```
**Production** (JWT-based, multi-user):
```python
# superset_config.py
MCP_AUTH_ENABLED = True
@@ -157,14 +211,12 @@ superset mcp run --port 5008 --use-factory-config
The MCP service runs as a **separate process** from the Superset web server.
**Important**:
- Requires same Python environment and configuration as Superset
- Shares database connections with main Superset app
- Can be scaled independently from web server
- Requires `fastmcp` package (optional dependency)
**Installation**:
```bash
# Install with MCP support
pip install apache-superset[fastmcp]
@@ -178,7 +230,6 @@ Use systemd, supervisord, or Kubernetes to manage the MCP service process.
See `superset/mcp_service/PRODUCTION.md` for deployment guides.
**Security**:
- Development: Uses `MCP_DEV_USERNAME` for single-user access
- Production: **MUST** configure JWT authentication
- See `superset/mcp_service/SECURITY.md` for details
@@ -191,50 +242,14 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
- Developer Guide: `superset/mcp_service/CLAUDE.md`
- Quick Start: `superset/mcp_service/README.md`
### MCP Tool Observability
---
MCP (Model Context Protocol) tools now include enhanced observability instrumentation for monitoring and debugging:
**Two-layer instrumentation:**
1. **Middleware layer** (`LoggingMiddleware`): Automatically logs all MCP tool calls with `duration_ms` and `success` status in the audit log (Action Log UI, logs table)
2. **Sub-operation tracking**: All 19 MCP tools include granular `event_logger.log_context()` blocks for tracking individual operations like validation, database writes, and query execution
**Action naming convention:**
- Tool-level logs: `mcp_tool_call` (via middleware)
- Sub-operation logs: `mcp.{tool_name}.{operation}` (e.g., `mcp.generate_chart.validation`, `mcp.execute_sql.query_execution`)
**Querying MCP logs:**
```sql
-- Top slowest MCP operations
SELECT action, COUNT(*) as calls, AVG(duration_ms) as avg_ms
FROM logs
WHERE action LIKE 'mcp.%'
GROUP BY action
ORDER BY avg_ms DESC
LIMIT 20;
-- MCP tool success rate
SELECT
json_extract(curated_payload, '$.tool') as tool,
COUNT(*) as total_calls,
SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) as successful,
ROUND(100.0 * SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) / COUNT(*), 2) as success_rate
FROM logs
WHERE action = 'mcp_tool_call'
GROUP BY tool
ORDER BY total_calls DESC;
```
**Security note:** Sensitive parameters (passwords, API keys, tokens) are automatically redacted in logs as `[REDACTED]`.
### APP_NAME configuration
- [35621](https://github.com/apache/superset/pull/35621): The default hash algorithm has changed from MD5 to SHA-256 for improved security and FedRAMP compliance. This affects cache keys for thumbnails, dashboard digests, chart digests, and filter option names. Existing cached data will be invalidated upon upgrade. To opt out of this change and maintain backward compatibility, set `HASH_ALGORITHM = "md5"` in your `superset_config.py`.
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
### Breaking Changes
- [37370](https://github.com/apache/superset/pull/37370): The `APP_NAME` configuration variable no longer controls the browser window/tab title or other frontend branding. Application names should now be configured using the theme system with the `brandAppName` token. The `APP_NAME` config is still used for backend contexts (MCP service, logs, etc.) and serves as a fallback if `brandAppName` is not set.
- **Migration:**
```python
# Before (Superset 5.x)
APP_NAME = "My Custom App"
@@ -253,22 +268,16 @@ ORDER BY total_calls DESC;
APP_NAME = "My Custom App"
# But you should migrate to THEME_DEFAULT.token.brandAppName
```
- **Note:** For dark mode, set the same tokens in `THEME_DARK` configuration.
### CUSTOM_FONT_URLS configuration
- [36317](https://github.com/apache/superset/pull/36317): The `CUSTOM_FONT_URLS` configuration option has been removed. Use the new per-theme `fontUrls` token in `THEME_DEFAULT` or database-managed themes instead.
- **Before:**
```python
CUSTOM_FONT_URLS = [
"https://fonts.example.com/myfont.css",
]
```
- **After:**
```python
THEME_DEFAULT = {
"token": {
@@ -280,13 +289,7 @@ ORDER BY total_calls DESC;
}
```
### Other
- [35621](https://github.com/apache/superset/pull/35621): The default hash algorithm has changed from MD5 to SHA-256 for improved security and FedRAMP compliance. This affects cache keys for thumbnails, dashboard digests, chart digests, and filter option names. Existing cached data will be invalidated upon upgrade. To opt out of this change and maintain backward compatibility, set `HASH_ALGORITHM = "md5"` in your `superset_config.py`.
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
## 6.0.0
- [33055](https://github.com/apache/superset/pull/33055): Upgrades Flask-AppBuilder to 5.0.0. The AUTH_OID authentication type has been deprecated and is no longer available as an option in Flask-AppBuilder. OpenID (OID) is considered a deprecated authentication protocol - if you are using AUTH_OID, you will need to migrate to an alternative authentication method such as OAuth, LDAP, or database authentication before upgrading.
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
- [34782](https://github.com/apache/superset/pull/34782): Dataset exports now include the dataset ID in their file name (similar to charts and dashboards). If managing assets as code, make sure to rename existing dataset YAMLs to include the ID (and avoid duplicated files).
@@ -295,8 +298,8 @@ ORDER BY total_calls DESC;
- Change any hex color values to one of: `"success"`, `"processing"`, `"error"`, `"warning"`, `"default"`
- Custom colors are no longer supported to maintain consistency with Ant Design components
- [34561](https://github.com/apache/superset/pull/34561) Added tiled screenshot functionality for Playwright-based reports to handle large dashboards more efficiently. When enabled (default: `SCREENSHOT_TILED_ENABLED = True`), dashboards with 20+ charts or height exceeding 5000px will be captured using multiple viewport-sized tiles and combined into a single image. This improves report generation performance and reliability for large dashboards.
Note: Pillow is now a required dependency (previously optional) to support image processing for tiled screenshots.
`thumbnails` optional dependency is now deprecated and will be removed in the next major release (7.0).
Note: Pillow is now a required dependency (previously optional) to support image processing for tiled screenshots.
`thumbnails` optional dependency is now deprecated and will be removed in the next major release (7.0).
- [33084](https://github.com/apache/superset/pull/33084) The DISALLOWED_SQL_FUNCTIONS configuration now includes additional potentially sensitive database functions across PostgreSQL, MySQL, SQLite, MS SQL Server, and ClickHouse. Existing queries using these functions may now be blocked. Review your SQL Lab queries and dashboards if you encounter "disallowed function" errors after upgrading
- [34235](https://github.com/apache/superset/pull/34235) CSV exports now use `utf-8-sig` encoding by default to include a UTF-8 BOM, improving compatibility with Excel.
- [34258](https://github.com/apache/superset/pull/34258) changing the default in Dockerfile to INCLUDE_CHROMIUM="false" (from "true") in the past. This ensures the `lean` layer is lean by default, and people can opt-in to the `chromium` layer by setting the build arg `INCLUDE_CHROMIUM=true`. This is a breaking change for anyone using the `lean` layer, as it will no longer include Chromium by default.
@@ -686,6 +689,7 @@ ORDER BY total_calls DESC;
- [11509](https://github.com/apache/superset/pull/12491): Dataset metadata updates check user ownership, only owners or an Admin are allowed.
- Security simplification (SIP-19), the following permission domains were simplified:
- [12072](https://github.com/apache/superset/pull/12072): `Query` with `can_read`, `can_write`
- [12036](https://github.com/apache/superset/pull/12036): `Database` with `can_read`, `can_write`.
- [12012](https://github.com/apache/superset/pull/12036): `Dashboard` with `can_read`, `can_write`.

View File

@@ -1,162 +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.
*/}
---
title: AWS IAM Authentication
sidebar_label: AWS IAM Authentication
sidebar_position: 15
---
# AWS IAM Authentication for AWS Databases
Superset supports IAM-based authentication for **Amazon Aurora** (PostgreSQL and MySQL) and **Amazon Redshift**. IAM auth eliminates the need for database passwords — Superset generates a short-lived auth token using temporary AWS credentials instead.
Cross-account IAM role assumption via STS `AssumeRole` is supported, allowing a Superset deployment in one AWS account to connect to databases in a different account.
## Prerequisites
- Enable the `AWS_DATABASE_IAM_AUTH` feature flag in `superset_config.py`. IAM authentication is gated behind this flag; if it is disabled, connections using `aws_iam` fail with *"AWS IAM database authentication is not enabled."*
```python
FEATURE_FLAGS = {
"AWS_DATABASE_IAM_AUTH": True,
}
```
- `boto3` must be installed in your Superset environment:
```bash
pip install boto3
```
- The Superset server's IAM role (or static credentials) must have permission to call `sts:AssumeRole` (for cross-account) or the same-account permissions for the target service:
- **Aurora (RDS)**: `rds-db:connect`
- **Redshift provisioned**: `redshift:GetClusterCredentials`
- **Redshift Serverless**: `redshift-serverless:GetCredentials` and `redshift-serverless:GetWorkgroup`
- SSL must be enabled on the Aurora / Redshift endpoint (required for IAM token auth).
## Configuration
IAM authentication is configured via the **encrypted_extra** field of the database connection. Access this field in the **Advanced** → **Security** section of the database connection form, under **Secure Extra**.
### Aurora PostgreSQL or Aurora MySQL
```json
{
"aws_iam": {
"enabled": true,
"role_arn": "arn:aws:iam::222222222222:role/SupersetDatabaseAccess",
"external_id": "superset-prod-12345",
"region": "us-east-1",
"db_username": "superset_iam_user",
"session_duration": 3600
}
}
```
| Field | Required | Description |
|-------|----------|-------------|
| `enabled` | Yes | Set to `true` to activate IAM auth |
| `role_arn` | No | ARN of the cross-account IAM role to assume via STS. Omit for same-account auth |
| `external_id` | No | External ID for the STS `AssumeRole` call, if required by the target role's trust policy |
| `region` | Yes | AWS region of the database cluster |
| `db_username` | Yes | The database username associated with the IAM identity |
| `session_duration` | No | STS session duration in seconds (default: `3600`) |
### Redshift (Serverless)
```json
{
"aws_iam": {
"enabled": true,
"role_arn": "arn:aws:iam::222222222222:role/SupersetRedshiftAccess",
"region": "us-east-1",
"workgroup_name": "my-workgroup",
"db_name": "dev"
}
}
```
### Redshift (Provisioned Cluster)
```json
{
"aws_iam": {
"enabled": true,
"role_arn": "arn:aws:iam::222222222222:role/SupersetRedshiftAccess",
"region": "us-east-1",
"cluster_identifier": "my-cluster",
"db_username": "superset_iam_user",
"db_name": "dev"
}
}
```
## Cross-Account IAM Setup
To connect to a database in Account B from a Superset deployment in Account A:
**1. In Account B — create a database-access role:**
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["rds-db:connect"],
"Resource": "arn:aws:rds-db:us-east-1:222222222222:dbuser/db-XXXXXXXXXXXX/superset_iam_user"
}
]
}
```
**Trust policy** (allows Account A's Superset role to assume it):
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/SupersetInstanceRole"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "superset-prod-12345"
}
}
}
]
}
```
**2. In Account A — grant Superset's role permission to assume the Account B role:**
```json
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::222222222222:role/SupersetDatabaseAccess"
}
```
**3. Configure the database connection in Superset** using the `role_arn` and `external_id` from the trust policy (as shown in the configuration example above).
## Credential Caching
STS credentials are cached in memory keyed by `(role_arn, region, external_id)` with a 10-minute TTL. This reduces the number of STS API calls when multiple queries are executed with the same connection. Tokens are refreshed automatically before expiry.

View File

@@ -138,33 +138,14 @@ THUMBNAIL_CACHE_CONFIG = init_thumbnail_cache
```
Using the above example cache keys for dashboards will be `superset_thumb__dashboard__{ID}`. You can
override the base URL for Selenium using:
override the base URL for selenium using:
```
WEBDRIVER_BASEURL = "https://superset.company.com"
```
To control which user account is used for rendering thumbnails and warming up caches, configure
`THUMBNAIL_EXECUTORS` and `CACHE_WARMUP_EXECUTORS`. Each accepts a list of executor types (which
resolve to an owner, creator, modifier, or the currently-logged-in user) and/or a `FixedExecutor`
pinned to a specific username. By default, thumbnails render as the current user
(`ExecutorType.CURRENT_USER`) and cache warmup runs as the chart/dashboard owner
(`ExecutorType.OWNER`).
To force both to run as a dedicated service account (`admin` in this example):
```python
from superset.tasks.types import ExecutorType, FixedExecutor
THUMBNAIL_EXECUTORS = [FixedExecutor("admin")]
CACHE_WARMUP_EXECUTORS = [FixedExecutor("admin")]
```
Use a dedicated read-only service account here rather than a personal admin account, so that
thumbnail rendering and cache warmup tasks don't fail if a specific user's credentials change.
Additional Selenium WebDriver configuration can be set using `WEBDRIVER_CONFIGURATION`. You can
implement a custom function to authenticate Selenium. The default function uses the `flask-login`
Additional selenium web drive configuration can be set using `WEBDRIVER_CONFIGURATION`. You can
implement a custom function to authenticate selenium. The default function uses the `flask-login`
session cookie. Here's an example of a custom function signature:
```python
@@ -178,20 +159,6 @@ Then on configuration:
WEBDRIVER_AUTH_FUNC = auth_driver
```
## ETag Support for Thumbnails
Thumbnail and screenshot endpoints return `ETag` response headers based on the cached content digest. Clients can use conditional requests to avoid downloading unchanged images:
```
GET /api/v1/chart/42/thumbnail/
If-None-Match: "abc123..."
→ 304 Not Modified (if unchanged)
→ 200 OK (with new image if changed)
```
This is particularly useful for embedded dashboards and external integrations that periodically poll for updated screenshots — unchanged thumbnails return immediately with no payload.
## Distributed Coordination Backend
Superset supports an optional distributed coordination (`DISTRIBUTED_COORDINATION_CONFIG`) for

View File

@@ -364,26 +364,6 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
]
```
### PKCE Support
For public OAuth2 clients that cannot securely store a client secret, enable Proof Key for Code Exchange (PKCE) by adding `code_challenge_method` to the `remote_app` configuration:
```python
OAUTH_PROVIDERS = [
{
'name': 'myProvider',
'remote_app': {
'client_id': 'myClientId',
'client_secret': 'mySecret', # may be empty for pure public clients
'code_challenge_method': 'S256', # enables PKCE
'server_metadata_url': 'https://myAuthorizationServer/.well-known/openid-configuration'
}
}
]
```
PKCE (`S256`) is recommended for all OAuth2 flows, even when a client secret is present, as it protects against authorization code interception attacks.
## LDAP Authentication
FAB supports authenticating user credentials against an LDAP server.

View File

@@ -10,10 +10,6 @@ version: 1
The superset cli allows you to import and export datasources from and to YAML. Datasources include
databases. The data is expected to be organized in the following hierarchy:
:::info
Superset's ZIP-based import/export also covers **dashboards**, **charts**, and **saved queries**, exercised through the UI and REST API. The [Dashboard Import Overwrite Behavior](#dashboard-import-overwrite-behavior) and [UUIDs in API Responses](#uuids-in-api-responses) sections below document the behavior shared across all asset types.
:::
```text
├──databases
| ├──database_1
@@ -30,10 +26,6 @@ Superset's ZIP-based import/export also covers **dashboards**, **charts**, and *
| └── ... (more databases)
```
:::note
When you export a database connection, the `masked_encrypted_extra` field (used for sensitive connection parameters such as service account JSON, OAuth tokens, and other encrypted credentials) is included in the export. When importing on another instance, these values are decrypted and re-encrypted using the destination instance's `SECRET_KEY`. Ensure the receiving instance has a valid `SECRET_KEY` configured before importing.
:::
## Exporting Datasources to YAML
You can print your current datasources to stdout by running:
@@ -83,29 +75,6 @@ The optional username flag **-u** sets the user used for the datasource import.
superset import_datasources -p <path / filename> -u 'admin'
```
## Dashboard Import Overwrite Behavior
When importing a dashboard ZIP with the **overwrite** option enabled, any existing charts that are part of the dashboard are **replaced** rather than duplicated. This applies to:
- Charts whose UUID matches a chart already present in the target instance
- The full chart configuration (query, visualization type, columns, metrics) is replaced by the imported version
If you import without the overwrite flag, existing charts with conflicting UUIDs are left unchanged and the import skips those objects. Use overwrite when you want to push a fully updated dashboard (including chart definitions) from a development or staging environment to production.
## UUIDs in API Responses
The REST API POST endpoints for **datasets**, **charts**, and **dashboards** include the auto-generated `uuid` field in the response body:
```json
{
"id": 42,
"uuid": "b8a8d5c3-1234-4abc-8def-0123456789ab",
...
}
```
UUIDs remain stable across import/export cycles and can be used for cross-environment workflows — for example, recording a UUID when creating a chart in development and using it to identify the matching chart after importing into production.
## Legacy Importing Datasources
### From older versions of Superset to current version

View File

@@ -501,7 +501,6 @@ All MCP settings go in `superset_config.py`. Defaults are defined in `superset/m
| `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) |
| `MCP_RBAC_ENABLED` | `True` | Enforce Superset's role-based access control on MCP tool calls. When `True`, each tool checks that the authenticated user has the required FAB permission before executing. Disable only for testing or trusted-network deployments. |
### Authentication
@@ -517,7 +516,6 @@ All MCP settings go in `superset_config.py`. Defaults are defined in `superset/m
| `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 |
| `MCP_USER_RESOLVER` | `None` | Custom function `(app, access_token) -> username` to extract a Superset username from a validated JWT token. When `None`, the default resolver checks `preferred_username`, `username`, `email`, and `sub` claims in that order. |
### Response Size Guard
@@ -601,43 +599,6 @@ MCP_STORE_CONFIG = {
| `event_store_max_events` | `100` | Maximum events retained per session |
| `event_store_ttl` | `3600` | Event TTL in seconds |
### Tool Search
By default the MCP server exposes a lightweight tool-search interface instead of advertising every tool at once. This reduces the initial context sent to the LLM by ~70%, which lowers cost and latency. The AI client discovers tools on demand by calling `search_tools` and then invokes them via `call_tool`.
```python
MCP_TOOL_SEARCH_CONFIG = {
"enabled": True,
"strategy": "bm25", # "bm25" (natural language) or "regex"
"max_results": 5,
"always_visible": [ # Tools always listed (pinned)
"health_check",
"get_instance_info",
],
"search_tool_name": "search_tools",
"call_tool_name": "call_tool",
"include_schemas": False, # False=summary mode (name + parameters_hint)
"compact_schemas": True, # Strip $defs (only applies when include_schemas=True)
"max_description_length": 300,
}
```
| Key | Default | Description |
|-----|---------|-------------|
| `enabled` | `True` | Enable tool search. When `False`, all tools are listed upfront |
| `strategy` | `"bm25"` | Search ranking algorithm. `"bm25"` supports natural language; `"regex"` supports pattern matching |
| `max_results` | `5` | Maximum tools returned per search query |
| `always_visible` | See above | Tools that always appear in `list_tools`, regardless of search |
| `include_schemas` | `False` | When `False` (default, "summary mode"), search results omit `inputSchema` entirely and include a lightweight `parameters_hint` listing top-level parameter names. Set to `True` to include the full `inputSchema` in search results. Full schemas are always used when a tool is actually invoked via `call_tool`. |
| `compact_schemas` | `True` | Strip `$defs` / `$ref` and replace with `{"type": "object"}` in search results to reduce token cost. Only takes effect when `include_schemas=True` — ignored in summary mode. |
| `max_description_length` | `300` | Truncate tool descriptions in search results (0 = no truncation). Applies in both summary and full-schema modes. |
:::tip
Set `enabled: False` to revert to the traditional "show all tools at once" behavior, which some clients or workflows may prefer.
:::
Tool search reduces the initial token cost from ~1520K tokens (full catalog) down to ~45K tokens (pinned tools + search interface) — roughly 85% savings at the start of each conversation.
### Session & CSRF
These values are flat-merged into the Flask app config used by the MCP server process:
@@ -659,102 +620,6 @@ MCP_CSRF_CONFIG = {
---
## Access Control
### RBAC Enforcement
The MCP server respects Superset's full role-based access control (RBAC). Every authenticated user can only access the data and operations their Superset roles permit — the same rules that apply in the Superset UI apply through MCP.
Each tool declares one or more required FAB permissions. The table below maps tool groups to their permission requirements:
| Tool group | Required FAB permission |
|------------|------------------------|
| `list_charts`, `get_chart_info`, `get_chart_data`, `get_chart_preview`, `generate_chart`, `update_chart` | `can_read` on `Chart` (read), `can_write` on `Chart` (mutate) |
| `list_dashboards`, `get_dashboard_info`, `generate_dashboard`, `add_chart_to_existing_dashboard` | `can_read` on `Dashboard` (read), `can_write` on `Dashboard` (mutate) |
| `list_datasets`, `get_dataset_info`, `create_virtual_dataset` | `can_read` on `Dataset` (read), `can_write` on `Dataset` (mutate) |
| `list_databases`, `get_database_info` | `can_read` on `Database` |
| `execute_sql` | `can_execute_sql_query` on `SQLLab` |
| `open_sql_lab_with_context` | `can_read` on `SQLLab` |
| `save_sql_query` | `can_write` on `SavedQuery` |
| `health_check` | None (public) |
To disable RBAC checking globally (for trusted-network deployments or testing), set:
```python
# superset_config.py
MCP_RBAC_ENABLED = False
```
:::warning
Disabling RBAC removes all permission checks from MCP tool calls. Only do this on isolated, internal deployments where all MCP users are trusted admins.
:::
### Audit Log
All MCP tool calls are recorded in Superset's action log. You can view them at **Settings → Action Log** (admin only). Each log entry records:
- The tool name (e.g., `mcp.generate_chart.db_write`)
- The authenticated user
- A timestamp
This makes MCP activity fully auditable alongside regular Superset activity. The action log uses the same event logger as the rest of Superset, so existing log ingestion pipelines (e.g., sending logs to Elasticsearch or a SIEM) capture MCP events automatically.
### Middleware Pipeline
Every MCP request passes through a middleware stack before reaching the tool function. The default stack (assembled in `build_middleware_list()` in `server.py`) is:
| Middleware | Purpose | Default |
|------------|---------|---------|
| `StructuredContentStripperMiddleware` | Strips `structuredContent` from responses for Claude.ai bridge compatibility | Enabled |
| `LoggingMiddleware` | Logs each tool call with user, parameters, and duration | Enabled |
| `GlobalErrorHandlerMiddleware` | Catches unhandled exceptions and sanitizes sensitive data before it reaches the client | Enabled |
| `ResponseSizeGuardMiddleware` | Estimates token count, warns at 80% of limit, blocks at limit | Enabled (configurable via `MCP_RESPONSE_SIZE_CONFIG`) |
| `ResponseCachingMiddleware` | Caches read-heavy tool responses (in-memory or Redis) | Disabled (enable via `MCP_CACHE_CONFIG`) |
Additional middleware classes (`RateLimitMiddleware`, `FieldPermissionsMiddleware`, `PrivateToolMiddleware`) are implemented in `superset/mcp_service/middleware.py` but are not added to the default pipeline. They are available for operators who want to layer them in via a custom startup path.
### Error Sanitization
The `GlobalErrorHandlerMiddleware` automatically redacts sensitive information from all error messages before they reach the LLM client. The following are replaced with generic messages:
- **Database connection strings** — replaced with a generic connection error message
- **API keys and tokens** — redacted from error traces
- **File system paths** — stripped to prevent information disclosure
- **IP addresses** — removed from error context
This ensures that a misconfigured database connection or an unexpected exception never leaks credentials or internal topology to the LLM or its users. All regex patterns used for redaction are bounded to prevent ReDoS attacks.
---
## Performance
### Connection Pooling
Each MCP server process maintains its own SQLAlchemy connection pool to the database. For multi-worker deployments, total open connections = **workers × pool size**.
```python
# superset_config.py
SQLALCHEMY_POOL_SIZE = 5
SQLALCHEMY_MAX_OVERFLOW = 10
SQLALCHEMY_POOL_TIMEOUT = 30
SQLALCHEMY_POOL_RECYCLE = 3600 # Recycle connections after 1 hour
```
For a 3-pod Kubernetes deployment with the defaults above, expect up to 3 × (5 + 10) = 45 connections. Size your database's `max_connections` accordingly.
### Response Caching
Enable response caching for read-heavy workloads (dashboards/datasets that don't change frequently). With the in-memory backend (default when `MCP_STORE_CONFIG` is disabled), caching is per-process. Use Redis-backed caching for consistent cache hits across multiple pods:
```python
MCP_CACHE_CONFIG = {"enabled": True, "call_tool_ttl": 3600}
MCP_STORE_CONFIG = {"enabled": True, "CACHE_REDIS_URL": "redis://redis:6379/0"}
```
Mutating tools (`generate_chart`, `update_chart`, `execute_sql`, `generate_dashboard`) are always excluded from caching regardless of this setting.
---
## Troubleshooting
### Server won't start
@@ -799,32 +664,6 @@ Mutating tools (`generate_chart`, `update_chart`, `execute_sql`, `generate_dashb
---
## Audit Events
All MCP tool calls are logged to Superset's event logger, the same system used by the web UI (viewable at **Settings → Action Log**). Each event captures:
- **Action**: `mcp.<tool_name>.<phase>` (e.g., `mcp.list_databases.query`)
- **User**: the resolved Superset username from the JWT or dev config
- **Timestamp**: when the operation ran
This means MCP activity is auditable alongside normal user activity. No additional configuration is required — logging is on by default whenever the event logger is enabled in your Superset deployment.
## Tool Pagination
MCP list tools (`list_datasets`, `list_charts`, `list_dashboards`, `list_databases`) use **offset pagination** via `page` (1-based) and `page_size` parameters. Responses include `page`, `page_size`, `total_count`, `total_pages`, `has_previous`, and `has_next`. To iterate through all results:
```python
# Example: fetch all charts across pages
all_charts = []
page = 1
while True:
result = mcp.list_charts(page=page, page_size=50)
all_charts.extend(result["charts"])
if not result.get("has_next"):
break
page += 1
```
## Security Best Practices
- **Use TLS** for all production MCP endpoints -- place the server behind a reverse proxy with HTTPS

View File

@@ -84,35 +84,6 @@ THEME_DARK = {
# - OS preference detection is automatically enabled
```
### App Branding
The application name shown in the browser title bar and navigation can be
set via the `brandAppName` theme token:
```python
THEME_DEFAULT = {
"token": {
"brandAppName": "Acme Analytics",
# ... other tokens
}
}
```
Or in the theme CRUD UI JSON editor:
```json
{
"token": {
"brandAppName": "Acme Analytics"
}
}
```
The existing `APP_NAME` Python config key continues to work for backward compatibility.
`brandAppName` takes precedence when both are set, and allows different themes to carry different brand names.
Email and alert/report notification subjects are driven by backend settings such as
`EMAIL_REPORTS_SUBJECT_PREFIX` and `APP_NAME`, not by this theme token.
### Migration from Configuration to UI
When `ENABLE_UI_THEME_ADMINISTRATION = True`:
@@ -341,25 +312,11 @@ Available chart types for `echartsOptionsOverridesByChartType`:
- `echarts_heatmap` - Heatmaps
- `echarts_mixed_timeseries` - Mixed time series
### Array Property Overrides
Array properties (such as color palettes) are fully supported in overrides. Arrays are **replaced entirely** rather than merged, so specify the complete array:
```python
THEME_DEFAULT = {
"token": { ... },
"echartsOptionsOverrides": {
# Replace the default color palette for all ECharts visualizations
"color": ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b"]
}
}
```
### Best Practices
1. **Start with global overrides** for consistent styling across all charts
2. **Use chart-specific overrides** for unique requirements per visualization type
3. **Test thoroughly** as overrides use deep merge for objects, but arrays are completely replaced — always specify the full array value
3. **Test thoroughly** as overrides use deep merge - nested objects are combined, but arrays are completely replaced
4. **Document your overrides** to help team members understand custom styling
5. **Consider performance** - complex overrides may impact chart rendering speed

View File

@@ -44,15 +44,6 @@ only see the objects that they have access to.
The **sql_lab** role grants access to SQL Lab. Note that while **Admin** users have access
to all databases by default, both **Alpha** and **Gamma** users need to be given access on a per database basis.
Beyond the base `sql_lab` role, two additional SQL Lab permissions must be explicitly granted for users who need these capabilities:
| Permission | Feature |
|------------|---------|
| `can_estimate_query_cost` on `SQLLab` | Estimate query cost before running |
| `can_format_sql` on `SQLLab` | Format SQL using the database's dialect |
Grant these in **Security → List Roles** by adding the permissions to the relevant role.
### Public
The **Public** role is the most restrictive built-in role, designed specifically for anonymous/unauthenticated
@@ -183,8 +174,6 @@ However, it is crucial to understand the following:
By combining Superset's configurable safeguards with strong database-level security practices, you can achieve a more robust and layered security posture.
**Dataset Sample Access**: The `get_samples()` endpoint now enforces datasource-level access control. Users can only fetch sample rows from datasets they have been explicitly granted access to — the same permission check applied when running chart queries. This closes a prior gap where unauthenticated or under-privileged access could retrieve sample data.
### REST API for user & role management
Flask-AppBuilder supports a REST API for user CRUD,

View File

@@ -485,7 +485,7 @@ Frontend assets (TypeScript, JavaScript, CSS, and images) must be compiled in or
First, be sure you are using the following versions of Node.js and npm:
- `Node.js`: Version 22 (LTS)
- `Node.js`: Version 20
- `npm`: Version 10
We recommend using [nvm](https://github.com/nvm-sh/nvm) to manage your node environment:

View File

@@ -39,13 +39,17 @@ 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.
superset-extensions validate: Validates the extension structure and metadata consistency.
superset-extensions update: Updates derived and generated files in the extension project.
Use --version [<version>] to update the version (prompts if no value given).
Use --license [<license>] to update the license (prompts if no value given).
```
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/
@@ -76,7 +80,7 @@ dataset-references/
```
**Note**: With publisher `my-org` and name `dataset-references`, the technical names are:
- Directory name: `dataset-references` (kebab-case)
- 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)

View File

@@ -75,7 +75,7 @@ This approach ensures that extensions from different organizations cannot confli
This creates a complete project structure:
```
hello-world/
my-org.hello-world/
├── extension.json # Extension metadata and configuration
├── backend/ # Backend Python code
│ ├── src/

View File

@@ -63,12 +63,6 @@ by clicking the **Connect** button in the bottom right corner of the modal windo
Congratulations, you've just added a new data source in Superset!
### Sharing a Database Connection
When adding a new database, you can share the connection with other Superset users. Shared connections appear in other users' database lists, making it easier to collaborate on the same data without requiring each user to configure the same connection separately.
To share a connection, enable the **Share connection with other users** option in the **Advanced** tab of the database connection modal before saving. You can change sharing settings later by editing the database connection.
### Registering a new table
Now that youve configured a data source, you can select specific tables (called **Datasets** in Superset)
@@ -86,22 +80,6 @@ we register the **cleaned_sales_data** table from the **examples** database.
To finish, click the **Add** button in the bottom right corner. You should now see your dataset in the list of datasets.
### Organizing Datasets into Folders
The Datasets list view supports **folders** for organizing datasets into groups. To create and manage folders:
1. In the **Datasets** list, click the **Folders** panel on the left sidebar.
2. Click **+ New Folder** to create a top-level folder, or drag an existing folder to nest it.
3. Drag dataset rows onto a folder to move them in, or right-click a dataset and select **Move to folder**.
Folders are per-user organizational aids — they do not affect dataset access permissions or how other users see the datasets.
### Uploading Files via the OS File Manager (PWA)
When Superset is installed as a **Progressive Web App (PWA)** from your browser, your operating system will offer Superset as an option when opening CSV, Excel (`.xls`/`.xlsx`), and Parquet files. Double-clicking or right-clicking a supported file and selecting "Open with Superset" navigates directly to the upload workflow for that file.
To install Superset as a PWA, look for the install icon in your browser's address bar (Chrome, Edge) when visiting your Superset instance over HTTPS. PWA installation requires HTTPS and a valid manifest — your admin needs to confirm the app manifest is served correctly.
### Customizing column properties
Now that you've registered your dataset, you can configure column properties
@@ -256,112 +234,6 @@ For example, when running the local development build, the following will disabl
Top Nav and remove the Filter Bar:
`http://localhost:8088/superset/dashboard/my-dashboard/?standalone=1&show_filters=0`
### Table Chart Features
The **Table** chart type has several advanced capabilities worth knowing:
#### Conditional Formatting
Conditional formatting rules highlight cells based on their values. Rules can be applied to:
- **Numeric columns** — color cells above/below a threshold, or use a gradient across a range
- **String columns** — highlight cells matching specific text values or patterns
- **Boolean columns** — color cells that are `true` or `false`, or `null`/`not null`
Each rule has a **"Use gradient"** toggle: enabled applies a varying opacity (lighter = further from threshold), disabled applies a solid fill at full opacity regardless of value.
#### HTML Rendering in Table Cells
Table chart cells can render raw HTML, enabling rich formatting such as hyperlinks, colored badges, and icons directly in the data. Enable this per-column in the chart's **Column Configuration** panel by toggling **Render HTML**.
:::caution
Only enable HTML rendering for columns sourced from data you control. Rendering untrusted HTML can expose users to cross-site scripting (XSS) risks.
:::
#### Column Header Tooltips
Column headers display a tooltip with the column's **Description** from the dataset editor when the user hovers over them. Keep dataset column descriptions up to date to improve chart discoverability.
#### Display Controls
In dashboard view mode (without entering Edit mode), charts with configurable display options expose a **Display Controls** panel accessible from the chart's context menu. This surfaces controls such as Time Grain, Time Column, and layer visibility for applicable chart types — making it easy to adjust a chart's view without going to Explore.
### AG Grid Interactive Table
The **AG Grid Interactive Table** chart type is Superset's fully-featured data grid, suitable for large paginated datasets where the standard Table chart is not enough.
#### Server-Side Column Filters
AG Grid supports server-side column filters that query the full dataset — not just the loaded page. Filters are applied before data is sent to the browser, so results are correct even across millions of rows.
**Available filter types:**
| Column type | Filter options |
|---|---|
| Text | Contains, equals, starts with, ends with |
| Number | Equals, not equal, less than, greater than, between |
| Date | Before, after, between, blank |
| Set | Select from a list of distinct values |
**AND / OR logic:** Each column supports combining multiple conditions with AND or OR. Filters from different columns are always combined with AND.
**Interaction with pagination:** Server-side filters run as WHERE clauses in the underlying SQL query, so pagination always operates over the already-filtered result set.
#### Time Shift (Time Comparison)
AG Grid Interactive Table supports **Time Shift** (time comparison), matching the behavior of the standard Table chart. In the **Advanced Analytics** → **Time Comparison** section of the chart configuration, enter a shift expression (e.g., `1 year ago`, `minus 7 days`) to add comparison columns showing values from the offset period. Dashboard-level time range overrides apply to both the base and comparison periods.
### Dynamic Currency Formatting
Chart metric values can display currencies dynamically rather than using a fixed currency code. To enable:
1. Open the dataset editor for your dataset (**Datasets → Edit**).
2. In the **Advanced** tab, set **Currency Code Column** to the name of a column in your dataset that contains ISO 4217 currency codes (e.g., `USD`, `EUR`, `GBP`).
3. In the Explore chart configuration, open the metric's **Number format** section and select **Auto-detect** for currency.
When Auto-detect is active, each row uses the currency code from the designated column, so a single chart can display values in multiple currencies — each formatted correctly for its currency.
### ECharts Option Editor
For ECharts-based chart types (line, bar, area, scatter, pie, and others), Explore includes an advanced **ECharts Option Editor** that accepts raw JSON overrides for the underlying ECharts configuration.
Access it via the **Customize** tab → **ECharts Options** section at the bottom of the panel. The JSON you enter is deep-merged on top of Superset's generated ECharts config, so you can override specific options without rewriting the entire config.
**Example:** override the legend position and add a custom title:
```json
{
"legend": { "orient": "vertical", "right": "5%", "top": "middle" },
"title": { "text": "My Custom Title", "left": "center" }
}
```
:::caution
ECharts option overrides bypass Superset's validation layer. Invalid option keys are silently ignored by ECharts. Overrides that conflict with Superset-generated options (e.g., `series`) may produce unexpected results.
:::
### Table Chart: Exporting Filtered Data
When the **Search Box** is visible in a Table chart, the **Download** action exports only the rows currently visible after the search filter is applied — not the full underlying dataset. This matches the visual output and is intentional. To export the full dataset regardless of search state, use the **Download as CSV** option from the chart's three-dot menu in the dashboard or from the Explore chart toolbar before applying a search filter.
### Sharing a Specific Tab
When a dashboard has tabs, each tab gets its own shareable URL. Navigate to the tab you want to share and copy the URL from your browser's address bar — the tab anchor is encoded in the URL so that anyone opening the link lands directly on that tab.
### Auto-Refresh
Dashboards can be configured to refresh automatically at a fixed interval without user interaction. Open a dashboard, click the **⋮** (more options) menu in the top-right, and select **Set auto-refresh interval**. Choose an interval (e.g., every 10 seconds, 1 minute, or 10 minutes). The setting is per-session and resets when you close the tab.
:::note
Auto-refresh triggers a full data reload for all charts on the dashboard. For dashboards with expensive queries, choose longer intervals to avoid overloading your database.
:::
### Last Queried Timestamp
Charts can display a "Last queried at" timestamp showing when the chart data was last fetched. This is useful on auto-refreshing dashboards to confirm data freshness. Enable it in **Dashboard Properties → Styling → Show last queried time**.
### Saving a Chart to a Specific Tab
When saving or adding a chart to a dashboard from Explore, you can select which tab it should land on using the tab tree-select dropdown in the "Add to dashboard" modal.
:::resources
- [Dashboard Customization](https://docs.preset.io/docs/dashboard-customization) - Advanced dashboard styling and layout options
- [Blog: BI Dashboard Best Practices](https://preset.io/blog/bi-dashboard-best-practices/)

View File

@@ -1,130 +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.
*/}
---
title: Embedding Superset
sidebar_position: 6
---
# Embedding Superset
Superset dashboards can be embedded directly in host applications using the `@superset-ui/embedded-sdk` package.
:::info Prerequisites
- The `EMBEDDED_SUPERSET` feature flag must be enabled.
- The embedding domain and allowed origins must be configured by an admin.
:::
## Quick Start
Install the SDK:
```bash
npm install @superset-ui/embedded-sdk
```
Embed a dashboard:
```javascript
import { embedDashboard } from '@superset-ui/embedded-sdk';
embedDashboard({
id: 'dashboard-uuid-here', // from Dashboard → Embed
supersetDomain: 'https://superset.example.com',
mountPoint: document.getElementById('superset-container'),
fetchGuestToken: () => fetchTokenFromYourBackend(),
dashboardUiConfig: {
hideTitle: true,
filters: { expanded: false },
},
});
```
`fetchGuestToken` must return a **guest token** obtained from your server by calling Superset's `/api/v1/security/guest_token/` endpoint with a service account. Do not call this endpoint from client-side code.
---
## Callbacks
### `resolvePermalinkUrl`
When a user copies a permalink from an embedded dashboard, Superset generates a URL on its own domain. In an embedded context this URL is usually not meaningful to the host application's users — the dashboard is rendered inside the host app, not at the Superset URL.
The `resolvePermalinkUrl` callback lets the host app intercept permalink generation and return a URL on the host domain instead:
```javascript
embedDashboard({
id: 'my-dashboard-uuid',
supersetDomain: 'https://superset.example.com',
mountPoint: document.getElementById('superset-container'),
fetchGuestToken: () => fetchGuestToken(),
/**
* Called when Superset generates a permalink.
* @param {Object} args - { key: string } — the permalink key
* @returns {string | null} - your host URL, or null to use Superset's default
*/
resolvePermalinkUrl: ({ key }) => {
return `https://myapp.example.com/dashboard?permalink=${key}`;
},
});
```
If the callback returns `null` or is not provided, Superset uses its own permalink URL as a fallback.
---
## Feature Flags for Embedded Mode
### `DISABLE_EMBEDDED_SUPERSET_LOGOUT`
Hides the logout button when Superset is embedded in a host application. This is useful when the host application manages the session lifecycle and you do not want users to accidentally log out of the embedded Superset session:
```python
# superset_config.py
FEATURE_FLAGS = {
"EMBEDDED_SUPERSET": True,
"DISABLE_EMBEDDED_SUPERSET_LOGOUT": True,
}
```
When enabled, the **Logout** menu item is removed from the user avatar dropdown in the embedded view. The session can still be invalidated server-side by revoking the guest token.
### `EMBEDDED_SUPERSET`
Must be `True` to enable the embedded SDK and the guest token endpoint. Without this flag, `embedDashboard` will fail to load.
---
## URL Parameters
The following URL parameters can be passed through the `urlParams` option in `dashboardUiConfig` or appended to the embedded iframe URL:
| Parameter | Values | Effect |
|-----------|--------|--------|
| `standalone` | `0`, `1`, `2`, `3` | `0`: normal; `1`: hide nav; `2`: hide nav + title; `3`: hide nav + title + tabs |
| `show_filters` | `0`, `1` | Show or hide the native filter bar |
| `expand_filters` | `0`, `1` | Start with filter bar expanded or collapsed |
---
## Security Notes
- **Guest tokens expire** — their lifetime is controlled by the `GUEST_TOKEN_JWT_EXP_SECONDS` config (default: 5 minutes). Refresh tokens before they expire using a token refresh mechanism in your host app.
- **Row-level security** — pass `rls` rules in the guest token request to restrict which rows are visible to the embedded user.
- **Allowed domains** — restrict which host origins can embed a dashboard by setting **Allowed Domains** per-dashboard in the *Embed* settings modal. Superset checks the request's `Referer` header against this list before serving the embedded view; an empty list allows any origin, so configure this explicitly for production.

View File

@@ -329,27 +329,6 @@ various options in this section, refer to the
Lastly, save your chart as Tutorial Resample and add it to the Tutorial Dashboard. Go to the
tutorial dashboard to see the four charts side by side and compare the different outputs.
### SQL Lab Tips
**Schema and table browser**: The left-side table browser uses a collapsible treeview — click a schema to expand its tables, and click a table to see its columns and sample data inline. This makes navigating large schemas much faster than the previous flat list.
**Find in editor**: Press **Ctrl+F** (or **Cmd+F** on Mac) to open the Monaco find/replace widget inside the SQL editor without leaving the editor.
**Resizable panels**: The dividers between the SQL editor, schema browser, and results pane are draggable. Adjust them to match your workflow and screen size.
**Dialect-aware Format SQL**: The **Format SQL** button applies the SQL dialect of the currently selected database — Trino, Presto, MySQL, PostgreSQL, etc. — rather than a generic formatter. Switch to a different database in the toolbar and re-format to get dialect-specific output. Jinja template syntax (`{{ }}`, `{% %}`) is preserved during formatting and will not cause format errors.
### Time Range Natural Language Expressions
The **Custom** time range picker accepts natural language expressions alongside specific dates:
- **Relative**: `Last 7 days`, `Last month`, `Last quarter`, `Last year`
- **Anchored**: `previous calendar week`, `previous calendar month`
- **"First of" expressions**: `first day of this week`, `first day of this month`, `first day of this quarter`, `first day of this year`, `first week of this year`
- **Offsets**: `30 days ago`, `1 year ago`, `next week`
These expressions are evaluated at query time, so saved charts always display data relative to the current date.
:::resources
- [Chart Walkthroughs](https://docs.preset.io/docs/chart-walkthroughs) - Detailed guides for most chart types
- [Blog: Why Apache ECharts is the Future of Apache Superset](https://preset.io/blog/2021-4-1-why-echarts/)

View File

@@ -33,29 +33,6 @@ SQL templating must be enabled by your administrator via the `ENABLE_TEMPLATE_PR
For advanced configuration options, see the [SQL Templating Configuration Guide](/admin-docs/configuration/sql-templating).
:::
## Using Jinja in Calculated Columns
Jinja template macros are available in calculated column expressions in the dataset editor — not just in SQL Lab queries and virtual datasets. This allows column expressions to reference the current user or dynamic context.
**Example: User-scoped calculated column**
```sql
CASE WHEN sales_rep = '{{ current_username() }}' THEN amount ELSE 0 END
```
**Example: Conditional display based on role**
Because `current_user_roles()` returns a Python list, test role membership with a Jinja
conditional at template time rather than matching against the list's string representation:
```sql
{% if 'Finance' in current_user_roles() %}revenue{% else %}NULL{% endif %} AS finance_revenue
```
:::note
The `ENABLE_TEMPLATE_PROCESSING` feature flag must be enabled by your administrator for Jinja in calculated columns to work.
:::
## Basic Usage
Jinja templates use double curly braces `{{ }}` for expressions and `{% %}` for logic blocks.
@@ -266,7 +243,6 @@ Using `remove_filter=True` applies the filter in the inner query for better perf
- Use `|tojson` to serialize arrays as JSON strings
- Test queries with explicit parameter values before relying on filter context
- For complex templating needs, ask your administrator about custom Jinja macros
- **Format SQL is Jinja-aware**: The "Format SQL" button in SQL Lab correctly preserves `{{ }}` and `{% %}` template syntax and applies your selected database's SQL dialect when formatting.
:::resources
- [Admin Guide: SQL Templating Configuration](/admin-docs/configuration/sql-templating)

View File

@@ -55,10 +55,9 @@ Ask your AI assistant to browse what's available in your Superset instance:
Describe the visualization you want and AI creates it for you:
- **Preview-first workflow** -- by default AI generates an Explore link so you can review the chart before it is saved. Say "save it" to commit permanently
- **Create charts from natural language** -- describe what you want to see and AI picks the right chart type, metrics, and dimensions
- **Preview before saving** -- `generate_chart` defaults to `save_chart=False`, showing the chart in Explore before it's committed. Ask AI to save once you're satisfied.
- **Modify existing charts** -- `update_chart` also supports preview mode so you can review changes before saving (update filters, change chart types, add metrics)
- **Preview before saving** -- AI generates a preview so you can review before committing
- **Modify existing charts** -- update filters, change chart types, add metrics
- **Get Explore links** -- open any chart in Superset's Explore view for further refinement
**Example prompts:**
@@ -66,16 +65,6 @@ Describe the visualization you want and AI creates it for you:
> "Update chart 42 to use a line chart instead"
> "Give me a link to explore this chart further"
:::tip Preview-first workflow
Charts are **not saved by default**. The workflow is intentionally iterative:
1. **Explore** — AI generates an Explore link so you can see the chart before it exists in Superset
2. **Iterate** — ask the AI to adjust the chart; changes are previewed without touching the database
3. **Save** — when you're happy, say "save it" and the chart is permanently stored
To skip the preview and save immediately, include "and save it" in your prompt.
:::
### Create Dashboards
Build dashboards from a collection of charts:
@@ -87,40 +76,16 @@ Build dashboards from a collection of charts:
> "Create a dashboard called 'Q4 Sales Overview' with charts 10, 15, and 22"
> "Add the revenue trend chart to the executive dashboard"
### Browse Databases
Discover what database connections are configured in your Superset instance:
- **List databases** -- see all database connections you have access to
- **Get database details** -- name, backend type (PostgreSQL, Snowflake, etc.), and connection status
**Example prompts:**
> "What databases are connected to Superset?"
> "Show me details about the data warehouse connection"
### Create Virtual Datasets
Build ad-hoc SQL datasets that can be used as the basis for charts:
- **Create virtual datasets** -- write a SQL query and save it as a reusable dataset
- **Use immediately in charts** -- the returned dataset ID can be passed directly to chart creation
**Example prompts:**
> "Create a dataset from: SELECT region, SUM(revenue) as total_revenue FROM orders GROUP BY region"
> "Make a virtual dataset called 'monthly_signups' from the users table filtered to last 12 months"
### Run SQL Queries
Execute SQL directly through your AI assistant:
- **Run queries** -- execute SQL with full Superset RBAC enforcement (you can only query data your roles allow)
- **Open SQL Lab** -- get a link to SQL Lab pre-populated with a query, ready to run and explore
- **Save queries** -- save a SQL query to SQL Lab's Saved Queries for later reuse
**Example prompts:**
> "Run this query: SELECT region, SUM(revenue) FROM sales GROUP BY region"
> "Open SQL Lab with a query to show the top 10 customers by order count"
> "Save this query as 'Weekly Revenue Report'"
### Analyze Chart Data
@@ -230,59 +195,27 @@ Ask your admin for the MCP server URL and any authentication tokens you need.
## Available Tools Reference
### Exploration & Discovery
| Tool | Description |
|------|-------------|
| `health_check` | Verify the MCP server is running and connected |
| `get_instance_info` | Get instance statistics (dataset, chart, dashboard counts) |
| `get_schema` | Discover available charts, datasets, and dashboards with schema info |
### Datasets
| Tool | Description |
|------|-------------|
| `list_datasets` | List datasets with filtering and search |
| `get_dataset_info` | Get dataset metadata (columns, metrics, filters) |
| `create_virtual_dataset` | Create a virtual dataset from a SQL query |
### Charts
| Tool | Description |
|------|-------------|
| `list_charts` | List charts with filtering and search |
| `get_chart_info` | Get chart metadata and configuration |
| `get_chart_data` | Retrieve chart data (JSON, CSV, or Excel) |
| `get_chart_preview` | Generate a chart preview (URL, ASCII, table, or Vega-Lite) |
| `get_chart_type_schema` | Get the configuration schema for a chart type |
| `generate_chart` | Create a new chart from a specification (defaults to preview mode — review before saving) |
| `update_chart` | Modify an existing chart's configuration (pass `generate_preview=False` to persist immediately instead of returning a preview URL) |
| `generate_chart` | Create a new chart from a specification |
| `update_chart` | Modify an existing chart's configuration |
| `update_chart_preview` | Update a cached chart preview without saving |
| `generate_explore_link` | Generate an Explore URL for interactive visualization |
### Dashboards
| Tool | Description |
|------|-------------|
| `list_dashboards` | List dashboards with filtering and search |
| `get_dashboard_info` | Get dashboard metadata and layout |
| `generate_dashboard` | Create a new dashboard with specified charts |
| `add_chart_to_existing_dashboard` | Add a chart to an existing dashboard |
### SQL
| Tool | Description |
|------|-------------|
| `execute_sql` | Run a SQL query with RBAC enforcement |
| `save_sql_query` | Persist a SQL query to SQL Lab's saved queries |
| `open_sql_lab_with_context` | Open SQL Lab with a pre-populated query |
### Databases
| Tool | Description |
|------|-------------|
| `list_databases` | List configured database connections |
| `get_database_info` | Get details about a specific database connection |
| `generate_explore_link` | Generate an Explore URL for interactive visualization |
---

View File

@@ -55,22 +55,22 @@
"@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",
"baseline-browser-mapping": "^2.10.0",
"caniuse-lite": "^1.0.30001775",
"antd": "^6.3.2",
"baseline-browser-mapping": "^2.10.7",
"caniuse-lite": "^1.0.30001780",
"docusaurus-plugin-openapi-docs": "^4.6.0",
"docusaurus-theme-openapi-docs": "^4.6.0",
"js-yaml": "^4.1.1",
@@ -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",
@@ -106,7 +106,7 @@
"globals": "^17.4.0",
"prettier": "^3.8.1",
"typescript": "~5.9.3",
"typescript-eslint": "^8.56.1",
"typescript-eslint": "^8.57.1",
"webpack": "^5.105.4"
},
"browserslist": {

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,

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"
@@ -5136,185 +5117,100 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@8.56.1":
version "8.56.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz#b1ce606d87221daec571e293009675992f0aae76"
integrity sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==
"@typescript-eslint/eslint-plugin@8.57.1", "@typescript-eslint/eslint-plugin@^8.52.0":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.1.tgz#ddfdfb30f8b5ccee7f3c21798b377c51370edd55"
integrity sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==
dependencies:
"@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.56.1"
"@typescript-eslint/type-utils" "8.56.1"
"@typescript-eslint/utils" "8.56.1"
"@typescript-eslint/visitor-keys" "8.56.1"
"@typescript-eslint/scope-manager" "8.57.1"
"@typescript-eslint/type-utils" "8.57.1"
"@typescript-eslint/utils" "8.57.1"
"@typescript-eslint/visitor-keys" "8.57.1"
ignore "^7.0.5"
natural-compare "^1.4.0"
ts-api-utils "^2.4.0"
"@typescript-eslint/eslint-plugin@^8.52.0":
version "8.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz#5aec3db807a6b8437ea5d5ebf7bd16b4119aba8d"
integrity sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==
"@typescript-eslint/parser@8.57.1", "@typescript-eslint/parser@^8.56.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.57.1.tgz#d523e559b148264055c0a49a29d5f50c7de659c2"
integrity sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==
dependencies:
"@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.56.0"
"@typescript-eslint/type-utils" "8.56.0"
"@typescript-eslint/utils" "8.56.0"
"@typescript-eslint/visitor-keys" "8.56.0"
ignore "^7.0.5"
natural-compare "^1.4.0"
ts-api-utils "^2.4.0"
"@typescript-eslint/parser@8.56.1", "@typescript-eslint/parser@^8.56.1":
version "8.56.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.56.1.tgz#21d13b3d456ffb08614c1d68bb9a4f8d9237cdc7"
integrity sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==
dependencies:
"@typescript-eslint/scope-manager" "8.56.1"
"@typescript-eslint/types" "8.56.1"
"@typescript-eslint/typescript-estree" "8.56.1"
"@typescript-eslint/visitor-keys" "8.56.1"
"@typescript-eslint/scope-manager" "8.57.1"
"@typescript-eslint/types" "8.57.1"
"@typescript-eslint/typescript-estree" "8.57.1"
"@typescript-eslint/visitor-keys" "8.57.1"
debug "^4.4.3"
"@typescript-eslint/project-service@8.56.0":
version "8.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.56.0.tgz#bb8562fecd8f7922e676fc6a1189c20dd7991d73"
integrity sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==
"@typescript-eslint/project-service@8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.57.1.tgz#16af9fe16eedbd7085e4fdc29baa73715c0c55c5"
integrity sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.56.0"
"@typescript-eslint/types" "^8.56.0"
"@typescript-eslint/tsconfig-utils" "^8.57.1"
"@typescript-eslint/types" "^8.57.1"
debug "^4.4.3"
"@typescript-eslint/project-service@8.56.1":
version "8.56.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.56.1.tgz#65c8d645f028b927bfc4928593b54e2ecd809244"
integrity sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==
"@typescript-eslint/scope-manager@8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz#4524d7e7b420cb501807499684d435ae129aaf35"
integrity sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.56.1"
"@typescript-eslint/types" "^8.56.1"
debug "^4.4.3"
"@typescript-eslint/types" "8.57.1"
"@typescript-eslint/visitor-keys" "8.57.1"
"@typescript-eslint/scope-manager@8.56.0":
version "8.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz#604030a4c6433df3728effdd441d47f45a86edb4"
integrity sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==
"@typescript-eslint/tsconfig-utils@8.57.1", "@typescript-eslint/tsconfig-utils@^8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.1.tgz#9233443ec716882a6f9e240fd900a73f0235f3d7"
integrity sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==
"@typescript-eslint/type-utils@8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.57.1.tgz#c49af1347b5869ca85155547a8f34f84ab386fd9"
integrity sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==
dependencies:
"@typescript-eslint/types" "8.56.0"
"@typescript-eslint/visitor-keys" "8.56.0"
"@typescript-eslint/scope-manager@8.56.1":
version "8.56.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz#254df93b5789a871351335dd23e20bc164060f24"
integrity sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==
dependencies:
"@typescript-eslint/types" "8.56.1"
"@typescript-eslint/visitor-keys" "8.56.1"
"@typescript-eslint/tsconfig-utils@8.56.0", "@typescript-eslint/tsconfig-utils@^8.56.0":
version "8.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz#2538ce83cbc376e685487960cbb24b65fe2abc4e"
integrity sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==
"@typescript-eslint/tsconfig-utils@8.56.1", "@typescript-eslint/tsconfig-utils@^8.56.1":
version "8.56.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz#1afa830b0fada5865ddcabdc993b790114a879b7"
integrity sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==
"@typescript-eslint/type-utils@8.56.0":
version "8.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz#72b4edc1fc73988998f1632b3ec99c2a66eaac6e"
integrity sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==
dependencies:
"@typescript-eslint/types" "8.56.0"
"@typescript-eslint/typescript-estree" "8.56.0"
"@typescript-eslint/utils" "8.56.0"
"@typescript-eslint/types" "8.57.1"
"@typescript-eslint/typescript-estree" "8.57.1"
"@typescript-eslint/utils" "8.57.1"
debug "^4.4.3"
ts-api-utils "^2.4.0"
"@typescript-eslint/type-utils@8.56.1":
version "8.56.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz#7a6c4fabf225d674644931e004302cbbdd2f2e24"
integrity sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==
"@typescript-eslint/types@8.57.1", "@typescript-eslint/types@^8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.57.1.tgz#54b27a8a25a7b45b4f978c3f8e00c4c78f11142c"
integrity sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==
"@typescript-eslint/typescript-estree@8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz#a9fd28d4a0ec896aa9a9a7e0cead62ea24f99e76"
integrity sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==
dependencies:
"@typescript-eslint/types" "8.56.1"
"@typescript-eslint/typescript-estree" "8.56.1"
"@typescript-eslint/utils" "8.56.1"
debug "^4.4.3"
ts-api-utils "^2.4.0"
"@typescript-eslint/types@8.56.0", "@typescript-eslint/types@^8.56.0":
version "8.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.56.0.tgz#a2444011b9a98ca13d70411d2cbfed5443b3526a"
integrity sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==
"@typescript-eslint/types@8.56.1", "@typescript-eslint/types@^8.56.1":
version "8.56.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.56.1.tgz#975e5942bf54895291337c91b9191f6eb0632ab9"
integrity sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==
"@typescript-eslint/typescript-estree@8.56.0":
version "8.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz#fadbc74c14c5bac947db04980ff58bb178701c2e"
integrity sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==
dependencies:
"@typescript-eslint/project-service" "8.56.0"
"@typescript-eslint/tsconfig-utils" "8.56.0"
"@typescript-eslint/types" "8.56.0"
"@typescript-eslint/visitor-keys" "8.56.0"
debug "^4.4.3"
minimatch "^9.0.5"
semver "^7.7.3"
tinyglobby "^0.2.15"
ts-api-utils "^2.4.0"
"@typescript-eslint/typescript-estree@8.56.1":
version "8.56.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz#3b9e57d8129a860c50864c42188f761bdef3eab0"
integrity sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==
dependencies:
"@typescript-eslint/project-service" "8.56.1"
"@typescript-eslint/tsconfig-utils" "8.56.1"
"@typescript-eslint/types" "8.56.1"
"@typescript-eslint/visitor-keys" "8.56.1"
"@typescript-eslint/project-service" "8.57.1"
"@typescript-eslint/tsconfig-utils" "8.57.1"
"@typescript-eslint/types" "8.57.1"
"@typescript-eslint/visitor-keys" "8.57.1"
debug "^4.4.3"
minimatch "^10.2.2"
semver "^7.7.3"
tinyglobby "^0.2.15"
ts-api-utils "^2.4.0"
"@typescript-eslint/utils@8.56.0":
version "8.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.56.0.tgz#063ce6f702ec603de1b83ee795ed5e877d6f7841"
integrity sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==
"@typescript-eslint/utils@8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.57.1.tgz#e40f5a7fcff02fd24092a7b52bd6ec029fb50465"
integrity sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==
dependencies:
"@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.56.0"
"@typescript-eslint/types" "8.56.0"
"@typescript-eslint/typescript-estree" "8.56.0"
"@typescript-eslint/scope-manager" "8.57.1"
"@typescript-eslint/types" "8.57.1"
"@typescript-eslint/typescript-estree" "8.57.1"
"@typescript-eslint/utils@8.56.1":
version "8.56.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.56.1.tgz#5a86acaf9f1b4c4a85a42effb217f73059f6deb7"
integrity sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==
"@typescript-eslint/visitor-keys@8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz#3af4f88118924d3be983d4b8ae84803f11fe4563"
integrity sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==
dependencies:
"@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.56.1"
"@typescript-eslint/types" "8.56.1"
"@typescript-eslint/typescript-estree" "8.56.1"
"@typescript-eslint/visitor-keys@8.56.0":
version "8.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz#7d6592ab001827d3ce052155edf7ecad19688d7d"
integrity sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==
dependencies:
"@typescript-eslint/types" "8.56.0"
eslint-visitor-keys "^5.0.0"
"@typescript-eslint/visitor-keys@8.56.1":
version "8.56.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz#50e03475c33a42d123dc99e63acf1841c0231f87"
integrity sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==
dependencies:
"@typescript-eslint/types" "8.56.1"
"@typescript-eslint/types" "8.57.1"
eslint-visitor-keys "^5.0.0"
"@ungap/structured-clone@^1.0.0":
@@ -5668,14 +5564,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"
@@ -5683,11 +5579,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"
@@ -5703,7 +5599,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"
@@ -5980,10 +5876,10 @@ base64-js@^1.3.1, base64-js@^1.5.1:
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
baseline-browser-mapping@^2.10.0, baseline-browser-mapping@^2.9.0, baseline-browser-mapping@^2.9.19:
version "2.10.0"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz#5b09935025bf8a80e29130251e337c6a7fc8cbb9"
integrity sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==
baseline-browser-mapping@^2.10.7, baseline-browser-mapping@^2.9.0, baseline-browser-mapping@^2.9.19:
version "2.10.7"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.7.tgz#2c017adffe4f7bbe93c2e55526cc1869d36f588c"
integrity sha512-1ghYO3HnxGec0TCGBXiDLVns4eCSx4zJpxnHrlqFQajmhfKMQBzUGDdkMK7fUW7PTHTeLf+j87aTuKuuwWzMGw==
batch@0.6.1:
version "0.6.1"
@@ -6228,15 +6124,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001759:
version "1.0.30001770"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz#4dc47d3b263a50fbb243448034921e0a88591a84"
integrity sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==
caniuse-lite@^1.0.30001775:
version "1.0.30001775"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz#9572266e3f7f77efee5deac1efeb4795879d1b7f"
integrity sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001780:
version "1.0.30001780"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz#0e413de292808868a62ed9118822683fa120a110"
integrity sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==
ccount@^2.0.0:
version "2.0.1"
@@ -11354,13 +11245,6 @@ minimatch@^5.0.1:
dependencies:
brace-expansion "^2.0.1"
minimatch@^9.0.5:
version "9.0.5"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz"
integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
dependencies:
brace-expansion "^2.0.1"
minimist@^1.2.0:
version "1.2.8"
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
@@ -14327,12 +14211,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"
@@ -14977,15 +14861,15 @@ types-ramda@^0.30.1:
dependencies:
ts-toolbelt "^9.6.0"
typescript-eslint@^8.56.1:
version "8.56.1"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.56.1.tgz#15a9fcc5d2150a0d981772bb36f127a816fe103f"
integrity sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==
typescript-eslint@^8.57.1:
version "8.57.1"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.57.1.tgz#573f97d3e48bbb67290b47dde1b7cb3b5d01dc4f"
integrity sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==
dependencies:
"@typescript-eslint/eslint-plugin" "8.56.1"
"@typescript-eslint/parser" "8.56.1"
"@typescript-eslint/typescript-estree" "8.56.1"
"@typescript-eslint/utils" "8.56.1"
"@typescript-eslint/eslint-plugin" "8.57.1"
"@typescript-eslint/parser" "8.57.1"
"@typescript-eslint/typescript-estree" "8.57.1"
"@typescript-eslint/utils" "8.57.1"
typescript@~5.9.3:
version "5.9.3"

View File

@@ -372,7 +372,6 @@ unfixable = []
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.lint.per-file-ignores]
"superset/mcp_service/app.py" = ["S608", "E501"] # LLM instruction text: SQL examples (S608) and long lines in multiline string (E501)
"scripts/*" = ["TID251"]
"setup.py" = ["TID251"]
"superset/config.py" = ["TID251"]

View File

@@ -120,7 +120,7 @@ flask==2.3.3
# flask-session
# flask-sqlalchemy
# flask-wtf
flask-appbuilder==5.0.2
flask-appbuilder==5.1.0
# via
# apache-superset (pyproject.toml)
# apache-superset-core

View File

@@ -259,7 +259,7 @@ flask==2.3.3
# flask-sqlalchemy
# flask-testing
# flask-wtf
flask-appbuilder==5.0.2
flask-appbuilder==5.1.0
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -996,6 +996,8 @@ tabulate==0.9.0
# via
# -c requirements/base-constraint.txt
# apache-superset
tomli-w==1.2.0
# via apache-superset-extensions-cli
tomlkit==0.13.3
# via pylint
tqdm==4.67.1

View File

@@ -45,17 +45,7 @@ if [ ${#js_ts_files[@]} -gt 0 ]; then
# Skip custom OXC build in pre-commit for speed
export SKIP_CUSTOM_OXC=true
# Use quiet mode in pre-commit to reduce noise (only show errors)
# Capture output so we can treat "No files found" (all files ignored by
# ignorePatterns) as success rather than a false-positive failure.
output=$(npx oxlint --config oxlint.json --fix --quiet "${js_ts_files[@]}" 2>&1) || {
if echo "$output" | grep -q "No files found"; then
echo "No files to lint after applying ignore patterns"
exit 0
fi
echo "$output" >&2
exit 1
}
[ -n "$output" ] && echo "$output"
npx oxlint --config oxlint.json --fix --quiet "${js_ts_files[@]}"
else
echo "No JavaScript/TypeScript files to lint"
fi

View File

@@ -18,20 +18,20 @@
[project]
name = "apache-superset-core"
version = "0.1.0rc3"
version = "0.1.0rc1"
description = "Core Python package for building Apache Superset backend extensions and integrations"
readme = "README.md"
authors = [
{ name = "Apache Software Foundation", email = "dev@superset.apache.org" },
]
license = "Apache-2.0"
license-files = ["LICENSE.txt"]
license = { file="LICENSE.txt" }
requires-python = ">=3.10"
keywords = ["superset", "apache", "analytics", "business-intelligence", "extensions", "visualization"]
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",

View File

@@ -17,20 +17,20 @@
[project]
name = "apache-superset-extensions-cli"
version = "0.1.0rc3"
version = "0.1.0rc2"
description = "Official command-line interface for building, bundling, and managing Apache Superset extensions"
readme = "README.md"
authors = [
{ name = "Apache Software Foundation", email = "dev@superset.apache.org" },
]
license = "Apache-2.0"
license-files = ["LICENSE.txt"]
license = { file="LICENSE.txt" }
requires-python = ">=3.10"
keywords = ["superset", "apache", "cli", "extensions", "analytics", "business-intelligence", "development-tools"]
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
@@ -49,6 +49,7 @@ dependencies = [
"jinja2>=3.1.6",
"semver>=3.0.4",
"tomli>=2.2.1; python_version < '3.11'",
"tomli-w>=1.2.0",
"watchdog>=6.0.0",
]

View File

@@ -50,6 +50,8 @@ from superset_extensions_cli.utils import (
validate_display_name,
validate_publisher,
validate_technical_name,
write_json,
write_toml,
)
REMOTE_ENTRY_REGEX = re.compile(r"^remoteEntry\..+\.js$")
@@ -292,6 +294,7 @@ def app() -> None:
@app.command()
def validate() -> None:
"""Validate the extension structure and metadata consistency."""
validate_npm()
cwd = Path.cwd()
@@ -372,12 +375,167 @@ def validate() -> None:
click.secho(" Convention requires: frontend/src/index.tsx", fg="yellow")
sys.exit(1)
# Validate version and license consistency across extension.json, frontend, and backend
mismatches: list[str] = []
frontend_pkg_path = cwd / "frontend" / "package.json"
frontend_pkg = None
if frontend_pkg_path.is_file():
frontend_pkg = read_json(frontend_pkg_path)
if frontend_pkg:
if frontend_pkg.get("version") != extension.version:
mismatches.append(
f" frontend/package.json version: {frontend_pkg.get('version')} "
f"(expected {extension.version})"
)
if extension.license and frontend_pkg.get("license") != extension.license:
mismatches.append(
f" frontend/package.json license: {frontend_pkg.get('license')} "
f"(expected {extension.license})"
)
backend_pyproject_path = cwd / "backend" / "pyproject.toml"
if backend_pyproject_path.is_file():
backend_pyproject = read_toml(backend_pyproject_path)
if backend_pyproject:
project = backend_pyproject.get("project", {})
if project.get("version") != extension.version:
mismatches.append(
f" backend/pyproject.toml version: {project.get('version')} "
f"(expected {extension.version})"
)
if extension.license and project.get("license") != extension.license:
mismatches.append(
f" backend/pyproject.toml license: {project.get('license')} "
f"(expected {extension.license})"
)
if mismatches:
click.secho("❌ Metadata mismatch detected:", err=True, fg="red")
for mismatch in mismatches:
click.secho(mismatch, err=True, fg="red")
click.secho(
"Run `superset-extensions update` to sync from extension.json.",
fg="yellow",
)
sys.exit(1)
click.secho("✅ Validation successful", fg="green")
@app.command()
@click.option(
"--version",
"version_opt",
is_flag=False,
flag_value="__prompt__",
default=None,
help="Set a new version. Prompts for value if none given.",
)
@click.option(
"--license",
"license_opt",
is_flag=False,
flag_value="__prompt__",
default=None,
help="Set a new license. Prompts for value if none given.",
)
def update(version_opt: str | None, license_opt: str | None) -> None:
"""Update derived and generated files in the extension project."""
cwd = Path.cwd()
extension_json_path = cwd / "extension.json"
extension_data = read_json(extension_json_path)
if not extension_data:
click.secho("❌ extension.json not found.", err=True, fg="red")
sys.exit(1)
try:
extension = ExtensionConfig.model_validate(extension_data)
except Exception as e:
click.secho(f"❌ Invalid extension.json: {e}", err=True, fg="red")
sys.exit(1)
# Resolve version: prompt if flag used without value
if version_opt == "__prompt__":
version_opt = click.prompt("Version", default=extension.version)
target_version = (
version_opt
if version_opt and version_opt != extension.version
else extension.version
)
# Resolve license: prompt if flag used without value
if license_opt == "__prompt__":
license_opt = click.prompt("License", default=extension.license or "")
target_license = (
license_opt
if license_opt and license_opt != extension.license
else extension.license
)
updated: list[str] = []
# Update extension.json if version or license changed
ext_changed = False
if version_opt and version_opt != extension.version:
extension_data["version"] = target_version
ext_changed = True
if license_opt and license_opt != extension.license:
extension_data["license"] = target_license
ext_changed = True
if ext_changed:
try:
ExtensionConfig.model_validate(extension_data)
except Exception as e:
click.secho(f"❌ Invalid value: {e}", err=True, fg="red")
sys.exit(1)
write_json(extension_json_path, extension_data)
updated.append("extension.json")
# Update frontend/package.json
frontend_pkg_path = cwd / "frontend" / "package.json"
if frontend_pkg_path.is_file():
frontend_pkg = read_json(frontend_pkg_path)
if frontend_pkg:
pkg_changed = False
if frontend_pkg.get("version") != target_version:
frontend_pkg["version"] = target_version
pkg_changed = True
if target_license and frontend_pkg.get("license") != target_license:
frontend_pkg["license"] = target_license
pkg_changed = True
if pkg_changed:
write_json(frontend_pkg_path, frontend_pkg)
updated.append("frontend/package.json")
# Update backend/pyproject.toml
backend_pyproject_path = cwd / "backend" / "pyproject.toml"
if backend_pyproject_path.is_file():
backend_pyproject = read_toml(backend_pyproject_path)
if backend_pyproject:
project = backend_pyproject.setdefault("project", {})
toml_changed = False
if project.get("version") != target_version:
project["version"] = target_version
toml_changed = True
if target_license and project.get("license") != target_license:
project["license"] = target_license
toml_changed = True
if toml_changed:
write_toml(backend_pyproject_path, backend_pyproject)
updated.append("backend/pyproject.toml")
if updated:
for path in updated:
click.secho(f"✅ Updated {path}", fg="green")
else:
click.secho("✅ All files already up to date.", fg="green")
@app.command()
@click.pass_context
def build(ctx: click.Context) -> None:
"""Build extension assets."""
ctx.invoke(validate)
cwd = Path.cwd()
frontend_dir = cwd / "frontend"
@@ -413,6 +571,7 @@ def build(ctx: click.Context) -> None:
)
@click.pass_context
def bundle(ctx: click.Context, output: Path | None) -> None:
"""Package the extension into a .supx file."""
ctx.invoke(build)
cwd = Path.cwd()
@@ -426,9 +585,9 @@ def bundle(ctx: click.Context, output: Path | None) -> None:
sys.exit(1)
manifest = json.loads(manifest_path.read_text())
name = manifest["name"]
id_ = manifest["id"]
version = manifest["version"]
default_filename = f"{name}-{version}.supx"
default_filename = f"{id_}-{version}.supx"
if output is None:
zip_path = Path(default_filename)
@@ -453,6 +612,7 @@ def bundle(ctx: click.Context, output: Path | None) -> None:
@app.command()
@click.pass_context
def dev(ctx: click.Context) -> None:
"""Automatically rebuild the extension as files change."""
cwd = Path.cwd()
frontend_dir = cwd / "frontend"
backend_dir = cwd / "backend"
@@ -647,6 +807,7 @@ def init(
frontend_opt: bool | None,
backend_opt: bool | None,
) -> None:
"""Scaffold a new extension project."""
# Get extension names with graceful validation
names = prompt_for_extension_info(display_name_opt, publisher_opt, name_opt)
@@ -663,7 +824,7 @@ def init(
else click.confirm("Include backend?", default=True)
)
target_dir = Path.cwd() / names["name"]
target_dir = Path.cwd() / names["id"]
if target_dir.exists():
click.secho(f"❌ Directory {target_dir} already exists.", fg="red")
sys.exit(1)

View File

@@ -21,6 +21,8 @@ import sys
from pathlib import Path
from typing import Any
import tomli_w
from superset_core.extensions.constants import (
DISPLAY_NAME_PATTERN,
PUBLISHER_PATTERN,
@@ -109,6 +111,14 @@ def read_json(path: Path) -> dict[str, Any] | None:
return json.loads(path.read_text())
def write_json(path: Path, data: dict[str, Any]) -> None:
path.write_text(json.dumps(data, indent=2) + "\n")
def write_toml(path: Path, data: dict[str, Any]) -> None:
path.write_text(tomli_w.dumps(data))
def _normalize_for_identifiers(name: str) -> str:
"""
Normalize display name to clean lowercase words.

View File

@@ -17,10 +17,12 @@
from __future__ import annotations
import json
import os
from pathlib import Path
import pytest
import tomli_w
from click.testing import CliRunner
@@ -138,3 +140,69 @@ def extension_setup_for_bundling():
(backend_dir / "__init__.py").write_text("# init")
return _setup
@pytest.fixture
def extension_with_versions():
"""Create an extension directory structure with configurable versions and licenses."""
def _create(
base_path: Path,
ext_version: str = "1.0.0",
frontend_version: str | None = None,
backend_version: str | None = None,
ext_license: str | None = "Apache-2.0",
frontend_license: str | None = None,
backend_license: str | None = None,
) -> None:
extension_json = {
"publisher": "test-org",
"name": "test-extension",
"displayName": "Test Extension",
"version": ext_version,
"permissions": [],
}
if ext_license is not None:
extension_json["license"] = ext_license
(base_path / "extension.json").write_text(json.dumps(extension_json))
if frontend_version is not None:
frontend_dir = base_path / "frontend"
frontend_dir.mkdir(exist_ok=True)
(frontend_dir / "src").mkdir(exist_ok=True)
(frontend_dir / "src" / "index.tsx").write_text("// entry")
pkg = {
"name": "@test-org/test-extension",
"version": frontend_version,
}
if frontend_license is not None:
pkg["license"] = frontend_license
elif ext_license is not None:
pkg["license"] = ext_license
(frontend_dir / "package.json").write_text(json.dumps(pkg, indent=2))
if backend_version is not None:
backend_dir = base_path / "backend"
backend_dir.mkdir(exist_ok=True)
src_dir = backend_dir / "src" / "test_org" / "test_extension"
src_dir.mkdir(parents=True, exist_ok=True)
(src_dir / "entrypoint.py").write_text("# entry")
project = {
"name": "test-org-test-extension",
"version": backend_version,
}
if backend_license is not None:
project["license"] = backend_license
elif ext_license is not None:
project["license"] = ext_license
pyproject = {
"project": project,
"tool": {
"apache_superset_extensions": {
"build": {"include": ["src/**/*.py"]}
}
},
}
(backend_dir / "pyproject.toml").write_text(tomli_w.dumps(pyproject))
return _create

View File

@@ -121,7 +121,7 @@ def test_build_command_success_flow(
# Setup mocks
mock_rebuild_frontend.return_value = "remoteEntry.abc123.js"
mock_read_toml.return_value = {
"project": {"name": "test"},
"project": {"name": "test", "version": "1.0.0"},
"tool": {
"apache_superset_extensions": {
"build": {"include": ["src/test_org/test_extension/**/*.py"]}
@@ -162,7 +162,7 @@ def test_build_command_handles_frontend_build_failure(
# Setup mocks
mock_rebuild_frontend.return_value = None # Indicates failure
mock_read_toml.return_value = {
"project": {"name": "test"},
"project": {"name": "test", "version": "1.0.0"},
"tool": {
"apache_superset_extensions": {
"build": {"include": ["src/test_org/test_extension/**/*.py"]}

View File

@@ -43,10 +43,10 @@ def test_bundle_command_creates_zip_with_default_name(
result = cli_runner.invoke(app, ["bundle"])
assert result.exit_code == 0
assert "✅ Bundle created: test-extension-1.0.0.supx" in result.output
assert "✅ Bundle created: test-org.test-extension-1.0.0.supx" in result.output
# Verify zip file was created
zip_path = isolated_filesystem / "test-extension-1.0.0.supx"
zip_path = isolated_filesystem / "test-org.test-extension-1.0.0.supx"
assert_file_exists(zip_path)
# Verify zip contents
@@ -100,7 +100,7 @@ def test_bundle_command_with_output_directory(
assert result.exit_code == 0
# Verify zip file was created in output directory
expected_path = output_dir / "test-extension-1.0.0.supx"
expected_path = output_dir / "test-org.test-extension-1.0.0.supx"
assert_file_exists(expected_path)
assert f"✅ Bundle created: {expected_path}" in result.output
@@ -193,7 +193,7 @@ def test_bundle_includes_all_files_recursively(
assert result.exit_code == 0
# Verify zip file and contents
zip_path = isolated_filesystem / "complex-extension-2.1.0.supx"
zip_path = isolated_filesystem / "complex-org.complex-extension-2.1.0.supx"
assert_file_exists(zip_path)
with zipfile.ZipFile(zip_path, "r") as zipf:

View File

@@ -48,12 +48,12 @@ def test_init_creates_extension_with_both_frontend_and_backend(
)
# Verify directory structure
extension_path = isolated_filesystem / "test-extension"
extension_path = isolated_filesystem / "test-org.test-extension"
assert_directory_exists(extension_path, "main extension directory")
expected_structure = create_test_extension_structure(
isolated_filesystem,
"test-extension",
"test-org.test-extension",
include_frontend=True,
include_backend=True,
)
@@ -74,7 +74,7 @@ def test_init_creates_extension_with_frontend_only(
assert result.exit_code == 0, f"Command failed with output: {result.output}"
extension_path = isolated_filesystem / "test-extension"
extension_path = isolated_filesystem / "test-org.test-extension"
assert_directory_exists(extension_path)
# Should have frontend directory and package.json
@@ -97,7 +97,7 @@ def test_init_creates_extension_with_backend_only(
assert result.exit_code == 0, f"Command failed with output: {result.output}"
extension_path = isolated_filesystem / "test-extension"
extension_path = isolated_filesystem / "test-org.test-extension"
assert_directory_exists(extension_path)
# Should have backend directory and pyproject.toml
@@ -120,7 +120,7 @@ def test_init_creates_extension_with_neither_frontend_nor_backend(
assert result.exit_code == 0, f"Command failed with output: {result.output}"
extension_path = isolated_filesystem / "test-extension"
extension_path = isolated_filesystem / "test-org.test-extension"
assert_directory_exists(extension_path)
# Should only have extension.json
@@ -138,8 +138,8 @@ def test_init_accepts_valid_display_name(cli_runner, isolated_filesystem):
result = cli_runner.invoke(app, ["init"], input=cli_input)
assert result.exit_code == 0, f"Should accept display name: {result.output}"
assert Path("my-awesome-extension").exists(), (
"Directory with extension name should be created"
assert Path("test-org.my-awesome-extension").exists(), (
"Directory for generated composite ID should be created"
)
@@ -152,21 +152,23 @@ def test_init_accepts_mixed_alphanumeric_name(cli_runner, isolated_filesystem):
assert result.exit_code == 0, (
f"Mixed alphanumeric display name should be valid: {result.output}"
)
assert Path("tool-123").exists(), "Directory for 'tool-123' should be created"
assert Path("test-org.tool-123").exists(), (
"Directory for 'test-org.tool-123' should be created"
)
@pytest.mark.cli
@pytest.mark.parametrize(
"display_name,expected_dir",
"display_name,expected_id",
[
("Test Extension", "test-extension"),
("My Tool v2", "my-tool-v2"),
("Dashboard Helper", "dashboard-helper"),
("Chart Builder Pro", "chart-builder-pro"),
("Test Extension", "test-org.test-extension"),
("My Tool v2", "test-org.my-tool-v2"),
("Dashboard Helper", "test-org.dashboard-helper"),
("Chart Builder Pro", "test-org.chart-builder-pro"),
],
)
def test_init_with_various_display_names(cli_runner, display_name, expected_dir):
"""Test that init accepts various display names and creates directory named after extension."""
def test_init_with_various_display_names(cli_runner, display_name, expected_id):
"""Test that init accepts various display names and generates proper IDs."""
with cli_runner.isolated_filesystem():
cli_input = f"{display_name}\n\ntest-org\n0.1.0\nApache-2.0\ny\ny\n"
result = cli_runner.invoke(app, ["init"], input=cli_input)
@@ -174,8 +176,8 @@ def test_init_with_various_display_names(cli_runner, display_name, expected_dir)
assert result.exit_code == 0, (
f"Valid display name '{display_name}' was rejected: {result.output}"
)
assert Path(expected_dir).exists(), (
f"Directory '{expected_dir}' was not created"
assert Path(expected_id).exists(), (
f"Directory for '{expected_id}' was not created"
)
@@ -185,7 +187,7 @@ def test_init_fails_when_directory_already_exists(
):
"""Test that init fails gracefully when target directory already exists."""
# Create the directory first
existing_dir = isolated_filesystem / "test-extension"
existing_dir = isolated_filesystem / "test-org.test-extension"
existing_dir.mkdir()
result = cli_runner.invoke(app, ["init"], input=cli_input_both)
@@ -202,7 +204,7 @@ def test_extension_json_content_is_correct(
result = cli_runner.invoke(app, ["init"], input=cli_input_both)
assert result.exit_code == 0
extension_path = isolated_filesystem / "test-extension"
extension_path = isolated_filesystem / "test-org.test-extension"
extension_json_path = extension_path / "extension.json"
# Verify the JSON structure and values
@@ -236,7 +238,7 @@ def test_frontend_package_json_content_is_correct(
result = cli_runner.invoke(app, ["init"], input=cli_input_both)
assert result.exit_code == 0
extension_path = isolated_filesystem / "test-extension"
extension_path = isolated_filesystem / "test-org.test-extension"
package_json_path = extension_path / "frontend" / "package.json"
# Verify the package.json structure and values
@@ -265,7 +267,7 @@ def test_backend_pyproject_toml_is_created(
result = cli_runner.invoke(app, ["init"], input=cli_input_both)
assert result.exit_code == 0
extension_path = isolated_filesystem / "test-extension"
extension_path = isolated_filesystem / "test-org.test-extension"
pyproject_path = extension_path / "backend" / "pyproject.toml"
assert_file_exists(pyproject_path, "backend pyproject.toml")
@@ -303,7 +305,7 @@ def test_gitignore_content_is_correct(cli_runner, isolated_filesystem, cli_input
result = cli_runner.invoke(app, ["init"], input=cli_input_both)
assert result.exit_code == 0
extension_path = isolated_filesystem / "test-extension"
extension_path = isolated_filesystem / "test-org.test-extension"
gitignore_path = extension_path / ".gitignore"
assert_file_exists(gitignore_path, ".gitignore")
@@ -328,7 +330,7 @@ def test_init_with_custom_version_and_license(cli_runner, isolated_filesystem):
assert result.exit_code == 0
extension_path = isolated_filesystem / "my-extension"
extension_path = isolated_filesystem / "test-org.my-extension"
extension_json_path = extension_path / "extension.json"
assert_json_content(
@@ -355,10 +357,10 @@ def test_full_init_workflow_integration(cli_runner, isolated_filesystem):
assert result.exit_code == 0
# Verify complete directory structure
extension_path = isolated_filesystem / "awesome-charts"
extension_path = isolated_filesystem / "awesome-org.awesome-charts"
expected_structure = create_test_extension_structure(
isolated_filesystem,
"awesome-charts",
"awesome-org.awesome-charts",
include_frontend=True,
include_backend=True,
)
@@ -410,7 +412,7 @@ def test_init_non_interactive_with_all_options(cli_runner, isolated_filesystem):
assert result.exit_code == 0, f"Command failed with output: {result.output}"
assert "🎉 Extension My Extension (ID: my-org.my-ext) initialized" in result.output
extension_path = isolated_filesystem / "my-ext"
extension_path = isolated_filesystem / "my-org.my-ext"
assert_directory_exists(extension_path)
assert_directory_exists(extension_path / "frontend")
assert_directory_exists(extension_path / "backend")
@@ -447,7 +449,7 @@ def test_init_frontend_only_with_cli_options(cli_runner, isolated_filesystem):
assert result.exit_code == 0, f"Command failed with output: {result.output}"
extension_path = isolated_filesystem / "frontend-ext"
extension_path = isolated_filesystem / "frontend-org.frontend-ext"
assert_directory_exists(extension_path / "frontend")
assert not (extension_path / "backend").exists()
@@ -476,7 +478,7 @@ def test_init_backend_only_with_cli_options(cli_runner, isolated_filesystem):
assert result.exit_code == 0, f"Command failed with output: {result.output}"
extension_path = isolated_filesystem / "backend-ext"
extension_path = isolated_filesystem / "backend-org.backend-ext"
assert not (extension_path / "frontend").exists()
assert_directory_exists(extension_path / "backend")
@@ -503,7 +505,7 @@ def test_init_prompts_for_missing_options(cli_runner, isolated_filesystem):
assert result.exit_code == 0, f"Command failed with output: {result.output}"
extension_path = isolated_filesystem / "default-ext"
extension_path = isolated_filesystem / "default-org.default-ext"
extension_json = load_json_file(extension_path / "extension.json")
assert extension_json["version"] == "0.1.0"
assert extension_json["license"] == "Apache-2.0"

View File

@@ -0,0 +1,172 @@
# 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
import pytest
from superset_extensions_cli.cli import app
from superset_extensions_cli.utils import read_json, read_toml
@pytest.mark.cli
def test_update_syncs_versions(
cli_runner, isolated_filesystem, extension_with_versions
):
"""Test update syncs frontend and backend versions from extension.json."""
extension_with_versions(
isolated_filesystem,
ext_version="2.0.0",
frontend_version="1.0.0",
backend_version="1.0.0",
)
result = cli_runner.invoke(app, ["update"])
assert result.exit_code == 0
assert "Updated frontend/package.json" in result.output
assert "Updated backend/pyproject.toml" in result.output
frontend_pkg = read_json(isolated_filesystem / "frontend" / "package.json")
assert frontend_pkg["version"] == "2.0.0"
backend_pyproject = read_toml(isolated_filesystem / "backend" / "pyproject.toml")
assert backend_pyproject["project"]["version"] == "2.0.0"
@pytest.mark.cli
def test_update_noop_when_all_match(
cli_runner, isolated_filesystem, extension_with_versions
):
"""Test update reports no changes when everything already matches."""
extension_with_versions(
isolated_filesystem,
ext_version="1.0.0",
frontend_version="1.0.0",
backend_version="1.0.0",
)
result = cli_runner.invoke(app, ["update"])
assert result.exit_code == 0
assert "All files already up to date" in result.output
@pytest.mark.cli
def test_update_fails_without_extension_json(cli_runner, isolated_filesystem):
"""Test update fails when extension.json is missing."""
result = cli_runner.invoke(app, ["update"])
assert result.exit_code != 0
assert "extension.json not found" in result.output
@pytest.mark.cli
def test_update_with_version_flag(
cli_runner, isolated_filesystem, extension_with_versions
):
"""Test --version updates extension.json first, then syncs all files."""
extension_with_versions(
isolated_filesystem,
ext_version="1.0.0",
frontend_version="1.0.0",
backend_version="1.0.0",
)
result = cli_runner.invoke(app, ["update", "--version", "3.0.0"])
assert result.exit_code == 0
assert "Updated extension.json" in result.output
assert "Updated frontend/package.json" in result.output
assert "Updated backend/pyproject.toml" in result.output
ext = read_json(isolated_filesystem / "extension.json")
assert ext["version"] == "3.0.0"
frontend_pkg = read_json(isolated_filesystem / "frontend" / "package.json")
assert frontend_pkg["version"] == "3.0.0"
backend_pyproject = read_toml(isolated_filesystem / "backend" / "pyproject.toml")
assert backend_pyproject["project"]["version"] == "3.0.0"
@pytest.mark.cli
def test_update_with_license_flag(
cli_runner, isolated_filesystem, extension_with_versions
):
"""Test --license updates license across all files."""
extension_with_versions(
isolated_filesystem,
ext_version="1.0.0",
frontend_version="1.0.0",
backend_version="1.0.0",
ext_license="Apache-2.0",
)
result = cli_runner.invoke(app, ["update", "--license", "MIT"])
assert result.exit_code == 0
assert "Updated extension.json" in result.output
assert "Updated frontend/package.json" in result.output
assert "Updated backend/pyproject.toml" in result.output
ext = read_json(isolated_filesystem / "extension.json")
assert ext["license"] == "MIT"
frontend_pkg = read_json(isolated_filesystem / "frontend" / "package.json")
assert frontend_pkg["license"] == "MIT"
backend_pyproject = read_toml(isolated_filesystem / "backend" / "pyproject.toml")
assert backend_pyproject["project"]["license"] == "MIT"
@pytest.mark.cli
def test_update_version_prompt_default(
cli_runner, isolated_filesystem, extension_with_versions
):
"""Test --version without value prompts with current version as default."""
extension_with_versions(
isolated_filesystem,
ext_version="1.0.0",
frontend_version="1.0.0",
backend_version="1.0.0",
)
# Hit enter to accept default — nothing should change
result = cli_runner.invoke(app, ["update", "--version"], input="\n")
assert result.exit_code == 0
assert "All files already up to date" in result.output
@pytest.mark.cli
def test_update_rejects_invalid_version(
cli_runner, isolated_filesystem, extension_with_versions
):
"""Test --version with an invalid semver string exits with error."""
extension_with_versions(
isolated_filesystem,
ext_version="1.0.0",
)
result = cli_runner.invoke(app, ["update", "--version", "not-a-version"])
assert result.exit_code != 0
assert "Invalid value" in result.output
# Verify extension.json was not modified
ext = read_json(isolated_filesystem / "extension.json")
assert ext["version"] == "1.0.0"

View File

@@ -207,3 +207,66 @@ def test_validate_npm_with_empty_version_output_raises_error(mock_run, mock_whic
# semver.compare will raise ValueError for empty version
with pytest.raises(ValueError):
validate_npm()
# Version Consistency Tests
@pytest.mark.cli
def test_validate_fails_on_version_mismatch(
cli_runner, isolated_filesystem, extension_with_versions
):
"""Test validate fails when frontend/backend versions differ from extension.json."""
extension_with_versions(
isolated_filesystem,
ext_version="2.0.0",
frontend_version="1.0.0",
backend_version="1.0.0",
)
with patch("superset_extensions_cli.cli.validate_npm"):
result = cli_runner.invoke(app, ["validate"])
assert result.exit_code != 0
assert "Metadata mismatch" in result.output
assert "superset-extensions update" in result.output
@pytest.mark.cli
def test_validate_passes_with_matching_versions(
cli_runner, isolated_filesystem, extension_with_versions
):
"""Test validate passes when all versions match extension.json."""
extension_with_versions(
isolated_filesystem,
ext_version="1.0.0",
frontend_version="1.0.0",
backend_version="1.0.0",
)
with patch("superset_extensions_cli.cli.validate_npm"):
result = cli_runner.invoke(app, ["validate"])
assert result.exit_code == 0
assert "Validation successful" in result.output
@pytest.mark.cli
def test_validate_fails_on_license_mismatch(
cli_runner, isolated_filesystem, extension_with_versions
):
"""Test validate fails when frontend/backend licenses differ from extension.json."""
extension_with_versions(
isolated_filesystem,
ext_version="1.0.0",
frontend_version="1.0.0",
backend_version="1.0.0",
ext_license="Apache-2.0",
frontend_license="MIT",
backend_license="MIT",
)
with patch("superset_extensions_cli.cli.validate_npm"):
result = cli_runner.invoke(app, ["validate"])
assert result.exit_code != 0
assert "Metadata mismatch" in result.output
assert "license" in result.output

View File

@@ -20,7 +20,7 @@ from __future__ import annotations
import json
import pytest
from superset_extensions_cli.utils import read_json, read_toml
from superset_extensions_cli.utils import read_json, read_toml, write_json, write_toml
# Read JSON Tests
@@ -269,3 +269,32 @@ def test_read_toml_with_permission_denied(isolated_filesystem):
toml_file.chmod(0o644)
except (OSError, PermissionError):
pass
# Write JSON Tests
@pytest.mark.unit
def test_write_json_round_trip(isolated_filesystem):
"""Test write_json then read_json round-trip preserves content."""
data = {"name": "test-extension", "version": "2.0.0", "nested": {"key": "value"}}
json_file = isolated_filesystem / "output.json"
write_json(json_file, data)
result = read_json(json_file)
assert result == data
# Write TOML Tests
@pytest.mark.unit
def test_write_toml_round_trip(isolated_filesystem):
"""Test write_toml then read_toml round-trip preserves content."""
data = {
"project": {"name": "test-package", "version": "1.0.0"},
"tool": {"apache_superset_extensions": {"build": {"include": ["src/**/*.py"]}}},
}
toml_file = isolated_filesystem / "output.toml"
write_toml(toml_file, data)
result = read_toml(toml_file)
assert result == data

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-.*|(?!geostyler)lodash|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

@@ -1,6 +1,6 @@
{
"name": "superset",
"version": "6.1.0",
"version": "0.0.0-dev",
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
"keywords": [
"big",
@@ -170,11 +170,11 @@
"fs-extra": "^11.3.3",
"fuse.js": "^7.1.0",
"geolib": "^3.3.4",
"geostyler": "^14.1.3",
"geostyler": "^18.3.1",
"geostyler-data": "^1.1.0",
"geostyler-openlayers-parser": "^4.3.0",
"geostyler-style": "7.5.0",
"geostyler-wfs-parser": "^2.0.3",
"geostyler-openlayers-parser": "^5.4.0",
"geostyler-style": "11.0.2",
"geostyler-wfs-parser": "^3.0.1",
"google-auth-library": "^10.6.1",
"immer": "^11.1.4",
"interweave": "^13.1.1",
@@ -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",
@@ -305,7 +305,7 @@
"babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"babel-plugin-lodash": "^3.3.4",
"baseline-browser-mapping": "^2.10.0",
"baseline-browser-mapping": "^2.10.7",
"cheerio": "1.2.0",
"concurrently": "^9.2.1",
"copy-webpack-plugin": "^13.0.1",
@@ -323,10 +323,9 @@
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest-dom": "^5.5.0",
"eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-no-only-tests": "^3.3.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",
@@ -336,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",
"mini-css-extract-plugin": "^2.10.0",
"lightningcss": "^1.32.0",
"mini-css-extract-plugin": "^2.10.1",
"open-cli": "^8.0.0",
"oxlint": "^1.51.0",
"oxlint": "^1.53.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",
@@ -361,6 +360,7 @@
"style-loader": "^4.0.0",
"swc-loader": "^0.2.7",
"terser-webpack-plugin": "^5.3.17",
"thread-loader": "^4.0.4",
"ts-jest": "^29.4.6",
"tscw-config": "^1.1.2",
"tsx": "^4.21.0",

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

@@ -1,6 +1,6 @@
{
"name": "@apache-superset/core",
"version": "0.1.0-rc3",
"version": "0.1.0-rc1",
"description": "This package contains UI elements, APIs, and utility functions used by Superset.",
"sideEffects": false,
"main": "lib/index.js",
@@ -70,8 +70,8 @@
"files": [
"lib"
],
"author": "Apache Software Foundation",
"license": "Apache-2.0",
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.28.6",
"@babel/core": "^7.29.0",

View File

@@ -29,20 +29,14 @@ import '@fontsource/ibm-plex-mono/600.css';
/* eslint-enable import/extensions */
import { css, useTheme, Global } from '@emotion/react';
import { useThemeMode } from './utils/themeUtils';
export const GlobalStyles = () => {
const theme = useTheme();
const isDark = useThemeMode();
return (
<Global
key={`global-${theme.colorLink}`}
styles={css`
// SPA
html {
color-scheme: ${isDark ? 'dark' : 'light'};
}
html,
body,
#app {

View File

@@ -839,73 +839,3 @@ test('Theme includes both echartsOptionsOverrides and echartsOptionsOverridesByC
},
});
});
test('colorLink derives from colorPrimary when merging with base theme', () => {
const baseTheme: AnyThemeConfig = {
token: {
colorPrimary: '#2893B3',
colorLink: '#2893B3',
colorInfo: '#66bcfe',
},
};
const userTheme: AnyThemeConfig = {
token: {
colorPrimary: '#f759ab',
},
};
const theme = Theme.fromConfig(userTheme, baseTheme);
expect(theme.theme.colorPrimary).toBe('#f759ab');
expect(theme.theme.colorLink).toBe('#f759ab');
expect(theme.theme.colorInfo).toBe('#66bcfe');
});
test('colorLink is not overridden when user explicitly sets it', () => {
const baseTheme: AnyThemeConfig = {
token: {
colorPrimary: '#2893B3',
colorLink: '#2893B3',
},
};
const userTheme: AnyThemeConfig = {
token: {
colorPrimary: '#f759ab',
colorLink: '#ff0000',
},
};
const theme = Theme.fromConfig(userTheme, baseTheme);
expect(theme.theme.colorPrimary).toBe('#f759ab');
expect(theme.theme.colorLink).toBe('#ff0000');
});
test('colorLink derives from colorPrimary in setConfig when not explicitly set', () => {
const theme = Theme.fromConfig();
theme.setConfig({
token: {
colorPrimary: '#f759ab',
},
});
expect(theme.theme.colorPrimary).toBe('#f759ab');
expect(theme.theme.colorLink).toBe('#f759ab');
});
test('colorLink is preserved in setConfig when explicitly set', () => {
const theme = Theme.fromConfig();
theme.setConfig({
token: {
colorPrimary: '#f759ab',
colorLink: '#ff0000',
},
});
expect(theme.theme.colorPrimary).toBe('#f759ab');
expect(theme.theme.colorLink).toBe('#ff0000');
});

View File

@@ -66,17 +66,6 @@ export class Theme {
mergedConfig = mergeWith({}, baseTheme, config, (objValue, srcValue) =>
Array.isArray(srcValue) ? srcValue : undefined,
);
// In Ant Design v5, colorLink derives from colorInfo, not colorPrimary.
// Currently we expectlinks to follow the brand/primary color. When the user
// overrides colorPrimary without explicitly setting colorLink, update the
// merged colorLink so links match the new primary palette.
if (config.token?.colorPrimary && !config.token?.colorLink) {
const mToken = mergedConfig?.token;
if (mToken) {
mToken.colorLink = mToken.colorPrimary;
}
}
} else if (baseTheme && !config) {
mergedConfig = baseTheme;
}
@@ -109,10 +98,6 @@ export class Theme {
setConfig(config: AnyThemeConfig): void {
const antdConfig = normalizeThemeConfig(config);
if (antdConfig.token?.colorPrimary && !antdConfig.token?.colorLink) {
antdConfig.token.colorLink = antdConfig.token.colorPrimary;
}
// First phase: Let Ant Design compute the tokens
const tokens = Theme.getFilteredAntdTheme(antdConfig);

View File

@@ -168,55 +168,6 @@ export interface SupersetSpecificTokens {
* Defaults to colorPrimaryBgHover if not specified.
*/
colorEditorSelection?: string;
// Secondary button tokens (Superset-specific)
// Ant Design's filled variant has no component tokens, so we provide our own.
// These fallback to colorPrimary* derived tokens when not set.
/**
* Text color for secondary buttons.
* Fallback: colorPrimary
*/
buttonSecondaryColor?: string;
/**
* Background color for secondary buttons.
* Fallback: colorPrimaryBg
*/
buttonSecondaryBg?: string;
/**
* Border color for secondary buttons.
* Fallback: transparent
*/
buttonSecondaryBorderColor?: string;
/**
* Text color for secondary buttons on hover.
* Fallback: colorPrimary
*/
buttonSecondaryHoverColor?: string;
/**
* Background color for secondary buttons on hover.
* Fallback: colorPrimaryBgHover
*/
buttonSecondaryHoverBg?: string;
/**
* Border color for secondary buttons on hover.
* Fallback: transparent
*/
buttonSecondaryHoverBorderColor?: string;
/**
* Text color for secondary buttons when active/pressed.
* Fallback: colorPrimary
*/
buttonSecondaryActiveColor?: string;
/**
* Background color for secondary buttons when active/pressed.
* Fallback: colorPrimaryBorder
*/
buttonSecondaryActiveBg?: string;
/**
* Border color for secondary buttons when active/pressed.
* Fallback: transparent
*/
buttonSecondaryActiveBorderColor?: string;
}
/**

View File

@@ -26,8 +26,7 @@
"dependencies": {
"@apache-superset/core": "*",
"@types/react": "*",
"lodash": "^4.17.23",
"tinycolor2": "*"
"lodash": "^4.17.23"
},
"peerDependencies": {
"@ant-design/icons": "^5.2.6",

View File

@@ -59,7 +59,7 @@ export function ColumnOption({
const type = hasExpression ? 'expression' : type_generic;
const [tooltipText, setTooltipText] = useState<ReactNode>(column.column_name);
const [columnTypeTooltipText, setcolumnTypeTooltipText] = useState<ReactNode>(
getColumnTypeTooltipNode(column),
column.type,
);
useLayoutEffect(() => {

View File

@@ -21,11 +21,7 @@ import { styled, css } from '@apache-superset/core/theme';
export const ControlSubSectionHeader = styled.div`
${({ theme }) => css`
font-weight: ${theme.fontWeightStrong};
margin-top: ${theme.sizeUnit * 3}px;
margin-bottom: ${theme.sizeUnit}px;
font-size: ${theme.fontSizeSM}px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: ${theme.colorTextSecondary};
`}
`;

View File

@@ -20,7 +20,6 @@ import { ReactNode, RefObject } from 'react';
import { t } from '@apache-superset/core/translation';
import { css, styled } from '@apache-superset/core/theme';
import { GenericDataType } from '@apache-superset/core/common';
import { ColumnMeta, Metric } from '@superset-ui/chart-controls';
const TooltipSectionWrapper = styled.div`
@@ -65,29 +64,11 @@ export const getColumnLabelText = (column: ColumnMeta): string =>
column.verbose_name || column.column_name;
export const getColumnTypeTooltipNode = (column: ColumnMeta): ReactNode => {
const rawType = typeof column.type === 'string' ? column.type.trim() : '';
let typeLabel: ReactNode | null = null;
if (rawType && rawType.toLowerCase() !== 'column') {
typeLabel = rawType;
} else if (typeof column.type_generic === 'number') {
if (column.type_generic === GenericDataType.String) {
typeLabel = t('string');
} else if (column.type_generic === GenericDataType.Numeric) {
typeLabel = t('numeric');
} else if (column.type_generic === GenericDataType.Temporal) {
typeLabel = t('timestamp');
} else if (column.type_generic === GenericDataType.Boolean) {
typeLabel = t('boolean');
}
}
if (!typeLabel) {
if (!column.type) {
return null;
}
return <TooltipSection label={t('Column type')} text={typeLabel} />;
return <TooltipSection label={t('Column type')} text={column.type} />;
};
export const getColumnTooltipNode = (

View File

@@ -1,139 +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.
*/
import { isMatrixifyVisible } from './matrixifyControls';
/**
* Helper to build a controls object matching the shape used by
* control panel visibility callbacks.
*/
function makeControls(
overrides: Record<string, unknown> = {},
): Record<string, { value: unknown }> {
const defaults: Record<string, unknown> = {
matrixify_enable: false,
matrixify_mode_rows: 'disabled',
matrixify_mode_columns: 'disabled',
matrixify_dimension_selection_mode_rows: 'members',
matrixify_dimension_selection_mode_columns: 'members',
};
const merged = { ...defaults, ...overrides };
return Object.fromEntries(
Object.entries(merged).map(([k, v]) => [k, { value: v }]),
);
}
// ── matrixify_enable guard ──────────────────────────────────────────
test('returns false when matrixify_enable is false, even with active axis modes', () => {
const controls = makeControls({
matrixify_enable: false,
matrixify_mode_rows: 'metrics',
matrixify_mode_columns: 'dimensions',
});
expect(isMatrixifyVisible(controls, 'rows')).toBe(false);
expect(isMatrixifyVisible(controls, 'columns')).toBe(false);
});
test('returns false when matrixify_enable is undefined (old form_data without the field)', () => {
const controls = makeControls({
matrixify_mode_rows: 'metrics',
});
delete (controls as any).matrixify_enable;
expect(isMatrixifyVisible(controls, 'rows')).toBe(false);
});
test('returns false when controls object is undefined', () => {
expect(isMatrixifyVisible(undefined, 'rows')).toBe(false);
});
// ── axis mode checks ────────────────────────────────────────────────
test('returns false when axis mode is disabled', () => {
const controls = makeControls({
matrixify_enable: true,
matrixify_mode_rows: 'disabled',
});
expect(isMatrixifyVisible(controls, 'rows')).toBe(false);
});
test('returns true when matrixify_enable is true and axis mode is metrics', () => {
const controls = makeControls({
matrixify_enable: true,
matrixify_mode_rows: 'metrics',
});
expect(isMatrixifyVisible(controls, 'rows')).toBe(true);
});
test('returns true when matrixify_enable is true and axis mode is dimensions', () => {
const controls = makeControls({
matrixify_enable: true,
matrixify_mode_columns: 'dimensions',
});
expect(isMatrixifyVisible(controls, 'columns')).toBe(true);
});
// ── mode filter ─────────────────────────────────────────────────────
test('returns false when mode filter does not match axis value', () => {
const controls = makeControls({
matrixify_enable: true,
matrixify_mode_rows: 'metrics',
});
expect(isMatrixifyVisible(controls, 'rows', 'dimensions')).toBe(false);
});
test('returns true when mode filter matches axis value', () => {
const controls = makeControls({
matrixify_enable: true,
matrixify_mode_rows: 'dimensions',
});
expect(isMatrixifyVisible(controls, 'rows', 'dimensions')).toBe(true);
});
// ── selectionMode filter ────────────────────────────────────────────
test('returns true when selectionMode matches', () => {
const controls = makeControls({
matrixify_enable: true,
matrixify_mode_rows: 'dimensions',
matrixify_dimension_selection_mode_rows: 'topn',
});
expect(isMatrixifyVisible(controls, 'rows', 'dimensions', 'topn')).toBe(true);
});
test('returns false when selectionMode does not match', () => {
const controls = makeControls({
matrixify_enable: true,
matrixify_mode_rows: 'dimensions',
matrixify_dimension_selection_mode_rows: 'members',
});
expect(isMatrixifyVisible(controls, 'rows', 'dimensions', 'topn')).toBe(
false,
);
});
test('ignores selectionMode filter when mode is metrics', () => {
const controls = makeControls({
matrixify_enable: true,
matrixify_mode_columns: 'metrics',
});
// selectionMode only applies to dimensions mode, should be ignored
expect(isMatrixifyVisible(controls, 'columns', 'metrics', 'topn')).toBe(true);
});

View File

@@ -36,8 +36,6 @@ const isMatrixifyVisible = (
mode?: 'metrics' | 'dimensions',
selectionMode?: 'members' | 'topn' | 'all',
) => {
if (controls?.matrixify_enable?.value !== true) return false;
const modeControl = `matrixify_mode_${axis}`;
const selectionModeControl = `matrixify_dimension_selection_mode_${axis}`;
@@ -374,4 +372,4 @@ matrixifyControls.matrixify_show_column_headers = {
visibility: ({ controls }) => isMatrixifyVisible(controls, 'columns'),
};
export { matrixifyControls, isMatrixifyVisible };
export { matrixifyControls };

View File

@@ -507,11 +507,6 @@ export type ColorFormatters = {
) => string | undefined;
}[];
export type ResolvedColorFormatterResult = {
backgroundColor?: string;
color?: string;
};
export default {};
export function isColumnMeta(column: AnyDict): column is ColumnMeta {

View File

@@ -20,13 +20,11 @@ import memoizeOne from 'memoize-one';
import { isString, isBoolean } from 'lodash';
import { isBlank } from '@apache-superset/core/utils';
import { addAlpha, DataRecord } from '@superset-ui/core';
import tinycolor from 'tinycolor2';
import {
ColorFormatters,
Comparator,
ConditionalFormattingConfig,
MultipleValueComparators,
ResolvedColorFormatterResult,
} from '../types';
export const round = (num: number, precision = 0) =>
@@ -35,11 +33,6 @@ export const round = (num: number, precision = 0) =>
const MIN_OPACITY_BOUNDED = 0.05;
const MIN_OPACITY_UNBOUNDED = 0;
const MAX_OPACITY = 1;
const READABLE_TEXT_COLORS = [
{ r: 0, g: 0, b: 0 },
{ r: 255, g: 255, b: 255 },
];
export const getOpacity = (
value: number | string | boolean | null,
cutoffPoint: number | string,
@@ -332,59 +325,3 @@ export const getColorFormatters = memoizeOne(
[],
) ?? [],
);
export const getReadableTextColor = (
backgroundColor: string | undefined,
surfaceColor: string,
): string | undefined => {
if (!backgroundColor) {
return undefined;
}
const background = tinycolor(backgroundColor);
const surface = tinycolor(surfaceColor);
if (!background.isValid() || !surface.isValid()) {
return undefined;
}
const { r: bgR, g: bgG, b: bgB, a: bgAlpha } = background.toRgb();
const { r: surfaceR, g: surfaceG, b: surfaceB } = surface.toRgb();
const alpha = bgAlpha;
const compositeColor = tinycolor({
r: bgR * alpha + surfaceR * (1 - alpha),
g: bgG * alpha + surfaceG * (1 - alpha),
b: bgB * alpha + surfaceB * (1 - alpha),
});
return tinycolor
.mostReadable(compositeColor, READABLE_TEXT_COLORS, {
includeFallbackColors: true,
level: 'AA',
size: 'small',
})
.toRgbString();
};
export const getNormalizedTextColor = (
color: string | undefined,
): string | undefined => {
if (!color) {
return undefined;
}
const parsedColor = tinycolor(color);
if (!parsedColor.isValid()) {
return color;
}
return parsedColor.setAlpha(1).toRgbString();
};
export const getTextColorForBackground = (
result: ResolvedColorFormatterResult,
surfaceColor: string,
): string | undefined =>
getNormalizedTextColor(result.color) ??
getReadableTextColor(result.backgroundColor, surfaceColor);

View File

@@ -24,7 +24,6 @@ import {
getMetricTooltipNode,
getColumnTypeTooltipNode,
} from '../../src/components/labelUtils';
import { GenericDataType } from '@apache-superset/core/common';
test("should get column name when column doesn't have verbose_name", () => {
expect(
@@ -90,24 +89,6 @@ test('should get column datatype rendered as tooltip when column has a type', ()
expect(screen.getByText('text')).toBeVisible();
});
test('should fall back to generic data type label when type is "column"', () => {
render(
<>
{getColumnTypeTooltipNode({
id: 123,
column_name: 'column name',
verbose_name: '',
description: '',
type: 'column',
type_generic: GenericDataType.String,
})}
</>,
);
expect(screen.getByText('Column type')).toBeVisible();
expect(screen.getByText('string')).toBeVisible();
});
test('should get column name, verbose name and description when it has a verbose name', () => {
const ref = { current: { scrollWidth: 100, clientWidth: 100 } };
render(

View File

@@ -24,11 +24,6 @@ import {
getColorFormatters,
getColorFunction,
} from '../../src';
import {
getReadableTextColor,
getNormalizedTextColor,
getTextColorForBackground,
} from '../../src/utils/getColorFormatters';
configure();
const mockData = [
@@ -112,64 +107,6 @@ test('getColorFunction LESS_THAN', () => {
expect(colorFunction(50)).toEqual('#FF0000FF');
});
test('getReadableTextColor returns white for dark backgrounds', () => {
expect(getReadableTextColor('#111111', '#ffffff')).toBe('rgb(255, 255, 255)');
});
test('getReadableTextColor returns black for light backgrounds', () => {
expect(getReadableTextColor('#f5f5f5', '#ffffff')).toBe('rgb(0, 0, 0)');
});
test('getReadableTextColor blends alpha over the provided surface', () => {
expect(getReadableTextColor('rgba(0, 0, 0, 0.6)', '#ffffff')).toBe(
'rgb(255, 255, 255)',
);
expect(getReadableTextColor('rgba(255, 255, 255, 0.6)', '#000000')).toBe(
'rgb(0, 0, 0)',
);
});
test('getReadableTextColor returns undefined for invalid colors', () => {
expect(getReadableTextColor('not-a-color', '#ffffff')).toBeUndefined();
expect(getReadableTextColor('#111111', 'not-a-color')).toBeUndefined();
});
test('getTextColorForBackground prefers explicit text color', () => {
expect(
getTextColorForBackground(
{ backgroundColor: '#111111', color: '#ace1c4ff' },
'#ffffff',
),
).toBe('rgb(172, 225, 196)');
});
test('getNormalizedTextColor removes alpha from explicit text colors', () => {
expect(getNormalizedTextColor('#ace1c40d')).toBe('rgb(172, 225, 196)');
expect(getNormalizedTextColor('rgba(172, 225, 196, 0.2)')).toBe(
'rgb(172, 225, 196)',
);
});
test('getNormalizedTextColor preserves invalid explicit text colors', () => {
expect(getNormalizedTextColor('not-a-color')).toBe('not-a-color');
});
test('getTextColorForBackground normalizes explicit text color alpha', () => {
expect(
getTextColorForBackground(
{ backgroundColor: '#111111', color: '#ace1c40d' },
'#ffffff',
),
).toBe('rgb(172, 225, 196)');
});
test('getTextColorForBackground falls back to adaptive contrast', () => {
expect(
getTextColorForBackground({ backgroundColor: '#111111' }, '#ffffff'),
).toBe('rgb(255, 255, 255)');
expect(getTextColorForBackground({}, '#ffffff')).toBeUndefined();
});
test('getColorFunction GREATER_OR_EQUAL', () => {
const colorFunction = getColorFunction(
{

View File

@@ -41,7 +41,7 @@
"d3-scale": "^4.0.2",
"d3-time": "^3.1.0",
"d3-time-format": "^4.1.0",
"dompurify": "^3.3.1",
"dompurify": "^3.3.3",
"fetch-retry": "^6.0.0",
"handlebars": "^4.7.8",
"jed": "^1.1.1",

View File

@@ -39,7 +39,6 @@ const createSqlMetric = (label: string, sql: string): AdhocMetric => ({
const baseFormData: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'metrics',
matrixify_mode_columns: 'metrics',
matrixify_rows: [createAdhocMetric('Revenue'), createAdhocMetric('Profit')],
@@ -78,7 +77,6 @@ test('should generate grid for dimensions mode', () => {
const dimensionFormData: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'dimensions',
matrixify_mode_columns: 'dimensions',
matrixify_dimension_rows: {
@@ -119,7 +117,6 @@ test('should generate grid for mixed mode (metrics rows, dimensions columns)', (
const mixedFormData: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'metrics',
matrixify_mode_columns: 'dimensions',
matrixify_rows: [createAdhocMetric('Total Sales')],
@@ -142,7 +139,6 @@ test('should handle empty configuration', () => {
const emptyFormData: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'metrics',
matrixify_mode_columns: 'metrics',
matrixify_rows: [],
@@ -161,7 +157,6 @@ test('should handle single row and column', () => {
const singleCellFormData: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'metrics',
matrixify_mode_columns: 'metrics',
matrixify_rows: [createAdhocMetric('Count')],
@@ -182,7 +177,6 @@ test('should handle string metrics', () => {
const stringMetricFormData: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'metrics',
matrixify_mode_columns: 'metrics',
matrixify_rows: ['count', 'sum'],
@@ -196,30 +190,10 @@ test('should handle string metrics', () => {
expect(grid!.colHeaders).toEqual(['avg', 'max']);
});
test('should skip missing column metrics when generating cell form data', () => {
const missingColumnMetricFormData: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'metrics',
matrixify_mode_columns: 'metrics',
matrixify_rows: [createAdhocMetric('Revenue')],
matrixify_columns: [null],
};
const grid = generateMatrixifyGrid(missingColumnMetricFormData);
expect(grid).not.toBeNull();
expect(grid!.cells[0][0]!.formData.metrics).toEqual([
createAdhocMetric('Revenue'),
]);
});
test('should not escape HTML entities in cell titles', () => {
const formDataWithSpecialChars: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'metrics',
matrixify_mode_columns: 'metrics',
matrixify_rows: [createAdhocMetric('Sales & Revenue')],
@@ -335,7 +309,6 @@ test('should generate single-column grid when only rows are configured', () => {
const rowsOnlyFormData: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'metrics',
matrixify_rows: [createAdhocMetric('Revenue'), createAdhocMetric('Profit')],
// No column config
@@ -353,7 +326,6 @@ test('should generate single-row grid when only columns are configured', () => {
const colsOnlyFormData: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_columns: 'metrics',
matrixify_columns: [
createSqlMetric('Q1', 'SUM(q1)'),
@@ -387,7 +359,6 @@ test('should return empty string header for null metric in array (line 76)', ()
const formData: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'metrics',
matrixify_mode_columns: 'metrics',
matrixify_rows: [null],
@@ -402,7 +373,6 @@ test('should return empty string header for empty-string dimension value (line 8
const formData: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'dimensions',
matrixify_mode_columns: 'dimensions',
matrixify_dimension_rows: { dimension: 'country', values: [''] },
@@ -417,7 +387,6 @@ test('should skip dimension filter when value is undefined (lines 151, 165)', ()
const formData: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'dimensions',
matrixify_mode_columns: 'dimensions',
matrixify_dimension_rows: {
@@ -449,7 +418,6 @@ test('should handle metrics without labels', () => {
const metricsWithoutLabels: TestFormData = {
viz_type: 'table',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'metrics',
matrixify_mode_columns: 'metrics',
matrixify_rows: [

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