Compare commits

...

128 Commits

Author SHA1 Message Date
Evan Rusackas
6341a6ae41 style: run prettier on jsx-a11y prefer-tag-over-role changes
Fixes pre-commit CI failure on #42078 by reformatting the files
touched in the previous commit that weren't run through prettier.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-16 00:07:26 -07:00
Evan Rusackas
455a7128b9 chore(a11y): enable jsx-a11y/prefer-tag-over-role as error
Converts custom role="button"/role="img" widgets to native <button> tags
(or drops the redundant role) across the frontend so screen readers and
other assistive tech get real semantics and built-in keyboard behavior
instead of hand-rolled role/tabIndex/onKeyDown shims.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 18:05:53 -07:00
Greg Neighbors
8ce6d42942 feat(mcp): add restore_chart and restore_dashboard tools (#41842)
Co-authored-by: Greg Neighbors <gregneighbors@Gregs-Air-2.lan>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 15:51:15 -07:00
Greg Neighbors
2bbb7d0638 feat(mcp): histogram and box plot chart type plugins (#41860)
Co-authored-by: Greg Neighbors <gregneighbors@Gregs-Air-2.lan>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 15:50:09 -07:00
Evan Rusackas
1b0c6aaed3 chore(a11y): enable jsx-a11y/click-events-have-key-events as error (#42009)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 15:49:45 -07:00
dependabot[bot]
1f786f1949 chore(deps): bump websocket-driver from 0.7.4 to 0.7.5 in /superset-frontend (#42094)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-15 15:49:15 -07:00
Evan Rusackas
c540f782a3 fix(ci): remove Python 3.10 from the test matrix to unblock CI (#42058)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 15:48:47 -07:00
Joe Li
6fe4655ba2 fix(semantic-layers): separate grain and offset time-axis resolvers (#42093)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 15:33:52 -07:00
Elizabeth Thompson
689fc34ac2 fix: replace deprecated appbuilder.app with current_app in test_explore_redirect (#42086) 2026-07-15 15:05:15 -07:00
Elizabeth Thompson
e564389a01 fix(a11y): associate Name label with input in CssTemplateModal (#42081) 2026-07-15 15:04:33 -07:00
Elizabeth Thompson
da518d7a10 fix(a11y): associate Description label with TagModal input (#42034) 2026-07-15 15:03:40 -07:00
Joe Li
c6da740ce2 fix(app-root): restore legacy redirects for HEAD and the /superset app root (#42015)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:13:46 -07:00
Beto Dealmeida
e28b259de0 fix(semantic-layers): expose time grains in Explore for SemanticView datasources (#41456) 2026-07-15 12:25:41 -07:00
Elizabeth Thompson
beb9d53687 fix(plugin-chart-echarts): use echarts 5.6.0 i18n export path for locale import (#42055)
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-15 11:45:14 -07:00
Mike Bridge
90f9238f8a fix(charts): preserve time filter for expression axes (#42052)
Co-authored-by: Mike Bridge <michael.bridge@ext.preset.io>
2026-07-15 14:27:50 -03:00
Amin Ghadersohi
f38fff2a19 test(mcp): close systematic test-coverage gaps in mcp_service (#41924) 2026-07-15 12:30:04 -04:00
Amin Ghadersohi
cec9afb165 fix(mcp): await ctx.info calls in update_dashboard tool (#41920)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-15 12:28:44 -04:00
dependabot[bot]
753113d169 chore(deps-dev): bump trino from 0.337.0 to 0.338.0 (#42059)
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-07-15 08:13:33 -07:00
dependabot[bot]
5c8e14e9dc chore(deps-dev): bump hdbcli from 2.28.21 to 2.29.23 (#42060)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-15 08:13:30 -07:00
dependabot[bot]
e66d58361a chore(deps-dev): update clickhouse-connect requirement from <2.0,>=1.1.1 to >=1.4.2,<2.0 (#42062)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-15 08:13:26 -07:00
dependabot[bot]
7bc1895050 chore(deps-dev): update pydoris requirement from <2.0.0,>=1.0.0 to >=1.2.0,<2.0.0 (#42063)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-15 08:13:22 -07:00
dependabot[bot]
071c431580 chore(deps-dev): bump @types/node from 26.1.0 to 26.1.1 in /superset-websocket (#42076)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-15 08:13:19 -07:00
dependabot[bot]
53c4603c8c chore(deps-dev): bump @types/node from 26.1.0 to 26.1.1 in /superset-frontend (#42077)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-15 08:13:15 -07:00
Mehmet Salih Yavuz
f010affbc2 fix(reports): paginate Slack recipient picker for large workspaces (#41998) 2026-07-15 18:00:53 +03:00
Amin Ghadersohi
6c2b7aceb5 fix(mcp): correct decorator order on query_dataset tool (#41925) 2026-07-15 10:42:11 -04:00
Daniel Vaz Gaspar
2af66b2c9b fix(async-queries): add Celery task expiry to GAQ async query tasks (#42084)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-07-15 15:04:56 +01:00
Evan Rusackas
a540f56f5c fix(reports): raise typed executor-not-found error in alert-query path (#41486)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 23:53:03 -07:00
aikawa-ohno
aa85455a5c fix(i18n): Update Japanese translations (#41984)
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:07:38 -07:00
dependabot[bot]
d80267d00b chore(deps): bump github/codeql-action/init from 4.36.3 to 4.37.0 (#42068)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 21:21:25 -07:00
yousoph
7953382d10 feat(submenu_export): Add export to png and pdf options for charts (#38535)
Co-authored-by: Ramiro Aquino Romero <ramiroaquinoromero@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Joe Li <joe@preset.io>
Co-authored-by: Claude <claude@anthropic.com>
2026-07-14 21:21:12 -07:00
dependabot[bot]
abafe195bf chore(deps): bump click from 8.4.1 to 8.4.2 (#42064)
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-07-14 20:38:32 -07:00
dependabot[bot]
b4373f60b3 chore(deps-dev): bump fastmcp from 3.4.2 to 3.4.3 (#42065)
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-07-14 20:38:30 -07:00
dependabot[bot]
6dcd95e14a chore(deps-dev): bump syntaqlite from 0.6.0 to 0.7.0 (#42067)
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-07-14 20:38:27 -07:00
dependabot[bot]
ad8f2cf268 chore(deps): bump github/codeql-action/analyze from 4.36.3 to 4.37.0 (#42069)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 20:38:21 -07:00
Mafi
9db88203e1 fix(migration): use sqlalchemy.orm.declarative_base to avoid MovedIn20Warning (#42056)
Co-authored-by: Matt Fitzgerald <matt.fitzgerald@preset.io>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-14 21:08:43 -04:00
Joe Li
13121fcd58 fix(ci): skip unsupported React 19 Dependabot updates (#42047) 2026-07-15 08:03:14 +07:00
Evan Rusackas
be768efc0f fix(ci): make pre-commit fail closed instead of silently passing on an empty file list (#42044)
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:20:56 -07:00
Elizabeth Thompson
d68e84e731 fix(databricks): classify insufficient-permissions errors as 4xx (#41945)
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-14 13:44:41 -07:00
Mafi
0a3c263606 fix(migration): strip metricSqlExpressions from ag_grid_table params (#41591)
Co-authored-by: Matt Fitzgerald <matt.fitzgerald@preset.io>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-14 16:42:32 -04:00
Amin Ghadersohi
bd9ba24266 fix(mcp): add AliasChoices to chart/dashboard/dataset request schemas (#41597)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-14 14:24:12 -04:00
Evan Rusackas
4446967d0f chore(lint): convert Storybook stories and shared helpers to function components (#39451)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-14 11:13:47 -07:00
Evan Rusackas
c04a0295ef fix(chart): keep query-context updates bound to the chart's datasource (#40955)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-14 11:13:33 -07:00
Mike Bridge
e3a0d0e41e fix(versioning): isolate action metadata writes (#41943)
Co-authored-by: Mike Bridge <michael.bridge@ext.preset.io>
2026-07-14 11:10:12 -07:00
Hans Yu
256cc71c87 refactor: import from sqlalchemy.orm (#41981) 2026-07-14 10:29:18 -07:00
JUST.in DO IT
4b07d43050 fix(sqllab): collapse left sidebar shows compact db selector in top bar (#41898) 2026-07-14 09:01:30 -07:00
dependabot[bot]
ef3a9e925e chore(deps-dev): bump @typescript-eslint/eslint-plugin from 8.62.0 to 8.63.0 in /superset-websocket (#42020)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2026-07-14 08:58:58 -07:00
Mehmet Salih Yavuz
76bb5f8e69 fix(dashboard): prevent native filter loss when saving chart customizations (#42032)
Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com>
2026-07-14 18:30:12 +03:00
dependabot[bot]
1f41899deb chore(deps-dev): bump @typescript-eslint/eslint-plugin from 8.62.1 to 8.63.0 in /superset-frontend (#42023)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 21:35:48 +07:00
Evan Rusackas
c4bee525ea fix(ci): correct mismatched version comment on actions/setup-node pin (#42036)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 21:32:50 +07:00
dependabot[bot]
0dcf67fa27 chore(deps-dev): bump typescript-eslint from 8.62.1 to 8.63.0 in /docs (#42024)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 21:31:49 +07:00
Evan Rusackas
4e10a96253 chore(ci): correct actions/setup-node version comment to v6.4.0 (#42038)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 21:31:08 +07:00
Evan Rusackas
24959d1656 fix(ci): correct setup-node version comment in tech-debt.yml (#42037) 2026-07-14 21:30:47 +07:00
Luiz Otavio
7075e9f253 fix(event-log): match embedded routes without trailing slash in logger middleware (#42005)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
2026-07-14 11:12:50 -03:00
Evan Rusackas
d5e75c4813 fix(ci): correct setup-node version comment in tag-release.yml (#42039) 2026-07-14 19:43:13 +07:00
dependabot[bot]
cc32d16e0c chore(deps-dev): bump @typescript-eslint/parser from 8.62.0 to 8.63.0 in /superset-websocket (#42021)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:28 -07:00
dependabot[bot]
258f4c035b chore(deps-dev): bump typescript-eslint from 8.62.1 to 8.63.0 in /superset-websocket (#42022)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:25 -07:00
dependabot[bot]
d49365757c chore(deps-dev): bump @typescript-eslint/parser from 8.61.0 to 8.63.0 in /docs (#42025)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:18 -07:00
dependabot[bot]
68881a60ca chore(deps): bump caniuse-lite from 1.0.30001802 to 1.0.30001803 in /docs (#42026)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:14 -07:00
dependabot[bot]
41efdb3082 chore(deps-dev): bump webpack-sources from 3.5.0 to 3.5.1 in /superset-frontend (#42027)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:11 -07:00
dependabot[bot]
e86dcf7b82 chore(deps): bump actions/setup-java from 5.4.0 to 5.5.0 (#42028)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:08 -07:00
dependabot[bot]
35d05cc278 chore(deps-dev): bump @swc/plugin-emotion from 14.14.1 to 14.15.0 in /superset-frontend (#42029)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:04 -07:00
dependabot[bot]
115ce12ff7 chore(deps-dev): bump @typescript-eslint/parser from 8.62.1 to 8.63.0 in /superset-frontend (#42030)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:01 -07:00
dependabot[bot]
ccfccdf237 chore(deps-dev): bump oxlint from 1.72.0 to 1.73.0 in /superset-frontend (#42031)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:13:56 -07:00
Antonio Rivero
42523f8cc4 fix(async-queries): back off polling when async event requests keep failing (#42012) 2026-07-14 12:57:45 +02:00
Hans Yu
e165762bb7 refactor: begin transaction to automatically commit (#41979)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-07-13 22:45:40 -07:00
Hans Yu
8afad27c84 chore: convert warning to error if parameters not passed as collection (#41977) 2026-07-13 21:25:23 -07:00
Evan Rusackas
e420812eb7 test(filters): guard BIGINT filter value precision (#27510) (#41963)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-13 21:20:38 -07:00
Mike Bridge
993a43396a fix: restore all-files pre-commit checks (#42014)
Co-authored-by: Mike Bridge <michael.bridge@ext.preset.io>
Co-authored-by: Joe Li <joe@preset.io>
2026-07-13 17:35:55 -07:00
Joe Li
a2a71760ed fix(e2e): repair master Playwright smoke tests (#42013) 2026-07-13 17:13:35 -07:00
Evan Rusackas
836dce9b05 fix(i18n): reset embedded start guard and align menu error handling (#41491)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:03:53 -07:00
Evan Rusackas
2857b29ab9 test(sqla): assert simple metric quotes columns requiring quoting (#30637) (#41961)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-13 15:58:34 -07:00
Evan Rusackas
3c648ca264 fix(swagger): support URL prefix via APPLICATION_ROOT in OpenAPI and Swagger UI (#40908)
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: rsbhatti <rajvindrasinghbhatti12@gmail.com>
2026-07-13 15:47:09 -07:00
Evan Rusackas
25ce83cc58 fix(dashboard): apply auto-refresh interval in standalone mode (#41740)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 15:46:34 -07:00
Elizabeth Thompson
ce8219672e fix(a11y): add accessible labels to EmbedCodeContent height/width inputs (#41995) 2026-07-13 15:05:28 -07:00
Elizabeth Thompson
3e957b63d4 fix(a11y): make CopyToClipboard keyboard-operable for custom copyNode elements (#41958) 2026-07-13 15:04:44 -07:00
Elizabeth Thompson
9818da445a fix(a11y): add aria-label to cross-filter search icon (#41619) 2026-07-13 15:03:08 -07:00
yousoph
8262aa7d6b fix(explore): restore drag-and-drop reordering broken by @dnd-kit migration (#41637)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 14:25:15 -07:00
innovark
d4a31d1d78 fix: add subdirectory deployment support for brandSpinnerUrl (#37523)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-07-13 14:01:14 -07:00
Evan Rusackas
18f1dd394b test(security): prove Drill By access does not require can_explore (#27900) (#41967)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-13 12:05:16 -07:00
Amin Ghadersohi
62ccdfacc2 fix(mcp): make list-item truncation cap configurable in get_dashboard_info (#41698) 2026-07-13 14:46:10 -04:00
Evan Rusackas
c5131bff47 chore(a11y): enable 9 more jsx-a11y oxlint rules as errors (#42006)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 11:39:13 -07:00
Hans Yu
521e51429a chore: as_declarative() warnings are irrelevant (#41982) 2026-07-13 11:37:45 -07:00
Durgaprasad M L
64df96be00 fix(nvd3): invoke shiftMetric() in TimePivot formDataOverrides (#41916) 2026-07-13 10:44:50 -07:00
Hans Yu
d43a0d7633 chore: sqlalchemy.database package warnings are irrelevant (#41978) 2026-07-13 10:41:41 -07:00
Amin Ghadersohi
873566c827 fix(mcp): stop masking dashboard lookup DB errors as not-found (#41919) 2026-07-13 13:32:35 -04:00
Evan Rusackas
e063f5093c feat(frontend): upgrade Ant Design from v5 to v6 (#41636)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com>
2026-07-13 10:13:30 -07:00
Evan Rusackas
1627fab741 fix(i18n): make every catalog compile clean, canonicalize, fix ja plurals (#41828)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 10:08:03 -07:00
Enzo Martellucci
5dd060b714 fix(chart-echarts): disable animation for report screenshots so time-shift lines render fully (#42003) 2026-07-13 19:02:45 +02:00
Hans Yu
68ebc22e1e refactor: replace Engine.execute() with Connection.execute() (#41917) 2026-07-13 09:40:33 -07:00
Enzo Martellucci
8f75f1a353 fix(dashboard): deleted Display Control reappears after Apply Filters (#41999) 2026-07-13 16:25:47 +02:00
Mehmet Salih Yavuz
2fac66d1a3 feat(mcp): add update_dataset_metric tool for editing saved dataset metrics (#40975) 2026-07-13 15:44:57 +03:00
Luiz Otavio
029d49539b fix(event-log): match embedded routes precisely in logger middleware (#41942) 2026-07-13 08:46:23 -03:00
dependabot[bot]
3c6982252b chore(deps-dev): bump webpack from 5.108.3 to 5.108.4 in /superset-frontend (#41989)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 18:08:48 +07:00
dependabot[bot]
1c7a3f395f chore(deps): bump fs-extra from 11.3.2 to 11.3.6 in /superset-frontend (#41990)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 18:08:05 +07:00
dependabot[bot]
f7b7bad9a8 chore(deps-dev): bump tsx from 4.22.5 to 4.23.0 in /superset-frontend (#41985)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:24 -07:00
dependabot[bot]
0cf217cdea chore(deps-dev): bump vitest from 4.1.9 to 4.1.10 in /superset-websocket (#41986)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:21 -07:00
dependabot[bot]
96bf1a1e4c chore(deps): bump caniuse-lite from 1.0.30001800 to 1.0.30001802 in /docs (#41987)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:18 -07:00
dependabot[bot]
9180ace1dd chore(deps): bump actions/labeler from 6.1.0 to 6.2.0 (#41988)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:15 -07:00
dependabot[bot]
bb5d7dbb17 chore(deps): bump immer from 11.1.9 to 11.1.11 in /superset-frontend (#41991)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:12 -07:00
dependabot[bot]
73925922a9 chore(deps): bump react-arborist from 3.12.0 to 3.13.2 in /superset-frontend (#41992)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:09 -07:00
dependabot[bot]
8f339545ad chore(deps-dev): bump baseline-browser-mapping from 2.10.41 to 2.10.42 in /superset-frontend (#41993)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:06 -07:00
Evan Rusackas
c762b75ddc chore(lint): upgrade no-unused-vars rule from warn to error (#41132)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 03:11:09 -07:00
Evan Rusackas
c0e5f5226d fix(explore): let chart owners overwrite when owners come through as objects (#41352)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 03:10:00 -07:00
dependabot[bot]
d380663e71 chore(deps-dev): bump syntaqlite from 0.4.2 to 0.6.0 (#41952)
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-07-12 20:17:34 -07:00
dependabot[bot]
98c3ec7a71 chore(deps): bump sqlglot from 30.8.0 to 30.12.0 (#41948)
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: Evan <evan@preset.io>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 16:20:07 -07:00
dependabot[bot]
12f28ce235 chore(deps): bump holidays from 0.82 to 0.99 (#41949)
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-07-12 16:19:51 -07:00
Evan Rusackas
e403d9b074 chore(lint): convert legacy plugin chart components to function components (#39452)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com>
2026-07-13 00:37:46 +02:00
isaac-jaynes-imperva
0ca51d9796 fix(Database Connection Form): Added validation to db credential input (#37919)
Co-authored-by: codeant-ai-for-open-source[bot] <244253245+codeant-ai-for-open-source[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 09:36:41 -07:00
dependabot[bot]
db1912c6d9 chore(deps): bump cachetools from 6.2.1 to 7.1.4 (#41947)
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-07-12 09:36:28 -07:00
Evan Rusackas
2a18a556b0 chore(frontend): lint Emotion CSS-in-JS with Stylelint (#41871)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com>
2026-07-12 08:45:02 -07:00
Evan Rusackas
a1d1d69f5e ci: enforce SQLAlchemy 2.0 deprecation warnings in unit-test CI (#41915)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 08:43:09 -07:00
Moussa Leblouba
13c5a32402 fix(tests): correct stale TYPE_CHECKING import path in base_data_loader (#41972)
Co-authored-by: Moussa Leblouba <mlablouba@gmail.com>
2026-07-12 20:26:11 +07:00
Evan Rusackas
958d1ab256 test(rls): assert dataset search returns all mask matches (#29707) (#41968)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-12 20:23:29 +07:00
Evan Rusackas
b8c0171976 test(rls): assert related/subjects honors search filter for roles (#31466) (#41969)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-12 20:22:39 +07:00
Evan Rusackas
49dc0acd82 fix(pre-commit): prettier hook drops first file and never batches (#41944)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-12 11:17:27 +07:00
dependabot[bot]
5e6b29d1a3 chore(deps-dev): bump sqlalchemy-hana from 0.4.0 to 3.0.3 (#41953)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:59:46 -07:00
Anatolii
189f258e0c fix(drill): coerce temporal drill filter values (#40180)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Superset Dev <dev@superset.apache.org>
2026-07-11 15:24:51 -07:00
yousoph
f5deda7864 fix(dataset): copy catalog field when duplicating a BigQuery dataset (#41106)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
2026-07-11 15:24:21 -07:00
Elizabeth Thompson
b237aefb1e fix(a11y): add aria-label to RightMenu documentation and bug-report icon links (#41903) 2026-07-11 15:03:24 -07:00
Elizabeth Thompson
0ecf34d80e fix(a11y): add aria-label to ViewportControl text inputs (#41931) 2026-07-11 15:02:44 -07:00
Dhimas Ardinata
a03cabffa7 perf(csv): avoid regex in CSV value escaping (#40195)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:52:50 -07:00
Krupa Vadher
e852147182 test: add edge-case tests for datetime_to_epoch function (#39404)
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
2026-07-11 11:51:46 -07:00
dependabot[bot]
df209cedbf chore(deps-dev): update sqlalchemy-firebird requirement from <2.2,>=0.7.0 to >=0.8.0,<2.2 (#41950)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 10:51:36 -07:00
Mike Bridge
b641008da6 fix(versioning): preserve complete multi-flush history (#41940)
Co-authored-by: Mike Bridge <michael.bridge@ext.preset.io>
2026-07-11 10:50:48 -07:00
Luis Carbonell
678fcba8b4 feat(helm): Superset 6 structured config schema with deprecation path (#41777) 2026-07-11 10:50:20 -07:00
Greg Neighbors
5c12f216d5 feat(mcp): add delete_chart and delete_dashboard tools (#41472)
Co-authored-by: Greg Neighbors <gregneighbors@Gregs-Air-2.lan>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 10:47:27 -07:00
Ujjwal Jain
7a752d1c22 fix(dashboard): restore arrow key navigation in modal inputs opened from dropdown (#37978) 2026-07-11 10:39:28 -07:00
dependabot[bot]
51ce2b11ad chore(deps-dev): update impyla requirement from <0.23,>0.16.2 to >=0.24.0,<0.25 (#41951)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 05:18:31 -07:00
713 changed files with 25956 additions and 6222 deletions

View File

@@ -32,8 +32,6 @@ runs:
elif [ "$INPUT_PYTHON_VERSION" = "next" ]; then
# currently disabled in GHA matrixes because of library compatibility issues
RESOLVED_VERSION="3.12"
elif [ "$INPUT_PYTHON_VERSION" = "previous" ]; then
RESOLVED_VERSION="3.10"
elif printf '%s' "$INPUT_PYTHON_VERSION" | grep -Eq '^[0-9]+\.[0-9]+(\.[0-9]+)?$'; then
RESOLVED_VERSION="$INPUT_PYTHON_VERSION"
else

View File

@@ -11,7 +11,15 @@ updates:
- package-ecosystem: "npm"
ignore:
- dependency-name: "@rjsf/*"
# TODO: remove below entries until React >= 19.0.0
# TODO: remove below entries once the application supports React >= 19.0.0
- dependency-name: "react"
update-types: ["version-update:semver-major"]
- dependency-name: "react-dom"
update-types: ["version-update:semver-major"]
- dependency-name: "@types/react"
update-types: ["version-update:semver-major"]
- dependency-name: "@types/react-dom"
update-types: ["version-update:semver-major"]
- dependency-name: "react-icons"
# JSDOM v30 doesn't play well with Jest v30
# Source: https://jestjs.io/blog#known-issues

View File

@@ -64,7 +64,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
category: "/language:${{matrix.language}}"

View File

@@ -31,7 +31,7 @@ jobs:
# token, which makes npm attempt token auth and skip the OIDC
# trusted-publishing exchange. With no .npmrc auth line, npm authenticates
# via OIDC against the default registry (registry.npmjs.org).
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: "./superset-embedded-sdk/.nvmrc"
- run: npm ci

View File

@@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: "./superset-embedded-sdk/.nvmrc"
registry-url: "https://registry.npmjs.org"

View File

@@ -37,7 +37,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
with:
distribution: "temurin"
java-version: "11"

View File

@@ -14,7 +14,7 @@ jobs:
pull-requests: write
runs-on: ubuntu-24.04
steps:
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
- uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
with:
sync-labels: true

View File

@@ -23,7 +23,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
with:
distribution: "temurin"
java-version: "11"

View File

@@ -7,6 +7,11 @@ on:
- "[0-9].[0-9]*"
pull_request:
types: [synchronize, opened, reopened, ready_for_review]
# Nightly full-tree sweep. Per-PR runs only lint changed files, so a change
# that invalidates an untouched file (e.g. a type change that breaks an
# importing test) can pass every PR yet leave master red. This catches that.
schedule:
- cron: "0 6 * * *"
permissions:
contents: read
@@ -25,13 +30,16 @@ jobs:
# Run the full version spread on push (master/release) and nightly,
# but only the current version on PRs — lint/format/type results
# rarely differ across patch versions, so 3x per PR is wasteful.
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "previous", "next"]') }}
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
submodules: recursive
# Full history so we can diff a PR/push against its base commit to
# determine changed files (see "Determine changed files" below).
fetch-depth: 0
- name: Setup Python
uses: ./.github/actions/setup-backend/
@@ -45,7 +53,7 @@ jobs:
run: go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.14.2
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: "superset-frontend/.nvmrc"
cache: "npm"
@@ -69,19 +77,82 @@ jobs:
restore-keys: |
pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-
- name: Get changed files
- name: Determine changed files
id: changed_files
uses: ./.github/actions/file-changes-action
with:
output: " "
env:
EVENT_NAME: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
BEFORE_SHA: ${{ github.event.before }}
run: |
set -euo pipefail
# Scheduled runs check the whole tree (see the pre-commit step).
if [ "${EVENT_NAME}" = "schedule" ]; then
echo "mode=all" >> "$GITHUB_OUTPUT"
exit 0
fi
# Resolve the commit to diff against.
base=""
if [ "${EVENT_NAME}" = "pull_request" ]; then
base="${BASE_SHA}"
elif [ -n "${BEFORE_SHA:-}" ] && \
[ "${BEFORE_SHA}" != "0000000000000000000000000000000000000000" ]; then
base="${BEFORE_SHA}"
fi
# Fail closed: if the diff base can't be resolved, check every file
# instead of silently checking nothing. Previously an empty file list
# made `pre-commit run --files` a no-op that still reported success,
# which let unlinted code reach master.
if [ -z "${base}" ] || ! git cat-file -e "${base}^{commit}" 2>/dev/null; then
echo "::notice::Could not resolve a diff base; falling back to --all-files."
echo "mode=all" >> "$GITHUB_OUTPUT"
exit 0
fi
# Files present in HEAD that changed since the base (drop deletions).
files="$(git diff --name-only --diff-filter=ACMRT "${base}...HEAD")"
if [ -z "${files}" ]; then
echo "mode=none" >> "$GITHUB_OUTPUT"
else
echo "mode=files" >> "$GITHUB_OUTPUT"
{
echo "files<<__CHANGED_FILES_EOF__"
echo "${files}"
echo "__CHANGED_FILES_EOF__"
} >> "$GITHUB_OUTPUT"
fi
- name: pre-commit
env:
MODE: ${{ steps.changed_files.outputs.mode }}
CHANGED_FILES: ${{ steps.changed_files.outputs.files }}
run: |
set +e # Don't exit immediately on failure
export SKIP=type-checking-frontend
pre-commit run --files $CHANGED_FILES
case "${MODE}" in
all)
echo " Running pre-commit on all files."
pre-commit run --all-files
;;
files)
echo " Running pre-commit on changed files:"
echo "${CHANGED_FILES}"
# shellcheck disable=SC2086
pre-commit run --files ${CHANGED_FILES}
;;
none)
echo " No source files changed; nothing for pre-commit to check."
exit 0
;;
*)
echo "⚠️ Unrecognized changed-files mode '${MODE}'; checking all files."
pre-commit run --all-files
;;
esac
PRE_COMMIT_EXIT_CODE=$?
git diff --quiet --exit-code
GIT_DIFF_EXIT_CODE=$?

View File

@@ -50,7 +50,7 @@ jobs:
- name: Install Node.js
if: env.HAS_TAGS
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: "./superset-frontend/.nvmrc"

View File

@@ -117,7 +117,7 @@ jobs:
build: "true"
- name: Use Node.js 20
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20

View File

@@ -66,12 +66,12 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: "./docs/.nvmrc"
- name: Setup Python
uses: ./.github/actions/setup-backend/
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
with:
distribution: "zulu"
java-version: "21"

View File

@@ -78,7 +78,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: "./docs/.nvmrc"
- name: yarn install
@@ -118,7 +118,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: "./docs/.nvmrc"
- name: yarn install

View File

@@ -128,7 +128,7 @@ jobs:
with:
run: testdata
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: "./superset-frontend/.nvmrc"
cache: "npm"
@@ -238,7 +238,7 @@ jobs:
with:
run: playwright_testdata
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: "./superset-frontend/.nvmrc"
cache: "npm"

View File

@@ -25,7 +25,7 @@ jobs:
matrix:
# Full version spread on push (master/release) + nightly; current only
# on PRs to cut runner cost (cross-version breaks are caught at merge).
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["previous", "current", "next"]') }}
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
defaults:
run:
working-directory: superset-extensions-cli

View File

@@ -114,7 +114,7 @@ jobs:
with:
run: playwright_testdata
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: "./superset-frontend/.nvmrc"
cache: "npm"

View File

@@ -135,7 +135,7 @@ jobs:
matrix:
# Full version spread on push (master/release) + nightly; current only
# on PRs to cut runner cost (cross-version breaks are caught at merge).
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "previous", "next"]') }}
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
env:
PYTHONPATH: ${{ github.workspace }}
SUPERSET_CONFIG: tests.integration_tests.superset_test_config

View File

@@ -50,9 +50,15 @@ jobs:
matrix:
# Full version spread on push (master/release) + nightly; current only
# on PRs to cut runner cost (cross-version breaks are caught at merge).
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["previous", "current", "next"]') }}
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
env:
PYTHONPATH: ${{ github.workspace }}
# Promotes the SQLAlchemy 2.0 deprecation warnings already locked in as
# errors via pytest.ini's `filterwarnings` to actually run in CI, so a
# regression on those fails the build instead of relying on a
# contributor remembering to set this locally. See the migration
# battleplan: https://github.com/apache/superset/discussions/40273
SQLALCHEMY_WARN_20: "1"
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

View File

@@ -38,7 +38,7 @@ jobs:
- name: Setup Node.js
if: steps.check.outputs.frontend
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: "./superset-frontend/.nvmrc"
cache: "npm"

View File

@@ -75,7 +75,7 @@ jobs:
- name: Use Node.js 20
# zizmor: ignore[cache-poisoning] - node only runs the supersetbot CLI; no dependency cache is enabled
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20
package-manager-cache: false
@@ -133,7 +133,7 @@ jobs:
- name: Use Node.js 20
# zizmor: ignore[cache-poisoning] - node only runs the supersetbot CLI; no dependency cache is enabled
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20
package-manager-cache: false

View File

@@ -37,7 +37,7 @@ jobs:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: "./superset-frontend/.nvmrc"

3
.gitmodules vendored
View File

@@ -21,9 +21,6 @@
[submodule ".github/actions/pr-lint-action"]
path = .github/actions/pr-lint-action
url = https://github.com/morrisoncole/pr-lint-action
[submodule ".github/actions/file-changes-action"]
path = .github/actions/file-changes-action
url = https://github.com/trilom/file-changes-action
[submodule ".github/actions/cached-dependencies"]
path = .github/actions/cached-dependencies
url = https://github.com/apache-superset/cached-dependencies

View File

@@ -63,7 +63,7 @@ repos:
hooks:
- id: prettier-frontend
name: prettier (frontend)
entry: bash -c 'cd superset-frontend && for file in "$@"; do npx prettier --write "${file#superset-frontend/}"; done'
entry: bash -c 'cd superset-frontend && files=(); for f in "$@"; do files+=("${f#superset-frontend/}"); done; npx prettier --write -- "${files[@]}"' --
language: system
pass_filenames: true
files: ^superset-frontend/.*\.(js|jsx|ts|tsx|css|scss|sass|json)$
@@ -81,6 +81,12 @@ repos:
language: system
pass_filenames: true
files: ^superset-frontend/.*\.(js|jsx|ts|tsx)$
- id: stylelint-frontend
name: stylelint (frontend css-in-js)
entry: ./scripts/stylelint.sh
language: system
pass_filenames: true
files: ^superset-frontend/.*\.(js|jsx|ts|tsx)$
- id: eslint-docs
name: eslint (docs)
entry: bash -c 'cd docs && FILES=$(printf "%s\n" "$@" | sed "s|^docs/||" | tr "\n" " ") && yarn eslint --fix --quiet $FILES'

View File

@@ -7,6 +7,7 @@
.codecov.yml
.eslintrc
.eslintignore
.stylelintignore
.flake8
.nvmrc
.prettierrc

View File

@@ -153,6 +153,16 @@ virtual-dataset SQL) raises a parse error in SQL Lab and dashboard-generated
queries. Deployments that legitimately run queries above this size should raise
the value, and `SQL_MAX_PARSE_LENGTH = None` disables the check entirely.
### Ant Design upgraded from v5 to v6
The frontend now builds against Ant Design 6, and `@superset-ui/core` / `@apache-superset/core` peer-depend on `antd ^6`. Custom plugins, extensions, and themes that interact with Ant Design need review:
- **Internal DOM classes were renamed**, so any custom CSS targeting `.ant-*` internals silently stops matching. Notable renames: `.ant-tabs-content-holder``.ant-tabs-body-holder`, `.ant-tabs-content``.ant-tabs-body`, `.ant-tabs-tabpane``.ant-tabs-content`; `.ant-select-selector``.ant-select-content`, `.ant-select-selection-placeholder``.ant-select-placeholder`, `.ant-select-arrow``.ant-select-suffix`; `.ant-tooltip-inner``.ant-tooltip-container`; `.ant-popover-inner``.ant-popover-container`; `.ant-steps-item-tail``.ant-steps-item-rail`.
- **Some component props changed or were removed** — e.g. `Select` no longer accepts `dropdownAlign`, `visible`/`onVisibleChange` are `open`/`onOpenChange`, `Dropdown` `overlay` is `menu`, `Steps.Step` children are the `items` prop, and `styles.body` on Tooltip/Popover is `styles.container`.
- **CSS variables are on by default** in antd 6, and `ThemeConfig.cssVar` no longer accepts a boolean; Superset theme configs using `cssVar: true`/`false` are coerced (`true``{}`, `false` → omitted).
Theme tokens are unaffected — antd 6 removed none of the tokens Superset exposes, so existing theme configurations continue to work. See the [Ant Design v6 migration guide](https://ant.design/docs/react/migration-v6) for the complete upstream list.
### 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.

View File

@@ -529,6 +529,7 @@ MCP_RESPONSE_SIZE_CONFIG = {
"enabled": True,
"token_limit": 25000,
"warn_threshold_pct": 80,
"max_list_items": 100,
"excluded_tools": [
"health_check",
"get_chart_preview",
@@ -543,6 +544,7 @@ MCP_RESPONSE_SIZE_CONFIG = {
| `enabled` | `True` | Enable response size checking |
| `token_limit` | `25000` | Maximum estimated token count per response |
| `warn_threshold_pct` | `80` | Warn when response exceeds this percentage of the limit |
| `max_list_items` | `100` | Cap on list-field length (e.g. `charts`, `native_filters`) applied to the `get_*_info` tools before falling back to more aggressive truncation. Raised from a hardcoded 30 in earlier versions; the higher default only keeps more data before the same token-budget fallback kicks in, so it's not a breaking change, but tenants that tuned workflows around the old 30-item cap should lower this value explicitly. |
| `excluded_tools` | See above | Tools exempt from size checking (e.g., tools that return URLs, not data) |
### Caching

View File

@@ -63,7 +63,7 @@
"@swc/core": "^1.15.43",
"antd": "^6.5.0",
"baseline-browser-mapping": "^2.10.40",
"caniuse-lite": "^1.0.30001799",
"caniuse-lite": "^1.0.30001803",
"docusaurus-plugin-openapi-docs": "^5.1.0",
"docusaurus-theme-openapi-docs": "^5.1.0",
"js-yaml": "^5.2.0",
@@ -90,7 +90,7 @@
"@types/js-yaml": "^4.0.9",
"@types/react": "^19.1.8",
"@typescript-eslint/eslint-plugin": "^8.59.3",
"@typescript-eslint/parser": "^8.61.0",
"@typescript-eslint/parser": "^8.63.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.6",
@@ -98,7 +98,7 @@
"globals": "^17.7.0",
"prettier": "^3.9.1",
"typescript": "~6.0.3",
"typescript-eslint": "^8.62.0",
"typescript-eslint": "^8.63.0",
"webpack": "^5.108.2"
},
"browserslist": {

View File

@@ -5504,100 +5504,110 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@8.62.1", "@typescript-eslint/eslint-plugin@^8.59.3":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz#1736dcdca6cae3359d818456a47d18b674761f7f"
integrity sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==
"@typescript-eslint/eslint-plugin@8.63.0", "@typescript-eslint/eslint-plugin@^8.59.3":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz#0d85d0ec1a28b0e35f484cc8220a8bf084019e71"
integrity sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==
dependencies:
"@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.62.1"
"@typescript-eslint/type-utils" "8.62.1"
"@typescript-eslint/utils" "8.62.1"
"@typescript-eslint/visitor-keys" "8.62.1"
"@typescript-eslint/scope-manager" "8.63.0"
"@typescript-eslint/type-utils" "8.63.0"
"@typescript-eslint/utils" "8.63.0"
"@typescript-eslint/visitor-keys" "8.63.0"
ignore "^7.0.5"
natural-compare "^1.4.0"
ts-api-utils "^2.5.0"
"@typescript-eslint/parser@8.62.1", "@typescript-eslint/parser@^8.61.0":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.62.1.tgz#d3f7ba18f1bf78bfb7256fea021d1927b48e7080"
integrity sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==
"@typescript-eslint/parser@8.63.0", "@typescript-eslint/parser@^8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.63.0.tgz#2993338c379903e6afc72c3532ae6e15b97334d4"
integrity sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==
dependencies:
"@typescript-eslint/scope-manager" "8.62.1"
"@typescript-eslint/types" "8.62.1"
"@typescript-eslint/typescript-estree" "8.62.1"
"@typescript-eslint/visitor-keys" "8.62.1"
"@typescript-eslint/scope-manager" "8.63.0"
"@typescript-eslint/types" "8.63.0"
"@typescript-eslint/typescript-estree" "8.63.0"
"@typescript-eslint/visitor-keys" "8.63.0"
debug "^4.4.3"
"@typescript-eslint/project-service@8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.62.1.tgz#78d880eb1cf6859b5ec263d04f95403e9f90ae47"
integrity sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==
"@typescript-eslint/project-service@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.63.0.tgz#01a3d0550a860127444a9939749ab434591cd71a"
integrity sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.62.1"
"@typescript-eslint/types" "^8.62.1"
"@typescript-eslint/tsconfig-utils" "^8.63.0"
"@typescript-eslint/types" "^8.63.0"
debug "^4.4.3"
"@typescript-eslint/scope-manager@8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz#7ee65e9a6eb3ccdc4816593a4ff38840306de88a"
integrity sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==
"@typescript-eslint/scope-manager@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz#c9cf7ecd234f7ec346f62e5c7d28dfaf4d507b4d"
integrity sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==
dependencies:
"@typescript-eslint/types" "8.62.1"
"@typescript-eslint/visitor-keys" "8.62.1"
"@typescript-eslint/types" "8.63.0"
"@typescript-eslint/visitor-keys" "8.63.0"
"@typescript-eslint/tsconfig-utils@8.62.1", "@typescript-eslint/tsconfig-utils@^8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz#e2b5f24fe721044189cb7e81117c96d75979d627"
integrity sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==
"@typescript-eslint/tsconfig-utils@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz#f7e1cf9a029bb71f4027ffa4194ba82afb564cd3"
integrity sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==
"@typescript-eslint/type-utils@8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz#ebd30b13bacb13070917259a23309cf644121f9a"
integrity sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==
"@typescript-eslint/tsconfig-utils@^8.63.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz#c62ac8ea9173c3cac8b38b8e66e30a046b548851"
integrity sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==
"@typescript-eslint/type-utils@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz#9c00f362140186c588da86b3e10c212800b64b85"
integrity sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==
dependencies:
"@typescript-eslint/types" "8.62.1"
"@typescript-eslint/typescript-estree" "8.62.1"
"@typescript-eslint/utils" "8.62.1"
"@typescript-eslint/types" "8.63.0"
"@typescript-eslint/typescript-estree" "8.63.0"
"@typescript-eslint/utils" "8.63.0"
debug "^4.4.3"
ts-api-utils "^2.5.0"
"@typescript-eslint/types@8.62.1", "@typescript-eslint/types@^8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.62.1.tgz#c58be954e483b2fc98275374d5bcb40b99842dc1"
integrity sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==
"@typescript-eslint/types@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.63.0.tgz#6b32b0a5913520554d81a986acfffba2d32b6963"
integrity sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==
"@typescript-eslint/typescript-estree@8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz#98c1bb17635d5b026b24193a8d29188ac64380ff"
integrity sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==
"@typescript-eslint/types@^8.63.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.64.0.tgz#b41f8ef5dd40616908658b991197a9d486cda60b"
integrity sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==
"@typescript-eslint/typescript-estree@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz#a6f9c9cd290e98203ad29850b0d72529dc83be73"
integrity sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==
dependencies:
"@typescript-eslint/project-service" "8.62.1"
"@typescript-eslint/tsconfig-utils" "8.62.1"
"@typescript-eslint/types" "8.62.1"
"@typescript-eslint/visitor-keys" "8.62.1"
"@typescript-eslint/project-service" "8.63.0"
"@typescript-eslint/tsconfig-utils" "8.63.0"
"@typescript-eslint/types" "8.63.0"
"@typescript-eslint/visitor-keys" "8.63.0"
debug "^4.4.3"
minimatch "^10.2.2"
semver "^7.7.3"
tinyglobby "^0.2.15"
ts-api-utils "^2.5.0"
"@typescript-eslint/utils@8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.62.1.tgz#1622b75c7e6df308181dd0b44855dc4228da0457"
integrity sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==
"@typescript-eslint/utils@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.63.0.tgz#b6a6c8aff1cebd1de4410b3a42b7ec9ba6703f23"
integrity sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==
dependencies:
"@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.62.1"
"@typescript-eslint/types" "8.62.1"
"@typescript-eslint/typescript-estree" "8.62.1"
"@typescript-eslint/scope-manager" "8.63.0"
"@typescript-eslint/types" "8.63.0"
"@typescript-eslint/typescript-estree" "8.63.0"
"@typescript-eslint/visitor-keys@8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz#499657d77ffafb8a99eb1d6c97847ca430234722"
integrity sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==
"@typescript-eslint/visitor-keys@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz#2853a8e33b52f23570338f96cc89cb223daabd44"
integrity sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==
dependencies:
"@typescript-eslint/types" "8.62.1"
"@typescript-eslint/types" "8.63.0"
eslint-visitor-keys "^5.0.0"
"@ungap/structured-clone@^1.0.0":
@@ -6567,10 +6577,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001799:
version "1.0.30001800"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz#b896c773e1c39400809415162bb5320371291b36"
integrity sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001799, caniuse-lite@^1.0.30001803:
version "1.0.30001803"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001803.tgz#b2a5d696e042bc8304dcd4942c39fe330fbbcb24"
integrity sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==
ccount@^2.0.0:
version "2.0.1"
@@ -15262,15 +15272,15 @@ types-ramda@^0.30.1:
dependencies:
ts-toolbelt "^9.6.0"
typescript-eslint@^8.62.0:
version "8.62.1"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.62.1.tgz#eb93fd94d527aa04ec5b844fb0b4ada613cc7d3f"
integrity sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==
typescript-eslint@^8.63.0:
version "8.63.0"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.63.0.tgz#1c2b65c989572af7113fbae7f54c7ffab0fbeb12"
integrity sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==
dependencies:
"@typescript-eslint/eslint-plugin" "8.62.1"
"@typescript-eslint/parser" "8.62.1"
"@typescript-eslint/typescript-estree" "8.62.1"
"@typescript-eslint/utils" "8.62.1"
"@typescript-eslint/eslint-plugin" "8.63.0"
"@typescript-eslint/parser" "8.63.0"
"@typescript-eslint/typescript-estree" "8.63.0"
"@typescript-eslint/utils" "8.63.0"
typescript@~6.0.3:
version "6.0.3"

View File

@@ -29,7 +29,7 @@ maintainers:
- name: craig-rueda
email: craig@craigrueda.com
url: https://github.com/craig-rueda
version: 0.19.0 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
version: 0.20.0 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
dependencies:
- name: postgresql
version: 16.7.27

View File

@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
# superset
![Version: 0.19.0](https://img.shields.io/badge/Version-0.19.0-informational?style=flat-square)
![Version: 0.20.0](https://img.shields.io/badge/Version-0.20.0-informational?style=flat-square)
Apache Superset is a modern, enterprise-ready business intelligence web application
@@ -74,10 +74,42 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
|-----|------|---------|-------------|
| affinity | object | `{}` | |
| bootstrapScript | string | see `values.yaml` | Install additional packages and do any other bootstrap configuration in this script For production clusters it's recommended to build own image with this step done in CI |
| cache | object | `{"asyncQueries":{"keyPrefix":"qc-","timeout":86400},"cacheDb":null,"cacheUrl":null,"celeryDb":null,"celeryUrl":null,"defaultTimeout":86400,"driver":"","enabled":true,"host":null,"keyPrefix":"superset_","password":null,"port":null,"resultsBackendKeyPrefix":"superset_results","sentinel":null,"ssl":{"ca_certs":null,"certfile":null,"enabled":false,"keyfile":null,"ssl_cert_reqs":"required"},"user":""}` | Redis cache configuration for Superset Redis is optional but recommended for caching and Celery. If redis.enabled (chart dependency) is true, defaults point to the chart's Redis instance. |
| cache.asyncQueries | object | `{"keyPrefix":"qc-","timeout":86400}` | Async queries configuration |
| cache.cacheDb | string | `nil` | Redis database number for cache (default: 1 when unset; legacy redis_cache_db is honored) |
| cache.cacheUrl | string | `nil` | Full Redis cache URL (overrides host/port/user/pass if set) |
| cache.celeryDb | string | `nil` | Redis database number for Celery (default: 0 when unset; legacy redis_celery_db is honored) |
| cache.celeryUrl | string | `nil` | Full Redis Celery URL (overrides host/port/user/pass if set) |
| cache.defaultTimeout | int | `86400` | Default cache timeout in seconds |
| cache.driver | string | `""` | Custom Redis driver (e.g. TLS/managed variants); overrides the redis proto in URLs when set. Ports the legacy supersetNode.connections.redis_driver escape hatch. |
| cache.enabled | bool | `true` | Enable Redis-based features (cache, Celery). Set to false to disable Redis usage entirely. |
| cache.host | string | `nil` | Redis host (default: {{ .Release.Name }}-redis-headless) |
| cache.keyPrefix | string | `"superset_"` | Cache key prefix |
| cache.password | string | `nil` | Redis password |
| cache.port | string | `nil` | Redis port (default: 6379 when unset; legacy supersetNode.connections.redis_port is honored) |
| cache.resultsBackendKeyPrefix | string | `"superset_results"` | Results backend key prefix |
| cache.sentinel | string | `nil` | Redis Sentinel configuration (optional) |
| cache.ssl | object | `{"ca_certs":null,"certfile":null,"enabled":false,"keyfile":null,"ssl_cert_reqs":"required"}` | Redis SSL configuration |
| cache.user | string | `""` | Redis user (optional, for Redis ACL) |
| cluster | object | `{"databaseServiceName":null,"domain":".svc.cluster.local","redisServiceName":null,"websocketServiceName":null}` | Kubernetes cluster configuration Used for constructing service URLs between chart components |
| cluster.databaseServiceName | string | `nil` | Database service name (default: {{ .Release.Name }}-postgresql) Override if using a different service name for the database |
| cluster.domain | string | `".svc.cluster.local"` | Kubernetes cluster domain (default: .svc.cluster.local) Override if using a custom cluster domain |
| cluster.redisServiceName | string | `nil` | Redis service name (default: {{ .Release.Name }}-redis-headless) Override if using a different service name for Redis |
| cluster.websocketServiceName | string | `nil` | WebSocket service name (default: {{ .Release.Name }}-ws) Override if using a different service name for the WebSocket service |
| config | object | `{}` | Superset configuration properties Set any configuration property from superset/config.py here See https://github.com/apache/superset/blob/master/superset/config.py for all available options |
| configFromSecret | string | `"{{ template \"superset.fullname\" . }}-config"` | The name of the secret which we will use to generate a superset_config.py file Note: this secret must have the key superset_config.py in it and can include other files as well |
| configMountPath | string | `"/app/pythonpath"` | |
| configOverrides | object | `{}` | A dictionary of overrides to append at the end of superset_config.py - the name does not matter WARNING: the order is not guaranteed Files can be passed as helm --set-file configOverrides.my-override=my-file.py |
| configOverridesFiles | object | `{}` | Same as above but the values are files |
| database | object | `{"driver":null,"host":null,"name":null,"password":null,"port":null,"ssl":{"enabled":false,"mode":"require"},"uri":null,"user":null}` | Database connection configuration for the Superset metadata database |
| database.driver | string | `nil` | Database driver used when uri is not set (default: postgresql+psycopg2 when unset; legacy supersetNode.connections.db_type is honored when this is unset) |
| database.host | string | `nil` | Database host (default: {{ .Release.Name }}-postgresql) |
| database.name | string | `nil` | Database name (default: superset, resolved via superset.db.name) |
| database.password | string | `nil` | Database password (default: superset, resolved via superset.db.password) ⚠️ CHANGE THIS for production |
| database.port | string | `nil` | Database port (default: 5432 when unset; legacy supersetNode.connections.db_port is honored) |
| database.ssl | object | `{"enabled":false,"mode":"require"}` | Database SSL configuration |
| database.uri | string | `nil` | Full database URI (overrides host/port/user/pass/name if set) Example: "postgresql+psycopg2://user:pass@host:5432/dbname" |
| database.user | string | `nil` | Database user (default: superset, resolved via superset.db.user) |
| envFromSecret | string | `"{{ template \"superset.fullname\" . }}-env"` | The name of the secret which we will use to populate env vars in deployed pods This can be useful for secret keys, etc. |
| envFromSecrets | list | `[]` | This can be a list of templated strings |
| extraConfigMountPath | string | `"/app/configs"` | |
@@ -89,7 +121,9 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| extraSecrets | object | `{}` | Extra files to be mounted as Secrets on the path specified in `configMountPath` |
| extraVolumeMounts | list | `[]` | |
| extraVolumes | list | `[]` | |
| featureFlags | object | `{}` | Feature flags configuration See https://github.com/apache/superset/blob/master/RESOURCES/FEATURE_FLAGS.md |
| fullnameOverride | string | `nil` | Provide a name to override the full names of resources |
| globalPodAnnotations | object | `{}` | Global pod annotations to be added to all pods Use this to set annotations that apply to all Superset components Component-specific podAnnotations will be merged with these global annotations |
| hostAliases | list | `[]` | Custom hostAliases for all superset pods # https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/ |
| image.pullPolicy | string | `"IfNotPresent"` | |
| image.repository | string | `"apachesuperset.docker.scarf.sh/apache/superset"` | |
@@ -116,7 +150,7 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| init.enabled | bool | `true` | |
| init.extraContainers | list | `[]` | Launch additional containers into init job pod |
| init.initContainers | list | a container waiting for postgres | List of initContainers |
| init.initscript | string | a script to create admin user and initialize roles | A Superset init script |
| init.initscript | string | unused; kept for backwards-compatibility only | DEPRECATED: this field is no longer used by the chart. The init script is rendered entirely from the internal `superset.initScript` template (which runs `superset db upgrade`, `superset init`, admin creation, and examples). Any customization placed here is silently ignored. See UPGRADING.md. |
| init.jobAnnotations."helm.sh/hook" | string | `"post-install,post-upgrade"` | |
| init.jobAnnotations."helm.sh/hook-delete-policy" | string | `"before-hook-creation"` | |
| init.loadExamples | bool | `false` | |
@@ -143,6 +177,7 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| service.type | string | `"ClusterIP"` | |
| serviceAccount.annotations | object | `{}` | |
| serviceAccount.create | bool | `false` | Create custom service account for Superset. If create: true and serviceAccountName is not provided, `superset.fullname` will be used. |
| serviceAccount.name | string | `""` | Service account name to use (if not specified, defaults to release name + chart name) |
| serviceAccountName | string | `nil` | Specify service account name to be used |
| supersetCeleryBeat.affinity | object | `{}` | Affinity to be added to supersetCeleryBeat deployment |
| supersetCeleryBeat.command | list | a `celery beat` command | Command |
@@ -214,20 +249,7 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| supersetNode.autoscaling.minReplicas | int | `1` | |
| supersetNode.autoscaling.targetCPUUtilizationPercentage | int | `80` | |
| supersetNode.command | list | See `values.yaml` | Startup command |
| supersetNode.connections.db_host | string | `"{{ .Release.Name }}-postgresql"` | |
| supersetNode.connections.db_name | string | `"superset"` | |
| supersetNode.connections.db_pass | string | `"superset"` | |
| supersetNode.connections.db_port | string | `"5432"` | |
| supersetNode.connections.db_type | string | `"postgresql"` | Database type for Superset metadata (Supported types: "postgresql", "mysql") |
| supersetNode.connections.db_user | string | `"superset"` | |
| supersetNode.connections.redis_cache_db | string | `"1"` | |
| supersetNode.connections.redis_celery_db | string | `"0"` | |
| supersetNode.connections.redis_driver | string | `""` | |
| supersetNode.connections.redis_host | string | `"{{ .Release.Name }}-redis-headless"` | Change in case of bringing your own redis and then also set redis.enabled:false |
| supersetNode.connections.redis_port | string | `"6379"` | |
| supersetNode.connections.redis_ssl.enabled | bool | `false` | |
| supersetNode.connections.redis_ssl.ssl_cert_reqs | string | `"CERT_NONE"` | |
| supersetNode.connections.redis_user | string | `""` | |
| supersetNode.connections | object | `{}` | |
| supersetNode.containerSecurityContext | object | `{}` | |
| supersetNode.deploymentAdditionalPodSpec | object | `{}` | Custom pod spec to be added to supersetNode deployment |
| supersetNode.deploymentAnnotations | object | `{}` | Annotations to be added to supersetNode deployment |
@@ -334,6 +356,11 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| supersetWorker.deploymentLabels | object | `{}` | Labels to be added to supersetWorker deployment |
| supersetWorker.extraContainers | list | `[]` | Launch additional containers into supersetWorker pod |
| supersetWorker.forceReload | bool | `false` | If true, forces deployment to reload on each upgrade |
| supersetWorker.healthCheck | object | `{"enabled":false,"livenessFile":"/tmp/celery_worker_alive","livenessHeartbeatInterval":10,"readinessFile":"/tmp/celery_worker_ready"}` | Celery worker file-based health check (worker writes readiness/liveness files via signals; point supersetWorker.readinessProbe/livenessProbe at these files to use them) |
| supersetWorker.healthCheck.enabled | bool | `false` | Enable the file-based Celery worker health check |
| supersetWorker.healthCheck.livenessFile | string | `"/tmp/celery_worker_alive"` | Liveness file (touched periodically by a heartbeat thread) |
| supersetWorker.healthCheck.livenessHeartbeatInterval | int | `10` | Seconds between liveness heartbeats |
| supersetWorker.healthCheck.readinessFile | string | `"/tmp/celery_worker_ready"` | Readiness file (created when the worker is ready, removed on shutdown) |
| supersetWorker.initContainers | list | a container waiting for postgres and redis | Init container |
| supersetWorker.lifecycle | object | `{}` | Container lifecycle hooks for the worker pod |
| supersetWorker.livenessProbe.exec.command | list | a `celery inspect ping` command | Liveness probe command |

161
helm/superset/UPGRADING.md Normal file
View File

@@ -0,0 +1,161 @@
<!--
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.
-->
# Upgrading the Superset Helm Chart
## Upgrading to chart 0.20.0
Chart 0.20.0 introduces a structured connection schema. The old keys listed below are **DEPRECATED** — they still work (auto-mapped to the new keys at render time) and a deprecation warning is printed in `helm install`/`helm upgrade` NOTES, but they will be removed in a future release. Migrate as soon as possible.
### 1. Connection schema: `supersetNode.connections.*` → `database.*` / `cache.*`
**Before (deprecated — still honored):**
```yaml
supersetNode:
connections:
db_host: "pg-host"
db_port: "5432"
db_user: "superset"
db_pass: "superset"
db_name: "superset"
redis_host: "redis-host"
redis_port: "6379"
redis_cache_db: "1"
redis_celery_db: "0"
redis_driver: "rediss"
```
**After (new structured keys — recommended):**
```yaml
database:
host: "pg-host"
port: 5432
user: "superset"
password: "superset"
name: "superset"
cache:
host: "redis-host"
port: 6379
cacheDb: 1
celeryDb: 0
driver: "rediss" # optional: custom Redis driver / TLS variant
```
Key-by-key mapping summary:
| Old key | New key |
|---|---|
| `supersetNode.connections.db_host` | `database.host` |
| `supersetNode.connections.db_port` | `database.port` |
| `supersetNode.connections.db_user` | `database.user` |
| `supersetNode.connections.db_pass` | `database.password` |
| `supersetNode.connections.db_name` | `database.name` |
| `supersetNode.connections.redis_host` | `cache.host` |
| `supersetNode.connections.redis_port` | `cache.port` |
| `supersetNode.connections.redis_cache_db` | `cache.cacheDb` |
| `supersetNode.connections.redis_celery_db` | `cache.celeryDb` |
| `supersetNode.connections.redis_driver` | `cache.driver` |
### 2. Service account: root `serviceAccountName` → `serviceAccount.name`
**Before (deprecated — still honored):**
```yaml
serviceAccountName: my-sa
```
**After (new key — recommended):**
```yaml
serviceAccount:
name: my-sa
```
### 3. Init script: `init.initscript` is deprecated and replaced by the built-in template
> **This is a behavior change.** The chart no longer uses `init.initscript`. The init script is rendered
> entirely from an internal chart template (`superset.initScript`), which runs the full initialization
> sequence:
>
> 1. `superset db upgrade` — applies all pending database schema migrations
> 2. `superset init` — initializes roles and permissions
> 3. Admin user creation (when `init.createAdmin: true`)
> 4. Example data loading (when `init.loadExamples: true`)
> 5. Datasource import (when `import_datasources.yaml` is present)
>
> A future PR will optionally split the database migration step into a dedicated upgrade Job for
> zero-downtime deployments. Until that PR lands, migrations run as part of the init Job above.
If you customized `init.initscript` in your `values.yaml`, that customization is silently ignored.
Move any customizations to `config` or `configOverrides`:
```yaml
# Move custom Python config here:
config:
MY_SETTING: "value"
configOverrides:
my_custom_override: |
# Python snippet appended to superset_config.py
MY_SETTING = "value"
```
If your use case cannot be covered by `config` or `configOverrides`, please open an issue so the maintainers
can evaluate extending the template.
### 4. New top-level sections: `config.*` and `featureFlags.*`
Two new sections provide direct Superset configuration passthrough without needing raw `configOverrides`.
**`config.*` — direct Superset config properties:**
```yaml
config:
SECRET_KEY: "$(SUPERSET_SECRET_KEY)"
ROW_LIMIT: 50000
WTF_CSRF_ENABLED: true
SQLALCHEMY_POOL_SIZE: 10
```
Each key is injected verbatim into `superset_config.py`. String values are quoted; non-string values
(integers, booleans) are rendered as-is.
**`featureFlags.*` — feature flag overrides:**
```yaml
featureFlags:
ALERT_REPORTS: true
DASHBOARD_RBAC: true
ENABLE_TEMPLATE_PROCESSING: false
```
This is equivalent to setting `FEATURE_FLAGS = {...}` in `superset_config.py`, but is more readable and
schema-validated.
### 5. `values.schema.json` — early validation
The chart now ships a `values.schema.json` that validates the values you provide. Wrong types (e.g., a string
where an integer is expected) or unknown keys in structured sections will cause `helm install`/`helm upgrade`
to fail immediately with a descriptive error instead of producing a broken deployment.
If you get a validation error after upgrading, check that your overridden values match the types documented in
`values.yaml` and the schema.

View File

@@ -35,3 +35,9 @@
echo "Visit http://127.0.0.1:8088 to use your application"
kubectl port-forward service/superset 8088:8088 --namespace {{ .Release.Namespace }}
{{- end }}
{{- $warnings := include "superset.deprecationWarnings" . }}
{{- if trim $warnings }}
⚠️ DEPRECATION WARNINGS
{{ $warnings }}
{{- end }}

View File

@@ -47,11 +47,12 @@ If release name contains chart name it will be used as a full name.
Create the name of the service account to use
*/}}
{{- define "superset.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{- default (include "superset.fullname" .) .Values.serviceAccountName -}}
{{- else -}}
{{- default "default" .Values.serviceAccountName -}}
{{- end -}}
{{- $name := coalesce .Values.serviceAccount.name .Values.serviceAccountName -}}
{{- if .Values.serviceAccount.create -}}
{{- default (include "superset.fullname" .) $name -}}
{{- else -}}
{{- default "default" $name -}}
{{- end -}}
{{- end -}}
{{/*
@@ -105,80 +106,572 @@ app.kubernetes.io/component: {{ .component }}
{{- end -}}
{{- define "superset-config" }}
{{/*
Coalescing resolvers for DB and Redis connection parameters.
Each resolver checks (in order):
1. New top-level database.* / cache.* values
2. Legacy supersetNode.connections.* keys (deprecation path, using safe index to avoid errors on absent maps)
3. cluster.* service name overrides
4. Hard-coded defaults derived from the release name
Call with root context: {{ include "superset.db.host" . }}
*/}}
{{/*
Helper to safely read .Values.supersetNode.connections.<key> without erroring when maps are absent.
*/}}
{{- define "_superset.legacyConn" -}}
{{- $sn := index .Values "supersetNode" | default dict -}}
{{- $conn := index $sn "connections" | default dict -}}
{{- $conn | toJson -}}
{{- end -}}
{{- define "superset.db.host" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- tpl (coalesce .Values.database.host (index $conn "db_host") .Values.cluster.databaseServiceName (printf "%s-postgresql" .Release.Name)) $ -}}
{{- end -}}
{{- define "superset.db.port" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- if .Values.database.port -}}
{{- .Values.database.port | toString -}}
{{- else -}}
{{- coalesce (index $conn "db_port") "5432" -}}
{{- end -}}
{{- end -}}
{{- define "superset.db.user" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- coalesce .Values.database.user (index $conn "db_user") "superset" -}}
{{- end -}}
{{- define "superset.db.password" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- coalesce .Values.database.password (index $conn "db_pass") "superset" -}}
{{- end -}}
{{- define "superset.db.name" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- coalesce .Values.database.name (index $conn "db_name") "superset" -}}
{{- end -}}
{{- define "superset.db.driver" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- coalesce .Values.database.driver (index $conn "db_type") "postgresql+psycopg2" -}}
{{- end -}}
{{- define "superset.redis.host" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- tpl (coalesce .Values.cache.host (index $conn "redis_host") .Values.cluster.redisServiceName (printf "%s-redis-headless" .Release.Name)) $ -}}
{{- end -}}
{{- define "superset.redis.port" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- if .Values.cache.port -}}
{{- .Values.cache.port | toString -}}
{{- else -}}
{{- coalesce (index $conn "redis_port") "6379" -}}
{{- end -}}
{{- end -}}
{{- define "superset.redis.user" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- coalesce .Values.cache.user (index $conn "redis_user") "" -}}
{{- end -}}
{{- define "superset.redis.password" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- coalesce .Values.cache.password (index $conn "redis_password") "" -}}
{{- end -}}
{{- define "superset.redis.cacheDb" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- if not (kindIs "invalid" .Values.cache.cacheDb) -}}
{{- .Values.cache.cacheDb | toString -}}
{{- else -}}
{{- coalesce (index $conn "redis_cache_db") "1" -}}
{{- end -}}
{{- end -}}
{{- define "superset.redis.celeryDb" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- if not (kindIs "invalid" .Values.cache.celeryDb) -}}
{{- .Values.cache.celeryDb | toString -}}
{{- else -}}
{{- coalesce (index $conn "redis_celery_db") "0" -}}
{{- end -}}
{{- end -}}
{{- define "superset.redis.proto" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- if .Values.cache.driver }}{{ .Values.cache.driver }}{{- else if index $conn "redis_driver" }}{{ index $conn "redis_driver" }}{{- else if .Values.cache.ssl.enabled }}rediss{{- else }}redis{{- end -}}
{{- end -}}
{{- define "superset.config" }}
{{- /* SECURITY: Validate admin password is set if admin creation is enabled */}}
{{- if and .Values.init.createAdmin (or (not .Values.init.adminUser.password) (eq .Values.init.adminUser.password "")) }}
{{- fail "SECURITY ERROR: init.createAdmin is true but init.adminUser.password is empty. You must set a secure password using --set init.adminUser.password='your-password' or via external secret." }}
{{- end }}
import os
import json
from urllib.parse import quote
{{- if or .Values.config.cacheConfig .Values.config.dataCacheConfig .Values.config.resultsBackend .Values.config.celeryConfig .Values.cache.enabled }}
from flask_caching.backends.rediscache import RedisCache
{{- end }}
def env(key, default=None):
return os.getenv(key, default)
# Redis Base URL
{{- if .Values.supersetNode.connections.redis_password }}
REDIS_BASE_URL=f"{env('REDIS_DRIVER') or env('REDIS_PROTO')}://{env('REDIS_USER', '')}:{env('REDIS_PASSWORD')}@{env('REDIS_HOST')}:{env('REDIS_PORT')}"
{{- /* Database Configuration - Superset always requires a database */}}
{{- if .Values.database.uri }}
SQLALCHEMY_DATABASE_URI = {{ .Values.database.uri | quote }}
{{- else }}
REDIS_BASE_URL=f"{env('REDIS_DRIVER') or env('REDIS_PROTO')}://{env('REDIS_HOST')}:{env('REDIS_PORT')}"
{{- $driver := include "superset.db.driver" . }}
{{- $sslParams := "" }}
{{- if and (hasKey .Values.database "ssl") .Values.database.ssl.enabled }}
{{- $sslMode := .Values.database.ssl.mode | default "require" }}
{{- $sslParams = printf "?sslmode=%s" $sslMode }}
{{- end }}
SQLALCHEMY_DATABASE_URI = f"{{ $driver }}://{quote(env('DB_USER', ''), safe='')}:{quote(env('DB_PASS', ''), safe='')}@{env('DB_HOST')}:{env('DB_PORT')}/{env('DB_NAME')}{{ $sslParams }}"
{{- end }}
{{- if hasKey .Values.config "SQLALCHEMY_TRACK_MODIFICATIONS" }}
SQLALCHEMY_TRACK_MODIFICATIONS = {{ .Values.config.SQLALCHEMY_TRACK_MODIFICATIONS | toString | title }}
{{- else }}
SQLALCHEMY_TRACK_MODIFICATIONS = False
{{- end }}
# Redis URL Params
{{- if .Values.supersetNode.connections.redis_ssl.enabled }}
REDIS_URL_PARAMS = f"?ssl_cert_reqs={env('REDIS_SSL_CERT_REQS')}"
{{- /* Redis Configuration - only if Redis cache is configured */}}
{{- if .Values.cache.enabled }}
{{- if .Values.cache.cacheUrl }}
CACHE_REDIS_URL = {{ .Values.cache.cacheUrl | quote }}
{{- else }}
{{- $useSSL := and (hasKey .Values.cache "ssl") .Values.cache.ssl.enabled }}
_redis_user = quote(env('REDIS_USER', ''), safe='')
_redis_password = quote(env('REDIS_PASSWORD', ''), safe='')
_redis_auth = f"{_redis_user}:{_redis_password}@" if (_redis_user or _redis_password) else ""
REDIS_BASE_URL = f"{{ include "superset.redis.proto" . }}://{_redis_auth}{env('REDIS_HOST')}:{env('REDIS_PORT')}"
{{- if $useSSL }}
{{- $sslCertReqs := .Values.cache.ssl.ssl_cert_reqs | default "required" }}
REDIS_URL_PARAMS = f"?ssl_cert_reqs={{ $sslCertReqs }}"
{{- else }}
REDIS_URL_PARAMS = ""
{{- end}}
{{- end }}
{{- $cacheDb := include "superset.redis.cacheDb" . }}
CACHE_REDIS_URL = f"{REDIS_BASE_URL}/{{ $cacheDb }}{REDIS_URL_PARAMS}"
{{- end }}
{{- if .Values.cache.celeryUrl }}
CELERY_REDIS_URL = {{ .Values.cache.celeryUrl | quote }}
{{- else if not .Values.cache.cacheUrl }}
{{- $celeryDb := include "superset.redis.celeryDb" . }}
CELERY_REDIS_URL = f"{REDIS_BASE_URL}/{{ $celeryDb }}{REDIS_URL_PARAMS}"
{{- else }}
{{- if or .Values.config.celeryConfig (not .Values.cache.enabled) }}
{{- /* Custom celeryConfig provided or cache disabled - OK */}}
{{- else }}
{{- fail "CONFIGURATION ERROR: cache.cacheUrl is set but cache.celeryUrl is not set. When using cacheUrl, you must also set celeryUrl for Celery to work. Alternatively, set config.celeryConfig to provide a custom Celery configuration." }}
{{- end }}
{{- end }}
{{- end }}
# Build Redis URLs
CACHE_REDIS_URL = f"{REDIS_BASE_URL}/{env('REDIS_DB', 1)}{REDIS_URL_PARAMS}"
CELERY_REDIS_URL = f"{REDIS_BASE_URL}/{env('REDIS_CELERY_DB', 0)}{REDIS_URL_PARAMS}"
MAPBOX_API_KEY = env('MAPBOX_API_KEY', '')
{{- /* Cache Configuration */}}
{{- if .Values.config.cacheConfig }}
CACHE_CONFIG = json.loads({{ .Values.config.cacheConfig | toJson | quote }})
{{- else if .Values.cache.enabled }}
CACHE_CONFIG = {
'CACHE_TYPE': 'RedisCache',
'CACHE_DEFAULT_TIMEOUT': 300,
'CACHE_KEY_PREFIX': 'superset_',
'CACHE_REDIS_URL': CACHE_REDIS_URL,
'CACHE_TYPE': 'RedisCache',
'CACHE_DEFAULT_TIMEOUT': {{ .Values.cache.defaultTimeout | default (.Values.config.cacheDefaultTimeout | default 86400) | int }},
'CACHE_KEY_PREFIX': {{ .Values.cache.keyPrefix | default "superset_" | quote }},
'CACHE_REDIS_URL': CACHE_REDIS_URL,
}
{{- end }}
{{- if .Values.config.dataCacheConfig }}
DATA_CACHE_CONFIG = json.loads({{ .Values.config.dataCacheConfig | toJson | quote }})
{{- else if .Values.config.cacheConfig }}
DATA_CACHE_CONFIG = CACHE_CONFIG
{{- else if .Values.cache.enabled }}
DATA_CACHE_CONFIG = CACHE_CONFIG
{{- end }}
{{- /* SQLLAB_ASYNC_TIME_LIMIT_SEC - Required for async_queries module import (default: 6 hours) */}}
{{- if .Values.config.SQLLAB_ASYNC_TIME_LIMIT_SEC }}
SQLLAB_ASYNC_TIME_LIMIT_SEC = {{ .Values.config.SQLLAB_ASYNC_TIME_LIMIT_SEC | int }}
{{- else }}
from datetime import timedelta
SQLLAB_ASYNC_TIME_LIMIT_SEC = int(timedelta(hours=6).total_seconds())
{{- end }}
if os.getenv("SQLALCHEMY_DATABASE_URI"):
SQLALCHEMY_DATABASE_URI = os.getenv("SQLALCHEMY_DATABASE_URI")
else:
{{- if eq .Values.supersetNode.connections.db_type "postgresql" }}
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
{{- else if eq .Values.supersetNode.connections.db_type "mysql" }}
SQLALCHEMY_DATABASE_URI = f"mysql+mysqldb://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
{{- else }}
{{ fail (printf "Unsupported database type: %s. Please use 'postgresql' or 'mysql'." .Values.supersetNode.connections.db_type) }}
{{- end }}
{{- /* Celery Configuration */}}
{{- if .Values.config.celeryConfig }}
{{- if kindIs "string" .Values.config.celeryConfig }}
{{ .Values.config.celeryConfig }}
{{- else }}
class CeleryConfig:
{{- range $key, $value := .Values.config.celeryConfig }}
{{ $key }} = json.loads({{ $value | toJson | quote }})
{{- end }}
{{- if hasKey .Values.config.celeryConfig "imports" }}
CELERY_IMPORTS = CeleryConfig.imports
{{- else }}
CELERY_IMPORTS = ()
{{- end }}
CELERY_CONFIG = CeleryConfig
{{- end }}
{{- else if .Values.cache.enabled }}
from celery.schedules import crontab
from datetime import timedelta
class CeleryConfig:
imports = ("superset.sql_lab", )
broker_url = CELERY_REDIS_URL
result_backend = CELERY_REDIS_URL
imports = (
"superset.sql_lab",
"superset.tasks.scheduler",
"superset.tasks.thumbnails",
"superset.tasks.cache",
)
broker_connection_retry_on_startup = True
worker_prefetch_multiplier = 10
task_acks_late = True
broker_url = CELERY_REDIS_URL
result_backend = CELERY_REDIS_URL
task_annotations = {
"sql_lab.get_sql_results": {
"rate_limit": "100/s",
},
}
beat_schedule = {
"reports.scheduler": {
"task": "reports.scheduler",
"schedule": crontab(minute="*", hour="*"),
"options": {"expires": int(timedelta(weeks=1).total_seconds())},
},
"reports.prune_log": {
"task": "reports.prune_log",
"schedule": crontab(minute=0, hour=0),
},
}
CELERY_IMPORTS = CeleryConfig.imports
CELERY_CONFIG = CeleryConfig
RESULTS_BACKEND = RedisCache(
host=env('REDIS_HOST'),
{{- if .Values.supersetNode.connections.redis_password }}
password=env('REDIS_PASSWORD'),
{{- end }}
port=env('REDIS_PORT'),
key_prefix='superset_results',
{{- if .Values.supersetNode.connections.redis_ssl.enabled }}
ssl=True,
ssl_cert_reqs=env('REDIS_SSL_CERT_REQS'),
{{- end }}
)
{{- end }}
{{ if .Values.configOverrides }}
# Overrides
{{- /* Celery Worker Health Check - File-based health probes for Kubernetes */}}
{{- if and .Values.supersetWorker.healthCheck .Values.supersetWorker.healthCheck.enabled }}
# Celery Worker Health Check Configuration
import threading
from celery import bootsteps
from celery.signals import worker_ready, worker_shutdown, worker_init
_readiness_file = {{ .Values.supersetWorker.healthCheck.readinessFile | default "/tmp/celery_worker_ready" | quote }}
_liveness_file = {{ .Values.supersetWorker.healthCheck.livenessFile | default "/tmp/celery_worker_alive" | quote }}
_heartbeat_interval = {{ .Values.supersetWorker.healthCheck.livenessHeartbeatInterval | default 10 | int }}
_liveness_thread = None
_liveness_stop_event = None
@worker_ready.connect
def create_ready_file(sender, **kwargs):
try:
open(_readiness_file, 'w').close()
except Exception as e:
print(f"Warning: Could not create readiness file: {e}")
@worker_shutdown.connect
def remove_ready_file(sender, **kwargs):
global _liveness_thread, _liveness_stop_event
if _liveness_stop_event:
_liveness_stop_event.set()
if _liveness_thread:
_liveness_thread.join(timeout=5)
try:
if os.path.exists(_readiness_file):
os.remove(_readiness_file)
if os.path.exists(_liveness_file):
os.remove(_liveness_file)
except Exception as e:
print(f"Warning: Could not remove health check files: {e}")
@worker_init.connect
def start_liveness_heartbeat(sender, **kwargs):
global _liveness_thread, _liveness_stop_event
_liveness_stop_event = threading.Event()
def update_liveness():
while not _liveness_stop_event.is_set():
try:
with open(_liveness_file, 'w') as f:
f.write(str(os.getpid()))
except Exception as e:
print(f"Warning: Could not update liveness file: {e}")
_liveness_stop_event.wait(_heartbeat_interval)
_liveness_thread = threading.Thread(target=update_liveness, daemon=True)
_liveness_thread.start()
{{- else }}
CELERY_WORKER_HEALTH_CHECK_ENABLED = False
{{- end }}
{{- /* Results Backend */}}
{{- $redisHostForBackend := include "superset.redis.host" . }}
{{- $redisPortForBackend := include "superset.redis.port" . }}
{{- $redisPasswordForBackend := include "superset.redis.password" . }}
{{- if .Values.config.resultsBackend }}
{{- if kindIs "string" .Values.config.resultsBackend }}
RESULTS_BACKEND = {{ .Values.config.resultsBackend }}
{{- else }}
RESULTS_BACKEND = RedisCache(
host={{ $redisHostForBackend | quote }},
{{- if $redisPasswordForBackend }}
password={{ $redisPasswordForBackend | quote }},
{{- end }}
port={{ $redisPortForBackend | int }},
key_prefix={{ .Values.cache.resultsBackendKeyPrefix | default "superset_results" | quote }},
{{- if and (hasKey .Values.cache "ssl") .Values.cache.ssl.enabled }}
ssl=True,
ssl_cert_reqs={{ .Values.cache.ssl.ssl_cert_reqs | default "required" | quote }},
{{- end }}
)
{{- end }}
{{- else if .Values.cache.enabled }}
RESULTS_BACKEND = RedisCache(
host={{ $redisHostForBackend | quote }},
{{- if $redisPasswordForBackend }}
password={{ $redisPasswordForBackend | quote }},
{{- end }}
port={{ $redisPortForBackend | int }},
key_prefix={{ .Values.cache.resultsBackendKeyPrefix | default "superset_results" | quote }},
{{- if and (hasKey .Values.cache "ssl") .Values.cache.ssl.enabled }}
ssl=True,
ssl_cert_reqs={{ .Values.cache.ssl.ssl_cert_reqs | default "required" | quote }},
{{- end }}
)
{{- end }}
{{- /* Global Async Queries Cache Backend */}}
{{- $redisUserForGaq := include "superset.redis.user" . }}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_CACHE_BACKEND }}
GLOBAL_ASYNC_QUERIES_CACHE_BACKEND = json.loads({{ .Values.config.GLOBAL_ASYNC_QUERIES_CACHE_BACKEND | toJson | quote }})
{{- else if .Values.cache.enabled }}
GLOBAL_ASYNC_QUERIES_CACHE_BACKEND = {
"CACHE_TYPE": "RedisCache",
"CACHE_REDIS_HOST": {{ $redisHostForBackend | quote }},
"CACHE_REDIS_PORT": {{ $redisPortForBackend | int }},
{{- if $redisUserForGaq }}
"CACHE_REDIS_USER": {{ $redisUserForGaq | quote }},
{{- end }}
{{- if $redisPasswordForBackend }}
"CACHE_REDIS_PASSWORD": {{ $redisPasswordForBackend | quote }},
{{- else }}
"CACHE_REDIS_PASSWORD": "",
{{- end }}
"CACHE_REDIS_DB": {{ .Values.cache.asyncQueries.db | default .Values.cache.cacheDb | default 0 | int }},
"CACHE_KEY_PREFIX": {{ .Values.cache.asyncQueries.keyPrefix | default "qc-" | quote }},
"CACHE_DEFAULT_TIMEOUT": {{ .Values.cache.asyncQueries.timeout | default 86400 | int }},
{{- if and .Values.cache.sentinel .Values.cache.sentinel.enabled }}
{{- if .Values.cache.sentinel.sentinels }}
"CACHE_REDIS_SENTINELS": {{ .Values.cache.sentinel.sentinels | toJson }},
{{- else }}
{{- fail "CONFIGURATION ERROR: cache.sentinel.enabled is true but cache.sentinel.sentinels is not set. You must provide Sentinel host(s) in cache.sentinel.sentinels (e.g., [['sentinel-host', 26379]])." }}
{{- end }}
"CACHE_REDIS_SENTINEL_MASTER": {{ .Values.cache.sentinel.master | default "mymaster" | quote }},
{{- if .Values.cache.sentinel.password }}
"CACHE_REDIS_SENTINEL_PASSWORD": {{ .Values.cache.sentinel.password | quote }},
{{- else }}
"CACHE_REDIS_SENTINEL_PASSWORD": None,
{{- end }}
{{- end }}
{{- if and (hasKey .Values.cache "ssl") .Values.cache.ssl.enabled }}
"CACHE_REDIS_SSL": True,
"CACHE_REDIS_SSL_CERTFILE": {{ if .Values.cache.ssl.certfile }}{{ .Values.cache.ssl.certfile | quote }}{{ else }}None{{ end }},
"CACHE_REDIS_SSL_KEYFILE": {{ if .Values.cache.ssl.keyfile }}{{ .Values.cache.ssl.keyfile | quote }}{{ else }}None{{ end }},
"CACHE_REDIS_SSL_CERT_REQS": {{ .Values.cache.ssl.ssl_cert_reqs | default "required" | quote }},
"CACHE_REDIS_SSL_CA_CERTS": {{ if .Values.cache.ssl.ca_certs }}{{ .Values.cache.ssl.ca_certs | quote }}{{ else }}None{{ end }},
{{- else }}
"CACHE_REDIS_SSL": False,
"CACHE_REDIS_SSL_CERTFILE": None,
"CACHE_REDIS_SSL_KEYFILE": None,
"CACHE_REDIS_SSL_CERT_REQS": {{ .Values.cache.ssl.ssl_cert_reqs | default "required" | quote }},
"CACHE_REDIS_SSL_CA_CERTS": None,
{{- end }}
}
{{- end }}
{{- /* Global Async Queries Results Backend */}}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND }}
GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND = json.loads({{ .Values.config.GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND | toJson | quote }})
{{- else if .Values.cache.enabled }}
GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND = {
"backend": "redis",
"host": {{ $redisHostForBackend | quote }},
"port": {{ $redisPortForBackend | int }},
"prefix": {{ .Values.cache.asyncQueries.keyPrefix | default "qc-" | quote }},
"db": {{ .Values.cache.asyncQueries.db | default .Values.cache.cacheDb | default 0 | int }},
{{- if $redisPasswordForBackend }}
"password": {{ $redisPasswordForBackend | quote }},
{{- end }}
}
{{- end }}
{{- /* Feature Flags */}}
{{- if .Values.featureFlags }}
FEATURE_FLAGS = {
{{- range $key, $value := .Values.featureFlags }}
{{- if kindIs "bool" $value }}
"{{ $key }}": {{ if $value }}True{{ else }}False{{ end }},
{{- else if kindIs "string" $value }}
"{{ $key }}": {{ $value | quote }},
{{- else if kindIs "float64" $value }}
"{{ $key }}": {{ $value }},
{{- else if kindIs "int" $value }}
"{{ $key }}": {{ $value }},
{{- else if kindIs "invalid" $value }}
"{{ $key }}": None,
{{- else }}
"{{ $key }}": json.loads({{ $value | toJson | quote }}),
{{- end }}
{{- end }}
}
{{- end }}
{{- /* FAB Security API - Required for List Roles view in 6.0.0+ */}}
{{- if not (hasKey .Values.config "FAB_ADD_SECURITY_API") }}
FAB_ADD_SECURITY_API = True
{{- end }}
{{- if not (hasKey .Values.config "FAB_ADD_SECURITY_VIEWS") }}
FAB_ADD_SECURITY_VIEWS = True
{{- end }}
{{- /* Global Async Queries Transport - Auto-configure for websockets if enabled */}}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_TRANSPORT }}
GLOBAL_ASYNC_QUERIES_TRANSPORT = {{ .Values.config.GLOBAL_ASYNC_QUERIES_TRANSPORT | quote }}
{{- else if .Values.supersetWebsockets.enabled }}
GLOBAL_ASYNC_QUERIES_TRANSPORT = "ws"
{{- else }}
GLOBAL_ASYNC_QUERIES_TRANSPORT = "polling"
{{- end }}
{{- /* Global Async Queries WebSocket URL */}}
{{- $wsUrl := "" }}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL }}
{{- $wsUrl = .Values.config.GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL }}
GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL = {{ $wsUrl | quote }}
{{- else if and .Values.supersetWebsockets.enabled .Values.supersetWebsockets.websocketUrl }}
{{- $wsUrl = .Values.supersetWebsockets.websocketUrl }}
GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL = {{ $wsUrl | quote }}
{{- else if .Values.supersetWebsockets.enabled }}
{{- $wsServiceName := .Values.cluster.websocketServiceName }}
{{- if not $wsServiceName }}
{{- $wsServiceName = printf "%s-ws" (include "superset.fullname" .) }}
{{- end }}
{{- $wsPort := .Values.supersetWebsockets.service.port | default 8080 }}
{{- $wsPath := "/ws" }}
{{- $clusterDomain := .Values.cluster.domain | default ".svc.cluster.local" }}
{{- $wsUrl = printf "ws://%s.%s%s:%d%s" $wsServiceName .Release.Namespace $clusterDomain $wsPort $wsPath }}
GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL = {{ $wsUrl | quote }}
{{- end }}
{{- /* Global Async Queries JWT Secret */}}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_JWT_SECRET }}
GLOBAL_ASYNC_QUERIES_JWT_SECRET = {{ .Values.config.GLOBAL_ASYNC_QUERIES_JWT_SECRET | quote }}
{{- else if and .Values.supersetWebsockets.enabled .Values.supersetWebsockets.config.jwtSecret }}
GLOBAL_ASYNC_QUERIES_JWT_SECRET = {{ .Values.supersetWebsockets.config.jwtSecret | quote }}
{{- end }}
{{- /* Global Async Queries JWT Cookie Settings */}}
{{- if hasKey .Values.config "GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE" }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE = {{ .Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE | toString | title }}
{{- else if and .Values.supersetWebsockets.enabled (or (hasPrefix "wss://" $wsUrl) .Values.ingress.tls) }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE = True
{{- else if .Values.supersetWebsockets.enabled }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE = False
{{- end }}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE = {{ .Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE | quote }}
{{- else if .Values.supersetWebsockets.enabled }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE = "Lax"
{{- end }}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME = {{ .Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME | quote }}
{{- else if and .Values.supersetWebsockets.enabled .Values.supersetWebsockets.config.jwtCookieName }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME = {{ .Values.supersetWebsockets.config.jwtCookieName | quote }}
{{- end }}
{{- /* Content Security Policy (CSP) */}}
{{- if and .Values.supersetWebsockets.enabled $wsUrl (not (hasKey .Values.config "TALISMAN_CONFIG")) }}
TALISMAN_CONFIG = {
"content_security_policy": {
"base-uri": ["'self'"],
"default-src": ["'self'"],
"img-src": [
"'self'",
"blob:",
"data:",
"https://apachesuperset.gateway.scarf.sh",
"https://static.scarf.sh/",
"ows.terrestris.de",
"https://cdn.document360.io",
],
"worker-src": ["'self'", "blob:"],
"connect-src": [
"'self'",
{{ $wsUrl | quote }},
"https://api.mapbox.com",
"https://events.mapbox.com",
"https://tile.openstreetmap.org",
"https://tile.osm.ch",
],
"object-src": "'none'",
"style-src": [
"'self'",
"'unsafe-inline'",
],
"script-src": ["'self'", "'strict-dynamic'"],
},
"content_security_policy_nonce_in": ["script-src"],
{{- if or (hasPrefix "wss://" $wsUrl) .Values.ingress.tls }}
"force_https": True,
"session_cookie_secure": True,
{{- else }}
"force_https": False,
"session_cookie_secure": False,
{{- end }}
}
{{- end }}
{{- /* General Configuration - iterate through all config values */}}
{{- range $key, $value := .Values.config }}
{{- if and (ne $key "cacheConfig") (ne $key "dataCacheConfig") (ne $key "celeryConfig") (ne $key "resultsBackend") (ne $key "GLOBAL_ASYNC_QUERIES_CACHE_BACKEND") (ne $key "GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND") (ne $key "GLOBAL_ASYNC_QUERIES_TRANSPORT") (ne $key "GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL") (ne $key "GLOBAL_ASYNC_QUERIES_JWT_SECRET") (ne $key "GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE") (ne $key "GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE") (ne $key "GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME") (ne $key "TALISMAN_CONFIG") (ne $key "SQLLAB_ASYNC_TIME_LIMIT_SEC") (ne $key "SQLALCHEMY_TRACK_MODIFICATIONS") }}
{{- if kindIs "map" $value }}
{{ $key }} = json.loads({{ $value | toJson | quote }})
{{- else if kindIs "slice" $value }}
{{ $key }} = json.loads({{ $value | toJson | quote }})
{{- else if kindIs "bool" $value }}
{{ $key }} = {{ if $value }}True{{ else }}False{{ end }}
{{- else if kindIs "string" $value }}
{{- if or (hasPrefix "f\"" $value) (hasPrefix "F\"" $value) (hasPrefix "r\"" $value) (hasPrefix "R\"" $value) (hasPrefix "b\"" $value) (hasPrefix "B\"" $value) }}
{{ $key }} = {{ $value }}
{{- else }}
{{ $key }} = {{ $value | quote }}
{{- end }}
{{- else if kindIs "invalid" $value }}
{{ $key }} = None
{{- else }}
{{ $key }} = {{ $value | toJson }}
{{- end }}
{{- end }}
{{- end }}
{{- /* Custom Config Overrides */}}
{{- if .Values.configOverrides }}
# Custom Overrides
{{- range $key, $value := .Values.configOverrides }}
# {{ $key }}
{{ tpl $value $ }}
{{- end }}
{{- end }}
{{ if .Values.configOverridesFiles }}
{{- if .Values.configOverridesFiles }}
# Overrides from files
{{- $files := .Files }}
{{- range $key, $value := .Values.configOverridesFiles }}
@@ -187,7 +680,57 @@ RESULTS_BACKEND = RedisCache(
{{- end }}
{{- end }}
{{- end -}}
{{- define "superset.initScript" -}}
#!/bin/sh
set -eu
echo "Upgrading DB schema..."
superset db upgrade
echo "Initializing roles and permissions..."
superset init
echo "Init job: Creating admin user and loading initial data..."
{{- if .Values.init.createAdmin }}
echo "Creating admin user (if not present)..."
if superset fab list-users 2>/dev/null | grep -qF {{ printf "username:%s" .Values.init.adminUser.username | squote }}; then
echo "Admin user already exists, skipping."
else
superset fab create-admin \
--username {{ .Values.init.adminUser.username | squote }} \
--firstname {{ .Values.init.adminUser.firstname | squote }} \
--lastname {{ .Values.init.adminUser.lastname | squote }} \
--email {{ .Values.init.adminUser.email | squote }} \
--password {{ .Values.init.adminUser.password | squote }}
fi
{{- else }}
echo "Skipping admin creation (init.createAdmin=false)"
{{- end }}
{{- if .Values.init.loadExamples }}
echo "Loading examples..."
superset load_examples
{{- else }}
echo "Skipping examples (init.loadExamples=false)"
{{- end }}
if [ -f "{{ .Values.extraConfigMountPath }}/import_datasources.yaml" ]; then
echo "Importing database connections..."
superset import_datasources -p {{ .Values.extraConfigMountPath }}/import_datasources.yaml
fi
echo "Init job complete."
{{- end -}}
{{/*
Deprecation warnings returns a newline-separated list of active deprecation messages,
or empty string when no deprecated keys are set. Rendered in NOTES.txt after install/upgrade.
*/}}
{{- define "superset.deprecationWarnings" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- if gt (len (keys $conn)) 0 }}
- supersetNode.connections.* is deprecated; use database.* and cache.* (auto-mapped for now). See UPGRADING.md
{{- end }}
{{- if .Values.serviceAccountName }}
- root serviceAccountName is deprecated; use serviceAccount.name (auto-mapped for now). See UPGRADING.md
{{- end }}
{{- end -}}
{{- define "supersetNode.selectorLabels" -}}
app.kubernetes.io/name: {{ include "superset.name" . }}

View File

@@ -37,9 +37,12 @@ spec:
template:
metadata:
annotations:
checksum/superset_config.py: {{ include "superset-config" . | sha256sum }}
checksum/superset_config.py: {{ include "superset.config" . | sha256sum }}
checksum/superset_bootstrap.sh: {{ tpl .Values.bootstrapScript . | sha256sum }}
checksum/connections: {{ .Values.supersetNode.connections | toYaml | sha256sum }}
checksum/database: {{ .Values.database | toYaml | sha256sum }}
checksum/redis: {{ .Values.cache | toYaml | sha256sum }}
checksum/config: {{ .Values.config | toYaml | sha256sum }}
checksum/featureFlags: {{ .Values.featureFlags | toYaml | sha256sum }}
checksum/extraConfigs: {{ .Values.extraConfigs | toYaml | sha256sum }}
checksum/extraSecrets: {{ .Values.extraSecrets | toYaml | sha256sum }}
checksum/extraSecretEnv: {{ .Values.extraSecretEnv | toYaml | sha256sum }}
@@ -52,6 +55,9 @@ spec:
{{- if .Values.supersetCeleryBeat.podAnnotations }}
{{- toYaml .Values.supersetCeleryBeat.podAnnotations | nindent 8 }}
{{- end }}
{{- with .Values.globalPodAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "supersetCeleryBeat.selectorLabels" . | nindent 8 }}
{{- if .Values.supersetCeleryBeat.podLabels }}

View File

@@ -36,11 +36,14 @@ spec:
template:
metadata:
annotations:
checksum/config: {{ include "superset-config" . | sha256sum }}
checksum/config: {{ include "superset.config" . | sha256sum }}
checksum/secrets: {{ tpl (toJson .Values.extraSecretEnv) . | sha256sum }}
{{- if .Values.supersetCeleryFlower.podAnnotations }}
{{- toYaml .Values.supersetCeleryFlower.podAnnotations | nindent 8 }}
{{- end }}
{{- with .Values.globalPodAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "supersetCeleryFlower.selectorLabels" . | nindent 8 }}
{{- if .Values.supersetCeleryFlower.podLabels }}

View File

@@ -43,9 +43,12 @@ spec:
template:
metadata:
annotations:
checksum/superset_config.py: {{ include "superset-config" . | sha256sum }}
checksum/superset_config.py: {{ include "superset.config" . | sha256sum }}
checksum/superset_bootstrap.sh: {{ tpl .Values.bootstrapScript . | sha256sum }}
checksum/connections: {{ .Values.supersetNode.connections | toYaml | sha256sum }}
checksum/database: {{ .Values.database | toYaml | sha256sum }}
checksum/redis: {{ .Values.cache | toYaml | sha256sum }}
checksum/config: {{ .Values.config | toYaml | sha256sum }}
checksum/featureFlags: {{ .Values.featureFlags | toYaml | sha256sum }}
checksum/extraConfigs: {{ .Values.extraConfigs | toYaml | sha256sum }}
checksum/extraSecrets: {{ .Values.extraSecrets | toYaml | sha256sum }}
checksum/extraSecretEnv: {{ .Values.extraSecretEnv | toYaml | sha256sum }}
@@ -58,6 +61,9 @@ spec:
{{- if .Values.supersetWorker.podAnnotations }}
{{- toYaml .Values.supersetWorker.podAnnotations | nindent 8 }}
{{- end }}
{{- with .Values.globalPodAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "supersetWorker.selectorLabels" . | nindent 8 }}
{{- if .Values.supersetWorker.podLabels }}

View File

@@ -44,6 +44,9 @@ spec:
{{- if .Values.supersetWebsockets.podAnnotations }}
{{- toYaml .Values.supersetWebsockets.podAnnotations | nindent 8 }}
{{- end }}
{{- with .Values.globalPodAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "supersetWebsockets.selectorLabels" . | nindent 8 }}
{{- if .Values.supersetWebsockets.podLabels }}

View File

@@ -44,10 +44,13 @@ spec:
metadata:
annotations:
# Force reload on config changes
checksum/superset_config.py: {{ include "superset-config" . | sha256sum }}
checksum/superset_init.sh: {{ tpl .Values.init.initscript . | sha256sum }}
checksum/superset_config.py: {{ include "superset.config" . | sha256sum }}
checksum/superset_init.sh: {{ include "superset.initScript" . | sha256sum }}
checksum/superset_bootstrap.sh: {{ tpl .Values.bootstrapScript . | sha256sum }}
checksum/connections: {{ .Values.supersetNode.connections | toYaml | sha256sum }}
checksum/database: {{ .Values.database | toYaml | sha256sum }}
checksum/redis: {{ .Values.cache | toYaml | sha256sum }}
checksum/config: {{ .Values.config | toYaml | sha256sum }}
checksum/featureFlags: {{ .Values.featureFlags | toYaml | sha256sum }}
checksum/extraConfigs: {{ .Values.extraConfigs | toYaml | sha256sum }}
checksum/extraSecrets: {{ .Values.extraSecrets | toYaml | sha256sum }}
checksum/extraSecretEnv: {{ .Values.extraSecretEnv | toYaml | sha256sum }}
@@ -60,6 +63,9 @@ spec:
{{- if .Values.supersetNode.podAnnotations }}
{{- toYaml .Values.supersetNode.podAnnotations | nindent 8 }}
{{- end }}
{{- with .Values.globalPodAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "supersetNode.selectorLabels" . | nindent 8 }}
{{- if .Values.supersetNode.podLabels }}

View File

@@ -26,24 +26,28 @@ metadata:
{{- include "superset.labels" . | nindent 4 }}
type: Opaque
stringData:
REDIS_HOST: {{ tpl .Values.supersetNode.connections.redis_host . | quote }}
REDIS_USER: {{ .Values.supersetNode.connections.redis_user | quote }}
{{- if .Values.supersetNode.connections.redis_password }}
REDIS_PASSWORD: {{ .Values.supersetNode.connections.redis_password | quote }}
DB_HOST: {{ include "superset.db.host" . | quote }}
DB_PORT: {{ include "superset.db.port" . | quote }}
DB_USER: {{ include "superset.db.user" . | quote }}
DB_PASS: {{ include "superset.db.password" . | quote }}
DB_NAME: {{ include "superset.db.name" . | quote }}
{{- if .Values.database.ssl.enabled }}
DB_SSL_MODE: {{ .Values.database.ssl.mode | default "require" | quote }}
{{- end }}
{{- if .Values.cache.enabled }}
REDIS_HOST: {{ include "superset.redis.host" . | quote }}
REDIS_PORT: {{ include "superset.redis.port" . | quote }}
REDIS_USER: {{ include "superset.redis.user" . | quote }}
{{- if include "superset.redis.password" . }}
REDIS_PASSWORD: {{ include "superset.redis.password" . | quote }}
{{- end }}
REDIS_DB: {{ include "superset.redis.cacheDb" . | quote }}
REDIS_CELERY_DB: {{ include "superset.redis.celeryDb" . | quote }}
REDIS_PROTO: {{ include "superset.redis.proto" . | quote }}
{{- if .Values.cache.driver }}
REDIS_DRIVER: {{ .Values.cache.driver | quote }}
{{- end }}
REDIS_PORT: {{ .Values.supersetNode.connections.redis_port | quote }}
REDIS_PROTO: {{ if .Values.supersetNode.connections.redis_ssl.enabled }}"rediss"{{ else }}"redis"{{ end }}
REDIS_DRIVER: {{ .Values.supersetNode.connections.redis_driver | quote }}
REDIS_DB: {{ .Values.supersetNode.connections.redis_cache_db | quote }}
REDIS_CELERY_DB: {{ .Values.supersetNode.connections.redis_celery_db | quote }}
{{- if .Values.supersetNode.connections.redis_ssl.enabled }}
REDIS_SSL_CERT_REQS: {{ .Values.supersetNode.connections.redis_ssl.ssl_cert_reqs | default "CERT_NONE" | quote }}
{{- end }}
DB_HOST: {{ tpl .Values.supersetNode.connections.db_host . | quote }}
DB_PORT: {{ .Values.supersetNode.connections.db_port | quote }}
DB_USER: {{ .Values.supersetNode.connections.db_user | quote }}
DB_PASS: {{ .Values.supersetNode.connections.db_pass | quote }}
DB_NAME: {{ .Values.supersetNode.connections.db_name | quote }}
{{- if .Values.extraSecretEnv }}
{{- range $key, $value := .Values.extraSecretEnv }}
{{ $key }}: {{ $value | quote }}

View File

@@ -27,9 +27,9 @@ metadata:
type: Opaque
stringData:
superset_config.py: |
{{- include "superset-config" . | nindent 4 }}
{{- include "superset.config" . | nindent 4 }}
superset_init.sh: |
{{- tpl .Values.init.initscript . | nindent 4 }}
{{- include "superset.initScript" . | nindent 4 }}
superset_bootstrap.sh: |
{{- tpl .Values.bootstrapScript . | nindent 4 }}

View File

@@ -0,0 +1,132 @@
#
# 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: config checksums + global pod annotations
templates:
- deployment.yaml
- deployment-worker.yaml
- deployment-beat.yaml
- deployment-flower.yaml
- deployment-ws.yaml
tests:
- it: main deployment has new config checksums, not the old connections checksum
template: deployment.yaml
asserts:
- exists:
path: 'spec.template.metadata.annotations["checksum/database"]'
- exists:
path: 'spec.template.metadata.annotations["checksum/config"]'
- notExists:
path: 'spec.template.metadata.annotations["checksum/connections"]'
- it: main deployment applies globalPodAnnotations
template: deployment.yaml
set:
globalPodAnnotations:
team: data
asserts:
- equal:
path: 'spec.template.metadata.annotations.team'
value: data
- it: globalPodAnnotations coexist with per-component podAnnotations on deployment.yaml
template: deployment.yaml
set:
globalPodAnnotations:
global: "yes"
supersetNode:
podAnnotations:
component: "yes"
asserts:
- equal:
path: 'spec.template.metadata.annotations.global'
value: "yes"
- equal:
path: 'spec.template.metadata.annotations.component'
value: "yes"
- it: worker deployment has config and database checksums
template: deployment-worker.yaml
asserts:
- exists:
path: 'spec.template.metadata.annotations["checksum/database"]'
- exists:
path: 'spec.template.metadata.annotations["checksum/config"]'
- it: worker deployment applies globalPodAnnotations
template: deployment-worker.yaml
set:
globalPodAnnotations:
team: data
asserts:
- equal:
path: 'spec.template.metadata.annotations.team'
value: data
- it: beat deployment has config and database checksums
template: deployment-beat.yaml
set:
supersetCeleryBeat:
enabled: true
asserts:
- exists:
path: 'spec.template.metadata.annotations["checksum/database"]'
- exists:
path: 'spec.template.metadata.annotations["checksum/config"]'
- it: beat deployment applies globalPodAnnotations
template: deployment-beat.yaml
set:
supersetCeleryBeat:
enabled: true
globalPodAnnotations:
team: data
asserts:
- equal:
path: 'spec.template.metadata.annotations.team'
value: data
- it: flower deployment has config checksum
template: deployment-flower.yaml
set:
supersetCeleryFlower:
enabled: true
asserts:
- exists:
path: 'spec.template.metadata.annotations["checksum/config"]'
- it: flower deployment applies globalPodAnnotations
template: deployment-flower.yaml
set:
supersetCeleryFlower:
enabled: true
globalPodAnnotations:
team: data
asserts:
- equal:
path: 'spec.template.metadata.annotations.team'
value: data
- it: websockets deployment has wsconfig checksum
template: deployment-ws.yaml
set:
supersetWebsockets:
enabled: true
asserts:
- exists:
path: 'spec.template.metadata.annotations["checksum/wsconfig"]'
- it: websockets deployment applies globalPodAnnotations
template: deployment-ws.yaml
set:
supersetWebsockets:
enabled: true
globalPodAnnotations:
team: data
asserts:
- equal:
path: 'spec.template.metadata.annotations.team'
value: data

View File

@@ -0,0 +1,94 @@
#
# 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: superset_config.py rendering
templates:
- secret-superset-config.yaml
tests:
- it: builds SQLALCHEMY_DATABASE_URI from env with percent-encoded creds
set: {database: {host: pg.svc, port: 5432, user: su, password: pw, name: sset}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'SQLALCHEMY_DATABASE_URI = f"postgresql\+psycopg2://\{quote\(env\(.DB_USER'}
- it: honors database.uri override
set: {database.uri: "postgresql://x:y@h:5432/d"}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'SQLALCHEMY_DATABASE_URI = "postgresql://x:y@h:5432/d"'}
- it: imports required for env/json handling are present
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'import json'}
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'from urllib\.parse import quote'}
- it: redis creds are percent-encoded and read from env
set: {cache: {enabled: true, password: "p@ss"}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'quote\(env\(.REDIS_PASSWORD'}
- it: config map values render via json.loads
set: {config: {MY_MAP: {enabled: true}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'MY_MAP = json\.loads\('}
- it: config nil values render as None
set: {config: {MY_NIL: null}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'MY_NIL = None'}
- it: supersetWorker healthCheck enabled renders readiness file path
set: {supersetWorker: {healthCheck: {enabled: true}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: '/tmp/celery_worker_ready'}
- it: SQLALCHEMY_TRACK_MODIFICATIONS defaults False
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'SQLALCHEMY_TRACK_MODIFICATIONS = False'}
- it: renders feature flags
set: {featureFlags: {ALERT_REPORTS: true}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: '"ALERT_REPORTS": True'}
- it: SQLALCHEMY_TRACK_MODIFICATIONS user-set true renders Python True
set: {config: {SQLALCHEMY_TRACK_MODIFICATIONS: true}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'SQLALCHEMY_TRACK_MODIFICATIONS = True'}
- it: celeryConfig without imports emits a safe empty CELERY_IMPORTS (no AttributeError)
set: {config: {celeryConfig: {broker_url: "redis://x"}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'CELERY_IMPORTS = \(\)'}
- notMatchRegex: {path: 'stringData["superset_config.py"]', pattern: 'CELERY_IMPORTS = CeleryConfig\.imports'}
- it: legacy db_type maps to the SQLAlchemy driver (deprecation path)
set: {supersetNode: {connections: {db_type: "postgresql+pg8000"}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'SQLALCHEMY_DATABASE_URI = f"postgresql\+pg8000://'}
- it: init script single-quotes the admin username (no shell injection)
set: {init: {createAdmin: true, adminUser: {password: "x"}}}
asserts:
- matchRegex: {path: 'stringData["superset_init.sh"]', pattern: "grep -qF 'username:"}
- notMatchRegex: {path: 'stringData["superset_init.sh"]', pattern: 'create-admin[\s\S]*\|\| true'}
- it: sentinel disabled does not fail render
set: {cache: {enabled: true, sentinel: {enabled: false}}}
asserts:
- hasDocuments:
count: 1
- it: ssl certfile path is quoted
set: {cache: {enabled: true, ssl: {enabled: true, certfile: "/etc/ssl/cert.pem"}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: '"CACHE_REDIS_SSL_CERTFILE": "/etc/ssl/cert.pem"'}
- it: explicit JWT cookie secure false is respected
set: {supersetWebsockets: {enabled: true}, config: {GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE: false}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE = False'}
- it: celeryConfig non-string values render as valid Python via json.loads
set: {config: {celeryConfig: {broker_url: "redis://x", task_acks_late: true}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'task_acks_late = json\.loads\('}
- it: featureFlags non-scalar values render as valid Python via json.loads
set: {featureFlags: {SOME_MAP: {nested: true}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: '"SOME_MAP": json\.loads\('}

View File

@@ -0,0 +1,28 @@
#
# 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: deprecation path does not fail render
templates:
- secret-superset-config.yaml
tests:
- it: legacy supersetNode.connections still renders (no fail)
set: {supersetNode.connections.db_host: legacy}
asserts:
- hasDocuments: {count: 1}
- it: legacy root serviceAccountName still renders (no fail)
set: {serviceAccountName: my-sa}
asserts:
- hasDocuments: {count: 1}

View File

@@ -0,0 +1,63 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
suite: superset_init.sh rendering
templates:
- secret-superset-config.yaml
tests:
- it: renders superset db upgrade in init script
asserts:
- matchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'superset db upgrade'
- it: renders superset init in init script
asserts:
- matchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'superset init'
- it: renders db upgrade before admin creation
set:
init:
createAdmin: true
adminUser:
username: admin
firstname: Admin
lastname: User
email: admin@example.com
password: adminpass
asserts:
- matchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'superset db upgrade'
- matchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'superset init'
- matchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'fab create-admin'
- it: admin creation is idempotent and does not swallow real failures
set:
init:
createAdmin: true
adminUser:
password: "x"
asserts:
- matchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'superset fab list-users'
- notMatchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'create-admin.*\|\| true'

View File

@@ -0,0 +1,71 @@
#
# 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: secret-env schema + deprecation mapping
templates:
- secret-env.yaml
tests:
- it: emits DB_* and REDIS_* from new schema
set:
database: {host: pg.svc, port: 5432, user: su, password: pw, name: superset}
cache: {enabled: true, host: rd.svc, port: 6379, cacheDb: 1, celeryDb: 0}
asserts:
- equal: {path: 'stringData.DB_HOST', value: pg.svc}
- equal: {path: 'stringData.DB_PORT', value: "5432"}
- equal: {path: 'stringData.REDIS_HOST', value: rd.svc}
- it: maps ALL legacy supersetNode.connections.* keys (deprecation path, no fail)
set:
cache:
enabled: true
supersetNode:
connections:
db_host: legacy-pg
db_port: "5555"
db_user: legacy-user
db_pass: legacy-pass
db_name: legacy-name
redis_host: legacy-rd
redis_port: "6380"
redis_cache_db: "7"
redis_celery_db: "8"
redis_driver: rediss
asserts:
- equal: {path: 'stringData.DB_HOST', value: legacy-pg}
- equal: {path: 'stringData.DB_PORT', value: "5555"}
- equal: {path: 'stringData.DB_USER', value: legacy-user}
- equal: {path: 'stringData.DB_PASS', value: legacy-pass}
- equal: {path: 'stringData.DB_NAME', value: legacy-name}
- equal: {path: 'stringData.REDIS_HOST', value: legacy-rd}
- equal: {path: 'stringData.REDIS_PORT', value: "6380"}
- equal: {path: 'stringData.REDIS_DB', value: "7"}
- equal: {path: 'stringData.REDIS_CELERY_DB', value: "8"}
- equal: {path: 'stringData.REDIS_PROTO', value: rediss}
- it: new-schema values still win over legacy when both are set
set:
database: {host: new-host, port: 1234}
supersetNode:
connections: {db_host: old-host, db_port: "9999"}
asserts:
- equal: {path: 'stringData.DB_HOST', value: new-host}
- equal: {path: 'stringData.DB_PORT', value: "1234"}
- it: emits REDIS_DRIVER when cache.driver set
set: {cache: {enabled: true, driver: "rediss+managed"}}
asserts:
- equal: {path: 'stringData.REDIS_DRIVER', value: "rediss+managed"}
- it: omits redis vars when cache disabled
set: {cache.enabled: false}
asserts:
- notExists: {path: 'stringData.REDIS_HOST'}

View File

@@ -0,0 +1,28 @@
#
# 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: serviceAccountName resolution
templates:
- serviceaccount.yaml
tests:
- it: uses serviceAccount.name when set
set: {serviceAccount: {create: true, name: my-sa}}
asserts:
- equal: {path: 'metadata.name', value: my-sa}
- it: falls back to legacy root serviceAccountName (deprecation path)
set: {serviceAccount: {create: true}, serviceAccountName: legacy-sa}
asserts:
- equal: {path: 'metadata.name', value: legacy-sa}

View File

@@ -0,0 +1,30 @@
#
# 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: values defaults
templates:
- secret-env.yaml
tests:
- it: renders with new database defaults
set:
database.host: db.example
cache.enabled: true
asserts:
- hasDocuments:
count: 1
- equal:
path: 'stringData.DB_HOST'
value: db.example

View File

@@ -0,0 +1,140 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"additionalProperties": true,
"properties": {
"database": {
"type": "object",
"additionalProperties": true,
"properties": {
"uri": {
"type": ["string", "null"]
},
"driver": {
"type": ["string", "null"]
},
"host": {
"type": ["string", "null"]
},
"port": {
"type": ["integer", "string", "null"]
},
"user": {
"type": ["string", "null"]
},
"password": {
"type": ["string", "null"]
},
"name": {
"type": ["string", "null"]
},
"ssl": {
"type": "object",
"additionalProperties": true,
"properties": {
"enabled": {
"type": "boolean"
},
"mode": {
"type": "string"
}
}
}
}
},
"cache": {
"type": "object",
"additionalProperties": true,
"properties": {
"enabled": {
"type": "boolean"
},
"driver": {
"type": "string"
},
"host": {
"type": ["string", "null"]
},
"port": {
"type": ["integer", "string", "null"]
},
"cacheDb": {
"type": ["integer", "string", "null"]
},
"celeryDb": {
"type": ["integer", "string", "null"]
},
"ssl": {
"type": "object",
"additionalProperties": true,
"properties": {
"enabled": {
"type": "boolean"
}
}
}
}
},
"cluster": {
"type": "object",
"additionalProperties": true,
"properties": {
"domain": {
"type": "string"
},
"databaseServiceName": {
"type": ["string", "null"]
},
"redisServiceName": {
"type": ["string", "null"]
},
"websocketServiceName": {
"type": ["string", "null"]
}
}
},
"config": {
"type": "object",
"additionalProperties": true
},
"featureFlags": {
"type": "object",
"additionalProperties": true
},
"serviceAccount": {
"type": "object",
"additionalProperties": true,
"properties": {
"create": {
"type": "boolean"
},
"name": {
"type": "string"
},
"annotations": {
"type": "object",
"additionalProperties": true
}
}
},
"globalPodAnnotations": {
"type": "object",
"additionalProperties": true
},
"image": {
"type": "object",
"additionalProperties": true,
"properties": {
"repository": {
"type": "string"
},
"tag": {
"type": ["string", "null"]
},
"pullPolicy": {
"type": "string"
}
}
}
}
}

View File

@@ -28,9 +28,30 @@ nameOverride: ~
# -- Provide a name to override the full names of resources
fullnameOverride: ~
# -- Kubernetes cluster configuration
# Used for constructing service URLs between chart components
cluster:
# -- Kubernetes cluster domain (default: .svc.cluster.local)
# Override if using a custom cluster domain
domain: ".svc.cluster.local"
# -- Database service name (default: {{ .Release.Name }}-postgresql)
# Override if using a different service name for the database
databaseServiceName: ~
# -- Redis service name (default: {{ .Release.Name }}-redis-headless)
# Override if using a different service name for Redis
redisServiceName: ~
# -- WebSocket service name (default: {{ .Release.Name }}-ws)
# Override if using a different service name for the WebSocket service
websocketServiceName: ~
# -- Labels to be added to all resources
extraLabels: {}
# -- Global pod annotations to be added to all pods
# Use this to set annotations that apply to all Superset components
# Component-specific podAnnotations will be merged with these global annotations
globalPodAnnotations: {}
# -- User ID directive. This user must have enough permissions to run the bootstrap script
# Running containers as root is not recommended in production. Change this to another UID - e.g. 1000 to be more secure
runAsUser: 0
@@ -46,6 +67,8 @@ serviceAccountName: ~
serviceAccount:
# -- Create custom service account for Superset. If create: true and serviceAccountName is not provided, `superset.fullname` will be used.
create: false
# -- Service account name to use (if not specified, defaults to release name + chart name)
name: ""
annotations: {}
# -- Install additional packages and do any other bootstrap configuration in this script
@@ -244,6 +267,89 @@ hostAliases: []
# - nodns.my.lan
# ip: 18.27.36.45
# -- Database connection configuration for the Superset metadata database
database:
# -- Full database URI (overrides host/port/user/pass/name if set)
# Example: "postgresql+psycopg2://user:pass@host:5432/dbname"
uri: ~
# -- Database driver used when uri is not set (default: postgresql+psycopg2 when unset;
# legacy supersetNode.connections.db_type is honored when this is unset)
driver: ~
# -- Database host (default: {{ .Release.Name }}-postgresql)
host: ~
# -- Database port (default: 5432 when unset; legacy supersetNode.connections.db_port is honored)
port: ~
# -- Database user (default: superset, resolved via superset.db.user)
user: ~
# -- Database password (default: superset, resolved via superset.db.password)
# ⚠️ CHANGE THIS for production
password: ~
# -- Database name (default: superset, resolved via superset.db.name)
name: ~
# -- Database SSL configuration
ssl:
enabled: false
mode: require
# -- Redis cache configuration for Superset
# Redis is optional but recommended for caching and Celery.
# If redis.enabled (chart dependency) is true, defaults point to the chart's Redis instance.
cache:
# -- Enable Redis-based features (cache, Celery). Set to false to disable Redis usage entirely.
enabled: true
# -- Full Redis cache URL (overrides host/port/user/pass if set)
cacheUrl: ~
# -- Full Redis Celery URL (overrides host/port/user/pass if set)
celeryUrl: ~
# -- Redis host (default: {{ .Release.Name }}-redis-headless)
host: ~
# -- Redis port (default: 6379 when unset; legacy supersetNode.connections.redis_port is honored)
port: ~
# -- Redis user (optional, for Redis ACL)
user: ""
# -- Redis password
password: ~
# -- Redis database number for cache (default: 1 when unset; legacy redis_cache_db is honored)
cacheDb: ~
# -- Redis database number for Celery (default: 0 when unset; legacy redis_celery_db is honored)
celeryDb: ~
# -- Cache key prefix
keyPrefix: "superset_"
# -- Default cache timeout in seconds
defaultTimeout: 86400
# -- Results backend key prefix
resultsBackendKeyPrefix: "superset_results"
# -- Async queries configuration
asyncQueries:
keyPrefix: "qc-"
timeout: 86400
# -- Redis Sentinel configuration (optional)
sentinel: ~
# -- Redis SSL configuration
ssl:
enabled: false
ssl_cert_reqs: required
certfile: ~
keyfile: ~
ca_certs: ~
# -- Custom Redis driver (e.g. TLS/managed variants); overrides the redis proto in URLs when set.
# Ports the legacy supersetNode.connections.redis_driver escape hatch.
driver: ""
# -- Superset configuration properties
# Set any configuration property from superset/config.py here
# See https://github.com/apache/superset/blob/master/superset/config.py for all available options
config: {}
# SECRET_KEY: "your-secret-key-here"
# ROW_LIMIT: 50000
# DEBUG: false
# -- Feature flags configuration
# See https://github.com/apache/superset/blob/master/RESOURCES/FEATURE_FLAGS.md
featureFlags: {}
# ALERT_REPORTS: true
# DASHBOARD_RBAC: true
# Superset node configuration
supersetNode:
replicas:
@@ -270,28 +376,8 @@ supersetNode:
- "/bin/sh"
- "-c"
- ". {{ .Values.configMountPath }}/superset_bootstrap.sh; exec /usr/bin/run-server.sh"
connections:
# -- Change in case of bringing your own redis and then also set redis.enabled:false
redis_host: "{{ .Release.Name }}-redis-headless"
redis_port: "6379"
redis_user: ""
# redis_password: superset
redis_cache_db: "1"
redis_celery_db: "0"
# Or SSL port is usually 6380
# Update following for using Redis with SSL
redis_ssl:
enabled: false
ssl_cert_reqs: CERT_NONE
redis_driver: ""
# You need to change below configuration incase bringing own PostgresSQL instance and also set postgresql.enabled:false
# -- Database type for Superset metadata (Supported types: "postgresql", "mysql")
db_type: "postgresql"
db_host: "{{ .Release.Name }}-postgresql"
db_port: "5432"
db_user: superset
db_pass: superset
db_name: superset
# DEPRECATED: use database.* and cache.* instead (still honored if set). See UPGRADING.md
connections: {}
env: {}
# -- If true, forces deployment to reload on each upgrade
forceReload: false
@@ -508,6 +594,17 @@ supersetWorker:
startupProbe: {}
# -- No startup/readiness probes by default since we don't really care about its startup time (it doesn't serve traffic)
readinessProbe: {}
# -- Celery worker file-based health check (worker writes readiness/liveness files via signals;
# point supersetWorker.readinessProbe/livenessProbe at these files to use them)
healthCheck:
# -- Enable the file-based Celery worker health check
enabled: false
# -- Readiness file (created when the worker is ready, removed on shutdown)
readinessFile: "/tmp/celery_worker_ready"
# -- Liveness file (touched periodically by a heartbeat thread)
livenessFile: "/tmp/celery_worker_alive"
# -- Seconds between liveness heartbeats
livenessHeartbeatInterval: 10
# -- Set priorityClassName for supersetWorker pods
priorityClassName: ~
@@ -870,8 +967,11 @@ init:
requests:
cpu: "250m"
memory: "128Mi"
# -- A Superset init script
# @default -- a script to create admin user and initialize roles
# -- DEPRECATED: this field is no longer used by the chart.
# The init script is rendered entirely from the internal `superset.initScript` template
# (which runs `superset db upgrade`, `superset init`, admin creation, and examples).
# Any customization placed here is silently ignored. See UPGRADING.md.
# @default -- unused; kept for backwards-compatibility only
initscript: |-
#!/bin/sh
set -eu

View File

@@ -43,9 +43,9 @@ 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>=6.2.1, <7",
"cachetools>=7.1.4, <8",
"celery>=5.6.3, <6.0.0",
"click>=8.4.0",
"click>=8.4.2",
"click-option-group",
"colorama",
"flask-cors>=6.0.5, <7.0",
@@ -67,7 +67,7 @@ dependencies = [
"gunicorn>=26.0.0, <27; sys_platform != 'win32'",
"hashids>=1.3.1, <2",
# holidays>=0.45 required for security fix
"holidays>=0.45, <1",
"holidays>=0.99, <1",
"humanize",
"isodate",
"jsonpath-ng>=1.8.0, <2",
@@ -110,7 +110,7 @@ dependencies = [
"sqlalchemy>=1.4, <2",
"sqlalchemy-continuum>=1.6.0, <2.0.0",
"sqlalchemy-utils>=0.42.1, <0.43", # expanding lowerbound to work with pydoris
"sqlglot>=30.8.0, <31",
"sqlglot>=30.12.0, <31",
# newer pandas needs 0.9+
"tabulate>=0.10.0, <1.0",
"typing-extensions>=4, <5",
@@ -130,7 +130,7 @@ bigquery = [
"sqlalchemy-bigquery>=1.17.0",
"google-cloud-bigquery>=3.42.1",
]
clickhouse = ["clickhouse-connect>=1.1.1, <2.0"]
clickhouse = ["clickhouse-connect>=1.4.2, <2.0"]
cockroachdb = ["cockroachdb>=0.3.5, <0.4"]
crate = ["sqlalchemy-cratedb>=0.41.0, <1"]
d1 = [
@@ -155,17 +155,17 @@ elasticsearch = ["elasticsearch-dbapi>=0.2.13, <0.3.0"]
exasol = ["sqlalchemy-exasol>=2.4.0, <8.0"]
excel = ["xlrd>=2.0.2, <2.1"]
fastmcp = [
"fastmcp>=3.4.2,<4.0",
"fastmcp>=3.4.3,<4.0",
# tiktoken backs the response-size-guard token estimator. Without
# it, the middleware falls back to a coarser character-based
# heuristic that under-counts JSON-heavy MCP responses.
"tiktoken>=0.13.0,<1.0",
]
firebird = ["sqlalchemy-firebird>=0.7.0, <2.2"]
firebird = ["sqlalchemy-firebird>=0.8.0, <2.2"]
firebolt = ["firebolt-sqlalchemy>=1.0.0, <2"]
gevent = ["gevent>=26.4.0"]
gsheets = ["shillelagh[gsheetsapi]>=1.4.4, <2"]
hana = ["hdbcli==2.28.21", "sqlalchemy_hana==0.4.0"]
hana = ["hdbcli==2.29.23", "sqlalchemy_hana==3.0.3"]
hive = [
"pyhive[hive]>=0.6.5;python_version<'3.11'",
"pyhive[hive_pure_sasl]>=0.7.0",
@@ -173,7 +173,7 @@ hive = [
"thrift>=0.23.0, <1.0.0",
"thrift_sasl>=0.4.3, < 1.0.0",
]
impala = ["impyla>0.16.2, <0.23"]
impala = ["impyla>=0.24.0, <0.25"]
kusto = ["sqlalchemy-kusto>=3.1.2, <4"]
kylin = ["kylinpy>=2.8.4, <2.9"]
mssql = ["pymssql>=2.3.13, <3"]
@@ -192,14 +192,14 @@ pinot = ["pinotdb>=5.0.0, <10.0.0"]
playwright = ["playwright>=1.61.0, <2"]
postgres = ["psycopg2-binary==2.9.12"]
presto = ["pyhive[presto]>=0.6.5"]
trino = ["trino>=0.337.0"]
trino = ["trino>=0.338.0"]
prophet = ["prophet>=1.1.6, <2"]
redshift = ["sqlalchemy-redshift>=0.8.1, <0.9"]
risingwave = ["sqlalchemy-risingwave"]
shillelagh = ["shillelagh[all]>=1.4.4, <2"]
singlestore = ["sqlalchemy-singlestoredb>=1.2.1, <2"]
snowflake = ["snowflake-sqlalchemy>=1.10.2, <2"]
sqlite = ["syntaqlite>=0.1.0,<0.5.0"]
sqlite = ["syntaqlite>=0.7.0,<0.8.0"]
spark = [
"pyhive[hive]>=0.6.5;python_version<'3.11'",
"pyhive[hive_pure_sasl]>=0.7",
@@ -215,7 +215,7 @@ thumbnails = [] # deprecated, will be removed in 7.0
vertica = ["sqlalchemy-vertica-python>= 0.6.3, < 0.7"]
netezza = ["nzalchemy>=11.0.2, < 11.2"]
starrocks = ["starrocks>=1.3.3, <2"]
doris = ["pydoris>=1.0.0, <2.0.0"]
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"]
development = [
@@ -244,7 +244,7 @@ development = [
"ruff",
"sqloxide",
"statsd",
"syntaqlite>=0.4.2,<0.5.0",
"syntaqlite>=0.7.0,<0.8.0",
]
[project.urls]

View File

@@ -35,13 +35,13 @@ filterwarnings =
# error:"SqlMetric" 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 ``as_declarative\(\)`` function is now available: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 connection.execute\(\) method:sqlalchemy.exc.RemovedIn20Warning
# error:The current statement is being autocommitted using implicit autocommit:sqlalchemy.exc.RemovedIn20Warning
# error:The `database` package is deprecated: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 ``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
# error:"User" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning

View File

@@ -44,7 +44,7 @@ cachelib==0.13.0
# via
# flask-caching
# flask-session
cachetools==6.2.1
cachetools==7.1.4
# via apache-superset (pyproject.toml)
cattrs==25.1.1
# via requests-cache
@@ -60,7 +60,7 @@ cffi==2.0.0
# pynacl
charset-normalizer==3.4.2
# via requests
click==8.4.1
click==8.4.2
# via
# apache-superset (pyproject.toml)
# celery
@@ -175,7 +175,7 @@ h11==0.16.0
# via wsproto
hashids==1.3.1
# via apache-superset (pyproject.toml)
holidays==0.82
holidays==0.100
# via apache-superset (pyproject.toml)
humanize==4.12.3
# via apache-superset (pyproject.toml)
@@ -414,7 +414,7 @@ sqlalchemy-utils==0.42.1
# apache-superset (pyproject.toml)
# apache-superset-core
# flask-appbuilder
sqlglot==30.8.0
sqlglot==30.12.0
# via
# apache-superset (pyproject.toml)
# apache-superset-core

View File

@@ -97,7 +97,7 @@ cachelib==0.13.0
# -c requirements/base-constraint.txt
# flask-caching
# flask-session
cachetools==6.2.1
cachetools==7.1.4
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -130,7 +130,7 @@ charset-normalizer==3.4.2
# via
# -c requirements/base-constraint.txt
# requests
click==8.4.1
click==8.4.2
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -238,9 +238,9 @@ et-xmlfile==2.0.0
# openpyxl
exceptiongroup==1.3.0
# via fastmcp-slim
fastmcp==3.4.2
fastmcp==3.4.4
# via apache-superset
fastmcp-slim==3.4.2
fastmcp-slim==3.4.4
# via fastmcp
filelock==3.20.3
# via
@@ -407,7 +407,7 @@ hashids==1.3.1
# via
# -c requirements/base-constraint.txt
# apache-superset
holidays==0.82
holidays==0.100
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -998,7 +998,7 @@ sqlalchemy-utils==0.42.1
# apache-superset
# apache-superset-core
# flask-appbuilder
sqlglot==30.8.0
sqlglot==30.12.0
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -1017,7 +1017,7 @@ starlette==1.3.1
# mcp
statsd==4.0.1
# via apache-superset
syntaqlite==0.4.2
syntaqlite==0.7.1
# via apache-superset
tabulate==0.10.0
# via
@@ -1033,7 +1033,7 @@ tqdm==4.67.1
# via
# cmdstanpy
# prophet
trino==0.337.0
trino==0.338.0
# via apache-superset
trio==0.33.0
# via

View File

@@ -153,10 +153,11 @@ def main( # noqa: C901
)
print(f"Migration goes from {down_revision} to {revision}")
current_revision = db.engine.execute(
text("SELECT version_num FROM alembic_version")
).scalar()
print(f"Current version of the DB is {current_revision}")
with db.engine.connect() as conn:
current_revision = conn.execute(
text("SELECT version_num FROM alembic_version")
).scalar()
print(f"Current version of the DB is {current_revision}")
if current_revision != down_revision:
if not force:

49
scripts/stylelint.sh Executable file
View File

@@ -0,0 +1,49 @@
#!/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.
set -e
script_dir="$(dirname "$(realpath "$0")")"
root_dir="$(dirname "$script_dir")"
frontend_dir=superset-frontend
if [[ ! -d "$root_dir/$frontend_dir" ]]; then
echo "Error: $frontend_dir directory not found in $root_dir" >&2
exit 1
fi
cd "$root_dir/$frontend_dir"
# Filter files to only include JS/TS files and remove the frontend dir prefix
js_ts_files=()
for file in "$@"; do
# Remove superset-frontend/ prefix if present
cleaned_file="${file#$frontend_dir/}"
# Only include JS/TS files (Emotion styles live in these)
if [[ "$cleaned_file" =~ \.(js|jsx|ts|tsx)$ ]]; then
js_ts_files+=("$cleaned_file")
fi
done
# Only run if we have JS/TS files to lint. --allow-empty-input keeps the hook
# green when every passed file is covered by .stylelintignore.
if [ ${#js_ts_files[@]} -gt 0 ]; then
npx stylelint --allow-empty-input "${js_ts_files[@]}"
else
echo "No JavaScript/TypeScript files to lint for styles"
fi

View File

@@ -92,4 +92,33 @@ do
fi
done
# A few UI labels ("% calculation" etc.) parse as accidentally-valid
# %-format directives (a space flag plus a conversion character), so
# babel auto-flags them python-format on every write and msgfmt then
# fatals on their translations. The app never %-formats them; strip the
# flag AFTER the update pass (babel re-adds it during the update, so
# this must run last). Line-targeted so canonical wrapping is untouched.
python3 - <<'PYEOF'
import glob
TARGETS = {'msgid "% calculation"\n', 'msgid "% of parent"\n', 'msgid "% of total"\n'}
paths = ["superset/translations/messages.pot"] + sorted(
glob.glob("superset/translations/*/LC_MESSAGES/messages.po")
)
for path in paths:
lines = open(path, encoding="utf-8").readlines()
changed = False
for i, line in enumerate(lines):
if line in TARGETS and i > 0 and lines[i - 1].startswith("#,"):
tokens = [t.strip() for t in lines[i - 1][2:].split(",")]
if "python-format" in tokens:
tokens = [t for t in tokens if t != "python-format"]
if "no-python-format" not in tokens:
tokens.append("no-python-format")
lines[i - 1] = "#, " + ", ".join(tokens) + "\n"
changed = True
if changed:
open(path, "w", encoding="utf-8").writelines(lines)
PYEOF
cd $CURRENT_DIR

View File

@@ -22,7 +22,7 @@ import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import reducerIndex from 'spec/helpers/reducerIndex';
import { Global } from '@emotion/react';
import { App, Layout, Space, Content } from 'antd';
import { App, Layout } from 'antd';
import 'src/theme.ts';
import './storybook.css';

View File

@@ -17,45 +17,38 @@
* under the License.
*/
import { Component, ReactNode } from 'react';
import { useState, useCallback, ReactNode } from 'react';
export type Props = {
children: ReactNode;
expandableWhat?: string;
};
type State = {
open: boolean;
};
export default function Expandable({ children, expandableWhat }: Props) {
const [open, setOpen] = useState(false);
export default class Expandable extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { open: false };
this.handleToggle = this.handleToggle.bind(this);
}
const handleToggle = useCallback(() => {
setOpen(prevOpen => !prevOpen);
}, []);
handleToggle() {
this.setState(({ open }) => ({ open: !open }));
}
const label = expandableWhat
? `${open ? 'Hide' : 'Show'} ${expandableWhat}`
: open
? 'Hide'
: 'Show';
render() {
const { open } = this.state;
const { children, expandableWhat } = this.props;
return (
<div>
<button
type="button"
className="btn btn-primary btn-sm"
onClick={this.handleToggle}
>
{`${open ? 'Hide' : 'Show'} ${expandableWhat}`}
</button>
<br />
<br />
{open ? children : null}
</div>
);
}
return (
<div>
<button
type="button"
className="btn btn-primary btn-sm"
onClick={handleToggle}
>
{label}
</button>
<br />
<br />
{open ? children : null}
</div>
);
}

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import { Component, ReactNode } from 'react';
import { useState, useEffect, useCallback, useRef, ReactNode } from 'react';
import { t } from '@apache-superset/core/translation';
import {
SupersetClient,
@@ -36,12 +36,6 @@ export type Props = {
postPayload?: string;
};
type State = {
didVerify: boolean;
error?: Error | SupersetApiError;
payload?: object;
};
export const renderError = (error: Error) => (
<div>
The following error occurred, make sure you have <br />
@@ -54,29 +48,37 @@ export const renderError = (error: Error) => (
</div>
);
export default class VerifyCORS extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { didVerify: false };
this.handleVerify = this.handleVerify.bind(this);
}
export default function VerifyCORS({
children,
endpoint,
host,
method,
postPayload,
}: Props): JSX.Element {
const [didVerify, setDidVerify] = useState(false);
const [error, setError] = useState<Error | SupersetApiError | undefined>(
undefined,
);
const [payload, setPayload] = useState<object | undefined>(undefined);
componentDidUpdate(prevProps: Props) {
const { endpoint, host, postPayload, method } = this.props;
const prevPropsRef = useRef({ endpoint, host, postPayload, method });
useEffect(() => {
const prevProps = prevPropsRef.current;
if (
(this.state.didVerify || this.state.error) &&
(didVerify || error) &&
(prevProps.endpoint !== endpoint ||
prevProps.host !== host ||
prevProps.postPayload !== postPayload ||
prevProps.method !== method)
) {
// eslint-disable-next-line react/no-did-update-set-state
this.setState({ didVerify: false, error: undefined });
setDidVerify(false);
setError(undefined);
}
}
prevPropsRef.current = { endpoint, host, postPayload, method };
}, [endpoint, host, postPayload, method, didVerify, error]);
handleVerify() {
const { endpoint, host, postPayload, method } = this.props;
const handleVerify = useCallback(() => {
SupersetClient.reset();
SupersetClient.configure({
credentials: 'include',
@@ -94,43 +96,40 @@ export default class VerifyCORS extends Component<Props, State> {
}
return { error: 'Must provide valid endpoint and payload.' };
})
.then(result =>
this.setState({ didVerify: true, error: undefined, payload: result }),
)
.catch(error => this.setState({ error }));
}
.then(result => {
setDidVerify(true);
setError(undefined);
setPayload(result);
})
.catch(err => setError(err));
}, [endpoint, host, method, postPayload]);
render() {
const { didVerify, error, payload } = this.state;
const { children } = this.props;
return didVerify ? (
children({ payload })
) : (
<div className="row">
<div className="col-md-10">
This example requires CORS requests from this domain. <br />
<br />
1) enable CORS requests in your Superset App from{' '}
{`${window.location.origin}`}
<br />
2) configure your Superset App host name below <br />
3) click below to verify authentication. You may debug CORS further
using the `@superset-ui/connection` story. <br />
<br />
<Button type="primary" size="small" onClick={this.handleVerify}>
{t('Verify')}
</Button>
<br />
<br />
</div>
{error && (
<div className="col-md-8">
<ErrorMessage error={error} />
</div>
)}
return didVerify ? (
<>{children({ payload })}</>
) : (
<div className="row">
<div className="col-md-10">
This example requires CORS requests from this domain. <br />
<br />
1) enable CORS requests in your Superset App from{' '}
{`${window.location.origin}`}
<br />
2) configure your Superset App host name below <br />
3) click below to verify authentication. You may debug CORS further
using the `@superset-ui/connection` story. <br />
<br />
<Button type="primary" size="small" onClick={handleVerify}>
{t('Verify')}
</Button>
<br />
<br />
</div>
);
}
{error && (
<div className="col-md-8">
<ErrorMessage error={error} />
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,9 @@
# Files whose Emotion template literals nest a `css` tag inside an
# interpolation (e.g. styled(X)`${props => css`...`}`) or otherwise use a
# structure that postcss-styled-syntax cannot parse. Stylelint reports a
# CssSyntaxError on these rather than a real lint finding, so they are
# excluded until the syntax gains nested-template support.
src/components/ListView/Filters/Base.ts
src/features/databases/DatabaseModal/styles.ts
src/features/databases/UploadDataModel/styles.ts
packages/superset-ui-core/src/components/Typography/index.tsx

View File

@@ -471,19 +471,19 @@ export function applyAdvancedTimeRangeFilterOnDashboard(
endRange?: string,
) {
cy.get('.control-label').contains('Range type').should('be.visible');
cy.get('.ant-popover-content .ant-select-selector')
cy.get('.ant-popover-content .ant-select-content')
.should('be.visible')
.click();
cy.get(`[label="Advanced"]`).should('be.visible').click();
cy.get('.section-title').contains('Advanced Time Range').should('be.visible');
if (startRange) {
cy.get('.ant-popover-inner-content')
cy.get('.ant-popover-content')
.find('[class^=ant-input]')
.first()
.type(`${startRange}`);
}
if (endRange) {
cy.get('.ant-popover-inner-content')
cy.get('.ant-popover-content')
.find('[class^=ant-input]')
.last()
.type(`${endRange}`);
@@ -514,15 +514,14 @@ export function inputNativeFilterDefaultValue(
)
.eq(1)
.within(() => {
cy.get('.ant-select-selection-search-input').type(
`${defaultValue}{enter}`,
{ force: true },
);
cy.get('.ant-select-input').type(`${defaultValue}{enter}`, {
force: true,
});
});
});
} else {
cy.getBySel('default-input').within(() => {
cy.get('.ant-select-selection-search-input').click();
cy.get('.ant-select-input').click();
cy.get('.ant-select-item-option-content').contains(defaultValue).click();
});
}

View File

@@ -53,9 +53,7 @@ export function saveChartToDashboard(chartName: string, dashboardName: string) {
.then($modal => {
cy.wait(500);
cy.wrap($modal)
.find(
'.ant-select-selection-search-input[aria-label*="Select a dashboard"]',
)
.find('.ant-select-input[aria-label*="Select a dashboard"]')
.type(dashboardName, { force: true });
cy.get(`.ant-select-item-option[title="${dashboardName}"]`).click();

View File

@@ -52,7 +52,7 @@ export const securityAccess = {
};
export const homePage = {
homeSection: {
sectionArea: '.ant-collapse-content-box',
sectionArea: '.ant-collapse-body',
sectionElement: '.ant-card-meta-title',
},
sections: {
@@ -91,7 +91,7 @@ export const databasesPage = {
preferredBlockSheets: '.preferred > :nth-child(6)',
supportedDatabasesText: '.control-label',
orChoose: '.available-label',
dbDropdown: '[class="ant-select-selection-search-input"]',
dbDropdown: '.ant-select-input',
dbDropdownMenu: '.rc-virtual-list-holder-inner',
dbDropdownMenuItem: '[class="ant-select-item-option-content"]',
infoAlert: '.ant-alert',
@@ -277,7 +277,7 @@ export const chartListView = {
header: {
cardView: '[aria-label="card-view"]',
listView: '[aria-label="list-view"]',
sort: '[class="ant-select-selection-search-input"][aria-label="Sort"]',
sort: '.ant-select-input[aria-label="Sort"]',
sortRecentlyModifiedMenuOption: '[label="Recently modified"]',
sortAlphabeticalMenuOption: '[label="Alphabetical"]',
sortDropdown: '.Select__menu',
@@ -338,20 +338,20 @@ export const nativeFilters = {
filtersPanel: {
filterName: dataTestLocator('filters-config-modal__name-input'),
datasetName: dataTestLocator('filters-config-modal__datasource-input'),
filterInfoInput: '.ant-select-selection-search-input',
filterInfoInput: '.ant-select-input',
inputDropdown: '.ant-select-item-option-content',
columnEmptyInput: '.ant-select-selection-placeholder',
columnEmptyInput: '.ant-select-placeholder',
filterTypeInput: dataTestLocator('filters-config-modal__filter-type'),
fieldInput: dataTestLocator('field-input'),
filterTypeItem: '.ant-select-selection-item',
filterTypeItem: '.ant-select-content-has-value, .ant-select-selection-item',
filterGear: dataTestLocator('filterbar-orientation-icon'),
},
filterFromDashboardView: {
filterValueInput: '[class="ant-select-selection-search-input"]',
filterValueInput: '.ant-select-input',
expand: dataTestLocator('filter-bar__expand-button'),
collapse: dataTestLocator('filter-bar__collapse-button'),
filterName: dataTestLocator('filter-control-name'),
filterContent: '.ant-select-selection-item',
filterContent: '.ant-select-content-has-value, .ant-select-selection-item',
createFilterButton: dataTestLocator('filter-bar__create-filter'),
timeRangeFilterContent: dataTestLocator('time-range-trigger'),
},
@@ -365,8 +365,8 @@ export const nativeFilters = {
checkedCheckbox: '.ant-checkbox-wrapper-checked',
infoTooltip: '[aria-label="Show info tooltip"]',
parentFilterInput: dataTestLocator('parent-filter-input'),
filterPlaceholder: '.ant-select-selection-placeholder',
collapsedSectionContainer: '[class="ant-collapse-content-box"]',
filterPlaceholder: '.ant-select-placeholder',
collapsedSectionContainer: '[class="ant-collapse-body"]',
},
filtersList: {
list: '.ant-tabs-nav-list',
@@ -376,7 +376,7 @@ export const nativeFilters = {
removeIcon: '[aria-label="delete"]',
},
filterItem: dataTestLocator('form-item-value'),
filterItemDropdown: '.ant-select-selection-search',
filterItemDropdown: '.ant-select-input',
applyFilter: dataTestLocator('filter-bar__apply-button'),
defaultInput: dataTestLocator('default-input'),
filterIcon: dataTestLocator('filter-icon'),
@@ -484,7 +484,7 @@ export const exploreView = {
saveModal: {
modal: '.ant-modal-content',
chartNameInput: dataTestLocator('new-chart-name'),
dashboardNameInput: '.ant-select-selection-search-input',
dashboardNameInput: '.ant-select-input',
addToDashboardInput: dataTestLocator(
'save-chart-modal-select-dashboard-form',
),
@@ -495,7 +495,7 @@ export const exploreView = {
},
controlPanel: {
panel: dataTestLocator('control-tabs'),
categoryArea: '.ant-collapse-content-box',
categoryArea: '.ant-collapse-body',
dragField: dataTestLocator('datasource'),
metricsField: dataTestLocator('metrics'),
optionField: dataTestLocator('option-label'),
@@ -594,7 +594,7 @@ export const exploreView = {
},
};
export const createChartView = {
chooseDatasetInput: '.ant-select-selection-search-input',
chooseDatasetInput: '.ant-select-input',
chooseDatasetOption: '.ant-select-item-option-content',
chooseDatasetList: '.rc-virtual-list-holder-inner',
tableVizType: "[alt='Table']",
@@ -641,7 +641,7 @@ export const dashboardView = {
secondTabSalesDashboard: dataTestLocator('dragdroppable-object'),
},
timeRangeModal: {
rangeTypeField: '.ant-select-selection-item',
rangeTypeField: '.ant-select-content-has-value, .ant-select-selection-item',
startTimeInputNumber: '.ant-input-number-input',
datePicker: '.ant-picker-input',
applyButton: dataTestLocator('date-filter-control__apply-button'),

View File

@@ -75,7 +75,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/*.|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|geostyler|geostyler-.*|(?!geostyler)lodash|react-error-boundary|react-json-tree|react-base16-styling|lodash-es|rbush|quickselect|react-diff-viewer-continued|storybook/*.|json-stringify-pretty-compact)',
'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-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|geostyler|geostyler-.*|(?!geostyler)lodash|react-error-boundary|react-json-tree|react-base16-styling|lodash-es|rbush|quickselect|react-diff-viewer-continued|storybook/*.|json-stringify-pretty-compact)',
],
preset: 'ts-jest',
transform: {

View File

@@ -52,10 +52,6 @@
// require-default-props, sort-comp, static-property-placement
// (prefer-stateless-function / function-component-definition
// are represented by react/prefer-function-component below)
// jsx-a11y: interactive-supports-focus,
// no-interactive-element-to-noninteractive-role,
// no-noninteractive-element-interactions,
// no-noninteractive-element-to-interactive-role
// typescript: naming-convention
// unicorn: prevent-abbreviations
@@ -196,6 +192,7 @@
// === JSX-a11y rules ===
"jsx-a11y/alt-text": "error",
"jsx-a11y/anchor-ambiguous-text": "error",
"jsx-a11y/anchor-has-content": "error",
"jsx-a11y/anchor-is-valid": "error",
"jsx-a11y/aria-activedescendant-has-tabindex": "error",
@@ -203,21 +200,33 @@
"jsx-a11y/aria-proptypes": "error",
"jsx-a11y/aria-role": ["error", { "ignoreNonDOM": false }],
"jsx-a11y/aria-unsupported-elements": "error",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/autocomplete-valid": "error",
"jsx-a11y/click-events-have-key-events": "error",
"jsx-a11y/control-has-associated-label": "error",
"jsx-a11y/heading-has-content": "error",
"jsx-a11y/html-has-lang": "error",
"jsx-a11y/iframe-has-title": "error",
"jsx-a11y/img-redundant-alt": "error",
"jsx-a11y/interactive-supports-focus": "error",
"jsx-a11y/label-has-associated-control": "error",
"jsx-a11y/lang": "error",
"jsx-a11y/media-has-caption": "error",
"jsx-a11y/mouse-events-have-key-events": "off",
"jsx-a11y/mouse-events-have-key-events": "error",
"jsx-a11y/no-access-key": "error",
"jsx-a11y/no-aria-hidden-on-focusable": "error",
"jsx-a11y/no-autofocus": ["error", { "ignoreNonDOM": true }],
"jsx-a11y/no-distracting-elements": "error",
"jsx-a11y/no-interactive-element-to-noninteractive-role": "error",
"jsx-a11y/no-noninteractive-element-interactions": "error",
"jsx-a11y/no-noninteractive-element-to-interactive-role": "error",
"jsx-a11y/no-noninteractive-tabindex": "error",
"jsx-a11y/no-redundant-roles": "error",
// TODO: Graduate to "error" — remaining violations are event-boundary
// wrappers (divs/spans whose handlers only stopPropagation) and test
// scaffolding where adding a role is semantically wrong. Needs a
// dedicated pass; see PR enabling the other jsx-a11y interactive rules.
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/prefer-tag-over-role": "error",
"jsx-a11y/role-has-required-aria-props": "error",
"jsx-a11y/role-supports-aria-props": "error",
"jsx-a11y/scope": "error",
@@ -232,7 +241,14 @@
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrors": "none"
}
],
"@typescript-eslint/prefer-optional-chain": "error",
// === Unicorn rules (bonus coverage) ===
@@ -282,6 +298,16 @@
"rules": {
"jsx-a11y/no-redundant-roles": "off"
}
},
{
// The pivot table intentionally exposes `role="grid"` on its <table>
// for interactive grid semantics (asserted by tableRenders.test.tsx).
"files": [
"plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx"
],
"rules": {
"jsx-a11y/no-noninteractive-element-to-interactive-role": "off"
}
}
],
"ignorePatterns": [

File diff suppressed because it is too large Load Diff

View File

@@ -57,7 +57,9 @@
"lint:all": "npx oxlint --config oxlint.json && npm run type",
"lint-fix": "npx oxlint --config oxlint.json --fix --quiet",
"lint-fix:all": "npx oxlint --config oxlint.json --fix",
"lint:full": "npm run lint && npm run check:custom-rules",
"lint:full": "npm run lint && npm run check:custom-rules && npm run stylelint",
"stylelint": "stylelint \"{src,packages/*/src,plugins/*/src}/**/*.{ts,tsx}\"",
"stylelint-fix": "stylelint --fix \"{src,packages/*/src,plugins/*/src}/**/*.{ts,tsx}\"",
"check:custom-rules": "node scripts/check-custom-rules.js",
"check:storybook-coverage": "node scripts/check-storybook-coverage.js",
"ensure-oxc": "echo 'OXC linter is ready' && npx oxlint --version",
@@ -128,10 +130,9 @@
"@luma.gl/shadertools": "~9.2.5",
"@luma.gl/webgl": "~9.2.5",
"@reduxjs/toolkit": "^1.9.3",
"@rjsf/antd": "^5.24.13",
"@rjsf/core": "^5.24.13",
"@rjsf/utils": "^5.24.3",
"@rjsf/validator-ajv8": "^5.24.13",
"@rjsf/core": "^6.6.2",
"@rjsf/utils": "^6.6.2",
"@rjsf/validator-ajv8": "^6.6.2",
"@scarf/scarf": "^1.4.0",
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
"@superset-ui/core": "file:./packages/superset-ui-core",
@@ -167,7 +168,7 @@
"@visx/xychart": "^4.0.0",
"ag-grid-community": "36.0.0",
"ag-grid-react": "36.0.0",
"antd": "^5.26.0",
"antd": "^6.0.0",
"chrono-node": "^2.9.1",
"classnames": "^2.2.5",
"content-disposition": "^2.0.1",
@@ -186,7 +187,7 @@
"geostyler-style": "11.0.2",
"geostyler-wfs-parser": "^3.0.1",
"google-auth-library": "^10.9.0",
"immer": "^11.1.9",
"immer": "^11.1.11",
"interweave": "^13.1.1",
"jquery": "^4.0.0",
"js-levenshtein": "^1.1.6",
@@ -205,7 +206,7 @@
"query-string": "9.4.1",
"re-resizable": "^6.11.2",
"react": "^18.3.0",
"react-arborist": "^3.12.0",
"react-arborist": "^3.13.2",
"react-checkbox-tree": "^1.8.0",
"react-diff-viewer-continued": "^4.2.2",
"react-dnd": "^11.1.3",
@@ -272,7 +273,7 @@
"@storybook/test-runner": "0.24.4",
"@svgr/webpack": "^8.1.0",
"@swc/core": "^1.15.43",
"@swc/plugin-emotion": "^14.14.1",
"@swc/plugin-emotion": "^14.15.0",
"@swc/plugin-transform-imports": "^12.5.0",
"@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^6.9.1",
@@ -286,7 +287,7 @@
"@types/json-bigint": "^1.0.4",
"@types/lodash-es": "^4.17.12",
"@types/mousetrap": "^1.6.15",
"@types/node": "^26.1.0",
"@types/node": "^26.1.1",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@types/react-loadable": "^5.5.11",
@@ -299,13 +300,13 @@
"@types/rison": "0.1.0",
"@types/tinycolor2": "^1.4.3",
"@types/unzipper": "^0.10.11",
"@typescript-eslint/eslint-plugin": "^8.62.1",
"@typescript-eslint/parser": "^8.62.1",
"@typescript-eslint/eslint-plugin": "^8.63.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.10.41",
"baseline-browser-mapping": "^2.10.42",
"cheerio": "1.2.0",
"concurrently": "^10.0.3",
"copy-webpack-plugin": "^14.0.0",
@@ -344,8 +345,9 @@
"mini-css-extract-plugin": "^2.10.2",
"minimizer-webpack-plugin": "^5.6.1",
"open-cli": "^9.0.0",
"oxlint": "^1.72.0",
"oxlint": "^1.73.0",
"po2json": "^0.4.5",
"postcss-styled-syntax": "^0.7.2",
"prettier": "3.9.4",
"prettier-plugin-packagejson": "^3.0.2",
"process": "^0.11.10",
@@ -358,20 +360,21 @@
"speed-measure-webpack-plugin": "^1.6.0",
"storybook": "10.4.6",
"style-loader": "^4.0.0",
"stylelint": "^17.14.0",
"swc-loader": "^0.2.7",
"ts-jest": "^29.4.11",
"tscw-config": "^1.1.2",
"tsx": "^4.22.5",
"tsx": "^4.23.0",
"typescript": "5.4.5",
"unzipper": "^0.12.5",
"vm-browserify": "^1.1.2",
"wait-on": "^9.0.10",
"webpack": "^5.108.3",
"webpack": "^5.108.4",
"webpack-bundle-analyzer": "^5.3.0",
"webpack-cli": "^7.0.3",
"webpack-dev-server": "^5.2.5",
"webpack-manifest-plugin": "^6.0.1",
"webpack-sources": "^3.5.0",
"webpack-sources": "^3.5.1",
"webpack-visualizer-plugin2": "^2.0.0"
},
"peerDependencies": {
@@ -387,6 +390,9 @@
},
"overrides": {
"uuid": "$uuid",
"@great-expectations/jsonforms-antd-renderers": {
"antd": "$antd"
},
"core-js": "^3.38.1",
"dompurify": "^3.4.11",
"esbuild": "^0.28.1",

View File

@@ -110,7 +110,7 @@
"react-loadable": "^5.5.0",
"tinycolor2": "*",
"lodash": "^4.18.1",
"antd": "^5.26.0",
"antd": "^6.0.0",
"jed": "^1.1.1"
},
"scripts": {

View File

@@ -38,7 +38,7 @@ export const GlobalStyles = () => {
<Global
key={`global-${theme.colorLink}`}
styles={css`
// SPA
/* SPA */
html {
color-scheme: ${isDark ? 'dark' : 'light'};
}
@@ -93,7 +93,7 @@ export const GlobalStyles = () => {
}
}
// Overriding bootstrap styles
/* Overriding bootstrap styles */
#app {
flex: 1 1 auto;
position: relative;
@@ -105,8 +105,18 @@ export const GlobalStyles = () => {
cursor: pointer;
}
// Override geostyler CSS that hides AntD ColorPicker alpha input
// See: https://github.com/apache/superset/issues/34721
/* antd 6 removed the Tag's default trailing margin (v5 shipped */
/* margin-inline-end: 8px on every tag) in favor of parents spacing */
/* tags via flex/Space gaps. The app's layouts predate that and rely */
/* on the v5 default (e.g. the dashboard header's Published tag), */
/* so restore it for visual parity. Remove once Tag-adjacent layouts */
/* declare their own gaps. */
.ant-tag {
margin-inline-end: ${theme.marginXS}px;
}
/* Override geostyler CSS that hides AntD ColorPicker alpha input */
/* See: https://github.com/apache/superset/issues/34721 */
.ant-color-picker .ant-color-picker-alpha-input {
display: block;
}
@@ -117,7 +127,7 @@ export const GlobalStyles = () => {
}
.superset-explore-popover.ant-popover
.ant-popover-inner:has(.ant-popover-title) {
.ant-popover-container:has(.ant-popover-title) {
padding-top: 0;
}
.superset-explore-popover.ant-popover .ant-popover-title {
@@ -126,7 +136,7 @@ export const GlobalStyles = () => {
line-height: 1;
}
.superset-explore-popover.ant-popover
.ant-popover-inner:has(.ant-popover-title)
.ant-popover-container:has(.ant-popover-title)
.ant-tabs-tab {
padding-top: 0;
}

View File

@@ -18,7 +18,34 @@
*/
import { theme as antdThemeImport } from 'antd';
import { Theme } from './Theme';
import { AnyThemeConfig, ThemeAlgorithm } from './types';
import { AnyThemeConfig, ThemeAlgorithm, allowedAntdTokens } from './types';
/**
* A fixed, Superset-representative base theme used by the characterization
* tests below. Mirrors the real THEME_DEFAULT token set so the snapshots are
* stable and meaningful. Do NOT churn this casually — it is the baseline the
* Ant Design v6 upgrade is measured against.
*/
const SUPERSET_BASE_THEME: AnyThemeConfig = {
token: {
colorPrimary: '#2893B3',
colorError: '#e04355',
colorWarning: '#fcc700',
colorSuccess: '#5ac189',
colorInfo: '#66bcfe',
fontFamily: "'Inter', Helvetica, Arial",
fontFamilyCode: "'IBM Plex Mono', 'Courier New', monospace",
},
};
/** Collect the computed value of every allow-listed antd token, sorted. */
function pickAllowedTokens(theme: Theme): Record<string, unknown> {
const out: Record<string, unknown> = {};
[...allowedAntdTokens].sort().forEach(key => {
out[key] = (theme.theme as unknown as Record<string, unknown>)[key];
});
return out;
}
// Mock emotion's cache to avoid actual DOM operations
jest.mock('@emotion/cache', () => ({
@@ -338,10 +365,10 @@ test('Theme edge cases correctly applies base theme tokens in dark mode', () =>
algorithm: antdThemeImport.defaultAlgorithm,
};
const baseThemeDark: AnyThemeConfig = {
const baseThemeDark = {
...baseTheme,
algorithm: antdThemeImport.darkAlgorithm,
};
} as AnyThemeConfig;
// Simulate light mode with base theme
const lightTheme = Theme.fromConfig({}, baseTheme);
@@ -473,10 +500,10 @@ test('Theme base theme integration handles base theme with dark algorithm correc
},
};
const baseThemeDark: AnyThemeConfig = {
const baseThemeDark = {
...baseTheme,
algorithm: antdThemeImport.darkAlgorithm,
};
} as AnyThemeConfig;
const userDarkTheme: AnyThemeConfig = {
algorithm: antdThemeImport.darkAlgorithm,
@@ -525,7 +552,7 @@ test('Theme base theme integration works with real-world Superset base theme con
const darkTheme = Theme.fromConfig(themeDark, {
...supersetBaseTheme,
algorithm: antdThemeImport.darkAlgorithm,
});
} as AnyThemeConfig);
expect(darkTheme.theme.fontFamily).toBe("'Inter', Helvetica, Arial");
const darkSerialized = darkTheme.toSerializedConfig();
@@ -604,7 +631,9 @@ test('Theme base theme integration handles cssVar, hashed and inherit properties
// User properties override/add to base
expect(serialized.inherit).toBe(true);
expect(serialized.cssVar).toBe(true);
// Ant Design v6 dropped boolean `cssVar`; Superset normalizes `true` to the
// object form `{}` at the antd boundary (CSS variables are on by default).
expect(serialized.cssVar).toEqual({});
expect(serialized.hashed).toBe(false);
// Tokens are still merged
@@ -909,3 +938,81 @@ test('colorLink is preserved in setConfig when explicitly set', () => {
expect(theme.theme.colorPrimary).toBe('#f759ab');
expect(theme.theme.colorLink).toBe('#ff0000');
});
// ---------------------------------------------------------------------------
// Characterization tests — theming safety net for the Ant Design v6 upgrade.
//
// These lock the bridge between Superset's theme and Ant Design's design
// tokens (getDesignToken -> allowedAntdTokens -> SupersetTheme). When antd
// renames or removes a token, the completeness tests fail loudly instead of
// letting a token silently resolve to `undefined`, and the snapshots make any
// value drift reviewable. See packages/superset-core/src/theme/types.ts.
// ---------------------------------------------------------------------------
test('every allowed antd token resolves to a defined value in light mode', () => {
const theme = Theme.fromConfig(
{ algorithm: antdThemeImport.defaultAlgorithm },
SUPERSET_BASE_THEME,
);
const missing = allowedAntdTokens.filter(
key =>
(theme.theme as unknown as Record<string, unknown>)[key] === undefined,
);
// A non-empty list means antd dropped/renamed a token we still allow-list.
expect(missing).toEqual([]);
});
test('every allowed antd token resolves to a defined value in dark mode', () => {
const theme = Theme.fromConfig(
{ algorithm: antdThemeImport.darkAlgorithm },
SUPERSET_BASE_THEME,
);
const missing = allowedAntdTokens.filter(
key =>
(theme.theme as unknown as Record<string, unknown>)[key] === undefined,
);
expect(missing).toEqual([]);
});
test('allowed antd token key set is stable', () => {
// Locks the *set* of tokens Superset exposes (independent of their values),
// so additions/removals to allowedAntdTokens are an explicit, reviewed diff.
expect([...allowedAntdTokens].sort()).toMatchSnapshot();
});
test('computed light-mode token values match snapshot', () => {
const theme = Theme.fromConfig(
{ algorithm: antdThemeImport.defaultAlgorithm },
SUPERSET_BASE_THEME,
);
expect(pickAllowedTokens(theme)).toMatchSnapshot();
});
test('computed dark-mode token values match snapshot', () => {
const theme = Theme.fromConfig(
{ algorithm: antdThemeImport.darkAlgorithm },
SUPERSET_BASE_THEME,
);
expect(pickAllowedTokens(theme)).toMatchSnapshot();
});
test('dark mode actually diverges from light mode for background tokens', () => {
const light = Theme.fromConfig(
{ algorithm: antdThemeImport.defaultAlgorithm },
SUPERSET_BASE_THEME,
);
const dark = Theme.fromConfig(
{ algorithm: antdThemeImport.darkAlgorithm },
SUPERSET_BASE_THEME,
);
// Guards the dark algorithm wiring: base surface and text must invert.
expect(dark.theme.colorBgBase).not.toBe(light.theme.colorBgBase);
expect(dark.theme.colorTextBase).not.toBe(light.theme.colorTextBase);
});

View File

@@ -0,0 +1,730 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`allowed antd token key set is stable 1`] = `
[
"borderRadius",
"borderRadiusLG",
"borderRadiusOuter",
"borderRadiusSM",
"borderRadiusXS",
"boxShadow",
"boxShadowDrawerLeft",
"boxShadowDrawerRight",
"boxShadowDrawerUp",
"boxShadowPopoverArrow",
"boxShadowSecondary",
"boxShadowTabsOverflowBottom",
"boxShadowTabsOverflowLeft",
"boxShadowTabsOverflowRight",
"boxShadowTabsOverflowTop",
"boxShadowTertiary",
"colorBgBase",
"colorBgBlur",
"colorBgContainer",
"colorBgContainerDisabled",
"colorBgElevated",
"colorBgLayout",
"colorBgMask",
"colorBgSpotlight",
"colorBgTextActive",
"colorBgTextHover",
"colorBorder",
"colorBorderBg",
"colorBorderSecondary",
"colorError",
"colorErrorActive",
"colorErrorBg",
"colorErrorBgActive",
"colorErrorBgHover",
"colorErrorBorder",
"colorErrorBorderHover",
"colorErrorHover",
"colorErrorOutline",
"colorErrorText",
"colorErrorTextActive",
"colorErrorTextHover",
"colorFill",
"colorFillAlter",
"colorFillContent",
"colorFillContentHover",
"colorFillQuaternary",
"colorFillSecondary",
"colorFillTertiary",
"colorHighlight",
"colorIcon",
"colorIconHover",
"colorInfo",
"colorInfoActive",
"colorInfoBg",
"colorInfoBgHover",
"colorInfoBorder",
"colorInfoBorderHover",
"colorInfoHover",
"colorInfoText",
"colorInfoTextActive",
"colorInfoTextHover",
"colorLink",
"colorLinkActive",
"colorLinkHover",
"colorPrimary",
"colorPrimaryActive",
"colorPrimaryBg",
"colorPrimaryBgHover",
"colorPrimaryBorder",
"colorPrimaryBorderHover",
"colorPrimaryHover",
"colorPrimaryText",
"colorPrimaryTextActive",
"colorPrimaryTextHover",
"colorSplit",
"colorSuccess",
"colorSuccessActive",
"colorSuccessBg",
"colorSuccessBgHover",
"colorSuccessBorder",
"colorSuccessBorderHover",
"colorSuccessHover",
"colorSuccessText",
"colorSuccessTextActive",
"colorSuccessTextHover",
"colorText",
"colorTextBase",
"colorTextDescription",
"colorTextDisabled",
"colorTextHeading",
"colorTextLabel",
"colorTextLightSolid",
"colorTextPlaceholder",
"colorTextQuaternary",
"colorTextSecondary",
"colorTextTertiary",
"colorWarning",
"colorWarningActive",
"colorWarningBg",
"colorWarningBgHover",
"colorWarningBorder",
"colorWarningBorderHover",
"colorWarningHover",
"colorWarningOutline",
"colorWarningText",
"colorWarningTextActive",
"colorWarningTextHover",
"colorWhite",
"controlHeight",
"controlHeightLG",
"controlHeightSM",
"controlHeightXS",
"controlInteractiveSize",
"controlItemBgActive",
"controlItemBgActiveDisabled",
"controlItemBgActiveHover",
"controlItemBgHover",
"controlOutline",
"controlOutlineWidth",
"controlPaddingHorizontal",
"controlPaddingHorizontalSM",
"controlTmpOutline",
"fontFamily",
"fontFamilyCode",
"fontHeight",
"fontHeightLG",
"fontHeightSM",
"fontSize",
"fontSizeHeading1",
"fontSizeHeading2",
"fontSizeHeading3",
"fontSizeHeading4",
"fontSizeHeading5",
"fontSizeIcon",
"fontSizeLG",
"fontSizeSM",
"fontSizeXL",
"fontWeightStrong",
"lineHeight",
"lineHeightHeading1",
"lineHeightHeading2",
"lineHeightHeading3",
"lineHeightHeading4",
"lineHeightHeading5",
"lineHeightLG",
"lineHeightSM",
"lineType",
"lineWidth",
"lineWidthBold",
"lineWidthFocus",
"linkDecoration",
"linkFocusDecoration",
"linkHoverDecoration",
"margin",
"marginLG",
"marginMD",
"marginSM",
"marginXL",
"marginXS",
"marginXXL",
"marginXXS",
"motion",
"motionBase",
"motionDurationFast",
"motionDurationMid",
"motionDurationSlow",
"motionEaseInBack",
"motionEaseInOut",
"motionEaseInOutCirc",
"motionEaseInQuint",
"motionEaseOut",
"motionEaseOutBack",
"motionEaseOutCirc",
"motionEaseOutQuint",
"motionUnit",
"opacityImage",
"opacityLoading",
"padding",
"paddingContentHorizontal",
"paddingContentHorizontalLG",
"paddingContentHorizontalSM",
"paddingContentVertical",
"paddingContentVerticalLG",
"paddingContentVerticalSM",
"paddingLG",
"paddingMD",
"paddingSM",
"paddingXL",
"paddingXS",
"paddingXXS",
"screenLG",
"screenLGMax",
"screenLGMin",
"screenMD",
"screenMDMax",
"screenMDMin",
"screenSM",
"screenSMMax",
"screenSMMin",
"screenXL",
"screenXLMax",
"screenXLMin",
"screenXS",
"screenXSMax",
"screenXSMin",
"screenXXL",
"screenXXLMin",
"size",
"sizeLG",
"sizeMD",
"sizeMS",
"sizePopupArrow",
"sizeSM",
"sizeStep",
"sizeUnit",
"sizeXL",
"sizeXS",
"sizeXXL",
"sizeXXS",
"wireframe",
"zIndexBase",
"zIndexPopupBase",
]
`;
exports[`computed dark-mode token values match snapshot 1`] = `
{
"borderRadius": 6,
"borderRadiusLG": 8,
"borderRadiusOuter": 4,
"borderRadiusSM": 4,
"borderRadiusXS": 2,
"boxShadow": "
0 6px 16px 0 rgba(255,255,255,0.016),
0 3px 6px -4px rgba(255,255,255,0.024),
0 9px 28px 8px rgba(255,255,255,0.010000000000000002)
",
"boxShadowDrawerLeft": "
6px 0 16px 0 rgba(255,255,255,0.016),
3px 0 6px -4px rgba(255,255,255,0.024),
9px 0 28px 8px rgba(255,255,255,0.010000000000000002)
",
"boxShadowDrawerRight": "
-6px 0 16px 0 rgba(255,255,255,0.016),
-3px 0 6px -4px rgba(255,255,255,0.024),
-9px 0 28px 8px rgba(255,255,255,0.010000000000000002)
",
"boxShadowDrawerUp": "
0 6px 16px 0 rgba(255,255,255,0.016),
0 3px 6px -4px rgba(255,255,255,0.024),
0 9px 28px 8px rgba(255,255,255,0.010000000000000002)
",
"boxShadowPopoverArrow": "2px 2px 5px rgba(255,255,255,0.010000000000000002)",
"boxShadowSecondary": "
0 6px 16px 0 rgba(255,255,255,0.016),
0 3px 6px -4px rgba(255,255,255,0.024),
0 9px 28px 8px rgba(255,255,255,0.010000000000000002)
",
"boxShadowTabsOverflowBottom": "inset 0 -10px 8px -8px rgba(255,255,255,0.016)",
"boxShadowTabsOverflowLeft": "inset 10px 0 8px -8px rgba(255,255,255,0.016)",
"boxShadowTabsOverflowRight": "inset -10px 0 8px -8px rgba(255,255,255,0.016)",
"boxShadowTabsOverflowTop": "inset 0 10px 8px -8px rgba(255,255,255,0.016)",
"boxShadowTertiary": "
0 1px 2px 0 rgba(255,255,255,0.010000000000000002),
0 1px 6px -1px rgba(255,255,255,0.006),
0 2px 4px 0 rgba(255,255,255,0.006)
",
"colorBgBase": "#000",
"colorBgBlur": "rgba(255,255,255,0.04)",
"colorBgContainer": "#141414",
"colorBgContainerDisabled": "rgba(255,255,255,0.08)",
"colorBgElevated": "#1f1f1f",
"colorBgLayout": "#000000",
"colorBgMask": "rgba(0,0,0,0.45)",
"colorBgSpotlight": "#424242",
"colorBgTextActive": "rgba(255,255,255,0.18)",
"colorBgTextHover": "rgba(255,255,255,0.12)",
"colorBorder": "#424242",
"colorBorderBg": "#141414",
"colorBorderSecondary": "#303030",
"colorError": "#e04355",
"colorErrorActive": "#99333e",
"colorErrorBg": "#271619",
"colorErrorBgActive": "#512228",
"colorErrorBgHover": "#3e1b20",
"colorErrorBorder": "#512228",
"colorErrorBorderHover": "#702931",
"colorErrorHover": "#d7646e",
"colorErrorOutline": "rgba(231,42,76,0.09)",
"colorErrorText": "#c13c4b",
"colorErrorTextActive": "#99333e",
"colorErrorTextHover": "#d7646e",
"colorFill": "rgba(255,255,255,0.18)",
"colorFillAlter": "rgba(255,255,255,0.04)",
"colorFillContent": "rgba(255,255,255,0.12)",
"colorFillContentHover": "rgba(255,255,255,0.18)",
"colorFillQuaternary": "rgba(255,255,255,0.04)",
"colorFillSecondary": "rgba(255,255,255,0.12)",
"colorFillTertiary": "rgba(255,255,255,0.08)",
"colorHighlight": "#c13c4b",
"colorIcon": "rgba(255,255,255,0.45)",
"colorIconHover": "rgba(255,255,255,0.85)",
"colorInfo": "#66bcfe",
"colorInfoActive": "#4981ac",
"colorInfoBg": "#19222c",
"colorInfoBgHover": "#223545",
"colorInfoBorder": "#2d465a",
"colorInfoBorderHover": "#39607d",
"colorInfoHover": "#39607d",
"colorInfoText": "#5aa3db",
"colorInfoTextActive": "#4981ac",
"colorInfoTextHover": "#83bfe8",
"colorLink": "#2893B3",
"colorLinkActive": "#21677b",
"colorLinkHover": "#1d4d5c",
"colorPrimary": "#2893B3",
"colorPrimaryActive": "#21677b",
"colorPrimaryBg": "#1a3a44",
"colorPrimaryBgHover": "#1d4d5c",
"colorPrimaryBorder": "#1a3a44",
"colorPrimaryBorderHover": "#1d4d5c",
"colorPrimaryHover": "#4499ae",
"colorPrimaryText": "#25809b",
"colorPrimaryTextActive": "#21677b",
"colorPrimaryTextHover": "#4499ae",
"colorSplit": "rgba(253,253,253,0.12)",
"colorSuccess": "#5ac189",
"colorSuccessActive": "#428460",
"colorSuccessBg": "#17231d",
"colorSuccessBgHover": "#1f362a",
"colorSuccessBorder": "#294837",
"colorSuccessBorderHover": "#346249",
"colorSuccessHover": "#346249",
"colorSuccessText": "#50a777",
"colorSuccessTextActive": "#428460",
"colorSuccessTextHover": "#77bc94",
"colorText": "rgba(255,255,255,0.85)",
"colorTextBase": "#fff",
"colorTextDescription": "rgba(255,255,255,0.45)",
"colorTextDisabled": "rgba(255,255,255,0.25)",
"colorTextHeading": "rgba(255,255,255,0.85)",
"colorTextLabel": "rgba(255,255,255,0.65)",
"colorTextLightSolid": "#fff",
"colorTextPlaceholder": "rgba(255,255,255,0.25)",
"colorTextQuaternary": "rgba(255,255,255,0.25)",
"colorTextSecondary": "rgba(255,255,255,0.65)",
"colorTextTertiary": "rgba(255,255,255,0.45)",
"colorWarning": "#fcc700",
"colorWarningActive": "#ab8807",
"colorWarningBg": "#2b2411",
"colorWarningBgHover": "#45370f",
"colorWarningBorder": "#5a4a0e",
"colorWarningBorderHover": "#7c650b",
"colorWarningHover": "#7c650b",
"colorWarningOutline": "rgba(173,127,0,0.15)",
"colorWarningText": "#d9ac03",
"colorWarningTextActive": "#ab8807",
"colorWarningTextHover": "#e8c427",
"colorWhite": "#fff",
"controlHeight": 32,
"controlHeightLG": 40,
"controlHeightSM": 24,
"controlHeightXS": 16,
"controlInteractiveSize": 16,
"controlItemBgActive": "#1a3a44",
"controlItemBgActiveDisabled": "rgba(255,255,255,0.18)",
"controlItemBgActiveHover": "#1d4d5c",
"controlItemBgHover": "rgba(255,255,255,0.08)",
"controlOutline": "rgba(49,201,249,0.21)",
"controlOutlineWidth": 2,
"controlPaddingHorizontal": 12,
"controlPaddingHorizontalSM": 8,
"controlTmpOutline": "rgba(255,255,255,0.04)",
"fontFamily": "'Inter', Helvetica, Arial",
"fontFamilyCode": "'IBM Plex Mono', 'Courier New', monospace",
"fontHeight": 22,
"fontHeightLG": 24,
"fontHeightSM": 20,
"fontSize": 14,
"fontSizeHeading1": 38,
"fontSizeHeading2": 30,
"fontSizeHeading3": 24,
"fontSizeHeading4": 20,
"fontSizeHeading5": 16,
"fontSizeIcon": 12,
"fontSizeLG": 16,
"fontSizeSM": 12,
"fontSizeXL": 20,
"fontWeightStrong": 600,
"lineHeight": 1.5714285714285714,
"lineHeightHeading1": 1.2105263157894737,
"lineHeightHeading2": 1.2666666666666666,
"lineHeightHeading3": 1.3333333333333333,
"lineHeightHeading4": 1.4,
"lineHeightHeading5": 1.5,
"lineHeightLG": 1.5,
"lineHeightSM": 1.6666666666666667,
"lineType": "solid",
"lineWidth": 1,
"lineWidthBold": 2,
"lineWidthFocus": 3,
"linkDecoration": "none",
"linkFocusDecoration": "none",
"linkHoverDecoration": "none",
"margin": 16,
"marginLG": 24,
"marginMD": 20,
"marginSM": 12,
"marginXL": 32,
"marginXS": 8,
"marginXXL": 48,
"marginXXS": 4,
"motion": true,
"motionBase": 0,
"motionDurationFast": "0.1s",
"motionDurationMid": "0.2s",
"motionDurationSlow": "0.3s",
"motionEaseInBack": "cubic-bezier(0.71, -0.46, 0.88, 0.6)",
"motionEaseInOut": "cubic-bezier(0.645, 0.045, 0.355, 1)",
"motionEaseInOutCirc": "cubic-bezier(0.78, 0.14, 0.15, 0.86)",
"motionEaseInQuint": "cubic-bezier(0.755, 0.05, 0.855, 0.06)",
"motionEaseOut": "cubic-bezier(0.215, 0.61, 0.355, 1)",
"motionEaseOutBack": "cubic-bezier(0.12, 0.4, 0.29, 1.46)",
"motionEaseOutCirc": "cubic-bezier(0.08, 0.82, 0.17, 1)",
"motionEaseOutQuint": "cubic-bezier(0.23, 1, 0.32, 1)",
"motionUnit": 0.1,
"opacityImage": 1,
"opacityLoading": 0.65,
"padding": 16,
"paddingContentHorizontal": 16,
"paddingContentHorizontalLG": 24,
"paddingContentHorizontalSM": 16,
"paddingContentVertical": 12,
"paddingContentVerticalLG": 16,
"paddingContentVerticalSM": 8,
"paddingLG": 24,
"paddingMD": 20,
"paddingSM": 12,
"paddingXL": 32,
"paddingXS": 8,
"paddingXXS": 4,
"screenLG": 992,
"screenLGMax": 1199,
"screenLGMin": 992,
"screenMD": 768,
"screenMDMax": 991,
"screenMDMin": 768,
"screenSM": 576,
"screenSMMax": 767,
"screenSMMin": 576,
"screenXL": 1200,
"screenXLMax": 1599,
"screenXLMin": 1200,
"screenXS": 480,
"screenXSMax": 575,
"screenXSMin": 480,
"screenXXL": 1600,
"screenXXLMin": 1600,
"size": 16,
"sizeLG": 24,
"sizeMD": 20,
"sizeMS": 16,
"sizePopupArrow": 16,
"sizeSM": 12,
"sizeStep": 4,
"sizeUnit": 4,
"sizeXL": 32,
"sizeXS": 8,
"sizeXXL": 48,
"sizeXXS": 4,
"wireframe": false,
"zIndexBase": 0,
"zIndexPopupBase": 1000,
}
`;
exports[`computed light-mode token values match snapshot 1`] = `
{
"borderRadius": 6,
"borderRadiusLG": 8,
"borderRadiusOuter": 4,
"borderRadiusSM": 4,
"borderRadiusXS": 2,
"boxShadow": "
0 6px 16px 0 rgba(0,0,0,0.08),
0 3px 6px -4px rgba(0,0,0,0.12),
0 9px 28px 8px rgba(0,0,0,0.05)
",
"boxShadowDrawerLeft": "
6px 0 16px 0 rgba(0,0,0,0.08),
3px 0 6px -4px rgba(0,0,0,0.12),
9px 0 28px 8px rgba(0,0,0,0.05)
",
"boxShadowDrawerRight": "
-6px 0 16px 0 rgba(0,0,0,0.08),
-3px 0 6px -4px rgba(0,0,0,0.12),
-9px 0 28px 8px rgba(0,0,0,0.05)
",
"boxShadowDrawerUp": "
0 6px 16px 0 rgba(0,0,0,0.08),
0 3px 6px -4px rgba(0,0,0,0.12),
0 9px 28px 8px rgba(0,0,0,0.05)
",
"boxShadowPopoverArrow": "2px 2px 5px rgba(0,0,0,0.05)",
"boxShadowSecondary": "
0 6px 16px 0 rgba(0,0,0,0.08),
0 3px 6px -4px rgba(0,0,0,0.12),
0 9px 28px 8px rgba(0,0,0,0.05)
",
"boxShadowTabsOverflowBottom": "inset 0 -10px 8px -8px rgba(0,0,0,0.08)",
"boxShadowTabsOverflowLeft": "inset 10px 0 8px -8px rgba(0,0,0,0.08)",
"boxShadowTabsOverflowRight": "inset -10px 0 8px -8px rgba(0,0,0,0.08)",
"boxShadowTabsOverflowTop": "inset 0 10px 8px -8px rgba(0,0,0,0.08)",
"boxShadowTertiary": "
0 1px 2px 0 rgba(0,0,0,0.05),
0 1px 6px -1px rgba(0,0,0,0.03),
0 2px 4px 0 rgba(0,0,0,0.03)
",
"colorBgBase": "#fff",
"colorBgBlur": "transparent",
"colorBgContainer": "#ffffff",
"colorBgContainerDisabled": "rgba(0,0,0,0.04)",
"colorBgElevated": "#ffffff",
"colorBgLayout": "#f5f5f5",
"colorBgMask": "rgba(0,0,0,0.45)",
"colorBgSpotlight": "rgba(0,0,0,0.85)",
"colorBgTextActive": "rgba(0,0,0,0.15)",
"colorBgTextHover": "rgba(0,0,0,0.06)",
"colorBorder": "#d9d9d9",
"colorBorderBg": "#ffffff",
"colorBorderSecondary": "#f0f0f0",
"colorError": "#e04355",
"colorErrorActive": "#ba2f43",
"colorErrorBg": "#fff0f0",
"colorErrorBgActive": "#ffc7c8",
"colorErrorBgHover": "#fff0f0",
"colorErrorBorder": "#ffc7c8",
"colorErrorBorderHover": "#fa9ba0",
"colorErrorHover": "#ed6d78",
"colorErrorOutline": "rgba(255,5,5,0.06)",
"colorErrorText": "#e04355",
"colorErrorTextActive": "#ba2f43",
"colorErrorTextHover": "#ed6d78",
"colorFill": "rgba(0,0,0,0.15)",
"colorFillAlter": "rgba(0,0,0,0.02)",
"colorFillContent": "rgba(0,0,0,0.06)",
"colorFillContentHover": "rgba(0,0,0,0.15)",
"colorFillQuaternary": "rgba(0,0,0,0.02)",
"colorFillSecondary": "rgba(0,0,0,0.06)",
"colorFillTertiary": "rgba(0,0,0,0.04)",
"colorHighlight": "#e04355",
"colorIcon": "rgba(0,0,0,0.45)",
"colorIconHover": "rgba(0,0,0,0.88)",
"colorInfo": "#66bcfe",
"colorInfoActive": "#4c97d9",
"colorInfoBg": "#f0fbff",
"colorInfoBgHover": "#f0faff",
"colorInfoBorder": "#e0f5ff",
"colorInfoBorderHover": "#b8e5ff",
"colorInfoHover": "#b8e5ff",
"colorInfoText": "#66bcfe",
"colorInfoTextActive": "#4c97d9",
"colorInfoTextHover": "#8fd2ff",
"colorLink": "#2893B3",
"colorLinkActive": "#186d8c",
"colorLinkHover": "#6ebccc",
"colorPrimary": "#2893B3",
"colorPrimaryActive": "#186d8c",
"colorPrimaryBg": "#e4f1f2",
"colorPrimaryBgHover": "#c5e2e6",
"colorPrimaryBorder": "#98d0d9",
"colorPrimaryBorderHover": "#6ebccc",
"colorPrimaryHover": "#49a8bf",
"colorPrimaryText": "#2893b3",
"colorPrimaryTextActive": "#186d8c",
"colorPrimaryTextHover": "#49a8bf",
"colorSplit": "rgba(5,5,5,0.06)",
"colorSuccess": "#5ac189",
"colorSuccessActive": "#419c6d",
"colorSuccessBg": "#f0fff4",
"colorSuccessBgHover": "#e6f5eb",
"colorSuccessBorder": "#dae8df",
"colorSuccessBorderHover": "#addbbf",
"colorSuccessHover": "#addbbf",
"colorSuccessText": "#5ac189",
"colorSuccessTextActive": "#419c6d",
"colorSuccessTextHover": "#82cfa2",
"colorText": "rgba(0,0,0,0.88)",
"colorTextBase": "#000",
"colorTextDescription": "rgba(0,0,0,0.45)",
"colorTextDisabled": "rgba(0,0,0,0.25)",
"colorTextHeading": "rgba(0,0,0,0.88)",
"colorTextLabel": "rgba(0,0,0,0.65)",
"colorTextLightSolid": "#fff",
"colorTextPlaceholder": "rgba(0,0,0,0.25)",
"colorTextQuaternary": "rgba(0,0,0,0.25)",
"colorTextSecondary": "rgba(0,0,0,0.65)",
"colorTextTertiary": "rgba(0,0,0,0.45)",
"colorWarning": "#fcc700",
"colorWarningActive": "#d6a100",
"colorWarningBg": "#fffee6",
"colorWarningBgHover": "#fff7a3",
"colorWarningBorder": "#fff07a",
"colorWarningBorderHover": "#ffe552",
"colorWarningHover": "#ffe552",
"colorWarningOutline": "rgba(255,245,5,0.1)",
"colorWarningText": "#fcc700",
"colorWarningTextActive": "#d6a100",
"colorWarningTextHover": "#ffd829",
"colorWhite": "#fff",
"controlHeight": 32,
"controlHeightLG": 40,
"controlHeightSM": 24,
"controlHeightXS": 16,
"controlInteractiveSize": 16,
"controlItemBgActive": "#e4f1f2",
"controlItemBgActiveDisabled": "rgba(0,0,0,0.15)",
"controlItemBgActiveHover": "#c5e2e6",
"controlItemBgHover": "rgba(0,0,0,0.04)",
"controlOutline": "rgba(10,128,137,0.11)",
"controlOutlineWidth": 2,
"controlPaddingHorizontal": 12,
"controlPaddingHorizontalSM": 8,
"controlTmpOutline": "rgba(0,0,0,0.02)",
"fontFamily": "'Inter', Helvetica, Arial",
"fontFamilyCode": "'IBM Plex Mono', 'Courier New', monospace",
"fontHeight": 22,
"fontHeightLG": 24,
"fontHeightSM": 20,
"fontSize": 14,
"fontSizeHeading1": 38,
"fontSizeHeading2": 30,
"fontSizeHeading3": 24,
"fontSizeHeading4": 20,
"fontSizeHeading5": 16,
"fontSizeIcon": 12,
"fontSizeLG": 16,
"fontSizeSM": 12,
"fontSizeXL": 20,
"fontWeightStrong": 600,
"lineHeight": 1.5714285714285714,
"lineHeightHeading1": 1.2105263157894737,
"lineHeightHeading2": 1.2666666666666666,
"lineHeightHeading3": 1.3333333333333333,
"lineHeightHeading4": 1.4,
"lineHeightHeading5": 1.5,
"lineHeightLG": 1.5,
"lineHeightSM": 1.6666666666666667,
"lineType": "solid",
"lineWidth": 1,
"lineWidthBold": 2,
"lineWidthFocus": 3,
"linkDecoration": "none",
"linkFocusDecoration": "none",
"linkHoverDecoration": "none",
"margin": 16,
"marginLG": 24,
"marginMD": 20,
"marginSM": 12,
"marginXL": 32,
"marginXS": 8,
"marginXXL": 48,
"marginXXS": 4,
"motion": true,
"motionBase": 0,
"motionDurationFast": "0.1s",
"motionDurationMid": "0.2s",
"motionDurationSlow": "0.3s",
"motionEaseInBack": "cubic-bezier(0.71, -0.46, 0.88, 0.6)",
"motionEaseInOut": "cubic-bezier(0.645, 0.045, 0.355, 1)",
"motionEaseInOutCirc": "cubic-bezier(0.78, 0.14, 0.15, 0.86)",
"motionEaseInQuint": "cubic-bezier(0.755, 0.05, 0.855, 0.06)",
"motionEaseOut": "cubic-bezier(0.215, 0.61, 0.355, 1)",
"motionEaseOutBack": "cubic-bezier(0.12, 0.4, 0.29, 1.46)",
"motionEaseOutCirc": "cubic-bezier(0.08, 0.82, 0.17, 1)",
"motionEaseOutQuint": "cubic-bezier(0.23, 1, 0.32, 1)",
"motionUnit": 0.1,
"opacityImage": 1,
"opacityLoading": 0.65,
"padding": 16,
"paddingContentHorizontal": 16,
"paddingContentHorizontalLG": 24,
"paddingContentHorizontalSM": 16,
"paddingContentVertical": 12,
"paddingContentVerticalLG": 16,
"paddingContentVerticalSM": 8,
"paddingLG": 24,
"paddingMD": 20,
"paddingSM": 12,
"paddingXL": 32,
"paddingXS": 8,
"paddingXXS": 4,
"screenLG": 992,
"screenLGMax": 1199,
"screenLGMin": 992,
"screenMD": 768,
"screenMDMax": 991,
"screenMDMin": 768,
"screenSM": 576,
"screenSMMax": 767,
"screenSMMin": 576,
"screenXL": 1200,
"screenXLMax": 1599,
"screenXLMin": 1200,
"screenXS": 480,
"screenXSMax": 575,
"screenXSMin": 480,
"screenXXL": 1600,
"screenXXLMin": 1600,
"size": 16,
"sizeLG": 24,
"sizeMD": 20,
"sizeMS": 16,
"sizePopupArrow": 16,
"sizeSM": 12,
"sizeStep": 4,
"sizeUnit": 4,
"sizeXL": 32,
"sizeXS": 8,
"sizeXXL": 48,
"sizeXXS": 4,
"wireframe": false,
"zIndexBase": 0,
"zIndexPopupBase": 1000,
}
`;

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { theme as antdThemeImport } from 'antd';
import { theme as antdThemeImport, type MappingAlgorithm } from 'antd';
import {
type AntdThemeConfig,
type AnyThemeConfig,
@@ -48,8 +48,8 @@ export function isSerializableConfig(
export function deserializeThemeConfig(
config: SerializableThemeConfig,
): AntdThemeConfig {
const { algorithm, ...rest } = config;
const algorithmMap: Record<string, any> = {
const { algorithm, cssVar, ...rest } = config;
const algorithmMap: Record<string, MappingAlgorithm> = {
default: antdThemeImport.defaultAlgorithm,
dark: antdThemeImport.darkAlgorithm,
compact: antdThemeImport.compactAlgorithm,
@@ -74,9 +74,17 @@ export function deserializeThemeConfig(
resolvedAlgorithm = antdThemeImport.defaultAlgorithm;
}
// Ant Design v6 dropped `boolean` from ThemeConfig['cssVar'] (it is now
// object-only, and CSS variables are enabled by default). Superset keeps a
// boolean-friendly serializable API, so coerce at the antd boundary:
// `true` -> `{}` (defaults), `false`/undefined -> omit (antd default).
const normalizedCssVar =
typeof cssVar === 'boolean' ? (cssVar ? {} : undefined) : cssVar;
return {
...rest,
algorithm: resolvedAlgorithm,
...(normalizedCssVar !== undefined ? { cssVar: normalizedCssVar } : {}),
};
}

View File

@@ -31,7 +31,7 @@
"lodash-es": "^4.18.1"
},
"peerDependencies": {
"@ant-design/icons": "^5.6.1",
"@ant-design/icons": "^5.6.1 || ^6.0.0",
"@emotion/react": "^11.4.1",
"@superset-ui/core": "*",
"@testing-library/dom": "^9.3.4",

View File

@@ -54,7 +54,7 @@
"react-ace": "^14.0.1",
"react-draggable": "^4.7.0",
"react-error-boundary": "^6.1.2",
"react-js-cron": "^5.2.0",
"react-js-cron": "^6.0.2",
"react-markdown": "^10.1.0",
"react-resize-detector": "^7.1.2",
"react-syntax-highlighter": "^16.1.1",
@@ -78,7 +78,7 @@
"@types/d3-time-format": "^4.0.3",
"@types/jquery": "^4.0.1",
"@types/lodash": "^4.17.24",
"@types/node": "^26.1.0",
"@types/node": "^26.1.1",
"@types/prop-types": "^15.7.15",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/react-table": "^7.7.20",
@@ -100,7 +100,7 @@
"@types/react-loadable": "*",
"@types/react-window": "^1.8.8",
"@types/tinycolor2": "*",
"antd": "^5.26.0",
"antd": "^6.0.0",
"nanoid": "^5.0.9",
"react": "^18.3.0",
"react-dom": "^18.3.0",

View File

@@ -92,7 +92,7 @@ const MatrixNoDataComponent = () => {
* Individual grid cell component - memoized to prevent unnecessary re-renders
*/
const MatrixifyGridCell = memo(
({ cell, rowHeight, datasource, hooks }: MatrixifyGridCellProps) => {
({ cell, hooks }: MatrixifyGridCellProps) => {
// Use computed title from template (will be empty string if no template)
const cellLabel = cell.title || '';

View File

@@ -118,7 +118,6 @@ interface MatrixifyGridRendererProps {
function MatrixifyGridRenderer({
formData,
datasource,
width,
height,
hooks,
}: MatrixifyGridRendererProps) {
@@ -249,7 +248,7 @@ function MatrixifyGridRenderer({
{/* Row cells for this column group */}
{row
.slice(colGroup.startIdx, colGroup.endIdx)
.map((cell, colIdx) =>
.map(cell =>
cell ? (
<MatrixifyGridCell
key={cell.id}

View File

@@ -103,6 +103,6 @@ test('has proper accessibility attributes', () => {
render(<ActionButton {...defaultProps} />);
const button = screen.getByRole('button');
expect(button).toHaveAttribute('tabIndex', '0');
expect(button).toHaveAttribute('role', 'button');
expect(button.tagName).toBe('BUTTON');
expect(button).toHaveAttribute('type', 'button');
});

View File

@@ -18,6 +18,7 @@
*/
import type { ReactElement, ReactNode } from 'react';
import cx from 'classnames';
import { Tooltip, type TooltipPlacement } from '@superset-ui/core/components';
import { css, useTheme } from '@apache-superset/core/theme';
@@ -27,6 +28,9 @@ export interface ActionProps {
placement?: TooltipPlacement;
icon: ReactNode;
onClick: () => void;
className?: string;
disabled?: boolean;
dataTest?: string;
}
export const ActionButton = ({
@@ -35,14 +39,26 @@ export const ActionButton = ({
placement,
icon,
onClick,
className,
disabled = false,
dataTest,
}: ActionProps) => {
const theme = useTheme();
const actionButton = (
<span
role="button"
tabIndex={0}
<button
type="button"
aria-disabled={disabled}
aria-label={typeof tooltip === 'string' ? tooltip : label}
css={css`
appearance: none;
border: none;
background: none;
padding: 0;
margin: 0;
font: inherit;
line-height: 1;
display: inline-flex;
align-items: center;
cursor: pointer;
color: ${theme.colorIcon};
margin-right: ${theme.sizeUnit}px;
@@ -51,13 +67,17 @@ export const ActionButton = ({
fill: ${theme.colorPrimary};
}
}
&.disabled {
color: ${theme.colorTextDisabled};
cursor: not-allowed;
}
`}
className="action-button"
data-test={label}
onClick={onClick}
className={cx('action-button', className, { disabled })}
data-test={dataTest ?? label}
onClick={disabled ? undefined : onClick}
>
{icon}
</span>
</button>
);
const tooltipId = `${label.replaceAll(' ', '-').toLowerCase()}-tooltip`;

View File

@@ -444,7 +444,6 @@ export function AsyncAceEditor(
/* Adjust tooltip styles */
.ace_tooltip {
margin-left: ${token.margin}px;
padding: ${token.sizeUnit * 2}px;
background-color: ${token.colorBgElevated} !important;
color: ${token.colorText} !important;
border: 1px solid ${token.colorBorderSecondary};

View File

@@ -21,7 +21,11 @@ import type { ButtonGroupProps } from './types';
export function ButtonGroup(props: ButtonGroupProps) {
const { className, children } = props;
return (
// role="group" is the correct ARIA pattern for a generic button toolbar;
// the suggested native tags (fieldset, etc.) carry unrelated form
// semantics and unwanted default browser styling.
<div
// eslint-disable-next-line jsx-a11y/prefer-tag-over-role
role="group"
className={className}
css={{

View File

@@ -82,8 +82,10 @@ test('collapses on click', async () => {
await userEvent.click(screen.getAllByRole('button')[0]);
expect(screen.getByText('Content 1').parentNode).toHaveClass(
'ant-collapse-content-hidden',
// antd v6 moved the hidden state to the panel wrapper element
// (ant-collapse-panel-hidden) instead of the content wrapper.
expect(screen.getByText('Content 1').parentElement).toHaveClass(
'ant-collapse-panel-hidden',
);
expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
});

View File

@@ -52,10 +52,10 @@ const StyledCollapse = styled((props: CollapseProps) => (
`
border-bottom: 1px solid ${theme.colorBorderSecondary};
`}
.ant-collapse-content {
.ant-collapse-panel {
color: ${({ theme }) => theme.colorText};
.ant-collapse-content-box {
.ant-collapse-body {
.loading.inline {
margin: ${({ theme }) => theme.sizeUnit * 12}px auto;
display: block;

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import { render } from '@superset-ui/core/spec';
import { render, waitFor } from '@superset-ui/core/spec';
import * as ReactCronPicker from 'react-js-cron';
import { CronPicker } from '.';
@@ -37,3 +37,25 @@ test('Should send correct props to ReactCronPicker', () => {
expect.anything(),
);
});
// The invalid-cron error styling was silently dead under antd 6 until
// react-js-cron was bumped to v6 (its v5 stylesheet targeted antd 5 Select
// classes that no longer exist). Pin the error contract: an invalid value
// must fire onError and apply the vendor's error class — the hook that both
// the vendor stylesheet and Superset's overrides style the error state
// through.
test('flags an invalid cron value with the error state', async () => {
const onError = jest.fn();
const { container } = render(
<CronPicker value="not a cron" setValue={jest.fn()} onError={onError} />,
);
await waitFor(() =>
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ type: expect.any(String) }),
),
);
expect(
// eslint-disable-next-line testing-library/no-node-access
container.querySelector('.react-js-cron-error'),
).toBeInTheDocument();
});

View File

@@ -175,7 +175,7 @@ export const CronPicker = styled((props: CronProps) => (
.react-js-cron-select.ant-select {
width: 100%;
.ant-select-selector {
.ant-select-content {
flex-wrap: nowrap;
}
}
@@ -188,12 +188,12 @@ export const CronPicker = styled((props: CronProps) => (
}
}
.react-js-cron-custom-select .ant-select-selection-placeholder {
.react-js-cron-custom-select .ant-select-placeholder {
flex: auto;
border-radius: ${theme.borderRadius}px;
}
.react-js-cron-custom-select .ant-select-selection-overflow-item {
.react-js-cron-custom-select .ant-select-content-item {
align-self: center;
}

View File

@@ -338,9 +338,9 @@ export const DropdownContainer = forwardRef(
<>
<Global
styles={css`
.ant-popover-inner {
// Some OS versions only show the scroll when hovering.
// These settings will make the scroll always visible.
.ant-popover-container {
/* Some OS versions only show the scroll when hovering. */
/* These settings will make the scroll always visible. */
::-webkit-scrollbar {
-webkit-appearance: none;
width: 14px;
@@ -361,7 +361,8 @@ export const DropdownContainer = forwardRef(
<Popover
styles={{
body: {
// antd v6 renamed the inner content slot `body` -> `container`
container: {
maxHeight: `${MAX_HEIGHT}px`,
overflow: showOverflow ? 'auto' : 'visible',
},

View File

@@ -134,6 +134,10 @@ const ImageContainer = ({
? imageMap[image as keyof typeof imageMap]
: image;
return (
// Groups Empty's SVG illustration + description into one accessible
// image; can't be a literal <img> since it's a component tree, not an
// image file reference.
// eslint-disable-next-line jsx-a11y/prefer-tag-over-role
<div role="img" aria-label="empty">
<Empty
description={false}

View File

@@ -25,8 +25,12 @@ import { Icons } from '@superset-ui/core/components/Icons';
import { Tooltip } from '../Tooltip';
import type { FaveStarProps } from './types';
const StyledLink = styled.a`
const StyledLink = styled.button`
${({ theme }) => css`
appearance: none;
border: none;
background: none;
font: inherit;
font-size: ${theme.fontSizeXL}px;
display: flex;
padding: 0 0 0 ${theme.sizeUnit * 2}px;
@@ -55,11 +59,10 @@ export const FaveStar = ({
const content = (
<StyledLink
href="#"
type="button"
onClick={onClick}
className="fave-unfave-icon"
data-test="fave-unfave-icon"
role="button"
>
{isStarred ? (
<Icons.StarFilled

View File

@@ -28,6 +28,10 @@ const StyledInput = styled(Input)`
margin: ${({ theme }) => `${theme.sizeUnit}px 0 ${theme.sizeUnit * 2}px`};
`;
const StyledTextArea = styled(Input.TextArea)`
margin: ${({ theme }) => `${theme.sizeUnit}px 0 ${theme.sizeUnit * 2}px`};
`;
const StyledInputPassword = styled(Input.Password)`
margin: ${({ theme }) => `${theme.sizeUnit}px 0 ${theme.sizeUnit * 2}px`};
`;
@@ -62,6 +66,8 @@ export const LabeledErrorBoundInput = ({
get_url,
description,
isValidating = false,
renderAsTextArea,
textAreaCss,
...props
}: LabeledErrorBoundInputProps) => {
const hasError = !!errorMessage;
@@ -96,8 +102,14 @@ export const LabeledErrorBoundInput = ({
</Tooltip>
)
}
// input[type=password] doesn't get an implicit "textbox" role
// (unlike other text inputs), so this explicit override is
// needed for it to be discoverable via role-based queries/AT.
// eslint-disable-next-line jsx-a11y/prefer-tag-over-role
role="textbox"
/>
) : renderAsTextArea ? (
<StyledTextArea css={textAreaCss} {...props} {...validationMethods} />
) : (
<StyledInput {...props} {...validationMethods} />
)}

View File

@@ -16,7 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
import type { SerializedStyles } from '@emotion/react';
export type { FormProps, FormInstance, FormItemProps } from 'antd/es/form';
export type { SerializedStyles };
export interface LabeledErrorBoundInputProps {
label?: string;
@@ -31,5 +34,7 @@ export interface LabeledErrorBoundInputProps {
classname?: string;
visibilityToggle?: boolean;
isValidating?: boolean;
renderAsTextArea?: boolean;
textAreaCss?: SerializedStyles;
[x: string]: any;
}

View File

@@ -79,8 +79,12 @@ const IconButton: React.FC<IconButtonProps> = ({
};
return (
// antd's Card renders a fixed <div> (no polymorphic tag support) with
// its own rich internal layout (cover/title/tooltip); that doesn't map
// onto a native <button>, so role="button" is used instead.
<Card
hoverable
// eslint-disable-next-line jsx-a11y/prefer-tag-over-role
role="button"
tabIndex={0}
aria-label={buttonText}

View File

@@ -63,11 +63,11 @@ const IconBlock = styled.div`
span {
margin-top: ${({ theme }) =>
2 * theme.sizeUnit}px; // Add spacing between icon and name
2 * theme.sizeUnit}px; /* Add spacing between icon and name */
font-size: ${({ theme }) =>
theme.fontSizeSM}; // Optional: adjust font size for elegance
theme.fontSizeSM}; /* Optional: adjust font size for elegance */
color: ${({ theme }) =>
theme.colorText}; // Optional: subtle color for the name
theme.colorText}; /* Optional: subtle color for the name */
}
`;

View File

@@ -18,6 +18,7 @@
*/
import { fireEvent, render } from '@superset-ui/core/spec';
import { Icons } from '../Icons';
import { Label } from '.';
import { LabelGallery, options } from './Label.stories';
@@ -39,6 +40,21 @@ test('renders with monospace prop', () => {
expect(getByText('monospace text')).toBeInTheDocument();
});
// Regression: Ant Design v6's Tag clones the element passed via its `icon` prop
// and overwrites that element's inline `style`, which silently dropped the
// icon's own color. Label wraps the icon in a span so the wrapper (not the
// icon) is Tag's clone target and the icon keeps its explicit color.
test('preserves a custom icon color (antd v6 Tag icon-style regression)', () => {
const { container } = render(
<Label icon={<Icons.CheckCircleOutlined iconColor="#aabbcc" />}>
labeled
</Label>,
);
expect(container.querySelector('[role="img"]')).toHaveStyle({
color: '#aabbcc',
});
});
// test stories from the storybook!
test('renders all the storybook gallery variants', () => {
const { container } = render(<LabelGallery />);

View File

@@ -34,7 +34,7 @@ export const Label = forwardRef<HTMLSpanElement, LabelProps>((props, ref) => {
onClick,
children,
icon,
id,
id: _id,
...rest
} = props;
@@ -76,7 +76,27 @@ export const Label = forwardRef<HTMLSpanElement, LabelProps>((props, ref) => {
onClick={onClick}
role={onClick ? 'button' : undefined}
style={style}
icon={icon}
/*
* Ant Design v6's Tag clones the `icon` element and overrides its inline
* `style` (see antd/es/tag: cloneElement(icon, { style: mergedStyles.icon })),
* which would drop the icon's own color/size. Wrapping the icon in a span
* lets Tag override the (empty) wrapper style while the real icon keeps its
* own inline styling.
*/
icon={
icon ? (
<span
css={css`
display: inline-flex;
align-items: center;
`}
>
{icon}
</span>
) : (
icon
)
}
css={labelStyles}
{...rest}
>

View File

@@ -99,6 +99,10 @@ export function Loading({
$spinnerHeight="auto"
$opacity={opacity}
className={cls('loading', position, className)}
// role="status" is the standard WAI-ARIA live-region pattern for a
// loading spinner; <output> (the suggested tag) is for form
// calculation results, not a fit here.
// eslint-disable-next-line jsx-a11y/prefer-tag-over-role
role="status"
aria-live="polite"
aria-label={t('Loading')}

View File

@@ -120,8 +120,8 @@ export const StyledModal = styled(BaseModal)<StyledModalProps>`
right: 0;
display: flex;
justify-content: center;
// Keep the close button clickable when modal body content uses
// position: sticky with elevated z-index (e.g. DatabaseModal header).
/* Keep the close button clickable when modal body content uses */
/* position: sticky with elevated z-index (e.g. DatabaseModal header). */
z-index: ${theme.zIndexPopupBase + 1};
}
@@ -344,6 +344,8 @@ const CustomModal = ({
className="draggable-trigger"
onMouseOver={() => dragDisabled && setDragDisabled(false)}
onMouseOut={() => !dragDisabled && setDragDisabled(true)}
onFocus={() => dragDisabled && setDragDisabled(false)}
onBlur={() => !dragDisabled && setDragDisabled(true)}
>
{title}
</div>

View File

@@ -16,7 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import { render, screen, userEvent } from '@superset-ui/core/spec';
import { render, screen, userEvent, fireEvent } from '@superset-ui/core/spec';
import { ModalTrigger } from '.';
const mockedProps = {
@@ -74,3 +75,46 @@ test('should render a modal after click', async () => {
await userEvent.click(screen.getByRole('button'));
expect(screen.getByRole('dialog')).toBeInTheDocument();
});
test('stops propagation of navigation keys to parent elements', async () => {
const handleParentKeyDown = jest.fn();
render(
<div onKeyDown={handleParentKeyDown}>
<ModalTrigger
triggerNode={<span>Trigger</span>}
modalBody={<input data-test="modal-input" />}
/>
</div>,
);
await userEvent.click(screen.getByText('Trigger'));
const input = await screen.findByTestId('modal-input');
const blockedKeys = [
'ArrowLeft',
'ArrowRight',
'ArrowUp',
'ArrowDown',
'Home',
'End',
];
for (const key of blockedKeys) {
handleParentKeyDown.mockClear();
fireEvent.keyDown(input, { key });
expect(handleParentKeyDown).not.toHaveBeenCalled();
}
// `Tab` must be checked before `Escape`: pressing `Escape` legitimately
// closes the modal (a real global listener unmounts the dialog), so any
// key fired on the stale `input` reference afterwards can no longer
// bubble anywhere.
const allowedKeys = ['Tab', 'Escape'];
for (const key of allowedKeys) {
handleParentKeyDown.mockClear();
fireEvent.keyDown(input, { key });
expect(handleParentKeyDown).toHaveBeenCalled();
}
});

View File

@@ -16,17 +16,27 @@
* specific language governing permissions and limitations
* under the License.
*/
import { handleKeyboardActivation } from '../../utils';
import {
forwardRef,
ForwardedRef,
useState,
ReactNode,
MouseEvent,
SyntheticEvent,
} from 'react';
import { Button } from '../Button';
import { Modal } from '../Modal';
const MENU_NAVIGATION_KEYS = new Set([
'ArrowLeft',
'ArrowRight',
'ArrowUp',
'ArrowDown',
'Home',
'End',
]);
export interface ModalTriggerProps {
dialogClassName?: string;
triggerNode: ReactNode;
@@ -87,7 +97,7 @@ export const ModalTrigger = forwardRef(
onExit?.();
};
const open = (e: MouseEvent) => {
const open = (e: SyntheticEvent) => {
e.preventDefault();
beforeOpen?.();
setShowModal(true);
@@ -117,7 +127,18 @@ export const ModalTrigger = forwardRef(
</Button>
)}
{!isButton && (
<div data-test="span-modal-trigger" onClick={open} role="button">
// Generic wrapper used by 19+ callers passing arbitrary
// triggerNode content that relies on block-level <div> layout;
// swapping to <button> risks a layout regression across callers
// with no shared CSS to guide a safe reset.
<div
data-test="span-modal-trigger"
onClick={open}
onKeyDown={handleKeyboardActivation(open)}
// eslint-disable-next-line jsx-a11y/prefer-tag-over-role
role="button"
tabIndex={0}
>
{triggerNode}
</div>
)}
@@ -140,7 +161,16 @@ export const ModalTrigger = forwardRef(
draggableConfig={draggableConfig}
destroyOnHidden={destroyOnHidden}
>
{modalBody}
<div
style={{ display: 'contents' }}
onKeyDown={e => {
if (MENU_NAVIGATION_KEYS.has(e.key)) {
e.stopPropagation();
}
}}
>
{modalBody}
</div>
</Modal>
</>
);

View File

@@ -41,7 +41,7 @@ const defaultProps: PageHeaderWithActionsProps = {
data-test="additional-actions-menu"
/>
),
menuDropdownProps: { onVisibleChange: jest.fn(), visible: true },
menuDropdownProps: { onOpenChange: jest.fn(), open: true },
};
test('Renders', async () => {
@@ -52,5 +52,5 @@ test('Renders', async () => {
expect(screen.getByText('Save')).toBeVisible();
await userEvent.click(screen.getByLabelText('Menu actions trigger'));
expect(defaultProps.menuDropdownProps.onVisibleChange).toHaveBeenCalled();
expect(defaultProps.menuDropdownProps.onOpenChange).toHaveBeenCalled();
});

View File

@@ -103,7 +103,18 @@ const PopoverDropdown = (props: PopoverDropdownProps) => {
})),
}}
>
<div role="button" css={{ display: 'flex', alignItems: 'center' }}>
<button
type="button"
css={{
appearance: 'none',
border: 'none',
background: 'none',
padding: 0,
font: 'inherit',
display: 'flex',
alignItems: 'center',
}}
>
{selected && renderButton(selected)}
<Icons.DownOutlined
iconSize="s"
@@ -112,7 +123,7 @@ const PopoverDropdown = (props: PopoverDropdownProps) => {
marginLeft: theme.sizeUnit * 0.5,
}}
/>
</div>
</button>
</Dropdown>
);
};

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