Compare commits

..

73 Commits

Author SHA1 Message Date
Evan
9c273b6d52 fix(ci): pin PY_VER build-arg so PR docker builds use Python 3.11+
supersetbot's "dev"/"lean" presets hardcode --build-arg PY_VER=3.10-slim-bookworm,
which lands ahead of our --extra-flags and overrides the Dockerfile's own
3.11 default. Now that requires-python is >=3.11, that stale pin makes uv
resolve apache-superset-core against Python 3.10 and fail. Append our own
PY_VER override, which wins as the last occurrence of the flag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 00:00:43 -07:00
Evan
f9df4da8ae fix: restore mypy type: ignore for LegacyPrefixRedirectMiddleware wsgi_app arg
Dropping the sys.version_info conditional import in app.py also dropped
the `# type: ignore[arg-type]` mypy needs for the wsgi_app reassignment
after the conditional AppRootMiddleware rewrap, unrelated to the Python
version gate itself. Restores the exact comment/ignore already present
on master (identical code, unmodified there).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 20:33:05 -07:00
Evan Rusackas
332fe2e74b chore: drop Python 3.10 support, require Python 3.11+
CI, packaging metadata, and docs now reflect Python 3.11 as the minimum
supported version. Also collapses the sys.version_info compat shims in
superset/app.py, superset/middleware/legacy_prefix_redirect.py, and
superset/utils/backports.py, which existed solely to support running on
Python 3.10 (StrEnum and wsgiref.types were both added in 3.11).

Removing the 3.10 floor also drops the now-dead pyhive<0.7.0 marker for
python_version<'3.11' and obsoletes the need to pin syntaqlite below
0.5.0 for 3.10 compatibility.

Closes #42007

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 09:47:01 -07:00
JUST.in DO IT
4b07d43050 fix(sqllab): collapse left sidebar shows compact db selector in top bar (#41898) 2026-07-14 09:01:30 -07:00
dependabot[bot]
ef3a9e925e chore(deps-dev): bump @typescript-eslint/eslint-plugin from 8.62.0 to 8.63.0 in /superset-websocket (#42020)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2026-07-14 08:58:58 -07:00
Mehmet Salih Yavuz
76bb5f8e69 fix(dashboard): prevent native filter loss when saving chart customizations (#42032)
Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com>
2026-07-14 18:30:12 +03:00
dependabot[bot]
1f41899deb chore(deps-dev): bump @typescript-eslint/eslint-plugin from 8.62.1 to 8.63.0 in /superset-frontend (#42023)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 21:35:48 +07:00
Evan Rusackas
c4bee525ea fix(ci): correct mismatched version comment on actions/setup-node pin (#42036)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 21:32:50 +07:00
dependabot[bot]
0dcf67fa27 chore(deps-dev): bump typescript-eslint from 8.62.1 to 8.63.0 in /docs (#42024)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 21:31:49 +07:00
Evan Rusackas
4e10a96253 chore(ci): correct actions/setup-node version comment to v6.4.0 (#42038)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 21:31:08 +07:00
Evan Rusackas
24959d1656 fix(ci): correct setup-node version comment in tech-debt.yml (#42037) 2026-07-14 21:30:47 +07:00
Luiz Otavio
7075e9f253 fix(event-log): match embedded routes without trailing slash in logger middleware (#42005)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
2026-07-14 11:12:50 -03:00
Evan Rusackas
d5e75c4813 fix(ci): correct setup-node version comment in tag-release.yml (#42039) 2026-07-14 19:43:13 +07:00
dependabot[bot]
cc32d16e0c chore(deps-dev): bump @typescript-eslint/parser from 8.62.0 to 8.63.0 in /superset-websocket (#42021)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:28 -07:00
dependabot[bot]
258f4c035b chore(deps-dev): bump typescript-eslint from 8.62.1 to 8.63.0 in /superset-websocket (#42022)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:25 -07:00
dependabot[bot]
d49365757c chore(deps-dev): bump @typescript-eslint/parser from 8.61.0 to 8.63.0 in /docs (#42025)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:18 -07:00
dependabot[bot]
68881a60ca chore(deps): bump caniuse-lite from 1.0.30001802 to 1.0.30001803 in /docs (#42026)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:14 -07:00
dependabot[bot]
41efdb3082 chore(deps-dev): bump webpack-sources from 3.5.0 to 3.5.1 in /superset-frontend (#42027)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:11 -07:00
dependabot[bot]
e86dcf7b82 chore(deps): bump actions/setup-java from 5.4.0 to 5.5.0 (#42028)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:08 -07:00
dependabot[bot]
35d05cc278 chore(deps-dev): bump @swc/plugin-emotion from 14.14.1 to 14.15.0 in /superset-frontend (#42029)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:04 -07:00
dependabot[bot]
115ce12ff7 chore(deps-dev): bump @typescript-eslint/parser from 8.62.1 to 8.63.0 in /superset-frontend (#42030)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:14:01 -07:00
dependabot[bot]
ccfccdf237 chore(deps-dev): bump oxlint from 1.72.0 to 1.73.0 in /superset-frontend (#42031)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 05:13:56 -07:00
Antonio Rivero
42523f8cc4 fix(async-queries): back off polling when async event requests keep failing (#42012) 2026-07-14 12:57:45 +02:00
Hans Yu
e165762bb7 refactor: begin transaction to automatically commit (#41979)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-07-13 22:45:40 -07:00
Hans Yu
8afad27c84 chore: convert warning to error if parameters not passed as collection (#41977) 2026-07-13 21:25:23 -07:00
Evan Rusackas
e420812eb7 test(filters): guard BIGINT filter value precision (#27510) (#41963)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-13 21:20:38 -07:00
Mike Bridge
993a43396a fix: restore all-files pre-commit checks (#42014)
Co-authored-by: Mike Bridge <michael.bridge@ext.preset.io>
Co-authored-by: Joe Li <joe@preset.io>
2026-07-13 17:35:55 -07:00
Joe Li
a2a71760ed fix(e2e): repair master Playwright smoke tests (#42013) 2026-07-13 17:13:35 -07:00
Evan Rusackas
836dce9b05 fix(i18n): reset embedded start guard and align menu error handling (#41491)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:03:53 -07:00
Evan Rusackas
2857b29ab9 test(sqla): assert simple metric quotes columns requiring quoting (#30637) (#41961)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-13 15:58:34 -07:00
Evan Rusackas
3c648ca264 fix(swagger): support URL prefix via APPLICATION_ROOT in OpenAPI and Swagger UI (#40908)
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: rsbhatti <rajvindrasinghbhatti12@gmail.com>
2026-07-13 15:47:09 -07:00
Evan Rusackas
25ce83cc58 fix(dashboard): apply auto-refresh interval in standalone mode (#41740)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 15:46:34 -07:00
Elizabeth Thompson
ce8219672e fix(a11y): add accessible labels to EmbedCodeContent height/width inputs (#41995) 2026-07-13 15:05:28 -07:00
Elizabeth Thompson
3e957b63d4 fix(a11y): make CopyToClipboard keyboard-operable for custom copyNode elements (#41958) 2026-07-13 15:04:44 -07:00
Elizabeth Thompson
9818da445a fix(a11y): add aria-label to cross-filter search icon (#41619) 2026-07-13 15:03:08 -07:00
yousoph
8262aa7d6b fix(explore): restore drag-and-drop reordering broken by @dnd-kit migration (#41637)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 14:25:15 -07:00
innovark
d4a31d1d78 fix: add subdirectory deployment support for brandSpinnerUrl (#37523)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-07-13 14:01:14 -07:00
Evan Rusackas
18f1dd394b test(security): prove Drill By access does not require can_explore (#27900) (#41967)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-13 12:05:16 -07:00
Amin Ghadersohi
62ccdfacc2 fix(mcp): make list-item truncation cap configurable in get_dashboard_info (#41698) 2026-07-13 14:46:10 -04:00
Evan Rusackas
c5131bff47 chore(a11y): enable 9 more jsx-a11y oxlint rules as errors (#42006)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 11:39:13 -07:00
Hans Yu
521e51429a chore: as_declarative() warnings are irrelevant (#41982) 2026-07-13 11:37:45 -07:00
Durgaprasad M L
64df96be00 fix(nvd3): invoke shiftMetric() in TimePivot formDataOverrides (#41916) 2026-07-13 10:44:50 -07:00
Hans Yu
d43a0d7633 chore: sqlalchemy.database package warnings are irrelevant (#41978) 2026-07-13 10:41:41 -07:00
Amin Ghadersohi
873566c827 fix(mcp): stop masking dashboard lookup DB errors as not-found (#41919) 2026-07-13 13:32:35 -04:00
Evan Rusackas
e063f5093c feat(frontend): upgrade Ant Design from v5 to v6 (#41636)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com>
2026-07-13 10:13:30 -07:00
Evan Rusackas
1627fab741 fix(i18n): make every catalog compile clean, canonicalize, fix ja plurals (#41828)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 10:08:03 -07:00
Enzo Martellucci
5dd060b714 fix(chart-echarts): disable animation for report screenshots so time-shift lines render fully (#42003) 2026-07-13 19:02:45 +02:00
Hans Yu
68ebc22e1e refactor: replace Engine.execute() with Connection.execute() (#41917) 2026-07-13 09:40:33 -07:00
Enzo Martellucci
8f75f1a353 fix(dashboard): deleted Display Control reappears after Apply Filters (#41999) 2026-07-13 16:25:47 +02:00
Mehmet Salih Yavuz
2fac66d1a3 feat(mcp): add update_dataset_metric tool for editing saved dataset metrics (#40975) 2026-07-13 15:44:57 +03:00
Luiz Otavio
029d49539b fix(event-log): match embedded routes precisely in logger middleware (#41942) 2026-07-13 08:46:23 -03:00
dependabot[bot]
3c6982252b chore(deps-dev): bump webpack from 5.108.3 to 5.108.4 in /superset-frontend (#41989)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 18:08:48 +07:00
dependabot[bot]
1c7a3f395f chore(deps): bump fs-extra from 11.3.2 to 11.3.6 in /superset-frontend (#41990)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 18:08:05 +07:00
dependabot[bot]
f7b7bad9a8 chore(deps-dev): bump tsx from 4.22.5 to 4.23.0 in /superset-frontend (#41985)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:24 -07:00
dependabot[bot]
0cf217cdea chore(deps-dev): bump vitest from 4.1.9 to 4.1.10 in /superset-websocket (#41986)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:21 -07:00
dependabot[bot]
96bf1a1e4c chore(deps): bump caniuse-lite from 1.0.30001800 to 1.0.30001802 in /docs (#41987)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:18 -07:00
dependabot[bot]
9180ace1dd chore(deps): bump actions/labeler from 6.1.0 to 6.2.0 (#41988)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:15 -07:00
dependabot[bot]
bb5d7dbb17 chore(deps): bump immer from 11.1.9 to 11.1.11 in /superset-frontend (#41991)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:12 -07:00
dependabot[bot]
73925922a9 chore(deps): bump react-arborist from 3.12.0 to 3.13.2 in /superset-frontend (#41992)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:09 -07:00
dependabot[bot]
8f339545ad chore(deps-dev): bump baseline-browser-mapping from 2.10.41 to 2.10.42 in /superset-frontend (#41993)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 03:26:06 -07:00
Evan Rusackas
c762b75ddc chore(lint): upgrade no-unused-vars rule from warn to error (#41132)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 03:11:09 -07:00
Evan Rusackas
c0e5f5226d fix(explore): let chart owners overwrite when owners come through as objects (#41352)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 03:10:00 -07:00
dependabot[bot]
d380663e71 chore(deps-dev): bump syntaqlite from 0.4.2 to 0.6.0 (#41952)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-12 20:17:34 -07:00
dependabot[bot]
98c3ec7a71 chore(deps): bump sqlglot from 30.8.0 to 30.12.0 (#41948)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 16:20:07 -07:00
dependabot[bot]
12f28ce235 chore(deps): bump holidays from 0.82 to 0.99 (#41949)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-12 16:19:51 -07:00
Evan Rusackas
e403d9b074 chore(lint): convert legacy plugin chart components to function components (#39452)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com>
2026-07-13 00:37:46 +02:00
isaac-jaynes-imperva
0ca51d9796 fix(Database Connection Form): Added validation to db credential input (#37919)
Co-authored-by: codeant-ai-for-open-source[bot] <244253245+codeant-ai-for-open-source[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 09:36:41 -07:00
dependabot[bot]
db1912c6d9 chore(deps): bump cachetools from 6.2.1 to 7.1.4 (#41947)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-12 09:36:28 -07:00
Evan Rusackas
2a18a556b0 chore(frontend): lint Emotion CSS-in-JS with Stylelint (#41871)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com>
2026-07-12 08:45:02 -07:00
Evan Rusackas
a1d1d69f5e ci: enforce SQLAlchemy 2.0 deprecation warnings in unit-test CI (#41915)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 08:43:09 -07:00
Moussa Leblouba
13c5a32402 fix(tests): correct stale TYPE_CHECKING import path in base_data_loader (#41972)
Co-authored-by: Moussa Leblouba <mlablouba@gmail.com>
2026-07-12 20:26:11 +07:00
Evan Rusackas
958d1ab256 test(rls): assert dataset search returns all mask matches (#29707) (#41968)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-12 20:23:29 +07:00
Evan Rusackas
b8c0171976 test(rls): assert related/subjects honors search filter for roles (#31466) (#41969)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-07-12 20:22:39 +07:00
425 changed files with 11665 additions and 3247 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

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

View File

@@ -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

@@ -47,7 +47,7 @@ jobs:
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
@@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ jobs:
# Run the full version spread on push (master/release) and nightly,
# but only the current version on PRs — lint/format/type results
# rarely differ across patch versions, so 3x per PR is wasteful.
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "previous", "next"]') }}
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -45,7 +45,7 @@ jobs:
run: go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.14.2
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: "superset-frontend/.nvmrc"
cache: "npm"

View File

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

View File

@@ -98,7 +98,7 @@ jobs:
# 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
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 \
.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -81,6 +81,12 @@ repos:
language: system
pass_filenames: true
files: ^superset-frontend/.*\.(js|jsx|ts|tsx)$
- id: stylelint-frontend
name: stylelint (frontend css-in-js)
entry: ./scripts/stylelint.sh
language: system
pass_filenames: true
files: ^superset-frontend/.*\.(js|jsx|ts|tsx)$
- id: eslint-docs
name: eslint (docs)
entry: bash -c 'cd docs && FILES=$(printf "%s\n" "$@" | sed "s|^docs/||" | tr "\n" " ") && yarn eslint --fix --quiet $FILES'

View File

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

View File

@@ -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,13 @@ assists people when migrating to a new version.
## Next
### 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,
@@ -153,6 +160,16 @@ virtual-dataset SQL) raises a parse error in SQL Lab and dashboard-generated
queries. Deployments that legitimately run queries above this size should raise
the value, and `SQL_MAX_PARSE_LENGTH = None` disables the check entirely.
### Ant Design upgraded from v5 to v6
The frontend now builds against Ant Design 6, and `@superset-ui/core` / `@apache-superset/core` peer-depend on `antd ^6`. Custom plugins, extensions, and themes that interact with Ant Design need review:
- **Internal DOM classes were renamed**, so any custom CSS targeting `.ant-*` internals silently stops matching. Notable renames: `.ant-tabs-content-holder``.ant-tabs-body-holder`, `.ant-tabs-content``.ant-tabs-body`, `.ant-tabs-tabpane``.ant-tabs-content`; `.ant-select-selector``.ant-select-content`, `.ant-select-selection-placeholder``.ant-select-placeholder`, `.ant-select-arrow``.ant-select-suffix`; `.ant-tooltip-inner``.ant-tooltip-container`; `.ant-popover-inner``.ant-popover-container`; `.ant-steps-item-tail``.ant-steps-item-rail`.
- **Some component props changed or were removed** — e.g. `Select` no longer accepts `dropdownAlign`, `visible`/`onVisibleChange` are `open`/`onOpenChange`, `Dropdown` `overlay` is `menu`, `Steps.Step` children are the `items` prop, and `styles.body` on Tooltip/Popover is `styles.container`.
- **CSS variables are on by default** in antd 6, and `ThemeConfig.cssVar` no longer accepts a boolean; Superset theme configs using `cssVar: true`/`false` are coerced (`true``{}`, `false` → omitted).
Theme tokens are unaffected — antd 6 removed none of the tokens Superset exposes, so existing theme configurations continue to work. See the [Ant Design v6 migration guide](https://ant.design/docs/react/migration-v6) for the complete upstream list.
### Guest-token RLS rules reject unknown fields
The `rls` rules passed to `POST /api/v1/security/guest_token/` are now validated strictly: a rule may only contain `dataset` and `clause`. Previously unknown fields were silently dropped, so a mistyped or legacy scope key (most commonly `datasource` instead of `dataset`) produced a rule with no `dataset`, which is treated as a *global* rule applied to every dataset the embedded resource can reach. Such a request now returns HTTP 400 identifying the offending field instead of issuing a token with an unintended global rule. Integrators that were sending extra fields in RLS rules must remove them; valid dataset-scoped (`{"dataset": 41, "clause": "..."}`) and global (`{"clause": "..."}`) rules are unaffected.

View File

@@ -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

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

View File

@@ -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

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

View File

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

View File

@@ -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",
]
@@ -43,7 +42,7 @@ dependencies = [
# ``google-auth`` 2.53+ dropped it, so Superset must declare it
# explicitly to keep fresh ``pip install apache-superset`` working
# without the ``base.txt`` lock file (#40962).
"cachetools>=6.2.1, <7",
"cachetools>=7.1.4, <8",
"celery>=5.6.3, <6.0.0",
"click>=8.4.0",
"click-option-group",
@@ -67,7 +66,7 @@ dependencies = [
"gunicorn>=26.0.0, <27; sys_platform != 'win32'",
"hashids>=1.3.1, <2",
# holidays>=0.45 required for security fix
"holidays>=0.45, <1",
"holidays>=0.99, <1",
"humanize",
"isodate",
"jsonpath-ng>=1.8.0, <2",
@@ -110,7 +109,7 @@ dependencies = [
"sqlalchemy>=1.4, <2",
"sqlalchemy-continuum>=1.6.0, <2.0.0",
"sqlalchemy-utils>=0.42.1, <0.43", # expanding lowerbound to work with pydoris
"sqlglot>=30.8.0, <31",
"sqlglot>=30.12.0, <31",
# newer pandas needs 0.9+
"tabulate>=0.10.0, <1.0",
"typing-extensions>=4, <5",
@@ -167,7 +166,6 @@ gevent = ["gevent>=26.4.0"]
gsheets = ["shillelagh[gsheetsapi]>=1.4.4, <2"]
hana = ["hdbcli==2.28.21", "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",
@@ -199,9 +197,8 @@ risingwave = ["sqlalchemy-risingwave"]
shillelagh = ["shillelagh[all]>=1.4.4, <2"]
singlestore = ["sqlalchemy-singlestoredb>=1.2.1, <2"]
snowflake = ["snowflake-sqlalchemy>=1.10.2, <2"]
sqlite = ["syntaqlite>=0.1.0,<0.5.0"]
sqlite = ["syntaqlite>=0.6.0,<0.7.0"]
spark = [
"pyhive[hive]>=0.6.5;python_version<'3.11'",
"pyhive[hive_pure_sasl]>=0.7",
"tableschema",
"thrift>=0.23.0, <1",
@@ -244,7 +241,7 @@ development = [
"ruff",
"sqloxide",
"statsd",
"syntaqlite>=0.4.2,<0.5.0",
"syntaqlite>=0.6.0,<0.7.0",
]
[project.urls]
@@ -344,8 +341,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

@@ -35,13 +35,13 @@ filterwarnings =
# error:"SqlMetric" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
# error:"TableColumn" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
# error:"TaggedObject" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
# error:The ``as_declarative\(\)`` function is now available:sqlalchemy.exc.RemovedIn20Warning
# error:The autoload parameter is deprecated:sqlalchemy.exc.RemovedIn20Warning
# error:The connection.execute\(\) method:sqlalchemy.exc.RemovedIn20Warning
error:The current statement is being autocommitted using implicit autocommit:sqlalchemy.exc.RemovedIn20Warning
error:The connection.execute\(\) method:sqlalchemy.exc.RemovedIn20Warning
# error:The current statement is being autocommitted using implicit autocommit:sqlalchemy.exc.RemovedIn20Warning
# error:The `database` package is deprecated:sqlalchemy.exc.RemovedIn20Warning
# error:The ``declarative_base\(\)`` function is now available:sqlalchemy.exc.RemovedIn20Warning
# error:The Engine.execute\(\) method is considered legacy:sqlalchemy.exc.RemovedIn20Warning
error:The Engine.execute\(\) method is considered legacy:sqlalchemy.exc.RemovedIn20Warning
error:The legacy calling style of select\(\) is deprecated:sqlalchemy.exc.RemovedIn20Warning
error:The "whens" argument to case:sqlalchemy.exc.RemovedIn20Warning
# error:"User" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning

View File

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

View File

@@ -97,7 +97,7 @@ cachelib==0.13.0
# -c requirements/base-constraint.txt
# flask-caching
# flask-session
cachetools==6.2.1
cachetools==7.1.4
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -407,7 +407,7 @@ hashids==1.3.1
# via
# -c requirements/base-constraint.txt
# apache-superset
holidays==0.82
holidays==0.100
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -998,7 +998,7 @@ sqlalchemy-utils==0.42.1
# apache-superset
# apache-superset-core
# flask-appbuilder
sqlglot==30.8.0
sqlglot==30.12.0
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -1017,7 +1017,7 @@ starlette==1.3.1
# mcp
statsd==4.0.1
# via apache-superset
syntaqlite==0.4.2
syntaqlite==0.6.0
# via apache-superset
tabulate==0.10.0
# via

View File

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

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",
),

49
scripts/stylelint.sh Executable file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -e
script_dir="$(dirname "$(realpath "$0")")"
root_dir="$(dirname "$script_dir")"
frontend_dir=superset-frontend
if [[ ! -d "$root_dir/$frontend_dir" ]]; then
echo "Error: $frontend_dir directory not found in $root_dir" >&2
exit 1
fi
cd "$root_dir/$frontend_dir"
# Filter files to only include JS/TS files and remove the frontend dir prefix
js_ts_files=()
for file in "$@"; do
# Remove superset-frontend/ prefix if present
cleaned_file="${file#$frontend_dir/}"
# Only include JS/TS files (Emotion styles live in these)
if [[ "$cleaned_file" =~ \.(js|jsx|ts|tsx)$ ]]; then
js_ts_files+=("$cleaned_file")
fi
done
# Only run if we have JS/TS files to lint. --allow-empty-input keeps the hook
# green when every passed file is covered by .stylelintignore.
if [ ${#js_ts_files[@]} -gt 0 ]; then
npx stylelint --allow-empty-input "${js_ts_files[@]}"
else
echo "No JavaScript/TypeScript files to lint for styles"
fi

View File

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

View File

@@ -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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -75,7 +75,7 @@ module.exports = {
// @ant-design/colors and @ant-design/fast-color are allowed through because
// @ant-design/icons >= 6.3 deep-imports the ESM build of @ant-design/colors
// from its CJS output, so babel-jest must transform those files.
'node_modules/(?!@ant-design/(colors|fast-color)|@formatjs/.*|d3-(array|interpolate|color|time|scale|time-format|format)|internmap|@mapbox/tiny-sdf|remark-gfm|(?!@ngrx|(?!deck.gl)|d3-scale)|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|uuid|@rjsf/*.|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|geostyler|geostyler-.*|(?!geostyler)lodash|react-error-boundary|react-json-tree|react-base16-styling|lodash-es|rbush|quickselect|react-diff-viewer-continued|storybook/*.|json-stringify-pretty-compact)',
'node_modules/(?!@ant-design/(colors|fast-color)|@formatjs/.*|d3-(array|interpolate|color|time|scale|time-format|format)|internmap|@mapbox/tiny-sdf|remark-gfm|(?!@ngrx|(?!deck.gl)|d3-scale)|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|uuid|@rjsf/*.|@x0k/.*|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|geostyler|geostyler-.*|(?!geostyler)lodash|react-error-boundary|react-json-tree|react-base16-styling|lodash-es|rbush|quickselect|react-diff-viewer-continued|storybook/*.|json-stringify-pretty-compact)',
],
preset: 'ts-jest',
transform: {

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -54,7 +54,7 @@
"react-ace": "^14.0.1",
"react-draggable": "^4.7.0",
"react-error-boundary": "^6.1.2",
"react-js-cron": "^5.2.0",
"react-js-cron": "^6.0.2",
"react-markdown": "^10.1.0",
"react-resize-detector": "^7.1.2",
"react-syntax-highlighter": "^16.1.1",
@@ -100,7 +100,7 @@
"@types/react-loadable": "*",
"@types/react-window": "^1.8.8",
"@types/tinycolor2": "*",
"antd": "^5.26.0",
"antd": "^6.0.0",
"nanoid": "^5.0.9",
"react": "^18.3.0",
"react-dom": "^18.3.0",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -60,6 +60,7 @@ export const FaveStar = ({
className="fave-unfave-icon"
data-test="fave-unfave-icon"
role="button"
tabIndex={0}
>
{isStarred ? (
<Icons.StarFilled

View File

@@ -28,6 +28,10 @@ const StyledInput = styled(Input)`
margin: ${({ theme }) => `${theme.sizeUnit}px 0 ${theme.sizeUnit * 2}px`};
`;
const StyledTextArea = styled(Input.TextArea)`
margin: ${({ theme }) => `${theme.sizeUnit}px 0 ${theme.sizeUnit * 2}px`};
`;
const StyledInputPassword = styled(Input.Password)`
margin: ${({ theme }) => `${theme.sizeUnit}px 0 ${theme.sizeUnit * 2}px`};
`;
@@ -62,6 +66,8 @@ export const LabeledErrorBoundInput = ({
get_url,
description,
isValidating = false,
renderAsTextArea,
textAreaCss,
...props
}: LabeledErrorBoundInputProps) => {
const hasError = !!errorMessage;
@@ -98,6 +104,8 @@ export const LabeledErrorBoundInput = ({
}
role="textbox"
/>
) : renderAsTextArea ? (
<StyledTextArea css={textAreaCss} {...props} {...validationMethods} />
) : (
<StyledInput {...props} {...validationMethods} />
)}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -106,7 +106,11 @@ test('stops propagation of navigation keys to parent elements', async () => {
expect(handleParentKeyDown).not.toHaveBeenCalled();
}
const allowedKeys = ['Escape', 'Tab'];
// `Tab` must be checked before `Escape`: pressing `Escape` legitimately
// closes the modal (a real global listener unmounts the dialog), so any
// key fired on the stale `input` reference afterwards can no longer
// bubble anywhere.
const allowedKeys = ['Tab', 'Escape'];
for (const key of allowedKeys) {
handleParentKeyDown.mockClear();

View File

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

View File

@@ -36,6 +36,7 @@ const RefreshLabel = ({
<Icons.SyncOutlined
iconSize="l"
role="button"
tabIndex={disabled ? -1 : 0}
onClick={disabled ? undefined : onClick}
css={(theme: SupersetTheme) => ({
cursor: 'pointer',

View File

@@ -119,10 +119,22 @@ const findAllSelectOptions = () =>
waitFor(() => getElementsByClassName('.ant-select-item-option-content'));
const findSelectValue = () =>
waitFor(() => getElementByClassName('.ant-select-selection-item'));
// antd v6: single-mode value is `.ant-select-content-has-value`, multiple-mode
// tags remain `.ant-select-selection-item`.
waitFor(() =>
getElementByClassName(
'.ant-select-content-has-value, .ant-select-selection-item',
),
);
const findAllSelectValues = () =>
waitFor(() => getElementsByClassName('.ant-select-selection-item'));
// antd v6: multiple-mode tags keep `.ant-select-selection-item`, single-mode
// value is `.ant-select-content-has-value`.
waitFor(() =>
getElementsByClassName(
'.ant-select-selection-item, .ant-select-content-has-value',
),
);
const clearAll = () => userEvent.click(screen.getByLabelText('close-circle'));
@@ -381,7 +393,7 @@ test('searches for custom fields', async () => {
test('removes duplicated values', async () => {
render(<AsyncSelect {...defaultProps} mode="multiple" allowNewOptions />);
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => 'a,b,b,b,c,d,d',
@@ -400,7 +412,7 @@ test('removes duplicated values', async () => {
test('trims whitespace from pasted comma-separated values', async () => {
render(<AsyncSelect {...defaultProps} mode="multiple" allowNewOptions />);
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => 'a, b, c , d',
@@ -833,7 +845,7 @@ test('Renders only an overflow tag if dropdown is open in oneLine mode', async (
);
await open();
const withinSelector = within(getElementByClassName('.ant-select-selector'));
const withinSelector = within(getElementByClassName('.ant-select-content'));
await waitFor(() => {
expect(
withinSelector.queryByText(OPTIONS[0].label),
@@ -906,7 +918,7 @@ test('fires onChange when pasting a selection', async () => {
const onChange = jest.fn();
render(<AsyncSelect {...defaultProps} onChange={onChange} />);
await open();
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => OPTIONS[0].label,
@@ -1183,7 +1195,7 @@ test('keeps loading indicator while a newer request is in flight after a stale r
});
const isSpinnerVisible = (): boolean =>
Boolean(document.querySelector('.ant-select-arrow .ant-spin'));
Boolean(document.querySelector('.ant-select-suffix .ant-spin'));
try {
render(<AsyncSelect {...defaultProps} options={loadOptions} />);
@@ -1331,7 +1343,7 @@ test('appends page>1 results during an active search and discards them when sear
// Wait for loading to finish so handlePagination's `!isLoading` gate is
// open before we fire scroll.
await waitFor(() =>
expect(document.querySelector('.ant-select-arrow .ant-spin')).toBeNull(),
expect(document.querySelector('.ant-select-suffix .ant-spin')).toBeNull(),
);
// Trigger pagination by dispatching a scroll event on the virtual-list
@@ -1411,7 +1423,7 @@ test('pasting an existing option does not duplicate it', async () => {
}));
render(<AsyncSelect {...defaultProps} options={options} />);
await open();
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => OPTIONS[0].label,
@@ -1439,7 +1451,7 @@ test('pasting an existing option does not duplicate it in multiple mode', async
/>,
);
await open();
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => 'John,Liam,Peter',
@@ -1461,7 +1473,7 @@ test('pasting an non-existent option should not add it if allowNewOptions is fal
/>,
);
await open();
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => 'John',
@@ -1475,7 +1487,7 @@ test('onChange is called with the value property when pasting an option that was
const onChange = jest.fn();
render(<AsyncSelect {...defaultProps} onChange={onChange} />);
await open();
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const lastOption = OPTIONS[OPTIONS.length - 1];
const paste = createEvent.paste(input, {
clipboardData: {

View File

@@ -86,6 +86,7 @@ import {
MAX_TAG_COUNT,
TOKEN_SEPARATORS,
DEFAULT_SORT_COMPARATOR,
DROPDOWN_BUILTIN_PLACEMENTS,
} from './constants';
const Error = ({ error }: { error: string }) => (
@@ -152,6 +153,7 @@ const AsyncSelect = forwardRef(
ref: ForwardedRef<AsyncSelectRef>,
) => {
const isSingleMode = mode === 'single';
const shouldShowSearch = allowNewOptions ? true : Boolean(showSearch);
const [selectValue, setSelectValue] = useState(value);
const [inputValue, setInputValue] = useState('');
const [isLoading, setIsLoading] = useState(loading);
@@ -693,7 +695,14 @@ const AsyncSelect = forwardRef(
setSelectValue(value);
}
} else {
const token = tokenSeparators.find(token => pastedText.includes(token));
// antd v6 widened `tokenSeparators` to `string[] | (input => string[])`;
// Superset always uses the array form.
const separators = Array.isArray(tokenSeparators)
? tokenSeparators
: [];
const token = separators.find((token: string) =>
pastedText.includes(token),
);
const array = token
? uniq(
pastedText
@@ -770,7 +779,7 @@ const AsyncSelect = forwardRef(
// surface, but the underlying input accepts it and we rely on that.
onPaste={onPaste}
onPopupScroll={handlePagination}
onSearch={showSearch ? handleOnSearch : undefined}
onSearch={shouldShowSearch ? handleOnSearch : undefined}
onSelect={
handleOnSelect as unknown as (
value: unknown,
@@ -781,10 +790,15 @@ const AsyncSelect = forwardRef(
options={fullSelectOptions}
optionRender={option => <Space>{option.label || option.value}</Space>}
placeholder={placeholder}
showSearch={allowNewOptions ? true : showSearch}
showSearch={shouldShowSearch}
tokenSeparators={tokenSeparators}
builtinPlacements={DROPDOWN_BUILTIN_PLACEMENTS}
value={selectValue}
suffixIcon={getSuffixIcon(isLoading, showSearch, isDropdownVisible)}
suffixIcon={getSuffixIcon(
isLoading,
shouldShowSearch,
isDropdownVisible,
)}
menuItemSelectedIcon={
invertSelection ? (
<StyledStopOutlined iconSize="m" aria-label="stop" />

View File

@@ -108,10 +108,22 @@ const findAllSelectOptions = () =>
waitFor(() => getElementsByClassName('.ant-select-item-option-content'));
const findSelectValue = () =>
waitFor(() => getElementByClassName('.ant-select-selection-item'));
// antd v6: single-mode value is `.ant-select-content-has-value`, multiple-mode
// tags remain `.ant-select-selection-item`.
waitFor(() =>
getElementByClassName(
'.ant-select-content-has-value, .ant-select-selection-item',
),
);
const findAllSelectValues = () =>
waitFor(() => [...getElementsByClassName('.ant-select-selection-item')]);
// antd v6: multiple-mode tags keep `.ant-select-selection-item`, single-mode
// value is `.ant-select-content-has-value`.
waitFor(() => [
...getElementsByClassName(
'.ant-select-selection-item, .ant-select-content-has-value',
),
]);
const clearAll = () => userEvent.click(screen.getByLabelText('close-circle'));
@@ -363,7 +375,7 @@ test('searches for custom fields', async () => {
test('removes duplicated values', async () => {
render(<Select {...defaultProps} mode="multiple" allowNewOptions />);
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => 'a,b,b,b,c,d,d',
@@ -380,7 +392,7 @@ test('removes duplicated values', async () => {
test('trims whitespace from pasted comma-separated values', async () => {
render(<Select {...defaultProps} mode="multiple" allowNewOptions />);
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => 'a, b, c , d',
@@ -777,7 +789,7 @@ test('Renders only an overflow tag if dropdown is open in oneLine mode', async (
);
await open();
const withinSelector = within(getElementByClassName('.ant-select-selector'));
const withinSelector = within(getElementByClassName('.ant-select-content'));
await waitFor(() => {
expect(
withinSelector.queryByText(OPTIONS[0].label),
@@ -810,7 +822,7 @@ test('Maintains stable maxTagCount to prevent click target disappearing in oneLi
/>,
);
const withinSelector = within(getElementByClassName('.ant-select-selector'));
const withinSelector = within(getElementByClassName('.ant-select-content'));
expect(withinSelector.getByText(OPTIONS[0].label)).toBeVisible();
expect(withinSelector.getByText('+ 2 ...')).toBeVisible();
@@ -847,9 +859,7 @@ test('dropdown width matches input width after tags collapse in oneLine mode', a
// Wait for RAF to complete and tags to collapse
await waitFor(() => {
const withinSelector = within(
getElementByClassName('.ant-select-selector'),
);
const withinSelector = within(getElementByClassName('.ant-select-content'));
expect(
withinSelector.queryByText(OPTIONS[0].label),
).not.toBeInTheDocument();
@@ -1085,7 +1095,7 @@ test('fires onChange when pasting a selection', async () => {
const onChange = jest.fn();
render(<Select {...defaultProps} onChange={onChange} />);
await open();
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => OPTIONS[0].label,
@@ -1113,7 +1123,7 @@ test('does not duplicate options when using numeric values', async () => {
test('pasting an existing option does not duplicate it', async () => {
render(<Select {...defaultProps} options={[OPTIONS[0]]} />);
await open();
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => OPTIONS[0].label,
@@ -1139,7 +1149,7 @@ test('pasting an existing option does not duplicate it in multiple mode', async
/>,
);
await open();
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => 'John,Liam,Peter',
@@ -1153,7 +1163,7 @@ test('pasting an existing option does not duplicate it in multiple mode', async
test('pasting an non-existent option should not add it if allowNewOptions is false', async () => {
render(<Select {...defaultProps} options={[]} allowNewOptions={false} />);
await open();
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => 'John',
@@ -1179,7 +1189,7 @@ test('keeps pasted values outside loaded options when allowNewOptionsOnPaste is
onChange={onChange}
/>,
);
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
// Liam is a loaded option; OutsideValue is not in the loaded page.
@@ -1215,7 +1225,7 @@ test('trims whitespace around pasted comma-separated values', async () => {
onChange={onChange}
/>,
);
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
// Note the space after the comma — it must not leak into the value.
@@ -1248,7 +1258,7 @@ test('does not create an empty option when pasting blank text', async () => {
onChange={onChange}
/>,
);
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => ' ',
@@ -1269,7 +1279,7 @@ test('does not create an empty option when pasting blank text', async () => {
test('drops pasted values outside loaded options when allowNewOptionsOnPaste is false', async () => {
render(<Select {...defaultProps} mode="multiple" allowNewOptions={false} />);
const input = getElementByClassName('.ant-select-selection-search-input');
const input = getElementByClassName('.ant-select-input');
const paste = createEvent.paste(input, {
clipboardData: {
getData: () => 'Liam,OutsideValue',

View File

@@ -66,7 +66,7 @@ import {
} from './styles';
import {
DEFAULT_SORT_COMPARATOR,
DROPDOWN_ALIGN_BOTTOM,
DROPDOWN_BUILTIN_PLACEMENTS,
EMPTY_OPTIONS,
MAX_TAG_COUNT,
TOKEN_SEPARATORS,
@@ -130,7 +130,9 @@ const Select = forwardRef(
ref: Ref<RefSelectProps>,
) => {
const isSingleMode = mode === 'single';
const shouldShowSearch = allowNewOptions ? true : showSearch;
// antd v6 widened `showSearch` to `boolean | SearchConfig`; coerce to a
// plain boolean for Superset's internal toggles and helpers.
const shouldShowSearch = allowNewOptions ? true : Boolean(showSearch);
const [selectValue, setSelectValue] = useState(value);
const [inputValue, setInputValue] = useState('');
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
@@ -719,7 +721,14 @@ const Select = forwardRef(
setSelectValue(value);
}
} else {
const token = tokenSeparators.find(token => pastedText.includes(token));
// antd v6 widened `tokenSeparators` to `string[] | (input => string[])`;
// Superset always uses the array form.
const separators = Array.isArray(tokenSeparators)
? tokenSeparators
: [];
const token = separators.find((token: string) =>
pastedText.includes(token),
);
const array = token
? uniq(
pastedText
@@ -868,8 +877,8 @@ const Select = forwardRef(
optionRender={option => <Space>{option.label || option.value}</Space>}
oneLine={oneLine}
popupMatchSelectWidth={oneLine ? dropdownWidth : true}
builtinPlacements={DROPDOWN_BUILTIN_PLACEMENTS}
css={props.css}
dropdownAlign={DROPDOWN_ALIGN_BOTTOM}
{...props}
showSearch={shouldShowSearch}
ref={ref}

View File

@@ -18,7 +18,10 @@
*/
import type { LabeledValue as AntdLabeledValue } from 'antd/es/select';
import { DEFAULT_SORT_COMPARATOR } from './constants';
import {
DEFAULT_SORT_COMPARATOR,
DROPDOWN_BUILTIN_PLACEMENTS,
} from './constants';
test('DEFAULT_SORT_COMPARATOR sorts by label text when both labels are strings', () => {
const a = { value: 'b', label: 'banana' } as AntdLabeledValue;
@@ -47,3 +50,46 @@ test('DEFAULT_SORT_COMPARATOR uses rankedSearchCompare when search is provided',
// 'bc' is an exact match to search 'bc', so it should sort first (lower index = negative diff)
expect(DEFAULT_SORT_COMPARATOR(a, b, 'bc')).toBeGreaterThan(0);
});
// Characterization of the dropdown-positioning fix from #36963. antd 6
// removed Select's `dropdownAlign`, so the fix is expressed through
// `builtinPlacements` — which replaces antd's ENTIRE placement map. This
// pins two things a future edit could silently break:
// 1. every placement disables horizontal viewport adjustment (the point
// of #36963 — the popup must not shift away from its input), and
// 2. all four of antd's placements are present with antd's own
// points/offsets, so replacing the map does not change unrelated
// positioning behavior.
test('DROPDOWN_BUILTIN_PLACEMENTS pins #36963 across all four antd placements', () => {
const placements = DROPDOWN_BUILTIN_PLACEMENTS as Record<
string,
{
points: string[];
offset: number[];
overflow: { adjustX: boolean; adjustY: boolean };
}
>;
expect(Object.keys(placements).sort()).toEqual([
'bottomLeft',
'bottomRight',
'topLeft',
'topRight',
]);
// antd 6's default points/offsets, unchanged
expect(placements.bottomLeft.points).toEqual(['tl', 'bl']);
expect(placements.bottomLeft.offset).toEqual([0, 4]);
expect(placements.bottomRight.points).toEqual(['tr', 'br']);
expect(placements.bottomRight.offset).toEqual([0, 4]);
expect(placements.topLeft.points).toEqual(['bl', 'tl']);
expect(placements.topLeft.offset).toEqual([0, -4]);
expect(placements.topRight.points).toEqual(['br', 'tr']);
expect(placements.topRight.offset).toEqual([0, -4]);
// the #36963 delta: no horizontal adjustment, vertical still allowed
Object.values(placements).forEach(placement => {
expect(placement.overflow.adjustX).toBe(false);
expect(placement.overflow.adjustY).toBe(true);
});
});

View File

@@ -16,13 +16,54 @@
* specific language governing permissions and limitations
* under the License.
*/
import { LabeledValue as AntdLabeledValue } from 'antd/es/select';
import {
LabeledValue as AntdLabeledValue,
SelectProps as AntdSelectProps,
} from 'antd/es/select';
import { t } from '@apache-superset/core/translation';
import { rankedSearchCompare } from '../../utils/rankedSearchCompare';
import { RawValue, SelectProps } from './types';
import { RawValue } from './types';
export const MAX_TAG_COUNT = 4;
const PLACEMENT_SHARED_CONFIG = {
overflow: { adjustX: false, adjustY: true, shiftY: true },
htmlRegion: 'visible' as const,
dynamicInset: true,
};
/**
* antd 6's default Select popup placements with horizontal viewport
* adjustment disabled, preserving the dropdown-positioning fix from
* #36963. antd 6 removed the `dropdownAlign` prop that fix was expressed
* through; `builtinPlacements` is its supported replacement, but it
* replaces the entire placement map, so all four entries are provided
* (mirroring antd's own defaults except for `adjustX`).
*/
export const DROPDOWN_BUILTIN_PLACEMENTS: AntdSelectProps['builtinPlacements'] =
{
bottomLeft: {
...PLACEMENT_SHARED_CONFIG,
points: ['tl', 'bl'],
offset: [0, 4],
},
bottomRight: {
...PLACEMENT_SHARED_CONFIG,
points: ['tr', 'br'],
offset: [0, 4],
},
topLeft: {
...PLACEMENT_SHARED_CONFIG,
points: ['bl', 'tl'],
offset: [0, -4],
},
topRight: {
...PLACEMENT_SHARED_CONFIG,
points: ['br', 'tr'],
offset: [0, -4],
},
};
export const TOKEN_SEPARATORS = [',', '\r\n', '\n', '\t', ';'];
export const EMPTY_OPTIONS = [];
@@ -33,12 +74,6 @@ export const SELECT_ALL_VALUE: RawValue = t('Select All');
export const VIRTUAL_THRESHOLD = 20;
export const DROPDOWN_ALIGN_BOTTOM: SelectProps['dropdownAlign'] = {
points: ['tl', 'bl'],
offset: [0, 4],
overflow: { adjustX: 0, adjustY: 1 },
};
export const SELECT_ALL_OPTION = {
value: SELECT_ALL_VALUE,
label: String(SELECT_ALL_VALUE),

View File

@@ -54,11 +54,13 @@ export const StyledSelect = styled(Select, {
flex: ${headerPosition === 'left' ? 1 : 0};
line-height: ${theme.sizeXL}px;
&& .ant-select-selection-search {
&& .ant-select-input {
left: 0px;
}
&& .ant-select-selection-item, .ant-select-selection-placeholder {
&& .ant-select-content,
&& .ant-select-selection-item,
&& .ant-select-placeholder {
max-height: ${theme.sizeXL}px;
}
.ant-select-selection-item::after {
@@ -68,16 +70,16 @@ export const StyledSelect = styled(Select, {
${
oneLine &&
`
.ant-select-selection-overflow {
.ant-select-content {
flex-wrap: nowrap;
}
.ant-select-selection-overflow-item:not(.ant-select-selection-overflow-item-rest):not(.ant-select-selection-overflow-item-suffix) {
.ant-select-content-item:not(.ant-select-content-item-rest):not(.ant-select-content-item-suffix) {
flex-shrink: 1;
min-width: ${theme.sizeUnit * 13}px;
}
.ant-select-selection-overflow-item-suffix {
.ant-select-content-item-suffix {
flex: unset;
min-width: 0px;
}

View File

@@ -71,12 +71,24 @@ export type AntdExposedProps = Pick<
| 'virtual'
| 'getPopupContainer'
| 'menuItemSelectedIcon'
| 'dropdownAlign'
>;
export type SelectOptionsType = Exclude<AntdProps['options'], undefined>;
export interface BaseSelectProps extends AntdExposedProps {
/**
* Whether the select is searchable. antd 6 also accepts a `SearchConfig`
* object here, but Superset's Select manages search behavior itself
* (filtering, sorting, "create option" handling), so only the boolean
* form is supported — an object would be silently discarded.
*/
showSearch?: boolean;
/**
* Separators used to tokenize pasted text into multiple values.
* antd 6 also accepts a function form, but Superset's paste handling
* only supports the array form.
*/
tokenSeparators?: string[];
/**
* Optional CSS class name to apply to the select container
*/

View File

@@ -99,9 +99,12 @@ test('Body should be visible', () => {
test('Body content should be blurred loading', () => {
render(<TableCollection {...defaultProps} loading />);
expect(screen.getByTestId('listview-table').parentNode).toHaveClass(
'ant-spin-blur',
);
// antd v6 removed the `ant-spin-blur` class. The body content is now dimmed
// via CSS applied to `.ant-spin-container` while its wrapping `.ant-spin`
// carries the `.ant-spin-spinning` class.
const container = screen.getByTestId('listview-table').parentNode;
expect(container).toHaveClass('ant-spin-container');
expect(container?.parentNode).toHaveClass('ant-spin-spinning');
});
test('Should the loading-indicator be visible during loading', () => {

View File

@@ -48,7 +48,7 @@ describe('Tabs', () => {
expect(getByText('Tab 3')).toBeInTheDocument();
const activeTabContent = container.querySelector(
'.ant-tabs-tabpane-active',
'.ant-tabs-content-active',
);
expect(activeTabContent).toBeDefined();
@@ -61,7 +61,7 @@ describe('Tabs', () => {
const { container } = render(<Tabs items={defaultItems} />);
const tabsElement = container.querySelector('.ant-tabs');
const tabsNav = container.querySelector('.ant-tabs-nav');
const tabsContent = container.querySelector('.ant-tabs-content-holder');
const tabsContent = container.querySelector('.ant-tabs-body-holder');
expect(tabsElement).toBeDefined();
expect(tabsNav).toBeDefined();
@@ -213,7 +213,7 @@ describe('Tabs', () => {
expect(getByText('Legacy Tab 2')).toBeInTheDocument();
const activeTabContent = container.querySelector(
'.ant-tabs-tabpane-active [data-testid="legacy-content-1"]',
'.ant-tabs-content-active [data-testid="legacy-content-1"]',
);
expect(activeTabContent).toBeDefined();
@@ -311,9 +311,9 @@ test('fullHeight prop renders component hierarchy correctly', () => {
const { container } = render(<Tabs items={defaultItems} fullHeight />);
const tabsElement = container.querySelector('.ant-tabs');
const contentHolder = container.querySelector('.ant-tabs-content-holder');
const content = container.querySelector('.ant-tabs-content');
const tabPane = container.querySelector('.ant-tabs-tabpane');
const contentHolder = container.querySelector('.ant-tabs-body-holder');
const content = container.querySelector('.ant-tabs-body');
const tabPane = container.querySelector('.ant-tabs-content');
expect(tabsElement).toBeInTheDocument();
expect(contentHolder).toBeInTheDocument();
@@ -343,9 +343,7 @@ test('fullHeight prop maintains structure when content updates', () => {
rerender(<Tabs items={newItems} fullHeight />);
const updatedTabsElement = container.querySelector('.ant-tabs');
const updatedContentHolder = container.querySelector(
'.ant-tabs-content-holder',
);
const updatedContentHolder = container.querySelector('.ant-tabs-body-holder');
expect(updatedTabsElement).toBeInTheDocument();
expect(updatedContentHolder).toBeInTheDocument();
@@ -359,7 +357,7 @@ test('fullHeight prop works with allowOverflow to handle tall content', () => {
const tabsElement = container.querySelector('.ant-tabs') as HTMLElement;
const contentHolder = container.querySelector(
'.ant-tabs-content-holder',
'.ant-tabs-body-holder',
) as HTMLElement;
expect(tabsElement).toBeInTheDocument();

View File

@@ -48,7 +48,7 @@ const StyledTabs = ({
overflow: ${allowOverflow ? 'visible' : 'hidden'};
${fullHeight && 'height: 100%;'}
.ant-tabs-content-holder {
.ant-tabs-body-holder {
overflow: ${allowOverflow ? 'visible' : 'auto'};
${fullHeight && 'height: 100%;'}
${
@@ -57,10 +57,10 @@ const StyledTabs = ({
}
${contentPadding}
}
.ant-tabs-content {
.ant-tabs-body {
${fullHeight && 'height: 100%;'}
}
.ant-tabs-tabpane {
.ant-tabs-content {
${fullHeight && 'height: 100%;'}
${contentStyle}
}
@@ -112,7 +112,7 @@ const Tabs = Object.assign(StyledTabs, {
const StyledEditableTabs = styled(StyledTabs)`
${({ theme, contentStyle }) => `
.ant-tabs-content-holder {
.ant-tabs-body-holder {
background: ${theme.colorBgContainer};
${contentStyle}
}

View File

@@ -67,7 +67,7 @@ function findMatchingTimezone(
export default function TimezoneSelector({
onTimezoneChange,
timezone,
minWidth = MIN_SELECT_WIDTH,
minWidth: _minWidth = MIN_SELECT_WIDTH,
placeholder,
...rest
}: TimezoneSelectorProps) {

View File

@@ -78,7 +78,7 @@ InteractiveTooltip.argTypes = {
control: { type: 'color' },
description: 'Custom background color for the tooltip.',
},
onVisibleChange: { action: 'onVisibleChange' },
onOpenChange: { action: 'onOpenChange' },
};
InteractiveTooltip.parameters = {

View File

@@ -27,7 +27,8 @@ export const Tooltip = forwardRef<TooltipRef, TooltipProps>(
<AntdTooltip
ref={ref}
styles={{
body: { overflow: 'hidden', textOverflow: 'ellipsis' },
// antd v6 renamed the inner content slot `body` -> `container`
container: { overflow: 'hidden', textOverflow: 'ellipsis' },
root: overlayStyle ?? {},
}}
{...props}

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { render, screen, userEvent, waitFor } from '@superset-ui/core/spec';
import { render, screen, userEvent } from '@superset-ui/core/spec';
import TooltipParagraph from '.';
test('starts hidden with default props', () => {
@@ -42,30 +42,11 @@ test('not render on hover when not truncated', async () => {
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
});
test('render on hover when truncated', async () => {
render(
<div style={{ width: '200px' }}>
<TooltipParagraph>
<span data-test="test-text">This is too long and should truncate.</span>
</TooltipParagraph>
</div>,
);
// Get the div with the ellipsis class to verify it's truncated
const ellipsisElement = screen
.getByTestId('test-text')
.closest('.ant-typography-ellipsis');
expect(ellipsisElement).toBeInTheDocument();
// Hover over the text
await userEvent.hover(screen.getByTestId('test-text'));
// In Ant Design v5, we can check if the aria-describedby attribute is present
// which indicates the tooltip functionality is active
await waitFor(() => {
const element = screen
.getByTestId('test-text')
.closest('[aria-describedby]');
expect(element).toHaveAttribute('aria-describedby');
});
});
// NOTE: there is intentionally no "renders tooltip when truncated" test here.
// The tooltip only activates once antd's Typography reports the text as
// truncated via its `onEllipsis` callback, which depends on real layout
// measurement (offset/scroll widths, ResizeObserver). jsdom performs no layout,
// so truncation is never detected and the tooltip never opens. Ant Design v5 set
// `aria-describedby` on the trigger regardless, which let this case be asserted;
// v6 only wires it once the tooltip has content, so the truncated branch is no
// longer observable in jsdom. It is covered by visual / end-to-end testing.

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