Compare commits

...

69 Commits

Author SHA1 Message Date
Elizabeth Thompson
7c00f5567f fix(filters): preserve backend metric-based sorting in select filters (#35130)
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-13 13:34:54 +03:00
Elizabeth Thompson
76c5b40c13 fix(embedded): fix template context for embedded views
- Use render_app_template() in EmbeddedView to provide proper template context
- Move tokens variable definition before its usage in spa.html template
- Fixes 'tokens is undefined' error in embedded dashboard views

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-12 18:03:23 -07:00
Elizabeth Thompson
9617b8b392 fix(modals): make Modal.confirm dialogs respect theme mode
- Add App wrapper to SupersetThemeProvider to enable theme inheritance for imperative modals
- Modal.confirm dialogs now properly inherit dark/light theme from ConfigProvider
- Simplifies theming approach by leveraging antd's built-in App context

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-12 17:56:32 -07:00
Elizabeth Thompson
a7b180d8dd fix(filters): remove filter bar gap, fix loading screen theming, fix filter sorting, and fix modal theming
- Remove top padding from horizontal filter bar to eliminate visual gap
- Set themed background color on loading screen to respect dark mode
- Remove fallback "Loading..." text to prevent Times New Roman display
- Use colorBgBase theme token for proper light/dark mode support
- Fix filter value sorting to preserve backend metric-based order when sortMetric is specified
- Make Modal.confirm theme-aware to respect current light/dark mode

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-12 17:18:53 -07:00
Gabriel Torres Ruiz
36daa2dc3f fix(ListView): implement AntD pagination for ListView component (#35057) 2025-09-12 14:15:05 -07:00
Gabriel Torres Ruiz
7fd5a7668b fix(charts): fix legend theming and hollow symbols in dark mode (#35123) 2025-09-12 23:33:29 +03:00
Mehmet Salih Yavuz
95333e34b1 fix(theming): Lighter text colors on dark mode (#35114) 2025-09-12 15:52:54 +03:00
Daniel Vaz Gaspar
a9fb853e3e fix: Bump FAB to 5.X (#33055)
Co-authored-by: Joe Li <joe@preset.io>
2025-09-12 09:21:37 +01:00
Rafael Benitez
dea9068647 fix(DashboardEditor): CSS template selector UI in dashboard properties modal restored (#35106) 2025-09-11 15:34:16 -07:00
Elizabeth Thompson
3416bd1479 chore: add codeowners (#35107) 2025-09-11 14:07:21 -07:00
Michael S. Molina
e729b2dbb4 fix: SQL Lab tab events (#35105) 2025-09-11 17:53:51 -03:00
SBIN2010
06261f262b fix: page size options 'all' correct in table and remove PAGE_SIZE_OPTIONS in handlebars (#35095) 2025-09-11 22:46:48 +03:00
SBIN2010
454ed1883f feat(BoxPlot): add chart data zoom (#35097) 2025-09-11 21:29:02 +03:00
Priyanshu Kumar
b42060c880 fix(pie): fixes pie chart other click error (#35086) 2025-09-11 14:19:52 +03:00
Gabriel Torres Ruiz
7bf16d805d fix(theming): replace error color with bolt icon for local themes (#35090) 2025-09-11 14:18:29 +03:00
Rafael Benitez
529adebe1b fix(templates): Restores templates files accidentally removed (#35094) 2025-09-11 10:31:29 +02:00
Devanjan Banerjee
eb4351af83 feat(Timeseries & MixedTimeseries): Force selected timegrain on timeseries intervals when the x-axis is of timestamp type (#34595) 2025-09-10 20:27:23 -07:00
Rafael Benitez
5a2411fa64 fix(settingsMenu): Version (#35096) 2025-09-10 15:08:05 -07:00
JUST.in DO IT
078c1701f4 feat(sqllab extension): core query interface api (#35077) 2025-09-10 13:17:09 -07:00
LisaHusband
a7d349a5c6 fix(drill-to-detail): ensure axis label filters map to original column names (#34694)
Co-authored-by: bito-code-review[bot] <188872107+bito-code-review[bot]@users.noreply.github.com>
2025-09-10 18:18:37 +03:00
Mehmet Salih Yavuz
7a20a65a4d feat(embedded): Change function signature of setupExtensions (#35062)
Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
2025-09-10 13:06:00 +03:00
Mehmet Salih Yavuz
912ed2ba80 fix(timeshifts): Add missing feature flag to enum (#35072) 2025-09-09 20:39:03 +03:00
dependabot[bot]
fedb3ca941 chore(deps-dev): bump @typescript-eslint/parser from 8.40.0 to 8.41.0 in /docs (#34934)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 10:33:33 -07:00
Renat
42b15b6840 fix(translation): order incompatible placeholders, translated placeholders, semantic changes (#34722)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-09 10:24:17 -07:00
Gabriel Torres Ruiz
2f64343186 feat(dataset): create usage tab for dataset (#34707) 2025-09-09 10:23:16 -07:00
Nicolas
65376c7baf fix(Table Chart): render null dates properly (#34558) 2025-09-09 10:22:54 -07:00
Michael S. Molina
4c2b27e7f0 fix: Change database event in core (#35071) 2025-09-09 11:50:49 -03:00
Amin Ghadersohi
15e4e8df94 fix(utils): Suppress pandas date parsing warnings in normalize_dttm_col (#35042) 2025-09-08 18:17:33 -07:00
SBIN2010
c5f220a9ff fix(table): table search input placeholder (#35064) 2025-09-09 03:55:39 +03:00
Avindra Goolcharan
b05def1a8a fix(superset-embedded-sdk/release-if-necessary): remove axios (#35038) 2025-09-08 17:47:31 -07:00
Ganesh-Babu-GB
da7f6efea8 docs(typos): correct spelling mistakes (#35043)
Co-authored-by: ganesh <dev.ganeshbabu.in@gmail.com>
2025-09-08 11:12:55 -07:00
Luiz Otavio
1c2b9db4f0 fix: Upload CSV as Dataset (#34763) 2025-09-08 11:48:23 -03:00
Gabriel Torres Ruiz
0fce5ecfa5 fix(dashboard): normalize spacings and background colors (#35001) 2025-09-05 19:13:42 -07:00
Elizabeth Thompson
385471c34d fix(utils): ensure webdriver timeout compatibility with urllib3 2.x (#34440) 2025-09-05 16:25:15 -07:00
Rafael Benitez
bef1f4d045 fix(theming): Icons in ExecutionLogList and Country map chart tooltip theme consistency (#34828) 2025-09-05 15:02:53 -07:00
SBIN2010
5a3182ce21 fix: mixed timeseries chart add legend margin (#35036) 2025-09-05 14:44:14 -07:00
catpineapple
9efb80dbf4 fix(tests): one of integration test in TestSqlaTableModel does not support MySQL "concat" (#35007)
Co-authored-by: Mehmet Salih Yavuz <salih.yavuz@proton.me>
2025-09-05 21:11:38 +03:00
dependabot[bot]
a20b236809 chore(deps): bump sha.js from 2.4.11 to 2.4.12 in /docs (#34797)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 10:30:19 -07:00
Beto Dealmeida
4e969d19d1 feat: allow create metric and add to folder in single request (#34993) 2025-09-05 13:28:45 -04:00
dependabot[bot]
876257fb94 chore(deps): bump ts-loader from 9.5.2 to 9.5.4 in /docs (#34956)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 10:28:17 -07:00
dependabot[bot]
472e599f91 chore(deps): bump @rjsf/validator-ajv8 from 5.24.12 to 5.24.13 in /superset-frontend (#34953)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 10:27:35 -07:00
dependabot[bot]
d826e90395 chore(deps-dev): bump @typescript-eslint/parser from 8.33.0 to 8.41.0 in /superset-websocket (#34959)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 10:24:21 -07:00
Damian Pendrak
c65cb284e6 fix(chart): change "No query." to "Query cannot be loaded" in Multi Layer Deck.gl Chart (#34973) 2025-09-05 10:22:45 -07:00
JUST.in DO IT
bc54b7970a fix(echarts): rename time series shifted for isTimeComparisonValue (#35022) 2025-09-05 08:39:46 -07:00
Vitor Avila
ce74ae095d feat: Use dashboard name for screenshot download (#34988) 2025-09-05 02:16:45 -03:00
SBIN2010
9424538bb1 feat: add sort legend to legend section (#34911) 2025-09-04 16:41:47 -07:00
SBIN2010
031fb4b5a8 fix: display legend mixed timeseries chart (#35005) 2025-09-04 16:39:57 -07:00
Evan Rusackas
7fb7ac8bef fix(sql): Add Impala dialect support to sqlglot parser (#34662)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Joe Li <joe@preset.io>
2025-09-04 11:07:09 -07:00
Mehmet Salih Yavuz
569a7b33a5 fix(theming): more visual bugs (#34987) 2025-09-04 20:44:07 +03:00
Mehmet Salih Yavuz
59df0d6f15 fix(RoleListEditModal): display user's other properties in table (#35017) 2025-09-04 20:43:59 +03:00
Michael S. Molina
2e4ccffc11 fix: Add TypeScript declaration file generation to @apache-superset/core package (#35002) 2025-09-04 12:59:43 -03:00
catpineapple
2e51d02806 fix: doris genericDataType modify (#35011) 2025-09-04 08:21:15 -07:00
sha174n
8406a827dd fix(deps): expand pyarrow version range to <19 (#34870) 2025-09-03 22:14:19 -07:00
Joe Li
ea0a77daaf fix(tests): resolve AlertReportModal checkmark test failures (#34995) 2025-09-03 10:44:20 -07:00
dependabot[bot]
e5e3ddb24e chore(deps): bump codecov/codecov-action from 3 to 5 (#34972)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:37:23 -07:00
dependabot[bot]
7320ad9a0a chore(deps): bump actions/first-interaction from 2 to 3 (#34970)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:36:51 -07:00
dependabot[bot]
3dbe593a4a chore(deps): bump actions/checkout from 4 to 5 (#34969)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:36:16 -07:00
dependabot[bot]
61f359d565 chore(deps): bump actions/setup-java from 4 to 5 (#34968)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:35:13 -07:00
dependabot[bot]
e77ff267a1 chore(deps): bump luxon from 3.6.1 to 3.7.1 in /superset-frontend (#34967)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:33:55 -07:00
dependabot[bot]
c426723275 chore(deps): bump geostyler-data from 1.0.0 to 1.1.0 in /superset-frontend (#34966)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:33:35 -07:00
dependabot[bot]
d2a1d86561 chore(deps): bump actions/download-artifact from 4 to 5 (#34963)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:32:25 -07:00
dependabot[bot]
0cd0b37983 chore(deps): bump @rjsf/core from 5.24.1 to 5.24.13 in /superset-frontend (#34950)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:27:35 -07:00
dependabot[bot]
a6b4ff9847 chore(deps): bump react-syntax-highlighter from 15.6.1 to 15.6.6 in /superset-frontend (#34949)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:27:01 -07:00
dependabot[bot]
a81282adeb chore(deps-dev): bump ts-jest from 29.3.1 to 29.4.1 in /superset-websocket (#34947)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:26:38 -07:00
dependabot[bot]
3ba3c09c47 chore(deps-dev): bump @types/jsonwebtoken from 9.0.9 to 9.0.10 in /superset-websocket (#34946)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:26:19 -07:00
dependabot[bot]
3081c7fb62 chore(deps-dev): bump fork-ts-checker-webpack-plugin from 9.0.2 to 9.1.0 in /superset-frontend (#34942)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:25:19 -07:00
dependabot[bot]
fa5b0d7281 chore(deps-dev): bump cross-env from 7.0.3 to 10.0.0 in /superset-frontend (#34941)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:24:59 -07:00
dependabot[bot]
1444ef36b9 chore(deps-dev): bump @babel/core from 7.27.7 to 7.28.3 in /superset-frontend (#34940)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:24:29 -07:00
dependabot[bot]
15d2f22eb4 chore(deps): bump react-virtualized-auto-sizer and @types/react-virtualized-auto-sizer in /superset-frontend (#34938)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 10:23:39 -07:00
294 changed files with 8441 additions and 4941 deletions

14
.github/CODEOWNERS vendored
View File

@@ -33,10 +33,10 @@
# Notify PMC members of changes to extension-related files
/superset-core/ @michael-s-molina @villebro
/superset-extensions-cli/ @michael-s-molina @villebro
/superset/core/ @michael-s-molina @villebro
/superset/extensions/ @michael-s-molina @villebro
/superset-frontend/src/packages/superset-core/ @michael-s-molina @villebro
/superset-frontend/src/core/ @michael-s-molina @villebro
/superset-frontend/src/extensions/ @michael-s-molina @villebro
/superset-core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset-extensions-cli/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset/core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset/extensions/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset-frontend/src/packages/superset-core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset-frontend/src/core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset-frontend/src/extensions/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje

View File

@@ -32,7 +32,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: true
ref: master

View File

@@ -31,7 +31,7 @@ jobs:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
if: steps.check_queued.outputs.count >= 20
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Cancel duplicate workflow runs
if: steps.check_queued.outputs.count >= 20

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -25,7 +25,7 @@ jobs:
pull-requests: write
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Check and notify
uses: actions/github-script@v7
with:

View File

@@ -71,7 +71,7 @@ jobs:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 1

View File

@@ -31,7 +31,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Check for file changes
id: check

View File

@@ -27,7 +27,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout Repository"
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: "Dependency Review"
uses: actions/dependency-review-action@v4
continue-on-error: true
@@ -53,7 +53,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: "Checkout Repository"
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Python
uses: ./.github/actions/setup-backend/

View File

@@ -42,7 +42,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
@@ -117,7 +117,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
- name: Check for file changes

View File

@@ -28,7 +28,7 @@ jobs:
run:
working-directory: superset-embedded-sdk
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version-file: './superset-embedded-sdk/.nvmrc'

View File

@@ -18,7 +18,7 @@ jobs:
run:
working-directory: superset-embedded-sdk
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version-file: './superset-embedded-sdk/.nvmrc'

View File

@@ -160,7 +160,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ needs.ephemeral-env-label.outputs.sha }} : ${{steps.get-sha.outputs.sha}} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: ${{ needs.ephemeral-env-label.outputs.sha }}
persist-credentials: false
@@ -220,7 +220,7 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
persist-credentials: false

View File

@@ -27,12 +27,12 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "11"

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Set up Node.js
uses: actions/setup-node@v4

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -15,12 +15,12 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '11'

View File

@@ -16,7 +16,7 @@ jobs:
pull-requests: write
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -21,7 +21,7 @@ jobs:
python-version: ["current", "previous", "next"]
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -27,7 +27,7 @@ jobs:
pull-requests: write
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -26,7 +26,7 @@ jobs:
name: Bump version and publish package(s)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
# pulls all commits (needed for lerna / semantic release to correctly version)
fetch-depth: 0

View File

@@ -147,7 +147,7 @@ jobs:
- name: Checkout PR code (only if build needed)
if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.build_needed == 'true'
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: ${{ steps.check.outputs.target_sha }}
persist-credentials: false

View File

@@ -37,7 +37,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -51,7 +51,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive
@@ -41,7 +41,7 @@ jobs:
node-version-file: './docs/.nvmrc'
- name: Setup Python
uses: ./.github/actions/setup-backend/
- uses: actions/setup-java@v4
- uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: '21'

View File

@@ -18,7 +18,7 @@ jobs:
name: Link Checking
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
# Do not bump this linkinator-action version without opening
# an ASF Infra ticket to allow the new version first!
- uses: JustinBeckwith/linkinator-action@v1.11.0
@@ -56,7 +56,7 @@ jobs:
working-directory: docs
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -69,21 +69,21 @@ jobs:
# Conditional checkout based on context
- name: Checkout for push or pull_request event
if: github.event_name == 'push' || github.event_name == 'pull_request'
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Checkout using ref (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
ref: ${{ github.event.inputs.ref }}
submodules: recursive
- name: Checkout using PR ID (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge

View File

@@ -24,7 +24,7 @@ jobs:
working-directory: superset-extensions-cli
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive
@@ -49,7 +49,7 @@ jobs:
- name: Upload coverage reports to Codecov
if: steps.check.outputs.superset-extensions-cli
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v5
with:
file: ./coverage.xml
flags: superset-extensions-cli

View File

@@ -23,7 +23,7 @@ jobs:
should-run: ${{ steps.check.outputs.frontend }}
steps:
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
fetch-depth: 0
@@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: docker-image
@@ -101,7 +101,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Coverage Artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
pattern: coverage-artifacts-*
path: coverage/
@@ -127,7 +127,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: docker-image
@@ -151,7 +151,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: docker-image

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -29,7 +29,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: ${{ inputs.ref || github.ref_name }}
persist-credentials: true

View File

@@ -41,7 +41,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive
@@ -99,7 +99,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive
@@ -152,7 +152,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -48,7 +48,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive
@@ -108,7 +108,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -24,7 +24,7 @@ jobs:
PYTHONPATH: ${{ github.workspace }}
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive
@@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
submodules: recursive

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
- name: Install dependencies

View File

@@ -38,7 +38,7 @@ jobs:
});
- name: "Checkout ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false

View File

@@ -47,7 +47,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -107,7 +107,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0

View File

@@ -27,7 +27,7 @@ jobs:
name: Generate Reports
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Set up Node.js
uses: actions/setup-node@v4

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: Welcome Message
uses: actions/first-interaction@v2
uses: actions/first-interaction@v3
continue-on-error: true
with:
repo-token: ${{ github.token }}

View File

@@ -23,6 +23,8 @@ This file documents any backwards-incompatible changes in Superset and
assists people when migrating to a new version.
## Next
- [33055](https://github.com/apache/superset/pull/33055): Upgrades Flask-AppBuilder to 5.0.0. The AUTH_OID authentication type has been deprecated and is no longer available as an option in Flask-AppBuilder. OpenID (OID) is considered a deprecated authentication protocol - if you are using AUTH_OID, you will need to migrate to an alternative authentication method such as OAuth, LDAP, or database authentication before upgrading.
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
- [34782](https://github.com/apache/superset/pull/34782): Dataset exports now include the dataset ID in their file name (similar to charts and dashboards). If managing assets as code, make sure to rename existing dataset YAMLs to include the ID (and avoid duplicated files).
- [34536](https://github.com/apache/superset/pull/34536): The `ENVIRONMENT_TAG_CONFIG` color values have changed to support only Ant Design semantic colors. Update your `superset_config.py`:

View File

@@ -363,110 +363,6 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
]
```
### Keycloak-Specific Configuration using Flask-OIDC
If you are using Keycloak as OpenID Connect 1.0 Provider, the above configuration based on [`Authlib`](https://authlib.org/) might not work. In this case using [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is a viable option.
Make sure the pip package [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is installed on the webserver. This was successfully tested using version 2.2.0. This package requires [`Flask-OpenID`](https://pypi.org/project/Flask-OpenID/) as a dependency.
The following code defines a new security manager. Add it to a new file named `keycloak_security_manager.py`, placed in the same directory as your `superset_config.py` file.
```python
from flask_appbuilder.security.manager import AUTH_OID
from superset.security import SupersetSecurityManager
from flask_oidc import OpenIDConnect
from flask_appbuilder.security.views import AuthOIDView
from flask_login import login_user
from urllib.parse import quote
from flask_appbuilder.views import ModelView, SimpleFormView, expose
from flask import (
redirect,
request
)
import logging
class OIDCSecurityManager(SupersetSecurityManager):
def __init__(self, appbuilder):
super(OIDCSecurityManager, self).__init__(appbuilder)
if self.auth_type == AUTH_OID:
self.oid = OpenIDConnect(self.appbuilder.get_app)
self.authoidview = AuthOIDCView
class AuthOIDCView(AuthOIDView):
@expose('/login/', methods=['GET', 'POST'])
def login(self, flag=True):
sm = self.appbuilder.sm
oidc = sm.oid
@self.appbuilder.sm.oid.require_login
def handle_login():
user = sm.auth_user_oid(oidc.user_getfield('email'))
if user is None:
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email'])
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'),
info.get('email'), sm.find_role('Gamma'))
login_user(user, remember=False)
return redirect(self.appbuilder.get_url_for_index)
return handle_login()
@expose('/logout/', methods=['GET', 'POST'])
def logout(self):
oidc = self.appbuilder.sm.oid
oidc.logout()
super(AuthOIDCView, self).logout()
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
return redirect(
oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))
```
Then add to your `superset_config.py` file:
```python
from keycloak_security_manager import OIDCSecurityManager
from flask_appbuilder.security.manager import AUTH_OID, AUTH_REMOTE_USER, AUTH_DB, AUTH_LDAP, AUTH_OAUTH
import os
AUTH_TYPE = AUTH_OID
SECRET_KEY: 'SomethingNotEntirelySecret'
OIDC_CLIENT_SECRETS = '/path/to/client_secret.json'
OIDC_ID_TOKEN_COOKIE_SECURE = False
OIDC_OPENID_REALM: '<myRealm>'
OIDC_INTROSPECTION_AUTH_METHOD: 'client_secret_post'
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
# Will allow user self registration, allowing to create Flask users from Authorized User
AUTH_USER_REGISTRATION = True
# The default user self registration role
AUTH_USER_REGISTRATION_ROLE = 'Public'
```
Store your client-specific OpenID information in a file called `client_secret.json`. Create this file in the same directory as `superset_config.py`:
```json
{
"<myOpenIDProvider>": {
"issuer": "https://<myKeycloakDomain>/realms/<myRealm>",
"auth_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/auth",
"client_id": "https://<myKeycloakDomain>",
"client_secret": "<myClientSecret>",
"redirect_uris": [
"https://<SupersetWebserver>/oauth-authorized/<myOpenIDProvider>"
],
"userinfo_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/userinfo",
"token_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token",
"token_introspection_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token/introspect"
}
}
```
## LDAP Authentication
FAB supports authenticating user credentials against an LDAP server.

View File

@@ -620,10 +620,10 @@ See [how tos](/docs/contributing/howtos#linting)
:::tip
`act` compatibility of Superset's GHAs is not fully tested. Running `act` locally may or may not
work for different actions, and may require fine tunning and local secret-handling.
work for different actions, and may require fine tuning and local secret-handling.
For those more intricate GHAs that are tricky to run locally, we recommend iterating
directly on GHA's infrastructure, by pushing directly on a branch and monitoring GHA logs.
For more targetted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
For more targeted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
:::
For automation and CI/CD, Superset makes extensive use of GitHub Actions (GHA). You

View File

@@ -232,7 +232,7 @@ CYPRESS_CONFIG=true docker compose up --build
```
`docker compose` will get to work and expose a Cypress-ready Superset app.
This app uses a different database schema (`superset_cypress`) to keep it isolated from
your other dev environmen(s)t, a specific set of examples, and a set of configurations that
your other dev environment(s), a specific set of examples, and a set of configurations that
aligns with the expectations within the end-to-end tests. Also note that it's served on a
different port than the default port for the backend (`8088`).
@@ -627,7 +627,7 @@ feature flag to `true`, you can add the following line to the PR body/descriptio
FEATURE_TAGGING_SYSTEM=true
```
Simarly, it's possible to disable feature flags with:
Similarly, it's possible to disable feature flags with:
```
FEATURE_TAGGING_SYSTEM=false

View File

@@ -282,5 +282,5 @@ address.
When running `docker compose up`, docker will build what is required behind the scene, but
may use the docker cache if assets already exist. Running `docker compose build` prior to
`docker compose up` or the equivalent shortcut `docker compose up --build` ensures that your
docker images matche the definition in the repository. This should only apply to the main
docker images match the definition in the repository. This should only apply to the main
docker-compose.yml file (default) and not to the alternative methods defined above.

View File

@@ -65,7 +65,7 @@
"storybook": "^8.6.11",
"swagger-ui-react": "^5.27.1",
"tinycolor2": "^1.4.2",
"ts-loader": "^9.5.2"
"ts-loader": "^9.5.4"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.8.1",
@@ -73,7 +73,7 @@
"@eslint/js": "^9.32.0",
"@types/react": "^19.1.8",
"@typescript-eslint/eslint-plugin": "^8.37.0",
"@typescript-eslint/parser": "^8.37.0",
"@typescript-eslint/parser": "^8.42.0",
"eslint": "^9.34.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.3",

View File

@@ -363,110 +363,6 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
]
```
### Keycloak-Specific Configuration using Flask-OIDC
If you are using Keycloak as OpenID Connect 1.0 Provider, the above configuration based on [`Authlib`](https://authlib.org/) might not work. In this case using [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is a viable option.
Make sure the pip package [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is installed on the webserver. This was successfully tested using version 2.2.0. This package requires [`Flask-OpenID`](https://pypi.org/project/Flask-OpenID/) as a dependency.
The following code defines a new security manager. Add it to a new file named `keycloak_security_manager.py`, placed in the same directory as your `superset_config.py` file.
```python
from flask_appbuilder.security.manager import AUTH_OID
from superset.security import SupersetSecurityManager
from flask_oidc import OpenIDConnect
from flask_appbuilder.security.views import AuthOIDView
from flask_login import login_user
from urllib.parse import quote
from flask_appbuilder.views import ModelView, SimpleFormView, expose
from flask import (
redirect,
request
)
import logging
class OIDCSecurityManager(SupersetSecurityManager):
def __init__(self, appbuilder):
super(OIDCSecurityManager, self).__init__(appbuilder)
if self.auth_type == AUTH_OID:
self.oid = OpenIDConnect(self.appbuilder.get_app)
self.authoidview = AuthOIDCView
class AuthOIDCView(AuthOIDView):
@expose('/login/', methods=['GET', 'POST'])
def login(self, flag=True):
sm = self.appbuilder.sm
oidc = sm.oid
@self.appbuilder.sm.oid.require_login
def handle_login():
user = sm.auth_user_oid(oidc.user_getfield('email'))
if user is None:
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email'])
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'),
info.get('email'), sm.find_role('Gamma'))
login_user(user, remember=False)
return redirect(self.appbuilder.get_url_for_index)
return handle_login()
@expose('/logout/', methods=['GET', 'POST'])
def logout(self):
oidc = self.appbuilder.sm.oid
oidc.logout()
super(AuthOIDCView, self).logout()
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
return redirect(
oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))
```
Then add to your `superset_config.py` file:
```python
from keycloak_security_manager import OIDCSecurityManager
from flask_appbuilder.security.manager import AUTH_OID, AUTH_REMOTE_USER, AUTH_DB, AUTH_LDAP, AUTH_OAUTH
import os
AUTH_TYPE = AUTH_OID
SECRET_KEY: 'SomethingNotEntirelySecret'
OIDC_CLIENT_SECRETS = '/path/to/client_secret.json'
OIDC_ID_TOKEN_COOKIE_SECURE = False
OIDC_OPENID_REALM: '<myRealm>'
OIDC_INTROSPECTION_AUTH_METHOD: 'client_secret_post'
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
# Will allow user self registration, allowing to create Flask users from Authorized User
AUTH_USER_REGISTRATION = True
# The default user self registration role
AUTH_USER_REGISTRATION_ROLE = 'Public'
```
Store your client-specific OpenID information in a file called `client_secret.json`. Create this file in the same directory as `superset_config.py`:
```json
{
"<myOpenIDProvider>": {
"issuer": "https://<myKeycloakDomain>/realms/<myRealm>",
"auth_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/auth",
"client_id": "https://<myKeycloakDomain>",
"client_secret": "<myClientSecret>",
"redirect_uris": [
"https://<SupersetWebserver>/oauth-authorized/<myOpenIDProvider>"
],
"userinfo_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/userinfo",
"token_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token",
"token_introspection_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token/introspect"
}
}
```
## LDAP Authentication
FAB supports authenticating user credentials against an LDAP server.

View File

@@ -620,10 +620,10 @@ See [how tos](/docs/contributing/howtos#linting)
:::tip
`act` compatibility of Superset's GHAs is not fully tested. Running `act` locally may or may not
work for different actions, and may require fine tunning and local secret-handling.
work for different actions, and may require fine tuning and local secret-handling.
For those more intricate GHAs that are tricky to run locally, we recommend iterating
directly on GHA's infrastructure, by pushing directly on a branch and monitoring GHA logs.
For more targetted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
For more targeted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
:::
For automation and CI/CD, Superset makes extensive use of GitHub Actions (GHA). You

View File

@@ -232,7 +232,7 @@ CYPRESS_CONFIG=true docker compose up --build
```
`docker compose` will get to work and expose a Cypress-ready Superset app.
This app uses a different database schema (`superset_cypress`) to keep it isolated from
your other dev environmen(s)t, a specific set of examples, and a set of configurations that
your other dev environment(s), a specific set of examples, and a set of configurations that
aligns with the expectations within the end-to-end tests. Also note that it's served on a
different port than the default port for the backend (`8088`).
@@ -627,7 +627,7 @@ feature flag to `true`, you can add the following line to the PR body/descriptio
FEATURE_TAGGING_SYSTEM=true
```
Simarly, it's possible to disable feature flags with:
Similarly, it's possible to disable feature flags with:
```
FEATURE_TAGGING_SYSTEM=false

View File

@@ -282,5 +282,5 @@ address.
When running `docker compose up`, docker will build what is required behind the scene, but
may use the docker cache if assets already exist. Running `docker compose build` prior to
`docker compose up` or the equivalent shortcut `docker compose up --build` ensures that your
docker images matche the definition in the repository. This should only apply to the main
docker images match the definition in the repository. This should only apply to the main
docker-compose.yml file (default) and not to the alternative methods defined above.

View File

@@ -4102,7 +4102,7 @@
natural-compare "^1.4.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/parser@8.40.0", "@typescript-eslint/parser@^8.37.0":
"@typescript-eslint/parser@8.40.0":
version "8.40.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.40.0.tgz#1bc9f3701ced29540eb76ff2d95ce0d52ddc7e69"
integrity sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==
@@ -4113,6 +4113,17 @@
"@typescript-eslint/visitor-keys" "8.40.0"
debug "^4.3.4"
"@typescript-eslint/parser@^8.42.0":
version "8.42.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.42.0.tgz#20ea66f4867981fb5bb62cbe1454250fc4a440ab"
integrity sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==
dependencies:
"@typescript-eslint/scope-manager" "8.42.0"
"@typescript-eslint/types" "8.42.0"
"@typescript-eslint/typescript-estree" "8.42.0"
"@typescript-eslint/visitor-keys" "8.42.0"
debug "^4.3.4"
"@typescript-eslint/project-service@8.40.0":
version "8.40.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.40.0.tgz#1b7ba6079ff580c3215882fe75a43e5d3ed166b9"
@@ -4122,6 +4133,15 @@
"@typescript-eslint/types" "^8.40.0"
debug "^4.3.4"
"@typescript-eslint/project-service@8.42.0":
version "8.42.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.42.0.tgz#636eb3418b6c42c98554dce884943708bf41a583"
integrity sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.42.0"
"@typescript-eslint/types" "^8.42.0"
debug "^4.3.4"
"@typescript-eslint/scope-manager@8.40.0":
version "8.40.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz#2fbfcc8643340d8cd692267e61548b946190be8a"
@@ -4130,11 +4150,24 @@
"@typescript-eslint/types" "8.40.0"
"@typescript-eslint/visitor-keys" "8.40.0"
"@typescript-eslint/scope-manager@8.42.0":
version "8.42.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.42.0.tgz#36016757bc85b46ea42bae47b61f9421eddedde3"
integrity sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==
dependencies:
"@typescript-eslint/types" "8.42.0"
"@typescript-eslint/visitor-keys" "8.42.0"
"@typescript-eslint/tsconfig-utils@8.40.0", "@typescript-eslint/tsconfig-utils@^8.40.0":
version "8.40.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz#8e8fdb9b988854aedd04abdde3239c4bdd2d26e4"
integrity sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==
"@typescript-eslint/tsconfig-utils@8.42.0", "@typescript-eslint/tsconfig-utils@^8.42.0":
version "8.42.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.42.0.tgz#21a3e74396fd7443ff930bc41b27789ba7e9236e"
integrity sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==
"@typescript-eslint/type-utils@8.40.0":
version "8.40.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz#a7e4a1f0815dd0ba3e4eef945cc87193ca32c422"
@@ -4146,11 +4179,16 @@
debug "^4.3.4"
ts-api-utils "^2.1.0"
"@typescript-eslint/types@8.40.0", "@typescript-eslint/types@^8.40.0":
"@typescript-eslint/types@8.40.0":
version "8.40.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.40.0.tgz#0b580fdf643737aa5c01285314b5c6e9543846a9"
integrity sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==
"@typescript-eslint/types@8.42.0", "@typescript-eslint/types@^8.40.0", "@typescript-eslint/types@^8.42.0":
version "8.42.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.42.0.tgz#ae15c09cebda20473772902033328e87372db008"
integrity sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==
"@typescript-eslint/typescript-estree@8.40.0":
version "8.40.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz#295149440ce7da81c790a4e14e327599a3a1e5c9"
@@ -4167,6 +4205,22 @@
semver "^7.6.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/typescript-estree@8.42.0":
version "8.42.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.42.0.tgz#593c3af87d4462252c0d7239d1720b84a1b56864"
integrity sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==
dependencies:
"@typescript-eslint/project-service" "8.42.0"
"@typescript-eslint/tsconfig-utils" "8.42.0"
"@typescript-eslint/types" "8.42.0"
"@typescript-eslint/visitor-keys" "8.42.0"
debug "^4.3.4"
fast-glob "^3.3.2"
is-glob "^4.0.3"
minimatch "^9.0.4"
semver "^7.6.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/utils@8.40.0":
version "8.40.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.40.0.tgz#8d0c6430ed2f5dc350784bb0d8be514da1e54054"
@@ -4185,6 +4239,14 @@
"@typescript-eslint/types" "8.40.0"
eslint-visitor-keys "^4.2.1"
"@typescript-eslint/visitor-keys@8.42.0":
version "8.42.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.42.0.tgz#87c6caaa1ac307bc73a87c1fc469f88f0162f27e"
integrity sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==
dependencies:
"@typescript-eslint/types" "8.42.0"
eslint-visitor-keys "^4.2.1"
"@ungap/structured-clone@^1.0.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
@@ -13139,10 +13201,10 @@ ts-dedent@^2.0.0, ts-dedent@^2.2.0:
resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
ts-loader@^9.5.2:
version "9.5.2"
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.2.tgz#1f3d7f4bb709b487aaa260e8f19b301635d08020"
integrity sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==
ts-loader@^9.5.4:
version "9.5.4"
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.4.tgz#44b571165c10fb5a90744aa5b7e119233c4f4585"
integrity sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==
dependencies:
chalk "^4.1.0"
enhanced-resolve "^5.0.0"

View File

@@ -48,7 +48,7 @@ dependencies = [
"cryptography>=42.0.4, <45.0.0",
"deprecation>=2.1.0, <2.2.0",
"flask>=2.2.5, <3.0.0",
"flask-appbuilder>=4.8.1, <5.0.0",
"flask-appbuilder>=5.0.0,<6",
"flask-caching>=2.1.0, <3",
"flask-compress>=1.13, <2.0",
"flask-talisman>=1.0.0, <2.0",
@@ -88,7 +88,7 @@ dependencies = [
"python-dateutil",
"python-dotenv", # optional dependencies for Flask but required for Superset, see https://flask.palletsprojects.com/en/stable/installation/#optional-dependencies
"python-geohash",
"pyarrow>=16.1.0, <17", # before upgrading pyarrow, check that all db dependencies support this, see e.g. https://github.com/apache/superset/pull/34693
"pyarrow>=16.1.0, <19", # before upgrading pyarrow, check that all db dependencies support this, see e.g. https://github.com/apache/superset/pull/34693
"pyyaml>=6.0.0, <7.0.0",
"PyJWT>=2.4.0, <3.0",
"redis>=4.6.0, <5.0",

View File

@@ -114,11 +114,9 @@ flask==2.3.3
# flask-session
# flask-sqlalchemy
# flask-wtf
flask-appbuilder==4.8.1
# via
# apache-superset (pyproject.toml)
# apache-superset-core
flask-babel==2.0.0
flask-appbuilder==5.0.0
# via apache-superset (pyproject.toml)
flask-babel==3.1.0
# via flask-appbuilder
flask-caching==2.3.1
# via apache-superset (pyproject.toml)

View File

@@ -208,12 +208,11 @@ flask==2.3.3
# flask-sqlalchemy
# flask-testing
# flask-wtf
flask-appbuilder==4.8.1
flask-appbuilder==5.0.0
# via
# -c requirements/base-constraint.txt
# apache-superset
# apache-superset-core
flask-babel==2.0.0
flask-babel==3.1.0
# via
# -c requirements/base-constraint.txt
# flask-appbuilder

View File

@@ -42,7 +42,7 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"flask-appbuilder>=4.5.3, <5.0.0",
"flask-appbuilder>=5.0.0,<6",
]
[project.urls]

View File

@@ -19,7 +19,6 @@
"@babel/preset-typescript": "^7.24.7",
"@types/jest": "^29.5.12",
"@types/node": "^22.5.4",
"axios": "^1.7.7",
"babel-loader": "^9.1.3",
"jest": "^29.7.0",
"tscw-config": "^1.1.2",
@@ -3233,24 +3232,6 @@
"sprintf-js": "~1.0.2"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
},
"node_modules/axios": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
"dev": true,
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/babel-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
@@ -3644,19 +3625,6 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -3843,18 +3811,6 @@
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
"dev": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
@@ -4045,15 +4001,6 @@
"node": ">=0.10.0"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -4063,20 +4010,6 @@
"node": ">=8"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.19",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz",
@@ -4137,57 +4070,12 @@
"is-arrayish": "^0.2.1"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-module-lexer": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
"dev": true
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escalade": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
@@ -4628,42 +4516,6 @@
"node": ">=8"
}
},
"node_modules/follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dev": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fs-readdir-recursive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
@@ -4717,30 +4569,6 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-package-type": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
@@ -4750,19 +4578,6 @@
"node": ">=8.0.0"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@@ -4825,18 +4640,6 @@
"node": ">=4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -4865,45 +4668,6 @@
"node": ">=4"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -7092,15 +6856,6 @@
"tmpl": "1.0.5"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -7414,12 +7169,6 @@
"node": ">= 6"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true
},
"node_modules/punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@@ -10724,23 +10473,6 @@
"sprintf-js": "~1.0.2"
}
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
},
"axios": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
"dev": true,
"requires": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"babel-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
@@ -11023,16 +10755,6 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"requires": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
}
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -11158,15 +10880,6 @@
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
"dev": true
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"requires": {
"delayed-stream": "~1.0.0"
}
},
"commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
@@ -11299,29 +11012,12 @@
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"dev": true
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
},
"detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
"integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
"dev": true
},
"dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"requires": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
}
},
"electron-to-chromium": {
"version": "1.5.19",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz",
@@ -11365,45 +11061,12 @@
"is-arrayish": "^0.2.1"
}
},
"es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true
},
"es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true
},
"es-module-lexer": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
"dev": true
},
"es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"requires": {
"es-errors": "^1.3.0"
}
},
"es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"requires": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
}
},
"escalade": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
@@ -11712,25 +11375,6 @@
"path-exists": "^4.0.0"
}
},
"follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true
},
"form-data": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
}
},
"fs-readdir-recursive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
@@ -11768,40 +11412,12 @@
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"requires": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
}
},
"get-package-type": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
"dev": true
},
"get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"requires": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
}
},
"get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@@ -11844,12 +11460,6 @@
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true
},
"gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true
},
"graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -11871,30 +11481,6 @@
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true
},
"has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true
},
"has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"requires": {
"has-symbols": "^1.0.3"
}
},
"hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"requires": {
"function-bind": "^1.1.2"
}
},
"html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -13505,12 +13091,6 @@
"tmpl": "1.0.5"
}
},
"math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true
},
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -13742,12 +13322,6 @@
"sisteransi": "^1.0.5"
}
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",

View File

@@ -43,7 +43,6 @@
"@babel/preset-typescript": "^7.24.7",
"@types/jest": "^29.5.12",
"@types/node": "^22.5.4",
"axios": "^1.7.7",
"babel-loader": "^9.1.3",
"jest": "^29.7.0",
"tscw-config": "^1.1.2",

View File

@@ -18,7 +18,6 @@
*/
const { execSync } = require('child_process');
const axios = require('axios');
const { name, version } = require('./package.json');
function log(...args) {
@@ -36,9 +35,7 @@ function logError(...args) {
// npm commands output a bunch of garbage in the edge cases,
// and require sending semi-validated strings to the command line,
// so let's just use good old http.
const { status } = await axios.get(packageUrl, {
validateStatus: (status) => true // we literally just want the status so any status is valid
});
const { status } = await fetch(packageUrl);
if (status === 200) {
log('version already exists on npm, exiting');

View File

@@ -1,766 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// eslint-disable-next-line import/no-extraneous-dependencies
import { Interception } from 'cypress/types/net-stubbing';
import { waitForChartLoad } from 'cypress/utils';
import { SUPPORTED_CHARTS_DASHBOARD } from 'cypress/utils/urls';
import {
openTopLevelTab,
SUPPORTED_TIER1_CHARTS,
SUPPORTED_TIER2_CHARTS,
} from './utils';
import {
interceptExploreJson,
interceptV1ChartData,
interceptFormDataKey,
} from '../explore/utils';
const interceptDrillInfo = () => {
cy.intercept('GET', '**/api/v1/dataset/*/drill_info/*', {
statusCode: 200,
body: {
result: {
id: 1,
changed_on_humanized: '2 days ago',
created_on_humanized: 'a week ago',
table_name: 'birth_names',
changed_by: {
first_name: 'Admin',
last_name: 'User',
},
created_by: {
first_name: 'Admin',
last_name: 'User',
},
owners: [
{
first_name: 'Admin',
last_name: 'User',
},
],
columns: [
{
column_name: 'gender',
verbose_name: null,
},
{
column_name: 'state',
verbose_name: null,
},
{
column_name: 'name',
verbose_name: null,
},
{
column_name: 'ds',
verbose_name: null,
},
],
},
},
}).as('drillInfo');
};
const closeModal = () => {
cy.get('body').then($body => {
if ($body.find('[data-test="close-drill-by-modal"]').length) {
cy.getBySel('close-drill-by-modal').click({ force: true });
}
});
};
const openTableContextMenu = (
cellContent: string,
tableSelector = "[data-test-viz-type='table']",
) => {
cy.get(tableSelector).scrollIntoView();
cy.get(tableSelector).contains(cellContent).first().rightclick();
};
const drillBy = (targetDrillByColumn: string, isLegacy = false) => {
if (isLegacy) {
interceptExploreJson('legacyData');
} else {
interceptV1ChartData();
}
cy.get('.ant-dropdown:not(.ant-dropdown-hidden)', { timeout: 15000 })
.should('be.visible')
.find("[role='menu'] [role='menuitem']")
.contains(/^Drill by$/)
.trigger('mouseover', { force: true });
cy.get(
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
{ timeout: 15000 },
)
.should('be.visible')
.find('[role="menuitem"]')
.contains(new RegExp(`^${targetDrillByColumn}$`))
.click();
cy.get(
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
).trigger('mouseout', { clientX: 0, clientY: 0, force: true });
cy.get(
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
).should('not.exist');
if (isLegacy) {
return cy.wait('@legacyData');
}
return cy.wait('@v1Data');
};
const verifyExpectedFormData = (
interceptedRequest: Interception,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expectedFormData: Record<string, any>,
) => {
const actualFormData = interceptedRequest.request.body?.form_data;
Object.entries(expectedFormData).forEach(([key, val]) => {
expect(actualFormData?.[key]).to.eql(val);
});
};
const testEchart = (
vizType: string,
chartName: string,
drillClickCoordinates: [[number, number], [number, number]],
furtherDrillDimension = 'name',
) => {
cy.get(`[data-test-viz-type='${vizType}'] canvas`).then($canvas => {
// click 'boy'
cy.wrap($canvas).scrollIntoView();
cy.wrap($canvas).trigger(
'mouseover',
drillClickCoordinates[0][0],
drillClickCoordinates[0][1],
);
cy.wrap($canvas).rightclick(
drillClickCoordinates[0][0],
drillClickCoordinates[0][1],
);
drillBy('state').then(intercepted => {
verifyExpectedFormData(intercepted, {
groupby: ['state'],
adhoc_filters: [
{
clause: 'WHERE',
comparator: 'boy',
expressionType: 'SIMPLE',
operator: '==',
operatorId: 'EQUALS',
subject: 'gender',
},
],
});
});
cy.getBySel(`"Drill by: ${chartName}-modal"`).as('drillByModal');
cy.get('@drillByModal')
.find('.draggable-trigger')
.should('contain', chartName);
cy.get('@drillByModal')
.find('.ant-breadcrumb')
.should('be.visible')
.and('contain', 'gender (boy)')
.and('contain', '/')
.and('contain', 'state');
cy.get('@drillByModal')
.find('[data-test="drill-by-chart"]')
.should('be.visible');
// further drill
cy.get(`[data-test="drill-by-chart"] canvas`).then($canvas => {
// click 'other'
cy.wrap($canvas).scrollIntoView();
cy.wrap($canvas).trigger(
'mouseover',
drillClickCoordinates[1][0],
drillClickCoordinates[1][1],
);
cy.wrap($canvas).rightclick(
drillClickCoordinates[1][0],
drillClickCoordinates[1][1],
);
drillBy(furtherDrillDimension).then(intercepted => {
verifyExpectedFormData(intercepted, {
groupby: [furtherDrillDimension],
adhoc_filters: [
{
clause: 'WHERE',
comparator: 'boy',
expressionType: 'SIMPLE',
operator: '==',
operatorId: 'EQUALS',
subject: 'gender',
},
{
clause: 'WHERE',
comparator: 'other',
expressionType: 'SIMPLE',
operator: '==',
operatorId: 'EQUALS',
subject: 'state',
},
],
});
});
cy.get('@drillByModal')
.find('[data-test="drill-by-chart"]')
.should('be.visible');
// undo - back to drill by state
interceptV1ChartData('drillByUndo');
cy.get('@drillByModal')
.find('.ant-breadcrumb')
.should('be.visible')
.and('contain', 'gender (boy)')
.and('contain', '/')
.and('contain', 'state (other)')
.and('contain', furtherDrillDimension)
.contains('state (other)')
.click();
cy.wait('@drillByUndo').then(intercepted => {
verifyExpectedFormData(intercepted, {
groupby: ['state'],
adhoc_filters: [
{
clause: 'WHERE',
comparator: 'boy',
expressionType: 'SIMPLE',
operator: '==',
operatorId: 'EQUALS',
subject: 'gender',
},
],
});
});
cy.get('@drillByModal')
.find('.ant-breadcrumb')
.should('be.visible')
.and('contain', 'gender (boy)')
.and('contain', '/')
.and('not.contain', 'state (other)')
.and('not.contain', furtherDrillDimension)
.and('contain', 'state');
cy.get('@drillByModal')
.find('[data-test="drill-by-chart"]')
.should('be.visible');
});
});
};
describe('Drill by modal', () => {
beforeEach(() => {
closeModal();
});
before(() => {
interceptDrillInfo();
cy.visit(SUPPORTED_CHARTS_DASHBOARD);
});
describe('Modal actions + Table', () => {
before(() => {
closeModal();
interceptDrillInfo();
openTopLevelTab('Tier 1');
SUPPORTED_TIER1_CHARTS.forEach(waitForChartLoad);
});
it.only('opens the modal from the context menu', () => {
openTableContextMenu('boy');
drillBy('state').then(intercepted => {
verifyExpectedFormData(intercepted, {
groupby: ['state'],
adhoc_filters: [
{
clause: 'WHERE',
comparator: 'boy',
expressionType: 'SIMPLE',
operator: '==',
operatorId: 'EQUALS',
subject: 'gender',
},
],
});
});
cy.getBySel('"Drill by: Table-modal"').as('drillByModal');
cy.get('@drillByModal')
.find('.draggable-trigger')
.should('contain', 'Drill by: Table');
cy.get('@drillByModal')
.find('[data-test="metadata-bar"]')
.should('be.visible');
cy.get('@drillByModal')
.find('.ant-breadcrumb')
.should('be.visible')
.and('contain', 'gender (boy)')
.and('contain', '/')
.and('contain', 'state');
cy.get('@drillByModal')
.find('[data-test="drill-by-chart"]')
.should('be.visible')
.and('contain', 'state')
.and('contain', 'sum__num');
// further drilling
openTableContextMenu('CA', '[data-test="drill-by-chart"]');
drillBy('name').then(intercepted => {
verifyExpectedFormData(intercepted, {
groupby: ['name'],
adhoc_filters: [
{
clause: 'WHERE',
comparator: 'boy',
expressionType: 'SIMPLE',
operator: '==',
operatorId: 'EQUALS',
subject: 'gender',
},
{
clause: 'WHERE',
comparator: 'CA',
expressionType: 'SIMPLE',
operator: '==',
operatorId: 'EQUALS',
subject: 'state',
},
],
});
});
cy.get('@drillByModal')
.find('[data-test="drill-by-chart"]')
.should('be.visible')
.and('not.contain', 'state')
.and('contain', 'name')
.and('contain', 'sum__num');
// undo - back to drill by state
interceptV1ChartData('drillByUndo');
interceptFormDataKey();
cy.get('@drillByModal')
.find('.ant-breadcrumb')
.should('be.visible')
.and('contain', 'gender (boy)')
.and('contain', '/')
.and('contain', 'state (CA)')
.and('contain', 'name')
.contains('state (CA)')
.click();
cy.wait('@drillByUndo').then(intercepted => {
verifyExpectedFormData(intercepted, {
groupby: ['state'],
adhoc_filters: [
{
clause: 'WHERE',
comparator: 'boy',
expressionType: 'SIMPLE',
operator: '==',
operatorId: 'EQUALS',
subject: 'gender',
},
],
});
});
cy.get('@drillByModal')
.find('[data-test="drill-by-chart"]')
.should('be.visible')
.and('not.contain', 'name')
.and('contain', 'state')
.and('contain', 'sum__num');
cy.get('@drillByModal')
.find('.ant-breadcrumb')
.should('be.visible')
.and('contain', 'gender (boy)')
.and('contain', '/')
.and('not.contain', 'state (CA)')
.and('not.contain', 'name')
.and('contain', 'state');
cy.get('@drillByModal')
.find('[data-test="drill-by-display-toggle"]')
.contains('Table')
.click();
cy.getBySel('drill-by-chart').should('not.exist');
cy.get('@drillByModal')
.find('[data-test="drill-by-results-table"]')
.should('be.visible');
cy.wait('@formDataKey').then(intercept => {
cy.get('@drillByModal')
.contains('Edit chart')
.should('have.attr', 'href')
.and(
'contain',
`/explore/?form_data_key=${intercept.response?.body?.key}`,
);
});
});
});
describe('Tier 1 charts', () => {
before(() => {
closeModal();
interceptDrillInfo();
openTopLevelTab('Tier 1');
SUPPORTED_TIER1_CHARTS.forEach(waitForChartLoad);
});
it('Pivot Table', () => {
openTableContextMenu('boy', "[data-test-viz-type='pivot_table_v2']");
drillBy('name').then(intercepted => {
verifyExpectedFormData(intercepted, {
groupbyRows: ['state'],
groupbyColumns: ['name'],
adhoc_filters: [
{
clause: 'WHERE',
comparator: 'boy',
expressionType: 'SIMPLE',
operator: '==',
operatorId: 'EQUALS',
subject: 'gender',
},
],
});
});
cy.getBySel('"Drill by: Pivot Table-modal"').as('drillByModal');
cy.get('@drillByModal')
.find('.draggable-trigger')
.should('contain', 'Drill by: Pivot Table');
cy.get('@drillByModal')
.find('.ant-breadcrumb')
.should('be.visible')
.and('contain', 'gender (boy)')
.and('contain', '/')
.and('contain', 'name');
cy.get('@drillByModal')
.find('[data-test="drill-by-chart"]')
.should('be.visible')
.and('contain', 'state')
.and('contain', 'name')
.and('contain', 'sum__num')
.and('not.contain', 'Gender');
openTableContextMenu('CA', '[data-test="drill-by-chart"]');
drillBy('ds').then(intercepted => {
verifyExpectedFormData(intercepted, {
groupbyColumns: ['name'],
groupbyRows: ['ds'],
adhoc_filters: [
{
clause: 'WHERE',
comparator: 'boy',
expressionType: 'SIMPLE',
operator: '==',
operatorId: 'EQUALS',
subject: 'gender',
},
{
clause: 'WHERE',
comparator: 'CA',
expressionType: 'SIMPLE',
operator: '==',
operatorId: 'EQUALS',
subject: 'state',
},
],
});
});
cy.get('@drillByModal')
.find('[data-test="drill-by-chart"]')
.should('be.visible')
.and('contain', 'name')
.and('contain', 'ds')
.and('contain', 'sum__num')
.and('not.contain', 'state');
interceptV1ChartData('drillByUndo');
cy.get('@drillByModal')
.find('.ant-breadcrumb')
.should('be.visible')
.and('contain', 'gender (boy)')
.and('contain', '/')
.and('contain', 'name (CA)')
.and('contain', 'ds')
.contains('name (CA)')
.click();
cy.wait('@drillByUndo').then(intercepted => {
verifyExpectedFormData(intercepted, {
groupbyRows: ['state'],
groupbyColumns: ['name'],
adhoc_filters: [
{
clause: 'WHERE',
comparator: 'boy',
expressionType: 'SIMPLE',
operator: '==',
operatorId: 'EQUALS',
subject: 'gender',
},
],
});
});
cy.get('@drillByModal')
.find('[data-test="drill-by-chart"]')
.should('be.visible')
.and('not.contain', 'ds')
.and('contain', 'state')
.and('contain', 'name')
.and('contain', 'sum__num');
cy.get('@drillByModal')
.find('.ant-breadcrumb')
.should('be.visible')
.and('contain', 'gender (boy)')
.and('contain', '/')
.and('not.contain', 'name (CA)')
.and('not.contain', 'ds')
.and('contain', 'name');
});
it('Line chart', () => {
testEchart('echarts_timeseries_line', 'Line Chart', [
[85, 93],
[85, 93],
]);
});
it('Area Chart', () => {
testEchart('echarts_area', 'Area Chart', [
[85, 93],
[85, 93],
]);
});
it('Scatter Chart', () => {
testEchart('echarts_timeseries_scatter', 'Scatter Chart', [
[85, 93],
[85, 93],
]);
});
it.skip('Bar Chart', () => {
testEchart('echarts_timeseries_bar', 'Bar Chart', [
[85, 94],
[490, 68],
]);
});
it('Pie Chart', () => {
testEchart('pie', 'Pie Chart', [
[243, 167],
[534, 248],
]);
});
});
describe('Tier 2 charts', () => {
before(() => {
closeModal();
interceptDrillInfo();
openTopLevelTab('Tier 2');
SUPPORTED_TIER2_CHARTS.forEach(waitForChartLoad);
});
it('Box Plot Chart', () => {
testEchart(
'box_plot',
'Box Plot Chart',
[
[139, 277],
[787, 441],
],
'ds',
);
});
it('Generic Chart', () => {
testEchart('echarts_timeseries', 'Generic Chart', [
[85, 93],
[85, 93],
]);
});
it('Smooth Line Chart', () => {
testEchart('echarts_timeseries_smooth', 'Smooth Line Chart', [
[85, 93],
[85, 93],
]);
});
it('Step Line Chart', () => {
testEchart('echarts_timeseries_step', 'Step Line Chart', [
[85, 93],
[85, 93],
]);
});
it('Funnel Chart', () => {
testEchart('funnel', 'Funnel Chart', [
[154, 80],
[421, 39],
]);
});
it('Gauge Chart', () => {
testEchart('gauge_chart', 'Gauge Chart', [
[151, 95],
[300, 143],
]);
});
it.skip('Radar Chart', () => {
testEchart('radar', 'Radar Chart', [
[182, 49],
[423, 91],
]);
});
it('Treemap V2 Chart', () => {
testEchart('treemap_v2', 'Treemap V2 Chart', [
[145, 84],
[220, 105],
]);
});
it.skip('Mixed Chart', () => {
cy.get('[data-test-viz-type="mixed_timeseries"] canvas').then($canvas => {
// click 'boy'
cy.wrap($canvas).scrollIntoView();
cy.wrap($canvas).trigger('mouseover', 85, 93);
cy.wrap($canvas).rightclick(85, 93);
drillBy('name').then(intercepted => {
const { queries } = intercepted.request.body;
expect(queries[0].columns).to.eql(['name']);
expect(queries[0].filters).to.eql([
{ col: 'gender', op: '==', val: 'boy' },
]);
expect(queries[1].columns).to.eql(['state']);
expect(queries[1].filters).to.eql([]);
});
cy.getBySel('"Drill by: Mixed Chart-modal"').as('drillByModal');
cy.get('@drillByModal')
.find('.draggable-trigger')
.should('contain', 'Mixed Chart');
cy.get('@drillByModal')
.find('.ant-breadcrumb')
.should('be.visible')
.and('contain', 'gender (boy)')
.and('contain', '/')
.and('contain', 'name');
cy.get('@drillByModal')
.find('[data-test="drill-by-chart"]')
.should('be.visible');
// further drill
cy.get(`[data-test="drill-by-chart"] canvas`).then($canvas => {
// click second query
cy.wrap($canvas).scrollIntoView();
cy.wrap($canvas).trigger('mouseover', 261, 114);
cy.wrap($canvas).rightclick(261, 114);
drillBy('ds').then(intercepted => {
const { queries } = intercepted.request.body;
expect(queries[0].columns).to.eql(['name']);
expect(queries[0].filters).to.eql([
{ col: 'gender', op: '==', val: 'boy' },
]);
expect(queries[1].columns).to.eql(['ds']);
expect(queries[1].filters).to.eql([
{ col: 'state', op: '==', val: 'other' },
]);
});
cy.get('@drillByModal')
.find('[data-test="drill-by-chart"]')
.should('be.visible');
// undo - back to drill by state
interceptV1ChartData('drillByUndo');
cy.get('@drillByModal')
.find('.ant-breadcrumb')
.should('be.visible')
.and('contain', 'gender (boy)')
.and('contain', '/')
.and('contain', 'name (other)')
.and('contain', 'ds')
.contains('name (other)')
.click();
cy.wait('@drillByUndo').then(intercepted => {
const { queries } = intercepted.request.body;
expect(queries[0].columns).to.eql(['name']);
expect(queries[0].filters).to.eql([
{ col: 'gender', op: '==', val: 'boy' },
]);
expect(queries[1].columns).to.eql(['state']);
expect(queries[1].filters).to.eql([]);
});
cy.get('@drillByModal')
.find('.ant-breadcrumb')
.should('be.visible')
.and('contain', 'gender (boy)')
.and('contain', '/')
.and('not.contain', 'name (other)')
.and('not.contain', 'ds')
.and('contain', 'name');
cy.get('@drillByModal')
.find('[data-test="drill-by-chart"]')
.should('be.visible');
});
});
});
});
});

View File

@@ -19,9 +19,9 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@reduxjs/toolkit": "^1.9.3",
"@rjsf/core": "^5.21.1",
"@rjsf/core": "^5.24.13",
"@rjsf/utils": "^5.24.3",
"@rjsf/validator-ajv8": "^5.24.12",
"@rjsf/validator-ajv8": "^5.24.13",
"@scarf/scarf": "^1.4.0",
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
"@superset-ui/core": "file:./packages/superset-ui-core",
@@ -57,6 +57,7 @@
"antd": "^5.24.6",
"chrono-node": "^2.7.8",
"classnames": "^2.2.5",
"content-disposition": "^0.5.4",
"d3-color": "^3.1.0",
"d3-scale": "^2.1.2",
"dayjs": "^1.11.13",
@@ -70,7 +71,7 @@
"fuse.js": "^7.0.0",
"geolib": "^2.0.24",
"geostyler": "^14.1.3",
"geostyler-data": "^1.0.0",
"geostyler-data": "^1.1.0",
"geostyler-openlayers-parser": "^4.3.0",
"geostyler-qgis-parser": "2.0.1",
"geostyler-style": "7.5.0",
@@ -84,7 +85,7 @@
"json-bigint": "^1.0.0",
"json-stringify-pretty-compact": "^2.0.0",
"lodash": "^4.17.21",
"luxon": "^3.5.0",
"luxon": "^3.7.1",
"mapbox-gl": "^3.13.0",
"markdown-to-jsx": "^7.7.4",
"match-sorter": "^6.3.4",
@@ -117,7 +118,7 @@
"react-split": "^2.0.9",
"react-table": "^7.8.0",
"react-transition-group": "^4.4.5",
"react-virtualized-auto-sizer": "^1.0.25",
"react-virtualized-auto-sizer": "^1.0.26",
"react-window": "^1.8.10",
"redux": "^4.2.1",
"redux-localstorage": "^0.4.1",
@@ -139,7 +140,7 @@
"@applitools/eyes-storybook": "^3.55.6",
"@babel/cli": "^7.27.2",
"@babel/compat-data": "^7.28.0",
"@babel/core": "^7.26.0",
"@babel/core": "^7.28.3",
"@babel/eslint-parser": "^7.25.9",
"@babel/node": "^7.22.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
@@ -174,6 +175,7 @@
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^12.8.3",
"@types/content-disposition": "^0.5.9",
"@types/dom-to-image": "^2.6.7",
"@types/jest": "^29.5.14",
"@types/js-levenshtein": "^1.1.3",
@@ -189,8 +191,7 @@
"@types/react-resizable": "^3.0.8",
"@types/react-router-dom": "^5.3.3",
"@types/react-transition-group": "^4.4.12",
"@types/react-ultimate-pagination": "^1.2.4",
"@types/react-virtualized-auto-sizer": "^1.0.4",
"@types/react-virtualized-auto-sizer": "^1.0.8",
"@types/react-window": "^1.8.8",
"@types/redux-localstorage": "^1.0.8",
"@types/redux-mock-store": "^1.0.6",
@@ -208,7 +209,7 @@
"babel-plugin-typescript-to-proptypes": "^2.0.0",
"cheerio": "1.1.0",
"copy-webpack-plugin": "^13.0.0",
"cross-env": "^7.0.3",
"cross-env": "^10.0.0",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.2",
"eslint": "^8.56.0",
@@ -233,7 +234,7 @@
"eslint-plugin-testing-library": "^6.4.0",
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
"fetch-mock": "^11.1.5",
"fork-ts-checker-webpack-plugin": "^9.0.2",
"fork-ts-checker-webpack-plugin": "^9.1.0",
"history": "^5.3.0",
"html-webpack-plugin": "^5.6.3",
"imports-loader": "^5.0.0",
@@ -1099,22 +1100,22 @@
}
},
"node_modules/@babel/core": {
"version": "7.27.7",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz",
"integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz",
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.27.5",
"@babel/generator": "^7.28.3",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.27.3",
"@babel/helpers": "^7.27.6",
"@babel/parser": "^7.27.7",
"@babel/helper-module-transforms": "^7.28.3",
"@babel/helpers": "^7.28.3",
"@babel/parser": "^7.28.3",
"@babel/template": "^7.27.2",
"@babel/traverse": "^7.27.7",
"@babel/types": "^7.27.7",
"@babel/traverse": "^7.28.3",
"@babel/types": "^7.28.2",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -1169,15 +1170,15 @@
}
},
"node_modules/@babel/generator": {
"version": "7.27.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz",
"integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.27.5",
"@babel/types": "^7.27.3",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"@babel/parser": "^7.28.3",
"@babel/types": "^7.28.2",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
},
"engines": {
@@ -1301,6 +1302,15 @@
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/@babel/helper-globals": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-member-expression-to-functions": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz",
@@ -1329,15 +1339,15 @@
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
"integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
"integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1",
"@babel/traverse": "^7.27.3"
"@babel/traverse": "^7.28.3"
},
"engines": {
"node": ">=6.9.0"
@@ -1463,14 +1473,14 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
"integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz",
"integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.27.2",
"@babel/types": "^7.27.6"
"@babel/types": "^7.28.2"
},
"engines": {
"node": ">=6.9.0"
@@ -1501,12 +1511,12 @@
}
},
"node_modules/@babel/parser": {
"version": "7.27.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz",
"integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
"integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.7"
"@babel/types": "^7.28.2"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -3145,27 +3155,27 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.27.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz",
"integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
"integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.27.5",
"@babel/parser": "^7.27.7",
"@babel/generator": "^7.28.3",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.28.3",
"@babel/template": "^7.27.2",
"@babel/types": "^7.27.7",
"debug": "^4.3.1",
"globals": "^11.1.0"
"@babel/types": "^7.28.2",
"debug": "^4.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
"version": "7.27.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz",
"integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==",
"version": "7.28.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
@@ -4580,6 +4590,13 @@
"global-box": "*"
}
},
"node_modules/@epic-web/invariant": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
"dev": true,
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
@@ -7022,17 +7039,13 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"license": "MIT",
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
@@ -7044,15 +7057,6 @@
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
@@ -7071,9 +7075,9 @@
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"version": "0.3.30",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
"integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -10843,15 +10847,14 @@
"license": "MIT"
},
"node_modules/@rjsf/core": {
"version": "5.24.1",
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-5.24.1.tgz",
"integrity": "sha512-x3oZ9MfoBA4SPKhvNkqagqnjUbSKuT3gGW3NgH+cowAma33fYs9/hfhPUOvXySdPqh0layuLlnI1W5MHeBmVzg==",
"version": "5.24.13",
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-5.24.13.tgz",
"integrity": "sha512-ONTr14s7LFIjx2VRFLuOpagL76sM/HPy6/OhdBfq6UukINmTIs6+aFN0GgcR0aXQHFDXQ7f/fel0o/SO05Htdg==",
"license": "Apache-2.0",
"dependencies": {
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"markdown-to-jsx": "^7.4.1",
"nanoid": "^3.3.7",
"prop-types": "^15.8.1"
},
"engines": {
@@ -10862,24 +10865,6 @@
"react": "^16.14.0 || >=17"
}
},
"node_modules/@rjsf/core/node_modules/nanoid": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/@rjsf/utils": {
"version": "5.24.3",
"resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.24.3.tgz",
@@ -10900,9 +10885,9 @@
}
},
"node_modules/@rjsf/validator-ajv8": {
"version": "5.24.12",
"resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.24.12.tgz",
"integrity": "sha512-IMXdCjvDNdvb+mDgZC3AlAtr0pjYKq5s0GcLECjG5PuiX7Ib4JaDQHZY5ZJdKblMfgzhsn8AAOi573jXAt7BHQ==",
"version": "5.24.13",
"resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.24.13.tgz",
"integrity": "sha512-oWHP7YK581M8I5cF1t+UXFavnv+bhcqjtL1a7MG/Kaffi0EwhgcYjODrD8SsnrhncsEYMqSECr4ZOEoirnEUWw==",
"license": "Apache-2.0",
"dependencies": {
"ajv": "^8.12.0",
@@ -15266,6 +15251,13 @@
"@types/node": "*"
}
},
"node_modules/@types/content-disposition": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.9.tgz",
"integrity": "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/conventional-commits-parser": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz",
@@ -16085,24 +16077,15 @@
"@types/react": "*"
}
},
"node_modules/@types/react-ultimate-pagination": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/react-ultimate-pagination/-/react-ultimate-pagination-1.2.4.tgz",
"integrity": "sha512-1y9jLt3KEFGzFD+99qVpJUI/Eu4cEx48sClB957eGoepWRLVVi+r1UBj0157Mg7HYZcIF4I1/qGZYaBBQWhaqg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-virtualized-auto-sizer": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.4.tgz",
"integrity": "sha512-nhYwlFiYa8M3S+O2T9QO/e1FQUYMr/wJENUdf/O0dhRi1RS/93rjrYQFYdbUqtdFySuhrtnEDX29P6eKOttY+A==",
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.8.tgz",
"integrity": "sha512-keJpNyhiwfl2+N12G1ocCVA5ZDBArbPLe/S90X3kt7fam9naeHdaYYWbpe2sHczp70JWJ+2QLhBE8kLvLuVNjA==",
"deprecated": "This is a stub types definition. react-virtualized-auto-sizer provides its own type definitions, so you do not need this installed.",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*"
"react-virtualized-auto-sizer": "*"
}
},
"node_modules/@types/react-window": {
@@ -21943,7 +21926,6 @@
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
@@ -22357,22 +22339,21 @@
"license": "MIT"
},
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz",
"integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.1"
"@epic-web/invariant": "^1.0.0",
"cross-spawn": "^7.0.6"
},
"bin": {
"cross-env": "src/bin/cross-env.js",
"cross-env-shell": "src/bin/cross-env-shell.js"
"cross-env": "dist/bin/cross-env.js",
"cross-env-shell": "dist/bin/cross-env-shell.js"
},
"engines": {
"node": ">=10.14",
"npm": ">=6",
"yarn": ">=1"
"node": ">=20"
}
},
"node_modules/cross-spawn": {
@@ -27488,15 +27469,15 @@
}
},
"node_modules/fork-ts-checker-webpack-plugin": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz",
"integrity": "sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==",
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz",
"integrity": "sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.16.7",
"chalk": "^4.1.2",
"chokidar": "^3.5.3",
"chokidar": "^4.0.1",
"cosmiconfig": "^8.2.0",
"deepmerge": "^4.2.2",
"fs-extra": "^10.0.0",
@@ -27508,8 +27489,7 @@
"tapable": "^2.2.1"
},
"engines": {
"node": ">=12.13.0",
"yarn": ">=1.0.0"
"node": ">=14.21.3"
},
"peerDependencies": {
"typescript": ">3.6.0",
@@ -27560,6 +27540,22 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
@@ -27582,6 +27578,20 @@
"dev": true,
"license": "MIT"
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
@@ -28046,27 +28056,18 @@
}
},
"node_modules/geostyler-data": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/geostyler-data/-/geostyler-data-1.0.0.tgz",
"integrity": "sha512-ctmk6OsunL427Uaa1HME/blTyBbl0Ihu+vPV1Irqz3ip80qvNLwDEr46xI5HwMeyrsWH8o76kfA0sF6oecW1BA==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/geostyler-data/-/geostyler-data-1.1.0.tgz",
"integrity": "sha512-0tUEF0RbiGM9eVqoLbMc20Bl5A1x3PHbn2Ca0yPJx65S0nh3rHfd82OmgFwHzzfsukDmqIn0VoOGJFjmQAmAcw==",
"license": "BSD-2-Clause",
"dependencies": {
"@types/geojson": "7946.0.7",
"@types/json-schema": "7.0.3"
"@types/geojson": "^7946.0.7",
"@types/json-schema": "^7.0.3"
},
"funding": {
"url": "https://opencollective.com/geostyler"
}
},
"node_modules/geostyler-data/node_modules/@types/geojson": {
"version": "7946.0.7",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz",
"integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==",
"license": "MIT"
},
"node_modules/geostyler-data/node_modules/@types/json-schema": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz",
"integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==",
"license": "MIT"
},
"node_modules/geostyler-geojson-parser": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/geostyler-geojson-parser/-/geostyler-geojson-parser-1.0.1.tgz",
@@ -29270,6 +29271,7 @@
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -37667,9 +37669,9 @@
}
},
"node_modules/luxon": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz",
"integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==",
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz",
"integrity": "sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -46360,9 +46362,9 @@
}
},
"node_modules/prismjs": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
"license": "MIT",
"engines": {
"node": ">=6"
@@ -48547,16 +48549,16 @@
}
},
"node_modules/react-syntax-highlighter": {
"version": "15.6.1",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz",
"integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==",
"version": "15.6.6",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.6.tgz",
"integrity": "sha512-DgXrc+AZF47+HvAPEmn7Ua/1p10jNoVZVI/LoPiYdtY+OM+/nG5yefLHKJwdKqY1adMuHFbeyBaG9j64ML7vTw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.3.1",
"highlight.js": "^10.4.1",
"highlightjs-vue": "^1.0.0",
"lowlight": "^1.17.0",
"prismjs": "^1.27.0",
"prismjs": "^1.30.0",
"refractor": "^3.6.0"
},
"peerDependencies": {
@@ -48644,9 +48646,9 @@
}
},
"node_modules/react-virtualized-auto-sizer": {
"version": "1.0.25",
"resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.25.tgz",
"integrity": "sha512-YHsksEGDfsHbHuaBVDYwJmcktblcHGafz4ZVuYPQYuSHMUGjpwmUCrAOcvMSGMwwk1eFWj1M/1GwYpNPuyhaBg==",
"version": "1.0.26",
"resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.26.tgz",
"integrity": "sha512-CblNyiNVw2o+hsa5/49NH2ogGxZ+t+3aweRvNSq7TVjDIlwk7ir4lencEg5HxHeSzwNarSkNkiu0qJSOXtxm5A==",
"license": "MIT",
"peerDependencies": {
"react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0",
@@ -58624,7 +58626,7 @@
"yosay": "^3.0.0"
},
"devDependencies": {
"cross-env": "^7.0.3",
"cross-env": "^10.0.0",
"fs-extra": "^11.3.0",
"jest": "^30.0.5",
"yeoman-test": "^10.1.1"
@@ -60629,17 +60631,18 @@
},
"packages/superset-core": {
"name": "@apache-superset/core",
"version": "0.0.1-rc2",
"version": "0.0.1-rc3",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.9",
"@babel/core": "^7.28.3",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.26.0",
"@types/react": "^17.0.83",
"install": "^0.13.0",
"npm": "^11.1.0"
"npm": "^11.1.0",
"typescript": "^5.0.0"
},
"peerDependencies": {
"antd": "^5.24.6",
@@ -63385,7 +63388,7 @@
"react-js-cron": "^5.2.0",
"react-markdown": "^8.0.7",
"react-resize-detector": "^7.1.2",
"react-syntax-highlighter": "^15.4.5",
"react-syntax-highlighter": "^15.6.6",
"react-ultimate-pagination": "^1.3.2",
"regenerator-runtime": "^0.14.1",
"rehype-raw": "^7.0.0",
@@ -64401,7 +64404,7 @@
"react-resizable": "^3.0.5"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/core": "^7.28.3",
"@babel/preset-env": "^7.27.2",
"@babel/preset-react": "^7.27.1",
"@babel/preset-typescript": "^7.23.3",
@@ -64791,13 +64794,6 @@
"ajv": "^6.9.1"
}
},
"packages/superset-ui-demo/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"packages/superset-ui-demo/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -64879,105 +64875,6 @@
}
}
},
"packages/superset-ui-demo/node_modules/fork-ts-checker-webpack-plugin": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz",
"integrity": "sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.16.7",
"chalk": "^4.1.2",
"chokidar": "^4.0.1",
"cosmiconfig": "^8.2.0",
"deepmerge": "^4.2.2",
"fs-extra": "^10.0.0",
"memfs": "^3.4.1",
"minimatch": "^3.0.4",
"node-abort-controller": "^3.0.1",
"schema-utils": "^3.1.1",
"semver": "^7.3.5",
"tapable": "^2.2.1"
},
"engines": {
"node": ">=14.21.3"
},
"peerDependencies": {
"typescript": ">3.6.0",
"webpack": "^5.11.0"
}
},
"packages/superset-ui-demo/node_modules/fork-ts-checker-webpack-plugin/node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"packages/superset-ui-demo/node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": {
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
"integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"import-fresh": "^3.3.0",
"js-yaml": "^4.1.0",
"parse-json": "^5.2.0",
"path-type": "^4.0.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/d-fischer"
},
"peerDependencies": {
"typescript": ">=4.9.5"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"packages/superset-ui-demo/node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"packages/superset-ui-demo/node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"packages/superset-ui-demo/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -64985,20 +64882,6 @@
"dev": true,
"license": "MIT"
},
"packages/superset-ui-demo/node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"packages/superset-ui-demo/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",

View File

@@ -87,9 +87,9 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@reduxjs/toolkit": "^1.9.3",
"@rjsf/core": "^5.21.1",
"@rjsf/core": "^5.24.13",
"@rjsf/utils": "^5.24.3",
"@rjsf/validator-ajv8": "^5.24.12",
"@rjsf/validator-ajv8": "^5.24.13",
"@scarf/scarf": "^1.4.0",
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
"@superset-ui/core": "file:./packages/superset-ui-core",
@@ -125,6 +125,7 @@
"antd": "^5.24.6",
"chrono-node": "^2.7.8",
"classnames": "^2.2.5",
"content-disposition": "^0.5.4",
"d3-color": "^3.1.0",
"d3-scale": "^2.1.2",
"dayjs": "^1.11.13",
@@ -138,7 +139,7 @@
"fuse.js": "^7.0.0",
"geolib": "^2.0.24",
"geostyler": "^14.1.3",
"geostyler-data": "^1.0.0",
"geostyler-data": "^1.1.0",
"geostyler-openlayers-parser": "^4.3.0",
"geostyler-qgis-parser": "2.0.1",
"geostyler-style": "7.5.0",
@@ -152,7 +153,7 @@
"json-bigint": "^1.0.0",
"json-stringify-pretty-compact": "^2.0.0",
"lodash": "^4.17.21",
"luxon": "^3.5.0",
"luxon": "^3.7.1",
"mapbox-gl": "^3.13.0",
"markdown-to-jsx": "^7.7.4",
"match-sorter": "^6.3.4",
@@ -185,7 +186,7 @@
"react-split": "^2.0.9",
"react-table": "^7.8.0",
"react-transition-group": "^4.4.5",
"react-virtualized-auto-sizer": "^1.0.25",
"react-virtualized-auto-sizer": "^1.0.26",
"react-window": "^1.8.10",
"redux": "^4.2.1",
"redux-localstorage": "^0.4.1",
@@ -207,7 +208,7 @@
"@applitools/eyes-storybook": "^3.55.6",
"@babel/cli": "^7.27.2",
"@babel/compat-data": "^7.28.0",
"@babel/core": "^7.26.0",
"@babel/core": "^7.28.3",
"@babel/eslint-parser": "^7.25.9",
"@babel/node": "^7.22.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
@@ -242,6 +243,7 @@
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^12.8.3",
"@types/content-disposition": "^0.5.9",
"@types/dom-to-image": "^2.6.7",
"@types/jest": "^29.5.14",
"@types/js-levenshtein": "^1.1.3",
@@ -257,8 +259,7 @@
"@types/react-resizable": "^3.0.8",
"@types/react-router-dom": "^5.3.3",
"@types/react-transition-group": "^4.4.12",
"@types/react-ultimate-pagination": "^1.2.4",
"@types/react-virtualized-auto-sizer": "^1.0.4",
"@types/react-virtualized-auto-sizer": "^1.0.8",
"@types/react-window": "^1.8.8",
"@types/redux-localstorage": "^1.0.8",
"@types/redux-mock-store": "^1.0.6",
@@ -276,7 +277,7 @@
"babel-plugin-typescript-to-proptypes": "^2.0.0",
"cheerio": "1.1.0",
"copy-webpack-plugin": "^13.0.0",
"cross-env": "^7.0.3",
"cross-env": "^10.0.0",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.2",
"eslint": "^8.56.0",
@@ -301,7 +302,7 @@
"eslint-plugin-testing-library": "^6.4.0",
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
"fetch-mock": "^11.1.5",
"fork-ts-checker-webpack-plugin": "^9.0.2",
"fork-ts-checker-webpack-plugin": "^9.1.0",
"history": "^5.3.0",
"html-webpack-plugin": "^5.6.3",
"imports-loader": "^5.0.0",

View File

@@ -34,7 +34,7 @@
"yosay": "^3.0.0"
},
"devDependencies": {
"cross-env": "^7.0.3",
"cross-env": "^10.0.0",
"fs-extra": "^11.3.0",
"jest": "^30.0.5",
"yeoman-test": "^10.1.1"

View File

@@ -1,32 +1,32 @@
{
"name": "@apache-superset/core",
"version": "0.0.1-rc2",
"version": "0.0.1-rc3",
"description": "This package contains UI elements, APIs, and utility functions used by Superset.",
"sideEffects": false,
"main": "lib/index.js",
"module": "esm/index.js",
"types": "lib/index.d.ts",
"files": [
"esm",
"lib"
],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.9",
"@babel/core": "^7.28.3",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.26.0",
"@types/react": "^17.0.83",
"install": "^0.13.0",
"npm": "^11.1.0"
"npm": "^11.1.0",
"typescript": "^5.0.0"
},
"peerDependencies": {
"antd": "^5.24.6",
"react": "^17.0.2"
},
"scripts": {
"build": "babel src --out-dir lib --extensions \".ts,.tsx\"",
"build": "babel src --out-dir lib --extensions \".ts,.tsx\" && tsc --emitDeclarationOnly",
"type": "tsc --noEmit"
},
"publishConfig": {

View File

@@ -31,12 +31,32 @@ import { Contributions } from './contributions';
/**
* Represents a database column with its name and data type.
*/
export declare interface Column {
/** The name of the column */
export type Column = {
/**
* Label of the column
*/
name: string;
/** The data type of the column (e.g., 'INTEGER', 'VARCHAR', 'TIMESTAMP') */
/**
* Column name defined
*/
column_name: string;
/**
* The data type of the column (e.g., 'INTEGER', 'VARCHAR', 'TIMESTAMP')
*/
type: string;
}
/**
* Generic data type format
*/
type_generic: GenericDataType;
/**
* True if the column is date format
*/
is_dttm: boolean;
};
/**
* Represents a database table with its name and column definitions.
@@ -76,6 +96,45 @@ export declare interface Database {
schemas: Schema[];
}
// Keep in sync with superset/errors.py
export type ErrorLevel = 'info' | 'warning' | 'error';
/**
* Superset error object structure.
* Contains details about an error that occurred within Superset.
*/
export type SupersetError = {
/**
* Error types, see enum of SupersetErrorType in superset/errors.py
*/
error_type: string;
/**
* Extra properties based on the error types
*/
extra: Record<string, any>;
/**
* Level of the error type
*/
level: ErrorLevel;
/**
* Detail description for the error
*/
message: string;
};
/**
* Generic data types, see enum of the same name in superset/utils/core.py.
*/
export enum GenericDataType {
Numeric = 0,
String = 1,
Temporal = 2,
Boolean = 3,
}
/**
* Represents a type which can release resources, such
* as event listening or a timer.

View File

@@ -29,7 +29,7 @@
* - Global APIs: Functions and events available across the entire SQL Lab interface
*/
import { Event, Database } from './core';
import { Event, Database, SupersetError, Column } from './core';
/**
* Represents an SQL editor instance within a SQL Lab tab.
@@ -111,6 +111,126 @@ export interface Tab {
panels: Panel[];
}
export enum CTASMethod {
Table = 'TABLE',
View = 'VIEW',
}
export interface CTAS {
/**
* Create method for CTAS creation request.
*/
method: CTASMethod;
/**
* Temporary table name for creation using a CTAS query.
*/
tempTable: string | null;
}
export interface QueryContext {
/**
* Unique query ID on client side.
*/
clientId: string;
/**
* Contains CTAS if the query requests table creation.
*/
ctas: CTAS | null;
/**
* Requested row limit for the query.
*/
requestedLimit: number | null;
/**
* True if the query execution result will be/was delivered asynchronously
*/
runAsync?: boolean;
/**
* Start datetime for the query in a numerical timestamp
*/
startDttm: number;
/**
* The tab instance associated with the request query
*/
tab: Tab;
/**
* A key-value JSON associated with Jinja template variables
*/
templateParameters: Record<string, any>;
}
export interface QueryErrorResultContext extends QueryContext {
/**
* Finished datetime for the query in a numerical timestamp
*/
endDttm: number;
/**
* Error message returned from DB engine
*/
errorMessage: string;
/**
* Error details in a SupersetError structure
*/
errors: SupersetError[] | null;
/**
* Executed SQL after parsing Jinja templates.
*/
executedSql: string | null;
}
export interface QueryResultContext extends QueryContext {
/**
* Actual number of rows returned by the query.
*/
appliedLimit: number;
/**
* Major factor that is determining the row limit of the query results.
*/
appliedLimitingFactor: string;
/**
* Finished datetime for the query in a numerical timestamp.
*/
endDttm: number;
/**
* Executed SQL after parsing Jinja templates.
*/
executedSql: string;
/**
* Remote query id stored in backend.
*/
remoteId: number;
/**
* Query result data and metadata.
*/
result: QueryResult;
}
export interface QueryResult {
/**
* Column metadata associated with the query result.
*/
columns: Column[];
/**
* Query result data.
*/
data: Record<string, any>[];
}
/**
* Tab-scoped Events and Functions
*
@@ -240,30 +360,30 @@ export declare const onDidChangeTabTitle: Event<string>;
/**
* Event fired when a query starts running in the current tab.
* Provides the editor state at the time of query execution.
* Provides the query request state at the time of query execution.
*
* @example
* ```typescript
* onDidQueryRun.event((editor) => {
* console.log('Query started on database:', editor.databaseId);
* console.log('Query content:', editor.content);
* onDidQueryRun.event((query) => {
* console.log('Query started on database:', query.tab.editor.databaseId);
* console.log('Query content:', query.tab.editor.content);
* });
* ```
*/
export declare const onDidQueryRun: Event<Editor>;
export declare const onDidQueryRun: Event<QueryContext>;
/**
* Event fired when a running query is stopped in the current tab.
* Provides the editor state when the query was stopped.
* Provides the query request state when the query was stopped.
*
* @example
* ```typescript
* onDidQueryStop.event((editor) => {
* console.log('Query stopped for database:', editor.databaseId);
* onDidQueryStop.event((query) => {
* console.log('Query stopped for database:', query.tab.editor.databaseId);
* });
* ```
*/
export declare const onDidQueryStop: Event<Editor>;
export declare const onDidQueryStop: Event<QueryContext>;
/**
* Event fired when a query fails in the current tab.
@@ -273,12 +393,12 @@ export declare const onDidQueryStop: Event<Editor>;
*
* @example
* ```typescript
* onDidQueryFail.event((error) => {
* console.error('Query failed:', error);
* onDidQueryFail.event((result) => {
* console.error('Query failed:', result.errorMessage);
* });
* ```
*/
export declare const onDidQueryFail: Event<string>;
export declare const onDidQueryFail: Event<QueryErrorResultContext>;
/**
* Event fired when a query succeeds in the current tab.
@@ -288,12 +408,13 @@ export declare const onDidQueryFail: Event<string>;
*
* @example
* ```typescript
* onDidQuerySuccess.event((result) => {
* console.log('Query succeeded:', result);
* onDidQuerySuccess.event((query) => {
* console.log('Query succeeded:', query.result.data);
* console.log('Query executed content:', query.executedSql);
* });
* ```
*/
export declare const onDidQuerySuccess: Event<string>;
export declare const onDidQuerySuccess: Event<QueryResultContext>;
/**
* Global Events and Functions

View File

@@ -10,7 +10,9 @@
"baseUrl": ".",
"module": "esnext",
"moduleResolution": "node",
"skipLibCheck": true
"skipLibCheck": true,
"target": "es2020",
"esModuleInterop": true
},
"include": ["src/**/*.ts*"],
"exclude": ["lib"]

View File

@@ -43,15 +43,17 @@ export const renameOperator: PostProcessingFactory<PostProcessingRename> = (
// remove or rename top level of column name(metric name) in the MultiIndex when
// 1) at least 1 metric
// 2) dimension exist or multiple time shift metrics exist
// 3) xAxis exist
// 4) truncate_metric in form_data and truncate_metric is true
// 2) xAxis exist
// 3a) isTimeComparisonValue
// 3b-1) dimension exist or multiple time shift metrics exist
// 3b-2) truncate_metric in form_data and truncate_metric is true
if (
metrics.length > 0 &&
(columns.length > 0 || timeOffsets.length > 1) &&
xAxisLabel &&
truncate_metric !== undefined &&
!!truncate_metric
(isTimeComparisonValue ||
((columns.length > 0 || timeOffsets.length > 1) &&
truncate_metric !== undefined &&
!!truncate_metric))
) {
const renamePairs: [string, string | null][] = [];
if (

View File

@@ -160,6 +160,44 @@ test('should add renameOperator if exists derived metrics', () => {
});
});
test('should add renameOperator if isTimeComparisonValue without columns', () => {
[
ComparisonType.Difference,
ComparisonType.Ratio,
ComparisonType.Percentage,
].forEach(type => {
expect(
renameOperator(
{
...formData,
...{
comparison_type: type,
time_compare: ['1 year ago'],
},
},
{
...queryObject,
...{
columns: [],
metrics: ['sum(val)', 'avg(val2)'],
},
},
),
).toEqual({
operation: 'rename',
options: {
columns: {
[`${type}__avg(val2)__avg(val2)__1 year ago`]:
'avg(val2), 1 year ago',
[`${type}__sum(val)__sum(val)__1 year ago`]: 'sum(val), 1 year ago',
},
inplace: true,
level: 0,
},
});
});
});
test('should add renameOperator if x_axis does not exist', () => {
expect(
renameOperator(

View File

@@ -54,7 +54,7 @@
"react-js-cron": "^5.2.0",
"react-draggable": "^4.5.0",
"react-resize-detector": "^7.1.2",
"react-syntax-highlighter": "^15.4.5",
"react-syntax-highlighter": "^15.6.6",
"react-ultimate-pagination": "^1.3.2",
"react-error-boundary": "^6.0.0",
"react-markdown": "^8.0.7",

View File

@@ -393,10 +393,7 @@ export const FullSQLEditor = AsyncAceEditor(
},
);
export const MarkdownEditor = AsyncAceEditor([
'mode/markdown',
'theme/textmate',
]);
export const MarkdownEditor = AsyncAceEditor(['mode/markdown', 'theme/github']);
export const TextAreaEditor = AsyncAceEditor([
'mode/markdown',

View File

@@ -48,15 +48,9 @@ export const CollapseLabelInModal: React.FC<CollapseLabelInModalProps> = ({
{title}{' '}
{validateCheckStatus !== undefined &&
(validateCheckStatus ? (
<Icons.CheckCircleOutlined
iconColor={theme.colorSuccess}
aria-label="check-circle"
/>
<Icons.CheckCircleOutlined iconColor={theme.colorSuccess} />
) : (
<Icons.ExclamationCircleOutlined
iconColor={theme.colorError}
aria-label="error-circle"
/>
<Icons.ExclamationCircleOutlined iconColor={theme.colorError} />
))}
</Typography.Title>
<Typography.Paragraph

View File

@@ -60,7 +60,7 @@ const EmptyStateContainer = styled.div`
flex-direction: column;
width: 100%;
height: 100%;
color: ${theme.colorTextQuaternary};
color: ${theme.colorTextTertiary};
align-items: center;
justify-content: center;
padding: ${theme.sizeUnit * 4}px;
@@ -84,7 +84,7 @@ const EmptyStateContainer = styled.div`
const Title = styled.p<{ size: EmptyStateSize }>`
${({ theme, size }) => css`
font-size: ${size === 'large' ? theme.fontSizeLG : theme.fontSize}px;
color: ${theme.colorTextQuaternary};
color: ${theme.colorTextTertiary};
margin-top: ${size === 'large' ? theme.sizeUnit * 4 : theme.sizeUnit * 2}px;
font-weight: ${theme.fontWeightStrong};
`}
@@ -93,7 +93,7 @@ const Title = styled.p<{ size: EmptyStateSize }>`
const Description = styled.p<{ size: EmptyStateSize }>`
${({ theme, size }) => css`
font-size: ${size === 'large' ? theme.fontSize : theme.fontSizeSM}px;
color: ${theme.colorTextQuaternary};
color: ${theme.colorTextTertiary};
margin-top: ${theme.sizeUnit * 2}px;
`}
`;

View File

@@ -22,7 +22,7 @@ import { AntdIconType, BaseIconProps, CustomIconType, IconType } from './types';
const genAriaLabel = (fileName: string) => {
const name = fileName.replace(/_/g, '-'); // Replace underscores with dashes
const words = name.split(/(?=[A-Z])/); // Split at uppercase letters
const words = name.split(/(?<=[a-z])(?=[A-Z])/); // Split at lowercase-to-uppercase transitions
if (words.length === 2) {
return words[0].toLowerCase();

View File

@@ -368,9 +368,13 @@ const CustomModal = ({
};
CustomModal.displayName = 'Modal';
// Theme-aware confirmation modal - now inherits theme through App wrapper in SupersetThemeProvider
const themedConfirm = (props: Parameters<typeof AntdModal.confirm>[0]) =>
AntdModal.confirm(props);
export const Modal = Object.assign(CustomModal, {
error: AntdModal.error,
warning: AntdModal.warning,
confirm: AntdModal.confirm,
confirm: themedConfirm,
useModal: AntdModal.useModal,
});

View File

@@ -1,37 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { render, screen, userEvent } from '@superset-ui/core/spec';
import { Ellipsis } from './Ellipsis';
test('Ellipsis - click when the button is enabled', async () => {
const click = jest.fn();
render(<Ellipsis onClick={click} />);
expect(click).toHaveBeenCalledTimes(0);
await userEvent.click(screen.getByRole('button'));
expect(click).toHaveBeenCalledTimes(1);
});
test('Ellipsis - click when the button is disabled', async () => {
const click = jest.fn();
render(<Ellipsis onClick={click} disabled />);
expect(click).toHaveBeenCalledTimes(0);
await userEvent.click(screen.getByRole('button'));
expect(click).toHaveBeenCalledTimes(0);
});

View File

@@ -1,38 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import classNames from 'classnames';
import { PaginationButtonProps } from './types';
export function Ellipsis({ disabled, onClick }: PaginationButtonProps) {
return (
<li className={classNames({ disabled })}>
<span
role="button"
tabIndex={disabled ? -1 : 0}
onClick={e => {
e.preventDefault();
if (!disabled) onClick(e);
}}
>
</span>
</li>
);
}

View File

@@ -1,47 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { render, screen, userEvent } from '@superset-ui/core/spec';
import { Item } from './Item';
test('Item - click when the item is not active', async () => {
const click = jest.fn();
render(
<Item onClick={click}>
<div data-test="test" />
</Item>,
);
expect(click).toHaveBeenCalledTimes(0);
await userEvent.click(screen.getByRole('button'));
expect(click).toHaveBeenCalledTimes(1);
expect(screen.getByTestId('test')).toBeInTheDocument();
});
test('Item - click when the item is active', async () => {
const click = jest.fn();
render(
<Item onClick={click} active>
<div data-test="test" />
</Item>,
);
expect(click).toHaveBeenCalledTimes(0);
await userEvent.click(screen.getByRole('button'));
expect(click).toHaveBeenCalledTimes(0);
expect(screen.getByTestId('test')).toBeInTheDocument();
});

View File

@@ -1,45 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ReactNode } from 'react';
import classNames from 'classnames';
import { PaginationButtonProps } from './types';
interface PaginationItemButton extends PaginationButtonProps {
active?: boolean;
children: ReactNode;
}
export function Item({ active, children, onClick }: PaginationItemButton) {
return (
<li className={classNames({ active })}>
<span
role="button"
tabIndex={0}
aria-current={active ? 'page' : undefined}
onClick={e => {
e.preventDefault();
if (!active) onClick(e);
}}
>
{children}
</span>
</li>
);
}

View File

@@ -1,37 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { render, screen, userEvent } from '@superset-ui/core/spec';
import { Next } from './Next';
test('Next - click when the button is enabled', async () => {
const click = jest.fn();
render(<Next onClick={click} />);
expect(click).toHaveBeenCalledTimes(0);
await userEvent.click(screen.getByRole('button'));
expect(click).toHaveBeenCalledTimes(1);
});
test('Next - click when the button is disabled', async () => {
const click = jest.fn();
render(<Next onClick={click} disabled />);
expect(click).toHaveBeenCalledTimes(0);
await userEvent.click(screen.getByRole('button'));
expect(click).toHaveBeenCalledTimes(0);
});

View File

@@ -1,37 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { render, screen, userEvent } from '@superset-ui/core/spec';
import { Prev } from './Prev';
test('Prev - click when the button is enabled', async () => {
const click = jest.fn();
render(<Prev onClick={click} />);
expect(click).toHaveBeenCalledTimes(0);
await userEvent.click(screen.getByRole('button'));
expect(click).toHaveBeenCalledTimes(1);
});
test('Prev - click when the button is disabled', async () => {
const click = jest.fn();
render(<Prev onClick={click} disabled />);
expect(click).toHaveBeenCalledTimes(0);
await userEvent.click(screen.getByRole('button'));
expect(click).toHaveBeenCalledTimes(0);
});

View File

@@ -1,75 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { render, screen, cleanup } from '@superset-ui/core/spec';
import Wrapper from './Wrapper';
// Add cleanup after each test
afterEach(async () => {
cleanup();
// Wait for any pending effects to complete
await new Promise(resolve => setTimeout(resolve, 0));
});
jest.mock('./Next', () => ({
Next: () => <div data-test="next" />,
}));
jest.mock('./Prev', () => ({
Prev: () => <div data-test="prev" />,
}));
jest.mock('./Item', () => ({
Item: () => <div data-test="item" />,
}));
jest.mock('./Ellipsis', () => ({
Ellipsis: () => <div data-test="ellipsis" />,
}));
test('Pagination rendering correctly', async () => {
render(
<Wrapper>
<li data-test="test" />
</Wrapper>,
);
expect(screen.getByRole('navigation')).toBeInTheDocument();
expect(screen.getByTestId('test')).toBeInTheDocument();
});
test('Next attribute', async () => {
render(<Wrapper.Next onClick={jest.fn()} />);
expect(screen.getByTestId('next')).toBeInTheDocument();
});
test('Prev attribute', async () => {
render(<Wrapper.Next onClick={jest.fn()} />);
expect(screen.getByTestId('next')).toBeInTheDocument();
});
test('Item attribute', async () => {
render(
<Wrapper.Item onClick={jest.fn()}>
<></>
</Wrapper.Item>,
);
expect(screen.getByTestId('item')).toBeInTheDocument();
});
test('Ellipsis attribute', async () => {
render(<Wrapper.Ellipsis onClick={jest.fn()} />);
expect(screen.getByTestId('ellipsis')).toBeInTheDocument();
});

View File

@@ -1,90 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { styled } from '@superset-ui/core';
import { Next } from './Next';
import { Prev } from './Prev';
import { Item } from './Item';
import { Ellipsis } from './Ellipsis';
interface PaginationProps {
children: JSX.Element | JSX.Element[];
}
const PaginationList = styled.ul`
${({ theme }) => `
display: inline-block;
padding: ${theme.sizeUnit * 3}px;
li {
display: inline;
margin: 0 4px;
> span {
padding: 8px 12px;
text-decoration: none;
background-color: ${theme.colorBgContainer};
border: 1px solid ${theme.colorBorder};
border-radius: ${theme.borderRadius}px;
color: ${theme.colorText};
&:hover,
&:focus {
z-index: 2;
color: ${theme.colorText};
background-color: ${theme.colorBgLayout};
}
}
&.disabled {
span {
background-color: transparent;
cursor: default;
&:focus {
outline: none;
}
}
}
&.active {
span {
z-index: 3;
color: ${theme.colorBgLayout};
cursor: default;
background-color: ${theme.colorPrimary};
&:focus {
outline: none;
}
}
}
}
`}
`;
function Pagination({ children }: PaginationProps) {
return <PaginationList role="navigation">{children}</PaginationList>;
}
Pagination.Next = Next;
Pagination.Prev = Prev;
Pagination.Item = Item;
Pagination.Ellipsis = Ellipsis;
export default Pagination;

View File

@@ -1,47 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import Pagination from '@superset-ui/core/components/Pagination/Wrapper';
import {
createUltimatePagination,
ITEM_TYPES,
} from 'react-ultimate-pagination';
const ListViewPagination = createUltimatePagination({
WrapperComponent: Pagination,
itemTypeToComponent: {
[ITEM_TYPES.PAGE]: ({ value, isActive, onClick }) => (
<Pagination.Item active={isActive} onClick={onClick}>
{value}
</Pagination.Item>
),
[ITEM_TYPES.ELLIPSIS]: ({ isActive, onClick }) => (
<Pagination.Ellipsis disabled={isActive} onClick={onClick} />
),
[ITEM_TYPES.PREVIOUS_PAGE_LINK]: ({ isActive, onClick }) => (
<Pagination.Prev disabled={isActive} onClick={onClick} />
),
[ITEM_TYPES.NEXT_PAGE_LINK]: ({ isActive, onClick }) => (
<Pagination.Next disabled={isActive} onClick={onClick} />
),
[ITEM_TYPES.FIRST_PAGE_LINK]: () => null,
[ITEM_TYPES.LAST_PAGE_LINK]: () => null,
},
});
export default ListViewPagination;

View File

@@ -100,3 +100,109 @@ test('Should the loading-indicator be visible during loading', () => {
expect(screen.getByTestId('loading-indicator')).toBeVisible();
});
test('Pagination controls should be rendered when pageSize is provided', () => {
const paginationProps = {
...defaultProps,
pageSize: 2,
totalCount: 3,
pageIndex: 0,
onPageChange: jest.fn(),
};
render(<TableCollection {...paginationProps} />);
expect(screen.getByRole('list')).toBeInTheDocument();
});
test('Pagination should call onPageChange when page is changed', async () => {
const onPageChange = jest.fn();
const paginationProps = {
...defaultProps,
pageSize: 2,
totalCount: 3,
pageIndex: 0,
onPageChange,
};
const { rerender } = render(<TableCollection {...paginationProps} />);
// Simulate pagination change
await screen.findByTitle('Next Page');
// Verify onPageChange would be called with correct arguments
// The actual AntD pagination will handle the click internally
expect(onPageChange).toBeDefined();
// Verify that re-rendering with new pageIndex works
rerender(<TableCollection {...paginationProps} pageIndex={1} />);
expect(screen.getByRole('list')).toBeInTheDocument();
});
test('Pagination callback should be stable across re-renders', () => {
const onPageChange = jest.fn();
const paginationProps = {
...defaultProps,
pageSize: 2,
totalCount: 3,
pageIndex: 0,
onPageChange,
};
const { rerender } = render(<TableCollection {...paginationProps} />);
// Re-render with same props
rerender(<TableCollection {...paginationProps} />);
// onPageChange should not have been called during re-render
expect(onPageChange).not.toHaveBeenCalled();
});
test('Should display correct page info when showRowCount is true', () => {
const paginationProps = {
...defaultProps,
pageSize: 2,
totalCount: 3,
pageIndex: 0,
onPageChange: jest.fn(),
showRowCount: true,
};
render(<TableCollection {...paginationProps} />);
// AntD pagination shows page info
expect(screen.getByText('1-2 of 3')).toBeInTheDocument();
});
test('Should not display page info when showRowCount is false', () => {
const paginationProps = {
...defaultProps,
pageSize: 2,
totalCount: 3,
pageIndex: 0,
onPageChange: jest.fn(),
showRowCount: false,
};
render(<TableCollection {...paginationProps} />);
// Page info should not be shown
expect(screen.queryByText('1-2 of 3')).not.toBeInTheDocument();
});
test('Bulk selection should work with pagination', () => {
const toggleRowSelected = jest.fn();
const toggleAllRowsSelected = jest.fn();
const selectionProps = {
...defaultProps,
bulkSelectEnabled: true,
selectedFlatRows: [],
toggleRowSelected,
toggleAllRowsSelected,
pageSize: 2,
totalCount: 3,
pageIndex: 0,
onPageChange: jest.fn(),
};
render(<TableCollection {...selectionProps} />);
// Check that selection checkboxes are rendered
const checkboxes = screen.getAllByRole('checkbox');
expect(checkboxes.length).toBeGreaterThan(0);
});

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { HTMLAttributes, memo, useMemo } from 'react';
import { HTMLAttributes, memo, useMemo, useCallback } from 'react';
import {
ColumnInstance,
HeaderGroup,
@@ -47,15 +47,25 @@ interface TableCollectionProps<T extends object> {
toggleAllRowsSelected?: (value?: boolean) => void;
sticky?: boolean;
size?: TableSize;
pageIndex?: number;
pageSize?: number;
totalCount?: number;
onPageChange?: (page: number, pageSize: number) => void;
isPaginationSticky?: boolean;
showRowCount?: boolean;
}
const StyledTable = styled(Table)`
${({ theme }) => `
const StyledTable = styled(Table)<{
isPaginationSticky?: boolean;
showRowCount?: boolean;
}>`
${({ theme, isPaginationSticky, showRowCount }) => `
th.ant-column-cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.actions {
opacity: 0;
font-size: ${theme.fontSizeXL}px;
@@ -72,15 +82,18 @@ const StyledTable = styled(Table)`
}
}
}
.ant-table-column-title {
line-height: initial;
}
.ant-table-row:hover {
.actions {
opacity: 1;
transition: opacity ease-in ${theme.motionDurationMid};
}
}
.ant-table-cell {
max-width: 320px;
font-feature-settings: 'tnum' 1;
@@ -91,10 +104,37 @@ const StyledTable = styled(Table)`
padding-left: ${theme.sizeUnit * 4}px;
white-space: nowrap;
}
.ant-table-placeholder .ant-table-cell {
border-bottom: 0;
}
&.ant-table-wrapper .ant-table-pagination.ant-pagination {
display: flex;
justify-content: center;
margin: ${showRowCount ? theme.sizeUnit * 4 : 0}px 0 ${showRowCount ? theme.sizeUnit * 14 : 0}px 0;
position: relative;
.ant-pagination-total-text {
color: ${theme.colorTextBase};
margin-inline-end: 0;
position: absolute;
top: ${theme.sizeUnit * 12}px;
}
${
isPaginationSticky &&
`
position: sticky;
bottom: 0;
left: 0;
z-index: 1;
background-color: ${theme.colorBgElevated};
padding: ${theme.sizeUnit * 2}px 0;
`
}
}
// Hotfix - antd doesn't apply background color to overflowing cells
& table {
background-color: ${theme.colorBgContainer};
@@ -116,13 +156,22 @@ function TableCollection<T extends object>({
prepareRow,
sticky,
size = TableSize.Middle,
pageIndex = 0,
pageSize = 25,
totalCount = 0,
onPageChange,
isPaginationSticky = false,
showRowCount = true,
}: TableCollectionProps<T>) {
const mappedColumns = mapColumns<T>(
columns,
headerGroups,
columnsForWrapText,
const mappedColumns = useMemo(
() => mapColumns<T>(columns, headerGroups, columnsForWrapText),
[columns, headerGroups, columnsForWrapText],
);
const mappedRows = useMemo(
() => mapRows(rows, prepareRow),
[rows, prepareRow],
);
const mappedRows = mapRows(rows, prepareRow);
const selectedRowKeys = useMemo(
() => selectedFlatRows?.map(row => row.id) || [],
@@ -147,6 +196,68 @@ function TableCollection<T extends object>({
toggleRowSelected,
toggleAllRowsSelected,
]);
const handlePaginationChange = useCallback(
(page: number, size: number) => {
const validPage = Math.max(0, (page || 1) - 1);
const validSize = size || pageSize;
onPageChange?.(validPage, validSize);
},
[pageSize, onPageChange],
);
const showTotalFunc = useCallback(
(total: number, range: [number, number]) =>
`${range[0]}-${range[1]} of ${total}`,
[],
);
const handleTableChange = useCallback(
(_pagination: any, _filters: any, sorter: SorterResult) => {
if (sorter && sorter.field) {
setSortBy?.([
{
id: sorter.field,
desc: sorter.order === 'descend',
},
] as SortingRule<T>[]);
}
},
[setSortBy],
);
const paginationConfig = useMemo(() => {
if (totalCount === 0) return false;
const config: any = {
pageSize,
size: 'default' as const,
showSizeChanger: false,
showQuickJumper: false,
align: 'center' as const,
showTotal: showRowCount ? showTotalFunc : undefined,
};
if (onPageChange) {
config.current = pageIndex + 1;
config.total = totalCount;
config.onChange = handlePaginationChange;
} else {
if (pageIndex > 0) config.defaultCurrent = pageIndex + 1;
config.total = totalCount;
}
return config;
}, [
pageSize,
totalCount,
showRowCount,
showTotalFunc,
pageIndex,
handlePaginationChange,
onPageChange,
]);
return (
<StyledTable
loading={loading}
@@ -155,12 +266,15 @@ function TableCollection<T extends object>({
data={mappedRows}
size={size}
data-test="listview-table"
pagination={false}
pagination={paginationConfig}
scroll={{ x: 'max-content' }}
tableLayout="auto"
rowKey="rowId"
rowSelection={rowSelection}
locale={{ emptyText: null }}
sortDirections={['ascend', 'descend', 'ascend']}
isPaginationSticky={isPaginationSticky}
showRowCount={showRowCount}
components={{
header: {
cell: (props: HTMLAttributes<HTMLTableCellElement>) => (
@@ -176,14 +290,7 @@ function TableCollection<T extends object>({
),
},
}}
onChange={(_pagination, _filters, sorter: SorterResult) => {
setSortBy?.([
{
id: sorter.field,
desc: sorter.order === 'descend',
},
] as SortingRule<T>[]);
}}
onChange={handleTableChange}
/>
);
}

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { render, screen, userEvent } from '@superset-ui/core/spec';
import { render, screen, userEvent, waitFor } from '@superset-ui/core/spec';
import { TableView, TableViewProps } from '.';
const mockedProps: TableViewProps = {
@@ -30,6 +30,7 @@ const mockedProps: TableViewProps = {
{
accessor: 'age',
Header: 'Age',
sortable: true,
id: 'age',
},
{
@@ -78,10 +79,10 @@ test('should render the cells', () => {
test('should render the pagination', () => {
render(<TableView {...mockedProps} />);
expect(screen.getByRole('navigation')).toBeInTheDocument();
expect(screen.getAllByRole('button')).toHaveLength(4);
expect(screen.getByText('«')).toBeInTheDocument();
expect(screen.getByText('»')).toBeInTheDocument();
expect(screen.getByRole('list')).toBeInTheDocument();
expect(screen.getAllByRole('button')).toHaveLength(2);
expect(screen.getByTitle('Previous Page')).toBeInTheDocument();
expect(screen.getByTitle('Next Page')).toBeInTheDocument();
});
test('should show the row count by default', () => {
@@ -104,45 +105,63 @@ test('should NOT render the pagination when disabled', () => {
withPagination: false,
};
render(<TableView {...withoutPaginationProps} />);
expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
expect(screen.queryByRole('list')).not.toBeInTheDocument();
});
test('should NOT render the pagination when fewer rows than page size', () => {
test('should render the pagination even when fewer rows than page size', () => {
const withoutPaginationProps = {
...mockedProps,
pageSize: 3,
};
render(<TableView {...withoutPaginationProps} />);
expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
expect(screen.getByRole('list')).toBeInTheDocument();
});
test('should change page when « and » buttons are clicked', async () => {
test('should change page when pagination is clicked', async () => {
render(<TableView {...mockedProps} />);
const nextBtn = screen.getByText('»');
const prevBtn = screen.getByText('«');
await userEvent.click(nextBtn);
expect(screen.getAllByRole('cell')).toHaveLength(3);
expect(screen.getByText('321')).toBeInTheDocument();
expect(screen.getByText('10')).toBeInTheDocument();
expect(screen.getByText('Kate')).toBeInTheDocument();
expect(screen.queryByText('Emily')).not.toBeInTheDocument();
await userEvent.click(prevBtn);
expect(screen.getAllByRole('cell')).toHaveLength(3);
expect(screen.getByText('123')).toBeInTheDocument();
expect(screen.getByText('27')).toBeInTheDocument();
expect(screen.getByText('Emily')).toBeInTheDocument();
expect(screen.queryByText('Kate')).not.toBeInTheDocument();
const page2 = screen.getByRole('listitem', { name: '2' });
await userEvent.click(page2);
await waitFor(() => {
expect(screen.getAllByRole('cell')).toHaveLength(3);
expect(screen.getByText('321')).toBeInTheDocument();
expect(screen.getByText('10')).toBeInTheDocument();
expect(screen.getByText('Kate')).toBeInTheDocument();
expect(screen.queryByText('Emily')).not.toBeInTheDocument();
});
const page1 = screen.getByRole('listitem', { name: '1' });
await userEvent.click(page1);
await waitFor(() => {
expect(screen.getAllByRole('cell')).toHaveLength(3);
expect(screen.getByText('123')).toBeInTheDocument();
expect(screen.getByText('27')).toBeInTheDocument();
expect(screen.getByText('Emily')).toBeInTheDocument();
expect(screen.queryByText('Kate')).not.toBeInTheDocument();
});
});
test('should sort by age', async () => {
render(<TableView {...mockedProps} />);
await userEvent.click(screen.getAllByTestId('sort-header')[1]);
expect(screen.getAllByTestId('table-row-cell')[1]).toHaveTextContent('10');
await waitFor(() => {
expect(screen.getAllByTestId('table-row-cell')[1]).toHaveTextContent('10');
});
await userEvent.click(screen.getAllByTestId('sort-header')[1]);
expect(screen.getAllByTestId('table-row-cell')[1]).toHaveTextContent('27');
await waitFor(() => {
expect(screen.getAllByTestId('table-row-cell')[1]).toHaveTextContent('27');
});
});
test('should sort by initialSortBy DESC', () => {
@@ -208,3 +227,146 @@ test('should render the right wrap content text by columnsForWrapText', () => {
'ant-table-cell-ellipsis',
);
});
test('should handle server-side pagination', async () => {
const onServerPagination = jest.fn();
const serverPaginationProps = {
...mockedProps,
serverPagination: true,
onServerPagination,
totalCount: 10,
pageSize: 2,
};
render(<TableView {...serverPaginationProps} />);
// Click next page
const page2 = screen.getByRole('listitem', { name: '2' });
await userEvent.click(page2);
await waitFor(() => {
expect(onServerPagination).toHaveBeenCalledWith({
pageIndex: 1,
});
});
});
test('should handle server-side sorting', async () => {
const onServerPagination = jest.fn();
const serverPaginationProps = {
...mockedProps,
serverPagination: true,
onServerPagination,
};
render(<TableView {...serverPaginationProps} />);
// Click on sortable column
await userEvent.click(screen.getAllByTestId('sort-header')[0]);
await waitFor(() => {
expect(onServerPagination).toHaveBeenCalledWith({
pageIndex: 0,
sortBy: [{ id: 'id', desc: false }],
});
});
});
test('pagination callbacks should be stable across re-renders', () => {
const onServerPagination = jest.fn();
const serverPaginationProps = {
...mockedProps,
serverPagination: true,
onServerPagination,
totalCount: 10,
pageSize: 2,
};
const { rerender } = render(<TableView {...serverPaginationProps} />);
// Re-render with same props
rerender(<TableView {...serverPaginationProps} />);
// onServerPagination should not have been called during re-render
expect(onServerPagination).not.toHaveBeenCalled();
});
test('should scroll to top when scrollTopOnPagination is true', async () => {
const scrollToSpy = jest
.spyOn(window, 'scrollTo')
.mockImplementation(() => {});
const scrollProps = {
...mockedProps,
scrollTopOnPagination: true,
pageSize: 1,
};
render(<TableView {...scrollProps} />);
// Click next page
const page2 = screen.getByRole('listitem', { name: '2' });
await userEvent.click(page2);
await waitFor(() => {
expect(scrollToSpy).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
});
scrollToSpy.mockRestore();
});
test('should NOT scroll to top when scrollTopOnPagination is false', async () => {
const scrollToSpy = jest
.spyOn(window, 'scrollTo')
.mockImplementation(() => {});
const scrollProps = {
...mockedProps,
scrollTopOnPagination: false,
pageSize: 1,
};
render(<TableView {...scrollProps} />);
// Click next page
const page2 = screen.getByRole('listitem', { name: '2' });
await userEvent.click(page2);
await waitFor(() => {
expect(screen.getByText('321')).toBeInTheDocument();
});
expect(scrollToSpy).not.toHaveBeenCalled();
scrollToSpy.mockRestore();
});
test('should handle totalCount of 0 correctly', () => {
const emptyProps = {
...mockedProps,
data: [],
totalCount: 0,
};
render(<TableView {...emptyProps} />);
// Pagination should not be shown when totalCount is 0
expect(screen.queryByRole('list')).not.toBeInTheDocument();
});
test('should handle large datasets with pagination', () => {
const largeDataset = Array.from({ length: 100 }, (_, i) => ({
id: i,
age: 20 + i,
name: `Person ${i}`,
}));
const largeDataProps = {
...mockedProps,
data: largeDataset,
pageSize: 10,
};
render(<TableView {...largeDataProps} />);
// Should show only first page (10 items)
expect(screen.getAllByTestId('table-row')).toHaveLength(10);
// Should show pagination with correct page count
expect(screen.getByRole('list')).toBeInTheDocument();
expect(screen.getByText('1-10 of 100')).toBeInTheDocument();
});

View File

@@ -16,16 +16,17 @@
* specific language governing permissions and limitations
* under the License.
*/
import { memo, useEffect, useRef } from 'react';
import { memo, useEffect, useRef, useMemo, useCallback } from 'react';
import { isEqual } from 'lodash';
import { styled, t } from '@superset-ui/core';
import { styled } from '@superset-ui/core';
import { useFilters, usePagination, useSortBy, useTable } from 'react-table';
import { Empty } from '@superset-ui/core/components';
import Pagination from '@superset-ui/core/components/Pagination';
import TableCollection from '@superset-ui/core/components/TableCollection';
import { TableSize } from '@superset-ui/core/components/Table';
import { SortByType, ServerPagination } from './types';
const NOOP_SERVER_PAGINATION = () => {};
const DEFAULT_PAGE_SIZE = 10;
export enum EmptyWrapperType {
@@ -96,29 +97,6 @@ const TableViewStyles = styled.div<{
}
`;
const PaginationStyles = styled.div<{
isPaginationSticky?: boolean;
}>`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: ${({ theme }) => theme.colorBgElevated};
${({ isPaginationSticky }) =>
isPaginationSticky &&
`
position: sticky;
bottom: 0;
left: 0;
`};
.row-count-container {
margin-top: ${({ theme }) => theme.sizeUnit * 2}px;
color: ${({ theme }) => theme.colorText};
}
`;
const RawTableView = ({
columns,
data,
@@ -133,16 +111,21 @@ const RawTableView = ({
showRowCount = true,
serverPagination = false,
columnsForWrapText,
onServerPagination = () => {},
scrollTopOnPagination = false,
onServerPagination = NOOP_SERVER_PAGINATION,
scrollTopOnPagination = true,
size = TableSize.Middle,
...props
}: TableViewProps) => {
const initialState = {
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
pageIndex: initialPageIndex ?? 0,
sortBy: initialSortBy,
};
const tableRef = useRef<HTMLTableElement>(null);
const initialState = useMemo(
() => ({
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
pageIndex: initialPageIndex ?? 0,
sortBy: initialSortBy,
}),
[initialPageSize, initialPageIndex, initialSortBy],
);
const {
getTableProps,
@@ -151,10 +134,9 @@ const RawTableView = ({
page,
rows,
prepareRow,
pageCount,
gotoPage,
setSortBy,
state: { pageIndex, pageSize, sortBy },
state: { pageIndex, sortBy },
} = useTable(
{
columns,
@@ -162,36 +144,94 @@ const RawTableView = ({
initialState,
manualPagination: serverPagination,
manualSortBy: serverPagination,
pageCount: Math.ceil(totalCount / initialState.pageSize),
pageCount: serverPagination
? Math.ceil(totalCount / initialState.pageSize)
: undefined,
autoResetSortBy: false,
},
useFilters,
useSortBy,
usePagination,
...(withPagination ? [usePagination] : []),
);
const content = withPagination ? page : rows;
let EmptyWrapperComponent;
switch (emptyWrapperType) {
case EmptyWrapperType.Small:
EmptyWrapperComponent = ({ children }: any) => <>{children}</>;
break;
case EmptyWrapperType.Default:
default:
EmptyWrapperComponent = ({ children }: any) => (
<EmptyWrapper>{children}</EmptyWrapper>
);
}
const isEmpty = !loading && content.length === 0;
const hasPagination = pageCount > 1 && withPagination;
const tableRef = useRef<HTMLTableElement>(null);
const handleGotoPage = (p: number) => {
if (scrollTopOnPagination) {
tableRef?.current?.scroll(0, 0);
const EmptyWrapperComponent = useMemo(() => {
switch (emptyWrapperType) {
case EmptyWrapperType.Small:
return ({ children }: any) => <>{children}</>;
case EmptyWrapperType.Default:
default:
return ({ children }: any) => <EmptyWrapper>{children}</EmptyWrapper>;
}
gotoPage(p);
};
}, [emptyWrapperType]);
const content = useMemo(
() => (withPagination ? page : rows),
[withPagination, page, rows],
);
const isEmpty = useMemo(
() => !loading && content.length === 0,
[loading, content.length],
);
const handleScrollToTop = useCallback(() => {
if (scrollTopOnPagination) {
if (tableRef?.current) {
if (typeof tableRef.current.scrollTo === 'function') {
tableRef.current.scrollTo({ top: 0, behavior: 'smooth' });
} else if (typeof tableRef.current.scroll === 'function') {
tableRef.current.scroll(0, 0);
}
}
if (typeof window !== 'undefined' && window.scrollTo)
window.scrollTo({ top: 0, behavior: 'smooth' });
}
}, [scrollTopOnPagination]);
const handlePageChange = useCallback(
(p: number) => {
if (scrollTopOnPagination) handleScrollToTop();
gotoPage(p);
},
[scrollTopOnPagination, handleScrollToTop, gotoPage],
);
const paginationProps = useMemo(() => {
if (!withPagination) {
return {
pageIndex: 0,
pageSize: data.length,
totalCount: 0,
onPageChange: undefined,
};
}
if (serverPagination) {
return {
pageIndex,
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
totalCount,
onPageChange: handlePageChange,
};
}
return {
pageIndex,
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
totalCount: data.length,
onPageChange: handlePageChange,
};
}, [
withPagination,
serverPagination,
pageIndex,
initialPageSize,
totalCount,
data.length,
handlePageChange,
]);
useEffect(() => {
if (serverPagination && pageIndex !== initialState.pageIndex) {
@@ -199,7 +239,7 @@ const RawTableView = ({
pageIndex,
});
}
}, [pageIndex]);
}, [initialState.pageIndex, onServerPagination, pageIndex, serverPagination]);
useEffect(() => {
if (serverPagination && !isEqual(sortBy, initialState.sortBy)) {
@@ -208,61 +248,38 @@ const RawTableView = ({
sortBy,
});
}
}, [sortBy]);
}, [initialState.sortBy, onServerPagination, serverPagination, sortBy]);
return (
<>
<TableViewStyles {...props} ref={tableRef}>
<TableCollection
getTableProps={getTableProps}
getTableBodyProps={getTableBodyProps}
prepareRow={prepareRow}
headerGroups={headerGroups}
rows={content}
columns={columns}
loading={loading}
setSortBy={setSortBy}
size={size}
columnsForWrapText={columnsForWrapText}
/>
{isEmpty && (
<EmptyWrapperComponent>
{noDataText ? (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description={noDataText}
/>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
</EmptyWrapperComponent>
)}
</TableViewStyles>
{hasPagination && (
<PaginationStyles
className="pagination-container"
isPaginationSticky={props.isPaginationSticky}
>
<Pagination
totalPages={pageCount || 0}
currentPage={pageCount ? pageIndex + 1 : 0}
onChange={(p: number) => handleGotoPage(p - 1)}
hideFirstAndLastPageLinks
/>
{showRowCount && (
<div className="row-count-container">
{!loading &&
t(
'%s-%s of %s',
pageSize * pageIndex + (page.length && 1),
pageSize * pageIndex + page.length,
totalCount,
)}
</div>
<TableViewStyles {...props} ref={tableRef}>
<TableCollection
getTableProps={getTableProps}
getTableBodyProps={getTableBodyProps}
prepareRow={prepareRow}
headerGroups={headerGroups}
rows={content}
columns={columns}
loading={loading}
setSortBy={setSortBy}
size={size}
columnsForWrapText={columnsForWrapText}
isPaginationSticky={props.isPaginationSticky}
showRowCount={showRowCount}
{...paginationProps}
/>
{isEmpty && (
<EmptyWrapperComponent>
{noDataText ? (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description={noDataText}
/>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
</PaginationStyles>
</EmptyWrapperComponent>
)}
</>
</TableViewStyles>
);
};

View File

@@ -0,0 +1,306 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { fireEvent, render } from '@superset-ui/core/spec';
import Tabs, { EditableTabs, LineEditableTabs } from './Tabs';
describe('Tabs', () => {
const defaultItems = [
{
key: '1',
label: 'Tab 1',
children: <div data-testid="tab1-content">Tab 1 content</div>,
},
{
key: '2',
label: 'Tab 2',
children: <div data-testid="tab2-content">Tab 2 content</div>,
},
{
key: '3',
label: 'Tab 3',
children: <div data-testid="tab3-content">Tab 3 content</div>,
},
];
describe('Basic Tabs', () => {
it('should render tabs with default props', () => {
const { getByText, container } = render(<Tabs items={defaultItems} />);
expect(getByText('Tab 1')).toBeInTheDocument();
expect(getByText('Tab 2')).toBeInTheDocument();
expect(getByText('Tab 3')).toBeInTheDocument();
const activeTabContent = container.querySelector(
'.ant-tabs-tabpane-active',
);
expect(activeTabContent).toBeDefined();
expect(
activeTabContent?.querySelector('[data-testid="tab1-content"]'),
).toBeDefined();
});
it('should render tabs component structure', () => {
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');
expect(tabsElement).toBeDefined();
expect(tabsNav).toBeDefined();
expect(tabsContent).toBeDefined();
});
it('should apply default tabBarStyle with padding', () => {
const { container } = render(<Tabs items={defaultItems} />);
const tabsNav = container.querySelector('.ant-tabs-nav') as HTMLElement;
// Check that tabBarStyle is applied (default padding is added)
expect(tabsNav?.style?.paddingLeft).toBeDefined();
});
it('should merge custom tabBarStyle with defaults', () => {
const customStyle = { paddingRight: '20px', backgroundColor: 'red' };
const { container } = render(
<Tabs items={defaultItems} tabBarStyle={customStyle} />,
);
const tabsNav = container.querySelector('.ant-tabs-nav') as HTMLElement;
expect(tabsNav?.style?.paddingLeft).toBeDefined();
expect(tabsNav?.style?.paddingRight).toBe('20px');
expect(tabsNav?.style?.backgroundColor).toBe('red');
});
it('should handle allowOverflow prop', () => {
const { container: allowContainer } = render(
<Tabs items={defaultItems} allowOverflow />,
);
const { container: disallowContainer } = render(
<Tabs items={defaultItems} allowOverflow={false} />,
);
expect(allowContainer.querySelector('.ant-tabs')).toBeDefined();
expect(disallowContainer.querySelector('.ant-tabs')).toBeDefined();
});
it('should disable animation by default', () => {
const { container } = render(<Tabs items={defaultItems} />);
const tabsElement = container.querySelector('.ant-tabs');
expect(tabsElement?.className).not.toContain('ant-tabs-animated');
});
it('should handle tab change events', () => {
const onChangeMock = jest.fn();
const { getByText } = render(
<Tabs items={defaultItems} onChange={onChangeMock} />,
);
fireEvent.click(getByText('Tab 2'));
expect(onChangeMock).toHaveBeenCalledWith('2');
});
it('should pass through additional props to Antd Tabs', () => {
const onTabClickMock = jest.fn();
const { getByText } = render(
<Tabs
items={defaultItems}
onTabClick={onTabClickMock}
size="large"
centered
/>,
);
fireEvent.click(getByText('Tab 2'));
expect(onTabClickMock).toHaveBeenCalled();
});
});
describe('EditableTabs', () => {
it('should render with editable features', () => {
const { container } = render(<EditableTabs items={defaultItems} />);
const tabsElement = container.querySelector('.ant-tabs');
expect(tabsElement?.className).toContain('ant-tabs-card');
expect(tabsElement?.className).toContain('ant-tabs-editable-card');
});
it('should handle onEdit callback for add/remove actions', () => {
const onEditMock = jest.fn();
const itemsWithRemove = defaultItems.map(item => ({
...item,
closable: true,
}));
const { container } = render(
<EditableTabs items={itemsWithRemove} onEdit={onEditMock} />,
);
const removeButton = container.querySelector('.ant-tabs-tab-remove');
expect(removeButton).toBeDefined();
fireEvent.click(removeButton!);
expect(onEditMock).toHaveBeenCalledWith(expect.any(String), 'remove');
});
it('should have default props set correctly', () => {
expect(EditableTabs.defaultProps?.type).toBe('editable-card');
expect(EditableTabs.defaultProps?.animated).toEqual({
inkBar: true,
tabPane: false,
});
});
});
describe('LineEditableTabs', () => {
it('should render as line-style editable tabs', () => {
const { container } = render(<LineEditableTabs items={defaultItems} />);
const tabsElement = container.querySelector('.ant-tabs');
expect(tabsElement?.className).toContain('ant-tabs-card');
expect(tabsElement?.className).toContain('ant-tabs-editable-card');
});
it('should render with line-specific styling', () => {
const { container } = render(<LineEditableTabs items={defaultItems} />);
const inkBar = container.querySelector('.ant-tabs-ink-bar');
expect(inkBar).toBeDefined();
});
});
describe('TabPane Legacy Support', () => {
it('should support TabPane component access', () => {
expect(Tabs.TabPane).toBeDefined();
expect(EditableTabs.TabPane).toBeDefined();
expect(LineEditableTabs.TabPane).toBeDefined();
});
it('should render using legacy TabPane syntax', () => {
const { getByText, container } = render(
<Tabs>
<Tabs.TabPane tab="Legacy Tab 1" key="1">
<div data-testid="legacy-content-1">Legacy content 1</div>
</Tabs.TabPane>
<Tabs.TabPane tab="Legacy Tab 2" key="2">
<div data-testid="legacy-content-2">Legacy content 2</div>
</Tabs.TabPane>
</Tabs>,
);
expect(getByText('Legacy Tab 1')).toBeInTheDocument();
expect(getByText('Legacy Tab 2')).toBeInTheDocument();
const activeTabContent = container.querySelector(
'.ant-tabs-tabpane-active [data-testid="legacy-content-1"]',
);
expect(activeTabContent).toBeDefined();
expect(activeTabContent?.textContent).toBe('Legacy content 1');
});
});
describe('Edge Cases', () => {
it('should handle empty items array', () => {
const { container } = render(<Tabs items={[]} />);
const tabsElement = container.querySelector('.ant-tabs');
expect(tabsElement).toBeDefined();
});
it('should handle undefined items', () => {
const { container } = render(<Tabs />);
const tabsElement = container.querySelector('.ant-tabs');
expect(tabsElement).toBeDefined();
});
it('should handle tabs with no content', () => {
const itemsWithoutContent = [
{ key: '1', label: 'Tab 1' },
{ key: '2', label: 'Tab 2' },
];
const { getByText } = render(<Tabs items={itemsWithoutContent} />);
expect(getByText('Tab 1')).toBeInTheDocument();
expect(getByText('Tab 2')).toBeInTheDocument();
});
it('should handle allowOverflow default value', () => {
const { container } = render(<Tabs items={defaultItems} />);
expect(container.querySelector('.ant-tabs')).toBeDefined();
});
});
describe('Accessibility', () => {
it('should render with proper ARIA roles', () => {
const { container } = render(<Tabs items={defaultItems} />);
const tablist = container.querySelector('[role="tablist"]');
const tabs = container.querySelectorAll('[role="tab"]');
expect(tablist).toBeDefined();
expect(tabs.length).toBe(3);
});
it('should support keyboard navigation', () => {
const { container, getByText } = render(<Tabs items={defaultItems} />);
const firstTab = container.querySelector('[role="tab"]');
const secondTab = getByText('Tab 2');
if (firstTab) {
fireEvent.keyDown(firstTab, { key: 'ArrowRight', code: 'ArrowRight' });
}
fireEvent.click(secondTab);
expect(secondTab).toBeInTheDocument();
});
});
describe('Styling Integration', () => {
it('should accept and apply custom CSS classes', () => {
const { container } = render(
<Tabs items={defaultItems} className="custom-tabs-class" />,
);
const tabsElement = container.querySelector('.ant-tabs');
expect(tabsElement?.className).toContain('custom-tabs-class');
});
it('should accept and apply custom styles', () => {
const customStyle = { minHeight: '200px' };
const { container } = render(
<Tabs items={defaultItems} style={customStyle} />,
);
const tabsElement = container.querySelector('.ant-tabs') as HTMLElement;
expect(tabsElement?.style?.minHeight).toBe('200px');
});
});
});

View File

@@ -29,14 +29,18 @@ export interface TabsProps extends AntdTabsProps {
const StyledTabs = ({
animated = false,
allowOverflow = true,
tabBarStyle,
...props
}: TabsProps) => {
const theme = useTheme();
const defaultTabBarStyle = { paddingLeft: theme.sizeUnit * 4 };
const mergedStyle = { ...defaultTabBarStyle, ...tabBarStyle };
return (
<AntdTabs
animated={animated}
{...props}
tabBarStyle={{ paddingLeft: theme.sizeUnit * 4 }}
tabBarStyle={mergedStyle}
css={theme => css`
overflow: ${allowOverflow ? 'visible' : 'hidden'};

View File

@@ -19,7 +19,7 @@
/* eslint-disable react-prefer-function-component/react-prefer-function-component */
// eslint-disable-next-line no-restricted-syntax
import React from 'react';
import { theme as antdThemeImport, ConfigProvider } from 'antd';
import { theme as antdThemeImport, ConfigProvider, App } from 'antd';
// @fontsource/* v5.1+ doesn't play nice with eslint-import plugin v2.31+
/* eslint-disable import/extensions */
@@ -243,7 +243,7 @@ export class Theme {
<ThemeProvider theme={themeState.theme}>
<GlobalStyles />
<ConfigProvider theme={themeState.antdConfig}>
{children}
<App>{children}</App>
</ConfigProvider>
</ThemeProvider>
</EmotionCacheProvider>

View File

@@ -34,6 +34,7 @@ export enum FeatureFlag {
DashboardVirtualization = 'DASHBOARD_VIRTUALIZATION',
DashboardRbac = 'DASHBOARD_RBAC',
DatapanelClosedByDefault = 'DATAPANEL_CLOSED_BY_DEFAULT',
DateRangeTimeshiftsEnabled = 'DATE_RANGE_TIMESHIFTS_ENABLED',
/** @deprecated */
DrillToDetail = 'DRILL_TO_DETAIL',
DrillBy = 'DRILL_BY',

View File

@@ -52,7 +52,7 @@
"react-resizable": "^3.0.5"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/core": "^7.28.3",
"@babel/preset-env": "^7.27.2",
"@babel/preset-react": "^7.27.1",
"@babel/preset-typescript": "^7.23.3",

View File

@@ -58,11 +58,13 @@ export default styled(CountryMap)`
}
.superset-legacy-chart-country-map text.result-text {
fill: ${theme.colorText};
font-weight: ${theme.fontWeightLight};
font-size: ${theme.fontSizeXL}px;
}
.superset-legacy-chart-country-map text.big-text {
fill: ${theme.colorText};
font-weight: ${theme.fontWeightStrong};
font-size: ${theme.fontSizeLG}px;
}

View File

@@ -158,6 +158,7 @@ const config: ControlPanelConfig = {
},
},
],
['zoomable'],
],
},
],

View File

@@ -73,6 +73,7 @@ export default function transformProps(
yAxisTitleMargin,
yAxisTitlePosition,
sliceId,
zoomable,
} = formData as BoxPlotQueryFormData;
const refs: Refs = {};
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
@@ -284,6 +285,26 @@ export default function transformProps(
},
},
series,
toolbox: {
show: zoomable,
feature: {
dataZoom: {
title: {
zoom: 'zoom area',
back: 'restore zoom',
},
},
},
},
dataZoom: zoomable
? [
{
type: 'inside',
zoomOnMouseWheel: false,
moveOnMouseWheel: true,
},
]
: [],
};
return {

View File

@@ -128,6 +128,7 @@ export default function transformProps(chartProps: EchartsBubbleChartProps) {
legendOrientation,
legendMargin,
legendType,
legendSort,
sliceId,
}: EchartsBubbleFormData = { ...DEFAULT_FORM_DATA, ...formData };
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
@@ -230,7 +231,10 @@ export default function transformProps(chartProps: EchartsBubbleChartProps) {
},
legend: {
...getLegendProps(legendType, legendOrientation, showLegend, theme),
data: Array.from(legends),
data: Array.from(legends).sort((a: string, b: string) => {
if (!legendSort) return 0;
return legendSort === 'asc' ? a.localeCompare(b) : b.localeCompare(a);
}),
},
tooltip: {
show: !inContextMenu,

View File

@@ -112,6 +112,7 @@ export default function transformProps(
legendMargin,
legendOrientation,
legendType,
legendSort,
metric = '',
numberFormat,
currencyFormat,
@@ -290,7 +291,10 @@ export default function transformProps(
},
legend: {
...getLegendProps(legendType, legendOrientation, showLegend, theme),
data: keys,
data: keys.sort((a: string, b: string) => {
if (!legendSort) return 0;
return legendSort === 'asc' ? a.localeCompare(b) : b.localeCompare(a);
}),
},
series,
};

View File

@@ -131,6 +131,7 @@ export default function transformProps(chartProps: EchartsGanttChartProps) {
legendMargin,
legendOrientation,
legendType,
legendSort,
showLegend,
yAxisTitle,
yAxisTitleMargin,
@@ -330,6 +331,18 @@ export default function transformProps(chartProps: EchartsGanttChartProps) {
},
);
const legendData = series
.map(entry => {
const { name } = entry;
if (name === null || name === undefined) return '';
return String(name);
})
.filter(name => name !== '')
.sort((a, b) => {
if (!legendSort) return 0;
return legendSort === 'asc' ? a.localeCompare(b) : b.localeCompare(a);
});
const tooltipFormatterMap = {
[GenericDataType.Numeric]: tooltipValuesFormatter,
[GenericDataType.String]: undefined,
@@ -365,6 +378,7 @@ export default function transformProps(chartProps: EchartsGanttChartProps) {
zoomable,
legendState,
),
data: legendData,
},
grid: {
...defaultGrid,

View File

@@ -188,6 +188,7 @@ export default function transformProps(
legendMargin,
legendOrientation,
legendType,
legendSort,
showLegend,
baseEdgeWidth,
baseNodeSize,
@@ -353,7 +354,10 @@ export default function transformProps(
},
legend: {
...getLegendProps(legendType, legendOrientation, showLegend, theme),
data: categoryList,
data: categoryList.sort((a: string, b: string) => {
if (!legendSort) return 0;
return legendSort === 'asc' ? a.localeCompare(b) : b.localeCompare(a);
}),
},
series,
};

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