Compare commits

...

160 Commits

Author SHA1 Message Date
Maxime Beauchemin
297515db62 big migration 2025-06-02 17:13:32 -07:00
Maxime Beauchemin
0cecb061f0 migrate all 2025-06-02 17:03:30 -07:00
Maxime Beauchemin
1f2c20fb5b small test 2025-06-02 16:25:31 -07:00
Maxime Beauchemin
5c4fcdb867 fix scripts 2025-06-02 16:16:57 -07:00
Maxime Beauchemin
3880cd5642 more config 2025-06-02 15:57:24 -07:00
Maxime Beauchemin
062b7937f5 more prep 2025-06-02 13:55:31 -07:00
Maxime Beauchemin
6bfbbbf89e chore: PoC of using ts-migrate 2025-06-02 13:49:37 -07:00
Maxime Beauchemin
ef8ba1cbeb fix unit test live 2025-06-01 11:32:52 -07:00
Maxime Beauchemin
926d2bf031 fix type errors 2025-06-01 10:40:59 -07:00
Maxime Beauchemin
bd77f82cc9 Merge branch 'master' into template_less 2025-06-01 10:23:58 -07:00
Maxime Beauchemin
9c7b676bfc fix Tooltip-related imports/exports 2025-05-31 16:06:46 -07:00
Maxime Beauchemin
28db9ad7fc chore: moving reusable components from src/components to packages/superset-ui-core/src/components (#33593) 2025-05-31 15:40:05 -07:00
Mehmet Salih Yavuz
58435e3e28 feat(Login): Migrate login page to frontend (#33255) 2025-05-29 14:22:21 +03:00
Enzo Martellucci
8e48fdbd6f fix(theming): Fix visual regressions from theming P2/3 (#33587) 2025-05-27 11:27:09 +02:00
Maxime Beauchemin
17ef5b67d1 fix native filters styling 2025-05-23 16:22:42 -07:00
Maxime Beauchemin
c279d08d5e fix: remove all less styling! (#33445)
Co-authored-by: Enzo Martellucci <enzomartellucci@gmail.com>
Co-authored-by: Mehmet Salih Yavuz <salih.yavuz@proton.me>
2025-05-23 14:28:47 -07:00
Mehmet Salih Yavuz
2979c30703 fix(theming): Duplicate select values bug (#33558) 2025-05-22 12:18:07 +02:00
Maxime Beauchemin
20a17be0f3 Merge branch 'master' into template_less 2025-05-20 17:18:19 -07:00
Maxime Beauchemin
4070dba438 fixing package-lock 2025-05-19 18:47:15 -07:00
Maxime Beauchemin
0af5770a49 Merge branch 'master' into template_less 2025-05-19 18:31:35 -07:00
Maxime Beauchemin
33f2ffd2a9 chore: rip antd-v4 from the app! (#33411) 2025-05-15 10:42:56 -07:00
Mehmet Salih Yavuz
fcea7e4af9 feat(theming): Support theming on embedding-sdk (#33360) 2025-05-14 16:06:16 +03:00
Mehmet Salih Yavuz
7258dc9ea0 fix(theming): Merge ci issues and new antd4 component to antd5 (#33429) 2025-05-14 14:30:44 +03:00
Maxime Beauchemin
14645a2dfa another try at fixing the GHA 2025-05-13 22:42:21 -07:00
Maxime Beauchemin
6fe8a54b6f fix the e2e GHA 2025-05-13 22:37:35 -07:00
Maxime Beauchemin
b9710f947c set the test for docker-compose-image-tag to only happen on merge to master 2025-05-13 22:22:26 -07:00
Maxime Beauchemin
399788442a dummy commit to trigger CI 2025-05-13 22:13:26 -07:00
Mehmet Salih Yavuz
b03c425393 fix(theming): Fix various visual regressions from theming (#33302) 2025-05-13 22:29:24 +03:00
Maxime Beauchemin
e862b5cddd more generic error catching 2025-05-13 08:47:19 -07:00
Maxime Beauchemin
5a67713f3f Merge branch 'master' into template_less 2025-05-13 08:36:44 -07:00
Maxime Beauchemin
f00adac73e apply default theme boxShadow to cards 2025-05-12 10:17:02 -07:00
Maxime Beauchemin
1c28138938 bigger icon in sql lab tab headers 2025-05-12 10:17:02 -07:00
Mehmet Salih Yavuz
cf68c879e2 fix: post merge fix imports 2025-05-12 10:22:53 +03:00
Mehmet Salih Yavuz
5b991800c3 fix: minor import issues 2025-05-12 10:15:27 +03:00
Mehmet Salih Yavuz
7472714ce7 Merge branch 'master' into template_less 2025-05-12 10:12:28 +03:00
Enzo Martellucci
67aa991099 refactor(ui): replace native HTML elements with Ant Design 5 components for consistent theming (#33231) 2025-05-12 10:00:05 +03:00
Maxime Beauchemin
5267ec2028 fix DnD options caret and separator 2025-05-08 17:44:14 -07:00
Maxime Beauchemin
3983ee0c2f fix category icon in VizPicker 2025-05-08 17:03:55 -07:00
Maxime Beauchemin
863a0bea5c Merge branch 'master' into template_less 2025-05-07 15:44:03 -07:00
Enzo Martellucci
d635c2a9ab fix: replace invalid theme tokens 2025-05-07 20:52:18 +02:00
Enzo Martellucci
2c038f5bd6 Merge branch 'master' into template_less 2025-05-07 20:25:48 +02:00
Maxime Beauchemin
fc031ca35b fix: various theme-related tweaks and touches (#33372) 2025-05-06 09:08:48 -07:00
Maxime Beauchemin
89424894f3 Merge branch 'master' into template_less 2025-05-05 15:43:17 -07:00
Enzo Martellucci
9da62ed63a refactor(InfoTooltipWithTrigger): Replace support for fa icons with antd5 icons (#33256)
- Define and centralized tooltip icon variants (info, warning, notice, error, question)
- Replace old FontAwesome icons
- Update tests to reflect the new icons and variant behavior
2025-05-05 18:07:15 +02:00
Enzo Martellucci
49bcf79f71 Merge branch 'master' into template_less 2025-05-05 15:31:30 +02:00
Maxime Beauchemin
e529d84e34 fix: button colors and icons (#33287) 2025-05-02 10:43:53 +03:00
Mehmet Salih Yavuz
f580da88ca fix(Theming): DatabaseModal visual fixes (#33286)
Fix visual regressions in DatabaseModal introduced by theming changes
2025-04-30 16:04:50 +02:00
Enzo Martellucci
b040d52c2d refactor(Components): Use named imports - P.2 (#33249) 2025-04-25 15:11:52 -07:00
Mehmet Salih Yavuz
a7b7e6319c refactor(Table): Use Table instead of html <table> in CollectionTable (#33159) 2025-04-25 09:52:04 -07:00
Enzo Martellucci
c217f56aea refactor(components): Replace native HTML elements with Ant Design v5 components (#33090)
This PR introduces a refactor that replaces native HTML elements with their Ant Design v5 equivalents across the codebase. The goal is to ensure a more consistent UI, better accessibility, and improved maintainability by leveraging standardized components from Ant Design.
2025-04-25 09:48:12 +02:00
Mehmet Salih Yavuz
d43657ed90 fix: post merge 2025-04-24 20:57:53 +03:00
Mehmet Salih Yavuz
6c01173c21 Merge branch 'master' into template_less 2025-04-24 18:23:33 +03:00
Mehmet Salih Yavuz
4709eb0153 fix(Select): Merge conflicts and Select bug (#33165) 2025-04-24 17:00:00 +02:00
Maxime Beauchemin
51f719f8d4 set colorLink == colorPrimary 2025-04-16 13:17:00 -07:00
Damian Pendrak
50535a92a0 fix: upgrade tabs in database modal (#33038)
Co-authored-by: Maxime Beauchemin <maximebeauchemin@gmail.com>
Co-authored-by: Mehmet Salih Yavuz <salih.yavuz@proton.me>
2025-04-16 21:39:49 +03:00
Alexandru Soare
3fe5db1e72 refactor(Tabs): Tabs to use items instead of TabPanel (#33057)
Co-authored-by: Mehmet Salih Yavuz <salih.yavuz@proton.me>
2025-04-16 15:44:23 +03:00
Kamil Gabryjelski
08c9d28545 Fix changes in DatasourcePanelItem after merge 2025-04-16 14:04:40 +02:00
Mehmet Salih Yavuz
86d5537a7f Merge branch 'master' into template_less 2025-04-16 13:22:21 +03:00
Mehmet Salih Yavuz
18c3f0adb6 fix(theming): Fix various ci issues (#33147) 2025-04-16 12:46:57 +03:00
Mehmet Salih Yavuz
5e8cd7a6ee fix: provide id's 2025-04-16 00:05:04 +03:00
Maxime Beauchemin
dcea6c09ca fix imports 2025-04-15 13:30:45 -07:00
Maxime Beauchemin
dff7c1b50d post merge conflicts 2025-04-15 11:49:19 -07:00
Maxime Beauchemin
4f93c2d2e7 Merge branch 'master' into template_less 2025-04-15 11:45:12 -07:00
Maxime Beauchemin
334aa1a672 fix a unit test 2025-04-14 10:41:03 -07:00
Enzo Martellucci
2667a14678 Merge branch 'master' into template_less 2025-04-14 11:39:05 +02:00
Maxime Beauchemin
29ba5adf21 fix scripts/change_detector.py 2025-04-12 13:13:00 -07:00
Maxime Beauchemin
f18455cc2d fix a python build issue 2025-04-12 13:02:32 -07:00
Maxime Beauchemin
4123d25873 Merge branch 'master' into template_less 2025-04-12 12:57:53 -07:00
Geido
662f33b7de refactor(Components): Use named imports - P.1 (#33081) 2025-04-11 18:45:55 +03:00
Enzo Martellucci
c14dcecd8f refactor(breadcrumb): Upgrade Breadcrumb component from AntD v4 to AntD v5 (#32905) 2025-04-11 14:28:10 +03:00
Maxime Beauchemin
6a4730bbbe Merge branch 'master' into template_less 2025-04-09 12:50:09 -07:00
Pius Iniobong
739caa19cb refactor(Checkbox): Upgrade component to Ant Design v5 (#32980)
Co-authored-by: Diego Pucci <diegopucci.me@gmail.com>
2025-04-09 14:48:02 +03:00
Maxime Beauchemin
8bb02e2958 fix: use Input & TextArea instead of native <input> and <textarea> (#32989) 2025-04-08 11:33:12 -07:00
Maxime Beauchemin
7c2fd55104 restyling page header and buttons 2025-04-08 11:19:21 -07:00
Maxime Beauchemin
6c8e72b889 Fix theming in Explore west panel - Search Input 2025-04-08 11:19:21 -07:00
Mehmet Salih Yavuz
3b198ab656 refactor(Table): Use our custom Table component in CRUD views (#32964)
Co-authored-by: Geido <60598000+geido@users.noreply.github.com>
2025-04-08 19:05:58 +03:00
Maxime Beauchemin
c993abe58c Merge branch 'master' into template_less 2025-04-08 08:33:41 -07:00
Maxime Beauchemin
a9bc4655a4 use colorBorder in AddSliceCard 2025-04-08 08:33:09 -07:00
Mehmet Salih Yavuz
b835478514 refactor(Select): Migrate Select component to Ant Design 5 (#32514)
Co-authored-by: Maxime Beauchemin <maximebeauchemin@gmail.com>
Co-authored-by: Diego Pucci <diegopucci.me@gmail.com>
2025-04-08 15:14:15 +03:00
Damian Pendrak
3950cf065e refactor(Collapse): Upgrade Collapse to Antd5 (#32959)
Co-authored-by: Maxime Beauchemin <maximebeauchemin@gmail.com>
2025-04-07 14:35:39 -07:00
Maxime Beauchemin
c7d2881d04 Merge branch 'master' into template_less 2025-04-07 13:02:10 -07:00
Alexandru Soare
33febb669e refactor(Tabs): Upgrade Tabs to Antd5 (#32810)
Co-authored-by: Maxime Beauchemin <maximebeauchemin@gmail.com>
2025-04-07 18:01:37 +03:00
Enzo Martellucci
6254db34cd refactor(Components): Create wrappers for Ant Design 5 direct exports (#32705)
Co-authored-by: Diego Pucci <diegopucci.me@gmail.com>
Co-authored-by: Geido <60598000+geido@users.noreply.github.com>
Co-authored-by: Mehmet Salih Yavuz <salih.yavuz@proton.me>
2025-04-07 14:48:18 +03:00
Maxime Beauchemin
e6df194201 fix merge-related issues 2025-04-02 18:01:05 -07:00
Maxime Beauchemin
2580a8ba78 Merge branch 'master' into template_less 2025-04-02 17:59:35 -07:00
Maxime Beauchemin
6b58ef155e feat: Theme to include brand logo configuration 2025-04-02 17:55:50 -07:00
Mehmet Salih Yavuz
6f73e58b25 refactor(theming): Fixes to previously migrated components (#32845) 2025-04-02 17:55:12 -07:00
Maxime Beauchemin
bc85a118ba fix some type issues 2025-04-02 13:21:42 -07:00
Maxime Beauchemin
70a5925b03 theming the viz picker 2025-04-02 10:26:43 -07:00
Maxime Beauchemin
d266835820 remove console.log 2025-04-01 13:59:58 -07:00
Maxime Beauchemin
952658ee63 fix links and bring GlobalStyles to superset-ui/core 2025-04-01 13:54:08 -07:00
Maxime Beauchemin
27d723fba1 force colorLink in GlobalStyles 2025-04-01 13:03:23 -07:00
Mehmet Salih Yavuz
971715931b refactor(form): Migrate Form component to Ant Design 5 (#32729) 2025-04-01 09:29:17 -07:00
Maxime Beauchemin
e3342bb731 Merge branch 'master' into template_less 2025-04-01 00:45:31 -07:00
Maxime Beauchemin
506c8387fc lint 2025-03-29 15:32:02 -07:00
Maxime Beauchemin
9dedb588ba altering comment for THEME_OVERRIDES in superset/config.py 2025-03-29 15:30:35 -07:00
Maxime Beauchemin
8b69958f19 centralize theming stuff in AsyncAceEditor 2025-03-29 15:14:00 -07:00
Maxime Beauchemin
1dd8a76113 set Global body bg color to colorBgBase 2025-03-28 17:40:30 -07:00
Maxime Beauchemin
cde1da6285 Merge branch 'master' into template_less 2025-03-28 17:24:20 -07:00
Enzo Martellucci
3665ebcb4b refactor(table): Upgrade table component from antd4 to antd5 (#32378)
Co-authored-by: Diego Pucci <diegopucci.me@gmail.com>
Co-authored-by: Mehmet Salih Yavuz <salih.yavuz@proton.me>
2025-03-28 15:11:25 -07:00
Maxime Beauchemin
c31d70dd12 one minor edit to fix a cypress test 2025-03-27 16:07:53 -07:00
Maxime Beauchemin
f217865435 oopsy daisy 2025-03-27 15:16:25 -07:00
Maxime Beauchemin
501874980e fix cypress test 2025-03-27 14:30:46 -07:00
Maxime Beauchemin
e7b2b586b6 Merge branch 'master' into template_less 2025-03-27 13:59:33 -07:00
Maxime Beauchemin
6a15aaf562 ThemeEditor 2025-03-27 13:48:45 -07:00
Maxime Beauchemin
f5b680699f ThemeEditor 2025-03-27 12:17:06 -07:00
Maxime Beauchemin
3c289a927d a theme editor 2025-03-26 22:57:47 -07:00
Maxime Beauchemin
5805f242d0 minor color tweak 2025-03-26 21:54:22 -07:00
Maxime Beauchemin
4f0a4454ec styling the AsyncAceEditor 2025-03-26 21:51:30 -07:00
Maxime Beauchemin
d752b0f06a fixing themes in Storybook 2025-03-26 18:51:20 -07:00
Maxime Beauchemin
06d737ec9f add theming support to Storybook 2025-03-26 18:02:49 -07:00
Maxime Beauchemin
04729794c8 fix test in dashboard/actions.test.js 2025-03-25 20:56:18 -07:00
Maxime Beauchemin
68ea9ac4d0 fix ErrorMessageWithStackTrace 2025-03-25 20:38:29 -07:00
Maxime Beauchemin
1afa4971d1 fix SavedQueries test 2025-03-25 20:28:44 -07:00
Maxime Beauchemin
5418f09864 fix test for BasicErrorAlert.tsx 2025-03-25 20:24:56 -07:00
Maxime Beauchemin
aabeefb761 fix types in Gauge/transformProps.test.ts 2025-03-25 20:17:59 -07:00
Maxime Beauchemin
023c7da07b fix another test 2025-03-25 20:02:14 -07:00
Maxime Beauchemin
7af32d4c70 fix theme test 2025-03-25 19:43:23 -07:00
Maxime Beauchemin
58724b1c5c fix test 2025-03-25 19:39:38 -07:00
Maxime Beauchemin
f9494128bc fix some tests 2025-03-25 19:15:22 -07:00
Maxime Beauchemin
cebff5e726 improve styles in HighlightedSql 2025-03-25 12:54:38 -07:00
Maxime Beauchemin
bcb6da18ef Merge branch 'master' into template_less 2025-03-25 12:03:09 -07:00
Maxime Beauchemin
344c8f5c37 theming react-code-highlighter or whatev it's called 2025-03-25 09:44:17 -07:00
Maxime Beauchemin
d3f450fca0 fix theme for ag-grid in SQL LAB 2025-03-24 19:24:03 -07:00
Maxime Beauchemin
11a29b1610 fix Collapse color content 2025-03-24 17:17:13 -07:00
Maxime Beauchemin
54d67b679b fix table viz colors 2025-03-24 17:12:30 -07:00
Maxime Beauchemin
64c480a8f1 aligning some colors tokens 2025-03-24 17:01:41 -07:00
Maxime Beauchemin
cf6816064d going vanila on tooltips 2025-03-24 16:41:50 -07:00
Maxime Beauchemin
59e402ac68 fix icon on Explore's cached label 2025-03-24 13:57:41 -07:00
Maxime Beauchemin
5042248ed7 Fix table row separator in TableCollection 2025-03-24 13:26:06 -07:00
Maxime Beauchemin
56e3d165dd theme toast icons and various cosmetic updates 2025-03-24 12:54:01 -07:00
Maxime Beauchemin
8ce144983d fix CopyOutlined icon in dataset view to align with sizing 2025-03-24 10:44:07 -07:00
Maxime Beauchemin
4afbfd11e0 Merge branch 'master' into template_less 2025-03-24 09:18:21 -07:00
Maxime Beauchemin
31eb10590e touchups 2025-03-24 09:17:45 -07:00
Maxime Beauchemin
358633e98d some theming fixes 2025-03-20 08:40:15 -07:00
Maxime Beauchemin
b7bc1113ac Merge branch 'master' into template_less 2025-03-20 07:33:55 -07:00
Maxime Beauchemin
11bc4965e3 echarts theming 2025-03-19 20:09:00 -07:00
Maxime Beauchemin
1ca0f34210 more button background 2025-03-19 17:02:57 -07:00
Maxime Beauchemin
9cb6c3b039 color 2025-03-19 16:57:58 -07:00
Maxime Beauchemin
b9be692e55 fix MoreOutlined button background 2025-03-19 16:28:10 -07:00
Maxime Beauchemin
e0d86df5a5 merging 2025-03-19 12:58:37 -07:00
Maxime Beauchemin
2f80ebb3e8 minor progress 2025-03-18 19:02:59 -07:00
Maxime Beauchemin
83f47d3ca1 fixing vizes 2025-03-18 18:38:21 -07:00
Maxime Beauchemin
48df49d89c fix BIG number 2025-03-18 18:32:54 -07:00
Maxime Beauchemin
f16600ee86 fix sankey theme 2025-03-18 18:32:43 -07:00
Maxime Beauchemin
7dbe05f6d8 fix treemap 2025-03-18 18:32:35 -07:00
Maxime Beauchemin
2d461deb68 echarts 2025-03-18 18:32:26 -07:00
Maxime Beauchemin
dbc7db981c rebased 2025-03-17 14:02:25 -07:00
Maxime Beauchemin
5faf0189e8 switch primary color, keep old hex as comment in case we want to rollback 2025-03-17 13:55:17 -07:00
Maxime Beauchemin
4b55a928c9 set DARK ff to false 2025-03-17 13:55:16 -07:00
Maxime Beauchemin
d15c6d361b fixing storybook 2025-03-17 13:55:16 -07:00
Maxime Beauchemin
6b5d53ad39 fix dashboard left panel background-color 2025-03-17 13:55:16 -07:00
Maxime Beauchemin
b575aa6aac make secondary filled/primary 2025-03-17 13:55:16 -07:00
Maxime Beauchemin
d131c29f3b fix a few buttons 2025-03-17 13:55:16 -07:00
Maxime Beauchemin
22cbec1d95 ts-ignore 2025-03-17 13:55:16 -07:00
Maxime Beauchemin
b2b7b899a3 fix unit test 2025-03-17 13:55:16 -07:00
Maxime Beauchemin
ac81eefe3f fix types/tests 2025-03-17 13:55:16 -07:00
Maxime Beauchemin
8d361205f6 adjusting some buttons 2025-03-17 13:55:15 -07:00
Maxime Beauchemin
352aa36823 make Cancel secondary 2025-03-17 13:55:15 -07:00
Maxime Beauchemin
336763f0c9 feat: messing with the theme 2025-03-17 13:55:12 -07:00
1581 changed files with 37762 additions and 26313 deletions

1
.gitattributes vendored
View File

@@ -1,3 +1,4 @@
docker/**/*.sh text eol=lf
*.svg binary
*.ipynb binary
*.geojson binary

View File

@@ -111,6 +111,9 @@ jobs:
docker compose up superset-init --exit-code-from superset-init
docker-compose-image-tag:
# Run this job only on pushes to master (not for PRs)
# goal is to check that building the latest image works, not required for all PR pushes
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"

View File

@@ -53,6 +53,14 @@ jobs:
cd docs
yarn install --immutable
- name: Cache pre-commit environments
uses: actions/cache@v3
with:
path: ~/.cache/pre-commit
key: pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
restore-keys: |
pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-
- name: pre-commit
run: |
set +e # Don't exit immediately on failure

View File

@@ -73,6 +73,7 @@ jobs:
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
@@ -137,9 +138,16 @@ jobs:
NODE_OPTIONS: "--max-old-space-size=4096"
with:
run: cypress-run-all ${{ env.USE_DASHBOARD }} ${{ matrix.app_root }}
- name: Set safe app root
if: failure()
id: set-safe-app-root
run: |
APP_ROOT="${{ matrix.app_root }}"
SAFE_APP_ROOT=${APP_ROOT//\//_}
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
- name: Upload Artifacts
uses: actions/upload-artifact@v4
if: failure()
with:
path: ${{ github.workspace }}/superset-frontend/cypress-base/cypress/screenshots
name: cypress-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}-${{ matrix.parallel_id }}
name: cypress-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}-${{ matrix.parallel_id }}--${{ steps.set-safe-app-root.outputs.safe_app_root }}

View File

@@ -26,6 +26,8 @@ jobs:
uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Check for File Changes
id: check
@@ -39,6 +41,10 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "git rev-parse --short HEAD"
git rev-parse --short HEAD
echo "git show -s --format=raw HEAD"
git show -s --format=raw HEAD
docker buildx build \
-t $TAG \
--cache-from=type=registry,ref=apache/superset-cache:3.10-slim-bookworm \
@@ -115,24 +121,6 @@ jobs:
files: merged-output/coverage-summary.json
slug: apache/superset
core-cover:
needs: frontend-build
if: needs.frontend-build.outputs.should-run == 'true'
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@v4
with:
name: docker-image
- name: Load Docker Image
run: docker load < docker-image.tar.gz
- name: superset-ui/core coverage
run: |
docker run --rm $TAG bash -c \
"npm run core:cover"
lint-frontend:
needs: frontend-build
if: needs.frontend-build.outputs.should-run == 'true'
@@ -144,7 +132,8 @@ jobs:
name: docker-image
- name: Load Docker Image
run: docker load < docker-image.tar.gz
run: |
docker load < docker-image.tar.gz
- name: eslint
run: |

View File

@@ -58,7 +58,7 @@ repos:
- id: prettier
additional_dependencies:
- prettier@3.5.3
args: ["--ignore-path=./superset-frontend/.prettierignore"]
args: ["--ignore-path=./superset-frontend/.prettierignore", "--exclude", "site-packages"]
files: "superset-frontend"
- repo: local
hooks:

View File

@@ -26,6 +26,7 @@ assists people when migrating to a new version.
- [33116](https://github.com/apache/superset/pull/33116) In Echarts Series charts (e.g. Line, Area, Bar, etc.) charts, the `x_axis_sort_series` and `x_axis_sort_series_ascending` form data items have been renamed with `x_axis_sort` and `x_axis_sort_asc`.
There's a migration added that can potentially affect a significant number of existing charts.
- [32317](https://github.com/apache/superset/pull/32317) The horizontal filter bar feature is now out of testing/beta development and its feature flag `HORIZONTAL_FILTER_BAR` has been removed.
- [31590](https://github.com/apache/superset/pull/31590) Marks the begining of intricate work around supporting dynamic Theming, and breaks support for [THEME_OVERRIDES](https://github.com/apache/superset/blob/732de4ac7fae88e29b7f123b6cbb2d7cd411b0e4/superset/config.py#L671) in favor of a new theming system based on AntD V5. Likely this will be in disrepair until settling over the 5.x lifecycle.
- [31976](https://github.com/apache/superset/pull/31976) Removed the `DISABLE_LEGACY_DATASOURCE_EDITOR` feature flag. The previous value of the feature flag was `True` and now the feature is permanently removed.
- [31959](https://github.com/apache/superset/pull/32000) Removes CSV_UPLOAD_MAX_SIZE config, use your web server to control file upload size.
- [31959](https://github.com/apache/superset/pull/31959) Removes the following endpoints from data uploads: `/api/v1/database/<id>/<file type>_upload` and `/api/v1/database/<file type>_metadata`, in favour of new one (Details on the PR). And simplifies permissions.

View File

@@ -89,6 +89,7 @@ export type EmbeddedDashboard = {
callbackFn: ObserveDataMaskCallbackFn,
) => void;
getDataMask: () => Record<string, any>;
setThemeConfig: (themeConfig: Record<string, any>) => void;
};
/**
@@ -245,6 +246,18 @@ export async function embedDashboard({
ourPort.start();
ourPort.defineMethod('observeDataMask', callbackFn);
};
// TODO: Add proper types once theming branch is merged
const setThemeConfig = async (themeConfig: Record<string, any>): Promise<void> => {
try {
ourPort.emit('setThemeConfig', { themeConfig });
log('Theme config sent successfully (or at least message dispatched)');
} catch (error) {
log(
'Error sending theme config. Ensure the iframe side implements the "setThemeConfig" method.',
);
throw error;
}
};
return {
getScrollSize,
@@ -253,5 +266,6 @@ export async function embedDashboard({
getActiveTabs,
observeDataMask,
getDataMask,
setThemeConfig
};
}

View File

@@ -19,7 +19,6 @@
coverage/**
dist/*
src/assets/images/*
src/assets/stylesheets/*
node_modules/*
node_modules*/*
vendor/*

View File

@@ -52,7 +52,7 @@ const restrictedImportsRules = {
message: 'Lodash Memoize is unsafe! Please use memoize-one instead',
},
'no-testing-library-react': {
name: '@testing-library/react',
name: '@superset-ui/core/spec',
message: 'Please use spec/helpers/testing-library instead',
},
'no-testing-library-react-dom-utils': {
@@ -63,10 +63,6 @@ const restrictedImportsRules = {
name: 'antd',
message: 'Please import Ant components from the index of src/components',
},
'no-antd-v5': {
name: 'antd-v5',
message: 'Please import Ant v5 components from the index of src/components',
},
'no-superset-theme': {
name: '@superset-ui/core',
importNames: ['supersetTheme'],
@@ -101,6 +97,15 @@ module.exports = {
// resolve modules from `/superset_frontend/node_modules` and `/superset_frontend`
moduleDirectory: ['node_modules', '.'],
},
typescript: {
alwaysTryTypes: true,
project: [
'./tsconfig.json',
'./packages/superset-ui-core/tsconfig.json',
'./packages/superset-ui-chart-controls/',
'./plugins/*/tsconfig.json',
],
},
},
// only allow import from top level of module
'import/core-modules': importCoreModules,
@@ -239,6 +244,14 @@ module.exports = {
'ImportNamespaceSpecifier[parent.source.value!=/^(\\.|src)/]',
message: 'Wildcard imports are not allowed',
},
{
selector: "TSTypeReference[typeName.name='$TSFixMe']",
message: 'Fix this $TSFixMe type',
},
{
selector: "TSTypeReference[typeName.name='$TSFixMeFunction']",
message: 'Fix this $TSFixMeFunction type',
},
],
'no-restricted-imports': [
'error',
@@ -295,9 +308,9 @@ module.exports = {
'error',
{
paths: Object.values(restrictedImportsRules).filter(
r => r.name !== 'antd-v5',
r => r.name !== 'antd',
),
patterns: ['antd/*'],
patterns: [],
},
],
},
@@ -330,7 +343,9 @@ module.exports = {
rules: {
'import/no-extraneous-dependencies': [
'error',
{ devDependencies: true },
{
devDependencies: true,
},
],
'no-only-tests/no-only-tests': 'error',
'max-classes-per-file': 0,
@@ -373,7 +388,7 @@ module.exports = {
'fixtures.*',
'cypress-base/cypress/**/*',
'Stories.tsx',
'packages/superset-ui-core/src/style/index.tsx',
'packages/superset-ui-core/src/theme/index.tsx',
],
rules: {
'theme-colors/no-literal-colors': 0,

View File

@@ -17,31 +17,75 @@
* under the License.
*/
import { withJsx } from '@mihkeleidast/storybook-addon-source';
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import { AntdThemeProvider } from '../src/components/AntdThemeProvider';
import { themeObject, css, exampleThemes } from '@superset-ui/core';
import { combineReducers, createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import reducerIndex from 'spec/helpers/reducerIndex';
import { GlobalStyles } from '../src/GlobalStyles';
import { Global } from '@emotion/react';
import { App, Layout, Space, Content } from 'antd';
import 'src/theme.ts';
import './storybook.css';
export const GlobalStylesOverrides = () => (
<Global
styles={css`
html,
body,
#storybook-root {
margin: 0 !important;
padding: 0 !important;
min-height: 100vh !important;
}
.ant-app {
min-height: 100vh !important;
}
`}
/>
);
const store = createStore(
combineReducers(reducerIndex),
{},
compose(applyMiddleware(thunk)),
);
const themeDecorator = Story => (
<ThemeProvider theme={supersetTheme}>
<AntdThemeProvider>
<GlobalStyles />
<Story />
</AntdThemeProvider>
</ThemeProvider>
);
export const globalTypes = {
theme: {
name: 'Theme',
description: 'Global theme for components',
defaultValue: 'superset',
toolbar: {
icon: 'paintbrush',
items: Object.keys(exampleThemes),
},
},
};
const themeDecorator = (Story, context) => {
const themeKey = context.globals.theme || 'superset';
themeObject.setConfig(exampleThemes[themeKey]);
return (
<themeObject.SupersetThemeProvider>
<App>
<GlobalStylesOverrides />
<Layout
style={{
minHeight: '100vh',
width: '100%',
padding: 24,
backgroundColor: themeObject.theme.colorBgBase,
}}
>
<Story {...context} />
</Layout>
</App>
</themeObject.SupersetThemeProvider>
);
};
const providerDecorator = Story => (
<Provider store={store}>
@@ -81,5 +125,5 @@ export const parameters = {
],
},
},
controls: { expanded: true, sort: 'alpha' },
controls: { expanded: true, sort: 'alpha', disableSaveFromUI: true },
};

View File

@@ -1,3 +1,2 @@
body {
background: transparent;
}

View File

@@ -0,0 +1,56 @@
/**
* 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 { LOGIN } from 'cypress/utils/urls';
function interceptLogin() {
cy.intercept('POST', '/login/').as('login');
}
describe('Login view', () => {
beforeEach(() => {
cy.visit(LOGIN);
});
it('should load login page', () => {
cy.getBySel('login-form').should('be.visible');
cy.getBySel('username-input').should('be.visible');
cy.getBySel('password-input').should('be.visible');
cy.getBySel('login-button').should('be.visible');
});
it('should redirect to login with incorrect username and password', () => {
interceptLogin();
cy.getBySel('login-form').should('be.visible');
cy.getBySel('username-input').type('admin');
cy.getBySel('password-input').type('wrongpassword');
cy.getBySel('login-button').click();
cy.wait('@login');
cy.url().should('include', LOGIN);
});
it('should login with correct username and password', () => {
interceptLogin();
cy.getBySel('login-form').should('be.visible');
cy.getBySel('username-input').type('admin');
cy.getBySel('password-input').type('general');
cy.getBySel('login-button').click();
cy.wait('@login');
cy.getCookies().should('have.length', 1);
});
});

View File

@@ -0,0 +1,37 @@
/**
* 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 { REGISTER } from 'cypress/utils/urls';
describe('Register view', () => {
beforeEach(() => {
cy.visit(REGISTER);
});
it('should load register page', () => {
cy.getBySel('register-form').should('be.visible');
cy.getBySel('username-input').should('be.visible');
cy.getBySel('first-name-input').should('be.visible');
cy.getBySel('last-name-input').should('be.visible');
cy.getBySel('email-input').should('be.visible');
cy.getBySel('password-input').should('be.visible');
cy.getBySel('confirm-password-input').should('be.visible');
cy.getBySel('register-button').should('be.visible');
});
});

View File

@@ -35,12 +35,12 @@ function orderAlphabetical() {
}
function openProperties() {
cy.get('[aria-label="more"]').eq(1).click();
cy.get('[aria-label="more"]').eq(0).click();
cy.getBySel('chart-list-edit-option').click();
}
function openMenu() {
cy.get('[aria-label="more"]').eq(1).click();
cy.get('[aria-label="more"]').eq(0).click();
}
function confirmDelete() {
@@ -81,12 +81,13 @@ describe('Charts list', () => {
cy.wait('@get');
});
it.only('should show the newly added dashboards in a tooltip', () => {
it('should show the newly added dashboards in a tooltip', () => {
interceptDashboardGet();
visitSampleChartFromList('1 - Sample chart');
saveChartToDashboard('1 - Sample dashboard');
saveChartToDashboard('2 - Sample dashboard');
saveChartToDashboard('3 - Sample dashboard');
saveChartToDashboard('1 - Sample chart', '1 - Sample dashboard');
saveChartToDashboard('1 - Sample chart', '2 - Sample dashboard');
saveChartToDashboard('1 - Sample chart', '3 - Sample dashboard');
saveChartToDashboard('1 - Sample chart', '4 - Sample dashboard');
visitChartList();
cy.getBySel('count-crosslinks').should('be.visible');
@@ -95,8 +96,6 @@ describe('Charts list', () => {
describe('list mode', () => {
before(() => {
cy.createSampleDashboards([0, 1, 2, 3]);
cy.createSampleCharts([0]);
visitChartList();
setGridMode('list');
});
@@ -112,18 +111,10 @@ describe('Charts list', () => {
cy.getBySel('sort-header').eq(7).contains('Actions');
});
it('should sort correctly in list mode', () => {
cy.getBySel('sort-header').eq(1).click();
cy.getBySel('table-row').first().contains('Area Chart');
cy.getBySel('sort-header').eq(1).click();
cy.getBySel('table-row').first().contains("World's Population");
cy.getBySel('sort-header').eq(1).click();
});
it('should bulk select in list mode', () => {
toggleBulkSelect();
cy.get('#header-toggle-all').click();
cy.get('[aria-label="checkbox-on"]').should('have.length', 26);
cy.get('[aria-label="Select all"]').click();
cy.get('input[type="checkbox"]:checked').should('have.length', 26);
cy.getBySel('bulk-select-copy').contains('25 Selected');
cy.getBySel('bulk-select-action')
.should('have.length', 2)
@@ -132,7 +123,7 @@ describe('Charts list', () => {
expect($btns).to.contain('Export');
});
cy.getBySel('bulk-select-deselect-all').click();
cy.get('[aria-label="checkbox-on"]').should('have.length', 0);
cy.get('input[type="checkbox"]:checked').should('have.length', 0);
cy.getBySel('bulk-select-copy').contains('0 Selected');
cy.getBySel('bulk-select-action').should('not.exist');
});
@@ -164,11 +155,6 @@ describe('Charts list', () => {
cy.getBySel('bulk-select-action').should('not.exist');
});
it('should sort in card mode', () => {
orderAlphabetical();
cy.getBySel('styled-card').first().contains('% Rural');
});
it('should preserve other filters when sorting', () => {
cy.getBySel('styled-card').should('have.length', 25);
setFilter('Type', 'Big Number');
@@ -179,40 +165,12 @@ describe('Charts list', () => {
describe('common actions', () => {
beforeEach(() => {
cy.createSampleCharts([0, 1, 2, 3]);
visitChartList();
});
it('should allow to favorite/unfavorite', () => {
cy.intercept({ url: `**/api/v1/chart/*/favorites/`, method: 'POST' }).as(
'select',
);
cy.intercept({
url: `**/api/v1/chart/*/favorites/`,
method: 'DELETE',
}).as('unselect');
setGridMode('card');
orderAlphabetical();
cy.getBySel('styled-card').first().contains('% Rural');
cy.getBySel('styled-card')
.first()
.find("[aria-label='favorite-unselected']")
.click();
cy.wait('@select');
cy.getBySel('styled-card')
.first()
.find("[aria-label='favorite-selected']")
.click();
cy.wait('@unselect');
cy.getBySel('styled-card')
.first()
.find("[aria-label='favorite-selected']")
.should('not.exist');
});
it('should bulk delete correctly', () => {
cy.createSampleCharts([0, 1, 2, 3]);
interceptBulkDelete();
toggleBulkSelect();
@@ -220,9 +178,10 @@ describe('Charts list', () => {
setGridMode('card');
orderAlphabetical();
cy.getBySel('styled-card').eq(1).contains('1 - Sample chart').click();
cy.getBySel('styled-card').eq(2).contains('2 - Sample chart').click();
cy.getBySel('bulk-select-action').eq(0).contains('Delete').click();
cy.getBySel('skeleton-card').should('not.exist');
cy.getBySel('styled-card').contains('1 - Sample chart').click();
cy.getBySel('styled-card').contains('2 - Sample chart').click();
cy.getBySel('bulk-select-action').contains('Delete').click();
confirmDelete();
cy.wait('@bulkDelete');
cy.getBySel('styled-card')
@@ -234,56 +193,71 @@ describe('Charts list', () => {
// bulk deletes in list-view
setGridMode('list');
cy.getBySel('table-row').eq(1).contains('3 - Sample chart');
cy.getBySel('table-row').eq(2).contains('4 - Sample chart');
cy.get('.loading').should('not.exist');
cy.getBySel('table-row').contains('3 - Sample chart').should('exist');
cy.getBySel('table-row').contains('4 - Sample chart').should('exist');
cy.get('[data-test="table-row"] input[type="checkbox"]').eq(0).click();
cy.get('[data-test="table-row"] input[type="checkbox"]').eq(1).click();
cy.get('[data-test="table-row"] input[type="checkbox"]').eq(2).click();
cy.getBySel('bulk-select-action').eq(0).contains('Delete').click();
confirmDelete();
cy.wait('@bulkDelete');
cy.getBySel('table-row').eq(1).should('not.contain', '3 - Sample chart');
cy.getBySel('table-row').eq(2).should('not.contain', '4 - Sample chart');
cy.get('.loading').should('exist');
cy.get('.loading').should('not.exist');
cy.getBySel('table-row').eq(0).should('not.contain', '3 - Sample chart');
cy.getBySel('table-row').eq(1).should('not.contain', '4 - Sample chart');
});
it('should delete correctly', () => {
it('should delete correctly in card mode', () => {
cy.createSampleCharts([0, 1]);
interceptDelete();
// deletes in card-view
setGridMode('card');
orderAlphabetical();
cy.getBySel('styled-card').eq(1).contains('1 - Sample chart');
cy.getBySel('styled-card').contains('1 - Sample chart');
openMenu();
cy.getBySel('chart-list-delete-option').click();
confirmDelete();
cy.wait('@delete');
cy.getBySel('styled-card')
.eq(1)
.should('not.contain', '1 - Sample chart');
.contains('1 - Sample chart')
.should('not.exist');
});
// deletes in list-view
setGridMode('list');
cy.getBySel('table-row').eq(1).contains('2 - Sample chart');
cy.getBySel('delete').eq(1).click();
it('should delete correctly in list mode', () => {
cy.createSampleCharts([2, 3]);
interceptDelete();
cy.getBySel('sort-header').contains('Name').click();
// Modal closes immediatly without this
cy.wait(2000);
cy.getBySel('table-row').eq(0).contains('3 - Sample chart');
cy.getBySel('delete').eq(0).click();
confirmDelete();
cy.wait('@delete');
cy.getBySel('table-row').eq(1).should('not.contain', '2 - Sample chart');
cy.get('.loading').should('exist');
cy.get('.loading').should('not.exist');
cy.getBySel('table-row').eq(0).should('not.contain', '3 - Sample chart');
});
it('should edit correctly', () => {
cy.createSampleCharts([0]);
interceptUpdate();
// edits in card-view
setGridMode('card');
orderAlphabetical();
cy.getBySel('styled-card').eq(1).contains('1 - Sample chart');
cy.getBySel('skeleton-card').should('not.exist');
cy.getBySel('styled-card').eq(0).contains('1 - Sample chart');
// change title
openProperties();
cy.getBySel('properties-modal-name-input').type(' | EDITED');
cy.get('button:contains("Save")').click();
cy.wait('@update');
cy.getBySel('styled-card').eq(1).contains('1 - Sample chart | EDITED');
cy.getBySel('styled-card').eq(0).contains('1 - Sample chart | EDITED');
// edits in list-view
setGridMode('list');

View File

@@ -47,12 +47,12 @@ describe.skip('Dashboard top-level controls', () => {
// Solution: pause the network before clicking, assert, then unpause network.
cy.get('[data-test="refresh-chart-menu-item"]').should(
'have.class',
'antd5-dropdown-menu-item-disabled',
'ant-dropdown-menu-item-disabled',
);
waitForChartLoad(mapSpec);
cy.get('[data-test="refresh-chart-menu-item"]').should(
'not.have.class',
'antd5-dropdown-menu-item-disabled',
'ant-dropdown-menu-item-disabled',
);
});
});
@@ -65,7 +65,7 @@ describe.skip('Dashboard top-level controls', () => {
cy.get('[aria-label="ellipsis"]').click();
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
'not.have.class',
'antd5-dropdown-menu-item-disabled',
'ant-dropdown-menu-item-disabled',
);
cy.get('[data-test="refresh-dashboard-menu-item"]').click({
@@ -73,7 +73,7 @@ describe.skip('Dashboard top-level controls', () => {
});
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
'have.class',
'antd5-dropdown-menu-item-disabled',
'ant-dropdown-menu-item-disabled',
);
// wait all charts force refreshed.
@@ -94,7 +94,7 @@ describe.skip('Dashboard top-level controls', () => {
cy.get('[aria-label="ellipsis"]').click();
cy.get('[data-test="refresh-dashboard-menu-item"]').and(
'not.have.class',
'antd5-dropdown-menu-item-disabled',
'ant-dropdown-menu-item-disabled',
);
});
});

View File

@@ -62,6 +62,6 @@ describe('Dashboard actions', () => {
// Verify the color of the outlined star (gray)
cy.get('@starIconOutlinedAfter')
.should('have.css', 'color')
.and('eq', 'rgb(178, 178, 178)');
.and('eq', 'rgb(133, 133, 133)');
});
});

View File

@@ -54,14 +54,14 @@ const drillBy = (targetDrillByColumn: string, isLegacy = false) => {
interceptV1ChartData();
}
cy.get('.antd5-dropdown:not(.antd5-dropdown-hidden)')
cy.get('.ant-dropdown:not(.ant-dropdown-hidden)')
.should('be.visible')
.find("[role='menu'] [role='menuitem']")
.contains(/^Drill by$/)
.trigger('mouseover', { force: true });
cy.get(
'.antd5-dropdown-menu-submenu:not(.antd5-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
)
.should('be.visible')
.find('[role="menuitem"]')

View File

@@ -34,8 +34,8 @@ function openModalFromMenu(chartType: string) {
cy.get(
`[data-test-viz-type='${chartType}'] [aria-label='More Options']`,
).click();
cy.get('.antd5-dropdown')
.not('.antd5-dropdown-hidden')
cy.get('.ant-dropdown')
.not('.ant-dropdown-hidden')
.find("[role='menu'] [role='menuitem']")
.eq(5)
.should('contain', 'Drill to detail')
@@ -46,8 +46,8 @@ function openModalFromMenu(chartType: string) {
function drillToDetail(targetMenuItem: string) {
interceptSamples();
cy.get('.antd5-dropdown')
.not('.antd5-dropdown-hidden')
cy.get('.ant-dropdown')
.not('.ant-dropdown-hidden')
.first()
.find("[role='menu'] [role='menuitem']")
.contains(new RegExp(`^${targetMenuItem}$`))
@@ -61,14 +61,14 @@ function drillToDetail(targetMenuItem: string) {
const drillToDetailBy = (targetDrill: string) => {
interceptSamples();
cy.get('.antd5-dropdown:not(.antd5-dropdown-hidden)')
cy.get('.ant-dropdown:not(.ant-dropdown-hidden)')
.should('be.visible')
.find("[role='menu'] [role='menuitem']")
.contains(/^Drill to detail by$/)
.trigger('mouseover', { force: true });
cy.get(
'.antd5-dropdown-menu-submenu:not(.antd5-dropdown-menu-submenu-hidden) [data-test="drill-to-detail-by-submenu"]',
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-to-detail-by-submenu"]',
)
.should('be.visible')
.find('[role="menuitem"]')
@@ -121,7 +121,10 @@ function testTimeChart(vizType: string) {
});
}
describe('Drill to detail modal', () => {
// TODO fix this test, it has issues with autoscrolling and the locked title
// flakes intricately when the righClick is obstructed by the title.
// Tried many option around scrollIntoView, force, etc. but no luck.
describe.skip('Drill to detail modal', () => {
beforeEach(() => {
closeModal();
});
@@ -463,7 +466,7 @@ describe('Drill to detail modal', () => {
});
// close the filter and test that data was reloaded
cy.getBySel('filter-col').find("[aria-label='close']").click();
cy.getBySel('filter-col').find("[aria-label='Close']").click();
cy.wait('@samples');
cy.getBySel('row-count-label').should('contain', '75.7k rows');
cy.get('.ant-pagination-item-active').should('contain', '1');

View File

@@ -17,7 +17,12 @@
* under the License.
*/
import { SAMPLE_DASHBOARD_1, TABBED_DASHBOARD } from 'cypress/utils/urls';
import { drag, resize, waitForChartLoad } from 'cypress/utils';
import {
drag,
resize,
setSelectSearchInput,
waitForChartLoad,
} from 'cypress/utils';
import { edit } from 'brace';
import {
interceptExploreUpdate,
@@ -34,21 +39,12 @@ function editDashboard() {
cy.getBySel('edit-dashboard-button').click();
}
function closeModal() {
cy.getBySel('properties-modal-cancel-button').click({ force: true });
}
function openProperties() {
cy.get('body').then($body => {
if ($body.find('[data-test="properties-modal-cancel-button"]').length) {
closeModal();
}
cy.getBySel('actions-trigger').click({ force: true });
cy.getBySel('header-actions-menu')
.contains('Edit properties')
.click({ force: true });
cy.get('.antd5-modal-body').should('be.visible');
});
cy.getBySel('actions-trigger').click({ force: true });
cy.getBySel('header-actions-menu')
.contains('Edit properties')
.click({ force: true });
cy.get('.ant-modal-body').should('be.visible');
}
function assertMetadata(text: string) {
@@ -65,7 +61,7 @@ function assertMetadata(text: string) {
}
function openAdvancedProperties() {
cy.get('.antd5-modal-body')
cy.get('.ant-modal-body')
.contains('Advanced')
.should('be.visible')
.click({ force: true });
@@ -150,12 +146,10 @@ function selectColorScheme(
target = 'dashboard-edit-properties-form',
) {
cy.get(`[data-test="${target}"] input[aria-label="Select color scheme"]`)
.first()
.should('exist')
.then($input => {
cy.wrap($input).click({ force: true });
cy.wrap($input).type(color.slice(0, 5), { force: true });
setSelectSearchInput($input, color.slice(0, 5));
});
cy.getBySel(color).click({ force: true });
}
function saveAndGo(dashboard = 'Tabbed Dashboard') {
@@ -1095,7 +1089,7 @@ describe('Dashboard edit', () => {
cy.allowConsoleErrors(['Error: A valid color scheme is required']);
writeMetadata('{"color_scheme":"wrongcolorscheme"}');
applyChanges();
cy.get('.antd5-modal-body')
cy.get('.ant-modal-body')
.contains('A valid color scheme is required')
.should('be.visible');
});
@@ -1130,7 +1124,7 @@ describe('Dashboard edit', () => {
it('should filter charts', () => {
interceptCharts();
cy.get('[role="checkbox"]').click();
cy.get('input[type="checkbox"]').click();
cy.getBySel('dashboard-charts-filter-search-input').type('Unicode');
cy.wait('@filtering');
cy.getBySel('chart-card')
@@ -1140,8 +1134,8 @@ describe('Dashboard edit', () => {
});
// TODO fix this test! This was the #1 flaky test as of 4/21/23 according to cypress dashboard.
xit('should disable the Save button when undoing', () => {
cy.get('[role="checkbox"]').click();
it.skip('should disable the Save button when undoing', () => {
cy.get('input[type="checkbox"]').click();
dragComponent('Unicode Cloud', 'card-title', false);
cy.getBySel('header-save-button').should('be.enabled');
discardChanges();
@@ -1155,13 +1149,13 @@ describe('Dashboard edit', () => {
});
it('should add charts', () => {
cy.get('[role="checkbox"]').click();
cy.get('input[type="checkbox"]').click();
dragComponent();
cy.getBySel('dashboard-component-chart-holder').should('have.length', 1);
});
it.skip('should remove added charts', () => {
cy.get('[role="checkbox"]').click();
cy.get('input[type="checkbox"]').click();
dragComponent('Unicode Cloud');
cy.getBySel('dashboard-component-chart-holder').should('have.length', 1);
cy.getBySel('dashboard-delete-component-button').click();
@@ -1204,7 +1198,7 @@ describe('Dashboard edit', () => {
});
it('should save', () => {
cy.get('[role="checkbox"]').click();
cy.get('input[type="checkbox"]').click();
dragComponent();
cy.getBySel('header-save-button').should('be.enabled');
saveChanges();

View File

@@ -57,16 +57,16 @@ function setFilterBarOrientation(orientation: 'vertical' | 'horizontal') {
.trigger('mouseover');
if (orientation === 'vertical') {
cy.get('.antd5-dropdown-menu-item-selected')
cy.get('.ant-dropdown-menu-item-selected')
.contains('Horizontal (Top)')
.should('exist');
cy.get('.antd5-dropdown-menu-item').contains('Vertical (Left)').click();
cy.get('.ant-dropdown-menu-item').contains('Vertical (Left)').click();
cy.getBySel('dashboard-filters-panel').should('exist');
} else {
cy.get('.antd5-dropdown-menu-item-selected')
cy.get('.ant-dropdown-menu-item-selected')
.contains('Vertical (Left)')
.should('exist');
cy.get('.antd5-dropdown-menu-item').contains('Horizontal (Top)').click();
cy.get('.ant-dropdown-menu-item').contains('Horizontal (Top)').click();
cy.getBySel('loading-indicator').should('exist');
cy.getBySel('filter-bar').should('exist');
cy.getBySel('dashboard-filters-panel').should('not.exist');
@@ -138,7 +138,7 @@ describe('Horizontal FilterBar', () => {
cy.getBySel('dropdown-container-btn').should('not.exist');
});
it('should show "more filters" and scroll', () => {
it.only('should show "more filters" and scroll', () => {
prepareDashboardFilters([
{ name: 'test_1', column: 'country_name', datasetId: 2 },
{ name: 'test_2', column: 'country_code', datasetId: 2 },
@@ -157,11 +157,11 @@ describe('Horizontal FilterBar', () => {
cy.get('.filter-item-wrapper').should('have.length', 3);
openMoreFilters();
cy.getBySel('form-item-value').should('have.length', 12);
cy.getBySel('filter-control-name').contains('test_10').should('be.visible');
cy.getBySel('filter-control-name').contains('test_3').should('be.visible');
cy.getBySel('filter-control-name')
.contains('test_12')
.should('not.be.visible');
cy.get('.antd5-popover-inner').scrollTo('bottom');
cy.getBySel('filter-control-name').contains('test_12').scrollIntoView();
cy.getBySel('filter-control-name').contains('test_12').should('be.visible');
});
@@ -197,7 +197,7 @@ describe('Horizontal FilterBar', () => {
applyNativeFilterValueWithIndex(8, testItems.filterDefaultValue);
cy.get(nativeFilters.applyFilter).click({ force: true });
cy.wait('@chart');
cy.get('.antd5-scroll-number.antd5-badge-count').should(
cy.get('.ant-scroll-number.ant-badge-count').should(
'have.attr',
'title',
'1',

View File

@@ -199,14 +199,16 @@ describe('Native filters', () => {
.should('be.visible')
.click();
cy.get('[data-test="range-filter-from-input"]').type('{selectall}5');
cy.get('[data-test="range-filter-from-input"]').type('{selectall}40');
cy.get('[data-test="range-filter-to-input"]')
.should('be.visible')
.click();
cy.get('[data-test="range-filter-to-input"]').type('{selectall}50');
cy.get(nativeFilters.applyFilter).click();
cy.get(nativeFilters.applyFilter).click({
force: true,
});
// Assert that the URL contains 'native_filters'
cy.url().then(u => {
@@ -215,7 +217,7 @@ describe('Native filters', () => {
cy.get('[data-test="range-filter-from-input"]')
.invoke('val')
.should('equal', '5');
.should('equal', '40');
// Assert that the "To" input has the correct value
cy.get('[data-test="range-filter-to-input"]')

View File

@@ -293,7 +293,11 @@ describe('Native filters', () => {
it('Verify setting options and tooltips for value filter', () => {
enterNativeFilterEditModal(false);
cy.contains('Filter value is required').should('be.visible').click();
cy.contains('Filter value is required').scrollIntoView();
cy.contains('Filter value is required').should('be.visible').click({
force: true,
});
checkNativeFilterTooltip(0, nativeFilterTooltips.preFilter);
checkNativeFilterTooltip(1, nativeFilterTooltips.defaultValue);
cy.get(nativeFilters.modal.container).should('be.visible');

View File

@@ -18,7 +18,11 @@
*/
import { dashboardView, nativeFilters } from 'cypress/support/directories';
import { ChartSpec, waitForChartLoad } from 'cypress/utils';
import {
ChartSpec,
setSelectSearchInput,
waitForChartLoad,
} from 'cypress/utils';
export const WORLD_HEALTH_CHARTS = [
{ name: '% Rural', viz: 'world_map' },
@@ -264,10 +268,11 @@ export function fillNativeFilterForm(
dataset?: string,
filterColumn?: string,
) {
cy.get(nativeFilters.filtersPanel.filterTypeInput)
.find(nativeFilters.filtersPanel.filterTypeItem)
.click({ multiple: true, force: true });
cy.get(`[label="${type}"]`).click({ multiple: true, force: true });
cy.get(nativeFilters.filtersPanel.filterTypeInput).within(() => {
cy.get('input').then($input => {
setSelectSearchInput($input, type);
});
});
cy.get(nativeFilters.modal.container)
.find(nativeFilters.filtersPanel.filterName)
.last()
@@ -280,31 +285,23 @@ export function fillNativeFilterForm(
.find(nativeFilters.filtersPanel.filterName)
.last()
.type(name, { scrollBehavior: false, force: true });
if (dataset) {
cy.get(nativeFilters.modal.container)
.find(nativeFilters.filtersPanel.datasetName)
.last()
.click({ force: true, scrollBehavior: false });
cy.get(nativeFilters.modal.container)
.find(nativeFilters.filtersPanel.datasetName)
.type(`${dataset}`, { scrollBehavior: false });
cy.get('div[aria-label="Dataset"]').within(() => {
cy.get('input').then($input => {
setSelectSearchInput($input, dataset, true);
});
});
cy.get(nativeFilters.silentLoading).should('not.exist');
cy.get(`[label="${dataset}"]`).click({ multiple: true, force: true });
}
cy.get(nativeFilters.silentLoading).should('not.exist');
if (filterColumn) {
cy.get(nativeFilters.filtersPanel.filterInfoInput)
.last()
.click({ force: true });
cy.get(nativeFilters.filtersPanel.filterInfoInput)
.last()
.type(filterColumn);
cy.get(nativeFilters.filtersPanel.inputDropdown)
.should('be.visible', { timeout: 20000 })
.last()
.click();
cy.get('div[aria-label="Column select"]').within(() => {
cy.get('input').then($input => {
setSelectSearchInput($input, filterColumn, true);
});
});
}
cy.get(nativeFilters.silentLoading).should('not.exist');
}
/** ************************************************************************
@@ -442,6 +439,9 @@ export function checkNativeFilterTooltip(index: number, value: string) {
.eq(index)
.trigger('mouseover');
cy.contains(`${value}`);
cy.get(nativeFilters.filterConfigurationSections.infoTooltip)
.eq(index)
.trigger('mouseout');
}
/** ************************************************************************
@@ -455,20 +455,20 @@ export function applyAdvancedTimeRangeFilterOnDashboard(
startRange?: string,
endRange?: string,
) {
cy.get('.control-label').contains('RANGE TYPE').should('be.visible');
cy.get('.antd5-popover-content .ant-select-selector')
cy.get('.control-label').contains('Range type').should('be.visible');
cy.get('.ant-popover-content .ant-select-selector')
.should('be.visible')
.click();
cy.get(`[label="Advanced"]`).should('be.visible').click();
cy.get('.section-title').contains('Advanced Time Range').should('be.visible');
if (startRange) {
cy.get('.antd5-popover-inner-content')
cy.get('.ant-popover-inner-content')
.find('[class^=ant-input]')
.first()
.type(`${startRange}`);
}
if (endRange) {
cy.get('.antd5-popover-inner-content')
cy.get('.ant-popover-inner-content')
.find('[class^=ant-input]')
.last()
.type(`${endRange}`);

View File

@@ -79,16 +79,20 @@ describe('Dashboards list', () => {
it('should sort correctly in list mode', () => {
cy.getBySel('sort-header').eq(1).click();
cy.getBySel('loading-indicator').should('not.exist');
cy.getBySel('table-row').first().contains('Supported Charts Dashboard');
cy.getBySel('sort-header').eq(1).click();
cy.getBySel('loading-indicator').should('not.exist');
cy.getBySel('table-row').first().contains("World Bank's Data");
cy.getBySel('sort-header').eq(1).click();
});
it('should bulk select in list mode', () => {
toggleBulkSelect();
cy.get('#header-toggle-all').click();
cy.get('[aria-label="checkbox-on"]').should('have.length', 6);
cy.get('[aria-label="Select all"]').click();
cy.get('.ant-checkbox-input')
.should('be.checked')
.should('have.length', 6);
cy.getBySel('bulk-select-copy').contains('5 Selected');
cy.getBySel('bulk-select-action')
.should('have.length', 2)
@@ -97,7 +101,7 @@ describe('Dashboards list', () => {
expect($btns).to.contain('Export');
});
cy.getBySel('bulk-select-deselect-all').click();
cy.get('[aria-label="checkbox-on"]').should('have.length', 0);
cy.get('input[type="checkbox"]:checked').should('have.length', 0);
cy.getBySel('bulk-select-copy').contains('0 Selected');
cy.getBySel('bulk-select-action').should('not.exist');
});
@@ -195,6 +199,8 @@ describe('Dashboards list', () => {
cy.get('[data-test="table-row"] input[type="checkbox"]').eq(1).click();
cy.getBySel('bulk-select-action').eq(0).contains('Delete').click();
confirmDelete(true);
cy.getBySel('loading-indicator').should('exist');
cy.getBySel('loading-indicator').should('not.exist');
cy.getBySel('table-row')
.eq(0)
.should('not.contain', '3 - Sample dashboard');

View File

@@ -30,36 +30,36 @@ import {
const SAMPLE_DASHBOARDS_INDEXES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function openDashboardsAddedTo() {
cy.getBySel('actions-trigger').click();
cy.get('.antd5-dropdown-menu-submenu-title')
cy.getBySel('actions-trigger').should('be.visible').click();
cy.get('.ant-dropdown-menu-submenu-title')
.contains('On dashboards')
.trigger('mouseover', { force: true });
}
function closeDashboardsAddedTo() {
cy.get('.antd5-dropdown-menu-submenu-title')
cy.get('.ant-dropdown-menu-submenu-title')
.contains('On dashboards')
.trigger('mouseout', { force: true });
cy.getBySel('actions-trigger').click();
}
function verifyDashboardsSubmenuItem(dashboardName) {
cy.get('.antd5-dropdown-menu-submenu-popup').contains(dashboardName);
cy.get('.ant-dropdown-menu-submenu-popup').contains(dashboardName);
closeDashboardsAddedTo();
}
function verifyDashboardSearch() {
openDashboardsAddedTo();
cy.get('.antd5-dropdown-menu-submenu-popup').trigger('mouseover');
cy.get('.antd5-dropdown-menu-submenu-popup')
cy.get('.ant-dropdown-menu-submenu-popup').trigger('mouseover');
cy.get('.ant-dropdown-menu-submenu-popup')
.find('input[placeholder="Search"]')
.type('1');
cy.get('.antd5-dropdown-menu-submenu-popup').contains('1 - Sample dashboard');
cy.get('.antd5-dropdown-menu-submenu-popup')
cy.get('.ant-dropdown-menu-submenu-popup').contains('1 - Sample dashboard');
cy.get('.ant-dropdown-menu-submenu-popup')
.find('input[placeholder="Search"]')
.type('Blahblah');
cy.get('.antd5-dropdown-menu-submenu-popup').contains('No results found');
cy.get('.antd5-dropdown-menu-submenu-popup')
cy.get('.ant-dropdown-menu-submenu-popup').contains('No results found');
cy.get('.ant-dropdown-menu-submenu-popup')
.find('[aria-label="close-circle"]')
.click();
closeDashboardsAddedTo();
@@ -68,8 +68,8 @@ function verifyDashboardSearch() {
function verifyDashboardLink() {
interceptDashboardGet();
openDashboardsAddedTo();
cy.get('.antd5-dropdown-menu-submenu-popup').trigger('mouseover');
cy.get('.antd5-dropdown-menu-submenu-popup a')
cy.get('.ant-dropdown-menu-submenu-popup').trigger('mouseover');
cy.get('.ant-dropdown-menu-submenu-popup a')
.first()
.invoke('removeAttr', 'target')
.click();
@@ -80,8 +80,8 @@ function verifyMetabar(text) {
cy.getBySel('metadata-bar').contains(text);
}
function saveAndVerifyDashboard(number) {
saveChartToDashboard(`${number} - Sample dashboard`);
function saveAndVerifyDashboard(chartName, number) {
saveChartToDashboard(chartName, `${number} - Sample dashboard`);
verifyMetabar(
number > 1 ? `Added to ${number} dashboards` : 'Added to 1 dashboard',
);
@@ -106,17 +106,17 @@ describe('Cross-referenced dashboards', () => {
openDashboardsAddedTo();
verifyDashboardsSubmenuItem('None');
saveAndVerifyDashboard('1');
saveAndVerifyDashboard('2');
saveAndVerifyDashboard('3');
saveAndVerifyDashboard('4');
saveAndVerifyDashboard('5');
saveAndVerifyDashboard('6');
saveAndVerifyDashboard('7');
saveAndVerifyDashboard('8');
saveAndVerifyDashboard('9');
saveAndVerifyDashboard('10');
saveAndVerifyDashboard('11');
saveAndVerifyDashboard('1 - Sample chart', '1');
saveAndVerifyDashboard('1 - Sample chart', '2');
saveAndVerifyDashboard('1 - Sample chart', '3');
saveAndVerifyDashboard('1 - Sample chart', '4');
saveAndVerifyDashboard('1 - Sample chart', '5');
saveAndVerifyDashboard('1 - Sample chart', '6');
saveAndVerifyDashboard('1 - Sample chart', '7');
saveAndVerifyDashboard('1 - Sample chart', '8');
saveAndVerifyDashboard('1 - Sample chart', '9');
saveAndVerifyDashboard('1 - Sample chart', '10');
saveAndVerifyDashboard('1 - Sample chart', '11');
verifyDashboardSearch();
verifyDashboardLink();

View File

@@ -19,7 +19,7 @@
// ***********************************************
// Tests for setting controls in the UI
// ***********************************************
import { interceptChart } from 'cypress/utils';
import { interceptChart, setSelectSearchInput } from 'cypress/utils';
describe('Datasource control', () => {
const newMetricName = `abc${Date.now()}`;
@@ -40,44 +40,44 @@ describe('Datasource control', () => {
// create new metric
cy.get('[data-test="crud-add-table-item"]', { timeout: 10000 }).click();
cy.wait(1000);
cy.get(
'[data-test="table-content-rows"] [data-test="editable-title-input"]',
)
cy.get('.ant-table-body [data-test="textarea-editable-title-input"]')
.first()
.click();
cy.get(
'[data-test="table-content-rows"] [data-test="editable-title-input"]',
)
cy.get('.ant-table-body [data-test="textarea-editable-title-input"]')
.first()
.focus();
cy.focused().clear({ force: true });
cy.focused().type(`${newMetricName}{enter}`, { force: true });
cy.get('[data-test="datasource-modal-save"]').click();
cy.get('.antd5-modal-confirm-btns button').contains('OK').click();
cy.get('.ant-modal-confirm-btns button').contains('OK').click();
// select new metric
cy.get('[data-test=metrics]')
.contains('Drop columns/metrics here or click')
.click();
cy.get('input[aria-label="Select saved metrics"]').type(
`${newMetricName}{enter}`,
);
cy.get('input[aria-label="Select saved metrics"]')
.should('exist')
.then($input => {
setSelectSearchInput($input, newMetricName);
});
// delete metric
cy.get('[data-test="datasource-menu-trigger"]').click();
cy.get('[data-test="edit-dataset"]').click();
cy.get('.antd5-modal-content').within(() => {
cy.get('.ant-modal-content').within(() => {
cy.get('[data-test="collection-tab-Metrics"]')
.contains('Metrics')
.click();
});
cy.get(`input[value="${newMetricName}"]`)
cy.get(`[data-test="textarea-editable-title-input"]`)
.contains(newMetricName)
.closest('tr')
.find('[data-test="crud-delete-icon"]')
.click();
cy.get('[data-test="datasource-modal-save"]').click();
cy.get('.antd5-modal-confirm-btns button').contains('OK').click();
cy.get('.ant-modal-confirm-btns button').contains('OK').click();
cy.get('[data-test="metrics"]').contains(newMetricName).should('not.exist');
});
});
@@ -91,7 +91,7 @@ describe('Color scheme control', () => {
});
it('should show color options with and without tooltips', () => {
cy.get('#controlSections-tab-display').click();
cy.get('#controlSections-tab-CUSTOMIZE').click();
cy.get('.ant-select-selection-item .color-scheme-label').contains(
'Superset Colors',
);
@@ -102,10 +102,19 @@ describe('Color scheme control', () => {
cy.get('.color-scheme-tooltip').contains('Superset Colors');
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
cy.get('.color-scheme-label')
.contains('Superset Colors')
.trigger('mouseover');
cy.get('.color-scheme-label')
.contains('Superset Colors')
.trigger('mouseout');
cy.focused().type('lyftColors');
cy.getBySel('lyftColors').should('exist');
cy.getBySel('lyftColors').trigger('mouseover');
cy.get('.color-scheme-tooltip').should('not.exist');
cy.getBySel('lyftColors').trigger('mouseover', { force: true });
cy.get('.color-scheme-tooltip').should('not.be.visible');
});
});
describe('VizType control', () => {
@@ -120,7 +129,7 @@ describe('VizType control', () => {
cy.contains('View all charts').click();
cy.get('.antd5-modal-content').within(() => {
cy.get('.ant-modal-content').within(() => {
cy.get('button').contains('KPI').click(); // change categories
cy.get('[role="button"]').contains('Big Number').click();
cy.get('button').contains('Select').click();

View File

@@ -42,8 +42,8 @@ describe('Test explore links', () => {
cy.wait('@chartData').then(() => {
cy.get('code');
});
cy.get('.antd5-modal-content').within(() => {
cy.get('button.antd5-modal-close').first().click({ force: true });
cy.get('.ant-modal-content').within(() => {
cy.get('button.ant-modal-close').first().click({ force: true });
});
});
@@ -124,7 +124,7 @@ describe('Test explore links', () => {
.find('input[aria-label="Select a dashboard"]')
.type(`${dashboardTitle}`, { force: true });
cy.get(`.ant-select-item[label="${dashboardTitle}"]`).click({
cy.get(`.ant-select-item[title="${dashboardTitle}"]`).click({
force: true,
});
@@ -157,7 +157,7 @@ describe('Test explore links', () => {
.find('input[aria-label="Select a dashboard"]')
.type(`${dashboardTitle}{enter}`, { force: true });
cy.get(`.ant-select-item[label="${dashboardTitle}"]`).click({
cy.get(`.ant-select-item[title="${dashboardTitle}"]`).click({
force: true,
});

View File

@@ -67,7 +67,7 @@ export function setFilter(filter: string, option: string) {
cy.wait('@filtering');
}
export function saveChartToDashboard(dashboardName: string) {
export function saveChartToDashboard(chartName: string, dashboardName: string) {
interceptDashboardGet();
interceptUpdate();
interceptExploreGet();
@@ -75,23 +75,30 @@ export function saveChartToDashboard(dashboardName: string) {
cy.getBySel('query-save-button')
.should('be.enabled')
.should('not.be.disabled')
.click();
cy.getBySelLike('chart-modal').should('be.visible');
cy.get(
'[data-test="save-chart-modal-select-dashboard-form"] [aria-label="Select a dashboard"]',
)
.first()
.click();
cy.get(
'.ant-select-selection-search-input[aria-label="Select a dashboard"]',
).type(dashboardName, { force: true });
cy.get(`.ant-select-item-option[title="${dashboardName}"]`).click();
cy.getBySel('btn-modal-save').click();
.click({ force: true });
cy.wait('@update');
cy.getBySel('save-modal-body')
.should('be.visible')
.then($modal => {
cy.wait(500);
cy.wrap($modal)
.find(
'.ant-select-selection-search-input[aria-label="Select a dashboard"]',
)
.type(dashboardName, { force: true });
cy.wrap($modal)
.find(`.ant-select-item-option[title="${dashboardName}"]`)
.click();
cy.getBySel('btn-modal-save').click();
cy.wait('@update');
});
cy.getBySel('save-modal-body').should('not.exist');
cy.getBySel('query-save-button').should('be.disabled');
cy.wait('@get');
cy.wait('@getExplore');
cy.contains(`was added to dashboard [${dashboardName}]`);
cy.contains(`Chart [${chartName}] has been overwritten`);
cy.getBySel('query-save-button').should('be.enabled');
}
export function visitSampleChartFromList(chartName: string) {

View File

@@ -49,7 +49,7 @@ describe('Visualization > Box Plot', () => {
it('should allow type to search color schemes', () => {
verify(BOX_PLOT_FORM_DATA);
cy.get('#controlSections-tab-display').click();
cy.get('#controlSections-tab-CUSTOMIZE').click();
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
cy.focused().type('supersetColors{enter}');

View File

@@ -89,7 +89,7 @@ describe('Visualization > Compare', () => {
it('should allow type to search color schemes and apply the scheme', () => {
verify(COMPARE_FORM_DATA);
cy.get('#controlSections-tab-display').click();
cy.get('#controlSections-tab-CUSTOMIZE').click();
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
cy.focused().type('supersetColors{enter}');

View File

@@ -36,10 +36,10 @@ describe('Download Chart > Bar chart', () => {
};
cy.visitChartByParams(formData);
cy.get('.header-with-actions .antd5-dropdown-trigger').click();
cy.get(':nth-child(3) > .antd5-dropdown-menu-submenu-title').click();
cy.get('.header-with-actions .ant-dropdown-trigger').click();
cy.get(':nth-child(3) > .ant-dropdown-menu-submenu-title').click();
cy.get(
'.antd5-dropdown-menu-submenu > .antd5-dropdown-menu li:nth-child(3)',
'.ant-dropdown-menu-submenu > .ant-dropdown-menu li:nth-child(3)',
).click();
cy.verifyDownload('.jpg', {
contains: true,

View File

@@ -64,7 +64,7 @@ describe('Visualization > Gauge', () => {
it('should allow type to search color schemes', () => {
verify(GAUGE_FORM_DATA);
cy.get('#controlSections-tab-display').click();
cy.get('#controlSections-tab-CUSTOMIZE').click();
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
cy.focused().type('bnbColors{enter}');

View File

@@ -80,7 +80,7 @@ describe('Visualization > Graph', () => {
it('should allow type to search color schemes', () => {
verify(GRAPH_FORM_DATA);
cy.get('#controlSections-tab-display').click();
cy.get('#controlSections-tab-CUSTOMIZE').click();
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
cy.focused().type('bnbColors{enter}');

View File

@@ -71,7 +71,7 @@ describe('Visualization > Pie', () => {
it('should allow type to search color schemes', () => {
verify(PIE_FORM_DATA);
cy.get('#controlSections-tab-display').click();
cy.get('#controlSections-tab-CUSTOMIZE').click();
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
cy.focused().type('supersetColors{enter}');

View File

@@ -86,7 +86,7 @@ describe('Visualization > Sunburst', () => {
it('should allow type to search color schemes', () => {
verify(SUNBURST_FORM_DATA);
cy.get('#controlSections-tab-display').click();
cy.get('#controlSections-tab-CUSTOMIZE').click();
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
cy.focused().type('supersetColors{enter}');

View File

@@ -193,7 +193,9 @@ describe('Visualization > Table', () => {
});
// should display in raw records mode
cy.get('div[data-test="query_mode"] .btn.active').contains('Raw records');
cy.get(
'div[data-test="query_mode"] .ant-radio-button-wrapper-checked',
).contains('Raw records');
cy.get('div[data-test="all_columns"]').should('be.visible');
cy.get('div[data-test="groupby"]').should('not.exist');
@@ -201,8 +203,12 @@ describe('Visualization > Table', () => {
cy.get('[data-test="row-count-label"]').contains('100 rows');
// should allow switch back to aggregate mode
cy.get('div[data-test="query_mode"] .btn').contains('Aggregate').click();
cy.get('div[data-test="query_mode"] .btn.active').contains('Aggregate');
cy.get('div[data-test="query_mode"] .ant-radio-button-wrapper')
.contains('Aggregate')
.click();
cy.get(
'div[data-test="query_mode"] .ant-radio-button-wrapper-checked',
).contains('Aggregate');
cy.get('div[data-test="all_columns"]').should('not.exist');
cy.get('div[data-test="groupby"]').should('be.visible');
});
@@ -254,6 +260,8 @@ describe('Visualization > Table', () => {
});
it('Test row limit with server pagination toggle', () => {
const serverPaginationSelector =
'[data-test="server_pagination-header"] div.pull-left [type="checkbox"]';
cy.visitChartByParams({
...VIZ_DEFAULTS,
metrics: ['count'],
@@ -261,7 +269,7 @@ describe('Visualization > Table', () => {
});
// Enable server pagination
cy.get('[data-test="server_pagination-header"] div.pull-left').click();
cy.get(serverPaginationSelector).click();
// Click row limit control and select high value (200k)
cy.get('div[aria-label="Row limit"]').click();
@@ -275,7 +283,7 @@ describe('Visualization > Table', () => {
cy.get('[data-test="error-tooltip"]').should('not.exist');
// Disable server pagination
cy.get('[data-test="server_pagination-header"] div.pull-left').click();
cy.get(serverPaginationSelector).click();
// Verify error tooltip appears
cy.get('[data-test="error-tooltip"]').should('be.visible');
@@ -284,17 +292,17 @@ describe('Visualization > Table', () => {
cy.get('[data-test="error-tooltip"]').trigger('mouseover');
// Verify tooltip content
cy.get('.antd5-tooltip-inner').should('be.visible');
cy.get('.antd5-tooltip-inner').should(
cy.get('.ant-tooltip-inner').should('be.visible');
cy.get('.ant-tooltip-inner').should(
'contain',
'Server pagination needs to be enabled for values over',
);
// Hide the tooltip by adding display:none style
cy.get('.antd5-tooltip').invoke('attr', 'style', 'display: none');
cy.get('.ant-tooltip').invoke('attr', 'style', 'display: none');
// Enable server pagination again
cy.get('[data-test="server_pagination-header"] div.pull-left').click();
cy.get(serverPaginationSelector).click();
cy.get('[data-test="error-tooltip"]').should('not.exist');
@@ -319,11 +327,11 @@ describe('Visualization > Table', () => {
.trigger('mouseover');
// Wait for tooltip content and verify
cy.get('.antd5-tooltip-inner').should('exist');
cy.get('.antd5-tooltip-inner').should('be.visible');
cy.get('.ant-tooltip-inner').should('exist');
cy.get('.ant-tooltip-inner').should('be.visible');
// Verify tooltip content separately
cy.get('.antd5-tooltip-inner').should('contain', 'Value cannot exceed');
cy.get('.ant-tooltip-inner').should('contain', 'Value cannot exceed');
});
it('Test sorting with server pagination enabled', () => {
@@ -393,12 +401,12 @@ describe('Visualization > Table', () => {
cy.wait('@chartData');
// Basic search test
cy.get('span.dt-global-filter input.form-control.input-sm').should(
'be.visible',
);
const searchInputSelector = '.dt-global-filter input';
cy.get('span.dt-global-filter input.form-control.input-sm').type('John');
// Basic search test
cy.get(searchInputSelector).should('be.visible');
cy.get(searchInputSelector).type('John');
cy.wait('@chartData');
@@ -407,11 +415,11 @@ describe('Visualization > Table', () => {
});
// Clear and test case-insensitive search
cy.get('span.dt-global-filter input.form-control.input-sm').clear();
cy.get(searchInputSelector).clear();
cy.wait('@chartData');
cy.get('span.dt-global-filter input.form-control.input-sm').type('mary');
cy.get(searchInputSelector).type('mary');
cy.wait('@chartData');
@@ -420,9 +428,9 @@ describe('Visualization > Table', () => {
});
// Test special characters
cy.get('span.dt-global-filter input.form-control.input-sm').clear();
cy.get(searchInputSelector).clear();
cy.get('span.dt-global-filter input.form-control.input-sm').type('Nicole');
cy.get(searchInputSelector).type('Nicole');
cy.wait('@chartData');
@@ -431,9 +439,9 @@ describe('Visualization > Table', () => {
});
// Test no results
cy.get('span.dt-global-filter input.form-control.input-sm').clear();
cy.get(searchInputSelector).clear();
cy.get('span.dt-global-filter input.form-control.input-sm').type('XYZ123');
cy.get(searchInputSelector).type('XYZ123');
cy.wait('@chartData');
@@ -450,9 +458,9 @@ describe('Visualization > Table', () => {
cy.get('.ant-select-item-option').contains('state').click();
cy.get('span.dt-global-filter input.form-control.input-sm').clear();
cy.get(searchInputSelector).clear();
cy.get('span.dt-global-filter input.form-control.input-sm').type('CA');
cy.get(searchInputSelector).type('CA');
cy.wait('@chartData');
cy.wait(1000);

View File

@@ -22,7 +22,7 @@ describe('SqlLab query tabs', () => {
});
const tablistSelector = '[data-test="sql-editor-tabs"] > [role="tablist"]';
const tabSelector = `${tablistSelector} [role="tab"]`;
const tabSelector = `${tablistSelector} [role="tab"]:not([type="button"])`;
it('allows you to create and close a tab', () => {
cy.get(tabSelector).then(tabs => {
@@ -80,9 +80,9 @@ describe('SqlLab query tabs', () => {
// configure some editor settings
cy.get(editorInput).type('some random query string', { force: true });
cy.get(queryLimitSelector).parent().click({ force: true });
cy.get('.antd5-dropdown-menu')
cy.get('.ant-dropdown-menu')
.last()
.find('.antd5-dropdown-menu-item')
.find('.ant-dropdown-menu-item')
.first()
.click({ force: true });

View File

@@ -25,16 +25,16 @@ export function dataTestChartName(chartName: string): string {
export const pageHeader = {
logo: '.navbar-brand > img',
headerNavigationItem: '.antd5-menu-submenu-title',
headerNavigationItem: '.ant-menu-submenu-title',
headerNavigationDropdown: "[aria-label='triangle-down']",
headerNavigationItemMenu: '.antd5-menu-item-group-list',
plusIcon: ':nth-child(2) > .antd5-menu-submenu-title',
headerNavigationItemMenu: '.ant-menu-item-group-list',
plusIcon: ':nth-child(2) > .ant-menu-submenu-title',
plusIconMenuOptions: {
sqlQueryOption: dataTestLocator('menu-item-SQL query'),
chartOption: dataTestLocator('menu-item-Chart'),
dashboardOption: dataTestLocator('menu-item-Dashboard'),
},
plusMenu: '.antd5-menu-submenu-popup',
plusMenu: '.ant-menu-submenu-popup',
barButtons: '[role="presentation"]',
sqlLabMenu: '[id="item_3$Menu"]',
dataMenu: '[id="item_4$Menu"]',
@@ -48,12 +48,12 @@ export const profile = {
favoritesSpace: '#rc-tabs-0-panel-2',
};
export const securityAccess = {
rolesBubble: '.antd5-badge-count',
rolesBubble: '.ant-badge-count',
};
export const homePage = {
homeSection: {
sectionArea: '.ant-collapse-content-box',
sectionElement: '.antd5-card-meta-title',
sectionElement: '.ant-card-meta-title',
},
sections: {
expandedSection: '.ant-collapse-item-active',
@@ -94,19 +94,19 @@ export const databasesPage = {
dbDropdown: '[class="ant-select-selection-search-input"]',
dbDropdownMenu: '.rc-virtual-list-holder-inner',
dbDropdownMenuItem: '[class="ant-select-item-option-content"]',
infoAlert: '.antd5-alert',
infoAlert: '.ant-alert',
serviceAccountInput: '[name="credentials_info"]',
connectionStep: {
modal: '.antd5-modal-content',
modalBody: '.antd5-modal-body',
modal: '.ant-modal-content',
modalBody: '.ant-modal-body',
stepTitle: '.css-7x6kk > h4',
helperBottom: '.helper-bottom',
postgresDatabase: '[name="database"]',
dbInput: '[name="database_name"]',
alertMessage: '.antd5-alert-message',
alertMessage: '.ant-alert-message',
errorField: '[role="alert"]',
uploadJson: '[title="Upload JSON file"]',
chooseFile: '[class="antd5-btn input-upload-btn"]',
chooseFile: '[class="ant-btn input-upload-btn"]',
additionalParameters: '[name="query_input"]',
sqlAlchemyUriInput: dataTestLocator('sqlalchemy-uri-input'),
advancedTab: '#rc-tabs-0-tab-2',
@@ -140,7 +140,7 @@ export const sqlLabView = {
tabsNavList: "[class='ant-tabs-nav-list']",
tab: "[class='ant-tabs-tab-btn']",
addTabButton: dataTestLocator('add-tab-icon'),
tooltip: '.antd5-tooltip-content',
tooltip: '.ant-tooltip-content',
tabName: '.css-1suejie',
schemaInput: '[data-test=DatabaseSelector] > :nth-child(2)',
loadingIndicator: '.Select__loading-indicator',
@@ -148,9 +148,9 @@ export const sqlLabView = {
examplesMenuItem: '[title="examples"]',
tableInput: ':nth-child(4) > .select > :nth-child(1)',
sqlEditor: '#brace-editor textarea',
saveAsButton: '.SaveQuery > .antd5-btn',
saveAsButton: '.SaveQuery > .ant-btn',
saveAsModal: {
footer: '.antd5-modal-footer',
footer: '.ant-modal-footer',
queryNameInput: 'input[class^="ant-input"]',
},
sqlToolbar: {
@@ -158,15 +158,15 @@ export const sqlLabView = {
runButton: '.css-d3dxop',
},
rowsLimit: {
dropdown: '.antd5-dropdown-menu',
limitButton: '.antd5-dropdown-menu-item',
dropdown: '.ant-dropdown-menu',
limitButton: '.ant-dropdown-menu-item',
limitButtonText: '.css-151uxnz',
limitTextWithValue: '[class="antd5-dropdown-trigger"]',
limitTextWithValue: '[class="ant-dropdown-trigger"]',
},
renderedTableHeader: '.ReactVirtualized__Table__headerRow',
renderedTableRow: '.ReactVirtualized__Table__row',
errorBody: '.error-body',
alertMessage: '.antd5-alert-message',
alertMessage: '.ant-alert-message',
historyTable: {
header: '[role=columnheader]',
table: '.QueryTable',
@@ -195,16 +195,16 @@ export const savedQuery = {
export const annotationLayersView = {
emptyDescription: {
description: '.ant-empty-description',
addAnnotationLayerButton: '.ant-empty-footer > .antd5-btn',
addAnnotationLayerButton: '.ant-empty-footer > .ant-btn',
},
modal: {
content: {
content: '.antd5-modal-body',
title: '.antd5-modal-body > :nth-child(2) > input',
content: '.ant-modal-body',
title: '.ant-modal-body > :nth-child(2) > input',
description: "[name='descr']",
},
footer: {
footer: '.antd5-modal-footer',
footer: '.ant-modal-footer',
addButton: dataTestLocator('modal-confirm-button'),
cancelButton: dataTestLocator('modal-cancel-button'),
},
@@ -216,7 +216,7 @@ export const datasetsList = {
newDatasetModal: {
inputField: '[class="section"]',
addButton: dataTestLocator('modal-confirm-button'),
body: '.antd5-modal-body',
body: '.ant-modal-body',
},
table: {
tableRow: {
@@ -261,7 +261,7 @@ export const datasetsList = {
},
},
deleteDatasetModal: {
modal: '.antd5-modal-content',
modal: '.ant-modal-content',
deleteInput: dataTestLocator('delete-modal-input'),
deleteButton: dataTestLocator('modal-confirm-button'),
text: '.css-kxmt87',
@@ -275,8 +275,8 @@ export const chartListView = {
bulkSelect: dataTestLocator('bulk-select'),
},
header: {
cardView: '[aria-label="appstore"]',
listView: '[aria-label="unordered-list"]',
cardView: '[aria-label="card-view"]',
listView: '[aria-label="list-view"]',
sort: '[class="ant-select-selection-search-input"][aria-label="Sort"]',
sortRecentlyModifiedMenuOption: '[label="Recently modified"]',
sortAlphabeticalMenuOption: '[label="Alphabetical"]',
@@ -284,7 +284,7 @@ export const chartListView = {
},
card: {
card: dataTestLocator('styled-card'),
cardCover: '[class="antd5-card-cover"]',
cardCover: '[class="ant-card-cover"]',
cardImage: '[class="gradient-container"]',
starIcon: dataTestLocator('fave-unfave-icon'),
},
@@ -294,8 +294,8 @@ export const chartListView = {
},
table: {
bulkSelect: {
checkboxOff: '[aria-label="checkbox-off"]',
checkboxOn: '[aria-label="checkbox-on"]',
checkboxOff: 'input[type="checkbox"]:checked',
checkboxOn: 'input[type="checkbox"]:not(:checked)',
action: dataTestLocator('bulk-select-action'),
},
tableList: dataTestLocator('listview-table'),
@@ -316,14 +316,14 @@ export const chartListView = {
};
export const nativeFilters = {
modal: {
container: '.antd5-modal',
footer: '.antd5-modal-footer',
container: '.ant-modal',
footer: '.ant-modal-footer',
saveButton: dataTestLocator('native-filter-modal-save-button'),
cancelButton: dataTestLocator('native-filter-modal-cancel-button'),
confirmCancelButton: dataTestLocator(
'native-filter-modal-confirm-cancel-button',
),
alertXUnsavedFilters: '.antd5-alert-message',
alertXUnsavedFilters: '.ant-alert-message',
tabsList: {
filterItemsContainer: dataTestLocator('filter-title-container'),
tabsContainer: '[class="ant-tabs-nav-list"]',
@@ -398,7 +398,7 @@ export const dashboardListView = {
},
card: {
card: dataTestLocator('styled-card'),
cardCover: '[class="antd5-card-cover"]',
cardCover: '[class="ant-card-cover"]',
cardImage: '[class="gradient-container"]',
selectedStarIcon: "[aria-label='star']",
unselectedStarIcon: "[aria-label='star']",
@@ -432,7 +432,7 @@ export const dashboardListView = {
newDashboardButton: '.css-yff34v',
},
importModal: {
selectFileButton: '.ant-upload > .antd5-btn > span',
selectFileButton: '.ant-upload > .ant-btn > span',
importButton: dataTestLocator('modal-confirm-button'),
},
header: {
@@ -474,15 +474,15 @@ export const exploreView = {
},
chartAreaItem: '.nv-legend-text',
viewQueryModal: {
container: '.antd5-modal-content',
closeButton: 'button.antd5-modal-close',
container: '.ant-modal-content',
closeButton: 'button.ant-modal-close',
},
embedCodeModal: {
container: dataTestLocator('embed-code-popover'),
textfield: dataTestLocator('embed-code-textarea'),
},
saveModal: {
modal: '.antd5-modal-content',
modal: '.ant-modal-content',
chartNameInput: dataTestLocator('new-chart-name'),
dashboardNameInput: '.ant-select-selection-search-input',
addToDashboardInput: dataTestLocator(
@@ -553,7 +553,7 @@ export const exploreView = {
timeSection: {
timeRangeFilter: dataTestLocator('time-range-trigger'),
timeRangeFilterModal: {
container: '.antd5-popover-content',
container: '.ant-popover-content',
footer: '.footer',
cancelButton: dataTestLocator('cancel-button'),
configureLastTimeRange: {
@@ -578,15 +578,15 @@ export const exploreView = {
},
},
editDatasetModal: {
container: '.antd5-modal-content',
container: '.ant-modal-content',
datasetTabsContainer: dataTestLocator('edit-dataset-tabs'),
saveButton: dataTestLocator('datasource-modal-save'),
metricsTab: {
addItem: dataTestLocator('crud-add-table-item'),
rowsContainer: dataTestLocator('table-content-rows'),
rowsContainer: '.ant-table-body',
},
confirmModal: {
okButton: '.antd5-modal-confirm-btns .antd5-btn-primary',
okButton: '.ant-modal-confirm-btns .ant-btn-primary',
},
},
visualizationTypeModal: {
@@ -617,12 +617,12 @@ export const dashboardView = {
closeButton: dataTestLocator('close-button'),
},
saveModal: {
modal: '.antd5-modal-content',
modal: '.ant-modal-content',
dashboardNameInput: '.ant-input',
saveButton: dataTestLocator('modal-save-dashboard-button'),
},
dashboardProperties: {
modal: '.antd5-modal-content',
modal: '.ant-modal-content',
dashboardTitleInput: dataTestLocator('dashboard-title-input'),
modalButton: '[type="button"]',
},
@@ -631,7 +631,7 @@ export const dashboardView = {
refreshChart: dataTestLocator('refresh-chart-menu-item'),
},
threeDotsMenuIcon:
'.header-with-actions .right-button-panel .antd5-dropdown-trigger',
'.header-with-actions .right-button-panel .ant-dropdown-trigger',
threeDotsMenuDropdown: dataTestLocator('header-actions-menu'),
refreshDashboard: dataTestLocator('refresh-dashboard-menu-item'),
saveAsMenuOption: dataTestLocator('save-as-menu-item'),

View File

@@ -69,7 +69,21 @@ Cypress.Commands.add('loadDashboardFixtures', () =>
}),
);
const PATHS_TO_SKIP_LOGIN = ['login', 'register'];
const skipLogin = () => {
for (const path of PATHS_TO_SKIP_LOGIN) {
if (Cypress.currentTest.title.toLowerCase().includes(path)) {
return true;
}
}
return false;
};
before(() => {
if (skipLogin()) {
return;
}
cy.login();
Cypress.Cookies.defaults({ preserve: 'session' });
cy.loadChartFixtures();
@@ -77,6 +91,9 @@ before(() => {
});
beforeEach(() => {
if (skipLogin()) {
return;
}
cy.cleanDashboards();
cy.cleanCharts();
});

View File

@@ -143,3 +143,29 @@ export function resize(selector: string) {
},
};
}
export const setSelectSearchInput = (
$input: any,
value: string,
async = false,
) => {
// Ant Design 5 Select crashes Chromium with type/click events when showSearch is true.
// This copies the value directly to the input element as a workaround.
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
'value',
)?.set;
nativeInputValueSetter?.call($input[0], value);
// Trigger the input and change events
if (async) {
$input[0].dispatchEvent(new Event('mousedown', { bubbles: true }));
}
$input[0].dispatchEvent(new Event('input', { bubbles: true }));
$input[0].dispatchEvent(new Event('change', { bubbles: true }));
cy.get('.ant-select-item-option-content').should('exist').first().click({
force: true,
});
};

View File

@@ -28,3 +28,5 @@ export const DATABASE_LIST = '/databaseview/list';
export const DATASET_LIST_PATH = 'tablemodelview/list';
export const ALERT_LIST = '/alert/list/';
export const REPORT_LIST = '/report/list/';
export const LOGIN = '/login/';
export const REGISTER = '/register/';

View File

@@ -16,10 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
// timezone for unit tests
process.env.TZ = 'America/New_York';
module.exports = {
testRegex:
'\\/superset-frontend\\/(spec|src|plugins|packages|tools)\\/.*(_spec|\\.test)\\.[jt]sx?$',
@@ -30,7 +28,9 @@ module.exports = {
'^src/(.*)$': '<rootDir>/src/$1',
'^spec/(.*)$': '<rootDir>/spec/$1',
// mapping plugins of superset-ui to source code
'@superset-ui/(.*)$': '<rootDir>/node_modules/@superset-ui/$1/src',
'^@superset-ui/([^/]+)/(.*)$':
'<rootDir>/node_modules/@superset-ui/$1/src/$2',
'^@superset-ui/([^/]+)$': '<rootDir>/node_modules/@superset-ui/$1/src',
},
testEnvironment: 'jsdom',
modulePathIgnorePatterns: ['<rootDir>/packages/generator-superset'],
@@ -55,7 +55,7 @@ module.exports = {
],
coverageReporters: ['lcov', 'json-summary', 'html', 'text'],
transformIgnorePatterns: [
'node_modules/(?!d3-(interpolate|color|time)|remark-gfm|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|@rjsf/*.|sinon|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|jest-enzyme)',
'node_modules/(?!d3-(interpolate|color|time)|remark-gfm|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|@rjsf/*.|sinon|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|jest-enzyme|geostyler|geostyler-.*)',
],
preset: 'ts-jest',
transform: {

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,7 @@
"src/setup/*"
],
"scripts": {
"_prettier": "prettier './({src,spec,cypress-base,plugins,packages,.storybook}/**/*{.js,.jsx,.ts,.tsx,.css,.less,.scss,.sass}|package.json)'",
"_prettier": "prettier './({src,spec,cypress-base,plugins,packages,.storybook}/**/*{.js,.jsx,.ts,.tsx,.css,.scss,.sass}|package.json)'",
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production BABEL_ENV=\"${BABEL_ENV:=production}\" webpack --color --mode production",
"build-dev": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=development webpack --mode=development --color",
"build-instrumented": "cross-env NODE_ENV=production BABEL_ENV=instrumented webpack --mode=production --color",
@@ -70,7 +70,10 @@
"storybook": "cross-env NODE_ENV=development BABEL_ENV=development storybook dev -p 6006",
"tdd": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --watch",
"test": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80% --silent",
"test-loud": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80%",
"type": "tsc --noEmit",
"tsc": "tsc ",
"ts-migrate-all": "ts-migrate rename . && ts-migrate migrate --aliases tsfixme . && ts-migrate reignore .",
"update-maps": "jupyter nbconvert --to notebook --execute --inplace 'plugins/legacy-plugin-chart-country-map/scripts/Country Map GeoJSON Generator.ipynb' -Xfrozen_modules=off",
"validate-release": "../RELEASING/validate_this_release.sh"
},
@@ -81,12 +84,9 @@
"last 3 edge versions"
],
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.13.3",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.3.0",
"@fontsource/fira-code": "^5.0.18",
"@fontsource/inter": "^5.0.20",
"@reduxjs/toolkit": "^1.9.3",
"@rjsf/core": "^5.21.1",
"@rjsf/utils": "^5.24.3",
@@ -115,6 +115,7 @@
"@superset-ui/switchboard": "file:./packages/superset-ui-switchboard",
"@types/d3-format": "^3.0.1",
"@types/d3-time-format": "^4.0.3",
"@types/react-google-recaptcha": "^2.1.9",
"@visx/axis": "^3.8.0",
"@visx/grid": "^3.5.0",
"@visx/responsive": "^3.0.0",
@@ -122,22 +123,16 @@
"@visx/tooltip": "^3.0.0",
"@visx/xychart": "^3.5.1",
"abortcontroller-polyfill": "^1.7.8",
"ace-builds": "^1.41.0",
"ag-grid-community": "33.1.1",
"ag-grid-react": "33.1.1",
"antd": "4.10.3",
"antd-v5": "npm:antd@^5.18.0",
"bootstrap": "^3.4.1",
"brace": "^0.11.1",
"antd": "^5.24.6",
"chrono-node": "^2.7.8",
"classnames": "^2.2.5",
"core-js": "^3.38.1",
"d3-color": "^3.1.0",
"d3-scale": "^2.1.2",
"dayjs": "^1.11.13",
"dom-to-image-more": "^3.2.0",
"dom-to-pdf": "^0.3.2",
"dompurify": "^3.2.4",
"echarts": "^5.6.0",
"emotion-rgba": "0.0.12",
"eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings",
@@ -145,12 +140,14 @@
"fs-extra": "^11.2.0",
"fuse.js": "^7.0.0",
"geolib": "^2.0.24",
"geostyler": "^12.0.2",
"geostyler": "^14.1.3",
"geostyler-data": "^1.0.0",
"geostyler-openlayers-parser": "^4.3.0",
"geostyler-qgis-parser": "^2.0.0",
"geostyler-style": "^7.5.0",
"geostyler-wfs-parser": "^2.0.3",
"googleapis": "^130.0.0",
"html-webpack-plugin": "^5.6.3",
"immer": "^10.1.1",
"interweave": "^13.1.0",
"jquery": "^3.7.1",
@@ -174,17 +171,15 @@
"rc-trigger": "^5.3.4",
"re-resizable": "^6.10.1",
"react": "^17.0.2",
"react-ace": "^10.1.0",
"react-checkbox-tree": "^1.8.0",
"react-color": "^2.13.8",
"react-diff-viewer-continued": "^3.4.0",
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^17.0.2",
"react-draggable": "^4.4.6",
"react-google-recaptcha": "^3.1.0",
"react-hot-loader": "^4.13.1",
"react-intersection-observer": "^9.16.0",
"react-js-cron": "^2.1.2",
"react-json-tree": "^0.17.0",
"react-lines-ellipsis": "^0.15.4",
"react-loadable": "^5.5.0",
@@ -198,14 +193,12 @@
"react-syntax-highlighter": "^15.4.5",
"react-table": "^7.8.0",
"react-transition-group": "^4.4.5",
"react-ultimate-pagination": "^1.3.2",
"react-virtualized-auto-sizer": "^1.0.25",
"react-window": "^1.8.10",
"redux": "^4.2.1",
"redux-localstorage": "^0.4.1",
"redux-thunk": "^2.1.0",
"redux-undo": "^1.0.0-beta9-9-7",
"regenerator-runtime": "^0.14.1",
"rimraf": "^6.0.1",
"rison": "^0.1.1",
"scroll-into-view-if-needed": "^3.1.0",
@@ -263,12 +256,12 @@
"@types/dom-to-image": "^2.6.7",
"@types/enzyme": "^3.10.18",
"@types/fetch-mock": "^7.3.2",
"@types/jest": "^29.5.12",
"@types/jquery": "^3.5.8",
"@types/jest": "^29.5.14",
"@types/js-levenshtein": "^1.1.3",
"@types/json-bigint": "^1.0.4",
"@types/math-expression-evaluator": "^1.3.3",
"@types/mousetrap": "^1.6.15",
"@types/node": "^22.12.0",
"@types/react": "^17.0.83",
"@types/react-dom": "^17.0.26",
"@types/react-gravatar": "^2.6.14",
@@ -278,7 +271,6 @@
"@types/react-resizable": "^3.0.8",
"@types/react-router-dom": "^5.3.3",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/react-table": "^7.7.20",
"@types/react-transition-group": "^4.4.12",
"@types/react-ultimate-pagination": "^1.2.4",
"@types/react-virtualized-auto-sizer": "^1.0.4",
@@ -287,6 +279,7 @@
"@types/redux-mock-store": "^1.0.6",
"@types/rison": "0.1.0",
"@types/sinon": "^17.0.3",
"@types/testing-library__jest-dom": "^5.14.9",
"@types/tinycolor2": "^1.4.3",
"@types/yargs": "12 - 18",
"@typescript-eslint/eslint-plugin": "^5.62.0",
@@ -305,9 +298,12 @@
"css-minimizer-webpack-plugin": "^7.0.2",
"enzyme": "^3.11.0",
"enzyme-matchers": "^7.1.2",
"esbuild": "^0.20.0",
"esbuild-loader": "^4.2.2",
"eslint": "^8.56.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^7.2.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint-plugin-cypress": "^3.6.0",
"eslint-plugin-file-progress": "^1.5.0",
@@ -331,13 +327,13 @@
"html-webpack-plugin": "^5.6.3",
"imports-loader": "^5.0.0",
"jest": "^29.7.0",
"jest-environment-enzyme": "^7.1.2",
"jest-environment-jsdom": "^29.7.0",
"jest-enzyme": "^7.1.2",
"jest-html-reporter": "^3.10.2",
"jest-websocket-mock": "^2.5.0",
"jsdom": "^26.0.0",
"lerna": "^8.2.1",
"less": "^4.2.0",
"less-loader": "^12.2.0",
"mini-css-extract-plugin": "^2.9.0",
"open-cli": "^8.0.0",
"po2json": "^0.4.5",
@@ -355,7 +351,10 @@
"thread-loader": "^4.0.4",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.1",
"ts-migrate": "^0.1.35",
"ts-migrate-plugins": "^0.1.35",
"tscw-config": "^1.1.2",
"tsx": "^4.19.2",
"typescript": "5.1.6",
"vm-browserify": "^1.1.2",
"webpack": "^5.98.0",
@@ -366,6 +365,12 @@
"webpack-sources": "^3.2.3",
"webpack-visualizer-plugin2": "^1.1.0"
},
"peerDependencies": {
"ace-builds": "^1.41.0",
"core-js": "^3.38.1",
"react-ace": "^10.1.0",
"regenerator-runtime": "^0.14.1"
},
"engines": {
"node": "^20.16.0",
"npm": "^10.8.1"
@@ -375,7 +380,8 @@
"d3-color": "^3.1.0",
"puppeteer": "^22.4.1",
"underscore": "^1.13.7",
"jspdf": "^3.0.1"
"jspdf": "^3.0.1",
"nwsapi": "^2.2.13"
},
"readme": "ERROR: No README data found!",
"scarfSettings": {

View File

@@ -4194,7 +4194,7 @@
"peerDependencies": {
"@emotion/react": "^11.4.1",
"@types/react": "*",
"antd": "^4.9.4",
"antd": "^5.24.6",
"react": "^16.13.1",
"react-dom": "^16.13.1"
}

View File

@@ -25,29 +25,14 @@ import { <%= packageLabel %>Props, <%= packageLabel %>StylesProps } from './type
// Theming variables are provided for your use via a ThemeProvider
// imported from @superset-ui/core. For variables available, please visit
// https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-core/src/style/index.ts
// https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-core/src/theme/index.ts
const Styles = styled.div<<%= packageLabel %>StylesProps>`
background-color: ${({ theme }) => theme.colors.secondary.light2};
padding: ${({ theme }) => theme.gridUnit * 4}px;
border-radius: ${({ theme }) => theme.gridUnit * 2}px;
background-color: ${({ theme }) => theme.colors.primary.light2};
padding: ${({ theme }) => theme.sizeUnit * 4}px;
border-radius: ${({ theme }) => theme.borderRadius}px;
height: ${({ height }) => height}px;
width: ${({ width }) => width}px;
h3 {
/* You can use your props to control CSS! */
margin-top: 0;
margin-bottom: ${({ theme }) => theme.gridUnit * 3}px;
font-size: ${({ theme, headerFontSize }) =>
theme.typography.sizes[headerFontSize]}px;
font-weight: ${({ theme, boldText }) =>
theme.typography.weights[boldText ? 'bold' : 'normal']};
}
pre {
height: ${({ theme, headerFontSize, height }) =>
height - theme.gridUnit * 12 - theme.typography.sizes[headerFontSize]}px;
}
`;
/**

View File

@@ -40,7 +40,6 @@
"@testing-library/react-hooks": "*",
"@testing-library/user-event": "*",
"ace-builds": "^1.4.14",
"antd": "4.10.3",
"brace": "^0.11.1",
"memoize-one": "^5.1.1",
"react": "^17.0.2",

View File

@@ -18,7 +18,7 @@
*/
import { kebabCase } from 'lodash';
import { t, useTheme, styled } from '@superset-ui/core';
import Tooltip from './Tooltip';
import { Tooltip } from '@superset-ui/core/components';
interface CertifiedIconWithTooltipProps {
certifiedBy?: string | null;
@@ -27,7 +27,7 @@ interface CertifiedIconWithTooltipProps {
}
const StyledDiv = styled.div`
margin-bottom: ${({ theme }) => theme.gridUnit * 2}px;
margin-bottom: ${({ theme }) => theme.sizeUnit * 2}px;
`;
function CertifiedIconWithTooltip({
@@ -58,7 +58,7 @@ function CertifiedIconWithTooltip({
>
<g>
<path
fill={theme.colors.primary.base}
fill={theme.colorPrimary}
d="M23,12l-2.44-2.79l0.34-3.69l-3.61-0.82L15.4,1.5L12,2.96L8.6,1.5L6.71,4.69L3.1,5.5L3.44,9.2L1,12l2.44,2.79l-0.34,3.7 l3.61,0.82L8.6,22.5l3.4-1.47l3.4,1.46l1.89-3.19l3.61-0.82l-0.34-3.69L23,12z M9.38,16.01L7,13.61c-0.39-0.39-0.39-1.02,0-1.41 l0.07-0.07c0.39-0.39,1.03-0.39,1.42,0l1.61,1.62l5.15-5.16c0.39-0.39,1.03-0.39,1.42,0l0.07,0.07c0.39,0.39,0.39,1.02,0,1.41 l-5.92,5.94C10.41,16.4,9.78,16.4,9.38,16.01z"
/>
</g>

View File

@@ -17,8 +17,12 @@
* under the License.
*/
import { useState, ReactNode, useLayoutEffect, RefObject } from 'react';
import { css, SafeMarkdown, styled, SupersetTheme } from '@superset-ui/core';
import { Tooltip } from './Tooltip';
import { css, styled, SupersetTheme } from '@superset-ui/core';
import {
SafeMarkdown,
Tooltip,
InfoTooltip,
} from '@superset-ui/core/components';
import { ColumnTypeLabel } from './ColumnTypeLabel/ColumnTypeLabel';
import CertifiedIconWithTooltip from './CertifiedIconWithTooltip';
import { ColumnMeta } from '../types';
@@ -28,7 +32,6 @@ import {
getColumnTypeTooltipNode,
} from './labelUtils';
import { SQLPopover } from './SQLPopover';
import InfoTooltipWithTrigger from './InfoTooltipWithTrigger';
export type ColumnOptionProps = {
column: ColumnMeta;
@@ -40,7 +43,7 @@ const StyleOverrides = styled.span`
display: flex;
align-items: center;
svg {
margin-right: ${({ theme }) => theme.gridUnit}px;
margin-right: ${({ theme }) => theme.sizeUnit}px;
}
`;
@@ -82,7 +85,7 @@ export function ColumnOption({
<span
className="option-label column-option-label"
css={(theme: SupersetTheme) => css`
margin-right: ${theme.gridUnit}px;
margin-right: ${theme.sizeUnit}px;
`}
ref={labelRef}
>
@@ -98,15 +101,13 @@ export function ColumnOption({
/>
)}
{warningMarkdown && (
<InfoTooltipWithTrigger
className="text-warning"
icon="warning"
<InfoTooltip
type="warning"
tooltip={<SafeMarkdown source={warningMarkdown} />}
label={`warn-${column.column_name}`}
iconsStyle={{ marginLeft: 0 }}
iconStyle={{ marginLeft: 0 }}
{...(column.error_text && {
className: 'text-danger',
icon: 'exclamation-circle',
type: 'error',
})}
/>
)}

View File

@@ -39,9 +39,9 @@ const TypeIconWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: ${theme.gridUnit * 6}px;
height: ${theme.gridUnit * 6}px;
margin-right: ${theme.gridUnit}px;
width: ${theme.sizeUnit * 6}px;
height: ${theme.sizeUnit * 6}px;
margin-right: ${theme.sizeUnit}px;
&& svg {
margin-right: 0;

View File

@@ -17,9 +17,9 @@
* under the License.
*/
import { ReactNode } from 'react';
import { t } from '@superset-ui/core';
import { InfoTooltipWithTrigger } from './InfoTooltipWithTrigger';
import { Tooltip } from './Tooltip';
import { t, css } from '@superset-ui/core';
import { InfoCircleOutlined } from '@ant-design/icons';
import { InfoTooltip, Tooltip } from '@superset-ui/core/components';
type ValidationError = string;
@@ -60,7 +60,7 @@ export function ControlHeader({
<span>
{description && (
<span>
<InfoTooltipWithTrigger
<InfoTooltip
label={t('description')}
tooltip={description}
placement="top"
@@ -70,11 +70,11 @@ export function ControlHeader({
)}
{renderTrigger && (
<span>
<InfoTooltipWithTrigger
<InfoTooltip
label={t('bolt')}
tooltip={t('Changing this control takes effect instantly')}
placement="top"
icon="bolt"
type="notice"
/>{' '}
</span>
)}
@@ -88,6 +88,7 @@ export function ControlHeader({
return null;
}
const labelClass = validationErrors.length > 0 ? 'text-danger' : '';
return (
<div className="ControlHeader" data-test={`${name}-header`}>
<div className="pull-left">
@@ -98,25 +99,30 @@ export function ControlHeader({
tabIndex={0}
onClick={onClick}
className={labelClass}
style={{ cursor: onClick ? 'pointer' : '' }}
>
{label}
</span>{' '}
{warning && (
<span>
<Tooltip id="error-tooltip" placement="top" title={warning}>
{/* TODO: Remove fa-icon */}
{/* eslint-disable-next-line icons/no-fa-icons-usage */}
<i className="fa fa-exclamation-circle text-warning" />
<InfoCircleOutlined
css={theme => css`
font-size: ${theme.sizeUnit * 3}px;
color: ${theme.colorError};
`}
/>
</Tooltip>{' '}
</span>
)}
{danger && (
<span>
<Tooltip id="error-tooltip" placement="top" title={danger}>
{/* TODO: Remove fa-icon */}
{/* eslint-disable-next-line icons/no-fa-icons-usage */}
<i className="fa fa-exclamation-circle text-danger" />
<InfoCircleOutlined
css={theme => css`
font-size: ${theme.sizeUnit * 3}px;
color: ${theme.colorError};
`}
/>{' '}
</Tooltip>{' '}
</span>
)}
@@ -127,18 +133,17 @@ export function ControlHeader({
placement="top"
title={validationErrors.join(' ')}
>
{/* TODO: Remove fa-icon */}
{/* eslint-disable-next-line icons/no-fa-icons-usage */}
<i className="fa fa-exclamation-circle text-danger" />
<InfoCircleOutlined
css={theme => css`
font-size: ${theme.sizeUnit * 3}px;
color: ${theme.colorError};
`}
/>{' '}
</Tooltip>{' '}
</span>
)}
{renderOptionalIcons()}
{required && (
<span className="text-danger m-l-4">
<strong>*</strong>
</span>
)}
{required && <strong> *</strong>}
</label>
</div>
{rightNode && <div className="pull-right">{rightNode}</div>}

View File

@@ -20,9 +20,8 @@ import { styled, css } from '@superset-ui/core';
export const ControlSubSectionHeader = styled.div`
${({ theme }) => css`
font-weight: ${theme.typography.weights.bold};
font-size: ${theme.typography.sizes.s};
margin-bottom: ${theme.gridUnit}px;
font-weight: ${theme.fontWeightStrong};
margin-bottom: ${theme.sizeUnit}px;
font-size: ${theme.fontSizeSM}px;
`}
`;
export default ControlSubSectionHeader;

View File

@@ -17,5 +17,4 @@
* under the License.
*/
export { Dropdown } from 'antd';
export type { DropDownProps } from 'antd/lib/dropdown';
export { Dropdown, type DropdownProps } from '@superset-ui/core/components';

View File

@@ -1,80 +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 { CSSProperties } from 'react';
import { kebabCase } from 'lodash';
import { t } from '@superset-ui/core';
import { Tooltip, TooltipProps, TooltipPlacement } from './Tooltip';
export interface InfoTooltipWithTriggerProps {
label?: string;
tooltip?: TooltipProps['title'];
icon?: string;
onClick?: () => void;
placement?: TooltipPlacement;
bsStyle?: string;
className?: string;
iconsStyle?: CSSProperties;
}
export function InfoTooltipWithTrigger({
label,
tooltip,
bsStyle,
onClick,
icon = 'info-circle',
className = 'text-muted',
placement = 'right',
iconsStyle = {},
}: InfoTooltipWithTriggerProps) {
const iconClass = `fa fa-${icon} ${className} ${
bsStyle ? `text-${bsStyle}` : ''
}`;
const iconEl = (
<i
role="button"
aria-label={t('Show info tooltip')}
tabIndex={0}
className={iconClass}
style={{ cursor: onClick ? 'pointer' : undefined, ...iconsStyle }}
onClick={onClick}
onKeyPress={
onClick &&
(event => {
if (event.key === 'Enter' || event.key === ' ') {
onClick();
}
})
}
/>
);
if (!tooltip) {
return iconEl;
}
return (
<Tooltip
id={`${kebabCase(label)}-tooltip`}
title={tooltip}
placement={placement}
>
{iconEl}
</Tooltip>
);
}
export default InfoTooltipWithTrigger;

View File

@@ -17,5 +17,4 @@
* under the License.
*/
export { Menu } from 'antd';
export type { MenuProps } from 'antd/lib/menu';
export { Menu, type MenuProps } from '@superset-ui/core/components';

View File

@@ -18,17 +18,16 @@
*/
import { useState, ReactNode, useLayoutEffect, RefObject } from 'react';
import { css, styled, Metric, SupersetTheme } from '@superset-ui/core';
import {
css,
styled,
Metric,
SafeMarkdown,
SupersetTheme,
} from '@superset-ui/core';
import InfoTooltipWithTrigger from './InfoTooltipWithTrigger';
Typography,
// TODO: somehow doesn't work with our main Tooltip (?)
RawAntdTooltip as Tooltip,
InfoTooltip,
} from '@superset-ui/core/components';
import { ColumnTypeLabel } from './ColumnTypeLabel/ColumnTypeLabel';
import CertifiedIconWithTooltip from './CertifiedIconWithTooltip';
import Tooltip from './Tooltip';
import { getMetricTooltipNode } from './labelUtils';
import { SQLPopover } from './SQLPopover';
@@ -37,7 +36,7 @@ const FlexRowContainer = styled.div`
display: flex;
> svg {
margin-right: ${({ theme }) => theme.gridUnit}px;
margin-right: ${({ theme }) => theme.sizeUnit}px;
}
`;
@@ -61,23 +60,26 @@ export function MetricOption({
url = '',
}: MetricOptionProps) {
const verbose = metric.verbose_name || metric.metric_name || metric.label;
const link = url ? (
<a href={url} target={openInNewWindow ? '_blank' : ''} rel="noreferrer">
{verbose}
</a>
) : (
verbose
);
const label = (
<span
className="option-label metric-option-label"
css={(theme: SupersetTheme) => css`
margin-right: ${theme.gridUnit}px;
margin-right: ${theme.sizeUnit}px;
`}
ref={labelRef}
>
{link}
{url ? (
<Typography.Link
href={url}
target={openInNewWindow ? '_blank' : ''}
rel="noreferrer"
>
{verbose}
</Typography.Link>
) : (
verbose
)}
</span>
);
@@ -111,15 +113,13 @@ export function MetricOption({
/>
)}
{warningMarkdown && (
<InfoTooltipWithTrigger
className="text-warning"
icon="warning"
<InfoTooltip
type="warning"
tooltip={<SafeMarkdown source={warningMarkdown} />}
label={`warn-${metric.metric_name}`}
iconsStyle={{ marginLeft: 0 }}
iconStyle={{ marginLeft: 0 }}
{...(metric.error_text && {
className: 'text-danger',
icon: 'exclamation-circle',
type: 'error',
})}
/>
)}

View File

@@ -17,19 +17,18 @@
* under the License.
*/
import { useEffect, useState } from 'react';
import { Popover } from 'antd-v5';
import { Popover, type PopoverProps } from '@superset-ui/core/components';
import type ReactAce from 'react-ace';
import type { PopoverProps } from 'antd-v5/lib/popover';
import { CalculatorOutlined } from '@ant-design/icons';
import { css, styled, useTheme, t } from '@superset-ui/core';
const StyledCalculatorIcon = styled(CalculatorOutlined)`
${({ theme }) => css`
color: ${theme.colors.grayscale.base};
font-size: ${theme.typography.sizes.s}px;
font-size: ${theme.fontSizeSM}px;
& svg {
margin-left: ${theme.gridUnit}px;
margin-right: ${theme.gridUnit}px;
margin-left: ${theme.sizeUnit}px;
margin-right: ${theme.sizeUnit}px;
}
`}
`;
@@ -65,9 +64,9 @@ export const SQLPopover = (props: PopoverProps & { sqlExpression: string }) => {
readOnly
wrapEnabled
style={{
border: `1px solid ${theme.colors.grayscale.light2}`,
background: theme.colors.secondary.light5,
maxWidth: theme.gridUnit * 100,
border: `1px solid ${theme.colorBorder}`,
background: theme.colorPrimaryBg,
maxWidth: theme.sizeUnit * 100,
}}
/>
}

View File

@@ -17,7 +17,10 @@
* under the License.
*/
import { useState, ReactNode } from 'react';
import AntdSelect, { SelectProps as AntdSelectProps } from 'antd/lib/select';
import {
RawAntdSelect as AntdSelect,
type RawAntdSelectProps as AntdSelectProps,
} from '@superset-ui/core/components';
export const { Option }: any = AntdSelect;
@@ -35,7 +38,7 @@ export type SelectProps<VT> = Omit<AntdSelectProps<VT>, 'options'> & {
export default function Select<VT extends string | number>({
creatable,
onSearch,
dropdownMatchSelectWidth = false,
popupMatchSelectWidth = false,
minWidth = '100%',
showSearch: showSearch_ = true,
onChange,
@@ -73,7 +76,7 @@ export default function Select<VT extends string | number>({
return (
<AntdSelect<VT>
dropdownMatchSelectWidth={dropdownMatchSelectWidth}
popupMatchSelectWidth={popupMatchSelectWidth}
showSearch={showSearch}
onSearch={handleSearch}
onChange={handleChange}

View File

@@ -1,60 +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 { useTheme } from '@superset-ui/core';
import { Tooltip as BaseTooltip } from 'antd-v5';
import {
TooltipProps as BaseTooltipProps,
TooltipPlacement as BaseTooltipPlacement,
} from 'antd-v5/lib/tooltip';
export type TooltipProps = BaseTooltipProps;
export type TooltipPlacement = BaseTooltipPlacement;
export const Tooltip = ({
overlayStyle = {},
color,
...props
}: BaseTooltipProps) => {
const theme = useTheme();
const defaultColor = `${theme.colors.grayscale.dark2}e6`;
return (
<BaseTooltip
styles={{
root: {
fontSize: theme.typography.sizes.s,
lineHeight: '1.6',
maxWidth: theme.gridUnit * 62,
minWidth: theme.gridUnit * 30,
...overlayStyle,
},
}}
// make the tooltip display closer to the label
align={{ offset: [0, 1] }}
color={defaultColor || color}
trigger="hover"
placement="bottom"
// don't allow hovering over the tooltip
mouseLeaveDelay={0}
{...props}
/>
);
};
export default Tooltip;

View File

@@ -29,18 +29,18 @@ const TooltipSectionWrapper = styled.div`
overflow: hidden;
text-overflow: ellipsis;
font-size: ${theme.typography.sizes.s}px;
font-size: ${theme.fontSizeSM}px;
line-height: 1.2;
&:not(:last-of-type) {
margin-bottom: ${theme.gridUnit * 2}px;
margin-bottom: ${theme.sizeUnit * 2}px;
}
`}
`;
const TooltipSectionLabel = styled.span`
${({ theme }) => css`
font-weight: ${theme.typography.weights.bold};
font-weight: ${theme.fontWeightStrong};
`}
`;

View File

@@ -25,14 +25,12 @@ export * from './operators';
// can't do `export * as sections from './sections'`, babel-transformer will fail
export const sections = sectionsModule;
export * from './components/InfoTooltipWithTrigger';
export * from './components/ColumnOption';
export * from './components/ColumnTypeLabel/ColumnTypeLabel';
export * from './components/ControlSubSectionHeader';
export * from './components/Dropdown';
export * from './components/Menu';
export * from './components/MetricOption';
export * from './components/Tooltip';
export * from './components/ControlHeader';
export * from './shared-controls';

View File

@@ -17,7 +17,8 @@
* under the License.
*/
import { ReactNode } from 'react';
import { JsonValue, useTheme } from '@superset-ui/core';
import { JsonValue } from '@superset-ui/core';
import { Radio } from '@superset-ui/core/components';
import { ControlHeader } from '../../components/ControlHeader';
// [value, label]
@@ -42,51 +43,19 @@ export default function RadioButtonControl({
...props
}: RadioButtonControlProps) {
const currentValue = initialValue || options[0][0];
const theme = useTheme();
return (
<div
css={{
'.btn svg': {
position: 'relative',
top: '0.2em',
},
'.btn:focus': {
outline: 'none',
},
'.control-label': {
color: theme.colors.grayscale.base,
marginBottom: theme.gridUnit,
},
'.control-label + .btn-group': {
marginTop: '1px',
},
'.btn-group .btn-default': {
color: theme.colors.grayscale.dark1,
},
'.btn-group .btn.active': {
background: theme.colors.grayscale.light4,
fontWeight: theme.typography.weights.bold,
boxShadow: 'none',
},
}}
>
<div>
<ControlHeader {...props} />
<div className="btn-group btn-group-sm">
<Radio.Group
value={currentValue}
onChange={e => onChange(e.target.value)}
>
{options.map(([val, label]) => (
<button
key={JSON.stringify(val)}
type="button"
className={`btn btn-default ${
val === currentValue ? 'active' : ''
}`}
onClick={() => {
onChange(val);
}}
>
<Radio.Button key={JSON.stringify(val)} value={val}>
{label}
</button>
</Radio.Button>
))}
</div>
</Radio.Group>
</div>
);
}

View File

@@ -300,7 +300,7 @@ export interface FilterOption<T extends SelectOption> {
data: T;
}
// Ref: superset-frontend/src/components/Select/SupersetStyledSelect.tsx
// Ref: superset-frontend/@superset-ui/core/components/Select/SupersetStyledSelect.tsx
export interface SelectControlConfig<
O extends SelectOption = SelectOption,
T extends SelectControlType = SelectControlType,
@@ -371,7 +371,9 @@ export type CustomControlItem = {
export const isCustomControlItem = (obj: unknown): obj is CustomControlItem =>
typeof obj === 'object' &&
obj !== null &&
// @ts-expect-error TS(2339): Property 'name' does not exist on type 'object'.
typeof ('name' in obj && obj.name) === 'string' &&
// @ts-expect-error TS(2339): Property 'config' does not exist on type 'object'.
typeof ('config' in obj && obj.config) === 'object' &&
(obj as CustomControlItem).config !== null;

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import '@testing-library/jest-dom';
import { render } from '@testing-library/react';
import { render } from '@superset-ui/core/spec';
import {
ThemeProvider,
supersetTheme,
@@ -26,17 +26,21 @@ import {
import { ColumnOption, ColumnOptionProps } from '../../src';
jest.mock('../../src/components/SQLPopover', () => ({
jest.mock('@superset-ui/chart-controls/components/SQLPopover', () => ({
SQLPopover: () => <div data-test="mock-sql-popover" />,
}));
jest.mock('../../src/components/ColumnTypeLabel/ColumnTypeLabel', () => ({
ColumnTypeLabel: ({ type }: { type: string }) => (
<div data-test="mock-column-type-label">{type}</div>
),
jest.mock(
'@superset-ui/chart-controls/components/ColumnTypeLabel/ColumnTypeLabel',
() => ({
ColumnTypeLabel: ({ type }: { type: string }) => (
<div data-test="mock-column-type-label">{type}</div>
),
}),
);
jest.mock('@superset-ui/core/components/InfoTooltip', () => ({
InfoTooltip: () => <div data-test="mock-tooltip" />,
}));
jest.mock('../../src/components/InfoTooltipWithTrigger', () => () => (
<div data-test="mock-info-tooltip-with-trigger" />
));
const defaultProps: ColumnOptionProps = {
column: {
@@ -114,11 +118,11 @@ test('dttm column has correct column label if showType is true', () => {
String(GenericDataType.Temporal),
);
});
test('doesnt show InfoTooltipWithTrigger when no warning', () => {
test('doesnt show InfoTooltip when no warning', () => {
const { queryByText } = setup();
expect(queryByText('mock-info-tooltip-with-trigger')).not.toBeInTheDocument();
expect(queryByText('mock-tooltip')).not.toBeInTheDocument();
});
test('shows a warning with InfoTooltipWithTrigger when it contains warning', () => {
test('shows a warning with InfoTooltip when it contains warning', () => {
const { getByTestId } = setup({
...defaultProps,
column: {
@@ -126,5 +130,5 @@ test('shows a warning with InfoTooltipWithTrigger when it contains warning', ()
warning_text: 'This is a warning',
},
});
expect(getByTestId('mock-info-tooltip-with-trigger')).toBeInTheDocument();
expect(getByTestId('mock-tooltip')).toBeInTheDocument();
});

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import { isValidElement } from 'react';
import { render, screen } from '@testing-library/react';
import { render, screen } from '@superset-ui/core/spec';
import '@testing-library/jest-dom';
import { GenericDataType } from '@superset-ui/core';

View File

@@ -17,11 +17,11 @@
* under the License.
*/
import '@testing-library/jest-dom';
import { fireEvent, render } from '@testing-library/react';
import { fireEvent, render } from '@superset-ui/core/spec';
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
import { InfoTooltipWithTrigger, InfoTooltipWithTriggerProps } from '../../src';
import { InfoTooltip, InfoTooltipProps } from '@superset-ui/core/components';
jest.mock('../../src/components/Tooltip', () => ({
jest.mock('@superset-ui/core/components/Tooltip', () => ({
Tooltip: ({ children }: { children: React.ReactNode }) => (
<div data-test="mock-tooltip">{children}</div>
),
@@ -29,10 +29,10 @@ jest.mock('../../src/components/Tooltip', () => ({
const defaultProps = {};
const setup = (props: Partial<InfoTooltipWithTriggerProps> = {}) =>
const setup = (props: Partial<InfoTooltipProps> = {}) =>
render(
<ThemeProvider theme={supersetTheme}>
<InfoTooltipWithTrigger {...defaultProps} {...props} />
<InfoTooltip {...defaultProps} {...props} />
</ThemeProvider>,
);
@@ -44,31 +44,29 @@ test('renders a tooltip', () => {
expect(getAllByTestId('mock-tooltip').length).toEqual(1);
});
test('renders an info icon', () => {
const { container } = setup();
expect(container.getElementsByClassName('fa-info-circle')).toHaveLength(1);
});
test('responds to keypresses', () => {
test('responds to keydown events', () => {
const clickHandler = jest.fn();
const { getByRole } = setup({
label: 'test',
tooltip: 'this is a test',
onClick: clickHandler,
});
fireEvent.keyPress(getByRole('button'), {
fireEvent.keyDown(getByRole('button'), {
key: 'Tab',
code: 9,
charCode: 9,
});
expect(clickHandler).toHaveBeenCalledTimes(0);
fireEvent.keyPress(getByRole('button'), {
fireEvent.keyDown(getByRole('button'), {
key: 'Enter',
code: 13,
charCode: 13,
});
expect(clickHandler).toHaveBeenCalledTimes(1);
fireEvent.keyPress(getByRole('button'), {
fireEvent.keyDown(getByRole('button'), {
key: ' ',
code: 32,
charCode: 32,
@@ -76,9 +74,47 @@ test('responds to keypresses', () => {
expect(clickHandler).toHaveBeenCalledTimes(2);
});
test('has a bsStyle', () => {
test('finds the info circle icon inside info variant', () => {
const { container } = setup({
bsStyle: 'something',
type: 'info',
});
expect(container.getElementsByClassName('text-something')).toHaveLength(1);
const iconSpan = container.querySelector('svg[data-icon="info-circle"]');
expect(iconSpan).toBeInTheDocument();
});
test('finds the warning icon inside warning variant', () => {
const { container } = setup({
type: 'warning',
});
const iconSpan = container.querySelector('svg[data-icon="warning"]');
expect(iconSpan).toBeInTheDocument();
});
test('finds the close circle icon inside error variant', () => {
const { container } = setup({
type: 'error',
});
const iconSpan = container.querySelector('svg[data-icon="close-circle"]');
expect(iconSpan).toBeInTheDocument();
});
test('finds the question circle icon inside question variant', () => {
const { container } = setup({
type: 'question',
});
const iconSpan = container.querySelector('svg[data-icon="question-circle"]');
expect(iconSpan).toBeInTheDocument();
});
test('finds the thunderbolt icon inside notice variant', () => {
const { container } = setup({
type: 'notice',
});
const iconSpan = container.querySelector('svg[data-icon="thunderbolt"]');
expect(iconSpan).toBeInTheDocument();
});

View File

@@ -17,24 +17,31 @@
* under the License.
*/
import '@testing-library/jest-dom';
import { render } from '@testing-library/react';
import { render } from '@superset-ui/core/spec';
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
import { MetricOption, MetricOptionProps } from '../../src';
import {
MetricOption,
MetricOptionProps,
} from '../../src/components/MetricOption';
jest.mock('../../src/components/InfoTooltipWithTrigger', () => () => (
<div data-test="mock-info-tooltip-with-trigger" />
));
jest.mock('../../src/components/ColumnTypeLabel/ColumnTypeLabel', () => ({
ColumnTypeLabel: () => <div data-test="mock-column-type-label" />,
jest.mock('@superset-ui/core/components/InfoTooltip', () => ({
InfoTooltip: () => <div data-test="mock-tooltip" />,
}));
jest.mock(
'../../src/components/Tooltip',
'@superset-ui/chart-controls/components/ColumnTypeLabel/ColumnTypeLabel',
() => ({
ColumnTypeLabel: () => <div data-test="mock-column-type-label" />,
}),
);
jest.mock(
'@superset-ui/core/components/Tooltip',
() =>
({ children }: { children: React.ReactNode }) => (
<div data-test="mock-tooltip">{children}</div>
),
);
jest.mock('../../src/components/SQLPopover', () => ({
jest.mock('@superset-ui/chart-controls/components/SQLPopover', () => ({
SQLPopover: () => <div data-test="mock-sql-popover" />,
}));
@@ -65,9 +72,9 @@ test('shows a label with verbose_name', () => {
expect(lbl).toHaveLength(1);
expect(`${lbl[0].textContent}`).toEqual(defaultProps.metric.verbose_name);
});
test('shows a InfoTooltipWithTrigger', () => {
test('shows a InfoTooltip', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-info-tooltip-with-trigger')).toBeInTheDocument();
expect(getByTestId('mock-tooltip')).toBeInTheDocument();
});
test('shows SQL Popover trigger', () => {
const { getByTestId } = setup();
@@ -82,14 +89,14 @@ test('shows a label with metric_name when no verbose_name', () => {
});
expect(getByText(defaultProps.metric.metric_name)).toBeInTheDocument();
});
test('doesnt show InfoTooltipWithTrigger when no warning', () => {
test('doesnt show InfoTooltip when no warning', () => {
const { queryByText } = setup({
metric: {
...defaultProps.metric,
warning_text: '',
},
});
expect(queryByText('mock-info-tooltip-with-trigger')).not.toBeInTheDocument();
expect(queryByText('mock-tooltip')).not.toBeInTheDocument();
});
test('sets target="_blank" when openInNewWindow is true', () => {
const { getByRole } = setup({

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import { ReactElement } from 'react';
import { render, screen } from '@testing-library/react';
import { render, screen } from '@superset-ui/core/spec';
import '@testing-library/jest-dom';
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
import {

View File

@@ -129,7 +129,7 @@ test('returns empty array if timeseries_limit_metric is an empty array', () => {
expect(
extractExtraMetrics({
...baseFormData,
// @ts-ignore
// @ts-expect-error TS(2322): Type 'never[]' is not assignable to type 'QueryFor... Remove this comment to see the full error message
timeseries_limit_metric: [],
}),
).toEqual([]);

View File

@@ -51,7 +51,7 @@ describe('defineSavedMetrics', () => {
uuid: '1',
},
]);
// @ts-ignore
// @ts-expect-error TS(2322): Type 'undefined' is not assignable to type 'Metric... Remove this comment to see the full error message
expect(defineSavedMetrics({ ...dataset, metrics: undefined })).toEqual([]);
});

View File

@@ -284,7 +284,7 @@ describe('getColorFunction()', () => {
it('getColorFunction unsupported operator', () => {
const colorFunction = getColorFunction(
{
// @ts-ignore
// @ts-expect-error TS(2322): Type '"unsupported operator"' is not assignable to... Remove this comment to see the full error message
operator: 'unsupported operator',
targetValue: 50,
colorScheme: '#FF0000',

View File

@@ -0,0 +1,68 @@
/**
* 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.
*/
{
"plugins": ["jest", "jest-dom", "no-only-tests", "testing-library"],
"env": {
"jest/globals": true
},
"settings": {
"jest": {
"version": "detect"
}
},
"extends": [
"plugin:jest/recommended",
"plugin:jest-dom/recommended",
"plugin:testing-library/react"
],
"overrides": [
{
"files": [
"**/*.stories.*",
"**/*.overview.*",
"**/fixtures.*"
],
"rules": {
"import/no-extraneous-dependencies": "off"
}
}
],
"rules": {
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
"jest/consistent-test-it": "error",
"no-only-tests/no-only-tests": "error",
"prefer-promise-reject-errors": 0,
"testing-library/no-node-access": "off",
"testing-library/prefer-screen-queries": "off",
"testing-library/no-container": "off",
"testing-library/await-async-queries": "off",
"testing-library/await-async-utils": "off",
"testing-library/no-await-sync-events": "off",
"testing-library/no-render-in-lifecycle": "off",
"testing-library/no-unnecessary-act": "off",
"testing-library/no-wait-for-multiple-assertions": "off",
"testing-library/await-async-events": "off",
"testing-library/no-wait-for-side-effects": "off",
"testing-library/prefer-presence-queries": "off",
"testing-library/render-result-naming-convention": "off",
"testing-library/prefer-find-by": "off",
"testing-library/no-manual-cleanup": "off"
}
}

View File

@@ -0,0 +1,29 @@
/**
* 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 { SVGProps, forwardRef } from 'react';
const SvgrMock = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(
(props, ref) => <svg ref={ref} {...props} />,
);
SvgrMock.displayName = 'SvgrMock';
export const ReactComponent = SvgrMock;
export default SvgrMock;

View File

@@ -24,21 +24,37 @@
"lib"
],
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@babel/runtime": "^7.25.6",
"@fontsource/fira-code": "^5.0.18",
"@fontsource/inter": "^5.0.20",
"@types/json-bigint": "^1.0.4",
"ace-builds": "^1.41.0",
"brace": "^0.11.1",
"classnames": "^2.2.5",
"csstype": "^3.1.3",
"core-js": "^3.38.1",
"d3-format": "^1.3.2",
"dayjs": "^1.11.13",
"d3-interpolate": "^3.0.1",
"d3-scale": "^3.0.0",
"d3-time": "^3.1.0",
"d3-time-format": "^4.1.0",
"dompurify": "^3.2.4",
"fetch-retry": "^6.0.0",
"jed": "^1.1.1",
"lodash": "^4.17.21",
"math-expression-evaluator": "^2.0.6",
"pretty-ms": "^9.2.0",
"re-resizable": "^6.10.1",
"react-ace": "^10.1.0",
"react-js-cron": "^2.1.2",
"react-draggable": "^4.4.6",
"react-resize-detector": "^7.1.2",
"react-ultimate-pagination": "^1.3.2",
"react-error-boundary": "^5.0.0",
"react-markdown": "^8.0.7",
"regenerator-runtime": "^0.14.1",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark-gfm": "^3.0.1",
@@ -56,8 +72,10 @@
"@types/d3-scale": "^2.1.1",
"@types/d3-time": "^3.0.4",
"@types/d3-time-format": "^4.0.3",
"@types/react-table": "^7.7.20",
"@types/enzyme": "^3.10.18",
"@types/fetch-mock": "^7.3.8",
"@types/jquery": "^3.5.8",
"@types/lodash": "^4.17.16",
"@types/math-expression-evaluator": "^1.3.3",
"@types/node": "^22.10.3",
@@ -70,6 +88,7 @@
"timezone-mock": "1.3.6"
},
"peerDependencies": {
"antd": "^5.24.6",
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
@@ -80,6 +99,7 @@
"@testing-library/user-event": "*",
"@types/react": "*",
"@types/react-loadable": "*",
"@types/react-window": "^1.8.8",
"@types/tinycolor2": "*",
"nanoid": "^5.0.9",
"react": "^17.0.2",
@@ -88,5 +108,27 @@
},
"publishConfig": {
"access": "public"
},
"exports": {
".": {
"import": "./esm/index.js",
"require": "./lib/index.js",
"types": "./lib/index.d.ts"
},
"./components/*": {
"import": "./esm/components/*/index.js",
"require": "./lib/components/*/index.js",
"types": "./lib/components/*/index.d.ts"
},
"./components": {
"import": "./esm/components/index.js",
"require": "./lib/components/index.js",
"types": "./lib/components/index.d.ts"
},
"./utils/*": {
"import": "./esm/utils/*.js",
"require": "./lib/utils/*.js",
"types": "./lib/utils/*.d.ts"
}
}
}

View File

@@ -16,8 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import { CSSProperties, PureComponent, ReactNode } from 'react';
import { CSSProperties, ReactNode } from 'react';
import { Table, type TableColumnsType } from 'antd';
interface TooltipRowData {
key: string | number;
@@ -27,43 +27,52 @@ interface TooltipRowData {
valueStyle?: CSSProperties;
}
const defaultProps = {
className: '',
data: [] as TooltipRowData[],
};
type Props = {
interface TooltipTableProps {
className?: string;
data: TooltipRowData[];
} & Readonly<typeof defaultProps>;
}
const VALUE_CELL_STYLE: CSSProperties = { paddingLeft: 8, textAlign: 'right' };
export default class TooltipTable extends PureComponent<Props, {}> {
static defaultProps = defaultProps;
const TooltipTable = ({ className = '', data }: TooltipTableProps) => {
const columns: TableColumnsType<TooltipRowData> = [
{
title: '',
dataIndex: 'keyColumn',
key: 'keyColumn',
render: (text, record) => (
<div style={record.keyStyle}>{record.keyColumn ?? record.key}</div>
),
},
{
title: '',
dataIndex: 'valueColumn',
key: 'valueColumn',
align: 'right',
render: (text, record) => (
<div
style={
record.valueStyle
? { ...VALUE_CELL_STYLE, ...record.valueStyle }
: VALUE_CELL_STYLE
}
>
{record.valueColumn}
</div>
),
},
];
render() {
const { className, data } = this.props;
return (
<Table
className={className}
columns={columns}
dataSource={data}
pagination={false}
showHeader={false}
bordered={false}
/>
);
};
return (
<table className={className}>
<tbody>
{data.map(({ key, keyColumn, keyStyle, valueColumn, valueStyle }) => (
<tr key={key}>
<td style={keyStyle}>{keyColumn ?? key}</td>
<td
style={
valueStyle
? { ...VALUE_CELL_STYLE, ...valueStyle }
: VALUE_CELL_STYLE
}
>
{valueColumn}
</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default TooltipTable;

View File

@@ -18,7 +18,7 @@
*/
import { t } from '@superset-ui/core';
import { SupersetTheme } from '../../style';
import { SupersetTheme } from '../..';
import { FallbackPropsWithDimension } from './SuperChart';
export type Props = FallbackPropsWithDimension;

View File

@@ -18,7 +18,7 @@
*/
import { CSSProperties } from 'react';
import { css, styled } from '../../style';
import { css, styled } from '../../theme';
import { t } from '../../translation';
const MESSAGE_STYLES: CSSProperties = { maxWidth: 800 };
@@ -36,16 +36,16 @@ const Container = styled.div<{
text-align: center;
height: ${height}px;
width: ${width}px;
padding: ${theme.gridUnit * 4}px;
padding: ${theme.sizeUnit * 4}px;
& .no-results-title {
font-size: ${theme.typography.sizes.l}px;
font-weight: ${theme.typography.weights.bold};
padding-bottom: ${theme.gridUnit * 2};
font-size: ${theme.fontSizeLG}px;
font-weight: ${theme.fontWeightStrong};
padding-bottom: ${theme.sizeUnit * 2};
}
& .no-results-body {
font-size: ${theme.typography.sizes.m}px;
font-size: ${theme.fontSize}px;
}
`}
`;

View File

@@ -18,7 +18,7 @@
*/
// eslint-disable-next-line no-restricted-syntax -- whole React import is required for `reactify.test.tsx` Jest test passing.
import React, { Component, ComponentClass, WeakValidationMap } from 'react';
import { Component, ComponentClass, WeakValidationMap } from 'react';
// TODO: Note that id and className can collide between Props and ReactifyProps
// leading to (likely) unexpected behaviors. We should either require Props to not

View File

@@ -37,7 +37,7 @@ import {
SetDataMaskHook,
} from '../types/Base';
import { QueryData, DataRecordFilters } from '..';
import { SupersetTheme } from '../../style';
import { SupersetTheme } from '../../theme';
// TODO: more specific typing for these fields of ChartProps
type AnnotationData = PlainObject;

View File

@@ -16,7 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import Alert, { AlertProps } from './index';
import { Alert } from '.';
import type { AlertProps } from './types';
type AlertType = Required<Pick<AlertProps, 'type'>>;
type AlertTypeValue = AlertType['type'];
@@ -30,7 +31,7 @@ const bigText =
'purus convallis placerat in at nunc. Nulla nec viverra augue.';
export default {
title: 'Alert',
title: 'Components/Alert',
component: Alert,
};

View File

@@ -16,15 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
import { PropsWithChildren } from 'react';
import { Alert as AntdAlert } from 'antd-v5';
import { AlertProps as AntdAlertProps } from 'antd-v5/lib/alert';
import { Alert as AntdAlert } from 'antd';
import type { AlertProps } from './types';
export type AlertProps = PropsWithChildren<
Omit<AntdAlertProps, 'children'> & { roomBelow?: boolean }
>;
export default function Alert(props: AlertProps) {
export const Alert = (props: AlertProps) => {
const {
type = 'info',
description,
@@ -46,4 +41,6 @@ export default function Alert(props: AlertProps) {
{...rest}
/>
);
}
};
export type { AlertProps };

View File

@@ -16,12 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
import type { PropsWithChildren } from 'react';
import type { AlertProps as AntdAlertProps } from 'antd/es/alert';
import { Divider as AntdDivider } from 'antd-v5';
import type { DividerProps } from 'antd-v5/es/divider';
export function Divider(props: DividerProps) {
return <AntdDivider {...props} />;
}
export { DividerProps };
export type AlertProps = PropsWithChildren<
Omit<AntdAlertProps, 'children'> & { roomBelow?: boolean }
>;

View File

@@ -16,8 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
import { SwitchProps } from 'antd-v5/lib/switch';
import { Switch as AntdSwitch } from 'antd-v5';
export const Switch = (props: SwitchProps) => <AntdSwitch {...props} />;
export type { SwitchProps };
import { ConfigProvider, type ConfigProviderProps } from 'antd';
export const AntdThemeProvider = ({
children,
...rest
}: ConfigProviderProps) => (
<ConfigProvider {...rest}>{children}</ConfigProvider>
);

View File

@@ -24,9 +24,10 @@ import {
CssEditor,
JsonEditor,
ConfigEditor,
AsyncAceEditorOptions,
} from '.';
import type { AsyncAceEditorOptions } from './types';
type EditorType =
| 'sql'
| 'full-sql'
@@ -47,7 +48,7 @@ const editorTypes: EditorType[] = [
];
export default {
title: 'AsyncAceEditor',
title: 'Components/AsyncAceEditor',
};
const parseEditorType = (editorType: EditorType) => {

View File

@@ -16,8 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
import { render, screen, waitFor } from 'spec/helpers/testing-library';
import AsyncAceEditor, {
import { render, screen, waitFor } from '@superset-ui/core/spec';
import {
AsyncAceEditor,
SQLEditor,
FullSQLEditor,
MarkdownEditor,
@@ -25,9 +26,9 @@ import AsyncAceEditor, {
CssEditor,
JsonEditor,
ConfigEditor,
AceModule,
AsyncAceEditorOptions,
} from 'src/components/AsyncAceEditor';
} from '.';
import type { AceModule, AsyncAceEditorOptions } from './types';
const selector = '[id="ace-editor"]';

View File

@@ -16,7 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
import DOMPurify from 'dompurify';
type Props = {

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { forwardRef, useEffect, ComponentType } from 'react';
import { forwardRef, useEffect, useCallback, ComponentType } from 'react';
import type {
Editor as OrigEditor,
@@ -27,10 +27,10 @@ import type {
import type AceEditor from 'react-ace';
import type { IAceEditorProps } from 'react-ace';
import AsyncEsmComponent, {
import {
AsyncEsmComponent,
PlaceholderProps,
} from 'src/components/AsyncEsmComponent';
import useEffectEvent from 'src/hooks/useEffectEvent';
} from '@superset-ui/core/components/AsyncEsmComponent';
import { useTheme, css } from '@superset-ui/core';
import { Global } from '@emotion/react';
@@ -67,7 +67,7 @@ export interface AceCompleterKeyword extends AceCompleterKeywordData {
* Async loaders to import brace modules. Must manually create call `import(...)`
* promises because webpack can only analyze async imports statically.
*/
const aceModuleLoaders = {
export const aceModuleLoaders = {
'mode/sql': () => import('brace/mode/sql'),
'mode/markdown': () => import('brace/mode/markdown'),
'mode/css': () => import('brace/mode/css'),
@@ -102,7 +102,7 @@ export type AsyncAceEditorOptions = {
/**
* Get an async AceEditor with automatical loading of specified ace modules.
*/
export default function AsyncAceEditor(
export function AsyncAceEditor(
aceModules: AceModule[],
{
defaultMode,
@@ -155,9 +155,10 @@ export default function AsyncAceEditor(
},
ref,
) {
const supersetTheme = useTheme();
const token = useTheme();
const langTools = acequire('ace/ext/language_tools');
const setCompleters = useEffectEvent(
const setCompleters = useCallback(
(keywords: AceCompleterKeyword[]) => {
const completer = {
getCompletions: (
@@ -180,7 +181,9 @@ export default function AsyncAceEditor(
};
langTools.setCompleters([completer]);
},
[langTools, mode],
);
useEffect(() => {
if (keywords) {
setCompleters(keywords);
@@ -192,36 +195,148 @@ export default function AsyncAceEditor(
<Global
key="ace-tooltip-global"
styles={css`
.ace_editor {
border: 1px solid ${token.colorBorder} !important;
background-color: ${token.colorBgContainer} !important;
}
/* Basic editor styles with dark mode support */
.ace_editor.ace-github,
.ace_editor.ace-textmate {
background-color: ${token.colorBgContainer} !important;
color: ${token.colorText} !important;
}
/* Adjust gutter colors */
.ace_editor .ace_gutter {
background-color: ${token.colorBgElevated} !important;
color: ${token.colorTextSecondary} !important;
}
/* Adjust selection color */
.ace_editor .ace_selection {
background-color: ${token.colorPrimaryBgHover} !important;
}
/* Improve active line highlighting */
.ace_editor .ace_active-line {
background-color: ${token.colorPrimaryBg} !important;
}
/* Fix indent guides and print margin (80 chars line) */
.ace_editor .ace_indent-guide,
.ace_editor .ace_print-margin {
background-color: ${token.colorSplit} !important;
opacity: 0.5;
}
/* Adjust cursor color */
.ace_editor .ace_cursor {
color: ${token.colorPrimaryText} !important;
}
/* Syntax highlighting using semantic color tokens */
.ace_editor .ace_keyword {
color: ${token.colorPrimaryText} !important;
}
.ace_editor .ace_string {
color: ${token.colorSuccessText} !important;
}
.ace_editor .ace_constant {
color: ${token.colorWarningActive} !important;
}
.ace_editor .ace_function {
color: ${token.colorInfoText} !important;
}
.ace_editor .ace_comment {
color: ${token.colorTextTertiary} !important;
}
.ace_editor .ace_variable {
color: ${token.colorTextSecondary} !important;
}
/* Adjust tooltip styles */
.ace_tooltip {
all: unset;
position: fixed;
z-index: 9999;
background: ${supersetTheme.colors.grayscale.light5};
border: 1px solid ${supersetTheme.colors.grayscale.light1};
padding: ${supersetTheme.gridUnit}px
${supersetTheme.gridUnit * 2}px;
line-height: 1.4;
max-width: 400px;
min-width: 200px;
pointer-events: auto;
font-size: ${supersetTheme.typography.sizes.m}px;
margin-left: ${token.margin}px;
padding: 0px;
background-color: ${token.colorBgElevated} !important;
color: ${token.colorText} !important;
border: 1px solid ${token.colorBorderSecondary};
box-shadow: ${token.boxShadow};
border-radius: ${token.borderRadius}px;
}
& .tooltip-detail {
background-color: ${token.colorBgContainer};
white-space: pre-wrap;
word-break: break-all;
min-width: ${token.sizeXXL * 5}px;
max-width: ${token.sizeXXL * 10}px;
& .tooltip-detail-head {
background-color: ${token.colorBgElevated};
color: ${token.colorText};
display: flex;
column-gap: ${token.padding}px;
align-items: baseline;
justify-content: space-between;
}
& .tooltip-detail-title {
font-weight: bold;
font-size: ${supersetTheme.typography.sizes.m}px;
display: flex;
column-gap: ${token.padding}px;
}
& .tooltip-detail-body {
font-size: ${supersetTheme.typography.sizes.s}px;
padding: ${supersetTheme.gridUnit}px;
word-break: break-word;
color: ${token.colorTextSecondary};
}
& .tooltip-detail-head,
& .tooltip-detail-body {
padding: ${token.padding}px ${token.paddingLG}px;
}
& .tooltip-detail-footer {
font-size: ${supersetTheme.typography.sizes.s}px;
border-top: 1px ${token.colorSplit} solid;
padding: 0 ${token.paddingLG}px;
color: ${token.colorTextTertiary};
font-size: ${token.fontSizeSM}px;
}
& .tooltip-detail-meta {
& > .ant-tag {
margin-right: 0px;
}
}
}
/* Adjust the searchbox to match theme */
.ace_search {
background-color: ${token.colorBgContainer} !important;
color: ${token.colorText} !important;
border: 1px solid ${token.colorBorder} !important;
}
.ace_search_field {
background-color: ${token.colorBgContainer} !important;
color: ${token.colorText} !important;
border: 1px solid ${token.colorBorder} !important;
}
.ace_button {
background-color: ${token.colorBgElevated} !important;
color: ${token.colorText} !important;
border: 1px solid ${token.colorBorder} !important;
}
.ace_button:hover {
background-color: ${token.colorPrimaryBg} !important;
}
`}
/>

View File

@@ -0,0 +1,68 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { Editor as OrigEditor, TextMode as OrigTextMode } from 'brace';
import type { ComponentType } from 'react';
import type { IAceEditorProps } from 'react-ace';
import type { PlaceholderProps } from '../AsyncEsmComponent/types';
import { aceModuleLoaders } from '.';
export interface AceCompleterKeywordData {
name: string;
value: string;
score: number;
meta: string;
docText?: string;
docHTML?: string;
}
export type TextMode = OrigTextMode & { $id: string };
export interface AceCompleter {
insertMatch: (
data?: Editor | { value: string } | string,
options?: AceCompleterKeywordData,
) => void;
}
export type Editor = OrigEditor & {
completer: AceCompleter;
completers: AceCompleter[];
};
export interface AceCompleterKeyword extends AceCompleterKeywordData {
completer?: AceCompleter;
}
export type AceModule = keyof typeof aceModuleLoaders;
export type AsyncAceEditorProps = IAceEditorProps & {
keywords?: AceCompleterKeyword[];
};
export type AceEditorMode = 'sql';
export type AceEditorTheme = 'textmate' | 'github';
export type AsyncAceEditorOptions = {
defaultMode?: AceEditorMode;
defaultTheme?: AceEditorTheme;
defaultTabSize?: number;
fontFamily?: string;
placeholder?: ComponentType<
PlaceholderProps & Partial<IAceEditorProps>
> | null;
};

View File

@@ -16,10 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
import AsyncEsmComponent, { PlaceholderProps } from '.';
import { AsyncEsmComponent } from '.';
import type { PlaceholderProps } from './types';
export default {
title: 'AsyncEsmComponent',
title: 'Components/AsyncEsmComponent',
};
const Placeholder = () => <span>Loading...</span>;

View File

@@ -16,8 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import { render, screen } from 'spec/helpers/testing-library';
import AsyncEsmComponent from 'src/components/AsyncEsmComponent';
import { render, screen } from '@superset-ui/core/spec';
import { AsyncEsmComponent } from '.';
const Placeholder = () => <span>Loading...</span>;

View File

@@ -17,7 +17,6 @@
* under the License.
*/
import {
CSSProperties,
useEffect,
useState,
RefObject,
@@ -28,16 +27,8 @@ import {
RefAttributes,
} from 'react';
import Loading from '../Loading';
export type PlaceholderProps = {
showLoadingForImport?: boolean;
width?: string | number;
height?: string | number;
placeholderStyle?: CSSProperties;
} & {
[key: string]: any;
};
import { Loading } from '../Loading';
import type { PlaceholderProps } from './types';
function DefaultPlaceholder({
width,
@@ -62,7 +53,7 @@ function DefaultPlaceholder({
* Asynchronously import an ES module as a React component, render a placeholder
* first (if provided) and re-render once import is complete.
*/
export default function AsyncEsmComponent<
export function AsyncEsmComponent<
P = PlaceholderProps,
M = ComponentType<P> | { default: ComponentType<P> },
>(
@@ -128,6 +119,7 @@ export default function AsyncEsmComponent<
const Component = component || placeholder;
return Component ? (
// placeholder does not get the ref
// @ts-expect-error TS(2322): Type '{ ref: RefObject<ComponentType<FullProps>> |... Remove this comment to see the full error message
<Component ref={Component === component ? ref : null} {...props} />
) : null;
});
@@ -138,3 +130,5 @@ export default function AsyncEsmComponent<
preload: typeof waitForPromise;
};
}
export type { PlaceholderProps };

View File

@@ -0,0 +1,28 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { CSSProperties } from 'react';
export type PlaceholderProps = {
showLoadingForImport?: boolean;
width?: string | number;
height?: string | number;
placeholderStyle?: CSSProperties;
} & {
[key: string]: any;
};

View File

@@ -0,0 +1,267 @@
/**
* 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 { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { AutoComplete } from '.';
import type { AutoCompleteProps } from './types';
export default {
title: 'Components/AutoComplete',
component: AutoComplete,
argTypes: {
style: {
control: 'object',
description: 'Custom styles for AutoComplete',
},
placeholder: {
control: 'text',
description: 'Placeholder text for AutoComplete',
},
value: {
control: 'text',
description: 'Selected option',
table: {
type: { summary: 'string' },
},
},
disabled: {
control: 'boolean',
description: 'Disable the AutoComplete',
defaultValue: false,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
},
},
popupMatchSelectWidth: {
control: 'number',
description: 'Width of the dropdown',
defaultValue: 252,
},
allowClear: {
control: 'boolean',
description: 'Show clear button',
defaultValue: false,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
},
},
autoFocus: {
control: 'boolean',
description: 'If get focus when component mounted',
defaultValue: false,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
},
},
backfill: {
control: 'boolean',
description: 'If backfill selected item the input when using keyboard',
defaultValue: false,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
},
},
popupClassName: {
control: 'text',
description: 'The className of dropdown menu',
},
filterOption: {
control: 'boolean',
description:
'If true, filters options by input. If a function, filters options using `inputValue` and `option`. Returns true to include the option, false to exclude it.',
defaultValue: true,
table: {
type: { summary: 'boolean | function(inputValue, option)' },
defaultValue: { summary: 'true' },
},
},
notFoundContent: {
control: 'text',
description: 'Specify content to show when no result matches.',
defaultValue: undefined,
table: {
type: { summary: 'ReactNode' },
defaultValue: { summary: '-' },
},
},
open: {
control: 'boolean',
description: 'Controlled open state of dropdown',
defaultValue: undefined,
table: {
type: { summary: 'boolean' },
},
},
status: {
control: 'select',
options: [undefined, 'error', 'warning'],
description: 'Set validation status',
defaultValue: undefined,
},
size: {
control: 'select',
options: [undefined, 'large', 'middle', 'small'],
description: 'The size of the input box',
defaultValue: undefined,
},
variant: {
control: 'select',
options: ['outlined', 'borderless', 'filled'],
description: 'Variants of input',
defaultValue: 'outlined',
},
virtual: {
control: 'boolean',
description: 'Disable virtual scroll when set to false',
defaultValue: true,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'true' },
},
},
// reference of additional props
defaultValue: {
control: false,
description: 'Initial selected option from the `options` array.',
table: {
type: { summary: 'string' },
},
},
defaultOpen: {
control: false,
description: 'Initial open state of dropdown',
defaultValue: undefined,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
},
},
defaultActiveFirstOption: {
control: false,
description: 'Whether active first option by default',
defaultValue: undefined,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
},
},
dropdownRender: {
control: false,
description:
'Custom render function for dropdown content. `(menus: ReactNode) => ReactNode`',
table: {
type: { summary: '(menus: ReactNode) => ReactNode' },
defaultValue: { summary: '-' },
},
},
options: {
control: false,
description:
'Select options. Will get better performance than using JSX elements.',
table: {
type: { summary: '{ label: string, value: string }[]' },
defaultValue: { summary: '-' },
},
},
children: {
control: false,
description:
'Can be used in two ways:\n' +
'1. Customize input element (e.g., `<Input />`, `<TextArea />`).\n' +
'2. Provide data source for auto-complete (`React.ReactElement<OptionProps>` or an array of such elements).',
table: {
type: {
summary:
'React.ReactElement<InputProps> | React.ReactElement<OptionProps> | React.ReactElement<OptionProps>[]',
},
defaultValue: { summary: '-' },
},
},
getPopupContainer: {
control: false,
description:
'Parent node of the dropdown. Defaults to `body`. If you encounter positioning issues during scrolling, try setting it to the scrollable area and positioning it relative to that.',
defaultValue: () => document.body,
table: {
type: { summary: '(triggerNode) => HTMLElement' },
defaultValue: { summary: '() => document.body' },
},
},
},
parameters: {
docs: {
description: {
component: 'AutoComplete component for search functionality.',
},
},
},
} as Meta<typeof AutoComplete>;
const getRandomInt = (max: number, min = 0) =>
Math.floor(Math.random() * (max - min + 1)) + min;
const searchResult = (query: string) =>
Array.from({ length: getRandomInt(5) }).map((_, idx) => {
const category = `${query}${idx}`;
return {
value: category,
label: (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>
Found {query} on{' '}
<a
href={`https://github.com/apache/superset?q=${query}`}
target="_blank"
rel="noopener noreferrer"
>
{category}
</a>
</span>
<span>{getRandomInt(200, 100)} results</span>
</div>
),
};
});
const AutoCompleteWithOptions = (args: AutoCompleteProps) => {
const [options, setOptions] = useState<AutoCompleteProps['options']>([]);
const handleSearch = (value: string) => {
setOptions(value ? searchResult(value) : []);
};
return <AutoComplete {...args} options={options} onSearch={handleSearch} />;
};
type Story = StoryObj<typeof AutoComplete>;
export const AutoCompleteStory: Story = {
args: {
style: { width: 300 },
placeholder: 'Type to search...',
},
render: (args: AutoCompleteProps) => (
<div style={{ margin: '20px' }}>
<AutoCompleteWithOptions {...args} />
</div>
),
};

View File

@@ -0,0 +1,78 @@
/**
* 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 { useState } from 'react';
import { render, screen, userEvent, waitFor } from '@superset-ui/core/spec';
import { Input } from '../Input';
import { AutoComplete } from '.';
const searchResult = (query: string): Array<{ value: string; label: string }> =>
Array.from({ length: 3 }).map((_, idx) => ({
value: `${query}${idx}`,
label: `${query} result ${idx}`,
}));
const AutoCompleteTest = () => {
const [options, setOptions] = useState<{ value: string; label: string }[]>(
[],
);
const handleSearch = (value: string) => {
setOptions(value ? searchResult(value) : []);
};
return (
<AutoComplete options={options} onSearch={handleSearch}>
<Input placeholder="Type to search..." />
</AutoComplete>
);
};
describe('AutoComplete Component', () => {
it('renders input field', () => {
render(<AutoCompleteTest />);
expect(
screen.getByPlaceholderText('Type to search...'),
).toBeInTheDocument();
});
it('shows options when user types', async () => {
render(<AutoCompleteTest />);
const input = screen.getByPlaceholderText('Type to search...');
await userEvent.type(input, 'test');
await waitFor(() => {
expect(screen.getByText('test result 0')).toBeInTheDocument();
expect(screen.getByText('test result 1')).toBeInTheDocument();
expect(screen.getByText('test result 2')).toBeInTheDocument();
});
});
it('selecting an option updates input value', async () => {
render(<AutoCompleteTest />);
const input = screen.getByPlaceholderText('Type to search...');
await userEvent.type(input, 'test');
await waitFor(() => {
expect(screen.getByText('test result 0')).toBeInTheDocument();
});
await userEvent.click(screen.getByText('test result 0'));
expect(input).toHaveValue('test0');
});
});

View File

@@ -0,0 +1,20 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { AutoComplete } from 'antd';
export type { AutoCompleteProps } from './types';

View File

@@ -0,0 +1,19 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export type { AutoCompleteProps } from 'antd/es/auto-complete';

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