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 docker/**/*.sh text eol=lf
*.svg binary *.svg binary
*.ipynb binary *.ipynb binary
*.geojson binary

View File

@@ -111,6 +111,9 @@ jobs:
docker compose up superset-init --exit-code-from superset-init docker compose up superset-init --exit-code-from superset-init
docker-compose-image-tag: 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 runs-on: ubuntu-24.04
steps: steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"

View File

@@ -53,6 +53,14 @@ jobs:
cd docs cd docs
yarn install --immutable 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 - name: pre-commit
run: | run: |
set +e # Don't exit immediately on failure set +e # Don't exit immediately on failure

View File

@@ -73,6 +73,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
submodules: recursive submodules: recursive
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Checkout using ref (workflow_dispatch) - name: Checkout using ref (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != '' if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -137,9 +138,16 @@ jobs:
NODE_OPTIONS: "--max-old-space-size=4096" NODE_OPTIONS: "--max-old-space-size=4096"
with: with:
run: cypress-run-all ${{ env.USE_DASHBOARD }} ${{ matrix.app_root }} 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 - name: Upload Artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
if: failure() if: failure()
with: with:
path: ${{ github.workspace }}/superset-frontend/cypress-base/cypress/screenshots 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 uses: actions/checkout@v4
with: with:
persist-credentials: false 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 - name: Check for File Changes
id: check id: check
@@ -39,6 +41,10 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | 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 \ docker buildx build \
-t $TAG \ -t $TAG \
--cache-from=type=registry,ref=apache/superset-cache:3.10-slim-bookworm \ --cache-from=type=registry,ref=apache/superset-cache:3.10-slim-bookworm \
@@ -115,24 +121,6 @@ jobs:
files: merged-output/coverage-summary.json files: merged-output/coverage-summary.json
slug: apache/superset 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: lint-frontend:
needs: frontend-build needs: frontend-build
if: needs.frontend-build.outputs.should-run == 'true' if: needs.frontend-build.outputs.should-run == 'true'
@@ -144,7 +132,8 @@ jobs:
name: docker-image name: docker-image
- name: Load Docker Image - name: Load Docker Image
run: docker load < docker-image.tar.gz run: |
docker load < docker-image.tar.gz
- name: eslint - name: eslint
run: | run: |

View File

@@ -58,7 +58,7 @@ repos:
- id: prettier - id: prettier
additional_dependencies: additional_dependencies:
- prettier@3.5.3 - prettier@3.5.3
args: ["--ignore-path=./superset-frontend/.prettierignore"] args: ["--ignore-path=./superset-frontend/.prettierignore", "--exclude", "site-packages"]
files: "superset-frontend" files: "superset-frontend"
- repo: local - repo: local
hooks: 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`. - [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. 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. - [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. - [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/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. - [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, callbackFn: ObserveDataMaskCallbackFn,
) => void; ) => void;
getDataMask: () => Record<string, any>; getDataMask: () => Record<string, any>;
setThemeConfig: (themeConfig: Record<string, any>) => void;
}; };
/** /**
@@ -245,6 +246,18 @@ export async function embedDashboard({
ourPort.start(); ourPort.start();
ourPort.defineMethod('observeDataMask', callbackFn); 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 { return {
getScrollSize, getScrollSize,
@@ -253,5 +266,6 @@ export async function embedDashboard({
getActiveTabs, getActiveTabs,
observeDataMask, observeDataMask,
getDataMask, getDataMask,
setThemeConfig
}; };
} }

View File

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

View File

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

View File

@@ -17,31 +17,75 @@
* under the License. * under the License.
*/ */
import { withJsx } from '@mihkeleidast/storybook-addon-source'; import { withJsx } from '@mihkeleidast/storybook-addon-source';
import { supersetTheme, ThemeProvider } from '@superset-ui/core'; import { themeObject, css, exampleThemes } from '@superset-ui/core';
import { AntdThemeProvider } from '../src/components/AntdThemeProvider';
import { combineReducers, createStore, applyMiddleware, compose } from 'redux'; import { combineReducers, createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import reducerIndex from 'spec/helpers/reducerIndex'; 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 'src/theme.ts';
import './storybook.css'; 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( const store = createStore(
combineReducers(reducerIndex), combineReducers(reducerIndex),
{}, {},
compose(applyMiddleware(thunk)), compose(applyMiddleware(thunk)),
); );
const themeDecorator = Story => ( export const globalTypes = {
<ThemeProvider theme={supersetTheme}> theme: {
<AntdThemeProvider> name: 'Theme',
<GlobalStyles /> description: 'Global theme for components',
<Story /> defaultValue: 'superset',
</AntdThemeProvider> toolbar: {
</ThemeProvider> 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 => ( const providerDecorator = Story => (
<Provider store={store}> <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 { 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() { 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(); cy.getBySel('chart-list-edit-option').click();
} }
function openMenu() { function openMenu() {
cy.get('[aria-label="more"]').eq(1).click(); cy.get('[aria-label="more"]').eq(0).click();
} }
function confirmDelete() { function confirmDelete() {
@@ -81,12 +81,13 @@ describe('Charts list', () => {
cy.wait('@get'); 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(); interceptDashboardGet();
visitSampleChartFromList('1 - Sample chart'); visitSampleChartFromList('1 - Sample chart');
saveChartToDashboard('1 - Sample dashboard'); saveChartToDashboard('1 - Sample chart', '1 - Sample dashboard');
saveChartToDashboard('2 - Sample dashboard'); saveChartToDashboard('1 - Sample chart', '2 - Sample dashboard');
saveChartToDashboard('3 - Sample dashboard'); saveChartToDashboard('1 - Sample chart', '3 - Sample dashboard');
saveChartToDashboard('1 - Sample chart', '4 - Sample dashboard');
visitChartList(); visitChartList();
cy.getBySel('count-crosslinks').should('be.visible'); cy.getBySel('count-crosslinks').should('be.visible');
@@ -95,8 +96,6 @@ describe('Charts list', () => {
describe('list mode', () => { describe('list mode', () => {
before(() => { before(() => {
cy.createSampleDashboards([0, 1, 2, 3]);
cy.createSampleCharts([0]);
visitChartList(); visitChartList();
setGridMode('list'); setGridMode('list');
}); });
@@ -112,18 +111,10 @@ describe('Charts list', () => {
cy.getBySel('sort-header').eq(7).contains('Actions'); 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', () => { it('should bulk select in list mode', () => {
toggleBulkSelect(); toggleBulkSelect();
cy.get('#header-toggle-all').click(); cy.get('[aria-label="Select all"]').click();
cy.get('[aria-label="checkbox-on"]').should('have.length', 26); cy.get('input[type="checkbox"]:checked').should('have.length', 26);
cy.getBySel('bulk-select-copy').contains('25 Selected'); cy.getBySel('bulk-select-copy').contains('25 Selected');
cy.getBySel('bulk-select-action') cy.getBySel('bulk-select-action')
.should('have.length', 2) .should('have.length', 2)
@@ -132,7 +123,7 @@ describe('Charts list', () => {
expect($btns).to.contain('Export'); expect($btns).to.contain('Export');
}); });
cy.getBySel('bulk-select-deselect-all').click(); 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-copy').contains('0 Selected');
cy.getBySel('bulk-select-action').should('not.exist'); cy.getBySel('bulk-select-action').should('not.exist');
}); });
@@ -164,11 +155,6 @@ describe('Charts list', () => {
cy.getBySel('bulk-select-action').should('not.exist'); 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', () => { it('should preserve other filters when sorting', () => {
cy.getBySel('styled-card').should('have.length', 25); cy.getBySel('styled-card').should('have.length', 25);
setFilter('Type', 'Big Number'); setFilter('Type', 'Big Number');
@@ -179,40 +165,12 @@ describe('Charts list', () => {
describe('common actions', () => { describe('common actions', () => {
beforeEach(() => { beforeEach(() => {
cy.createSampleCharts([0, 1, 2, 3]);
visitChartList(); 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', () => { it('should bulk delete correctly', () => {
cy.createSampleCharts([0, 1, 2, 3]);
interceptBulkDelete(); interceptBulkDelete();
toggleBulkSelect(); toggleBulkSelect();
@@ -220,9 +178,10 @@ describe('Charts list', () => {
setGridMode('card'); setGridMode('card');
orderAlphabetical(); orderAlphabetical();
cy.getBySel('styled-card').eq(1).contains('1 - Sample chart').click(); cy.getBySel('skeleton-card').should('not.exist');
cy.getBySel('styled-card').eq(2).contains('2 - Sample chart').click(); cy.getBySel('styled-card').contains('1 - Sample chart').click();
cy.getBySel('bulk-select-action').eq(0).contains('Delete').click(); cy.getBySel('styled-card').contains('2 - Sample chart').click();
cy.getBySel('bulk-select-action').contains('Delete').click();
confirmDelete(); confirmDelete();
cy.wait('@bulkDelete'); cy.wait('@bulkDelete');
cy.getBySel('styled-card') cy.getBySel('styled-card')
@@ -234,56 +193,71 @@ describe('Charts list', () => {
// bulk deletes in list-view // bulk deletes in list-view
setGridMode('list'); setGridMode('list');
cy.getBySel('table-row').eq(1).contains('3 - Sample chart'); cy.get('.loading').should('not.exist');
cy.getBySel('table-row').eq(2).contains('4 - Sample chart'); 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(1).click();
cy.get('[data-test="table-row"] input[type="checkbox"]').eq(2).click();
cy.getBySel('bulk-select-action').eq(0).contains('Delete').click(); cy.getBySel('bulk-select-action').eq(0).contains('Delete').click();
confirmDelete(); confirmDelete();
cy.wait('@bulkDelete'); cy.wait('@bulkDelete');
cy.getBySel('table-row').eq(1).should('not.contain', '3 - Sample chart'); cy.get('.loading').should('exist');
cy.getBySel('table-row').eq(2).should('not.contain', '4 - Sample chart'); 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(); interceptDelete();
// deletes in card-view // deletes in card-view
setGridMode('card'); setGridMode('card');
orderAlphabetical(); orderAlphabetical();
cy.getBySel('styled-card').eq(1).contains('1 - Sample chart'); cy.getBySel('styled-card').contains('1 - Sample chart');
openMenu(); openMenu();
cy.getBySel('chart-list-delete-option').click(); cy.getBySel('chart-list-delete-option').click();
confirmDelete(); confirmDelete();
cy.wait('@delete'); cy.wait('@delete');
cy.getBySel('styled-card') cy.getBySel('styled-card')
.eq(1) .contains('1 - Sample chart')
.should('not.contain', '1 - Sample chart'); .should('not.exist');
});
// deletes in list-view it('should delete correctly in list mode', () => {
setGridMode('list'); cy.createSampleCharts([2, 3]);
cy.getBySel('table-row').eq(1).contains('2 - Sample chart'); interceptDelete();
cy.getBySel('delete').eq(1).click(); 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(); confirmDelete();
cy.wait('@delete'); 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', () => { it('should edit correctly', () => {
cy.createSampleCharts([0]);
interceptUpdate(); interceptUpdate();
// edits in card-view // edits in card-view
setGridMode('card'); setGridMode('card');
orderAlphabetical(); 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 // change title
openProperties(); openProperties();
cy.getBySel('properties-modal-name-input').type(' | EDITED'); cy.getBySel('properties-modal-name-input').type(' | EDITED');
cy.get('button:contains("Save")').click(); cy.get('button:contains("Save")').click();
cy.wait('@update'); 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 // edits in list-view
setGridMode('list'); setGridMode('list');

View File

@@ -47,12 +47,12 @@ describe.skip('Dashboard top-level controls', () => {
// Solution: pause the network before clicking, assert, then unpause network. // Solution: pause the network before clicking, assert, then unpause network.
cy.get('[data-test="refresh-chart-menu-item"]').should( cy.get('[data-test="refresh-chart-menu-item"]').should(
'have.class', 'have.class',
'antd5-dropdown-menu-item-disabled', 'ant-dropdown-menu-item-disabled',
); );
waitForChartLoad(mapSpec); waitForChartLoad(mapSpec);
cy.get('[data-test="refresh-chart-menu-item"]').should( cy.get('[data-test="refresh-chart-menu-item"]').should(
'not.have.class', '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('[aria-label="ellipsis"]').click();
cy.get('[data-test="refresh-dashboard-menu-item"]').should( cy.get('[data-test="refresh-dashboard-menu-item"]').should(
'not.have.class', 'not.have.class',
'antd5-dropdown-menu-item-disabled', 'ant-dropdown-menu-item-disabled',
); );
cy.get('[data-test="refresh-dashboard-menu-item"]').click({ 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( cy.get('[data-test="refresh-dashboard-menu-item"]').should(
'have.class', 'have.class',
'antd5-dropdown-menu-item-disabled', 'ant-dropdown-menu-item-disabled',
); );
// wait all charts force refreshed. // wait all charts force refreshed.
@@ -94,7 +94,7 @@ describe.skip('Dashboard top-level controls', () => {
cy.get('[aria-label="ellipsis"]').click(); cy.get('[aria-label="ellipsis"]').click();
cy.get('[data-test="refresh-dashboard-menu-item"]').and( cy.get('[data-test="refresh-dashboard-menu-item"]').and(
'not.have.class', '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) // Verify the color of the outlined star (gray)
cy.get('@starIconOutlinedAfter') cy.get('@starIconOutlinedAfter')
.should('have.css', 'color') .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(); interceptV1ChartData();
} }
cy.get('.antd5-dropdown:not(.antd5-dropdown-hidden)') cy.get('.ant-dropdown:not(.ant-dropdown-hidden)')
.should('be.visible') .should('be.visible')
.find("[role='menu'] [role='menuitem']") .find("[role='menu'] [role='menuitem']")
.contains(/^Drill by$/) .contains(/^Drill by$/)
.trigger('mouseover', { force: true }); .trigger('mouseover', { force: true });
cy.get( 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') .should('be.visible')
.find('[role="menuitem"]') .find('[role="menuitem"]')

View File

@@ -34,8 +34,8 @@ function openModalFromMenu(chartType: string) {
cy.get( cy.get(
`[data-test-viz-type='${chartType}'] [aria-label='More Options']`, `[data-test-viz-type='${chartType}'] [aria-label='More Options']`,
).click(); ).click();
cy.get('.antd5-dropdown') cy.get('.ant-dropdown')
.not('.antd5-dropdown-hidden') .not('.ant-dropdown-hidden')
.find("[role='menu'] [role='menuitem']") .find("[role='menu'] [role='menuitem']")
.eq(5) .eq(5)
.should('contain', 'Drill to detail') .should('contain', 'Drill to detail')
@@ -46,8 +46,8 @@ function openModalFromMenu(chartType: string) {
function drillToDetail(targetMenuItem: string) { function drillToDetail(targetMenuItem: string) {
interceptSamples(); interceptSamples();
cy.get('.antd5-dropdown') cy.get('.ant-dropdown')
.not('.antd5-dropdown-hidden') .not('.ant-dropdown-hidden')
.first() .first()
.find("[role='menu'] [role='menuitem']") .find("[role='menu'] [role='menuitem']")
.contains(new RegExp(`^${targetMenuItem}$`)) .contains(new RegExp(`^${targetMenuItem}$`))
@@ -61,14 +61,14 @@ function drillToDetail(targetMenuItem: string) {
const drillToDetailBy = (targetDrill: string) => { const drillToDetailBy = (targetDrill: string) => {
interceptSamples(); interceptSamples();
cy.get('.antd5-dropdown:not(.antd5-dropdown-hidden)') cy.get('.ant-dropdown:not(.ant-dropdown-hidden)')
.should('be.visible') .should('be.visible')
.find("[role='menu'] [role='menuitem']") .find("[role='menu'] [role='menuitem']")
.contains(/^Drill to detail by$/) .contains(/^Drill to detail by$/)
.trigger('mouseover', { force: true }); .trigger('mouseover', { force: true });
cy.get( 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') .should('be.visible')
.find('[role="menuitem"]') .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(() => { beforeEach(() => {
closeModal(); closeModal();
}); });
@@ -463,7 +466,7 @@ describe('Drill to detail modal', () => {
}); });
// close the filter and test that data was reloaded // 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.wait('@samples');
cy.getBySel('row-count-label').should('contain', '75.7k rows'); cy.getBySel('row-count-label').should('contain', '75.7k rows');
cy.get('.ant-pagination-item-active').should('contain', '1'); cy.get('.ant-pagination-item-active').should('contain', '1');

View File

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

View File

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

View File

@@ -199,14 +199,16 @@ describe('Native filters', () => {
.should('be.visible') .should('be.visible')
.click(); .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"]') cy.get('[data-test="range-filter-to-input"]')
.should('be.visible') .should('be.visible')
.click(); .click();
cy.get('[data-test="range-filter-to-input"]').type('{selectall}50'); 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' // Assert that the URL contains 'native_filters'
cy.url().then(u => { cy.url().then(u => {
@@ -215,7 +217,7 @@ describe('Native filters', () => {
cy.get('[data-test="range-filter-from-input"]') cy.get('[data-test="range-filter-from-input"]')
.invoke('val') .invoke('val')
.should('equal', '5'); .should('equal', '40');
// Assert that the "To" input has the correct value // Assert that the "To" input has the correct value
cy.get('[data-test="range-filter-to-input"]') 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', () => { it('Verify setting options and tooltips for value filter', () => {
enterNativeFilterEditModal(false); 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(0, nativeFilterTooltips.preFilter);
checkNativeFilterTooltip(1, nativeFilterTooltips.defaultValue); checkNativeFilterTooltip(1, nativeFilterTooltips.defaultValue);
cy.get(nativeFilters.modal.container).should('be.visible'); cy.get(nativeFilters.modal.container).should('be.visible');

View File

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

View File

@@ -79,16 +79,20 @@ describe('Dashboards list', () => {
it('should sort correctly in list mode', () => { it('should sort correctly in list mode', () => {
cy.getBySel('sort-header').eq(1).click(); 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('table-row').first().contains('Supported Charts Dashboard');
cy.getBySel('sort-header').eq(1).click(); 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('table-row').first().contains("World Bank's Data");
cy.getBySel('sort-header').eq(1).click(); cy.getBySel('sort-header').eq(1).click();
}); });
it('should bulk select in list mode', () => { it('should bulk select in list mode', () => {
toggleBulkSelect(); toggleBulkSelect();
cy.get('#header-toggle-all').click(); cy.get('[aria-label="Select all"]').click();
cy.get('[aria-label="checkbox-on"]').should('have.length', 6); cy.get('.ant-checkbox-input')
.should('be.checked')
.should('have.length', 6);
cy.getBySel('bulk-select-copy').contains('5 Selected'); cy.getBySel('bulk-select-copy').contains('5 Selected');
cy.getBySel('bulk-select-action') cy.getBySel('bulk-select-action')
.should('have.length', 2) .should('have.length', 2)
@@ -97,7 +101,7 @@ describe('Dashboards list', () => {
expect($btns).to.contain('Export'); expect($btns).to.contain('Export');
}); });
cy.getBySel('bulk-select-deselect-all').click(); 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-copy').contains('0 Selected');
cy.getBySel('bulk-select-action').should('not.exist'); 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.get('[data-test="table-row"] input[type="checkbox"]').eq(1).click();
cy.getBySel('bulk-select-action').eq(0).contains('Delete').click(); cy.getBySel('bulk-select-action').eq(0).contains('Delete').click();
confirmDelete(true); confirmDelete(true);
cy.getBySel('loading-indicator').should('exist');
cy.getBySel('loading-indicator').should('not.exist');
cy.getBySel('table-row') cy.getBySel('table-row')
.eq(0) .eq(0)
.should('not.contain', '3 - Sample dashboard'); .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]; const SAMPLE_DASHBOARDS_INDEXES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function openDashboardsAddedTo() { function openDashboardsAddedTo() {
cy.getBySel('actions-trigger').click(); cy.getBySel('actions-trigger').should('be.visible').click();
cy.get('.antd5-dropdown-menu-submenu-title') cy.get('.ant-dropdown-menu-submenu-title')
.contains('On dashboards') .contains('On dashboards')
.trigger('mouseover', { force: true }); .trigger('mouseover', { force: true });
} }
function closeDashboardsAddedTo() { function closeDashboardsAddedTo() {
cy.get('.antd5-dropdown-menu-submenu-title') cy.get('.ant-dropdown-menu-submenu-title')
.contains('On dashboards') .contains('On dashboards')
.trigger('mouseout', { force: true }); .trigger('mouseout', { force: true });
cy.getBySel('actions-trigger').click(); cy.getBySel('actions-trigger').click();
} }
function verifyDashboardsSubmenuItem(dashboardName) { function verifyDashboardsSubmenuItem(dashboardName) {
cy.get('.antd5-dropdown-menu-submenu-popup').contains(dashboardName); cy.get('.ant-dropdown-menu-submenu-popup').contains(dashboardName);
closeDashboardsAddedTo(); closeDashboardsAddedTo();
} }
function verifyDashboardSearch() { function verifyDashboardSearch() {
openDashboardsAddedTo(); openDashboardsAddedTo();
cy.get('.antd5-dropdown-menu-submenu-popup').trigger('mouseover'); cy.get('.ant-dropdown-menu-submenu-popup').trigger('mouseover');
cy.get('.antd5-dropdown-menu-submenu-popup') cy.get('.ant-dropdown-menu-submenu-popup')
.find('input[placeholder="Search"]') .find('input[placeholder="Search"]')
.type('1'); .type('1');
cy.get('.antd5-dropdown-menu-submenu-popup').contains('1 - Sample dashboard'); cy.get('.ant-dropdown-menu-submenu-popup').contains('1 - Sample dashboard');
cy.get('.antd5-dropdown-menu-submenu-popup') cy.get('.ant-dropdown-menu-submenu-popup')
.find('input[placeholder="Search"]') .find('input[placeholder="Search"]')
.type('Blahblah'); .type('Blahblah');
cy.get('.antd5-dropdown-menu-submenu-popup').contains('No results found'); cy.get('.ant-dropdown-menu-submenu-popup').contains('No results found');
cy.get('.antd5-dropdown-menu-submenu-popup') cy.get('.ant-dropdown-menu-submenu-popup')
.find('[aria-label="close-circle"]') .find('[aria-label="close-circle"]')
.click(); .click();
closeDashboardsAddedTo(); closeDashboardsAddedTo();
@@ -68,8 +68,8 @@ function verifyDashboardSearch() {
function verifyDashboardLink() { function verifyDashboardLink() {
interceptDashboardGet(); interceptDashboardGet();
openDashboardsAddedTo(); openDashboardsAddedTo();
cy.get('.antd5-dropdown-menu-submenu-popup').trigger('mouseover'); cy.get('.ant-dropdown-menu-submenu-popup').trigger('mouseover');
cy.get('.antd5-dropdown-menu-submenu-popup a') cy.get('.ant-dropdown-menu-submenu-popup a')
.first() .first()
.invoke('removeAttr', 'target') .invoke('removeAttr', 'target')
.click(); .click();
@@ -80,8 +80,8 @@ function verifyMetabar(text) {
cy.getBySel('metadata-bar').contains(text); cy.getBySel('metadata-bar').contains(text);
} }
function saveAndVerifyDashboard(number) { function saveAndVerifyDashboard(chartName, number) {
saveChartToDashboard(`${number} - Sample dashboard`); saveChartToDashboard(chartName, `${number} - Sample dashboard`);
verifyMetabar( verifyMetabar(
number > 1 ? `Added to ${number} dashboards` : 'Added to 1 dashboard', number > 1 ? `Added to ${number} dashboards` : 'Added to 1 dashboard',
); );
@@ -106,17 +106,17 @@ describe('Cross-referenced dashboards', () => {
openDashboardsAddedTo(); openDashboardsAddedTo();
verifyDashboardsSubmenuItem('None'); verifyDashboardsSubmenuItem('None');
saveAndVerifyDashboard('1'); saveAndVerifyDashboard('1 - Sample chart', '1');
saveAndVerifyDashboard('2'); saveAndVerifyDashboard('1 - Sample chart', '2');
saveAndVerifyDashboard('3'); saveAndVerifyDashboard('1 - Sample chart', '3');
saveAndVerifyDashboard('4'); saveAndVerifyDashboard('1 - Sample chart', '4');
saveAndVerifyDashboard('5'); saveAndVerifyDashboard('1 - Sample chart', '5');
saveAndVerifyDashboard('6'); saveAndVerifyDashboard('1 - Sample chart', '6');
saveAndVerifyDashboard('7'); saveAndVerifyDashboard('1 - Sample chart', '7');
saveAndVerifyDashboard('8'); saveAndVerifyDashboard('1 - Sample chart', '8');
saveAndVerifyDashboard('9'); saveAndVerifyDashboard('1 - Sample chart', '9');
saveAndVerifyDashboard('10'); saveAndVerifyDashboard('1 - Sample chart', '10');
saveAndVerifyDashboard('11'); saveAndVerifyDashboard('1 - Sample chart', '11');
verifyDashboardSearch(); verifyDashboardSearch();
verifyDashboardLink(); verifyDashboardLink();

View File

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

View File

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

View File

@@ -67,7 +67,7 @@ export function setFilter(filter: string, option: string) {
cy.wait('@filtering'); cy.wait('@filtering');
} }
export function saveChartToDashboard(dashboardName: string) { export function saveChartToDashboard(chartName: string, dashboardName: string) {
interceptDashboardGet(); interceptDashboardGet();
interceptUpdate(); interceptUpdate();
interceptExploreGet(); interceptExploreGet();
@@ -75,23 +75,30 @@ export function saveChartToDashboard(dashboardName: string) {
cy.getBySel('query-save-button') cy.getBySel('query-save-button')
.should('be.enabled') .should('be.enabled')
.should('not.be.disabled') .should('not.be.disabled')
.click(); .click({ force: true });
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();
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('@get');
cy.wait('@getExplore'); cy.wait('@getExplore');
cy.contains(`was added to dashboard [${dashboardName}]`); 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) { export function visitSampleChartFromList(chartName: string) {

View File

@@ -49,7 +49,7 @@ describe('Visualization > Box Plot', () => {
it('should allow type to search color schemes', () => { it('should allow type to search color schemes', () => {
verify(BOX_PLOT_FORM_DATA); 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"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus(); cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
cy.focused().type('supersetColors{enter}'); 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', () => { it('should allow type to search color schemes and apply the scheme', () => {
verify(COMPARE_FORM_DATA); 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"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus(); cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
cy.focused().type('supersetColors{enter}'); cy.focused().type('supersetColors{enter}');

View File

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

View File

@@ -64,7 +64,7 @@ describe('Visualization > Gauge', () => {
it('should allow type to search color schemes', () => { it('should allow type to search color schemes', () => {
verify(GAUGE_FORM_DATA); 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"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus(); cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
cy.focused().type('bnbColors{enter}'); cy.focused().type('bnbColors{enter}');

View File

@@ -80,7 +80,7 @@ describe('Visualization > Graph', () => {
it('should allow type to search color schemes', () => { it('should allow type to search color schemes', () => {
verify(GRAPH_FORM_DATA); 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"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus(); cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
cy.focused().type('bnbColors{enter}'); cy.focused().type('bnbColors{enter}');

View File

@@ -71,7 +71,7 @@ describe('Visualization > Pie', () => {
it('should allow type to search color schemes', () => { it('should allow type to search color schemes', () => {
verify(PIE_FORM_DATA); 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"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus(); cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
cy.focused().type('supersetColors{enter}'); cy.focused().type('supersetColors{enter}');

View File

@@ -86,7 +86,7 @@ describe('Visualization > Sunburst', () => {
it('should allow type to search color schemes', () => { it('should allow type to search color schemes', () => {
verify(SUNBURST_FORM_DATA); 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"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus(); cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
cy.focused().type('supersetColors{enter}'); cy.focused().type('supersetColors{enter}');

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -4194,7 +4194,7 @@
"peerDependencies": { "peerDependencies": {
"@emotion/react": "^11.4.1", "@emotion/react": "^11.4.1",
"@types/react": "*", "@types/react": "*",
"antd": "^4.9.4", "antd": "^5.24.6",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^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 // Theming variables are provided for your use via a ThemeProvider
// imported from @superset-ui/core. For variables available, please visit // 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>` const Styles = styled.div<<%= packageLabel %>StylesProps>`
background-color: ${({ theme }) => theme.colors.secondary.light2}; background-color: ${({ theme }) => theme.colors.primary.light2};
padding: ${({ theme }) => theme.gridUnit * 4}px; padding: ${({ theme }) => theme.sizeUnit * 4}px;
border-radius: ${({ theme }) => theme.gridUnit * 2}px; border-radius: ${({ theme }) => theme.borderRadius}px;
height: ${({ height }) => height}px; height: ${({ height }) => height}px;
width: ${({ width }) => width}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/react-hooks": "*",
"@testing-library/user-event": "*", "@testing-library/user-event": "*",
"ace-builds": "^1.4.14", "ace-builds": "^1.4.14",
"antd": "4.10.3",
"brace": "^0.11.1", "brace": "^0.11.1",
"memoize-one": "^5.1.1", "memoize-one": "^5.1.1",
"react": "^17.0.2", "react": "^17.0.2",

View File

@@ -18,7 +18,7 @@
*/ */
import { kebabCase } from 'lodash'; import { kebabCase } from 'lodash';
import { t, useTheme, styled } from '@superset-ui/core'; import { t, useTheme, styled } from '@superset-ui/core';
import Tooltip from './Tooltip'; import { Tooltip } from '@superset-ui/core/components';
interface CertifiedIconWithTooltipProps { interface CertifiedIconWithTooltipProps {
certifiedBy?: string | null; certifiedBy?: string | null;
@@ -27,7 +27,7 @@ interface CertifiedIconWithTooltipProps {
} }
const StyledDiv = styled.div` const StyledDiv = styled.div`
margin-bottom: ${({ theme }) => theme.gridUnit * 2}px; margin-bottom: ${({ theme }) => theme.sizeUnit * 2}px;
`; `;
function CertifiedIconWithTooltip({ function CertifiedIconWithTooltip({
@@ -58,7 +58,7 @@ function CertifiedIconWithTooltip({
> >
<g> <g>
<path <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" 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> </g>

View File

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

View File

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

View File

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

View File

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

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. * under the License.
*/ */
export { Menu } from 'antd'; export { Menu, type MenuProps } from '@superset-ui/core/components';
export type { MenuProps } from 'antd/lib/menu';

View File

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

View File

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

View File

@@ -17,7 +17,10 @@
* under the License. * under the License.
*/ */
import { useState, ReactNode } from 'react'; 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; 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>({ export default function Select<VT extends string | number>({
creatable, creatable,
onSearch, onSearch,
dropdownMatchSelectWidth = false, popupMatchSelectWidth = false,
minWidth = '100%', minWidth = '100%',
showSearch: showSearch_ = true, showSearch: showSearch_ = true,
onChange, onChange,
@@ -73,7 +76,7 @@ export default function Select<VT extends string | number>({
return ( return (
<AntdSelect<VT> <AntdSelect<VT>
dropdownMatchSelectWidth={dropdownMatchSelectWidth} popupMatchSelectWidth={popupMatchSelectWidth}
showSearch={showSearch} showSearch={showSearch}
onSearch={handleSearch} onSearch={handleSearch}
onChange={handleChange} 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; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
font-size: ${theme.typography.sizes.s}px; font-size: ${theme.fontSizeSM}px;
line-height: 1.2; line-height: 1.2;
&:not(:last-of-type) { &:not(:last-of-type) {
margin-bottom: ${theme.gridUnit * 2}px; margin-bottom: ${theme.sizeUnit * 2}px;
} }
`} `}
`; `;
const TooltipSectionLabel = styled.span` const TooltipSectionLabel = styled.span`
${({ theme }) => css` ${({ 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 // can't do `export * as sections from './sections'`, babel-transformer will fail
export const sections = sectionsModule; export const sections = sectionsModule;
export * from './components/InfoTooltipWithTrigger';
export * from './components/ColumnOption'; export * from './components/ColumnOption';
export * from './components/ColumnTypeLabel/ColumnTypeLabel'; export * from './components/ColumnTypeLabel/ColumnTypeLabel';
export * from './components/ControlSubSectionHeader'; export * from './components/ControlSubSectionHeader';
export * from './components/Dropdown'; export * from './components/Dropdown';
export * from './components/Menu'; export * from './components/Menu';
export * from './components/MetricOption'; export * from './components/MetricOption';
export * from './components/Tooltip';
export * from './components/ControlHeader'; export * from './components/ControlHeader';
export * from './shared-controls'; export * from './shared-controls';

View File

@@ -17,7 +17,8 @@
* under the License. * under the License.
*/ */
import { ReactNode } from 'react'; 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'; import { ControlHeader } from '../../components/ControlHeader';
// [value, label] // [value, label]
@@ -42,51 +43,19 @@ export default function RadioButtonControl({
...props ...props
}: RadioButtonControlProps) { }: RadioButtonControlProps) {
const currentValue = initialValue || options[0][0]; const currentValue = initialValue || options[0][0];
const theme = useTheme();
return ( return (
<div <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',
},
}}
>
<ControlHeader {...props} /> <ControlHeader {...props} />
<div className="btn-group btn-group-sm"> <Radio.Group
value={currentValue}
onChange={e => onChange(e.target.value)}
>
{options.map(([val, label]) => ( {options.map(([val, label]) => (
<button <Radio.Button key={JSON.stringify(val)} value={val}>
key={JSON.stringify(val)}
type="button"
className={`btn btn-default ${
val === currentValue ? 'active' : ''
}`}
onClick={() => {
onChange(val);
}}
>
{label} {label}
</button> </Radio.Button>
))} ))}
</div> </Radio.Group>
</div> </div>
); );
} }

View File

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

View File

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

View File

@@ -17,11 +17,11 @@
* under the License. * under the License.
*/ */
import '@testing-library/jest-dom'; 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 { 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 }) => ( Tooltip: ({ children }: { children: React.ReactNode }) => (
<div data-test="mock-tooltip">{children}</div> <div data-test="mock-tooltip">{children}</div>
), ),
@@ -29,10 +29,10 @@ jest.mock('../../src/components/Tooltip', () => ({
const defaultProps = {}; const defaultProps = {};
const setup = (props: Partial<InfoTooltipWithTriggerProps> = {}) => const setup = (props: Partial<InfoTooltipProps> = {}) =>
render( render(
<ThemeProvider theme={supersetTheme}> <ThemeProvider theme={supersetTheme}>
<InfoTooltipWithTrigger {...defaultProps} {...props} /> <InfoTooltip {...defaultProps} {...props} />
</ThemeProvider>, </ThemeProvider>,
); );
@@ -44,31 +44,29 @@ test('renders a tooltip', () => {
expect(getAllByTestId('mock-tooltip').length).toEqual(1); expect(getAllByTestId('mock-tooltip').length).toEqual(1);
}); });
test('renders an info icon', () => { test('responds to keydown events', () => {
const { container } = setup();
expect(container.getElementsByClassName('fa-info-circle')).toHaveLength(1);
});
test('responds to keypresses', () => {
const clickHandler = jest.fn(); const clickHandler = jest.fn();
const { getByRole } = setup({ const { getByRole } = setup({
label: 'test', label: 'test',
tooltip: 'this is a test', tooltip: 'this is a test',
onClick: clickHandler, onClick: clickHandler,
}); });
fireEvent.keyPress(getByRole('button'), {
fireEvent.keyDown(getByRole('button'), {
key: 'Tab', key: 'Tab',
code: 9, code: 9,
charCode: 9, charCode: 9,
}); });
expect(clickHandler).toHaveBeenCalledTimes(0); expect(clickHandler).toHaveBeenCalledTimes(0);
fireEvent.keyPress(getByRole('button'), {
fireEvent.keyDown(getByRole('button'), {
key: 'Enter', key: 'Enter',
code: 13, code: 13,
charCode: 13, charCode: 13,
}); });
expect(clickHandler).toHaveBeenCalledTimes(1); expect(clickHandler).toHaveBeenCalledTimes(1);
fireEvent.keyPress(getByRole('button'), {
fireEvent.keyDown(getByRole('button'), {
key: ' ', key: ' ',
code: 32, code: 32,
charCode: 32, charCode: 32,
@@ -76,9 +74,47 @@ test('responds to keypresses', () => {
expect(clickHandler).toHaveBeenCalledTimes(2); expect(clickHandler).toHaveBeenCalledTimes(2);
}); });
test('has a bsStyle', () => { test('finds the info circle icon inside info variant', () => {
const { container } = setup({ 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. * under the License.
*/ */
import '@testing-library/jest-dom'; 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 { ThemeProvider, supersetTheme } from '@superset-ui/core';
import { MetricOption, MetricOptionProps } from '../../src'; import {
MetricOption,
MetricOptionProps,
} from '../../src/components/MetricOption';
jest.mock('../../src/components/InfoTooltipWithTrigger', () => () => ( jest.mock('@superset-ui/core/components/InfoTooltip', () => ({
<div data-test="mock-info-tooltip-with-trigger" /> InfoTooltip: () => <div data-test="mock-tooltip" />,
));
jest.mock('../../src/components/ColumnTypeLabel/ColumnTypeLabel', () => ({
ColumnTypeLabel: () => <div data-test="mock-column-type-label" />,
})); }));
jest.mock( 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 }) => ( ({ children }: { children: React.ReactNode }) => (
<div data-test="mock-tooltip">{children}</div> <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" />, SQLPopover: () => <div data-test="mock-sql-popover" />,
})); }));
@@ -65,9 +72,9 @@ test('shows a label with verbose_name', () => {
expect(lbl).toHaveLength(1); expect(lbl).toHaveLength(1);
expect(`${lbl[0].textContent}`).toEqual(defaultProps.metric.verbose_name); expect(`${lbl[0].textContent}`).toEqual(defaultProps.metric.verbose_name);
}); });
test('shows a InfoTooltipWithTrigger', () => { test('shows a InfoTooltip', () => {
const { getByTestId } = setup(); const { getByTestId } = setup();
expect(getByTestId('mock-info-tooltip-with-trigger')).toBeInTheDocument(); expect(getByTestId('mock-tooltip')).toBeInTheDocument();
}); });
test('shows SQL Popover trigger', () => { test('shows SQL Popover trigger', () => {
const { getByTestId } = setup(); 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(); expect(getByText(defaultProps.metric.metric_name)).toBeInTheDocument();
}); });
test('doesnt show InfoTooltipWithTrigger when no warning', () => { test('doesnt show InfoTooltip when no warning', () => {
const { queryByText } = setup({ const { queryByText } = setup({
metric: { metric: {
...defaultProps.metric, ...defaultProps.metric,
warning_text: '', 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', () => { test('sets target="_blank" when openInNewWindow is true', () => {
const { getByRole } = setup({ const { getByRole } = setup({

View File

@@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import { ReactElement } from 'react'; 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 '@testing-library/jest-dom';
import { ThemeProvider, supersetTheme } from '@superset-ui/core'; import { ThemeProvider, supersetTheme } from '@superset-ui/core';
import { import {

View File

@@ -129,7 +129,7 @@ test('returns empty array if timeseries_limit_metric is an empty array', () => {
expect( expect(
extractExtraMetrics({ extractExtraMetrics({
...baseFormData, ...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: [], timeseries_limit_metric: [],
}), }),
).toEqual([]); ).toEqual([]);

View File

@@ -51,7 +51,7 @@ describe('defineSavedMetrics', () => {
uuid: '1', 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([]); expect(defineSavedMetrics({ ...dataset, metrics: undefined })).toEqual([]);
}); });

View File

@@ -284,7 +284,7 @@ describe('getColorFunction()', () => {
it('getColorFunction unsupported operator', () => { it('getColorFunction unsupported operator', () => {
const colorFunction = getColorFunction( 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', operator: 'unsupported operator',
targetValue: 50, targetValue: 50,
colorScheme: '#FF0000', 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" "lib"
], ],
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.2.6",
"@babel/runtime": "^7.25.6", "@babel/runtime": "^7.25.6",
"@fontsource/fira-code": "^5.0.18",
"@fontsource/inter": "^5.0.20",
"@types/json-bigint": "^1.0.4", "@types/json-bigint": "^1.0.4",
"ace-builds": "^1.41.0",
"brace": "^0.11.1",
"classnames": "^2.2.5",
"csstype": "^3.1.3", "csstype": "^3.1.3",
"core-js": "^3.38.1",
"d3-format": "^1.3.2", "d3-format": "^1.3.2",
"dayjs": "^1.11.13",
"d3-interpolate": "^3.0.1", "d3-interpolate": "^3.0.1",
"d3-scale": "^3.0.0", "d3-scale": "^3.0.0",
"d3-time": "^3.1.0", "d3-time": "^3.1.0",
"d3-time-format": "^4.1.0", "d3-time-format": "^4.1.0",
"dompurify": "^3.2.4",
"fetch-retry": "^6.0.0", "fetch-retry": "^6.0.0",
"jed": "^1.1.1", "jed": "^1.1.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"math-expression-evaluator": "^2.0.6", "math-expression-evaluator": "^2.0.6",
"pretty-ms": "^9.2.0", "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-error-boundary": "^5.0.0",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"regenerator-runtime": "^0.14.1",
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0", "rehype-sanitize": "^6.0.0",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
@@ -56,8 +72,10 @@
"@types/d3-scale": "^2.1.1", "@types/d3-scale": "^2.1.1",
"@types/d3-time": "^3.0.4", "@types/d3-time": "^3.0.4",
"@types/d3-time-format": "^4.0.3", "@types/d3-time-format": "^4.0.3",
"@types/react-table": "^7.7.20",
"@types/enzyme": "^3.10.18", "@types/enzyme": "^3.10.18",
"@types/fetch-mock": "^7.3.8", "@types/fetch-mock": "^7.3.8",
"@types/jquery": "^3.5.8",
"@types/lodash": "^4.17.16", "@types/lodash": "^4.17.16",
"@types/math-expression-evaluator": "^1.3.3", "@types/math-expression-evaluator": "^1.3.3",
"@types/node": "^22.10.3", "@types/node": "^22.10.3",
@@ -70,6 +88,7 @@
"timezone-mock": "1.3.6" "timezone-mock": "1.3.6"
}, },
"peerDependencies": { "peerDependencies": {
"antd": "^5.24.6",
"@emotion/cache": "^11.4.0", "@emotion/cache": "^11.4.0",
"@emotion/react": "^11.4.1", "@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0", "@emotion/styled": "^11.3.0",
@@ -80,6 +99,7 @@
"@testing-library/user-event": "*", "@testing-library/user-event": "*",
"@types/react": "*", "@types/react": "*",
"@types/react-loadable": "*", "@types/react-loadable": "*",
"@types/react-window": "^1.8.8",
"@types/tinycolor2": "*", "@types/tinycolor2": "*",
"nanoid": "^5.0.9", "nanoid": "^5.0.9",
"react": "^17.0.2", "react": "^17.0.2",
@@ -88,5 +108,27 @@
}, },
"publishConfig": { "publishConfig": {
"access": "public" "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 * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { CSSProperties, ReactNode } from 'react';
import { CSSProperties, PureComponent, ReactNode } from 'react'; import { Table, type TableColumnsType } from 'antd';
interface TooltipRowData { interface TooltipRowData {
key: string | number; key: string | number;
@@ -27,43 +27,52 @@ interface TooltipRowData {
valueStyle?: CSSProperties; valueStyle?: CSSProperties;
} }
const defaultProps = { interface TooltipTableProps {
className: '',
data: [] as TooltipRowData[],
};
type Props = {
className?: string; className?: string;
data: TooltipRowData[]; data: TooltipRowData[];
} & Readonly<typeof defaultProps>; }
const VALUE_CELL_STYLE: CSSProperties = { paddingLeft: 8, textAlign: 'right' }; const VALUE_CELL_STYLE: CSSProperties = { paddingLeft: 8, textAlign: 'right' };
export default class TooltipTable extends PureComponent<Props, {}> { const TooltipTable = ({ className = '', data }: TooltipTableProps) => {
static defaultProps = defaultProps; 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() { return (
const { className, data } = this.props; <Table
className={className}
columns={columns}
dataSource={data}
pagination={false}
showHeader={false}
bordered={false}
/>
);
};
return ( export default TooltipTable;
<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>
);
}
}

View File

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

View File

@@ -18,7 +18,7 @@
*/ */
import { CSSProperties } from 'react'; import { CSSProperties } from 'react';
import { css, styled } from '../../style'; import { css, styled } from '../../theme';
import { t } from '../../translation'; import { t } from '../../translation';
const MESSAGE_STYLES: CSSProperties = { maxWidth: 800 }; const MESSAGE_STYLES: CSSProperties = { maxWidth: 800 };
@@ -36,16 +36,16 @@ const Container = styled.div<{
text-align: center; text-align: center;
height: ${height}px; height: ${height}px;
width: ${width}px; width: ${width}px;
padding: ${theme.gridUnit * 4}px; padding: ${theme.sizeUnit * 4}px;
& .no-results-title { & .no-results-title {
font-size: ${theme.typography.sizes.l}px; font-size: ${theme.fontSizeLG}px;
font-weight: ${theme.typography.weights.bold}; font-weight: ${theme.fontWeightStrong};
padding-bottom: ${theme.gridUnit * 2}; padding-bottom: ${theme.sizeUnit * 2};
} }
& .no-results-body { & .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. // 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 // TODO: Note that id and className can collide between Props and ReactifyProps
// leading to (likely) unexpected behaviors. We should either require Props to not // leading to (likely) unexpected behaviors. We should either require Props to not

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { forwardRef, useEffect, ComponentType } from 'react'; import { forwardRef, useEffect, useCallback, ComponentType } from 'react';
import type { import type {
Editor as OrigEditor, Editor as OrigEditor,
@@ -27,10 +27,10 @@ import type {
import type AceEditor from 'react-ace'; import type AceEditor from 'react-ace';
import type { IAceEditorProps } from 'react-ace'; import type { IAceEditorProps } from 'react-ace';
import AsyncEsmComponent, { import {
AsyncEsmComponent,
PlaceholderProps, PlaceholderProps,
} from 'src/components/AsyncEsmComponent'; } from '@superset-ui/core/components/AsyncEsmComponent';
import useEffectEvent from 'src/hooks/useEffectEvent';
import { useTheme, css } from '@superset-ui/core'; import { useTheme, css } from '@superset-ui/core';
import { Global } from '@emotion/react'; 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(...)` * Async loaders to import brace modules. Must manually create call `import(...)`
* promises because webpack can only analyze async imports statically. * promises because webpack can only analyze async imports statically.
*/ */
const aceModuleLoaders = { export const aceModuleLoaders = {
'mode/sql': () => import('brace/mode/sql'), 'mode/sql': () => import('brace/mode/sql'),
'mode/markdown': () => import('brace/mode/markdown'), 'mode/markdown': () => import('brace/mode/markdown'),
'mode/css': () => import('brace/mode/css'), 'mode/css': () => import('brace/mode/css'),
@@ -102,7 +102,7 @@ export type AsyncAceEditorOptions = {
/** /**
* Get an async AceEditor with automatical loading of specified ace modules. * Get an async AceEditor with automatical loading of specified ace modules.
*/ */
export default function AsyncAceEditor( export function AsyncAceEditor(
aceModules: AceModule[], aceModules: AceModule[],
{ {
defaultMode, defaultMode,
@@ -155,9 +155,10 @@ export default function AsyncAceEditor(
}, },
ref, ref,
) { ) {
const supersetTheme = useTheme(); const token = useTheme();
const langTools = acequire('ace/ext/language_tools'); const langTools = acequire('ace/ext/language_tools');
const setCompleters = useEffectEvent(
const setCompleters = useCallback(
(keywords: AceCompleterKeyword[]) => { (keywords: AceCompleterKeyword[]) => {
const completer = { const completer = {
getCompletions: ( getCompletions: (
@@ -180,7 +181,9 @@ export default function AsyncAceEditor(
}; };
langTools.setCompleters([completer]); langTools.setCompleters([completer]);
}, },
[langTools, mode],
); );
useEffect(() => { useEffect(() => {
if (keywords) { if (keywords) {
setCompleters(keywords); setCompleters(keywords);
@@ -192,36 +195,148 @@ export default function AsyncAceEditor(
<Global <Global
key="ace-tooltip-global" key="ace-tooltip-global"
styles={css` 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 { .ace_tooltip {
all: unset; margin-left: ${token.margin}px;
position: fixed; padding: 0px;
z-index: 9999; background-color: ${token.colorBgElevated} !important;
background: ${supersetTheme.colors.grayscale.light5}; color: ${token.colorText} !important;
border: 1px solid ${supersetTheme.colors.grayscale.light1}; border: 1px solid ${token.colorBorderSecondary};
padding: ${supersetTheme.gridUnit}px box-shadow: ${token.boxShadow};
${supersetTheme.gridUnit * 2}px; border-radius: ${token.borderRadius}px;
line-height: 1.4;
max-width: 400px;
min-width: 200px;
pointer-events: auto;
font-size: ${supersetTheme.typography.sizes.m}px;
} }
& .tooltip-detail { & .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 { & .tooltip-detail-title {
font-weight: bold; display: flex;
font-size: ${supersetTheme.typography.sizes.m}px; column-gap: ${token.padding}px;
} }
& .tooltip-detail-body { & .tooltip-detail-body {
font-size: ${supersetTheme.typography.sizes.s}px; word-break: break-word;
padding: ${supersetTheme.gridUnit}px; color: ${token.colorTextSecondary};
} }
& .tooltip-detail-head, & .tooltip-detail-head,
& .tooltip-detail-body { & .tooltip-detail-body {
padding: ${token.padding}px ${token.paddingLG}px;
} }
& .tooltip-detail-footer { & .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 * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import AsyncEsmComponent, { PlaceholderProps } from '.'; import { AsyncEsmComponent } from '.';
import type { PlaceholderProps } from './types';
export default { export default {
title: 'AsyncEsmComponent', title: 'Components/AsyncEsmComponent',
}; };
const Placeholder = () => <span>Loading...</span>; const Placeholder = () => <span>Loading...</span>;

View File

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

View File

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