Compare commits

...

76 Commits

Author SHA1 Message Date
Hugh A Miles II
670a95215c style: apply auto-walrus and ruff-format to query-context builder
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:13:44 -04:00
Hugh A Miles II
719649fe1a fix(dashboard): make form-data query-context rebuild faithful
Address EnxDev review — the generic rebuild silently dropped several
query aspects, so allowlisted charts could export a different dataset
than they show:

- Ordering: derive `orderby` from order_by_cols (raw) or the sort metric
  / first-metric-descending (aggregate), so a `row_limit` returns the
  chart's top-N instead of an arbitrary N.
- Time range: set `granularity` (from granularity/granularity_sqla) so
  `time_range` is actually applied — but only when there is an active
  range, so a numeric column saved as granularity_sqla with no range
  isn't forced through date bucketing (verified against a real export).
- Custom SQL filters: map `SQL` adhoc filters + legacy top-level `where`
  into `extras.where`/`extras.having` by clause instead of dropping them.
- Table specifics: carry `percent_metrics` into metrics and pass
  `time_grain_sqla` through `extras`.
- Big Number: only promote granularity_sqla to a grouping column for the
  trendline viz (`big_number`), never `big_number_total`.

Also point the MCP chart compile/preview helpers
(adhoc_filters_to_query_filters, _build_query_columns) at this shared
module so they stop diverging (preview_utils still had the pre-fix
`columns` bug), and address nits: drop the `-O`-stripped assert by
restructuring the loop, parse the saved query_context once, and correct
the shallow-copy comment.

Adds unit tests for every derived field plus an export test asserting the
full query body reaches ChartDataQueryContextSchema().load.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:03:21 -04:00
Hugh A Miles II
de7dfc1585 fix(dashboard): harden form-data query-context rebuild edge cases
Address review of the empty query-context rebuild:

- columns_from_form_data no longer lets a stale, explicitly-present-but-
  empty `columns: []` key shadow the group-by dimensions (which silently
  dropped grouping and changed the aggregation). Prefer raw columns only
  when non-empty, else fall back to groupby.
- build_query_context_from_form_data now also honors legacy simple
  `filters` (already in QueryObject {col, op, val} shape) in addition to
  `adhoc_filters`, so legacy charts export the same filtered data they
  show; malformed entries are dropped.
- _rebuild_viz_types checks the config for None explicitly instead of
  using `or`, so an operator can disable the rebuild with an empty set
  instead of it silently falling back to the default allowlist.

Adds unit tests for each case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 13:06:29 -04:00
Hugh A Miles II
a1212ec089 test(dashboard): cover query-context rebuild edge cases
Add unit tests for the previously-uncovered branches of the empty
query-context handling: x_axis-as-adhoc-dict and the granularity_sqla
column fallback in the form-data builder (now 100%), and the eligible-viz
skip paths in _resolve_query_context when the saved form data is
unparseable, non-object, or has no datasource. All new source lines are
now exercised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 10:01:28 -04:00
Hugh A Miles II
2bbada837b style: apply ruff-format to empty-query-context tests
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:14:59 -04:00
Hugh A Miles II
de29e984ff feat(dashboard): rebuild missing query context from form data in Excel export
Charts persist a query_context only once (re-)saved in Explore, so older
charts have params (form data) but no context and were skipped entirely
by the data export. When the saved context is empty, synthesize one from
the chart's form data (columns, metrics, adhoc filters, time range) and
run that instead.

The rebuild is a generic single-query mapping — it does not reproduce
plugin post-processing (pivot, rolling, forecast) or multi-query charts —
so it is gated to a conservative viz-type allowlist
(EXCEL_EXPORT_REBUILD_VIZ_TYPES: table, big_number_total, big_number,
pie). Charts of any other type without a saved context are still skipped
and listed for re-save, so no chart exports silently wrong or incomplete
data.

The form-data -> query-context logic lives in a new shared module
(superset.common.form_data_query_context), mirroring the approach the MCP
chart compile/preview path already uses. Adds unit tests for the builder
and export integration tests for the eligible-rebuild and
ineligible-skip paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 14:57:19 -04:00
Hugh A Miles II
c7a6c6fc9c fix(dashboard): skip charts with empty query context cleanly in Excel export
The export guard only skipped charts whose query_context was missing
(None/blank). A present-but-unusable context slipped through and failed
mid-export: "null" parsed to None and raised TypeError on item
assignment, while "{}"/{"queries": []} failed schema validation — all
landing in the generic error bucket with a noisy traceback instead of
the "no query context" remediation ("re-save in Explore"), which is the
correct guidance for every one of these cases.

Replace the shallow truthiness check with `_has_empty_query_context`,
which also treats an unparseable, non-object, or query-less context as
empty, and route all of them to ERROR_NO_QUERY_CONTEXT. Add a
parametrized test over blank/null/{}/empty-queries/malformed inputs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 12:38:33 -04:00
Hugh A. Miles II
00cb3037e7 feat(dashboard): export dashboard chart data to Excel (async, S3 + email) (#41133) 2026-07-21 12:17:00 -04:00
Hans Yu
6c58afe6ac refactor: do not cascade to TableColumn (#42222) 2026-07-21 09:00:47 -07:00
Evan Rusackas
f751716867 fix(trino): quote nested ROW field paths per-segment in physical column expressions (#41946)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 04:27:24 -07:00
dependabot[bot]
bafe5ad9ba chore(deps): bump actions/setup-java from 5.5.0 to 5.6.0 (#42260)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-21 01:15:32 -07:00
dependabot[bot]
a1d4cda0cb chore(deps-dev): bump typescript-eslint from 8.63.0 to 8.64.0 in /docs in the typescript-eslint group (#42259)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-21 01:15:28 -07:00
Evan Rusackas
2d3cbc162e fix(db_engine_specs): map var_string to STRING for MySQL/StarRocks (#41868)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 00:11:46 -07:00
Evan Rusackas
e4005f02c6 fix(sqla): render Jinja templates in calculated columns used via orderby adhoc metrics (#41870)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 00:11:26 -07:00
jesperct
8296fe8ce4 fix(home): restore navigation for saved query cards (#41912) 2026-07-20 17:33:32 -07:00
Elizabeth Thompson
4b659da5c4 fix(reports): positive per-tile chart readiness check for tiled screenshots (#42119)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: yousoph <sophieyou12@gmail.com>
2026-07-20 17:31:45 -07:00
worker24h
b4529c2654 feat: add distance unit (#24444)
Co-authored-by: xuxiaobing <xuxiaobing@neolix.cn>
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
2026-07-20 16:25:48 -07:00
Evan Rusackas
034823e99b chore: replace deprecated datetime.utcnow()/utcfromtimestamp() (#41502)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 15:37:57 -07:00
Evan Rusackas
abd6dc8e44 refactor: set cascade_backrefs=False for SavedQuery (#42212)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 15:37:28 -07:00
Evan Rusackas
b3757870cc fix(sqllab): apply SQL_QUERY_MUTATOR in SQL Lab when MUTATE_AFTER_SPLIT is set (#41127)
Co-authored-by: Lucas Wolkersdorfer <lucas.wolkersdorfer@rise-world.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 15:36:45 -07:00
dependabot[bot]
158e321992 chore(deps): bump axios from 1.16.0 to 1.18.1 in /superset-frontend (#42251)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 11:45:40 -07:00
Evan Rusackas
590357731b feat(ci): cancel CI runs when hold label applied, re-run on removal (#40387)
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com>
2026-07-20 10:35:26 -07:00
Elizabeth Thompson
64053271e8 fix(views): remove stray space in @deprecated new_target log message (#42218) 2026-07-20 10:35:02 -07:00
Matthew Chiang
940e2c34a2 fix(Calendar Heatmap): Add Back chart options for Calendar Heatmap (#26230)
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
2026-07-20 10:32:35 -07:00
Đỗ Trọng Hải
d65a816b39 chore(lint): toggle import/no-named-as-default to error level + fix all violations (#42214)
Signed-off-by: hainenber <dotronghai96@gmail.com>
2026-07-20 10:22:15 -07:00
Evan Rusackas
48faca5b8d refactor: set cascade_backrefs=False for SqlaTable (#42213)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 10:22:00 -07:00
Evan Rusackas
9178faf9b1 refactor: set cascade_backrefs=False for Query (#42210)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 10:20:09 -07:00
PRATHAMESH HUKKERI
15bc73facf test(table): add regression test for pagination reset on cell click (#42155)
Co-authored-by: Prathamesh Hukkeri <prathamesh04@users.noreply.github.com>
2026-07-20 10:15:29 -07:00
Evan Rusackas
e0d0eb6d02 fix(build): deterministic echarts locale imports; fingerprint loaded config (#42154)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 10:13:58 -07:00
Đỗ Trọng Hải
1f81b700c1 chore(ci): upgrade GHA runner image to Ubuntu 26.04 (#42191)
Signed-off-by: hainenber <dotronghai96@gmail.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: David <39565245+dmunozv04@users.noreply.github.com>
Co-authored-by: Hans Yu <hans.yu@outlook.de>
Co-authored-by: Shaitan <105581038+sha174n@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Rehan Islam <2025eb01502@online.bits-pilani.ac.in>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J0s3-H3nr1qu3 <hareboom@gmail.com>
Co-authored-by: José Teixeira <jose.teixeira@douroeci.com>
2026-07-20 09:43:19 -07:00
Jean Massucatto
3a3f087869 fix(native-filters): support double-quote escaping for comma in filte… (#39068)
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 09:42:52 -07:00
Evan Rusackas
f03a66db9c fix(dataset): apply HOURS OFFSET to all temporal columns, not just the time column (#41489)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 09:23:20 -07:00
Adolfo Carvalho
515dfef955 fix(dashboard import): Importing existing dashboard via UI won't update charts and datasets (#34880)
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Joe Li <joe@preset.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 09:22:06 -07:00
Evan Rusackas
5a17ae1224 chore: drop Python 3.10 support, require Python 3.11+ (#42045)
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2026-07-20 08:16:22 -07:00
Mehmet Salih Yavuz
c1e660fac8 fix(databases): reset import file entry after invalid file error (#42240) 2026-07-20 18:06:44 +03:00
dependabot[bot]
19ca088de6 chore(deps): bump baseline-browser-mapping from 2.10.42 to 2.10.43 in /docs (#42227)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 07:44:06 -07:00
dependabot[bot]
57dc3c0208 chore(deps): bump antd from 6.5.0 to 6.5.1 in /superset-frontend (#42234)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 21:42:51 +07:00
dependabot[bot]
ceffbe80f9 chore(deps): bump hot-shots from 16.0.0 to 17.0.0 in /superset-websocket (#42236)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 21:42:00 +07:00
dependabot[bot]
eed41b6435 chore(deps): bump actions/setup-node from 6.4.0 to 7.0.0 (#42229)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 21:33:36 +07:00
J0s3-H3nr1qu3
277c814c5a fix(i18n): review and complete Portuguese (pt_PT) translation catalog (#42137)
Co-authored-by: José Teixeira <jose.teixeira@douroeci.com>
2026-07-20 14:37:43 +01:00
dependabot[bot]
7e63b29741 chore(deps): bump antd from 6.5.0 to 6.5.1 in /docs (#42226)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 03:56:10 -07:00
dependabot[bot]
b5ae7cbd0d chore(deps): bump caniuse-lite from 1.0.30001803 to 1.0.30001805 in /docs (#42228)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 03:56:03 -07:00
dependabot[bot]
e044d7783a chore(deps): bump dompurify from 3.4.11 to 3.4.12 in /superset-frontend (#42231)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 03:55:59 -07:00
dependabot[bot]
9e8b9ac8cf chore(deps-dev): bump tsx from 4.23.0 to 4.23.1 in /superset-frontend (#42232)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 03:55:55 -07:00
dependabot[bot]
19588826c8 chore(deps-dev): bump baseline-browser-mapping from 2.10.42 to 2.10.43 in /superset-frontend (#42233)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 03:55:50 -07:00
dependabot[bot]
8b415502cf chore(deps): bump chrono-node from 2.9.1 to 2.10.0 in /superset-frontend (#42235)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 03:55:43 -07:00
Rehan Islam
09148d0af6 fix(time-table): correctly sort computed columns (#41976) 2026-07-19 20:56:53 -07:00
Shaitan
3e40bebba8 fix(import): enforce write permission for objects created by legacy import paths (#42205)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:50:58 -07:00
Hans Yu
36dbff091f refactor: do not cascade to TaggedObject (#42223) 2026-07-19 20:18:53 -07:00
David
9e9b7c4bbd feat(ci): run helm-unittest on pull requests (#42206) 2026-07-20 07:36:19 +07:00
dependabot[bot]
42a2aede78 chore(deps): bump pandas from 2.1.4 to 2.3.3 (#42192)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 15:42:02 -07:00
lguichard78
c4d50472a9 fix(login): oauth and ldap login ignoring superset_app_root (#34657) 2026-07-18 14:58:22 -07:00
Younsung Lee
cd045886d0 feat(helm): add Gateway API HTTPRoute support (#41073)
Signed-off-by: younsl <cysl@kakao.com>
2026-07-18 14:57:02 -07:00
Jean Massucatto
751f5eb663 fix(dashboard): disable Discard button when there are no unsaved changes (#40832)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-07-18 14:15:17 -07:00
serdukow
4e098b6f38 fix(ag-grid-table): use t('Main') for time comparison column keys to fix i18n (#40681)
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-18 13:00:53 -07:00
David
5ee4a81906 feat(helm): add Superset MCP server templates (#40507) 2026-07-18 12:59:40 -07:00
Mike Bridge
caf017bd0b feat(versioning): cross-entity version activity view (#41076)
Co-authored-by: Mike Bridge <michael.bridge@ext.preset.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 11:06:54 -07:00
jenwitteng
a83171bce6 fix(database): capture result metadata after fetch (#42127)
Signed-off-by: Jenwit Amonpongitsara <jenwit.amonpongitsara@agoda.com>
2026-07-18 10:54:36 -07:00
Đỗ Trọng Hải
6c13ab6657 feat(ci): optimize Storybook testing in CI by replacing http-server with Python3 static server (#41956)
Signed-off-by: hainenber <dotronghai96@gmail.com>
2026-07-18 10:53:54 -07:00
Đỗ Trọng Hải
b9ede492b7 chore(lint): toggle import/named and import/export lint rules to error level (#42189)
Signed-off-by: hainenber <dotronghai96@gmail.com>
2026-07-18 10:53:40 -07:00
Gagandeep Singh Choudhary
fb29db6119 fix(sqllab): preserve database state on SET_DATABASES (#41281)
Co-authored-by: Joe Li <joe@preset.io>
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 10:52:45 -07:00
David
0a66e4ea2d fix(helm): support initContainers in supersetWebsockets (#42201) 2026-07-18 10:29:42 -07:00
AYUSHI RAI
3e0f9e60c0 fix(charts): preserve minute/second precision in X-axis time labels f… (#41505)
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-18 10:28:03 -07:00
Durgaprasad M L
ff1cf5f24e fix(table): hide search dropdown when search box is disabled (#41772) 2026-07-18 10:23:54 -07:00
dependabot[bot]
023b60c8f0 chore(deps-dev): bump pandas-gbq from 0.19.1 to 0.35.0 (#42161)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-18 07:23:17 -07:00
dependabot[bot]
2ada286ee9 chore(deps-dev): update pydynamodb requirement from >=0.4.2 to >=0.8.2 (#42162)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-18 07:23:13 -07:00
dependabot[bot]
42e5640f60 chore(deps-dev): update nzalchemy requirement from <11.2,>=11.0.2 to >=11.1.2,<11.2 (#42164)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-18 07:23:09 -07:00
dependabot[bot]
74703906df chore(deps): bump dom-to-image-more from 3.10.0 to 3.10.2 in /superset-frontend (#42180)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-18 21:15:23 +07:00
dependabot[bot]
3c23394675 chore(deps-dev): bump eslint from 10.6.0 to 10.7.0 in /superset-websocket (#42159)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-18 16:12:08 +07:00
dependabot[bot]
b6ce28ff72 chore(deps): bump typing-extensions from 4.15.0 to 4.16.0 (#42165)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-18 16:11:46 +07:00
dependabot[bot]
79eff6b6f1 chore(deps): bump greenlet from 3.5.1 to 3.5.3 (#42166)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-18 16:08:28 +07:00
dependabot[bot]
a58012fc0f chore(deps): bump the docusaurus-openapi group in /docs with 2 updates (#42195)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-18 16:05:39 +07:00
dependabot[bot]
dab5f30842 chore(deps): bump @docusaurus/faster from 3.10.1 to 3.10.2 in /docs (#42179)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2026-07-18 15:55:18 +07:00
dependabot[bot]
8299e7f87c chore(deps-dev): bump storybook from 10.4.6 to 10.5.0 in /superset-frontend (#42185)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2026-07-18 15:41:22 +07:00
dependabot[bot]
f6acf68dfa chore(deps): bump react-intersection-observer from 10.0.3 to 10.1.0 in /superset-frontend (#42181)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-18 13:31:08 +07:00
dependabot[bot]
cce77b42f6 chore(deps-dev): bump @formatjs/intl-durationformat from 0.10.16 to 0.10.17 in /superset-frontend (#42184)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-18 13:11:17 +07:00
262 changed files with 19327 additions and 8274 deletions

View File

@@ -50,9 +50,8 @@ body:
attributes:
label: Python version
options:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- Not applicable
- I don't know
validations:

View File

@@ -65,6 +65,11 @@ updates:
lumagl:
patterns:
- "@luma.gl/*"
storybook:
patterns:
- "@storybook/*"
- "storybook"
- "eslint-plugin-storybook"
open-pull-requests-limit: 30
versioning-strategy: increase
cooldown:
@@ -99,6 +104,22 @@ updates:
directory: "/docs/"
schedule:
interval: "daily"
groups:
storybook:
patterns:
- "@storybook/*"
- "storybook"
docusaurus:
patterns:
- "@docusaurus/*"
docusaurus-openapi:
patterns:
- "docusaurus-plugin-openapi-docs"
- "docusaurus-theme-openapi-docs"
typescript-eslint:
patterns:
- "@typescript-eslint/*"
- "typescript-eslint"
open-pull-requests-limit: 10
versioning-strategy: increase
cooldown:

View File

@@ -23,7 +23,7 @@ on:
jobs:
bump-python-package:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
actions: write
contents: write
@@ -42,7 +42,7 @@ jobs:
- name: Set up Python ${{ inputs.python-version }}
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.10"
python-version: "3.11"
- name: Install uv
run: pip install uv

View File

@@ -19,7 +19,7 @@ concurrency:
jobs:
check-python-deps:
runs-on: ubuntu-22.04
runs-on: ubuntu-26.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

View File

@@ -19,7 +19,7 @@ concurrency:
jobs:
check_db_migration_conflict:
name: Check DB migration conflict
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
contents: read
pull-requests: write

View File

@@ -16,7 +16,7 @@ concurrency:
jobs:
changes:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 10
permissions:
contents: read
@@ -42,7 +42,7 @@ jobs:
# analysis runners don't spin up. push/schedule runs always proceed:
# the change-detector returns "all changed" for non-PR events.
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 30
permissions:
actions: read

View File

@@ -24,7 +24,7 @@ permissions:
jobs:
dependency-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
steps:
- name: "Checkout Repository"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -48,7 +48,7 @@ jobs:
python-dependency-liccheck:
# NOTE: Configuration for liccheck lives in our pyproject.yml.
# You cannot use a liccheck.ini file in this workflow.
runs-on: ubuntu-22.04
runs-on: ubuntu-26.04
steps:
- name: "Checkout Repository"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

View File

@@ -19,7 +19,7 @@ concurrency:
jobs:
changes:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 10
permissions:
contents: read
@@ -40,14 +40,14 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
setup_matrix:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 5
outputs:
matrix_config: ${{ steps.set_matrix.outputs.matrix_config }}
steps:
- id: set_matrix
run: |
MATRIX_CONFIG=$(if [ "${{ github.event_name }}" == "pull_request" ]; then echo '["dev", "lean"]'; else echo '["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]'; fi)
MATRIX_CONFIG=$(if [ "${{ github.event_name }}" == "pull_request" ]; then echo '["dev", "lean"]'; else echo '["dev", "lean", "websocket", "dockerize", "py311", "py312"]'; fi)
echo "matrix_config=${MATRIX_CONFIG}" >> $GITHUB_OUTPUT
echo $GITHUB_OUTPUT
@@ -58,7 +58,7 @@ jobs:
needs.changes.outputs.python == 'true' ||
needs.changes.outputs.frontend == 'true' ||
needs.changes.outputs.docker == 'true'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 60
strategy:
matrix:
@@ -123,13 +123,20 @@ jobs:
# 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.
#
# supersetbot's "dev"/"lean" presets pin their own --build-arg
# PY_VER, which lands ahead of --extra-flags on the assembled
# buildx command line; docker/buildx keeps the last value for a
# repeated --build-arg key, so appending PY_VER here overrides
# supersetbot's pin and keeps the build on the Dockerfile's own
# supported Python version.
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" \
--extra-flags "--build-arg PY_VER=3.11.14-slim-trixie --build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG" \
$PLATFORM_ARG; then
break
fi
@@ -173,7 +180,7 @@ jobs:
# goal is to check that building the latest image works, not required for all PR pushes
needs: changes
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.changes.outputs.docker == 'true'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 30
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"

View File

@@ -15,7 +15,7 @@ jobs:
# 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
runs-on: ubuntu-26.04
permissions:
contents: read
id-token: write # required for npm trusted publishing (OIDC)
@@ -31,7 +31,7 @@ jobs:
# token, which makes npm attempt token auth and skip the OIDC
# trusted-publishing exchange. With no .npmrc auth line, npm authenticates
# via OIDC against the default registry (registry.npmjs.org).
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: "./superset-embedded-sdk/.nvmrc"
- run: npm ci

View File

@@ -16,7 +16,7 @@ concurrency:
jobs:
embedded-sdk-test:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
defaults:
run:
working-directory: superset-embedded-sdk
@@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: "./superset-embedded-sdk/.nvmrc"
registry-url: "https://registry.npmjs.org"

View File

@@ -11,7 +11,7 @@ permissions:
jobs:
config:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
steps:
@@ -29,7 +29,7 @@ jobs:
needs: config
if: needs.config.outputs.has-secrets
name: Generate Report
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -37,7 +37,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: "temurin"
java-version: "11"

View File

@@ -19,7 +19,7 @@ concurrency:
jobs:
validate-all-ghas:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
contents: read
# Required for the zizmor action to upload its SARIF results to
@@ -32,7 +32,7 @@ jobs:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "20"

115
.github/workflows/hold-label-ci-gate.yml vendored Normal file
View File

@@ -0,0 +1,115 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
name: Hold Label CI Gate
on:
pull_request:
types: [labeled, unlabeled]
permissions: {}
jobs:
cancel-on-hold:
name: Cancel CI runs when hold label applied
if: github.event.action == 'labeled' && startsWith(github.event.label.name, 'hold')
runs-on: ubuntu-26.04
permissions:
actions: write
pull-requests: read
steps:
- name: Cancel in-progress workflow runs
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
const sha = context.payload.pull_request.head.sha;
const { data: { workflow_runs: runs } } =
await github.rest.actions.listWorkflowRunsForRepo({
owner,
repo,
head_sha: sha,
per_page: 100,
});
const active = runs.filter(r =>
['in_progress', 'queued', 'waiting', 'requested', 'pending'].includes(r.status)
);
core.info(`Found ${active.length} active run(s) to cancel for SHA ${sha}`);
for (const run of active) {
try {
await github.rest.actions.cancelWorkflowRun({
owner,
repo,
run_id: run.id,
});
core.info(`Cancelled run ${run.id} (${run.name})`);
} catch (err) {
core.warning(`Could not cancel run ${run.id}: ${err.message}`);
}
}
rerun-on-unhold:
name: Re-run CI when hold label removed
if: github.event.action == 'unlabeled' && startsWith(github.event.label.name, 'hold')
runs-on: ubuntu-24.04
permissions:
actions: write
pull-requests: read
steps:
- name: Re-trigger cancelled workflow runs
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
const sha = context.payload.pull_request.head.sha;
// Check that no other hold* labels remain on the PR
const labels = context.payload.pull_request.labels.map(l => l.name);
const stillHeld = labels.some(l => l.startsWith('hold'));
if (stillHeld) {
core.info('PR still has a hold label — skipping re-run.');
return;
}
const { data: { workflow_runs: runs } } =
await github.rest.actions.listWorkflowRunsForRepo({
owner,
repo,
head_sha: sha,
per_page: 100,
});
const cancelled = runs.filter(r => r.conclusion === 'cancelled');
core.info(`Found ${cancelled.length} cancelled run(s) to re-trigger for SHA ${sha}`);
for (const run of cancelled) {
try {
await github.rest.actions.reRunWorkflow({
owner,
repo,
run_id: run.id,
});
core.info(`Re-triggered run ${run.id} (${run.name})`);
} catch (err) {
core.warning(`Could not re-run ${run.id}: ${err.message}`);
}
}

View File

@@ -9,7 +9,7 @@ on:
jobs:
superbot-orglabel:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
contents: read
pull-requests: write

View File

@@ -12,7 +12,7 @@ jobs:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
steps:
- uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
with:

View File

@@ -6,7 +6,7 @@ on:
jobs:
latest-release:
name: Add/update tag to new release
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
contents: write

View File

@@ -15,7 +15,7 @@ concurrency:
jobs:
license_check:
name: License Check
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -23,7 +23,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: "temurin"
java-version: "11"

View File

@@ -17,7 +17,7 @@ concurrency:
jobs:
check-hold-label:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
steps:
- name: Check for 'hold' label
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0

View File

@@ -21,7 +21,7 @@ concurrency:
jobs:
lint-check:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
contents: read
pull-requests: write

View File

@@ -23,7 +23,7 @@ concurrency:
jobs:
pre-commit:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 20
strategy:
matrix:
@@ -53,7 +53,7 @@ jobs:
run: go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.14.2
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: "superset-frontend/.nvmrc"
cache: "npm"

View File

@@ -11,7 +11,7 @@ permissions:
jobs:
config:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
steps:
@@ -29,7 +29,7 @@ jobs:
needs: config
if: needs.config.outputs.has-secrets
name: Bump version and publish package(s)
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
contents: write
steps:
@@ -50,7 +50,7 @@ jobs:
- name: Install Node.js
if: env.HAS_TAGS
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: "./superset-frontend/.nvmrc"

View File

@@ -40,7 +40,7 @@ concurrency:
jobs:
config:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
latest-release: ${{ steps.latest.outputs.tag }}
@@ -93,12 +93,12 @@ jobs:
needs: config
if: needs.config.outputs.has-secrets == '1'
name: docker-rebuild
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
strategy:
# Mirror the same matrix the release publisher uses so every variant
# operators consume from Docker Hub gets the refreshed base.
matrix:
build_preset: ["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]
build_preset: ["dev", "lean", "websocket", "dockerize", "py311", "py312"]
fail-fast: false
steps:
- name: "Checkout release tag: ${{ needs.config.outputs.latest-release }}"
@@ -117,7 +117,7 @@ jobs:
build: "true"
- name: Use Node.js 20
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 20
@@ -155,7 +155,7 @@ jobs:
notify-on-failure:
needs: [config, docker-rebuild]
if: failure() && needs.config.outputs.has-secrets == '1'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
contents: read
issues: write

View File

@@ -19,7 +19,7 @@ concurrency:
jobs:
test-load-examples:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
env:
PYTHONPATH: ${{ github.workspace }}
SUPERSET_CONFIG: tests.integration_tests.superset_test_config

View File

@@ -33,7 +33,7 @@ permissions:
jobs:
config:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
steps:
@@ -57,7 +57,7 @@ jobs:
(github.event_name != 'workflow_run' ||
github.event.workflow_run.head_repository.full_name == github.repository)
name: Build & Deploy
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
steps:
- name: "Checkout ${{ github.event.workflow_run.head_sha || github.sha }}"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -66,12 +66,12 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: "./docs/.nvmrc"
- name: Setup Python
uses: ./.github/actions/setup-backend/
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: "zulu"
java-version: "21"

View File

@@ -67,7 +67,7 @@ jobs:
# Build docs when PR changes docs/** (uses committed databases.json)
if: github.event_name == 'pull_request'
name: Build (PR trigger)
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
defaults:
run:
working-directory: docs
@@ -78,7 +78,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: "./docs/.nvmrc"
- name: yarn install
@@ -106,7 +106,7 @@ jobs:
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_repository.full_name == github.repository
name: Build (after integration tests)
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
defaults:
run:
working-directory: docs
@@ -118,7 +118,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: "./docs/.nvmrc"
- name: yarn install

View File

@@ -28,7 +28,7 @@ concurrency:
jobs:
changes:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 10
permissions:
contents: read
@@ -50,8 +50,7 @@ jobs:
cypress-matrix:
needs: changes
if: (needs.changes.outputs.python == 'true' || needs.changes.outputs.frontend == 'true') && github.event.pull_request.draft == false
# Somehow one test flakes on 24.04 for unknown reasons, this is the only GHA left on 22.04
runs-on: ubuntu-22.04
runs-on: ubuntu-26.04
timeout-minutes: 30
permissions:
contents: read
@@ -128,7 +127,7 @@ jobs:
with:
run: testdata
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: "./superset-frontend/.nvmrc"
cache: "npm"
@@ -173,7 +172,7 @@ jobs:
playwright-tests:
needs: changes
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-22.04
runs-on: ubuntu-26.04
timeout-minutes: 30
permissions:
contents: read
@@ -238,7 +237,7 @@ jobs:
with:
run: playwright_testdata
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: "./superset-frontend/.nvmrc"
cache: "npm"
@@ -301,7 +300,7 @@ jobs:
cypress-matrix-required:
needs: [changes, cypress-matrix]
if: always()
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 5
permissions: {}
steps:
@@ -323,7 +322,7 @@ jobs:
playwright-tests-required:
needs: [changes, playwright-tests]
if: always()
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 5
permissions: {}
steps:

View File

@@ -19,7 +19,7 @@ concurrency:
jobs:
test-superset-extensions-cli-package:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 30
strategy:
matrix:

View File

@@ -21,7 +21,7 @@ permissions:
jobs:
frontend-build:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 30
outputs:
should-run: ${{ steps.check.outputs.frontend }}
@@ -51,7 +51,7 @@ jobs:
git show -s --format=raw HEAD
docker buildx build \
-t $TAG \
--cache-from=type=registry,ref=apache/superset-cache:3.10-slim-trixie \
--cache-from=type=registry,ref=apache/superset-cache:3.11-slim-trixie \
--target superset-node-ci \
.
@@ -74,7 +74,7 @@ jobs:
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8]
fail-fast: false
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 20
steps:
- name: Download Docker Image Artifact
@@ -104,7 +104,7 @@ jobs:
report-coverage:
needs: [sharded-jest-tests]
if: needs.frontend-build.outputs.should-run == 'true'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 15
permissions:
id-token: write
@@ -146,7 +146,7 @@ jobs:
lint-frontend:
needs: frontend-build
if: needs.frontend-build.outputs.should-run == 'true'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 20
steps:
- name: Download Docker Image Artifact
@@ -171,7 +171,7 @@ jobs:
validate-frontend:
needs: frontend-build
if: needs.frontend-build.outputs.should-run == 'true'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 20
steps:
- name: Download Docker Image Artifact
@@ -191,7 +191,7 @@ jobs:
test-storybook:
needs: frontend-build
if: needs.frontend-build.outputs.should-run == 'true'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 25
steps:
- name: Download Docker Image Artifact

View File

@@ -19,7 +19,7 @@ concurrency:
jobs:
lint-test:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -31,7 +31,7 @@ jobs:
- name: Set up Helm
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
with:
version: v3.16.4
version: v3.21.3
- name: Setup Python
uses: ./.github/actions/setup-backend/
@@ -59,3 +59,9 @@ jobs:
CT_LINT_CONF: lintconf.yaml
CT_SINCE: HEAD
CT_CHART_REPOS: bitnami=https://charts.bitnami.com/bitnami
- name: Set up helm-unittest
run: helm plugin install https://github.com/helm-unittest/helm-unittest --version v1.1.1
- name: Run helm-unittest
run: helm unittest helm/superset

View File

@@ -20,7 +20,7 @@ on:
jobs:
release:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
contents: write
pull-requests: write

View File

@@ -24,7 +24,7 @@ concurrency:
jobs:
changes:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 10
permissions:
contents: read
@@ -48,7 +48,7 @@ jobs:
playwright-tests-experimental:
needs: changes
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-22.04
runs-on: ubuntu-26.04
timeout-minutes: 30
continue-on-error: true
permissions:
@@ -114,7 +114,7 @@ jobs:
with:
run: playwright_testdata
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: "./superset-frontend/.nvmrc"
cache: "npm"

View File

@@ -20,7 +20,7 @@ concurrency:
jobs:
changes:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 10
permissions:
contents: read
@@ -41,7 +41,7 @@ jobs:
test-mysql:
needs: changes
if: needs.changes.outputs.python == 'true'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 45
permissions:
id-token: write
@@ -127,7 +127,7 @@ jobs:
test-postgres:
needs: changes
if: needs.changes.outputs.python == 'true'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 45
permissions:
id-token: write
@@ -188,7 +188,7 @@ jobs:
test-sqlite:
needs: changes
if: needs.changes.outputs.python == 'true'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 45
permissions:
id-token: write
@@ -243,7 +243,7 @@ jobs:
test-postgres-required:
needs: [changes, test-postgres]
if: always()
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 5
steps:
- name: Check test-postgres result

View File

@@ -16,7 +16,7 @@ concurrency:
jobs:
changes:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 10
permissions:
contents: read
@@ -37,7 +37,7 @@ jobs:
test-postgres-presto:
needs: changes
if: needs.changes.outputs.python == 'true'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 45
permissions:
id-token: write
@@ -100,7 +100,7 @@ jobs:
test-postgres-hive:
needs: changes
if: needs.changes.outputs.python == 'true'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 45
permissions:
id-token: write

View File

@@ -21,7 +21,7 @@ concurrency:
jobs:
changes:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 10
permissions:
contents: read
@@ -42,7 +42,7 @@ jobs:
unit-tests:
needs: changes
if: needs.changes.outputs.python == 'true'
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 30
permissions:
id-token: write
@@ -99,7 +99,7 @@ jobs:
unit-tests-required:
needs: [changes, unit-tests]
if: always()
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 5
steps:
- name: Check unit-tests result

View File

@@ -20,7 +20,7 @@ permissions:
jobs:
post-comment:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
# Only act when the Translations workflow failed (which means a regression
# was detected — the workflow exits 1 on regression).
if: github.event.workflow_run.conclusion == 'failure'

View File

@@ -19,7 +19,7 @@ concurrency:
jobs:
frontend-check-translations:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
contents: read
pull-requests: read
@@ -38,7 +38,7 @@ jobs:
- name: Setup Node.js
if: steps.check.outputs.frontend
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: "./superset-frontend/.nvmrc"
cache: "npm"
@@ -55,7 +55,7 @@ jobs:
npm run build-translation
babel-extract:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
contents: read
pull-requests: read

View File

@@ -21,7 +21,7 @@ concurrency:
jobs:
app-checks:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
timeout-minutes: 20
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
@@ -29,7 +29,7 @@ jobs:
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: './superset-websocket/.nvmrc'
- name: Install dependencies

View File

@@ -15,7 +15,7 @@ on:
jobs:
supersetbot:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
if: >
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@supersetbot'))

View File

@@ -32,7 +32,7 @@ concurrency:
jobs:
config:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
steps:
@@ -50,13 +50,13 @@ jobs:
needs: config
if: needs.config.outputs.has-secrets
name: docker-release
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
contents: write
strategy:
matrix:
build_preset:
["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]
["dev", "lean", "websocket", "dockerize", "py311", "py312"]
fail-fast: false
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
@@ -75,7 +75,7 @@ jobs:
- name: Use Node.js 20
# zizmor: ignore[cache-poisoning] - node only runs the supersetbot CLI; no dependency cache is enabled
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 20
package-manager-cache: false
@@ -120,7 +120,7 @@ jobs:
update-prs-with-release-info:
needs: config
if: needs.config.outputs.has-secrets
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
contents: read
pull-requests: write
@@ -133,7 +133,7 @@ jobs:
- name: Use Node.js 20
# zizmor: ignore[cache-poisoning] - node only runs the supersetbot CLI; no dependency cache is enabled
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 20
package-manager-cache: false

View File

@@ -11,7 +11,7 @@ permissions:
jobs:
config:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
steps:
@@ -28,7 +28,7 @@ jobs:
process-and-upload:
needs: config
if: needs.config.outputs.has-secrets
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
name: Generate Reports
steps:
- name: Checkout Repository
@@ -37,7 +37,7 @@ jobs:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: "./superset-frontend/.nvmrc"

View File

@@ -7,7 +7,7 @@ on:
jobs:
welcome:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
if: github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
permissions:
pull-requests: write

View File

@@ -15,8 +15,8 @@
# limitations under the License.
#
# Python version installed; we need 3.10-3.11
PYTHON=`command -v python3.11 || command -v python3.10`
# Python version installed; we need 3.11-3.12
PYTHON=`command -v python3.11 || command -v python3.12`
.PHONY: install superset venv pre-commit up down logs ps nuke ports open
@@ -76,7 +76,7 @@ update-js:
venv:
# Create a virtual environment and activate it (recommended)
if ! [ -x "${PYTHON}" ]; then echo "You need Python 3.10 or 3.11 installed"; exit 1; fi
if ! [ -x "${PYTHON}" ]; then echo "You need Python 3.11 or 3.12 installed"; exit 1; fi
test -d venv || ${PYTHON} -m venv venv # setup a python3 virtualenv
. venv/bin/activate

View File

@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
FROM python:3.10-slim-trixie
FROM python:3.11-slim-trixie
RUN useradd --user-group --create-home --no-log-init --shell /bin/bash superset

View File

@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
FROM python:3.10-slim-trixie
FROM python:3.11-slim-trixie
RUN useradd --user-group --create-home --no-log-init --shell /bin/bash superset

View File

@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
FROM python:3.10-slim-trixie
FROM python:3.11-slim-trixie
ARG VERSION
RUN git clone --depth 1 --branch ${VERSION} https://github.com/apache/superset.git /superset

View File

@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
FROM python:3.10-slim-trixie
FROM python:3.11-slim-trixie
RUN apt-get update -y
RUN apt-get install -y \

View File

@@ -24,6 +24,55 @@ assists people when migrating to a new version.
## Next
### Dashboard "Export Data to Excel" requires a Celery worker and S3 bucket
A new dashboard action exports every chart's data to a single multi-sheet
`.xlsx` asynchronously. It is disabled by default and turns on only when
`EXCEL_EXPORT_S3_BUCKET` is set (the endpoint returns `501` otherwise). It also
requires a running Celery worker and a configured SMTP transport, since the task
emails the requesting user a pre-signed download link. New config keys:
`EXCEL_EXPORT_S3_BUCKET`, `EXCEL_EXPORT_S3_KEY_PREFIX`,
`EXCEL_EXPORT_LINK_TTL_SECONDS`, `EXCEL_EXPORT_S3_CLIENT_KWARGS`,
`EXCEL_EXPORT_TABLE_VIZ_TYPES`, and `EXCEL_EXPORT_REBUILD_VIZ_TYPES`.
The feature depends on `boto3`, which is **not** installed by default; install it
with `pip install apache-superset[excel-export]`.
Charts store their `query_context` only once they have been (re-)saved in
Explore, so older charts may have none. For a conservative allowlist of viz types
(`EXCEL_EXPORT_REBUILD_VIZ_TYPES`, default `table`, `big_number_total`,
`big_number`, `pie`) the export rebuilds a query context from the chart's saved
form data so those charts still export. The rebuild is a generic single-query
mapping and does **not** reproduce plugin post-processing (pivot, rolling,
forecast) or multi-query charts, so any chart of another type without a saved
query context is skipped and listed in the email for the user to re-save.
A second mode, **Export Images to Excel**, embeds non-table charts as rendered
images (which viz types stay tabular is controlled by
`EXCEL_EXPORT_TABLE_VIZ_TYPES`). It renders through the headless webdriver, so the
menu option only appears when the webdriver screenshot feature flags
(`ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS`,
`ENABLE_DASHBOARD_DOWNLOAD_WEBDRIVER_SCREENSHOT`) are enabled.
Deployments that override `CELERY_CONFIG` must add
`"superset.tasks.export_dashboard_excel"` to their `imports` tuple, or the task
will not register and exports will silently never run.
### SQL_QUERY_MUTATOR now honors MUTATE_AFTER_SPLIT in SQL Lab
SQL Lab now applies `SQL_QUERY_MUTATOR` according to `MUTATE_AFTER_SPLIT`, matching the documented semantics and the chart/query path. This only affects deployments that define `SQL_QUERY_MUTATOR` in `superset_config.py`:
- With `MUTATE_AFTER_SPLIT = True`, the mutator previously never ran in SQL Lab; it now runs on each individual statement (including on engines like BigQuery and Kusto that execute multiple statements as one block, where each statement is mutated before the statements are joined).
- With `MUTATE_AFTER_SPLIT = False` (the default), multi-statement SQL Lab queries previously applied the mutator to each statement separately; the mutator now runs once on the whole un-split query, as documented. Single-statement queries are unaffected.
- With `MUTATE_AFTER_SPLIT = False` on engines that execute statements individually, the mutator's output is re-parsed to split it into statements. A mutator that emits SQL Superset's parser cannot parse will now fail with a clear parse error before execution, and one that strips a query down to nothing raises an invalid-SQL error instead of executing an empty query.
### Python 3.10 support removed
Python 3.10 is no longer supported. Superset now requires **Python 3.11 or higher**.
Update your environment (virtualenv, Docker base image, CI configuration, etc.) to
Python 3.11+ before upgrading. The `apache/superset-cache:3.10-slim-trixie` and
`py310` Docker image variants are no longer published.
### Owners, dashboard roles, and RLS roles replaced by Subjects
Superset now uses subject-based access assignments for dashboards, charts, datasets,
@@ -216,6 +265,12 @@ The `thumbnail_url` field has been removed from `GET /api/v1/dashboard/` list re
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.
### Dashboard import can overwrite related charts, datasets, and databases
Re-importing an existing dashboard previously overwrote only the dashboard itself; its related charts, datasets, and databases were never updated (the importer hardcoded `overwrite=False` for them). They can now be overwritten as part of the import.
A new `overwrite_all` form field controls this, and defaults to `false` everywhere, so existing behavior is preserved: passing `overwrite=true` alone still overwrites only the dashboard, exactly as before. To also overwrite the related charts, datasets, and databases on the `/api/v1/dashboard/import/` endpoint, pass `overwrite_all=true` explicitly. The import modal in the UI exposes this as an "also overwrite all assets" checkbox, and the CLI `superset import-dashboards` and the `ImportDashboardsCommand` likewise default `overwrite_all` to `false`.
### Tagging fix for `create_all`-bootstrapped schemas
Only affects deployments whose metadata schema was created with SQLAlchemy's `create_all` (rather than `superset db upgrade`) on a foreign-key-enforcing backend — PostgreSQL, or MySQL with `FOREIGN_KEY_CHECKS=1`. Such schemas carry three invalid foreign keys on `tagged_object.object_id` that break tagging (`TAGGING_SYSTEM = True`) with a `ForeignKeyViolation`. Schemas built via `superset db upgrade` are unaffected.
@@ -241,6 +296,19 @@ A few save- and import-path internals change **unconditionally** (independent of
These are behavior changes that take effect on upgrade regardless of `ENABLE_VERSIONING_CAPTURE`; no operator action is required.
### Cross-entity version activity stream
A read-only companion to the version-history endpoints: each entity type gains a `GET /api/v1/{chart,dashboard,dataset}/<uuid>/activity/` endpoint returning a chronological, access-filtered stream of edits — the entity's own edits plus, for charts and dashboards, transitive edits to related entities during their association windows. Datasets have no related layer in V2, so `include=related` returns an empty stream for a dataset and `include=all` reduces to the dataset's own edits.
| Param | Type | Default | Purpose |
|---|---|---|---|
| `since` / `until` | ISO 8601 | — | Bound `issued_at` |
| `include` | `self` \| `related` \| `all` | `all` | Own edits, related edits, or both |
| `q` | string | — | Case-insensitive search over the full history, applied before pagination (so `count` reflects matches) |
| `page` / `page_size` | integer | `0` / `25` | Pagination (`page_size` clamped to 200) |
Authorization reuses the resource's `can_read` permission and per-object `raise_for_access`; related-entity rows are visibility-filtered to what the caller may see. The stream is empty unless version capture is on (`ENABLE_VERSIONING_CAPTURE`).
### 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`.

View File

@@ -71,7 +71,7 @@ x-common-build: &common-build
context: .
target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean`
cache_from:
- apache/superset-cache:3.10-slim-trixie
- apache/superset-cache:3.11-slim-trixie
args:
DEV_MODE: "true"
INCLUDE_CHROMIUM: ${INCLUDE_CHROMIUM:-false}

View File

@@ -33,7 +33,7 @@ x-common-build: &common-build
context: .
target: dev
cache_from:
- apache/superset-cache:3.10-slim-trixie
- apache/superset-cache:3.11-slim-trixie
services:
redis:

View File

@@ -38,7 +38,7 @@ x-common-build: &common-build
context: .
target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean`
cache_from:
- apache/superset-cache:3.10-slim-trixie
- apache/superset-cache:3.11-slim-trixie
args:
DEV_MODE: "true"
INCLUDE_CHROMIUM: ${INCLUDE_CHROMIUM:-false}

View File

@@ -87,6 +87,7 @@ class CeleryConfig:
"superset.tasks.scheduler",
"superset.tasks.thumbnails",
"superset.tasks.cache",
"superset.tasks.export_dashboard_excel",
)
result_backend = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_RESULTS_DB}"
worker_prefetch_multiplier = 1

View File

@@ -99,7 +99,7 @@ See [Connecting AI Clients](#connecting-ai-clients) for Claude Code, Claude Web,
## Prerequisites
- Apache Superset 5.0+ running and accessible
- Python 3.10+
- Python 3.11+
- The `fastmcp` package (`pip install fastmcp`)
---

View File

@@ -379,7 +379,7 @@ functioning across environments.
Make sure your machine meets the [OS dependencies](https://superset.apache.org/docs/installation/pypi#os-dependencies) before following these steps.
You also need to install MySQL.
Ensure that you are using Python version 3.9, 3.10 or 3.11, then proceed with:
Ensure that you are using Python version 3.11 or 3.12, then proceed with:
```bash
# Create a virtual environment and activate it (recommended)

View File

@@ -82,7 +82,7 @@ If applicable, add screenshots or recordings.
### Environment
- Superset version: [e.g., 3.0.0]
- Python version: [e.g., 3.9.7]
- Python version: [e.g., 3.11.7]
- Node version: [e.g., 18.17.0]
- Database: [e.g., PostgreSQL 14]
- Browser: [e.g., Chrome 120]
@@ -125,7 +125,7 @@ No error messages in browser console or server logs.
### Environment
- Superset version: 3.0.0
- Python version: 3.9.16
- Python version: 3.11.16
- Database: PostgreSQL 14.9
- Browser: Chrome 120.0.6099.71
- OS: macOS 14.2

View File

@@ -52,7 +52,7 @@ Everything you need to contribute to the Apache Superset project. This section i
## Development Resources
### Prerequisites
- **Python**: 3.9, 3.10, or 3.11
- **Python**: 3.11 or 3.12
- **Node.js**: 18.x or 20.x
- **npm**: 9.x or 10.x
- **Git**: Basic understanding

View File

@@ -0,0 +1,99 @@
---
title: Exporting Dashboard Data to Excel
hide_title: true
sidebar_position: 7
version: 1
---
# Exporting Dashboard Data to Excel
Superset can export every chart on a dashboard to a single Excel workbook, with
each chart's underlying data rendered as its own worksheet. The export reflects
the dashboard's currently applied filters and runs asynchronously: when it
finishes, the requesting user receives an email with a time-limited download
link.
## Using the export
From a dashboard, open the **... (actions) → Download** submenu and choose
**Export Data to Excel**. The action appears for users who have the dashboard
`can_export` permission. You'll see a confirmation that the export is being
prepared; the workbook arrives by email when it's ready.
A second option, **Export Images to Excel**, embeds each non-table chart as a
rendered image (tables stay tabular) instead of exporting raw data. Because it
renders charts through the headless webdriver, this option only appears when the
webdriver screenshot feature flags are enabled (see the prerequisites below);
which viz types stay tabular is controlled by `EXCEL_EXPORT_TABLE_VIZ_TYPES`.
Notes on the generated workbook:
- One worksheet per chart, named `{chart_id} - {chart title}` (truncated to
Excel's 31-character limit; the chart id keeps names unique).
- Charts nested in tabs are included.
- Data reflects the dashboard's active filter state at the time of export.
- A chart with no saved query context is skipped and listed in the email; open
the chart in Explore and re-save it to include it next time.
- Row counts per sheet are capped the same way as the chart-level CSV/Excel
export (`ROW_LIMIT`, bounded by `SQL_MAX_ROW`), and never exceed Excel's
per-sheet maximum.
## Prerequisites
This feature is **disabled by default**. It requires:
1. **The `boto3` dependency.** It is not installed by default; install it with
`pip install apache-superset[excel-export]`. Without it, exports fail and the
user receives a failure email.
2. **An S3 bucket.** Set `EXCEL_EXPORT_S3_BUCKET`. Until it is set, the export
endpoint returns `501` and the menu action surfaces a "not configured"
message.
3. **A running Celery worker.** The export runs as a Celery task. If no worker
is running, the request is accepted but nothing is produced.
4. **A configured SMTP transport.** The download link is delivered by email
using the same settings as alerts & reports (`SMTP_*`,
`EMAIL_REPORTS_SUBJECT_PREFIX`).
**Export Images to Excel** additionally requires a working headless webdriver —
the same infrastructure scheduled reports and thumbnails use (`WEBDRIVER_*`,
plus the `ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS` and
`ENABLE_DASHBOARD_DOWNLOAD_WEBDRIVER_SCREENSHOT` feature flags). The menu option
is hidden when those flags are off; if the webdriver is unreachable, image
charts come back empty even though the data export path still works.
Deployments that override `CELERY_CONFIG` must add
`"superset.tasks.export_dashboard_excel"` to the `imports` tuple, or the task
will not register.
## Configuration keys
| Key | Default | Description |
| --- | --- | --- |
| `EXCEL_EXPORT_S3_BUCKET` | `None` | Destination bucket. Required; `501` if unset. |
| `EXCEL_EXPORT_S3_KEY_PREFIX` | `"dashboard-exports/"` | Key prefix: `{prefix}{dashboard_id}/{job_id}.xlsx`. |
| `EXCEL_EXPORT_LINK_TTL_SECONDS` | `86400` | Lifetime of the pre-signed download URL (24h). |
| `EXCEL_EXPORT_S3_CLIENT_KWARGS` | `{}` | Extra kwargs for `boto3.client("s3", ...)` — e.g. `region_name`, or `endpoint_url` for MinIO/LocalStack. |
| `EXCEL_EXPORT_TABLE_VIZ_TYPES` | `None` | Viz types kept tabular in **Export Images to Excel** mode; every other type is embedded as an image. `None` uses the built-in default (`table`, `pivot_table`, `pivot_table_v2`). |
Credentials and region resolve through the standard boto3 chain (environment
variables, shared config, or instance role) unless overridden via
`EXCEL_EXPORT_S3_CLIENT_KWARGS`. The worker needs `s3:PutObject` on the bucket.
## Security considerations
- The emailed link is a **pre-signed S3 URL**: anyone who holds it can download
the workbook until it expires. Keep the bucket **private**, enable
encryption, and consider a lifecycle rule to delete objects after a few days.
Lower `EXCEL_EXPORT_LINK_TTL_SECONDS` if 24 hours is too long for your data.
- The export runs with the requesting user's permissions; each chart's query is
access-checked, so users only ever receive data they are entitled to.
## Limitations
- **Embedded dashboards / guest tokens are not supported** in this version,
because guest users have no email address to deliver the link to. Logged-in
users viewing an embedded dashboard can still use the export.
- The default **Export Data to Excel** mode exports data only (no visual
styling). Use **Export Images to Excel** to embed rendered chart images, which
requires the webdriver infrastructure described in the prerequisites.
- Scheduled/automated exports are not part of this feature.

View File

@@ -44,12 +44,12 @@
},
"dependencies": {
"@ant-design/icons": "^6.2.5",
"@docusaurus/core": "^3.10.1",
"@docusaurus/faster": "^3.10.1",
"@docusaurus/plugin-client-redirects": "^3.10.1",
"@docusaurus/preset-classic": "3.10.1",
"@docusaurus/theme-live-codeblock": "^3.10.1",
"@docusaurus/theme-mermaid": "^3.10.1",
"@docusaurus/core": "^3.10.2",
"@docusaurus/faster": "^3.10.2",
"@docusaurus/plugin-client-redirects": "^3.10.2",
"@docusaurus/preset-classic": "3.10.2",
"@docusaurus/theme-live-codeblock": "^3.10.2",
"@docusaurus/theme-mermaid": "^3.10.2",
"@emotion/core": "^11.0.0",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.14.1",
@@ -58,14 +58,14 @@
"@fontsource/inter": "^5.2.8",
"@mdx-js/react": "^3.1.1",
"@saucelabs/theme-github-codeblock": "^0.3.0",
"@storybook/addon-docs": "^10.4.5",
"@storybook/addon-docs": "^10.5.0",
"@superset-ui/core": "^0.20.4",
"@swc/core": "^1.15.43",
"antd": "^6.5.0",
"baseline-browser-mapping": "^2.10.40",
"caniuse-lite": "^1.0.30001803",
"docusaurus-plugin-openapi-docs": "^5.1.1",
"docusaurus-theme-openapi-docs": "^5.1.1",
"antd": "^6.5.1",
"baseline-browser-mapping": "^2.10.43",
"caniuse-lite": "^1.0.30001805",
"docusaurus-plugin-openapi-docs": "^5.1.2",
"docusaurus-theme-openapi-docs": "^5.1.2",
"js-yaml": "^5.2.0",
"json-bigint": "^1.0.0",
"prism-react-renderer": "^2.4.1",
@@ -77,20 +77,20 @@
"react-table": "^7.8.0",
"remark-import-partial": "^0.0.2",
"reselect": "^5.2.0",
"storybook": "^10.4.5",
"storybook": "^10.5.0",
"swagger-ui-react": "^5.32.8",
"swc-loader": "^0.2.7",
"tinycolor2": "^1.4.2",
"unist-util-visit": "^5.1.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.10.1",
"@docusaurus/tsconfig": "^3.10.1",
"@docusaurus/module-type-aliases": "^3.10.2",
"@docusaurus/tsconfig": "^3.10.2",
"@eslint/js": "^9.39.2",
"@types/js-yaml": "^4.0.9",
"@types/react": "^19.1.8",
"@typescript-eslint/eslint-plugin": "^8.59.3",
"@typescript-eslint/parser": "^8.63.0",
"@typescript-eslint/eslint-plugin": "^8.64.0",
"@typescript-eslint/parser": "^8.64.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.6",
@@ -98,7 +98,7 @@
"globals": "^17.7.0",
"prettier": "^3.9.5",
"typescript": "~6.0.3",
"typescript-eslint": "^8.63.0",
"typescript-eslint": "^8.64.0",
"webpack": "^5.108.2"
},
"browserslist": {
@@ -124,8 +124,6 @@
"serialize-javascript": "7.0.5",
"d3-color": "3.1.0",
"ws": "^8.21.0",
"@docusaurus/core/@docusaurus/utils/gray-matter/js-yaml": "^3.15.0",
"@docusaurus/core/**/js-yaml": "^4.3.0",
"docusaurus-plugin-openapi-docs/**/js-yaml": "^4.3.0"
},
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
# superset
![Version: 0.21.0](https://img.shields.io/badge/Version-0.21.0-informational?style=flat-square)
![Version: 0.22.0](https://img.shields.io/badge/Version-0.22.0-informational?style=flat-square)
Apache Superset is a modern, enterprise-ready business intelligence web application
@@ -125,6 +125,13 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| fullnameOverride | string | `nil` | Provide a name to override the full names of resources |
| globalPodAnnotations | object | `{}` | Global pod annotations to be added to all pods Use this to set annotations that apply to all Superset components Component-specific podAnnotations will be merged with these global annotations |
| hostAliases | list | `[]` | Custom hostAliases for all superset pods # https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/ |
| httproute | object | see `values.yaml` | Gateway API HTTPRoute for exposing Superset via a Gateway. Requires the Gateway API CRDs (gateway.networking.k8s.io/v1) installed in the cluster. |
| httproute.annotations | object | `{}` | Annotations to add to the HTTPRoute |
| httproute.apiVersion | string | `"gateway.networking.k8s.io/v1"` | HTTPRoute apiVersion. Override to gateway.networking.k8s.io/v1beta1 for older Gateway API installations that have not promoted HTTPRoute to v1. |
| httproute.hostnames | list | `[]` | Hostnames that match against the HTTP Host header (templated) |
| httproute.labels | object | `{}` | Additional labels to add to the HTTPRoute |
| httproute.parentRefs | list | `[]` | Gateways this HTTPRoute attaches to |
| httproute.rules | list | `[{"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]` | Routing rules. Each rule is backed by the Superset service. Set `weight` per rule to leave room for traffic splitting (defaults to 1). When `supersetWebsockets.enabled` is true, an extra rule routing `supersetWebsockets.ingress.path` to the `-ws` service is appended automatically, mirroring the ingress behavior. |
| image.pullPolicy | string | `"IfNotPresent"` | |
| image.repository | string | `"apachesuperset.docker.scarf.sh/apache/superset"` | |
| image.tag | string | `nil` | |
@@ -246,6 +253,60 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| supersetCeleryFlower.startupProbe.successThreshold | int | `1` | |
| supersetCeleryFlower.startupProbe.timeoutSeconds | int | `1` | |
| supersetCeleryFlower.topologySpreadConstraints | list | `[]` | TopologySpreadConstrains to be added to supersetCeleryFlower deployments |
| supersetMcp.affinity | object | `{}` | Affinity to be added to supersetMcp deployment |
| supersetMcp.command | list | a `superset mcp run` command | Command |
| supersetMcp.containerSecurityContext | object | `{}` | |
| supersetMcp.deploymentAdditionalPodSpec | object | `{}` | Custom pod spec to be added to supersetMcp deployment |
| supersetMcp.deploymentAnnotations | object | `{}` | Annotations to be added to supersetMcp deployment |
| supersetMcp.deploymentLabels | object | `{}` | Labels to be added to supersetMcp deployment |
| supersetMcp.enabled | bool | `false` | Enables the Superset MCP Server. To expose it via the shared ingress at /mcp, also set supersetMcp.ingress.enabled=true. WARNING: this requires fastMCP to be installed, which can be done by installing `apache-superset[fastmcp]` |
| supersetMcp.extraContainers | list | `[]` | Launch additional containers into supersetMcp pods |
| supersetMcp.extraInitContainers | list | `[]` | Extra init containers appended after supersetMcp initContainers |
| supersetMcp.forceReload | bool | `false` | If true, forces deployment to reload on each upgrade |
| supersetMcp.ingress.enabled | bool | `false` | If true, the MCP server will be exposed via the ingress /mcp subpath |
| supersetMcp.ingress.path | string | `"/mcp"` | |
| supersetMcp.ingress.pathType | string | `"Prefix"` | |
| supersetMcp.initContainers | list | a container waiting for postgres and redis | List of init containers |
| supersetMcp.lifecycle | object | `{}` | Container lifecycle hooks for the worker pod |
| supersetMcp.livenessProbe.failureThreshold | int | `3` | |
| supersetMcp.livenessProbe.httpGet.path | string | `"/health"` | |
| supersetMcp.livenessProbe.httpGet.port | string | `"mcp"` | |
| supersetMcp.livenessProbe.initialDelaySeconds | int | `15` | |
| supersetMcp.livenessProbe.periodSeconds | int | `15` | |
| supersetMcp.livenessProbe.successThreshold | int | `1` | |
| supersetMcp.livenessProbe.timeoutSeconds | int | `3` | |
| supersetMcp.podAnnotations | object | `{}` | Annotations to be added to supersetMcp pods |
| supersetMcp.podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":1,"minAvailable":1}` | Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetMcp pods |
| supersetMcp.podDisruptionBudget.enabled | bool | `false` | Whether the pod disruption budget should be created |
| supersetMcp.podDisruptionBudget.maxUnavailable | int | `1` | If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/\#specifying-a-poddisruptionbudget |
| supersetMcp.podDisruptionBudget.minAvailable | int | `1` | If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/\#specifying-a-poddisruptionbudget |
| supersetMcp.podLabels | object | `{}` | Labels to be added to supersetMcp pods |
| supersetMcp.podSecurityContext | object | `{}` | |
| supersetMcp.priorityClassName | string | `nil` | Set priorityClassName for supersetMcp pods |
| supersetMcp.readinessProbe.failureThreshold | int | `3` | |
| supersetMcp.readinessProbe.httpGet.path | string | `"/health"` | |
| supersetMcp.readinessProbe.httpGet.port | string | `"mcp"` | |
| supersetMcp.readinessProbe.initialDelaySeconds | int | `15` | |
| supersetMcp.readinessProbe.periodSeconds | int | `15` | |
| supersetMcp.readinessProbe.successThreshold | int | `1` | |
| supersetMcp.readinessProbe.timeoutSeconds | int | `3` | |
| supersetMcp.replicaCount | int | `1` | |
| supersetMcp.resources | object | `{}` | Resource settings for the supersetMcp pods - these settings overwrite might existing values from the global resources object defined above. |
| supersetMcp.service.annotations | object | `{}` | |
| supersetMcp.service.loadBalancerIP | string | `nil` | |
| supersetMcp.service.nodePort.http | int | `"nil"` | |
| supersetMcp.service.port | int | `5008` | |
| supersetMcp.service.type | string | `"ClusterIP"` | |
| supersetMcp.startupProbe.failureThreshold | int | `60` | |
| supersetMcp.startupProbe.httpGet.path | string | `"/health"` | |
| supersetMcp.startupProbe.httpGet.port | string | `"mcp"` | |
| supersetMcp.startupProbe.initialDelaySeconds | int | `15` | |
| supersetMcp.startupProbe.periodSeconds | int | `5` | |
| supersetMcp.startupProbe.successThreshold | int | `1` | |
| supersetMcp.startupProbe.timeoutSeconds | int | `3` | |
| supersetMcp.strategy | object | `{}` | |
| supersetMcp.terminationGracePeriodSeconds | string | `nil` | Pod termination grace period (seconds) for the worker pod so in-flight tasks can drain before SIGKILL |
| supersetMcp.topologySpreadConstraints | list | `[]` | TopologySpreadConstrains to be added to supersetMcp deployments |
| supersetNode.affinity | object | `{}` | Affinity to be added to supersetNode deployment |
| supersetNode.autoscaling.enabled | bool | `false` | |
| supersetNode.autoscaling.maxReplicas | int | `100` | |
@@ -305,11 +366,13 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| supersetWebsockets.deploymentAnnotations | object | `{}` | |
| supersetWebsockets.enabled | bool | `false` | This is only required if you intend to use `GLOBAL_ASYNC_QUERIES` in `ws` mode see https://superset.apache.org/docs/contributing/misc#async-chart-queries |
| supersetWebsockets.extraContainers | list | `[]` | Launch additional containers into supersetWebsockets pods |
| supersetWebsockets.extraInitContainers | list | `[]` | Extra init containers appended after supersetWebsockets initContainers |
| supersetWebsockets.image.pullPolicy | string | `"IfNotPresent"` | |
| supersetWebsockets.image.repository | string | `"oneacrefund/superset-websocket"` | There is no official image (yet), this one is community-supported |
| supersetWebsockets.image.tag | string | `"latest"` | |
| supersetWebsockets.ingress.path | string | `"/ws"` | |
| supersetWebsockets.ingress.pathType | string | `"Prefix"` | |
| supersetWebsockets.initContainers | list | a container waiting for redis | List of initContainers |
| supersetWebsockets.livenessProbe.failureThreshold | int | `3` | |
| supersetWebsockets.livenessProbe.httpGet.path | string | `"/health"` | |
| supersetWebsockets.livenessProbe.httpGet.port | string | `"ws"` | |

View File

@@ -761,3 +761,9 @@ app.kubernetes.io/name: {{ include "superset.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: worker
{{- end }}
{{- define "supersetMcp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "superset.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: mcp
{{- end }}

View File

@@ -0,0 +1,197 @@
{{/*
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.
*/}}
{{- if .Values.supersetMcp.enabled -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "superset.fullname" . }}-mcp
namespace: {{ .Release.Namespace }}
labels:
{{- include "superset.componentLabels" (dict "component" "mcp" "root" .) | nindent 4 }}
{{- if .Values.supersetMcp.deploymentAnnotations }}
annotations: {{- toYaml .Values.supersetMcp.deploymentAnnotations | nindent 4 }}
{{- end }}
spec:
replicas: {{ .Values.supersetMcp.replicaCount }}
selector:
matchLabels:
{{- include "supersetMcp.selectorLabels" . | nindent 6 }}
{{- if .Values.supersetMcp.strategy }}
strategy: {{- toYaml .Values.supersetMcp.strategy | nindent 4 }}
{{- end }}
template:
metadata:
annotations:
checksum/superset_config.py: {{ include "superset.config" . | sha256sum }}
checksum/superset_bootstrap.sh: {{ tpl .Values.bootstrapScript . | sha256sum }}
checksum/database: {{ .Values.database | toYaml | sha256sum }}
checksum/redis: {{ .Values.cache | toYaml | sha256sum }}
checksum/config: {{ .Values.config | toYaml | sha256sum }}
checksum/featureFlags: {{ .Values.featureFlags | toYaml | sha256sum }}
checksum/extraConfigs: {{ .Values.extraConfigs | toYaml | sha256sum }}
checksum/extraSecrets: {{ .Values.extraSecrets | toYaml | sha256sum }}
checksum/extraSecretEnv: {{ .Values.extraSecretEnv | toYaml | sha256sum }}
checksum/configOverrides: {{ .Values.configOverrides | toYaml | sha256sum }}
checksum/configOverridesFiles: {{ .Values.configOverridesFiles | toYaml | sha256sum }}
{{- if .Values.supersetMcp.forceReload }}
# Optionally force the thing to reload
force-reload: {{ randAlphaNum 5 | quote }}
{{- end }}
{{- if .Values.supersetMcp.podAnnotations }}
{{- toYaml .Values.supersetMcp.podAnnotations | nindent 8 }}
{{- end }}
{{- with .Values.globalPodAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "supersetMcp.selectorLabels" . | nindent 8 }}
{{- if .Values.supersetMcp.podLabels }}
{{- toYaml .Values.supersetMcp.podLabels | nindent 8 }}
{{- end }}
spec:
{{- if .Values.supersetMcp.deploymentAdditionalPodSpec }}
{{- tpl (toYaml .Values.supersetMcp.deploymentAdditionalPodSpec) . | nindent 6 }}
{{- end }}
{{- if or (.Values.serviceAccount.create) (.Values.serviceAccountName) }}
serviceAccountName: {{ template "superset.serviceAccountName" . }}
{{- end }}
securityContext:
runAsUser: {{ .Values.runAsUser }}
{{- if .Values.supersetMcp.podSecurityContext }}
{{- toYaml .Values.supersetMcp.podSecurityContext | nindent 8 }}
{{- end }}
{{- if or .Values.supersetMcp.initContainers .Values.supersetMcp.extraInitContainers }}
initContainers:
{{- if .Values.supersetMcp.initContainers }}
{{- tpl (toYaml .Values.supersetMcp.initContainers) . | nindent 6 }}
{{- end }}
{{- if .Values.supersetMcp.extraInitContainers }}
{{- tpl (toYaml .Values.supersetMcp.extraInitContainers) . | nindent 6 }}
{{- end }}
{{- end }}
{{- with .Values.hostAliases }}
hostAliases: {{- toYaml . | nindent 6 }}
{{- end }}
containers:
- name: "{{ .Chart.Name }}-mcp"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- if .Values.supersetMcp.containerSecurityContext }}
securityContext: {{- toYaml .Values.supersetMcp.containerSecurityContext | nindent 12 }}
{{- end }}
command: {{ tpl (toJson .Values.supersetMcp.command) . }}
env:
{{- range $key, $value := .Values.extraEnv }}
- name: {{ $key | quote}}
value: {{ $value | quote }}
{{- end }}
{{- if .Values.extraEnvRaw }}
{{- toYaml .Values.extraEnvRaw | nindent 12 }}
{{- end }}
envFrom:
- secretRef:
name: {{ tpl .Values.envFromSecret . | quote }}
{{- range .Values.envFromSecrets }}
- secretRef:
name: {{ tpl . $ | quote }}
{{- end }}
ports:
- name: mcp
containerPort: {{ .Values.supersetMcp.service.port }}
protocol: TCP
volumeMounts:
- name: superset-config
mountPath: {{ .Values.configMountPath | quote }}
readOnly: true
{{- if .Values.extraConfigs }}
- name: superset-extra-config
mountPath: {{ .Values.extraConfigMountPath | quote }}
readOnly: true
{{- end }}
{{- with .Values.extraVolumeMounts }}
{{- tpl (toYaml .) $ | nindent 12 -}}
{{- end }}
{{- if .Values.supersetMcp.startupProbe }}
startupProbe: {{- .Values.supersetMcp.startupProbe | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.supersetMcp.readinessProbe }}
readinessProbe: {{- .Values.supersetMcp.readinessProbe | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.supersetMcp.livenessProbe }}
livenessProbe: {{- .Values.supersetMcp.livenessProbe | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.supersetMcp.lifecycle }}
lifecycle: {{- .Values.supersetMcp.lifecycle | toYaml | nindent 12 }}
{{- end }}
resources:
{{- if .Values.supersetMcp.resources }}
{{- toYaml .Values.supersetMcp.resources | nindent 12 }}
{{- else }}
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
{{- if .Values.supersetMcp.extraContainers }}
{{- tpl (toYaml .Values.supersetMcp.extraContainers) . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}
{{- end }}
{{- if or .Values.affinity .Values.supersetMcp.affinity }}
affinity:
{{- with .Values.affinity }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.supersetMcp.affinity }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- if .Values.supersetMcp.priorityClassName }}
priorityClassName: {{ .Values.supersetMcp.priorityClassName }}
{{- end }}
{{- if or .Values.topologySpreadConstraints .Values.supersetMcp.topologySpreadConstraints }}
topologySpreadConstraints:
{{- with .Values.topologySpreadConstraints }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.supersetMcp.topologySpreadConstraints }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- with .Values.tolerations }}
tolerations: {{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.supersetMcp.terminationGracePeriodSeconds }}
terminationGracePeriodSeconds: {{ .Values.supersetMcp.terminationGracePeriodSeconds }}
{{- end }}
{{- if .Values.imagePullSecrets }}
imagePullSecrets: {{- toYaml .Values.imagePullSecrets | nindent 8 }}
{{- end }}
volumes:
- name: superset-config
secret:
secretName: {{ tpl .Values.configFromSecret . }}
{{- if .Values.extraConfigs }}
- name: superset-extra-config
configMap:
name: {{ template "superset.fullname" . }}-extra-config
{{- end }}
{{- with .Values.extraVolumes }}
{{- tpl (toYaml .) $ | nindent 8 -}}
{{- end }}
{{- end -}}

View File

@@ -64,6 +64,15 @@ spec:
{{- if .Values.supersetWebsockets.podSecurityContext }}
{{- toYaml .Values.supersetWebsockets.podSecurityContext | nindent 8 }}
{{- end }}
{{- if or .Values.supersetWebsockets.initContainers .Values.supersetWebsockets.extraInitContainers }}
initContainers:
{{- if .Values.supersetWebsockets.initContainers }}
{{- tpl (toYaml .Values.supersetWebsockets.initContainers) . | nindent 6 }}
{{- end }}
{{- if .Values.supersetWebsockets.extraInitContainers }}
{{- tpl (toYaml .Values.supersetWebsockets.extraInitContainers) . | nindent 6 }}
{{- end }}
{{- end }}
{{- with .Values.hostAliases }}
hostAliases: {{- toYaml . | nindent 6 }}
{{- end }}

View File

@@ -0,0 +1,83 @@
{{/*
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.
*/}}
{{- if .Values.httproute.enabled -}}
{{- $fullName := include "superset.fullname" . -}}
apiVersion: {{ .Values.httproute.apiVersion | default "gateway.networking.k8s.io/v1" }}
kind: HTTPRoute
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace }}
labels:
app: {{ template "superset.name" . }}
chart: {{ template "superset.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- if .Values.extraLabels }}
{{- toYaml .Values.extraLabels | nindent 4 }}
{{- end }}
{{- with .Values.httproute.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.httproute.annotations }}
annotations: {{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.httproute.parentRefs }}
parentRefs:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.httproute.hostnames }}
hostnames:
{{- tpl (toYaml .) $ | nindent 4 }}
{{- end }}
rules:
{{- range .Values.httproute.rules }}
- backendRefs:
- group: ''
kind: Service
name: {{ $fullName }}
port: {{ $.Values.service.port }}
weight: {{ .weight | default 1 }}
{{- with .filters }}
filters:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .matches }}
matches:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .timeouts }}
timeouts:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- if .Values.supersetWebsockets.enabled }}
- backendRefs:
- group: ''
kind: Service
name: {{ $fullName }}-ws
port: {{ .Values.supersetWebsockets.service.port }}
weight: 1
matches:
- path:
type: PathPrefix
value: {{ .Values.supersetWebsockets.ingress.path }}
{{- end }}
{{- end }}

View File

@@ -64,6 +64,15 @@ spec:
port:
name: ws
{{- end }}
{{- if and $.Values.supersetMcp.enabled $.Values.supersetMcp.ingress.enabled }}
- path: {{ $.Values.supersetMcp.ingress.path }}
pathType: {{ $.Values.supersetMcp.ingress.pathType }}
backend:
service:
name: "{{ template "superset.fullname" $ }}-mcp"
port:
name: mcp
{{- end }}
{{- end }}
{{- if .Values.ingress.extraHostsRaw }}
{{- toYaml .Values.ingress.extraHostsRaw | nindent 4 }}

View File

@@ -0,0 +1,42 @@
{{/*
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.
*/}}
{{- with .Values.supersetMcp.podDisruptionBudget }}
{{- if .enabled -}}
{{- if and .minAvailable .maxUnavailable }}
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
{{- end}}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "superset.fullname" $ }}-mcp-pdb
labels:
{{- include "superset.componentLabels" (dict "component" "mcp" "root" $) | nindent 4 }}
spec:
{{- if .minAvailable }}
minAvailable: {{ .minAvailable }}
{{- end }}
{{- if .maxUnavailable }}
maxUnavailable: {{ .maxUnavailable }}
{{- end }}
selector:
matchLabels:
{{- include "supersetMcp.selectorLabels" $ | nindent 6 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,46 @@
{{/*
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.
*/}}
{{- if .Values.supersetMcp.enabled }}
apiVersion: v1
kind: Service
metadata:
name: "{{ template "superset.fullname" . }}-mcp"
namespace: {{ .Release.Namespace }}
labels:
{{- include "superset.componentLabels" (dict "component" "mcp" "root" .) | nindent 4 }}
{{- with .Values.supersetMcp.service.annotations }}
annotations: {{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.supersetMcp.service.type }}
ports:
- port: {{ .Values.supersetMcp.service.port }}
targetPort: mcp
protocol: TCP
name: mcp
{{- if and (or (eq .Values.supersetMcp.service.type "NodePort") (eq .Values.supersetMcp.service.type "LoadBalancer")) (not (empty .Values.supersetMcp.service.nodePort.http)) }}
nodePort: {{ .Values.supersetMcp.service.nodePort.http }}
{{- end }}
selector:
{{- include "supersetMcp.selectorLabels" . | nindent 4 }}
{{- if .Values.supersetMcp.service.loadBalancerIP }}
loadBalancerIP: {{ .Values.supersetMcp.service.loadBalancerIP }}
{{- end }}
{{- end }}

View File

@@ -21,6 +21,7 @@ templates:
- deployment-beat.yaml
- deployment-flower.yaml
- deployment-ws.yaml
- deployment-mcp.yaml
tests:
- it: main deployment has new config checksums, not the old connections checksum
template: deployment.yaml
@@ -130,3 +131,24 @@ tests:
- equal:
path: 'spec.template.metadata.annotations.team'
value: data
- it: mcp deployment has config and database checksums
template: deployment-mcp.yaml
set:
supersetMcp:
enabled: true
asserts:
- exists:
path: 'spec.template.metadata.annotations["checksum/database"]'
- exists:
path: 'spec.template.metadata.annotations["checksum/config"]'
- it: mcp deployment applies globalPodAnnotations
template: deployment-mcp.yaml
set:
supersetMcp:
enabled: true
globalPodAnnotations:
team: data
asserts:
- equal:
path: 'spec.template.metadata.annotations.team'
value: data

View File

@@ -20,6 +20,8 @@ templates:
- deployment-worker.yaml
- deployment-beat.yaml
- deployment-flower.yaml
- deployment-ws.yaml
- deployment-mcp.yaml
- init-job.yaml
tests:
- it: renders only the default wait-for container by default
@@ -150,6 +152,42 @@ tests:
path: spec.template.spec.initContainers[1].name
value: prepare-assets
- it: appends websockets extraInitContainers after wait-for-redis
template: deployment-ws.yaml
set:
supersetWebsockets.enabled: true
supersetWebsockets.extraInitContainers:
- name: prepare-assets
image: busybox
asserts:
- lengthEqual:
path: spec.template.spec.initContainers
count: 2
- equal:
path: spec.template.spec.initContainers[0].name
value: wait-for-redis
- equal:
path: spec.template.spec.initContainers[1].name
value: prepare-assets
- it: appends mcp extraInitContainers after wait-for-postgres-redis
template: deployment-mcp.yaml
set:
supersetMcp.enabled: true
supersetMcp.extraInitContainers:
- name: prepare-assets
image: busybox
asserts:
- lengthEqual:
path: spec.template.spec.initContainers
count: 2
- equal:
path: spec.template.spec.initContainers[0].name
value: wait-for-postgres-redis
- equal:
path: spec.template.spec.initContainers[1].name
value: prepare-assets
- it: appends init job extraInitContainers after wait-for-postgres
template: init-job.yaml
set:

View File

@@ -21,9 +21,11 @@ templates:
- deployment-beat.yaml
- deployment-flower.yaml
- deployment-ws.yaml
- deployment-mcp.yaml
- service.yaml
- service-ws.yaml
- service-flower.yaml
- service-mcp.yaml
- init-job.yaml
- ingress.yaml
- configmap-superset.yaml
@@ -34,6 +36,7 @@ templates:
- pdb-beat.yaml
- pdb-flower.yaml
- pdb-ws.yaml
- pdb-mcp.yaml
# These tests validate that Kubernetes recommended labels are consistently applied
# across all chart resources per https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/
@@ -185,6 +188,36 @@ tests:
path: metadata.labels["app.kubernetes.io/component"]
value: websocket
# =============================================================================
# MCP Deployment Labels
# =============================================================================
- it: should have all recommended labels on mcp deployment
template: deployment-mcp.yaml
set:
supersetMcp.enabled: true
asserts:
- isNotNull:
path: metadata.labels["app.kubernetes.io/name"]
- isNotNull:
path: metadata.labels["app.kubernetes.io/instance"]
- isNotNull:
path: metadata.labels["app.kubernetes.io/version"]
- isNotNull:
path: metadata.labels["app.kubernetes.io/managed-by"]
- isNotNull:
path: metadata.labels["app.kubernetes.io/part-of"]
- isNotNull:
path: metadata.labels["app.kubernetes.io/component"]
- it: should have correct component label on mcp deployment
template: deployment-mcp.yaml
set:
supersetMcp.enabled: true
asserts:
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: mcp
# =============================================================================
# Service Labels
# =============================================================================
@@ -249,6 +282,27 @@ tests:
path: metadata.labels["app.kubernetes.io/component"]
value: flower
- it: should have all recommended labels on mcp service
template: service-mcp.yaml
set:
supersetMcp.enabled: true
asserts:
- isNotNull:
path: metadata.labels["app.kubernetes.io/name"]
- isNotNull:
path: metadata.labels["app.kubernetes.io/instance"]
- isNotNull:
path: metadata.labels["app.kubernetes.io/component"]
- it: should have correct component label on mcp service
template: service-mcp.yaml
set:
supersetMcp.enabled: true
asserts:
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: mcp
# =============================================================================
# Init Job Labels
# =============================================================================
@@ -366,6 +420,51 @@ tests:
path: spec.template.metadata.labels["app.kubernetes.io/component"]
value: worker
- it: should set selector matchLabels to concrete values on mcp deployment
template: deployment-mcp.yaml
set:
supersetMcp.enabled: true
asserts:
- equal:
path: spec.selector.matchLabels["app.kubernetes.io/name"]
value: superset
- equal:
path: spec.selector.matchLabels["app.kubernetes.io/instance"]
value: RELEASE-NAME
- equal:
path: spec.selector.matchLabels["app.kubernetes.io/component"]
value: mcp
- it: should match pod template labels to the selector on mcp deployment
template: deployment-mcp.yaml
set:
supersetMcp.enabled: true
asserts:
- equal:
path: spec.template.metadata.labels["app.kubernetes.io/name"]
value: superset
- equal:
path: spec.template.metadata.labels["app.kubernetes.io/instance"]
value: RELEASE-NAME
- equal:
path: spec.template.metadata.labels["app.kubernetes.io/component"]
value: mcp
- it: should set selector to concrete values on mcp service
template: service-mcp.yaml
set:
supersetMcp.enabled: true
asserts:
- equal:
path: spec.selector["app.kubernetes.io/name"]
value: superset
- equal:
path: spec.selector["app.kubernetes.io/instance"]
value: RELEASE-NAME
- equal:
path: spec.selector["app.kubernetes.io/component"]
value: mcp
# =============================================================================
# Extra Labels Support
# =============================================================================
@@ -505,6 +604,26 @@ tests:
path: spec.selector.matchLabels["app.kubernetes.io/component"]
value: websocket
- it: should have recommended labels and matching selector on mcp pdb
template: pdb-mcp.yaml
set:
supersetMcp.enabled: true
supersetMcp.podDisruptionBudget.enabled: true
supersetMcp.podDisruptionBudget.maxUnavailable: null
asserts:
- equal:
path: metadata.labels["app.kubernetes.io/instance"]
value: RELEASE-NAME
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: mcp
- equal:
path: spec.selector.matchLabels["app.kubernetes.io/instance"]
value: RELEASE-NAME
- equal:
path: spec.selector.matchLabels["app.kubernetes.io/component"]
value: mcp
- it: should use recommended labels on init job pod template
template: init-job.yaml
set:

View File

@@ -246,6 +246,40 @@ ingress:
# hosts:
# - chart-example.local
# -- Gateway API HTTPRoute for exposing Superset via a Gateway.
# Requires the Gateway API CRDs (gateway.networking.k8s.io/v1) installed in the cluster.
# @default -- see `values.yaml`
httproute:
enabled: false
# -- HTTPRoute apiVersion. Override to gateway.networking.k8s.io/v1beta1 for
# older Gateway API installations that have not promoted HTTPRoute to v1.
apiVersion: gateway.networking.k8s.io/v1
# -- Additional labels to add to the HTTPRoute
labels: {}
# -- Annotations to add to the HTTPRoute
annotations: {}
# -- Gateways this HTTPRoute attaches to
parentRefs: []
# - name: my-gateway
# namespace: gateway-system
# sectionName: https
# -- Hostnames that match against the HTTP Host header (templated)
hostnames: []
# - chart-example.local
# -- Routing rules. Each rule is backed by the Superset service. Set `weight`
# per rule to leave room for traffic splitting (defaults to 1). When
# `supersetWebsockets.enabled` is true, an extra rule routing
# `supersetWebsockets.ingress.path` to the `-ws` service is appended
# automatically, mirroring the ingress behavior.
rules:
- matches:
- path:
type: PathPrefix
value: /
# weight: 1
# filters: []
# timeouts: {}
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
@@ -867,6 +901,38 @@ supersetWebsockets:
# -- (int)
http: nil
command: []
# -- List of initContainers
# @default -- a container waiting for redis
initContainers:
- name: wait-for-redis
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
envFrom:
- secretRef:
name: "{{ tpl .Values.envFromSecret . }}"
command:
- /bin/bash
- -c
- |
# See supersetNode.initContainers for the rationale.
SECONDS=0
until (exec 3<>/dev/tcp/"$REDIS_HOST"/"$REDIS_PORT") 2>/dev/null; do
if [ "$SECONDS" -ge 120 ]; then
echo "timeout waiting for redis at $REDIS_HOST:$REDIS_PORT after 120s" >&2
exit 1
fi
echo "waiting for redis at $REDIS_HOST:$REDIS_PORT (elapsed ${SECONDS}s)"
sleep 2
done
echo "redis at $REDIS_HOST:$REDIS_PORT is up"
resources:
limits:
memory: "256Mi"
requests:
cpu: "250m"
memory: "128Mi"
# -- Extra init containers appended after supersetWebsockets initContainers
extraInitContainers: []
resources: {}
# -- Launch additional containers into supersetWebsockets pods
extraContainers: []
@@ -912,6 +978,144 @@ supersetWebsockets:
# -- Set priorityClassName for supersetWebsockets pods
priorityClassName: ~
supersetMcp:
# -- Enables the Superset MCP Server. To expose it via the shared ingress at /mcp,
# also set supersetMcp.ingress.enabled=true.
# WARNING: this requires fastMCP to be installed, which can be done by installing `apache-superset[fastmcp]`
enabled: false
replicaCount: 1
# -- Sets the [pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for supersetMcp pods
podDisruptionBudget:
# -- Whether the pod disruption budget should be created
enabled: false
# -- If set, maxUnavailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/\#specifying-a-poddisruptionbudget
minAvailable: 1
# -- If set, minAvailable must not be set - see https://kubernetes.io/docs/tasks/run-application/configure-pdb/\#specifying-a-poddisruptionbudget
maxUnavailable: 1
# -- Command
# @default -- a `superset mcp run` command
command:
- "/bin/sh"
- "-c"
- ". {{ .Values.configMountPath }}/superset_bootstrap.sh; superset mcp run --host 0.0.0.0 --port {{ .Values.supersetMcp.service.port }}"
# -- If true, forces deployment to reload on each upgrade
forceReload: false
ingress:
# -- If true, the MCP server will be exposed via the ingress /mcp subpath
enabled: false
path: /mcp
pathType: Prefix
service:
type: ClusterIP
annotations: {}
loadBalancerIP: ~
port: 5008
nodePort:
# -- (int)
http: nil
startupProbe:
httpGet:
path: /health
port: mcp
initialDelaySeconds: 15
timeoutSeconds: 3
failureThreshold: 60
periodSeconds: 5
successThreshold: 1
livenessProbe:
httpGet:
path: /health
port: mcp
initialDelaySeconds: 15
timeoutSeconds: 3
failureThreshold: 3
periodSeconds: 15
successThreshold: 1
readinessProbe:
httpGet:
path: /health
port: mcp
initialDelaySeconds: 15
timeoutSeconds: 3
failureThreshold: 3
periodSeconds: 15
successThreshold: 1
# -- List of init containers
# @default -- a container waiting for postgres and redis
initContainers:
- name: wait-for-postgres-redis
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
envFrom:
- secretRef:
name: "{{ tpl .Values.envFromSecret . }}"
command:
- /bin/bash
- -c
- |
# See supersetNode.initContainers for the rationale.
SECONDS=0
wait_for() {
local host=$1 port=$2 name=$3
until (exec 3<>/dev/tcp/"$host"/"$port") 2>/dev/null; do
if [ "$SECONDS" -ge 120 ]; then
echo "timeout waiting for $name at $host:$port after 120s" >&2
exit 1
fi
echo "waiting for $name at $host:$port (elapsed ${SECONDS}s)"
sleep 2
done
echo "$name at $host:$port is up"
}
wait_for "$DB_HOST" "$DB_PORT" postgres
wait_for "$REDIS_HOST" "$REDIS_PORT" redis
resources:
limits:
memory: "256Mi"
requests:
cpu: "250m"
memory: "128Mi"
# -- Extra init containers appended after supersetMcp initContainers
extraInitContainers: []
# -- Launch additional containers into supersetMcp pods
extraContainers: []
# -- Annotations to be added to supersetMcp deployment
deploymentAnnotations: {}
# -- Labels to be added to supersetMcp deployment
deploymentLabels: {}
# -- Custom pod spec to be added to supersetMcp deployment
deploymentAdditionalPodSpec: {}
# -- Affinity to be added to supersetMcp deployment
affinity: {}
# -- TopologySpreadConstrains to be added to supersetMcp deployments
topologySpreadConstraints: []
# -- Annotations to be added to supersetMcp pods
podAnnotations: {}
# -- Labels to be added to supersetMcp pods
podLabels: {}
strategy: {}
# type: RollingUpdate
# rollingUpdate:
# maxSurge: 25%
# maxUnavailable: 25%
# -- Container lifecycle hooks for the worker pod
lifecycle: {}
# -- Pod termination grace period (seconds) for the worker pod so in-flight tasks can drain before SIGKILL
terminationGracePeriodSeconds: ~
# -- Resource settings for the supersetMcp pods - these settings overwrite might existing values from the global resources object defined above.
resources: {}
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
podSecurityContext: {}
containerSecurityContext: {}
# -- Set priorityClassName for supersetMcp pods
priorityClassName: ~
init:
# Configure resources
# Warning: fab command consumes a lot of ram and can

View File

@@ -24,13 +24,12 @@ name = "apache_superset"
description = "A modern, enterprise-ready business intelligence web application"
readme = "README.md"
dynamic = ["version", "scripts", "entry-points"]
requires-python = ">=3.10"
requires-python = ">=3.11"
license = { file="LICENSE.txt" }
authors = [
{ name = "Apache Software Foundation", email = "dev@superset.apache.org" },
]
classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
@@ -63,7 +62,7 @@ dependencies = [
"flask-session>=0.4.0, <1.0",
"flask-wtf>=1.3.0, <2.0",
"geopy",
"greenlet<=3.5.1, >=3.5.1",
"greenlet<=3.5.3, >=3.5.3",
"gunicorn>=26.0.0, <27; sys_platform != 'win32'",
"hashids>=1.3.1, <2",
# holidays>=0.45 required for security fix
@@ -84,7 +83,7 @@ dependencies = [
"packaging",
# --------------------------
# pandas and related (wanting pandas[performance] without numba as it's 100+MB and not needed)
"pandas[excel]>=2.1.4, <2.4",
"pandas[excel]>=2.3.3, <2.4",
"bottleneck", # recommended performance dependency for pandas, see https://pandas.pydata.org/docs/getting_started/install.html#performance-dependencies-recommended
# --------------------------
"parsedatetime",
@@ -113,7 +112,7 @@ dependencies = [
"sqlglot>=30.12.0, <31",
# newer pandas needs 0.9+
"tabulate>=0.10.0, <1.0",
"typing-extensions>=4, <5",
"typing-extensions>=4.16.0, <5",
"waitress; sys_platform == 'win32'",
"watchdog>=6.0.0",
"wtforms>=3.2.2, <4",
@@ -126,7 +125,7 @@ dependencies = [
athena = ["pyathena[pandas]>=2, <4"]
aurora-data-api = ["preset-sqlalchemy-aurora-data-api>=0.2.8,<0.3"]
bigquery = [
"pandas-gbq>=0.19.1",
"pandas-gbq>=0.35.0",
"sqlalchemy-bigquery>=1.17.0",
"google-cloud-bigquery>=3.42.1",
]
@@ -149,11 +148,15 @@ dremio = ["sqlalchemy-dremio>=1.2.1, <4"]
drill = ["sqlalchemy-drill>=1.1.10, <2"]
druid = ["pydruid>=0.6.5,<0.7"]
duckdb = ["duckdb>=1.5.4,<2", "duckdb-engine>=0.17.0"]
dynamodb = ["pydynamodb>=0.4.2"]
dynamodb = ["pydynamodb>=0.8.2"]
solr = ["sqlalchemy-solr >= 0.2.4.3"]
elasticsearch = ["elasticsearch-dbapi>=0.2.13, <0.3.0"]
exasol = ["sqlalchemy-exasol>=2.4.0, <8.0"]
excel = ["xlrd>=2.0.2, <2.1"]
# Async dashboard "Export Data/Images to Excel": uploads the workbook to S3 and
# emails a pre-signed link. boto3 is imported lazily by superset.utils.s3, so
# installing this extra is only required to actually run exports.
excel-export = ["boto3"]
fastmcp = [
"fastmcp>=3.4.3,<4.0",
# tiktoken backs the response-size-guard token estimator. Without
@@ -167,7 +170,6 @@ gevent = ["gevent>=26.4.0"]
gsheets = ["shillelagh[gsheetsapi]>=1.4.4, <2"]
hana = ["hdbcli==2.29.25", "sqlalchemy_hana==3.0.3"]
hive = [
"pyhive[hive]>=0.6.5;python_version<'3.11'",
"pyhive[hive_pure_sasl]>=0.7.0",
"tableschema",
"thrift>=0.23.0, <1.0.0",
@@ -201,7 +203,6 @@ singlestore = ["sqlalchemy-singlestoredb>=1.2.1, <2"]
snowflake = ["snowflake-sqlalchemy>=1.10.2, <2"]
sqlite = ["syntaqlite>=0.7.0,<0.8.0"]
spark = [
"pyhive[hive]>=0.6.5;python_version<'3.11'",
"pyhive[hive_pure_sasl]>=0.7",
"tableschema",
"thrift>=0.23.0, <1",
@@ -213,7 +214,7 @@ tdengine = [
teradata = ["teradatasql>=20.0.0.62"]
thumbnails = [] # deprecated, will be removed in 7.0
vertica = ["sqlalchemy-vertica-python>= 0.6.3, < 0.7"]
netezza = ["nzalchemy>=11.0.2, < 11.2"]
netezza = ["nzalchemy>= 11.1.2, < 11.2"]
starrocks = ["starrocks>=1.3.3, <2"]
doris = ["pydoris>=1.2.0, <2.0.0"]
oceanbase = ["oceanbase_py>=0.0.1.2"]
@@ -344,8 +345,8 @@ exclude = [
line-length = 88
indent-width = 4
# Assume Python 3.10
target-version = "py310"
# Assume Python 3.11
target-version = "py311"
[tool.ruff.lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.

View File

@@ -29,12 +29,12 @@ 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:"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:"TableColumn" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
error:"TaggedObject" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
error:The autoload parameter is deprecated:sqlalchemy.exc.RemovedIn20Warning
error:The current statement is being autocommitted using implicit autocommit:sqlalchemy.exc.RemovedIn20Warning
error:The connection.execute\(\) method:sqlalchemy.exc.RemovedIn20Warning

View File

@@ -164,7 +164,7 @@ google-auth==2.53.0
# via
# -r requirements/base.in
# shillelagh
greenlet==3.5.1
greenlet==3.5.3
# via
# apache-superset (pyproject.toml)
# shillelagh
@@ -275,7 +275,7 @@ packaging==25.0
# kombu
# limits
# shillelagh
pandas==2.1.4
pandas==2.3.3
# via apache-superset (pyproject.toml)
paramiko==3.5.1
# via
@@ -331,6 +331,8 @@ pyparsing==3.2.3
# via apache-superset (pyproject.toml)
pysocks==1.7.1
# via urllib3
python-calamine==0.8.2
# via pandas
python-dateutil==2.9.0.post0
# via
# apache-superset (pyproject.toml)
@@ -427,7 +429,7 @@ trio==0.33.0
# trio-websocket
trio-websocket==0.12.2
# via selenium
typing-extensions==4.15.0
typing-extensions==4.16.0
# via
# apache-superset (pyproject.toml)
# alembic

View File

@@ -340,7 +340,6 @@ gevent==26.4.0
google-api-core==2.23.0
# via
# google-cloud-bigquery
# google-cloud-bigquery-storage
# google-cloud-core
# pandas-gbq
# sqlalchemy-bigquery
@@ -350,7 +349,6 @@ google-auth==2.53.0
# google-api-core
# google-auth-oauthlib
# google-cloud-bigquery
# google-cloud-bigquery-storage
# google-cloud-core
# pandas-gbq
# pydata-google-auth
@@ -365,8 +363,6 @@ google-cloud-bigquery==3.42.1
# apache-superset
# pandas-gbq
# sqlalchemy-bigquery
google-cloud-bigquery-storage==2.26.0
# via pandas-gbq
google-cloud-core==2.4.1
# via google-cloud-bigquery
google-crc32c==1.6.0
@@ -377,7 +373,7 @@ googleapis-common-protos==1.66.0
# via
# google-api-core
# grpcio-status
greenlet==3.5.1
greenlet==3.5.3
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -638,10 +634,11 @@ packaging==25.0
# kombu
# limits
# matplotlib
# pandas-gbq
# pytest
# shillelagh
# sqlalchemy-bigquery
pandas==2.1.4
pandas==2.3.3
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -649,7 +646,7 @@ pandas==2.1.4
# db-dtypes
# pandas-gbq
# prophet
pandas-gbq==0.19.1
pandas-gbq==0.35.0
# via apache-superset
parameterized==0.9.0
# via apache-superset
@@ -705,18 +702,17 @@ prompt-toolkit==3.0.51
prophet==1.2.0
# via apache-superset
proto-plus==1.25.0
# via
# google-api-core
# google-cloud-bigquery-storage
# via google-api-core
protobuf==5.29.6
# via
# google-api-core
# google-cloud-bigquery-storage
# googleapis-common-protos
# grpcio-status
# proto-plus
psutil==6.1.0
# via apache-superset
# via
# apache-superset
# pandas-gbq
psycopg2-binary==2.9.12
# via apache-superset
py-key-value-aio==0.4.4
@@ -823,6 +819,10 @@ pytest-mock==3.10.0
# via
# apache-superset
# apache-superset-extensions-cli
python-calamine==0.8.2
# via
# -c requirements/base-constraint.txt
# pandas
python-dateutil==2.9.0.post0
# via
# -c requirements/base-constraint.txt
@@ -1043,7 +1043,7 @@ trio-websocket==0.12.2
# via
# -c requirements/base-constraint.txt
# selenium
typing-extensions==4.15.0
typing-extensions==4.16.0
# via
# -c requirements/base-constraint.txt
# alembic

View File

@@ -133,8 +133,8 @@ def main(docker: bool, frontend: bool, backend: bool) -> None: # noqa: C901
requirements = [
Requirement(
"python",
(Version("3.10.0"), Version("3.10.999")),
(Version("3.9.0"), Version("3.11.999")),
(Version("3.11.0"), Version("3.11.999")),
(Version("3.11.0"), Version("3.12.999")),
"backend",
"python --version",
),

View File

@@ -21,7 +21,7 @@ under the License.
[![PyPI version](https://badge.fury.io/py/apache-superset-core.svg)](https://badge.fury.io/py/apache-superset-core)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
The official core package for building Apache Superset backend extensions and integrations. This package provides essential building blocks including base classes, API utilities, type definitions, and decorators for both the host application and extensions.

View File

@@ -26,7 +26,7 @@ authors = [
]
license = "Apache-2.0"
license-files = ["LICENSE.txt"]
requires-python = ">=3.10"
requires-python = ">=3.11"
keywords = ["superset", "apache", "analytics", "business-intelligence", "extensions", "visualization"]
classifiers = [
"Development Status :: 3 - Alpha",
@@ -34,7 +34,6 @@ classifiers = [
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Database",

View File

@@ -21,7 +21,7 @@ under the License.
[![PyPI version](https://badge.fury.io/py/apache-superset-extensions-cli.svg)](https://badge.fury.io/py/apache-superset-extensions-cli)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
Official command-line interface for building, bundling, and managing Apache Superset extensions. This CLI tool provides developers with everything needed to create, develop, and package extensions for the Superset ecosystem.

View File

@@ -25,7 +25,7 @@ authors = [
]
license = "Apache-2.0"
license-files = ["LICENSE.txt"]
requires-python = ">=3.10"
requires-python = ">=3.11"
keywords = ["superset", "apache", "cli", "extensions", "analytics", "business-intelligence", "development-tools"]
classifiers = [
"Development Status :: 3 - Alpha",
@@ -33,7 +33,6 @@ classifiers = [
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Database",

View File

@@ -111,16 +111,10 @@
"no-object-constructor": "error",
// === Import plugin rules ===
// TODO: Fix incorrect named imports in Storybook and other files
"import/named": "warn",
// TODO: Fix duplicate exports in shared-controls and other modules
// Temporarily disabled during OXC migration
"import/export": "warn",
// TODO: Re-enable after fixing default export patterns across codebase
// This is temporarily disabled during OXC migration to unblock CI
// Tracking issue: [Create issue after PR merge]
"import/no-named-as-default": "off",
"import/no-named-as-default-member": "off",
"import/named": "error",
"import/export": "error",
"import/no-named-as-default": "error",
"import/no-named-as-default-member": "error",
"import/no-mutable-exports": "error",
"import/no-amd": "error",
"import/first": "error",

View File

@@ -83,13 +83,13 @@
"@visx/xychart": "^4.0.0",
"ag-grid-community": "36.0.0",
"ag-grid-react": "36.0.0",
"antd": "^6.0.0",
"chrono-node": "^2.9.1",
"antd": "^6.5.1",
"chrono-node": "^2.10.0",
"classnames": "^2.2.5",
"content-disposition": "^2.0.1",
"d3-scale": "^4.0.2",
"dayjs": "^1.11.21",
"dom-to-image-more": "^3.10.0",
"dom-to-image-more": "^3.10.2",
"dom-to-pdf": "^0.3.2",
"echarts": "^6.1.0",
"fast-glob": "^3.3.2",
@@ -128,7 +128,7 @@
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^18.3.0",
"react-google-recaptcha": "^3.1.0",
"react-intersection-observer": "^10.0.3",
"react-intersection-observer": "^10.1.0",
"react-json-tree": "^0.20.0",
"react-lines-ellipsis": "^0.16.1",
"react-loadable": "^5.5.0",
@@ -178,13 +178,13 @@
"@babel/types": "^7.29.7",
"@emotion/babel-plugin": "^11.13.5",
"@emotion/jest": "^11.14.2",
"@formatjs/intl-durationformat": "^0.10.16",
"@formatjs/intl-durationformat": "^0.10.17",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@playwright/test": "^1.61.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
"@storybook/addon-docs": "10.4.6",
"@storybook/addon-links": "10.4.6",
"@storybook/react-webpack5": "10.4.6",
"@storybook/addon-docs": "10.5.0",
"@storybook/addon-links": "10.5.0",
"@storybook/react-webpack5": "10.5.0",
"@storybook/test-runner": "0.24.4",
"@svgr/webpack": "^8.1.0",
"@swc/core": "^1.15.43",
@@ -221,7 +221,7 @@
"babel-loader": "^10.1.1",
"babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"baseline-browser-mapping": "^2.10.42",
"baseline-browser-mapping": "^2.10.43",
"cheerio": "1.2.0",
"concurrently": "^10.0.3",
"copy-webpack-plugin": "^14.0.0",
@@ -240,14 +240,13 @@
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react-prefer-function-component": "^5.0.0",
"eslint-plugin-react-you-might-not-need-an-effect": "^1.0.1",
"eslint-plugin-storybook": "10.4.6",
"eslint-plugin-storybook": "10.5.0",
"eslint-plugin-testing-library": "^7.16.2",
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
"fetch-mock": "^12.6.0",
"fork-ts-checker-webpack-plugin": "^9.1.0",
"history": "^5.3.0",
"html-webpack-plugin": "^5.6.7",
"http-server": "^14.1.1",
"imports-loader": "^5.0.0",
"jest": "^30.4.2",
"jest-environment-jsdom": "^30.4.1",
@@ -273,13 +272,13 @@
"source-map": "^0.7.6",
"source-map-support": "^0.5.21",
"speed-measure-webpack-plugin": "^1.6.0",
"storybook": "10.4.6",
"storybook": "10.5.0",
"style-loader": "^4.0.0",
"stylelint": "^17.14.0",
"swc-loader": "^0.2.7",
"ts-jest": "^29.4.11",
"tscw-config": "^1.1.2",
"tsx": "^4.23.0",
"tsx": "^4.23.1",
"typescript": "5.4.5",
"unzipper": "^0.12.5",
"vm-browserify": "^1.1.2",
@@ -4093,38 +4092,38 @@
}
},
"node_modules/@formatjs/bigdecimal": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/@formatjs/bigdecimal/-/bigdecimal-0.2.6.tgz",
"integrity": "sha512-aPzKsGQOkQRHUEbyO/ZtYfr4EqaBQnSs6U4tzTla1xBnIdEHgY2GqEqso28UMwWRkzKqqTj5+/6BmuOsRkfn2A==",
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@formatjs/bigdecimal/-/bigdecimal-0.2.7.tgz",
"integrity": "sha512-B4+XY/e4u5v261r3a37Kq2ax91gPZSKGyVpx8X5dygAggLlUz+6MeBpmAhaBFGB8WQ4JB1qD+QtKHk9aJkEb9Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@formatjs/fast-memoize": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.6.tgz",
"integrity": "sha512-H5aexk1Le7T9TPmscacZ+1pR6CTa2n1wq+HDVGXhH8TzUlQQpeXzZs91dRtmFHrbeNbjPFPfQujUqm7MHgVoXQ==",
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.7.tgz",
"integrity": "sha512-zXfhLpvA6T7+efdt9JLbBwZ00tT7NsBMDVnDu8rpHeNNv8KfRZAMo2gkG0k9lK/Nzc//3kJ9pImsfuJxk3KhUA==",
"dev": true,
"license": "MIT"
},
"node_modules/@formatjs/intl-durationformat": {
"version": "0.10.16",
"resolved": "https://registry.npmjs.org/@formatjs/intl-durationformat/-/intl-durationformat-0.10.16.tgz",
"integrity": "sha512-Ponn8K7eQAA7qeZRRD85lrvsByfeRDxw6aEE9EXapQ47wknptM0aYgumImVsMaBdJikpWYryGAMRZIB7IYYfmQ==",
"version": "0.10.17",
"resolved": "https://registry.npmjs.org/@formatjs/intl-durationformat/-/intl-durationformat-0.10.17.tgz",
"integrity": "sha512-YXm0u5WMaSQ8Gr3th33RFNzoI24nMDScg6iZ9EQZk2CNY3Sg19qklq+FQIuuQEbrDwBlQGrMSDE97m4mETnOxw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@formatjs/bigdecimal": "0.2.6",
"@formatjs/intl-localematcher": "0.8.11"
"@formatjs/bigdecimal": "0.2.7",
"@formatjs/intl-localematcher": "0.8.12"
}
},
"node_modules/@formatjs/intl-localematcher": {
"version": "0.8.11",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.11.tgz",
"integrity": "sha512-MzBbR9ifqgtzHwUl2PGEDtRVZGXF9nXPtrMyN6/iKbQaim1Ob7fnPJQMllVkHm3RK74o5VqnLrZIE9egWJhf5g==",
"version": "0.8.12",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.12.tgz",
"integrity": "sha512-5H3r5ZJ2jZqHEv9K343lvHmeMDKMxssawAVD2H4J9xtu0ZXb6MlNxwLqdwBxJSHFU0C24KSZnffgmAi+59mK4A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@formatjs/fast-memoize": "3.1.6"
"@formatjs/fast-memoize": "3.1.7"
}
},
"node_modules/@gar/promise-retry": {
@@ -9239,13 +9238,13 @@
}
},
"node_modules/@rc-component/dropdown": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.2.tgz",
"integrity": "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.3.tgz",
"integrity": "sha512-YTST/N6kpqpDz3IMuM/PSSZnrDpSOA6dgHv12gPA90ZTSLv2CoqkZ0+9NtwTY6BeO7dstPblSic2QJg7dSFy/g==",
"license": "MIT",
"dependencies": {
"@rc-component/trigger": "^3.0.0",
"@rc-component/util": "^1.2.1",
"@rc-component/util": "^1.11.1",
"clsx": "^2.1.1"
},
"peerDependencies": {
@@ -9641,9 +9640,9 @@
}
},
"node_modules/@rc-component/table": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.10.2.tgz",
"integrity": "sha512-b3PjqB9Gp25p5t/zq+9QrbXbodkptT8/zvLmwgd2FNPUUtaYyDnQqfxeD5a7ao8E8lpinLHsi2u2vdfPhyNvAw==",
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.10.4.tgz",
"integrity": "sha512-HwoTnrwc29zeoXkXGhWqzJh8FIibGUxi1jM4LtoSzmR9d5Vv5osUQpZxnXKBP8iOCvyD6BQzZm1nXJRcnrxpAg==",
"license": "MIT",
"dependencies": {
"@rc-component/context": "^2.0.1",
@@ -9751,15 +9750,15 @@
}
},
"node_modules/@rc-component/trigger": {
"version": "3.9.1",
"resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.9.1.tgz",
"integrity": "sha512-LNsYvz60mrLJ/kRvKcHE7boUvcQfVMCfRqZ71x3Fo9AOiZ1KKIEqkzMA8DNvz2V3Bcvir/vwQNn7JF1NPODQ7Q==",
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.10.1.tgz",
"integrity": "sha512-mXlDN0IXdtV8Yqqm8195ECCyrbmfvvfKvwVvSlH0+qvKD6BUF8gRhEjSy0FOcD1+CcDRHgTiX99LoxfQrmh3Cw==",
"license": "MIT",
"dependencies": {
"@rc-component/motion": "^1.1.4",
"@rc-component/portal": "^2.2.0",
"@rc-component/resize-observer": "^1.1.1",
"@rc-component/util": "^1.2.1",
"@rc-component/motion": "^1.3.3",
"@rc-component/portal": "^2.2.1",
"@rc-component/resize-observer": "^1.1.2",
"@rc-component/util": "^1.11.1",
"clsx": "^2.1.1"
},
"engines": {
@@ -10321,16 +10320,16 @@
"license": "MIT"
},
"node_modules/@storybook/addon-docs": {
"version": "10.4.6",
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.4.6.tgz",
"integrity": "sha512-aWAfP5JMiT5a3zBJizwroCRzOCqZwDTJmvsYvwMD3ilIEa/kT1vhf6Xrbk4XIPhDwbh8Hpb/Gfnka1xBYEISWg==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.5.0.tgz",
"integrity": "sha512-kbZGdX+mysiY4xezhpcFEwzQ1zSXcMhSS3u09Qt8+w5XetCAMMSv/yAxzpi6z+YoH+EdQ53e1q3MFtPUkzkfUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@mdx-js/react": "^3.0.0",
"@storybook/csf-plugin": "10.4.6",
"@storybook/csf-plugin": "10.5.0",
"@storybook/icons": "^2.0.2",
"@storybook/react-dom-shim": "10.4.6",
"@storybook/react-dom-shim": "10.5.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"ts-dedent": "^2.0.0"
@@ -10341,7 +10340,7 @@
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"storybook": "^10.4.6"
"storybook": "^10.5.0"
},
"peerDependenciesMeta": {
"@types/react": {
@@ -10350,9 +10349,9 @@
}
},
"node_modules/@storybook/addon-links": {
"version": "10.4.6",
"resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-10.4.6.tgz",
"integrity": "sha512-VGfERTsGRFmfvNP3SKprFWkC6Od5kXzSutT5PSZjQ/O9NnCdHhd/RILxFDN2TzZn9ywDc7t5b4AldKmSYCv3EQ==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-10.5.0.tgz",
"integrity": "sha512-pZlBqBjJc9Lo3LbcPx2ch0+BXD/efFQ0x9v7w+siCTs4IfqiePBuMK6ZSBQfxai+x/zHm0vKNTmOikEvP8j1wA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10365,7 +10364,7 @@
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"storybook": "^10.4.6"
"storybook": "^10.5.0"
},
"peerDependenciesMeta": {
"@types/react": {
@@ -10377,13 +10376,13 @@
}
},
"node_modules/@storybook/builder-webpack5": {
"version": "10.4.6",
"resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-10.4.6.tgz",
"integrity": "sha512-klJUCBlkr1mFqXxsyYc7akIBhT5VOBTRcHChSFXq6Kbh+qQYOvfmgBpY0Tv+f7ffBmzp6gxpTorg7eGxlklxeQ==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-10.5.0.tgz",
"integrity": "sha512-RFqlqK/wCtI9JzZ45o7aTsh+tDsg1TXn8KcLimBPh/Dfp77wZsZaOD+eS+0Fb8d2EbOgReLS500KjZruX3f3JQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@storybook/core-webpack": "10.4.6",
"@storybook/core-webpack": "10.5.0",
"case-sensitive-paths-webpack-plugin": "^2.4.0",
"cjs-module-lexer": "^1.2.3",
"css-loader": "^7.1.2",
@@ -10391,6 +10390,7 @@
"fork-ts-checker-webpack-plugin": "^9.1.0",
"html-webpack-plugin": "^5.5.0",
"magic-string": "^0.30.5",
"semver": "^7.7.3",
"style-loader": "^4.0.0",
"terser-webpack-plugin": "^5.3.17",
"ts-dedent": "^2.0.0",
@@ -10404,7 +10404,7 @@
"url": "https://opencollective.com/storybook"
},
"peerDependencies": {
"storybook": "^10.4.6"
"storybook": "^10.5.0"
},
"peerDependenciesMeta": {
"typescript": {
@@ -10413,9 +10413,9 @@
}
},
"node_modules/@storybook/core-webpack": {
"version": "10.4.6",
"resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.4.6.tgz",
"integrity": "sha512-DDhpgaFGb1AC0lzfR2LOozCj+uT14tsr2R9551PHgcC3ru1yfhL2DNbIjdiMuQP8nVm+2HKeXWhybJGYtk9z9g==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.5.0.tgz",
"integrity": "sha512-8O/h7nk1h91w4DBtZo6yo89cvPxzryTcYaFxOsSG5jrn/HMWxZIcyXm3nu3metCbTffnVBu2pmLuHaiCkv/9Ng==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10426,13 +10426,13 @@
"url": "https://opencollective.com/storybook"
},
"peerDependencies": {
"storybook": "^10.4.6"
"storybook": "^10.5.0"
}
},
"node_modules/@storybook/csf-plugin": {
"version": "10.4.6",
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.4.6.tgz",
"integrity": "sha512-NILLxDqpA/JR/AazGWpsz+4fadJwRU4uhHephGtYpVOWnQA/DkJfKT6zpcJVq8+QA8A2zKMLX3GVKsXIrxjuDA==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.5.0.tgz",
"integrity": "sha512-R7VFw6FnDZTWct0ekOcFnTfDgdHnnAuQW+AbGG9A9IZPvyH9uLJivK7L86+r9MITRrO2+v81HaFDsT/OFk6ZkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10445,7 +10445,7 @@
"peerDependencies": {
"esbuild": "*",
"rollup": "*",
"storybook": "^10.4.6",
"storybook": "^10.5.0",
"vite": "*",
"webpack": "*"
},
@@ -10483,17 +10483,17 @@
}
},
"node_modules/@storybook/preset-react-webpack": {
"version": "10.4.6",
"resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-10.4.6.tgz",
"integrity": "sha512-D6EjK1sknC/1GmdWQjV38HEP+tBxJ5ObNuCUWQzGsJqsiq45HaOemZbIyJqxp3dYx2/YpFd0bawn7PlOP4yNOQ==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-10.5.0.tgz",
"integrity": "sha512-x26YF06xSR8sYhHn/cOmbJl8VGQhOWGd49teU80cCwdUHTSu98jVAPyGQhuGmHEw3WndnWarIxXIu1iGhZ414w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@storybook/core-webpack": "10.4.6",
"@storybook/core-webpack": "10.5.0",
"@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0",
"@types/semver": "^7.7.1",
"magic-string": "^0.30.5",
"react-docgen": "^7.1.1",
"react-docgen": "^8.0.2",
"resolve": "^1.22.8",
"semver": "^7.7.3",
"tsconfig-paths": "^4.2.0",
@@ -10506,7 +10506,7 @@
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"storybook": "^10.4.6"
"storybook": "^10.5.0"
},
"peerDependenciesMeta": {
"typescript": {
@@ -10515,14 +10515,14 @@
}
},
"node_modules/@storybook/react": {
"version": "10.4.6",
"resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.4.6.tgz",
"integrity": "sha512-9Y7YecrVFe1/01KYjfOLxVqTg2Aq+IO6TEv6sC2U0PfD0AWCSCmQ91QqgBpN/XW4aFFWoiZNinyXMUlU8zxy2w==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.5.0.tgz",
"integrity": "sha512-2IhddiREy7NqAqnFsEOfW6HBG7azIcLxhRQYploSsaemOncR29xhzvAZsG+xlWgrtvxv4h3fYQTTR4TNn8CgPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@storybook/global": "^5.0.0",
"@storybook/react-dom-shim": "10.4.6",
"@storybook/react-dom-shim": "10.5.0",
"react-docgen": "^8.0.2",
"react-docgen-typescript": "^2.2.2"
},
@@ -10535,7 +10535,7 @@
"@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"storybook": "^10.4.6",
"storybook": "^10.5.0",
"typescript": ">= 4.9.x"
},
"peerDependenciesMeta": {
@@ -10615,9 +10615,9 @@
}
},
"node_modules/@storybook/react-dom-shim": {
"version": "10.4.6",
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.4.6.tgz",
"integrity": "sha512-iGNmKzrq9vgl2PDrYAnZKI+yvac3Ym+lJXXuQaqlFRS23zA5MNm4EBX+rAG7WulqchoK6NaZ0KQOs2mAgEpTMg==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.0.tgz",
"integrity": "sha512-GwGA6zDj4Cfw6vGsdfPKfF39L0W6solNbbnjdjGa57m2ooASkidLhrXo2wgC9wh3o/jcfonmYPDRGY6sEhGDtg==",
"dev": true,
"license": "MIT",
"funding": {
@@ -10629,7 +10629,7 @@
"@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"storybook": "^10.4.6"
"storybook": "^10.5.0"
},
"peerDependenciesMeta": {
"@types/react": {
@@ -10641,15 +10641,15 @@
}
},
"node_modules/@storybook/react-webpack5": {
"version": "10.4.6",
"resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-10.4.6.tgz",
"integrity": "sha512-34rOHFRa4NrBnMKsQMq8QTvw65QQJUtasYfEOw1/Pa9+CFWfFQZ3kfDLRxatphUyc0rbKxmVrVE4SFEMY/eMsA==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-10.5.0.tgz",
"integrity": "sha512-Z5Tpg0jNelMOzxfP47oH+AESyol/52WzariOJB6/rUAj5S9vMb/IM7KrF/3QQUa/j3om6dmclxA83CUhGgreig==",
"dev": true,
"license": "MIT",
"dependencies": {
"@storybook/builder-webpack5": "10.4.6",
"@storybook/preset-react-webpack": "10.4.6",
"@storybook/react": "10.4.6"
"@storybook/builder-webpack5": "10.5.0",
"@storybook/preset-react-webpack": "10.5.0",
"@storybook/react": "10.5.0"
},
"funding": {
"type": "opencollective",
@@ -10658,7 +10658,7 @@
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"storybook": "^10.4.6",
"storybook": "^10.5.0",
"typescript": ">= 4.9.x"
},
"peerDependenciesMeta": {
@@ -10667,28 +10667,6 @@
}
}
},
"node_modules/@storybook/react/node_modules/react-docgen": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-8.0.3.tgz",
"integrity": "sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.28.0",
"@babel/traverse": "^7.28.0",
"@babel/types": "^7.28.2",
"@types/babel__core": "^7.20.5",
"@types/babel__traverse": "^7.20.7",
"@types/doctrine": "^0.0.9",
"@types/resolve": "^1.20.2",
"doctrine": "^3.0.0",
"resolve": "^1.22.1",
"strip-indent": "^4.0.0"
},
"engines": {
"node": "^20.9.0 || >=22"
}
},
"node_modules/@storybook/test-runner": {
"version": "0.24.4",
"resolved": "https://registry.npmjs.org/@storybook/test-runner/-/test-runner-0.24.4.tgz",
@@ -14449,16 +14427,16 @@
}
},
"node_modules/antd": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/antd/-/antd-6.5.0.tgz",
"integrity": "sha512-9zbVc9UukfGuqCvIAov01nlpDQWfARNmZQyt21ZhqLX7ilXmi4cdkp12xA48WEmXRXwZvno8A03qQuGE9JG8fg==",
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/antd/-/antd-6.5.1.tgz",
"integrity": "sha512-VZVVF9zYI6S0NHqboVhCoY9Iiqj6dphW1NPB+sEaAf2HuIQ0haXWXj7ZvAXTRDzusktV6+cvvrSZEdRi4twATg==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^8.0.1",
"@ant-design/cssinjs": "^2.1.2",
"@ant-design/cssinjs-utils": "^2.1.2",
"@ant-design/fast-color": "^3.0.1",
"@ant-design/icons": "^6.3.1",
"@ant-design/icons": "^6.3.2",
"@ant-design/react-slick": "~2.0.0",
"@babel/runtime": "^7.29.2",
"@rc-component/cascader": "~1.17.0",
@@ -14467,7 +14445,7 @@
"@rc-component/color-picker": "~3.1.1",
"@rc-component/dialog": "~1.10.0",
"@rc-component/drawer": "~1.4.2",
"@rc-component/dropdown": "~1.0.2",
"@rc-component/dropdown": "~1.0.3",
"@rc-component/form": "~1.8.5",
"@rc-component/image": "~1.9.0",
"@rc-component/input": "~1.3.1",
@@ -14488,13 +14466,13 @@
"@rc-component/slider": "~1.1.1",
"@rc-component/steps": "~1.2.2",
"@rc-component/switch": "~1.0.3",
"@rc-component/table": "~1.10.2",
"@rc-component/table": "~1.10.4",
"@rc-component/tabs": "~1.11.0",
"@rc-component/tooltip": "~1.4.0",
"@rc-component/tour": "~2.4.0",
"@rc-component/tree": "~1.3.2",
"@rc-component/tree-select": "~1.11.0",
"@rc-component/trigger": "^3.9.1",
"@rc-component/trigger": "^3.10.0",
"@rc-component/upload": "~1.1.1",
"@rc-component/util": "^1.11.1",
"clsx": "^2.1.1",
@@ -14907,17 +14885,45 @@
}
},
"node_modules/axios": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz",
"integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==",
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz",
"integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==",
"dev": true,
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.16.0",
"form-data": "^4.0.5",
"https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
}
},
"node_modules/axios/node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/axios/node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/axios/node_modules/proxy-from-env": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
@@ -15235,9 +15241,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.10.42",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz",
"integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==",
"version": "2.10.43",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz",
"integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -15247,26 +15253,6 @@
"node": ">=6.0.0"
}
},
"node_modules/basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "5.1.2"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/basic-auth/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true,
"license": "MIT"
},
"node_modules/batch": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
@@ -16402,9 +16388,9 @@
}
},
"node_modules/chrono-node": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.9.1.tgz",
"integrity": "sha512-nqP8Zp11efCYQIESXPxeDM8ikzN5BDb3Zzou+a66fZq+X2hzKFdsNLQE2/uBAh//BZEMbaMo1eTnagK7hOenAg==",
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.10.0.tgz",
"integrity": "sha512-5fJ4zr5W/5DEf+8FMPMXF2qk9L5dc1rAP9Pw009iMlKAghPgx5o7aUcnjEMe6PTwqiqTa5yzmfSm05EHuwr31Q==",
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
@@ -17248,16 +17234,6 @@
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
"license": "MIT"
},
"node_modules/corser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/cosmiconfig": {
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
@@ -18676,9 +18652,9 @@
"license": "MIT"
},
"node_modules/dom-to-image-more": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-3.10.0.tgz",
"integrity": "sha512-APrFEimSmH4phJKs8DlURuSr3BFwDqNi62l14bx73Wrhx0OhE4dd0qzZR+4E3sH8PnHxvax1LOrosC9oXRFA5A==",
"version": "3.10.2",
"resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-3.10.2.tgz",
"integrity": "sha512-+ZNbiRB7Fpsfzn8WSCmAIKUFSXp8uahlI+LsY8t8oWtAATqokeKS3eD3oQikheouap6aVMht8y0b4oAobWRr9w==",
"license": "MIT"
},
"node_modules/dom-to-pdf": {
@@ -18721,9 +18697,9 @@
}
},
"node_modules/dompurify": {
"version": "3.4.11",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz",
"integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==",
"version": "3.4.12",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz",
"integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -19848,17 +19824,18 @@
}
},
"node_modules/eslint-plugin-storybook": {
"version": "10.4.6",
"resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.4.6.tgz",
"integrity": "sha512-CfGSXn6zFspeYTU8R7v797MOmJFj8xc6MWf/oGuRwbKeMoSwnliR+OlXSjMZYM1D6gfmwiuH1VX58LSHdn+ZPg==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.5.0.tgz",
"integrity": "sha512-UyhbK11baKAYqzyDUHlwDlm1aP/wxPMR0vFmsxA5984BGaPwarhoXUWng1Xa0cd9BdPbQ+zr/y8ADk6XxtwkYg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "^8.48.0",
"@typescript-eslint/utils": "^8.48.0"
},
"peerDependencies": {
"eslint": ">=8",
"storybook": "^10.4.6"
"storybook": "^10.5.0"
}
},
"node_modules/eslint-plugin-testing-library": {
@@ -23617,61 +23594,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/http-server": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz",
"integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==",
"dev": true,
"license": "MIT",
"dependencies": {
"basic-auth": "^2.0.1",
"chalk": "^4.1.2",
"corser": "^2.0.1",
"he": "^1.2.0",
"html-encoding-sniffer": "^3.0.0",
"http-proxy": "^1.18.1",
"mime": "^1.6.0",
"minimist": "^1.2.6",
"opener": "^1.5.1",
"portfinder": "^1.0.28",
"secure-compare": "3.0.1",
"union": "~0.5.0",
"url-join": "^4.0.1"
},
"bin": {
"http-server": "bin/http-server"
},
"engines": {
"node": ">=12"
}
},
"node_modules/http-server/node_modules/html-encoding-sniffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
"dev": true,
"license": "MIT",
"dependencies": {
"whatwg-encoding": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/http-server/node_modules/whatwg-encoding": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
"dev": true,
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
@@ -33085,20 +33007,6 @@
"node": ">=10"
}
},
"node_modules/portfinder": {
"version": "1.0.38",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz",
"integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==",
"dev": true,
"license": "MIT",
"dependencies": {
"async": "^3.2.6",
"debug": "^4.3.6"
},
"engines": {
"node": ">= 10.12"
}
},
"node_modules/possible-typed-array-names": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
@@ -35136,17 +35044,17 @@
}
},
"node_modules/react-docgen": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-7.1.1.tgz",
"integrity": "sha512-hlSJDQ2synMPKFZOsKo9Hi8WWZTC7POR8EmWvTSjow+VDgKzkmjQvFm2fk0tmRw+f0vTOIYKlarR0iL4996pdg==",
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-8.0.3.tgz",
"integrity": "sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.18.9",
"@babel/traverse": "^7.18.9",
"@babel/types": "^7.18.9",
"@types/babel__core": "^7.18.0",
"@types/babel__traverse": "^7.18.0",
"@babel/core": "^7.28.0",
"@babel/traverse": "^7.28.0",
"@babel/types": "^7.28.2",
"@types/babel__core": "^7.20.5",
"@types/babel__traverse": "^7.20.7",
"@types/doctrine": "^0.0.9",
"@types/resolve": "^1.20.2",
"doctrine": "^3.0.0",
@@ -35154,7 +35062,7 @@
"strip-indent": "^4.0.0"
},
"engines": {
"node": ">=16.14.0"
"node": "^20.9.0 || >=22"
}
},
"node_modules/react-docgen-typescript": {
@@ -35218,9 +35126,9 @@
}
},
"node_modules/react-intersection-observer": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-10.0.3.tgz",
"integrity": "sha512-luICLMbs0zxTO/70Zy7K5jOXkABPEVSAF8T3FdZUlctsrIaPLmx8TZe2SSA+CY2HGWfz2INyNTnp82pxNNsShA==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-10.1.0.tgz",
"integrity": "sha512-V8HDu3+Llg6OEhOxx8LnUSS0t4VS+1Xk9ZatkI8Jct/H0CwKnqFTCu8NT3q7ghJTghTdIrEMPSWr2dkKPG+gdQ==",
"license": "MIT",
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
@@ -37024,13 +36932,6 @@
"compute-scroll-into-view": "^3.0.2"
}
},
"node_modules/secure-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
"integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
"dev": true,
"license": "MIT"
},
"node_modules/seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
@@ -38380,20 +38281,22 @@
}
},
"node_modules/storybook": {
"version": "10.4.6",
"resolved": "https://registry.npmjs.org/storybook/-/storybook-10.4.6.tgz",
"integrity": "sha512-6wkA6LxfDSSilloITsrFOJfsnw0mDUP2h8Ls+lRt8oRsudtz2RWFhLv+Toiwg6NW7hUpdTDc2hzR7DztJid6+A==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/storybook/-/storybook-10.5.0.tgz",
"integrity": "sha512-dRhM/kSSvHQR8DmZO41v5sJuz9U6zDjjR2gRBTgZN2RBSXbmF0Brvgszrvvxyx2VfxuYKzhB+xumKwWkwlBtig==",
"dev": true,
"license": "MIT",
"dependencies": {
"@storybook/global": "^5.0.0",
"@storybook/icons": "^2.0.2",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/user-event": "^14.6.1",
"@vitest/expect": "3.2.4",
"@vitest/spy": "3.2.4",
"@webcontainer/env": "^1.1.1",
"esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0 || ^0.28.0",
"jsonc-parser": "^3.3.1",
"open": "^10.2.0",
"oxc-parser": "^0.127.0",
"oxc-resolver": "^11.19.1",
@@ -38412,7 +38315,7 @@
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"prettier": "^2 || ^3",
"vite-plus": "^0.1.15"
"vite-plus": "^0.1.15 || ^0.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
@@ -38426,6 +38329,26 @@
}
}
},
"node_modules/storybook/node_modules/@testing-library/dom": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
"@types/aria-query": "^5.0.1",
"aria-query": "5.3.0",
"dom-accessibility-api": "^0.5.9",
"lz-string": "^1.5.0",
"picocolors": "1.1.1",
"pretty-format": "^27.0.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/storybook/node_modules/@testing-library/user-event": {
"version": "14.6.1",
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
@@ -38440,6 +38363,16 @@
"@testing-library/dom": ">=7.21.4"
}
},
"node_modules/storybook/node_modules/aria-query": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"dequal": "^2.0.3"
}
},
"node_modules/storybook/node_modules/define-lazy-prop": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
@@ -38469,6 +38402,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/storybook/node_modules/jsonc-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
"integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
"dev": true,
"license": "MIT"
},
"node_modules/storybook/node_modules/open": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz",
@@ -40354,9 +40294,9 @@
"license": "0BSD"
},
"node_modules/tsx": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.0.tgz",
"integrity": "sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==",
"version": "4.23.1",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz",
"integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -40835,18 +40775,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/union": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
"dev": true,
"dependencies": {
"qs": "^6.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/union-value": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
@@ -41025,9 +40953,9 @@
}
},
"node_modules/unplugin/node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -41168,13 +41096,6 @@
"integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==",
"license": "MIT"
},
"node_modules/url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"dev": true,
"license": "MIT"
},
"node_modules/url-template": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
@@ -43656,7 +43577,7 @@
"d3-time": "^3.1.0",
"d3-time-format": "^4.1.0",
"dayjs": "^1.11.21",
"dompurify": "^3.4.11",
"dompurify": "^3.4.12",
"fetch-retry": "^6.0.0",
"handlebars": "^4.7.9",
"jed": "^1.1.1",
@@ -44039,7 +43960,7 @@
"dependencies": {
"d3": "^3.5.17",
"d3-tip": "^0.9.1",
"dompurify": "^3.4.11",
"dompurify": "^3.4.12",
"fast-safe-stringify": "^2.1.1",
"lodash": "^4.18.1",
"lodash-es": "^4.18.1",

View File

@@ -84,7 +84,7 @@
"prune": "rm -rf ./{packages,plugins}/*/{node_modules,lib,esm,tsconfig.tsbuildinfo,package-lock.json} ./.temp_cache",
"storybook": "cross-env NODE_ENV=development BABEL_ENV=development storybook dev -p 6006",
"test-storybook": "test-storybook",
"test-storybook:ci": "concurrently --kill-others --success first --names \"SB,TEST\" --prefix-colors \"magenta,blue\" \"npx http-server storybook-static --port 6006 --silent\" \"npx wait-on tcp:127.0.0.1:6006 && npm run test-storybook -- --maxWorkers=2\"",
"test-storybook:ci": "concurrently --kill-others --success first --names \"SB,TEST\" --prefix-colors \"magenta,blue\" \"python3 -m http.server 6006 --directory storybook-static\" \"npx wait-on tcp:127.0.0.1:6006 && npm run test-storybook -- --maxWorkers=2\"",
"tdd": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --watch",
"test": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80% --silent",
"test-loud": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80%",
@@ -168,13 +168,13 @@
"@visx/xychart": "^4.0.0",
"ag-grid-community": "36.0.0",
"ag-grid-react": "36.0.0",
"antd": "^6.0.0",
"chrono-node": "^2.9.1",
"antd": "^6.5.1",
"chrono-node": "^2.10.0",
"classnames": "^2.2.5",
"content-disposition": "^2.0.1",
"d3-scale": "^4.0.2",
"dayjs": "^1.11.21",
"dom-to-image-more": "^3.10.0",
"dom-to-image-more": "^3.10.2",
"dom-to-pdf": "^0.3.2",
"echarts": "^6.1.0",
"fast-glob": "^3.3.2",
@@ -213,7 +213,7 @@
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^18.3.0",
"react-google-recaptcha": "^3.1.0",
"react-intersection-observer": "^10.0.3",
"react-intersection-observer": "^10.1.0",
"react-json-tree": "^0.20.0",
"react-lines-ellipsis": "^0.16.1",
"react-loadable": "^5.5.0",
@@ -263,13 +263,13 @@
"@babel/types": "^7.29.7",
"@emotion/babel-plugin": "^11.13.5",
"@emotion/jest": "^11.14.2",
"@formatjs/intl-durationformat": "^0.10.16",
"@formatjs/intl-durationformat": "^0.10.17",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@playwright/test": "^1.61.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
"@storybook/addon-docs": "10.4.6",
"@storybook/addon-links": "10.4.6",
"@storybook/react-webpack5": "10.4.6",
"@storybook/addon-docs": "10.5.0",
"@storybook/addon-links": "10.5.0",
"@storybook/react-webpack5": "10.5.0",
"@storybook/test-runner": "0.24.4",
"@svgr/webpack": "^8.1.0",
"@swc/core": "^1.15.43",
@@ -306,7 +306,7 @@
"babel-loader": "^10.1.1",
"babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"baseline-browser-mapping": "^2.10.42",
"baseline-browser-mapping": "^2.10.43",
"cheerio": "1.2.0",
"concurrently": "^10.0.3",
"copy-webpack-plugin": "^14.0.0",
@@ -325,14 +325,13 @@
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react-prefer-function-component": "^5.0.0",
"eslint-plugin-react-you-might-not-need-an-effect": "^1.0.1",
"eslint-plugin-storybook": "10.4.6",
"eslint-plugin-storybook": "10.5.0",
"eslint-plugin-testing-library": "^7.16.2",
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
"fetch-mock": "^12.6.0",
"fork-ts-checker-webpack-plugin": "^9.1.0",
"history": "^5.3.0",
"html-webpack-plugin": "^5.6.7",
"http-server": "^14.1.1",
"imports-loader": "^5.0.0",
"jest": "^30.4.2",
"jest-environment-jsdom": "^30.4.1",
@@ -358,13 +357,13 @@
"source-map": "^0.7.6",
"source-map-support": "^0.5.21",
"speed-measure-webpack-plugin": "^1.6.0",
"storybook": "10.4.6",
"storybook": "10.5.0",
"style-loader": "^4.0.0",
"stylelint": "^17.14.0",
"swc-loader": "^0.2.7",
"ts-jest": "^29.4.11",
"tscw-config": "^1.1.2",
"tsx": "^4.23.0",
"tsx": "^4.23.1",
"typescript": "5.4.5",
"unzipper": "^0.12.5",
"vm-browserify": "^1.1.2",

View File

@@ -18,8 +18,7 @@
*/
import { Jed as BaseJed, JedOptions, DomainData, Translations } from './jed';
export * from './jed';
export { default as __hack_reexport_jed } from './jed';
export { Translations, DomainConfig, DomainData, JedOptions } from './jed';
/**
* Superset supported languages.

View File

@@ -152,7 +152,9 @@ export const dndAdhocFilterControl: SharedControlConfig<
type: 'DndFilterSelect',
label: t('Filters'),
default: [],
description: '',
description: t(
'Add columns to filter by. When typing or pasting filter values, commas will separate values into multiple entries. To include a comma within a value, wrap it in double quotes: "San Francisco, CA"',
),
mapStateToProps: ({ datasource, form_data }) => ({
columns: isDataset(datasource)
? datasource.columns.filter(c => c.filterable)

View File

@@ -19,8 +19,7 @@
export { default as sharedControls } from './sharedControls';
// React control components
export { default as sharedControlComponents } from './components';
export { aggregationControl } from './customControls';
export * from './components';
export * from './customControls';
export * from './components';
export * from './mixins';
export * from './dndControls';

View File

@@ -68,6 +68,9 @@ export const D3_FORMAT_OPTIONS: [string, string][] = [
'MEMORY_TRANSFER_RATE_BINARY',
t('Memory transfer rate in bytes - binary (1024B => 1KiB/s)'),
],
['LENGTH', t('Length in m (12345m => 12.35km)')],
['LENGTH_CM_KM', t('Length in cm (12345678cm => 123.46km)')],
['LENGTH_CM_M', t('Length in cm (12345cm => 123.45m)')],
];
export const D3_TIME_FORMAT_DOCS = t(

View File

@@ -43,7 +43,7 @@
"d3-time": "^3.1.0",
"d3-time-format": "^4.1.0",
"dayjs": "^1.11.21",
"dompurify": "^3.4.11",
"dompurify": "^3.4.12",
"fetch-retry": "^6.0.0",
"handlebars": "^4.7.9",
"jed": "^1.1.1",

View File

@@ -22,7 +22,6 @@ import * as CategoricalColorNamespace from './CategoricalColorNamespace';
export { CategoricalColorNamespace };
export { default as CategoricalColorScale } from './CategoricalColorScale';
export { default as CategoricalScheme } from './CategoricalScheme';
export * from './CategoricalScheme';
export { default as getCategoricalSchemeRegistry } from './CategoricalSchemeRegistrySingleton';
export { default as ColorScheme } from './ColorScheme';
export * from './ColorScheme';

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import Markdown from 'markdown-to-jsx';
import MarkdownToJsx from 'markdown-to-jsx';
import AtomicDesign from './atomic-design.png';
export default {
@@ -25,7 +25,7 @@ export default {
export const DesignSystem = () => (
<>
<Markdown>
<MarkdownToJsx>
{`
# Superset Design System
@@ -39,7 +39,7 @@ export const DesignSystem = () => (
While the Superset Design System will use Atomic Design principles, we choose a different language to describe the elements.
`}
</Markdown>
</MarkdownToJsx>
<table style={{ borderCollapse: 'collapse', margin: '16px 0' }}>
<thead>
<tr>

View File

@@ -17,14 +17,14 @@
* under the License.
*/
import Markdown from 'markdown-to-jsx';
import MarkdownToJsx from 'markdown-to-jsx';
export default {
title: 'Design System/Components/DropdownContainer/Overview',
};
export const Overview = () => (
<Markdown>
<MarkdownToJsx>
{`
# Usage
@@ -41,5 +41,5 @@ The component accepts any React element which ensures a high level of variabilit
To check the component in detail and its interactions, check the [DropdownContainer](/story/design-system-components-dropdowncontainer--component) page.
`}
</Markdown>
</MarkdownToJsx>
);

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import Markdown from 'markdown-to-jsx';
import MarkdownToJsx from 'markdown-to-jsx';
export default {
title: 'Design System/Components/MetadataBar/Overview',
@@ -25,7 +25,7 @@ export default {
export const MetadataBarOverview = () => (
<>
<Markdown>
<MarkdownToJsx>
{`
# Metadata bar
@@ -42,8 +42,8 @@ Some of the common applications in Superset are:
## [Basic example]((./?path=/docs/design-system-components-metadatabar-examples--basic)
`}
</Markdown>
<Markdown>
</MarkdownToJsx>
<MarkdownToJsx>
{`
## Variations
@@ -62,9 +62,9 @@ This process is important to make sure the new type is reviewed by the design te
To check each content type in detail and its interactions, check the [MetadataBar](/story/design-system-components-metadatabar-examples--basic) page.
Below you can find the configurations for each content type:
`}
</Markdown>
</MarkdownToJsx>
<Markdown>
<MarkdownToJsx>
{`
\`\`\`
export enum MetadataType {
@@ -79,9 +79,9 @@ export enum MetadataType {
};
\`\`\`
`}
</Markdown>
</MarkdownToJsx>
<Markdown>
<MarkdownToJsx>
{`
\`\`\`
export type Dashboards = {
@@ -92,9 +92,9 @@ export type Dashboards = {
};
\`\`\`
`}
</Markdown>
</MarkdownToJsx>
<Markdown>
<MarkdownToJsx>
{`
\`\`\`
export type Description = {
@@ -104,9 +104,9 @@ export type Description = {
};
\`\`\`
`}
</Markdown>
</MarkdownToJsx>
<Markdown>
<MarkdownToJsx>
{`
\`\`\`
export type LastModified = {
@@ -117,9 +117,9 @@ export type LastModified = {
};
\`\`\`
`}
</Markdown>
</MarkdownToJsx>
<Markdown>
<MarkdownToJsx>
{`
\`\`\`
export type Owner = {
@@ -131,9 +131,9 @@ export type Owner = {
};
\`\`\`
`}
</Markdown>
</MarkdownToJsx>
<Markdown>
<MarkdownToJsx>
{`
\`\`\`
export type Rows = {
@@ -143,9 +143,9 @@ export type Rows = {
};
\`\`\`
`}
</Markdown>
</MarkdownToJsx>
<Markdown>
<MarkdownToJsx>
{`
\`\`\`
export type Sql = {
@@ -155,9 +155,9 @@ export type Sql = {
};
\`\`\`
`}
</Markdown>
</MarkdownToJsx>
<Markdown>
<MarkdownToJsx>
{`
\`\`\`
export type Table = {
@@ -167,9 +167,9 @@ export type Table = {
};
\`\`\`
`}
</Markdown>
</MarkdownToJsx>
<Markdown>
<MarkdownToJsx>
{`
\`\`\`
export type Tags = {
@@ -179,6 +179,6 @@ export type Tags = {
};
\`\`\`
`}
</Markdown>
</MarkdownToJsx>
</>
);

View File

@@ -57,9 +57,12 @@ import {
getSuffixIcon,
dropDownRenderHelper,
handleFilterOptionHelper,
makeQuoteAwareTokenizer,
mapOptions,
getOption,
isObject,
splitWithQuoteEscaping,
stripSurroundingQuotes,
isEqual as utilsIsEqual,
} from './utils';
import {
@@ -173,6 +176,21 @@ const AsyncSelect = forwardRef(
// request is still pending.
const inFlightFetchesRef = useRef(0);
const mappedMode = isSingleMode ? undefined : 'multiple';
const reconcileTokensRef = useRef<(tokens: string[]) => void>(() => {});
const fullSelectOptionsRef = useRef<SelectOptionsType>(EMPTY_OPTIONS);
const quoteAwareTokenSeparators = useMemo(() => {
const tokenize = makeQuoteAwareTokenizer(tokenSeparators);
return (input: string) => {
const tokens = tokenize(input);
if (tokens.length !== 1 || tokens[0] !== input) {
reconcileTokensRef.current(tokens);
}
return tokens;
};
}, [tokenSeparators]);
const allowFetch = !fetchOnlyOnSearch || inputValue;
const [maxTagCount, setMaxTagCount] = useState(
propsMaxTagCount ?? MAX_TAG_COUNT,
@@ -281,6 +299,39 @@ const AsyncSelect = forwardRef(
onSelect?.(selectedItem, option);
};
// The underlying Select silently drops tokens it cannot match against the
// rendered options. That happens whenever tokenization outpaces the
// debounced option registration, e.g. dead-key keyboard layouts deliver a
// closing quote and a separator in a single input event.
reconcileTokensRef.current = (tokens: string[]) => {
if (isSingleMode || !allowNewOptions) {
return;
}
setTimeout(() => {
tokens.forEach(token => {
const matched = getOption(token, fullSelectOptionsRef.current, true);
const matchedValue = isObject(matched) ? matched.value : matched;
if (hasOption(matchedValue ?? token, selectValueRef.current)) {
return;
}
const option = isObject(matched)
? (matched as AntdLabeledValue)
: { label: token, value: token, isNewOption: true };
if (!matched) {
setSelectOptions(previous =>
hasOption(token, previous, true)
? previous
: [option, ...previous],
);
}
handleOnSelect(
{ label: option.label, value: option.value } as AntdLabeledValue,
option as AntdLabeledValue,
);
});
});
};
const handleOnDeselect: SelectProps['onDeselect'] = (value, option) => {
if (Array.isArray(selectValue)) {
if (isLabeledValue(value)) {
@@ -318,6 +369,8 @@ const AsyncSelect = forwardRef(
[onError],
);
fullSelectOptionsRef.current = fullSelectOptions;
const mergeData = useCallback(
(data: SelectOptionsType) => {
let mergedData: SelectOptionsType = [];
@@ -394,6 +447,14 @@ const AsyncSelect = forwardRef(
initialOptionsRef.current = accumulated;
if (!fetchOnlyOnSearch && accumulated.length >= totalCount) {
setAllValuesLoaded(true);
// Once every base value is loaded, searches are served by
// client-side filtering (fetchPage short-circuits), so the
// full set must reach the live options even when this
// response lands mid-search — otherwise the dropdown stays
// empty for the active search.
if (!matchesCurrentSearch) {
mergeData(accumulated);
}
}
fetchedQueries.current.set(key, totalCount);
if (matchesCurrentSearch) {
@@ -455,10 +516,11 @@ const AsyncSelect = forwardRef(
const handleOnSearch = debounce((search: string) => {
const searchValue = search.trim();
if (allowNewOptions) {
const newOption = searchValue &&
!hasOption(searchValue, fullSelectOptions, true) && {
label: searchValue,
value: searchValue,
const unquotedSearch = stripSurroundingQuotes(searchValue);
const newOption = unquotedSearch &&
!hasOption(unquotedSearch, fullSelectOptions, true) && {
label: unquotedSearch,
value: unquotedSearch,
isNewOption: true,
};
const cleanSelectOptions = fullSelectOptions.filter(
@@ -695,22 +757,11 @@ const AsyncSelect = forwardRef(
setSelectValue(value);
}
} else {
// antd v6 widened `tokenSeparators` to `string[] | (input => string[])`;
// Superset always uses the array form.
// Superset's prop is the array form; antd receives the function form
const separators = Array.isArray(tokenSeparators)
? tokenSeparators
: [];
const token = separators.find((token: string) =>
pastedText.includes(token),
);
const array = token
? uniq(
pastedText
.split(token)
.map(s => s.trim())
.filter(Boolean),
)
: [pastedText.trim()].filter(Boolean);
const array = uniq(splitWithQuoteEscaping(pastedText, separators));
const values = (
await Promise.all(array.map(item => getPastedTextValue(item)))
).filter(item => item !== undefined) as AntdLabeledValue[];
@@ -791,7 +842,7 @@ const AsyncSelect = forwardRef(
optionRender={option => <Space>{option.label || option.value}</Space>}
placeholder={placeholder}
showSearch={shouldShowSearch}
tokenSeparators={tokenSeparators}
tokenSeparators={quoteAwareTokenSeparators}
builtinPlacements={DROPDOWN_BUILTIN_PLACEMENTS}
value={selectValue}
suffixIcon={getSuffixIcon(

View File

@@ -390,6 +390,93 @@ test('removes duplicated values', async () => {
expect(values[3]).toHaveTextContent('d');
});
test('typing an unquoted comma splits the value into a chip', async () => {
render(<Select {...defaultProps} mode="multiple" allowNewOptions />);
await type('Australia');
expect(await findSelectOption('Australia')).toBeInTheDocument();
await type(',', undefined, false);
const values = await findAllSelectValues();
expect(values.length).toBe(1);
expect(values[0]).toHaveTextContent('Australia');
});
test('closing quote and separator arriving in a single input event still create the chip', async () => {
render(<Select {...defaultProps} mode="multiple" allowNewOptions />);
await open();
await type('"Australia, U');
// dead-key keyboard layouts deliver the closing quote together with the
// next character in one input event
fireEvent.change(getSelect(), { target: { value: '"Australia, US",' } });
await waitFor(() =>
expect(getElementsByClassName('.ant-select-selection-item')).toHaveLength(
1,
),
);
expect(getElementByClassName('.ant-select-selection-item')).toHaveTextContent(
'Australia, US',
);
});
test('an options prop update mid-typing keeps the created option tokenizable', async () => {
const { rerender } = render(
<Select {...defaultProps} mode="multiple" allowNewOptions />,
);
await type('"Australia, US"');
expect(await findSelectOption('Australia, US')).toBeInTheDocument();
rerender(
<Select
{...defaultProps}
options={[...OPTIONS]}
mode="multiple"
allowNewOptions
/>,
);
await type(',', undefined, false);
await waitFor(() =>
expect(getElementsByClassName('.ant-select-selection-item')).toHaveLength(
1,
),
);
expect(getElementByClassName('.ant-select-selection-item')).toHaveTextContent(
'Australia, US',
);
});
test('typing a comma inside double quotes keeps the value as a single chip', async () => {
render(<Select {...defaultProps} mode="multiple" allowNewOptions />);
await type('"Australia, US"');
expect(await findSelectOption('Australia, US')).toBeInTheDocument();
await type(',', undefined, false);
const values = await findAllSelectValues();
expect(values.length).toBe(1);
expect(values[0]).toHaveTextContent('Australia, US');
});
test('prevents the default paste action when pasted values are consumed', async () => {
render(<Select {...defaultProps} mode="multiple" allowNewOptions />);
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => `${OPTIONS[0].label},${OPTIONS[1].label}`,
},
});
fireEvent(input, paste);
await findAllSelectValues();
expect(paste.defaultPrevented).toBe(true);
});
test('allows the default paste action when nothing is consumable', async () => {
render(<Select {...defaultProps} mode="multiple" />);
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => 'zzz,yyy',
},
});
fireEvent(input, paste);
expect(paste.defaultPrevented).toBe(false);
});
test('trims whitespace from pasted comma-separated values', async () => {
render(<Select {...defaultProps} mode="multiple" allowNewOptions />);
const input = getElementByClassName('.ant-select-input');

View File

@@ -49,10 +49,13 @@ import {
hasOption,
isLabeledValue,
isObject,
makeQuoteAwareTokenizer,
mapOptions,
mapValues,
sortComparatorWithSearchHelper,
sortSelectedFirstHelper,
splitWithQuoteEscaping,
stripSurroundingQuotes,
isEqual as utilsIsEqual,
} from './utils';
import { RawValue, SelectOptionsType, SelectProps } from './types';
@@ -192,6 +195,19 @@ const Select = forwardRef(
const mappedMode = isSingleMode ? undefined : 'multiple';
const reconcileTokensRef = useRef<(tokens: string[]) => void>(() => {});
const quoteAwareTokenSeparators = useMemo(() => {
const tokenize = makeQuoteAwareTokenizer(tokenSeparators);
return (input: string) => {
const tokens = tokenize(input);
if (tokens.length !== 1 || tokens[0] !== input) {
reconcileTokensRef.current(tokens);
}
return tokens;
};
}, [tokenSeparators]);
const sortSelectedFirst = useCallback(
(a: AntdLabeledValue, b: AntdLabeledValue) =>
sortSelectedFirstHelper(a, b, selectValue),
@@ -267,6 +283,11 @@ const Select = forwardRef(
);
}, [selectOptions, selectValue, sortSelectedFirst]);
const fullSelectOptionsRef = useRef(fullSelectOptions);
fullSelectOptionsRef.current = fullSelectOptions;
const selectValueRef = useRef(selectValue);
selectValueRef.current = selectValue;
const enabledOptions = useMemo(
() => visibleOptions.filter(option => !option.disabled),
[visibleOptions],
@@ -366,6 +387,42 @@ const Select = forwardRef(
onSelect?.(selectedItem, option);
};
// The underlying Select silently drops tokens it cannot match against the
// rendered options. That happens whenever tokenization outpaces the
// debounced option registration, e.g. dead-key keyboard layouts deliver a
// closing quote and a separator in a single input event.
reconcileTokensRef.current = (tokens: string[]) => {
if (isSingleMode || !allowNewOptions) {
return;
}
setTimeout(() => {
tokens.forEach(token => {
const matched = getOption(token, fullSelectOptionsRef.current, true);
const matchedValue = isObject(matched) ? matched.value : matched;
if (hasOption(matchedValue ?? token, selectValueRef.current)) {
return;
}
const option = isObject(matched)
? (matched as AntdLabeledValue)
: { label: token, value: token, isNewOption: true };
if (!matched) {
const addOption = (previous: SelectOptionsType) =>
hasOption(token, previous, true)
? previous
: [option, ...previous];
setSelectOptions(addOption);
setVisibleOptions(addOption);
}
handleOnSelect(
(labelInValue
? { label: option.label, value: option.value }
: option.value) as string | AntdLabeledValue,
option as AntdLabeledValue,
);
});
});
};
const clear = () => {
if (isSingleMode) {
setSelectValue(undefined);
@@ -424,12 +481,14 @@ const Select = forwardRef(
const optionsWithoutTemporary = ensureIsArray(fullSelectOptions).filter(
opt => !opt.isNewOption,
);
const unquotedSearch = stripSurroundingQuotes(searchValue);
const shouldCreateNewOption =
searchValue && !hasOption(searchValue, optionsWithoutTemporary, true);
unquotedSearch &&
!hasOption(unquotedSearch, optionsWithoutTemporary, true);
const newOption = shouldCreateNewOption && {
label: searchValue,
value: searchValue,
label: unquotedSearch,
value: unquotedSearch,
isNewOption: true,
};
const cleanSelectOptions = ensureIsArray(fullSelectOptions).filter(
@@ -721,22 +780,11 @@ const Select = forwardRef(
setSelectValue(value);
}
} else {
// antd v6 widened `tokenSeparators` to `string[] | (input => string[])`;
// Superset always uses the array form.
// Superset's prop is the array form; antd receives the function form
const separators = Array.isArray(tokenSeparators)
? tokenSeparators
: [];
const token = separators.find((token: string) =>
pastedText.includes(token),
);
const array = token
? uniq(
pastedText
.split(token)
.map(item => item.trim())
.filter(Boolean),
)
: [pastedText.trim()].filter(Boolean);
const array = uniq(splitWithQuoteEscaping(pastedText, separators));
const newOptions: SelectOptionsType = [];
// When `allowNewOptionsOnPaste` is set, accept pasted values that are
@@ -762,6 +810,10 @@ const Select = forwardRef(
})
.filter(item => item !== undefined);
if (values.length > 0) {
e.preventDefault();
}
if (newOptions.length > 0) {
const updatedOptions = [...fullSelectOptions, ...newOptions];
setSelectOptions(updatedOptions);
@@ -854,7 +906,7 @@ const Select = forwardRef(
}
onClear={handleClear}
placeholder={placeholder}
tokenSeparators={tokenSeparators}
tokenSeparators={quoteAwareTokenSeparators}
value={selectValue}
virtual={
virtual !== undefined

View File

@@ -0,0 +1,140 @@
/**
* 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 {
splitWithQuoteEscaping,
stripSurroundingQuotes,
makeQuoteAwareTokenizer,
} from './utils';
test('stripSurroundingQuotes removes matching surrounding double quotes', () => {
expect(stripSurroundingQuotes('"foo"')).toBe('foo');
});
test('stripSurroundingQuotes trims whitespace before stripping', () => {
expect(stripSurroundingQuotes(' "foo" ')).toBe('foo');
});
test('stripSurroundingQuotes leaves text untouched when only one side is quoted', () => {
expect(stripSurroundingQuotes('"foo')).toBe('"foo');
expect(stripSurroundingQuotes('foo"')).toBe('foo"');
});
test('stripSurroundingQuotes preserves quotes in the middle of the string', () => {
expect(stripSurroundingQuotes('a"b"c')).toBe('a"b"c');
});
test('stripSurroundingQuotes returns an empty string for `""`', () => {
expect(stripSurroundingQuotes('""')).toBe('');
});
test('stripSurroundingQuotes does not strip a single lone quote', () => {
expect(stripSurroundingQuotes('"')).toBe('"');
});
test('stripSurroundingQuotes returns an empty string for empty/whitespace input', () => {
expect(stripSurroundingQuotes('')).toBe('');
expect(stripSurroundingQuotes(' ')).toBe('');
});
test('stripSurroundingQuotes unescapes doubled quotes when stripping', () => {
expect(stripSurroundingQuotes('"Bob ""The Builder"", Inc"')).toBe(
'Bob "The Builder", Inc',
);
expect(stripSurroundingQuotes('"a""b"')).toBe('a"b');
});
test('splitWithQuoteEscaping splits by separator and trims tokens', () => {
expect(splitWithQuoteEscaping('a, b ,c', [','])).toEqual(['a', 'b', 'c']);
});
test('splitWithQuoteEscaping treats doubled quotes inside a quoted region as a literal quote', () => {
expect(splitWithQuoteEscaping('"Bob ""The Builder"", Inc"', [','])).toEqual([
'Bob "The Builder", Inc',
]);
expect(splitWithQuoteEscaping('"a ""b""",c', [','])).toEqual(['a "b"', 'c']);
});
test('splitWithQuoteEscaping preserves separator characters inside double quotes', () => {
expect(splitWithQuoteEscaping('"a, b",c', [','])).toEqual(['a, b', 'c']);
});
test('splitWithQuoteEscaping strips quote characters from the output tokens', () => {
expect(splitWithQuoteEscaping('"foo","bar"', [','])).toEqual(['foo', 'bar']);
});
test('splitWithQuoteEscaping drops empty tokens from adjacent or edge separators', () => {
expect(splitWithQuoteEscaping('a,,b', [','])).toEqual(['a', 'b']);
expect(splitWithQuoteEscaping(',a,b,', [','])).toEqual(['a', 'b']);
});
test('splitWithQuoteEscaping treats unclosed quotes as opening a region that runs to end', () => {
expect(splitWithQuoteEscaping('"a, b', [','])).toEqual(['a, b']);
});
test('splitWithQuoteEscaping returns the original text when no separator is present', () => {
expect(splitWithQuoteEscaping('hello world', [','])).toEqual(['hello world']);
});
test('splitWithQuoteEscaping strips surrounding quotes when no separator is present', () => {
expect(splitWithQuoteEscaping('"Canada"', [','])).toEqual(['Canada']);
expect(splitWithQuoteEscaping('"Bob ""B"""', [','])).toEqual(['Bob "B"']);
});
test('splitWithQuoteEscaping uses the first separator from the list that appears in the text', () => {
expect(splitWithQuoteEscaping('a;b,c', [',', ';'])).toEqual(['a;b', 'c']);
expect(splitWithQuoteEscaping('a;b', [',', ';'])).toEqual(['a', 'b']);
});
test('splitWithQuoteEscaping supports multi-character separators', () => {
expect(splitWithQuoteEscaping('a, b, c', [', '])).toEqual(['a', 'b', 'c']);
});
test('makeQuoteAwareTokenizer returns input unchanged while quotes are open', () => {
const tokenize = makeQuoteAwareTokenizer([',']);
expect(tokenize('"Australia, US')).toEqual(['"Australia, US']);
});
test('makeQuoteAwareTokenizer returns input unchanged when separators only appear inside quotes', () => {
const tokenize = makeQuoteAwareTokenizer([',']);
expect(tokenize('"Australia, US"')).toEqual(['"Australia, US"']);
});
test('makeQuoteAwareTokenizer returns input unchanged when no separator is present', () => {
const tokenize = makeQuoteAwareTokenizer([',']);
expect(tokenize('Australia')).toEqual(['Australia']);
});
test('makeQuoteAwareTokenizer splits on a trailing unquoted separator', () => {
const tokenize = makeQuoteAwareTokenizer([',']);
expect(tokenize('Australia,')).toEqual(['Australia']);
});
test('makeQuoteAwareTokenizer splits mixed quoted and unquoted values', () => {
const tokenize = makeQuoteAwareTokenizer([',']);
expect(tokenize('"Australia, Austria",Canada')).toEqual([
'Australia, Austria',
'Canada',
]);
});
test('makeQuoteAwareTokenizer detects any separator from the list outside quotes', () => {
const tokenize = makeQuoteAwareTokenizer([',', '\n']);
expect(tokenize('a\nb')).toEqual(['a', 'b']);
expect(tokenize('"a\nb"')).toEqual(['"a\nb"']);
});

View File

@@ -201,6 +201,15 @@ export const dropDownRenderHelper = (
);
};
// Strips surrounding double quotes from a string, e.g. `"foo"` → `foo`.
export function stripSurroundingQuotes(text: string): string {
const trimmed = text.trim();
if (trimmed.length > 1 && trimmed.startsWith('"') && trimmed.endsWith('"')) {
return trimmed.slice(1, -1).replace(/""/g, '"');
}
return trimmed;
}
export const handleFilterOptionHelper = (
search: string,
option: AntdLabeledValue,
@@ -216,7 +225,7 @@ export const handleFilterOptionHelper = (
}
if (filterOption) {
const searchValue = search.trim().toLowerCase();
const searchValue = stripSurroundingQuotes(search).toLowerCase();
if (optionFilterProps?.length) {
return optionFilterProps.some(prop => {
const optionProp = option?.[prop as keyof CustomLabeledValue]
@@ -250,3 +259,67 @@ export const mapOptions = (values: SelectOptionsType): Record<string, any>[] =>
key: opt.value,
...opt,
}));
// Splits text by separators, preserving commas inside double quotes.
export function splitWithQuoteEscaping(
text: string,
separators: string[],
): string[] {
const separator = separators.find(sep => text.includes(sep));
if (!separator) {
return [stripSurroundingQuotes(text)].filter(Boolean);
}
const results: string[] = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < text.length; i += 1) {
const char = text[i];
if (char === '"') {
if (inQuotes && text[i + 1] === '"') {
current += '"';
i += 1;
} else {
inQuotes = !inQuotes;
}
} else if (!inQuotes && text.startsWith(separator, i)) {
results.push(current.trim());
current = '';
i += separator.length - 1;
} else {
current += char;
}
}
if (current.trim()) {
results.push(current.trim());
}
return results.filter(Boolean);
}
function hasUnquotedSeparator(text: string, separators: string[]): boolean {
let inQuotes = false;
for (let i = 0; i < text.length; i += 1) {
if (text[i] === '"') {
inQuotes = !inQuotes;
} else if (!inQuotes && separators.some(sep => text.startsWith(sep, i))) {
return true;
}
}
return false;
}
// Quote-aware tokenizer for antd's function form of `tokenSeparators`.
// Returning the input unchanged tells the Select not to tokenize, so typing
// continues while quotes are open or separators only appear inside them.
export function makeQuoteAwareTokenizer(
separators: string[],
): (input: string) => string[] {
return (input: string) =>
hasUnquotedSeparator(input, separators)
? splitWithQuoteEscaping(input, separators)
: [input];
}

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import Markdown from 'markdown-to-jsx';
import MarkdownToJsx from 'markdown-to-jsx';
export default {
title: 'Design System/Components/Table/TableOverview',
@@ -24,7 +24,7 @@ export default {
export const Overview = () => (
<>
<Markdown>
<MarkdownToJsx>
{`
# Table
@@ -44,9 +44,9 @@ This component provides a general use Table.
### [Basic example](./?path=/docs/design-system-components-table-examples--basic)
`}
</Markdown>
</MarkdownToJsx>
<Markdown>
<MarkdownToJsx>
{`
### Data and Columns
@@ -169,8 +169,8 @@ By default, each column will render the value as simple text. Often you will wan
Cell Renderers are React components provided to the optional \`render\` attribute on a column definition that enables injecting a specific React component to enable this.
`}
</Markdown>
<Markdown>
</MarkdownToJsx>
<MarkdownToJsx>
{`
For convenience and consistency, the Table component provides pre-built Cell Renderers for:
@@ -193,8 +193,8 @@ The following data types can be displayed in table cells.
The table can be set to a loading state simply by setting the loading prop to true | false
`}
</Markdown>
<Markdown>
</MarkdownToJsx>
<MarkdownToJsx>
{`
---
@@ -204,8 +204,8 @@ The table displays a set number of rows at a time, the user navigates the table
The default page size and page size options for the menu are configurable via the \`pageSizeOptions\` and \`defaultPageSize\` props.
NOTE: Pagination controls will only display when the data for the table has more records than the default page size.
`}
</Markdown>
<Markdown>
</MarkdownToJsx>
<MarkdownToJsx>
{`
\`\`\`
@@ -222,8 +222,8 @@ contained in the dataset on the server being paged through. When the user naviga
function enabling data fetching to occur when the user changes the page.
`}
</Markdown>
<Markdown>
</MarkdownToJsx>
<MarkdownToJsx>
{`
\`\`\`
@@ -293,8 +293,8 @@ NOTE: Row event handlers will be ignored when table is running with \`virtualize
Support for row event handlers may be added in future versions of the Table.
`}
</Markdown>
<Markdown>
</MarkdownToJsx>
<MarkdownToJsx>
{`
---
@@ -348,8 +348,8 @@ The prop \`resizable\` enables table columns to be resized by the user dragging
column to increase or decrease the columns' width
`}
</Markdown>
<Markdown>
</MarkdownToJsx>
<MarkdownToJsx>
{`
### [Drag & Drop Columns](./?path=/docs/design-system-components-table-examples--reorderable-columns)
@@ -368,6 +368,6 @@ const handleDrop = (ev:Event) => {
}
\`\`\`
`}
</Markdown>
</MarkdownToJsx>
</>
);

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import Markdown from 'markdown-to-jsx';
import MarkdownToJsx from 'markdown-to-jsx';
export default {
title: 'Design System/Components/Table/Cell Renderers/ActionCell/Overview"',
@@ -25,7 +25,7 @@ export default {
export const ActionCell = () => (
<>
<Markdown>
<MarkdownToJsx>
{`
# ActionCell
@@ -34,6 +34,6 @@ specific to the data in the table row that the cell is a member of.
### [Basic example](./?path=/docs/design-system-components-table-cell-renderers-actioncell--basic)
`}
</Markdown>
</MarkdownToJsx>
</>
);

View File

@@ -0,0 +1,51 @@
/*
* 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 NumberFormatter from '../NumberFormatter';
export type LengthConvertType = 'm => km' | 'cm => km' | 'cm => m';
export default function createLengthFormatter(
config: {
description?: string;
id?: string;
label?: string;
convertType?: LengthConvertType;
} = {},
) {
const { description, id, label, convertType } = config;
return new NumberFormatter({
description,
formatFunc: (value: number) => {
if (convertType === 'm => km') {
return `${(value / 1000).toFixed(2)}km`;
}
if (convertType === 'cm => km') {
return `${(value / 100_000).toFixed(2)}km`;
}
if (convertType === 'cm => m') {
return `${(value / 100).toFixed(2)}m`;
}
return value.toString();
},
id: id ?? 'length_format',
label: label ?? `Length formatter`,
});
}

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