mirror of
https://github.com/apache/superset.git
synced 2026-04-30 13:34:20 +00:00
Compare commits
178 Commits
semantic-l
...
spinner-po
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbd19f9772 | ||
|
|
45a032a2f8 | ||
|
|
0a07520dea | ||
|
|
1be1ca1eee | ||
|
|
253a72f58a | ||
|
|
ae98d5fa6a | ||
|
|
96d7209eb5 | ||
|
|
fa8819770e | ||
|
|
72fc7ac27f | ||
|
|
56089aab3a | ||
|
|
33b069a580 | ||
|
|
1eb65ec7ac | ||
|
|
0cacaba495 | ||
|
|
a9f744f8a2 | ||
|
|
b41961a2a6 | ||
|
|
04d0053a89 | ||
|
|
84287be168 | ||
|
|
4676f5c756 | ||
|
|
0315a3723f | ||
|
|
20a33b0744 | ||
|
|
3e34efef52 | ||
|
|
54e2c96720 | ||
|
|
fd7b8f6bd1 | ||
|
|
3eb04b0867 | ||
|
|
90967c6bb1 | ||
|
|
ef8ba1cbeb | ||
|
|
926d2bf031 | ||
|
|
bd77f82cc9 | ||
|
|
9c7b676bfc | ||
|
|
28db9ad7fc | ||
|
|
58435e3e28 | ||
|
|
8e48fdbd6f | ||
|
|
17ef5b67d1 | ||
|
|
c279d08d5e | ||
|
|
2979c30703 | ||
|
|
20a17be0f3 | ||
|
|
4070dba438 | ||
|
|
0af5770a49 | ||
|
|
33f2ffd2a9 | ||
|
|
fcea7e4af9 | ||
|
|
7258dc9ea0 | ||
|
|
14645a2dfa | ||
|
|
6fe8a54b6f | ||
|
|
b9710f947c | ||
|
|
399788442a | ||
|
|
b03c425393 | ||
|
|
e862b5cddd | ||
|
|
5a67713f3f | ||
|
|
f00adac73e | ||
|
|
1c28138938 | ||
|
|
cf68c879e2 | ||
|
|
5b991800c3 | ||
|
|
7472714ce7 | ||
|
|
67aa991099 | ||
|
|
5267ec2028 | ||
|
|
3983ee0c2f | ||
|
|
863a0bea5c | ||
|
|
d635c2a9ab | ||
|
|
2c038f5bd6 | ||
|
|
fc031ca35b | ||
|
|
89424894f3 | ||
|
|
9da62ed63a | ||
|
|
49bcf79f71 | ||
|
|
e529d84e34 | ||
|
|
f580da88ca | ||
|
|
b040d52c2d | ||
|
|
a7b7e6319c | ||
|
|
c217f56aea | ||
|
|
d43657ed90 | ||
|
|
6c01173c21 | ||
|
|
4709eb0153 | ||
|
|
51f719f8d4 | ||
|
|
50535a92a0 | ||
|
|
3fe5db1e72 | ||
|
|
08c9d28545 | ||
|
|
86d5537a7f | ||
|
|
18c3f0adb6 | ||
|
|
5e8cd7a6ee | ||
|
|
dcea6c09ca | ||
|
|
dff7c1b50d | ||
|
|
4f93c2d2e7 | ||
|
|
334aa1a672 | ||
|
|
2667a14678 | ||
|
|
29ba5adf21 | ||
|
|
f18455cc2d | ||
|
|
4123d25873 | ||
|
|
662f33b7de | ||
|
|
c14dcecd8f | ||
|
|
6a4730bbbe | ||
|
|
739caa19cb | ||
|
|
8bb02e2958 | ||
|
|
7c2fd55104 | ||
|
|
6c8e72b889 | ||
|
|
3b198ab656 | ||
|
|
c993abe58c | ||
|
|
a9bc4655a4 | ||
|
|
b835478514 | ||
|
|
3950cf065e | ||
|
|
c7d2881d04 | ||
|
|
33febb669e | ||
|
|
6254db34cd | ||
|
|
e6df194201 | ||
|
|
2580a8ba78 | ||
|
|
6b58ef155e | ||
|
|
6f73e58b25 | ||
|
|
bc85a118ba | ||
|
|
70a5925b03 | ||
|
|
d266835820 | ||
|
|
952658ee63 | ||
|
|
27d723fba1 | ||
|
|
971715931b | ||
|
|
e3342bb731 | ||
|
|
506c8387fc | ||
|
|
9dedb588ba | ||
|
|
8b69958f19 | ||
|
|
1dd8a76113 | ||
|
|
cde1da6285 | ||
|
|
3665ebcb4b | ||
|
|
c31d70dd12 | ||
|
|
f217865435 | ||
|
|
501874980e | ||
|
|
e7b2b586b6 | ||
|
|
6a15aaf562 | ||
|
|
f5b680699f | ||
|
|
3c289a927d | ||
|
|
5805f242d0 | ||
|
|
4f0a4454ec | ||
|
|
d752b0f06a | ||
|
|
06d737ec9f | ||
|
|
04729794c8 | ||
|
|
68ea9ac4d0 | ||
|
|
1afa4971d1 | ||
|
|
5418f09864 | ||
|
|
aabeefb761 | ||
|
|
023c7da07b | ||
|
|
7af32d4c70 | ||
|
|
58724b1c5c | ||
|
|
f9494128bc | ||
|
|
cebff5e726 | ||
|
|
bcb6da18ef | ||
|
|
344c8f5c37 | ||
|
|
d3f450fca0 | ||
|
|
11a29b1610 | ||
|
|
54d67b679b | ||
|
|
64c480a8f1 | ||
|
|
cf6816064d | ||
|
|
59e402ac68 | ||
|
|
5042248ed7 | ||
|
|
56e3d165dd | ||
|
|
8ce144983d | ||
|
|
4afbfd11e0 | ||
|
|
31eb10590e | ||
|
|
358633e98d | ||
|
|
b7bc1113ac | ||
|
|
11bc4965e3 | ||
|
|
1ca0f34210 | ||
|
|
9cb6c3b039 | ||
|
|
b9be692e55 | ||
|
|
e0d86df5a5 | ||
|
|
2f80ebb3e8 | ||
|
|
83f47d3ca1 | ||
|
|
48df49d89c | ||
|
|
f16600ee86 | ||
|
|
7dbe05f6d8 | ||
|
|
2d461deb68 | ||
|
|
dbc7db981c | ||
|
|
5faf0189e8 | ||
|
|
4b55a928c9 | ||
|
|
d15c6d361b | ||
|
|
6b5d53ad39 | ||
|
|
b575aa6aac | ||
|
|
d131c29f3b | ||
|
|
22cbec1d95 | ||
|
|
b2b7b899a3 | ||
|
|
ac81eefe3f | ||
|
|
8d361205f6 | ||
|
|
352aa36823 | ||
|
|
336763f0c9 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,3 +1,4 @@
|
||||
docker/**/*.sh text eol=lf
|
||||
*.svg binary
|
||||
*.ipynb binary
|
||||
*.geojson binary
|
||||
|
||||
3
.github/workflows/docker.yml
vendored
3
.github/workflows/docker.yml
vendored
@@ -111,6 +111,9 @@ jobs:
|
||||
docker compose up superset-init --exit-code-from superset-init
|
||||
|
||||
docker-compose-image-tag:
|
||||
# Run this job only on pushes to master (not for PRs)
|
||||
# goal is to check that building the latest image works, not required for all PR pushes
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
|
||||
8
.github/workflows/pre-commit.yml
vendored
8
.github/workflows/pre-commit.yml
vendored
@@ -53,6 +53,14 @@ jobs:
|
||||
cd docs
|
||||
yarn install --immutable
|
||||
|
||||
- name: Cache pre-commit environments
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
restore-keys: |
|
||||
pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-
|
||||
|
||||
- name: pre-commit
|
||||
run: |
|
||||
set +e # Don't exit immediately on failure
|
||||
|
||||
10
.github/workflows/superset-e2e.yml
vendored
10
.github/workflows/superset-e2e.yml
vendored
@@ -73,6 +73,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout using ref (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
|
||||
uses: actions/checkout@v4
|
||||
@@ -137,9 +138,16 @@ jobs:
|
||||
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||
with:
|
||||
run: cypress-run-all ${{ env.USE_DASHBOARD }} ${{ matrix.app_root }}
|
||||
- name: Set safe app root
|
||||
if: failure()
|
||||
id: set-safe-app-root
|
||||
run: |
|
||||
APP_ROOT="${{ matrix.app_root }}"
|
||||
SAFE_APP_ROOT=${APP_ROOT//\//_}
|
||||
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
path: ${{ github.workspace }}/superset-frontend/cypress-base/cypress/screenshots
|
||||
name: cypress-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}-${{ matrix.parallel_id }}
|
||||
name: cypress-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}-${{ matrix.parallel_id }}--${{ steps.set-safe-app-root.outputs.safe_app_root }}
|
||||
|
||||
27
.github/workflows/superset-frontend.yml
vendored
27
.github/workflows/superset-frontend.yml
vendored
@@ -26,6 +26,8 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Check for File Changes
|
||||
id: check
|
||||
@@ -39,6 +41,10 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "git rev-parse --short HEAD"
|
||||
git rev-parse --short HEAD
|
||||
echo "git show -s --format=raw HEAD"
|
||||
git show -s --format=raw HEAD
|
||||
docker buildx build \
|
||||
-t $TAG \
|
||||
--cache-from=type=registry,ref=apache/superset-cache:3.10-slim-bookworm \
|
||||
@@ -115,24 +121,6 @@ jobs:
|
||||
files: merged-output/coverage-summary.json
|
||||
slug: apache/superset
|
||||
|
||||
core-cover:
|
||||
needs: frontend-build
|
||||
if: needs.frontend-build.outputs.should-run == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
- name: Load Docker Image
|
||||
run: docker load < docker-image.tar.gz
|
||||
|
||||
- name: superset-ui/core coverage
|
||||
run: |
|
||||
docker run --rm $TAG bash -c \
|
||||
"npm run core:cover"
|
||||
|
||||
lint-frontend:
|
||||
needs: frontend-build
|
||||
if: needs.frontend-build.outputs.should-run == 'true'
|
||||
@@ -144,7 +132,8 @@ jobs:
|
||||
name: docker-image
|
||||
|
||||
- name: Load Docker Image
|
||||
run: docker load < docker-image.tar.gz
|
||||
run: |
|
||||
docker load < docker-image.tar.gz
|
||||
|
||||
- name: eslint
|
||||
run: |
|
||||
|
||||
@@ -58,7 +58,7 @@ repos:
|
||||
- id: prettier
|
||||
additional_dependencies:
|
||||
- prettier@3.5.3
|
||||
args: ["--ignore-path=./superset-frontend/.prettierignore"]
|
||||
args: ["--ignore-path=./superset-frontend/.prettierignore", "--exclude", "site-packages"]
|
||||
files: "superset-frontend"
|
||||
- repo: local
|
||||
hooks:
|
||||
|
||||
@@ -26,6 +26,7 @@ assists people when migrating to a new version.
|
||||
- [33116](https://github.com/apache/superset/pull/33116) In Echarts Series charts (e.g. Line, Area, Bar, etc.) charts, the `x_axis_sort_series` and `x_axis_sort_series_ascending` form data items have been renamed with `x_axis_sort` and `x_axis_sort_asc`.
|
||||
There's a migration added that can potentially affect a significant number of existing charts.
|
||||
- [32317](https://github.com/apache/superset/pull/32317) The horizontal filter bar feature is now out of testing/beta development and its feature flag `HORIZONTAL_FILTER_BAR` has been removed.
|
||||
- [31590](https://github.com/apache/superset/pull/31590) Marks the begining of intricate work around supporting dynamic Theming, and breaks support for [THEME_OVERRIDES](https://github.com/apache/superset/blob/732de4ac7fae88e29b7f123b6cbb2d7cd411b0e4/superset/config.py#L671) in favor of a new theming system based on AntD V5. Likely this will be in disrepair until settling over the 5.x lifecycle.
|
||||
- [31976](https://github.com/apache/superset/pull/31976) Removed the `DISABLE_LEGACY_DATASOURCE_EDITOR` feature flag. The previous value of the feature flag was `True` and now the feature is permanently removed.
|
||||
- [31959](https://github.com/apache/superset/pull/32000) Removes CSV_UPLOAD_MAX_SIZE config, use your web server to control file upload size.
|
||||
- [31959](https://github.com/apache/superset/pull/31959) Removes the following endpoints from data uploads: `/api/v1/database/<id>/<file type>_upload` and `/api/v1/database/<file type>_metadata`, in favour of new one (Details on the PR). And simplifies permissions.
|
||||
|
||||
@@ -89,6 +89,7 @@ export type EmbeddedDashboard = {
|
||||
callbackFn: ObserveDataMaskCallbackFn,
|
||||
) => void;
|
||||
getDataMask: () => Record<string, any>;
|
||||
setThemeConfig: (themeConfig: Record<string, any>) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -245,6 +246,18 @@ export async function embedDashboard({
|
||||
ourPort.start();
|
||||
ourPort.defineMethod('observeDataMask', callbackFn);
|
||||
};
|
||||
// TODO: Add proper types once theming branch is merged
|
||||
const setThemeConfig = async (themeConfig: Record<string, any>): Promise<void> => {
|
||||
try {
|
||||
ourPort.emit('setThemeConfig', { themeConfig });
|
||||
log('Theme config sent successfully (or at least message dispatched)');
|
||||
} catch (error) {
|
||||
log(
|
||||
'Error sending theme config. Ensure the iframe side implements the "setThemeConfig" method.',
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getScrollSize,
|
||||
@@ -253,5 +266,6 @@ export async function embedDashboard({
|
||||
getActiveTabs,
|
||||
observeDataMask,
|
||||
getDataMask,
|
||||
setThemeConfig
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
coverage/**
|
||||
dist/*
|
||||
src/assets/images/*
|
||||
src/assets/stylesheets/*
|
||||
node_modules/*
|
||||
node_modules*/*
|
||||
vendor/*
|
||||
|
||||
@@ -52,7 +52,7 @@ const restrictedImportsRules = {
|
||||
message: 'Lodash Memoize is unsafe! Please use memoize-one instead',
|
||||
},
|
||||
'no-testing-library-react': {
|
||||
name: '@testing-library/react',
|
||||
name: '@superset-ui/core/spec',
|
||||
message: 'Please use spec/helpers/testing-library instead',
|
||||
},
|
||||
'no-testing-library-react-dom-utils': {
|
||||
@@ -63,10 +63,6 @@ const restrictedImportsRules = {
|
||||
name: 'antd',
|
||||
message: 'Please import Ant components from the index of src/components',
|
||||
},
|
||||
'no-antd-v5': {
|
||||
name: 'antd-v5',
|
||||
message: 'Please import Ant v5 components from the index of src/components',
|
||||
},
|
||||
'no-superset-theme': {
|
||||
name: '@superset-ui/core',
|
||||
importNames: ['supersetTheme'],
|
||||
@@ -101,6 +97,15 @@ module.exports = {
|
||||
// resolve modules from `/superset_frontend/node_modules` and `/superset_frontend`
|
||||
moduleDirectory: ['node_modules', '.'],
|
||||
},
|
||||
typescript: {
|
||||
alwaysTryTypes: true,
|
||||
project: [
|
||||
'./tsconfig.json',
|
||||
'./packages/superset-ui-core/tsconfig.json',
|
||||
'./packages/superset-ui-chart-controls/',
|
||||
'./plugins/*/tsconfig.json',
|
||||
],
|
||||
},
|
||||
},
|
||||
// only allow import from top level of module
|
||||
'import/core-modules': importCoreModules,
|
||||
@@ -295,9 +300,9 @@ module.exports = {
|
||||
'error',
|
||||
{
|
||||
paths: Object.values(restrictedImportsRules).filter(
|
||||
r => r.name !== 'antd-v5',
|
||||
r => r.name !== 'antd',
|
||||
),
|
||||
patterns: ['antd/*'],
|
||||
patterns: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -330,7 +335,9 @@ module.exports = {
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{ devDependencies: true },
|
||||
{
|
||||
devDependencies: true,
|
||||
},
|
||||
],
|
||||
'no-only-tests/no-only-tests': 'error',
|
||||
'max-classes-per-file': 0,
|
||||
@@ -373,7 +380,7 @@ module.exports = {
|
||||
'fixtures.*',
|
||||
'cypress-base/cypress/**/*',
|
||||
'Stories.tsx',
|
||||
'packages/superset-ui-core/src/style/index.tsx',
|
||||
'packages/superset-ui-core/src/theme/index.tsx',
|
||||
],
|
||||
rules: {
|
||||
'theme-colors/no-literal-colors': 0,
|
||||
|
||||
@@ -17,31 +17,75 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { withJsx } from '@mihkeleidast/storybook-addon-source';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
import { AntdThemeProvider } from '../src/components/AntdThemeProvider';
|
||||
import { themeObject, css, exampleThemes } from '@superset-ui/core';
|
||||
import { combineReducers, createStore, applyMiddleware, compose } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import { Provider } from 'react-redux';
|
||||
import reducerIndex from 'spec/helpers/reducerIndex';
|
||||
import { GlobalStyles } from '../src/GlobalStyles';
|
||||
import { Global } from '@emotion/react';
|
||||
import { App, Layout, Space, Content } from 'antd';
|
||||
|
||||
import 'src/theme.ts';
|
||||
import './storybook.css';
|
||||
|
||||
export const GlobalStylesOverrides = () => (
|
||||
<Global
|
||||
styles={css`
|
||||
html,
|
||||
body,
|
||||
#storybook-root {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
min-height: 100vh !important;
|
||||
}
|
||||
|
||||
.ant-app {
|
||||
min-height: 100vh !important;
|
||||
}
|
||||
`}
|
||||
/>
|
||||
);
|
||||
|
||||
const store = createStore(
|
||||
combineReducers(reducerIndex),
|
||||
{},
|
||||
compose(applyMiddleware(thunk)),
|
||||
);
|
||||
|
||||
const themeDecorator = Story => (
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
<AntdThemeProvider>
|
||||
<GlobalStyles />
|
||||
<Story />
|
||||
</AntdThemeProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
export const globalTypes = {
|
||||
theme: {
|
||||
name: 'Theme',
|
||||
description: 'Global theme for components',
|
||||
defaultValue: 'superset',
|
||||
toolbar: {
|
||||
icon: 'paintbrush',
|
||||
items: Object.keys(exampleThemes),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const themeDecorator = (Story, context) => {
|
||||
const themeKey = context.globals.theme || 'superset';
|
||||
themeObject.setConfig(exampleThemes[themeKey]);
|
||||
|
||||
return (
|
||||
<themeObject.SupersetThemeProvider>
|
||||
<App>
|
||||
<GlobalStylesOverrides />
|
||||
<Layout
|
||||
style={{
|
||||
minHeight: '100vh',
|
||||
width: '100%',
|
||||
padding: 24,
|
||||
backgroundColor: themeObject.theme.colorBgBase,
|
||||
}}
|
||||
>
|
||||
<Story {...context} />
|
||||
</Layout>
|
||||
</App>
|
||||
</themeObject.SupersetThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const providerDecorator = Story => (
|
||||
<Provider store={store}>
|
||||
@@ -81,5 +125,5 @@ export const parameters = {
|
||||
],
|
||||
},
|
||||
},
|
||||
controls: { expanded: true, sort: 'alpha' },
|
||||
controls: { expanded: true, sort: 'alpha', disableSaveFromUI: true },
|
||||
};
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
body {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -16,19 +16,22 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import { supersetTheme } from '@superset-ui/core';
|
||||
import { NULL_DISPLAY } from 'src/constants';
|
||||
import NullCell from '.';
|
||||
|
||||
test('renders null value', async () => {
|
||||
render(<NullCell />);
|
||||
expect(screen.getByText(NULL_DISPLAY)).toBeInTheDocument();
|
||||
});
|
||||
import { REGISTER } from 'cypress/utils/urls';
|
||||
|
||||
test('renders with gray font', async () => {
|
||||
render(<NullCell />);
|
||||
expect(screen.getByText(NULL_DISPLAY)).toHaveStyle(
|
||||
`color: ${supersetTheme.colors.grayscale.light1}`,
|
||||
);
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -35,12 +35,12 @@ function orderAlphabetical() {
|
||||
}
|
||||
|
||||
function openProperties() {
|
||||
cy.get('[aria-label="more"]').eq(1).click();
|
||||
cy.get('[aria-label="more"]').eq(0).click();
|
||||
cy.getBySel('chart-list-edit-option').click();
|
||||
}
|
||||
|
||||
function openMenu() {
|
||||
cy.get('[aria-label="more"]').eq(1).click();
|
||||
cy.get('[aria-label="more"]').eq(0).click();
|
||||
}
|
||||
|
||||
function confirmDelete() {
|
||||
@@ -81,12 +81,13 @@ describe('Charts list', () => {
|
||||
cy.wait('@get');
|
||||
});
|
||||
|
||||
it.only('should show the newly added dashboards in a tooltip', () => {
|
||||
it('should show the newly added dashboards in a tooltip', () => {
|
||||
interceptDashboardGet();
|
||||
visitSampleChartFromList('1 - Sample chart');
|
||||
saveChartToDashboard('1 - Sample dashboard');
|
||||
saveChartToDashboard('2 - Sample dashboard');
|
||||
saveChartToDashboard('3 - Sample dashboard');
|
||||
saveChartToDashboard('1 - Sample chart', '1 - Sample dashboard');
|
||||
saveChartToDashboard('1 - Sample chart', '2 - Sample dashboard');
|
||||
saveChartToDashboard('1 - Sample chart', '3 - Sample dashboard');
|
||||
saveChartToDashboard('1 - Sample chart', '4 - Sample dashboard');
|
||||
visitChartList();
|
||||
|
||||
cy.getBySel('count-crosslinks').should('be.visible');
|
||||
@@ -95,8 +96,6 @@ describe('Charts list', () => {
|
||||
|
||||
describe('list mode', () => {
|
||||
before(() => {
|
||||
cy.createSampleDashboards([0, 1, 2, 3]);
|
||||
cy.createSampleCharts([0]);
|
||||
visitChartList();
|
||||
setGridMode('list');
|
||||
});
|
||||
@@ -112,18 +111,10 @@ describe('Charts list', () => {
|
||||
cy.getBySel('sort-header').eq(7).contains('Actions');
|
||||
});
|
||||
|
||||
it('should sort correctly in list mode', () => {
|
||||
cy.getBySel('sort-header').eq(1).click();
|
||||
cy.getBySel('table-row').first().contains('Area Chart');
|
||||
cy.getBySel('sort-header').eq(1).click();
|
||||
cy.getBySel('table-row').first().contains("World's Population");
|
||||
cy.getBySel('sort-header').eq(1).click();
|
||||
});
|
||||
|
||||
it('should bulk select in list mode', () => {
|
||||
toggleBulkSelect();
|
||||
cy.get('#header-toggle-all').click();
|
||||
cy.get('[aria-label="checkbox-on"]').should('have.length', 26);
|
||||
cy.get('[aria-label="Select all"]').click();
|
||||
cy.get('input[type="checkbox"]:checked').should('have.length', 26);
|
||||
cy.getBySel('bulk-select-copy').contains('25 Selected');
|
||||
cy.getBySel('bulk-select-action')
|
||||
.should('have.length', 2)
|
||||
@@ -132,7 +123,7 @@ describe('Charts list', () => {
|
||||
expect($btns).to.contain('Export');
|
||||
});
|
||||
cy.getBySel('bulk-select-deselect-all').click();
|
||||
cy.get('[aria-label="checkbox-on"]').should('have.length', 0);
|
||||
cy.get('input[type="checkbox"]:checked').should('have.length', 0);
|
||||
cy.getBySel('bulk-select-copy').contains('0 Selected');
|
||||
cy.getBySel('bulk-select-action').should('not.exist');
|
||||
});
|
||||
@@ -164,11 +155,6 @@ describe('Charts list', () => {
|
||||
cy.getBySel('bulk-select-action').should('not.exist');
|
||||
});
|
||||
|
||||
it('should sort in card mode', () => {
|
||||
orderAlphabetical();
|
||||
cy.getBySel('styled-card').first().contains('% Rural');
|
||||
});
|
||||
|
||||
it('should preserve other filters when sorting', () => {
|
||||
cy.getBySel('styled-card').should('have.length', 25);
|
||||
setFilter('Type', 'Big Number');
|
||||
@@ -179,40 +165,12 @@ describe('Charts list', () => {
|
||||
|
||||
describe('common actions', () => {
|
||||
beforeEach(() => {
|
||||
cy.createSampleCharts([0, 1, 2, 3]);
|
||||
visitChartList();
|
||||
});
|
||||
|
||||
it('should allow to favorite/unfavorite', () => {
|
||||
cy.intercept({ url: `**/api/v1/chart/*/favorites/`, method: 'POST' }).as(
|
||||
'select',
|
||||
);
|
||||
cy.intercept({
|
||||
url: `**/api/v1/chart/*/favorites/`,
|
||||
method: 'DELETE',
|
||||
}).as('unselect');
|
||||
|
||||
setGridMode('card');
|
||||
orderAlphabetical();
|
||||
|
||||
cy.getBySel('styled-card').first().contains('% Rural');
|
||||
cy.getBySel('styled-card')
|
||||
.first()
|
||||
.find("[aria-label='favorite-unselected']")
|
||||
.click();
|
||||
cy.wait('@select');
|
||||
cy.getBySel('styled-card')
|
||||
.first()
|
||||
.find("[aria-label='favorite-selected']")
|
||||
.click();
|
||||
cy.wait('@unselect');
|
||||
cy.getBySel('styled-card')
|
||||
.first()
|
||||
.find("[aria-label='favorite-selected']")
|
||||
.should('not.exist');
|
||||
});
|
||||
|
||||
it('should bulk delete correctly', () => {
|
||||
cy.createSampleCharts([0, 1, 2, 3]);
|
||||
|
||||
interceptBulkDelete();
|
||||
toggleBulkSelect();
|
||||
|
||||
@@ -220,9 +178,10 @@ describe('Charts list', () => {
|
||||
setGridMode('card');
|
||||
orderAlphabetical();
|
||||
|
||||
cy.getBySel('styled-card').eq(1).contains('1 - Sample chart').click();
|
||||
cy.getBySel('styled-card').eq(2).contains('2 - Sample chart').click();
|
||||
cy.getBySel('bulk-select-action').eq(0).contains('Delete').click();
|
||||
cy.getBySel('skeleton-card').should('not.exist');
|
||||
cy.getBySel('styled-card').contains('1 - Sample chart').click();
|
||||
cy.getBySel('styled-card').contains('2 - Sample chart').click();
|
||||
cy.getBySel('bulk-select-action').contains('Delete').click();
|
||||
confirmDelete();
|
||||
cy.wait('@bulkDelete');
|
||||
cy.getBySel('styled-card')
|
||||
@@ -234,56 +193,71 @@ describe('Charts list', () => {
|
||||
|
||||
// bulk deletes in list-view
|
||||
setGridMode('list');
|
||||
cy.getBySel('table-row').eq(1).contains('3 - Sample chart');
|
||||
cy.getBySel('table-row').eq(2).contains('4 - Sample chart');
|
||||
cy.get('.loading').should('not.exist');
|
||||
cy.getBySel('table-row').contains('3 - Sample chart').should('exist');
|
||||
cy.getBySel('table-row').contains('4 - Sample chart').should('exist');
|
||||
cy.get('[data-test="table-row"] input[type="checkbox"]').eq(0).click();
|
||||
cy.get('[data-test="table-row"] input[type="checkbox"]').eq(1).click();
|
||||
cy.get('[data-test="table-row"] input[type="checkbox"]').eq(2).click();
|
||||
cy.getBySel('bulk-select-action').eq(0).contains('Delete').click();
|
||||
confirmDelete();
|
||||
cy.wait('@bulkDelete');
|
||||
cy.getBySel('table-row').eq(1).should('not.contain', '3 - Sample chart');
|
||||
cy.getBySel('table-row').eq(2).should('not.contain', '4 - Sample chart');
|
||||
cy.get('.loading').should('exist');
|
||||
cy.get('.loading').should('not.exist');
|
||||
cy.getBySel('table-row').eq(0).should('not.contain', '3 - Sample chart');
|
||||
cy.getBySel('table-row').eq(1).should('not.contain', '4 - Sample chart');
|
||||
});
|
||||
|
||||
it('should delete correctly', () => {
|
||||
it('should delete correctly in card mode', () => {
|
||||
cy.createSampleCharts([0, 1]);
|
||||
interceptDelete();
|
||||
|
||||
// deletes in card-view
|
||||
setGridMode('card');
|
||||
orderAlphabetical();
|
||||
|
||||
cy.getBySel('styled-card').eq(1).contains('1 - Sample chart');
|
||||
cy.getBySel('styled-card').contains('1 - Sample chart');
|
||||
openMenu();
|
||||
cy.getBySel('chart-list-delete-option').click();
|
||||
confirmDelete();
|
||||
cy.wait('@delete');
|
||||
cy.getBySel('styled-card')
|
||||
.eq(1)
|
||||
.should('not.contain', '1 - Sample chart');
|
||||
.contains('1 - Sample chart')
|
||||
.should('not.exist');
|
||||
});
|
||||
|
||||
// deletes in list-view
|
||||
setGridMode('list');
|
||||
cy.getBySel('table-row').eq(1).contains('2 - Sample chart');
|
||||
cy.getBySel('delete').eq(1).click();
|
||||
it('should delete correctly in list mode', () => {
|
||||
cy.createSampleCharts([2, 3]);
|
||||
interceptDelete();
|
||||
cy.getBySel('sort-header').contains('Name').click();
|
||||
|
||||
// Modal closes immediatly without this
|
||||
cy.wait(2000);
|
||||
|
||||
cy.getBySel('table-row').eq(0).contains('3 - Sample chart');
|
||||
cy.getBySel('delete').eq(0).click();
|
||||
confirmDelete();
|
||||
cy.wait('@delete');
|
||||
cy.getBySel('table-row').eq(1).should('not.contain', '2 - Sample chart');
|
||||
cy.get('.loading').should('exist');
|
||||
cy.get('.loading').should('not.exist');
|
||||
cy.getBySel('table-row').eq(0).should('not.contain', '3 - Sample chart');
|
||||
});
|
||||
|
||||
it('should edit correctly', () => {
|
||||
cy.createSampleCharts([0]);
|
||||
interceptUpdate();
|
||||
|
||||
// edits in card-view
|
||||
setGridMode('card');
|
||||
orderAlphabetical();
|
||||
cy.getBySel('styled-card').eq(1).contains('1 - Sample chart');
|
||||
cy.getBySel('skeleton-card').should('not.exist');
|
||||
cy.getBySel('styled-card').eq(0).contains('1 - Sample chart');
|
||||
|
||||
// change title
|
||||
openProperties();
|
||||
cy.getBySel('properties-modal-name-input').type(' | EDITED');
|
||||
cy.get('button:contains("Save")').click();
|
||||
cy.wait('@update');
|
||||
cy.getBySel('styled-card').eq(1).contains('1 - Sample chart | EDITED');
|
||||
cy.getBySel('styled-card').eq(0).contains('1 - Sample chart | EDITED');
|
||||
|
||||
// edits in list-view
|
||||
setGridMode('list');
|
||||
|
||||
@@ -47,12 +47,12 @@ describe.skip('Dashboard top-level controls', () => {
|
||||
// Solution: pause the network before clicking, assert, then unpause network.
|
||||
cy.get('[data-test="refresh-chart-menu-item"]').should(
|
||||
'have.class',
|
||||
'antd5-dropdown-menu-item-disabled',
|
||||
'ant-dropdown-menu-item-disabled',
|
||||
);
|
||||
waitForChartLoad(mapSpec);
|
||||
cy.get('[data-test="refresh-chart-menu-item"]').should(
|
||||
'not.have.class',
|
||||
'antd5-dropdown-menu-item-disabled',
|
||||
'ant-dropdown-menu-item-disabled',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -65,7 +65,7 @@ describe.skip('Dashboard top-level controls', () => {
|
||||
cy.get('[aria-label="ellipsis"]').click();
|
||||
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
|
||||
'not.have.class',
|
||||
'antd5-dropdown-menu-item-disabled',
|
||||
'ant-dropdown-menu-item-disabled',
|
||||
);
|
||||
|
||||
cy.get('[data-test="refresh-dashboard-menu-item"]').click({
|
||||
@@ -73,7 +73,7 @@ describe.skip('Dashboard top-level controls', () => {
|
||||
});
|
||||
cy.get('[data-test="refresh-dashboard-menu-item"]').should(
|
||||
'have.class',
|
||||
'antd5-dropdown-menu-item-disabled',
|
||||
'ant-dropdown-menu-item-disabled',
|
||||
);
|
||||
|
||||
// wait all charts force refreshed.
|
||||
@@ -94,7 +94,7 @@ describe.skip('Dashboard top-level controls', () => {
|
||||
cy.get('[aria-label="ellipsis"]').click();
|
||||
cy.get('[data-test="refresh-dashboard-menu-item"]').and(
|
||||
'not.have.class',
|
||||
'antd5-dropdown-menu-item-disabled',
|
||||
'ant-dropdown-menu-item-disabled',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,6 +62,6 @@ describe('Dashboard actions', () => {
|
||||
// Verify the color of the outlined star (gray)
|
||||
cy.get('@starIconOutlinedAfter')
|
||||
.should('have.css', 'color')
|
||||
.and('eq', 'rgb(178, 178, 178)');
|
||||
.and('eq', 'rgb(133, 133, 133)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,14 +54,14 @@ const drillBy = (targetDrillByColumn: string, isLegacy = false) => {
|
||||
interceptV1ChartData();
|
||||
}
|
||||
|
||||
cy.get('.antd5-dropdown:not(.antd5-dropdown-hidden)')
|
||||
cy.get('.ant-dropdown:not(.ant-dropdown-hidden)')
|
||||
.should('be.visible')
|
||||
.find("[role='menu'] [role='menuitem']")
|
||||
.contains(/^Drill by$/)
|
||||
.trigger('mouseover', { force: true });
|
||||
|
||||
cy.get(
|
||||
'.antd5-dropdown-menu-submenu:not(.antd5-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
|
||||
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-by-submenu"]',
|
||||
)
|
||||
.should('be.visible')
|
||||
.find('[role="menuitem"]')
|
||||
|
||||
@@ -34,8 +34,8 @@ function openModalFromMenu(chartType: string) {
|
||||
cy.get(
|
||||
`[data-test-viz-type='${chartType}'] [aria-label='More Options']`,
|
||||
).click();
|
||||
cy.get('.antd5-dropdown')
|
||||
.not('.antd5-dropdown-hidden')
|
||||
cy.get('.ant-dropdown')
|
||||
.not('.ant-dropdown-hidden')
|
||||
.find("[role='menu'] [role='menuitem']")
|
||||
.eq(5)
|
||||
.should('contain', 'Drill to detail')
|
||||
@@ -46,8 +46,8 @@ function openModalFromMenu(chartType: string) {
|
||||
function drillToDetail(targetMenuItem: string) {
|
||||
interceptSamples();
|
||||
|
||||
cy.get('.antd5-dropdown')
|
||||
.not('.antd5-dropdown-hidden')
|
||||
cy.get('.ant-dropdown')
|
||||
.not('.ant-dropdown-hidden')
|
||||
.first()
|
||||
.find("[role='menu'] [role='menuitem']")
|
||||
.contains(new RegExp(`^${targetMenuItem}$`))
|
||||
@@ -61,14 +61,14 @@ function drillToDetail(targetMenuItem: string) {
|
||||
const drillToDetailBy = (targetDrill: string) => {
|
||||
interceptSamples();
|
||||
|
||||
cy.get('.antd5-dropdown:not(.antd5-dropdown-hidden)')
|
||||
cy.get('.ant-dropdown:not(.ant-dropdown-hidden)')
|
||||
.should('be.visible')
|
||||
.find("[role='menu'] [role='menuitem']")
|
||||
.contains(/^Drill to detail by$/)
|
||||
.trigger('mouseover', { force: true });
|
||||
|
||||
cy.get(
|
||||
'.antd5-dropdown-menu-submenu:not(.antd5-dropdown-menu-submenu-hidden) [data-test="drill-to-detail-by-submenu"]',
|
||||
'.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden) [data-test="drill-to-detail-by-submenu"]',
|
||||
)
|
||||
.should('be.visible')
|
||||
.find('[role="menuitem"]')
|
||||
@@ -121,7 +121,10 @@ function testTimeChart(vizType: string) {
|
||||
});
|
||||
}
|
||||
|
||||
describe('Drill to detail modal', () => {
|
||||
// TODO fix this test, it has issues with autoscrolling and the locked title
|
||||
// flakes intricately when the righClick is obstructed by the title.
|
||||
// Tried many option around scrollIntoView, force, etc. but no luck.
|
||||
describe.skip('Drill to detail modal', () => {
|
||||
beforeEach(() => {
|
||||
closeModal();
|
||||
});
|
||||
@@ -463,7 +466,7 @@ describe('Drill to detail modal', () => {
|
||||
});
|
||||
|
||||
// close the filter and test that data was reloaded
|
||||
cy.getBySel('filter-col').find("[aria-label='close']").click();
|
||||
cy.getBySel('filter-col').find("[aria-label='Close']").click();
|
||||
cy.wait('@samples');
|
||||
cy.getBySel('row-count-label').should('contain', '75.7k rows');
|
||||
cy.get('.ant-pagination-item-active').should('contain', '1');
|
||||
|
||||
@@ -17,7 +17,12 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { SAMPLE_DASHBOARD_1, TABBED_DASHBOARD } from 'cypress/utils/urls';
|
||||
import { drag, resize, waitForChartLoad } from 'cypress/utils';
|
||||
import {
|
||||
drag,
|
||||
resize,
|
||||
setSelectSearchInput,
|
||||
waitForChartLoad,
|
||||
} from 'cypress/utils';
|
||||
import { edit } from 'brace';
|
||||
import {
|
||||
interceptExploreUpdate,
|
||||
@@ -34,21 +39,12 @@ function editDashboard() {
|
||||
cy.getBySel('edit-dashboard-button').click();
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
cy.getBySel('properties-modal-cancel-button').click({ force: true });
|
||||
}
|
||||
|
||||
function openProperties() {
|
||||
cy.get('body').then($body => {
|
||||
if ($body.find('[data-test="properties-modal-cancel-button"]').length) {
|
||||
closeModal();
|
||||
}
|
||||
cy.getBySel('actions-trigger').click({ force: true });
|
||||
cy.getBySel('header-actions-menu')
|
||||
.contains('Edit properties')
|
||||
.click({ force: true });
|
||||
cy.get('.antd5-modal-body').should('be.visible');
|
||||
});
|
||||
cy.getBySel('actions-trigger').click({ force: true });
|
||||
cy.getBySel('header-actions-menu')
|
||||
.contains('Edit properties')
|
||||
.click({ force: true });
|
||||
cy.get('.ant-modal-body').should('be.visible');
|
||||
}
|
||||
|
||||
function assertMetadata(text: string) {
|
||||
@@ -65,7 +61,7 @@ function assertMetadata(text: string) {
|
||||
}
|
||||
|
||||
function openAdvancedProperties() {
|
||||
cy.get('.antd5-modal-body')
|
||||
cy.get('.ant-modal-body')
|
||||
.contains('Advanced')
|
||||
.should('be.visible')
|
||||
.click({ force: true });
|
||||
@@ -150,12 +146,10 @@ function selectColorScheme(
|
||||
target = 'dashboard-edit-properties-form',
|
||||
) {
|
||||
cy.get(`[data-test="${target}"] input[aria-label="Select color scheme"]`)
|
||||
.first()
|
||||
.should('exist')
|
||||
.then($input => {
|
||||
cy.wrap($input).click({ force: true });
|
||||
cy.wrap($input).type(color.slice(0, 5), { force: true });
|
||||
setSelectSearchInput($input, color.slice(0, 5));
|
||||
});
|
||||
cy.getBySel(color).click({ force: true });
|
||||
}
|
||||
|
||||
function saveAndGo(dashboard = 'Tabbed Dashboard') {
|
||||
@@ -1095,7 +1089,7 @@ describe('Dashboard edit', () => {
|
||||
cy.allowConsoleErrors(['Error: A valid color scheme is required']);
|
||||
writeMetadata('{"color_scheme":"wrongcolorscheme"}');
|
||||
applyChanges();
|
||||
cy.get('.antd5-modal-body')
|
||||
cy.get('.ant-modal-body')
|
||||
.contains('A valid color scheme is required')
|
||||
.should('be.visible');
|
||||
});
|
||||
@@ -1130,7 +1124,7 @@ describe('Dashboard edit', () => {
|
||||
|
||||
it('should filter charts', () => {
|
||||
interceptCharts();
|
||||
cy.get('[role="checkbox"]').click();
|
||||
cy.get('input[type="checkbox"]').click();
|
||||
cy.getBySel('dashboard-charts-filter-search-input').type('Unicode');
|
||||
cy.wait('@filtering');
|
||||
cy.getBySel('chart-card')
|
||||
@@ -1140,8 +1134,8 @@ describe('Dashboard edit', () => {
|
||||
});
|
||||
|
||||
// TODO fix this test! This was the #1 flaky test as of 4/21/23 according to cypress dashboard.
|
||||
xit('should disable the Save button when undoing', () => {
|
||||
cy.get('[role="checkbox"]').click();
|
||||
it.skip('should disable the Save button when undoing', () => {
|
||||
cy.get('input[type="checkbox"]').click();
|
||||
dragComponent('Unicode Cloud', 'card-title', false);
|
||||
cy.getBySel('header-save-button').should('be.enabled');
|
||||
discardChanges();
|
||||
@@ -1155,13 +1149,13 @@ describe('Dashboard edit', () => {
|
||||
});
|
||||
|
||||
it('should add charts', () => {
|
||||
cy.get('[role="checkbox"]').click();
|
||||
cy.get('input[type="checkbox"]').click();
|
||||
dragComponent();
|
||||
cy.getBySel('dashboard-component-chart-holder').should('have.length', 1);
|
||||
});
|
||||
|
||||
it.skip('should remove added charts', () => {
|
||||
cy.get('[role="checkbox"]').click();
|
||||
cy.get('input[type="checkbox"]').click();
|
||||
dragComponent('Unicode Cloud');
|
||||
cy.getBySel('dashboard-component-chart-holder').should('have.length', 1);
|
||||
cy.getBySel('dashboard-delete-component-button').click();
|
||||
@@ -1204,7 +1198,7 @@ describe('Dashboard edit', () => {
|
||||
});
|
||||
|
||||
it('should save', () => {
|
||||
cy.get('[role="checkbox"]').click();
|
||||
cy.get('input[type="checkbox"]').click();
|
||||
dragComponent();
|
||||
cy.getBySel('header-save-button').should('be.enabled');
|
||||
saveChanges();
|
||||
|
||||
@@ -57,16 +57,16 @@ function setFilterBarOrientation(orientation: 'vertical' | 'horizontal') {
|
||||
.trigger('mouseover');
|
||||
|
||||
if (orientation === 'vertical') {
|
||||
cy.get('.antd5-dropdown-menu-item-selected')
|
||||
cy.get('.ant-dropdown-menu-item-selected')
|
||||
.contains('Horizontal (Top)')
|
||||
.should('exist');
|
||||
cy.get('.antd5-dropdown-menu-item').contains('Vertical (Left)').click();
|
||||
cy.get('.ant-dropdown-menu-item').contains('Vertical (Left)').click();
|
||||
cy.getBySel('dashboard-filters-panel').should('exist');
|
||||
} else {
|
||||
cy.get('.antd5-dropdown-menu-item-selected')
|
||||
cy.get('.ant-dropdown-menu-item-selected')
|
||||
.contains('Vertical (Left)')
|
||||
.should('exist');
|
||||
cy.get('.antd5-dropdown-menu-item').contains('Horizontal (Top)').click();
|
||||
cy.get('.ant-dropdown-menu-item').contains('Horizontal (Top)').click();
|
||||
cy.getBySel('loading-indicator').should('exist');
|
||||
cy.getBySel('filter-bar').should('exist');
|
||||
cy.getBySel('dashboard-filters-panel').should('not.exist');
|
||||
@@ -138,7 +138,7 @@ describe('Horizontal FilterBar', () => {
|
||||
cy.getBySel('dropdown-container-btn').should('not.exist');
|
||||
});
|
||||
|
||||
it('should show "more filters" and scroll', () => {
|
||||
it.only('should show "more filters" and scroll', () => {
|
||||
prepareDashboardFilters([
|
||||
{ name: 'test_1', column: 'country_name', datasetId: 2 },
|
||||
{ name: 'test_2', column: 'country_code', datasetId: 2 },
|
||||
@@ -157,11 +157,11 @@ describe('Horizontal FilterBar', () => {
|
||||
cy.get('.filter-item-wrapper').should('have.length', 3);
|
||||
openMoreFilters();
|
||||
cy.getBySel('form-item-value').should('have.length', 12);
|
||||
cy.getBySel('filter-control-name').contains('test_10').should('be.visible');
|
||||
cy.getBySel('filter-control-name').contains('test_3').should('be.visible');
|
||||
cy.getBySel('filter-control-name')
|
||||
.contains('test_12')
|
||||
.should('not.be.visible');
|
||||
cy.get('.antd5-popover-inner').scrollTo('bottom');
|
||||
cy.getBySel('filter-control-name').contains('test_12').scrollIntoView();
|
||||
cy.getBySel('filter-control-name').contains('test_12').should('be.visible');
|
||||
});
|
||||
|
||||
@@ -197,7 +197,7 @@ describe('Horizontal FilterBar', () => {
|
||||
applyNativeFilterValueWithIndex(8, testItems.filterDefaultValue);
|
||||
cy.get(nativeFilters.applyFilter).click({ force: true });
|
||||
cy.wait('@chart');
|
||||
cy.get('.antd5-scroll-number.antd5-badge-count').should(
|
||||
cy.get('.ant-scroll-number.ant-badge-count').should(
|
||||
'have.attr',
|
||||
'title',
|
||||
'1',
|
||||
|
||||
@@ -199,14 +199,16 @@ describe('Native filters', () => {
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
cy.get('[data-test="range-filter-from-input"]').type('{selectall}5');
|
||||
cy.get('[data-test="range-filter-from-input"]').type('{selectall}40');
|
||||
|
||||
cy.get('[data-test="range-filter-to-input"]')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
cy.get('[data-test="range-filter-to-input"]').type('{selectall}50');
|
||||
cy.get(nativeFilters.applyFilter).click();
|
||||
cy.get(nativeFilters.applyFilter).click({
|
||||
force: true,
|
||||
});
|
||||
|
||||
// Assert that the URL contains 'native_filters'
|
||||
cy.url().then(u => {
|
||||
@@ -215,7 +217,7 @@ describe('Native filters', () => {
|
||||
|
||||
cy.get('[data-test="range-filter-from-input"]')
|
||||
.invoke('val')
|
||||
.should('equal', '5');
|
||||
.should('equal', '40');
|
||||
|
||||
// Assert that the "To" input has the correct value
|
||||
cy.get('[data-test="range-filter-to-input"]')
|
||||
|
||||
@@ -293,7 +293,11 @@ describe('Native filters', () => {
|
||||
|
||||
it('Verify setting options and tooltips for value filter', () => {
|
||||
enterNativeFilterEditModal(false);
|
||||
cy.contains('Filter value is required').should('be.visible').click();
|
||||
cy.contains('Filter value is required').scrollIntoView();
|
||||
|
||||
cy.contains('Filter value is required').should('be.visible').click({
|
||||
force: true,
|
||||
});
|
||||
checkNativeFilterTooltip(0, nativeFilterTooltips.preFilter);
|
||||
checkNativeFilterTooltip(1, nativeFilterTooltips.defaultValue);
|
||||
cy.get(nativeFilters.modal.container).should('be.visible');
|
||||
|
||||
@@ -18,7 +18,11 @@
|
||||
*/
|
||||
|
||||
import { dashboardView, nativeFilters } from 'cypress/support/directories';
|
||||
import { ChartSpec, waitForChartLoad } from 'cypress/utils';
|
||||
import {
|
||||
ChartSpec,
|
||||
setSelectSearchInput,
|
||||
waitForChartLoad,
|
||||
} from 'cypress/utils';
|
||||
|
||||
export const WORLD_HEALTH_CHARTS = [
|
||||
{ name: '% Rural', viz: 'world_map' },
|
||||
@@ -264,10 +268,11 @@ export function fillNativeFilterForm(
|
||||
dataset?: string,
|
||||
filterColumn?: string,
|
||||
) {
|
||||
cy.get(nativeFilters.filtersPanel.filterTypeInput)
|
||||
.find(nativeFilters.filtersPanel.filterTypeItem)
|
||||
.click({ multiple: true, force: true });
|
||||
cy.get(`[label="${type}"]`).click({ multiple: true, force: true });
|
||||
cy.get(nativeFilters.filtersPanel.filterTypeInput).within(() => {
|
||||
cy.get('input').then($input => {
|
||||
setSelectSearchInput($input, type);
|
||||
});
|
||||
});
|
||||
cy.get(nativeFilters.modal.container)
|
||||
.find(nativeFilters.filtersPanel.filterName)
|
||||
.last()
|
||||
@@ -280,31 +285,23 @@ export function fillNativeFilterForm(
|
||||
.find(nativeFilters.filtersPanel.filterName)
|
||||
.last()
|
||||
.type(name, { scrollBehavior: false, force: true });
|
||||
|
||||
if (dataset) {
|
||||
cy.get(nativeFilters.modal.container)
|
||||
.find(nativeFilters.filtersPanel.datasetName)
|
||||
.last()
|
||||
.click({ force: true, scrollBehavior: false });
|
||||
cy.get(nativeFilters.modal.container)
|
||||
.find(nativeFilters.filtersPanel.datasetName)
|
||||
.type(`${dataset}`, { scrollBehavior: false });
|
||||
cy.get('div[aria-label="Dataset"]').within(() => {
|
||||
cy.get('input').then($input => {
|
||||
setSelectSearchInput($input, dataset, true);
|
||||
});
|
||||
});
|
||||
cy.get(nativeFilters.silentLoading).should('not.exist');
|
||||
cy.get(`[label="${dataset}"]`).click({ multiple: true, force: true });
|
||||
}
|
||||
cy.get(nativeFilters.silentLoading).should('not.exist');
|
||||
|
||||
if (filterColumn) {
|
||||
cy.get(nativeFilters.filtersPanel.filterInfoInput)
|
||||
.last()
|
||||
.click({ force: true });
|
||||
cy.get(nativeFilters.filtersPanel.filterInfoInput)
|
||||
.last()
|
||||
.type(filterColumn);
|
||||
cy.get(nativeFilters.filtersPanel.inputDropdown)
|
||||
.should('be.visible', { timeout: 20000 })
|
||||
.last()
|
||||
.click();
|
||||
cy.get('div[aria-label="Column select"]').within(() => {
|
||||
cy.get('input').then($input => {
|
||||
setSelectSearchInput($input, filterColumn, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
cy.get(nativeFilters.silentLoading).should('not.exist');
|
||||
}
|
||||
|
||||
/** ************************************************************************
|
||||
@@ -442,6 +439,9 @@ export function checkNativeFilterTooltip(index: number, value: string) {
|
||||
.eq(index)
|
||||
.trigger('mouseover');
|
||||
cy.contains(`${value}`);
|
||||
cy.get(nativeFilters.filterConfigurationSections.infoTooltip)
|
||||
.eq(index)
|
||||
.trigger('mouseout');
|
||||
}
|
||||
|
||||
/** ************************************************************************
|
||||
@@ -455,20 +455,20 @@ export function applyAdvancedTimeRangeFilterOnDashboard(
|
||||
startRange?: string,
|
||||
endRange?: string,
|
||||
) {
|
||||
cy.get('.control-label').contains('RANGE TYPE').should('be.visible');
|
||||
cy.get('.antd5-popover-content .ant-select-selector')
|
||||
cy.get('.control-label').contains('Range type').should('be.visible');
|
||||
cy.get('.ant-popover-content .ant-select-selector')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
cy.get(`[label="Advanced"]`).should('be.visible').click();
|
||||
cy.get('.section-title').contains('Advanced Time Range').should('be.visible');
|
||||
if (startRange) {
|
||||
cy.get('.antd5-popover-inner-content')
|
||||
cy.get('.ant-popover-inner-content')
|
||||
.find('[class^=ant-input]')
|
||||
.first()
|
||||
.type(`${startRange}`);
|
||||
}
|
||||
if (endRange) {
|
||||
cy.get('.antd5-popover-inner-content')
|
||||
cy.get('.ant-popover-inner-content')
|
||||
.find('[class^=ant-input]')
|
||||
.last()
|
||||
.type(`${endRange}`);
|
||||
|
||||
@@ -79,16 +79,20 @@ describe('Dashboards list', () => {
|
||||
|
||||
it('should sort correctly in list mode', () => {
|
||||
cy.getBySel('sort-header').eq(1).click();
|
||||
cy.getBySel('loading-indicator').should('not.exist');
|
||||
cy.getBySel('table-row').first().contains('Supported Charts Dashboard');
|
||||
cy.getBySel('sort-header').eq(1).click();
|
||||
cy.getBySel('loading-indicator').should('not.exist');
|
||||
cy.getBySel('table-row').first().contains("World Bank's Data");
|
||||
cy.getBySel('sort-header').eq(1).click();
|
||||
});
|
||||
|
||||
it('should bulk select in list mode', () => {
|
||||
toggleBulkSelect();
|
||||
cy.get('#header-toggle-all').click();
|
||||
cy.get('[aria-label="checkbox-on"]').should('have.length', 6);
|
||||
cy.get('[aria-label="Select all"]').click();
|
||||
cy.get('.ant-checkbox-input')
|
||||
.should('be.checked')
|
||||
.should('have.length', 6);
|
||||
cy.getBySel('bulk-select-copy').contains('5 Selected');
|
||||
cy.getBySel('bulk-select-action')
|
||||
.should('have.length', 2)
|
||||
@@ -97,7 +101,7 @@ describe('Dashboards list', () => {
|
||||
expect($btns).to.contain('Export');
|
||||
});
|
||||
cy.getBySel('bulk-select-deselect-all').click();
|
||||
cy.get('[aria-label="checkbox-on"]').should('have.length', 0);
|
||||
cy.get('input[type="checkbox"]:checked').should('have.length', 0);
|
||||
cy.getBySel('bulk-select-copy').contains('0 Selected');
|
||||
cy.getBySel('bulk-select-action').should('not.exist');
|
||||
});
|
||||
@@ -195,6 +199,8 @@ describe('Dashboards list', () => {
|
||||
cy.get('[data-test="table-row"] input[type="checkbox"]').eq(1).click();
|
||||
cy.getBySel('bulk-select-action').eq(0).contains('Delete').click();
|
||||
confirmDelete(true);
|
||||
cy.getBySel('loading-indicator').should('exist');
|
||||
cy.getBySel('loading-indicator').should('not.exist');
|
||||
cy.getBySel('table-row')
|
||||
.eq(0)
|
||||
.should('not.contain', '3 - Sample dashboard');
|
||||
|
||||
@@ -30,36 +30,36 @@ import {
|
||||
const SAMPLE_DASHBOARDS_INDEXES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
|
||||
function openDashboardsAddedTo() {
|
||||
cy.getBySel('actions-trigger').click();
|
||||
cy.get('.antd5-dropdown-menu-submenu-title')
|
||||
cy.getBySel('actions-trigger').should('be.visible').click();
|
||||
cy.get('.ant-dropdown-menu-submenu-title')
|
||||
.contains('On dashboards')
|
||||
.trigger('mouseover', { force: true });
|
||||
}
|
||||
|
||||
function closeDashboardsAddedTo() {
|
||||
cy.get('.antd5-dropdown-menu-submenu-title')
|
||||
cy.get('.ant-dropdown-menu-submenu-title')
|
||||
.contains('On dashboards')
|
||||
.trigger('mouseout', { force: true });
|
||||
cy.getBySel('actions-trigger').click();
|
||||
}
|
||||
|
||||
function verifyDashboardsSubmenuItem(dashboardName) {
|
||||
cy.get('.antd5-dropdown-menu-submenu-popup').contains(dashboardName);
|
||||
cy.get('.ant-dropdown-menu-submenu-popup').contains(dashboardName);
|
||||
closeDashboardsAddedTo();
|
||||
}
|
||||
|
||||
function verifyDashboardSearch() {
|
||||
openDashboardsAddedTo();
|
||||
cy.get('.antd5-dropdown-menu-submenu-popup').trigger('mouseover');
|
||||
cy.get('.antd5-dropdown-menu-submenu-popup')
|
||||
cy.get('.ant-dropdown-menu-submenu-popup').trigger('mouseover');
|
||||
cy.get('.ant-dropdown-menu-submenu-popup')
|
||||
.find('input[placeholder="Search"]')
|
||||
.type('1');
|
||||
cy.get('.antd5-dropdown-menu-submenu-popup').contains('1 - Sample dashboard');
|
||||
cy.get('.antd5-dropdown-menu-submenu-popup')
|
||||
cy.get('.ant-dropdown-menu-submenu-popup').contains('1 - Sample dashboard');
|
||||
cy.get('.ant-dropdown-menu-submenu-popup')
|
||||
.find('input[placeholder="Search"]')
|
||||
.type('Blahblah');
|
||||
cy.get('.antd5-dropdown-menu-submenu-popup').contains('No results found');
|
||||
cy.get('.antd5-dropdown-menu-submenu-popup')
|
||||
cy.get('.ant-dropdown-menu-submenu-popup').contains('No results found');
|
||||
cy.get('.ant-dropdown-menu-submenu-popup')
|
||||
.find('[aria-label="close-circle"]')
|
||||
.click();
|
||||
closeDashboardsAddedTo();
|
||||
@@ -68,8 +68,8 @@ function verifyDashboardSearch() {
|
||||
function verifyDashboardLink() {
|
||||
interceptDashboardGet();
|
||||
openDashboardsAddedTo();
|
||||
cy.get('.antd5-dropdown-menu-submenu-popup').trigger('mouseover');
|
||||
cy.get('.antd5-dropdown-menu-submenu-popup a')
|
||||
cy.get('.ant-dropdown-menu-submenu-popup').trigger('mouseover');
|
||||
cy.get('.ant-dropdown-menu-submenu-popup a')
|
||||
.first()
|
||||
.invoke('removeAttr', 'target')
|
||||
.click();
|
||||
@@ -80,8 +80,8 @@ function verifyMetabar(text) {
|
||||
cy.getBySel('metadata-bar').contains(text);
|
||||
}
|
||||
|
||||
function saveAndVerifyDashboard(number) {
|
||||
saveChartToDashboard(`${number} - Sample dashboard`);
|
||||
function saveAndVerifyDashboard(chartName, number) {
|
||||
saveChartToDashboard(chartName, `${number} - Sample dashboard`);
|
||||
verifyMetabar(
|
||||
number > 1 ? `Added to ${number} dashboards` : 'Added to 1 dashboard',
|
||||
);
|
||||
@@ -106,17 +106,17 @@ describe('Cross-referenced dashboards', () => {
|
||||
openDashboardsAddedTo();
|
||||
verifyDashboardsSubmenuItem('None');
|
||||
|
||||
saveAndVerifyDashboard('1');
|
||||
saveAndVerifyDashboard('2');
|
||||
saveAndVerifyDashboard('3');
|
||||
saveAndVerifyDashboard('4');
|
||||
saveAndVerifyDashboard('5');
|
||||
saveAndVerifyDashboard('6');
|
||||
saveAndVerifyDashboard('7');
|
||||
saveAndVerifyDashboard('8');
|
||||
saveAndVerifyDashboard('9');
|
||||
saveAndVerifyDashboard('10');
|
||||
saveAndVerifyDashboard('11');
|
||||
saveAndVerifyDashboard('1 - Sample chart', '1');
|
||||
saveAndVerifyDashboard('1 - Sample chart', '2');
|
||||
saveAndVerifyDashboard('1 - Sample chart', '3');
|
||||
saveAndVerifyDashboard('1 - Sample chart', '4');
|
||||
saveAndVerifyDashboard('1 - Sample chart', '5');
|
||||
saveAndVerifyDashboard('1 - Sample chart', '6');
|
||||
saveAndVerifyDashboard('1 - Sample chart', '7');
|
||||
saveAndVerifyDashboard('1 - Sample chart', '8');
|
||||
saveAndVerifyDashboard('1 - Sample chart', '9');
|
||||
saveAndVerifyDashboard('1 - Sample chart', '10');
|
||||
saveAndVerifyDashboard('1 - Sample chart', '11');
|
||||
|
||||
verifyDashboardSearch();
|
||||
verifyDashboardLink();
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// ***********************************************
|
||||
// Tests for setting controls in the UI
|
||||
// ***********************************************
|
||||
import { interceptChart } from 'cypress/utils';
|
||||
import { interceptChart, setSelectSearchInput } from 'cypress/utils';
|
||||
|
||||
describe('Datasource control', () => {
|
||||
const newMetricName = `abc${Date.now()}`;
|
||||
@@ -40,44 +40,44 @@ describe('Datasource control', () => {
|
||||
// create new metric
|
||||
cy.get('[data-test="crud-add-table-item"]', { timeout: 10000 }).click();
|
||||
cy.wait(1000);
|
||||
cy.get(
|
||||
'[data-test="table-content-rows"] [data-test="editable-title-input"]',
|
||||
)
|
||||
cy.get('.ant-table-body [data-test="textarea-editable-title-input"]')
|
||||
.first()
|
||||
.click();
|
||||
|
||||
cy.get(
|
||||
'[data-test="table-content-rows"] [data-test="editable-title-input"]',
|
||||
)
|
||||
cy.get('.ant-table-body [data-test="textarea-editable-title-input"]')
|
||||
.first()
|
||||
.focus();
|
||||
cy.focused().clear({ force: true });
|
||||
cy.focused().type(`${newMetricName}{enter}`, { force: true });
|
||||
|
||||
cy.get('[data-test="datasource-modal-save"]').click();
|
||||
cy.get('.antd5-modal-confirm-btns button').contains('OK').click();
|
||||
cy.get('.ant-modal-confirm-btns button').contains('OK').click();
|
||||
// select new metric
|
||||
cy.get('[data-test=metrics]')
|
||||
.contains('Drop columns/metrics here or click')
|
||||
.click();
|
||||
|
||||
cy.get('input[aria-label="Select saved metrics"]').type(
|
||||
`${newMetricName}{enter}`,
|
||||
);
|
||||
cy.get('input[aria-label="Select saved metrics"]')
|
||||
.should('exist')
|
||||
.then($input => {
|
||||
setSelectSearchInput($input, newMetricName);
|
||||
});
|
||||
|
||||
// delete metric
|
||||
cy.get('[data-test="datasource-menu-trigger"]').click();
|
||||
cy.get('[data-test="edit-dataset"]').click();
|
||||
cy.get('.antd5-modal-content').within(() => {
|
||||
cy.get('.ant-modal-content').within(() => {
|
||||
cy.get('[data-test="collection-tab-Metrics"]')
|
||||
.contains('Metrics')
|
||||
.click();
|
||||
});
|
||||
cy.get(`input[value="${newMetricName}"]`)
|
||||
cy.get(`[data-test="textarea-editable-title-input"]`)
|
||||
.contains(newMetricName)
|
||||
.closest('tr')
|
||||
.find('[data-test="crud-delete-icon"]')
|
||||
.click();
|
||||
cy.get('[data-test="datasource-modal-save"]').click();
|
||||
cy.get('.antd5-modal-confirm-btns button').contains('OK').click();
|
||||
cy.get('.ant-modal-confirm-btns button').contains('OK').click();
|
||||
cy.get('[data-test="metrics"]').contains(newMetricName).should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -91,7 +91,7 @@ describe('Color scheme control', () => {
|
||||
});
|
||||
|
||||
it('should show color options with and without tooltips', () => {
|
||||
cy.get('#controlSections-tab-display').click();
|
||||
cy.get('#controlSections-tab-CUSTOMIZE').click();
|
||||
cy.get('.ant-select-selection-item .color-scheme-label').contains(
|
||||
'Superset Colors',
|
||||
);
|
||||
@@ -102,10 +102,19 @@ describe('Color scheme control', () => {
|
||||
cy.get('.color-scheme-tooltip').contains('Superset Colors');
|
||||
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
|
||||
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
|
||||
|
||||
cy.get('.color-scheme-label')
|
||||
.contains('Superset Colors')
|
||||
.trigger('mouseover');
|
||||
|
||||
cy.get('.color-scheme-label')
|
||||
.contains('Superset Colors')
|
||||
.trigger('mouseout');
|
||||
|
||||
cy.focused().type('lyftColors');
|
||||
cy.getBySel('lyftColors').should('exist');
|
||||
cy.getBySel('lyftColors').trigger('mouseover');
|
||||
cy.get('.color-scheme-tooltip').should('not.exist');
|
||||
cy.getBySel('lyftColors').trigger('mouseover', { force: true });
|
||||
cy.get('.color-scheme-tooltip').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
describe('VizType control', () => {
|
||||
@@ -120,7 +129,7 @@ describe('VizType control', () => {
|
||||
|
||||
cy.contains('View all charts').click();
|
||||
|
||||
cy.get('.antd5-modal-content').within(() => {
|
||||
cy.get('.ant-modal-content').within(() => {
|
||||
cy.get('button').contains('KPI').click(); // change categories
|
||||
cy.get('[role="button"]').contains('Big Number').click();
|
||||
cy.get('button').contains('Select').click();
|
||||
|
||||
@@ -42,8 +42,8 @@ describe('Test explore links', () => {
|
||||
cy.wait('@chartData').then(() => {
|
||||
cy.get('code');
|
||||
});
|
||||
cy.get('.antd5-modal-content').within(() => {
|
||||
cy.get('button.antd5-modal-close').first().click({ force: true });
|
||||
cy.get('.ant-modal-content').within(() => {
|
||||
cy.get('button.ant-modal-close').first().click({ force: true });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,7 +124,7 @@ describe('Test explore links', () => {
|
||||
.find('input[aria-label="Select a dashboard"]')
|
||||
.type(`${dashboardTitle}`, { force: true });
|
||||
|
||||
cy.get(`.ant-select-item[label="${dashboardTitle}"]`).click({
|
||||
cy.get(`.ant-select-item[title="${dashboardTitle}"]`).click({
|
||||
force: true,
|
||||
});
|
||||
|
||||
@@ -157,7 +157,7 @@ describe('Test explore links', () => {
|
||||
.find('input[aria-label="Select a dashboard"]')
|
||||
.type(`${dashboardTitle}{enter}`, { force: true });
|
||||
|
||||
cy.get(`.ant-select-item[label="${dashboardTitle}"]`).click({
|
||||
cy.get(`.ant-select-item[title="${dashboardTitle}"]`).click({
|
||||
force: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ export function setFilter(filter: string, option: string) {
|
||||
cy.wait('@filtering');
|
||||
}
|
||||
|
||||
export function saveChartToDashboard(dashboardName: string) {
|
||||
export function saveChartToDashboard(chartName: string, dashboardName: string) {
|
||||
interceptDashboardGet();
|
||||
interceptUpdate();
|
||||
interceptExploreGet();
|
||||
@@ -75,23 +75,30 @@ export function saveChartToDashboard(dashboardName: string) {
|
||||
cy.getBySel('query-save-button')
|
||||
.should('be.enabled')
|
||||
.should('not.be.disabled')
|
||||
.click();
|
||||
cy.getBySelLike('chart-modal').should('be.visible');
|
||||
cy.get(
|
||||
'[data-test="save-chart-modal-select-dashboard-form"] [aria-label="Select a dashboard"]',
|
||||
)
|
||||
.first()
|
||||
.click();
|
||||
cy.get(
|
||||
'.ant-select-selection-search-input[aria-label="Select a dashboard"]',
|
||||
).type(dashboardName, { force: true });
|
||||
cy.get(`.ant-select-item-option[title="${dashboardName}"]`).click();
|
||||
cy.getBySel('btn-modal-save').click();
|
||||
.click({ force: true });
|
||||
|
||||
cy.wait('@update');
|
||||
cy.getBySel('save-modal-body')
|
||||
.should('be.visible')
|
||||
.then($modal => {
|
||||
cy.wait(500);
|
||||
cy.wrap($modal)
|
||||
.find(
|
||||
'.ant-select-selection-search-input[aria-label="Select a dashboard"]',
|
||||
)
|
||||
.type(dashboardName, { force: true });
|
||||
cy.wrap($modal)
|
||||
.find(`.ant-select-item-option[title="${dashboardName}"]`)
|
||||
.click();
|
||||
cy.getBySel('btn-modal-save').click();
|
||||
cy.wait('@update');
|
||||
});
|
||||
cy.getBySel('save-modal-body').should('not.exist');
|
||||
cy.getBySel('query-save-button').should('be.disabled');
|
||||
cy.wait('@get');
|
||||
cy.wait('@getExplore');
|
||||
cy.contains(`was added to dashboard [${dashboardName}]`);
|
||||
cy.contains(`Chart [${chartName}] has been overwritten`);
|
||||
cy.getBySel('query-save-button').should('be.enabled');
|
||||
}
|
||||
|
||||
export function visitSampleChartFromList(chartName: string) {
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('Visualization > Box Plot', () => {
|
||||
it('should allow type to search color schemes', () => {
|
||||
verify(BOX_PLOT_FORM_DATA);
|
||||
|
||||
cy.get('#controlSections-tab-display').click();
|
||||
cy.get('#controlSections-tab-CUSTOMIZE').click();
|
||||
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
|
||||
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
|
||||
cy.focused().type('supersetColors{enter}');
|
||||
|
||||
@@ -89,7 +89,7 @@ describe('Visualization > Compare', () => {
|
||||
it('should allow type to search color schemes and apply the scheme', () => {
|
||||
verify(COMPARE_FORM_DATA);
|
||||
|
||||
cy.get('#controlSections-tab-display').click();
|
||||
cy.get('#controlSections-tab-CUSTOMIZE').click();
|
||||
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
|
||||
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
|
||||
cy.focused().type('supersetColors{enter}');
|
||||
|
||||
@@ -36,10 +36,10 @@ describe('Download Chart > Bar chart', () => {
|
||||
};
|
||||
|
||||
cy.visitChartByParams(formData);
|
||||
cy.get('.header-with-actions .antd5-dropdown-trigger').click();
|
||||
cy.get(':nth-child(3) > .antd5-dropdown-menu-submenu-title').click();
|
||||
cy.get('.header-with-actions .ant-dropdown-trigger').click();
|
||||
cy.get(':nth-child(3) > .ant-dropdown-menu-submenu-title').click();
|
||||
cy.get(
|
||||
'.antd5-dropdown-menu-submenu > .antd5-dropdown-menu li:nth-child(3)',
|
||||
'.ant-dropdown-menu-submenu > .ant-dropdown-menu li:nth-child(3)',
|
||||
).click();
|
||||
cy.verifyDownload('.jpg', {
|
||||
contains: true,
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('Visualization > Gauge', () => {
|
||||
it('should allow type to search color schemes', () => {
|
||||
verify(GAUGE_FORM_DATA);
|
||||
|
||||
cy.get('#controlSections-tab-display').click();
|
||||
cy.get('#controlSections-tab-CUSTOMIZE').click();
|
||||
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
|
||||
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
|
||||
cy.focused().type('bnbColors{enter}');
|
||||
|
||||
@@ -80,7 +80,7 @@ describe('Visualization > Graph', () => {
|
||||
it('should allow type to search color schemes', () => {
|
||||
verify(GRAPH_FORM_DATA);
|
||||
|
||||
cy.get('#controlSections-tab-display').click();
|
||||
cy.get('#controlSections-tab-CUSTOMIZE').click();
|
||||
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
|
||||
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
|
||||
cy.focused().type('bnbColors{enter}');
|
||||
|
||||
@@ -71,7 +71,7 @@ describe('Visualization > Pie', () => {
|
||||
it('should allow type to search color schemes', () => {
|
||||
verify(PIE_FORM_DATA);
|
||||
|
||||
cy.get('#controlSections-tab-display').click();
|
||||
cy.get('#controlSections-tab-CUSTOMIZE').click();
|
||||
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
|
||||
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
|
||||
cy.focused().type('supersetColors{enter}');
|
||||
|
||||
@@ -86,7 +86,7 @@ describe('Visualization > Sunburst', () => {
|
||||
it('should allow type to search color schemes', () => {
|
||||
verify(SUNBURST_FORM_DATA);
|
||||
|
||||
cy.get('#controlSections-tab-display').click();
|
||||
cy.get('#controlSections-tab-CUSTOMIZE').click();
|
||||
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
|
||||
cy.get('.Control[data-test="color_scheme"] input[type="search"]').focus();
|
||||
cy.focused().type('supersetColors{enter}');
|
||||
|
||||
@@ -193,7 +193,9 @@ describe('Visualization > Table', () => {
|
||||
});
|
||||
|
||||
// should display in raw records mode
|
||||
cy.get('div[data-test="query_mode"] .btn.active').contains('Raw records');
|
||||
cy.get(
|
||||
'div[data-test="query_mode"] .ant-radio-button-wrapper-checked',
|
||||
).contains('Raw records');
|
||||
cy.get('div[data-test="all_columns"]').should('be.visible');
|
||||
cy.get('div[data-test="groupby"]').should('not.exist');
|
||||
|
||||
@@ -201,8 +203,12 @@ describe('Visualization > Table', () => {
|
||||
cy.get('[data-test="row-count-label"]').contains('100 rows');
|
||||
|
||||
// should allow switch back to aggregate mode
|
||||
cy.get('div[data-test="query_mode"] .btn').contains('Aggregate').click();
|
||||
cy.get('div[data-test="query_mode"] .btn.active').contains('Aggregate');
|
||||
cy.get('div[data-test="query_mode"] .ant-radio-button-wrapper')
|
||||
.contains('Aggregate')
|
||||
.click();
|
||||
cy.get(
|
||||
'div[data-test="query_mode"] .ant-radio-button-wrapper-checked',
|
||||
).contains('Aggregate');
|
||||
cy.get('div[data-test="all_columns"]').should('not.exist');
|
||||
cy.get('div[data-test="groupby"]').should('be.visible');
|
||||
});
|
||||
@@ -254,6 +260,8 @@ describe('Visualization > Table', () => {
|
||||
});
|
||||
|
||||
it('Test row limit with server pagination toggle', () => {
|
||||
const serverPaginationSelector =
|
||||
'[data-test="server_pagination-header"] div.pull-left [type="checkbox"]';
|
||||
cy.visitChartByParams({
|
||||
...VIZ_DEFAULTS,
|
||||
metrics: ['count'],
|
||||
@@ -261,7 +269,7 @@ describe('Visualization > Table', () => {
|
||||
});
|
||||
|
||||
// Enable server pagination
|
||||
cy.get('[data-test="server_pagination-header"] div.pull-left').click();
|
||||
cy.get(serverPaginationSelector).click();
|
||||
|
||||
// Click row limit control and select high value (200k)
|
||||
cy.get('div[aria-label="Row limit"]').click();
|
||||
@@ -275,7 +283,7 @@ describe('Visualization > Table', () => {
|
||||
cy.get('[data-test="error-tooltip"]').should('not.exist');
|
||||
|
||||
// Disable server pagination
|
||||
cy.get('[data-test="server_pagination-header"] div.pull-left').click();
|
||||
cy.get(serverPaginationSelector).click();
|
||||
|
||||
// Verify error tooltip appears
|
||||
cy.get('[data-test="error-tooltip"]').should('be.visible');
|
||||
@@ -284,17 +292,17 @@ describe('Visualization > Table', () => {
|
||||
cy.get('[data-test="error-tooltip"]').trigger('mouseover');
|
||||
|
||||
// Verify tooltip content
|
||||
cy.get('.antd5-tooltip-inner').should('be.visible');
|
||||
cy.get('.antd5-tooltip-inner').should(
|
||||
cy.get('.ant-tooltip-inner').should('be.visible');
|
||||
cy.get('.ant-tooltip-inner').should(
|
||||
'contain',
|
||||
'Server pagination needs to be enabled for values over',
|
||||
);
|
||||
|
||||
// Hide the tooltip by adding display:none style
|
||||
cy.get('.antd5-tooltip').invoke('attr', 'style', 'display: none');
|
||||
cy.get('.ant-tooltip').invoke('attr', 'style', 'display: none');
|
||||
|
||||
// Enable server pagination again
|
||||
cy.get('[data-test="server_pagination-header"] div.pull-left').click();
|
||||
cy.get(serverPaginationSelector).click();
|
||||
|
||||
cy.get('[data-test="error-tooltip"]').should('not.exist');
|
||||
|
||||
@@ -319,11 +327,11 @@ describe('Visualization > Table', () => {
|
||||
.trigger('mouseover');
|
||||
|
||||
// Wait for tooltip content and verify
|
||||
cy.get('.antd5-tooltip-inner').should('exist');
|
||||
cy.get('.antd5-tooltip-inner').should('be.visible');
|
||||
cy.get('.ant-tooltip-inner').should('exist');
|
||||
cy.get('.ant-tooltip-inner').should('be.visible');
|
||||
|
||||
// Verify tooltip content separately
|
||||
cy.get('.antd5-tooltip-inner').should('contain', 'Value cannot exceed');
|
||||
cy.get('.ant-tooltip-inner').should('contain', 'Value cannot exceed');
|
||||
});
|
||||
|
||||
it('Test sorting with server pagination enabled', () => {
|
||||
@@ -393,12 +401,12 @@ describe('Visualization > Table', () => {
|
||||
|
||||
cy.wait('@chartData');
|
||||
|
||||
// Basic search test
|
||||
cy.get('span.dt-global-filter input.form-control.input-sm').should(
|
||||
'be.visible',
|
||||
);
|
||||
const searchInputSelector = '.dt-global-filter input';
|
||||
|
||||
cy.get('span.dt-global-filter input.form-control.input-sm').type('John');
|
||||
// Basic search test
|
||||
cy.get(searchInputSelector).should('be.visible');
|
||||
|
||||
cy.get(searchInputSelector).type('John');
|
||||
|
||||
cy.wait('@chartData');
|
||||
|
||||
@@ -407,11 +415,11 @@ describe('Visualization > Table', () => {
|
||||
});
|
||||
|
||||
// Clear and test case-insensitive search
|
||||
cy.get('span.dt-global-filter input.form-control.input-sm').clear();
|
||||
cy.get(searchInputSelector).clear();
|
||||
|
||||
cy.wait('@chartData');
|
||||
|
||||
cy.get('span.dt-global-filter input.form-control.input-sm').type('mary');
|
||||
cy.get(searchInputSelector).type('mary');
|
||||
|
||||
cy.wait('@chartData');
|
||||
|
||||
@@ -420,9 +428,9 @@ describe('Visualization > Table', () => {
|
||||
});
|
||||
|
||||
// Test special characters
|
||||
cy.get('span.dt-global-filter input.form-control.input-sm').clear();
|
||||
cy.get(searchInputSelector).clear();
|
||||
|
||||
cy.get('span.dt-global-filter input.form-control.input-sm').type('Nicole');
|
||||
cy.get(searchInputSelector).type('Nicole');
|
||||
|
||||
cy.wait('@chartData');
|
||||
|
||||
@@ -431,9 +439,9 @@ describe('Visualization > Table', () => {
|
||||
});
|
||||
|
||||
// Test no results
|
||||
cy.get('span.dt-global-filter input.form-control.input-sm').clear();
|
||||
cy.get(searchInputSelector).clear();
|
||||
|
||||
cy.get('span.dt-global-filter input.form-control.input-sm').type('XYZ123');
|
||||
cy.get(searchInputSelector).type('XYZ123');
|
||||
|
||||
cy.wait('@chartData');
|
||||
|
||||
@@ -450,9 +458,9 @@ describe('Visualization > Table', () => {
|
||||
|
||||
cy.get('.ant-select-item-option').contains('state').click();
|
||||
|
||||
cy.get('span.dt-global-filter input.form-control.input-sm').clear();
|
||||
cy.get(searchInputSelector).clear();
|
||||
|
||||
cy.get('span.dt-global-filter input.form-control.input-sm').type('CA');
|
||||
cy.get(searchInputSelector).type('CA');
|
||||
|
||||
cy.wait('@chartData');
|
||||
cy.wait(1000);
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('SqlLab query tabs', () => {
|
||||
});
|
||||
|
||||
const tablistSelector = '[data-test="sql-editor-tabs"] > [role="tablist"]';
|
||||
const tabSelector = `${tablistSelector} [role="tab"]`;
|
||||
const tabSelector = `${tablistSelector} [role="tab"]:not([type="button"])`;
|
||||
|
||||
it('allows you to create and close a tab', () => {
|
||||
cy.get(tabSelector).then(tabs => {
|
||||
@@ -80,9 +80,9 @@ describe('SqlLab query tabs', () => {
|
||||
// configure some editor settings
|
||||
cy.get(editorInput).type('some random query string', { force: true });
|
||||
cy.get(queryLimitSelector).parent().click({ force: true });
|
||||
cy.get('.antd5-dropdown-menu')
|
||||
cy.get('.ant-dropdown-menu')
|
||||
.last()
|
||||
.find('.antd5-dropdown-menu-item')
|
||||
.find('.ant-dropdown-menu-item')
|
||||
.first()
|
||||
.click({ force: true });
|
||||
|
||||
|
||||
@@ -25,16 +25,16 @@ export function dataTestChartName(chartName: string): string {
|
||||
|
||||
export const pageHeader = {
|
||||
logo: '.navbar-brand > img',
|
||||
headerNavigationItem: '.antd5-menu-submenu-title',
|
||||
headerNavigationItem: '.ant-menu-submenu-title',
|
||||
headerNavigationDropdown: "[aria-label='triangle-down']",
|
||||
headerNavigationItemMenu: '.antd5-menu-item-group-list',
|
||||
plusIcon: ':nth-child(2) > .antd5-menu-submenu-title',
|
||||
headerNavigationItemMenu: '.ant-menu-item-group-list',
|
||||
plusIcon: ':nth-child(2) > .ant-menu-submenu-title',
|
||||
plusIconMenuOptions: {
|
||||
sqlQueryOption: dataTestLocator('menu-item-SQL query'),
|
||||
chartOption: dataTestLocator('menu-item-Chart'),
|
||||
dashboardOption: dataTestLocator('menu-item-Dashboard'),
|
||||
},
|
||||
plusMenu: '.antd5-menu-submenu-popup',
|
||||
plusMenu: '.ant-menu-submenu-popup',
|
||||
barButtons: '[role="presentation"]',
|
||||
sqlLabMenu: '[id="item_3$Menu"]',
|
||||
dataMenu: '[id="item_4$Menu"]',
|
||||
@@ -48,12 +48,12 @@ export const profile = {
|
||||
favoritesSpace: '#rc-tabs-0-panel-2',
|
||||
};
|
||||
export const securityAccess = {
|
||||
rolesBubble: '.antd5-badge-count',
|
||||
rolesBubble: '.ant-badge-count',
|
||||
};
|
||||
export const homePage = {
|
||||
homeSection: {
|
||||
sectionArea: '.ant-collapse-content-box',
|
||||
sectionElement: '.antd5-card-meta-title',
|
||||
sectionElement: '.ant-card-meta-title',
|
||||
},
|
||||
sections: {
|
||||
expandedSection: '.ant-collapse-item-active',
|
||||
@@ -94,19 +94,19 @@ export const databasesPage = {
|
||||
dbDropdown: '[class="ant-select-selection-search-input"]',
|
||||
dbDropdownMenu: '.rc-virtual-list-holder-inner',
|
||||
dbDropdownMenuItem: '[class="ant-select-item-option-content"]',
|
||||
infoAlert: '.antd5-alert',
|
||||
infoAlert: '.ant-alert',
|
||||
serviceAccountInput: '[name="credentials_info"]',
|
||||
connectionStep: {
|
||||
modal: '.antd5-modal-content',
|
||||
modalBody: '.antd5-modal-body',
|
||||
modal: '.ant-modal-content',
|
||||
modalBody: '.ant-modal-body',
|
||||
stepTitle: '.css-7x6kk > h4',
|
||||
helperBottom: '.helper-bottom',
|
||||
postgresDatabase: '[name="database"]',
|
||||
dbInput: '[name="database_name"]',
|
||||
alertMessage: '.antd5-alert-message',
|
||||
alertMessage: '.ant-alert-message',
|
||||
errorField: '[role="alert"]',
|
||||
uploadJson: '[title="Upload JSON file"]',
|
||||
chooseFile: '[class="antd5-btn input-upload-btn"]',
|
||||
chooseFile: '[class="ant-btn input-upload-btn"]',
|
||||
additionalParameters: '[name="query_input"]',
|
||||
sqlAlchemyUriInput: dataTestLocator('sqlalchemy-uri-input'),
|
||||
advancedTab: '#rc-tabs-0-tab-2',
|
||||
@@ -140,7 +140,7 @@ export const sqlLabView = {
|
||||
tabsNavList: "[class='ant-tabs-nav-list']",
|
||||
tab: "[class='ant-tabs-tab-btn']",
|
||||
addTabButton: dataTestLocator('add-tab-icon'),
|
||||
tooltip: '.antd5-tooltip-content',
|
||||
tooltip: '.ant-tooltip-content',
|
||||
tabName: '.css-1suejie',
|
||||
schemaInput: '[data-test=DatabaseSelector] > :nth-child(2)',
|
||||
loadingIndicator: '.Select__loading-indicator',
|
||||
@@ -148,9 +148,9 @@ export const sqlLabView = {
|
||||
examplesMenuItem: '[title="examples"]',
|
||||
tableInput: ':nth-child(4) > .select > :nth-child(1)',
|
||||
sqlEditor: '#brace-editor textarea',
|
||||
saveAsButton: '.SaveQuery > .antd5-btn',
|
||||
saveAsButton: '.SaveQuery > .ant-btn',
|
||||
saveAsModal: {
|
||||
footer: '.antd5-modal-footer',
|
||||
footer: '.ant-modal-footer',
|
||||
queryNameInput: 'input[class^="ant-input"]',
|
||||
},
|
||||
sqlToolbar: {
|
||||
@@ -158,15 +158,15 @@ export const sqlLabView = {
|
||||
runButton: '.css-d3dxop',
|
||||
},
|
||||
rowsLimit: {
|
||||
dropdown: '.antd5-dropdown-menu',
|
||||
limitButton: '.antd5-dropdown-menu-item',
|
||||
dropdown: '.ant-dropdown-menu',
|
||||
limitButton: '.ant-dropdown-menu-item',
|
||||
limitButtonText: '.css-151uxnz',
|
||||
limitTextWithValue: '[class="antd5-dropdown-trigger"]',
|
||||
limitTextWithValue: '[class="ant-dropdown-trigger"]',
|
||||
},
|
||||
renderedTableHeader: '.ReactVirtualized__Table__headerRow',
|
||||
renderedTableRow: '.ReactVirtualized__Table__row',
|
||||
errorBody: '.error-body',
|
||||
alertMessage: '.antd5-alert-message',
|
||||
alertMessage: '.ant-alert-message',
|
||||
historyTable: {
|
||||
header: '[role=columnheader]',
|
||||
table: '.QueryTable',
|
||||
@@ -195,16 +195,16 @@ export const savedQuery = {
|
||||
export const annotationLayersView = {
|
||||
emptyDescription: {
|
||||
description: '.ant-empty-description',
|
||||
addAnnotationLayerButton: '.ant-empty-footer > .antd5-btn',
|
||||
addAnnotationLayerButton: '.ant-empty-footer > .ant-btn',
|
||||
},
|
||||
modal: {
|
||||
content: {
|
||||
content: '.antd5-modal-body',
|
||||
title: '.antd5-modal-body > :nth-child(2) > input',
|
||||
content: '.ant-modal-body',
|
||||
title: '.ant-modal-body > :nth-child(2) > input',
|
||||
description: "[name='descr']",
|
||||
},
|
||||
footer: {
|
||||
footer: '.antd5-modal-footer',
|
||||
footer: '.ant-modal-footer',
|
||||
addButton: dataTestLocator('modal-confirm-button'),
|
||||
cancelButton: dataTestLocator('modal-cancel-button'),
|
||||
},
|
||||
@@ -216,7 +216,7 @@ export const datasetsList = {
|
||||
newDatasetModal: {
|
||||
inputField: '[class="section"]',
|
||||
addButton: dataTestLocator('modal-confirm-button'),
|
||||
body: '.antd5-modal-body',
|
||||
body: '.ant-modal-body',
|
||||
},
|
||||
table: {
|
||||
tableRow: {
|
||||
@@ -261,7 +261,7 @@ export const datasetsList = {
|
||||
},
|
||||
},
|
||||
deleteDatasetModal: {
|
||||
modal: '.antd5-modal-content',
|
||||
modal: '.ant-modal-content',
|
||||
deleteInput: dataTestLocator('delete-modal-input'),
|
||||
deleteButton: dataTestLocator('modal-confirm-button'),
|
||||
text: '.css-kxmt87',
|
||||
@@ -275,8 +275,8 @@ export const chartListView = {
|
||||
bulkSelect: dataTestLocator('bulk-select'),
|
||||
},
|
||||
header: {
|
||||
cardView: '[aria-label="appstore"]',
|
||||
listView: '[aria-label="unordered-list"]',
|
||||
cardView: '[aria-label="card-view"]',
|
||||
listView: '[aria-label="list-view"]',
|
||||
sort: '[class="ant-select-selection-search-input"][aria-label="Sort"]',
|
||||
sortRecentlyModifiedMenuOption: '[label="Recently modified"]',
|
||||
sortAlphabeticalMenuOption: '[label="Alphabetical"]',
|
||||
@@ -284,7 +284,7 @@ export const chartListView = {
|
||||
},
|
||||
card: {
|
||||
card: dataTestLocator('styled-card'),
|
||||
cardCover: '[class="antd5-card-cover"]',
|
||||
cardCover: '[class="ant-card-cover"]',
|
||||
cardImage: '[class="gradient-container"]',
|
||||
starIcon: dataTestLocator('fave-unfave-icon'),
|
||||
},
|
||||
@@ -294,8 +294,8 @@ export const chartListView = {
|
||||
},
|
||||
table: {
|
||||
bulkSelect: {
|
||||
checkboxOff: '[aria-label="checkbox-off"]',
|
||||
checkboxOn: '[aria-label="checkbox-on"]',
|
||||
checkboxOff: 'input[type="checkbox"]:checked',
|
||||
checkboxOn: 'input[type="checkbox"]:not(:checked)',
|
||||
action: dataTestLocator('bulk-select-action'),
|
||||
},
|
||||
tableList: dataTestLocator('listview-table'),
|
||||
@@ -316,14 +316,14 @@ export const chartListView = {
|
||||
};
|
||||
export const nativeFilters = {
|
||||
modal: {
|
||||
container: '.antd5-modal',
|
||||
footer: '.antd5-modal-footer',
|
||||
container: '.ant-modal',
|
||||
footer: '.ant-modal-footer',
|
||||
saveButton: dataTestLocator('native-filter-modal-save-button'),
|
||||
cancelButton: dataTestLocator('native-filter-modal-cancel-button'),
|
||||
confirmCancelButton: dataTestLocator(
|
||||
'native-filter-modal-confirm-cancel-button',
|
||||
),
|
||||
alertXUnsavedFilters: '.antd5-alert-message',
|
||||
alertXUnsavedFilters: '.ant-alert-message',
|
||||
tabsList: {
|
||||
filterItemsContainer: dataTestLocator('filter-title-container'),
|
||||
tabsContainer: '[class="ant-tabs-nav-list"]',
|
||||
@@ -398,7 +398,7 @@ export const dashboardListView = {
|
||||
},
|
||||
card: {
|
||||
card: dataTestLocator('styled-card'),
|
||||
cardCover: '[class="antd5-card-cover"]',
|
||||
cardCover: '[class="ant-card-cover"]',
|
||||
cardImage: '[class="gradient-container"]',
|
||||
selectedStarIcon: "[aria-label='star']",
|
||||
unselectedStarIcon: "[aria-label='star']",
|
||||
@@ -432,7 +432,7 @@ export const dashboardListView = {
|
||||
newDashboardButton: '.css-yff34v',
|
||||
},
|
||||
importModal: {
|
||||
selectFileButton: '.ant-upload > .antd5-btn > span',
|
||||
selectFileButton: '.ant-upload > .ant-btn > span',
|
||||
importButton: dataTestLocator('modal-confirm-button'),
|
||||
},
|
||||
header: {
|
||||
@@ -474,15 +474,15 @@ export const exploreView = {
|
||||
},
|
||||
chartAreaItem: '.nv-legend-text',
|
||||
viewQueryModal: {
|
||||
container: '.antd5-modal-content',
|
||||
closeButton: 'button.antd5-modal-close',
|
||||
container: '.ant-modal-content',
|
||||
closeButton: 'button.ant-modal-close',
|
||||
},
|
||||
embedCodeModal: {
|
||||
container: dataTestLocator('embed-code-popover'),
|
||||
textfield: dataTestLocator('embed-code-textarea'),
|
||||
},
|
||||
saveModal: {
|
||||
modal: '.antd5-modal-content',
|
||||
modal: '.ant-modal-content',
|
||||
chartNameInput: dataTestLocator('new-chart-name'),
|
||||
dashboardNameInput: '.ant-select-selection-search-input',
|
||||
addToDashboardInput: dataTestLocator(
|
||||
@@ -553,7 +553,7 @@ export const exploreView = {
|
||||
timeSection: {
|
||||
timeRangeFilter: dataTestLocator('time-range-trigger'),
|
||||
timeRangeFilterModal: {
|
||||
container: '.antd5-popover-content',
|
||||
container: '.ant-popover-content',
|
||||
footer: '.footer',
|
||||
cancelButton: dataTestLocator('cancel-button'),
|
||||
configureLastTimeRange: {
|
||||
@@ -578,15 +578,15 @@ export const exploreView = {
|
||||
},
|
||||
},
|
||||
editDatasetModal: {
|
||||
container: '.antd5-modal-content',
|
||||
container: '.ant-modal-content',
|
||||
datasetTabsContainer: dataTestLocator('edit-dataset-tabs'),
|
||||
saveButton: dataTestLocator('datasource-modal-save'),
|
||||
metricsTab: {
|
||||
addItem: dataTestLocator('crud-add-table-item'),
|
||||
rowsContainer: dataTestLocator('table-content-rows'),
|
||||
rowsContainer: '.ant-table-body',
|
||||
},
|
||||
confirmModal: {
|
||||
okButton: '.antd5-modal-confirm-btns .antd5-btn-primary',
|
||||
okButton: '.ant-modal-confirm-btns .ant-btn-primary',
|
||||
},
|
||||
},
|
||||
visualizationTypeModal: {
|
||||
@@ -617,12 +617,12 @@ export const dashboardView = {
|
||||
closeButton: dataTestLocator('close-button'),
|
||||
},
|
||||
saveModal: {
|
||||
modal: '.antd5-modal-content',
|
||||
modal: '.ant-modal-content',
|
||||
dashboardNameInput: '.ant-input',
|
||||
saveButton: dataTestLocator('modal-save-dashboard-button'),
|
||||
},
|
||||
dashboardProperties: {
|
||||
modal: '.antd5-modal-content',
|
||||
modal: '.ant-modal-content',
|
||||
dashboardTitleInput: dataTestLocator('dashboard-title-input'),
|
||||
modalButton: '[type="button"]',
|
||||
},
|
||||
@@ -631,7 +631,7 @@ export const dashboardView = {
|
||||
refreshChart: dataTestLocator('refresh-chart-menu-item'),
|
||||
},
|
||||
threeDotsMenuIcon:
|
||||
'.header-with-actions .right-button-panel .antd5-dropdown-trigger',
|
||||
'.header-with-actions .right-button-panel .ant-dropdown-trigger',
|
||||
threeDotsMenuDropdown: dataTestLocator('header-actions-menu'),
|
||||
refreshDashboard: dataTestLocator('refresh-dashboard-menu-item'),
|
||||
saveAsMenuOption: dataTestLocator('save-as-menu-item'),
|
||||
|
||||
@@ -69,7 +69,21 @@ Cypress.Commands.add('loadDashboardFixtures', () =>
|
||||
}),
|
||||
);
|
||||
|
||||
const PATHS_TO_SKIP_LOGIN = ['login', 'register'];
|
||||
|
||||
const skipLogin = () => {
|
||||
for (const path of PATHS_TO_SKIP_LOGIN) {
|
||||
if (Cypress.currentTest.title.toLowerCase().includes(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
before(() => {
|
||||
if (skipLogin()) {
|
||||
return;
|
||||
}
|
||||
cy.login();
|
||||
Cypress.Cookies.defaults({ preserve: 'session' });
|
||||
cy.loadChartFixtures();
|
||||
@@ -77,6 +91,9 @@ before(() => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
if (skipLogin()) {
|
||||
return;
|
||||
}
|
||||
cy.cleanDashboards();
|
||||
cy.cleanCharts();
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -28,3 +28,5 @@ export const DATABASE_LIST = '/databaseview/list';
|
||||
export const DATASET_LIST_PATH = 'tablemodelview/list';
|
||||
export const ALERT_LIST = '/alert/list/';
|
||||
export const REPORT_LIST = '/report/list/';
|
||||
export const LOGIN = '/login/';
|
||||
export const REGISTER = '/register/';
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// timezone for unit tests
|
||||
process.env.TZ = 'America/New_York';
|
||||
|
||||
module.exports = {
|
||||
testRegex:
|
||||
'\\/superset-frontend\\/(spec|src|plugins|packages|tools)\\/.*(_spec|\\.test)\\.[jt]sx?$',
|
||||
@@ -30,7 +28,9 @@ module.exports = {
|
||||
'^src/(.*)$': '<rootDir>/src/$1',
|
||||
'^spec/(.*)$': '<rootDir>/spec/$1',
|
||||
// mapping plugins of superset-ui to source code
|
||||
'@superset-ui/(.*)$': '<rootDir>/node_modules/@superset-ui/$1/src',
|
||||
'^@superset-ui/([^/]+)/(.*)$':
|
||||
'<rootDir>/node_modules/@superset-ui/$1/src/$2',
|
||||
'^@superset-ui/([^/]+)$': '<rootDir>/node_modules/@superset-ui/$1/src',
|
||||
},
|
||||
testEnvironment: 'jsdom',
|
||||
modulePathIgnorePatterns: ['<rootDir>/packages/generator-superset'],
|
||||
@@ -55,7 +55,7 @@ module.exports = {
|
||||
],
|
||||
coverageReporters: ['lcov', 'json-summary', 'html', 'text'],
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!d3-(interpolate|color|time)|remark-gfm|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|@rjsf/*.|sinon|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|jest-enzyme)',
|
||||
'node_modules/(?!d3-(interpolate|color|time)|remark-gfm|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|@rjsf/*.|sinon|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|jest-enzyme|geostyler|geostyler-.*)',
|
||||
],
|
||||
preset: 'ts-jest',
|
||||
transform: {
|
||||
|
||||
11244
superset-frontend/package-lock.json
generated
11244
superset-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,7 @@
|
||||
"src/setup/*"
|
||||
],
|
||||
"scripts": {
|
||||
"_prettier": "prettier './({src,spec,cypress-base,plugins,packages,.storybook}/**/*{.js,.jsx,.ts,.tsx,.css,.less,.scss,.sass}|package.json)'",
|
||||
"_prettier": "prettier './({src,spec,cypress-base,plugins,packages,.storybook}/**/*{.js,.jsx,.ts,.tsx,.css,.scss,.sass}|package.json)'",
|
||||
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production BABEL_ENV=\"${BABEL_ENV:=production}\" webpack --color --mode production",
|
||||
"build-dev": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=development webpack --mode=development --color",
|
||||
"build-instrumented": "cross-env NODE_ENV=production BABEL_ENV=instrumented webpack --mode=production --color",
|
||||
@@ -70,6 +70,7 @@
|
||||
"storybook": "cross-env NODE_ENV=development BABEL_ENV=development storybook dev -p 6006",
|
||||
"tdd": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --watch",
|
||||
"test": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80% --silent",
|
||||
"test-loud": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80%",
|
||||
"type": "tsc --noEmit",
|
||||
"update-maps": "jupyter nbconvert --to notebook --execute --inplace 'plugins/legacy-plugin-chart-country-map/scripts/Country Map GeoJSON Generator.ipynb' -Xfrozen_modules=off",
|
||||
"validate-release": "../RELEASING/validate_this_release.sh"
|
||||
@@ -81,12 +82,9 @@
|
||||
"last 3 edge versions"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@fontsource/fira-code": "^5.0.18",
|
||||
"@fontsource/inter": "^5.0.20",
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"@rjsf/core": "^5.21.1",
|
||||
"@rjsf/utils": "^5.24.3",
|
||||
@@ -115,6 +113,7 @@
|
||||
"@superset-ui/switchboard": "file:./packages/superset-ui-switchboard",
|
||||
"@types/d3-format": "^3.0.1",
|
||||
"@types/d3-time-format": "^4.0.3",
|
||||
"@types/react-google-recaptcha": "^2.1.9",
|
||||
"@visx/axis": "^3.8.0",
|
||||
"@visx/grid": "^3.5.0",
|
||||
"@visx/responsive": "^3.0.0",
|
||||
@@ -122,22 +121,16 @@
|
||||
"@visx/tooltip": "^3.0.0",
|
||||
"@visx/xychart": "^3.5.1",
|
||||
"abortcontroller-polyfill": "^1.7.8",
|
||||
"ace-builds": "^1.41.0",
|
||||
"ag-grid-community": "33.1.1",
|
||||
"ag-grid-react": "33.1.1",
|
||||
"antd": "4.10.3",
|
||||
"antd-v5": "npm:antd@^5.18.0",
|
||||
"bootstrap": "^3.4.1",
|
||||
"brace": "^0.11.1",
|
||||
"antd": "^5.24.6",
|
||||
"chrono-node": "^2.7.8",
|
||||
"classnames": "^2.2.5",
|
||||
"core-js": "^3.38.1",
|
||||
"d3-color": "^3.1.0",
|
||||
"d3-scale": "^2.1.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"dom-to-image-more": "^3.2.0",
|
||||
"dom-to-pdf": "^0.3.2",
|
||||
"dompurify": "^3.2.4",
|
||||
"echarts": "^5.6.0",
|
||||
"emotion-rgba": "0.0.12",
|
||||
"eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings",
|
||||
@@ -145,12 +138,14 @@
|
||||
"fs-extra": "^11.2.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"geolib": "^2.0.24",
|
||||
"geostyler": "^12.0.2",
|
||||
"geostyler": "^14.1.3",
|
||||
"geostyler-data": "^1.0.0",
|
||||
"geostyler-openlayers-parser": "^4.3.0",
|
||||
"geostyler-style": "^7.5.0",
|
||||
"geostyler-qgis-parser": "2.0.1",
|
||||
"geostyler-style": "7.5.0",
|
||||
"geostyler-wfs-parser": "^2.0.3",
|
||||
"googleapis": "^130.0.0",
|
||||
"html-webpack-plugin": "^5.6.3",
|
||||
"immer": "^10.1.1",
|
||||
"interweave": "^13.1.0",
|
||||
"jquery": "^3.7.1",
|
||||
@@ -174,17 +169,15 @@
|
||||
"rc-trigger": "^5.3.4",
|
||||
"re-resizable": "^6.10.1",
|
||||
"react": "^17.0.2",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-checkbox-tree": "^1.8.0",
|
||||
"react-color": "^2.13.8",
|
||||
"react-diff-viewer-continued": "^3.4.0",
|
||||
"react-dnd": "^11.1.3",
|
||||
"react-dnd-html5-backend": "^11.1.3",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-draggable": "^4.4.6",
|
||||
"react-google-recaptcha": "^3.1.0",
|
||||
"react-hot-loader": "^4.13.1",
|
||||
"react-intersection-observer": "^9.16.0",
|
||||
"react-js-cron": "^2.1.2",
|
||||
"react-json-tree": "^0.17.0",
|
||||
"react-lines-ellipsis": "^0.15.4",
|
||||
"react-loadable": "^5.5.0",
|
||||
@@ -198,18 +191,17 @@
|
||||
"react-syntax-highlighter": "^15.4.5",
|
||||
"react-table": "^7.8.0",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"react-ultimate-pagination": "^1.3.2",
|
||||
"react-virtualized-auto-sizer": "^1.0.25",
|
||||
"react-window": "^1.8.10",
|
||||
"redux": "^4.2.1",
|
||||
"redux-localstorage": "^0.4.1",
|
||||
"redux-thunk": "^2.1.0",
|
||||
"redux-undo": "^1.0.0-beta9-9-7",
|
||||
"regenerator-runtime": "^0.14.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"rison": "^0.1.1",
|
||||
"scroll-into-view-if-needed": "^3.1.0",
|
||||
"simple-zstd": "^1.4.2",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"tinycolor2": "^1.4.2",
|
||||
"urijs": "^1.19.8",
|
||||
"use-event-callback": "^0.1.0",
|
||||
@@ -262,12 +254,12 @@
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/dom-to-image": "^2.6.7",
|
||||
"@types/fetch-mock": "^7.3.2",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/jquery": "^3.5.8",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/js-levenshtein": "^1.1.3",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/math-expression-evaluator": "^1.3.3",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^22.12.0",
|
||||
"@types/react": "^17.0.83",
|
||||
"@types/react-dom": "^17.0.26",
|
||||
"@types/react-gravatar": "^2.6.14",
|
||||
@@ -277,7 +269,6 @@
|
||||
"@types/react-resizable": "^3.0.8",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@types/react-transition-group": "^4.4.12",
|
||||
"@types/react-ultimate-pagination": "^1.2.4",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.4",
|
||||
@@ -286,6 +277,7 @@
|
||||
"@types/redux-mock-store": "^1.0.6",
|
||||
"@types/rison": "0.1.0",
|
||||
"@types/sinon": "^17.0.3",
|
||||
"@types/testing-library__jest-dom": "^5.14.9",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
"@types/yargs": "12 - 18",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
@@ -304,6 +296,7 @@
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-prettier": "^7.2.0",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-import-resolver-typescript": "^3.7.0",
|
||||
"eslint-plugin-cypress": "^3.6.0",
|
||||
"eslint-plugin-file-progress": "^1.5.0",
|
||||
@@ -327,13 +320,13 @@
|
||||
"html-webpack-plugin": "^5.6.3",
|
||||
"imports-loader": "^5.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-enzyme": "^7.1.2",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-enzyme": "^7.1.2",
|
||||
"jest-html-reporter": "^3.10.2",
|
||||
"jest-websocket-mock": "^2.5.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"lerna": "^8.2.1",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^12.2.0",
|
||||
"mini-css-extract-plugin": "^2.9.0",
|
||||
"open-cli": "^8.0.0",
|
||||
"po2json": "^0.4.5",
|
||||
@@ -352,6 +345,7 @@
|
||||
"ts-jest": "^29.2.5",
|
||||
"ts-loader": "^9.5.1",
|
||||
"tscw-config": "^1.1.2",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "5.1.6",
|
||||
"vm-browserify": "^1.1.2",
|
||||
"webpack": "^5.98.0",
|
||||
@@ -362,6 +356,12 @@
|
||||
"webpack-sources": "^3.2.3",
|
||||
"webpack-visualizer-plugin2": "^1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ace-builds": "^1.41.0",
|
||||
"core-js": "^3.38.1",
|
||||
"react-ace": "^10.1.0",
|
||||
"regenerator-runtime": "^0.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.16.0",
|
||||
"npm": "^10.8.1"
|
||||
@@ -371,7 +371,8 @@
|
||||
"d3-color": "^3.1.0",
|
||||
"puppeteer": "^22.4.1",
|
||||
"underscore": "^1.13.7",
|
||||
"jspdf": "^3.0.1"
|
||||
"jspdf": "^3.0.1",
|
||||
"nwsapi": "^2.2.13"
|
||||
},
|
||||
"readme": "ERROR: No README data found!",
|
||||
"scarfSettings": {
|
||||
|
||||
@@ -4194,7 +4194,7 @@
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@types/react": "*",
|
||||
"antd": "^4.9.4",
|
||||
"antd": "^5.24.6",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1"
|
||||
}
|
||||
|
||||
@@ -25,29 +25,14 @@ import { <%= packageLabel %>Props, <%= packageLabel %>StylesProps } from './type
|
||||
|
||||
// Theming variables are provided for your use via a ThemeProvider
|
||||
// imported from @superset-ui/core. For variables available, please visit
|
||||
// https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-core/src/style/index.ts
|
||||
// https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-core/src/theme/index.ts
|
||||
|
||||
const Styles = styled.div<<%= packageLabel %>StylesProps>`
|
||||
background-color: ${({ theme }) => theme.colors.secondary.light2};
|
||||
padding: ${({ theme }) => theme.gridUnit * 4}px;
|
||||
border-radius: ${({ theme }) => theme.gridUnit * 2}px;
|
||||
background-color: ${({ theme }) => theme.colors.primary.light2};
|
||||
padding: ${({ theme }) => theme.sizeUnit * 4}px;
|
||||
border-radius: ${({ theme }) => theme.borderRadius}px;
|
||||
height: ${({ height }) => height}px;
|
||||
width: ${({ width }) => width}px;
|
||||
|
||||
h3 {
|
||||
/* You can use your props to control CSS! */
|
||||
margin-top: 0;
|
||||
margin-bottom: ${({ theme }) => theme.gridUnit * 3}px;
|
||||
font-size: ${({ theme, headerFontSize }) =>
|
||||
theme.typography.sizes[headerFontSize]}px;
|
||||
font-weight: ${({ theme, boldText }) =>
|
||||
theme.typography.weights[boldText ? 'bold' : 'normal']};
|
||||
}
|
||||
|
||||
pre {
|
||||
height: ${({ theme, headerFontSize, height }) =>
|
||||
height - theme.gridUnit * 12 - theme.typography.sizes[headerFontSize]}px;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
"@testing-library/react-hooks": "*",
|
||||
"@testing-library/user-event": "*",
|
||||
"ace-builds": "^1.4.14",
|
||||
"antd": "4.10.3",
|
||||
"brace": "^0.11.1",
|
||||
"memoize-one": "^5.1.1",
|
||||
"react": "^17.0.2",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
import { kebabCase } from 'lodash';
|
||||
import { t, useTheme, styled } from '@superset-ui/core';
|
||||
import Tooltip from './Tooltip';
|
||||
import { Tooltip } from '@superset-ui/core/components';
|
||||
|
||||
interface CertifiedIconWithTooltipProps {
|
||||
certifiedBy?: string | null;
|
||||
@@ -27,7 +27,7 @@ interface CertifiedIconWithTooltipProps {
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
margin-bottom: ${({ theme }) => theme.gridUnit * 2}px;
|
||||
margin-bottom: ${({ theme }) => theme.sizeUnit * 2}px;
|
||||
`;
|
||||
|
||||
function CertifiedIconWithTooltip({
|
||||
@@ -58,7 +58,7 @@ function CertifiedIconWithTooltip({
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
fill={theme.colors.primary.base}
|
||||
fill={theme.colorPrimary}
|
||||
d="M23,12l-2.44-2.79l0.34-3.69l-3.61-0.82L15.4,1.5L12,2.96L8.6,1.5L6.71,4.69L3.1,5.5L3.44,9.2L1,12l2.44,2.79l-0.34,3.7 l3.61,0.82L8.6,22.5l3.4-1.47l3.4,1.46l1.89-3.19l3.61-0.82l-0.34-3.69L23,12z M9.38,16.01L7,13.61c-0.39-0.39-0.39-1.02,0-1.41 l0.07-0.07c0.39-0.39,1.03-0.39,1.42,0l1.61,1.62l5.15-5.16c0.39-0.39,1.03-0.39,1.42,0l0.07,0.07c0.39,0.39,0.39,1.02,0,1.41 l-5.92,5.94C10.41,16.4,9.78,16.4,9.38,16.01z"
|
||||
/>
|
||||
</g>
|
||||
|
||||
@@ -17,8 +17,12 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { useState, ReactNode, useLayoutEffect, RefObject } from 'react';
|
||||
import { css, SafeMarkdown, styled, SupersetTheme } from '@superset-ui/core';
|
||||
import { Tooltip } from './Tooltip';
|
||||
import { css, styled, SupersetTheme } from '@superset-ui/core';
|
||||
import {
|
||||
SafeMarkdown,
|
||||
Tooltip,
|
||||
InfoTooltip,
|
||||
} from '@superset-ui/core/components';
|
||||
import { ColumnTypeLabel } from './ColumnTypeLabel/ColumnTypeLabel';
|
||||
import CertifiedIconWithTooltip from './CertifiedIconWithTooltip';
|
||||
import { ColumnMeta } from '../types';
|
||||
@@ -28,7 +32,6 @@ import {
|
||||
getColumnTypeTooltipNode,
|
||||
} from './labelUtils';
|
||||
import { SQLPopover } from './SQLPopover';
|
||||
import InfoTooltipWithTrigger from './InfoTooltipWithTrigger';
|
||||
|
||||
export type ColumnOptionProps = {
|
||||
column: ColumnMeta;
|
||||
@@ -40,7 +43,7 @@ const StyleOverrides = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
svg {
|
||||
margin-right: ${({ theme }) => theme.gridUnit}px;
|
||||
margin-right: ${({ theme }) => theme.sizeUnit}px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -82,7 +85,7 @@ export function ColumnOption({
|
||||
<span
|
||||
className="option-label column-option-label"
|
||||
css={(theme: SupersetTheme) => css`
|
||||
margin-right: ${theme.gridUnit}px;
|
||||
margin-right: ${theme.sizeUnit}px;
|
||||
`}
|
||||
ref={labelRef}
|
||||
>
|
||||
@@ -98,15 +101,13 @@ export function ColumnOption({
|
||||
/>
|
||||
)}
|
||||
{warningMarkdown && (
|
||||
<InfoTooltipWithTrigger
|
||||
className="text-warning"
|
||||
icon="warning"
|
||||
<InfoTooltip
|
||||
type="warning"
|
||||
tooltip={<SafeMarkdown source={warningMarkdown} />}
|
||||
label={`warn-${column.column_name}`}
|
||||
iconsStyle={{ marginLeft: 0 }}
|
||||
iconStyle={{ marginLeft: 0 }}
|
||||
{...(column.error_text && {
|
||||
className: 'text-danger',
|
||||
icon: 'exclamation-circle',
|
||||
type: 'error',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -39,9 +39,9 @@ const TypeIconWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: ${theme.gridUnit * 6}px;
|
||||
height: ${theme.gridUnit * 6}px;
|
||||
margin-right: ${theme.gridUnit}px;
|
||||
width: ${theme.sizeUnit * 6}px;
|
||||
height: ${theme.sizeUnit * 6}px;
|
||||
margin-right: ${theme.sizeUnit}px;
|
||||
|
||||
&& svg {
|
||||
margin-right: 0;
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { ReactNode } from 'react';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { InfoTooltipWithTrigger } from './InfoTooltipWithTrigger';
|
||||
import { Tooltip } from './Tooltip';
|
||||
import { t, css } from '@superset-ui/core';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { InfoTooltip, Tooltip } from '@superset-ui/core/components';
|
||||
|
||||
type ValidationError = string;
|
||||
|
||||
@@ -60,7 +60,7 @@ export function ControlHeader({
|
||||
<span>
|
||||
{description && (
|
||||
<span>
|
||||
<InfoTooltipWithTrigger
|
||||
<InfoTooltip
|
||||
label={t('description')}
|
||||
tooltip={description}
|
||||
placement="top"
|
||||
@@ -70,11 +70,11 @@ export function ControlHeader({
|
||||
)}
|
||||
{renderTrigger && (
|
||||
<span>
|
||||
<InfoTooltipWithTrigger
|
||||
<InfoTooltip
|
||||
label={t('bolt')}
|
||||
tooltip={t('Changing this control takes effect instantly')}
|
||||
placement="top"
|
||||
icon="bolt"
|
||||
type="notice"
|
||||
/>{' '}
|
||||
</span>
|
||||
)}
|
||||
@@ -88,6 +88,7 @@ export function ControlHeader({
|
||||
return null;
|
||||
}
|
||||
const labelClass = validationErrors.length > 0 ? 'text-danger' : '';
|
||||
|
||||
return (
|
||||
<div className="ControlHeader" data-test={`${name}-header`}>
|
||||
<div className="pull-left">
|
||||
@@ -98,25 +99,30 @@ export function ControlHeader({
|
||||
tabIndex={0}
|
||||
onClick={onClick}
|
||||
className={labelClass}
|
||||
style={{ cursor: onClick ? 'pointer' : '' }}
|
||||
>
|
||||
{label}
|
||||
</span>{' '}
|
||||
{warning && (
|
||||
<span>
|
||||
<Tooltip id="error-tooltip" placement="top" title={warning}>
|
||||
{/* TODO: Remove fa-icon */}
|
||||
{/* eslint-disable-next-line icons/no-fa-icons-usage */}
|
||||
<i className="fa fa-exclamation-circle text-warning" />
|
||||
<InfoCircleOutlined
|
||||
css={theme => css`
|
||||
font-size: ${theme.sizeUnit * 3}px;
|
||||
color: ${theme.colorError};
|
||||
`}
|
||||
/>
|
||||
</Tooltip>{' '}
|
||||
</span>
|
||||
)}
|
||||
{danger && (
|
||||
<span>
|
||||
<Tooltip id="error-tooltip" placement="top" title={danger}>
|
||||
{/* TODO: Remove fa-icon */}
|
||||
{/* eslint-disable-next-line icons/no-fa-icons-usage */}
|
||||
<i className="fa fa-exclamation-circle text-danger" />
|
||||
<InfoCircleOutlined
|
||||
css={theme => css`
|
||||
font-size: ${theme.sizeUnit * 3}px;
|
||||
color: ${theme.colorError};
|
||||
`}
|
||||
/>{' '}
|
||||
</Tooltip>{' '}
|
||||
</span>
|
||||
)}
|
||||
@@ -127,18 +133,17 @@ export function ControlHeader({
|
||||
placement="top"
|
||||
title={validationErrors.join(' ')}
|
||||
>
|
||||
{/* TODO: Remove fa-icon */}
|
||||
{/* eslint-disable-next-line icons/no-fa-icons-usage */}
|
||||
<i className="fa fa-exclamation-circle text-danger" />
|
||||
<InfoCircleOutlined
|
||||
css={theme => css`
|
||||
font-size: ${theme.sizeUnit * 3}px;
|
||||
color: ${theme.colorError};
|
||||
`}
|
||||
/>{' '}
|
||||
</Tooltip>{' '}
|
||||
</span>
|
||||
)}
|
||||
{renderOptionalIcons()}
|
||||
{required && (
|
||||
<span className="text-danger m-l-4">
|
||||
<strong>*</strong>
|
||||
</span>
|
||||
)}
|
||||
{required && <strong> *</strong>}
|
||||
</label>
|
||||
</div>
|
||||
{rightNode && <div className="pull-right">{rightNode}</div>}
|
||||
|
||||
@@ -20,9 +20,8 @@ import { styled, css } from '@superset-ui/core';
|
||||
|
||||
export const ControlSubSectionHeader = styled.div`
|
||||
${({ theme }) => css`
|
||||
font-weight: ${theme.typography.weights.bold};
|
||||
font-size: ${theme.typography.sizes.s};
|
||||
margin-bottom: ${theme.gridUnit}px;
|
||||
font-weight: ${theme.fontWeightStrong};
|
||||
margin-bottom: ${theme.sizeUnit}px;
|
||||
font-size: ${theme.fontSizeSM}px;
|
||||
`}
|
||||
`;
|
||||
export default ControlSubSectionHeader;
|
||||
|
||||
@@ -17,5 +17,4 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { Dropdown } from 'antd';
|
||||
export type { DropDownProps } from 'antd/lib/dropdown';
|
||||
export { Dropdown, type DropdownProps } from '@superset-ui/core/components';
|
||||
|
||||
@@ -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;
|
||||
@@ -17,5 +17,4 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { Menu } from 'antd';
|
||||
export type { MenuProps } from 'antd/lib/menu';
|
||||
export { Menu, type MenuProps } from '@superset-ui/core/components';
|
||||
|
||||
@@ -18,17 +18,16 @@
|
||||
*/
|
||||
import { useState, ReactNode, useLayoutEffect, RefObject } from 'react';
|
||||
|
||||
import { css, styled, Metric, SupersetTheme } from '@superset-ui/core';
|
||||
import {
|
||||
css,
|
||||
styled,
|
||||
Metric,
|
||||
SafeMarkdown,
|
||||
SupersetTheme,
|
||||
} from '@superset-ui/core';
|
||||
import InfoTooltipWithTrigger from './InfoTooltipWithTrigger';
|
||||
Typography,
|
||||
// TODO: somehow doesn't work with our main Tooltip (?)
|
||||
RawAntdTooltip as Tooltip,
|
||||
InfoTooltip,
|
||||
} from '@superset-ui/core/components';
|
||||
import { ColumnTypeLabel } from './ColumnTypeLabel/ColumnTypeLabel';
|
||||
import CertifiedIconWithTooltip from './CertifiedIconWithTooltip';
|
||||
import Tooltip from './Tooltip';
|
||||
import { getMetricTooltipNode } from './labelUtils';
|
||||
import { SQLPopover } from './SQLPopover';
|
||||
|
||||
@@ -37,7 +36,7 @@ const FlexRowContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
> svg {
|
||||
margin-right: ${({ theme }) => theme.gridUnit}px;
|
||||
margin-right: ${({ theme }) => theme.sizeUnit}px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -61,23 +60,26 @@ export function MetricOption({
|
||||
url = '',
|
||||
}: MetricOptionProps) {
|
||||
const verbose = metric.verbose_name || metric.metric_name || metric.label;
|
||||
const link = url ? (
|
||||
<a href={url} target={openInNewWindow ? '_blank' : ''} rel="noreferrer">
|
||||
{verbose}
|
||||
</a>
|
||||
) : (
|
||||
verbose
|
||||
);
|
||||
|
||||
const label = (
|
||||
<span
|
||||
className="option-label metric-option-label"
|
||||
css={(theme: SupersetTheme) => css`
|
||||
margin-right: ${theme.gridUnit}px;
|
||||
margin-right: ${theme.sizeUnit}px;
|
||||
`}
|
||||
ref={labelRef}
|
||||
>
|
||||
{link}
|
||||
{url ? (
|
||||
<Typography.Link
|
||||
href={url}
|
||||
target={openInNewWindow ? '_blank' : ''}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{verbose}
|
||||
</Typography.Link>
|
||||
) : (
|
||||
verbose
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -111,15 +113,13 @@ export function MetricOption({
|
||||
/>
|
||||
)}
|
||||
{warningMarkdown && (
|
||||
<InfoTooltipWithTrigger
|
||||
className="text-warning"
|
||||
icon="warning"
|
||||
<InfoTooltip
|
||||
type="warning"
|
||||
tooltip={<SafeMarkdown source={warningMarkdown} />}
|
||||
label={`warn-${metric.metric_name}`}
|
||||
iconsStyle={{ marginLeft: 0 }}
|
||||
iconStyle={{ marginLeft: 0 }}
|
||||
{...(metric.error_text && {
|
||||
className: 'text-danger',
|
||||
icon: 'exclamation-circle',
|
||||
type: 'error',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -17,19 +17,18 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Popover } from 'antd-v5';
|
||||
import { Popover, type PopoverProps } from '@superset-ui/core/components';
|
||||
import type ReactAce from 'react-ace';
|
||||
import type { PopoverProps } from 'antd-v5/lib/popover';
|
||||
import { CalculatorOutlined } from '@ant-design/icons';
|
||||
import { css, styled, useTheme, t } from '@superset-ui/core';
|
||||
|
||||
const StyledCalculatorIcon = styled(CalculatorOutlined)`
|
||||
${({ theme }) => css`
|
||||
color: ${theme.colors.grayscale.base};
|
||||
font-size: ${theme.typography.sizes.s}px;
|
||||
font-size: ${theme.fontSizeSM}px;
|
||||
& svg {
|
||||
margin-left: ${theme.gridUnit}px;
|
||||
margin-right: ${theme.gridUnit}px;
|
||||
margin-left: ${theme.sizeUnit}px;
|
||||
margin-right: ${theme.sizeUnit}px;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
@@ -65,9 +64,9 @@ export const SQLPopover = (props: PopoverProps & { sqlExpression: string }) => {
|
||||
readOnly
|
||||
wrapEnabled
|
||||
style={{
|
||||
border: `1px solid ${theme.colors.grayscale.light2}`,
|
||||
background: theme.colors.secondary.light5,
|
||||
maxWidth: theme.gridUnit * 100,
|
||||
border: `1px solid ${theme.colorBorder}`,
|
||||
background: theme.colorPrimaryBg,
|
||||
maxWidth: theme.sizeUnit * 100,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { useState, ReactNode } from 'react';
|
||||
import AntdSelect, { SelectProps as AntdSelectProps } from 'antd/lib/select';
|
||||
import {
|
||||
RawAntdSelect as AntdSelect,
|
||||
type RawAntdSelectProps as AntdSelectProps,
|
||||
} from '@superset-ui/core/components';
|
||||
|
||||
export const { Option }: any = AntdSelect;
|
||||
|
||||
@@ -35,7 +38,7 @@ export type SelectProps<VT> = Omit<AntdSelectProps<VT>, 'options'> & {
|
||||
export default function Select<VT extends string | number>({
|
||||
creatable,
|
||||
onSearch,
|
||||
dropdownMatchSelectWidth = false,
|
||||
popupMatchSelectWidth = false,
|
||||
minWidth = '100%',
|
||||
showSearch: showSearch_ = true,
|
||||
onChange,
|
||||
@@ -73,7 +76,7 @@ export default function Select<VT extends string | number>({
|
||||
|
||||
return (
|
||||
<AntdSelect<VT>
|
||||
dropdownMatchSelectWidth={dropdownMatchSelectWidth}
|
||||
popupMatchSelectWidth={popupMatchSelectWidth}
|
||||
showSearch={showSearch}
|
||||
onSearch={handleSearch}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -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;
|
||||
@@ -29,18 +29,18 @@ const TooltipSectionWrapper = styled.div`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
font-size: ${theme.typography.sizes.s}px;
|
||||
font-size: ${theme.fontSizeSM}px;
|
||||
line-height: 1.2;
|
||||
|
||||
&:not(:last-of-type) {
|
||||
margin-bottom: ${theme.gridUnit * 2}px;
|
||||
margin-bottom: ${theme.sizeUnit * 2}px;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const TooltipSectionLabel = styled.span`
|
||||
${({ theme }) => css`
|
||||
font-weight: ${theme.typography.weights.bold};
|
||||
font-weight: ${theme.fontWeightStrong};
|
||||
`}
|
||||
`;
|
||||
|
||||
|
||||
@@ -25,14 +25,12 @@ export * from './operators';
|
||||
// can't do `export * as sections from './sections'`, babel-transformer will fail
|
||||
export const sections = sectionsModule;
|
||||
|
||||
export * from './components/InfoTooltipWithTrigger';
|
||||
export * from './components/ColumnOption';
|
||||
export * from './components/ColumnTypeLabel/ColumnTypeLabel';
|
||||
export * from './components/ControlSubSectionHeader';
|
||||
export * from './components/Dropdown';
|
||||
export * from './components/Menu';
|
||||
export * from './components/MetricOption';
|
||||
export * from './components/Tooltip';
|
||||
export * from './components/ControlHeader';
|
||||
|
||||
export * from './shared-controls';
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { ReactNode } from 'react';
|
||||
import { JsonValue, useTheme } from '@superset-ui/core';
|
||||
import { JsonValue } from '@superset-ui/core';
|
||||
import { Radio } from '@superset-ui/core/components';
|
||||
import { ControlHeader } from '../../components/ControlHeader';
|
||||
|
||||
// [value, label]
|
||||
@@ -42,51 +43,19 @@ export default function RadioButtonControl({
|
||||
...props
|
||||
}: RadioButtonControlProps) {
|
||||
const currentValue = initialValue || options[0][0];
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<div
|
||||
css={{
|
||||
'.btn svg': {
|
||||
position: 'relative',
|
||||
top: '0.2em',
|
||||
},
|
||||
'.btn:focus': {
|
||||
outline: 'none',
|
||||
},
|
||||
'.control-label': {
|
||||
color: theme.colors.grayscale.base,
|
||||
marginBottom: theme.gridUnit,
|
||||
},
|
||||
'.control-label + .btn-group': {
|
||||
marginTop: '1px',
|
||||
},
|
||||
'.btn-group .btn-default': {
|
||||
color: theme.colors.grayscale.dark1,
|
||||
},
|
||||
'.btn-group .btn.active': {
|
||||
background: theme.colors.grayscale.light4,
|
||||
fontWeight: theme.typography.weights.bold,
|
||||
boxShadow: 'none',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<ControlHeader {...props} />
|
||||
<div className="btn-group btn-group-sm">
|
||||
<Radio.Group
|
||||
value={currentValue}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
>
|
||||
{options.map(([val, label]) => (
|
||||
<button
|
||||
key={JSON.stringify(val)}
|
||||
type="button"
|
||||
className={`btn btn-default ${
|
||||
val === currentValue ? 'active' : ''
|
||||
}`}
|
||||
onClick={() => {
|
||||
onChange(val);
|
||||
}}
|
||||
>
|
||||
<Radio.Button key={JSON.stringify(val)} value={val}>
|
||||
{label}
|
||||
</button>
|
||||
</Radio.Button>
|
||||
))}
|
||||
</div>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ export interface FilterOption<T extends SelectOption> {
|
||||
data: T;
|
||||
}
|
||||
|
||||
// Ref: superset-frontend/src/components/Select/SupersetStyledSelect.tsx
|
||||
// Ref: superset-frontend/@superset-ui/core/components/Select/SupersetStyledSelect.tsx
|
||||
export interface SelectControlConfig<
|
||||
O extends SelectOption = SelectOption,
|
||||
T extends SelectControlType = SelectControlType,
|
||||
|
||||
@@ -17,26 +17,26 @@
|
||||
* under the License.
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import {
|
||||
ThemeProvider,
|
||||
supersetTheme,
|
||||
GenericDataType,
|
||||
} from '@superset-ui/core';
|
||||
import { render } from '@superset-ui/core/spec';
|
||||
import { GenericDataType } from '@superset-ui/core';
|
||||
|
||||
import { ColumnOption, ColumnOptionProps } from '../../src';
|
||||
|
||||
jest.mock('../../src/components/SQLPopover', () => ({
|
||||
jest.mock('@superset-ui/chart-controls/components/SQLPopover', () => ({
|
||||
SQLPopover: () => <div data-test="mock-sql-popover" />,
|
||||
}));
|
||||
jest.mock('../../src/components/ColumnTypeLabel/ColumnTypeLabel', () => ({
|
||||
ColumnTypeLabel: ({ type }: { type: string }) => (
|
||||
<div data-test="mock-column-type-label">{type}</div>
|
||||
),
|
||||
jest.mock(
|
||||
'@superset-ui/chart-controls/components/ColumnTypeLabel/ColumnTypeLabel',
|
||||
() => ({
|
||||
ColumnTypeLabel: ({ type }: { type: string }) => (
|
||||
<div data-test="mock-column-type-label">{type}</div>
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
jest.mock('@superset-ui/core/components/InfoTooltip', () => ({
|
||||
InfoTooltip: () => <div data-test="mock-tooltip" />,
|
||||
}));
|
||||
jest.mock('../../src/components/InfoTooltipWithTrigger', () => () => (
|
||||
<div data-test="mock-info-tooltip-with-trigger" />
|
||||
));
|
||||
|
||||
const defaultProps: ColumnOptionProps = {
|
||||
column: {
|
||||
@@ -49,11 +49,7 @@ const defaultProps: ColumnOptionProps = {
|
||||
};
|
||||
|
||||
const setup = (props: Partial<ColumnOptionProps> = {}) =>
|
||||
render(
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
<ColumnOption {...defaultProps} {...props} />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
render(<ColumnOption {...defaultProps} {...props} />);
|
||||
test('shows a label with verbose_name', () => {
|
||||
const { container } = setup();
|
||||
const lbl = container.getElementsByClassName('option-label');
|
||||
@@ -114,11 +110,11 @@ test('dttm column has correct column label if showType is true', () => {
|
||||
String(GenericDataType.Temporal),
|
||||
);
|
||||
});
|
||||
test('doesnt show InfoTooltipWithTrigger when no warning', () => {
|
||||
test('doesnt show InfoTooltip when no warning', () => {
|
||||
const { queryByText } = setup();
|
||||
expect(queryByText('mock-info-tooltip-with-trigger')).not.toBeInTheDocument();
|
||||
expect(queryByText('mock-tooltip')).not.toBeInTheDocument();
|
||||
});
|
||||
test('shows a warning with InfoTooltipWithTrigger when it contains warning', () => {
|
||||
test('shows a warning with InfoTooltip when it contains warning', () => {
|
||||
const { getByTestId } = setup({
|
||||
...defaultProps,
|
||||
column: {
|
||||
@@ -126,5 +122,5 @@ test('shows a warning with InfoTooltipWithTrigger when it contains warning', ()
|
||||
warning_text: 'This is a warning',
|
||||
},
|
||||
});
|
||||
expect(getByTestId('mock-info-tooltip-with-trigger')).toBeInTheDocument();
|
||||
expect(getByTestId('mock-tooltip')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { isValidElement } from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render, screen } from '@superset-ui/core/spec';
|
||||
import '@testing-library/jest-dom';
|
||||
import { GenericDataType } from '@superset-ui/core';
|
||||
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
* under the License.
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
|
||||
import { InfoTooltipWithTrigger, InfoTooltipWithTriggerProps } from '../../src';
|
||||
import { fireEvent, render } from '@superset-ui/core/spec';
|
||||
import { InfoTooltip, InfoTooltipProps } from '@superset-ui/core/components';
|
||||
|
||||
jest.mock('../../src/components/Tooltip', () => ({
|
||||
jest.mock('@superset-ui/core/components/Tooltip', () => ({
|
||||
Tooltip: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-test="mock-tooltip">{children}</div>
|
||||
),
|
||||
@@ -29,12 +28,8 @@ jest.mock('../../src/components/Tooltip', () => ({
|
||||
|
||||
const defaultProps = {};
|
||||
|
||||
const setup = (props: Partial<InfoTooltipWithTriggerProps> = {}) =>
|
||||
render(
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
<InfoTooltipWithTrigger {...defaultProps} {...props} />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
const setup = (props: Partial<InfoTooltipProps> = {}) =>
|
||||
render(<InfoTooltip {...defaultProps} {...props} />);
|
||||
|
||||
test('renders a tooltip', () => {
|
||||
const { getAllByTestId } = setup({
|
||||
@@ -44,31 +39,29 @@ test('renders a tooltip', () => {
|
||||
expect(getAllByTestId('mock-tooltip').length).toEqual(1);
|
||||
});
|
||||
|
||||
test('renders an info icon', () => {
|
||||
const { container } = setup();
|
||||
expect(container.getElementsByClassName('fa-info-circle')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('responds to keypresses', () => {
|
||||
test('responds to keydown events', () => {
|
||||
const clickHandler = jest.fn();
|
||||
const { getByRole } = setup({
|
||||
label: 'test',
|
||||
tooltip: 'this is a test',
|
||||
onClick: clickHandler,
|
||||
});
|
||||
fireEvent.keyPress(getByRole('button'), {
|
||||
|
||||
fireEvent.keyDown(getByRole('button'), {
|
||||
key: 'Tab',
|
||||
code: 9,
|
||||
charCode: 9,
|
||||
});
|
||||
expect(clickHandler).toHaveBeenCalledTimes(0);
|
||||
fireEvent.keyPress(getByRole('button'), {
|
||||
|
||||
fireEvent.keyDown(getByRole('button'), {
|
||||
key: 'Enter',
|
||||
code: 13,
|
||||
charCode: 13,
|
||||
});
|
||||
expect(clickHandler).toHaveBeenCalledTimes(1);
|
||||
fireEvent.keyPress(getByRole('button'), {
|
||||
|
||||
fireEvent.keyDown(getByRole('button'), {
|
||||
key: ' ',
|
||||
code: 32,
|
||||
charCode: 32,
|
||||
@@ -76,9 +69,47 @@ test('responds to keypresses', () => {
|
||||
expect(clickHandler).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('has a bsStyle', () => {
|
||||
test('finds the info circle icon inside info variant', () => {
|
||||
const { container } = setup({
|
||||
bsStyle: 'something',
|
||||
type: 'info',
|
||||
});
|
||||
expect(container.getElementsByClassName('text-something')).toHaveLength(1);
|
||||
|
||||
const iconSpan = container.querySelector('svg[data-icon="info-circle"]');
|
||||
expect(iconSpan).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('finds the warning icon inside warning variant', () => {
|
||||
const { container } = setup({
|
||||
type: 'warning',
|
||||
});
|
||||
|
||||
const iconSpan = container.querySelector('svg[data-icon="warning"]');
|
||||
expect(iconSpan).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('finds the close circle icon inside error variant', () => {
|
||||
const { container } = setup({
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
const iconSpan = container.querySelector('svg[data-icon="close-circle"]');
|
||||
expect(iconSpan).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('finds the question circle icon inside question variant', () => {
|
||||
const { container } = setup({
|
||||
type: 'question',
|
||||
});
|
||||
|
||||
const iconSpan = container.querySelector('svg[data-icon="question-circle"]');
|
||||
expect(iconSpan).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('finds the thunderbolt icon inside notice variant', () => {
|
||||
const { container } = setup({
|
||||
type: 'notice',
|
||||
});
|
||||
|
||||
const iconSpan = container.querySelector('svg[data-icon="thunderbolt"]');
|
||||
expect(iconSpan).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -17,24 +17,30 @@
|
||||
* under the License.
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
|
||||
import { MetricOption, MetricOptionProps } from '../../src';
|
||||
import { render } from '@superset-ui/core/spec';
|
||||
import {
|
||||
MetricOption,
|
||||
MetricOptionProps,
|
||||
} from '../../src/components/MetricOption';
|
||||
|
||||
jest.mock('../../src/components/InfoTooltipWithTrigger', () => () => (
|
||||
<div data-test="mock-info-tooltip-with-trigger" />
|
||||
));
|
||||
jest.mock('../../src/components/ColumnTypeLabel/ColumnTypeLabel', () => ({
|
||||
ColumnTypeLabel: () => <div data-test="mock-column-type-label" />,
|
||||
jest.mock('@superset-ui/core/components/InfoTooltip', () => ({
|
||||
InfoTooltip: () => <div data-test="mock-tooltip" />,
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'../../src/components/Tooltip',
|
||||
'@superset-ui/chart-controls/components/ColumnTypeLabel/ColumnTypeLabel',
|
||||
() => ({
|
||||
ColumnTypeLabel: () => <div data-test="mock-column-type-label" />,
|
||||
}),
|
||||
);
|
||||
jest.mock(
|
||||
'@superset-ui/core/components/Tooltip',
|
||||
() =>
|
||||
({ children }: { children: React.ReactNode }) => (
|
||||
<div data-test="mock-tooltip">{children}</div>
|
||||
),
|
||||
);
|
||||
jest.mock('../../src/components/SQLPopover', () => ({
|
||||
jest.mock('@superset-ui/chart-controls/components/SQLPopover', () => ({
|
||||
SQLPopover: () => <div data-test="mock-sql-popover" />,
|
||||
}));
|
||||
|
||||
@@ -54,20 +60,16 @@ const defaultProps = {
|
||||
};
|
||||
|
||||
const setup = (props: Partial<MetricOptionProps> = {}) =>
|
||||
render(
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
<MetricOption {...defaultProps} {...props} />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
render(<MetricOption {...defaultProps} {...props} />);
|
||||
test('shows a label with verbose_name', () => {
|
||||
const { container } = setup();
|
||||
const lbl = container.getElementsByClassName('option-label');
|
||||
expect(lbl).toHaveLength(1);
|
||||
expect(`${lbl[0].textContent}`).toEqual(defaultProps.metric.verbose_name);
|
||||
});
|
||||
test('shows a InfoTooltipWithTrigger', () => {
|
||||
test('shows a InfoTooltip', () => {
|
||||
const { getByTestId } = setup();
|
||||
expect(getByTestId('mock-info-tooltip-with-trigger')).toBeInTheDocument();
|
||||
expect(getByTestId('mock-tooltip')).toBeInTheDocument();
|
||||
});
|
||||
test('shows SQL Popover trigger', () => {
|
||||
const { getByTestId } = setup();
|
||||
@@ -82,14 +84,14 @@ test('shows a label with metric_name when no verbose_name', () => {
|
||||
});
|
||||
expect(getByText(defaultProps.metric.metric_name)).toBeInTheDocument();
|
||||
});
|
||||
test('doesnt show InfoTooltipWithTrigger when no warning', () => {
|
||||
test('doesnt show InfoTooltip when no warning', () => {
|
||||
const { queryByText } = setup({
|
||||
metric: {
|
||||
...defaultProps.metric,
|
||||
warning_text: '',
|
||||
},
|
||||
});
|
||||
expect(queryByText('mock-info-tooltip-with-trigger')).not.toBeInTheDocument();
|
||||
expect(queryByText('mock-tooltip')).not.toBeInTheDocument();
|
||||
});
|
||||
test('sets target="_blank" when openInNewWindow is true', () => {
|
||||
const { getByRole } = setup({
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ReactElement } from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render, screen } from '@superset-ui/core/spec';
|
||||
import '@testing-library/jest-dom';
|
||||
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
|
||||
import {
|
||||
getColumnLabelText,
|
||||
getColumnTooltipNode,
|
||||
@@ -27,9 +25,6 @@ import {
|
||||
getColumnTypeTooltipNode,
|
||||
} from '../../src/components/labelUtils';
|
||||
|
||||
const renderWithTheme = (ui: ReactElement) =>
|
||||
render(<ThemeProvider theme={supersetTheme}>{ui}</ThemeProvider>);
|
||||
|
||||
test("should get column name when column doesn't have verbose_name", () => {
|
||||
expect(
|
||||
getColumnLabelText({
|
||||
@@ -78,7 +73,7 @@ test('should get null for column datatype tooltip when type is blank', () => {
|
||||
});
|
||||
|
||||
test('should get column datatype rendered as tooltip when column has a type', () => {
|
||||
renderWithTheme(
|
||||
render(
|
||||
<>
|
||||
{getColumnTypeTooltipNode({
|
||||
id: 123,
|
||||
@@ -96,7 +91,7 @@ test('should get column datatype rendered as tooltip when column has a type', ()
|
||||
|
||||
test('should get column name, verbose name and description when it has a verbose name', () => {
|
||||
const ref = { current: { scrollWidth: 100, clientWidth: 100 } };
|
||||
renderWithTheme(
|
||||
render(
|
||||
<>
|
||||
{getColumnTooltipNode(
|
||||
{
|
||||
@@ -120,7 +115,7 @@ test('should get column name, verbose name and description when it has a verbose
|
||||
|
||||
test('should get column name as tooltip if it overflowed', () => {
|
||||
const ref = { current: { scrollWidth: 200, clientWidth: 100 } };
|
||||
renderWithTheme(
|
||||
render(
|
||||
<>
|
||||
{getColumnTooltipNode(
|
||||
{
|
||||
@@ -141,7 +136,7 @@ test('should get column name as tooltip if it overflowed', () => {
|
||||
|
||||
test('should get column name, verbose name and description as tooltip if it overflowed', () => {
|
||||
const ref = { current: { scrollWidth: 200, clientWidth: 100 } };
|
||||
renderWithTheme(
|
||||
render(
|
||||
<>
|
||||
{getColumnTooltipNode(
|
||||
{
|
||||
@@ -180,7 +175,7 @@ test('should get null as tooltip in metric', () => {
|
||||
|
||||
test('should get metric name, verbose name and description as tooltip in metric', () => {
|
||||
const ref = { current: { scrollWidth: 100, clientWidth: 100 } };
|
||||
renderWithTheme(
|
||||
render(
|
||||
<>
|
||||
{getMetricTooltipNode(
|
||||
{
|
||||
@@ -203,7 +198,7 @@ test('should get metric name, verbose name and description as tooltip in metric'
|
||||
|
||||
test('should get metric name as tooltip if it overflowed', () => {
|
||||
const ref = { current: { scrollWidth: 200, clientWidth: 100 } };
|
||||
renderWithTheme(
|
||||
render(
|
||||
<>
|
||||
{getMetricTooltipNode(
|
||||
{
|
||||
@@ -224,7 +219,7 @@ test('should get metric name as tooltip if it overflowed', () => {
|
||||
|
||||
test('should get metric name, verbose name and description in tooltip if it overflowed', () => {
|
||||
const ref = { current: { scrollWidth: 200, clientWidth: 100 } };
|
||||
renderWithTheme(
|
||||
render(
|
||||
<>
|
||||
{getMetricTooltipNode(
|
||||
{
|
||||
|
||||
68
superset-frontend/packages/superset-ui-core/.eslintrc
Normal file
68
superset-frontend/packages/superset-ui-core/.eslintrc
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
module.exports = {};
|
||||
@@ -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.
|
||||
*/
|
||||
module.exports = 'test-file-stub';
|
||||
@@ -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;
|
||||
@@ -24,21 +24,37 @@
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@babel/runtime": "^7.25.6",
|
||||
"@fontsource/fira-code": "^5.0.18",
|
||||
"@fontsource/inter": "^5.0.20",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"ace-builds": "^1.41.0",
|
||||
"brace": "^0.11.1",
|
||||
"classnames": "^2.2.5",
|
||||
"csstype": "^3.1.3",
|
||||
"core-js": "^3.38.1",
|
||||
"d3-format": "^1.3.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^3.0.0",
|
||||
"d3-time": "^3.1.0",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"dompurify": "^3.2.4",
|
||||
"fetch-retry": "^6.0.0",
|
||||
"jed": "^1.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"math-expression-evaluator": "^2.0.6",
|
||||
"pretty-ms": "^9.2.0",
|
||||
"re-resizable": "^6.10.1",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-js-cron": "^5.2.0",
|
||||
"react-draggable": "^4.4.6",
|
||||
"react-resize-detector": "^7.1.2",
|
||||
"react-ultimate-pagination": "^1.3.2",
|
||||
"react-error-boundary": "^5.0.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"regenerator-runtime": "^0.14.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
@@ -56,7 +72,9 @@
|
||||
"@types/d3-scale": "^2.1.1",
|
||||
"@types/d3-time": "^3.0.4",
|
||||
"@types/d3-time-format": "^4.0.3",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@types/fetch-mock": "^7.3.8",
|
||||
"@types/jquery": "^3.5.8",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/math-expression-evaluator": "^1.3.3",
|
||||
"@types/node": "^22.10.3",
|
||||
@@ -69,6 +87,7 @@
|
||||
"timezone-mock": "1.3.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"antd": "^5.24.6",
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
@@ -79,13 +98,37 @@
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-loadable": "*",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/tinycolor2": "*",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./esm/index.js",
|
||||
"require": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts"
|
||||
},
|
||||
"./components/*": {
|
||||
"import": "./esm/components/*/index.js",
|
||||
"require": "./lib/components/*/index.js",
|
||||
"types": "./lib/components/*/index.d.ts"
|
||||
},
|
||||
"./components": {
|
||||
"import": "./esm/components/index.js",
|
||||
"require": "./lib/components/index.js",
|
||||
"types": "./lib/components/index.d.ts"
|
||||
},
|
||||
"./utils/*": {
|
||||
"import": "./esm/utils/*.js",
|
||||
"require": "./lib/utils/*.js",
|
||||
"types": "./lib/utils/*.d.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { CSSProperties, PureComponent, ReactNode } from 'react';
|
||||
import { CSSProperties, ReactNode } from 'react';
|
||||
import { Table, type TableColumnsType } from 'antd';
|
||||
|
||||
interface TooltipRowData {
|
||||
key: string | number;
|
||||
@@ -27,43 +27,52 @@ interface TooltipRowData {
|
||||
valueStyle?: CSSProperties;
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
className: '',
|
||||
data: [] as TooltipRowData[],
|
||||
};
|
||||
|
||||
type Props = {
|
||||
interface TooltipTableProps {
|
||||
className?: string;
|
||||
data: TooltipRowData[];
|
||||
} & Readonly<typeof defaultProps>;
|
||||
}
|
||||
|
||||
const VALUE_CELL_STYLE: CSSProperties = { paddingLeft: 8, textAlign: 'right' };
|
||||
|
||||
export default class TooltipTable extends PureComponent<Props, {}> {
|
||||
static defaultProps = defaultProps;
|
||||
const TooltipTable = ({ className = '', data }: TooltipTableProps) => {
|
||||
const columns: TableColumnsType<TooltipRowData> = [
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'keyColumn',
|
||||
key: 'keyColumn',
|
||||
render: (text, record) => (
|
||||
<div style={record.keyStyle}>{record.keyColumn ?? record.key}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'valueColumn',
|
||||
key: 'valueColumn',
|
||||
align: 'right',
|
||||
render: (text, record) => (
|
||||
<div
|
||||
style={
|
||||
record.valueStyle
|
||||
? { ...VALUE_CELL_STYLE, ...record.valueStyle }
|
||||
: VALUE_CELL_STYLE
|
||||
}
|
||||
>
|
||||
{record.valueColumn}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
const { className, data } = this.props;
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
pagination={false}
|
||||
showHeader={false}
|
||||
bordered={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<table className={className}>
|
||||
<tbody>
|
||||
{data.map(({ key, keyColumn, keyStyle, valueColumn, valueStyle }) => (
|
||||
<tr key={key}>
|
||||
<td style={keyStyle}>{keyColumn ?? key}</td>
|
||||
<td
|
||||
style={
|
||||
valueStyle
|
||||
? { ...VALUE_CELL_STYLE, ...valueStyle }
|
||||
: VALUE_CELL_STYLE
|
||||
}
|
||||
>
|
||||
{valueColumn}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default TooltipTable;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
import { t } from '@superset-ui/core';
|
||||
import { SupersetTheme } from '../../style';
|
||||
import { SupersetTheme } from '../..';
|
||||
import { FallbackPropsWithDimension } from './SuperChart';
|
||||
|
||||
export type Props = FallbackPropsWithDimension;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
import { CSSProperties } from 'react';
|
||||
import { css, styled } from '../../style';
|
||||
import { css, styled } from '../../theme';
|
||||
import { t } from '../../translation';
|
||||
|
||||
const MESSAGE_STYLES: CSSProperties = { maxWidth: 800 };
|
||||
@@ -36,16 +36,16 @@ const Container = styled.div<{
|
||||
text-align: center;
|
||||
height: ${height}px;
|
||||
width: ${width}px;
|
||||
padding: ${theme.gridUnit * 4}px;
|
||||
padding: ${theme.sizeUnit * 4}px;
|
||||
|
||||
& .no-results-title {
|
||||
font-size: ${theme.typography.sizes.l}px;
|
||||
font-weight: ${theme.typography.weights.bold};
|
||||
padding-bottom: ${theme.gridUnit * 2};
|
||||
font-size: ${theme.fontSizeLG}px;
|
||||
font-weight: ${theme.fontWeightStrong};
|
||||
padding-bottom: ${theme.sizeUnit * 2};
|
||||
}
|
||||
|
||||
& .no-results-body {
|
||||
font-size: ${theme.typography.sizes.m}px;
|
||||
font-size: ${theme.fontSize}px;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax -- whole React import is required for `reactify.test.tsx` Jest test passing.
|
||||
import React, { Component, ComponentClass, WeakValidationMap } from 'react';
|
||||
import { Component, ComponentClass, WeakValidationMap } from 'react';
|
||||
|
||||
// TODO: Note that id and className can collide between Props and ReactifyProps
|
||||
// leading to (likely) unexpected behaviors. We should either require Props to not
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
SetDataMaskHook,
|
||||
} from '../types/Base';
|
||||
import { QueryData, DataRecordFilters } from '..';
|
||||
import { SupersetTheme } from '../../style';
|
||||
import { SupersetTheme } from '../../theme';
|
||||
|
||||
// TODO: more specific typing for these fields of ChartProps
|
||||
type AnnotationData = PlainObject;
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import Alert, { AlertProps } from './index';
|
||||
import { Alert } from '.';
|
||||
import type { AlertProps } from './types';
|
||||
|
||||
type AlertType = Required<Pick<AlertProps, 'type'>>;
|
||||
type AlertTypeValue = AlertType['type'];
|
||||
@@ -30,7 +31,7 @@ const bigText =
|
||||
'purus convallis placerat in at nunc. Nulla nec viverra augue.';
|
||||
|
||||
export default {
|
||||
title: 'Alert',
|
||||
title: 'Components/Alert',
|
||||
component: Alert,
|
||||
};
|
||||
|
||||
@@ -16,15 +16,10 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { Alert as AntdAlert } from 'antd-v5';
|
||||
import { AlertProps as AntdAlertProps } from 'antd-v5/lib/alert';
|
||||
import { Alert as AntdAlert } from 'antd';
|
||||
import type { AlertProps } from './types';
|
||||
|
||||
export type AlertProps = PropsWithChildren<
|
||||
Omit<AntdAlertProps, 'children'> & { roomBelow?: boolean }
|
||||
>;
|
||||
|
||||
export default function Alert(props: AlertProps) {
|
||||
export const Alert = (props: AlertProps) => {
|
||||
const {
|
||||
type = 'info',
|
||||
description,
|
||||
@@ -46,4 +41,6 @@ export default function Alert(props: AlertProps) {
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export type { AlertProps };
|
||||
@@ -16,12 +16,9 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import type { AlertProps as AntdAlertProps } from 'antd/es/alert';
|
||||
|
||||
import { Divider as AntdDivider } from 'antd-v5';
|
||||
import type { DividerProps } from 'antd-v5/es/divider';
|
||||
|
||||
export function Divider(props: DividerProps) {
|
||||
return <AntdDivider {...props} />;
|
||||
}
|
||||
|
||||
export { DividerProps };
|
||||
export type AlertProps = PropsWithChildren<
|
||||
Omit<AntdAlertProps, 'children'> & { roomBelow?: boolean }
|
||||
>;
|
||||
@@ -16,8 +16,12 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { SwitchProps } from 'antd-v5/lib/switch';
|
||||
import { Switch as AntdSwitch } from 'antd-v5';
|
||||
|
||||
export const Switch = (props: SwitchProps) => <AntdSwitch {...props} />;
|
||||
export type { SwitchProps };
|
||||
import { ConfigProvider, type ConfigProviderProps } from 'antd';
|
||||
|
||||
export const AntdThemeProvider = ({
|
||||
children,
|
||||
...rest
|
||||
}: ConfigProviderProps) => (
|
||||
<ConfigProvider {...rest}>{children}</ConfigProvider>
|
||||
);
|
||||
@@ -24,9 +24,10 @@ import {
|
||||
CssEditor,
|
||||
JsonEditor,
|
||||
ConfigEditor,
|
||||
AsyncAceEditorOptions,
|
||||
} from '.';
|
||||
|
||||
import type { AsyncAceEditorOptions } from './types';
|
||||
|
||||
type EditorType =
|
||||
| 'sql'
|
||||
| 'full-sql'
|
||||
@@ -47,7 +48,7 @@ const editorTypes: EditorType[] = [
|
||||
];
|
||||
|
||||
export default {
|
||||
title: 'AsyncAceEditor',
|
||||
title: 'Components/AsyncAceEditor',
|
||||
};
|
||||
|
||||
const parseEditorType = (editorType: EditorType) => {
|
||||
@@ -16,8 +16,9 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { render, screen, waitFor } from 'spec/helpers/testing-library';
|
||||
import AsyncAceEditor, {
|
||||
import { render, screen, waitFor } from '@superset-ui/core/spec';
|
||||
import {
|
||||
AsyncAceEditor,
|
||||
SQLEditor,
|
||||
FullSQLEditor,
|
||||
MarkdownEditor,
|
||||
@@ -25,9 +26,9 @@ import AsyncAceEditor, {
|
||||
CssEditor,
|
||||
JsonEditor,
|
||||
ConfigEditor,
|
||||
AceModule,
|
||||
AsyncAceEditorOptions,
|
||||
} from 'src/components/AsyncAceEditor';
|
||||
} from '.';
|
||||
|
||||
import type { AceModule, AsyncAceEditorOptions } from './types';
|
||||
|
||||
const selector = '[id="ace-editor"]';
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
type Props = {
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { forwardRef, useEffect, ComponentType } from 'react';
|
||||
import { forwardRef, useEffect, useCallback, ComponentType } from 'react';
|
||||
|
||||
import type {
|
||||
Editor as OrigEditor,
|
||||
@@ -27,10 +27,10 @@ import type {
|
||||
import type AceEditor from 'react-ace';
|
||||
import type { IAceEditorProps } from 'react-ace';
|
||||
|
||||
import AsyncEsmComponent, {
|
||||
import {
|
||||
AsyncEsmComponent,
|
||||
PlaceholderProps,
|
||||
} from 'src/components/AsyncEsmComponent';
|
||||
import useEffectEvent from 'src/hooks/useEffectEvent';
|
||||
} from '@superset-ui/core/components/AsyncEsmComponent';
|
||||
import { useTheme, css } from '@superset-ui/core';
|
||||
import { Global } from '@emotion/react';
|
||||
|
||||
@@ -67,7 +67,7 @@ export interface AceCompleterKeyword extends AceCompleterKeywordData {
|
||||
* Async loaders to import brace modules. Must manually create call `import(...)`
|
||||
* promises because webpack can only analyze async imports statically.
|
||||
*/
|
||||
const aceModuleLoaders = {
|
||||
export const aceModuleLoaders = {
|
||||
'mode/sql': () => import('brace/mode/sql'),
|
||||
'mode/markdown': () => import('brace/mode/markdown'),
|
||||
'mode/css': () => import('brace/mode/css'),
|
||||
@@ -102,7 +102,7 @@ export type AsyncAceEditorOptions = {
|
||||
/**
|
||||
* Get an async AceEditor with automatical loading of specified ace modules.
|
||||
*/
|
||||
export default function AsyncAceEditor(
|
||||
export function AsyncAceEditor(
|
||||
aceModules: AceModule[],
|
||||
{
|
||||
defaultMode,
|
||||
@@ -155,9 +155,10 @@ export default function AsyncAceEditor(
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const supersetTheme = useTheme();
|
||||
const token = useTheme();
|
||||
const langTools = acequire('ace/ext/language_tools');
|
||||
const setCompleters = useEffectEvent(
|
||||
|
||||
const setCompleters = useCallback(
|
||||
(keywords: AceCompleterKeyword[]) => {
|
||||
const completer = {
|
||||
getCompletions: (
|
||||
@@ -180,7 +181,9 @@ export default function AsyncAceEditor(
|
||||
};
|
||||
langTools.setCompleters([completer]);
|
||||
},
|
||||
[langTools, mode],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (keywords) {
|
||||
setCompleters(keywords);
|
||||
@@ -192,36 +195,148 @@ export default function AsyncAceEditor(
|
||||
<Global
|
||||
key="ace-tooltip-global"
|
||||
styles={css`
|
||||
.ace_editor {
|
||||
border: 1px solid ${token.colorBorder} !important;
|
||||
background-color: ${token.colorBgContainer} !important;
|
||||
}
|
||||
|
||||
/* Basic editor styles with dark mode support */
|
||||
.ace_editor.ace-github,
|
||||
.ace_editor.ace-textmate {
|
||||
background-color: ${token.colorBgContainer} !important;
|
||||
color: ${token.colorText} !important;
|
||||
}
|
||||
|
||||
/* Adjust gutter colors */
|
||||
.ace_editor .ace_gutter {
|
||||
background-color: ${token.colorBgElevated} !important;
|
||||
color: ${token.colorTextSecondary} !important;
|
||||
}
|
||||
|
||||
/* Adjust selection color */
|
||||
.ace_editor .ace_selection {
|
||||
background-color: ${token.colorPrimaryBgHover} !important;
|
||||
}
|
||||
|
||||
/* Improve active line highlighting */
|
||||
.ace_editor .ace_active-line {
|
||||
background-color: ${token.colorPrimaryBg} !important;
|
||||
}
|
||||
|
||||
/* Fix indent guides and print margin (80 chars line) */
|
||||
.ace_editor .ace_indent-guide,
|
||||
.ace_editor .ace_print-margin {
|
||||
background-color: ${token.colorSplit} !important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Adjust cursor color */
|
||||
.ace_editor .ace_cursor {
|
||||
color: ${token.colorPrimaryText} !important;
|
||||
}
|
||||
|
||||
/* Syntax highlighting using semantic color tokens */
|
||||
.ace_editor .ace_keyword {
|
||||
color: ${token.colorPrimaryText} !important;
|
||||
}
|
||||
|
||||
.ace_editor .ace_string {
|
||||
color: ${token.colorSuccessText} !important;
|
||||
}
|
||||
|
||||
.ace_editor .ace_constant {
|
||||
color: ${token.colorWarningActive} !important;
|
||||
}
|
||||
|
||||
.ace_editor .ace_function {
|
||||
color: ${token.colorInfoText} !important;
|
||||
}
|
||||
|
||||
.ace_editor .ace_comment {
|
||||
color: ${token.colorTextTertiary} !important;
|
||||
}
|
||||
|
||||
.ace_editor .ace_variable {
|
||||
color: ${token.colorTextSecondary} !important;
|
||||
}
|
||||
|
||||
/* Adjust tooltip styles */
|
||||
.ace_tooltip {
|
||||
all: unset;
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background: ${supersetTheme.colors.grayscale.light5};
|
||||
border: 1px solid ${supersetTheme.colors.grayscale.light1};
|
||||
padding: ${supersetTheme.gridUnit}px
|
||||
${supersetTheme.gridUnit * 2}px;
|
||||
line-height: 1.4;
|
||||
max-width: 400px;
|
||||
min-width: 200px;
|
||||
pointer-events: auto;
|
||||
font-size: ${supersetTheme.typography.sizes.m}px;
|
||||
margin-left: ${token.margin}px;
|
||||
padding: 0px;
|
||||
background-color: ${token.colorBgElevated} !important;
|
||||
color: ${token.colorText} !important;
|
||||
border: 1px solid ${token.colorBorderSecondary};
|
||||
box-shadow: ${token.boxShadow};
|
||||
border-radius: ${token.borderRadius}px;
|
||||
}
|
||||
|
||||
& .tooltip-detail {
|
||||
background-color: ${token.colorBgContainer};
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
min-width: ${token.sizeXXL * 5}px;
|
||||
max-width: ${token.sizeXXL * 10}px;
|
||||
|
||||
& .tooltip-detail-head {
|
||||
background-color: ${token.colorBgElevated};
|
||||
color: ${token.colorText};
|
||||
display: flex;
|
||||
column-gap: ${token.padding}px;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
& .tooltip-detail-title {
|
||||
font-weight: bold;
|
||||
font-size: ${supersetTheme.typography.sizes.m}px;
|
||||
display: flex;
|
||||
column-gap: ${token.padding}px;
|
||||
}
|
||||
|
||||
& .tooltip-detail-body {
|
||||
font-size: ${supersetTheme.typography.sizes.s}px;
|
||||
padding: ${supersetTheme.gridUnit}px;
|
||||
word-break: break-word;
|
||||
color: ${token.colorTextSecondary};
|
||||
}
|
||||
|
||||
& .tooltip-detail-head,
|
||||
& .tooltip-detail-body {
|
||||
padding: ${token.padding}px ${token.paddingLG}px;
|
||||
}
|
||||
|
||||
& .tooltip-detail-footer {
|
||||
font-size: ${supersetTheme.typography.sizes.s}px;
|
||||
border-top: 1px ${token.colorSplit} solid;
|
||||
padding: 0 ${token.paddingLG}px;
|
||||
color: ${token.colorTextTertiary};
|
||||
font-size: ${token.fontSizeSM}px;
|
||||
}
|
||||
|
||||
& .tooltip-detail-meta {
|
||||
& > .ant-tag {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Adjust the searchbox to match theme */
|
||||
.ace_search {
|
||||
background-color: ${token.colorBgContainer} !important;
|
||||
color: ${token.colorText} !important;
|
||||
border: 1px solid ${token.colorBorder} !important;
|
||||
}
|
||||
|
||||
.ace_search_field {
|
||||
background-color: ${token.colorBgContainer} !important;
|
||||
color: ${token.colorText} !important;
|
||||
border: 1px solid ${token.colorBorder} !important;
|
||||
}
|
||||
|
||||
.ace_button {
|
||||
background-color: ${token.colorBgElevated} !important;
|
||||
color: ${token.colorText} !important;
|
||||
border: 1px solid ${token.colorBorder} !important;
|
||||
}
|
||||
|
||||
.ace_button:hover {
|
||||
background-color: ${token.colorPrimaryBg} !important;
|
||||
}
|
||||
`}
|
||||
/>
|
||||
@@ -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;
|
||||
};
|
||||
@@ -16,10 +16,11 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import AsyncEsmComponent, { PlaceholderProps } from '.';
|
||||
import { AsyncEsmComponent } from '.';
|
||||
import type { PlaceholderProps } from './types';
|
||||
|
||||
export default {
|
||||
title: 'AsyncEsmComponent',
|
||||
title: 'Components/AsyncEsmComponent',
|
||||
};
|
||||
|
||||
const Placeholder = () => <span>Loading...</span>;
|
||||
@@ -16,8 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import AsyncEsmComponent from 'src/components/AsyncEsmComponent';
|
||||
import { render, screen } from '@superset-ui/core/spec';
|
||||
import { AsyncEsmComponent } from '.';
|
||||
|
||||
const Placeholder = () => <span>Loading...</span>;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
CSSProperties,
|
||||
useEffect,
|
||||
useState,
|
||||
RefObject,
|
||||
@@ -28,16 +27,8 @@ import {
|
||||
RefAttributes,
|
||||
} from 'react';
|
||||
|
||||
import Loading from '../Loading';
|
||||
|
||||
export type PlaceholderProps = {
|
||||
showLoadingForImport?: boolean;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
placeholderStyle?: CSSProperties;
|
||||
} & {
|
||||
[key: string]: any;
|
||||
};
|
||||
import { Loading } from '../Loading';
|
||||
import type { PlaceholderProps } from './types';
|
||||
|
||||
function DefaultPlaceholder({
|
||||
width,
|
||||
@@ -62,7 +53,7 @@ function DefaultPlaceholder({
|
||||
* Asynchronously import an ES module as a React component, render a placeholder
|
||||
* first (if provided) and re-render once import is complete.
|
||||
*/
|
||||
export default function AsyncEsmComponent<
|
||||
export function AsyncEsmComponent<
|
||||
P = PlaceholderProps,
|
||||
M = ComponentType<P> | { default: ComponentType<P> },
|
||||
>(
|
||||
@@ -128,6 +119,7 @@ export default function AsyncEsmComponent<
|
||||
const Component = component || placeholder;
|
||||
return Component ? (
|
||||
// placeholder does not get the ref
|
||||
// @ts-ignore: Suppress TypeScript error for ref assignment
|
||||
<Component ref={Component === component ? ref : null} {...props} />
|
||||
) : null;
|
||||
});
|
||||
@@ -138,3 +130,5 @@ export default function AsyncEsmComponent<
|
||||
preload: typeof waitForPromise;
|
||||
};
|
||||
}
|
||||
|
||||
export type { PlaceholderProps };
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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>
|
||||
),
|
||||
};
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
@@ -16,10 +16,11 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Avatar, AvatarProps } from '.';
|
||||
import { Avatar } from '.';
|
||||
import type { AvatarProps } from './types';
|
||||
|
||||
export default {
|
||||
title: 'Avatar',
|
||||
title: 'Components/Avatar',
|
||||
component: Avatar,
|
||||
};
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { render } from 'spec/helpers/testing-library';
|
||||
import { Avatar } from 'src/components/Avatar';
|
||||
import { render } from '@superset-ui/core/spec';
|
||||
import { Avatar } from '.';
|
||||
|
||||
test('renders with default props', async () => {
|
||||
const { container } = render(<Avatar />);
|
||||
@@ -17,15 +17,15 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Avatar as AntdAvatar } from 'antd-v5';
|
||||
import { AvatarProps, GroupProps } from 'antd-v5/lib/avatar';
|
||||
import { Avatar as AntdAvatar } from 'antd';
|
||||
import type { AvatarProps, GroupProps as AvatarGroupProps } from './types';
|
||||
|
||||
export function Avatar(props: AvatarProps) {
|
||||
return <AntdAvatar {...props} />;
|
||||
}
|
||||
|
||||
export function AvatarGroup(props: GroupProps) {
|
||||
export function AvatarGroup(props: AvatarGroupProps) {
|
||||
return <AntdAvatar.Group {...props} />;
|
||||
}
|
||||
|
||||
export type { AvatarProps, GroupProps };
|
||||
export type { AvatarProps, AvatarGroupProps };
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user