Compare commits

...

196 Commits

Author SHA1 Message Date
Evan
7c1fa4ae72 fix(embedded): include legacy singular metric and reject malformed orderby in guest sort guard
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 23:00:13 -07:00
Claude Code
27c481d75d fix(embedded): allow guest users to sort table columns
query_context_modified() compared a guest request's orderby against the
stored chart's orderby with a strict subset check, alongside columns,
metrics and groupby. Saved charts usually store an empty orderby, so when
a guest clicked a column header to sort a table in an embedded dashboard
the new orderby value failed the subset check and the request was
rejected with "Guest user cannot modify chart payload", even though
sorting only changes result ordering and not which data is read.

Handle orderby separately: a guest may sort by any column or metric the
stored chart already references, but order-by terms that are not present
in the stored chart (e.g. a free-form random() expression) are still
rejected, preserving the existing SQL-injection guard. The columns/
metrics/groupby comparison is extracted into a helper to keep
query_context_modified within the complexity budget, and the inner loop's
variable shadowing of `key` is fixed along the way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-18 21:46:53 -07:00
rijekaDrina
79cfe4d9bc feat(i18n): add Serbian translations (Cyrillic + Latin) (#41137)
Signed-off-by: Aleksije Micic <aleksije.micic1997@gmail.com>
Co-authored-by: Aleksije Micic <aleksije.micic1997@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-06-18 17:45:04 -07:00
Evan Rusackas
3eae8cd614 fix(alerts): don't show a never-run report as a green success (#29622) (#41121)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-18 15:55:30 -07:00
Elizabeth Thompson
0c9ece65bb fix(reports): require user in get_screenshot, simplify Selenium lifecycle, and fail on tiled screenshot error (#41080)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 15:46:17 -07:00
Elizabeth Thompson
7040388ad1 fix(query_object_factory): normalize deprecated query fields before constructing QueryObject (#41204) 2026-06-18 15:02:07 -07:00
Elizabeth Thompson
a5ece52207 fix(views): add new_target to deprecated explore_json endpoints (#41159) 2026-06-18 15:02:03 -07:00
Evan Rusackas
a7c0f4b83d fix(embedded-sdk): omit registry-url so npm uses OIDC publishing (#41211)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 13:43:23 -07:00
Evan Rusackas
0f05239260 fix(embedded-sdk): clear placeholder token so npm uses OIDC publishing (#41210)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 13:26:32 -07:00
Evan Rusackas
60a7804193 fix(embedded-sdk): surface npm publish stderr in release script (#41206)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 13:16:15 -07:00
Evan Rusackas
4053f53c29 ci(embedded-sdk): fix release CI by publishing via npm trusted publishing (OIDC) (#41207)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:33:39 -07:00
Nitish Agarwal
7837054dbc fix(chart): cross-filter emits dimension value instead of metric label for stacked bars (#38120)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-06-18 11:24:36 -07:00
Evan Rusackas
69c8f37c67 docs(installation): fix PyPI install Python version and OS dependencies (#41178)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:03:50 -07:00
Abdul Rehman
76e2418f1e fix(mcp): add safeguards to ensure all MCP tools are wrapped with mcp_auth_hook (#40412)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-06-18 10:27:53 -07:00
Vitor Avila
b4e3452bfd fix(chart API): Do not duplicate Jinja-applied filters with filters_dashboard_id (#41131) 2026-06-18 14:25:54 -03:00
jesperct
188c84f1cd fix(explore): drop inherit/custom time shifts when switching to a viz that can't honor them (#40865) 2026-06-18 10:23:31 -07:00
dependabot[bot]
74ae5a45f9 chore(deps): bump dompurify from 3.4.9 to 3.4.11 in /superset-frontend (#41201)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 08:22:57 -07:00
dependabot[bot]
fc61918364 chore(deps): bump undici from 7.25.0 to 7.28.0 in /superset-frontend (#41202)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 08:22:47 -07:00
dependabot[bot]
3e811087de chore(deps): bump dompurify from 3.4.2 to 3.4.11 in /docs (#41203)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 08:22:39 -07:00
ksnikiforov
c218dc418b fix(dashboard): fixed first/last aggregations in pivot tables (#33275)
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com>
2026-06-18 10:49:12 +02:00
dependabot[bot]
c98ed92303 chore(deps): bump markdown-to-jsx from 9.8.1 to 9.8.2 in /superset-frontend (#41191)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 01:07:47 -07:00
dependabot[bot]
84c32ec132 chore(deps-dev): bump @types/node from 25.9.2 to 25.9.3 in /superset-frontend (#41190)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 00:47:15 -07:00
dependabot[bot]
8636875b39 chore(deps-dev): bump eslint-plugin-storybook from 10.4.2 to 10.4.3 in /superset-frontend (#41192)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 00:47:12 -07:00
dependabot[bot]
dde6974ac2 chore(deps): bump dom-to-image-more from 3.7.2 to 3.9.0 in /superset-frontend (#41193)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 00:47:08 -07:00
dependabot[bot]
e36eb6f47c chore(deps-dev): bump @types/node from 25.9.2 to 25.9.3 in /superset-websocket (#41186)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 00:26:54 -07:00
dependabot[bot]
f6e12278dc chore(deps-dev): update @types/node requirement from ^25.9.2 to ^25.9.3 in /superset-frontend/packages/superset-ui-core (#41188)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 00:26:51 -07:00
dependabot[bot]
43d5b6319b chore(deps-dev): bump baseline-browser-mapping from 2.10.34 to 2.10.35 in /superset-frontend (#41189)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 00:26:48 -07:00
melikmertd
ae0b1f0308 fix(countrymap chart): city names of Türkiye edited in Countrymap Chart. (#32497)
Co-authored-by: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com>
2026-06-17 21:46:11 -07:00
Evan Rusackas
4acb777a40 chore(sqllab): remove dead TableElement component and syncTable action (#41071)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-17 19:29:09 -07:00
Durgaprasad M L
7e98410743 fix(theme): embedded method overrides dashboard level config (#40777)
Co-authored-by: Mehmet Salih Yavuz <salih.yavuz@proton.me>
2026-06-17 18:33:04 -07:00
Hans Yu
883b7a286d refactor: update SQLAlchemy select() syntax to 2.0 (#40276) 2026-06-17 17:50:32 -07:00
Evan Rusackas
d9d8b2bcc0 chore(ci): correct action ref version comments (zizmor) (#41160)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:42:14 -07:00
Evan Rusackas
9da54eff84 chore(ci): set least-privilege workflow permissions (zizmor) (#41161)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:41:47 -07:00
dependabot[bot]
fb2b9fa8ff chore(deps): bump cryptography from 46.0.7 to 48.0.1 (#41010)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-17 15:01:20 -07:00
Dante R. Giuliano
31797005db docs(INTHEWILD): adding Tech Solution (#37178)
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Joe Li <joe@preset.io>
2026-06-17 14:59:15 -07:00
Evan Rusackas
ca2d340db3 fix(security): validate dynamic method dispatch in asyncEvent (#41163)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:58:34 -07:00
jesperct
ef82da8458 fix(charts): apply datetime format to unaggregated temporal columns (#41060) 2026-06-17 14:56:09 -07:00
Jean Massucatto
fee1cf9f08 chore(sqllab): remove dead TableElement component (#41029) 2026-06-17 14:54:41 -07:00
jesperct
2d2a8f3ab0 fix(plugin-chart-handlebars): follow the app theme in Customize code editors (#40952) 2026-06-17 14:52:52 -07:00
dependabot[bot]
a19093e65a chore(deps-dev): bump webpack-dev-server from 5.2.4 to 5.2.5 in /superset-frontend (#41168)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 12:55:44 -07:00
dependabot[bot]
b72a0a53c0 chore(deps): bump webpack-dev-server from 5.2.4 to 5.2.5 in /docs (#41169)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 12:55:40 -07:00
Thomas Bernhard
512b6f43c1 chore(embedded sdk): bump sdk version number (#40991) 2026-06-17 12:47:41 -07:00
Evan Rusackas
b18fab7fc1 ci(docker): free disk space before image build to fix "no space left on device" (#41068)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-17 12:43:43 -07:00
Evan Rusackas
b06c6b7464 ci: bump setup-python to v6 (Node 24) before Node 20 deprecation (#41066)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-17 11:56:47 -07:00
Evan Rusackas
bede4b2121 ci(docker): retry image build to absorb transient Docker Hub registry errors (#41069)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-17 11:56:23 -07:00
İbrahim Ercan
5e812c8757 feat(docker): add environment values to set log file for worker and beat (#40998)
Co-authored-by: Ibrahim Ercan <ibrahim.ercan@vlmedia.com.tr>
2026-06-17 10:42:45 -07:00
Craig Ingram
de390f22a4 fix(helm): Evaluate init.extraContainers templates (#31878)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-06-17 10:39:40 -07:00
dependabot[bot]
464c67d586 chore(deps-dev): bump @storybook/addon-links from 10.4.2 to 10.4.3 in /superset-frontend (#41146)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-17 10:17:00 -07:00
dependabot[bot]
7f7f87e823 chore(deps-dev): bump prettier from 3.8.3 to 3.8.4 in /docs (#41140)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 09:49:28 -07:00
dependabot[bot]
7c2f5142ce chore(deps-dev): bump yeoman-test from 11.5.2 to 11.5.3 in /superset-frontend/packages/generator-superset (#41142)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 09:35:54 -07:00
dependabot[bot]
874ac3dc01 chore(deps): bump @swc/core from 1.15.40 to 1.15.41 in /docs (#41143)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 09:35:46 -07:00
dependabot[bot]
f56e34d6e6 chore(deps-dev): bump @typescript-eslint/eslint-plugin from 8.60.1 to 8.61.0 in /superset-websocket (#41085)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 09:28:38 -07:00
dependabot[bot]
742a21f6f7 chore(deps-dev): bump prettier from 3.8.3 to 3.8.4 in /superset-websocket (#41138)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 09:28:21 -07:00
dependabot[bot]
a7c49ac9f2 chore(deps): bump baseline-browser-mapping from 2.10.34 to 2.10.35 in /docs (#41144)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 09:24:51 -07:00
dependabot[bot]
99d927eac7 chore(deps-dev): bump @swc/core from 1.15.40 to 1.15.41 in /superset-frontend (#41145)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 09:24:35 -07:00
dependabot[bot]
994594e4a8 chore(deps-dev): bump storybook from 10.4.2 to 10.4.3 in /superset-frontend (#41147)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 09:23:32 -07:00
dependabot[bot]
e92599fb50 chore(deps-dev): bump prettier from 3.8.3 to 3.8.4 in /superset-frontend (#41150)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 09:22:13 -07:00
Amin Ghadersohi
eebe1a1a5b fix(dashboards): remove thumbnail_url from list API to reduce cache cost (#38567) 2026-06-17 09:35:21 -06:00
Mehmet Salih Yavuz
664e777a84 chore(deps): bump react to ^18.3.0 (#40012) 2026-06-17 18:01:59 +03:00
Joao Amaral
750518cf6f fix(celery): check app context before session removal in teardown (#37574)
Co-authored-by: codeant-ai-for-open-source[bot] <244253245+codeant-ai-for-open-source[bot]@users.noreply.github.com>
Co-authored-by: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com>
Co-authored-by: Daniel Vaz Gaspar <danielvazgaspar@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Elizabeth Thompson <eschutho@gmail.com>
2026-06-17 10:44:27 -03:00
Michael S. Molina
59d1b5f300 fix(nav): prevent full reload when clicking logo; redirect / to welcome (#41119)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 09:27:17 -03:00
Xie Yanbo
a27ec1923e chore(export): Added ability to export chart YAML files with Unicode characters, fix #20331 (#28008)
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 07:55:19 +01:00
serverdevil
3e2174b50f fix(database): enable superset_app_root override for databaseview link (#33508)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Superset Dev <dev@superset.apache.org>
2026-06-16 20:24:49 -07:00
Gabriel Bourgeois
5b66443d48 fix(cli): inconsistent options for set-database-uri (#34893) 2026-06-16 17:50:51 -07:00
Korbinian Preisler
2ea7585490 chore(i18n): update German (de) translation (#40431)
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:47:57 -07:00
Simon Rühle
eeac76146c fix(helm): add host alias to init job (#33968)
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-16 17:44:47 -07:00
Shaitan
6a1091d576 fix(sql): broaden mutating-statement detection in SQL Lab parser (#40421)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: sha174n <pedro.sousa@preset.io>
2026-06-16 15:07:34 -07:00
Jakub Hrubý
8e82b6b2c3 fix(translation): loading translations in menu (#35640)
Co-authored-by: Jakub Hrubý <jakub.hruby@orgis.cz>
Co-authored-by: Jezevec <panjzvc@gmail.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-06-16 14:35:32 -07:00
Evan Rusackas
b0c5f99007 fix(oracle): replace deprecated cx-Oracle extra with oracledb (#41122)
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-16 14:32:11 -07:00
Elizabeth Thompson
f1ae683923 fix(deps): replace deprecated np.NaN with np.nan (#41118)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 14:19:08 -07:00
dependabot[bot]
d51d98891e chore(deps): bump flask-migrate from 3.1.0 to 4.1.0 (#41011)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-16 12:18:08 -07:00
dependabot[bot]
1f95a6c486 chore(deps): bump simplejson from 3.20.1 to 4.1.1 (#41082)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-16 11:00:42 -07:00
dependabot[bot]
e93cbd6c38 chore(deps): bump croniter from 6.0.0 to 6.2.2 (#41086)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-16 10:59:00 -07:00
dependabot[bot]
dca8af770c chore(deps-dev): bump typescript-eslint from 8.60.1 to 8.61.0 in /superset-websocket (#41087)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-16 10:58:39 -07:00
dependabot[bot]
81c1181519 chore(deps-dev): bump typescript-eslint from 8.60.1 to 8.61.0 in /docs (#41092)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-16 10:56:44 -07:00
dependabot[bot]
387c62919e chore(deps): bump hot-shots from 15.0.0 to 16.0.0 in /superset-websocket (#41107)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-16 10:56:22 -07:00
dependabot[bot]
77d7483f27 chore(deps-dev): bump @formatjs/intl-durationformat from 0.10.13 to 0.10.14 in /superset-frontend (#41109)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-16 10:54:22 -07:00
dependabot[bot]
1a8d08152d chore(deps): bump fuse.js from 7.4.1 to 7.4.2 in /superset-frontend (#41110)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-16 10:54:06 -07:00
Bob Jo
257dafeec5 fix(query): don't mutate ad-hoc ORDER BY expressions when building queries (#40993)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-16 13:03:39 -04:00
Alexandru Soare
6d08e79259 feat(security): Add extension hooks for custom access control, ownership, and asset lifecycle (#40707) 2026-06-16 15:25:03 +03:00
Geidō
01ed81785e fix(dashboard): required filters reliably apply default + Apply enables on change (#40470) 2026-06-16 11:23:05 +03:00
Vighnesh Tule
7b4efacbc2 fix(charts): add default padding to match other charts (#36895)
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@preset.io>
2026-06-15 21:05:17 -07:00
Amin Ghadersohi
7cb4990403 feat(mcp): add create_dataset tool to register physical tables as datasets (#40340)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:25:29 -04:00
dependabot[bot]
c90b2571d7 chore(deps-dev): bump xlrd from 2.0.1 to 2.0.2 (#41083)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 18:19:43 -07:00
dependabot[bot]
1a4941eee5 chore(deps-dev): bump hdbcli from 2.28.20 to 2.28.21 (#41084)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 18:19:33 -07:00
dependabot[bot]
d839cca995 chore(deps-dev): update pyocient requirement from <2,>=1.0.15 to >=1.0.15,<4 (#40941)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 18:18:25 -07:00
dependabot[bot]
0ec7e7df99 chore(deps): bump dompurify from 3.4.8 to 3.4.9 in /superset-frontend (#41089)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 18:16:36 -07:00
dependabot[bot]
9d8287e1bd chore(deps-dev): bump @typescript-eslint/parser from 8.60.1 to 8.61.0 in /superset-websocket (#41090)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 18:16:21 -07:00
dependabot[bot]
0c696cea7e chore(deps): bump google-auth-library from 10.6.2 to 10.7.0 in /superset-frontend (#41091)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 18:16:05 -07:00
dependabot[bot]
fe625a917e chore(deps-dev): bump @typescript-eslint/parser from 8.60.1 to 8.61.0 in /docs (#41093)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 18:14:51 -07:00
dependabot[bot]
a69f9eb00d chore(deps-dev): bump oxlint from 1.68.0 to 1.69.0 in /superset-frontend (#41094)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 18:14:27 -07:00
Evan Rusackas
1311d040ba feat(deckgl): add point radius controls for GeoJSON layer (#33247)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-15 17:38:43 -07:00
Evan Rusackas
6e2db42d98 chore(lint): convert dashboard components to function components (#39460)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com>
2026-06-15 16:39:12 -07:00
yousoph
28aedc82c3 fix(upload): database field shows validation warning after selecting a database (#41078) 2026-06-15 16:38:24 -07:00
Evan Rusackas
f56524bb71 chore(frontend): remove unused modules flagged by knip (#41072)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 16:38:00 -07:00
Evan Rusackas
4ae9980e4c chore(ci): remove unused Claude PR Assistant workflow (#41081)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 16:37:39 -07:00
Amin Ghadersohi
c1b5d05f83 fix(bigquery): set default dataset from schema in adjust_engine_params (#40776) 2026-06-15 18:37:06 -04:00
Evan Rusackas
e16bb29faf fix(embedded): allow guests to apply a Time Grain native filter (#32768) (#41017)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 15:22:21 -07:00
Elizabeth Thompson
09b4bc51a3 fix(charts): rename deprecated query object fields in schema before QueryObject construction (#41056) 2026-06-15 14:45:41 -07:00
Evan Rusackas
379435b7eb feat(ssh_tunnel): add opt-in server host key verification (#40673)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-15 12:17:14 -07:00
Amin Ghadersohi
4b96b91b53 feat(mcp): add aggregation field to BigNumberChartConfig for Big Number with Trendline (#40775)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-15 14:40:37 -04:00
dependabot[bot]
073599bd0c chore(deps-dev): bump @babel/core from 7.25.2 to 7.29.6 in /superset-embedded-sdk (#41057)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 11:20:01 -07:00
dependabot[bot]
9e2c4533c8 chore(deps): bump @babel/core from 7.28.6 to 7.29.7 in /docs (#41058)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 11:19:43 -07:00
dependabot[bot]
1ae115981f chore(deps): bump form-data from 4.0.5 to 4.0.6 in /superset-frontend/cypress-base (#41061)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 11:19:23 -07:00
dependabot[bot]
b078ae4b51 chore(deps): bump launch-editor from 2.11.1 to 2.14.1 in /docs (#41062)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 11:19:05 -07:00
dependabot[bot]
48b755470f chore(deps): bump form-data from 4.0.5 to 4.0.6 in /superset-frontend (#41063)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 11:18:48 -07:00
dependabot[bot]
dad7dae4f6 chore(deps): bump launch-editor from 2.9.1 to 2.14.1 in /superset-frontend (#41065)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 11:18:20 -07:00
dependabot[bot]
11e35eca3b chore(deps-dev): bump eslint from 8.57.1 to 10.4.1 in /superset-frontend (#40846)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-15 10:36:50 -07:00
dependabot[bot]
8093197c97 chore(deps): bump baseline-browser-mapping from 2.10.33 to 2.10.34 in /docs (#41036)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 10:31:50 -07:00
dependabot[bot]
919c6eddc1 chore(deps-dev): bump @types/node from 25.9.1 to 25.9.2 in /superset-websocket (#41032)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 10:22:14 -07:00
dependabot[bot]
886bb200d0 chore(deps): bump react-arborist from 3.8.0 to 3.10.1 in /superset-frontend (#41033)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 10:21:54 -07:00
dependabot[bot]
b5ca00d06b chore(deps-dev): bump @types/node from 25.9.3 to 25.9.2 in /superset-frontend/packages/superset-ui-core (#41035)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-15 10:21:11 -07:00
dependabot[bot]
5719f8e349 chore(deps-dev): bump @types/node from 25.9.1 to 25.9.2 in /superset-frontend (#41037)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 10:20:27 -07:00
dependabot[bot]
9d72a39e10 chore(deps): bump caniuse-lite from 1.0.30001793 to 1.0.30001797 in /docs (#41038)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 10:20:10 -07:00
dependabot[bot]
66733a5d72 chore(deps): bump codecov/codecov-action from 6.0.1 to 7.0.0 (#41039)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 10:19:52 -07:00
dependabot[bot]
a435002293 chore(deps-dev): bump @types/jquery from 4.0.0 to 4.0.1 in /superset-frontend (#41040)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 10:19:29 -07:00
dependabot[bot]
2d8447af42 chore(deps-dev): bump baseline-browser-mapping from 2.10.33 to 2.10.34 in /superset-frontend (#41041)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 10:19:12 -07:00
Minh Vu
bf5daf0a1e fix(mcp): redirect stdio click echo without recursion (#40814) 2026-06-15 10:10:40 -07:00
Joe Li
b656b1d477 fix(reports): escape LIKE wildcards in text filter and preserve typed screenshot width (#40980)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:59:40 -07:00
Joe Li
5a97e01d6e test(dashboard): migrate standalone mode Cypress spec to RTL (#40914)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-15 09:57:24 -07:00
Joe Li
38cc70de2f test(chart): URL params land on each query in chart-data request (#40913)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-15 09:57:04 -07:00
Richard Fogaca Nienkotter
a1bc3c67ed fix(embedded): skip CSRF token fetch for guest streaming chart exports (#41004) 2026-06-15 13:49:25 -03:00
Evan Rusackas
e5b6642b18 test(pivot-table): embedded E2E that collapse state survives scrolling (#33406) (#40820)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-15 08:53:14 -07:00
Evan Rusackas
dd3a61156b fix(handlebars): register a dayjs-backed formatDate helper (#32960) (#40817)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-15 08:40:08 -07:00
Amin Ghadersohi
820e3d18d3 fix(mcp): make search_tools query optional to fix null content on tool discovery (#40906) 2026-06-15 11:32:40 -04:00
Mehmet Salih Yavuz
2dd8fe362f fix(DynamicEditableTitle): preserve in-flight edits when title prop changes (#39861)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-15 18:14:18 +03:00
Evan Rusackas
9d2f625e55 feat(encrypt): selectable encryption engine + AES-GCM re-encryption migrator (#40654)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-15 08:10:08 -07:00
Đỗ Trọng Hải
d66895c566 build(dev-deps): update webpack-related packages to next major version (#41022) 2026-06-15 21:57:55 +07:00
Pat Buxton
2734bde504 fix(chart): Allow Admin non-owner to save chart (#37175) 2026-06-14 21:28:36 -07:00
Nitish Agarwal
f88f1fad04 fix(table): sort metric columns numerically regardless of display format (#39775) 2026-06-14 21:14:44 -07:00
Kim Jeong Ju
9ec9c8658b fix(query_context_processor): apply CSV encoding to follow the encoding settings of 'CSV_EXPORT' (#34806)
Co-authored-by: Jeong Ju, Kim <haje01@webzen.com>
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 08:55:25 -07:00
Evan Rusackas
9f81565625 feat(uploads): add optional max file size for data uploads (#40860) 2026-06-14 19:46:48 +07:00
Nitish Agarwal
c934498696 fix(dashboard): do not add importer as owner when overwriting existing dashboard (#38615)
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 05:10:03 -07:00
jesperct
99bdacd2e1 fix(explore): theme SQL autocomplete completion highlight consistently (#41005) 2026-06-14 12:40:48 +07:00
Evan Rusackas
48feb432de test(pie): guard NULL + named group values all render as slices (#33174) (#41018)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-14 11:46:56 +07:00
Evan Rusackas
669ddb2913 test(gauge): gauge should paint configured interval colors on a dashboard (#28766) (#40821)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-13 21:41:13 -07:00
dependabot[bot]
e7ecf5479a chore(deps): bump flask-wtf from 1.2.2 to 1.3.0 (#41015)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-13 16:54:02 -07:00
Vanessa Giannoni
c0fbe243e8 fix(echarts): cap tooltip at 80vh with internal scroll (#37022)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-13 16:53:28 -07:00
Cezar
df0668eda5 feat(api): return uuid in POST response for dataset, chart, and dashboard (#37806)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 16:53:08 -07:00
SBIN2010
6b682b1aa1 fix(pivot_table): next try refreshed pr for fixing sorting in pivot table (#38080)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-06-13 16:34:18 -07:00
Florian DANIEL aka Facyla
13145e8ad8 fix: Tooltip not displayed on bottom edge of the map (#37105)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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@preset.io>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 16:31:36 -07:00
Nitish Agarwal
02f32469b0 fix(pandas-postprocessing): resolve SettingWithCopyWarning in histogram and boxplot (#36605)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-06-13 16:31:32 -07:00
Evan Rusackas
d120b1c250 feat(security): enforce password complexity policy (min length + common-password blocklist) (#40670)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-13 16:31:23 -07:00
Hu Yuxuan
9d167dfada fix: type error bug in convert_tbl_column_to_sqla_col (#31780)
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: pre-commit <pre-commit@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-06-13 16:04:08 -07:00
Yuvraj Singh Chauhan
3c6239363b fix(db): preserve percent encoding in database passwords (#36783)
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 13:52:00 -07:00
Shaitan
5fb13f102a fix(network): validate target hostname in outbound requests (#39301)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Arpit Jain <3242828+arpitjain099@users.noreply.github.com>
Co-authored-by: Mafi <matt.fitzgerald@gmail.com>
Co-authored-by: Matt Fitzgerald <matt.fitzgerald@preset.io>
Co-authored-by: Richard Fogaca Nienkotter <63572350+richardfogaca@users.noreply.github.com>
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: sadpandajoe <jcli38@gmail.com>
Co-authored-by: JUST.in DO IT <justin.park@airbnb.com>
Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
Co-authored-by: sha174n <pedro.sousa@preset.io>
2026-06-13 20:26:58 +01:00
Shaitan
aa3d2b9e81 fix(dashboard): validate native-filter data requests against filter targets (#40979)
Co-authored-by: sha174n <pedro.sousa@preset.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 19:02:28 +01:00
Shaitan
e5986b32ff fix(charts): evaluate access before rendering query filters; isolate macro env (#40982)
Co-authored-by: sha174n <pedro.sousa@preset.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 19:01:54 +01:00
Shaitan
a41869ec7d fix(dashboard): build example-export sample data through the dataset query path (#40976)
Co-authored-by: sha174n <pedro.sousa@preset.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 19:01:35 +01:00
Cole Murray
6487cf03c5 fix(security): Add input validation to cancel_query_id to prevent injection (#36722)
Signed-off-by: ColeMurray <cole@waclaude.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-13 10:56:14 -07:00
dependabot[bot]
ca0596bca2 chore(deps-dev): bump progress from 1.6 to 1.6.1 (#41014)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-13 09:40:56 -07:00
Elizabeth Thompson
e4f82109e4 fix(mcp): suppress AuthlibDeprecationWarning from authlib.jose imports (#40977)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:19:12 -04:00
Shivam S.
8d315dd2b6 fix(UI): remove getPopupContainer prop causing dual scrollbars in dropdown (#36059)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-06-13 09:14:58 -07:00
Evan Rusackas
b05fe4857e fix(chart): require chart access for query_context-only updates (#40648)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-13 04:16:29 -07:00
dependabot[bot]
4a5c0d9042 chore(deps): bump joi from 17.13.3 to 17.13.4 in /docs (#41009)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-13 02:21:41 -07:00
dependabot[bot]
daff4fd87e chore(deps-dev): update pymssql requirement from <3,>=2.2.8 to >=2.3.13,<3 (#41012)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-13 02:21:03 -07:00
dependabot[bot]
a8e26c254f chore(deps-dev): bump tiktoken from 0.12.0 to 0.13.0 (#41013)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-13 02:20:28 -07:00
dependabot[bot]
e7337eacfc chore(deps-dev): bump sqlalchemy-bigquery from 1.15.0 to 1.17.0 (#41016)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-13 02:19:22 -07:00
dependabot[bot]
9e7e1ecdbc chore(deps): bump ioredis from 5.11.0 to 5.11.1 in /superset-websocket (#40989)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-12 11:14:22 -07:00
Evan Rusackas
8ed7ebb5b7 chore(i18n): lint + dev warning for eager t() in controlPanel configs (#40379)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-12 10:29:51 -07:00
Amin Ghadersohi
2f008afca9 fix(mcp): add dataset_id to update_chart to support rebinding chart datasource (#40853)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 10:42:53 -04:00
Evan Rusackas
814b72c6f9 feat(security): force password change on first use (opt-in) (#40669)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-11 22:23:10 -07:00
Evan Rusackas
663b47aa75 feat: support guest-token revocation per embedded dashboard (#40676)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-11 19:37:22 -07:00
Evan Rusackas
9938ee273f feat: terminate active sessions when an account is disabled (#40695)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 19:37:13 -07:00
Geidō
74845eaf0b fix(deps): cap paramiko <4.0 to keep SSH tunneling working (#40973)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Mehmet Salih Yavuz <salih.yavuz@proton.me>
2026-06-11 19:28:18 +03:00
Elizabeth Thompson
b0d7880ac0 fix: use Series.iloc for positional access in generate_join_column (#40936) 2026-06-11 08:34:38 -07:00
Mehmet Salih Yavuz
058be4b904 test(core): cover invalid raster tile URL fallback in mapStyles (#40974) 2026-06-11 18:31:50 +03:00
Abdul Rehman
42d0c4436e fix(permalink): accept null entries in activeTabs for v5-imported dashboards (#40969) 2026-06-11 22:03:27 +07:00
Alexandru Soare
378473a6fe fix(matrixify): Set singular metric field for pie and other single-me… (#40852) 2026-06-11 17:45:38 +03:00
dependabot[bot]
32ae0afcac chore(deps): bump github/codeql-action from 4.36.1 to 4.36.2 (#40966)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-11 21:27:32 +07:00
Đỗ Trọng Hải
db7e1c67d8 feat(build): update Node version to v24 (#40835)
Signed-off-by: hainenber <dotronghai96@gmail.com>
2026-06-11 21:26:23 +07:00
Evan Rusackas
6c5ad1e912 fix(sqllab): apply SQL controls (RLS/DML/disallowed) to cost estimation (#40662)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-11 05:33:42 -07:00
MAGHC
2b18dc0a5c fix(radar): apply number formatter to tooltip (#37033)
Signed-off-by: MAGHC <windowsapple95@gmail.com>
Signed-off-by: K-ESSENCE <windowsapple95@gmail.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-06-11 00:54:48 -07:00
dependabot[bot]
cc2845168d chore(deps-dev): update starrocks requirement from >=1.0.0 to >=1.3.3 (#40939)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-11 00:19:14 -07:00
dependabot[bot]
97073340cc chore(deps-dev): update sqlalchemy-exasol requirement from <8.0,>=2.4.0 to >=7.0.0,<8.0 (#40946)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-11 00:17:25 -07:00
Richard Fogaca Nienkotter
046b1b61b3 fix(maps): preserve OSM styles and configurable renderer defaults (#40804) 2026-06-10 22:26:00 -03:00
Sam Firke
da9756ef14 chore(issue template): bump version numbers to reflect 6.1.0 released (#40479) 2026-06-10 20:37:45 -04:00
Dylan Cavalcante
f79a88c685 test(core): add unit tests for split function (#40819)
Co-authored-by: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com>
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 16:12:35 -07:00
dependabot[bot]
b1d965932d chore(deps-dev): bump @typescript-eslint/eslint-plugin from 8.60.0 to 8.60.1 in /superset-websocket (#40888)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-10 12:46:38 -07:00
dependabot[bot]
7d046340dc chore(deps): bump ag-grid-react from 35.3.0 to 35.3.1 in /superset-frontend/packages/superset-ui-core (#40924)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-10 12:46:24 -07:00
dependabot[bot]
aa872cd0a1 chore(deps): bump dompurify from 3.4.9 to 3.4.8 in /superset-frontend/packages/superset-ui-core (#40938)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-10 12:45:33 -07:00
dependabot[bot]
b2c5a1ecb3 chore(deps): bump jsonpath-ng from 1.7.0 to 1.8.0 (#40940)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-10 12:45:21 -07:00
dependabot[bot]
6cd9bdee0b chore(deps-dev): bump @formatjs/intl-durationformat from 0.10.3 to 0.10.13 in /superset-frontend (#40925)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-10 12:44:40 -07:00
dependabot[bot]
a8a1d9c17d chore(deps): bump morgan from 1.10.1 to 1.11.0 in /superset-websocket/utils/client-ws-app (#40921)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-10 12:43:33 -07:00
dependabot[bot]
97058d2cf0 chore(deps): bump fuse.js from 7.3.0 to 7.4.1 in /superset-frontend (#40922)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-10 12:43:19 -07:00
dependabot[bot]
ef57409209 chore(deps): bump ag-grid-community from 35.3.0 to 35.3.1 in /superset-frontend/packages/superset-ui-core (#40923)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-10 12:43:06 -07:00
dependabot[bot]
5f06e66cf1 chore(deps): bump @deck.gl/mapbox from 9.3.2 to 9.3.3 in /superset-frontend (#40927)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-10 12:42:22 -07:00
dependabot[bot]
11af932099 chore(deps): bump dompurify from 3.4.7 to 3.4.8 in /superset-frontend/plugins/legacy-preset-chart-nvd3 (#40937)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-10 12:42:06 -07:00
dependabot[bot]
c9c05d8d0a chore(deps-dev): update thrift requirement from <1.0.0,>=0.14.1 to >=0.23.0,<1.0.0 (#40942)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-10 12:36:51 -07:00
dependabot[bot]
0f59705806 chore(deps): bump wtforms from 3.2.1 to 3.2.2 (#40943)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-10 12:36:26 -07:00
dependabot[bot]
320965612d chore(deps-dev): update clickhouse-connect requirement from <2.0,>=0.13.0 to >=1.1.1,<2.0 (#40944)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-10 12:36:03 -07:00
dependabot[bot]
c3df60c12b chore(deps): bump selenium from 4.32.0 to 4.44.0 (#40945)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-10 12:34:01 -07:00
dependabot[bot]
4f69949c10 chore(deps-dev): bump eslint-plugin-storybook from 10.4.1 to 10.4.2 in /superset-frontend (#40949)
Signed-off-by: dependabot[bot] <support@github.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>
2026-06-10 12:31:47 -07:00
bogdanmoale
3380496e9f feat(i18n): add Romanian (ro) translations (#36712)
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-10 12:25:42 -07:00
Michael S. Molina
248ccadecd fix(extensions): load extensions async to avoid blocking initial page render (#40915)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 16:23:38 -03:00
Joe Li
cc5a3ddd05 test(dashboard-filter): RTL coverage for horizontal filter bar (#40782)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-10 10:53:56 -07:00
480 changed files with 91050 additions and 14430 deletions

View File

@@ -41,8 +41,8 @@ body:
label: Superset version
options:
- master / latest-dev
- "6.1.0"
- "6.0.0"
- "5.0.0"
validations:
required: true
- type: dropdown

View File

@@ -42,7 +42,7 @@ runs:
fi
echo "python-version=$RESOLVED_VERSION" >> "$GITHUB_OUTPUT"
- name: Set up Python ${{ steps.set-python-version.outputs.python-version }}
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ steps.set-python-version.outputs.python-version }}
cache: ${{ inputs.cache }}

View File

@@ -3,10 +3,6 @@ enable-beta-ecosystems: true
updates:
- package-ecosystem: "github-actions"
directory: "/"
ignore:
# Ignore temporarily as release schedule is too mentally taxing for dep-handling maintainers
# Additionally, very few PRs are reviewed by this action.
- dependency-name: anthropics/claude-code-action
schedule:
interval: "daily"
cooldown:

View File

@@ -1,88 +0,0 @@
name: Claude PR Assistant
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
permissions:
contents: read
jobs:
check-permissions:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude'))
runs-on: ubuntu-latest
outputs:
allowed: ${{ steps.check.outputs.allowed }}
steps:
- name: Check if user is allowed
id: check
env:
COMMENTER: ${{ github.event.comment.user.login }}
run: |
# List of allowed users
ALLOWED_USERS="mistercrunch,rusackas"
echo "Checking permissions for user: $COMMENTER"
# Check if user is in allowed list
if [[ ",$ALLOWED_USERS," == *",$COMMENTER,"* ]]; then
echo "allowed=true" >> $GITHUB_OUTPUT
echo "✅ User $COMMENTER is allowed to use Claude"
else
echo "allowed=false" >> $GITHUB_OUTPUT
echo "❌ User $COMMENTER is not allowed to use Claude"
fi
deny-access:
needs: check-permissions
if: needs.check-permissions.outputs.allowed == 'false'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: Comment access denied
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
COMMENTER_LOGIN: ${{ github.event.comment.user.login || github.event.review.user.login || github.event.issue.user.login }}
with:
script: |
const commenter = process.env.COMMENTER_LOGIN;
const message = `👋 Hi @${commenter}!
Thanks for trying to use Claude Code, but currently only certain team members have access to this feature.
If you believe you should have access, please contact a project maintainer.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: message
});
claude-code-action:
needs: check-permissions
if: needs.check-permissions.outputs.allowed == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
fetch-depth: 1
- name: Run Claude PR Action
uses: anthropics/claude-code-action@5fb899572b81d2bb648d4d187173a2f423a9677c # beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
timeout_minutes: "60"

View File

@@ -47,6 +47,7 @@ jobs:
permissions:
actions: read
contents: read
pull-requests: read
security-events: write
strategy:
@@ -63,7 +64,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -74,6 +75,6 @@ jobs:
# queries: security-extended,security-and-quality
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
with:
category: "/language:${{matrix.language}}"

View File

@@ -75,6 +75,24 @@ jobs:
with:
persist-credentials: false
- name: Free up disk space
shell: bash
run: |
# Reclaim large preinstalled toolchains we don't use. The image
# build, and especially the docker-compose sanity check (which
# rebuilds from scratch whenever the registry cache image
# apache/superset-cache is unavailable), can otherwise exhaust the
# runner's root disk and fail with "no space left on device".
echo "Disk before cleanup:"; df -h /
sudo rm -rf \
/usr/share/dotnet \
/usr/local/lib/android \
/opt/ghc \
/usr/local/.ghcup \
/opt/hostedtoolcache/CodeQL \
/usr/local/share/boost || true
echo "Disk after cleanup:"; df -h /
- name: Setup Docker Environment
uses: ./.github/actions/setup-docker
with:
@@ -101,13 +119,27 @@ jobs:
PUSH_OR_LOAD="--load"
fi
supersetbot docker \
$PUSH_OR_LOAD \
--preset "$BUILD_PRESET" \
--context "$EVENT" \
--context-ref "$RELEASE" $FORCE_LATEST \
--extra-flags "--build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG" \
$PLATFORM_ARG
# Retry to absorb transient Docker Hub registry errors (base-image
# pull timeouts, 504/401 on push, ECONNRESET) that otherwise fail
# the whole job. buildx reuses the buildkit layer cache from the
# failed attempt, so a retry mostly re-does just the failed push.
for attempt in 1 2 3; do
if supersetbot docker \
$PUSH_OR_LOAD \
--preset "$BUILD_PRESET" \
--context "$EVENT" \
--context-ref "$RELEASE" $FORCE_LATEST \
--extra-flags "--build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG" \
$PLATFORM_ARG; then
break
fi
if [ "$attempt" -eq 3 ]; then
echo "::error::supersetbot docker build failed after 3 attempts"
exit 1
fi
echo "::warning::Build attempt ${attempt} failed; retrying in 30s..."
sleep 30
done
# in the context of push (using multi-platform build), we need to pull the image locally
- name: Docker pull
@@ -148,6 +180,21 @@ jobs:
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Free up disk space
shell: bash
run: |
# The sanity check rebuilds the image from scratch whenever the
# registry cache image apache/superset-cache is unavailable, which
# can exhaust the runner's root disk ("no space left on device").
echo "Disk before cleanup:"; df -h /
sudo rm -rf \
/usr/share/dotnet \
/usr/local/lib/android \
/opt/ghc \
/usr/local/.ghcup \
/opt/hostedtoolcache/CodeQL \
/usr/local/share/boost || true
echo "Disk after cleanup:"; df -h /
- name: Setup Docker Environment
uses: ./.github/actions/setup-docker
with:

View File

@@ -10,25 +10,15 @@ permissions:
contents: read
jobs:
config:
runs-on: ubuntu-24.04
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
steps:
- name: "Check for secrets"
id: check
shell: bash
run: |
if [ -n "${NPM_TOKEN}" ]; then
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
fi
env:
NPM_TOKEN: ${{ (secrets.NPM_TOKEN != '') || '' }}
build:
needs: config
if: needs.config.outputs.has-secrets
# Publishing uses npm trusted publishing (OIDC), so there is no NPM_TOKEN to
# gate on. Restrict to the canonical repo: forks cannot mint a valid OIDC
# token for this package and must not publish.
if: github.repository == 'apache/superset'
runs-on: ubuntu-24.04
permissions:
contents: read
id-token: write # required for npm trusted publishing (OIDC)
defaults:
run:
working-directory: superset-embedded-sdk
@@ -36,11 +26,23 @@ jobs:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
# Note: registry-url is intentionally omitted. When set, actions/setup-node
# writes an .npmrc with `_authToken=${NODE_AUTH_TOKEN}` and a placeholder
# 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
with:
node-version-file: "./superset-embedded-sdk/.nvmrc"
registry-url: "https://registry.npmjs.org"
# Diagnostic: confirm GitHub OIDC is granted to this job. npm trusted
# publishing needs the id-token request env vars; "no" here means OIDC was
# not issued and publishing cannot authenticate.
- name: Check OIDC availability
run: |
if [ -n "${ACTIONS_ID_TOKEN_REQUEST_URL}" ]; then
echo "GitHub OIDC available to job: yes"
else
echo "GitHub OIDC available to job: no"
fi
- run: npm ci
- run: npm run ci:release
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -37,7 +37,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: "temurin"
java-version: "11"

View File

@@ -23,7 +23,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: "temurin"
java-version: "11"

View File

@@ -48,7 +48,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "20"
node-version-file: "superset-frontend/.nvmrc"
cache: "npm"
cache-dependency-path: "superset-frontend/package-lock.json"

View File

@@ -71,7 +71,7 @@ jobs:
node-version-file: "./docs/.nvmrc"
- name: Setup Python
uses: ./.github/actions/setup-backend/
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: "zulu"
java-version: "21"

View File

@@ -56,7 +56,7 @@ jobs:
- name: Upload coverage reports to Codecov
if: steps.check.outputs.superset-extensions-cli
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
file: ./coverage.xml
flags: superset-extensions-cli

View File

@@ -134,7 +134,7 @@ jobs:
run: npx nyc merge coverage/ merged-output/coverage-summary.json
- name: Upload Code Coverage
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
flags: javascript
use_oidc: true

View File

@@ -1,6 +1,11 @@
# Python integration tests
name: Python-Integration
# Least-privilege default for GITHUB_TOKEN. Jobs that need more (e.g. OIDC for
# codecov uploads) opt in via their own job-level `permissions:` block.
permissions:
contents: read
on:
push:
branches:
@@ -85,7 +90,7 @@ jobs:
run: |
./scripts/python_tests.sh
- name: Upload code coverage
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
flags: python,mysql
verbose: true
@@ -173,7 +178,7 @@ jobs:
run: |
./scripts/python_tests.sh
- name: Upload code coverage
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
flags: python,postgres
verbose: true
@@ -222,7 +227,7 @@ jobs:
run: |
./scripts/python_tests.sh
- name: Upload code coverage
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
flags: python,sqlite
verbose: true

View File

@@ -90,7 +90,7 @@ jobs:
run: |
./scripts/python_tests.sh -m 'chart_data_flow or sql_json_flow'
- name: Upload code coverage
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
flags: python,presto
verbose: true
@@ -152,7 +152,7 @@ jobs:
pip install -e .[hive]
./scripts/python_tests.sh -m 'chart_data_flow or sql_json_flow'
- name: Upload code coverage
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
flags: python,hive
verbose: true

View File

@@ -1,6 +1,11 @@
# Python unit tests
name: Python-Unit
# Least-privilege default for GITHUB_TOKEN. Jobs that need more (e.g. OIDC for
# codecov uploads) opt in via their own job-level `permissions:` block.
permissions:
contents: read
on:
push:
branches:
@@ -72,7 +77,7 @@ jobs:
pytest --durations-min=0.5 --cov=superset/sql/ ./tests/unit_tests/sql/ --cache-clear --cov-fail-under=100
pytest --durations-min=0.5 --cov=superset/semantic_layers/ ./tests/unit_tests/semantic_layers/ --cache-clear --cov-fail-under=100
- name: Upload code coverage
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
flags: python,unit
verbose: true

View File

@@ -29,7 +29,7 @@ ARG BUILD_TRANSLATIONS="false"
######################################################################
# superset-node-ci used as a base for building frontend assets and CI
######################################################################
FROM --platform=${BUILDPLATFORM} node:22-trixie-slim AS superset-node-ci
FROM --platform=${BUILDPLATFORM} node:24-trixie-slim AS superset-node-ci
ARG BUILD_TRANSLATIONS
ENV BUILD_TRANSLATIONS=${BUILD_TRANSLATIONS}
ARG DEV_MODE="false" # Skip frontend build in dev mode

View File

@@ -396,6 +396,10 @@ categories:
url: https://www.techaudit.info
contributors: ["@ETselikov"]
- name: Tech Solution
url: https://www.tech-solution.com.ar/
contributors: ["@danteGiuliano", "@LeandroVallejos", "@McJaben", "@xJeree", "@zeo-return-null"]
- name: Tenable
url: https://www.tenable.com
contributors: ["@dflionis"]

View File

@@ -24,6 +24,67 @@ assists people when migrating to a new version.
## Next
### Pivot table First/Last aggregations follow data order
The pivot table chart's `First` and `Last` aggregations now return the first and last value in data (query result) order, instead of effectively returning the minimum and maximum. Existing pivot tables that use these aggregations for totals/subtotals may show different values after upgrading. For deterministic results, ensure the underlying query has a stable sort order.
### `thumbnail_url` removed from dashboard list API response
The `thumbnail_url` field has been removed from `GET /api/v1/dashboard/` list responses. External consumers relying on this field must now construct the thumbnail URL client-side using `id` and `changed_on_utc`:
```
/api/v1/dashboard/{id}/thumbnail/{changed_on_utc}/
```
The thumbnail endpoint redirects to the current digest URL regardless of whether the supplied digest is exact. If the image is not yet cached, that digest URL may return `202` and trigger async generation. Using `changed_on_utc` as the digest is sufficient for cache-busting purposes.
### Webhook alerts/reports block private/internal hosts by default
Webhook alert/report dispatch (`WebhookNotification.send`) now validates the target URL's host against the same private/internal-IP block applied to dataset import URLs. If the resolved host is in a loopback, link-local, private (RFC-1918), shared-CGNAT, or multicast range, the webhook is rejected with `NotificationParamException`.
Deployments that intentionally point webhooks at internal targets (chatops bridges, internal automation servers, on-premises Mattermost/Rocket.Chat, etc.) can opt out by setting `ALERT_REPORTS_WEBHOOK_ALLOW_INTERNAL_HOSTS = True` in `superset_config.py`. This mirrors the existing `DATASET_IMPORT_ALLOW_INTERNAL_DATA_URLS` opt-out for dataset imports.
### Impala cancel_query blocks private/internal hosts by default
The Impala engine spec's `cancel_query` issues an HTTP request from the Superset backend to the host configured on the Impala database connection. That host is now validated before the request: if it resolves to a private/internal IP range, the cancel call is refused and a warning is logged. Operators whose Impala cluster runs on an internal network can opt out by setting `IMPALA_CANCEL_QUERY_ALLOW_INTERNAL_HOSTS = True` in `superset_config.py`. This mirrors the dataset-import and webhook opt-out flags.
### Map chart renderer and OpenStreetMap migration behavior
The MapLibre migration for deck.gl charts preserves saved non-Mapbox styles on
the MapLibre-compatible path. Saved styles such as OpenStreetMap, `tile://`
tile templates, generic HTTPS style URLs, and charts without a saved style are
not reclassified as Mapbox during migration and do not require
`MAPBOX_API_KEY` only because of the migration.
Saved true Mapbox styles whose value starts with `mapbox://` remain
Mapbox-backed. If a Superset deployment does not configure `MAPBOX_API_KEY`,
those saved Mapbox charts keep the existing missing-key message instead of
silently falling back to MapLibre or another provider. In Explore, deck.gl and
point-cluster renderer controls preserve saved Mapbox state, but the Mapbox
choice is not available as a new working renderer without a configured key.
The MapLibre style choices include `Streets (OSM)`, backed by
`https://tile.openstreetmap.org/{z}/{x}/{y}.png`. This OpenStreetMap tile
service requires visible `© OpenStreetMap contributors` attribution and should
be used through normal browser map tile requests and caching; it is not intended
for bulk prefetch or offline tile downloads.
### Password complexity policy enabled by default
Superset now ships a default password-complexity policy, enforced (via Flask-AppBuilder) across self-registration, the user create/edit/reset forms, and the User REST API. The policy requires a minimum password length of 8 characters and rejects a built-in blocklist of common/guessable passwords.
This is enabled by default (`FAB_PASSWORD_COMPLEXITY_ENABLED = True`), so new or reset passwords that are too short or appear in the blocklist will be rejected where they were previously accepted. Existing stored passwords are unaffected until they are next changed.
Operators can tune or disable the policy via config:
- `AUTH_PASSWORD_MIN_LENGTH` — minimum length (default `8`).
- `AUTH_PASSWORD_COMMON_BLOCKLIST` — extra passwords to reject, in addition to the built-in list.
- `FAB_PASSWORD_COMPLEXITY_VALIDATOR` — replace with your own callable for custom rules.
- `FAB_PASSWORD_COMPLEXITY_ENABLED = False` — disable enforcement entirely.
### Data uploads bounded by UPLOAD_MAX_FILE_SIZE_BYTES
Single data-file uploads (CSV, Excel, columnar) are now bounded by the `UPLOAD_MAX_FILE_SIZE_BYTES` config option, which defaults to `100 * 1024 * 1024` (100 MB). Files larger than this are rejected with a `413` before their contents are buffered into memory. Set `UPLOAD_MAX_FILE_SIZE_BYTES = None` to disable the check and restore unbounded uploads.
### Duration formatter precision
The `DURATION` number formatter now uses `Intl.DurationFormat` for locale-aware output. By default, sub-second fields are omitted, so values that previously displayed fractional seconds with `pretty-ms`, such as `10500` milliseconds rendering as `10.5s`, now render as `10s`.
@@ -70,6 +131,24 @@ superset revoke-guest-tokens
This change is backward compatible. The feature is off by default, and even when enabled nothing is revoked until an admin explicitly bumps the version: the expected version starts at `0`, and tokens minted before this change (which carry no version claim) are treated as version `0`. No database migration is required.
### Sessions are terminated when an account is disabled
Disabling a user account (setting `active` to `False`, via the admin UI, REST API, or CLI) now terminates that user's outstanding sessions on their next request, instead of relying on a passive check. This works for both client-side cookie sessions and server-side session stores via a per-user invalidation epoch (`user_attribute.sessions_invalidated_at`, added by a migration). The mechanism is inert for users that were never disabled (NULL epoch), so there is no behavior change for active users. Re-enabling an account and logging in again starts a fresh, valid session. The migration backfills the epoch for accounts that are already disabled at upgrade time, so re-enabling such an account does not revive a session that predates this feature.
### Opt-in SSH tunnel server host key verification
SSH tunnels can now optionally pin the expected SSH server host key as a defense-in-depth measure against man-in-the-middle attacks. paramiko's transport performs no known-hosts checking by default, so previously the SSH server's identity was not verified. This feature is opt-in and off by default; existing tunnels are unaffected.
- A new nullable `server_host_key` column on the `ssh_tunnels` table stores the expected host key in authorized-key form (e.g. `ssh-ed25519 AAAA...`). It is a public key and is stored in plaintext. It can be set via the SSH tunnel POST/PUT payloads (`ssh_tunnel.server_host_key`).
- When a tunnel has `server_host_key` set, Superset connects to the SSH server, reads the host key it presents, and rejects the tunnel if it does not match.
- A new config flag `SSH_TUNNEL_STRICT_HOST_KEY_CHECKING` (default `False`) controls fail-closed behavior. When `True`, every tunnel must declare a `server_host_key`; a tunnel without one is rejected.
Runbook to adopt:
1. Capture the SSH server's host key, e.g. `ssh-keyscan -t ed25519 ssh.example.com` (verify it out-of-band).
2. Set that value on the tunnel's `server_host_key` (via the database/SSH tunnel API or UI payload).
3. Optionally set `SSH_TUNNEL_STRICT_HOST_KEY_CHECKING = True` in `superset_config.py` to require host-key verification on all tunnels.
### Dataset import validates catalog against the target connection
Importing a dataset now validates the `catalog` field against the target database connection. When the connection has multi-catalog disabled (`allow_multi_catalog` off) and the dataset's catalog is not the connection's default catalog, the import fails instead of silently persisting the non-default catalog. This matches the validation already enforced on the dataset update path and prevents imported datasets from querying an unintended database.
@@ -89,6 +168,36 @@ Both default to empty (no behavior change). They apply to both the `LOCAL_EXTENS
The Dynamic Group By chart customization now orders its display values according to the "Sort display control values" toggle: ascending (AZ), descending (ZA), or the dataset's source order when the toggle is unset. Previously the dropdown always sorted alphabetically. Existing dashboards where the toggle was never set will show options in source order instead of AZ; open the customization and enable the toggle to restore alphabetical ordering.
### Selectable encryption engine for app-encrypted fields (AES-GCM)
App-encrypted fields (database passwords, SSH tunnel credentials, OAuth tokens, etc.) can now use authenticated **AES-GCM** encryption instead of the historical unauthenticated **AES-CBC**. A new config selects the engine for the default adapter:
```python
# "aes" (AES-CBC, historical default) | "aes-gcm" (authenticated, recommended for new installs)
SQLALCHEMY_ENCRYPTED_FIELD_ENGINE = "aes"
```
**No action required / no behavior change:** the default remains `"aes"`, so existing installs are unaffected.
**Opting in on an existing install:** flipping the engine on a populated database without re-encrypting first will make stored secrets undecryptable, because the two ciphertext formats are not compatible. A migrator is provided. Recommended runbook:
1. Take a metadata-DB backup.
2. Re-encrypt existing secrets into the new engine (the `SECRET_KEY` is unchanged):
```bash
superset re-encrypt-secrets --engine aes-gcm
```
3. Set `SQLALCHEMY_ENCRYPTED_FIELD_ENGINE = "aes-gcm"` in your config.
4. Restart Superset.
5. Re-run the migrator once more after the restart:
```bash
superset re-encrypt-secrets --engine aes-gcm
```
A live instance keeps writing *new* secrets as AES-CBC during the window between step 2 and the restart in step 4; this second pass sweeps those up (it is idempotent, so already-migrated values are skipped).
Schedule the cutover in a quiet window. Runtime reads use only the single configured engine, so in a multi-worker deployment there is an unavoidable brief decrypt-outage between the migration commit and the last worker restarting with the new config — each migrator run is transactional, but the fleet-wide cutover is not zero-downtime.
The migration is transactional (all-or-nothing) and idempotent — it can be safely re-run or resumed. Note that AES-GCM, unlike AES-CBC, does not support querying directly over encrypted columns; audit any code that filters on an encrypted column before switching. See the SIP at `docs/sip/authenticated-encryption-at-rest.md` for details.
### Granular Export Controls
A new feature flag `GRANULAR_EXPORT_CONTROLS` introduces three fine-grained permissions that replace the legacy `can_csv` permission:

View File

@@ -71,12 +71,12 @@ case "${1}" in
worker)
echo "Starting Celery worker..."
# setting up only 2 workers by default to contain memory usage in dev environments
celery --app=superset.tasks.celery_app:app worker -O fair -l INFO --concurrency=${CELERYD_CONCURRENCY:-2}
celery --app=superset.tasks.celery_app:app worker -O fair -l INFO --concurrency=${CELERYD_CONCURRENCY:-2} ${WORKER_LOG_FILE:+--logfile=$WORKER_LOG_FILE}
;;
beat)
echo "Starting Celery beat..."
rm -f /tmp/celerybeat.pid
celery --app=superset.tasks.celery_app:app beat --pidfile /tmp/celerybeat.pid -l INFO -s "${SUPERSET_HOME}"/celerybeat-schedule
celery --app=superset.tasks.celery_app:app beat --pidfile /tmp/celerybeat.pid -l INFO -s "${SUPERSET_HOME}"/celerybeat-schedule ${BEAT_LOG_FILE:+--logfile=$BEAT_LOG_FILE}
;;
app)
echo "Starting web app (using development server)..."

View File

@@ -1 +1 @@
v22.22.0
v24.16.0

View File

@@ -22,31 +22,24 @@ level dependencies.
**Debian and Ubuntu**
Ubuntu **24.04** uses python 3.12 per default, which currently is not supported by Superset. You need to add a second python installation of 3.11 and install the required additional dependencies.
```bash
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install python3.11 python3.11-dev python3.11-venv build-essential libssl-dev libffi-dev libsasl2-dev libldap2-dev default-libmysqlclient-dev
```
In Ubuntu **20.04 and 22.04** the following command will ensure that the required dependencies are installed:
The following command will ensure that the required dependencies are installed (tested on Ubuntu 20.04, 22.04, and 24.04):
```bash
sudo apt-get install build-essential libssl-dev libffi-dev python3-dev python3-pip libsasl2-dev libldap2-dev default-libmysqlclient-dev
sudo apt-get install build-essential libssl-dev libffi-dev python3-dev python3-pip python3-venv libsasl2-dev libldap2-dev libpq-dev default-libmysqlclient-dev pkg-config
```
In Ubuntu **before 20.04** the following command will ensure that the required dependencies are installed:
```bash
sudo apt-get install build-essential libssl-dev libffi-dev python-dev python-pip libsasl2-dev libldap2-dev default-libmysqlclient-dev
```
Refer to the
[pyproject.toml](https://github.com/apache/superset/blob/master/pyproject.toml) file for the list of
Python versions officially supported by Superset, and install a matching `python3` interpreter for
your distribution. The `libpq-dev` package is only needed if you intend to connect to (or use) a
PostgreSQL database; you can omit it otherwise.
**Fedora and RHEL-derivative Linux distributions**
Install the following packages using the `yum` package manager:
```bash
sudo yum install gcc gcc-c++ libffi-devel python-devel python-pip python-wheel openssl-devel cyrus-sasl-devel openldap-devel
sudo yum install gcc gcc-c++ libffi-devel python3-devel python3-pip python3-wheel openssl-devel cyrus-sasl-devel openldap-devel
```
In more recent versions of CentOS and Fedora, you may need to install a slightly different set of packages using `dnf`:

View File

@@ -70,10 +70,10 @@
"@storybook/preview-api": "^8.6.18",
"@storybook/theming": "^8.6.15",
"@superset-ui/core": "^0.20.4",
"@swc/core": "^1.15.40",
"@swc/core": "^1.15.41",
"antd": "^6.4.3",
"baseline-browser-mapping": "^2.10.33",
"caniuse-lite": "^1.0.30001793",
"baseline-browser-mapping": "^2.10.35",
"caniuse-lite": "^1.0.30001797",
"docusaurus-plugin-openapi-docs": "^5.0.2",
"docusaurus-theme-openapi-docs": "^5.0.2",
"js-yaml": "^4.2.0",
@@ -101,15 +101,15 @@
"@types/js-yaml": "^4.0.9",
"@types/react": "^19.1.8",
"@typescript-eslint/eslint-plugin": "^8.59.3",
"@typescript-eslint/parser": "^8.60.1",
"@typescript-eslint/parser": "^8.61.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react": "^7.37.5",
"globals": "^17.6.0",
"prettier": "^3.8.3",
"prettier": "^3.8.4",
"typescript": "~6.0.3",
"typescript-eslint": "^8.60.1",
"typescript-eslint": "^8.61.0",
"webpack": "^5.107.2"
},
"browserslist": {

View File

@@ -0,0 +1,136 @@
<!--
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.
-->
# SIP: Authenticated encryption (AES-GCM) for app-encrypted fields
## [DRAFT — proposal for discussion]
This document is a draft proposal accompanying the code in this PR. It is
intended to seed the formal SIP discussion. The code here ships the
backward-compatible engine selection **and** the re-encryption migrator
(Phases 12 below); both are opt-in and change nothing for existing installs by
default. Flipping the default for fresh installs (Phase 3) remains future work.
## Motivation
Superset app-encrypts a number of sensitive fields before persisting them to
the metadata database, including:
- database connection passwords and `encrypted_extra` (`superset/models/core.py`),
- SSH tunnel credentials — password, private key, private-key password
(`superset/databases/ssh_tunnel/models.py`),
- OAuth2 tokens and other secrets stored via `EncryptedType`.
These fields are encrypted with `sqlalchemy_utils.EncryptedType`, which
**defaults to `AesEngine` (AES-CBC)**. AES-CBC provides confidentiality but is
**unauthenticated**: it has no integrity tag. An attacker with write access to
the ciphertext (e.g. direct metadata-DB access, a backup, or a compromised
replica) can perform **bit-flipping / chosen-ciphertext manipulation** to
silently alter the decrypted plaintext of a secret without detection.
`AesGcmEngine` (AES-GCM) is authenticated encryption: tampering causes
decryption to fail loudly rather than yielding attacker-influenced plaintext.
Using authenticated encryption for secrets at rest is an ASVS L1 expectation
(11.3.2 / cryptography best practice).
`config.py` already documents that operators *can* switch to GCM by writing a
custom `AbstractEncryptedFieldAdapter`, but:
1. it is opt-in, undocumented as a security recommendation, and easy to miss;
2. there is **no migration path** — flipping the engine on a populated database
makes every existing secret undecryptable, because GCM ciphertext is not
format-compatible with CBC.
## Proposed change
A three-part change, delivered incrementally so existing deployments are never
broken:
### Phase 1 — engine selection (this PR)
- Add a `SQLALCHEMY_ENCRYPTED_FIELD_ENGINE` config (`"aes"` | `"aes-gcm"`),
**defaulting to `"aes"`** (no behavior change for existing installs).
- Teach the default `SQLAlchemyUtilsAdapter` to honor it (an explicit `engine`
kwarg still wins, so the migrator can pin an engine).
- This lets **new** deployments choose AES-GCM from day one with a one-line
config, instead of writing a custom adapter.
### Phase 2 — CBC→GCM re-encryption migrator (this PR)
The existing `SecretsMigrator` (previously only used for `SECRET_KEY` rotation)
gains an **engine migration** mode that:
1. discovers every `EncryptedType` column (via `discover_encrypted_fields()`),
2. decrypts each value with the **source** engine (AES-CBC) under the current
`SECRET_KEY`,
3. re-encrypts with the **target** engine (AES-GCM),
4. runs transactionally per the existing all-or-nothing semantics, and is
idempotent per column (already-migrated values are skipped), so a run can be
safely repeated or resumed.
Exposed via a new `--engine` option on the existing CLI command:
`superset re-encrypt-secrets --engine aes-gcm`, runnable by operators with a DB
backup in hand. The `SECRET_KEY` is unchanged; an engine change and a key
rotation can also be combined (pass `--previous_secret_key` as well).
### Phase 3 — flip the default for new installs
Once the migrator and docs are in place, change the default to `"aes-gcm"` for
**fresh** installs only (e.g. keyed off an empty metadata DB / documented in
`UPDATING.md`), keeping existing installs on `"aes"` until they run Phase 2.
## New or changed public interfaces
- New config: `SQLALCHEMY_ENCRYPTED_FIELD_ENGINE: Literal["aes", "aes-gcm"]`.
- New (Phase 2) CLI: `superset re-encrypt-secrets --engine <name>`.
- No schema changes; ciphertext format changes per migrated column.
## Migration plan and compatibility
- **Backward compatible by default.** Phase 1 changes nothing unless the
operator opts in.
- Switching an existing deployment to `"aes-gcm"` **without** running the Phase
2 migrator will make existing secrets undecryptable — this is called out in
the config comment and must be in `UPDATING.md`.
- Recommended operator runbook: take a metadata-DB backup → run
`re-encrypt-secrets --engine aes-gcm` → set
`SQLALCHEMY_ENCRYPTED_FIELD_ENGINE = "aes-gcm"` → restart → re-run
`re-encrypt-secrets --engine aes-gcm` once more to sweep up any secrets a live
instance wrote as AES-CBC during the cutover window. The canonical, more
detailed version of this runbook lives in `UPDATING.md`; this is a summary.
- `AesEngine` allows queryability over encrypted fields; AES-GCM does not.
Any code that filters/queries on an encrypted column directly must be audited
before Phase 3 (none is expected, but it must be verified).
## Rejected alternatives
- **Flip the default immediately.** Rejected: bricks every existing
deployment's secrets with no migration path.
- **Document-only (custom adapter).** Status quo; high friction and no
migration tooling — most operators will never do it.
## Open questions
- GCM→CBC rollback (for operators who need queryability) already works via the
same command (`re-encrypt-secrets --engine aes`), since the migrator is
engine-symmetric. Should rollback be documented as a supported path or
discouraged?
- The migrator already supports a concurrent `SECRET_KEY` rotation + engine
change in a single pass (pass `--previous_secret_key` alongside `--engine`).
Is that combination worth calling out in the operator docs, or kept advanced?

View File

@@ -7235,10 +7235,10 @@
"pypi_packages": [
"oracledb"
],
"connection_string": "oracle://{username}:{password}@{hostname}:{port}",
"connection_string": "oracle+oracledb://{username}:{password}@{hostname}:{port}",
"default_port": 1521,
"notes": "Previously used cx_Oracle, now uses oracledb.",
"docs_url": "https://cx-oracle.readthedocs.io/en/latest/user_guide/installation.html",
"docs_url": "https://python-oracledb.readthedocs.io/en/latest/user_guide/installation.html",
"category": "Other Databases"
},
"engine": "oracle",

View File

@@ -265,6 +265,15 @@
js-tokens "^4.0.0"
picocolors "^1.1.1"
"@babel/code-frame@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7"
integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==
dependencies:
"@babel/helper-validator-identifier" "^7.29.7"
js-tokens "^4.0.0"
picocolors "^1.1.1"
"@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.0":
version "7.28.0"
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz"
@@ -275,20 +284,25 @@
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz"
integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==
"@babel/compat-data@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629"
integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
"@babel/core@^7.21.3", "@babel/core@^7.25.9":
version "7.28.6"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz"
integrity sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7"
integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==
dependencies:
"@babel/code-frame" "^7.28.6"
"@babel/generator" "^7.28.6"
"@babel/helper-compilation-targets" "^7.28.6"
"@babel/helper-module-transforms" "^7.28.6"
"@babel/helpers" "^7.28.6"
"@babel/parser" "^7.28.6"
"@babel/template" "^7.28.6"
"@babel/traverse" "^7.28.6"
"@babel/types" "^7.28.6"
"@babel/code-frame" "^7.29.7"
"@babel/generator" "^7.29.7"
"@babel/helper-compilation-targets" "^7.29.7"
"@babel/helper-module-transforms" "^7.29.7"
"@babel/helpers" "^7.29.7"
"@babel/parser" "^7.29.7"
"@babel/template" "^7.29.7"
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@jridgewell/remapping" "^2.3.5"
convert-source-map "^2.0.0"
debug "^4.1.0"
@@ -318,6 +332,17 @@
"@jridgewell/trace-mapping" "^0.3.28"
jsesc "^3.0.2"
"@babel/generator@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3"
integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==
dependencies:
"@babel/parser" "^7.29.7"
"@babel/types" "^7.29.7"
"@jridgewell/gen-mapping" "^0.3.12"
"@jridgewell/trace-mapping" "^0.3.28"
jsesc "^3.0.2"
"@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3":
version "7.27.3"
resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz"
@@ -325,7 +350,7 @@
dependencies:
"@babel/types" "^7.27.3"
"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2", "@babel/helper-compilation-targets@^7.28.6":
"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2":
version "7.28.6"
resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz"
integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==
@@ -336,6 +361,17 @@
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-compilation-targets@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz#7a1def704302401c47f64fa85589e974ae217042"
integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==
dependencies:
"@babel/compat-data" "^7.29.7"
"@babel/helper-validator-option" "^7.29.7"
browserslist "^4.24.0"
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3":
version "7.28.3"
resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz"
@@ -374,6 +410,11 @@
resolved "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz"
integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==
"@babel/helper-globals@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz#f04a96fbd8473241b1079243f5b3f03a3010ab7b"
integrity sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==
"@babel/helper-member-expression-to-functions@^7.27.1":
version "7.27.1"
resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz"
@@ -398,6 +439,14 @@
"@babel/traverse" "^7.28.6"
"@babel/types" "^7.28.6"
"@babel/helper-module-imports@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz#ef25048a518e828d7393fac5882ddd73921d7396"
integrity sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==
dependencies:
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.6":
version "7.28.6"
resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz"
@@ -407,6 +456,15 @@
"@babel/helper-validator-identifier" "^7.28.5"
"@babel/traverse" "^7.28.6"
"@babel/helper-module-transforms@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae"
integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==
dependencies:
"@babel/helper-module-imports" "^7.29.7"
"@babel/helper-validator-identifier" "^7.29.7"
"@babel/traverse" "^7.29.7"
"@babel/helper-optimise-call-expression@^7.27.1":
version "7.27.1"
resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz"
@@ -455,16 +513,31 @@
resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz"
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
"@babel/helper-string-parser@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f"
integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==
"@babel/helper-validator-identifier@^7.28.5":
version "7.28.5"
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz"
integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==
"@babel/helper-validator-identifier@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2"
integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==
"@babel/helper-validator-option@^7.27.1":
version "7.27.1"
resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz"
integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==
"@babel/helper-validator-option@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz#cf315be940213b354eb4abcc0bd01ebe3f73bc2a"
integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==
"@babel/helper-wrap-function@^7.27.1":
version "7.28.3"
resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz"
@@ -474,13 +547,13 @@
"@babel/traverse" "^7.28.3"
"@babel/types" "^7.28.2"
"@babel/helpers@^7.28.6":
version "7.28.6"
resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz"
integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==
"@babel/helpers@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607"
integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==
dependencies:
"@babel/template" "^7.28.6"
"@babel/types" "^7.28.6"
"@babel/template" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/parser@^7.28.6":
version "7.28.6"
@@ -496,6 +569,13 @@
dependencies:
"@babel/types" "^7.29.0"
"@babel/parser@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334"
integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==
dependencies:
"@babel/types" "^7.29.7"
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1":
version "7.27.1"
resolved "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz"
@@ -1172,6 +1252,15 @@
"@babel/parser" "^7.28.6"
"@babel/types" "^7.28.6"
"@babel/template@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700"
integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==
dependencies:
"@babel/code-frame" "^7.29.7"
"@babel/parser" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/traverse@^7.25.9", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.6":
version "7.28.6"
resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz"
@@ -1198,6 +1287,19 @@
"@babel/types" "^7.29.0"
debug "^4.3.1"
"@babel/traverse@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d"
integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==
dependencies:
"@babel/code-frame" "^7.29.7"
"@babel/generator" "^7.29.7"
"@babel/helper-globals" "^7.29.7"
"@babel/parser" "^7.29.7"
"@babel/template" "^7.29.7"
"@babel/types" "^7.29.7"
debug "^4.3.1"
"@babel/types@^7.21.3", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.4.4":
version "7.28.6"
resolved "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz"
@@ -1214,6 +1316,14 @@
"@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.28.5"
"@babel/types@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92"
integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
dependencies:
"@babel/helper-string-parser" "^7.29.7"
"@babel/helper-validator-identifier" "^7.29.7"
"@braintree/sanitize-url@^7.1.1":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz#ca2035b0fefe956a8676ff0c69af73e605fcd81f"
@@ -4033,86 +4143,86 @@
dependencies:
apg-lite "^1.0.4"
"@swc/core-darwin-arm64@1.15.40":
version "1.15.40"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.40.tgz#b05d715b04c4fd47baf59288233da85a683cc0bc"
integrity sha512-PaYyclfmQ++77D8ityYvmmVzHv9aG8ROwt2GfG6/ccloy4Hgf80qtOnzb9VYvPsUT7Ty1uhuDRhv3XYpf62qhQ==
"@swc/core-darwin-arm64@1.15.41":
version "1.15.41"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.41.tgz#4fcbc9cbb9dfc9027d66e2b23b8d1d0315d164bd"
integrity sha512-kREh6J5paQFvP3i7f/4FbqRNOJREutVFVOkder4GVyCBQ39YmER55cW/y1NNjwrchzFqgYswFn0mMDCqbqKzrw==
"@swc/core-darwin-x64@1.15.40":
version "1.15.40"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.40.tgz#3180daef5c1e47b435f8edd084509e0a5c0d883b"
integrity sha512-HbbPzvfLBUXjIB1Ezks+//lNUjmLjfyd63XSwprJgrZaXYdm70kohXPJUWdqKZozolFxbPaO+xtBaiUp6BoueA==
"@swc/core-darwin-x64@1.15.41":
version "1.15.41"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.41.tgz#726c60a893e2f1a07bee28f79b519b8e6489415b"
integrity sha512-N8B56ESFazZAWZyIkecADSPCwlLEinW7QLMEeotCpv4J7VXwfH+OLkmRL8o96UZ+1355fwHxDTS6/wK7yucvkA==
"@swc/core-linux-arm-gnueabihf@1.15.40":
version "1.15.40"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.40.tgz#18fcd3c70e48fdfae07c9f18751b1409ce1e5e84"
integrity sha512-SlRZsCjOCPR2LvFs0Ri/Xrx/5o5TCt8vl4gW6mX1hEZOG0a625RxzRHpHdAQNGykmAN/7IeaFAJG+QnNmxlHcA==
"@swc/core-linux-arm-gnueabihf@1.15.41":
version "1.15.41"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.41.tgz#08930e8015ca2fadc729546d5bd4b758a3999dda"
integrity sha512-6XrId2fyle0mS5xxON8rU84mPd2Cq1kDJRj+4BnQKTd7u+2kSA6Ww+JkOP0iTNqOqt9OXhPOEAjBHAuonWcdCg==
"@swc/core-linux-arm64-gnu@1.15.40":
version "1.15.40"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.40.tgz#26304933922f2a8e3194770e404403fc25a19c89"
integrity sha512-Q8byxJt2fh8CR3EUX6snBpy47AoBVm+In/+Z3rjDHMjC38ZvR9/gtUUNCT0tfrn4EdVsO8/QPi59nxrxvqxvBQ==
"@swc/core-linux-arm64-gnu@1.15.41":
version "1.15.41"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.41.tgz#6c27490a4013647a09ff64cea1d6b1169394602f"
integrity sha512-ynLIarxlkVnqHn1D0fKOVht6mNU5ks6lrH+MY3kkS+XFaGGgDxFZVjWKJlkYTKm3RCvBTfA8Ng5fLufXheMRKQ==
"@swc/core-linux-arm64-musl@1.15.40":
version "1.15.40"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.40.tgz#3402dfba04ba7b8ea81f243e2f8fa2c336b54d03"
integrity sha512-4z0MgHU+7M0pZDqBN1El7mFXDI1SBwinfcUkAyA4v8QrhOIUOZltySt2aStQLZGrdXVXM4Y4ylfiTC04ED+MoQ==
"@swc/core-linux-arm64-musl@1.15.41":
version "1.15.41"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.41.tgz#4cce52fbbbe78b1f99c2a4e3f9ad2629f6eae494"
integrity sha512-dXu/5vd4gh8symyhRF+4G7gOPkjmb4pONhh7sl+6GSiW0LOKZlfu5kXmyFbTz9smOT7jgr002qY9b1nujjXt2A==
"@swc/core-linux-ppc64-gnu@1.15.40":
version "1.15.40"
resolved "https://registry.yarnpkg.com/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.40.tgz#b3df9065cad352328c1eeef08a28fc9fe98785aa"
integrity sha512-fLI4iUgeSZu0eRWUXwe6YzPFx9gHbFiPkl8Rp3mJfP8OpNR3nTQCGPvHdDh9xniW7mVvgMY4ni7A4VzqI1KrpA==
"@swc/core-linux-ppc64-gnu@1.15.41":
version "1.15.41"
resolved "https://registry.yarnpkg.com/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.41.tgz#3d1fadd8d320e7250a6b2a2d9c0b0d4dac162f97"
integrity sha512-XGO6zVPXoPE0gf/XnI4jBbafNT13AYgoh6ns0JCSdOetI/kqVf0vhpz7NuNgAzZrMVCsmieqjPoTwViDgh4mOQ==
"@swc/core-linux-s390x-gnu@1.15.40":
version "1.15.40"
resolved "https://registry.yarnpkg.com/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.40.tgz#58e5b601f641dde81b30626ef66a668701ec918f"
integrity sha512-YqeKMAb7d4nQSGMJQ454IlaCENpzcDqhvBE9+CPfdnYpnUXxd+BSrB6Xk0YjW8UyoEhUj4p6quATCxbsp6J3jg==
"@swc/core-linux-s390x-gnu@1.15.41":
version "1.15.41"
resolved "https://registry.yarnpkg.com/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.41.tgz#6e4c54168d4a8d7852ef797437bd25e6fb5d7a50"
integrity sha512-0WUglRwyZtW+iMi7J3iFdrCxreZZIKf4egTwEQfIYRsqFax69A0OrFj+NIoFSE03xBT/IFRrg+S8K6f9Ky+4hA==
"@swc/core-linux-x64-gnu@1.15.40":
version "1.15.40"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.40.tgz#cf057dce0c148c53f2d30152baaf60ea29e5d59c"
integrity sha512-7HOuS1iGcme/j/TuL1TfmmLGiMQrjv/GmjyZeydl00FKPtpGXEldwqfI56xgd1YzrzoB2svWjxbGGyQ0TEASxg==
"@swc/core-linux-x64-gnu@1.15.41":
version "1.15.41"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.41.tgz#5f947698786e15e2f696e0c6b3afd25138bae86b"
integrity sha512-VxkuQK59c0tHm6uJZCUrS3cyA2JhGGfdU6e41SZz0x/JS+4Sm7C1mIc97In14vkZJopEt7yXA2TouCqZDSygEA==
"@swc/core-linux-x64-musl@1.15.40":
version "1.15.40"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.40.tgz#21fb1a4d0193e9bbcd1469ecd36166d2e96e4006"
integrity sha512-h4kZYHc7dpc9P9u4brRJaS8Pl7tPVHAeiLSzw7T5RfIJgAoSdaCMKzI/2Uay9gFhaw8uyCDl0L5q37r0EpAfIA==
"@swc/core-linux-x64-musl@1.15.41":
version "1.15.41"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.41.tgz#f4a0910cb273e39bcc09d572a08f62a355a93628"
integrity sha512-/0qXIu1ZxggLuovLb22vFfKHq2AA4n6Whw5UwmVCHk4pkw7KWnPIQpMCEqUMPsNkFJig7PPp/TSYFu8ZEb2rtQ==
"@swc/core-win32-arm64-msvc@1.15.40":
version "1.15.40"
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.40.tgz#1dba23b2b0db86b3d6d65da2abd627cc607a1fbc"
integrity sha512-+mQgKZXSj6mV38Zh05QaxSjUDmGP/R2JWlXZTDLSPkDzHU6p3GxN9eeSf5dfyDVU86946fmCvSzyl/ucImx8+A==
"@swc/core-win32-arm64-msvc@1.15.41":
version "1.15.41"
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.41.tgz#a55334b1b7c23a962d4219f332b6422f3c3374e4"
integrity sha512-Y481sMNZM6rECh9VO4+y26N1lWEDAyxnBZskUf37fl90uHE946VHfmiVQWT0uMFOhyJJFovGTRuF4W82dwewUg==
"@swc/core-win32-ia32-msvc@1.15.40":
version "1.15.40"
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.40.tgz#b2da1e33165d469467b1046a2189db468da488eb"
integrity sha512-yvwdPLGd25mcj/mNatjNQ0lZujtQD6psH3v9PNmMb+fSzjbNG8KIDxjFWrcV+fsFVLOkyOmdJsFmX7NAFjVyPw==
"@swc/core-win32-ia32-msvc@1.15.41":
version "1.15.41"
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.41.tgz#e1135f8d6857f6c48e4bfb6105568b37b3f88dc5"
integrity sha512-BAchBD5qeUzy3hiPSLJtaaoSm4blCLyYffOF1bGE4ETcV+OisqjUAwDQMJj++4bTpvMCDzwC+Bj3PmQyBCtscw==
"@swc/core-win32-x64-msvc@1.15.40":
version "1.15.40"
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.40.tgz#3563f7e8ce8708f5fda43eb8e0956ef11e0da320"
integrity sha512-OXtKsLU1bVtInzzDEAY2sYiF/rl4tvAnLLLpuMp3HzAOQZ5A+i69AKDhA1YLQTaMAqO3vzyYNVAYVRMPtSYD4w==
"@swc/core-win32-x64-msvc@1.15.41":
version "1.15.41"
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.41.tgz#52d241e2bf4c6154675c0ad447b29cbdb0ccb547"
integrity sha512-WOkA+fJ/ViVBQDsSV9JC52NACTe5PhlurA6viASDZGb7HR3KS01ZG7RZ+Bg6SVQFIoq3gSbTsskQVe6EbHFAYw==
"@swc/core@^1.15.40", "@swc/core@^1.7.39":
version "1.15.40"
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.40.tgz#941c949aa88c0d8d291f102f519f3c2c77701b90"
integrity sha512-2kwzJikRvgtNAG7MwVZY2vEzZjTxKIq5jXOihuSV/8U+Hej8Va22t65aKnJZs3P+NwojZvR8Mf8kyM7O+V8sQg==
"@swc/core@^1.15.41", "@swc/core@^1.7.39":
version "1.15.41"
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.41.tgz#a212c5040abd1ffd2ad6caf140f0d586ffcfaa6e"
integrity sha512-03nQq/082QRJJiOvp3FGbgxTGyyxMxohPTjhk/W9bD2J0tk4ukITI7goOhOO2WbaHn/lsPmo/zf8+DIXhwpgYQ==
dependencies:
"@swc/counter" "^0.1.3"
"@swc/types" "^0.1.26"
optionalDependencies:
"@swc/core-darwin-arm64" "1.15.40"
"@swc/core-darwin-x64" "1.15.40"
"@swc/core-linux-arm-gnueabihf" "1.15.40"
"@swc/core-linux-arm64-gnu" "1.15.40"
"@swc/core-linux-arm64-musl" "1.15.40"
"@swc/core-linux-ppc64-gnu" "1.15.40"
"@swc/core-linux-s390x-gnu" "1.15.40"
"@swc/core-linux-x64-gnu" "1.15.40"
"@swc/core-linux-x64-musl" "1.15.40"
"@swc/core-win32-arm64-msvc" "1.15.40"
"@swc/core-win32-ia32-msvc" "1.15.40"
"@swc/core-win32-x64-msvc" "1.15.40"
"@swc/core-darwin-arm64" "1.15.41"
"@swc/core-darwin-x64" "1.15.41"
"@swc/core-linux-arm-gnueabihf" "1.15.41"
"@swc/core-linux-arm64-gnu" "1.15.41"
"@swc/core-linux-arm64-musl" "1.15.41"
"@swc/core-linux-ppc64-gnu" "1.15.41"
"@swc/core-linux-s390x-gnu" "1.15.41"
"@swc/core-linux-x64-gnu" "1.15.41"
"@swc/core-linux-x64-musl" "1.15.41"
"@swc/core-win32-arm64-msvc" "1.15.41"
"@swc/core-win32-ia32-msvc" "1.15.41"
"@swc/core-win32-x64-msvc" "1.15.41"
"@swc/counter@^0.1.3":
version "0.1.3"
@@ -4812,110 +4922,110 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@8.60.1", "@typescript-eslint/eslint-plugin@^8.59.3":
version "8.60.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz#c1060bb8fa4be80624d3f3dec8dd9caca373af76"
integrity sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==
"@typescript-eslint/eslint-plugin@8.61.0", "@typescript-eslint/eslint-plugin@^8.59.3":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz#db20271974b94a3a54d3b9544e5f5b3481448400"
integrity sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==
dependencies:
"@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.60.1"
"@typescript-eslint/type-utils" "8.60.1"
"@typescript-eslint/utils" "8.60.1"
"@typescript-eslint/visitor-keys" "8.60.1"
"@typescript-eslint/scope-manager" "8.61.0"
"@typescript-eslint/type-utils" "8.61.0"
"@typescript-eslint/utils" "8.61.0"
"@typescript-eslint/visitor-keys" "8.61.0"
ignore "^7.0.5"
natural-compare "^1.4.0"
ts-api-utils "^2.5.0"
"@typescript-eslint/parser@8.60.1", "@typescript-eslint/parser@^8.60.1":
version "8.60.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.60.1.tgz#a9d7f30850384d34b41f4687dd8944823c09e289"
integrity sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==
"@typescript-eslint/parser@8.61.0", "@typescript-eslint/parser@^8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.61.0.tgz#1afe73c9ccce16b7a26d6b95f9400b0ccc34af87"
integrity sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==
dependencies:
"@typescript-eslint/scope-manager" "8.60.1"
"@typescript-eslint/types" "8.60.1"
"@typescript-eslint/typescript-estree" "8.60.1"
"@typescript-eslint/visitor-keys" "8.60.1"
"@typescript-eslint/scope-manager" "8.61.0"
"@typescript-eslint/types" "8.61.0"
"@typescript-eslint/typescript-estree" "8.61.0"
"@typescript-eslint/visitor-keys" "8.61.0"
debug "^4.4.3"
"@typescript-eslint/project-service@8.60.1":
version "8.60.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.60.1.tgz#eb29712f58d72c222fc727162e92f2ab4670971b"
integrity sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==
"@typescript-eslint/project-service@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.61.0.tgz#417a2feac32e8ebd336d63f068c3b42b736ea1ac"
integrity sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.60.1"
"@typescript-eslint/types" "^8.60.1"
"@typescript-eslint/tsconfig-utils" "^8.61.0"
"@typescript-eslint/types" "^8.61.0"
debug "^4.4.3"
"@typescript-eslint/scope-manager@8.60.1":
version "8.60.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz#2f875962eaad0a0789cc3c36aea9b4ddeb2dd9c8"
integrity sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==
"@typescript-eslint/scope-manager@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.61.0.tgz#93c2520d05653fe65eb9ee98efc74fd0134a7852"
integrity sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==
dependencies:
"@typescript-eslint/types" "8.60.1"
"@typescript-eslint/visitor-keys" "8.60.1"
"@typescript-eslint/types" "8.61.0"
"@typescript-eslint/visitor-keys" "8.61.0"
"@typescript-eslint/tsconfig-utils@8.60.1":
version "8.60.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz#bee8b942a13679a878101c9c74577d732062ed93"
integrity sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==
"@typescript-eslint/tsconfig-utils@^8.60.1":
"@typescript-eslint/tsconfig-utils@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.0.tgz#05d6e3ff20001674ebcd22d03dac29ee448043ba"
integrity sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==
"@typescript-eslint/type-utils@8.60.1":
version "8.60.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz#1ae45f0f2a701354beea4a58c2161e40a5e3c379"
integrity sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==
"@typescript-eslint/tsconfig-utils@^8.61.0":
version "8.61.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz#ca88080e0cf191d49516d7f300b67aa090d2254f"
integrity sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==
"@typescript-eslint/type-utils@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.61.0.tgz#50219b57e6b89cecfb1a15f093b15ec9ee019974"
integrity sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==
dependencies:
"@typescript-eslint/types" "8.60.1"
"@typescript-eslint/typescript-estree" "8.60.1"
"@typescript-eslint/utils" "8.60.1"
"@typescript-eslint/types" "8.61.0"
"@typescript-eslint/typescript-estree" "8.61.0"
"@typescript-eslint/utils" "8.61.0"
debug "^4.4.3"
ts-api-utils "^2.5.0"
"@typescript-eslint/types@8.60.1":
version "8.60.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.60.1.tgz#ccdc482ba9e17f9723a10ce240b5e67dad3046c4"
integrity sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==
"@typescript-eslint/types@^8.60.1":
"@typescript-eslint/types@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.61.0.tgz#0ddb46e012a4288292950bdd253db42f278ce64d"
integrity sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==
"@typescript-eslint/typescript-estree@8.60.1":
version "8.60.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz#016630b119228bf483ddc652703a6a038f3fdd74"
integrity sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==
"@typescript-eslint/types@^8.61.0":
version "8.61.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.61.1.tgz#0c51f518e4e6848371a1c988e859d59eb7522d5a"
integrity sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==
"@typescript-eslint/typescript-estree@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.0.tgz#98ca47260bbf627fc28f018b3a0abf00e3090690"
integrity sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==
dependencies:
"@typescript-eslint/project-service" "8.60.1"
"@typescript-eslint/tsconfig-utils" "8.60.1"
"@typescript-eslint/types" "8.60.1"
"@typescript-eslint/visitor-keys" "8.60.1"
"@typescript-eslint/project-service" "8.61.0"
"@typescript-eslint/tsconfig-utils" "8.61.0"
"@typescript-eslint/types" "8.61.0"
"@typescript-eslint/visitor-keys" "8.61.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.60.1":
version "8.60.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.60.1.tgz#31cf566095602d9fe8ad91837d2eb520b8de762b"
integrity sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==
"@typescript-eslint/utils@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.61.0.tgz#ed3546a052787e84ea6c5064d0919fc5eea8522f"
integrity sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==
dependencies:
"@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.60.1"
"@typescript-eslint/types" "8.60.1"
"@typescript-eslint/typescript-estree" "8.60.1"
"@typescript-eslint/scope-manager" "8.61.0"
"@typescript-eslint/types" "8.61.0"
"@typescript-eslint/typescript-estree" "8.61.0"
"@typescript-eslint/visitor-keys@8.60.1":
version "8.60.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz#165d1d8901137b944efaf18f00ab5ecb57f06995"
integrity sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==
"@typescript-eslint/visitor-keys@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.0.tgz#39b4e1ab8936d23bea973d39fd092f9aa21f275e"
integrity sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==
dependencies:
"@typescript-eslint/types" "8.60.1"
"@typescript-eslint/types" "8.61.0"
eslint-visitor-keys "^5.0.0"
"@ungap/structured-clone@^1.0.0":
@@ -5578,10 +5688,10 @@ base64-js@^1.3.1, base64-js@^1.5.1:
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
baseline-browser-mapping@^2.10.33, baseline-browser-mapping@^2.9.0, baseline-browser-mapping@^2.9.19:
version "2.10.33"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz#27c299b096404978831958d429f48390424c4f9b"
integrity sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==
baseline-browser-mapping@^2.10.35, baseline-browser-mapping@^2.9.0, baseline-browser-mapping@^2.9.19:
version "2.10.35"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.35.tgz#f0f2232e0de2d2f82cc491bcf830b05ed05937c6"
integrity sha512-honAfLBde0HAFLdNyBEfuuENkF6zR+ozxqxa/2zJKHBe1qzLqyTSeRKpdPEHAP03rlDGyQOPnCSxnVpVqQo9Mg==
batch@0.6.1:
version "0.6.1"
@@ -5824,10 +5934,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001793:
version "1.0.30001793"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz#238887ddf5fcfc8c36d872394d0a78a517312a72"
integrity sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001797:
version "1.0.30001797"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz#1332709e1439f01ff92085dd17001e0a45897ec0"
integrity sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==
ccount@^2.0.0:
version "2.0.1"
@@ -7142,17 +7252,10 @@ domhandler@^5.0.2, domhandler@^5.0.3:
dependencies:
domelementtype "^2.3.0"
dompurify@^3.3.1:
version "3.4.2"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.2.tgz#f0ff81be682c485505097ba8195a058d8f575218"
integrity sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==
optionalDependencies:
"@types/trusted-types" "^2.0.7"
dompurify@^3.4.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.1.tgz#521d04483ac12631b2aedf434a5f5390933b8789"
integrity sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==
dompurify@^3.3.1, dompurify@^3.4.0:
version "3.4.11"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.11.tgz#29c8ba496475f279ef4015784068452fb14a0680"
integrity sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==
optionalDependencies:
"@types/trusted-types" "^2.0.7"
@@ -9300,9 +9403,9 @@ jiti@^1.20.0:
integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==
joi@^17.9.2:
version "17.13.3"
resolved "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz"
integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==
version "17.13.4"
resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.4.tgz#ad6153d97ce558eb3a3b593e0d43eab51df1c474"
integrity sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==
dependencies:
"@hapi/hoek" "^9.3.0"
"@hapi/topo" "^5.1.0"
@@ -9532,12 +9635,12 @@ latest-version@^7.0.0:
package-json "^8.1.0"
launch-editor@^2.6.1:
version "2.11.1"
resolved "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz"
integrity sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==
version "2.14.1"
resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.14.1.tgz#f7e0da3f58aaea03fea01074d840b5f739ed7ddc"
integrity sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==
dependencies:
picocolors "^1.1.1"
shell-quote "^1.8.3"
shell-quote "^1.8.4"
layout-base@^1.0.0:
version "1.0.2"
@@ -12160,10 +12263,10 @@ prettier-linter-helpers@^1.0.1:
dependencies:
fast-diff "^1.1.2"
prettier@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.3.tgz#560f2de55bf01b4c0503bc629d5df99b9a1d09b0"
integrity sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==
prettier@^3.8.4:
version "3.8.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.4.tgz#f334f013ac04a96676f24dabc23c1c4ae1bae411"
integrity sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==
pretty-error@^4.0.0:
version "4.0.0"
@@ -13489,7 +13592,7 @@ shebang-regex@^3.0.0:
resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shell-quote@^1.8.3:
shell-quote@^1.8.4:
version "1.8.4"
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.4.tgz#2edd9a4dcefc96649e2e2cb12f637b1f1d92a190"
integrity sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==
@@ -14389,15 +14492,15 @@ types-ramda@^0.30.1:
dependencies:
ts-toolbelt "^9.6.0"
typescript-eslint@^8.60.1:
version "8.60.1"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.60.1.tgz#13db05c6eabb89669deec44545b788a0e9aee640"
integrity sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==
typescript-eslint@^8.61.0:
version "8.61.0"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.61.0.tgz#6927fb94f5f29623e370d33fd9fa61f15d6d996b"
integrity sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==
dependencies:
"@typescript-eslint/eslint-plugin" "8.60.1"
"@typescript-eslint/parser" "8.60.1"
"@typescript-eslint/typescript-estree" "8.60.1"
"@typescript-eslint/utils" "8.60.1"
"@typescript-eslint/eslint-plugin" "8.61.0"
"@typescript-eslint/parser" "8.61.0"
"@typescript-eslint/typescript-estree" "8.61.0"
"@typescript-eslint/utils" "8.61.0"
typescript@~6.0.3:
version "6.0.3"
@@ -14896,9 +14999,9 @@ webpack-dev-middleware@^7.4.2:
schema-utils "^4.0.0"
webpack-dev-server@^5.2.2:
version "5.2.4"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.4.tgz#6e6306ce59848ed322c235e48b326632b1eed6d6"
integrity sha512-GqDPGZN9bRqKBTkp4aWkobDDHMsrXKoGSdOH56smIri8qR0JG8gfL8/v/f/OZR3/OKXjG8uwJbFVhKm/FNU/UA==
version "5.2.5"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.5.tgz#648fceaac6a5736b0935e5c1e55d6aa1d0626119"
integrity sha512-4wZtCquSuv9CKX8oybo+mqxtxZqWz47uM1Ch94lxowBztOhWCbhqvRbfC/mODOwxgV2brY+JGZpHq58/SuVFYg==
dependencies:
"@types/bonjour" "^3.5.13"
"@types/connect-history-api-fallback" "^1.5.4"

View File

@@ -29,7 +29,7 @@ maintainers:
- name: craig-rueda
email: craig@craigrueda.com
url: https://github.com/craig-rueda
version: 0.16.0 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
version: 0.16.2 # 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.16.0](https://img.shields.io/badge/Version-0.16.0-informational?style=flat-square)
![Version: 0.16.2](https://img.shields.io/badge/Version-0.16.2-informational?style=flat-square)
Apache Superset is a modern, enterprise-ready business intelligence web application

View File

@@ -126,7 +126,7 @@ spec:
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
{{- if .Values.supersetCeleryBeat.extraContainers }}
{{- toYaml .Values.supersetCeleryBeat.extraContainers | nindent 8 }}
{{- tpl (toYaml .Values.supersetCeleryBeat.extraContainers) . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}

View File

@@ -121,7 +121,7 @@ spec:
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
{{- if .Values.supersetCeleryFlower.extraContainers }}
{{- toYaml .Values.supersetCeleryFlower.extraContainers | nindent 8 }}
{{- tpl (toYaml .Values.supersetCeleryFlower.extraContainers) . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}

View File

@@ -141,7 +141,7 @@ spec:
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
{{- if .Values.supersetWorker.extraContainers }}
{{- toYaml .Values.supersetWorker.extraContainers | nindent 8 }}
{{- tpl (toYaml .Values.supersetWorker.extraContainers) . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}

View File

@@ -120,7 +120,7 @@ spec:
livenessProbe: {{- .Values.supersetWebsockets.livenessProbe | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.supersetWebsockets.extraContainers }}
{{- toYaml .Values.supersetWebsockets.extraContainers | nindent 8 }}
{{- tpl (toYaml .Values.supersetWebsockets.extraContainers) . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}

View File

@@ -151,7 +151,7 @@ spec:
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
{{- if .Values.supersetNode.extraContainers }}
{{- toYaml .Values.supersetNode.extraContainers | nindent 8 }}
{{- tpl (toYaml .Values.supersetNode.extraContainers) . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}

View File

@@ -62,6 +62,9 @@ spec:
{{- if .Values.init.initContainers }}
initContainers: {{- tpl (toYaml .Values.init.initContainers) . | nindent 6 }}
{{- end }}
{{- with .Values.hostAliases }}
hostAliases: {{- toYaml . | nindent 6 }}
{{- end }}
containers:
- name: {{ template "superset.name" . }}-init-db
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
@@ -101,7 +104,7 @@ spec:
command: {{ tpl (toJson .Values.init.command) . }}
resources: {{- toYaml .Values.init.resources | nindent 10 }}
{{- if .Values.init.extraContainers }}
{{- toYaml .Values.init.extraContainers | nindent 6 }}
{{- tpl (toYaml .Values.init.extraContainers) . | nindent 6 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}

View File

@@ -43,9 +43,9 @@ dependencies = [
"click-option-group",
"colorama",
"flask-cors>=6.0.0, <7.0",
"croniter>=0.3.28",
"croniter>=6.2.2",
"cron-descriptor",
"cryptography>=42.0.4, <47.0.0",
"cryptography>=48.0.0, <49.0.0",
"deprecation>=2.1.0, <2.2.0",
"flask>=2.2.5, <4.0.0",
"flask-appbuilder>=5.2.1, <6.0.0",
@@ -53,9 +53,9 @@ dependencies = [
"flask-compress>=1.13, <2.0",
"flask-talisman>=1.0.0, <2.0",
"flask-login>=0.6.0, < 1.0",
"flask-migrate>=3.1.0, <5.0",
"flask-migrate>=4.1.0, <5.0",
"flask-session>=0.4.0, <1.0",
"flask-wtf>=1.1.0, <2.0",
"flask-wtf>=1.3.0, <2.0",
"geopy",
"greenlet>=3.0.3, <=3.5.0",
"gunicorn>=25.3.0, <26; sys_platform != 'win32'",
@@ -64,7 +64,7 @@ dependencies = [
"holidays>=0.45, <1",
"humanize",
"isodate",
"jsonpath-ng>=1.6.1, <2",
"jsonpath-ng>=1.8.0, <2",
"Mako>=1.2.2",
"markdown>=3.10.2",
# marshmallow>=4 has issues: https://github.com/apache/superset/issues/33162
@@ -80,7 +80,7 @@ dependencies = [
"bottleneck", # recommended performance dependency for pandas, see https://pandas.pydata.org/docs/getting_started/install.html#performance-dependencies-recommended
# --------------------------
"parsedatetime",
"paramiko>=3.4.0",
"paramiko>=3.4.0, <4.0", # 4.0 removed DSSKey, still referenced by sshtunnel
"pgsanity",
"Pillow>=11.0.0, <13",
"polyline>=2.0.0, <3.0",
@@ -94,10 +94,10 @@ dependencies = [
"PyJWT>=2.4.0, <3.0",
"redis>=5.0.0, <6.0",
"rison>=2.0.0, <3.0",
"selenium>=4.14.0, <5.0",
"selenium>=4.44.0, <5.0",
"shillelagh[gsheetsapi]>=1.4.4, <2.0",
"sshtunnel>=0.4.0, <0.5",
"simplejson>=3.15.0",
"simplejson>=4.1.1",
"slack_sdk>=3.19.0, <4",
"sqlalchemy>=1.4, <2",
"sqlalchemy-utils>=0.38.0, <0.43", # expanding lowerbound to work with pydoris
@@ -107,7 +107,7 @@ dependencies = [
"typing-extensions>=4, <5",
"waitress; sys_platform == 'win32'",
"watchdog>=6.0.0",
"wtforms>=2.3.3, <4",
"wtforms>=3.2.2, <4",
"wtforms-json",
"xlsxwriter>=3.2.9, <3.3",
]
@@ -118,10 +118,10 @@ athena = ["pyathena[pandas]>=2, <4"]
aurora-data-api = ["preset-sqlalchemy-aurora-data-api>=0.2.8,<0.3"]
bigquery = [
"pandas-gbq>=0.19.1",
"sqlalchemy-bigquery>=1.15.0",
"sqlalchemy-bigquery>=1.17.0",
"google-cloud-bigquery>=3.10.0",
]
clickhouse = ["clickhouse-connect>=0.13.0, <2.0"]
clickhouse = ["clickhouse-connect>=1.1.1, <2.0"]
cockroachdb = ["cockroachdb>=0.3.5, <0.4"]
crate = ["sqlalchemy-cratedb>=0.41.0, <1"]
d1 = [
@@ -143,41 +143,41 @@ duckdb = ["duckdb>=1.5.2,<2", "duckdb-engine>=0.17.0"]
dynamodb = ["pydynamodb>=0.4.2"]
solr = ["sqlalchemy-solr >= 0.2.0"]
elasticsearch = ["elasticsearch-dbapi>=0.2.13, <0.3.0"]
exasol = ["sqlalchemy-exasol >= 2.4.0, < 8.0"]
excel = ["xlrd>=1.2.0, <1.3"]
exasol = ["sqlalchemy-exasol>=2.4.0, <8.0"]
excel = ["xlrd>=2.0.2, <2.1"]
fastmcp = [
"fastmcp>=3.2.4,<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.7.0,<1.0",
"tiktoken>=0.13.0,<1.0",
]
firebird = ["sqlalchemy-firebird>=0.7.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.20", "sqlalchemy_hana==0.4.0"]
hana = ["hdbcli==2.28.21", "sqlalchemy_hana==0.4.0"]
hive = [
"pyhive[hive]>=0.6.5;python_version<'3.11'",
"pyhive[hive_pure_sasl]>=0.7.0",
"tableschema",
"thrift>=0.14.1, <1.0.0",
"thrift>=0.23.0, <1.0.0",
"thrift_sasl>=0.4.3, < 1.0.0",
]
impala = ["impyla>0.16.2, <0.23"]
kusto = ["sqlalchemy-kusto>=3.1.2, <4"]
kylin = ["kylinpy>=2.8.1, <2.9"]
mssql = ["pymssql>=2.2.8, <3"]
mssql = ["pymssql>=2.3.13, <3"]
# motherduck is an alias for duckdb - MotherDuck works via the duckdb driver
motherduck = ["apache-superset[duckdb]"]
mysql = ["mysqlclient>=2.1.0, <3"]
ocient = [
"sqlalchemy-ocient>=1.0.0",
"pyocient>=1.0.15, <2",
"pyocient>=1.0.15, <4",
"shapely",
"geojson",
]
oracle = ["cx-Oracle>8.0.0, <8.4"]
oracle = ["oracledb>=2.0.0, <5"]
parseable = ["sqlalchemy-parseable>=0.1.3,<0.2.0"]
pinot = ["pinotdb>=5.0.0, <10.0.0"]
playwright = ["playwright>=1.60.0, <2"]
@@ -195,7 +195,7 @@ spark = [
"pyhive[hive]>=0.6.5;python_version<'3.11'",
"pyhive[hive_pure_sasl]>=0.7",
"tableschema",
"thrift>=0.14.1, <1",
"thrift>=0.23.0, <1",
]
tdengine = [
"taospy>=2.7.21",
@@ -205,7 +205,7 @@ teradata = ["teradatasql>=16.20.0.23"]
thumbnails = [] # deprecated, will be removed in 7.0
vertica = ["sqlalchemy-vertica-python>= 0.6.3, < 0.7"]
netezza = ["nzalchemy>=11.0.2"]
starrocks = ["starrocks>=1.0.0"]
starrocks = ["starrocks>=1.3.3, <2"]
doris = ["pydoris>=1.0.0, <2.0.0"]
oceanbase = ["oceanbase_py>=0.0.1.2"]
ydb = ["ydb-sqlalchemy>=0.1.2", "ydb-sqlglot-plugin>=0.2.5"]
@@ -222,7 +222,7 @@ development = [
"pip",
"polib", # used by scripts/translations/ and their unit tests
"pre-commit",
"progress>=1.5,<2",
"progress>=1.6.1,<2",
"psutil",
"pyfakefs",
"pyinstrument>=5.1.2,<6",

View File

@@ -18,5 +18,30 @@
testpaths =
tests
python_files = *_test.py test_*.py *_tests.py *viz/utils.py
addopts = -p no:warnings
# `-p no:warnings` temporarily disabled in favor of more finely tuned `filterwarnings`.
#addopts = -p no:warnings
asyncio_mode = auto
# `ignore` is effectively equivalent to `-p no:warnings`.
# Always print RemovedIn20Warning when SQLALCHEMY_WARN_20=1.
# Additionally, raise errors for refactored RemovedIn20Warning cases to prevent regression.
filterwarnings =
ignore
always::sqlalchemy.exc.RemovedIn20Warning
# error:Passing a string to Connection.execute\(\) is deprecated:sqlalchemy.exc.RemovedIn20Warning
# error:"Query" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
# error:"SavedQuery" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
# error:"SqlaTable" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
# error:"SqlMetric" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
# error:"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 `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 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

@@ -26,7 +26,7 @@ filelock>=3.20.3,<4.0.0
brotli>=1.2.0,<2.0.0
numexpr>=2.9.0
# Security: CVE-2026-34073 (MEDIUM) - Improper Certificate Validation
cryptography>=46.0.7,<47.0.0
cryptography>=48.0.0,<49.0.0
# Security: Snyk - XSS vulnerability in Mako templates
mako>=1.3.11,<2.0.0
# Security: CVE-2024-52338 (CRITICAL) - Deserialization of untrusted data in IPC/Parquet readers

View File

@@ -50,7 +50,7 @@ cattrs==25.1.1
# via requests-cache
celery==5.5.2
# via apache-superset (pyproject.toml)
certifi==2025.6.15
certifi==2026.5.20
# via
# requests
# selenium
@@ -84,9 +84,9 @@ colorama==0.4.6
# flask-appbuilder
cron-descriptor==1.4.5
# via apache-superset (pyproject.toml)
croniter==6.0.0
croniter==6.2.2
# via apache-superset (pyproject.toml)
cryptography==46.0.7
cryptography==48.0.1
# via
# -r requirements/base.in
# apache-superset (pyproject.toml)
@@ -141,7 +141,7 @@ flask-login==0.6.3
# via
# apache-superset (pyproject.toml)
# flask-appbuilder
flask-migrate==3.1.0
flask-migrate==4.1.0
# via apache-superset (pyproject.toml)
flask-session==0.8.0
# via apache-superset (pyproject.toml)
@@ -151,7 +151,7 @@ flask-sqlalchemy==2.5.1
# flask-migrate
flask-talisman==1.1.0
# via apache-superset (pyproject.toml)
flask-wtf==1.2.2
flask-wtf==1.3.0
# via
# apache-superset (pyproject.toml)
# flask-appbuilder
@@ -194,7 +194,7 @@ jinja2==3.1.6
# via
# flask
# flask-babel
jsonpath-ng==1.7.0
jsonpath-ng==1.8.0
# via apache-superset (pyproject.toml)
jsonschema==4.23.0
# via
@@ -286,8 +286,6 @@ pillow==12.2.0
# via apache-superset (pyproject.toml)
platformdirs==4.3.8
# via requests-cache
ply==3.11
# via jsonpath-ng
polyline==2.0.2
# via apache-superset (pyproject.toml)
prison==0.2.1
@@ -325,7 +323,7 @@ pyjwt==2.12.0
# redis
pynacl==1.6.2
# via paramiko
pyopenssl==26.0.0
pyopenssl==26.2.0
# via
# -r requirements/base.in
# shillelagh
@@ -346,7 +344,6 @@ python-dotenv==1.2.2
# via apache-superset (pyproject.toml)
pytz==2025.2
# via
# croniter
# flask-babel
# pandas
pyxlsb==1.0.10
@@ -380,13 +377,13 @@ rpds-py==0.25.0
# referencing
rsa==4.9.1
# via google-auth
selenium==4.32.0
selenium==4.44.0
# via apache-superset (pyproject.toml)
setuptools==80.9.0
# via -r requirements/base.in
shillelagh==1.4.4
# via apache-superset (pyproject.toml)
simplejson==3.20.1
simplejson==4.1.1
# via apache-superset (pyproject.toml)
six==1.17.0
# via
@@ -423,7 +420,7 @@ sshtunnel==0.4.0
# via apache-superset (pyproject.toml)
tabulate==0.10.0
# via apache-superset (pyproject.toml)
trio==0.30.0
trio==0.33.0
# via
# selenium
# trio-websocket
@@ -480,7 +477,7 @@ wrapt==1.17.2
# via deprecated
wsproto==1.2.0
# via trio-websocket
wtforms==3.2.1
wtforms==3.2.2
# via
# apache-superset (pyproject.toml)
# flask-appbuilder

View File

@@ -112,7 +112,7 @@ celery==5.5.2
# via
# -c requirements/base-constraint.txt
# apache-superset
certifi==2025.6.15
certifi==2026.5.20
# via
# -c requirements/base-constraint.txt
# httpcore
@@ -174,11 +174,11 @@ cron-descriptor==1.4.5
# via
# -c requirements/base-constraint.txt
# apache-superset
croniter==6.0.0
croniter==6.2.2
# via
# -c requirements/base-constraint.txt
# apache-superset
cryptography==46.0.7
cryptography==48.0.1
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -293,7 +293,7 @@ flask-login==0.6.3
# -c requirements/base-constraint.txt
# apache-superset
# flask-appbuilder
flask-migrate==3.1.0
flask-migrate==4.1.0
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -312,7 +312,7 @@ flask-talisman==1.1.0
# apache-superset
flask-testing==0.8.1
# via apache-superset
flask-wtf==1.2.2
flask-wtf==1.3.0
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -471,7 +471,7 @@ jmespath==1.1.0
# via
# boto3
# botocore
jsonpath-ng==1.7.0
jsonpath-ng==1.8.0
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -674,10 +674,6 @@ platformdirs==4.3.8
# virtualenv
pluggy==1.5.0
# via pytest
ply==3.11
# via
# -c requirements/base-constraint.txt
# jsonpath-ng
polib==1.2.0
# via apache-superset
polyline==2.0.2
@@ -690,7 +686,7 @@ prison==0.2.1
# via
# -c requirements/base-constraint.txt
# flask-appbuilder
progress==1.6
progress==1.6.1
# via apache-superset
prompt-toolkit==3.0.51
# via
@@ -784,7 +780,7 @@ pynacl==1.6.2
# via
# -c requirements/base-constraint.txt
# paramiko
pyopenssl==26.0.0
pyopenssl==26.2.0
# via
# -c requirements/base-constraint.txt
# shillelagh
@@ -845,7 +841,6 @@ python-multipart==0.0.29
pytz==2025.2
# via
# -c requirements/base-constraint.txt
# croniter
# flask-babel
# pandas
# trino
@@ -925,7 +920,7 @@ s3transfer==0.16.0
# via boto3
secretstorage==3.5.0
# via keyring
selenium==4.32.0
selenium==4.44.0
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -943,7 +938,7 @@ shillelagh==1.4.4
# via
# -c requirements/base-constraint.txt
# apache-superset
simplejson==3.20.1
simplejson==4.1.1
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -980,7 +975,7 @@ sqlalchemy==1.4.54
# shillelagh
# sqlalchemy-bigquery
# sqlalchemy-utils
sqlalchemy-bigquery==1.15.0
sqlalchemy-bigquery==1.17.0
# via apache-superset
sqlalchemy-utils==0.42.0
# via
@@ -1011,7 +1006,7 @@ tabulate==0.10.0
# via
# -c requirements/base-constraint.txt
# apache-superset
tiktoken==0.12.0
tiktoken==0.13.0
# via apache-superset
tomli-w==1.2.0
# via apache-superset-extensions-cli
@@ -1023,7 +1018,7 @@ tqdm==4.67.1
# prophet
trino==0.330.0
# via apache-superset
trio==0.30.0
trio==0.33.0
# via
# -c requirements/base-constraint.txt
# selenium
@@ -1125,7 +1120,7 @@ wsproto==1.2.0
# via
# -c requirements/base-constraint.txt
# trio-websocket
wtforms==3.2.1
wtforms==3.2.2
# via
# -c requirements/base-constraint.txt
# apache-superset

View File

@@ -106,6 +106,7 @@ LANGUAGE_NAMES: dict[str, str] = {
"ru": "Russian",
"sk": "Slovak",
"sl": "Slovenian",
"sr": "Serbian",
"tr": "Turkish",
"uk": "Ukrainian",
"zh": "Chinese (Simplified)",

View File

@@ -1 +1 @@
v22.22.0
v24.16.0

View File

@@ -27,19 +27,6 @@
"webpack-cli": "^5.1.4"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/cli": {
"version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.25.6.tgz",
@@ -71,12 +58,12 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.28.5",
"@babel/helper-validator-identifier": "^7.29.7",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
@@ -85,32 +72,30 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz",
"integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
"integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz",
"integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==",
"version": "7.29.6",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.6.tgz",
"integrity": "sha512-QdxmAo/ikZqqRGA8s43ww8lcql6naWRvEz0FFrl6MIlc7Gi6TroXnSdWa5U/kq6fzcpqpHesicQxFZIieZbyIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.25.0",
"@babel/helper-compilation-targets": "^7.25.2",
"@babel/helper-module-transforms": "^7.25.2",
"@babel/helpers": "^7.25.0",
"@babel/parser": "^7.25.0",
"@babel/template": "^7.25.0",
"@babel/traverse": "^7.25.2",
"@babel/types": "^7.25.2",
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.6",
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-module-transforms": "^7.28.6",
"@babel/helpers": "^7.29.2",
"@babel/parser": "^7.29.3",
"@babel/template": "^7.28.6",
"@babel/traverse": "^7.29.0",
"@babel/types": "^7.29.0",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -126,13 +111,13 @@
}
},
"node_modules/@babel/generator": {
"version": "7.29.1",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
"integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.29.0",
"@babel/types": "^7.29.0",
"@babel/parser": "^7.29.7",
"@babel/types": "^7.29.7",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -169,15 +154,14 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz",
"integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
"integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.25.2",
"@babel/helper-validator-option": "^7.24.8",
"browserslist": "^4.23.1",
"@babel/compat-data": "^7.29.7",
"@babel/helper-validator-option": "^7.29.7",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
},
@@ -382,29 +366,28 @@
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz",
"integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
"integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
@@ -425,26 +408,25 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz",
"integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
"integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.25.0",
"@babel/types": "^7.25.6"
"@babel/template": "^7.29.7",
"@babel/types": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.29.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
"dev": true,
"dependencies": {
"@babel/types": "^7.29.0"
"@babel/types": "^7.29.7"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -1843,14 +1825,14 @@
}
},
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
"integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.28.6",
"@babel/parser": "^7.28.6",
"@babel/types": "^7.28.6"
"@babel/code-frame": "^7.29.7",
"@babel/parser": "^7.29.7",
"@babel/types": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1875,13 +1857,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
"@babel/helper-string-parser": "^7.29.7",
"@babel/helper-validator-identifier": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -2649,6 +2631,16 @@
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/remapping": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
@@ -7983,16 +7975,6 @@
}
},
"dependencies": {
"@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"requires": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"@babel/cli": {
"version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.25.6.tgz",
@@ -8011,38 +7993,38 @@
}
},
"@babel/code-frame": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.28.5",
"@babel/helper-validator-identifier": "^7.29.7",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
}
},
"@babel/compat-data": {
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz",
"integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
"integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
"dev": true
},
"@babel/core": {
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz",
"integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==",
"version": "7.29.6",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.6.tgz",
"integrity": "sha512-QdxmAo/ikZqqRGA8s43ww8lcql6naWRvEz0FFrl6MIlc7Gi6TroXnSdWa5U/kq6fzcpqpHesicQxFZIieZbyIA==",
"dev": true,
"requires": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.25.0",
"@babel/helper-compilation-targets": "^7.25.2",
"@babel/helper-module-transforms": "^7.25.2",
"@babel/helpers": "^7.25.0",
"@babel/parser": "^7.25.0",
"@babel/template": "^7.25.0",
"@babel/traverse": "^7.25.2",
"@babel/types": "^7.25.2",
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.6",
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-module-transforms": "^7.28.6",
"@babel/helpers": "^7.29.2",
"@babel/parser": "^7.29.3",
"@babel/template": "^7.28.6",
"@babel/traverse": "^7.29.0",
"@babel/types": "^7.29.0",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -8051,13 +8033,13 @@
}
},
"@babel/generator": {
"version": "7.29.1",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
"integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
"dev": true,
"requires": {
"@babel/parser": "^7.29.0",
"@babel/types": "^7.29.0",
"@babel/parser": "^7.29.7",
"@babel/types": "^7.29.7",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -8083,14 +8065,14 @@
}
},
"@babel/helper-compilation-targets": {
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz",
"integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
"integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
"dev": true,
"requires": {
"@babel/compat-data": "^7.25.2",
"@babel/helper-validator-option": "^7.24.8",
"browserslist": "^4.23.1",
"@babel/compat-data": "^7.29.7",
"@babel/helper-validator-option": "^7.29.7",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
}
@@ -8229,21 +8211,21 @@
}
},
"@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"dev": true
},
"@babel/helper-validator-option": {
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz",
"integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
"integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
"dev": true
},
"@babel/helper-wrap-function": {
@@ -8258,22 +8240,22 @@
}
},
"@babel/helpers": {
"version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz",
"integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
"integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
"dev": true,
"requires": {
"@babel/template": "^7.25.0",
"@babel/types": "^7.25.6"
"@babel/template": "^7.29.7",
"@babel/types": "^7.29.7"
}
},
"@babel/parser": {
"version": "7.29.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
"dev": true,
"requires": {
"@babel/types": "^7.29.0"
"@babel/types": "^7.29.7"
}
},
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
@@ -9157,14 +9139,14 @@
}
},
"@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
"integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.28.6",
"@babel/parser": "^7.28.6",
"@babel/types": "^7.28.6"
"@babel/code-frame": "^7.29.7",
"@babel/parser": "^7.29.7",
"@babel/types": "^7.29.7"
}
},
"@babel/traverse": {
@@ -9183,13 +9165,13 @@
}
},
"@babel/types": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
"@babel/helper-string-parser": "^7.29.7",
"@babel/helper-validator-identifier": "^7.29.7"
}
},
"@bcoe/v8-coverage": {
@@ -9771,6 +9753,16 @@
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"@jridgewell/remapping": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"requires": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@superset-ui/embedded-sdk",
"version": "0.3.0",
"version": "0.4.0",
"description": "SDK for embedding resources from Superset into your own application",
"access": "public",
"keywords": [

View File

@@ -47,7 +47,11 @@ function logError(...args) {
execSync('npm publish --access public', { stdio: 'pipe' });
log(`published ${version} to npm`);
} catch (err) {
console.error(String(err.stdout));
// npm writes failure details to stderr (auth/permission/registry
// errors in particular), so surface both streams to avoid masking
// the real cause in CI logs.
if (err.stdout) console.error(String(err.stdout));
if (err.stderr) console.error(String(err.stderr));
logError('Encountered an error, details should be above');
process.exitCode = 1;
}

View File

@@ -1,34 +0,0 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
**/*{.,-}min.js
**/*.sh
coverage/**
dist/*
src/assets/images/*
node_modules/*
node_modules*/*
vendor/*
docs/*
src/dashboard/deprecated/*
src/temp/*
**/node_modules
*.d.ts
coverage/
esm/
lib/
tmp/
storybook-static/

View File

@@ -1,523 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// Register TypeScript require hook so ESLint can load .ts plugin files
require('tsx/cjs');
const packageConfig = require('./package.json');
const importCoreModules = [];
Object.entries(packageConfig.dependencies).forEach(([pkg]) => {
if (/@superset-ui/.test(pkg)) {
importCoreModules.push(pkg);
}
});
// ignore files in production mode
let ignorePatterns = [];
if (process.env.NODE_ENV === 'production') {
ignorePatterns = [
'*.test.{js,ts,jsx,tsx}',
'plugins/**/test/**/*',
'packages/**/test/**/*',
'packages/generator-superset/**/*',
];
}
const restrictedImportsRules = {
'no-design-icons': {
name: '@ant-design/icons',
message:
'Avoid importing icons directly from @ant-design/icons. Use the src/components/Icons component instead.',
},
'no-moment': {
name: 'moment',
message:
'Please use the dayjs library instead of moment.js. See https://day.js.org',
},
'no-lodash-memoize': {
name: 'lodash/memoize',
message: 'Lodash Memoize is unsafe! Please use memoize-one instead',
},
'no-testing-library-react': {
name: '@superset-ui/core/spec',
message: 'Please use spec/helpers/testing-library instead',
},
'no-testing-library-react-dom-utils': {
name: '@testing-library/react-dom-utils',
message: 'Please use spec/helpers/testing-library instead',
},
'no-antd': {
name: 'antd',
message: 'Please import Ant components from the index of src/components',
},
'no-superset-theme': {
name: '@superset-ui/core',
importNames: ['supersetTheme'],
message:
'Please use the theme directly from the ThemeProvider rather than importing supersetTheme.',
},
'no-query-string': {
name: 'query-string',
message: 'Please use the URLSearchParams API instead of query-string.',
},
'no-jest-mock-console': {
name: 'jest-mock-console',
message: 'Please use native Jest spies, i.e. jest.spyOn(console, "warn")',
},
};
module.exports = {
extends: [
'eslint:recommended',
'plugin:import/recommended',
'plugin:react-prefer-function-component/recommended',
'plugin:storybook/recommended',
'prettier',
],
parser: '@babel/eslint-parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
requireConfigFile: false,
babelOptions: {
presets: ['@babel/preset-react', '@babel/preset-env'],
},
},
env: {
browser: true,
node: true,
es2020: true,
},
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
moduleDirectory: ['node_modules', '.'],
},
typescript: {
alwaysTryTypes: true,
project: [
'./tsconfig.json',
'./packages/superset-ui-core/tsconfig.json',
'./packages/superset-ui-chart-controls/',
'./plugins/*/tsconfig.json',
],
},
},
'import/core-modules': importCoreModules,
react: {
version: 'detect',
},
},
plugins: [
'import',
'lodash',
'theme-colors',
'icons',
'i18n-strings',
'react-prefer-function-component',
'react-you-might-not-need-an-effect',
'prettier',
],
rules: {
// === Essential Superset customizations ===
// Prettier integration
'prettier/prettier': 'error',
// Custom Superset rules
'theme-colors/no-literal-colors': 'error',
'icons/no-fa-icons-usage': 'error',
'i18n-strings/no-template-vars': 'error',
// Core ESLint overrides for Superset
'no-console': 'warn',
'no-unused-vars': 'off', // TypeScript handles this
camelcase: [
'error',
{
allow: ['^UNSAFE_', '__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'],
properties: 'never',
},
],
'prefer-destructuring': ['error', { object: true, array: false }],
'no-prototype-builtins': 0,
curly: 'off',
// Import plugin overrides
'import/extensions': [
'error',
'ignorePackages',
{
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never',
},
],
'import/no-cycle': 0,
'import/prefer-default-export': 0,
'import/no-named-as-default-member': 0,
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'test/**',
'tests/**',
'spec/**',
'**/__tests__/**',
'**/__mocks__/**',
'*.test.{js,jsx,ts,tsx}',
'*.spec.{js,jsx,ts,tsx}',
'**/*.test.{js,jsx,ts,tsx}',
'**/*.spec.{js,jsx,ts,tsx}',
'**/jest.config.js',
'**/jest.setup.js',
'**/webpack.config.js',
'**/webpack.config.*.js',
'**/.eslintrc*.js',
],
optionalDependencies: false,
},
],
// React plugin overrides
'react-prefer-function-component/react-prefer-function-component': 1,
// React effect best practices
'react-you-might-not-need-an-effect/no-empty-effect': 'error',
'react-you-might-not-need-an-effect/no-pass-live-state-to-parent': 'error',
'react-you-might-not-need-an-effect/no-initialize-state': 'error',
// Lodash
'lodash/import-scope': [2, 'member'],
// React effect best practices
'react-you-might-not-need-an-effect/no-reset-all-state-on-prop-change':
'error',
'react-you-might-not-need-an-effect/no-chain-state-updates': 'error',
'react-you-might-not-need-an-effect/no-event-handler': 'error',
'react-you-might-not-need-an-effect/no-derived-state': 'error',
// Storybook
'storybook/prefer-pascal-case': 'error',
// File progress
'file-progress/activate': 1,
// React effect rules
'react-you-might-not-need-an-effect/no-adjust-state-on-prop-change':
'error',
'react-you-might-not-need-an-effect/no-pass-data-to-parent': 'error',
// Restricted imports
'no-restricted-imports': [
'error',
{
paths: Object.values(restrictedImportsRules).filter(Boolean),
patterns: ['antd/*'],
},
],
// Temporarily disabled for migration
'no-unsafe-optional-chaining': 0,
'no-import-assign': 0,
'import/no-relative-packages': 0,
'no-promise-executor-return': 0,
'import/no-import-module-exports': 0,
// Restrict certain syntax patterns
'no-restricted-syntax': [
'error',
{
selector:
"ImportDeclaration[source.value='react'] :matches(ImportDefaultSpecifier, ImportNamespaceSpecifier)",
message:
'Default React import is not required due to automatic JSX runtime in React 16.4',
},
{
selector: 'ImportNamespaceSpecifier[parent.source.value!=/^(\\.|src)/]',
message: 'Wildcard imports are not allowed',
},
],
},
overrides: [
// Ban JavaScript files in src/ - all new code must be TypeScript
{
files: ['src/**/*.js', 'src/**/*.jsx'],
rules: {
'no-restricted-syntax': [
'error',
{
selector: 'Program',
message:
'JavaScript files are not allowed in src/. Please use TypeScript (.ts/.tsx) instead.',
},
],
},
},
// Ban JavaScript files in plugins/ - all plugin source code must be TypeScript
{
files: ['plugins/**/src/**/*.js', 'plugins/**/src/**/*.jsx'],
rules: {
'no-restricted-syntax': [
'error',
{
selector: 'Program',
message:
'JavaScript files are not allowed in plugins/. Please use TypeScript (.ts/.tsx) instead.',
},
],
},
},
// Ban JavaScript files in packages/ - with exceptions for config files and generators
{
files: ['packages/**/src/**/*.js', 'packages/**/src/**/*.jsx'],
excludedFiles: [
'packages/generator-superset/**/*', // Yeoman generator templates run via Node
'packages/**/__mocks__/**/*', // Test mocks
],
rules: {
'no-restricted-syntax': [
'error',
{
selector: 'Program',
message:
'JavaScript files are not allowed in packages/. Please use TypeScript (.ts/.tsx) instead.',
},
],
},
},
{
files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
extends: ['plugin:@typescript-eslint/recommended', 'prettier'],
plugins: ['@typescript-eslint/eslint-plugin'],
rules: {
// TypeScript-specific rule overrides
'@typescript-eslint/ban-ts-ignore': 0,
'@typescript-eslint/ban-ts-comment': 0,
'@typescript-eslint/ban-types': 0,
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'enum',
format: ['PascalCase'],
},
{
selector: 'enumMember',
format: ['PascalCase'],
},
],
'@typescript-eslint/no-empty-function': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-use-before-define': 'error',
'@typescript-eslint/no-non-null-assertion': 0,
'@typescript-eslint/explicit-function-return-type': 0,
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/prefer-optional-chain': 'error',
// Disable base rules that conflict with TS versions
'no-unused-vars': 'off',
'no-use-before-define': 'off',
'no-shadow': 'off',
// Import overrides for TypeScript
'import/extensions': [
'error',
'ignorePackages',
{
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never',
},
],
},
settings: {
'import/resolver': {
typescript: {},
},
},
},
{
files: ['packages/**'],
rules: {
'import/no-extraneous-dependencies': [
'error',
{ devDependencies: true },
],
'no-restricted-imports': [
'error',
{
paths: [
restrictedImportsRules['no-moment'],
restrictedImportsRules['no-lodash-memoize'],
restrictedImportsRules['no-superset-theme'],
],
patterns: [],
},
],
},
},
{
files: ['plugins/**'],
rules: {
'no-restricted-imports': [
'error',
{
paths: [
restrictedImportsRules['no-moment'],
restrictedImportsRules['no-lodash-memoize'],
],
patterns: [],
},
],
},
},
{
files: ['src/components/**', 'src/theme/**'],
rules: {
'no-restricted-imports': [
'error',
{
paths: Object.values(restrictedImportsRules).filter(
r => r.name !== 'antd',
),
patterns: [],
},
],
},
},
{
files: [
'*.test.ts',
'*.test.tsx',
'*.test.js',
'*.test.jsx',
'*.stories.tsx',
'*.stories.jsx',
'fixtures.*',
'**/test/**/*',
'**/tests/**/*',
'spec/**/*',
'**/fixtures/**/*',
'**/__mocks__/**/*',
'**/spec/**/*',
],
excludedFiles: 'cypress-base/cypress/**/*',
plugins: ['jest-dom', 'no-only-tests', 'testing-library'],
extends: ['plugin:jest-dom/recommended', 'plugin:testing-library/react'],
rules: {
'import/no-extraneous-dependencies': [
'error',
{ devDependencies: true },
],
'prefer-promise-reject-errors': 0,
'max-classes-per-file': 0,
// Temporary for migration
'testing-library/await-async-queries': 0,
'testing-library/await-async-utils': 0,
'testing-library/no-await-sync-events': 0,
'testing-library/no-render-in-lifecycle': 0,
'testing-library/no-unnecessary-act': 0,
'testing-library/no-wait-for-multiple-assertions': 0,
'testing-library/prefer-screen-queries': 0,
'testing-library/await-async-events': 0,
'testing-library/no-node-access': 0,
'testing-library/no-wait-for-side-effects': 0,
'testing-library/prefer-presence-queries': 0,
'testing-library/render-result-naming-convention': 0,
'testing-library/no-container': 0,
'testing-library/prefer-find-by': 0,
'testing-library/no-manual-cleanup': 0,
'no-restricted-syntax': [
'error',
{
selector:
"ImportDeclaration[source.value='react'] :matches(ImportDefaultSpecifier, ImportNamespaceSpecifier)",
message:
'Default React import is not required due to automatic JSX runtime in React 16.4',
},
],
'no-restricted-imports': 0,
},
},
{
files: [
'*.test.ts',
'*.test.tsx',
'*.test.js',
'*.test.jsx',
'*.stories.tsx',
'*.stories.jsx',
'fixtures.*',
'**/test/**/*',
'**/tests/**/*',
'spec/**/*',
'**/fixtures/**/*',
'**/__mocks__/**/*',
'**/spec/**/*',
'cypress-base/cypress/**/*',
'Stories.tsx',
'packages/superset-ui-core/src/theme/index.tsx',
],
rules: {
'theme-colors/no-literal-colors': 0,
'icons/no-fa-icons-usage': 0,
'i18n-strings/no-template-vars': 0,
'no-restricted-imports': 0,
},
},
{
files: [
'packages/**/*.stories.*',
'packages/**/*.overview.*',
'packages/**/fixtures.*',
],
rules: {
'import/no-extraneous-dependencies': 'off',
},
},
{
files: ['playwright/**/*.ts', 'playwright/**/*.js'],
rules: {
'import/no-extraneous-dependencies': [
'error',
{ devDependencies: true },
],
},
},
],
ignorePatterns,
};

View File

@@ -1,124 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// Register TypeScript require hook so ESLint can load .ts plugin files
require('tsx/cjs');
/**
* MINIMAL ESLint config - ONLY for rules OXC doesn't support
* This config is designed to be run alongside OXC linter
*
* Only covers:
* - Custom Superset plugins (theme-colors, icons, i18n)
* - Prettier formatting
* - File progress indicator
*/
module.exports = {
root: true,
// Don't report on eslint-disable comments for rules we don't have
reportUnusedDisableDirectives: false,
// Simple parser - no TypeScript needed since OXC handles that
parser: '@babel/eslint-parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
requireConfigFile: false,
babelOptions: {
presets: ['@babel/preset-react', '@babel/preset-env'],
},
},
env: {
browser: true,
node: true,
es2020: true,
},
plugins: [
// ONLY custom Superset plugins that OXC doesn't support
'theme-colors',
'icons',
'i18n-strings',
'file-progress',
'prettier',
],
rules: {
// === ONLY rules that OXC cannot handle ===
// Prettier integration (formatting)
'prettier/prettier': 'error',
// Custom Superset plugins
'theme-colors/no-literal-colors': 'error',
'icons/no-fa-icons-usage': 'error',
'i18n-strings/no-template-vars': 'error',
'file-progress/activate': 1,
// Explicitly turn off all other rules to avoid conflicts
// when the config gets merged with other configs
'import/no-unresolved': 'off',
'import/extensions': 'off',
'@typescript-eslint/naming-convention': 'off',
},
overrides: [
{
// Disable custom rules in test/story files
files: [
'**/*.test.*',
'**/*.spec.*',
'**/*.stories.*',
'**/test/**',
'**/tests/**',
'**/spec/**',
'**/__tests__/**',
'**/__mocks__/**',
'cypress-base/**',
'packages/superset-ui-core/src/theme/index.tsx',
],
rules: {
'theme-colors/no-literal-colors': 0,
'icons/no-fa-icons-usage': 0,
'i18n-strings/no-template-vars': 0,
'file-progress/activate': 0,
},
},
],
// Only check src/ files where theme/icon rules matter
ignorePatterns: [
'node_modules',
'dist',
'build',
'.next',
'coverage',
'*.min.js',
'vendor',
// Skip packages/plugins since they have different theming rules
'packages/**',
'plugins/**',
// Skip generated/external files
'*.generated.*',
'*.config.js',
'webpack.*',
// Temporary analysis files
'*.js', // Skip all standalone JS files in root
'*.json',
],
};

View File

@@ -1 +1 @@
v22.22.0
v24.16.0

View File

@@ -48,6 +48,7 @@ module.exports = {
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-transform-export-namespace-from',
['@babel/plugin-transform-class-properties', { loose: true }],
'@babel/plugin-transform-class-static-block',
['@babel/plugin-transform-optional-chaining', { loose: true }],
['@babel/plugin-transform-private-methods', { loose: true }],
['@babel/plugin-transform-nullish-coalescing-operator', { loose: true }],
@@ -106,7 +107,13 @@ module.exports = {
[
'babel-plugin-jsx-remove-data-test-id',
{
attributes: 'data-test',
// The plugin matches attribute names exactly (no prefix match),
// so each data-test* attribute must be listed explicitly.
attributes: [
'data-test',
'data-test-drag-source-id',
'data-test-drop-target-id',
],
},
],
],

View File

@@ -2058,6 +2058,24 @@
"node": ">=8"
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@istanbuljs/schema": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz",
@@ -2934,6 +2952,8 @@
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"peer": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
@@ -4353,6 +4373,8 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true,
"peer": true,
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
@@ -4686,16 +4708,15 @@
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
"hasown": "^2.0.4",
"mime-types": "^2.1.35"
},
"engines": {
"node": ">= 6"
@@ -5007,10 +5028,9 @@
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -5594,7 +5614,9 @@
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@@ -7756,7 +7778,9 @@
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true,
"peer": true
},
"node_modules/sshpk": {
"version": "1.18.0",
@@ -10204,6 +10228,20 @@
"get-package-type": "^0.1.0",
"js-yaml": "^3.13.1",
"resolve-from": "^5.0.0"
},
"dependencies": {
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"js-yaml": {
"version": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"requires": {
"argparse": "^2.0.1"
}
}
}
},
"@istanbuljs/schema": {
@@ -11006,6 +11044,8 @@
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"peer": true,
"requires": {
"sprintf-js": "~1.0.2"
}
@@ -12051,7 +12091,9 @@
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true,
"peer": true
},
"esquery": {
"version": "1.4.0",
@@ -12300,15 +12342,15 @@
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="
},
"form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
"hasown": "^2.0.4",
"mime-types": "^2.1.35"
}
},
"fromentries": {
@@ -12529,9 +12571,9 @@
}
},
"hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"requires": {
"function-bind": "^1.1.2"
}
@@ -12953,6 +12995,8 @@
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"peer": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@@ -14463,7 +14507,9 @@
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true,
"peer": true
},
"sshpk": {
"version": "1.18.0",

View File

@@ -65,6 +65,86 @@ const plugin: { rules: Record<string, Rule.RuleModule> } = {
};
},
},
'no-eager-t-in-config': {
meta: {
type: 'problem',
fixable: 'code',
docs: {
description:
'Disallow eager t()/tn() calls for `label` and `description` in config objects evaluated at module load (e.g., controlPanel files). The translation is captured at module-evaluation time, before i18n has loaded, and never updates when the user switches language. Wrap the call in an arrow function so it is evaluated at render time.',
},
schema: [
{
type: 'object',
properties: {
properties: {
type: 'array',
items: { type: 'string' },
},
},
additionalProperties: false,
},
],
messages: {
eager:
'Eager `{{property}}: {{fn}}(...)` is evaluated at module load, before i18n is initialized. Wrap in an arrow function: `{{property}}: () => {{fn}}(...)`.',
},
},
create(context: Rule.RuleContext): Rule.RuleListener {
const watchedProps: string[] = context.options[0]?.properties ?? [
'label',
'description',
];
const TRANSLATE_FNS = new Set(['t', 'tn']);
function handler(node: Node): void {
const prop = node as Node & {
key: { type: string; name?: string; value?: string };
value: Node & {
type: string;
callee?: { type: string; name?: string };
};
shorthand?: boolean;
computed?: boolean;
};
if (prop.shorthand || prop.computed) return;
const keyName =
prop.key.type === 'Identifier'
? prop.key.name
: prop.key.type === 'Literal'
? prop.key.value
: undefined;
if (typeof keyName !== 'string' || !watchedProps.includes(keyName)) {
return;
}
const callee = prop.value;
if (
callee.type !== 'CallExpression' ||
callee.callee?.type !== 'Identifier' ||
!callee.callee.name ||
!TRANSLATE_FNS.has(callee.callee.name)
) {
return;
}
context.report({
node: prop.value,
messageId: 'eager',
data: { property: keyName, fn: callee.callee.name },
fix(fixer) {
const source = context.getSourceCode().getText(prop.value);
return fixer.replaceText(prop.value, `() => ${source}`);
},
});
}
return {
Property: handler,
};
},
},
'sentence-case-buttons': {
meta: {
type: 'suggestion',

View File

@@ -0,0 +1,86 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { Rule } from 'eslint';
const { RuleTester } = require('eslint');
const plugin: { rules: Record<string, Rule.RuleModule> } = require('.');
const ruleTester = new RuleTester({
parserOptions: { ecmaVersion: 2020, sourceType: 'module' },
});
const rule: Rule.RuleModule = plugin.rules['no-eager-t-in-config'];
ruleTester.run('no-eager-t-in-config', rule, {
valid: [
// Lazy form — the recommended pattern
"const c = { label: () => t('Foo') };",
"const c = { description: () => t('Foo') };",
"const c = { label: () => tn('one', 'many', n) };",
// Static strings — no translation, no issue
"const c = { label: 'Foo' };",
// Other property names — unaffected
"const c = { name: t('Foo') };",
"const c = { title: t('Foo') };",
// Computed keys are too dynamic to lint usefully
"const c = { [labelKey]: t('Foo') };",
// Shorthand: `{ label }` — no value to inspect
'const label = t("Foo"); const c = { label };',
// t() called inside a function body — already lazy
"const c = { label: state => t('Foo') };",
// Non-t() call expressions are fine
"const c = { label: someOtherFn('Foo') };",
],
invalid: [
{
code: "const c = { label: t('Foo') };",
output: "const c = { label: () => t('Foo') };",
errors: [{ messageId: 'eager' }],
},
{
code: "const c = { description: t('Foo bar') };",
output: "const c = { description: () => t('Foo bar') };",
errors: [{ messageId: 'eager' }],
},
{
code: "const c = { label: tn('one', 'many', 2) };",
output: "const c = { label: () => tn('one', 'many', 2) };",
errors: [{ messageId: 'eager' }],
},
// String-literal keys are equivalent to identifier keys
{
code: "const c = { 'label': t('Foo') };",
output: "const c = { 'label': () => t('Foo') };",
errors: [{ messageId: 'eager' }],
},
// Custom watched-property list via rule option
{
code: "const c = { headerTitle: t('Foo') };",
output: "const c = { headerTitle: () => t('Foo') };",
options: [{ properties: ['headerTitle'] }],
errors: [{ messageId: 'eager' }],
},
// Nested config — fires per occurrence
{
code: "const c = { foo: { label: t('A'), description: t('B') } };",
output:
"const c = { foo: { label: () => t('A'), description: () => t('B') } };",
errors: [{ messageId: 'eager' }, { messageId: 'eager' }],
},
],
});

View File

@@ -31,12 +31,13 @@ const plugin: { rules: Record<string, Rule.RuleModule> } = require('.');
// Tests
//------------------------------------------------------------------------------
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 6 } });
const rule: Rule.RuleModule = plugin.rules['no-template-vars'];
const errors: Array<{ type: string }> = [
const errors: Array<{ message: string }> = [
{
type: 'CallExpression',
message:
"Don't use variables in translation string templates. Flask-babel is a static translation service, so it can't handle strings that include variables",
},
];

View File

@@ -31,7 +31,10 @@ const plugin: { rules: Record<string, Rule.RuleModule> } = require('.');
// Tests
//------------------------------------------------------------------------------
const ruleTester = new RuleTester({
parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } },
languageOptions: {
ecmaVersion: 6,
parserOptions: { ecmaFeatures: { jsx: true } },
},
});
const rule: Rule.RuleModule = plugin.rules['no-fa-icons-usage'];

View File

@@ -0,0 +1,137 @@
/**
* 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.
*/
/**
* MINIMAL ESLint flat config - ONLY for rules OXC doesn't support.
*
* This config is run alongside the OXC (oxlint) linter, which handles the
* bulk of linting. ESLint here only covers the custom Superset plugins and
* Prettier formatting that oxlint cannot express. It is consumed by
* `scripts/oxlint-metrics-uploader.js` (`npm run lint-stats`).
*
* Migrated from the legacy `.eslintrc.minimal.js` (eslintrc) format to flat
* config for ESLint v9+/v10, where eslintrc is no longer supported.
*
* Only covers:
* - Custom Superset plugins (theme-colors, icons, i18n-strings)
* - Prettier formatting
*/
// Register the TypeScript require hook so ESLint can load the .ts plugin files
// from eslint-rules/*.
require('tsx/cjs');
const tsParser = require('@typescript-eslint/parser');
const prettierPlugin = require('eslint-plugin-prettier');
const themeColorsPlugin = require('eslint-plugin-theme-colors');
const iconsPlugin = require('eslint-plugin-icons');
const i18nStringsPlugin = require('eslint-plugin-i18n-strings');
module.exports = [
// Files this config applies to. Flat config has no `--ext`; globs live here.
// Only check src/ files where the theme/icon/i18n rules matter.
{
ignores: [
'node_modules/**',
'dist/**',
'build/**',
'.next/**',
'coverage/**',
'**/*.min.js',
'vendor/**',
// Skip packages/plugins since they have different theming rules
'packages/**',
'plugins/**',
// Skip generated/external/config files
'**/*.generated.*',
'**/*.config.js',
'**/webpack.*',
'*.json',
],
},
{
files: ['**/*.{js,jsx,ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
sourceType: 'module',
// The @typescript-eslint parser handles both TS/TSX and plain JS/JSX and
// is compatible with ESLint v10's scope manager. (The legacy
// @babel/eslint-parser does not support ESLint v10.) The custom rules
// here are pure AST visitors and do not require type information, so no
// `project` is configured — this keeps parsing fast.
parser: tsParser,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
// Don't report on eslint-disable comments for rules we don't have.
linterOptions: {
reportUnusedDisableDirectives: false,
},
plugins: {
prettier: prettierPlugin,
'theme-colors': themeColorsPlugin,
icons: iconsPlugin,
'i18n-strings': i18nStringsPlugin,
},
rules: {
// Prettier integration (formatting)
'prettier/prettier': 'error',
// Custom Superset plugins
'theme-colors/no-literal-colors': 'error',
'icons/no-fa-icons-usage': 'error',
'i18n-strings/no-template-vars': 'error',
// Enabled only for controlPanel files via the override below.
'i18n-strings/no-eager-t-in-config': 'off',
},
},
{
// Eager t()/tn() in `label`/`description` config props is captured at
// module-load time, before i18n initializes — labels stay in the fallback
// language even after the user switches. Surfaced as a warning (with
// autofix to `() => t(...)`) wherever this is a real foot-gun:
// controlPanel files. Promote to `'error'` once the codebase is clean.
files: ['**/controlPanel.{ts,tsx,js,jsx}'],
rules: {
'i18n-strings/no-eager-t-in-config': 'warn',
},
},
{
// Disable custom rules in test/story files
files: [
'**/*.test.*',
'**/*.spec.*',
'**/*.stories.*',
'**/test/**',
'**/tests/**',
'**/spec/**',
'**/__tests__/**',
'**/__mocks__/**',
'cypress-base/**',
],
rules: {
'theme-colors/no-literal-colors': 'off',
'icons/no-fa-icons-usage': 'off',
'i18n-strings/no-template-vars': 'off',
},
},
];

View File

@@ -287,13 +287,15 @@
"ignorePatterns": [
"packages/generator-superset/**/*",
"cypress-base/**",
"node_modules/**",
"**/node_modules/**",
"build/**",
"dist/**",
"lib/**",
"esm/**",
"*.min.js",
"**/dist/**",
"**/lib/**",
"**/esm/**",
"**/*.min.js",
"**/*.d.ts",
"coverage/**",
"storybook-static/**",
".git/**",
"**/*.config.js",
"**/*.config.ts"

File diff suppressed because it is too large Load Diff

View File

@@ -173,19 +173,19 @@
"d3-color": "^3.1.0",
"d3-scale": "^4.0.2",
"dayjs": "^1.11.21",
"dom-to-image-more": "^3.7.2",
"dom-to-image-more": "^3.9.0",
"dom-to-pdf": "^0.3.2",
"echarts": "^5.6.0",
"fast-glob": "^3.3.2",
"fs-extra": "^11.3.5",
"fuse.js": "^7.3.0",
"fuse.js": "^7.4.2",
"geolib": "^3.3.14",
"geostyler": "^18.6.0",
"geostyler-data": "^1.1.0",
"geostyler-openlayers-parser": "^5.7.0",
"geostyler-style": "11.0.2",
"geostyler-wfs-parser": "^3.0.1",
"google-auth-library": "^10.6.2",
"google-auth-library": "^10.7.0",
"immer": "^11.1.8",
"interweave": "^13.1.1",
"jquery": "^4.0.0",
@@ -194,7 +194,7 @@
"json-stringify-pretty-compact": "^2.0.0",
"lodash": "^4.18.1",
"mapbox-gl": "^3.24.0",
"markdown-to-jsx": "^9.8.1",
"markdown-to-jsx": "^9.8.2",
"match-sorter": "^8.3.0",
"memoize-one": "^5.2.1",
"mousetrap": "^1.6.5",
@@ -203,13 +203,13 @@
"ol": "^10.9.0",
"query-string": "9.4.0",
"re-resizable": "^6.11.2",
"react": "^18.2.0",
"react-arborist": "^3.8.0",
"react": "^18.3.0",
"react-arborist": "^3.10.1",
"react-checkbox-tree": "^1.8.0",
"react-diff-viewer-continued": "^4.2.2",
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^18.2.0",
"react-dom": "^18.3.0",
"react-google-recaptcha": "^3.1.0",
"react-intersection-observer": "^10.0.3",
"react-json-tree": "^0.20.0",
@@ -261,16 +261,16 @@
"@babel/types": "^7.29.7",
"@emotion/babel-plugin": "^11.13.5",
"@emotion/jest": "^11.14.2",
"@formatjs/intl-durationformat": "^0.10.3",
"@formatjs/intl-durationformat": "^0.10.14",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@playwright/test": "^1.60.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
"@storybook/addon-docs": "10.4.1",
"@storybook/addon-links": "10.4.1",
"@storybook/react-webpack5": "10.4.1",
"@storybook/addon-docs": "10.4.3",
"@storybook/addon-links": "10.4.3",
"@storybook/react-webpack5": "10.4.3",
"@storybook/test-runner": "0.24.4",
"@svgr/webpack": "^8.1.0",
"@swc/core": "^1.15.40",
"@swc/core": "^1.15.41",
"@swc/plugin-emotion": "^14.12.0",
"@swc/plugin-transform-imports": "^12.5.0",
"@testing-library/dom": "^9.3.4",
@@ -280,13 +280,13 @@
"@types/content-disposition": "^0.5.9",
"@types/dom-to-image": "^2.6.7",
"@types/jest": "^30.0.0",
"@types/jquery": "^4.0.0",
"@types/jquery": "^4.0.1",
"@types/js-levenshtein": "^1.1.3",
"@types/json-bigint": "^1.0.4",
"@types/mousetrap": "^1.6.15",
"@types/node": "^25.9.1",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/node": "^25.9.3",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@types/react-loadable": "^5.5.11",
"@types/react-redux": "^7.1.10",
"@types/react-router-dom": "^5.3.3",
@@ -297,22 +297,22 @@
"@types/rison": "0.1.0",
"@types/tinycolor2": "^1.4.3",
"@types/unzipper": "^0.10.11",
"@typescript-eslint/eslint-plugin": "^8.60.1",
"@typescript-eslint/parser": "^8.59.4",
"@typescript-eslint/eslint-plugin": "^8.61.0",
"@typescript-eslint/parser": "^8.61.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",
"babel-plugin-lodash": "^3.3.4",
"baseline-browser-mapping": "^2.10.33",
"baseline-browser-mapping": "^2.10.35",
"cheerio": "1.2.0",
"concurrently": "^10.0.3",
"copy-webpack-plugin": "^14.0.0",
"cross-env": "^10.1.0",
"css-loader": "^7.1.4",
"css-minimizer-webpack-plugin": "^8.0.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^7.2.0",
"eslint": "^10.4.1",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-cypress": "^3.6.0",
@@ -320,12 +320,12 @@
"eslint-plugin-icons": "file:eslint-rules/eslint-plugin-icons",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest-dom": "^5.5.0",
"eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-lodash": "^8.0.0",
"eslint-plugin-no-only-tests": "^3.4.0",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react-prefer-function-component": "^5.0.0",
"eslint-plugin-react-you-might-not-need-an-effect": "^0.10.4",
"eslint-plugin-storybook": "10.4.1",
"eslint-plugin-react-you-might-not-need-an-effect": "^1.0.0",
"eslint-plugin-storybook": "10.4.3",
"eslint-plugin-testing-library": "^7.16.2",
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
"fetch-mock": "^12.6.0",
@@ -344,18 +344,19 @@
"lightningcss": "^1.32.0",
"mini-css-extract-plugin": "^2.10.2",
"open-cli": "^9.0.0",
"oxlint": "^1.68.0",
"oxlint": "^1.69.0",
"po2json": "^0.4.5",
"prettier": "3.8.3",
"prettier": "3.8.4",
"prettier-plugin-packagejson": "^3.0.2",
"process": "^0.11.10",
"react-dnd-test-backend": "^11.1.3",
"react-refresh": "^0.18.0",
"react-resizable": "^4.0.1",
"redux-mock-store": "^1.5.4",
"source-map": "^0.7.6",
"source-map-support": "^0.5.21",
"speed-measure-webpack-plugin": "^1.6.0",
"storybook": "10.4.1",
"storybook": "10.4.3",
"style-loader": "^4.0.0",
"swc-loader": "^0.2.7",
"terser-webpack-plugin": "^5.6.1",
@@ -368,9 +369,9 @@
"wait-on": "^9.0.10",
"webpack": "^5.107.2",
"webpack-bundle-analyzer": "^5.3.0",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.4",
"webpack-manifest-plugin": "^5.0.1",
"webpack-cli": "^7.0.3",
"webpack-dev-server": "^5.2.5",
"webpack-manifest-plugin": "^6.0.1",
"webpack-sources": "^3.5.0",
"webpack-visualizer-plugin2": "^2.0.0"
},
@@ -382,8 +383,8 @@
"regenerator-runtime": "^0.14.1"
},
"engines": {
"node": "^22.22.0",
"npm": "^10.8.1"
"node": "^24.16.0",
"npm": "^11.13.0"
},
"overrides": {
"uuid": "$uuid",
@@ -414,7 +415,16 @@
"@jest/types": "^30.4.0",
"jest-util": "^30.4.0",
"jest-circus": "^30.4.0",
"jest-environment-node": "^30.4.0"
"jest-environment-node": "^30.4.0",
"@babel/eslint-parser": {
"eslint": "$eslint"
},
"eslint-plugin-import": {
"eslint": "$eslint"
},
"eslint-plugin-jest-dom": {
"eslint": "$eslint"
}
},
"readme": "ERROR: No README data found!",
"scarfSettings": {

View File

@@ -37,7 +37,7 @@
"cross-env": "^10.1.0",
"fs-extra": "^11.3.5",
"jest": "^30.4.2",
"yeoman-test": "^11.5.2"
"yeoman-test": "^11.5.3"
},
"engines": {
"npm": ">= 4.0.0",

View File

@@ -97,8 +97,8 @@
"@fontsource/ibm-plex-mono": "^5.2.7",
"@fontsource/inter": "^5.2.6",
"nanoid": "^5.0.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^18.3.0",
"react-dom": "^18.3.0",
"react-loadable": "^5.5.0",
"tinycolor2": "*",
"lodash": "^4.18.1",

View File

@@ -37,7 +37,7 @@
* ```
*/
import { Disposable } from '../common';
import { Disposable, Event } from '../common';
/**
* Represents a menu item that links a view to a command.
@@ -102,3 +102,37 @@ export declare function registerMenuItem(
* ```
*/
export declare function getMenu(location: string): Menu | undefined;
/**
* Event fired when a menu item is registered.
*/
export interface MenuItemRegisteredEvent {
/** The menu item that was registered. */
item: MenuItem;
/** The location where the item was registered. */
location: string;
/** The group the item was placed in. */
group: 'primary' | 'secondary' | 'context';
}
/**
* Event fired when a menu item is unregistered.
*/
export interface MenuItemUnregisteredEvent {
/** The menu item that was unregistered. */
item: MenuItem;
/** The location where the item was registered. */
location: string;
/** The group the item was placed in. */
group: 'primary' | 'secondary' | 'context';
}
/**
* Event fired when a menu item is registered.
*/
export declare const onDidRegisterMenuItem: Event<MenuItemRegisteredEvent>;
/**
* Event fired when a menu item is unregistered.
*/
export declare const onDidUnregisterMenuItem: Event<MenuItemUnregisteredEvent>;

View File

@@ -26,7 +26,7 @@ test('t() warns and creates a default translator when called before configure',
const { t } = require('./TranslatorSingleton');
const result = t('hello');
expect(consoleSpy).toHaveBeenCalledWith(
'You should call configure(...) before calling other methods',
expect.stringMatching(/was called before configure\(\)/),
);
expect(result).toBe('hello');
consoleSpy.mockRestore();
@@ -54,7 +54,7 @@ test('resetTranslation resets the configured singleton', () => {
// After reset, calling t() should warn again
t('hello');
expect(consoleSpy).toHaveBeenCalledWith(
'You should call configure(...) before calling other methods',
expect.stringMatching(/was called before configure\(\)/),
);
consoleSpy.mockRestore();
});
@@ -96,6 +96,69 @@ test('tn() calls translateWithNumber on the singleton', () => {
});
});
test('pre-configure warning fires once per unique key', () => {
jest.isolateModules(() => {
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const { t } = require('./TranslatorSingleton');
t('apple');
t('apple');
t('apple');
t('banana');
expect(consoleSpy).toHaveBeenCalledTimes(2);
expect(consoleSpy).toHaveBeenNthCalledWith(
1,
expect.stringContaining('"apple"'),
);
expect(consoleSpy).toHaveBeenNthCalledWith(
2,
expect.stringContaining('"banana"'),
);
consoleSpy.mockRestore();
});
});
test('pre-configure warning suggests the lazy-function fix', () => {
jest.isolateModules(() => {
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const { t } = require('./TranslatorSingleton');
t('Sort ascending');
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('() => t("Sort ascending")'),
);
consoleSpy.mockRestore();
});
});
test('pre-configure warning is suppressed in production', () => {
jest.isolateModules(() => {
const originalEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const { t } = require('./TranslatorSingleton');
t('hello');
expect(consoleSpy).not.toHaveBeenCalled();
consoleSpy.mockRestore();
if (originalEnv !== undefined) {
process.env.NODE_ENV = originalEnv;
} else {
delete process.env.NODE_ENV;
}
});
});
test('resetTranslation clears the warned-keys dedupe set', () => {
jest.isolateModules(() => {
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const { t, resetTranslation } = require('./TranslatorSingleton');
t('hello');
expect(consoleSpy).toHaveBeenCalledTimes(1);
resetTranslation();
t('hello');
expect(consoleSpy).toHaveBeenCalledTimes(2);
consoleSpy.mockRestore();
});
});
test('resetTranslation does nothing when not yet configured', () => {
jest.isolateModules(() => {
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
@@ -105,7 +168,7 @@ test('resetTranslation does nothing when not yet configured', () => {
// The singleton is still unconfigured, so t() warns
t('hello');
expect(consoleSpy).toHaveBeenCalledWith(
'You should call configure(...) before calling other methods',
expect.stringMatching(/was called before configure\(\)/),
);
consoleSpy.mockRestore();
});

View File

@@ -25,6 +25,10 @@ import { TranslatorConfig, Translations, LocaleData } from './types';
let singleton: Translator | undefined;
let isConfigured = false;
// Tracks which keys have already triggered a pre-configure warning so the
// logs don't drown in repeated calls from large module-load fan-outs.
const warnedPreConfigureKeys = new Set<string>();
function configure(config?: TranslatorConfig) {
singleton = new Translator(config);
isConfigured = true;
@@ -33,10 +37,6 @@ function configure(config?: TranslatorConfig) {
}
function getInstance() {
if (!isConfigured) {
console.warn('You should call configure(...) before calling other methods');
}
if (typeof singleton === 'undefined') {
singleton = new Translator();
}
@@ -44,11 +44,32 @@ function getInstance() {
return singleton;
}
function warnPreConfigure(fn: 't' | 'tn', key: string) {
// Only warn in non-production builds — production callers may legitimately
// tolerate the fallback, and the noise isn't useful at runtime.
if (
typeof process !== 'undefined' &&
process.env?.NODE_ENV === 'production'
) {
return;
}
if (warnedPreConfigureKeys.has(key)) return;
warnedPreConfigureKeys.add(key);
console.warn(
`[i18n] ${fn}(${JSON.stringify(key)}) was called before configure() — ` +
`the result is the fallback language and will not update when the ` +
`user switches language. If this call is at module load (e.g., a ` +
`controlPanel \`label\`/\`description\`), wrap it in an arrow ` +
`function: \`() => ${fn}(${JSON.stringify(key)})\`.`,
);
}
function resetTranslation() {
if (isConfigured) {
isConfigured = false;
singleton = undefined;
}
warnedPreConfigureKeys.clear();
}
function addTranslation(key: string, translations: string[]) {
@@ -64,10 +85,12 @@ function addLocaleData(data: LocaleData) {
}
function t(input: string, ...args: unknown[]) {
if (!isConfigured) warnPreConfigure('t', input);
return getInstance().translate(input, ...args);
}
function tn(key: string, ...args: unknown[]) {
if (!isConfigured) warnPreConfigure('tn', key);
return getInstance().translateWithNumber(key, ...args);
}

View File

@@ -16,13 +16,26 @@
* specific language governing permissions and limitations
* under the License.
*/
export {}; // ensure this file is treated as a module so top-level declarations don't leak into global scope
type LoggingModule = typeof import('./index');
const loadLogging = (): LoggingModule['logging'] => {
let logging: LoggingModule['logging'] | undefined;
jest.isolateModules(() => {
({ logging } = jest.requireActual<LoggingModule>(
'@apache-superset/core/utils',
));
});
return logging!;
};
beforeEach(() => {
jest.resetModules();
jest.resetAllMocks();
});
test('should pipe to `console` methods', () => {
const { logging } = require('@apache-superset/core/utils');
const logging = loadLogging();
jest.spyOn(logging, 'debug').mockImplementation();
jest.spyOn(logging, 'log').mockImplementation();
@@ -50,20 +63,24 @@ test('should pipe to `console` methods', () => {
});
test('should use noop functions when console unavailable', () => {
const originalConsole = window.console;
Object.assign(window, { console: undefined });
const { logging } = require('@apache-superset/core/utils');
try {
const logging = loadLogging();
expect(() => {
logging.debug();
logging.log();
logging.info();
logging.warn('warn');
logging.error('error');
logging.trace();
logging.table([
[1, 2],
[3, 4],
]);
}).not.toThrow();
Object.assign(window, { console });
expect(() => {
logging.debug();
logging.log();
logging.info();
logging.warn('warn');
logging.error('error');
logging.trace();
logging.table([
[1, 2],
[3, 4],
]);
}).not.toThrow();
} finally {
Object.assign(window, { console: originalConsole });
}
});

View File

@@ -36,7 +36,7 @@
*/
import { ReactElement } from 'react';
import { Disposable } from '../common';
import { Disposable, Event } from '../common';
/**
* Represents a contributed view in the application.
@@ -88,3 +88,33 @@ export declare function registerView(
* ```
*/
export declare function getViews(location: string): View[] | undefined;
/**
* Event fired when a view is registered.
*/
export interface ViewRegisteredEvent {
/** The descriptor of the view that was registered. */
view: View;
/** The location where the view was registered. */
location: string;
}
/**
* Event fired when a view is unregistered.
*/
export interface ViewUnregisteredEvent {
/** The descriptor of the view that was unregistered. */
view: View;
/** The location where the view was registered. */
location: string;
}
/**
* Event fired when a view is registered.
*/
export declare const onDidRegisterView: Event<ViewRegisteredEvent>;
/**
* Event fired when a view is unregistered.
*/
export declare const onDidUnregisterView: Event<ViewUnregisteredEvent>;

View File

@@ -40,9 +40,9 @@
"ace-builds": "^1.4.14",
"brace": "^0.11.1",
"memoize-one": "^5.1.1",
"react": "^18.2.0",
"react": "^18.3.0",
"react-ace": "^10.1.0",
"react-dom": "^18.2.0"
"react-dom": "^18.3.0"
},
"publishConfig": {
"access": "public"

View File

@@ -204,8 +204,14 @@ export type TabOverride = 'data' | 'customize' | 'matrixify' | boolean;
* these configs will be passed to the UI component for control as props.
*
* - type: the control type, referencing a React component of the same name
* - label: the label as shown in the control's header
* - description: shown in the info tooltip of the control's header
* - label: the label as shown in the control's header. When the value involves
* `t()`/`tn()`, prefer the arrow-function form (`label: () => t('Foo')`) so
* the lookup runs at render time rather than at module load — eager
* `label: t('Foo')` captures the fallback language before i18n initializes
* and does not update on runtime language change. The
* `i18n-strings/no-eager-t-in-config` lint rule autofixes this.
* - description: shown in the info tooltip of the control's header. Same
* lazy-form guidance as `label`.
* - default: the default value when opening a new chart, or changing visualization type
* - renderTrigger: a bool that defines whether the visualization should be re-rendered
* when changed. This should `true` for controls that only affect the rendering (client side)

View File

@@ -43,7 +43,7 @@
"d3-time": "^3.1.0",
"d3-time-format": "^4.1.0",
"dayjs": "^1.11.21",
"dompurify": "^3.4.7",
"dompurify": "^3.4.11",
"fetch-retry": "^6.0.0",
"handlebars": "^4.7.9",
"jed": "^1.1.1",
@@ -75,9 +75,9 @@
"@types/d3-scale": "^2.1.1",
"@types/d3-time": "^3.0.4",
"@types/d3-time-format": "^4.0.3",
"@types/jquery": "^4.0.0",
"@types/jquery": "^4.0.1",
"@types/lodash": "^4.17.24",
"@types/node": "^25.9.1",
"@types/node": "^25.9.3",
"@types/prop-types": "^15.7.15",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/react-table": "^7.7.20",
@@ -101,8 +101,8 @@
"@types/tinycolor2": "*",
"antd": "^5.26.0",
"nanoid": "^5.0.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^18.3.0",
"react-dom": "^18.3.0",
"react-loadable": "^5.5.0",
"tinycolor2": "*"
},

View File

@@ -72,6 +72,7 @@ test('should generate a 2x2 grid for metrics mode', () => {
createAdhocMetric('Revenue'),
createSqlMetric('Q1', 'SUM(CASE WHEN quarter = 1 THEN value END)'),
]);
expect(firstCell!.formData.metric).toEqual(createAdhocMetric('Revenue'));
});
test('should generate grid for dimensions mode', () => {
@@ -213,6 +214,9 @@ test('should skip missing column metrics when generating cell form data', () =>
expect(grid!.cells[0][0]!.formData.metrics).toEqual([
createAdhocMetric('Revenue'),
]);
expect(grid!.cells[0][0]!.formData.metric).toEqual(
createAdhocMetric('Revenue'),
);
});
test('should not escape HTML entities in cell titles', () => {
@@ -471,6 +475,51 @@ test('should handle metrics without labels', () => {
expect(grid!.colHeaders).toEqual(['count']);
});
test('should set singular metric for singular-metric chart types like Pie', () => {
const rowMetricFormData: TestFormData = {
viz_type: 'pie',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'metrics',
matrixify_rows: [createAdhocMetric('Revenue'), createAdhocMetric('Profit')],
};
const grid = generateMatrixifyGrid(rowMetricFormData);
expect(grid).not.toBeNull();
expect(grid!.cells[0][0]!.formData.metrics).toEqual([
createAdhocMetric('Revenue'),
]);
expect(grid!.cells[0][0]!.formData.metric).toEqual(
createAdhocMetric('Revenue'),
);
expect(grid!.cells[1][0]!.formData.metrics).toEqual([
createAdhocMetric('Profit'),
]);
expect(grid!.cells[1][0]!.formData.metric).toEqual(
createAdhocMetric('Profit'),
);
});
test('should not overwrite singular metric in dimension-only mode', () => {
const dimensionFormData: TestFormData = {
viz_type: 'pie',
datasource: '1__table',
matrixify_enable: true,
matrixify_mode_rows: 'dimensions',
matrixify_dimension_rows: {
dimension: 'country',
values: ['USA', 'Canada'],
},
metric: 'existing_metric',
};
const grid = generateMatrixifyGrid(dimensionFormData);
expect(grid).not.toBeNull();
expect(grid!.cells[0][0]!.formData.metric).toBe('existing_metric');
});
test('should preserve slice_id and dashboardId for embedded dashboard permissions', () => {
const formDataWithDashboardContext: TestFormData = {
...baseFormData,

View File

@@ -197,6 +197,7 @@ function generateCellFormData(
// If we have metrics from the matrix, use them; otherwise keep original
if (metrics.length > 0) {
cellFormData.metrics = metrics;
cellFormData.metric = metrics[0];
}
return cellFormData;

View File

@@ -18,6 +18,7 @@
*/
import { createRef } from 'react';
import { render, screen, waitFor } from '@superset-ui/core/spec';
import { supersetTheme } from '@apache-superset/core/theme';
import type AceEditor from 'react-ace';
import {
AsyncAceEditor,
@@ -28,6 +29,7 @@ import {
CssEditor,
JsonEditor,
ConfigEditor,
aceCompletionHighlightStyles,
} from '.';
import type { AceModule, AsyncAceEditorOptions } from './types';
@@ -42,6 +44,17 @@ test('renders SQLEditor', async () => {
});
});
test('themes the autocomplete completion highlight from the theme', () => {
// Ace ships a hardcoded `color: #000` for the matched-prefix highlight, which
// is invisible on the dark autocomplete popup. The shared editor overrides it
// from the theme so every Ace editor (SQL Lab, Explore Custom SQL, ...) stays
// consistent.
const { styles } = aceCompletionHighlightStyles(supersetTheme);
expect(styles).toContain('.ace_completion-highlight');
expect(styles).toContain(supersetTheme.colorPrimaryText);
});
test('SQLEditor uses fontFamilyCode from theme', async () => {
const ref = createRef<AceEditor>();
const { container } = render(<SQLEditor ref={ref as React.Ref<never>} />);

View File

@@ -32,7 +32,7 @@ import {
AsyncEsmComponent,
PlaceholderProps,
} from '@superset-ui/core/components/AsyncEsmComponent';
import { useTheme, css } from '@apache-superset/core/theme';
import { useTheme, css, type SupersetTheme } from '@apache-superset/core/theme';
import { Global } from '@emotion/react';
export { getTooltipHTML } from './Tooltip';
@@ -105,6 +105,19 @@ export type AsyncAceEditorOptions = {
> | null;
};
/**
* Theme-aware styling for the matched-prefix highlight in the autocomplete
* popup. Ace ships a hardcoded `color: #000` that is invisible on the dark
* popup, so the override needs `!important` to win. Lives in the shared editor
* so every Ace editor (SQL Lab, Explore Custom SQL, ...) stays consistent.
*/
export const aceCompletionHighlightStyles = (token: SupersetTheme) => css`
.ace_completion-highlight {
color: ${token.colorPrimaryText} !important;
background-color: ${token.colorPrimaryBgHover};
}
`;
/**
* Get an async AceEditor with automatical loading of specified ace modules.
*/
@@ -370,6 +383,8 @@ export function AsyncAceEditor(
display: flex !important;
}
${aceCompletionHighlightStyles(token)}
&&& .tooltip-detail {
display: flex;
justify-content: center;

View File

@@ -17,15 +17,23 @@
* under the License.
*/
import { forwardRef } from 'react';
import { Avatar as AntdAvatar } from 'antd';
import type { AvatarProps, GroupProps as AvatarGroupProps } from './types';
export function Avatar(props: AvatarProps) {
return <AntdAvatar {...props} />;
}
export const Avatar = forwardRef<HTMLSpanElement, AvatarProps>((props, ref) => (
<AntdAvatar ref={ref} {...props} />
));
export function AvatarGroup(props: AvatarGroupProps) {
return <AntdAvatar.Group {...props} />;
}
// antd Avatar.Group is a plain function component without forwardRef; wrap in
// a span so this component can be a Tooltip / Popover trigger and skip the
// findDOMNode fallback.
export const AvatarGroup = forwardRef<HTMLSpanElement, AvatarGroupProps>(
(props, ref) => (
<span ref={ref}>
<AntdAvatar.Group {...props} />
</span>
),
);
export type { AvatarProps, AvatarGroupProps };

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Children, ReactElement, Fragment } from 'react';
import { Children, ReactElement, Fragment, forwardRef, Ref } from 'react';
import cx from 'classnames';
import { Button as AntdButton } from 'antd';
import { useTheme } from '@apache-superset/core/theme';
@@ -100,7 +100,7 @@ const BUTTON_STYLE_MAP: Record<
link: { type: 'link' },
};
export function Button(props: ButtonProps) {
function ButtonInner(props: ButtonProps, ref: Ref<HTMLElement>) {
const {
tooltip,
placement,
@@ -160,6 +160,7 @@ export function Button(props: ButtonProps) {
const button = (
<AntdButton
ref={ref as Ref<HTMLButtonElement & HTMLAnchorElement>}
href={disabled ? undefined : href}
disabled={disabled}
type={antdType}
@@ -235,4 +236,6 @@ export function Button(props: ButtonProps) {
return button;
}
export const Button = forwardRef<HTMLElement, ButtonProps>(ButtonInner);
export type { ButtonProps, OnClickHandler };

View File

@@ -75,7 +75,10 @@ export const DropdownButton = ({
id={`${kebabCase(tooltip)}-tooltip`}
title={tooltip}
>
{button}
{/* antd Dropdown.Button is a plain function component without
forwardRef; wrap in a span so the Tooltip can attach a ref to a
real DOM node and skip the findDOMNode fallback. */}
<span>{button}</span>
</Tooltip>
);
}

View File

@@ -70,11 +70,46 @@ test('a change event that arrives before isEditing flips is not dropped', () =>
});
test('prop changes mid-edit do not clobber unsaved typing', async () => {
const { rerender } = render(<Harness initialTitle="Foo" />);
// Rerender DynamicEditableTitle directly with a changed title prop so the
// sync effect actually runs. Going through Harness would not exercise the
// bug because Harness owns its own state and only reads initialTitle once.
const onSave = jest.fn();
const props = {
placeholder: 'placeholder',
canEdit: true,
label: 'Title',
onSave,
};
const { rerender } = render(<DynamicEditableTitle {...props} title="Foo" />);
const input = screen.getByRole('textbox') as HTMLInputElement;
userEvent.click(input);
await userEvent.type(input, 'X', { delay: 1 });
expect(input.value).toBe('FooX');
rerender(<Harness initialTitle="Foo" />);
rerender(<DynamicEditableTitle {...props} title="Bar" />);
expect(input.value).toBe('FooX');
// Locks in commit semantics: blur after a real edit must persist the
// user's typed value, even when a competing parent-driven title arrived
// mid-edit.
fireEvent.blur(input);
expect(onSave).toHaveBeenCalledWith('FooX');
});
test('passive focus then parent-driven title change then blur does not revert', () => {
// Phantom-revert scenario: user clicks the input but does not type, the
// parent autosaves a new title from elsewhere, then the user blurs. The
// component must NOT call onSave with the stale local value, otherwise it
// would silently overwrite the parent's update.
const onSave = jest.fn();
const props = {
placeholder: 'placeholder',
canEdit: true,
label: 'Title',
onSave,
};
const { rerender } = render(<DynamicEditableTitle {...props} title="Foo" />);
const input = screen.getByRole('textbox') as HTMLInputElement;
userEvent.click(input);
rerender(<DynamicEditableTitle {...props} title="Bar" />);
fireEvent.blur(input);
expect(onSave).not.toHaveBeenCalled();
});

View File

@@ -81,12 +81,25 @@ export const DynamicEditableTitle = memo(
const sizerRef = useRef<HTMLSpanElement>(null);
const inputRef = useRef<InputRef>(null);
// Tracks whether the user has actually typed since entering edit mode.
// Gates onSave so that passive focus (click without typing) followed by a
// parent-driven title change and blur does not silently revert the
// parent's update with our stale currentTitle.
const dirtyRef = useRef(false);
const { width: containerWidth, ref: containerRef } = useResizeDetector({
refreshMode: 'debounce',
});
useEffect(() => {
setCurrentTitle(title);
// Don't overwrite in-flight user input when the parent re-renders with a
// new title prop mid-edit. handleBlur already syncs currentTitle on commit;
// re-running this effect when isEditing flips would resync to a stale
// title prop, so isEditing is intentionally read via closure rather than
// listed as a dep.
if (!isEditing) {
setCurrentTitle(title);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [title]);
useEffect(() => {
if (isEditing) {
@@ -138,10 +151,19 @@ export const DynamicEditableTitle = memo(
return;
}
const formattedTitle = currentTitle.trim();
setCurrentTitle(formattedTitle);
if (title !== formattedTitle) {
// Only commit when the user actually typed. Passive focus must not
// overwrite a parent-driven title change that landed mid-edit.
if (dirtyRef.current && title !== formattedTitle) {
setCurrentTitle(formattedTitle);
onSave(formattedTitle);
} else if (!dirtyRef.current) {
// Drop any stale local state and resync to the latest title prop so a
// subsequent edit starts from the current parent value.
setCurrentTitle(title);
} else {
setCurrentTitle(formattedTitle);
}
dirtyRef.current = false;
setIsEditing(false);
}, [canEdit, currentTitle, onSave, title]);
@@ -158,6 +180,7 @@ export const DynamicEditableTitle = memo(
if (!isEditing) {
setIsEditing(true);
}
dirtyRef.current = true;
setCurrentTitle(ev.target.value);
},
[canEdit, isEditing],

View File

@@ -240,7 +240,10 @@ export function EditableTitle({
t("You don't have the rights to alter this title.")
}
>
{titleComponent}
{/* Wrap in span so the Tooltip can attach a ref to a DOM element.
antd's Input.TextArea forwards a non-DOM imperative handle, which
triggers a React 18 findDOMNode deprecation warning. */}
<span>{titleComponent}</span>
</Tooltip>
);
}

View File

@@ -16,47 +16,54 @@
* specific language governing permissions and limitations
* under the License.
*/
import { forwardRef } from 'react';
import { Tooltip } from '../Tooltip';
import { Button } from '../Button';
import type { IconTooltipProps } from './types';
export const IconTooltip = ({
children = null,
className = '',
onClick = () => undefined,
placement = 'top',
style = {},
tooltip = null,
mouseEnterDelay = 0.3,
mouseLeaveDelay = 0.15,
}: IconTooltipProps) => {
const iconTooltip = (
<Button
onClick={onClick}
style={{
padding: 0,
...style,
}}
buttonStyle="link"
className={`IconTooltip ${className}`}
>
{children}
</Button>
);
if (tooltip) {
return (
<Tooltip
id="tooltip"
title={tooltip}
placement={placement}
mouseEnterDelay={mouseEnterDelay}
mouseLeaveDelay={mouseLeaveDelay}
export const IconTooltip = forwardRef<HTMLElement, IconTooltipProps>(
(
{
children = null,
className = '',
onClick = () => undefined,
placement = 'top',
style = {},
tooltip = null,
mouseEnterDelay = 0.3,
mouseLeaveDelay = 0.15,
},
ref,
) => {
const iconTooltip = (
<Button
ref={ref}
onClick={onClick}
style={{
padding: 0,
...style,
}}
buttonStyle="link"
className={`IconTooltip ${className}`}
>
{iconTooltip}
</Tooltip>
{children}
</Button>
);
}
return iconTooltip;
};
if (tooltip) {
return (
<Tooltip
id="tooltip"
title={tooltip}
placement={placement}
mouseEnterDelay={mouseEnterDelay}
mouseLeaveDelay={mouseLeaveDelay}
>
{iconTooltip}
</Tooltip>
);
}
return iconTooltip;
},
);
export type { IconTooltipProps };

View File

@@ -165,7 +165,7 @@ import {
SlackOutlined,
ApiOutlined,
} from '@ant-design/icons';
import { FC } from 'react';
import { ForwardRefExoticComponent, RefAttributes, forwardRef } from 'react';
import { IconType } from './types';
import { BaseIconComponent } from './BaseIcon';
@@ -323,19 +323,25 @@ type AntdIconNames = keyof typeof AntdIcons;
export const antdEnhancedIcons: Record<
AntdIconNames,
FC<IconType>
ForwardRefExoticComponent<IconType & RefAttributes<HTMLSpanElement>>
> = Object.keys(AntdIcons)
.filter(key => !EXCLUDED_ICONS.some(excluded => key.includes(excluded)))
.reduce(
(acc, key) => {
acc[key as AntdIconNames] = (props: IconType) => (
<BaseIconComponent
component={AntdIcons[key as AntdIconNames]}
fileName={key}
{...props}
/>
acc[key as AntdIconNames] = forwardRef<HTMLSpanElement, IconType>(
(props, ref) => (
<BaseIconComponent
ref={ref}
component={AntdIcons[key as AntdIconNames]}
fileName={key}
{...props}
/>
),
);
return acc;
},
{} as Record<AntdIconNames, FC<IconType>>,
{} as Record<
AntdIconNames,
ForwardRefExoticComponent<IconType & RefAttributes<HTMLSpanElement>>
>,
);

View File

@@ -17,12 +17,12 @@
* under the License.
*/
import { FC, SVGProps, useEffect, useRef, useState } from 'react';
import { FC, SVGProps, forwardRef, useEffect, useRef, useState } from 'react';
import TransparentIcon from './svgs/transparent.svg';
import { IconType } from './types';
import { BaseIconComponent } from './BaseIcon';
const AsyncIcon = (props: IconType) => {
const AsyncIcon = forwardRef<HTMLSpanElement, IconType>((props, ref) => {
const [, setLoaded] = useState(false);
const ImportedSVG = useRef<FC<SVGProps<SVGSVGElement>>>();
const { fileName, customIcons, iconSize, iconColor, viewBox, ...restProps } =
@@ -46,6 +46,7 @@ const AsyncIcon = (props: IconType) => {
return (
<BaseIconComponent
ref={ref}
component={ImportedSVG.current || TransparentIcon}
fileName={fileName}
customIcons={customIcons}
@@ -55,6 +56,6 @@ const AsyncIcon = (props: IconType) => {
{...restProps}
/>
);
};
});
export default AsyncIcon;

View File

@@ -17,6 +17,7 @@
* under the License.
*/
import { forwardRef, type ComponentType } from 'react';
import { css, useTheme, getFontSize } from '@apache-superset/core/theme';
import { AntdIconType, BaseIconProps, CustomIconType, IconType } from './types';
@@ -35,65 +36,78 @@ const genAriaLabel = (fileName: string) => {
return name.toLowerCase();
};
export const BaseIconComponent: React.FC<
export const BaseIconComponent = forwardRef<
HTMLSpanElement | SVGSVGElement,
BaseIconProps & Omit<IconType, 'component'>
> = ({
component: Component,
iconColor,
iconSize,
viewBox,
customIcons,
fileName,
...rest
}) => {
const theme = useTheme();
const whatRole = rest?.onClick ? 'button' : 'img';
const ariaLabel = genAriaLabel(fileName || '');
const style = {
color: iconColor,
fontSize: iconSize
? `${getFontSize(theme, iconSize)}px`
: `${theme.fontSize}px`,
cursor: rest?.onClick ? 'pointer' : undefined,
};
>(
(
{
component: Component,
iconColor,
iconSize,
viewBox,
customIcons,
fileName,
...rest
},
ref,
) => {
const theme = useTheme();
const whatRole = rest?.onClick ? 'button' : 'img';
const ariaLabel = genAriaLabel(fileName || '');
const style = {
color: iconColor,
fontSize: iconSize
? `${getFontSize(theme, iconSize)}px`
: `${theme.fontSize}px`,
cursor: rest?.onClick ? 'pointer' : undefined,
};
return customIcons ? (
<span
role={whatRole}
aria-label={ariaLabel}
data-test={ariaLabel}
css={[
css`
display: inline-flex;
align-items: center;
line-height: 0;
vertical-align: middle;
`,
]}
>
<Component
viewBox={viewBox || '0 0 24 24'}
const AntdComponent = Component as ComponentType<
Record<string, unknown> & {
ref?: React.Ref<HTMLSpanElement | SVGSVGElement>;
}
>;
return customIcons ? (
<span
ref={ref as React.Ref<HTMLSpanElement>}
role={whatRole}
aria-label={ariaLabel}
data-test={ariaLabel}
css={[
css`
display: inline-flex;
align-items: center;
line-height: 0;
vertical-align: middle;
`,
]}
>
<Component
viewBox={viewBox || '0 0 24 24'}
style={style}
width={
iconSize
? `${getFontSize(theme, iconSize) || theme.fontSize}px`
: `${theme.fontSize}px`
}
height={
iconSize
? `${getFontSize(theme, iconSize) || theme.fontSize}px`
: `${theme.fontSize}px`
}
{...(rest as CustomIconType)}
/>
</span>
) : (
<AntdComponent
ref={ref}
role={whatRole}
style={style}
width={
iconSize
? `${getFontSize(theme, iconSize) || theme.fontSize}px`
: `${theme.fontSize}px`
}
height={
iconSize
? `${getFontSize(theme, iconSize) || theme.fontSize}px`
: `${theme.fontSize}px`
}
{...(rest as CustomIconType)}
aria-label={ariaLabel}
data-test={ariaLabel}
{...(rest as AntdIconType)}
/>
</span>
) : (
<Component
role={whatRole}
style={style}
aria-label={ariaLabel}
data-test={ariaLabel}
{...(rest as AntdIconType)}
/>
);
};
);
},
);

View File

@@ -17,12 +17,16 @@
* under the License.
*/
import { FC } from 'react';
import { ForwardRefExoticComponent, RefAttributes, forwardRef } from 'react';
import { antdEnhancedIcons } from './AntdEnhanced';
import AsyncIcon from './AsyncIcon';
import type { IconType } from './types';
type IconComponent = ForwardRefExoticComponent<
IconType & RefAttributes<HTMLSpanElement>
>;
/**
* Filename is going to be inferred from the icon name.
* i.e. BigNumberChartTile => assets/images/icons/big_number_chart_tile
@@ -58,15 +62,17 @@ const customIcons = [
'Undo',
] as const;
type CustomIconType = Record<(typeof customIcons)[number], FC<IconType>>;
type CustomIconType = Record<(typeof customIcons)[number], IconComponent>;
const iconOverrides: CustomIconType = {} as CustomIconType;
customIcons.forEach(customIcon => {
const fileName = customIcon
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
.toLowerCase();
iconOverrides[customIcon] = (props: IconType) => (
<AsyncIcon customIcons fileName={fileName} {...props} />
iconOverrides[customIcon] = forwardRef<HTMLSpanElement, IconType>(
(props, ref) => (
<AsyncIcon ref={ref} customIcons fileName={fileName} {...props} />
),
);
});
@@ -74,7 +80,7 @@ export type IconNameType =
| keyof typeof antdEnhancedIcons
| keyof typeof iconOverrides;
type IconComponentType = Record<IconNameType, FC<IconType>>;
type IconComponentType = Record<IconNameType, IconComponent>;
export const Icons: IconComponentType = {
...antdEnhancedIcons,

View File

@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { forwardRef } from 'react';
import { Tag } from '@superset-ui/core/components/Tag';
import { css } from '@emotion/react';
import { useTheme, getColorVariants } from '@apache-superset/core/theme';
@@ -23,7 +24,7 @@ import { DatasetTypeLabel } from './reusable/DatasetTypeLabel';
import { PublishedLabel } from './reusable/PublishedLabel';
import type { LabelProps } from './types';
export function Label(props: LabelProps) {
export const Label = forwardRef<HTMLSpanElement, LabelProps>((props, ref) => {
const theme = useTheme();
// Use Ant Design's motion duration instead of deprecated transitionTiming
const {
@@ -71,6 +72,7 @@ export function Label(props: LabelProps) {
return (
<Tag
ref={ref}
onClick={onClick}
role={onClick ? 'button' : undefined}
style={style}
@@ -81,6 +83,6 @@ export function Label(props: LabelProps) {
{children}
</Tag>
);
}
});
export { DatasetTypeLabel, PublishedLabel };
export type { LabelType } from './types';

View File

@@ -371,6 +371,9 @@ const CustomModal = ({
disabled={!draggable || dragDisabled}
bounds={bounds ?? false}
onStart={(event, uiData) => onDragStart(event, uiData)}
// Pass nodeRef so react-draggable does not fall back to
// ReactDOM.findDOMNode (deprecated in React 18+ Strict Mode).
nodeRef={draggableRef}
{...draggableConfig}
>
{resizable ? (

View File

@@ -16,11 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
import { forwardRef } from 'react';
import { Popover as AntdPopover } from 'antd';
import { PopoverProps as AntdPopoverProps } from 'antd/es/popover';
import type { TooltipRef } from 'antd/es/tooltip';
export interface PopoverProps extends AntdPopoverProps {
forceRender?: boolean;
}
export const Popover = (props: PopoverProps) => <AntdPopover {...props} />;
export const Popover = forwardRef<TooltipRef, PopoverProps>((props, ref) => (
<AntdPopover ref={ref} {...props} />
));

View File

@@ -16,10 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
import { MouseEventHandler, forwardRef } from 'react';
import { MouseEventHandler } from 'react';
import { SupersetTheme } from '@apache-superset/core/theme';
import { Icons } from '@superset-ui/core/components/Icons';
import type { IconType } from '@superset-ui/core/components/Icons/types';
import { Tooltip } from '../Tooltip';
export interface RefreshLabelProps {
@@ -32,25 +31,19 @@ const RefreshLabel = ({
onClick,
tooltipContent,
disabled,
}: RefreshLabelProps) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const IconWithoutRef = forwardRef((props: IconType, ref: any) => (
<Icons.SyncOutlined iconSize="l" {...props} />
));
return (
<Tooltip title={tooltipContent}>
<IconWithoutRef
role="button"
onClick={disabled ? undefined : onClick}
css={(theme: SupersetTheme) => ({
cursor: 'pointer',
color: theme.colorIcon,
'&:hover': { color: theme.colorPrimary },
})}
/>
</Tooltip>
);
};
}: RefreshLabelProps) => (
<Tooltip title={tooltipContent}>
<Icons.SyncOutlined
iconSize="l"
role="button"
onClick={disabled ? undefined : onClick}
css={(theme: SupersetTheme) => ({
cursor: 'pointer',
color: theme.colorIcon,
'&:hover': { color: theme.colorPrimary },
})}
/>
</Tooltip>
);
export default RefreshLabel;

View File

@@ -16,17 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
import { forwardRef } from 'react';
import { Tooltip as AntdTooltip } from 'antd';
import type { TooltipRef } from 'antd/es/tooltip';
import type { TooltipProps, TooltipPlacement } from './types';
export const Tooltip = ({ overlayStyle, ...props }: TooltipProps) => (
<AntdTooltip
styles={{
body: { overflow: 'hidden', textOverflow: 'ellipsis' },
root: overlayStyle ?? {},
}}
{...props}
/>
export const Tooltip = forwardRef<TooltipRef, TooltipProps>(
({ overlayStyle, ...props }, ref) => (
<AntdTooltip
ref={ref}
styles={{
body: { overflow: 'hidden', textOverflow: 'ellipsis' },
root: overlayStyle ?? {},
}}
{...props}
/>
),
);
export type { TooltipProps, TooltipPlacement };

View File

@@ -52,6 +52,13 @@ const SupersetClient: SupersetClientInterface = {
request: request => getInstance().request(request),
getCSRFToken: () => getInstance().getCSRFToken(),
getUrl: (...args) => getInstance().getUrl(...args),
get guestTokenHeaderName() {
try {
return getInstance().guestTokenHeaderName;
} catch {
return 'X-GuestToken';
}
},
};
export default SupersetClient;

View File

@@ -163,6 +163,7 @@ export interface SupersetClientInterface extends Pick<
configure: (config?: ClientConfig) => SupersetClientInterface;
reset: () => void;
getCSRFToken: () => CsrfPromise;
guestTokenHeaderName?: string;
}
export type SupersetClientResponse = Response | JsonResponse | TextResponse;

View File

@@ -35,3 +35,4 @@ export * from './typedMemo';
export * from './html';
export * from './tooltip';
export * from './merge';
export * from './mapStyles';

View File

@@ -0,0 +1,295 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
getBootstrapDataFromDocument,
getDefaultMapRenderer,
getMapProviderMapStyle,
getMapboxApiKeyFromBootstrap,
getMapRendererOptions,
hasMapboxApiKey,
isRasterTileTemplate,
OSM_TILE_ATTRIBUTION,
OSM_TILE_STYLE_URL,
resolveMapStyle,
} from './mapStyles';
test('OSM style metadata uses the approved URL and attribution', () => {
expect(OSM_TILE_STYLE_URL).toBe(
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
);
expect(OSM_TILE_ATTRIBUTION).toBe('© OpenStreetMap contributors');
});
test('Mapbox key helpers report absence and presence from bootstrap data', () => {
expect(getMapboxApiKeyFromBootstrap({ common: { conf: {} } })).toBe('');
expect(hasMapboxApiKey({ common: { conf: {} } })).toBe(false);
expect(
getMapboxApiKeyFromBootstrap({
common: { conf: { MAPBOX_API_KEY: 'pk.test' } },
}),
).toBe('pk.test');
expect(
getMapboxApiKeyFromBootstrap({
common: { conf: { MAPBOX_API_KEY: ' pk.test ' } },
}),
).toBe('pk.test');
expect(hasMapboxApiKey({ common: { conf: { MAPBOX_API_KEY: ' ' } } })).toBe(
false,
);
expect(
hasMapboxApiKey({ common: { conf: { MAPBOX_API_KEY: 'pk.test' } } }),
).toBe(true);
});
test('bootstrap data helper parses document data safely', () => {
document.body.innerHTML = `<div id="app" data-bootstrap='${JSON.stringify({
common: { conf: { MAPBOX_API_KEY: 'pk.document' } },
})}'></div>`;
expect(getBootstrapDataFromDocument()).toEqual({
common: { conf: { MAPBOX_API_KEY: 'pk.document' } },
});
document.body.innerHTML = `<div id="app" data-bootstrap='not-json'></div>`;
expect(getBootstrapDataFromDocument()).toBeUndefined();
document.body.innerHTML = '';
expect(getBootstrapDataFromDocument()).toBeUndefined();
});
test('bootstrap data helper returns undefined without a document', () => {
// jsdom defines `document` as a non-configurable global, so the SSR guard
// cannot be exercised by deleting it. Instead, re-evaluate the function's
// own source in a scope where `document` is shadowed with undefined. When
// running under coverage, the source is istanbul-instrumented and references
// its module-scoped counter, so the counter is injected to keep the guard's
// execution attributed to mapStyles.ts.
const source = getBootstrapDataFromDocument.toString();
const counterName = source.match(/cov_\w+/)?.[0] ?? 'unusedCoverageCounter';
const coverage = (globalThis as { __coverage__?: Record<string, unknown> })
.__coverage__;
const coverageEntry =
coverage?.[
Object.keys(coverage).find(file => file.endsWith('mapStyles.ts')) ?? ''
];
// eslint-disable-next-line no-new-func
const callWithoutDocument = new Function(
counterName,
'document',
`return (${source})();`,
);
expect(callWithoutDocument(() => coverageEntry, undefined)).toBeUndefined();
});
test('renderer options enable Mapbox only when a key is available', () => {
expect(getMapRendererOptions({ hasMapboxKey: true })).toEqual([
{ value: 'maplibre' },
{ value: 'mapbox' },
]);
expect(getMapRendererOptions({ hasMapboxKey: false })).toEqual([
{ value: 'maplibre' },
]);
});
test('renderer options preserve saved Mapbox without API-key labels', () => {
expect(
getMapRendererOptions({ hasMapboxKey: false, currentValue: 'mapbox' }),
).toEqual([{ value: 'maplibre' }, { value: 'mapbox', disabled: true }]);
});
test('map provider style helper preserves legacy non-Mapbox styles for MapLibre', () => {
expect(
getMapProviderMapStyle({
mapProvider: 'maplibre',
maplibreStyle: undefined,
mapboxStyle: OSM_TILE_STYLE_URL,
legacyMapStyle: 'https://example.com/fallback-style.json',
}),
).toEqual({
mapProvider: 'maplibre',
mapStyle: OSM_TILE_STYLE_URL,
});
});
test('map provider style helper does not send Mapbox URLs to MapLibre', () => {
expect(
getMapProviderMapStyle({
mapProvider: 'maplibre',
mapboxStyle: 'mapbox://styles/mapbox/dark-v11',
legacyMapStyle: 'https://example.com/fallback-style.json',
}),
).toEqual({
mapProvider: 'maplibre',
mapStyle: 'https://example.com/fallback-style.json',
});
});
test('map provider style helper uses Mapbox style when Mapbox is selected', () => {
expect(
getMapProviderMapStyle({
mapProvider: 'mapbox',
mapboxStyle: 'mapbox://styles/mapbox/dark-v11',
legacyMapStyle: 'https://example.com/fallback-style.json',
}),
).toEqual({
mapProvider: 'mapbox',
mapStyle: 'mapbox://styles/mapbox/dark-v11',
});
});
test('default renderer uses configured Mapbox only when a key is available', () => {
expect(
getDefaultMapRenderer({
common: {
conf: {
DEFAULT_MAP_RENDERER: 'mapbox',
MAPBOX_API_KEY: 'pk.test',
},
},
}),
).toBe('mapbox');
expect(
getDefaultMapRenderer({
common: { conf: { DEFAULT_MAP_RENDERER: 'mapbox' } },
}),
).toBe('maplibre');
expect(
getDefaultMapRenderer({
common: {
conf: {
DEFAULT_MAP_RENDERER: 'invalid',
MAPBOX_API_KEY: 'pk.test',
},
},
}),
).toBe('maplibre');
});
test('raster tile templates resolve to MapLibre raster style objects with attribution', () => {
const style = resolveMapStyle(OSM_TILE_STYLE_URL, 'default-style.json');
expect(style).toEqual({
version: 8,
sources: {
'osm-raster-tiles': {
type: 'raster',
tiles: [OSM_TILE_STYLE_URL],
tileSize: 256,
attribution: OSM_TILE_ATTRIBUTION,
},
},
layers: [
{
id: 'osm-raster-layer',
type: 'raster',
source: 'osm-raster-tiles',
minzoom: 0,
maxzoom: 22,
},
],
});
});
test('tile protocol raster templates are unwrapped before style resolution', () => {
const style = resolveMapStyle(
`tile://${OSM_TILE_STYLE_URL}`,
'default-style.json',
);
expect(typeof style).toBe('object');
if (typeof style !== 'string') {
expect(style.sources['osm-raster-tiles'].tiles).toEqual([
OSM_TILE_STYLE_URL,
]);
expect(style.sources['osm-raster-tiles'].attribution).toBe(
OSM_TILE_ATTRIBUTION,
);
}
});
test('OpenStreetMap subdomain raster templates receive OSM attribution', () => {
const osmSubdomainTileUrl =
'https://c.tile.openstreetmap.org/{z}/{x}/{y}.png';
const style = resolveMapStyle(
`tile://${osmSubdomainTileUrl}`,
'default-style.json',
);
expect(typeof style).toBe('object');
if (typeof style !== 'string') {
expect(style.sources['osm-raster-tiles'].tiles).toEqual([
osmSubdomainTileUrl,
]);
expect(style.sources['osm-raster-tiles'].attribution).toBe(
OSM_TILE_ATTRIBUTION,
);
}
});
test('custom raster tile templates do not receive OSM attribution', () => {
const customTileUrl = 'https://tiles.example.com/{z}/{x}/{y}.png';
const style = resolveMapStyle(
`tile://${customTileUrl}`,
'default-style.json',
);
expect(typeof style).toBe('object');
if (typeof style !== 'string') {
expect(style.sources['osm-raster-tiles'].tiles).toEqual([customTileUrl]);
expect(style.sources['osm-raster-tiles']).not.toHaveProperty('attribution');
}
});
test('relative raster tile templates do not receive OSM attribution', () => {
const relativeTileUrl = '/tiles/{z}/{x}/{y}.png';
const style = resolveMapStyle(relativeTileUrl, 'default-style.json');
expect(typeof style).toBe('object');
if (typeof style !== 'string') {
expect(style.sources['osm-raster-tiles'].tiles).toEqual([relativeTileUrl]);
expect(style.sources['osm-raster-tiles']).not.toHaveProperty('attribution');
}
});
test('lookalike OpenStreetMap hostnames do not receive OSM attribution', () => {
const lookalikeTileUrl =
'https://openstreetmap.org.example.com/{z}/{x}/{y}.png';
const style = resolveMapStyle(
`tile://${lookalikeTileUrl}`,
'default-style.json',
);
expect(typeof style).toBe('object');
if (typeof style !== 'string') {
expect(style.sources['osm-raster-tiles'].tiles).toEqual([lookalikeTileUrl]);
expect(style.sources['osm-raster-tiles']).not.toHaveProperty('attribution');
}
});
test('style JSON URLs pass through without raster wrapping', () => {
const styleUrl = 'https://example.com/styles/custom-style.json';
expect(isRasterTileTemplate(undefined)).toBe(false);
expect(isRasterTileTemplate(styleUrl)).toBe(false);
expect(resolveMapStyle(styleUrl, 'default-style.json')).toBe(styleUrl);
expect(resolveMapStyle(undefined, 'default-style.json')).toBe(
'default-style.json',
);
});

View File

@@ -0,0 +1,251 @@
/**
* 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.
*/
export type MapProvider = 'maplibre' | 'mapbox';
export type MapRendererOption = {
value: MapProvider;
disabled?: boolean;
};
export type MapProviderMapStyle = {
mapProvider?: unknown;
maplibreStyle?: unknown;
mapboxStyle?: unknown;
legacyMapStyle?: unknown;
};
export type SelectedMapProviderMapStyle = {
mapProvider: MapProvider;
mapStyle?: string;
};
export type RasterTileMapStyle = {
version: 8;
sources: {
[sourceId: string]: {
type: 'raster';
tiles: string[];
tileSize: 256;
attribution?: string;
};
};
layers: [
{
id: string;
type: 'raster';
source: string;
minzoom: 0;
maxzoom: 22;
},
];
};
export type ResolvedMapStyle = string | RasterTileMapStyle;
export const OSM_TILE_STYLE_URL =
'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
export const OSM_TILE_ATTRIBUTION = '© OpenStreetMap contributors';
export const MAPLIBRE_RENDERER_OPTION: MapRendererOption = {
value: 'maplibre',
};
export const MAPBOX_RENDERER_OPTION: MapRendererOption = {
value: 'mapbox',
};
export const DISABLED_MAPBOX_RENDERER_OPTION: MapRendererOption = {
...MAPBOX_RENDERER_OPTION,
disabled: true,
};
const TILE_PROTOCOL = 'tile://';
const RASTER_SOURCE_ID = 'osm-raster-tiles';
const RASTER_LAYER_ID = 'osm-raster-layer';
type BootstrapData = {
common?: {
conf?: {
DEFAULT_MAP_RENDERER?: unknown;
MAPBOX_API_KEY?: unknown;
};
};
};
export function getBootstrapDataFromDocument(): unknown {
if (typeof document === 'undefined') {
return undefined;
}
try {
const appContainer = document.getElementById('app');
const dataBootstrap = appContainer?.getAttribute('data-bootstrap');
return dataBootstrap ? JSON.parse(dataBootstrap) : undefined;
} catch {
return undefined;
}
}
export function getMapboxApiKeyFromBootstrap(
bootstrapData: unknown = getBootstrapDataFromDocument(),
): string {
const mapboxApiKey = (bootstrapData as BootstrapData | undefined)?.common
?.conf?.MAPBOX_API_KEY;
return typeof mapboxApiKey === 'string' ? mapboxApiKey.trim() : '';
}
export function hasMapboxApiKey(
bootstrapData: unknown = getBootstrapDataFromDocument(),
): boolean {
return getMapboxApiKeyFromBootstrap(bootstrapData).trim().length > 0;
}
export function getDefaultMapRenderer(
bootstrapData: unknown = getBootstrapDataFromDocument(),
): MapProvider {
const conf = (bootstrapData as BootstrapData | undefined)?.common?.conf;
const defaultRenderer = conf?.DEFAULT_MAP_RENDERER;
if (defaultRenderer === 'mapbox' && hasMapboxApiKey(bootstrapData)) {
return 'mapbox';
}
return 'maplibre';
}
export function getMapRendererOptions({
hasMapboxKey,
currentValue,
}: {
hasMapboxKey: boolean;
currentValue?: MapProvider;
}): MapRendererOption[] {
if (!hasMapboxKey && currentValue !== 'mapbox') {
return [MAPLIBRE_RENDERER_OPTION];
}
return [
MAPLIBRE_RENDERER_OPTION,
hasMapboxKey ? MAPBOX_RENDERER_OPTION : DISABLED_MAPBOX_RENDERER_OPTION,
];
}
function getNonEmptyString(value: unknown): string | undefined {
return typeof value === 'string' && value.trim().length > 0
? value
: undefined;
}
function isMapboxStyle(value: unknown): boolean {
return getNonEmptyString(value)?.startsWith('mapbox://') ?? false;
}
export function getMapProviderMapStyle({
mapProvider,
maplibreStyle,
mapboxStyle,
legacyMapStyle,
}: MapProviderMapStyle): SelectedMapProviderMapStyle {
const selectedMapProvider: MapProvider =
mapProvider === 'mapbox' ? 'mapbox' : 'maplibre';
const maplibreStyleValue = getNonEmptyString(maplibreStyle);
const mapboxStyleValue = getNonEmptyString(mapboxStyle);
const legacyMapStyleValue = getNonEmptyString(legacyMapStyle);
if (selectedMapProvider === 'mapbox') {
return {
mapProvider: selectedMapProvider,
mapStyle: mapboxStyleValue ?? legacyMapStyleValue,
};
}
return {
mapProvider: selectedMapProvider,
mapStyle:
maplibreStyleValue ??
(isMapboxStyle(mapboxStyleValue) ? undefined : mapboxStyleValue) ??
legacyMapStyleValue,
};
}
function unwrapTileProtocol(value: string): string {
return value.startsWith(TILE_PROTOCOL)
? value.slice(TILE_PROTOCOL.length)
: value;
}
export function isRasterTileTemplate(value: unknown): value is string {
if (typeof value !== 'string') {
return false;
}
const tileUrl = unwrapTileProtocol(value);
return ['{z}', '{x}', '{y}'].every(templateParam =>
tileUrl.includes(templateParam),
);
}
function isOpenStreetMapTileUrl(value: string): boolean {
try {
const hostname = new URL(value).hostname.toLowerCase();
return (
hostname === 'openstreetmap.org' ||
hostname.endsWith('.openstreetmap.org')
);
} catch {
return false;
}
}
export function buildRasterTileMapStyle(value: string): RasterTileMapStyle {
const tileUrl = unwrapTileProtocol(value);
const attribution = isOpenStreetMapTileUrl(tileUrl)
? { attribution: OSM_TILE_ATTRIBUTION }
: {};
return {
version: 8,
sources: {
[RASTER_SOURCE_ID]: {
type: 'raster',
tiles: [tileUrl],
tileSize: 256,
...attribution,
},
},
layers: [
{
id: RASTER_LAYER_ID,
type: 'raster',
source: RASTER_SOURCE_ID,
minzoom: 0,
maxzoom: 22,
},
],
};
}
export function resolveMapStyle(
value: string | undefined,
defaultStyle: string,
): ResolvedMapStyle {
if (!value) {
return defaultStyle;
}
return isRasterTileTemplate(value) ? buildRasterTileMapStyle(value) : value;
}

View File

@@ -172,4 +172,15 @@ describe('SupersetClient', () => {
const token = await SupersetClient.getCSRFToken();
expect(token).toBe('my_token');
});
test('guestTokenHeaderName returns the configured header name when instance exists', () => {
SupersetClient.configure({ guestTokenHeaderName: 'X-Custom-Guest' });
expect(SupersetClient.guestTokenHeaderName).toBe('X-Custom-Guest');
});
test('guestTokenHeaderName returns default X-GuestToken when instance is not configured', () => {
// Ensure instance is reset (afterEach calls SupersetClient.reset())
// Access the property without calling configure() first
expect(SupersetClient.guestTokenHeaderName).toBe('X-GuestToken');
});
});

View File

@@ -35,7 +35,7 @@ test('format milliseconds in human readable format with default options', () =>
});
test('format seconds in human readable format with default options', () => {
const formatter = createDurationFormatter({ multiplier: 1000 });
expect(formatter(-0.5)).toBe('-0s');
expect(formatter(-0.5)).toBe('0s');
expect(formatter(0.5)).toBe('0s');
expect(formatter(1)).toBe('1s');
expect(formatter(30)).toBe('30s');

View File

@@ -29,12 +29,33 @@ export class ExplorePage {
private static readonly SELECTORS = {
DATASOURCE_CONTROL: '[data-test="datasource-control"]',
VIZ_SWITCHER: '[data-test="fast-viz-switcher"]',
CHART_CONTAINER: '[data-test="chart-container"]',
} as const;
constructor(page: Page) {
this.page = page;
}
/**
* Navigates to the Explore page for a given chart and waits for it to load.
*
* @param chartId - ID of the chart (slice) to open
* @param options - Optional wait options
*/
async goto(chartId: number, options?: { timeout?: number }): Promise<void> {
await this.page.goto(`explore/?slice_id=${chartId}`);
await this.waitForPageLoad(options);
}
/**
* Gets the chart container locator (where the rendered viz appears).
*
* @returns Locator for the chart container
*/
getChartContainer(): Locator {
return this.page.locator(ExplorePage.SELECTORS.CHART_CONTAINER);
}
/**
* Waits for the Explore page to load.
* Validates URL contains /explore/ and datasource control is visible.

View File

@@ -0,0 +1,104 @@
/**
* 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.
*/
/**
* Regression for #32960: the `formatDate` Handlebars helper (provided by
* just-handlebars-helpers) stopped working after 4.1.2, rendering
* "i is not a function" (minified) / "moment is not a function" (dev) instead
* of the formatted date. The library helper resolves `moment` lazily via
* `global.moment` / `require('moment/min/moment-with-locales')`, which the
* bundled HandlebarsViewer no longer satisfies (it switched to dayjs).
*
* The fix registers a dayjs-backed `formatDate` override in HandlebarsViewer
* (superset-frontend/plugins/plugin-chart-handlebars). This spec guards it: it
* creates a Handlebars chart whose template uses `{{formatDate 'DD.MM.YYYY' ds}}`
* and asserts the chart renders a real formatted date rather than the helper
* error. Because the failure was a bundling/minification artifact (moment
* resolves fine under Jest's Node `require`), an E2E test is required to cover it.
*/
import { testWithAssets, expect } from '../../helpers/fixtures';
import { apiPostChart } from '../../helpers/api/chart';
import { getDatasetByName } from '../../helpers/api/dataset';
import { ExplorePage } from '../../pages/ExplorePage';
import { TIMEOUT } from '../../utils/constants';
const DATASET_NAME = 'birth_names';
testWithAssets(
'Handlebars formatDate helper renders a formatted date (#32960)',
async ({ page, testAssets }) => {
testWithAssets.setTimeout(TIMEOUT.SLOW_TEST);
const dataset = await getDatasetByName(page, DATASET_NAME);
if (!dataset) {
throw new Error(`Dataset ${DATASET_NAME} not found`);
}
const datasetId = dataset.id;
const params = {
datasource: `${datasetId}__table`,
viz_type: 'handlebars',
query_mode: 'aggregate',
groupby: ['ds'],
metrics: ['count'],
adhoc_filters: [],
row_limit: 5,
// Note: HTML_SANITIZATION (on by default) strips non-allowlisted
// attributes such as `class`, so the rendered markup is plain
// <ul>/<li> elements. The assertions below target `li` directly.
handlebarsTemplate:
'<ul>{{#each data}}' +
"<li>{{formatDate 'DD.MM.YYYY' ds}}</li>" +
'{{/each}}</ul>',
styleTemplate: '',
};
const chartResp = await apiPostChart(page, {
slice_name: `handlebars_format_date_${Date.now()}`,
viz_type: 'handlebars',
datasource_id: datasetId,
datasource_type: 'table',
params: JSON.stringify(params),
});
expect(chartResp.ok()).toBe(true);
// The chart API may return either a top-level `{ id }` or a wrapped
// `{ result: { id } }` shape; handle both and fail explicitly otherwise.
const chartBody = await chartResp.json();
const chartId: number = chartBody.result?.id ?? chartBody.id;
expect(chartId, 'chart creation should return an id').toBeTruthy();
testAssets.trackChart(chartId);
const explorePage = new ExplorePage(page);
await explorePage.goto(chartId);
const panel = explorePage.getChartContainer();
await panel.waitFor({ state: 'visible', timeout: TIMEOUT.PAGE_LOAD });
// The helper error surfaces as a "... is not a function" message rendered
// in place of the chart content.
await expect(panel).not.toContainText('is not a function', {
timeout: TIMEOUT.API_RESPONSE,
});
// At least one list item should contain a DD.MM.YYYY formatted date.
await expect(panel.locator('li').first()).toHaveText(/\d{2}\.\d{2}\.\d{4}/, {
timeout: TIMEOUT.API_RESPONSE,
});
},
);

View File

@@ -0,0 +1,202 @@
/**
* 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.
*/
/**
* Regression for #28766: a Gauge chart configured with interval bounds and
* interval colors (mapped to a categorical color scheme) sometimes renders the
* wrong interval colors when first loaded on a dashboard — a refresh fixes it.
*
* The gauge renders to a <canvas>, so this test reads pixels back from the
* rendered gauge and asserts the configured interval colors are present in the
* correct mapping. With `color_scheme: supersetColors` and
* `interval_color_indices: '1,2'`, the gauge axis must paint the scheme's 1st
* and 2nd colors (#1FA8C9 and #454E7C) and must NOT paint the 3rd (#5AC189),
* which would indicate a shifted / fallback palette.
*
* CI green => the gauge paints the configured interval colors on first load;
* merging closes #28766 and guards against regressions.
* CI red => the interval colors are wrong on first load; the bug is live in
* plugin-chart-echarts/src/Gauge (color-scheme resolution).
*/
import { testWithAssets, expect } from '../../helpers/fixtures';
import { apiPostChart, apiPutChart } from '../../helpers/api/chart';
import { apiPostDashboard } from '../../helpers/api/dashboard';
import { getDatasetByName } from '../../helpers/api/dataset';
import { DashboardPage } from '../../pages/DashboardPage';
const DATASET_NAME = 'birth_names';
// supersetColors palette (1-based, matching interval_color_indices):
// index 1 = #1FA8C9, index 2 = #454E7C, index 3 = #5AC189
const COLOR_INTERVAL_1: [number, number, number] = [31, 168, 201];
const COLOR_INTERVAL_2: [number, number, number] = [69, 78, 124];
const COLOR_UNUSED_3: [number, number, number] = [90, 193, 137];
testWithAssets(
'Gauge renders configured interval colors on a dashboard (#28766)',
async ({ page, testAssets }) => {
const dataset = await getDatasetByName(page, DATASET_NAME);
if (!dataset) {
throw new Error(`Dataset ${DATASET_NAME} not found`);
}
const datasetId = dataset.id;
const sliceName = `gauge_interval_colors_${Date.now()}`;
const chartParams = {
datasource: `${datasetId}__table`,
viz_type: 'gauge_chart',
metric: 'count',
adhoc_filters: [],
row_limit: 10,
color_scheme: 'supersetColors',
min_val: 0,
max_val: 100,
start_angle: 225,
end_angle: -45,
intervals: '50,100',
interval_color_indices: '1,2',
show_pointer: true,
number_format: 'SMART_NUMBER',
value_formatter: '{value}',
};
const chartResp = await apiPostChart(page, {
slice_name: sliceName,
viz_type: 'gauge_chart',
datasource_id: datasetId,
datasource_type: 'table',
params: JSON.stringify(chartParams),
});
expect(chartResp.ok()).toBe(true);
const chartBody = await chartResp.json();
// Normalize: API may return id at top level or inside result.
const chartId: number = chartBody.result?.id ?? chartBody.id;
if (!chartId) {
throw new Error(
`Chart creation returned no id. Response: ${JSON.stringify(chartBody)}`,
);
}
testAssets.trackChart(chartId);
const chartLayoutKey = `CHART-${chartId}`;
const positionJson = {
DASHBOARD_VERSION_KEY: 'v2',
ROOT_ID: { type: 'ROOT', id: 'ROOT_ID', children: ['GRID_ID'] },
GRID_ID: {
type: 'GRID',
id: 'GRID_ID',
children: ['ROW-1'],
parents: ['ROOT_ID'],
},
'ROW-1': {
type: 'ROW',
id: 'ROW-1',
children: [chartLayoutKey],
parents: ['ROOT_ID', 'GRID_ID'],
meta: { background: 'BACKGROUND_TRANSPARENT' },
},
[chartLayoutKey]: {
type: 'CHART',
id: chartLayoutKey,
children: [],
parents: ['ROOT_ID', 'GRID_ID', 'ROW-1'],
meta: {
chartId,
width: 6,
height: 60,
sliceName,
},
},
};
const dashResp = await apiPostDashboard(page, {
dashboard_title: `gauge_interval_colors_${Date.now()}`,
published: true,
position_json: JSON.stringify(positionJson),
json_metadata: JSON.stringify({ color_scheme: 'supersetColors' }),
});
expect(dashResp.ok()).toBe(true);
const dashBody = await dashResp.json();
const dashboardId: number = dashBody.result?.id ?? dashBody.id;
testAssets.trackDashboard(dashboardId);
await apiPutChart(page, chartId, { dashboards: [dashboardId] });
const dashboardPage = new DashboardPage(page);
await dashboardPage.gotoById(dashboardId);
await dashboardPage.waitForLoad();
await dashboardPage.waitForChartsToLoad();
const canvas = page.locator('[data-test="chart-container"] canvas').first();
await canvas.waitFor({ state: 'visible', timeout: 30_000 });
// Read the configured interval colors back from the rendered canvas.
// Poll because the gauge paints shortly after the chart container appears.
const countColors = () =>
canvas.evaluate(
(el: HTMLCanvasElement, targets: Array<[number, number, number]>) => {
const ctx = el.getContext('2d');
if (!ctx) return targets.map(() => 0);
const { data } = ctx.getImageData(0, 0, el.width, el.height);
const counts = targets.map(() => 0);
for (let i = 0; i < data.length; i += 4) {
if (data[i + 3] < 200) continue;
for (let t = 0; t < targets.length; t += 1) {
const [r, g, b] = targets[t];
if (
Math.abs(data[i] - r) < 12 &&
Math.abs(data[i + 1] - g) < 12 &&
Math.abs(data[i + 2] - b) < 12
) {
counts[t] += 1;
}
}
}
return counts;
},
[COLOR_INTERVAL_1, COLOR_INTERVAL_2, COLOR_UNUSED_3],
);
// Capture the counts inside the poll so the assertions below run against the
// exact paint snapshot that satisfied the poll, not a second canvas read.
let counts: number[] = [0, 0, 0];
await expect
.poll(
async () => {
counts = await countColors();
return counts[0];
},
{ timeout: 20_000 },
)
.toBeGreaterThan(50);
const [interval1, interval2, unused3] = counts;
expect(
interval1,
'Gauge should paint the 1st interval color (#1FA8C9)',
).toBeGreaterThan(50);
expect(
interval2,
'Gauge should paint the 2nd interval color (#454E7C)',
).toBeGreaterThan(50);
expect(
unused3,
'Gauge must not paint the 3rd palette color (#5AC189) — indicates a shifted/fallback palette (#28766)',
).toBe(0);
},
);

View File

@@ -0,0 +1,309 @@
/**
* 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.
*/
/**
* Regression for #33406: in a Pivot Table (v2) with nested rows, collapsing a
* row group with the [-] toggle should stay collapsed after the collapsed rows
* scroll out of the viewport and back. The bug reproduces specifically when the
* dashboard is embedded via an iframe — the collapse/expand state lives in the
* pivot renderer's local React state (`collapsedRows` initialised to `{}`), so
* anything that remounts the chart resets it and the rows re-expand.
*
* This spec runs on the embedded harness (the only place the bug is reported to
* reproduce). It collapses a top-level row, scrolls the embedded dashboard so
* the pivot leaves and re-enters the viewport, and asserts the row is still
* collapsed.
*
* CI green => collapse state survives the scroll round-trip; merging closes
* #33406 and guards against regressions.
* CI red => the rows re-expanded; the bug is live and the fix belongs in
* plugin-chart-pivot-table (lift collapse state out of transient
* component state, e.g. persist `collapsedRows`/`collapsedCols`).
*
* NOTE: the embedded suite only runs when the embedded SDK bundle is built and
* INCLUDE_EMBEDDED=true (CI sets both). It is skipped otherwise.
*/
import { test, expect, Browser, BrowserContext, Page } from '@playwright/test';
import { createServer, IncomingMessage, ServerResponse, Server } from 'http';
import { AddressInfo, Socket } from 'net';
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';
import {
apiEnableEmbedding,
getAccessToken,
getGuestToken,
} from '../../helpers/api/embedded';
import { apiPost, apiPut } from '../../helpers/api/requests';
import { apiPostDashboard, apiDeleteDashboard } from '../../helpers/api/dashboard';
import { apiDeleteChart } from '../../helpers/api/chart';
import { EmbeddedPage } from '../../pages/EmbeddedPage';
import { EMBEDDED } from '../../utils/constants';
const SUPERSET_DOMAIN = (() => {
const url = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8088';
return url.replace(/\/+$/, '');
})();
const SUPERSET_BASE_URL = SUPERSET_DOMAIN.endsWith('/')
? SUPERSET_DOMAIN
: `${SUPERSET_DOMAIN}/`;
const SDK_BUNDLE_PATH = join(
__dirname,
'../../../../superset-embedded-sdk/bundle/index.js',
);
const EMBED_APP_DIR = join(__dirname, '../../embedded-app');
const INDEX_HTML_PATH = join(EMBED_APP_DIR, 'index.html');
const DATASET_NAME = 'birth_names';
interface EmbedAppServer {
server: Server;
url: string;
close: () => Promise<void>;
}
async function startEmbedAppServer(): Promise<EmbedAppServer> {
const sockets = new Set<Socket>();
const server = createServer((req: IncomingMessage, res: ServerResponse) => {
const urlPath = req.url?.split('?')[0] || '/';
if (urlPath === '/sdk/index.js') {
if (!existsSync(SDK_BUNDLE_PATH)) {
res.writeHead(404);
res.end(
'SDK bundle not found. Run: cd superset-embedded-sdk && npm ci && npm run build',
);
return;
}
res.writeHead(200, { 'Content-Type': 'text/javascript' });
res.end(readFileSync(SDK_BUNDLE_PATH));
return;
}
if (urlPath === '/' || urlPath === '/index.html') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(readFileSync(INDEX_HTML_PATH));
return;
}
res.writeHead(404);
res.end('Not found');
});
server.on('connection', socket => {
sockets.add(socket);
socket.once('close', () => sockets.delete(socket));
});
await new Promise<void>((resolve, reject) => {
server.once('error', reject);
server.listen(0, '127.0.0.1', () => {
server.removeListener('error', reject);
resolve();
});
});
const address = server.address() as AddressInfo;
return {
server,
url: `http://127.0.0.1:${address.port}`,
close: () =>
new Promise<void>(resolve => {
for (const socket of sockets) socket.destroy();
sockets.clear();
server.close(() => resolve());
}),
};
}
function createAdminContext(browser: Browser): Promise<BrowserContext> {
return browser.newContext({
storageState: 'playwright/.auth/user.json',
baseURL: SUPERSET_BASE_URL,
});
}
async function findDatasetIdByName(page: Page, name: string): Promise<number> {
const query = `(filters:!((col:table_name,opr:eq,value:'${name}')))`;
const resp = await page.request.get(`api/v1/dataset/?q=${query}`);
const body = await resp.json();
if (!body.result?.length) {
throw new Error(`Dataset ${name} not found`);
}
return body.result[0].id;
}
test.describe('Embedded Pivot Table collapse state (#33406)', () => {
test.describe.configure({ mode: 'serial' });
test.setTimeout(90000);
let appServer: EmbedAppServer;
let accessToken: string;
let embedUuid: string;
let dashboardId: number;
let chartId: number;
test.beforeAll(async ({ browser }) => {
test.skip(
!existsSync(SDK_BUNDLE_PATH),
'Embedded SDK bundle not found. Build it with: cd superset-embedded-sdk && npm ci && npm run build',
);
appServer = await startEmbedAppServer();
const context = await createAdminContext(browser);
const setupPage = await context.newPage();
try {
const datasetId = await findDatasetIdByName(setupPage, DATASET_NAME);
const params = {
datasource: `${datasetId}__table`,
viz_type: 'pivot_table_v2',
groupbyRows: ['state', 'name'],
groupbyColumns: [],
metrics: ['count'],
metricsLayout: 'COLUMNS',
aggregateFunction: 'Count',
rowSubTotals: true,
rowTotals: true,
valueFormat: 'SMART_NUMBER',
row_limit: 1000,
order_desc: true,
};
const chartResp = await apiPost(setupPage, 'api/v1/chart/', {
slice_name: `pivot_collapse_repro_${Date.now()}`,
viz_type: 'pivot_table_v2',
datasource_id: datasetId,
datasource_type: 'table',
params: JSON.stringify(params),
});
chartId = (await chartResp.json()).id;
const chartLayoutKey = `CHART-${chartId}`;
const positionJson = {
DASHBOARD_VERSION_KEY: 'v2',
ROOT_ID: { type: 'ROOT', id: 'ROOT_ID', children: ['GRID_ID'] },
GRID_ID: {
type: 'GRID',
id: 'GRID_ID',
children: ['ROW-1'],
parents: ['ROOT_ID'],
},
'ROW-1': {
type: 'ROW',
id: 'ROW-1',
children: [chartLayoutKey],
parents: ['ROOT_ID', 'GRID_ID'],
meta: { background: 'BACKGROUND_TRANSPARENT' },
},
[chartLayoutKey]: {
type: 'CHART',
id: chartLayoutKey,
children: [],
parents: ['ROOT_ID', 'GRID_ID', 'ROW-1'],
meta: {
chartId,
width: 6,
height: 80,
sliceName: 'pivot_collapse_repro',
},
},
};
const dashResp = await apiPostDashboard(setupPage, {
dashboard_title: `pivot_collapse_repro_${Date.now()}`,
published: true,
position_json: JSON.stringify(positionJson),
});
const dashBody = await dashResp.json();
dashboardId = dashBody.id;
await apiPut(setupPage, `api/v1/chart/${chartId}`, {
dashboards: [dashboardId],
});
const embedded = await apiEnableEmbedding(setupPage, dashboardId);
embedUuid = embedded.uuid;
accessToken = await getAccessToken(setupPage);
} finally {
await context.close();
}
});
test.afterAll(async ({ browser }) => {
const context = await createAdminContext(browser);
try {
const cleanupPage = await context.newPage();
if (dashboardId !== undefined) {
await apiDeleteDashboard(cleanupPage, dashboardId, {
failOnStatusCode: false,
});
}
if (chartId !== undefined) {
await apiDeleteChart(cleanupPage, chartId, { failOnStatusCode: false });
}
} catch (err) {
// eslint-disable-next-line no-console
console.error('[pivot-collapse teardown] cleanup failed:', err);
} finally {
await context.close();
}
if (appServer) await appServer.close();
});
test('collapsed rows stay collapsed after a scroll round-trip', async ({
page,
}) => {
const embeddedPage = new EmbeddedPage(page);
await embeddedPage.exposeTokenFetcher(async () =>
getGuestToken(page, dashboardId, { accessToken }),
);
await embeddedPage.goto({
appUrl: appServer.url,
uuid: embedUuid,
supersetDomain: SUPERSET_DOMAIN,
});
await embeddedPage.waitForIframe();
await embeddedPage.waitForDashboardContent();
await embeddedPage.waitForChartRendered();
const rowLabels = embeddedPage.iframe.locator('.pvtRowLabel');
await expect
.poll(() => rowLabels.count(), { timeout: EMBEDDED.CHART_RENDER })
.toBeGreaterThan(1);
const expandedCount = await rowLabels.count();
// Collapse the first top-level row group via its [-] toggle. Scope to
// `.pvtTable` so we never match a stray `toggle` class elsewhere in the DOM.
await embeddedPage.iframe.locator('.pvtTable .toggle').first().click();
await expect
.poll(() => embeddedPage.iframe.locator('.pvtRowLabel').count(), {
timeout: EMBEDDED.CHART_RENDER,
})
.toBeLessThan(expandedCount);
const collapsedCount = await embeddedPage.iframe
.locator('.pvtRowLabel')
.count();
// Scroll the embedded dashboard so the pivot leaves the viewport, then back.
await embeddedPage.iframe.locator('body').evaluate(() => {
window.scrollTo(0, document.body.scrollHeight);
});
await page.waitForTimeout(800);
await embeddedPage.iframe.locator('body').evaluate(() => {
window.scrollTo(0, 0);
});
await page.waitForTimeout(1200);
// The collapsed group must remain collapsed (row-label count unchanged).
await expect(embeddedPage.iframe.locator('.pvtRowLabel')).toHaveCount(
collapsedCount,
);
});
});

View File

@@ -33,7 +33,7 @@
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"@apache-superset/core": "*",
"react": "^18.2.0"
"react": "^18.3.0"
},
"publishConfig": {
"access": "public"

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