Compare commits

..
Author SHA1 Message Date
hainenber 7b29ae4320 chore(ci): disable Git commit info capture in Playwright E2E tests to avoid timeout
Signed-off-by: hainenber <dotronghai96@gmail.com>
2026-08-15 15:06:55 +07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2e8a8031f8 chore(deps): bump cachetools from 7.1.6 to 7.1.7 (#43208)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-15 14:33:36 +07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> cece082eed chore(deps-dev): update pyocient requirement from <4,>=1.0.15 to >=3.9.0,<4 (#43211)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-15 14:33:21 +07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
22dde07a8e chore(deps): bump mako from 1.3.12 to 1.4.1 (#43207)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-15 14:33:06 +07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Joe LiEvan Rusackas
93cc520482 chore(deps): bump @googleapis/sheets from 13.0.2 to 14.0.0 in /superset-frontend (#43047)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Joe Li <joe@preset.io>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-08-15 14:23:13 +07:00
7679c5641a fix(lint-metrics): record oxlint plugin rules under their canonical id (#43198)
Co-authored-by: bikashJMV <bikash@jmv.co.in>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 14:21:38 +07:00
Evan RusackasandClaude Code f7c70568c3 fix(charts): fix Bubble chart crash and correct example's time_range to a year with real data (#43201)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-15 14:16:29 +07:00
Michael GerberandEvan Rusackas 69c4011a95 feat(embedded): Add setDataMask API method (#35754)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-08-14 22:07:24 -07:00
4684851336 fix(import): isolate per-tag import in a SAVEPOINT to avoid poisoned session (#42912) (#42920)
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-08-14 21:56:43 -07:00
e7139a7ac2 fix(models): enforce one OAuth2 token per user+database (#42897)
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 21:50:56 -07:00
21ae918656 fix(mcp): validate chart queries before persistence (#43128)
Co-authored-by: Bexultan Mustafin <bexultan.mustafin@ffins.kz>
Co-authored-by: Joe Li <joe@preset.io>
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
2026-08-14 17:48:19 -07:00
Joe Li ca94026e97 fix(sqllab): async queries no longer stuck at Running forever (#42896) 2026-08-14 17:47:10 -07:00
Evan RusackasandClaude Opus 4.8 a3bc2d908c ci(frontend): track bundle size over time with benchmark-action (#42511)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 17:40:46 -07:00
738d12677a fix(i18n-es): correct stranded translations in API key and semantic layer catalogs (#43080)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-14 17:40:23 -07:00
Elizabeth ThompsonandClaude Opus 4.8 2eedc609a8 fix(sql_lab): return 400 not 500 when raise_for_access hits malformed Jinja in results.py (#43145)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 15:02:47 -07:00
JUST.in DO IT f4587218dd fix(mcp): populate user_id in MCP audit logs (#42767) 2026-08-14 16:28:01 -04:00
Amin Ghadersohi 8967e6c2d3 fix(mcp): accept changed_on_delta_humanized as order_column in list tools (#42571) 2026-08-14 16:11:46 -04:00
Amin Ghadersohi 81e431cd50 fix(mcp): return all chart query results (#42824) 2026-08-14 16:10:26 -04:00
Amin Ghadersohi edfb009e1c fix(mcp): expose query context to Jinja macros (#42822) 2026-08-14 16:10:03 -04:00
Amin Ghadersohi e808fcbcad fix(mcp): bind all generated charts to dashboard time filters (#42490) 2026-08-14 16:07:50 -04:00
Evan RusackasandClaude Code 0a7ebe1dd1 fix(ci): stop superseded Docs Deployment runs from showing as cancelled (#42488)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-14 12:14:08 -07:00
Evan RusackasandClaude Opus 4.8 dd1afb029f fix(ci): stop py311/py312 docker builds from silently matching lean (#42509)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:07:23 -07:00
joeyandAmin Ghadersohi c068a8c09c fix(dashboard): preserve certification fields when Certification panel is closed (#42957)
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
2026-08-14 11:19:39 -07:00
Evan RusackasandClaude Code b62ec512d2 fix(explore): hide edit-properties menu item for non-owner/non-editor users (#38884) (#42737)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-14 11:15:10 -07:00
1e65d93a83 fix(sql): handle ORDER BY in embedded MSSQL queries (#43127)
Co-authored-by: Bexultan Mustafin <bexultan.mustafin@ffins.kz>
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
2026-08-14 14:07:10 -04:00
Gabriel Torres Ruiz 856599027a fix(mcp): harden the embedded-guest chart data-read path 2026-08-14 14:55:11 -03:00
Evan RusackasandClaude Code c395b9a238 fix(examples): migrate Life Expectancy VS Rural % example off the removed legacy bubble viz_type (#43152)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-14 10:27:14 -07:00
92728169de docs: document the zstd prerequisite for the frontend dev server (#43109)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-14 20:41:13 +07:00
DanielSwift1992 84c371d56e chore: fix stale developer_portal paths (#43143) 2026-08-14 20:12:08 +07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> a4c47359e6 chore(deps): bump the storybook group in /docs with 2 updates (#43154)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 02:34:25 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 7c0c5283c3 chore(deps-dev): bump the storybook group in /superset-frontend with 5 updates (#43155)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 02:34:20 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 9c5bde9491 chore(deps): bump mapbox-gl from 3.28.0 to 3.28.1 in /superset-frontend (#43156)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 02:34:16 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 3b99e092d0 chore(deps-dev): bump tsx from 4.23.9 to 4.23.10 in /superset-frontend (#43157)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 02:34:13 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> acf39e3ef0 chore(deps): bump dompurify from 3.4.12 to 3.4.13 in /superset-frontend (#43158)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 02:34:10 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 8523ea4d0a chore(deps): bump caniuse-lite from 1.0.30001806 to 1.0.30001807 in /docs (#43159)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 02:34:06 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 9bc3173e3a chore(deps): bump antd from 6.5.3 to 6.5.4 in /docs (#43160)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 02:34:03 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> a6db0d1cde chore(deps): bump query-string from 9.4.1 to 9.5.0 in /superset-frontend (#43161)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 02:34:00 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> aec567f7d6 chore(deps): bump antd from 6.5.3 to 6.5.4 in /superset-frontend (#43162)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 02:33:56 -07:00
Evan RusackasandClaude Code bacaf08a22 docs(perf): add Dashboard Performance guide covering virtualization, lazy tabs, and chart-count guidance (#40238)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-13 23:41:00 -07:00
a188e9473a feat(chart): let Drill By scope to the clicked x-axis value (#42296)
Co-authored-by: Jacob Hartmann (BUVM-STIL) <jacob.hartmann@stil.dk>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-13 17:02:27 -07:00
537f0fd2db fix(sqllab): use dialect-specific quote chars for autocomplete identifiers (#41492)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-13 14:04:38 -07:00
Joe Li 8f78f9eb9c chore: update CODEOWNERS for extension and config files (#43142) 2026-08-13 13:33:49 -07:00
Joe Li 930ba64fdf fix(ci): format ErrorMessage tests (#43114) 2026-08-13 13:19:08 -07:00
3fb58900b9 chore(duckdb): remove cursor.description workaround, no longer reproducible (#43101)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-13 13:16:16 -07:00
a63483b9b1 fix(sql): rebase Dremio dialect on sqlglot's native dialect (#43099)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-13 13:16:06 -07:00
Viktor HögbergandJoe Li 01d0772d4f fix(box-plot): distribute across field not marked as required for datasets without a temporal column (#43087)
Co-authored-by: Joe Li <joe@preset.io>
2026-08-13 13:15:51 -07:00
3c7633f935 fix(encrypt): stop naive padding from truncating secrets ending in '*' (#43074)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-13 13:15:37 -07:00
Evan RusackasandClaude Code f7a2f0ec50 feat(table): allow choosing Sum or Average for the "Show summary" totals row (#43027)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-13 13:15:23 -07:00
dc9547554b fix(echarts): normalize BigInt metric values before summing/dividing in stacked charts (#36401) (#42594)
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-13 13:14:33 -07:00
Evan RusackasandClaude Opus 4.8 67bbe0ac17 ci: speed up backend CI with astral-sh/setup-uv and apt package caching (#42498)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-13 13:14:24 -07:00
6211f9936b feat(mobile): Add mobile-friendly dashboard consumption mode (#37141)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-13 13:14:11 -07:00
Bernedotcom2312andClaude Opus 5 5b28158519 fix(helm): stop shipping conflicting podDisruptionBudget defaults (#42995)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 13:02:51 -07:00
Amin Ghadersohi 117b92a517 fix(mcp): expose table column formatting (#42658) 2026-08-13 15:21:44 -04:00
Evan RusackasandClaude e1468a709c fix(sunburst): keep SQL null and literal string "null" as distinct groups (#43013)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-13 11:07:06 -07:00
Evan RusackasandClaude Code c8cb09d458 fix(dev): stop the webpack dev-proxy from hanging on a mid-stream backend disconnect (#42811)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-13 11:06:30 -07:00
ec136f6d8d fix(charts): decode CSV bytes payload before feeding pandas StringIO (#32370) (#42735)
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-13 11:06:09 -07:00
d0658bacc8 fix(mcp): give concurrent tool calls isolated db sessions (#42629)
Co-authored-by: goingforstudying-ctrl <goingforstudying-ctrl@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-13 11:05:40 -07:00
ʈᵃᵢ b8ca729f9f fix(sql): only force a LIMIT onto query expressions (#43097) 2026-08-13 10:43:45 -07:00
Evan RusackasandClaude Code 25c2ca1127 ci: run pre-commit checks via prek (#42500)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-13 10:28:11 -07:00
Evan RusackasandClaude Code d5ae93c9b8 test(echarts): pin Bar chart X Axis Title flows through untouched (#42560) (#42599)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-13 10:27:32 -07:00
Evan RusackasandClaude fbeba10f75 fix(sqllab): make MenuDotsDropdown trigger focusable for tab rename (#43015)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-13 10:26:59 -07:00
Evan RusackasandClaude Opus 4.8 c8305b0ba9 feat(ci): publish Python unit test results as PR check annotations (#42503)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-13 10:26:42 -07:00
Evan RusackasandClaude Opus 4.8 e1777737a7 fix(docs): sync live component demos to the docs site's dark mode toggle (#42602)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-13 10:15:54 -07:00
Amin Ghadersohi 762fdccfde fix(oauth2): log database token failures (#42644) 2026-08-13 12:54:55 -04:00
Amin GhadersohiandClaude 5554b892ff fix(mcp): preserve table chart state during updates (#42655)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-13 12:54:05 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e2bb33b1da chore(deps): bump the ag-grid group across 1 directory with 2 updates (#43103)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-13 06:50:19 -07:00
Evan RusackasandClaude Opus 4.8 59361581cd fix(embedded): restore chart rendering for guest dashboards (#43095)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-13 05:38:57 -07:00
Evan RusackasandClaude Sonnet 5 08dfca9631 fix(ci): enforce a single Alembic migration head (#42890)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 05:38:45 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 276d6f04f5 chore(deps): bump actions/setup-java from 5.6.0 to 5.7.0 (#43102)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-13 02:33:25 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 524d99159a chore(deps): bump immer from 11.1.15 to 11.1.16 in /superset-frontend (#43104)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-13 02:33:21 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 657a2a4cb2 chore(deps): bump mapbox-gl from 3.27.0 to 3.28.0 in /superset-frontend (#43105)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-13 02:33:17 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> eefb3e3835 chore(deps-dev): bump tsx from 4.23.7 to 4.23.9 in /superset-frontend (#43106)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-13 02:33:13 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 07accd56f2 chore(deps): bump core-js from 3.49.0 to 3.50.0 in /superset-frontend (#43107)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-13 02:33:09 -07:00
Evan RusackasandClaude Opus 4.8 22caf221b7 fix(echarts): truncate values at Y axis bounds instead of dropping them (#42300)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-13 02:30:57 -07:00
Evan RusackasandClaude Fable 5 de2276225a test(playwright): pin drill-to-detail modal table height (#42406)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 22:48:49 -07:00
endimonan 685f26b1bf fix(cache): warm native dashboard filter defaults (#43073) 2026-08-12 22:30:48 -07:00
Anupam MedirattaandClaude Sonnet 4.6 739365979d fix: upgrade brace-expansion to 5.0.8 (CVE-2026-14257) (#42435)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-12 22:23:33 -07:00
bc1a8e0858 refactor(tags): align ExportTagsCommand with ExportModelsCommand (#42339)
Co-authored-by: Prathamesh Hukkeri <prathamesh04@users.noreply.github.com>
Co-authored-by: rusackas <evan@rusackas.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-08-12 21:44:53 -07:00
Evan RusackasandClaude Opus 4.8 0915a39bcb fix(dataset): improve dataset, report, and expression validation (#42929)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-12 20:13:45 -07:00
8014f782d3 feat: bump SQLAlchemy to 2.0 and flask-sqlalchemy to 3.1.1 (#42803)
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Superset Dev <dev@superset.apache.org>
2026-08-12 19:55:14 -07:00
endimonanandEvan Rusackas eb7d4cba42 fix(explore): hide Superset annotation source for users without annotation access (#43006)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-08-12 18:51:39 -07:00
Evan RusackasandClaude Opus 4.8 01ecefd732 fix(security_manager): stop SupersetAuthView from shadowing AUTH_REMOTE_USER (#42949)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-12 18:09:46 -07:00
Elizabeth Thompson 0c2f91968e fix: commit migration write in get_shared_value to stop recurring md5 deprecation warning (#42916) 2026-08-12 15:02:34 -07:00
Evan RusackasandClaude Code ed696b9933 test(sql): pin optimizer hint blocks survive format() round-trip (#38189) (#42733)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-12 14:49:55 -07:00
Joe Li 4baf1cf648 fix(reports): preserve Slack v1 private-channel text delivery (#42089) 2026-08-12 14:42:57 -07:00
Rafael BenitezandClaude Opus 4.8 aefee48223 fix(GridTable): remove unused rowSelection option to silence AG Grid error #200 (#43078)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-12 23:07:40 +02:00
Evan RusackasandClaude Fable 5 cc35056bc9 test(core): pin dashboard save-error toast mapping for non-JSON 403 responses (#42250)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 11:49:11 -07:00
Evan RusackasandClaude Code a0d7ec9faf fix(select): permission label search matches displayed label (#42041) (#42592)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-12 08:58:22 -07:00
onheapandkeyao_yang a501fed560 fix(rls): handle same-named CTEs and quoted aliases in the SQL rewrite (#43005)
Co-authored-by: keyao_yang <keyao.yang@airbnb.com>
2026-08-12 10:38:36 -03:00
Evan RusackasandClaude Code 4354b37b96 fix(sql-lab): apply SQL_QUERY_MUTATOR to streaming exports (#40465) (#42739)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-12 05:56:01 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> d840568f3b chore(deps): bump dompurify from 3.4.12 to 3.4.13 in /superset-frontend (#43082)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 02:56:56 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 174d35380d chore(deps-dev): bump tsx from 4.23.5 to 4.23.7 in /superset-frontend (#43083)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 02:56:52 -07:00
Evan RusackasandClaude Sonnet 5 2c10e6260f fix(explore): pin and fix clipped segments in horizontal row-contribution stacked bar charts (#42610)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 02:42:15 -07:00
Evan RusackasandClaude Code 8f6587d0e6 fix(caching): sort extra_cache_keys before hashing (#34543) (#42597)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-11 23:34:01 -07:00
Evan RusackasandClaude Code b4f3fae288 fix(sql): guard FORCE_LIMIT against SHOW statements (#36939) (#42588)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-11 23:33:15 -07:00
Evan RusackasandClaude Code 8e455034d0 chore(codeowners): trim stale ownership blocks, reword maps notice (#43079)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-11 23:29:46 -07:00
8734a232d8 fix(reports): apply chart number and currency formatting to tables sent as text (#42820)
Co-authored-by: Jean Massucatto <massucattoj@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 21:48:37 -07:00
Amin Ghadersohi 56573fa2cd feat(mcp): filter asset lists by certification (#42656) 2026-08-12 00:12:38 -04:00
Evan RusackasandClaude Code 885f00130c fix(sqla): drop stale main_dttm_col from dttm_cols when non-temporal (#30510) (#41964)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-11 19:38:53 -07:00
Evan RusackasandClaude Opus 4.8 584466e02b fix(sql-lab): improved SQL statement parsing and validation (#42928)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 19:35:43 -07:00
d570335f67 fix: bind permission-sync task to user id, use per-user RLS cache sentinel on parse failure (#42938)
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 19:35:40 -07:00
Evan RusackasandClaude Opus 4.8 a0099af88f fix(deck.gl): dismiss custom tooltips on hover-out (#43075)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 19:32:43 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 8bd25b3fed chore(deps-dev): bump the typescript-eslint group across 1 directory with 2 updates (#43038)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 16:28:52 -07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>rusackasClaude Opus 4.8
0a2cfb9fba chore(deps-dev): bump oxfmt from 0.61.0 to 0.62.0 in /superset-frontend (#43046)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 16:28:48 -07:00
5248367587 fix: verify database access before dataset repointing, and tab ownership before tab-state updates (#42936)
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 16:07:58 -07:00
madhushreeagandmadhushree agarwal bdf8ce69ab feat(Reports&Alerts): remove Selenium support, require Playwright (#43028)
Co-authored-by: madhushree agarwal <madhushree_agarwal@apple.com>
2026-08-11 16:06:26 -07:00
Dang Dao d91ca50872 perf(charts): reuse datasource in query context (#42476) 2026-08-11 14:31:47 -07:00
ShaitanandClaude Opus 4.8 f6077eeaa1 fix(dataset): validate stored column and metric expressions at query time (#42087)
Signed-off-by: sha174n <shaitan@zx48.net>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-11 21:48:26 +01:00
ShaitanandClaude Opus 4.8 0d57c5ab50 fix(dashboard): consistently apply the datasource-access check when serializing member charts and datasets (#42716)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 21:47:47 +01:00
Ayush SharafandAyush Kumar Sharaf edf30c7019 chore: add logging event for drill to detail (#42563)
Co-authored-by: Ayush Kumar Sharaf <ayush.sharaf@314ecorp.com>
2026-08-11 13:47:00 -07:00
SkinnyPigeonandEnzo Martellucci 3ded398a1f fix(chart): dataset dropdown sorted by name (#42475)
Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com>
2026-08-11 13:45:48 -07:00
63a81c2125 fix(dashboard): remeasure chart description height after markdown render (#41536)
Co-authored-by: rusackas <evan@rusackas.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 13:44:48 -07:00
af258c21d0 fix(mixed-chart): Mixed chart secondary yaxis title position misplaced fix (#42713)
Co-authored-by: Mallikarjuna Reddy N <MallikarjunaReddy.Nimmakayala@gevernova.com>
Co-authored-by: rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 13:13:34 -07:00
Joe LiandClaude Opus 5 9086bab2c2 ci(e2e): stop gunicorn worker recycling mid-run; slow-test budget for mixed-chart filter spec (#42867)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 12:43:51 -07:00
4c5a3356f3 fix(dashboard): redirect using sanitized slug from save response (#42853)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: rusackas <evan@rusackas.com>
2026-08-11 12:43:24 -07:00
998c408686 fix(rls): return a descriptive error for duplicate rule names (#42819)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: rusackas <evan@rusackas.com>
2026-08-11 12:41:24 -07:00
Joe Li a3d58d1211 fix(ci): use current actions for Docker refresh (#42482) 2026-08-11 12:04:09 -07:00
7b32b870e1 chore: Fix/remove hardcode of admin role (#27779)
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Joe Li <joe@preset.io>
Co-authored-by: rusackas <evan@rusackas.com>
2026-08-11 10:26:50 -07:00
Evan RusackasandSuperset Dev 3cd4150539 chore(deps): bump sqlglot from 30.15.0 to 30.16.0 (#43029)
Co-authored-by: Superset Dev <dev@superset.apache.org>
2026-08-11 10:16:44 -07:00
Mike BridgeandClaude Fable 5 2feb6c2bb5 refactor(deletion-retention): add declarative purge policies (#42888)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 09:55:36 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 5767c3981d chore(deps): bump the storybook group across 1 directory with 2 updates (#43037)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 09:48:45 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ec6b2d2347 chore(deps-dev): bump oxfmt from 0.61.0 to 0.62.0 in /superset-websocket (#43033)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 09:48:42 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> aade32cad8 chore(deps-dev): bump the typescript-eslint group across 1 directory with 3 updates (#43039)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 09:48:38 -07:00
Evan RusackasandClaude Opus 4.8 d6771e96d2 fix(core): let UnsavedChangesModal use Ant Design's automatic z-index stacking (#42548)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 09:42:49 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 305638e98c chore(deps-dev): bump baseline-browser-mapping from 2.11.11 to 2.11.12 in /superset-frontend (#43048)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 09:42:16 -07:00
Evan RusackasandClaude Sonnet 5 5099b6ffa9 fix(export): derive chart_hashes and zip from a single chart list (#42946)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 09:35:59 -07:00
Evan RusackasandAmin Ghadersohi bc436bacad chore(security): warn when legacy encryption engine is configured (#42937)
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
2026-08-11 09:35:46 -07:00
Evan RusackasandClaude e31a894be6 fix(common): remove unsupported cross join mode from left_join_df (#43016)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-11 09:35:16 -07:00
Evan RusackasandClaude 553808daa3 fix(reports): make webhook notification request timeout configurable (#43018)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-11 09:35:01 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 544756a631 chore(deps): bump github/codeql-action/analyze from 4.37.5 to 4.37.6 (#43040)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 09:34:49 -07:00
Evan RusackasandClaude Code c4e8a9da90 test(explore): add end-to-end regression test for DatasourcePanel through the real AutoSizer + react-window List pipeline (#43023)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-11 09:34:06 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 47adbe31d5 chore(deps-dev): bump typescript-eslint from 8.65.0 to 8.66.0 in /superset-websocket (#43034)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 06:43:16 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 64165fbdae chore(deps-dev): bump @typescript-eslint/parser from 8.65.0 to 8.66.0 in /superset-websocket (#43035)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 06:43:12 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 581fb876e4 chore(deps-dev): bump oxfmt from 0.61.0 to 0.62.0 in /docs (#43042)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 06:43:09 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 29e8e396ee chore(deps-dev): bump the storybook group in /superset-frontend with 5 updates (#43045)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 06:43:05 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 8bbf0cfc20 chore(deps-dev): bump oxlint from 1.76.0 to 1.77.0 in /superset-frontend (#43049)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 06:43:01 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 497c73e837 chore(deps): bump github/codeql-action/init from 4.37.5 to 4.37.6 (#43044)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 06:42:53 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> a5a6f5a5b1 chore(deps): bump ws from 8.21.1 to 8.21.2 in /superset-websocket (#43036)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 06:42:50 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 156a59a190 chore(deps): bump swagger-ui-react from 5.32.11 to 5.32.12 in /docs (#43041)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 06:42:46 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 590003cbee chore(deps): bump baseline-browser-mapping from 2.11.11 to 2.11.12 in /docs (#43043)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 06:42:43 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 579011dea9 chore(deps): bump nanoid from 6.0.0 to 6.0.1 in /superset-frontend (#43050)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 06:42:39 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 65d808a5fe chore(deps): bump markdown-to-jsx from 9.10.1 to 9.10.2 in /superset-frontend (#43052)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 06:42:34 -07:00
Evan RusackasandClaude 5ee279363c fix(mcp): reject list values for ne on relationship filters (#42944)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-11 00:42:13 -07:00
ShaitanandClaude Opus 5 34b2d3a533 fix(rls): match null-schema datasets in predicate lookup (#41426)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 19:58:51 -07:00
Evan GruhlkeyandJoe Li 7157b752ee fix(pie): apply percentage number format to labels (#42880)
Co-authored-by: Joe Li <joe@preset.io>
2026-08-10 19:36:03 -07:00
d84a00f3c6 feat(dashboard): handle empty chart query context in Excel export (#42284)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Elizabeth Thompson <eschutho@gmail.com>
2026-08-10 21:12:08 -04:00
Evan RusackasandClaude Sonnet 5 bfa1e775c4 fix(theming): guard ensureStaticPrefix against null/undefined input (#43019)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 17:41:33 -07:00
265269018e fix(mcp): require issuer-aware resolver for multi-issuer JWT, fix sanitizer input ordering (#42935)
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 15:56:46 -07:00
Evan RusackasandClaude Sonnet 5 5f204a9af2 fix(frontend): remove any type and duplicate route in routes.tsx (#43014)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 15:51:58 -07:00
Evan RusackasandClaude Sonnet 5 0c09cf087d fix(reports): translate execute-now success message (#43012)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 15:51:05 -07:00
Evan RusackasandClaude Sonnet 5 651f75b1e8 docs(dashboard): document native filters on semantic views (#43011)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 15:50:34 -07:00
Evan RusackasandClaude Sonnet 5 e48512568d docs(dashboard): document the expand_all_slices metadata flag (#43010)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 15:50:03 -07:00
Evan RusackasandClaude Sonnet 5 4d0eeb7d86 docs: document FAB_API_SWAGGER_UI_SUPERSET_APP_ROOT config flag (#43009)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 15:49:47 -07:00
Elizabeth Thompson fdfc49461f fix(sqllab): wrap process_template() in format_sql to prevent raw UndefinedError leak (#42917) 2026-08-10 15:02:10 -07:00
Mike MaandEvan Rusackas 2eb1e04283 fix: hydrate JWT user before list API filters (#39946)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-08-10 14:18:28 -07:00
HerestothegamersandClaude Fable 5 bc85f1eacf fix(database): resolve default schema when file upload schema is empty (#42603)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 13:56:47 -07:00
Gabriel Torres Ruiz 0dedc55f38 fix(dashboard): changing a theme no longer discards unsaved edits or reloads charts 2026-08-10 16:52:43 -03:00
Vijay MisalandJoe Li 178d9c4ceb fix(explore): stacked Timeseries Bar total excludes the sort-only metric (#42881)
Co-authored-by: Joe Li <joe@preset.io>
2026-08-10 12:40:18 -07:00
Luc Verdier 347774f9b8 fix(explore): stop the viz switcher from overflowing the control panel (#42911) 2026-08-10 12:36:23 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 776f70bf63 chore(deps): bump ioredis from 5.11.1 to 6.0.0 in /superset-websocket (#42959)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 12:35:56 -07:00
Evan RusackasandClaude 572f86c3c3 fix(deckgl): normalize legend swatch alpha channel to 0-1 for CSS rgba() (#42942)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-10 12:35:24 -07:00
Sepuri Sai Krishna 50c811280e fix(lru-cache): don't evict another entry when overwriting an existing key (#42409) 2026-08-10 12:29:20 -07:00
Bandar 9d6832c927 feat(formatting): add network throughput number formats for bits/s and bytes/s (#42437) 2026-08-10 12:29:05 -07:00
ShaitanandClaude Opus 4.8 b6504eb111 fix(sql): resolve schema/catalog-qualified table references in CTE detection (#42717)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-10 12:28:31 -07:00
Joe LiandClaude Opus 5 d62a9eeda6 fix(calendar): clean up d3-tip tooltips (#41589)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 12:28:06 -07:00
Mallikarjuna Reddy NimmakayalaandDamian Pendrak 651c046e69 fix(deckgl): remove visibility condition in deckgl fill color (#42712)
Co-authored-by: Damian Pendrak <dpendrak@gmail.com>
2026-08-10 20:58:44 +02:00
Joe Li d9ae0ec2fc fix(deckgl): hide legend when Legend Position is None (#42402) 2026-08-10 11:24:35 -07:00
varadendrasimha511 b98e157104 fix(sqla): preserve float precision when mixing int/float values in IN filters (#42752) 2026-08-10 11:12:43 -07:00
4defdbabd2 fix(dashboard): force-render virtualized charts before client-side export (#42561)
Signed-off-by: Jenwit Amonpongitsara <jenwit.amonpongitsara@agoda.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: rusackas <evan@rusackas.com>
2026-08-10 10:57:21 -07:00
Evan RusackasandClaude 7dc2afd377 docs(cache): document native filter option cache warm-up strategy (#42953)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-10 10:28:36 -07:00
Evan RusackasandClaude Sonnet 5 5bcd9cd078 docs(exploring-data): document CSV/XLSX download in drill modals (#42952)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 10:28:14 -07:00
Evan RusackasandClaude Sonnet 5 341ca94ba2 docs(security): document the read-only Subject REST API (#42951)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 10:27:54 -07:00
Evan RusackasandClaude Sonnet 5 4b68da82a7 docs: document number format presets, including distance/length units (#42950)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 10:27:22 -07:00
Evan RusackasandClaude Sonnet 5 a9eab4a0df docs: document color picker for conditional formatting rule colors (#42948)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 10:26:37 -07:00
Evan RusackasandClaude f60370b695 docs: document CACHE_REDIS_SOCKET_TIMEOUT/CONNECT_TIMEOUT config keys (#42947)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-10 10:26:18 -07:00
Evan RusackasandClaude Sonnet 5 c02dc7780b docs(mcp): add update_dashboard to write-tools enumeration (#42943)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 10:25:03 -07:00
Mehmet Salih Yavuz daf1718b44 fix(embedded): redact database errors in API responses to guest users (#42796) 2026-08-10 18:45:20 +03:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> b815640b8f chore(deps): bump actions/setup-java from 5.6.0 to 5.7.0 (#42968)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 07:54:41 -07:00
Antonio Rivero 9e2cb69a39 feat(database): identify configured database connection errors (#42996) 2026-08-10 14:14:42 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> db88abe0bb chore(deps-dev): bump globals from 17.8.0 to 17.9.0 in /superset-websocket (#42958)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 04:53:29 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> f53f634f23 chore(deps): bump antd from 6.5.2 to 6.5.3 in /docs (#42961)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 04:53:13 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 916c50284b chore(deps): bump github/codeql-action/analyze from 4.37.3 to 4.37.5 (#42965)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 11:39:14 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 6a2e20cdb7 chore(deps): bump zizmorcore/zizmor-action from 0.6.1 to 0.6.2 (#42960)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 01:29:49 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 9719c7de24 chore(deps-dev): bump globals from 17.8.0 to 17.9.0 in /docs (#42962)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 01:29:09 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 346dbd6a98 chore(deps): bump js-yaml from 5.2.2 to 5.2.3 in /docs (#42963)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 01:28:29 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 73478c5274 chore(deps): bump baseline-browser-mapping from 2.11.8 to 2.11.11 in /docs (#42964)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 01:28:25 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> d7edf610a9 chore(deps-dev): bump @babel/types from 7.29.7 to 7.29.8 in /superset-frontend in the babel group (#42966)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 01:28:20 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e12818897d chore(deps): bump github/codeql-action/init from 4.37.3 to 4.37.5 (#42967)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 01:28:16 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> d41032b70a chore(deps): bump Kesin11/actions-timeline from 3.1.1 to 3.2.0 (#42969)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 01:28:10 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 10cf2e20f9 chore(deps-dev): bump eslint-plugin-jest-dom from 5.7.0 to 5.10.1 in /superset-frontend (#42970)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 01:28:05 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 2be726ddad chore(deps-dev): bump tsx from 4.23.1 to 4.23.5 in /superset-frontend (#42971)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 01:28:02 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ee5fb095da chore(deps-dev): bump baseline-browser-mapping from 2.11.8 to 2.11.11 in /superset-frontend (#42972)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 01:27:58 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 4c9bd69eb4 chore(deps): bump antd from 6.5.2 to 6.5.3 in /superset-frontend (#42973)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 01:27:55 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 7cd6c74757 chore(deps-dev): bump @types/lodash from 4.17.24 to 4.17.25 in /superset-frontend (#42974)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 01:27:51 -07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
3539c41dab chore(deps): bump cryptography from 49.0.0 to 50.0.0 (#42906)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-09 23:42:10 -07:00
c09d5bf6a9 fix(helm): add missing task modules to default CeleryConfig imports (#42945)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com>
2026-08-09 22:06:45 -07:00
Đỗ Trọng Hải f8925cc726 feat(build): reinstate no-restricted-imports lint rule for lodash (#42940) 2026-08-10 09:28:41 +07:00
Jetha ChanandDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> fea84e81ef fix(tech-debt): measure oxlint metrics against the project's ruleset (#42955)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-10 09:26:15 +07:00
Amin Ghadersohi b413060030 chore(deps): restore redis floor to >=5.0.0 (#42915) 2026-08-09 17:41:54 -07:00
Đỗ Trọng HảiandJoe Li 3b164e4270 fix(ci): remove extraneous plugins build job (#42677)
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-08 17:27:51 -07:00
Dang DaoandEvan Rusackas bf294cfa43 perf(dashboard): avoid query context N+1 queries (#42474)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-08-08 17:27:04 -07:00
42e4030104 feat(security): add guest user attributes and get_guest_user_attribute() macro (#33924)
Co-authored-by: Yash Janoria <yash.janoria@314ecorp.com>
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
2026-08-08 16:56:07 -07:00
8181917f79 fix: Apply timezone offset to convert local time boundaries to UTC (#37014)
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-08 16:40:31 -07:00
d14ab29dcb feat(a11y): enable ECharts aria-label (#28171)
Co-authored-by: rusackas <evan@preset.io>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-08 15:36:24 -07:00
a75665b014 fix(i18n): improve Simplified Chinese translations (#42794)
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 12:51:59 -07:00
db5cd1aa38 chore(i18n): update Italian messages.po translations (#38948)
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 12:40:08 -07:00
abb6dd6e39 fix(i18n): add Tamil (ta) locale with corrected catalog path (#38962)
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 12:31:29 -07:00
4a587b8539 feat(security): actionable, request-access-aware data permission errors (#41843)
Co-authored-by: Greg Neighbors <gregneighbors@Gregs-Air-2.lan>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Greg Neighbors <gregneighbors@Gregs-MacBook-Air-2.local>
2026-08-08 11:51:15 -07:00
127a6f92a6 chore(i18n): update french po file to match latest pot file version (#40635)
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 10:43:30 -07:00
Evan RusackasandClaude Code 25481799f6 test(deckgl): pin GeoJSON layer against multi-row payloads (#34748) (#42734)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-08 08:06:05 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 1da7a7fdb1 chore(deps-dev): update starrocks requirement from <2,>=1.3.3 to >=1.3.4,<2 (#42903)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-08 01:54:54 -07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
185b5938ba chore(deps-dev): bump pyinstrument from 5.1.2 to 5.1.3 (#42905)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-08 01:54:51 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 503309531f chore(deps-dev): update playwright requirement from <2,>=1.61.0 to >=1.62.0,<2 (#42908)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-08 01:54:48 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 44a86b725f chore(deps-dev): update teradatasql requirement from >=20.0.0.63 to >=20.0.0.64 (#42909)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-08 01:54:46 -07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Amin GhadersohiClaude Sonnet 5
38727906a2 chore(deps): bump react-window and @types/react-window in /superset-frontend (#42528)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 23:02:27 -07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Joe LirusackasClaude Opus 4.8
2a9c5acb01 chore(deps): bump numpy from 1.26.4 to 2.4.6 (#42778)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Joe Li <joe@preset.io>
Co-authored-by: rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-07 22:55:20 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> d8b97e31fa chore(deps-dev): bump @playwright/test from 1.61.1 to 1.62.1 in /superset-frontend (#42699)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 21:05:56 -07:00
Bart SkowronandClaude Fable 5 bd03440ac8 feat(alerts-reports): add per-schedule toggle to include/exclude the Explore in Superset link (#42494)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 21:05:16 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> a6c478c59d chore(deps-dev): bump lerna from 9.0.4 to 10.0.0 in /superset-frontend (#42829)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-08 10:19:24 +07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> fd36419ca2 chore(deps): bump nanoid from 3.3.16 to 3.3.18 in /docs (#42902)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-08 10:08:38 +07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Joe Li
d80f4c841d chore(deps-dev): update sqlalchemy-risingwave requirement from <2.0.0,>=1.4.1 to <3.0.0,>=1.4.1 (#42856)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-08 09:59:53 +07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Joe Li
6baa467f59 chore(deps): bump google-auth-library from 10.9.1 to 11.0.0 in /superset-frontend (#42870)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-08 09:58:51 +07:00
b18015f63b feat: Color Picker replaces the color selection drop-down list in conditional formatting (#42053)
Co-authored-by: rusackas <evan@rusackas.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-08 02:00:56 +03:00
Elizabeth ThompsonandClaude Opus 4.8 eb8592d4bf fix(sqla): don't mislabel DB errors as ColumnNotFoundException in adhoc_column_to_sqla (#42889)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-07 15:03:49 -07:00
Gaston LaterzaandClaude Opus 5 55af4685a2 fix(i18n-es): correct fuzzy-matcher strandings in Spanish catalog (#42808)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 11:29:14 -07:00
Joe LiandClaude Opus 4.8 c901f01693 fix(datasets): preserve metric/column uuids on dataset export (#42393)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-07 11:15:24 -07:00
4c894affb2 fix(models): purge_oauth2_tokens filters on wrong column (#42211)
Signed-off-by: Martin Brodeur <addressedemartin@gmail.com>
Co-authored-by: rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-07 11:04:11 -07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Joe Li
0f9049608a chore(deps): bump dompurify from 3.4.12 to 3.4.13 in /superset-frontend (#42893)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-07 11:00:19 -07:00
Gabriel Torres Ruiz 91b1a6cac5 feat(theme): allow deployments to register extra theme tokens 2026-08-07 14:37:45 -03:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 86a328ca9e chore(deps): bump dompurify from 3.4.12 to 3.4.13 in /docs (#42894)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 10:23:30 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 38ba4a69bf chore(deps): bump react-intersection-observer from 10.1.0 to 11.0.0 in /superset-frontend (#42869)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 10:13:31 -07:00
Mike BridgeandMike Bridge 280253b1fb fix(deletion-retention): dedupe repeated blocked audits (#42863)
Co-authored-by: Mike Bridge <michael.bridge@ext.preset.io>
2026-08-07 10:11:36 -07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Joe Li
79c2cd367d chore(deps): update flask-sqlalchemy requirement from <3.0,>=2.5.1 to <4.0,>=2.5.1 (#42771)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Joe Li <joe@preset.io>
2026-08-07 09:42:42 -07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
fd6a0a60a6 chore(deps): bump holidays from 0.100 to 0.102 (#42860)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-07 09:41:57 -07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
c2b5311f47 chore(deps-dev): bump google-cloud-bigquery from 3.42.2 to 3.43.0 (#42861)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-07 09:41:26 -07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
70a82a28bf chore(deps): bump redis from 8.0.1 to 8.1.0 (#42857)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-07 09:41:03 -07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
f348cf425d chore(deps): bump shillelagh from 1.4.4 to 1.4.5 (#42858)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-07 09:41:00 -07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
74196cf694 chore(deps): bump markdown from 3.10.2 to 3.10.3 (#42859)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-07 09:40:57 -07:00
fe06ebe796 feat(versioning): enable version history and capture by default (#42801)
Co-authored-by: Mike Bridge <michael.bridge@ext.preset.io>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 10:25:48 -03:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> f1411ccd6c chore(deps-dev): update pyathena requirement from <4,>=3.35.2 to >=3.35.4,<4 (#42862)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 06:19:46 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 1770823f0a chore(deps-dev): bump eslint-plugin-jest-dom from 5.6.0 to 5.7.0 in /superset-frontend (#42868)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 06:18:31 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> b7b873321d chore(deps-dev): bump baseline-browser-mapping from 2.11.7 to 2.11.8 in /superset-frontend (#42872)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 06:17:22 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> d363cabec9 chore(deps): bump markdown-to-jsx from 9.9.0 to 9.10.1 in /superset-frontend (#42885)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 06:17:18 -07:00
Joe Li 8683d1608e fix(migrations): merge divergent Alembic heads into a single chain (#42878) 2026-08-07 09:27:03 -03:00
Mehmet Salih Yavuz 14583b6a5f fix(themes): serve system themes with the algorithm of the slot they fill (#42700) 2026-08-07 14:29:49 +03:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 27498d26aa chore(deps): bump the docusaurus-openapi group in /docs with 2 updates (#42883)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 02:42:39 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 39e66476e1 chore(deps): bump actions/setup-java from 5.6.0 to 5.7.0 (#42884)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 02:42:33 -07:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Đỗ Trọng Hải
8153a82489 chore(deps): bump baseline-browser-mapping from 2.11.7 to 2.11.8 in /docs (#42852)
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-08-06 23:24:24 -07:00
Grégoire GaillyandClaude Opus 4.8 448eb25d76 feat(docs): add documentation for usage of minio or other s3 compatible service as a cache backend or celery results backend (#35825)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 18:36:34 -07:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 9cec7411e9 chore(deps): bump mermaid from 11.16.0 to 11.16.1 in /docs (#42873)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-06 18:33:23 -07:00
Joe LiandJuliann Forbes ae66b697e5 fix(plugin-chart-table): align sticky header column dividers with table body (#42573)
Co-authored-by: Juliann Forbes <juliann@preset.io>
2026-08-06 15:40:44 -07:00
Joe LiandClaude Opus 4.8 22fb1b3a44 test(dashboard): migrate edit-mode component E2E tests to Playwright (#41438)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 15:22:19 -07:00
Elizabeth Thompson 7dd6ce835f fix(datasets): catch TemplateError instead of narrower TemplateSyntaxError in render_dataset_fields (#42802) 2026-08-06 15:05:08 -07:00
4f2147009f feat(dashboard): expand all chart descriptions (#32958)
Co-authored-by: Urban Pettersson <urban.pettersson@alteryx.com>
Co-authored-by: rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 14:47:30 -07:00
Evan RusackasandClaude Code 2ecce20e48 chore(viz): remove legacy explore_json + viz.py pipeline (#41714)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-06 13:22:15 -07:00
Elizabeth ThompsonandClaude 01a9fdc621 chore(reports): thread cache-key/execution-id log context through screenshot capture logs (#42657)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-06 09:43:12 -07:00
1308 changed files with 86320 additions and 34549 deletions
+1
View File
@@ -79,6 +79,7 @@ github:
- lint-check
- cypress-matrix-required
- dependency-review
- enforce-single-migration-head
- frontend-build
- playwright-tests-required
- pre-commit (current)
+10 -33
View File
@@ -1,22 +1,3 @@
# Notify all committers of DB migration changes, per SIP-59
/superset/migrations/ @mistercrunch @michael-s-molina @betodealmeida @eschutho @sadpandajoe @rusackas
# Notify some committers of changes in the components
/superset-frontend/src/components/Select/ @michael-s-molina @geido @kgabryje
/superset-frontend/src/components/MetadataBar/ @michael-s-molina @geido @kgabryje
/superset-frontend/src/components/DropdownContainer/ @michael-s-molina @geido @kgabryje
# Notify Helm Chart maintainers about changes in it
/helm/superset/ @dpgaspar @villebro @nytai @michael-s-molina @mistercrunch @rusackas @Antonio-RiveroMartnez @hainenber
# Notify E2E test maintainers of changes
/superset-frontend/playwright/ @sadpandajoe @geido @eschutho @rusackas @mistercrunch
/superset-frontend/cypress-base/ @sadpandajoe @geido @eschutho @rusackas @mistercrunch
# Notify PMC members of changes to GitHub Actions
/.github/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @kgabryje @sha174n @dpgaspar @sadpandajoe @hainenber
@@ -30,23 +11,19 @@
/.asf.yaml @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @kgabryje @dpgaspar @sha174n @Antonio-RiveroMartnez
# Maps are a finicky contribution process we care about
# Maps are fragile and political. GeoJson edits MUST be made in the Jupyter notebook or they'll be overwritten.
**/*.geojson @villebro @rusackas
**/*.ipynb @villebro @rusackas
/superset-frontend/plugins/legacy-plugin-chart-country-map/ @villebro @rusackas
# Notify translation maintainers of changes to translations
/superset/translations/ @sfirke @rusackas @villebro @sadpandajoe @hainenber
/superset-frontend/plugins/plugin-chart-country-map/ @villebro @rusackas
# Notify PMC members of changes to extension-related files
/docs/developer_portal/extensions/ @michael-s-molina @villebro @rusackas
/superset-core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset-extensions-cli/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset/core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset/extensions/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset-frontend/src/packages/superset-core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset-frontend/src/core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset-frontend/src/extensions/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/docs/developer_docs/extensions/ @michael-s-molina @villebro @rusackas
/superset-extensions-cli/ @michael-s-molina @villebro @rusackas @sadpandajoe
/superset/extensions/ @michael-s-molina @villebro @rusackas @sadpandajoe
/superset-frontend/src/extensions/ @michael-s-molina @villebro @rusackas @sadpandajoe
# Notify PMC members of config changes e.g. feature flags
/superset/config.py @michael-s-molina @villebro @rusackas @sadpandajoe
+19 -9
View File
@@ -5,10 +5,6 @@ inputs:
description: 'Python version to set up. Accepts a version number, "current", or "next".'
required: true
default: 'current'
cache:
description: 'Cache dependencies. Options: pip'
required: false
default: 'pip'
requirements-type:
description: 'Type of requirements to install. Options: base, development, default'
required: false
@@ -43,17 +39,31 @@ runs:
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ steps.set-python-version.outputs.python-version }}
cache: ${{ inputs.cache }}
- name: Install uv
if: inputs.install-superset == 'true'
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: ${{ steps.set-python-version.outputs.python-version }}
enable-cache: true
- name: Update apt package lists
# cache-apt-pkgs-action assumes a fresh `apt-cache` index (true on GitHub-hosted
# runners, not on all self-hosted/custom runner images), so refresh it explicitly
# or package lookups silently resolve to an empty list.
if: inputs.install-superset == 'true'
shell: bash
run: sudo apt-get update
- name: Install apt packages
if: inputs.install-superset == 'true'
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
with:
packages: libldap2-dev libsasl2-dev
version: 1.0
- name: Install dependencies
env:
INPUT_INSTALL_SUPERSET: ${{ inputs.install-superset }}
INPUT_REQUIREMENTS_TYPE: ${{ inputs.requirements-type }}
run: |
if [ "$INPUT_INSTALL_SUPERSET" = "true" ]; then
sudo apt-get update && sudo apt-get -y install libldap2-dev libsasl2-dev
pip install --upgrade pip setuptools wheel uv
if [ "$INPUT_REQUIREMENTS_TYPE" = "dev" ]; then
uv pip install --system -r requirements/development.txt
elif [ "$INPUT_REQUIREMENTS_TYPE" = "base" ]; then
+11 -8
View File
@@ -201,18 +201,23 @@ cypress-run-all() {
# navigation flow under E2E. We diverge from the entrypoint on:
# --timeout 120: heavy dashboard import/export specs exceed the 60s
# default
# --max-requests / --max-requests-jitter: recycle the worker under
# test load to avoid leaks accumulating across the run
# superset.app:create_app(): explicit factory so we don't depend on
# FLASK_APP being exported
#
# No --max-requests, matching the entrypoint's default of 0 (recycling
# off). With a single worker a recycle takes the whole backend offline for
# the graceful-timeout drain — browser keep-alive connections hold it open
# for the full 30s — plus ~5s of app boot. A run issues ~3800 requests in
# ~8 minutes, so recycling every 500 produced seven ~35s outages per run
# and flaked whichever specs happened to navigate into one. Lowering
# --graceful-timeout is not enough: a dashboard load plus chart render
# needs 6-10s, which still lands inside the window.
nohup gunicorn \
--bind "127.0.0.1:$port" \
--workers 1 \
--worker-class gthread \
--threads 20 \
--timeout 120 \
--max-requests 500 \
--max-requests-jitter 50 \
--access-logfile - \
--error-logfile - \
"superset.app:create_app()" \
@@ -294,16 +299,14 @@ playwright-run() {
export PLAYWRIGHT_BASE_URL
# See cypress-run-all() above for the args rationale (1 worker × 20
# gthread threads matching docker/entrypoints/run-server.sh, plus a
# 120s timeout and request-recycling for heavy E2E load).
# gthread threads matching docker/entrypoints/run-server.sh, a 120s
# timeout for heavy E2E load, and why worker recycling is off).
nohup gunicorn \
--bind "127.0.0.1:$port" \
--workers 1 \
--worker-class gthread \
--threads 20 \
--timeout 120 \
--max-requests 500 \
--max-requests-jitter 50 \
--access-logfile - \
--error-logfile - \
"superset.app:create_app()" \
+4 -1
View File
@@ -45,7 +45,10 @@ jobs:
python-version: "3.11"
- name: Install uv
run: pip install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: "3.11"
enable-cache: true
- name: supersetbot bump-python -p "${{ github.event.inputs.package }}"
env:
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
check-python-deps:
runs-on: ubuntu-26.04
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
+2 -2
View File
@@ -64,7 +64,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -75,6 +75,6 @@ jobs:
# queries: security-extended,security-and-quality
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
category: "/language:${{matrix.language}}"
+1 -1
View File
@@ -50,7 +50,7 @@ jobs:
# You cannot use a liccheck.ini file in this workflow.
runs-on: ubuntu-slim
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
+57 -8
View File
@@ -51,6 +51,53 @@ jobs:
echo "matrix_config=${MATRIX_CONFIG}" >> $GITHUB_OUTPUT
echo $GITHUB_OUTPUT
# Runs unconditionally (no dependency on `changes`, and no build-preset
# matrix restriction) so a regression in the PY_VER override logic is
# always caught on PRs. Without this, the real docker-build job only runs
# when the change detector flags docker/python/frontend changes (a
# workflow-only edit like this one does not), and even then the PR build
# matrix never includes the "py311"/"py312" presets that logic protects -
# so a break here would otherwise first surface on a push to master.
pyver-override-check:
name: verify docker build PY_VER override
runs-on: ubuntu-26.04
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup supersetbot
uses: ./.github/actions/setup-supersetbot/
- name: Assert PY_VER override applies to every preset except py311/py312
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
# Asserts against the actual buildx command line `supersetbot docker
# --dry-run` would run, not just this repo's own extra-flags helper,
# so a regression in supersetbot itself (dropping the py311/py312
# PY_VER pin, or reordering args so our override no longer lands
# last) is caught here too, instead of only surfacing on master.
assert_effective_py_ver() {
local preset="$1" expected="$2" extra_flags command actual
extra_flags="$(scripts/docker-build-extra-flags.sh "$preset" dummy-tag)"
command="$(supersetbot docker --preset "$preset" --platform linux/amd64 --extra-flags "$extra_flags" --dry-run)"
# docker buildx keeps the LAST value of a repeated --build-arg key.
actual="$(grep -oE -- '--build-arg PY_VER=[^[:space:]]+' <<<"$command" | tail -1)"
if [ "$actual" != "--build-arg PY_VER=$expected" ]; then
echo "::error::preset '$preset' expected effective --build-arg PY_VER=$expected, got: ${actual:-<none>} (full command: $command)"
exit 1
fi
}
for preset in dev lean websocket dockerize; do
assert_effective_py_ver "$preset" "3.11.14-slim-trixie"
done
assert_effective_py_ver py311 "3.11-slim-bookworm"
assert_effective_py_ver py312 "3.12-slim-bookworm"
echo "PY_VER override logic verified against the assembled buildx command for all build presets"
docker-build:
name: docker-build
needs: [setup_matrix, changes]
@@ -124,19 +171,21 @@ jobs:
# the whole job. buildx reuses the buildkit layer cache from the
# failed attempt, so a retry mostly re-does just the failed push.
#
# supersetbot's "dev"/"lean" presets pin their own --build-arg
# PY_VER, which lands ahead of --extra-flags on the assembled
# buildx command line; docker/buildx keeps the last value for a
# repeated --build-arg key, so appending PY_VER here overrides
# supersetbot's pin and keeps the build on the Dockerfile's own
# supported Python version.
# See scripts/docker-build-extra-flags.sh for why "py311"/"py312"
# are excluded from the PY_VER override applied to every other
# preset; that logic is also exercised on every PR by the
# always-on pyver-override-check job below, since this job itself
# only runs when the change detector flags docker/python/frontend
# changes and the PR build matrix never includes py311/py312.
EXTRA_FLAGS="$(scripts/docker-build-extra-flags.sh "$BUILD_PRESET" "$IMAGE_TAG")"
for attempt in 1 2 3; do
if supersetbot docker \
$PUSH_OR_LOAD \
--preset "$BUILD_PRESET" \
--context "$EVENT" \
--context-ref "$RELEASE" $FORCE_LATEST \
--extra-flags "--build-arg PY_VER=3.11.14-slim-trixie --build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG" \
--extra-flags "$EXTRA_FLAGS" \
$PLATFORM_ARG; then
break
fi
@@ -221,6 +270,6 @@ jobs:
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
@@ -0,0 +1,82 @@
# 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.
name: Enforce single Alembic migration head
on:
push:
branches:
- "master"
- "[0-9].[0-9]*"
pull_request:
types: [synchronize, opened, reopened, ready_for_review]
# No `paths:` filter on purpose: this job is a required status check, and a
# required check that never runs for a given PR blocks that PR from merging
# forever. It has to fire on every PR so it always reports a status; whether
# migrations changed is decided inside the job, not the trigger.
# cancel previous workflow jobs for PRs
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
enforce-single-migration-head:
runs-on: ubuntu-26.04
permissions:
contents: read
pull-requests: read
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check for migration file changes
id: check
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ github.token }}
script: |
if (context.eventName === 'push') {
core.setOutput('changed', 'true');
return;
}
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
const changed = files.some((f) => f.filename.startsWith('superset/migrations/'));
core.setOutput('changed', String(changed));
- name: Setup Python
if: steps.check.outputs.changed == 'true'
uses: ./.github/actions/setup-backend/
with:
requirements-type: base
- name: Assert a single Alembic head
if: steps.check.outputs.changed == 'true'
env:
SUPERSET__SQLALCHEMY_DATABASE_URI: "sqlite:///:memory:"
run: |
heads="$(superset db heads)"
echo "$heads"
head_count=$(printf '%s\n' "$heads" | grep -c .)
if [ "$head_count" -ne 1 ]; then
echo "::error::superset/migrations resolves to $head_count Alembic heads (expected exactly 1)."
echo "Another migration already landed with the same down_revision this branch was cut from."
echo "Add a no-op merge revision joining the heads: https://superset.apache.org/docs/contributing/development#merging-db-migrations"
exit 1
fi
@@ -0,0 +1,135 @@
name: Frontend bundle size (nightly baseline + analyzer)
# Refreshes the bundle-size baseline that superset-frontend.yml's `bundle-size`
# job compares PRs against, and publishes a browsable bundle-analyzer treemap
# report of the same build. Deliberately NOT triggered on every push to
# master: a day-old baseline/report is fine for catching relative
# regressions on PRs and for browsing what's actually in the bundle, and
# building the production bundle on every one of the many pushes master
# gets per day would burn CI time for no benefit a nightly refresh doesn't
# already cover.
on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch: {}
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
env:
TAG: apache/superset:bundle-size-nightly-${{ github.run_id }}
permissions:
contents: read
jobs:
refresh-baseline:
runs-on: ubuntu-26.04
timeout-minutes: 30
env:
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_BUNDLE_ANALYZER_SITE_ID }}
steps:
- name: "Checkout master"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: master
- name: Build Docker Image
run: |
docker buildx build \
-t $TAG \
--cache-from=type=registry,ref=apache/superset-cache:3.11-slim-trixie \
--target superset-node-ci \
.
# Same cache the PR-time bundle-size job restores/writes -- webpack's
# persistent filesystem cache turns a warm production build into ~20s
# instead of several minutes. See superset-frontend.yml for the
# matching restore step and why it's keyed this way.
- name: Restore webpack build cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: superset-frontend/.temp_cache
key: >-
webpack-prod-cache-${{ hashFiles('superset-frontend/package-lock.json',
'superset-frontend/babel.config.js', 'superset-frontend/tsconfig.json',
'superset-frontend/webpack.config.js') }}
# Only ever pull the last recorded data point off the cache, keyed by
# run ID -- `restore-keys` prefix-matches the most recently created
# entry. Absent on the very first run ever; benchmark-action starts a
# fresh history in that case.
- name: Restore bundle size history
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: bundle-size-history.json
key: bundle-size-history-${{ github.run_id }}
restore-keys: |
bundle-size-history-
# BUNDLE_ANALYZER rides along in the same build as BUNDLE_SIZE_STATS --
# they're independent env-gated additions in webpack.config.js (one
# sets `config.stats`, the other pushes plugins), so one production
# build produces both the numeric stats.json and the analyzer's
# report.html. Only report.html is mounted out, not
# BUNDLE_ANALYZER's sibling `statistics.html` sunburst -- that file is
# documented in webpack.config.js as routinely exceeding 100MB for
# this app (it's .gitignore'd for exactly that reason), too large to
# publish as a static site page.
- name: Build production bundle with stats and analyzer report
run: |
mkdir -p ${{ github.workspace }}/superset-frontend/bundle-stats
mkdir -p ${{ github.workspace }}/superset-frontend/.temp_cache
mkdir -p ${{ github.workspace }}/superset/static/assets
docker run \
-v ${{ github.workspace }}/superset-frontend/bundle-stats:/app/superset-frontend/bundle-stats \
-v ${{ github.workspace }}/superset-frontend/.temp_cache:/app/superset-frontend/.temp_cache \
-v ${{ github.workspace }}/superset/static/assets:/app/superset/static/assets \
--rm $TAG \
bash -c \
"npm i && BUNDLE_SIZE_STATS=true BUNDLE_ANALYZER=true npm run build -- --json=bundle-stats/stats.json"
- name: Summarize bundle size
run: |
node superset-frontend/scripts/bundle-size-summary.js \
superset-frontend/bundle-stats/stats.json > bundle-size-summary.json
rm -rf superset-frontend/bundle-stats
# No PR to comment on here, so comment-on-alert is off -- the job
# summary (summary-always) is the only surface for this run.
- name: Update bundle size baseline
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
with:
tool: customSmallerIsBetter
output-file-path: bundle-size-summary.json
external-data-json-path: bundle-size-history.json
fail-on-alert: false
summary-always: true
- name: Save bundle size history
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: bundle-size-history.json
key: bundle-size-history-${{ github.run_id }}
# Publishes the treemap to Netlify (the same host already used for
# superset-storybook.netlify.app and docs previews, reusing the
# existing NETLIFY_AUTH_TOKEN). Skipped until
# NETLIFY_BUNDLE_ANALYZER_SITE_ID exists -- create a new (free)
# Netlify site named superset-bundle-analyzer and add its site ID as
# that secret to turn this on; nothing else in this workflow depends
# on it.
- name: Publish bundle analyzer report to Netlify
if: ${{ env.NETLIFY_SITE_ID != '' }}
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
run: |
mkdir -p netlify-publish
cp superset/static/assets/report.html netlify-publish/index.html
# zizmor: ignore[adhoc-packages] - netlify-cli is a one-shot CI deploy
# tool, not an application dependency; a global/npx install has no
# lockfile context. Version pinned above the floor set by other
# ad-hoc installs in this repo (bump deliberately when upgrading).
npx --yes netlify-cli@27.0.1 deploy --prod --dir=netlify-publish
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: "temurin"
java-version: "11"
@@ -45,5 +45,8 @@ jobs:
- name: Run Script
run: bash .github/workflows/github-action-validator.sh
- name: Test docs-deploy freshness gate
run: bash .github/workflows/scripts/check-docs-deploy-freshness.test.sh
- name: Check for security issues on GHA workflows
uses: zizmorcore/zizmor-action@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1
uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: "temurin"
java-version: "11"
+21 -12
View File
@@ -53,6 +53,15 @@ jobs:
- name: Install helm-docs
run: go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.14.2
# Spike: run the existing .pre-commit-config.yaml through prek (a Rust
# reimplementation of pre-commit) instead of pre-commit itself, to see
# whether it's viable to speed up this job. CI-only — contributors keep
# installing/running `pre-commit` locally exactly as documented; nothing
# here changes that.
- name: Install prek
run: |
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/j178/prek/releases/download/v0.4.11/prek-installer.sh | sh
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
@@ -70,13 +79,13 @@ jobs:
cd docs
yarn install --immutable
- name: Cache pre-commit environments
- name: Cache prek environments
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pre-commit
key: pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
path: ~/.cache/prek
key: prek-v1-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
restore-keys: |
pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-
prek-v1-${{ runner.os }}-py${{ matrix.python-version }}-
- name: Determine changed files
id: changed_files
@@ -142,7 +151,7 @@ jobs:
} >> "$GITHUB_OUTPUT"
fi
- name: pre-commit
- name: pre-commit (via prek)
env:
MODE: ${{ steps.changed_files.outputs.mode }}
CHANGED_FILES: ${{ steps.changed_files.outputs.files }}
@@ -152,22 +161,22 @@ jobs:
case "${MODE}" in
all)
echo "️ Running pre-commit on all files."
pre-commit run --all-files
echo "️ Running prek on all files."
prek run --all-files
;;
files)
echo "️ Running pre-commit on changed files:"
echo "️ Running prek on changed files:"
echo "${CHANGED_FILES}"
# shellcheck disable=SC2086
pre-commit run --files ${CHANGED_FILES}
prek run --files ${CHANGED_FILES}
;;
none)
echo "️ No source files changed; nothing for pre-commit to check."
echo "️ No source files changed; nothing for prek to check."
exit 0
;;
*)
echo "⚠️ Unrecognized changed-files mode '${MODE}'; checking all files."
pre-commit run --all-files
prek run --all-files
;;
esac
PRE_COMMIT_EXIT_CODE=$?
@@ -195,6 +204,6 @@ jobs:
if: always()
runs-on: ubuntu-26.04
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
@@ -108,8 +108,18 @@ jobs:
fetch-depth: 0
persist-credentials: false
# Keep workflow tooling on the triggering revision. Release tags can
# contain action pins that no longer satisfy the repository allowlist.
- name: Checkout workflow actions
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
path: workflow-source
persist-credentials: false
sparse-checkout: .github/actions
- name: Setup Docker Environment
uses: ./.github/actions/setup-docker
uses: ./workflow-source/.github/actions/setup-docker
with:
dockerhub-user: ${{ secrets.DOCKERHUB_USER }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -122,7 +132,7 @@ jobs:
node-version: 20
- name: Setup supersetbot
uses: ./.github/actions/setup-supersetbot/
uses: ./workflow-source/.github/actions/setup-supersetbot/
- name: Rebuild and push
env:
@@ -171,7 +181,7 @@ jobs:
--repo "$REPOSITORY" \
--title "Scheduled Docker image refresh failed for ${LATEST_RELEASE}" \
--label "infra:container" \
--label "bug" \
--label "#bug" \
--body "The weekly Docker base-image refresh failed for release \`${LATEST_RELEASE}\`. Published images may be missing upstream base-layer security patches until this is resolved.
Failed run: ${RUN_URL}"
+49
View File
@@ -0,0 +1,49 @@
#!/bin/bash
#
# 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.
#
# Shared freshness gate used by the Docs Deployment workflow
# (superset-docs-deploy.yml) both up front (check-freshness) and again right
# before the deploy step (recheck-freshness). Writes an output declaring
# whether BUILD_SHA is still master's current tip, so a superseded run can
# skip cleanly instead of racing (and clobbering, or being force-cancelled
# by) a fresher run.
#
# Required env vars:
# BUILD_SHA - the commit SHA this run is building
# REPO - "owner/repo" to query, e.g. github.repository
# OUTPUT_NAME - the GITHUB_OUTPUT key to write, e.g. "is-current"
# GITHUB_OUTPUT - path to append outputs to (set by the Actions runner)
# Optional env vars:
# EVENT_NAME - if "workflow_dispatch", bypasses the check and always
# reports current, since a manual dispatch is a deliberate,
# one-off action rather than something racing other triggers
# GH_TOKEN - passed through to `gh`, needed to call the GitHub API
set -euo pipefail
if [ "${EVENT_NAME:-}" = "workflow_dispatch" ]; then
echo "${OUTPUT_NAME}=true" >>"$GITHUB_OUTPUT"
exit 0
fi
latest_sha="$(gh api "repos/${REPO}/commits/master" --jq .sha)"
if [ "${latest_sha}" = "${BUILD_SHA}" ]; then
echo "${OUTPUT_NAME}=true" >>"$GITHUB_OUTPUT"
else
echo "${OUTPUT_NAME}=false" >>"$GITHUB_OUTPUT"
echo "::notice::master has moved on to ${latest_sha} since ${BUILD_SHA} was triggered — skipping this stale run."
fi
@@ -0,0 +1,100 @@
#!/bin/bash
#
# 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.
#
# Exercises check-docs-deploy-freshness.sh against a stubbed `gh`, covering
# the dispatch-bypass, current-tip and stale-tip branches so the output
# contract (is-current / still-current) can't silently regress. Run
# directly, no extra tooling required:
# bash .github/workflows/scripts/check-docs-deploy-freshness.test.sh
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
script_under_test="${script_dir}/check-docs-deploy-freshness.sh"
failures=0
# Runs the script under test with a stubbed `gh` reporting $1 as master's
# latest sha, asserting that GITHUB_OUTPUT ends up containing exactly $4.
run_case() {
local case_name="$1"
local latest_sha="$2"
local build_sha="$3"
local event_name="$4"
local expected_line="$5"
local workdir
workdir="$(mktemp -d)"
trap 'rm -rf "${workdir}"' RETURN
# Fake `gh` that just echoes back the requested "latest" sha regardless of
# arguments, so the script under test never touches the network.
cat >"${workdir}/gh" <<EOF
#!/bin/bash
echo '${latest_sha}'
EOF
chmod +x "${workdir}/gh"
local output_file="${workdir}/github_output"
: >"${output_file}"
if PATH="${workdir}:${PATH}" \
GITHUB_OUTPUT="${output_file}" \
OUTPUT_NAME="is-current" \
REPO="apache/superset" \
BUILD_SHA="${build_sha}" \
EVENT_NAME="${event_name}" \
GH_TOKEN="fake-token" \
bash "${script_under_test}"; then
:
else
echo "FAIL (${case_name}): script exited non-zero"
failures=$((failures + 1))
return
fi
local actual
actual="$(cat "${output_file}")"
if [ "${actual}" = "${expected_line}" ]; then
echo "PASS (${case_name})"
else
echo "FAIL (${case_name}): expected '${expected_line}', got '${actual}'"
failures=$((failures + 1))
fi
}
# `gh` prints "should-not-be-called" for the dispatch case above the trick:
# it's never actually invoked since the bypass short-circuits before the
# `gh api` call, but the fake still needs a body.
run_case "workflow_dispatch bypasses the check" \
"unused" "abc123" "workflow_dispatch" \
"is-current=true"
run_case "build sha matches master's tip" \
"abc123" "abc123" "push" \
"is-current=true"
run_case "build sha is stale" \
"def456" "abc123" "push" \
"is-current=false"
if [ "${failures}" -gt 0 ]; then
echo "${failures} case(s) failed"
exit 1
fi
echo "All cases passed"
+1 -1
View File
@@ -41,7 +41,7 @@ jobs:
ports:
- 16379:6379
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
+77 -14
View File
@@ -18,16 +18,6 @@ on:
workflow_dispatch: {}
# Serialize deploys: the action pushes to apache/superset-site without
# rebasing, so concurrent runs race on the final push and the loser fails
# with `! [rejected] asf-site -> asf-site (fetch first)`. Cancel any
# in-progress run as soon as a newer one starts — the destination repo
# isn't touched until the final push step, so canceling mid-build is safe,
# and the freshest content always wins.
concurrency:
group: docs-deploy-asf-site
cancel-in-progress: true
permissions:
contents: read
actions: read
@@ -48,19 +38,71 @@ jobs:
env:
SUPERSET_SITE_BUILD: ${{ (secrets.SUPERSET_SITE_BUILD != '' && secrets.SUPERSET_SITE_BUILD != '') || '' }}
# Master gets frequent, sometimes bursty pushes, and each one can trigger a
# deploy attempt. Rather than let every superseded attempt get force-killed
# by the build-deploy concurrency group below (which shows up as a
# `cancelled` — i.e. red/failing-looking — check on that commit), have each
# run check up front whether it's still building master's current tip and,
# if not, skip cleanly. Deliberately outside the docs-deploy-asf-site
# concurrency group so it runs immediately for every trigger without
# blocking or being blocked by anything.
check-freshness:
runs-on: ubuntu-26.04
outputs:
is-current: ${{ steps.check.outputs.is-current }}
steps:
# Sparse checkout: this job's only job is to be fast, so it fetches
# nothing but the freshness-check script itself.
- name: Checkout freshness-check script
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
.github/workflows/scripts
sparse-checkout-cone-mode: false
- name: "Check whether this is still master's current commit"
id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
EVENT_NAME: ${{ github.event_name }}
REPO: ${{ github.repository }}
OUTPUT_NAME: is-current
run: .github/workflows/scripts/check-docs-deploy-freshness.sh
build-deploy:
needs: config
needs: [config, check-freshness]
# Only the run for master's current tip proceeds; anything superseded
# already skipped at check-freshness above instead of landing here.
# For workflow_run triggers, only deploy when the triggering run originated
# from this repository (not a fork), ensuring the checked-out code and any
# local actions executed with deploy credentials are trusted.
if: >-
needs.config.outputs.has-secrets &&
needs.check-freshness.outputs.is-current == 'true' &&
(github.event_name != 'workflow_run' ||
github.event.workflow_run.head_repository.full_name == github.repository)
name: Build & Deploy
runs-on: ubuntu-26.04
# Serialize deploys: the action pushes to apache/superset-site without
# rebasing, so concurrent runs race on the final push and the loser fails
# with `! [rejected] asf-site -> asf-site (fetch first)`. Queue instead of
# canceling: a run that already passed check-freshness can still be
# sitting in the queue for a runner when a newer run starts and finishes
# first. cancel-in-progress would let that stale, queued run kill the
# newer run's in-progress deploy the moment it's finally scheduled, and
# then skip itself at the re-check below — losing the deploy entirely.
# Queuing means the stale run just waits its turn and then no-ops at the
# re-check, so the fresher content that already deployed is never
# clobbered or lost. The check-freshness gate above means it should be
# rare for more than one run to reach this point, so the queue stays
# short in practice.
concurrency:
group: docs-deploy-asf-site
cancel-in-progress: false
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
@@ -76,12 +118,16 @@ jobs:
node-version-file: "./docs/.nvmrc"
- name: Setup Python
uses: ./.github/actions/setup-backend/
- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: "zulu"
java-version: "21"
- name: Install Graphviz
run: sudo apt-get install -y graphviz
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
with:
packages: graphviz
version: 1.0
execute_install_scripts: true
- name: Compute Entity Relationship diagram (ERD)
env:
SUPERSET_SECRET_KEY: not-a-secret
@@ -126,7 +172,24 @@ jobs:
working-directory: docs
run: |
yarn build
# The check-freshness job above narrows the window but doesn't close it: an
# older run can observe is-current=true, then sit through this build while a
# newer run's own freshness check also passes and it deploys and finishes
# first. If this (stale) run then wins entry into the concurrency group, it
# would overwrite the newer content that already deployed. Re-check right
# before the one step that actually mutates superset-site, so a stale run
# skips deploying instead of clobbering a fresher one that already ran.
- name: "Re-check freshness immediately before deploying"
id: recheck-freshness
if: github.event_name != 'workflow_dispatch'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
REPO: ${{ github.repository }}
OUTPUT_NAME: still-current
run: .github/workflows/scripts/check-docs-deploy-freshness.sh
- name: deploy docs
if: github.event_name == 'workflow_dispatch' || steps.recheck-freshness.outputs.still-current == 'true'
uses: ./.github/actions/github-action-push-to-another-repository
env:
API_TOKEN_GITHUB: ${{ secrets.SUPERSET_SITE_BUILD }}
+1 -1
View File
@@ -355,6 +355,6 @@ jobs:
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
@@ -76,6 +76,6 @@ jobs:
if: always()
runs-on: ubuntu-26.04
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
+98 -6
View File
@@ -182,11 +182,6 @@ jobs:
docker run --rm $TAG bash -c \
"npm ls --all --package-lock-only --depth=0 --json > /dev/null"
- name: Build Plugins Packages
run: |
docker run --rm $TAG bash -c \
"npm run plugins:build"
test-storybook:
needs: frontend-build
if: needs.frontend-build.outputs.should-run == 'true'
@@ -214,6 +209,103 @@ jobs:
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
# Compares a PR's own bundle size against the last nightly-recorded
# baseline (see frontend-bundle-size-nightly.yml, which owns actually
# persisting new baselines). PR-only: a push to master doesn't need this
# check re-run against itself, and re-persisting the baseline on every
# push to master -- which happens many times a day -- would burn a full
# production build for no benefit nightly refresh doesn't already cover.
bundle-size:
needs: frontend-build
if: needs.frontend-build.outputs.should-run == 'true' && github.event_name == 'pull_request'
runs-on: ubuntu-26.04
timeout-minutes: 15
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout Code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Download Docker Image Artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: docker-image
- name: Load Docker Image
run: |
zstd -d < docker-image.tar.zst | docker load
# webpack's persistent filesystem cache (superset-frontend/webpack.config.js)
# turns a warm production build into ~20s instead of several minutes,
# but GH-hosted runners are fresh VMs with nothing carried over between
# jobs -- without restoring it explicitly, every single PR would pay
# the full cold-build cost. Keyed on the same files webpack's own
# `buildDependencies` invalidates on, so a stale cache is never used.
- name: Restore webpack build cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: superset-frontend/.temp_cache
key: >-
webpack-prod-cache-${{ hashFiles('superset-frontend/package-lock.json',
'superset-frontend/babel.config.js', 'superset-frontend/tsconfig.json',
'superset-frontend/webpack.config.js') }}
# Only ever pull the last recorded data point off the cache, keyed by
# run ID -- `restore-keys` prefix-matches the most recently created
# entry, which is always the latest nightly run. Absent before the
# first nightly run ever happens; benchmark-action starts a fresh
# history in that case.
- name: Restore bundle size history
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: bundle-size-history.json
key: bundle-size-history-${{ github.run_id }}
restore-keys: |
bundle-size-history-
- name: Build production bundle with stats
run: |
mkdir -p ${{ github.workspace }}/superset-frontend/bundle-stats
mkdir -p ${{ github.workspace }}/superset-frontend/.temp_cache
docker run \
-v ${{ github.workspace }}/superset-frontend/bundle-stats:/app/superset-frontend/bundle-stats \
-v ${{ github.workspace }}/superset-frontend/.temp_cache:/app/superset-frontend/.temp_cache \
--rm $TAG \
bash -c \
"npm i && BUNDLE_SIZE_STATS=true npm run build -- --json=bundle-stats/stats.json"
- name: Summarize bundle size
run: |
node superset-frontend/scripts/bundle-size-summary.js \
superset-frontend/bundle-stats/stats.json > bundle-size-summary.json
rm -rf superset-frontend/bundle-stats
# Comparison + alert only -- this job never persists. See
# frontend-bundle-size-nightly.yml for why.
#
# comment-on-alert is gated to same-repo PRs: on a fork PR,
# GITHUB_TOKEN is forced read-only regardless of the `permissions`
# block above, so once the alert threshold is crossed the action's
# `pulls.createReview` call 403s. That error isn't gated by
# fail-on-alert (which only governs the deliberate alert-threshold
# failure) -- it propagates and fails the job outright. Fork PRs
# still get the comparison via the job summary (summary-always).
- name: Compare bundle size against nightly baseline
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
with:
tool: customSmallerIsBetter
output-file-path: bundle-size-summary.json
external-data-json-path: bundle-size-history.json
github-token: ${{ secrets.GITHUB_TOKEN }}
comment-on-alert: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
alert-threshold: "110%"
fail-on-alert: false
summary-always: true
@@ -22,7 +22,7 @@ jobs:
lint-test:
runs-on: ubuntu-slim
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
+12 -1
View File
@@ -155,6 +155,17 @@ jobs:
INCLUDE_EMBEDDED: "true"
with:
run: playwright-run "${{ matrix.app_root }}" embedded
- name: Run Playwright (Mobile Tests)
uses: ./.github/actions/cached-dependencies
env:
NODE_OPTIONS: "--max-old-space-size=4096"
# Scoped to this step for the same reason as the embedded flags
# above: the mobile consumption mode should not alter Flask's
# configuration for the required desktop test steps.
SUPERSET_FEATURE_MOBILE_CONSUMPTION_MODE: "true"
INCLUDE_MOBILE: "true"
with:
run: playwright-run "${{ matrix.app_root }}" mobile/
- name: Set safe app root
if: failure()
id: set-safe-app-root
@@ -178,6 +189,6 @@ jobs:
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
@@ -263,6 +263,6 @@ jobs:
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
@@ -166,6 +166,6 @@ jobs:
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
@@ -0,0 +1,70 @@
name: Python Unit Test Results
on:
# zizmor: ignore[dangerous-triggers] - runs in base-branch context and only consumes artifacts uploaded by Python-Unit; never checks out PR code (see note below)
workflow_run:
workflows: ["Python-Unit"]
types: [completed]
# This workflow publishes a check run annotating failing Python unit tests
# inline on the PR diff, using JUnit XML uploaded by the Python-Unit workflow.
# It uses the workflow_run trigger so that it always runs in the base-branch
# context and can safely be granted write permissions, even for PRs from
# forks or Dependabot.
#
# IMPORTANT: This workflow must NEVER check out code from the PR branch. All
# data comes from artifacts uploaded by the Python-Unit workflow.
permissions:
contents: read
checks: write
issues: read
actions: read
jobs:
report:
runs-on: ubuntu-26.04
timeout-minutes: 10
if: >
github.event.workflow_run.conclusion == 'success' ||
github.event.workflow_run.conclusion == 'failure'
steps:
# Fails soft (continue-on-error) because the source unit-tests job is
# itself gated on change detection: a docs-only PR skips it entirely,
# so there is nothing to download or report on.
- name: Download JUnit results
id: download
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
# merge-multiple is intentionally omitted: each matrix leg's
# artifact (junit-results-current, junit-results-next) uses the
# same XML filenames, so merging them into one directory would let
# one Python version's results overwrite the other's. Downloading
# into per-artifact subdirectories keeps both, and the glob below
# is recursive so it still picks up every XML file.
pattern: "junit-results-*"
path: artifacts
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Download event file
id: download-event
if: steps.download.outcome == 'success'
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: "Event File"
path: event
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Publish test results
if: steps.download.outcome == 'success' && steps.download-event.outcome == 'success'
uses: EnricoMi/publish-unit-test-result-action@d0a4676d0e0b938bc201470d88276b7c74c712b3 # v2.24.0
with:
commit: ${{ github.event.workflow_run.head_sha }}
event_file: event/event.json
event_name: ${{ github.event.workflow_run.event }}
files: "artifacts/**/*.xml"
check_name: "Python Unit Test Results"
comment_mode: "off"
+31 -4
View File
@@ -74,14 +74,14 @@ jobs:
SUPERSET_TESTENV: true
SUPERSET_SECRET_KEY: not-a-secret
run: |
pytest --durations-min=0.5 --cov-report= --cov=superset ./tests/common ./tests/unit_tests --cache-clear --maxfail=50
pytest --durations-min=0.5 --cov-report= --cov=superset ./tests/common ./tests/unit_tests --cache-clear --maxfail=50 --junit-xml=test-results/junit-unit.xml
- name: Python 100% coverage unit tests
env:
SUPERSET_TESTENV: true
SUPERSET_SECRET_KEY: not-a-secret
run: |
pytest --durations-min=0.5 --cov=superset/sql/ ./tests/unit_tests/sql/ --cache-clear --cov-fail-under=100
pytest --durations-min=0.5 --cov=superset/semantic_layers/ ./tests/unit_tests/semantic_layers/ --cache-clear --cov-fail-under=100
pytest --durations-min=0.5 --cov=superset/sql/ ./tests/unit_tests/sql/ --cache-clear --cov-fail-under=100 --junit-xml=test-results/junit-sql-coverage.xml
pytest --durations-min=0.5 --cov=superset/semantic_layers/ ./tests/unit_tests/semantic_layers/ --cache-clear --cov-fail-under=100 --junit-xml=test-results/junit-semantic-layers-coverage.xml
- name: Upload code coverage
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
@@ -89,6 +89,33 @@ jobs:
verbose: true
use_oidc: true
slug: apache/superset
# Uploaded even when a pytest step above fails, since that is exactly
# when the JUnit results are needed downstream, to annotate the PR with
# the failing tests. Consumed by the "Python Unit Test Results" workflow
# via workflow_run (see that workflow for why it can't just be a step
# here: it needs to run with write permissions, which this PR-triggered
# job can't safely have on a fork PR).
- name: Upload JUnit test results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: junit-results-${{ matrix.python-version }}
path: test-results/
retention-days: 7
# Uploads the raw pull_request event payload so the "Python Unit Test
# Results" workflow (running via workflow_run, in base-branch context) can
# look up which PR/commit to annotate without checking out untrusted code.
event-file:
runs-on: ubuntu-26.04
timeout-minutes: 5
steps:
- name: Upload event file
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: Event File
path: ${{ github.event_path }}
retention-days: 7
# Stable required-status-check anchor. `unit-tests` is a matrix job gated on
# change detection, so on non-Python PRs it is skipped and never produces its
@@ -105,7 +132,7 @@ jobs:
contents: read
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
+5 -2
View File
@@ -78,7 +78,10 @@ jobs:
- name: Install gettext tools
if: steps.check.outputs.python == 'true' || steps.check.outputs.frontend == 'true'
run: sudo apt-get update && sudo apt-get install -y gettext
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
with:
packages: gettext
version: 1.0
# Fetch the base ref so we can compare PR-introduced regressions
# against a fair baseline (also runs babel_update against the base
@@ -161,6 +164,6 @@ jobs:
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
with:
expand-composite-actions: true
+1
View File
@@ -138,6 +138,7 @@ PROJECT.md
.aider*
.claude_rc*
.claude/settings.local.json
.claude/worktrees/
.env.local
oxc-custom-build/
*.code-workspace
+1 -1
View File
@@ -167,7 +167,7 @@ The Developer Portal auto-generates MDX documentation from Storybook stories. **
### Generator Location
- Script: `docs/scripts/generate-superset-components.mjs`
- Wrapper: `docs/src/components/StorybookWrapper.jsx`
- Output: `docs/developer_portal/components/`
- Output: `docs/developer_docs/components/`
## Architecture Patterns
+1 -1
View File
@@ -35,4 +35,4 @@ The Developer Portal includes comprehensive guides for:
- [Code Review Process](https://superset.apache.org/developer_portal/contributing/code-review)
- [Development How-tos](https://superset.apache.org/developer_portal/contributing/howtos)
Source for the Developer Portal documentation is [located here](https://github.com/apache/superset/tree/master/docs/developer_portal).
Source for the Developer Portal documentation is [located here](https://github.com/apache/superset/tree/master/docs/developer_docs).
+2
View File
@@ -31,6 +31,8 @@ under the License.
[![Open PRs](https://img.shields.io/github/issues-pr/apache/superset)](https://github.com/apache/superset/pulls)
[![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://bit.ly/join-superset-slack)
[![Documentation](https://img.shields.io/badge/docs-apache.org-blue.svg)](https://superset.apache.org)
[![Storybook](https://img.shields.io/badge/storybook-live-ff4785.svg)](https://superset-storybook.netlify.app)
[![Bundle Analyzer](https://img.shields.io/badge/bundle%20analyzer-nightly-8dd6f9.svg)](https://superset-bundle-analyzer.netlify.app)
<picture width="500">
<source
+228 -34
View File
@@ -24,6 +24,81 @@ assists people when migrating to a new version.
## Next
### OAuth2 database callback metrics include their outcome
The unqualified `DatabaseRestApi.oauth2` StatsD counter has been replaced with
`DatabaseRestApi.oauth2.success`, `DatabaseRestApi.oauth2.warning`, and
`DatabaseRestApi.oauth2.error`. Update monitoring rules and dashboards that consume
the old counter to use the outcome-specific replacements.
- [42935](https://github.com/apache/superset/pull/42935): The MCP service now refuses to start (`MCPAuthConfigError`) when `MCP_JWT_ISSUER` trusts more than one issuer and no `MCP_USER_RESOLVER` is configured, instead of only logging a warning. This was already a documented misconfiguration (the default resolver isn't issuer-scoped, so distinct trusted issuers minting the same username/email would resolve to the same Superset user); deployments trusting multiple issuers must configure an `MCP_USER_RESOLVER` that derives its identity from the token's `iss` claim before upgrading. Single-issuer deployments are unaffected.
- [42393](https://github.com/apache/superset/pull/42393): Exported dataset YAML now carries a `uuid` for each metric and column so that custom folder assignments (which reference metrics/columns by UUID) survive an import into another workspace. This affects any export bundle that contains datasets, not just a dataset export: chart, dashboard, database and full-asset exports all embed the same dataset YAML, so a dashboard exported from this release also fails to import into an older one even though no dataset was exported directly. As with `folders` and `currency_code_column`, the affected `datasets/` files fail schema validation (`Unknown field: uuid`) when imported into Superset releases that predate this change; regenerate or hand-edit exports for older targets in mixed-version fleets.
- [42300](https://github.com/apache/superset/pull/42300): Timeseries charts (line/area/bar) with a Y-axis bound in effect — either an explicit `yAxisBounds` or one derived from `truncateYAxis` — now clamp out-of-range data points to that bound instead of letting ECharts drop the point (and the line segments around it) entirely. Any existing chart with a configured Y-axis bound and data outside it will look different after upgrading: a gap becomes a point pinned to the boundary. The clamp also rewrites the value ECharts reads for that point's tooltip and data label, so the displayed value is the bound rather than the true observation.
- [42087](https://github.com/apache/superset/pull/42087): Stored calculated-column and metric expressions are validated when a query is built, under the same sub-query policy already applied to adhoc expressions. Previously only the dataset update path checked them on save, so expressions written by v1 import, by dataset duplication, or before that check existed were never validated. Since `ALLOW_ADHOC_SUBQUERY` defaults to `False` (see [19242](https://github.com/apache/superset/pull/19242)), a dataset whose stored expression contains a sub-query works before upgrading and afterwards fails at chart render with `Custom SQL fields cannot contain sub-queries.` There is no migration step, and the error does not name the offending dataset column, so audit stored expressions before upgrading: either rewrite them without the sub-query, or set `ALLOW_ADHOC_SUBQUERY = True` to keep the previous behaviour for both stored and adhoc expressions.
### Selenium support removed — Playwright is now required for screenshots
Selenium support has been removed. **Playwright is now required** for all
report and thumbnail screenshot generation. Install it with:
```bash
pip install playwright && playwright install chromium
```
**Breaking config changes:**
- `PLAYWRIGHT_REPORTS_AND_THUMBNAILS` feature flag removed (Playwright is the only backend now)
- `WEBDRIVER_TYPE` config key removed (Playwright always uses Chromium)
- `WEBDRIVER_CONFIGURATION` config key removed (Selenium-only)
- `SCREENSHOT_PAGE_LOAD_WAIT` config key removed (Selenium-only)
- `SCREENSHOT_SELENIUM_RETRIES` config key removed (Selenium-only)
- `SCREENSHOT_WAIT_FOR_ERROR_MODAL_VISIBLE` config key removed (Selenium-only)
- `SCREENSHOT_WAIT_FOR_ERROR_MODAL_INVISIBLE` config key removed (Selenium-only)
- `MachineAuthProvider.authenticate_webdriver()` removed; use `authenticate_browser_context()` instead
**What operators should do before upgrading:**
1. Install Playwright: `pip install playwright && playwright install chromium`
2. Remove any references to the removed config keys from custom `superset_config.py`
3. If you subclassed `MachineAuthProvider`, remove any `authenticate_webdriver` override and migrate auth logic to `authenticate_browser_context`
### CSV/XLSX report exports of Table charts keep raw numeric values
Table and Pivot Table charts sent as text in a report email now apply the
chart's number and currency formatting so the values match what a user sees in
Explore. As part of this, the CSV and XLSX result formats return early before
formatting: previously the Table post-processor applied `d3NumberFormat` to
every result format, so CSV/XLSX exports contained pre-formatted strings.
CSV/XLSX exports now preserve numeric values and column types, which is better
for downstream analysis but is a visible change for anyone who relied on the
formatted text in those files. The rendered email body (the only place the
formatting is intended for) is unaffected.
### SQLAlchemy bumped to 2.0, flask-sqlalchemy to 3.1.1
Superset's core ORM dependencies move from SQLAlchemy 1.4 to 2.0 and
flask-sqlalchemy `<3.0` to 3.1.1, completing the migration tracked in
[discussion #40273](https://github.com/apache/superset/discussions/40273).
**Custom `db_engine_specs`, plugins, or extensions that import SQLAlchemy
internals directly** should review the
[SQLAlchemy 1.4-to-2.0 migration guide](https://docs.sqlalchemy.org/en/20/changelog/migration_20.html)
for API changes that affect them — most 1.4 code already runs unmodified
under 2.0's compatibility mode, but patterns like `Engine.execute()`,
string-keyed `Row` access, and `MetaData(bind=)` are removed outright.
**Several optional DB-connector extras remain capped below their
SQLAlchemy-2.0-only releases**, either because that bump is a separate
follow-up ([#42891](https://github.com/apache/superset/pull/42891): dremio,
exasol, firebird, redshift, risingwave) or because the upstream dialect
package has no SQLAlchemy 2.0 support yet at all (aurora-data-api, d1,
kusto, solr; ocient's 2.0 compatibility is unverified). Installing one of
these extras continues to pull a SQLAlchemy-1.4-line version of that
dialect; each package's constraint in `pyproject.toml` documents why.
No application-level configuration changes are required for deployments
that don't touch SQLAlchemy directly.
### Soft delete is on by default, and purging is live
`SOFT_DELETE` now ships **on** (`DEFAULT_FEATURE_FLAGS`), so deleting a
@@ -52,6 +127,33 @@ One caveat on turning soft delete back off: objects archived while it was on are
**resurrected** into normal listings, since the rows were never removed — an
emergency stop rather than a clean rollback.
### Version history is on by default
`VERSION_HISTORY` and `ENABLE_VERSIONING_CAPTURE` now both ship **on**. Every
save of a chart, dashboard, or dataset writes version rows, and the version
history panel appears on Explore and Dashboard pages. The two flip together
deliberately: a panel with capture off renders an empty "No history yet" that
misrepresents the entity as unchanged.
**What operators should expect:**
- **Storage growth.** Capture writes shadow rows per save, so the metadata
database grows with edit volume. The `version_history.prune_old_versions`
beat task removes rows whose transaction is older than
`SUPERSET_VERSION_HISTORY_RETENTION_DAYS` (default 30). A deployment that
replaces `CELERY_CONFIG` rather than inheriting it must carry both the
`superset.tasks.version_history_retention` import and the beat entry; a
startup warning names whichever is absent.
- **`PUT` responses change shape.** Entity updates now return populated
`old_version_uuid` / `new_version_uuid` fields and an `ETag` header, which
were null or absent while capture was off.
`ENABLE_VERSIONING_CAPTURE` is **retained permanently** as an operational
kill-switch — not removed with the rollout toggles. Setting it to a falsy value
stops capture within a restart, without a revert-and-redeploy. Unlike the
soft-delete toggle, turning it off is a clean stop: existing version rows remain
readable and no entity state is altered.
### Scheduled report execution now enforces one application deadline
Scheduled report (not alert) executions are now governed by a single
@@ -80,6 +182,58 @@ Behavior changes to be aware of:
if charts never mount. Thumbnails and non-report screenshots keep their
previous behavior.
### Embedded (guest token) API responses no longer echo database errors
API responses served to a guest-token principal now carry a generic
`An error occurred while fetching the data.` in place of the underlying error
(`You don't have permission to access this resource.` on a 401/403), and drop
the `stacktrace` and error `extra` payloads. Engine errors routinely quote
catalog, schema, table and column names of the warehouse, which embedded
viewers should not see. Errors Superset authors itself — access denials, OAuth2
redirects, timeouts, payload validation — keep their message and type, though
their `extra` is still reduced to the fields the client needs. Responses to
every non-guest principal are unchanged, and the full error is still logged
server-side.
### `UnsavedChangesModal` no longer accepts a `zIndex` prop
`@superset-ui/core`'s `UnsavedChangesModal` dropped its `zIndex` prop (and the
hardcoded default it fed) in favor of letting Ant Design's own stacking
handle placement. Callers passing `zIndex` to override the modal's layering
will now get a TypeScript error and must remove the prop; keeping a manual
override was exactly the footgun this change removes (see #42510). No
callers in the Superset frontend codebase itself passed this prop.
### Row-level security now filters table reads a same-named CTE used to hide
`extract_tables_from_statement()` decided whether a reference was a CTE by matching its
bare name against the enclosing scope's CTE names; it now resolves the name through
`Scope.cte_sources`. Three kinds of real table read whose bare name collided with a CTE's
were mistaken for the CTE and dropped from a statement's tables, so they were neither
RLS-filtered nor access-checked: a schema- or catalog-qualified reference, a non-recursive
CTE's own name inside its body, and a forward reference to a later `WITH` item.
```sql
WITH orders AS (SELECT 1 AS d) SELECT * FROM (SELECT * FROM public.orders) AS z
WITH orders AS (SELECT * FROM orders) SELECT * FROM orders
WITH q1 AS (SELECT key FROM q2), q2 AS (SELECT 1 AS key) SELECT * FROM q1
```
Each read is now reported, so it is filtered when `RLS_IN_SQLLAB` is enabled, matched
against `DISALLOWED_SQL_TABLES`, and requires dataset access under
`raise_for_access(force_dataset_match=True)`. A query that previously ran, reading those
rows unfiltered, may now be filtered or rejected. There is no opt-out — the previous
behavior was a row-level-security bypass.
### Table aliases keep their quoting through the row-level security rewrite
Both RLS transformers took the table alias as a string with its quoting stripped and
emitted it verbatim; they now carry the parsed identifier. Emitted SQL is unchanged for an
unquoted identifier; a quoted one keeps its quoting, and a column-alias list
(`FROM t AS x (c1, c2)`) survives the rewrite instead of being dropped. This repairs
row-level security for any aliased table on Snowflake, and for at least one statement shape
on MSSQL where the rewrite previously raised `AttributeError`.
### Principal listing APIs now honour related-field filters
Two authorization-related listing behaviors changed for API clients. Neither
@@ -128,12 +282,24 @@ A new dashboard action exports every chart's data to a single multi-sheet
requires a running Celery worker and a configured SMTP transport, since the task
emails the requesting user a pre-signed download link. New config keys:
`EXCEL_EXPORT_S3_BUCKET`, `EXCEL_EXPORT_S3_KEY_PREFIX`,
`EXCEL_EXPORT_LINK_TTL_SECONDS`, `EXCEL_EXPORT_S3_CLIENT_KWARGS`, and
`EXCEL_EXPORT_TABLE_VIZ_TYPES`.
`EXCEL_EXPORT_LINK_TTL_SECONDS`, `EXCEL_EXPORT_S3_CLIENT_KWARGS`,
`EXCEL_EXPORT_TABLE_VIZ_TYPES`, and `EXCEL_EXPORT_QUERY_CONTEXT_BUILDER`.
The feature depends on `boto3`, which is **not** installed by default; install it
with `pip install apache-superset[excel-export]`.
Charts store their `query_context` only once they have been (re-)saved in
Explore, so older charts may have none. For a fixed, conservative set of viz
types (`table`, `big_number_total`, `big_number`, `pie`) the export rebuilds a
query context from the chart's saved form data so those charts still export.
The rebuild is a single-query mapping and does **not** reproduce plugin
post-processing (pivot, rolling, forecast) or multi-query charts, so any chart of
another type without a saved query context is skipped and listed in the email for
the user to re-save. To cover those types, set `EXCEL_EXPORT_QUERY_CONTEXT_BUILDER`
to a callable that receives the chart's form data and returns a query-context
payload (or `None` to fall back to the built-in rebuild) — for example one backed
by a service that runs the chart's real frontend `buildQuery`.
A second mode, **Export Images to Excel**, embeds non-table charts as rendered
images (which viz types stay tabular is controlled by
`EXCEL_EXPORT_TABLE_VIZ_TYPES`). It renders through the headless webdriver, so the
@@ -289,6 +455,10 @@ are the intended model going forward; deprecating and removing implicit viewersh
in a later major version.
- [41044](https://github.com/apache/superset/issues/41044): Removes the deprecated `AVOID_COLORS_COLLISION` feature flag (it defaulted to `True`). Color-collision avoidance is now permanently enabled; any config override setting it to `False` is ignored.
- [41714](https://github.com/apache/superset/pull/41714): **Breaking — the legacy `explore_json` chart-data pipeline is removed** at its long-declared `5.0.0` EOL. The `/superset/explore_json/` and `/superset/explore_json/data/<cache_key>` endpoints, `superset/viz.py`, the `Slice.viz` property, the `get_viz` factory, the `load_explore_json_into_cache` celery task and the `viz=` overload of `security_manager.raise_for_access` are gone. Anything importing `superset.viz` must migrate to the QueryContext / `pandas_postprocessing` pipeline behind `/api/v1/chart/data`. All 15 remaining legacy charts were migrated first: most keep their `viz_type` and renderer (no action needed for saved charts), while saved nvd3 Bubble charts are auto-migrated to the ECharts Bubble Chart (`bubble_v2`) and saved "Time-series Percent Change" (`compare`) charts to the ECharts Line Chart, which restores the nvd3 renderer's interactive percent re-basing via a draggable baseline. The deck.gl Multiple Layers chart now fetches its layers entirely client-side, refitting the viewport as each layer's data arrives, and caps the number of sub-slices fanned out per chart at `DECK_MULTI_MAX_SLICES` (default 50, configurable); dashboard filter badges no longer aggregate child-layer filter metadata.
- [41714](https://github.com/apache/superset/pull/41714): Charts migrated in place keep a `NULL` saved query context until they are next opened in Explore (which regenerates it automatically) or re-saved. Until then, cache warm-up and annotation layers referencing such a chart report an actionable error rather than warming/rendering; opening the chart once resolves it.
- [41714](https://github.com/apache/superset/pull/41714): **Breaking for third-party viz plugins** — the `useLegacyApi` field of `ChartMetadata` in `@superset-ui/core` is removed. Plugins that set it must provide a `buildQuery` and consume `/api/v1/chart/data`. The migrated first-party packages also drop their `legacy-` prefix: `@superset-ui/legacy-plugin-chart-{calendar,chord,country-map,horizon,paired-t-test,parallel-coordinates,partition,rose,world-map}``@superset-ui/plugin-chart-*`, and `@superset-ui/legacy-preset-chart-nvd3``@superset-ui/preset-chart-nvd3`. The `can_explore_json` permission is no longer created or granted; custom roles referencing it should switch to the `can_read` permissions on `Chart`.
- [41813](https://github.com/apache/superset/pull/41813): `redis` (the Python client, `redis-py`) is bumped from 5.3.1 to 8.0.1. redis-py 8 changes several connection defaults; Superset's own Redis-backed features (`GLOBAL_ASYNC_QUERIES_CACHE_BACKEND`, `DISTRIBUTED_COORDINATION_CONFIG`, and the MCP Redis store) explicitly pin the pre-upgrade behavior so this bump is a no-op for them: the wire protocol stays RESP2 (not the new RESP3 default, which requires Redis/Sentinel 6+ to speak `HELLO`) and there is still no socket timeout by default (redis-py 8 defaults to 5s, which could otherwise newly time out large cached payloads or slow networks). The no-timeout default can now be overridden via two new config keys, `CACHE_REDIS_SOCKET_TIMEOUT` / `CACHE_REDIS_SOCKET_CONNECT_TIMEOUT`, on any `CacheConfig` dict using `CACHE_TYPE: RedisCache` or `RedisSentinelCache`. Separately, redis-py 6+ changed the default for `ssl_check_hostname` from `False` to `True` for SSL connections using `ssl_cert_reqs="required"` (the default) — this is a security improvement, so it has **not** been reverted; deployments with `CACHE_REDIS_SSL=True` whose certificates lack a hostname matching the connection address should set `CACHE_REDIS_SSL_CERT_REQS="none"` (disables cert verification entirely, matching hostname-check bypass) or replace the certificate. General-purpose cache/results backends configured via `CACHE_CONFIG` / `DATA_CACHE_CONFIG` / `RESULTS_BACKEND` with `CACHE_TYPE: RedisCache` go through `flask-caching`'s own Redis backend (outside Superset's code) and are subject to the same new defaults; pass `socket_timeout` / `protocol` via `CACHE_OPTIONS` there if needed. Celery broker and result-backend connections (built by `kombu`, also outside Superset's code) keep their no-socket-timeout behavior (`kombu` passes `socket_timeout=None` explicitly) but do **not** pin the wire protocol, so they follow redis-py's RESP3 default — which requires a Redis server new enough to speak `HELLO` (Redis 6+). Deployments using a pre-6.0 Redis server (EOL) as a Celery broker should upgrade the server before taking this bump.
@@ -308,7 +478,7 @@ in a later major version.
- **`SqlaTable.sql_url` query-string format.** `SqlaTable.sql_url` now URL-encodes `table_name` and joins it as a query parameter rather than concatenating a second `?`. Previously, with `Database.sql_url` returning `/sqllab/?dbid=<id>`, the concatenation produced `/sqllab/?dbid=<id>?table_name=<raw>` — a malformed second `?` that broke the query parser. External code that parsed the legacy `<base>?table_name=<raw>` shape now sees properly percent-encoded values (e.g. `/``%2F`, ` ``+` or `%20`); decode with `urllib.parse.parse_qsl`.
- **New config flag `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE` (default `False`).** Share/permalink URLs now substitute `window.location.origin` for the backend-supplied origin so a proxied or subdirectory-deployed Superset never hands the user an unreachable internal hostname. Operators whose reverse proxy correctly forwards `X-Forwarded-Host` *and* who want permalinks to carry the backend's literal origin can opt out by setting `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE = True` in `superset_config.py`. Default `False` (rewrite is on); flipping the default would regress the dominant proxied/subdir deployment to an unreachable host.
- **New config flag `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE` (default `False`).** Share/permalink URLs now substitute `window.location.origin` for the backend-supplied origin so a proxied or subdirectory-deployed Superset never hands the user an unreachable internal hostname. Operators whose reverse proxy correctly forwards `X-Forwarded-Host` _and_ who want permalinks to carry the backend's literal origin can opt out by setting `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE = True` in `superset_config.py`. Default `False` (rewrite is on); flipping the default would regress the dominant proxied/subdir deployment to an unreachable host.
- [41651](https://github.com/apache/superset/pull/41651): **New do-not-translate standard for translation catalogs.** Strings that must stay identical to the source — icon names (e.g. `bolt`), enum/option values (`step-after`), SQL keywords, API field names (`error_message`), code constants, and example placeholders — are now marked with a `#. do-not-translate` extracted comment. The list lives in the `superset/translations/do-not-translate.txt` registry; `scripts/translations/apply_do_not_translate.py` stamps the marker onto `messages.pot` during `babel_update.sh`, and `pybabel update` propagates it to every `.po`, so the status is consistent across all languages. The AI backfill (`backfill_po.py`) and translators leave these entries untranslated (source fallback). The legacy per-catalog convention (a `# Не переводить` translator comment in the `ru` catalog) is still honored for back-compat but is superseded by this standard; contributors adding new machine-read strings should add the msgid to the registry rather than annotating individual catalogs.
@@ -345,7 +515,7 @@ Theme tokens are unaffected — antd 6 removed none of the tokens Superset expos
### Guest-token RLS rules reject unknown fields
The `rls` rules passed to `POST /api/v1/security/guest_token/` are now validated strictly: a rule may only contain `dataset` and `clause`. Previously unknown fields were silently dropped, so a mistyped or legacy scope key (most commonly `datasource` instead of `dataset`) produced a rule with no `dataset`, which is treated as a *global* rule applied to every dataset the embedded resource can reach. Such a request now returns HTTP 400 identifying the offending field instead of issuing a token with an unintended global rule. Integrators that were sending extra fields in RLS rules must remove them; valid dataset-scoped (`{"dataset": 41, "clause": "..."}`) and global (`{"clause": "..."}`) rules are unaffected.
The `rls` rules passed to `POST /api/v1/security/guest_token/` are now validated strictly: a rule may only contain `dataset` and `clause`. Previously unknown fields were silently dropped, so a mistyped or legacy scope key (most commonly `datasource` instead of `dataset`) produced a rule with no `dataset`, which is treated as a _global_ rule applied to every dataset the embedded resource can reach. Such a request now returns HTTP 400 identifying the offending field instead of issuing a token with an unintended global rule. Integrators that were sending extra fields in RLS rules must remove them; valid dataset-scoped (`{"dataset": 41, "clause": "..."}`) and global (`{"clause": "..."}`) rules are unaffected.
### MCP service requires `MCP_JWT_AUDIENCE` when JWT auth is enabled
@@ -443,6 +613,7 @@ ALTER TABLE tagged_object DROP CONSTRAINT <constraint_name>;
-- MySQL: find names via `SHOW CREATE TABLE tagged_object;`
ALTER TABLE tagged_object DROP FOREIGN KEY <constraint_name>;
```
### Entity version-history infrastructure (gated off by default)
Introduces the schema and SQLAlchemy-Continuum wiring that captures version history for charts, dashboards, and datasets, plus read-only `GET /api/v1/{chart,dashboard,dataset}/<uuid>/versions/` endpoints. This ships **inert**: a new config flag `ENABLE_VERSIONING_CAPTURE` defaults to `False`, so no save writes any version rows and the endpoints return empty. It is an operational kill-switch (a release toggle that becomes a permanent ops switch), not a feature flag — set it to `True` to enable capture once validated. The migration is additive; existing entity `PUT` responses gain `old_version_uuid` / `new_version_uuid` body fields and an `ETag` header (both null/absent when capture is off).
@@ -459,12 +630,12 @@ These are behavior changes that take effect on upgrade regardless of `ENABLE_VER
A read-only companion to the version-history endpoints: each entity type gains a `GET /api/v1/{chart,dashboard,dataset}/<uuid>/activity/` endpoint returning a chronological, access-filtered stream of edits — the entity's own edits plus, for charts and dashboards, transitive edits to related entities during their association windows. Datasets have no related layer in V2, so `include=related` returns an empty stream for a dataset and `include=all` reduces to the dataset's own edits.
| Param | Type | Default | Purpose |
|---|---|---|---|
| `since` / `until` | ISO 8601 | — | Bound `issued_at` |
| `include` | `self` \| `related` \| `all` | `all` | Own edits, related edits, or both |
| `q` | string | — | Case-insensitive search over the full history, applied before pagination (so `count` reflects matches) |
| `page` / `page_size` | integer | `0` / `25` | Pagination (`page_size` clamped to 200) |
| Param | Type | Default | Purpose |
| -------------------- | ---------------------------- | ---------- | ------------------------------------------------------------------------------------------------------ |
| `since` / `until` | ISO 8601 | — | Bound `issued_at` |
| `include` | `self` \| `related` \| `all` | `all` | Own edits, related edits, or both |
| `q` | string | — | Case-insensitive search over the full history, applied before pagination (so `count` reflects matches) |
| `page` / `page_size` | integer | `0` / `25` | Pagination (`page_size` clamped to 200) |
Authorization reuses the resource's `can_read` permission and per-object `raise_for_access`; related-entity rows are visibility-filtered to what the caller may see. The stream is empty unless version capture is on (`ENABLE_VERSIONING_CAPTURE`).
@@ -476,7 +647,7 @@ Entity version history (the `version_transaction` / `*_version` shadow tables th
|---|---|---|
| `SUPERSET_VERSION_HISTORY_RETENTION_DAYS` | `30` | Version rows whose owning `version_transaction.issued_at` is older than this many days are pruned. Each entity's live row (`end_transaction_id IS NULL`) is always preserved, as are the live rows of its children and associations; closed historical rows (including the baseline) age out. Set to `0` or a negative value to disable pruning. |
The task ships in the default `CeleryConfig` (both the `superset.tasks.version_history_retention` import and the beat entry). A deployment that overrides `CELERY_CONFIG` without the beat entry logs a startup warning. When the override explicitly defines `imports`, a missing retention module is also reported; an absent `imports` setting is not diagnosed because Celery may register tasks through `include`, autodiscovery, or worker startup imports. Retention only prunes whatever history exists — capture itself is gated separately by `ENABLE_VERSIONING_CAPTURE` (ships off).
The task ships in the default `CeleryConfig` (both the `superset.tasks.version_history_retention` import and the beat entry). A deployment that overrides `CELERY_CONFIG` without the beat entry logs a startup warning. When the override explicitly defines `imports`, a missing retention module is also reported; an absent `imports` setting is not diagnosed because Celery may register tasks through `include`, autodiscovery, or worker startup imports. Retention only prunes whatever history exists — capture itself is gated separately by `ENABLE_VERSIONING_CAPTURE`, which now ships on.
### Deletion retention (soft-deleted entities are eventually purged)
@@ -486,7 +657,7 @@ Purging is **live by default** (`SOFT_DELETE_PURGE_DRY_RUN=False`), so the reten
Deployments that replace the default `CELERY_CONFIG` must ensure workers register `superset.tasks.deletion_retention` and schedule the `deletion_retention.purge_soft_deleted` task themselves. The shipped Docker development config uses `imports` and includes both entries. While `SOFT_DELETE` is statically enabled, a missing beat entry logs a startup warning; when the override explicitly defines `imports`, a missing purge module is also reported.
Operators can immediately erase a specific entity for compliance (GDPR) via `superset deletion-retention force-purge --uuid <uuid>`; this applies legacy hard-delete semantics — a live chart referencing a force-purged dataset is left without a datasource until re-pointed (the chart is not modified), and it purges the named entity even when it was never soft-deleted. Every purge writes an immutable, content-free audit record to the new `purge_audit_log` table that survives the entity it names: the **scheduled** purge fails closed (an entity whose audit row cannot be written is skipped and retried next run), while **force-purge** proceeds even if the audit write fails — the operator is present and deletion outranks audit for a compliance erasure.
Operators can immediately erase a specific entity for compliance (GDPR) via `superset deletion-retention force-purge --uuid <uuid>`; this applies legacy hard-delete semantics — a live chart referencing a force-purged dataset is left without a datasource until re-pointed (the chart is not modified), and it purges the named entity even when it was never soft-deleted. Every scheduled evaluation writes a provisional, content-free record to the new `purge_audit_log` table before the cascade starts. Meaningful retained outcomes survive the entity they name. Consecutive scheduled evaluations with the same blocked outcome suppress only the redundant current provisional record; completed outcomes, outcome transitions, and every force-purge attempt remain independent and immutable. The **scheduled** purge fails closed when its provisional record cannot be written, while **force-purge** proceeds even if the audit write fails — the operator is present and deletion outranks audit for a compliance erasure. Operators can monitor `deletion_retention.blocked_audit_suppressed` and `deletion_retention.blocked_audit_dedupe_fallback` to verify suppression and fail-safe fallback behavior without changing the existing blocked-workload gauge.
### Recently Archived view and permanent delete (purge) endpoints
@@ -548,6 +719,7 @@ Operators can tune or disable the policy via config:
### Data uploads bounded by UPLOAD_MAX_FILE_SIZE_BYTES
Single data-file uploads (CSV, Excel, columnar) are now bounded by the `UPLOAD_MAX_FILE_SIZE_BYTES` config option, which defaults to `100 * 1024 * 1024` (100 MB). Files larger than this are rejected with a `413` before their contents are buffered into memory. Set `UPLOAD_MAX_FILE_SIZE_BYTES = None` to disable the check and restore unbounded uploads.
### Currency symbol position follows the locale when unset
When a chart's currency control leaves the **Prefix or suffix** field empty, the currency symbol position is now derived from the deployment locale's own convention via `Intl.NumberFormat` instead of always defaulting to a suffix. For example, under the default `en-US` locale `USD`, `GBP`, and `EUR` render as a prefix (`$ 1,000`), while eurozone locales such as `fr-FR` render `EUR` as a suffix (`1 000 €`). An explicit Prefix/Suffix selection is always honored and is unaffected.
@@ -673,7 +845,7 @@ SQLALCHEMY_ENCRYPTED_FIELD_ENGINE = "aes"
```bash
superset re-encrypt-secrets --engine aes-gcm
```
A live instance keeps writing *new* secrets as AES-CBC during the window between step 2 and the restart in step 4; this second pass sweeps those up (it is idempotent, so already-migrated values are skipped).
A live instance keeps writing _new_ secrets as AES-CBC during the window between step 2 and the restart in step 4; this second pass sweeps those up (it is idempotent, so already-migrated values are skipped).
Schedule the cutover in a quiet window. Runtime reads use only the single configured engine, so in a multi-worker deployment there is an unavoidable brief decrypt-outage between the migration commit and the last worker restarting with the new config — each migrator run is transactional, but the fleet-wide cutover is not zero-downtime.
@@ -701,11 +873,11 @@ With the flag enabled: `DELETE /api/v1/dataset/<id>` no longer hard-deletes the
**Schema migration:** the migration adds a nullable `deleted_at` column and an index on it (`ix_tables_deleted_at`) to the `tables` table. The column add is instant; the index build runs inline (no `CONCURRENTLY`) and may briefly block writes on the `tables` table (INSERT/UPDATE/DELETE are queued while the index builds; reads are unaffected) on large Postgres deployments. MySQL InnoDB builds the index online (no blocking). Production deployments with many thousands of datasets should run this migration during a maintenance window.
**Rollback note:** if the application code is rolled back after datasets have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted datasets silently become live, active datasets with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) *before* downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
**Rollback note:** if the application code is rolled back after datasets have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted datasets silently become live, active datasets with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) _before_ downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
**SQL Lab / dataset-creation flows:** creating a dataset over a table whose dataset sits in the trash is refused. The SQL Lab "save as dataset" flow (`get_or_create_dataset`) and file uploads return a **422 naming the hidden twin and the restore endpoint**; the plain create, update, and duplicate paths currently fail with the generic "already exists" 422. In all cases the remediation is the same: restore the hidden dataset (or use a different table name). Perm-string maintenance also covers hidden rows: renaming a database rewrites `perm`/`schema_perm`/`catalog_perm` on soft-deleted datasets and their charts, so a later restore does not resurrect stale permission strings.
**Importer behavior:** importing a dataset YAML whose UUID matches an existing **soft-deleted** dataset is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dataset imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dataset's exact UUID is an explicit request to bring it back. The restore preserves the original PK, the chart back-reference, `table_columns`, and `sql_metrics`. Non-editors get `ImportFailedError`. Callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
**Importer behavior:** importing a dataset YAML whose UUID matches an existing **soft-deleted** dataset is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dataset imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored _and_ has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dataset's exact UUID is an explicit request to bring it back. The restore preserves the original PK, the chart back-reference, `table_columns`, and `sql_metrics`. Non-editors get `ImportFailedError`. Callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
**Uniqueness-validation changes that apply regardless of the feature flag:** two dataset uniqueness checks were tightened alongside this work and are active even with `SOFT_DELETE` off. (1) Create/update uniqueness treats a dataset whose `catalog` is `NULL` as belonging to the database's default catalog, so a legacy twin pair (`catalog=NULL` vs. `catalog=<default>`, same database/schema/name) that older versions allowed now fails validation with "already exists" when either row is edited — resolve by renaming or removing one of the twins. (2) Duplicating a dataset now checks name collisions scoped to the target (database, catalog, schema) instead of globally by name alone: duplicates into other databases that were previously blocked are now allowed.
@@ -725,12 +897,14 @@ With the flag enabled: `DELETE /api/v1/chart/<id>` no longer hard-deletes the ch
**Schema migration:** the migration adds a nullable `deleted_at` column and an index on it (`ix_slices_deleted_at`) to the `slices` table. The column add is instant; the index build runs inline (no `CONCURRENTLY`) and may briefly block writes on the `slices` table (INSERT/UPDATE/DELETE are queued while the index builds; reads are unaffected) on large Postgres deployments. MySQL InnoDB builds the index online (no blocking).
**Rollback note:** if the application code is rolled back after charts have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted charts silently become live, active charts with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) *before* downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
**Rollback note:** if the application code is rolled back after charts have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted charts silently become live, active charts with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) _before_ downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
**Importer behavior:** importing a chart YAML whose UUID matches an existing **soft-deleted** chart is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active chart imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted chart's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all out-of-archive references (`dashboard_slices` junctions, `report.chart_id`, tag rows). The operation is permission-gated: non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
**Importer behavior:** importing a chart YAML whose UUID matches an existing **soft-deleted** chart is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active chart imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored _and_ has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted chart's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all out-of-archive references (`dashboard_slices` junctions, `report.chart_id`, tag rows). The operation is permission-gated: non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
- [39914](https://github.com/apache/superset/pull/39914) `ALERT_REPORT_SLACK_V2` now defaults to `True` and the legacy Slack v1 integration (`Slack` recipient type, `files.upload` API) is deprecated for removal in the next major. Slack blocked new apps from `files.upload` in May 2024 and fully retired the method for all apps on November 12, 2025; because the v1 path sends files through `files.upload`, v1 file-bearing sends now fail at the API level — only text-only `chat_postMessage` still works via the legacy path. Grant your Slack bot the `channels:read` and `groups:read` scopes so existing `Slack` recipients can be auto-upgraded to `SlackV2` on next send. Operators who explicitly override the flag to `False`, or whose Slack bot is missing those scopes, will see deprecation warnings while text-only sends continue through the legacy path.
- [42089](https://github.com/apache/superset/pull/42089) automatically upgrades resolvable Slack v1 recipients, preserves text-only v1 delivery with execution warnings when migration cannot finish, and rejects retired v1 file uploads with actionable scope guidance. Slack delivery uses at-most-once terminal writes and a schedule-wide retry budget configured by `SLACK_SEND_RETRY_MAX_TIME`, clamped to the report's remaining working timeout. Deployments using `SupersetMetastoreCache` for the Slack channel cache must schedule the `slack.cache_channels` Celery task to repopulate misses outside report transactions; see [Alerts and Reports](https://superset.apache.org/admin-docs/configuration/alerts-reports#slack-delivery-timeouts-and-retries).
### Soft delete and restore for dashboards
**Everything in this section applies only when the `SOFT_DELETE` feature flag is enabled. The flag defaults to `False`** (`@lifecycle: development`), so on a default deployment `DELETE /api/v1/dashboard/<id>` continues to **hard-delete permanently** — nothing is recoverable. Enable `SOFT_DELETE` to get the behavior described below.
@@ -751,7 +925,7 @@ The partial-index replacement is dialect-dependent: PostgreSQL uses a native `WH
**Slug semantics:** on PostgreSQL and MySQL 8.0.13+, the slug of a soft-deleted dashboard is **free for reuse**. A new active dashboard can claim it immediately. Restoring a soft-deleted dashboard whose slug has since been claimed returns **422 with a clean error** (`DashboardSlugConflictError`) — rename one of the dashboards and retry; the restore is not silently rejected by a database-level constraint violation.
**Importer behavior:** importing a dashboard YAML whose UUID matches an existing **soft-deleted** dashboard is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dashboard imported without `overwrite=true` is returned unchanged (the import never mutates it), but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dashboard's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all pre-deletion relationship rows (`dashboard_slices` junctions, editor/viewer subjects, tags). Callers whose imports must never mutate existing state should treat bundles that may contain previously deleted UUIDs accordingly. The operation is permission-gated: it requires `can_write` and editorship of the deleted row (or admin) — non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
**Importer behavior:** importing a dashboard YAML whose UUID matches an existing **soft-deleted** dashboard is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dashboard imported without `overwrite=true` is returned unchanged (the import never mutates it), but a soft-deleted UUID match is restored _and_ has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dashboard's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all pre-deletion relationship rows (`dashboard_slices` junctions, editor/viewer subjects, tags). Callers whose imports must never mutate existing state should treat bundles that may contain previously deleted UUIDs accordingly. The operation is permission-gated: it requires `can_write` and editorship of the deleted row (or admin) — non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
### Engine spec capability flag: `supports_offset`
@@ -763,10 +937,10 @@ A new `BaseEngineSpec.supports_offset` attribute (default `True`) indicates whet
A new feature flag `GRANULAR_EXPORT_CONTROLS` introduces three fine-grained permissions that replace the legacy `can_csv` permission:
| Permission | Controls |
|---|---|
| `can_export_data` | CSV, Excel, JSON exports |
| `can_export_image` | Screenshot/PDF exports |
| Permission | Controls |
| -------------------- | ---------------------------- |
| `can_export_data` | CSV, Excel, JSON exports |
| `can_export_image` | Screenshot/PDF exports |
| `can_copy_clipboard` | Copy-to-clipboard operations |
When the feature flag is enabled, these permissions are enforced on both the frontend (disabled buttons with tooltips) and backend (403 responses from API endpoints). When disabled, legacy `can_csv` behavior is preserved.
@@ -810,14 +984,17 @@ The Kenya country map has been updated to reflect the 47 counties established un
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
@@ -852,6 +1029,7 @@ 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",
@@ -866,9 +1044,11 @@ 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:
@@ -881,7 +1061,9 @@ 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
@@ -894,12 +1076,14 @@ 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:
@@ -909,6 +1093,7 @@ superset/examples/
- `--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
@@ -918,6 +1103,7 @@ superset/examples/
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]`
@@ -927,6 +1113,7 @@ 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
@@ -935,6 +1122,7 @@ MCP_SERVICE_PORT = 5008
```
**Production** (JWT-based, multi-user):
```python
# superset_config.py
MCP_AUTH_ENABLED = True
@@ -980,12 +1168,14 @@ 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]
@@ -999,6 +1189,7 @@ 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
@@ -1018,8 +1209,10 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
- [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"
@@ -1063,16 +1256,16 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
Eight M:N association tables move from a synthetic `id INTEGER PRIMARY KEY` to a composite `PRIMARY KEY (fk1, fk2)` on their two foreign-key columns. The surrogate `id` is dropped, and the redundant `UNIQUE (fk1, fk2)` on the two tables that carried one is removed (now subsumed by the PK).
| Table | Composite PK |
|---|---|
| `dashboard_roles` | `(dashboard_id, role_id)` |
| `dashboard_slices` | `(dashboard_id, slice_id)` |
| `dashboard_user` | `(user_id, dashboard_id)` |
| Table | Composite PK |
| ---------------------- | ------------------------------- |
| `dashboard_roles` | `(dashboard_id, role_id)` |
| `dashboard_slices` | `(dashboard_id, slice_id)` |
| `dashboard_user` | `(user_id, dashboard_id)` |
| `report_schedule_user` | `(user_id, report_schedule_id)` |
| `rls_filter_roles` | `(role_id, rls_filter_id)` |
| `rls_filter_tables` | `(table_id, rls_filter_id)` |
| `slice_user` | `(user_id, slice_id)` |
| `sqlatable_user` | `(user_id, table_id)` |
| `rls_filter_roles` | `(role_id, rls_filter_id)` |
| `rls_filter_tables` | `(table_id, rls_filter_id)` |
| `slice_user` | `(user_id, slice_id)` |
| `sqlatable_user` | `(user_id, table_id)` |
**Before upgrading:**
@@ -1083,6 +1276,7 @@ Eight M:N association tables move from a synthetic `id INTEGER PRIMARY KEY` to a
For large `dashboard_slices` / `report_schedule_user` tables, see the operator runbook in [#39859](https://github.com/apache/superset/pull/39859) — pre-flight inventory queries, per-dialect lock-window sizing, and the duplicate / NULL-FK roll-up — to plan the maintenance window.
## 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).
@@ -1091,8 +1285,8 @@ For large `dashboard_slices` / `report_schedule_user` tables, see the operator r
- 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.
+1
View File
@@ -118,6 +118,7 @@ FEATURE_FLAGS = {
"ALERT_REPORTS": True,
"DATASET_FOLDERS": True,
"ENABLE_EXTENSIONS": True,
"MOBILE_CONSUMPTION_MODE": True,
"SEMANTIC_LAYERS": True,
}
EXTENSIONS_PATH = "/app/docker/extensions"
@@ -28,6 +28,7 @@ Alerts and reports are disabled by default. To turn them on, you'll need to chan
- Users can customize the email subject by including date code placeholders, which will automatically be replaced with the corresponding UTC date when the email is sent. To enable this functionality, activate the `"DATE_FORMAT_IN_EMAIL_SUBJECT"` [feature flag](/admin-docs/configuration/configuring-superset#feature-flags). This enables date formatting in email subjects, preventing all reporting emails from being grouped into the same thread (optional for the reporting feature).
- Use date codes from [strftime.org](https://strftime.org/) to create the email subject.
- If no date code is provided, the original string will be used as the email subject.
- Each alert/report has an "Include a link back to Superset" option (enabled by default) controlling whether the call-to-action link is included in email and Slack notifications. The link text in emails is configurable via `EMAIL_REPORTS_CTA`; the Slack message always uses "Explore in Superset". Uncheck the option when recipients should not receive a link to your Superset host, e.g. for external audiences.
##### Disable dry-run mode
@@ -82,6 +83,28 @@ SLACK_CACHE_TIMEOUT = int(timedelta(days=2).total_seconds())
SLACK_API_RATE_LIMIT_RETRY_COUNT = 5
```
When the cache backend is `SupersetMetastoreCache`, report execution does not
write channel listings into the cache because that backend commits the report's
database session. Schedule the dedicated warm-up task so cache misses are
repopulated outside report transactions:
```python
from celery.schedules import crontab
from superset.config import CeleryConfig
class CustomCeleryConfig(CeleryConfig):
beat_schedule = {
**CeleryConfig.beat_schedule,
"slack.cache_channels": {
"task": "slack.cache_channels",
"schedule": crontab(minute="0", hour="*"),
},
}
CELERY_CONFIG = CustomCeleryConfig
```
#### Slack Enterprise Grid (org-scoped tokens)
On a Slack Enterprise Grid org, an org-scoped token spans multiple workspaces, so
@@ -97,6 +120,38 @@ SLACK_TEAM_ID = "T01234567"
This defaults to `None` and only needs to be set when using an org-scoped token;
it is accepted but ignored for standard workspace-level tokens.
#### Slack delivery timeouts and retries
Slack delivery uses a request timeout and an application retry budget:
```python
# Timeout for one Slack API request, in seconds
SLACK_API_TIMEOUT = 30
# Retry budget shared by every Slack destination and upload phase
SLACK_SEND_RETRY_MAX_TIME = 150
# Number of explicit HTTP 429 responses retried using Slack's Retry-After value
SLACK_API_RATE_LIMIT_RETRY_COUNT = 2
# Cooldown after an on-demand channel-cache refresh
SLACK_CHANNEL_REFRESH_COOLDOWN_SECONDS = 300
```
All channels and upload phases in one report execution share a single
`SLACK_SEND_RETRY_MAX_TIME` budget. This prevents a large recipient list from
multiplying the report's wall-clock retry time. The budget is also clamped to
the report's remaining working timeout, leaving Celery's configured timeout lag
available for final state persistence. The effective configured budget is at
least one second longer than `SLACK_API_TIMEOUT`.
To avoid posting the same report twice, Superset does not replay terminal
`chat.postMessage` or `files.completeUploadExternal` operations after ambiguous
server or transport failures. Explicit Slack HTTP 429 responses remain
retryable. These delivery settings and semantics apply to Slack v2 reports and
legacy text-only Slack delivery, independently of the
`ALERT_REPORT_SLACK_V2` feature flag.
### Webhook integration
Superset can send alert and report notifications to any HTTP endpoint — useful for chat platforms, incident management tools, or custom automation.
@@ -174,6 +229,16 @@ ALERT_REPORTS_WEBHOOK_HTTPS_ONLY = True
When enabled, Superset rejects webhook configurations that use `http://` URLs.
#### Request Timeout
Webhook deliveries use a socket timeout so a request can't hang forever if the webhook target is unreachable, which would otherwise leave the report schedule stuck in a `WORKING` state. Configure it with:
```python
ALERT_REPORTS_WEBHOOK_TIMEOUT = 60 # seconds
```
Set to `None` to disable the timeout (not recommended).
#### Retry Behavior
Superset automatically retries webhook deliveries on `429 Too Many Requests` and `5xx` server errors using exponential backoff. Retries are bounded to roughly 120 seconds of cumulative wall-clock time (worst case ~210 seconds, because the bound is checked against the time elapsed before each attempt, so the final request can begin just under the limit and still run its full request timeout), after which the delivery is abandoned.
@@ -54,8 +54,8 @@ celery --app=superset.tasks.celery_app:app beat
To setup a result backend, you need to pass an instance of a derivative of `BaseCache` (`from
flask_caching.backends.base import BaseCache`) to the RESULTS_BACKEND configuration key in your
superset_config.py. You can use Memcached, Redis, S3 (https://pypi.python.org/pypi/s3werkzeugcache),
memory or the file system (in a single server-type setup or for testing), or to write your own
superset_config.py. You can use Memcached, Redis, S3, MinIO, memory
or the file system (in a single server-type setup or for testing), or to write your own
caching interface. Your `superset_config.py` may look something like:
```python
@@ -89,6 +89,12 @@ issues arise. Please clear your existing results cache store when upgrading an e
- SQL Lab will _only run your queries asynchronously if_ you enable **Asynchronous Query Execution**
in your database settings (Sources > Databases > Edit record).
- In order to use dedicated results backend, additional python libraries must be installed. These libraries can be installed using pip.
- [redis-py](https://pypi.org/project/redis/) for Redis.
- [pylibmc](https://pypi.org/project/pylibmc/) for memcached
- [s3werkzeugcache](https://pypi.python.org/pypi/s3werkzeugcache) for S3
- [minio-flask-cache](https://github.com/greggailly/minio-flask-cache) for MinIO or other S3 compatible service
## Celery Flower
Flower is a web based tool for monitoring the Celery cluster which you can install from pip:
+65 -3
View File
@@ -15,9 +15,8 @@ fail-fast behavior ensures operators are immediately aware of infrastructure iss
Superset uses [Flask-Caching](https://flask-caching.readthedocs.io/) for caching purposes.
Flask-Caching supports various caching backends, including Redis (recommended), Memcached,
SimpleCache (in-memory), or the local filesystem.
[Custom cache backends](https://flask-caching.readthedocs.io/en/latest/#custom-cache-backends)
are also supported.
SimpleCache (in-memory), MinIO/S3, or the local filesystem.
[Custom cache backends](https://flask-caching.readthedocs.io/en/latest/#custom-cache-backends) are also supported.
Caching can be configured by providing dictionaries in
`superset_config.py` that comply with [the Flask-Caching config specifications](https://flask-caching.readthedocs.io/en/latest/#configuring-flask-caching).
@@ -47,6 +46,7 @@ In order to use dedicated cache stores, additional python libraries must be inst
- For Redis: we recommend the [redis](https://pypi.python.org/pypi/redis) Python package
- Memcached: we recommend using [pylibmc](https://pypi.org/project/pylibmc/) client library as
`python-memcached` does not handle storing binary data correctly.
- MinIO (S3): we recommend using the [minio-flask-cache](https://github.com/greggailly/minio-flask-cache) package
These libraries can be installed using pip.
@@ -134,6 +134,50 @@ CELERY_CONFIG = CustomCeleryConfig
This will cache the top 5 most popular dashboards every hour. For other
strategies, check the `superset/tasks/cache.py` file.
### Warming Up Native Filter Options
Native filter Value-type dropdown option queries (e.g. `SELECT DISTINCT column FROM table`) are
cached the same way as chart data, via `DATA_CACHE_CONFIG`. However, the strategies above only warm
up chart render queries, so the first user to open a dashboard's filter dropdown after a cache entry
expires still triggers a fresh database query.
The `native_filter_options` strategy pre-populates the cache for these dropdown queries. It reads
each dashboard's `native_filter_configuration`, builds the same `filter_select` chart-data query the
frontend would send, and executes it as the configured `SUPERSET_CACHE_WARMUP_USER`:
```python
class CustomCeleryConfig(CeleryConfig):
beat_schedule = {
**CeleryConfig.beat_schedule,
'cache-warmup-native-filters': {
'task': 'cache-warmup',
'schedule': crontab(minute=0, hour=3), # daily at 03:00
'kwargs': {
'strategy_name': 'native_filter_options',
'dashboard_ids': [1, 2, 3],
},
},
}
```
Requirements and limitations:
- `SUPERSET_CACHE_WARMUP_USER` must be set to a user with access to the dashboards and datasets
referenced by the native filters.
- `DATA_CACHE_CONFIG` must use a backend that actually persists entries (Redis recommended); the
default `NullCache` discards writes, so warming has nothing to warm. The effective timeout also
needs to be positive — `NATIVE_FILTER_OPTIONS_CACHE_TIMEOUT = -1` disables cache writes for these
queries entirely, even with a working backend.
- Schedule the warm-up at least as often as the effective native filter cache timeout (whichever of
`NATIVE_FILTER_OPTIONS_CACHE_TIMEOUT`, the chart/dataset/database timeout, or `DATA_CACHE_CONFIG`'s
default applies). A looser schedule still leaves a window of cold, unwarmed queries between expiry
and the next run — the daily example above assumes a TTL of a day or more.
- Cache entries are warmed under the warm-up user's own cache partition, the same entry that user
would create by opening the filter dropdown manually. Users with a different role set or row-level
security context may still see a cache miss on first load.
- Cascading/dependent native filters and search-term variants of filter option queries are not
warmed by this strategy.
## Caching Thumbnails
This is an optional feature that can be turned on by activating its [feature flag](/admin-docs/configuration/configuring-superset#feature-flags) on config:
@@ -301,6 +345,24 @@ DISTRIBUTED_COORDINATION_CONFIG = {
}
```
By default, connections opened for `DISTRIBUTED_COORDINATION_CONFIG` (as well as
`GLOBAL_ASYNC_QUERIES_CACHE_BACKEND`, which uses the same `RedisCache`/`RedisSentinelCache`
backend) have no socket timeout. This can be overridden with `CACHE_REDIS_SOCKET_TIMEOUT` and
`CACHE_REDIS_SOCKET_CONNECT_TIMEOUT`, both in seconds:
```python
DISTRIBUTED_COORDINATION_CONFIG = {
"CACHE_TYPE": "RedisCache",
"CACHE_REDIS_HOST": "localhost",
"CACHE_REDIS_PORT": 6379,
"CACHE_REDIS_SOCKET_TIMEOUT": 5, # seconds
"CACHE_REDIS_SOCKET_CONNECT_TIMEOUT": 5, # seconds
}
```
These apply to `RedisSentinelCache` connections as well, covering both the sentinel-node
connections and the resolved master connection.
### Distributed Lock TTL
You can configure the default lock TTL (time-to-live) in seconds. Locks automatically expire after
@@ -307,6 +307,22 @@ to simplify the process of setting up a non-default root path across the service
In `docker/.env-local` set `SUPERSET_APP_ROOT` to the desired prefix and then bring the
services up with `docker compose up --detach`.
### Swagger UI
By default, Superset's Swagger UI and OpenAPI spec (enabled via `FAB_API_SWAGGER_UI`) are
served by Flask-AppBuilder and don't account for a non-root `APPLICATION_ROOT` prefix. If
you're running Superset behind a URL prefix and want the Swagger UI and OpenAPI spec to
resolve correctly, set:
```python
FAB_API_SWAGGER_UI_SUPERSET_APP_ROOT = True
```
in your `superset_config.py` file. This serves an `APPLICATION_ROOT`-aware Swagger UI and
OpenAPI spec at `/swagger/<version>` and `/api/<version>/_openapi` respectively, resolved
through the configured prefix. This flag only takes effect when `FAB_API_SWAGGER_UI` is
also enabled, and defaults to `False`.
## Custom OAuth2 Configuration
Superset is built on Flask-AppBuilder (FAB), which supports many providers out of the box
@@ -18,7 +18,7 @@ code is less ambiguous and is unique to all regions in the world.
## Included Maps
The current list of countries can be found in the src
[legacy-plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries.ts)
[plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/plugin-chart-country-map/src/countries.ts)
The Country Maps visualization already ships with the maps for the following countries:
@@ -31,10 +31,10 @@ The Country Maps visualization already ships with the maps for the following cou
## Adding a New Country
To add a new country to the list, you'd have to edit files in
[@superset-ui/legacy-plugin-chart-country-map](https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-country-map).
[@superset-ui/plugin-chart-country-map](https://github.com/apache/superset/tree/master/superset-frontend/plugins/plugin-chart-country-map).
1. Generate a new GeoJSON file for your country following the guide in [this Jupyter notebook](https://github.com/apache/superset/blob/master/superset-frontend/plugins/legacy-plugin-chart-country-map/scripts/Country%20Map%20GeoJSON%20Generator.ipynb).
2. Edit the countries list in [legacy-plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries.ts).
1. Generate a new GeoJSON file for your country following the guide in [this Jupyter notebook](https://github.com/apache/superset/blob/master/superset-frontend/plugins/plugin-chart-country-map/scripts/Country%20Map%20GeoJSON%20Generator.ipynb).
2. Edit the countries list in [plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/plugin-chart-country-map/src/countries.ts).
3. Install superset-frontend dependencies: `cd superset-frontend && npm install`
4. Verify your countries in Superset plugins storybook: `npm run plugins:storybook`.
5. Build and install Superset from source code.
@@ -0,0 +1,184 @@
---
title: Dashboard Performance
hide_title: true
sidebar_position: 5
version: 1
---
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Dashboard Performance
A dashboard's perceived speed is determined by three independent things: how
many charts have to render, how many queries the backend can execute
concurrently, and how quickly the underlying data warehouse can return
results. Superset gives you levers for the first two; the third belongs to
your warehouse. This page covers the dashboard-side levers and the practical
guidance around them.
## Is there a maximum chart count per dashboard?
**No hard limit is enforced** — Superset has no configuration key that
caps the number of charts on a dashboard. In practice, dashboards behave
well up to a few dozen charts. Beyond that, you'll typically feel friction
on the initial load and during cross-filter / time-range updates, even with
the lazy-loading optimizations described below.
Rough thresholds to keep in mind:
- **Under ~25 charts**: usually no perceptible problem.
- **2550 charts**: still fine, but you start to want tabs to break the
page into chunks the user actually looks at.
- **Over ~50 charts**: split into multiple dashboards or use tabs
aggressively. The bottleneck is rarely Superset itself — it's the
warehouse executing dozens of queries in parallel and the browser
rendering dozens of chart frames.
These are guidelines, not guarantees. A dashboard of 100 sparkline-style
charts hitting a fast cache behaves very differently from a dashboard of
20 heavy aggregations against a cold warehouse.
## Lazy rendering — `DASHBOARD_VIRTUALIZATION`
Superset's dashboard layout is virtualized at the row level. Charts that
are far below the user's current scroll position render a placeholder
instead of their visualization until the user scrolls them into view, and
go back to a placeholder if scrolled well past. The chart component itself
stays mounted throughout — only the visualization is swapped for a
placeholder — so this alone does **not** reduce backend query load; see
[Deferred data fetch](#deferred-data-fetch--dashboard_virtualization_defer_data)
below for that. This is on by default.
**Feature flag**: `DASHBOARD_VIRTUALIZATION` (default: `True`)
The flag is `stable` and marked for path-to-deprecation — meaning the
behavior will eventually be non-optional, but the flag still exists so
operators can disable it if a specific layout misbehaves.
**Behavior** (from `superset-frontend/src/dashboard/components/gridComponents/Row/Row.tsx`):
- A chart's visualization is rendered when its row scrolls within **1
viewport height** of the visible area.
- A chart's visualization is swapped back for a placeholder when its row
scrolls more than **4 viewport heights** away from the visible area.
- Tabs that aren't currently selected don't render their content at all
(see below).
- The placeholder-swap-back is skipped in **embedded** mode (so an
embedded dashboard keeps its charts rendered once they've been seen,
which avoids re-rendering on scroll-up). Both halves are skipped for
**headless / bot** rendering (so screenshot / report jobs load every
chart).
## Deferred data fetch — `DASHBOARD_VIRTUALIZATION_DEFER_DATA`
By default, `DASHBOARD_VIRTUALIZATION` only controls whether a chart's
*visualization* is rendered — the chart component still mounts and issues
its data request immediately, regardless of scroll position.
`DASHBOARD_VIRTUALIZATION_DEFER_DATA` is a supplementary flag that skips
the data request itself for charts that aren't currently in view, useful
for backends where opening a connection or compiling a query is expensive
even if the result would be thrown away. It only has an effect when
`DASHBOARD_VIRTUALIZATION` is also enabled — with virtualization off,
every chart is treated as in view, so there's nothing left to defer.
**Feature flag**: `DASHBOARD_VIRTUALIZATION_DEFER_DATA` (default: `False`)
Enable this if you see warehouse load spike on dashboard *open* even
though most charts are off-screen.
## Per-tab lazy loading
**This is on by default and has no flag.** A tab's content is not rendered
until the user activates that tab, so charts inside an unselected tab do
not fetch data on dashboard open. When the user clicks the tab, that
tab's charts mount and fetch in the normal way.
Practically: tabs are the single most effective tool for a large
dashboard. Splitting 60 charts across 4 tabs effectively turns dashboard
open into "load ~15 charts," and the remaining ones lazy-load only if the
user goes looking.
## Is there a switch to cap concurrent chart queries?
**No.** Superset does not implement a frontend-side concurrent-request
limiter. Each chart issues its own data request when it mounts, and the
browser handles parallelism — typically ~6 in-flight requests per origin
under HTTP/1.1, though HTTP/2 or HTTP/3 (if your deployment terminates
TLS that way) can multiplex considerably more over a single connection.
Backend throughput is bounded by your
Gunicorn worker count for synchronous query execution, or by your Celery
worker pool when [async queries](./async-queries-celery.mdx) are enabled.
If you need to throttle warehouse load, the right place is:
1. The warehouse itself (connection pool / concurrency limits).
2. Superset's Celery configuration (smaller worker pool when async
queries are on).
3. Splitting heavy charts across tabs or separate dashboards (each
dashboard load only fetches what's visible).
## Splitting strategies
When a dashboard outgrows comfortable performance, the options in order
of effort:
**1. Move sections into tabs.** Same dashboard, but only the active tab's
charts fetch. This is the cheapest change and often the only one needed.
**2. Cache aggressively.** A Redis cache backend (see
[Caching](./cache.mdx)) means repeat dashboard loads serve from cache
rather than re-hitting the warehouse. This is especially impactful for
dashboards opened by many users in close succession.
**3. Enable async queries.** [Async query execution](./async-queries-celery.mdx)
via Celery decouples query duration from request lifetime, so a slow
chart doesn't block the page. The user sees other charts come in as
their queries complete.
**4. Split into multiple dashboards.** Group related charts into purpose-
specific dashboards rather than one mega-dashboard. Link them from a
landing dashboard or a navigation menu.
**5. Pre-aggregate at the warehouse level.** If the same expensive
aggregation appears across many charts, materialize it as a view or
scheduled table in the warehouse so each chart query is a cheap lookup.
## Operational notes
- The feature flags above are set in `superset_config.py`, e.g.:
```python
FEATURE_FLAGS = {
"DASHBOARD_VIRTUALIZATION": True,
"DASHBOARD_VIRTUALIZATION_DEFER_DATA": True,
}
```
- See [Feature Flags](./feature-flags.mdx) for the full list of supported
flags and their lifecycle stages.
- Server-side screenshot jobs (alerts, scheduled reports, thumbnails)
render the dashboard in a headless, webdriver-controlled browser, which
intentionally bypasses row virtualization so the rendered artifact
includes every chart, not just the ones above the fold. User-triggered
"download as image/PDF" is different: it captures whatever's currently
rendered in the user's own browser, so it's still subject to
virtualization like any other page view. Metadata/YAML dashboard export
doesn't render the frontend at all, so virtualization doesn't apply to
it either.
@@ -315,6 +315,76 @@ Here's a concrete example:
WHERE country_code = 'US'
```
**Guest User Attributes**
The `{{ get_guest_user_attribute('attribute_name') }}` macro returns a specific attribute value from the guest user context.
This is useful when working with embedded Superset where guest tokens can contain custom attributes that need to be
accessed in SQL queries.
This macro only works when the current user is a guest user (authenticated via guest token). If the current user is
not a guest user, or if the specified attribute doesn't exist, the macro will return `None` or the provided default value.
If you have caching enabled in your Superset configuration, then by default the resolved value (whether it
came from the guest token, a null attribute, or the provided default) will be used by Superset when
calculating the cache key. A cache key is a unique identifier that determines if there's a cache hit in the
future and Superset can retrieve cached data. Including the resolved value on every branch ensures two guests
whose tokens render different SQL never share a cache entry.
You can disable the inclusion of the attribute value in the calculation of the
cache key by adding the following parameter to your Jinja code, but only do so
when the value cannot affect the query results:
```
{{ get_guest_user_attribute('department', add_to_cache_keys=False) }}
```
You can also provide a default value if the attribute is not found:
```
{{ get_guest_user_attribute('region', default='US') }}
```
Here's a concrete example of using guest user attributes in a query:
```sql
SELECT *
FROM sales_data
WHERE region = '{{ get_guest_user_attribute("user_region", default="global") }}'
AND department = '{{ get_guest_user_attribute("department") }}'
```
:::warning[Security Warning]
Guest token attributes come from the embedding application. By default,
`get_guest_user_attribute()` escapes string values — including strings nested inside
arrays and object values, and caller-supplied defaults — through the database dialect's
literal rendering (the same mechanism as `url_param()`). This covers dialect-specific
escape characters such as the backslash on MySQL/MariaDB, so the example above is safe
to interpolate directly. If you pass `escape_result=False`, or interpolate non-string
values (numbers, booleans), you are responsible for validating or allowlisting the
values, since they originate outside Superset.
If a guest attribute is an array and you plan to pipe it through the `|where_in` filter
(for example `full_name IN {{ get_guest_user_attribute('names')|where_in }}`), call
`get_guest_user_attribute('names', escape_result=False)`. `where_in` already applies its
own dialect-safe quoting, so escaping the values twice can corrupt them (a value such as
`O'Brien` would come back doubly escaped and match nothing).
Only individual string values are escaped as SQL literals. Interpolating an entire array
or object directly (rather than through `|where_in`, or by accessing a specific element)
renders Python's string form of that structure, which is not valid SQL, and object keys
are not escaped at all. Use `|where_in` for arrays, `|tojson` where you need a
JSON-stringified value, or read individual keys/elements out of the structure yourself.
The same double-escaping problem described above for `|where_in` applies to `|tojson`:
pass `escape_result=False` before piping to `|tojson` (for example
`{{ get_guest_user_attribute('profile', escape_result=False)|tojson }}`), since JSON
already handles its own quoting and re-escaping a value first would corrupt it (a nested
string such as `O'Brien` would come back as the altered `O''Brien` in the serialized
JSON).
:::
### Explicitly Including Values in Cache Key
The `{{ cache_key_wrapper() }}` function explicitly instructs Superset to add a value to the
+45
View File
@@ -198,6 +198,51 @@ Available per-entity overrides are:
When an override is set, it replaces `SUBJECTS_RELATED_TYPES` for that picker. When it is `None`,
the picker inherits the global default.
#### Looking Up Subjects via API
Superset exposes a read-only REST API for resolving subjects:
```
GET /api/v1/security/subject/
```
The main use case is **id mapping** — given a user, role, or group id, callers (including
extensions) can look up the corresponding subject entity, and vice versa. Access is gated by
the `can_read` permission on the `Subject` resource, which is granted to **Admins only** by
default, since subjects enumerate every user, role, and group on the instance. Callers without
that permission receive a `403`. Only `GET` (list, get, info) is exposed — there is no create,
update, or delete, because subjects are derived automatically from users, roles, and groups and
kept in sync internally.
**Resolve the subject for a given principal id** using [Rison](https://github.com/Nanonid/rison)
query syntax:
```
GET /api/v1/security/subject/?q=(filters:!((col:user_id,opr:eq,value:5)))
GET /api/v1/security/subject/?q=(filters:!((col:role_id,opr:eq,value:3)))
GET /api/v1/security/subject/?q=(filters:!((col:group_id,opr:eq,value:2)))
```
**Filter by subject type or active status:**
```
GET /api/v1/security/subject/?q=(filters:!((col:type,opr:eq,value:1)))
GET /api/v1/security/subject/?q=(filters:!((col:active,opr:eq,value:!t)))
```
The `type` column is an integer enum: `1` for User, `2` for Role, `3` for Group
(`superset.subjects.types.SubjectType`).
**Search by label:**
```
GET /api/v1/security/subject/?q=(filters:!((col:label,opr:subject_all_text,value:finance)))
```
Each subject in the response includes flat scalar ids (`user_id`, `role_id`, `group_id`) rather
than a nested object, so callers can match directly on whichever id they already have — only the
id field matching the subject's `type` is populated; the others are `null`.
### Dashboard Access Control
Access to dashboards is managed via editors (subjects that have edit permissions to the dashboard).
@@ -519,6 +519,30 @@ sh -c "$(curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.0/install
For those interested, you may also try out [avn](https://github.com/nvm-sh/nvm#deeper-shell-integration) to automatically switch to the node version that is required to run Superset frontend.
##### zstd
`npm run dev-server` proxies requests to your local Superset server and rewrites the HTML it returns, so it has to decompress responses sent with `Content-Encoding: zstd`. It does that with [`simple-zstd`](https://www.npmjs.com/package/simple-zstd), which wraps the system `zstd` binary instead of bundling one. That binary has to be on your `PATH`:
```bash
# macOS
brew install zstd
# Ubuntu/Debian
sudo apt install zstd
# Windows
choco install zstd
```
`simple-zstd` looks for the binary when it is first imported, not when a response is decompressed, so a missing `zstd` stops the dev server at startup with:
```
Error: Can not access zstd! Is it installed?
at Object.<anonymous> (.../node_modules/simple-zstd/dist/src/index.js:102:11)
```
The message names the dependency, but it surfaces from inside `webpack.proxy-config.js` while the webpack config is loading, which reads like a build-tooling failure rather than a missing system package.
#### Install dependencies
Install third-party dependencies listed in `package.json` via:
@@ -198,7 +198,7 @@ Each component should come with its dedicated storybook file.
**One component per story:** Each storybook file should only contain one component unless substantially different variants are required
**Component variants:** If the component behavior is substantially different when certain props are used, it is best to separate the story into different types. See the `superset-frontend/src/components/Select/Select.stories.tsx` as an example.
**Component variants:** If the component behavior is substantially different when certain props are used, it is best to separate the story into different types. See the `superset-frontend/packages/superset-ui-core/src/components/Select/Select.stories.tsx` as an example.
**Isolated state:** The storybook should show how the component works in an isolated state and with as few dependencies as possible
+16
View File
@@ -277,6 +277,22 @@ second etc). Example:
}
```
## How do I expand all chart descriptions on a dashboard by default?
Charts can have a markdown description, set in the chart's **Edit chart properties** dialog, that's
hidden by default and toggled on a per-chart basis from the chart's context menu on a dashboard. If
you'd rather have every chart's description expanded by default when the dashboard loads, add the
`expand_all_slices` key to the dashboard JSON Metadata field:
```json
{
"expand_all_slices": true
}
```
Charts that have already been manually expanded or collapsed on the dashboard keep that per-chart
override (tracked in the `expanded_slices` key) regardless of the `expand_all_slices` setting.
## Does Superset work with [insert database engine here]?
The [Connecting to Databases section](/user-docs/databases/) provides the best
@@ -165,6 +165,31 @@ You can also certify metrics if you'd like for your team in this view.
- [Blog: Unlocking the Power of Virtual Datasets](https://preset.io/blog/unlocking-the-power-of-virtual-datasets-in-apache-superset/)
:::
### Native filters on semantic views
When the `SEMANTIC_LAYERS` feature flag is enabled, Superset can connect to external semantic layers
(such as dbt Semantic Layer or Cube) and expose their semantic views as data sources alongside your
regular Datasets. Semantic views can be used as filter targets when adding a native (dashboard) filter,
the same way a Dataset can.
To add a filter on a semantic view:
1. Open the dashboard, click the **⋮** (more options) menu, and select **Edit dashboard**.
2. Open the Filter Bar and click **+ Add/Edit Filters**.
3. Add a new filter and, in the datasource dropdown, select a semantic view. Semantic views are listed
alongside datasets and can be identified by their type.
4. Select one of the semantic view's dimensions in the **Column** field, the same way you'd select a
column on a dataset.
5. Configure the remaining filter options (filter type, default value, scope, etc.) and click **Save**.
Any chart on the dashboard that's powered by the same semantic view is filtered by the selected
dimension when the filter is applied.
:::note
Semantic views and native filter support for them are part of the experimental Semantic Layers
feature and require the `SEMANTIC_LAYERS` feature flag to be enabled.
:::
### Creating charts in Explore view
Superset has 2 main interfaces for exploring data:
@@ -303,6 +328,10 @@ Conditional formatting rules highlight cells based on their values. Rules can be
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.
Each rule's color is set with a full color picker rather than a fixed dropdown of presets. Pick any custom color, or use the **Colors** preset swatches, which reference theme tokens (success, warning, error, and their background variants) so a rule's color updates automatically if the active theme changes, including switching between light and dark mode.
When a rule targets a column with an active time comparison, a **Trend colors** preset also appears, letting you color cells green for an increase and red for a decrease (or the reverse).
#### 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**.
+1
View File
@@ -129,3 +129,4 @@ The following URL parameters can be passed through the `urlParams` option in `da
- **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.
- **Redacted errors** — API responses to a guest token report a generic `An error occurred while fetching the data.` instead of the underlying error, since engine errors quote catalog, schema, table and column names. Errors Superset raises itself — access denials, timeouts, payload validation — keep their message, and the full error is always available in the server logs.
@@ -352,6 +352,13 @@ The **Custom** time range picker accepts natural language expressions alongside
These expressions are evaluated at query time, so saved charts always display data relative to the current date.
### Downloading Drill to Detail and Drill By Results
The **Drill to detail** and **Drill by** modals, available from a chart's context menu, show the row-level
data behind a chart (or behind a specific data point, when one is selected). Use the **Download** button in
the modal's toolbar to export the underlying result set as CSV or Excel (XLSX) without leaving the modal —
the export isn't limited to the page currently visible in the table.
:::resources
- [Chart Walkthroughs](https://docs.preset.io/docs/chart-walkthroughs) - Detailed guides for most chart types
@@ -32,8 +32,13 @@ Notes on the generated workbook:
Excel's 31-character limit; the chart id keeps names unique).
- Charts nested in tabs are included.
- Data reflects the dashboard's active filter state at the time of export.
- A chart with no saved query context is skipped and listed in the email; open
the chart in Explore and re-save it to include it next time.
- A chart with no saved query context (charts only store one once they've been
re-saved in Explore) still exports when it is a `table`, `big_number`,
`big_number_total` or `pie`, by rebuilding the query from the chart's saved
form data. Charts of other types — and charts relying on post-processing the
rebuild can't reproduce — are skipped and listed in the email; open the chart
in Explore and re-save it to include it next time, or configure
`EXCEL_EXPORT_QUERY_CONTEXT_BUILDER`.
- Row counts per sheet are capped the same way as the chart-level CSV/Excel
export (`ROW_LIMIT`, bounded by `SQL_MAX_ROW`), and never exceed Excel's
per-sheet maximum.
@@ -74,6 +79,7 @@ will not register.
| `EXCEL_EXPORT_LINK_TTL_SECONDS` | `86400` | Lifetime of the pre-signed download URL (24h). |
| `EXCEL_EXPORT_S3_CLIENT_KWARGS` | `{}` | Extra kwargs for `boto3.client("s3", ...)` — e.g. `region_name`, or `endpoint_url` for MinIO/LocalStack. |
| `EXCEL_EXPORT_TABLE_VIZ_TYPES` | `None` | Viz types kept tabular in **Export Images to Excel** mode; every other type is embedded as an image. `None` uses the built-in default (`table`, `pivot_table`, `pivot_table_v2`). |
| `EXCEL_EXPORT_QUERY_CONTEXT_BUILDER` | `None` | Optional `Callable[[form_data_dict], dict \| None]` to build a query context for a chart missing a saved one, tried before the built-in form-data rebuild. Point it at a service that runs the chart's real frontend `buildQuery` to faithfully export viz types the built-in rebuild can't handle. Must return `None` when it can't build faithfully, so the export falls back. |
Credentials and region resolve through the standard boto3 chain (environment
variables, shared config, or instance role) unless overridden via
@@ -0,0 +1,93 @@
---
title: Mobile Experience
sidebar_position: 7
version: 1
---
import useBaseUrl from "@docusaurus/useBaseUrl";
# Mobile Experience
Superset ships an optional, consumption-only mobile experience for viewing
dashboards on phones and other small screens. When enabled, screens below
768px wide get a layout built for touch: dashboards render their charts
stacked full-width, navigation collapses into a drawer, and dashboard
filters open in a slide-out panel.
The mobile experience is **read-only by design**. It is aimed at consumers
of analytics — people checking a dashboard from a phone — not at dashboard
authors. Authoring surfaces (chart builder, SQL Lab, dataset management,
and administrative screens) remain desktop-only.
## Enabling the mobile experience
The mobile experience is gated behind the `MOBILE_CONSUMPTION_MODE` feature
flag, which is off by default. Enable it in your `superset_config.py`:
```python
FEATURE_FLAGS = {
"MOBILE_CONSUMPTION_MODE": True,
}
```
With the flag disabled, Superset renders identically at every screen size,
and phones display the desktop layout scaled down (the pre-existing
behavior). The flag also controls whether Superset serves a viewport meta
tag, which is required for mobile browsers to apply the responsive layout
at their native width.
## What works on mobile
| Area | Mobile behavior |
| --- | --- |
| **Dashboards** | Charts stack vertically at full width, sized to the screen. Tab bars are sticky and swipeable. Native filters open in a drawer via the filter icon in the header. |
| **Dashboard list** | Card view with full-width cards; search and filters open in a drawer. |
| **Home** | Recents (dashboards only) and dashboard cards; desktop-only sections are hidden. |
| **Navigation** | A hamburger menu opens a drawer with links to dashboards, theme and language selection, and user info/logout. |
<div style={{display: 'flex', gap: '1rem', flexWrap: 'wrap'}}>
<img src={useBaseUrl("/img/screenshots/mobile/mobile_dashboard.jpg")} alt="A dashboard on mobile with charts stacked full width" width="260" />
<img src={useBaseUrl("/img/screenshots/mobile/mobile_filter_drawer.jpg")} alt="The dashboard filter drawer on mobile" width="260" />
<img src={useBaseUrl("/img/screenshots/mobile/mobile_dashboard_list.jpg")} alt="The dashboard list in card view on mobile" width="260" />
</div>
<div style={{display: 'flex', gap: '1rem', flexWrap: 'wrap', marginTop: '1rem'}}>
<img src={useBaseUrl("/img/screenshots/mobile/mobile_home.jpg")} alt="The Superset home page on mobile" width="260" />
<img src={useBaseUrl("/img/screenshots/mobile/mobile_nav_drawer.jpg")} alt="The mobile navigation drawer" width="260" />
<img src={useBaseUrl("/img/screenshots/mobile/mobile_unsupported.jpg")} alt="The screen shown for views that are not available on mobile" width="260" />
</div>
## What doesn't work on mobile
Everything not listed above shows a friendly "This view isn't available on
mobile" screen with shortcuts back to dashboards and the home page. That
includes:
- Chart builder (Explore) and chart-level links — chart titles on
dashboards are plain text on mobile, and chart entries are filtered out
of the home page's Recents feed
- SQL Lab and query history
- Creating or editing dashboards, charts, datasets, and databases
- List views other than dashboards (charts, datasets, saved queries, etc.)
- Administrative and settings screens
Editing controls are also removed from the screens that *are* supported:
the dashboard header hides the edit, publish, and favorite controls, and
dashboard/chart kebab menus are reduced to view-oriented actions.
If a device crosses the 768px threshold — for example, rotating a tablet
to landscape or resizing a window — the full desktop experience becomes
available immediately.
## Notes for operators
- The flag is deployment-wide; there is no per-role or per-user targeting.
- Dashboard permalinks and links shared from desktop resolve normally on
mobile as long as they point at dashboards.
- Embedded dashboards are unaffected: the embedded SDK controls its own
layout, and the viewport meta tag is only interpreted by the top-level
page.
- Dashboards loaded with a `standalone` URL param (used for iframe embeds
and kiosk-style displays) always render the desktop layout, regardless
of viewport width, since the standalone chrome doesn't expose the mobile
filter drawer's trigger.
@@ -0,0 +1,92 @@
---
title: Number Formatting
sidebar_position: 11
description: Reference for the built-in D3-based number format presets available on chart metrics and axes
keywords: [number format, d3 format, formatting, duration, memory, length, distance]
---
{/*
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.
*/}
# Number Formatting
Most chart types expose a **Number format** control (labeled **D3 Format**, **Y Axis Format**, or similar depending on the chart) wherever a metric or numeric axis can be formatted. This is available from the **Customize** tab, or from the metric's popover editor, depending on the chart type. Selecting one of the built-in presets below applies that formatting to the values Superset renders in the chart.
When an axis format control's chart has **Comparison display** set to **Percentage** (e.g. period-over-period comparisons), that control's choices are narrowed to percentage-only presets, hiding `SMART_NUMBER`, `~g`, and the duration/memory/length presets.
You can also type a custom [D3 format string](https://github.com/d3/d3-format) directly into the control if none of the presets fit your needs.
## Built-in presets
### General
| Key | Description |
| --- | --- |
| `SMART_NUMBER` | Adaptive formatting — automatically picks a reasonable precision based on the value |
| `~g` | Original value, using D3's general-format specifier (trims trailing zeros; may switch to exponential notation for very large or small values) |
### D3 format strings
These are raw [D3 format specifiers](https://github.com/d3/d3-format#locale_format). The dropdown shows a live preview of each one against a sample value.
| Format spec | What it does |
| --- | --- |
| `,d` | Integer, comma-grouped thousands |
| `.1s` | SI-prefix notation, 1 significant digit |
| `.3s` | SI-prefix notation, 3 significant digits |
| `,.1%` | Percentage, 1 decimal place, comma-grouped |
| `.2%` | Percentage, 2 decimal places |
| `.3%` | Percentage, 3 decimal places |
| `.4r` | Rounded to 4 significant digits |
| `,.1f` | Fixed-point, 1 decimal place, comma-grouped |
| `,.2f` | Fixed-point, 2 decimal places, comma-grouped |
| `,.3f` | Fixed-point, 3 decimal places, comma-grouped |
| `+,` | Comma-grouped, always shows the sign |
| `$,.2f` | Currency, 2 decimal places, comma-grouped |
### Duration
| Key | Description |
| --- | --- |
| `DURATION` | Duration in ms (`66000` => `1m 6s`) |
| `DURATION_SUB` | Duration in ms, with sub-second precision (`1.40008` => `1ms 400µs 80ns`) |
| `DURATION_COL` | Duration in ms, colon-separated (`10500` => `0:00:10.5`) |
### Memory
| Key | Description |
| --- | --- |
| `MEMORY_DECIMAL` | Memory in bytes, decimal (`1024B` => `1.024kB`) |
| `MEMORY_BINARY` | Memory in bytes, binary (`1024B` => `1KiB`) |
| `MEMORY_TRANSFER_RATE_DECIMAL` | Memory transfer rate in bytes, decimal (`1024B` => `1.024kB/s`) |
| `MEMORY_TRANSFER_RATE_BINARY` | Memory transfer rate in bytes, binary (`1024B` => `1KiB/s`) |
### Distance / length
| Key | Description |
| --- | --- |
| `LENGTH` | Length in meters, converted to kilometers (`12345m` => `12.35km`) |
| `LENGTH_CM_KM` | Length in centimeters, converted to kilometers (`12345678cm` => `123.46km`) |
| `LENGTH_CM_M` | Length in centimeters, converted to meters (`12345cm` => `123.45m`) |
Use these when a metric's underlying values are stored in meters or centimeters but are easier to read at a coarser unit — for example, distances traveled, cable/pipe lengths, or elevation changes.
## Currency
Some chart types also expose currency-specific formatting, including a dynamic mode that reads the currency from a column value. See [Dynamic Currency Formatting](./creating-your-first-dashboard#dynamic-currency-formatting) for details.
+14 -14
View File
@@ -58,15 +58,15 @@
"@fontsource/inter": "^5.3.0",
"@mdx-js/react": "^3.1.1",
"@saucelabs/theme-github-codeblock": "^0.3.0",
"@storybook/addon-docs": "^10.5.5",
"@storybook/addon-docs": "^10.5.7",
"@superset-ui/core": "^0.20.4",
"@swc/core": "^1.15.47",
"antd": "^6.5.2",
"baseline-browser-mapping": "^2.11.7",
"caniuse-lite": "^1.0.30001806",
"docusaurus-plugin-openapi-docs": "^5.1.2",
"docusaurus-theme-openapi-docs": "^5.1.2",
"js-yaml": "^5.2.2",
"antd": "^6.5.4",
"baseline-browser-mapping": "^2.11.12",
"caniuse-lite": "^1.0.30001807",
"docusaurus-plugin-openapi-docs": "^5.1.3",
"docusaurus-theme-openapi-docs": "^5.1.3",
"js-yaml": "^5.2.3",
"json-bigint": "^1.0.0",
"prism-react-renderer": "^2.4.1",
"react": "^18.3.1",
@@ -77,8 +77,8 @@
"react-table": "^7.8.0",
"remark-import-partial": "^0.0.2",
"reselect": "^5.2.0",
"storybook": "^10.5.5",
"swagger-ui-react": "^5.32.11",
"storybook": "^10.5.7",
"swagger-ui-react": "^5.32.12",
"swc-loader": "^0.2.7",
"tinycolor2": "^1.4.2",
"unist-util-visit": "^5.1.0"
@@ -89,14 +89,14 @@
"@eslint/js": "^9.39.2",
"@types/js-yaml": "^4.0.9",
"@types/react": "^19.1.8",
"@typescript-eslint/eslint-plugin": "^8.65.0",
"@typescript-eslint/parser": "^8.65.0",
"@typescript-eslint/eslint-plugin": "^8.66.0",
"@typescript-eslint/parser": "^8.66.0",
"eslint": "^9.39.2",
"eslint-plugin-react": "^7.37.5",
"globals": "^17.8.0",
"oxfmt": "^0.61.0",
"globals": "^17.9.0",
"oxfmt": "^0.62.0",
"typescript": "~6.0.3",
"typescript-eslint": "^8.65.0",
"typescript-eslint": "^8.66.0",
"webpack": "^5.109.2"
},
"browserslist": {
+50 -17
View File
@@ -68,6 +68,8 @@ function getProviders() {
const { themeObject } = require('@apache-superset/core/theme');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { App, ConfigProvider } = require('antd');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { useColorMode } = require('@docusaurus/theme-common');
// Configure Ant Design to render portals (tooltips, dropdowns, etc.)
// inside the closest .storybook-example container instead of document.body
@@ -78,15 +80,39 @@ function getProviders() {
return container || document.body;
};
// `themeObject` is a module-level singleton (superset-core/src/theme
// index.tsx: `Theme.fromConfig()`), created once with no dark/light
// config, so SupersetThemeProvider always rendered whatever that default
// algorithm was -- it had no way to know about Docusaurus's theme toggle.
// Docusaurus tracks the toggle in React context (useColorMode), so
// mirror it onto the singleton via the toggleDarkMode() method Theme
// already exposes for exactly this purpose.
//
// Use useLayoutEffect (not useEffect) so the sync runs before the
// browser paints. This component only ever mounts client-side (it's
// built inside a BrowserOnly callback), so there's no SSR mismatch
// concern -- and running synchronously before paint avoids a brief
// flash of the singleton's previous palette when a page loads directly
// in dark mode or the toggle fires during route navigation.
function ThemeSync({ children }) {
const { colorMode } = useColorMode();
React.useLayoutEffect(() => {
themeObject.toggleDarkMode(colorMode === 'dark');
}, [colorMode]);
return children;
}
SupersetProviders = ({ children }) => (
<themeObject.SupersetThemeProvider>
<ConfigProvider
getPopupContainer={getPopupContainer}
getTargetContainer={() => document.body}
>
<App>{children}</App>
</ConfigProvider>
</themeObject.SupersetThemeProvider>
<ThemeSync>
<themeObject.SupersetThemeProvider>
<ConfigProvider
getPopupContainer={getPopupContainer}
getTargetContainer={() => document.body}
>
<App>{children}</App>
</ConfigProvider>
</themeObject.SupersetThemeProvider>
</ThemeSync>
);
return SupersetProviders;
} catch (error) {
@@ -133,7 +159,7 @@ function LoadingPlaceholder() {
return (
<div
style={{
border: '1px solid #e8e8e8',
border: '1px solid var(--ifm-color-emphasis-300)',
borderRadius: '4px',
padding: '20px',
marginBottom: '20px',
@@ -141,7 +167,7 @@ function LoadingPlaceholder() {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#999',
color: 'var(--ifm-color-emphasis-600)',
}}
>
Loading component...
@@ -162,7 +188,7 @@ export function StoryExample({ component, props = {} }) {
<div
className="storybook-example"
style={{
border: '1px solid #e8e8e8',
border: '1px solid var(--ifm-color-emphasis-300)',
borderRadius: '4px',
padding: '20px',
marginBottom: '20px',
@@ -172,7 +198,7 @@ export function StoryExample({ component, props = {} }) {
{Component ? (
<Component {...restProps}>{children}</Component>
) : (
<div style={{ color: '#999' }}>
<div style={{ color: 'var(--ifm-color-emphasis-600)' }}>
Component &quot;{String(component)}&quot; not found
</div>
)}
@@ -373,7 +399,7 @@ function StoryWithControlsInner({
<div
className="storybook-example"
style={{
border: '1px solid #e8e8e8',
border: '1px solid var(--ifm-color-emphasis-300)',
borderRadius: '4px',
padding: '20px',
marginBottom: '20px',
@@ -393,7 +419,7 @@ function StoryWithControlsInner({
</Component>
</>
) : (
<div style={{ color: '#999' }}>
<div style={{ color: 'var(--ifm-color-emphasis-600)' }}>
Component &quot;{String(componentToRender)}&quot; not found
</div>
)}
@@ -403,7 +429,7 @@ function StoryWithControlsInner({
<div
className="storybook-controls"
style={{
border: '1px solid #e8e8e8',
border: '1px solid var(--ifm-color-emphasis-300)',
borderRadius: '4px',
padding: '20px',
marginBottom: '20px',
@@ -545,7 +571,7 @@ function ComponentGalleryInner({
if (!Component) {
return (
<div style={{ color: '#999' }}>
<div style={{ color: 'var(--ifm-color-emphasis-600)' }}>
Component &quot;{String(component)}&quot; not found
</div>
);
@@ -556,7 +582,14 @@ function ComponentGalleryInner({
<div className="component-gallery">
{sizes.map(size => (
<div key={size} style={{ marginBottom: 40 }}>
<h4 style={{ marginBottom: 16, color: '#666' }}>{size}</h4>
<h4
style={{
marginBottom: 16,
color: 'var(--ifm-color-emphasis-700)',
}}
>
{size}
</h4>
<div
style={{
display: 'flex',
+8 -8
View File
@@ -69,6 +69,12 @@
"lifecycle": "development",
"description": "Enable Matrixify feature for matrix-style chart layouts"
},
{
"name": "MOBILE_CONSUMPTION_MODE",
"default": false,
"lifecycle": "development",
"description": "Serve a consumption-only mobile experience (dashboards, dashboard list, and home page) on small screens; other views show a \"not supported on mobile\" screen. Authoring features are hidden on mobile when enabled."
},
{
"name": "OPTIMIZE_SQL",
"default": false,
@@ -107,9 +113,9 @@
},
{
"name": "VERSION_HISTORY",
"default": false,
"default": true,
"lifecycle": "development",
"description": "Enables the version history panel on Explore and Dashboard pages. History only accrues while ``ENABLE_VERSIONING_CAPTURE`` is also on; with capture off the panel renders but stays empty."
"description": "Enables the version history panel on Explore and Dashboard pages. History only accrues while ``ENABLE_VERSIONING_CAPTURE`` is also on; with capture off the panel renders but stays empty, so the two ship with matching defaults and should be changed together."
}
],
"testing": [
@@ -221,12 +227,6 @@
"lifecycle": "testing",
"description": "When impersonating a user, use the email prefix instead of username"
},
{
"name": "PLAYWRIGHT_REPORTS_AND_THUMBNAILS",
"default": false,
"lifecycle": "testing",
"description": "Replace Selenium with Playwright for reports and thumbnails. Supports deck.gl visualizations. Requires playwright pip package."
},
{
"name": "RLS_IN_SQLLAB",
"default": false,
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

+7
View File
@@ -11383,6 +11383,13 @@
},
"User3": {
"properties": {
"attributes": {
"additionalProperties": {
"nullable": true
},
"nullable": true,
"type": "object"
},
"first_name": {
"type": "string"
},
+627 -564
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -29,7 +29,7 @@ maintainers:
- name: craig-rueda
email: craig@craigrueda.com
url: https://github.com/craig-rueda
version: 0.22.4 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
version: 0.22.6 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
dependencies:
- name: postgresql
version: 16.7.27
+14 -14
View File
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
# superset
![Version: 0.22.4](https://img.shields.io/badge/Version-0.22.4-informational?style=flat-square)
![Version: 0.22.6](https://img.shields.io/badge/Version-0.22.6-informational?style=flat-square)
Apache Superset is a modern, enterprise-ready business intelligence web application
@@ -205,9 +205,9 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| supersetCeleryBeat.forceReload | bool | `false` | If true, forces deployment to reload on each upgrade |
| supersetCeleryBeat.initContainers | list | a container waiting for postgres | List of init containers |
| supersetCeleryBeat.podAnnotations | object | `{}` | Annotations to be added to supersetCeleryBeat pods |
| supersetCeleryBeat.podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":1,"minAvailable":1}` | Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetCeleryBeat pods |
| supersetCeleryBeat.podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":null,"minAvailable":1}` | Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetCeleryBeat pods |
| supersetCeleryBeat.podDisruptionBudget.enabled | bool | `false` | Whether the pod disruption budget should be created |
| supersetCeleryBeat.podDisruptionBudget.maxUnavailable | int | `1` | If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetCeleryBeat.podDisruptionBudget.maxUnavailable | string | `nil` | If set, minAvailable must be unset (`minAvailable: ~`) - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetCeleryBeat.podDisruptionBudget.minAvailable | int | `1` | If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetCeleryBeat.podLabels | object | `{}` | Labels to be added to supersetCeleryBeat pods |
| supersetCeleryBeat.podSecurityContext | object | `{}` | |
@@ -231,9 +231,9 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| supersetCeleryFlower.livenessProbe.successThreshold | int | `1` | |
| supersetCeleryFlower.livenessProbe.timeoutSeconds | int | `1` | |
| supersetCeleryFlower.podAnnotations | object | `{}` | Annotations to be added to supersetCeleryFlower pods |
| supersetCeleryFlower.podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":1,"minAvailable":1}` | Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetCeleryFlower pods |
| supersetCeleryFlower.podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":null,"minAvailable":1}` | Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetCeleryFlower pods |
| supersetCeleryFlower.podDisruptionBudget.enabled | bool | `false` | Whether the pod disruption budget should be created |
| supersetCeleryFlower.podDisruptionBudget.maxUnavailable | int | `1` | If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetCeleryFlower.podDisruptionBudget.maxUnavailable | string | `nil` | If set, minAvailable must be unset (`minAvailable: ~`) - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetCeleryFlower.podDisruptionBudget.minAvailable | int | `1` | If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetCeleryFlower.podLabels | object | `{}` | Labels to be added to supersetCeleryFlower pods |
| supersetCeleryFlower.podSecurityContext | object | `{}` | |
@@ -285,10 +285,10 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| supersetMcp.livenessProbe.successThreshold | int | `1` | |
| supersetMcp.livenessProbe.timeoutSeconds | int | `3` | |
| supersetMcp.podAnnotations | object | `{}` | Annotations to be added to supersetMcp pods |
| supersetMcp.podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":1,"minAvailable":1}` | Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetMcp pods |
| supersetMcp.podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":null,"minAvailable":1}` | Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetMcp pods |
| supersetMcp.podDisruptionBudget.enabled | bool | `false` | Whether the pod disruption budget should be created |
| supersetMcp.podDisruptionBudget.maxUnavailable | int | `1` | If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/\#specifying-a-poddisruptionbudget |
| supersetMcp.podDisruptionBudget.minAvailable | int | `1` | If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/\#specifying-a-poddisruptionbudget |
| supersetMcp.podDisruptionBudget.maxUnavailable | string | `nil` | If set, minAvailable must be unset (`minAvailable: ~`) - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetMcp.podDisruptionBudget.minAvailable | int | `1` | If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetMcp.podLabels | object | `{}` | Labels to be added to supersetMcp pods |
| supersetMcp.podSecurityContext | object | `{}` | |
| supersetMcp.priorityClassName | string | `nil` | Set priorityClassName for supersetMcp pods |
@@ -341,9 +341,9 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| supersetNode.livenessProbe.successThreshold | int | `1` | |
| supersetNode.livenessProbe.timeoutSeconds | int | `1` | |
| supersetNode.podAnnotations | object | `{}` | Annotations to be added to supersetNode pods |
| supersetNode.podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":1,"minAvailable":1}` | Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetNode pods |
| supersetNode.podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":null,"minAvailable":1}` | Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetNode pods |
| supersetNode.podDisruptionBudget.enabled | bool | `false` | Whether the pod disruption budget should be created |
| supersetNode.podDisruptionBudget.maxUnavailable | int | `1` | If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetNode.podDisruptionBudget.maxUnavailable | string | `nil` | If set, minAvailable must be unset (`minAvailable: ~`) - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetNode.podDisruptionBudget.minAvailable | int | `1` | If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetNode.podLabels | object | `{}` | Labels to be added to supersetNode pods |
| supersetNode.podSecurityContext | object | `{}` | |
@@ -391,9 +391,9 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| supersetWebsockets.livenessProbe.successThreshold | int | `1` | |
| supersetWebsockets.livenessProbe.timeoutSeconds | int | `1` | |
| supersetWebsockets.podAnnotations | object | `{}` | |
| supersetWebsockets.podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":1,"minAvailable":1}` | Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetWebsockets pods |
| supersetWebsockets.podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":null,"minAvailable":1}` | Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetWebsockets pods |
| supersetWebsockets.podDisruptionBudget.enabled | bool | `false` | Whether the pod disruption budget should be created |
| supersetWebsockets.podDisruptionBudget.maxUnavailable | int | `1` | If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetWebsockets.podDisruptionBudget.maxUnavailable | string | `nil` | If set, minAvailable must be unset (`minAvailable: ~`) - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetWebsockets.podDisruptionBudget.minAvailable | int | `1` | If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetWebsockets.podLabels | object | `{}` | |
| supersetWebsockets.podSecurityContext | object | `{}` | |
@@ -448,9 +448,9 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| supersetWorker.livenessProbe.successThreshold | int | `1` | |
| supersetWorker.livenessProbe.timeoutSeconds | int | `60` | |
| supersetWorker.podAnnotations | object | `{}` | Annotations to be added to supersetWorker pods |
| supersetWorker.podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":1,"minAvailable":1}` | Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetWorker pods |
| supersetWorker.podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":null,"minAvailable":1}` | Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetWorker pods |
| supersetWorker.podDisruptionBudget.enabled | bool | `false` | Whether the pod disruption budget should be created |
| supersetWorker.podDisruptionBudget.maxUnavailable | int | `1` | If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetWorker.podDisruptionBudget.maxUnavailable | string | `nil` | If set, minAvailable must be unset (`minAvailable: ~`) - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetWorker.podDisruptionBudget.minAvailable | int | `1` | If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget |
| supersetWorker.podLabels | object | `{}` | Labels to be added to supersetWorker pods |
| supersetWorker.podSecurityContext | object | `{}` | |
+1
View File
@@ -328,6 +328,7 @@ class CeleryConfig:
"superset.tasks.scheduler",
"superset.tasks.thumbnails",
"superset.tasks.cache",
"superset.tasks.slack",
)
broker_connection_retry_on_startup = True
worker_prefetch_multiplier = 10
+1 -1
View File
@@ -20,7 +20,7 @@
{{- with .Values.supersetCeleryBeat.podDisruptionBudget }}
{{- if .enabled -}}
{{- if and .minAvailable .maxUnavailable }}
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
{{- fail "supersetCeleryBeat.podDisruptionBudget: only one of minAvailable or maxUnavailable should be set - unset the other one (set it to null)" }}
{{- end}}
apiVersion: policy/v1
kind: PodDisruptionBudget
+1 -1
View File
@@ -20,7 +20,7 @@
{{- with .Values.supersetCeleryFlower.podDisruptionBudget }}
{{- if .enabled -}}
{{- if and .minAvailable .maxUnavailable }}
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
{{- fail "supersetCeleryFlower.podDisruptionBudget: only one of minAvailable or maxUnavailable should be set - unset the other one (set it to null)" }}
{{- end}}
apiVersion: policy/v1
kind: PodDisruptionBudget
+1 -1
View File
@@ -20,7 +20,7 @@
{{- with .Values.supersetMcp.podDisruptionBudget }}
{{- if .enabled -}}
{{- if and .minAvailable .maxUnavailable }}
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
{{- fail "supersetMcp.podDisruptionBudget: only one of minAvailable or maxUnavailable should be set - unset the other one (set it to null)" }}
{{- end}}
apiVersion: policy/v1
kind: PodDisruptionBudget
+1 -1
View File
@@ -20,7 +20,7 @@
{{- with .Values.supersetWorker.podDisruptionBudget }}
{{- if .enabled -}}
{{- if and .minAvailable .maxUnavailable }}
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
{{- fail "supersetWorker.podDisruptionBudget: only one of minAvailable or maxUnavailable should be set - unset the other one (set it to null)" }}
{{- end}}
apiVersion: policy/v1
kind: PodDisruptionBudget
+1 -1
View File
@@ -20,7 +20,7 @@
{{- with .Values.supersetWebsockets.podDisruptionBudget }}
{{- if .enabled -}}
{{- if and .minAvailable .maxUnavailable }}
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
{{- fail "supersetWebsockets.podDisruptionBudget: only one of minAvailable or maxUnavailable should be set - unset the other one (set it to null)" }}
{{- end}}
apiVersion: policy/v1
kind: PodDisruptionBudget
+1 -1
View File
@@ -20,7 +20,7 @@
{{- with .Values.supersetNode.podDisruptionBudget }}
{{- if .enabled -}}
{{- if and .minAvailable .maxUnavailable }}
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
{{- fail "supersetNode.podDisruptionBudget: only one of minAvailable or maxUnavailable should be set - unset the other one (set it to null)" }}
{{- end}}
apiVersion: policy/v1
kind: PodDisruptionBudget
+87
View File
@@ -0,0 +1,87 @@
#
# 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.
#
suite: pod disruption budgets
templates:
- pdb.yaml
- pdb-worker.yaml
- pdb-beat.yaml
- pdb-flower.yaml
- pdb-ws.yaml
- pdb-mcp.yaml
# The chart must not ship conflicting minAvailable/maxUnavailable defaults: enabling a
# PDB with `enabled: true` alone has to render, since Helm merges user values on top of
# the chart defaults and the templates fail when both fields are set.
tests:
- it: renders no PDB by default
asserts:
- hasDocuments:
count: 0
- it: renders with minAvailable only when just enabled is set
set:
supersetNode.podDisruptionBudget.enabled: true
supersetWorker.podDisruptionBudget.enabled: true
supersetCeleryBeat.podDisruptionBudget.enabled: true
supersetCeleryFlower.podDisruptionBudget.enabled: true
supersetWebsockets.podDisruptionBudget.enabled: true
supersetMcp.podDisruptionBudget.enabled: true
asserts:
- hasDocuments:
count: 1
- isKind:
of: PodDisruptionBudget
- equal:
path: spec.minAvailable
value: 1
- notExists:
path: spec.maxUnavailable
- it: honors an overridden minAvailable
template: pdb.yaml
set:
supersetNode.podDisruptionBudget.enabled: true
supersetNode.podDisruptionBudget.minAvailable: 2
asserts:
- equal:
path: spec.minAvailable
value: 2
- notExists:
path: spec.maxUnavailable
- it: honors maxUnavailable when minAvailable is unset
template: pdb-worker.yaml
set:
supersetWorker.podDisruptionBudget.enabled: true
supersetWorker.podDisruptionBudget.minAvailable: null
supersetWorker.podDisruptionBudget.maxUnavailable: 1
asserts:
- equal:
path: spec.maxUnavailable
value: 1
- notExists:
path: spec.minAvailable
- it: fails when both minAvailable and maxUnavailable are set explicitly
template: pdb.yaml
set:
supersetNode.podDisruptionBudget.enabled: true
supersetNode.podDisruptionBudget.minAvailable: 1
supersetNode.podDisruptionBudget.maxUnavailable: 1
asserts:
- failedTemplate:
errorMessage: "supersetNode.podDisruptionBudget: only one of minAvailable or maxUnavailable should be set - unset the other one (set it to null)"
+13 -13
View File
@@ -410,8 +410,8 @@ supersetNode:
enabled: false
# -- If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
minAvailable: 1
# -- If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
maxUnavailable: 1
# -- If set, minAvailable must be unset (`minAvailable: ~`) - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
maxUnavailable: ~
# -- Startup command
# @default -- See `values.yaml`
@@ -542,8 +542,8 @@ supersetWorker:
enabled: false
# -- If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
minAvailable: 1
# -- If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
maxUnavailable: 1
# -- If set, minAvailable must be unset (`minAvailable: ~`) - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
maxUnavailable: ~
# -- Worker startup command
# @default -- a `celery worker` command
command:
@@ -665,8 +665,8 @@ supersetCeleryBeat:
enabled: false
# -- If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
minAvailable: 1
# -- If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
maxUnavailable: 1
# -- If set, minAvailable must be unset (`minAvailable: ~`) - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
maxUnavailable: ~
# -- Command
# @default -- a `celery beat` command
command:
@@ -751,8 +751,8 @@ supersetCeleryFlower:
enabled: false
# -- If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
minAvailable: 1
# -- If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
maxUnavailable: 1
# -- If set, minAvailable must be unset (`minAvailable: ~`) - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
maxUnavailable: ~
# -- Command
# @default -- a `celery flower` command
command:
@@ -869,8 +869,8 @@ supersetWebsockets:
enabled: false
# -- If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
minAvailable: 1
# -- If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
maxUnavailable: 1
# -- If set, minAvailable must be unset (`minAvailable: ~`) - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
maxUnavailable: ~
ingress:
path: /ws
pathType: Prefix
@@ -999,10 +999,10 @@ supersetMcp:
podDisruptionBudget:
# -- Whether the pod disruption budget should be created
enabled: false
# -- If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/\#specifying-a-poddisruptionbudget
# -- If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
minAvailable: 1
# -- If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/\#specifying-a-poddisruptionbudget
maxUnavailable: 1
# -- If set, minAvailable must be unset (`minAvailable: ~`) - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
maxUnavailable: ~
# -- Command
# @default -- a `superset mcp run` command
command:
+47 -49
View File
@@ -42,7 +42,7 @@ dependencies = [
# ``google-auth`` 2.53+ dropped it, so Superset must declare it
# explicitly to keep fresh ``pip install apache-superset`` working
# without the ``base.txt`` lock file (#40962).
"cachetools>=7.1.6, <8",
"cachetools>=7.1.7, <8",
"celery>=5.6.3, <6.0.0",
"click>=8.4.2",
"click-option-group",
@@ -50,7 +50,7 @@ dependencies = [
"flask-cors>=6.0.5, <7.0",
"croniter>=6.2.4",
"cron-descriptor",
"cryptography>=49.0.0, <50.0.0",
"cryptography>=50.0.0, <51.0.0",
"deprecation>=2.1.0, <2.2.0",
"flask>=2.2.5, <4.0.0",
"flask-appbuilder>=5.2.2, <6.0.0",
@@ -60,27 +60,23 @@ dependencies = [
"flask-login>=0.6.0, < 1.0",
"flask-migrate>=4.1.0, <5.0",
"flask-session>=0.4.0, <1.0",
# Pinned explicitly below 3.0: 3.0.5 resolves without conflict and
# supports both SQLAlchemy 1.4 and 2.0, but real CI runs surfaced a
# structural incompatibility with Superset's current session/app-context
# handling across Celery task boundaries (see PR #42542) -- widespread
# "NoneType has no attribute X" failures and MySQL lock-wait timeouts,
# not just a connection-pool quirk. Needs dedicated investigation, not a
# driver-compat-prep bump; revisit alongside the actual SQLAlchemy 2.0
# core bump (discussion #40273, step 6).
"flask-sqlalchemy>=2.5.1, <3.0",
# Bumped to 3.1.1 alongside the SQLAlchemy 2.0 core bump (discussion
# #40273, step 6), which resolves the session/app-context handling
# across Celery task boundaries that previously blocked this (see
# PR #42542).
"flask-sqlalchemy>=3.1.1, <4.0",
"flask-wtf>=1.3.0, <2.0",
"geopy",
"greenlet<=3.5.4, >=3.5.4",
"gunicorn>=26.0.0, <27; sys_platform != 'win32'",
"hashids>=1.3.1, <2",
# holidays>=0.45 required for security fix
"holidays>=0.99, <1",
"holidays>=0.101, <1",
"humanize",
"isodate",
"jsonpath-ng>=1.8.0, <2",
"Mako>=1.2.2",
"markdown>=3.10.2",
"Mako>=1.4.1",
"markdown>=3.10.3",
# marshmallow 4 compatibility: see superset/marshmallow_compatibility.py for a
# Flask-AppBuilder workaround. Tracking issue:
# https://github.com/apache/superset/issues/33162
@@ -88,7 +84,7 @@ dependencies = [
"marshmallow-union>=0.1.15.post1",
"msgpack>=1.2.0, <1.3",
"nh3>=0.3.5, <0.4",
"numpy>1.23.5, <2.3",
"numpy>=1.23.5, <2.5",
"packaging",
# --------------------------
# pandas and related (wanting pandas[performance] without numba as it's 100+MB and not needed)
@@ -110,15 +106,15 @@ dependencies = [
"PyJWT>=2.4.0, <3.0",
"redis>=5.0.0, <9.0",
"rison>=2.0.1, <3.0",
"selenium>=4.46.0, <5.0",
"shillelagh[gsheetsapi]>=1.4.4, <2.0",
"shillelagh[gsheetsapi]>=1.4.5, <2.0",
"sshtunnel>=0.4.0, <0.5",
"simplejson>=4.1.1",
"slack_sdk>=3.43.0, <4",
"sqlalchemy>=1.4.43, <2", # 1.4.43 adds the python-oracledb (oracle+oracledb) dialect
"sqlalchemy>=2.0.0, <2.1",
"sqlalchemy-continuum>=1.6.0, <2.0.0",
"sqlalchemy-utils>=0.42.1, <0.43", # expanding lowerbound to work with pydoris
"sqlglot>=30.14.0, <31",
"sqlglot>=30.16.0, <31", # 30.16.0 adds Trino inline UDF IF/CASE routine statement parsing
# newer pandas needs 0.9+
"tabulate>=0.10.0, <1.0",
"typing-extensions>=4.16.0, <5",
@@ -131,7 +127,7 @@ dependencies = [
[project.optional-dependencies]
athena = ["pyathena[pandas]>=3.35.2, <4"]
athena = ["pyathena[pandas]>=3.35.4, <4"]
# No SQLAlchemy 2.0 support anywhere in this dialect's ecosystem today: our
# own preset-io/sqlalchemy-aurora-data-api fork is dead since 2021, and the
# more active community fork (cloud-utils/sqlalchemy-aurora-data-api) has an
@@ -143,7 +139,7 @@ bigquery = [
# 1.17.1 is likely the final release: googleapis/python-bigquery-sqlalchemy
# was archived 2026-05-16. Both 1.17.0 and 1.17.1 support SQLAlchemy 1.4/2.0.
"sqlalchemy-bigquery>=1.17.1",
"google-cloud-bigquery>=3.42.2",
"google-cloud-bigquery>=3.42.3",
]
clickhouse = ["clickhouse-connect>=1.6.0, <2.0"]
cockroachdb = ["cockroachdb>=0.3.5, <0.4"]
@@ -164,11 +160,10 @@ databricks = [
datafusion = ["flightsql-dbapi>=0.2.2, <0.3"]
db2 = ["ibm-db-sa<=0.4.4, >=0.4.4"]
denodo = ["denodo-sqlalchemy>=2.0.5,<2.1.0"]
# sqlalchemy-dremio 3.0.5+ hard-pins sqlalchemy~=2.0.41, dropping 1.4; 3.0.4
# is the last dual-compat release. Capped below 3.0.5 for now; widen back to
# <4 in lockstep with Superset's own SQLAlchemy 2.0 core bump (discussion
# #40273), not before.
dremio = ["sqlalchemy-dremio>=1.2.1, <3.0.5"]
# sqlalchemy-dremio 3.0.5+ hard-pins sqlalchemy~=2.0.41, dropping 1.4.
# Widened now that Superset's own SQLAlchemy 2.0 core bump has landed
# (discussion #40273).
dremio = ["sqlalchemy-dremio>=3.0.5, <4"]
# <2 was an artificial ceiling; upstream has no SQLAlchemy version cap and
# 1.1.10 already supports SQLAlchemy 2.0 (added `import_dbapi` in 1.1.7).
drill = ["sqlalchemy-drill>=1.1.10, <3"]
@@ -181,10 +176,9 @@ dynamodb = ["pydynamodb>=0.8.2"]
solr = ["sqlalchemy-solr>=0.2.4.3"]
elasticsearch = ["elasticsearch-dbapi>=0.2.13, <0.3.0"]
# sqlalchemy-exasol cuts hard from SQLAlchemy 1.4-only (<6.0.0) to 2.0-only
# (>=6.0.0) with no dual-compat release. Capped below 6.0.0 for now; bump to
# >=6.0.0,<8.0 in lockstep with Superset's own SQLAlchemy 2.0 core bump
# (discussion #40273), not before.
exasol = ["sqlalchemy-exasol>=2.4.0, <6.0.0"]
# (>=6.0.0) with no dual-compat release. Bumped now that Superset's own
# SQLAlchemy 2.0 core bump has landed (discussion #40273).
exasol = ["sqlalchemy-exasol>=6.0.0, <8.0"]
excel = ["xlrd>=2.0.2, <2.1"]
# Async dashboard "Export Data/Images to Excel": uploads the workbook to S3 and
# emails a pre-signed link. boto3 is imported lazily by superset.utils.s3, so
@@ -199,12 +193,12 @@ fastmcp = [
]
# sqlalchemy-firebird >=2.0.0 unconditionally requires SQLAlchemy 2.0 on
# Python >=3.8 (which covers Superset's >=3.11 floor), with no dual-compat
# release. Capped below 2.0.0 for now; bump to >=2.2.0 in lockstep with
# Superset's own SQLAlchemy 2.0 core bump (discussion #40273), not before.
firebird = ["sqlalchemy-firebird>=0.8.0, <2.0.0"]
# release. Bumped now that Superset's own SQLAlchemy 2.0 core bump has
# landed (discussion #40273).
firebird = ["sqlalchemy-firebird>=2.2.0"]
firebolt = ["firebolt-sqlalchemy>=1.1.2, <2"]
gevent = ["gevent>=26.7.0"]
gsheets = ["shillelagh[gsheetsapi]>=1.4.4, <2"]
gsheets = ["shillelagh[gsheetsapi]>=1.4.5, <2"]
hana = ["hdbcli==2.29.25", "sqlalchemy_hana==3.0.3"]
hive = [
"pyhive[hive_pure_sasl]>=0.7.0",
@@ -227,29 +221,28 @@ ocient = [
# unpinned sqlalchemy>=1.4 declared, but SQLAlchemy 2.0 support is
# unverified. Lower confidence than the other bumps in this PR.
"sqlalchemy-ocient>=3.0.0, <4",
"pyocient>=1.0.15, <4",
"pyocient>=3.9.0, <4",
"shapely",
"geojson",
]
oracle = ["oracledb>=4.0.2, <5"]
parseable = ["sqlalchemy-parseable>=0.1.6,<0.2.0"]
pinot = ["pinotdb>=5.0.0, <10.0.0"]
playwright = ["playwright>=1.61.0, <2"]
playwright = ["playwright>=1.62.0, <2"]
postgres = ["psycopg2-binary==2.9.12"]
presto = ["pyhive[presto]>=0.6.5"]
trino = ["trino>=0.338.0"]
prophet = ["prophet>=1.3.0, <2"]
# sqlalchemy-redshift cuts hard from SQLAlchemy 1.4-only (0.8.x) to 2.0-only
# (>=1.0.0) with no dual-compat release; the existing <0.9 ceiling already
# keeps this on the 1.4-only line. Bump to >=1.0.0 in lockstep with
# Superset's own SQLAlchemy 2.0 core bump (discussion #40273), not before.
redshift = ["sqlalchemy-redshift>=0.8.1, <0.9"]
# (>=1.0.0) with no dual-compat release. Bumped now that Superset's own
# SQLAlchemy 2.0 core bump has landed (discussion #40273).
redshift = ["sqlalchemy-redshift>=1.0.0"]
# No release of sqlalchemy-risingwave has ever supported both SQLAlchemy 1.4
# and 2.0 (version numbers don't track SQLAlchemy compat monotonically); pin
# to the newest 1.4-only release for now. Bump to >=2.0.0 in lockstep with
# Superset's own SQLAlchemy 2.0 core bump (discussion #40273), not before.
risingwave = ["sqlalchemy-risingwave>=1.4.1, <2.0.0"]
shillelagh = ["shillelagh[all]>=1.4.4, <2"]
# and 2.0 (version numbers don't track SQLAlchemy compat monotonically).
# Bumped to the 2.0-only line now that Superset's own SQLAlchemy 2.0 core
# bump has landed (discussion #40273).
risingwave = ["sqlalchemy-risingwave>=2.0.0"]
shillelagh = ["shillelagh[all]>=1.4.5, <2"]
singlestore = ["sqlalchemy-singlestoredb>=1.2.1, <2"]
snowflake = ["snowflake-sqlalchemy>=1.11.0, <2"]
sqlite = ["syntaqlite>=0.7.0,<0.8.0"]
@@ -262,11 +255,11 @@ tdengine = [
"taospy>=2.8.10",
"taos-ws-py>=0.7.0"
]
teradata = ["teradatasql>=20.0.0.63"]
teradata = ["teradatasql>=20.0.0.64"]
thumbnails = [] # deprecated, will be removed in 7.0
vertica = ["sqlalchemy-vertica-python>= 0.6.3, < 0.7"]
netezza = ["nzalchemy>= 11.1.2, < 11.2"]
starrocks = ["starrocks>=1.3.3, <2"]
starrocks = ["starrocks>=1.3.4, <2"]
doris = ["pydoris>=1.2.0, <2.0.0"]
oceanbase = ["oceanbase_py>=0.0.1.2"]
ydb = ["ydb-sqlalchemy>=0.1.22", "ydb-sqlglot-plugin>=0.2.8"]
@@ -286,7 +279,7 @@ development = [
"progress>=1.6.1,<2",
"psutil",
"pyfakefs",
"pyinstrument>=5.1.2,<6",
"pyinstrument>=5.1.3,<6",
"pylint",
"pytest<10.0.0", # cap below the next major until validated; the earlier <8 pin (current_app proxy timing) no longer reproduces
"pytest-asyncio",
@@ -309,7 +302,7 @@ combine_as_imports = true
include_trailing_comma = true
line_length = 88
known_first_party = "superset, apache-superset-core, apache-superset-extensions-cli"
known_third_party = "alembic, apispec, backoff, celery, click, colorama, cron_descriptor, croniter, cryptography, dateutil, deprecation, flask, flask_appbuilder, flask_babel, flask_caching, flask_compress, flask_jwt_extended, flask_login, flask_migrate, flask_sqlalchemy, flask_talisman, flask_testing, flask_wtf, freezegun, geohash, geopy, holidays, humanize, isodate, jinja2, jwt, markdown, markupsafe, marshmallow, marshmallow-union, msgpack, nh3, numpy, pandas, parameterized, parsedatetime, pgsanity, polyline, rison, progress, pyarrow, sqlalchemy_bigquery, pyhive, pyparsing, pytest, pytest_mock, pytz, redis, requests, selenium, setuptools, shillelagh, simplejson, slack, sqlalchemy, sqlalchemy_utils, syntaqlite, typing_extensions, urllib3, werkzeug, wtforms, wtforms_json, yaml"
known_third_party = "alembic, apispec, backoff, celery, click, colorama, cron_descriptor, croniter, cryptography, dateutil, deprecation, flask, flask_appbuilder, flask_babel, flask_caching, flask_compress, flask_jwt_extended, flask_login, flask_migrate, flask_sqlalchemy, flask_talisman, flask_testing, flask_wtf, freezegun, geohash, geopy, holidays, humanize, isodate, jinja2, jwt, markdown, markupsafe, marshmallow, marshmallow-union, msgpack, nh3, numpy, pandas, parameterized, parsedatetime, pgsanity, polyline, rison, progress, pyarrow, sqlalchemy_bigquery, pyhive, pyparsing, pytest, pytest_mock, pytz, redis, requests, setuptools, shillelagh, simplejson, slack, sqlalchemy, sqlalchemy_utils, syntaqlite, typing_extensions, urllib3, werkzeug, wtforms, wtforms_json, yaml"
multi_line_output = 3
order_by_type = false
@@ -535,6 +528,11 @@ authorized_licenses = [
# Seems ok, might need legal review
# https://github.com/urschrei/pypolyline/blob/master/LICENSE.md
polyline = "2"
# NumPy 2.x reports a combined SPDX license expression covering vendored
# code (BSD-3-Clause AND 0BSD AND MIT AND Zlib AND CC0-1.0), all of which
# are permissive OSI-approved licenses; the package itself remains
# BSD-3-Clause. https://github.com/numpy/numpy/blob/main/LICENSE.txt
numpy = "2"
# --------------------------------------------------------------
# TODO REMOVE THESE DEPS FROM CODEBASE
-20
View File
@@ -23,25 +23,5 @@ python_files = *_test.py test_*.py *_tests.py *viz/utils.py
asyncio_mode = auto
# `ignore` is effectively equivalent to `-p no:warnings`.
# Always print RemovedIn20Warning when SQLALCHEMY_WARN_20=1.
# Additionally, raise errors for refactored RemovedIn20Warning cases to prevent regression.
filterwarnings =
ignore
always::sqlalchemy.exc.RemovedIn20Warning
error:Passing a string to Connection.execute\(\) is deprecated:sqlalchemy.exc.RemovedIn20Warning
error:"Query" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
error:"ReportExecutionLog" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
error:"ReportRecipients" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
error:"SavedQuery" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
error:"SqlaTable" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
error:"SqlMetric" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
error:"SSHTunnel" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
error:"TableColumn" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
error:"TaggedObject" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
error:The autoload parameter is deprecated:sqlalchemy.exc.RemovedIn20Warning
error:The connection.execute\(\) method:sqlalchemy.exc.RemovedIn20Warning
error:The current statement is being autocommitted using implicit autocommit:sqlalchemy.exc.RemovedIn20Warning
error:The ``declarative_base\(\)`` function is now available:sqlalchemy.exc.RemovedIn20Warning
error:The Engine.execute\(\) method is considered legacy:sqlalchemy.exc.RemovedIn20Warning
error:The legacy calling style of select\(\) is deprecated:sqlalchemy.exc.RemovedIn20Warning
error:The "whens" argument to case:sqlalchemy.exc.RemovedIn20Warning
+2 -2
View File
@@ -26,9 +26,9 @@ filelock>=3.20.3,<4.0.0
brotli>=1.2.0,<2.0.0
numexpr>=2.9.0
# Security: CVE-2026-34073 (MEDIUM) - Improper Certificate Validation
cryptography>=49.0.0,<50.0.0
cryptography>=50.0.0,<51.0.0
# Security: Snyk - XSS vulnerability in Mako templates
mako>=1.3.11,<2.0.0
mako>=1.4.1,<2.0.0
# Security: CVE-2024-52338 (CRITICAL) - Deserialization of untrusted data in IPC/Parquet readers
pyarrow>=24.0.0,<26.0.0
# Security: CVE-2026-27459 - pyopenssl certificate validation
+14 -44
View File
@@ -20,10 +20,8 @@ attrs==25.3.0
# via
# cattrs
# jsonschema
# outcome
# referencing
# requests-cache
# trio
babel==2.17.0
# via flask-babel
backoff==2.2.1
@@ -46,16 +44,14 @@ cachelib==0.13.0
# via
# flask-caching
# flask-session
cachetools==7.1.6
cachetools==7.1.7
# via apache-superset (pyproject.toml)
cattrs==25.1.1
# via requests-cache
celery==5.6.3
# via apache-superset (pyproject.toml)
certifi==2026.5.20
# via
# requests
# selenium
# via requests
cffi==2.0.0
# via
# cryptography
@@ -88,7 +84,7 @@ cron-descriptor==1.4.5
# via apache-superset (pyproject.toml)
croniter==6.2.4
# via apache-superset (pyproject.toml)
cryptography==49.0.0
cryptography==50.0.0
# via
# -r requirements/base.in
# apache-superset (pyproject.toml)
@@ -148,7 +144,7 @@ flask-migrate==4.1.0
# via apache-superset (pyproject.toml)
flask-session==0.8.0
# via apache-superset (pyproject.toml)
flask-sqlalchemy==2.5.1
flask-sqlalchemy==3.1.1
# via
# apache-superset (pyproject.toml)
# flask-appbuilder
@@ -174,11 +170,9 @@ greenlet==3.5.4
# sqlalchemy
gunicorn==26.0.0
# via apache-superset (pyproject.toml)
h11==0.16.0
# via wsproto
hashids==1.3.1
# via apache-superset (pyproject.toml)
holidays==0.100
holidays==0.102
# via apache-superset (pyproject.toml)
humanize==4.12.3
# via apache-superset (pyproject.toml)
@@ -186,7 +180,6 @@ idna==3.15
# via
# email-validator
# requests
# trio
# url-normalize
isodate==0.7.2
# via
@@ -214,12 +207,12 @@ kombu==5.6.2
# via celery
limits==5.1.0
# via flask-limiter
mako==1.3.12
mako==1.4.1
# via
# -r requirements/base.in
# apache-superset (pyproject.toml)
# alembic
markdown==3.10.2
markdown==3.10.3
# via apache-superset (pyproject.toml)
markdown-it-py==3.0.0
# via rich
@@ -251,7 +244,7 @@ nh3==0.3.6
# via apache-superset (pyproject.toml)
numexpr==2.10.2
# via -r requirements/base.in
numpy==1.26.4
numpy==2.4.6
# via
# apache-superset (pyproject.toml)
# bottleneck
@@ -265,10 +258,6 @@ openpyxl==3.1.5
# via pandas
ordered-set==4.1.0
# via flask-limiter
outcome==1.3.0.post0
# via
# trio
# trio-websocket
packaging==25.0
# via
# apache-superset (pyproject.toml)
@@ -326,14 +315,12 @@ pyjwt==2.13.0
# flask-jwt-extended
pynacl==1.6.2
# via paramiko
pyopenssl==26.3.0
pyopenssl==26.4.0
# via
# -r requirements/base.in
# shillelagh
pyparsing==3.3.2
# via apache-superset (pyproject.toml)
pysocks==1.7.1
# via urllib3
python-calamine==0.8.2
# via pandas
python-dateutil==2.9.0.post0
@@ -357,7 +344,7 @@ pyyaml==6.0.3
# via
# apache-superset (pyproject.toml)
# apispec
redis==8.0.1
redis==8.1.0
# via apache-superset (pyproject.toml)
referencing==0.36.2
# via
@@ -380,11 +367,9 @@ rpds-py==0.25.0
# via
# jsonschema
# referencing
selenium==4.46.0
# via apache-superset (pyproject.toml)
setuptools==80.9.0
# via -r requirements/base.in
shillelagh==1.4.4
shillelagh==1.4.5
# via apache-superset (pyproject.toml)
simplejson==4.1.1
# via apache-superset (pyproject.toml)
@@ -396,11 +381,7 @@ six==1.17.0
# wtforms-json
slack-sdk==3.43.0
# via apache-superset (pyproject.toml)
sniffio==1.3.1
# via trio
sortedcontainers==2.4.0
# via trio
sqlalchemy==1.4.54
sqlalchemy==2.0.51
# via
# apache-superset (pyproject.toml)
# alembic
@@ -418,7 +399,7 @@ sqlalchemy-utils==0.42.1
# apache-superset (pyproject.toml)
# apache-superset-core
# flask-appbuilder
sqlglot==30.15.0
sqlglot==30.16.0
# via
# apache-superset (pyproject.toml)
# apache-superset-core
@@ -426,12 +407,6 @@ sshtunnel==0.4.0
# via apache-superset (pyproject.toml)
tabulate==0.10.0
# via apache-superset (pyproject.toml)
trio==0.33.0
# via
# selenium
# trio-websocket
trio-websocket==0.12.2
# via selenium
typing-extensions==4.16.0
# via
# apache-superset (pyproject.toml)
@@ -443,8 +418,8 @@ typing-extensions==4.16.0
# pydantic-core
# pyopenssl
# referencing
# selenium
# shillelagh
# sqlalchemy
# typing-inspection
typing-inspection==0.4.2
# via pydantic
@@ -461,7 +436,6 @@ urllib3==2.7.0
# -r requirements/base.in
# requests
# requests-cache
# selenium
vine==5.1.0
# via
# amqp
@@ -471,8 +445,6 @@ watchdog==6.0.0
# via apache-superset (pyproject.toml)
wcwidth==0.2.13
# via prompt-toolkit
websocket-client==1.8.0
# via selenium
werkzeug==3.1.6
# via
# -r requirements/base.in
@@ -483,8 +455,6 @@ werkzeug==3.1.6
# flask-login
wrapt==1.17.2
# via deprecated
wsproto==1.2.0
# via trio-websocket
wtforms==3.2.2
# via
# apache-superset (pyproject.toml)
+16 -60
View File
@@ -48,10 +48,8 @@ attrs==25.3.0
# cattrs
# cyclopts
# jsonschema
# outcome
# referencing
# requests-cache
# trio
authlib==1.6.12
# via fastmcp-slim
babel==2.17.0
@@ -101,7 +99,7 @@ cachelib==0.13.0
# -c requirements/base-constraint.txt
# flask-caching
# flask-session
cachetools==7.1.6
cachetools==7.1.7
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -122,7 +120,6 @@ certifi==2026.5.20
# httpcore
# httpx
# requests
# selenium
cffi==2.0.0
# via
# -c requirements/base-constraint.txt
@@ -182,7 +179,7 @@ croniter==6.2.4
# via
# -c requirements/base-constraint.txt
# apache-superset
cryptography==49.0.0
cryptography==50.0.0
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -309,7 +306,7 @@ flask-session==0.8.0
# via
# -c requirements/base-constraint.txt
# apache-superset
flask-sqlalchemy==2.5.1
flask-sqlalchemy==3.1.1
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -363,7 +360,7 @@ google-auth-oauthlib==1.2.1
# via
# pandas-gbq
# pydata-google-auth
google-cloud-bigquery==3.42.2
google-cloud-bigquery==3.43.0
# via
# apache-superset
# pandas-gbq
@@ -400,15 +397,13 @@ gunicorn==26.0.0
# apache-superset
h11==0.16.0
# via
# -c requirements/base-constraint.txt
# httpcore
# uvicorn
# wsproto
hashids==1.3.1
# via
# -c requirements/base-constraint.txt
# apache-superset
holidays==0.100
holidays==0.102
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -434,7 +429,6 @@ idna==3.15
# email-validator
# httpx
# requests
# trio
# url-normalize
importlib-metadata==8.7.0
# via
@@ -516,12 +510,12 @@ limits==5.1.0
# flask-limiter
lz4==4.4.5
# via trino
mako==1.3.12
mako==1.4.1
# via
# -c requirements/base-constraint.txt
# alembic
# apache-superset
markdown==3.10.2
markdown==3.10.3
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -581,7 +575,7 @@ nh3==0.3.6
# apache-superset
nodeenv==1.8.0
# via pre-commit
numpy==1.26.4
numpy==2.4.6
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -619,11 +613,6 @@ ordered-set==4.1.0
# flask-limiter
orjson==3.11.9
# via trino
outcome==1.3.0.post0
# via
# -c requirements/base-constraint.txt
# trio
# trio-websocket
packaging==25.0
# via
# -c requirements/base-constraint.txt
@@ -776,7 +765,7 @@ pygments==2.20.0
# rich
pyhive==0.7.0
# via apache-superset
pyinstrument==5.1.2
pyinstrument==5.1.3
# via apache-superset
pyjwt==2.13.0
# via
@@ -791,7 +780,7 @@ pynacl==1.6.2
# via
# -c requirements/base-constraint.txt
# paramiko
pyopenssl==26.3.0
pyopenssl==26.4.0
# via
# -c requirements/base-constraint.txt
# google-auth
@@ -803,10 +792,6 @@ pyparsing==3.3.2
# matplotlib
pyperclip==1.10.0
# via fastmcp-slim
pysocks==1.7.1
# via
# -c requirements/base-constraint.txt
# urllib3
pytest==7.4.4
# via
# apache-superset
@@ -874,7 +859,7 @@ pyyaml==6.0.3
# fastmcp-slim
# jsonschema-path
# pre-commit
redis==8.0.1
redis==8.1.0
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -934,10 +919,6 @@ s3transfer==0.16.0
# via boto3
secretstorage==3.5.0
# via keyring
selenium==4.46.0
# via
# -c requirements/base-constraint.txt
# apache-superset
semver==3.0.4
# via apache-superset-extensions-cli
setuptools==80.9.0
@@ -948,7 +929,7 @@ setuptools==80.9.0
# pydata-google-auth
# zope-event
# zope-interface
shillelagh==1.4.4
shillelagh==1.4.5
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -968,15 +949,8 @@ slack-sdk==3.43.0
# -c requirements/base-constraint.txt
# apache-superset
sniffio==1.3.1
# via
# -c requirements/base-constraint.txt
# anyio
# trio
sortedcontainers==2.4.0
# via
# -c requirements/base-constraint.txt
# trio
sqlalchemy==1.4.54
# via anyio
sqlalchemy==2.0.51
# via
# -c requirements/base-constraint.txt
# alembic
@@ -1002,7 +976,7 @@ sqlalchemy-utils==0.42.1
# apache-superset
# apache-superset-core
# flask-appbuilder
sqlglot==30.15.0
sqlglot==30.16.0
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -1039,15 +1013,6 @@ tqdm==4.67.1
# prophet
trino==0.338.0
# via apache-superset
trio==0.33.0
# via
# -c requirements/base-constraint.txt
# selenium
# trio-websocket
trio-websocket==0.12.2
# via
# -c requirements/base-constraint.txt
# selenium
typing-extensions==4.16.0
# via
# -c requirements/base-constraint.txt
@@ -1067,8 +1032,8 @@ typing-extensions==4.16.0
# pydantic-core
# pyopenssl
# referencing
# selenium
# shillelagh
# sqlalchemy
# starlette
# typing-inspection
typing-inspection==0.4.2
@@ -1100,7 +1065,6 @@ urllib3==2.7.0
# docker
# requests
# requests-cache
# selenium
uvicorn==0.37.0
# via
# fastmcp-slim
@@ -1124,10 +1088,6 @@ wcwidth==0.2.13
# via
# -c requirements/base-constraint.txt
# prompt-toolkit
websocket-client==1.8.0
# via
# -c requirements/base-constraint.txt
# selenium
websockets==15.0.1
# via fastmcp-slim
werkzeug==3.1.6
@@ -1142,10 +1102,6 @@ wrapt==1.17.2
# via
# -c requirements/base-constraint.txt
# deprecated
wsproto==1.2.0
# via
# -c requirements/base-constraint.txt
# trio-websocket
wtforms==3.2.2
# via
# -c requirements/base-constraint.txt
+1
View File
@@ -42,6 +42,7 @@ RETRYABLE_STATUS_CODES: frozenset[int] = frozenset({429})
PATTERNS = {
"python": [
r"^\.github/workflows/.*python",
r"^\.github/workflows/scheduled-docker-image-refresh\.yml$",
r"^docker-compose-image-tag\.yml$",
r"^tests/",
r"^superset/",
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# 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.
# Computes the `--extra-flags` value passed to `supersetbot docker` for a
# given build preset. Factored out of .github/workflows/docker.yml so the
# PY_VER override logic below can be exercised by an always-on CI check
# (docker.yml's docker-build job only runs when the change detector's
# docker/python/frontend outputs are true, and the PR build matrix never
# includes py311/py312 at all, so a regression here would otherwise go
# unnoticed until the fix actually runs on master) without duplicating -
# and risking drift from - the logic used by the real build step.
#
# supersetbot's "py311"/"py312" presets pin their own --build-arg PY_VER,
# which lands ahead of --extra-flags on the assembled buildx command line;
# docker/buildx keeps the last value for a repeated --build-arg key, so
# appending PY_VER here would override supersetbot's pin and silently make
# "py311"/"py312" build the exact same image as "lean". Every other preset
# gets the override so its build lands on the Dockerfile's own supported
# Python version.
#
# Usage: docker-build-extra-flags.sh <build_preset> <image_tag>
set -euo pipefail
BUILD_PRESET="${1:?usage: docker-build-extra-flags.sh <build_preset> <image_tag>}"
IMAGE_TAG="${2:?usage: docker-build-extra-flags.sh <build_preset> <image_tag>}"
EXTRA_FLAGS="--build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG"
if [ "$BUILD_PRESET" != "py311" ] && [ "$BUILD_PRESET" != "py312" ]; then
EXTRA_FLAGS="--build-arg PY_VER=3.11.14-slim-trixie $EXTRA_FLAGS"
fi
echo "$EXTRA_FLAGS"
+1 -1
View File
@@ -45,7 +45,7 @@ dependencies = [
"isodate>=0.7.0",
"pyarrow>=16.0.0",
"pydantic>=2.8.0",
"sqlalchemy>=1.4.0,<2.0",
"sqlalchemy>=2.0.0,<2.1",
"sqlalchemy-utils>=0.38.0, <0.43", # expanding lowerbound to work with pydoris
"sqlglot>=30.8.0, <31",
"typing-extensions>=4.0.0",
+506
View File
@@ -19,6 +19,7 @@
"@babel/preset-typescript": "7.29.7",
"@types/node": "^25.4.0",
"babel-loader": "^9.1.3",
"jsdom": "^26.1.0",
"tscw-config": "^1.1.2",
"typescript": "^5.9.3",
"vitest": "^4.0.18",
@@ -26,6 +27,27 @@
"webpack-cli": "^5.1.4"
}
},
"node_modules/@asamuzakjp/css-color": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
"integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@csstools/css-calc": "^2.1.3",
"@csstools/css-color-parser": "^3.0.9",
"@csstools/css-parser-algorithms": "^3.0.4",
"@csstools/css-tokenizer": "^3.0.3",
"lru-cache": "^10.4.3"
}
},
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
"license": "ISC"
},
"node_modules/@babel/cli": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.29.7.tgz",
@@ -1656,6 +1678,121 @@
"node": ">=6.9.0"
}
},
"node_modules/@csstools/color-helpers": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
"integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT-0",
"engines": {
"node": ">=18"
}
},
"node_modules/@csstools/css-calc": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
"integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-color-parser": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
"integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"dependencies": {
"@csstools/color-helpers": "^5.1.0",
"@csstools/css-calc": "^2.1.4"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-parser-algorithms": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
"integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-tokenizer": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
"integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz",
@@ -2501,6 +2638,16 @@
"acorn": "^8.14.0"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/ajv": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
@@ -2868,6 +3015,34 @@
"node": ">= 8"
}
},
"node_modules/cssstyle": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
"integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@asamuzakjp/css-color": "^3.2.0",
"rrweb-cssom": "^0.8.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/data-urls": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
"integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
"dev": true,
"license": "MIT",
"dependencies": {
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -2886,6 +3061,13 @@
}
}
},
"node_modules/decimal.js": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
"dev": true,
"license": "MIT"
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -2915,6 +3097,19 @@
"node": ">=10.13.0"
}
},
"node_modules/entities": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/envinfo": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz",
@@ -3316,6 +3511,60 @@
"node": ">= 0.4"
}
},
"node_modules/html-encoding-sniffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
"integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"whatwg-encoding": "^3.1.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/import-local": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
@@ -3438,6 +3687,13 @@
"node": ">=0.10.0"
}
},
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
"dev": true,
"license": "MIT"
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -3498,6 +3754,46 @@
"dev": true,
"license": "MIT"
},
"node_modules/jsdom": {
"version": "26.1.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssstyle": "^4.2.1",
"data-urls": "^5.0.0",
"decimal.js": "^10.5.0",
"html-encoding-sniffer": "^4.0.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"is-potential-custom-element-name": "^1.0.1",
"nwsapi": "^2.2.16",
"parse5": "^7.2.1",
"rrweb-cssom": "^0.8.0",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^5.1.1",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^3.1.1",
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.1.1",
"ws": "^8.18.0",
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"canvas": "^3.0.0"
},
"peerDependenciesMeta": {
"canvas": {
"optional": true
}
}
},
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -3977,6 +4273,13 @@
"node": ">=0.10.0"
}
},
"node_modules/nwsapi": {
"version": "2.2.24",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz",
"integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==",
"dev": true,
"license": "MIT"
},
"node_modules/obug": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
@@ -4033,6 +4336,19 @@
"node": ">=6"
}
},
"node_modules/parse5": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"entities": "^6.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -4142,6 +4458,16 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -4314,6 +4640,33 @@
"@rolldown/binding-win32-x64-msvc": "1.1.3"
}
},
"node_modules/rrweb-cssom": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
"integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
"dev": true,
"license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"license": "MIT"
},
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
"dev": true,
"license": "ISC",
"dependencies": {
"xmlchars": "^2.2.0"
},
"engines": {
"node": ">=v12.22.7"
}
},
"node_modules/schema-utils": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
@@ -4447,6 +4800,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true,
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
@@ -4592,6 +4952,26 @@
"node": ">=14.0.0"
}
},
"node_modules/tldts": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
"integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"tldts-core": "^6.1.86"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
"dev": true,
"license": "MIT"
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -4605,6 +4985,32 @@
"node": ">=8.0"
}
},
"node_modules/tough-cookie": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
"integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"tldts": "^6.1.32"
},
"engines": {
"node": ">=16"
}
},
"node_modules/tr46": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"dev": true,
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/tscw-config": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz",
@@ -4931,6 +5337,19 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
"dev": true,
"license": "MIT",
"dependencies": {
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/watchpack": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
@@ -4944,6 +5363,16 @@
"node": ">=10.13.0"
}
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/webpack": {
"version": "5.105.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz",
@@ -5070,6 +5499,44 @@
"node": ">=10.13.0"
}
},
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
"dev": true,
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-url": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"dev": true,
"license": "MIT",
"dependencies": {
"tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -5114,6 +5581,45 @@
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"node_modules/ws": {
"version": "8.21.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18"
}
},
"node_modules/xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true,
"license": "MIT"
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+1
View File
@@ -43,6 +43,7 @@
"@babel/preset-typescript": "7.29.7",
"@types/node": "^25.4.0",
"babel-loader": "^9.1.3",
"jsdom": "^26.1.0",
"tscw-config": "^1.1.2",
"typescript": "^5.9.3",
"vitest": "^4.0.18",
+213
View File
@@ -0,0 +1,213 @@
/**
* 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.
*/
// @vitest-environment jsdom
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { Switchboard } from "@superset-ui/switchboard";
import { embedDashboard } from "./index";
vi.mock("@superset-ui/switchboard");
function makeFakeJWT(claims: any) {
// not a valid jwt, but close enough for this code
const tokenifiedClaims = Buffer.from(JSON.stringify(claims)).toString(
"base64",
);
return `abc.${tokenifiedClaims}.xyz`;
}
describe("embedDashboard", () => {
let mountPoint: HTMLElement;
let mockSwitchboard: Switchboard;
beforeEach(() => {
mountPoint = document.createElement("div");
document.body.appendChild(mountPoint);
mockSwitchboard = {
emit: vi.fn(),
get: vi.fn(),
start: vi.fn(),
defineMethod: vi.fn(),
} as any;
// Constructor mocks must use `function`, since arrow functions cannot be
// invoked with `new`.
vi.mocked(Switchboard).mockImplementation(function () {
return mockSwitchboard;
} as any);
// Mock MessageChannel API
globalThis.MessageChannel = vi.fn(function (this: any) {
this.port1 = {};
this.port2 = {};
}) as any;
// Mock iframe load event and sandbox
const originalCreateElement = document.createElement.bind(document);
vi.spyOn(document, "createElement").mockImplementation(tagName => {
const element = originalCreateElement(tagName);
if (tagName === "iframe") {
// Mock sandbox DOMTokenList
(element as any).sandbox = {
add: vi.fn(),
};
// Mock contentWindow for postMessage
Object.defineProperty(element, "contentWindow", {
writable: true,
value: {
postMessage: vi.fn(),
},
});
setTimeout(() => {
element.dispatchEvent(new Event("load"));
}, 0);
}
return element;
});
});
afterEach(() => {
document.body.removeChild(mountPoint);
vi.restoreAllMocks();
});
test("setDataMask sends dataMask to iframe", async () => {
const fakeToken = makeFakeJWT({ exp: Date.now() / 1000 + 300 });
const mockFetchGuestToken = vi.fn().mockResolvedValue(fakeToken);
const testDataMask = {
"NATIVE_FILTER-1": {
filterState: {
value: ["value1", "value2"],
},
},
};
const dashboard = await embedDashboard({
id: "test-id",
supersetDomain: "https://superset.example.com",
mountPoint,
fetchGuestToken: mockFetchGuestToken,
});
dashboard.setDataMask(testDataMask);
expect(mockSwitchboard.get).toHaveBeenCalledWith("setDataMask", {
dataMask: testDataMask,
});
});
test("setDataMask sends empty dataMask", async () => {
const fakeToken = makeFakeJWT({ exp: Date.now() / 1000 + 300 });
const mockFetchGuestToken = vi.fn().mockResolvedValue(fakeToken);
const emptyDataMask = {};
const dashboard = await embedDashboard({
id: "test-id",
supersetDomain: "https://superset.example.com",
mountPoint,
fetchGuestToken: mockFetchGuestToken,
});
dashboard.setDataMask(emptyDataMask);
expect(mockSwitchboard.get).toHaveBeenCalledWith("setDataMask", {
dataMask: emptyDataMask,
});
});
test("setDataMask drops the change-trigger flags observeDataMask adds", async () => {
const fakeToken = makeFakeJWT({ exp: Date.now() / 1000 + 300 });
const mockFetchGuestToken = vi.fn().mockResolvedValue(fakeToken);
const observedMask = {
"NATIVE_FILTER-1": {
filterState: {
value: ["CA"],
},
},
crossFiltersChanged: false,
nativeFiltersChanged: true,
};
const dashboard = await embedDashboard({
id: "test-id",
supersetDomain: "https://superset.example.com",
mountPoint,
fetchGuestToken: mockFetchGuestToken,
});
dashboard.setDataMask(observedMask);
expect(mockSwitchboard.get).toHaveBeenCalledWith("setDataMask", {
dataMask: {
"NATIVE_FILTER-1": observedMask["NATIVE_FILTER-1"],
},
});
});
test("setDataMask sends complex dataMask with multiple filters", async () => {
const fakeToken = makeFakeJWT({ exp: Date.now() / 1000 + 300 });
const mockFetchGuestToken = vi.fn().mockResolvedValue(fakeToken);
const complexDataMask = {
"NATIVE_FILTER-1": {
filterState: {
value: ["CA", "NY"],
},
},
"NATIVE_FILTER-2": {
filterState: {
value: [2023, 2024],
},
},
};
const dashboard = await embedDashboard({
id: "test-id",
supersetDomain: "https://superset.example.com",
mountPoint,
fetchGuestToken: mockFetchGuestToken,
});
dashboard.setDataMask(complexDataMask);
expect(mockSwitchboard.get).toHaveBeenCalledWith("setDataMask", {
dataMask: complexDataMask,
});
});
test("setDataMask rejects when the embedded page does not support it", async () => {
const fakeToken = makeFakeJWT({ exp: Date.now() / 1000 + 300 });
const mockFetchGuestToken = vi.fn().mockResolvedValue(fakeToken);
vi.mocked(mockSwitchboard.get).mockRejectedValue(
new Error('Method "setDataMask" is not defined'),
);
const dashboard = await embedDashboard({
id: "test-id",
supersetDomain: "https://superset.example.com",
mountPoint,
fetchGuestToken: mockFetchGuestToken,
});
await expect(dashboard.setDataMask({})).rejects.toThrow(
'Method "setDataMask" is not defined',
);
});
});
+22
View File
@@ -115,6 +115,12 @@ export type EmbeddedDashboard = {
getActiveTabs: () => Promise<string[]>;
observeDataMask: (callbackFn: ObserveDataMaskCallbackFn) => void;
getDataMask: () => Promise<Record<string, any>>;
/**
* Applies a data mask to the dashboard.
* Rejects if the embedded Superset page does not support `setDataMask`,
* so a version mismatch surfaces instead of silently doing nothing.
*/
setDataMask: (dataMask: Record<string, any>) => Promise<void>;
getChartStates: () => Promise<Record<string, any>>;
getChartDataPayloads: (params?: {
chartId?: number;
@@ -355,6 +361,21 @@ export async function embedDashboard({
ourPort.get<string>("getDashboardPermalink", { anchor });
const getActiveTabs = () => ourPort.get<string[]>("getActiveTabs");
const getDataMask = () => ourPort.get<Record<string, any>>("getDataMask");
// `observeDataMask` hands the host a mask with the change-trigger booleans
// mixed in, so feeding that payload straight back into `setDataMask` is a
// natural thing for a host to do. Keep only the entries that look like a
// filter's mask, so those flags never reach the dashboard as filter ids.
// Sent with `get` rather than `emit` so the iframe acknowledges the call:
// an embedded page that predates `setDataMask` replies with an error instead
// of dropping the message silently.
const setDataMask = (dataMask: Record<string, any>) =>
ourPort.get<void>("setDataMask", {
dataMask: Object.fromEntries(
Object.entries(dataMask).filter(
([, mask]) => typeof mask === "object" && mask !== null,
),
),
});
const getChartStates = () =>
ourPort.get<Record<string, any>>("getChartStates");
const getChartDataPayloads = (params?: { chartId?: number }) =>
@@ -396,6 +417,7 @@ export async function embedDashboard({
getActiveTabs,
observeDataMask,
getDataMask,
setDataMask,
getChartStates,
getChartDataPayloads,
setThemeConfig,
@@ -1,82 +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 { SAMPLE_DASHBOARD_1 } from 'cypress/utils/urls';
import { drag } from 'cypress/utils';
import { interceptGet } from './utils';
import { interceptFiltering as interceptCharts } from '../explore/utils';
function editDashboard() {
cy.getBySel('edit-dashboard-button').click();
}
function dragComponent(
component = 'Unicode Cloud',
target = 'card-title',
withFiltering = true,
) {
if (withFiltering) {
cy.getBySel('dashboard-charts-filter-search-input').type(component, {
force: true,
});
cy.wait('@filtering');
}
cy.wait(500);
drag(`[data-test="${target}"]`, component).to(
'[data-test="grid-content"] [data-test="dragdroppable-object"]',
);
}
function visitEdit(sampleDashboard = SAMPLE_DASHBOARD_1) {
interceptCharts();
interceptGet();
if (sampleDashboard === SAMPLE_DASHBOARD_1) {
cy.createSampleDashboards([0]);
}
cy.visit(sampleDashboard);
cy.wait('@get');
editDashboard();
cy.get('.grid-container').should('exist');
cy.wait('@filtering');
cy.wait(500);
}
describe('Dashboard edit', () => {
describe('Components', () => {
beforeEach(() => {
visitEdit();
});
it('should add charts', () => {
cy.get('body').then($body => {
if ($body.find('.ant-modal-wrap').length > 0) {
cy.get('body').type('{esc}', { force: true });
cy.wait(1000);
cy.get('.ant-modal-close').click({ force: true });
cy.wait(500);
}
});
cy.get('input[type="checkbox"]').scrollIntoView();
cy.get('input[type="checkbox"]').click({ force: true });
dragComponent();
cy.getBySel('dashboard-component-chart-holder').should('have.length', 1);
});
});
});
@@ -138,6 +138,7 @@ export function prepareDashboardFilters(
chart_customization_config: [],
timed_refresh_immune_slices: [],
expanded_slices: {},
expand_all_slices: false,
refresh_frequency: 0,
color_scheme: '',
label_colors: {},
@@ -79,34 +79,6 @@ export function waitForChartLoad(chart: ChartSpec) {
});
}
/**
* Drag an element and drop it to another element.
* Usage:
* drag(source).to(target);
*/
export function drag(selector: string, content: string | number | RegExp) {
const dataTransfer = { data: {} };
return {
to(target: string | Cypress.Chainable) {
cy.get('.dragdroppable')
.contains(selector, content)
.trigger('mousedown', { which: 1, force: true });
cy.get('.dragdroppable')
.contains(selector, content)
.trigger('dragstart', { dataTransfer, force: true });
cy.get('.dragdroppable')
.contains(selector, content)
.trigger('drag', { force: true });
(typeof target === 'string' ? cy.get(target) : target)
.trigger('dragover', { dataTransfer, force: true })
.trigger('drop', { dataTransfer, force: true })
.trigger('dragend', { dataTransfer, force: true })
.trigger('mouseup', { which: 1, force: true });
},
};
}
export function resize(selector: string) {
return {
to(cordX: number, cordY: number) {
+8 -7
View File
@@ -3215,16 +3215,17 @@
"integrity": "sha512-Fc8Ne62jJlKHiG/ajlonC4Sd66Pq68fFwK4ihJGNZpGqboc324SQk+lRvMzpPRuJOmfrJefdG8/7JdWX4bzJ2Q=="
},
"node_modules/brace-expansion": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
"node": "20 || >=22"
}
},
"node_modules/browserslist": {
@@ -11184,9 +11185,9 @@
"integrity": "sha512-Fc8Ne62jJlKHiG/ajlonC4Sd66Pq68fFwK4ihJGNZpGqboc324SQk+lRvMzpPRuJOmfrJefdG8/7JdWX4bzJ2Q=="
},
"brace-expansion": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
"dev": true,
"peer": true,
"requires": {
@@ -36,6 +36,9 @@
"cypress": {
"form-data": "^2.3.4"
},
"minimatch@>=10": {
"brace-expansion": ">=5.0.8"
},
"qs": "^6.14.2",
"uuid": "^11.1.1"
},
+1 -1
View File
@@ -77,7 +77,7 @@ module.exports = {
// @ant-design/colors and @ant-design/fast-color are allowed through because
// @ant-design/icons >= 6.3 deep-imports the ESM build of @ant-design/colors
// from its CJS output, so babel-jest must transform those files.
'node_modules/(?!@ant-design/(colors|fast-color)|@formatjs/.*|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/*.|@x0k/.*|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-.*|hastscript|refractor|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|character-reference-invalid|is-alphanumerical|is-alphabetical|is-decimal|is-hexadecimal|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|storybook/*.|json-stringify-pretty-compact|@x0k/json-schema-merge)',
'node_modules/(?!@ant-design/(colors|fast-color)|@formatjs/.*|d3-(array|interpolate|color|time|scale|time-format|format|selection)|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/*.|@x0k/.*|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-.*|hastscript|refractor|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|character-reference-invalid|is-alphanumerical|is-alphabetical|is-decimal|is-hexadecimal|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|storybook/*.|json-stringify-pretty-compact|@x0k/json-schema-merge)',
],
preset: 'ts-jest',
transform: {
+13
View File
@@ -276,6 +276,19 @@
"test*WithInitialValues"
]
}
],
// === ESLint rules ===
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "lodash",
"message": "Please use tree-shakeable lodash-es instead"
}
]
}
]
},
"overrides": [
+1445 -2242
View File
File diff suppressed because it is too large Load Diff
+38 -38
View File
@@ -87,7 +87,7 @@
"test": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80% --silent",
"test-loud": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80%",
"type": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" tsc --noEmit",
"update-maps": "cd plugins/legacy-plugin-chart-country-map/scripts && jupyter nbconvert --to notebook --execute --inplace --allow-errors --ExecutePreprocessor.timeout=1200 'Country Map GeoJSON Generator.ipynb'",
"update-maps": "cd plugins/plugin-chart-country-map/scripts && jupyter nbconvert --to notebook --execute --inplace --allow-errors --ExecutePreprocessor.timeout=1200 'Country Map GeoJSON Generator.ipynb'",
"validate-release": "../RELEASING/validate_this_release.sh"
},
"dependencies": {
@@ -110,7 +110,7 @@
"@fontsource/fira-code": "^5.3.0",
"@fontsource/ibm-plex-mono": "^5.3.0",
"@fontsource/inter": "^5.3.0",
"@googleapis/sheets": "^13.0.2",
"@googleapis/sheets": "^14.0.0",
"@great-expectations/jsonforms-antd-renderers": "^2.2.10",
"@jsonforms/core": "^3.7.0",
"@jsonforms/react": "^3.7.0",
@@ -128,24 +128,22 @@
"@scarf/scarf": "^1.4.0",
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
"@superset-ui/core": "file:./packages/superset-ui-core",
"@superset-ui/legacy-plugin-chart-calendar": "file:./plugins/legacy-plugin-chart-calendar",
"@superset-ui/legacy-plugin-chart-chord": "file:./plugins/legacy-plugin-chart-chord",
"@superset-ui/legacy-plugin-chart-country-map": "file:./plugins/legacy-plugin-chart-country-map",
"@superset-ui/legacy-plugin-chart-horizon": "file:./plugins/legacy-plugin-chart-horizon",
"@superset-ui/legacy-plugin-chart-paired-t-test": "file:./plugins/legacy-plugin-chart-paired-t-test",
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "file:./plugins/legacy-plugin-chart-parallel-coordinates",
"@superset-ui/legacy-plugin-chart-partition": "file:./plugins/legacy-plugin-chart-partition",
"@superset-ui/legacy-plugin-chart-rose": "file:./plugins/legacy-plugin-chart-rose",
"@superset-ui/legacy-plugin-chart-world-map": "file:./plugins/legacy-plugin-chart-world-map",
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
"@superset-ui/plugin-chart-ag-grid-table": "file:./plugins/plugin-chart-ag-grid-table",
"@superset-ui/plugin-chart-calendar": "file:./plugins/plugin-chart-calendar",
"@superset-ui/plugin-chart-cartodiagram": "file:./plugins/plugin-chart-cartodiagram",
"@superset-ui/plugin-chart-chord": "file:./plugins/plugin-chart-chord",
"@superset-ui/plugin-chart-country-map": "file:./plugins/plugin-chart-country-map",
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
"@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
"@superset-ui/plugin-chart-horizon": "file:./plugins/plugin-chart-horizon",
"@superset-ui/plugin-chart-paired-t-test": "file:./plugins/plugin-chart-paired-t-test",
"@superset-ui/plugin-chart-parallel-coordinates": "file:./plugins/plugin-chart-parallel-coordinates",
"@superset-ui/plugin-chart-partition": "file:./plugins/plugin-chart-partition",
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
"@superset-ui/plugin-chart-point-cluster-map": "file:./plugins/plugin-chart-point-cluster-map",
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
"@superset-ui/plugin-chart-world-map": "file:./plugins/plugin-chart-world-map",
"@superset-ui/preset-chart-deckgl": "file:./plugins/preset-chart-deckgl",
"@superset-ui/switchboard": "file:./packages/superset-ui-switchboard",
"@types/d3-format": "^3.0.1",
@@ -158,9 +156,9 @@
"@visx/scale": "^4.0.0",
"@visx/tooltip": "^4.0.0",
"@visx/xychart": "^4.0.0",
"ag-grid-community": "36.0.2",
"ag-grid-react": "36.0.2",
"antd": "^6.5.2",
"ag-grid-community": "36.1.0",
"ag-grid-react": "36.1.0",
"antd": "^6.5.4",
"chrono-node": "^2.10.1",
"classnames": "^2.2.5",
"content-disposition": "^2.0.1",
@@ -178,8 +176,8 @@
"geostyler-openlayers-parser": "^5.7.1",
"geostyler-style": "11.0.2",
"geostyler-wfs-parser": "^3.0.1",
"google-auth-library": "^10.9.1",
"immer": "^11.1.15",
"google-auth-library": "^11.0.0",
"immer": "^11.1.16",
"interweave": "^13.1.1",
"jquery": "^4.0.0",
"js-levenshtein": "^1.1.6",
@@ -187,15 +185,15 @@
"json-stringify-pretty-compact": "^4.0.0",
"lodash": "^4.18.1",
"lodash-es": "^4.18.1",
"mapbox-gl": "^3.27.0",
"markdown-to-jsx": "^9.9.0",
"mapbox-gl": "^3.28.1",
"markdown-to-jsx": "^9.10.2",
"match-sorter": "^8.3.0",
"memoize-one": "^6.0.0",
"mousetrap": "^1.6.5",
"mustache": "^4.2.0",
"nanoid": "^6.0.0",
"nanoid": "^6.0.1",
"ol": "^10.10.0",
"query-string": "9.4.1",
"query-string": "9.5.0",
"re-resizable": "^6.11.2",
"react": "^18.3.0",
"react-arborist": "^3.16.0",
@@ -205,7 +203,7 @@
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^18.3.0",
"react-google-recaptcha": "^3.1.0",
"react-intersection-observer": "^10.1.0",
"react-intersection-observer": "^11.0.0",
"react-json-tree": "^0.20.0",
"react-lines-ellipsis": "^0.16.1",
"react-loadable": "^5.5.0",
@@ -218,7 +216,7 @@
"react-table": "^7.8.0",
"react-transition-group": "^4.4.5",
"react-virtualized-auto-sizer": "^1.0.26",
"react-window": "^1.8.10",
"react-window": "^2.3.0",
"redux": "^4.2.1",
"redux-localstorage": "^0.4.1",
"redux-thunk": "^2.1.0",
@@ -252,16 +250,16 @@
"@babel/register": "^7.29.7",
"@babel/runtime": "^7.29.7",
"@babel/runtime-corejs3": "^7.29.7",
"@babel/types": "^7.29.7",
"@babel/types": "^7.29.8",
"@emotion/babel-plugin": "^11.13.5",
"@emotion/jest": "^11.14.2",
"@formatjs/intl-durationformat": "^0.10.18",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@playwright/test": "^1.61.1",
"@playwright/test": "^1.62.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
"@storybook/addon-docs": "10.5.5",
"@storybook/addon-links": "10.5.5",
"@storybook/react-webpack5": "10.5.5",
"@storybook/addon-docs": "10.5.7",
"@storybook/addon-links": "10.5.7",
"@storybook/react-webpack5": "10.5.7",
"@storybook/test-runner": "0.24.4",
"@svgr/webpack": "^8.1.0",
"@swc/core": "^1.15.47",
@@ -286,19 +284,18 @@
"@types/react-redux": "^7.1.10",
"@types/react-router-dom": "^5.3.3",
"@types/react-transition-group": "^4.4.12",
"@types/react-window": "^1.8.8",
"@types/redux-localstorage": "^1.0.8",
"@types/redux-mock-store": "^1.0.6",
"@types/rison": "0.1.0",
"@types/tinycolor2": "^1.4.3",
"@types/unzipper": "^0.10.11",
"@typescript-eslint/eslint-plugin": "^8.65.0",
"@typescript-eslint/eslint-plugin": "^8.66.0",
"@typescript-eslint/parser": "^8.63.0",
"babel-jest": "^30.4.1",
"babel-loader": "^10.1.1",
"babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"baseline-browser-mapping": "^2.11.7",
"baseline-browser-mapping": "^2.11.12",
"cheerio": "1.2.0",
"concurrently": "^10.0.4",
"copy-webpack-plugin": "^14.0.0",
@@ -310,12 +307,12 @@
"eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings",
"eslint-plugin-icons": "file:eslint-rules/eslint-plugin-icons",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest-dom": "^5.6.0",
"eslint-plugin-jest-dom": "^5.10.1",
"eslint-plugin-lodash": "^8.0.0",
"eslint-plugin-no-only-tests": "^3.4.0",
"eslint-plugin-react-prefer-function-component": "^5.0.0",
"eslint-plugin-react-you-might-not-need-an-effect": "^1.0.1",
"eslint-plugin-storybook": "10.5.5",
"eslint-plugin-storybook": "10.5.7",
"eslint-plugin-testing-library": "^7.16.2",
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
"fetch-mock": "^12.6.0",
@@ -329,13 +326,13 @@
"jest-websocket-mock": "^2.5.0",
"js-yaml-loader": "^1.2.2",
"jsdom": "^30.0.1",
"lerna": "^9.0.4",
"lerna": "^10.0.0",
"lightningcss": "^1.33.0",
"mini-css-extract-plugin": "^2.10.2",
"minimizer-webpack-plugin": "^5.6.1",
"open-cli": "^9.0.0",
"oxfmt": "^0.61.0",
"oxlint": "^1.76.0",
"oxfmt": "^0.62.0",
"oxlint": "^1.77.0",
"po2json": "^0.4.5",
"postcss-styled-syntax": "^0.7.2",
"process": "^0.11.10",
@@ -346,13 +343,13 @@
"source-map": "^0.8.0",
"source-map-support": "^0.5.21",
"speed-measure-webpack-plugin": "^1.6.0",
"storybook": "10.5.5",
"storybook": "10.5.7",
"style-loader": "^4.0.0",
"stylelint": "^17.14.1",
"swc-loader": "^0.2.7",
"ts-jest": "^29.4.12",
"tscw-config": "^1.1.2",
"tsx": "^4.23.1",
"tsx": "^4.23.10",
"typescript": "5.4.5",
"unzipper": "^0.12.5",
"wait-on": "^9.1.0",
@@ -415,6 +412,9 @@
"lerna": {
"js-yaml": "^4.3.0"
},
"minimatch@>=10": {
"brace-expansion": ">=5.0.8"
},
"nwsapi": "^2.2.13",
"puppeteer": "^22.4.1",
"tar": "^7.5.16",
@@ -106,10 +106,9 @@
"@testing-library/jest-dom": "*",
"@testing-library/react": "^15.0.0",
"@testing-library/user-event": "*",
"@types/lodash": "^4.17.24",
"@types/lodash": "^4.17.25",
"@types/react": "*",
"@types/react-loadable": "*",
"@types/react-window": "^1.8.8",
"@types/tinycolor2": "*",
"typescript": "^5.0.0"
},
@@ -0,0 +1,205 @@
/**
* 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 { useLayoutEffect, type ReactNode } from 'react';
import { render, screen, act } from '@testing-library/react';
import { theme as antdThemeImport } from 'antd';
import { Theme } from './Theme';
// SupersetThemeProvider stores theme state via React.useState, then
// registers a listener (in a useLayoutEffect, on mount) that calls
// setThemeState whenever a *later* call to setConfig/toggleDarkMode runs
// on the same Theme instance. Every provider currently mounted from that
// instance listens independently, so toggling the instance updates all of
// them, not just the most recently rendered one. Consumers
// (docs/src/components/StorybookWrapper.jsx in particular) rely on this:
// they call toggleDarkMode() from outside, on one or more already-mounted
// providers sharing a single Theme instance, expecting it to propagate to
// all of them.
//
// The probe below reads the theme via antd's theme.useToken() -- the same
// context-consumption path every real antd component (Button, Input, ...)
// uses internally -- rather than reading themeObject.theme directly off the
// singleton. That distinction matters: React bails out of re-rendering a
// child whose element reference didn't change (the common "static children
// prop" case, true here since <Probe /> is passed once and never
// recreated), UNLESS that child consumes a React Context whose value
// changed, which bypasses the bail-out. A probe reading the plain object
// directly would misleadingly appear "not updated" even though every real
// themed component downstream re-renders correctly.
function makeProbe() {
let renderCount = 0;
let lastColorBgBase: string | undefined;
function Probe() {
const { token } = antdThemeImport.useToken();
renderCount += 1;
lastColorBgBase = token.colorBgBase;
return <div data-test="probe" />;
}
return {
Probe,
getRenderCount: () => renderCount,
getLastColorBgBase: () => lastColorBgBase,
};
}
test('an already-mounted SupersetThemeProvider re-renders context-consuming children when toggleDarkMode is called on the same instance', () => {
const themeObject = Theme.fromConfig();
const { Probe, getRenderCount, getLastColorBgBase } = makeProbe();
render(
<themeObject.SupersetThemeProvider>
<Probe />
</themeObject.SupersetThemeProvider>,
);
expect(screen.getByTestId('probe')).toBeTruthy();
const rendersBefore = getRenderCount();
const tokenBefore = getLastColorBgBase();
act(() => {
themeObject.toggleDarkMode(true);
});
expect(getRenderCount()).toBeGreaterThan(rendersBefore);
expect(getLastColorBgBase()).not.toBe(tokenBefore);
});
test('toggleDarkMode updates every concurrently mounted provider for the same theme instance', () => {
const themeObject = Theme.fromConfig();
const first = makeProbe();
const second = makeProbe();
render(
<>
<themeObject.SupersetThemeProvider>
<first.Probe />
</themeObject.SupersetThemeProvider>
<themeObject.SupersetThemeProvider>
<second.Probe />
</themeObject.SupersetThemeProvider>
</>,
);
const firstTokenBefore = first.getLastColorBgBase();
const secondTokenBefore = second.getLastColorBgBase();
act(() => {
themeObject.toggleDarkMode(true);
});
// Both providers share the same Theme instance, so both must pick up the
// toggle -- not just whichever one rendered last.
expect(first.getLastColorBgBase()).not.toBe(firstTokenBefore);
expect(second.getLastColorBgBase()).not.toBe(secondTokenBefore);
});
test('a toggleDarkMode call on a different theme instance does not affect a mounted provider', () => {
const mounted = Theme.fromConfig();
const other = Theme.fromConfig();
const { Probe, getRenderCount, getLastColorBgBase } = makeProbe();
render(
<mounted.SupersetThemeProvider>
<Probe />
</mounted.SupersetThemeProvider>,
);
const rendersBefore = getRenderCount();
const tokenBefore = getLastColorBgBase();
act(() => {
other.toggleDarkMode(true);
});
// Each Theme instance owns its own set of provider listeners; toggling a
// *different* instance must not re-render a provider mounted from another.
expect(getRenderCount()).toBe(rendersBefore);
expect(getLastColorBgBase()).toBe(tokenBefore);
});
test('a toggleDarkMode call after a provider unmounts does not throw and no longer updates it', () => {
const themeObject = Theme.fromConfig();
const { Probe, getRenderCount, getLastColorBgBase } = makeProbe();
const { unmount } = render(
<themeObject.SupersetThemeProvider>
<Probe />
</themeObject.SupersetThemeProvider>,
);
const rendersBefore = getRenderCount();
const tokenBefore = getLastColorBgBase();
unmount();
expect(() => {
act(() => {
themeObject.toggleDarkMode(true);
});
}).not.toThrow();
// The unmounted provider's listener was deregistered, so it shouldn't
// have re-rendered (or updated) in response to the toggle.
expect(getRenderCount()).toBe(rendersBefore);
expect(getLastColorBgBase()).toBe(tokenBefore);
});
test('a toggleDarkMode call from an ancestor layout effect during the initial commit is not dropped', () => {
// Regression harness for the initial-mount race: StorybookWrapper.jsx
// toggles the singleton from its own layout effect (ThemeSync) as soon
// as a demo mounts. SupersetThemeProvider must have its listener
// registered *before* that ancestor effect fires, which only holds if
// registration itself runs in a layout effect -- layout effects fire
// bottom-up, so this component (nested inside the toggling ancestor)
// registers first. If that registration ever regresses to a plain
// useEffect, it runs after the ancestor's toggle (passive effects are
// deferred until after all layout effects), the notification is
// dropped, and this probe would still show the pre-toggle palette.
const lightBaseline = Theme.fromConfig();
const baseline = makeProbe();
render(
<lightBaseline.SupersetThemeProvider>
<baseline.Probe />
</lightBaseline.SupersetThemeProvider>,
);
const lightColorBgBase = baseline.getLastColorBgBase();
const themeObject = Theme.fromConfig();
const { Probe, getLastColorBgBase } = makeProbe();
function AncestorToggler({ children }: { children: ReactNode }) {
useLayoutEffect(() => {
themeObject.toggleDarkMode(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return children;
}
render(
<AncestorToggler>
<themeObject.SupersetThemeProvider>
<Probe />
</themeObject.SupersetThemeProvider>
</AncestorToggler>,
);
expect(getLastColorBgBase()).not.toBe(lightColorBgBase);
});
@@ -160,6 +160,43 @@ test('Theme.setConfig correctly applies algorithm changes', () => {
expect(serialized.algorithm).toBe(ThemeAlgorithm.DARK);
});
test('Theme.setConfig with baseTheme merges the config over the base theme tokens', () => {
const baseTheme: AnyThemeConfig = {
token: { colorPrimary: '#111111', colorError: '#ff0000' },
};
const theme = Theme.fromConfig();
theme.setConfig({ token: { colorPrimary: '#0000ff' } }, baseTheme);
// Config wins for colorPrimary; the base theme fills the untouched colorError.
expect(theme.theme.colorPrimary).toBe('#0000ff');
expect(theme.theme.colorError).toBe('#ff0000');
});
test('Theme.setConfig with baseTheme keeps the base theme ECharts overrides', () => {
const baseTheme = {
token: { colorPrimary: '#111111' },
echartsOptionsOverrides: { backgroundColor: '#123456' },
echartsOptionsOverridesByChartType: {
pie: { itemStyle: { borderWidth: 2 } },
},
} as AnyThemeConfig & {
echartsOptionsOverrides: Record<string, unknown>;
echartsOptionsOverridesByChartType: Record<string, unknown>;
};
const theme = Theme.fromConfig();
// In-place update whose config sets no ECharts overrides: the base theme's
// overrides must survive, the same way its tokens do.
theme.setConfig({ token: { colorPrimary: '#0000ff' } }, baseTheme);
expect(theme.theme.echartsOptionsOverrides).toEqual({
backgroundColor: '#123456',
});
expect(theme.theme.echartsOptionsOverridesByChartType).toEqual({
pie: { itemStyle: { borderWidth: 2 } },
});
});
test('Theme.toggleDarkMode switches to dark algorithm when toggling dark mode on', () => {
const theme = Theme.fromConfig();
@@ -206,6 +243,47 @@ test('Theme.toggleDarkMode preserves other algorithms when toggling dark mode',
expect(serialized.algorithm).not.toContain(ThemeAlgorithm.DARK);
});
test('Theme.toggleDarkMode is a no-op when the requested mode is already active', () => {
// Pages with many live component demos (see docs/src/components/
// StorybookWrapper.jsx's ThemeSync) mount one dark-mode-sync bridge per
// demo, so a single toggle event can call toggleDarkMode once per demo
// with the same isDark value. Only the first of those calls should
// actually recompute the theme and fan out to providers.
const theme = Theme.fromConfig();
const setConfigSpy = jest.spyOn(theme, 'setConfig');
theme.toggleDarkMode(true);
expect(setConfigSpy).toHaveBeenCalledTimes(1);
// Repeating the same toggle should not recompute the theme again.
theme.toggleDarkMode(true);
theme.toggleDarkMode(true);
expect(setConfigSpy).toHaveBeenCalledTimes(1);
// Toggling to the other mode should still go through.
theme.toggleDarkMode(false);
expect(setConfigSpy).toHaveBeenCalledTimes(2);
setConfigSpy.mockRestore();
});
test('Theme.toggleDarkMode no-op check accounts for other algorithms in the array', () => {
// Start already in dark mode alongside a non-mode algorithm (compact).
const theme = Theme.fromConfig({
algorithm: [
antdThemeImport.compactAlgorithm,
antdThemeImport.darkAlgorithm,
],
});
const setConfigSpy = jest.spyOn(theme, 'setConfig');
// Already dark, so this should be a no-op rather than reordering the array.
theme.toggleDarkMode(true);
expect(setConfigSpy).not.toHaveBeenCalled();
setConfigSpy.mockRestore();
});
test('Theme.toSerializedConfig serializes theme config correctly', () => {
const theme = Theme.fromConfig({
token: {
@@ -25,7 +25,7 @@ import {
CacheProvider as EmotionCacheProvider,
} from '@emotion/react';
import createCache from '@emotion/cache';
import { noop, mergeWith } from 'lodash-es';
import { mergeWith } from 'lodash-es';
import { GlobalStyles } from './GlobalStyles';
import {
AntdThemeConfig,
@@ -64,10 +64,12 @@ export class Theme {
* @param config - The theme configuration
* @param baseTheme - Optional base theme to apply under the config
*/
static fromConfig(
// Merge a config over an optional base theme (arrays replace rather than
// deep-merge; a colorPrimary override without colorLink aligns colorLink).
private static mergeConfig(
config?: AnyThemeConfig,
baseTheme?: AnyThemeConfig,
): Theme {
): AnyThemeConfig | undefined {
let mergedConfig: AnyThemeConfig | undefined = config;
if (baseTheme && config) {
@@ -76,9 +78,9 @@ export class Theme {
);
// 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.
// We expect links to follow the brand/primary color, so when a config
// overrides colorPrimary without setting colorLink, align the merged
// colorLink with the new primary palette.
if (config.token?.colorPrimary && !config.token?.colorLink) {
const mToken = mergedConfig?.token;
if (mToken) {
@@ -89,7 +91,14 @@ export class Theme {
mergedConfig = baseTheme;
}
return new Theme({ config: mergedConfig });
return mergedConfig;
}
static fromConfig(
config?: AnyThemeConfig,
baseTheme?: AnyThemeConfig,
): Theme {
return new Theme({ config: Theme.mergeConfig(config, baseTheme) });
}
private static getFilteredAntdTheme(
@@ -110,12 +119,14 @@ export class Theme {
}
/**
* Update the theme using any theme configuration
* Automatically handles both AntdThemeConfig and SerializableThemeConfig
* Dark mode should be specified via the algorithm property in the config
* Update the theme using any theme configuration, optionally merged over a
* base theme. Automatically handles both AntdThemeConfig and
* SerializableThemeConfig. Dark mode should be specified via the algorithm
* property in the config.
*/
setConfig(config: AnyThemeConfig): void {
const antdConfig = normalizeThemeConfig(config);
setConfig(config: AnyThemeConfig, baseTheme?: AnyThemeConfig): void {
const mergedConfig = Theme.mergeConfig(config, baseTheme) ?? config;
const antdConfig = normalizeThemeConfig(mergedConfig);
if (antdConfig.token?.colorPrimary && !antdConfig.token?.colorLink) {
antdConfig.token.colorLink = antdConfig.token.colorPrimary;
@@ -124,11 +135,11 @@ export class Theme {
// First phase: Let Ant Design compute the tokens
const tokens = Theme.getFilteredAntdTheme(antdConfig);
// Extract Superset-specific properties from top-level config.
// These are custom properties that aren't part of Ant Design's token system
// but need to be passed through to the SupersetTheme for ECharts customization.
// Extract Superset-specific properties from the merged config (not the raw
// config) so a base theme's ECharts overrides survive in-place updates, the
// same way the Ant Design tokens above are taken from the merged config.
const { echartsOptionsOverrides, echartsOptionsOverridesByChartType } =
config as AnyThemeConfig & {
mergedConfig as AnyThemeConfig & {
echartsOptionsOverrides?: any;
echartsOptionsOverridesByChartType?: Record<string, any>;
};
@@ -145,8 +156,8 @@ export class Theme {
}),
} as SupersetTheme;
// Update the providers with the fully formed theme
this.updateProviders(
// Update every mounted provider with the fully formed theme
this.notifyProviders(
this.theme,
this.antdConfig,
createCache({ key: 'superset' }),
@@ -185,6 +196,27 @@ export class Theme {
newConfig.algorithm = newAlgorithm;
}
// Skip the update (and the notifyProviders fan-out it triggers) if the
// theme is already in the requested mode. Docs pages mount one
// dark-mode-sync bridge per live component demo (see
// docs/src/components/StorybookWrapper.jsx's ThemeSync), so a single
// toggle event calls this once per demo on the page. Without this
// check, every one of those calls would recompute the theme and
// notify every mounted provider, turning a single real toggle into
// O(n^2) provider notifications across n demos.
// Compare the algorithm sets rather than positions: reordering
// non-mode algorithms to the front doesn't change the effective
// theme, so it shouldn't count as a change either.
const currentAlgorithm = this.antdConfig.algorithm;
const algorithmUnchanged = Array.isArray(newConfig.algorithm)
? Array.isArray(currentAlgorithm) &&
newConfig.algorithm.length === currentAlgorithm.length &&
newConfig.algorithm.every(alg => currentAlgorithm.includes(alg))
: newConfig.algorithm === currentAlgorithm;
if (algorithmUnchanged) {
return;
}
// Update the theme with the new configuration
this.setConfig(newConfig);
}
@@ -193,13 +225,29 @@ export class Theme {
return JSON.stringify(serializeThemeConfig(this.antdConfig), null, 2);
}
private updateProviders(
// Every currently-mounted SupersetThemeProvider for this Theme instance
// registers a listener here (see the useEffect below). A single
// "last write wins" callback isn't enough once more than one provider can
// be mounted from the same Theme instance at a time -- e.g. multiple live
// component demos on one docs page -- since each render would overwrite
// the previous provider's callback and only the most-recently-rendered
// provider would ever hear about a setConfig/toggleDarkMode call.
private providerListeners = new Set<
(
theme: SupersetTheme,
antdConfig: AntdThemeConfig,
emotionCache: any,
) => void
>();
private notifyProviders(
theme: SupersetTheme,
antdConfig: AntdThemeConfig,
emotionCache: any,
): void {
noop(theme, antdConfig, emotionCache);
// Overridden at runtime by SupersetThemeProvider using setThemeState
this.providerListeners.forEach(listener =>
listener(theme, antdConfig, emotionCache),
);
}
SupersetThemeProvider({ children }: { children: React.ReactNode }) {
@@ -214,9 +262,42 @@ export class Theme {
emotionCache: createCache({ key: 'superset' }),
});
this.updateProviders = (theme, antdConfig, emotionCache) => {
setThemeState({ theme, antdConfig, emotionCache });
};
// Register (and, on unmount, deregister) this provider instance's own
// listener rather than assigning a single shared callback on every
// render, so every concurrently mounted provider for this Theme
// instance receives updates, not just the last one to render.
//
// Use useLayoutEffect (not useEffect) so registration happens in the
// same commit phase as any layout effect elsewhere that might call
// setConfig/toggleDarkMode on this instance during mount (e.g. the
// docs site's dark-mode sync in StorybookWrapper.jsx, which reads the
// toggle and pushes it onto the singleton via a layout effect of its
// own). Layout effects run bottom-up, so a listener registered here
// (this component is nested inside that caller) is guaranteed to be
// in place before an ancestor's layout effect can fire and notify it.
// If this were a passive effect instead, an ancestor's layout effect
// could call toggleDarkMode before this listener exists, dropping that
// notification, and the provider would render stale until a later
// toggle.
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useLayoutEffect(() => {
const listener = (
nextTheme: SupersetTheme,
nextAntdConfig: AntdThemeConfig,
nextEmotionCache: any,
) => {
setThemeState({
theme: nextTheme,
antdConfig: nextAntdConfig,
emotionCache: nextEmotionCache,
});
};
this.providerListeners.add(listener);
return () => {
this.providerListeners.delete(listener);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<EmotionCacheProvider value={themeState.emotionCache}>

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