mirror of
https://github.com/apache/superset.git
synced 2026-08-26 18:11:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3eb76219d5 | ||
|
|
3585e8235a | ||
|
|
34ffa37aaa | ||
|
|
02b3e43b66 | ||
|
|
f9530f31ab | ||
|
|
fd3849cef1 | ||
|
|
ef0ad01aa5 | ||
|
|
d480152735 | ||
|
|
cbfa94c4dd | ||
|
|
d2aeb29223 | ||
|
|
7bad69f523 | ||
|
|
9dd8c42f3d | ||
|
|
58fa19bf90 |
@@ -53,12 +53,6 @@ jobs:
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
# Promotes the SQLAlchemy 2.0 deprecation warnings already locked in as
|
||||
# errors via pytest.ini's `filterwarnings` to actually run in CI, so a
|
||||
# regression on those fails the build instead of relying on a
|
||||
# contributor remembering to set this locally. See the migration
|
||||
# battleplan: https://github.com/apache/superset/discussions/40273
|
||||
SQLALCHEMY_WARN_20: "1"
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
+3
-3
@@ -58,10 +58,10 @@
|
||||
"@fontsource/inter": "^5.3.0",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@saucelabs/theme-github-codeblock": "^0.3.0",
|
||||
"@storybook/addon-docs": "^10.5.8",
|
||||
"@storybook/addon-docs": "^10.5.9",
|
||||
"@superset-ui/core": "^0.20.4",
|
||||
"@swc/core": "^1.16.0",
|
||||
"antd": "^6.6.0",
|
||||
"antd": "^6.6.1",
|
||||
"baseline-browser-mapping": "^2.11.15",
|
||||
"caniuse-lite": "^1.0.30001809",
|
||||
"docusaurus-plugin-openapi-docs": "^5.2.0",
|
||||
@@ -77,7 +77,7 @@
|
||||
"react-table": "^7.8.0",
|
||||
"remark-import-partial": "^0.0.2",
|
||||
"reselect": "^5.2.0",
|
||||
"storybook": "^10.5.8",
|
||||
"storybook": "^10.5.9",
|
||||
"swagger-ui-react": "^5.32.13",
|
||||
"swc-loader": "^0.2.7",
|
||||
"tinycolor2": "^1.4.2",
|
||||
|
||||
+28
-28
@@ -3789,7 +3789,7 @@
|
||||
"@rc-component/util" "^1.3.0"
|
||||
clsx "^2.1.1"
|
||||
|
||||
"@rc-component/select@~1.10.0":
|
||||
"@rc-component/select@~1.10.0", "@rc-component/select@~1.10.1":
|
||||
version "1.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@rc-component/select/-/select-1.10.1.tgz#323b2f458a637e8e752f8341094783741c613c34"
|
||||
integrity sha512-H+yQsl+qED9NilQ3g6zdpsMwUgwVjrcMTkNHAWRVU/MoNCYgTbDgU+MIMgZDK+rVdd2JUfI/MkysMcZZ0cyQKw==
|
||||
@@ -3824,7 +3824,7 @@
|
||||
"@rc-component/util" "^1.3.0"
|
||||
clsx "^2.1.1"
|
||||
|
||||
"@rc-component/table@~1.11.0":
|
||||
"@rc-component/table@~1.11.1":
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@rc-component/table/-/table-1.11.1.tgz#7b5c2a7c26fd37b6a403082029b5a72fcb330a4d"
|
||||
integrity sha512-OWdS6DMmeWb7bJBGqPxYZpQbzBlBiXZUu2sqo6Ii7Sjs9GeK1IsrXrWk26SL2c6KEseabswdxrRj7WUm9LdECw==
|
||||
@@ -3866,7 +3866,7 @@
|
||||
"@rc-component/util" "^1.7.0"
|
||||
clsx "^2.1.1"
|
||||
|
||||
"@rc-component/tree-select@~1.16.0":
|
||||
"@rc-component/tree-select@~1.16.1":
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/@rc-component/tree-select/-/tree-select-1.16.1.tgz#dcaea96e396e98108cb29cc051840d4fbdda38cc"
|
||||
integrity sha512-a1Oi6EJhqAhdOxxupdJi6fP0RPHMKn5TcfkX2+llaQ4lF4nwfH7b6SCHcnsybaa2s+pk1yZYwVyeOYkDnEBRdg==
|
||||
@@ -4122,23 +4122,23 @@
|
||||
resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b"
|
||||
integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==
|
||||
|
||||
"@storybook/addon-docs@^10.5.8":
|
||||
version "10.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-10.5.8.tgz#767c10c7a4cc1b625b93f869b2a2b09fc8514f2e"
|
||||
integrity sha512-NlHiMKW/UvW/uL8HXFDCEVwoH3qZeGYZ/qlWax4d7H471b/T54MBq2KcB4ZrdA785FfIH3numAJdBb5jwn00Mg==
|
||||
"@storybook/addon-docs@^10.5.9":
|
||||
version "10.5.9"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-10.5.9.tgz#6d871977f7ad833dc142d12ee43490105dec4d07"
|
||||
integrity sha512-8sFsMkZYrrdqCLdV+hnwTwDF7RaBsBPRwl4wfc8ve9Q/7Yhi5REe/Xjvd8x1yn6fBPPw9tnID9dx6Agdnr81fw==
|
||||
dependencies:
|
||||
"@mdx-js/react" "^3.0.0"
|
||||
"@storybook/csf-plugin" "10.5.8"
|
||||
"@storybook/csf-plugin" "10.5.9"
|
||||
"@storybook/icons" "^2.0.2"
|
||||
"@storybook/react-dom-shim" "10.5.8"
|
||||
"@storybook/react-dom-shim" "10.5.9"
|
||||
react "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
react-dom "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
ts-dedent "^2.0.0"
|
||||
|
||||
"@storybook/csf-plugin@10.5.8":
|
||||
version "10.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-10.5.8.tgz#c626c5bfe55d0e279b2457e5cf150a788d1fc637"
|
||||
integrity sha512-/FHiMyOWWEXfwK/lM0WxmkP9GLzbSJJuzGtfeuNWSOVDnvAMbjavitxfHb5wSbWKIQo0XYC1EJ2Y7x91XNYP4w==
|
||||
"@storybook/csf-plugin@10.5.9":
|
||||
version "10.5.9"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-10.5.9.tgz#805e4c93a1704b220351d62bb0c74ce3b78c5e10"
|
||||
integrity sha512-4H5QIHQVtQYCuL43GCRLGjNQhZpQg9gL03ja0DV80kO2Dn9LEt6ol87bSnSjn4VDgcAXtgTzXFvRLknfVgAAqg==
|
||||
dependencies:
|
||||
unplugin "^2.3.5"
|
||||
|
||||
@@ -4152,10 +4152,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@storybook/icons/-/icons-2.1.0.tgz#edfc2450a39c5e780f28c6cbc49acd7bff59b41a"
|
||||
integrity sha512-Fxh9vYpX9bQqFeHRiY8h2ApeRGDzRSMLwJwNZ/AIRqnyOKHxRKL+yFe+ctEkVJmuptRE9u1Hrn8ZZNHyfDKKNg==
|
||||
|
||||
"@storybook/react-dom-shim@10.5.8":
|
||||
version "10.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-10.5.8.tgz#40cc3e32af424baa2e4109a325dae2ede29999e2"
|
||||
integrity sha512-N8D13/Xny+V3kfe1KBgsAHS0nKWXLLdgOOXS9poKdYzVwVCN+CGEGBxWX0zMMtdCptqa6/57em9coPlZMoO+bg==
|
||||
"@storybook/react-dom-shim@10.5.9":
|
||||
version "10.5.9"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-10.5.9.tgz#549793845bb8b966acd36002d33d7b54cc5c92d4"
|
||||
integrity sha512-7qZD6CSa64p1m/zX9tG4ALcEHlEk0Bx+5++4RL3MFlRCgCFfAomXsCHTzF0RyF8cI4vl+hS7Y0ryjQchVs6UQA==
|
||||
|
||||
"@superset-ui/core@^0.20.4":
|
||||
version "0.20.4"
|
||||
@@ -6181,10 +6181,10 @@ ansis@^3.2.0:
|
||||
resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.17.0.tgz#fa8d9c2a93fe7d1177e0c17f9eeb562a58a832d7"
|
||||
integrity sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==
|
||||
|
||||
antd@^6.6.0:
|
||||
version "6.6.0"
|
||||
resolved "https://registry.yarnpkg.com/antd/-/antd-6.6.0.tgz#8acb84c54b36594b5c1a9084c8acb6a03b79961b"
|
||||
integrity sha512-UDwWIbpmrCHB9ZQ+bPh4vQfB6DTI2ulIyoQ0Tc9xxalFblttiNGHl3ySBD9SyV/8+gUjFzfSx1+iU1Fog2i46w==
|
||||
antd@^6.6.1:
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/antd/-/antd-6.6.1.tgz#3235d76413b525b1f3287b87bdaf6ba0e7148521"
|
||||
integrity sha512-QHIHYoUk9N9nJy1T9fyxWKjY0qApdTEDd/6lzqYng8Uryv9FejNmbhKvYF7obGqB+TuLXQsPVF7fOVgyzM1KrQ==
|
||||
dependencies:
|
||||
"@ant-design/colors" "^8.0.1"
|
||||
"@ant-design/cssinjs" "^2.1.2"
|
||||
@@ -6217,16 +6217,16 @@ antd@^6.6.0:
|
||||
"@rc-component/rate" "~1.0.1"
|
||||
"@rc-component/resize-observer" "^1.1.2"
|
||||
"@rc-component/segmented" "~1.3.0"
|
||||
"@rc-component/select" "~1.10.0"
|
||||
"@rc-component/select" "~1.10.1"
|
||||
"@rc-component/slider" "~1.1.1"
|
||||
"@rc-component/steps" "~1.2.2"
|
||||
"@rc-component/switch" "~1.0.3"
|
||||
"@rc-component/table" "~1.11.0"
|
||||
"@rc-component/table" "~1.11.1"
|
||||
"@rc-component/tabs" "~1.12.0"
|
||||
"@rc-component/tooltip" "~1.5.0"
|
||||
"@rc-component/tour" "~2.4.0"
|
||||
"@rc-component/tree" "~1.4.0"
|
||||
"@rc-component/tree-select" "~1.16.0"
|
||||
"@rc-component/tree-select" "~1.16.1"
|
||||
"@rc-component/trigger" "^3.10.1"
|
||||
"@rc-component/upload" "~1.1.1"
|
||||
"@rc-component/util" "^1.12.0"
|
||||
@@ -14783,10 +14783,10 @@ stop-iteration-iterator@^1.1.0:
|
||||
es-errors "^1.3.0"
|
||||
internal-slot "^1.1.0"
|
||||
|
||||
storybook@^10.5.8:
|
||||
version "10.5.8"
|
||||
resolved "https://registry.yarnpkg.com/storybook/-/storybook-10.5.8.tgz#d5f051983e6232c0a73ea02149a72c7bafb43275"
|
||||
integrity sha512-rR4oFMSiWBSqI0lvsJPtcQUPj8+hzj3TkLu+Mw61Wo6YxPSb5FsLSHai0jZnuaIdKIlmu25KCfwlSQl4e1uvnA==
|
||||
storybook@^10.5.9:
|
||||
version "10.5.9"
|
||||
resolved "https://registry.yarnpkg.com/storybook/-/storybook-10.5.9.tgz#61f476fd73785dcf09e9198ddf404b9b8c06964a"
|
||||
integrity sha512-UfdMKSjEhIKr8LbqYyIE5r7vT/drL/PxN75YaouJ+UG0FssEy6cf49OdTF3kstAqVMHskc+zEqyRoiQHZXHwgA==
|
||||
dependencies:
|
||||
"@storybook/global" "^5.0.0"
|
||||
"@storybook/icons" "^2.0.2"
|
||||
|
||||
Generated
+86
-270
@@ -81,12 +81,12 @@
|
||||
"@visx/xychart": "^4.0.0",
|
||||
"ag-grid-community": "36.1.0",
|
||||
"ag-grid-react": "36.1.0",
|
||||
"antd": "^6.6.0",
|
||||
"antd": "^6.6.1",
|
||||
"chrono-node": "^2.10.1",
|
||||
"classnames": "^2.2.5",
|
||||
"content-disposition": "^2.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"dayjs": "^1.11.22",
|
||||
"dayjs": "^1.11.23",
|
||||
"dom-to-image-more": "^3.10.2",
|
||||
"dom-to-pdf": "^0.3.2",
|
||||
"echarts": "^6.1.0",
|
||||
@@ -180,9 +180,9 @@
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
|
||||
"@storybook/addon-docs": "10.5.8",
|
||||
"@storybook/addon-links": "10.5.8",
|
||||
"@storybook/react-webpack5": "10.5.8",
|
||||
"@storybook/addon-docs": "10.5.9",
|
||||
"@storybook/addon-links": "10.5.9",
|
||||
"@storybook/react-webpack5": "10.5.9",
|
||||
"@storybook/test-runner": "0.24.4",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@swc/core": "^1.16.0",
|
||||
@@ -218,7 +218,7 @@
|
||||
"babel-loader": "^10.1.1",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
||||
"baseline-browser-mapping": "^2.11.14",
|
||||
"baseline-browser-mapping": "^2.11.15",
|
||||
"cheerio": "1.2.0",
|
||||
"concurrently": "^10.0.5",
|
||||
"copy-webpack-plugin": "^14.0.0",
|
||||
@@ -235,7 +235,7 @@
|
||||
"eslint-plugin-no-only-tests": "^3.4.0",
|
||||
"eslint-plugin-react-prefer-function-component": "^5.0.0",
|
||||
"eslint-plugin-react-you-might-not-need-an-effect": "^1.0.1",
|
||||
"eslint-plugin-storybook": "10.5.8",
|
||||
"eslint-plugin-storybook": "10.5.9",
|
||||
"eslint-plugin-testing-library": "^7.16.2",
|
||||
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
|
||||
"fetch-mock": "^12.6.0",
|
||||
@@ -266,7 +266,7 @@
|
||||
"source-map": "^0.8.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"speed-measure-webpack-plugin": "^1.6.0",
|
||||
"storybook": "10.5.8",
|
||||
"storybook": "10.5.9",
|
||||
"style-loader": "^4.0.0",
|
||||
"stylelint": "^17.14.1",
|
||||
"swc-loader": "^0.2.7",
|
||||
@@ -9946,9 +9946,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rc-component/select": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.10.0.tgz",
|
||||
"integrity": "sha512-u/3yuF2kEXvTJXPy3P7qkVBkGGZcQo+m1uTuQqJa6qCnwdmDoEn1Rs3zTTi6y/RsLGiQGM0drPN5c/RfflCnow==",
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.10.1.tgz",
|
||||
"integrity": "sha512-H+yQsl+qED9NilQ3g6zdpsMwUgwVjrcMTkNHAWRVU/MoNCYgTbDgU+MIMgZDK+rVdd2JUfI/MkysMcZZ0cyQKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rc-component/overflow": "^1.0.0",
|
||||
@@ -10765,16 +10765,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@storybook/addon-docs": {
|
||||
"version": "10.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.5.8.tgz",
|
||||
"integrity": "sha512-NlHiMKW/UvW/uL8HXFDCEVwoH3qZeGYZ/qlWax4d7H471b/T54MBq2KcB4ZrdA785FfIH3numAJdBb5jwn00Mg==",
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.5.9.tgz",
|
||||
"integrity": "sha512-8sFsMkZYrrdqCLdV+hnwTwDF7RaBsBPRwl4wfc8ve9Q/7Yhi5REe/Xjvd8x1yn6fBPPw9tnID9dx6Agdnr81fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"@storybook/csf-plugin": "10.5.8",
|
||||
"@storybook/csf-plugin": "10.5.9",
|
||||
"@storybook/icons": "^2.0.2",
|
||||
"@storybook/react-dom-shim": "10.5.8",
|
||||
"@storybook/react-dom-shim": "10.5.9",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"ts-dedent": "^2.0.0"
|
||||
@@ -10785,7 +10785,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.8"
|
||||
"storybook": "10.5.9"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
@@ -10794,9 +10794,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/addon-docs/node_modules/@storybook/csf-plugin": {
|
||||
"version": "10.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.5.8.tgz",
|
||||
"integrity": "sha512-/FHiMyOWWEXfwK/lM0WxmkP9GLzbSJJuzGtfeuNWSOVDnvAMbjavitxfHb5wSbWKIQo0XYC1EJ2Y7x91XNYP4w==",
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.5.9.tgz",
|
||||
"integrity": "sha512-4H5QIHQVtQYCuL43GCRLGjNQhZpQg9gL03ja0DV80kO2Dn9LEt6ol87bSnSjn4VDgcAXtgTzXFvRLknfVgAAqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -10809,7 +10809,7 @@
|
||||
"peerDependencies": {
|
||||
"esbuild": "*",
|
||||
"rollup": "*",
|
||||
"storybook": "10.5.8",
|
||||
"storybook": "10.5.9",
|
||||
"vite": "*",
|
||||
"webpack": "*"
|
||||
},
|
||||
@@ -10829,9 +10829,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/addon-docs/node_modules/@storybook/react-dom-shim": {
|
||||
"version": "10.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.8.tgz",
|
||||
"integrity": "sha512-N8D13/Xny+V3kfe1KBgsAHS0nKWXLLdgOOXS9poKdYzVwVCN+CGEGBxWX0zMMtdCptqa6/57em9coPlZMoO+bg==",
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.9.tgz",
|
||||
"integrity": "sha512-7qZD6CSa64p1m/zX9tG4ALcEHlEk0Bx+5++4RL3MFlRCgCFfAomXsCHTzF0RyF8cI4vl+hS7Y0ryjQchVs6UQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -10843,7 +10843,7 @@
|
||||
"@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.8"
|
||||
"storybook": "10.5.9"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
@@ -10855,9 +10855,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/addon-links": {
|
||||
"version": "10.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-10.5.8.tgz",
|
||||
"integrity": "sha512-mpWw4alBJVGqgVh897LZ2keN/xnMHcH93wKJG+oGg4+cdEUA+06hCs5T4k+AS5Aa+EZ6LvdOoi2VPHssyQlCCA==",
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-10.5.9.tgz",
|
||||
"integrity": "sha512-ZDbPl6ia6hqjoV+CpQU3DjkXpc0TxUq6+y/rFD8w21dJMdqNWzY8zajHC8r4CfTWANjM1pGPUYisWtTKi1MxZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -10870,7 +10870,7 @@
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.8"
|
||||
"storybook": "10.5.9"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
@@ -10964,15 +10964,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5": {
|
||||
"version": "10.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-10.5.8.tgz",
|
||||
"integrity": "sha512-HkPi42WaoNSHC0DAERsJEF7Vhnluzsp/aiuhnH65GGYG5TmdLL9G8KDiYvXHGDCyb4RfoAPYrtzxaLMFfPPFvQ==",
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-10.5.9.tgz",
|
||||
"integrity": "sha512-mrCJub/WAt6RAU1P+bpBvcdTHMHePXLuNM+TuJl0Sl3r9Ta0YjDvtdODweud9sTEpVN3ao/fk0LpiaDMfLd84w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@storybook/builder-webpack5": "10.5.8",
|
||||
"@storybook/preset-react-webpack": "10.5.8",
|
||||
"@storybook/react": "10.5.8"
|
||||
"@storybook/builder-webpack5": "10.5.9",
|
||||
"@storybook/preset-react-webpack": "10.5.9",
|
||||
"@storybook/react": "10.5.9"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -10981,7 +10981,7 @@
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.8",
|
||||
"storybook": "10.5.9",
|
||||
"typescript": ">= 4.9.x"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@@ -10991,13 +10991,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5/node_modules/@storybook/builder-webpack5": {
|
||||
"version": "10.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-10.5.8.tgz",
|
||||
"integrity": "sha512-ke5x27gtWQ4gpXCLWxdGkr8ZlJwBykV/KjbBTAlC04dmS9OkI9MBzGj+TteUlgrcaN7LwoNTR5zRmxKStOZYzQ==",
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-10.5.9.tgz",
|
||||
"integrity": "sha512-XTLC95jP75V9NfhoUzDNKDCn9r0ZqXz4ZhrNzQXVDz4vNWkjU3/uDL6Ywh9WFu6Xrb+onQqSR9hlHjS/DOZj7Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@storybook/core-webpack": "10.5.8",
|
||||
"@storybook/core-webpack": "10.5.9",
|
||||
"case-sensitive-paths-webpack-plugin": "^2.4.0",
|
||||
"cjs-module-lexer": "^1.2.3",
|
||||
"css-loader": "^7.1.2",
|
||||
@@ -11019,7 +11019,7 @@
|
||||
"url": "https://opencollective.com/storybook"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"storybook": "10.5.8"
|
||||
"storybook": "10.5.9"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
@@ -11028,9 +11028,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5/node_modules/@storybook/builder-webpack5/node_modules/@storybook/core-webpack": {
|
||||
"version": "10.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.5.8.tgz",
|
||||
"integrity": "sha512-HccINB0UbTtnyJtKpaX+C35BRTSnAwnreIMwwI+LpeUd4x9mQg0G9orB7lfBBZwd5LQf8YhM2Vkjiawzo41GLg==",
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.5.9.tgz",
|
||||
"integrity": "sha512-YmXR9RJdQpH8EtWEIjLTr5LMGiCXSmZp/A9UkATY5xHM4qG2QwJB++jU2wv4nGDyaPiUcbzT2PQSXN6RXhiUZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -11041,17 +11041,17 @@
|
||||
"url": "https://opencollective.com/storybook"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"storybook": "10.5.8"
|
||||
"storybook": "10.5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5/node_modules/@storybook/preset-react-webpack": {
|
||||
"version": "10.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-10.5.8.tgz",
|
||||
"integrity": "sha512-0JjgVoX5t9Wb+gwddYHx/Ej7KFqwd65lpHXEhBoT4pFWRqVI0pvfHu42M+DRGjsgOye3uE+3pH4yHR3+0/fCHA==",
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-10.5.9.tgz",
|
||||
"integrity": "sha512-LOr8SoM2CejVCHeLyxbdRMiKuQb2q95CE5D75oK3+513mMZ8LxVPVApxy8B6FvFYNe9CTqVK+95jETefOqabTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@storybook/core-webpack": "10.5.8",
|
||||
"@storybook/core-webpack": "10.5.9",
|
||||
"@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0",
|
||||
"@types/semver": "^7.7.1",
|
||||
"magic-string": "^0.30.5",
|
||||
@@ -11068,7 +11068,7 @@
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.8"
|
||||
"storybook": "10.5.9"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
@@ -11077,9 +11077,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5/node_modules/@storybook/preset-react-webpack/node_modules/@storybook/core-webpack": {
|
||||
"version": "10.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.5.8.tgz",
|
||||
"integrity": "sha512-HccINB0UbTtnyJtKpaX+C35BRTSnAwnreIMwwI+LpeUd4x9mQg0G9orB7lfBBZwd5LQf8YhM2Vkjiawzo41GLg==",
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.5.9.tgz",
|
||||
"integrity": "sha512-YmXR9RJdQpH8EtWEIjLTr5LMGiCXSmZp/A9UkATY5xHM4qG2QwJB++jU2wv4nGDyaPiUcbzT2PQSXN6RXhiUZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -11090,18 +11090,18 @@
|
||||
"url": "https://opencollective.com/storybook"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"storybook": "10.5.8"
|
||||
"storybook": "10.5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5/node_modules/@storybook/react": {
|
||||
"version": "10.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.5.8.tgz",
|
||||
"integrity": "sha512-6qqkmqX6imtL+0Z9Uan2tIfYivOI0FiVmWr0zpqqQR15AkJ18JfNcNTQoyjeAlCO0Kei56SWqnu2qLq52TYplg==",
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.5.9.tgz",
|
||||
"integrity": "sha512-kApGOuNT26NkpioTsr1iT/Q2c44tA7OIsNUSyFqtT7W8k3fRn/jQWfrDegYxty0WG0wxdNMZq2ndRfOOF8HaHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/react-dom-shim": "10.5.8",
|
||||
"@storybook/react-dom-shim": "10.5.9",
|
||||
"react-docgen": "^8.0.2",
|
||||
"react-docgen-typescript": "^2.2.2"
|
||||
},
|
||||
@@ -11114,7 +11114,7 @@
|
||||
"@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.8",
|
||||
"storybook": "10.5.9",
|
||||
"typescript": ">= 4.9.x"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@@ -11130,9 +11130,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5/node_modules/@storybook/react/node_modules/@storybook/react-dom-shim": {
|
||||
"version": "10.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.8.tgz",
|
||||
"integrity": "sha512-N8D13/Xny+V3kfe1KBgsAHS0nKWXLLdgOOXS9poKdYzVwVCN+CGEGBxWX0zMMtdCptqa6/57em9coPlZMoO+bg==",
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.9.tgz",
|
||||
"integrity": "sha512-7qZD6CSa64p1m/zX9tG4ALcEHlEk0Bx+5++4RL3MFlRCgCFfAomXsCHTzF0RyF8cI4vl+hS7Y0ryjQchVs6UQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -11144,7 +11144,7 @@
|
||||
"@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.8"
|
||||
"storybook": "10.5.9"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
@@ -14928,9 +14928,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/antd": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/antd/-/antd-6.6.0.tgz",
|
||||
"integrity": "sha512-UDwWIbpmrCHB9ZQ+bPh4vQfB6DTI2ulIyoQ0Tc9xxalFblttiNGHl3ySBD9SyV/8+gUjFzfSx1+iU1Fog2i46w==",
|
||||
"version": "6.6.1",
|
||||
"resolved": "https://registry.npmjs.org/antd/-/antd-6.6.1.tgz",
|
||||
"integrity": "sha512-QHIHYoUk9N9nJy1T9fyxWKjY0qApdTEDd/6lzqYng8Uryv9FejNmbhKvYF7obGqB+TuLXQsPVF7fOVgyzM1KrQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^8.0.1",
|
||||
@@ -14964,16 +14964,16 @@
|
||||
"@rc-component/rate": "~1.0.1",
|
||||
"@rc-component/resize-observer": "^1.1.2",
|
||||
"@rc-component/segmented": "~1.3.0",
|
||||
"@rc-component/select": "~1.10.0",
|
||||
"@rc-component/select": "~1.10.1",
|
||||
"@rc-component/slider": "~1.1.1",
|
||||
"@rc-component/steps": "~1.2.2",
|
||||
"@rc-component/switch": "~1.0.3",
|
||||
"@rc-component/table": "~1.11.0",
|
||||
"@rc-component/table": "~1.11.1",
|
||||
"@rc-component/tabs": "~1.12.0",
|
||||
"@rc-component/tooltip": "~1.5.0",
|
||||
"@rc-component/tour": "~2.4.0",
|
||||
"@rc-component/tree": "~1.4.0",
|
||||
"@rc-component/tree-select": "~1.16.0",
|
||||
"@rc-component/tree-select": "~1.16.1",
|
||||
"@rc-component/trigger": "^3.10.1",
|
||||
"@rc-component/upload": "~1.1.1",
|
||||
"@rc-component/util": "^1.12.0",
|
||||
@@ -15715,9 +15715,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.11.14",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.14.tgz",
|
||||
"integrity": "sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==",
|
||||
"version": "2.11.15",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.15.tgz",
|
||||
"integrity": "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -18559,9 +18559,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.22",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.22.tgz",
|
||||
"integrity": "sha512-1YRnxzt/AabP3GHxnaB9/b+ZScCKu5TeF+co+BWG+lnWVIwEcTFc1FVE0WLNmNO3sA6GGXL40i5qkHfbLzpwrg==",
|
||||
"version": "1.11.23",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz",
|
||||
"integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debounce": {
|
||||
@@ -19140,11 +19140,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.4.12",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz",
|
||||
"integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==",
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz",
|
||||
"integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optional": true,
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
@@ -20237,9 +20236,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-storybook": {
|
||||
"version": "10.5.8",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.5.8.tgz",
|
||||
"integrity": "sha512-bf9W5nZyWdIaCUZf4aEZnEeD1mn+csNYX8dYUQjAo6L7/DkSLtr65R4zFZ1xeS4m6dOXO6UtUySesCSw4e8w1g==",
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.5.9.tgz",
|
||||
"integrity": "sha512-4Hrqccy/zttV0S/32TSUbqtizzj4sbkgYvv7UzHveH58v96PhrO+fSpOFAqYvzNDzdOU6smKwObFtzX+RZqj4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -20248,7 +20247,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=8",
|
||||
"storybook": "10.5.8"
|
||||
"storybook": "10.5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-testing-library": {
|
||||
@@ -37735,9 +37734,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/storybook": {
|
||||
"version": "10.5.8",
|
||||
"resolved": "https://registry.npmjs.org/storybook/-/storybook-10.5.8.tgz",
|
||||
"integrity": "sha512-rR4oFMSiWBSqI0lvsJPtcQUPj8+hzj3TkLu+Mw61Wo6YxPSb5FsLSHai0jZnuaIdKIlmu25KCfwlSQl4e1uvnA==",
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/storybook/-/storybook-10.5.9.tgz",
|
||||
"integrity": "sha512-UfdMKSjEhIKr8LbqYyIE5r7vT/drL/PxN75YaouJ+UG0FssEy6cf49OdTF3kstAqVMHskc+zEqyRoiQHZXHwgA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -42986,7 +42985,7 @@
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-time": "^3.1.0",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"dayjs": "^1.11.22",
|
||||
"dayjs": "^1.11.23",
|
||||
"dompurify": "^3.4.13",
|
||||
"fetch-retry": "^6.0.0",
|
||||
"handlebars": "^4.7.9",
|
||||
@@ -43146,189 +43145,6 @@
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"plugins/legacy-plugin-chart-calendar": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-calendar",
|
||||
"version": "0.20.3",
|
||||
"extraneous": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3-array": "^3.2.4",
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-tip": "^0.9.1",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-chord": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-chord",
|
||||
"version": "0.20.3",
|
||||
"extraneous": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^19.2.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-country-map": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-country-map",
|
||||
"version": "0.20.3",
|
||||
"extraneous": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"d3-array": "^3.2.4",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-horizon": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-horizon",
|
||||
"version": "0.20.3",
|
||||
"extraneous": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3-array": "^3.2.4",
|
||||
"d3-scale": "^4.0.2",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-paired-t-test": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-paired-t-test",
|
||||
"version": "0.20.3",
|
||||
"extraneous": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"distributions": "^2.2.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"reactable": "^1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-parallel-coordinates": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-parallel-coordinates",
|
||||
"version": "0.20.3",
|
||||
"extraneous": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3v3": "npm:d3@3.5.17",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-partition": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-partition",
|
||||
"version": "0.20.3",
|
||||
"extraneous": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"d3-hierarchy": "^3.1.2",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^15.0.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-rose": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-rose",
|
||||
"version": "0.20.3",
|
||||
"extraneous": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"nvd3-fork": "^2.0.5",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-world-map": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-world-map",
|
||||
"version": "0.20.3",
|
||||
"extraneous": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"d3-array": "^3.2.4",
|
||||
"datamaps": "^0.5.10",
|
||||
"prop-types": "^15.8.1",
|
||||
"tinycolor2": "^1.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-preset-chart-nvd3": {
|
||||
"name": "@superset-ui/legacy-preset-chart-nvd3",
|
||||
"version": "0.20.3",
|
||||
"extraneous": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"d3-tip": "^0.9.1",
|
||||
"dompurify": "^3.4.12",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"lodash": "^4.18.1",
|
||||
"lodash-es": "^4.18.1",
|
||||
"nvd3-fork": "^2.0.5",
|
||||
"prop-types": "^15.8.1",
|
||||
"urijs": "^1.19.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"dayjs": "^1.11.21",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-ag-grid-table": {
|
||||
"name": "@superset-ui/plugin-chart-ag-grid-table",
|
||||
"version": "0.20.3",
|
||||
|
||||
@@ -158,12 +158,12 @@
|
||||
"@visx/xychart": "^4.0.0",
|
||||
"ag-grid-community": "36.1.0",
|
||||
"ag-grid-react": "36.1.0",
|
||||
"antd": "^6.6.0",
|
||||
"antd": "^6.6.1",
|
||||
"chrono-node": "^2.10.1",
|
||||
"classnames": "^2.2.5",
|
||||
"content-disposition": "^2.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"dayjs": "^1.11.22",
|
||||
"dayjs": "^1.11.23",
|
||||
"dom-to-image-more": "^3.10.2",
|
||||
"dom-to-pdf": "^0.3.2",
|
||||
"echarts": "^6.1.0",
|
||||
@@ -257,9 +257,9 @@
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
|
||||
"@storybook/addon-docs": "10.5.8",
|
||||
"@storybook/addon-links": "10.5.8",
|
||||
"@storybook/react-webpack5": "10.5.8",
|
||||
"@storybook/addon-docs": "10.5.9",
|
||||
"@storybook/addon-links": "10.5.9",
|
||||
"@storybook/react-webpack5": "10.5.9",
|
||||
"@storybook/test-runner": "0.24.4",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@swc/core": "^1.16.0",
|
||||
@@ -295,7 +295,7 @@
|
||||
"babel-loader": "^10.1.1",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
||||
"baseline-browser-mapping": "^2.11.14",
|
||||
"baseline-browser-mapping": "^2.11.15",
|
||||
"cheerio": "1.2.0",
|
||||
"concurrently": "^10.0.5",
|
||||
"copy-webpack-plugin": "^14.0.0",
|
||||
@@ -312,7 +312,7 @@
|
||||
"eslint-plugin-no-only-tests": "^3.4.0",
|
||||
"eslint-plugin-react-prefer-function-component": "^5.0.0",
|
||||
"eslint-plugin-react-you-might-not-need-an-effect": "^1.0.1",
|
||||
"eslint-plugin-storybook": "10.5.8",
|
||||
"eslint-plugin-storybook": "10.5.9",
|
||||
"eslint-plugin-testing-library": "^7.16.2",
|
||||
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
|
||||
"fetch-mock": "^12.6.0",
|
||||
@@ -343,7 +343,7 @@
|
||||
"source-map": "^0.8.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"speed-measure-webpack-plugin": "^1.6.0",
|
||||
"storybook": "10.5.8",
|
||||
"storybook": "10.5.9",
|
||||
"style-loader": "^4.0.0",
|
||||
"stylelint": "^17.14.1",
|
||||
"swc-loader": "^0.2.7",
|
||||
|
||||
+28
-1
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { QueryFormMetric } from '@superset-ui/core';
|
||||
import { getTotalsMetrics } from './getTotalsMetrics';
|
||||
import { getTotalsMetrics, toTotalsAggregate } from './getTotalsMetrics';
|
||||
|
||||
const simpleMetric = (aggregate: string): QueryFormMetric =>
|
||||
({
|
||||
@@ -76,4 +76,31 @@ describe('getTotalsMetrics', () => {
|
||||
test('returns an empty array when given no metrics', () => {
|
||||
expect(getTotalsMetrics([], 'AVG')).toEqual([]);
|
||||
});
|
||||
|
||||
test("ORIGINAL keeps each metric's own aggregate", () => {
|
||||
const metrics = [
|
||||
simpleMetric('COUNT_DISTINCT'),
|
||||
sqlMetric(),
|
||||
savedMetric(),
|
||||
];
|
||||
const result = getTotalsMetrics(metrics, 'ORIGINAL');
|
||||
|
||||
expect(result).toBe(metrics);
|
||||
expect(result[0]).toEqual(
|
||||
expect.objectContaining({ aggregate: 'COUNT_DISTINCT' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toTotalsAggregate', () => {
|
||||
test.each(['SUM', 'AVG'] as const)('passes %s through', value => {
|
||||
expect(toTotalsAggregate(value)).toBe(value);
|
||||
});
|
||||
|
||||
test.each([undefined, null, '', 'MEDIAN', 'sum'])(
|
||||
'falls back to ORIGINAL for %p',
|
||||
value => {
|
||||
expect(toTotalsAggregate(value)).toBe('ORIGINAL');
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
+31
-11
@@ -18,26 +18,46 @@
|
||||
*/
|
||||
import { isAdhocMetricSimple, QueryFormMetric } from '@superset-ui/core';
|
||||
|
||||
export type TotalsAggregate = 'SUM' | 'AVG';
|
||||
/**
|
||||
* How the "Show summary" totals row aggregates each metric.
|
||||
*
|
||||
* ``ORIGINAL`` keeps every metric's own aggregation. It is the default because
|
||||
* overriding is not universally valid: ``SUM`` over a ``COUNT_DISTINCT`` of a
|
||||
* non-numeric column (a uuid, say) is rejected outright by the database, and
|
||||
* over a numeric id column it silently produces a meaningless number.
|
||||
*/
|
||||
export type TotalsAggregate = 'ORIGINAL' | 'SUM' | 'AVG';
|
||||
|
||||
/**
|
||||
* Build the metrics for a chart's "Show summary" totals query, overriding
|
||||
* each Simple (adhoc) metric's aggregate function with the user-chosen
|
||||
* totals aggregate. The totals query has no GROUP BY, so the database
|
||||
* evaluates each metric fresh over all rows -- swapping the aggregate here
|
||||
* is a correct, independent computation, not a re-aggregation of
|
||||
* already-aggregated per-row values.
|
||||
* Build the metrics for a chart's "Show summary" totals query.
|
||||
*
|
||||
* Custom-SQL metrics and saved (string) metrics pass through unchanged:
|
||||
* there is no safe way to rewrite an arbitrary SQL expression's aggregate
|
||||
* function without parsing it, so the totals row keeps their own native
|
||||
* aggregate for those.
|
||||
* With SUM or AVG, each Simple (adhoc) metric is cloned with its aggregate
|
||||
* replaced. The totals query has no GROUP BY, so the database evaluates each
|
||||
* metric fresh over all rows -- that swap is an independent computation, not a
|
||||
* re-aggregation of already-aggregated per-row values.
|
||||
*
|
||||
* Custom-SQL and saved (string) metrics always pass through unchanged: there is
|
||||
* no safe way to rewrite an arbitrary SQL expression's aggregate without
|
||||
* parsing it, so the totals row keeps their own native aggregate.
|
||||
*/
|
||||
export function getTotalsMetrics(
|
||||
metrics: QueryFormMetric[],
|
||||
aggregate: TotalsAggregate,
|
||||
): QueryFormMetric[] {
|
||||
if (aggregate === 'ORIGINAL') {
|
||||
return metrics;
|
||||
}
|
||||
return metrics.map(metric =>
|
||||
isAdhocMetricSimple(metric) ? { ...metric, aggregate } : metric,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrow a raw ``totals_aggregate`` form-data value to a TotalsAggregate.
|
||||
*
|
||||
* Anything other than an explicit SUM/AVG — including charts saved before the
|
||||
* control existed — keeps each metric's own aggregation.
|
||||
*/
|
||||
export function toTotalsAggregate(value: unknown): TotalsAggregate {
|
||||
return value === 'SUM' || value === 'AVG' ? value : 'ORIGINAL';
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-time": "^3.1.0",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"dayjs": "^1.11.22",
|
||||
"dayjs": "^1.11.23",
|
||||
"dompurify": "^3.4.13",
|
||||
"fetch-retry": "^6.0.0",
|
||||
"handlebars": "^4.7.9",
|
||||
|
||||
+2
-3
@@ -64,9 +64,8 @@ export default function createSmartNumberFormatter(
|
||||
description,
|
||||
formatFunc: value => `${getSign(value)}${formatValue(value)}`,
|
||||
id:
|
||||
id || signed
|
||||
? NumberFormats.SMART_NUMBER_SIGNED
|
||||
: NumberFormats.SMART_NUMBER,
|
||||
id ??
|
||||
(signed ? NumberFormats.SMART_NUMBER_SIGNED : NumberFormats.SMART_NUMBER),
|
||||
label: label ?? 'Adaptive formatter',
|
||||
});
|
||||
}
|
||||
|
||||
+6
@@ -24,6 +24,12 @@ describe('createSmartNumberFormatter(options)', () => {
|
||||
const formatter = createSmartNumberFormatter();
|
||||
expect(formatter).toBeInstanceOf(NumberFormatter);
|
||||
});
|
||||
test('uses the supplied formatter id regardless of signed option', () => {
|
||||
expect(createSmartNumberFormatter({ id: 'custom' }).id).toBe('custom');
|
||||
expect(
|
||||
createSmartNumberFormatter({ id: 'custom-signed', signed: true }).id,
|
||||
).toBe('custom-signed');
|
||||
});
|
||||
describe('using default options', () => {
|
||||
const formatter = createSmartNumberFormatter();
|
||||
test('formats 0 correctly', () => {
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
getTotalsMetrics,
|
||||
isTimeComparison,
|
||||
timeCompareOperator,
|
||||
TotalsAggregate,
|
||||
toTotalsAggregate,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { TableChartFormData } from './types';
|
||||
@@ -696,13 +696,16 @@ export const buildQueryUncached: BuildQuery<TableChartFormData> = (
|
||||
formData.show_totals &&
|
||||
queryMode === QueryMode.Aggregate,
|
||||
);
|
||||
const totalsAggregate: TotalsAggregate =
|
||||
formData.totals_aggregate === 'AVG' ? 'AVG' : 'SUM';
|
||||
const totalsAggregate = toTotalsAggregate(formData.totals_aggregate);
|
||||
// Raw-mode summary columns have no metric of their own to preserve, so
|
||||
// ORIGINAL has nothing to fall back to; sum them as before.
|
||||
const rawSummaryAggregate =
|
||||
totalsAggregate === 'ORIGINAL' ? 'SUM' : totalsAggregate;
|
||||
const totalsMetrics =
|
||||
rawSummaryColumns.length > 0
|
||||
? rawSummaryColumns.map(columnName => ({
|
||||
expressionType: 'SIMPLE' as const,
|
||||
aggregate: totalsAggregate,
|
||||
aggregate: rawSummaryAggregate,
|
||||
column: { column_name: columnName },
|
||||
label: columnName,
|
||||
}))
|
||||
|
||||
@@ -503,14 +503,18 @@ const config: ControlPanelConfig = {
|
||||
label: t('Summary aggregation'),
|
||||
renderTrigger: true,
|
||||
description: t(
|
||||
'Aggregation used for the summary row, independent of each ' +
|
||||
"metric's own aggregation. Only applies to simple metrics " +
|
||||
'(a metric built from custom SQL keeps its own aggregation ' +
|
||||
'in the summary row).',
|
||||
'Aggregation used for the summary row. By default each metric ' +
|
||||
'keeps its own aggregation; Sum and Average override it for ' +
|
||||
'the summary row only. The override applies to simple ' +
|
||||
'metrics (a metric built from custom SQL always keeps its ' +
|
||||
'own aggregation). Overriding a count or a distinct count ' +
|
||||
'sums the counted column instead, which fails outright on a ' +
|
||||
'non-numeric column.',
|
||||
),
|
||||
default: 'SUM',
|
||||
default: 'ORIGINAL',
|
||||
clearable: false,
|
||||
choices: [
|
||||
['ORIGINAL', t("Each metric's own")],
|
||||
['SUM', t('Sum')],
|
||||
['AVG', t('Average')],
|
||||
],
|
||||
|
||||
@@ -1561,7 +1561,7 @@ describe('plugin-chart-ag-grid-table', () => {
|
||||
expect(queries[1].metrics).toEqual(['count']);
|
||||
});
|
||||
|
||||
test('defaults aggregate-mode totals to SUM for a simple metric', () => {
|
||||
test("defaults aggregate-mode totals to the metric's own aggregate", () => {
|
||||
const simpleMetric = {
|
||||
expressionType: 'SIMPLE' as const,
|
||||
column: { column_name: 'sales' },
|
||||
@@ -1580,9 +1580,29 @@ describe('plugin-chart-ag-grid-table', () => {
|
||||
{ ownState: {} },
|
||||
);
|
||||
|
||||
expect(queries[1].metrics).toEqual([
|
||||
{ ...simpleMetric, aggregate: 'SUM' },
|
||||
]);
|
||||
expect(queries[1].metrics).toEqual([simpleMetric]);
|
||||
});
|
||||
|
||||
test('keeps COUNT_DISTINCT in aggregate-mode totals by default', () => {
|
||||
const countDistinctMetric = {
|
||||
expressionType: 'SIMPLE' as const,
|
||||
column: { column_name: 'contract_id' },
|
||||
aggregate: 'COUNT_DISTINCT' as const,
|
||||
label: 'contracts',
|
||||
};
|
||||
const { queries } = buildQuery(
|
||||
{
|
||||
viz_type: VizType.Table,
|
||||
datasource: '11__table',
|
||||
query_mode: QueryMode.Aggregate,
|
||||
groupby: ['state'],
|
||||
metrics: [countDistinctMetric],
|
||||
show_totals: true,
|
||||
},
|
||||
{ ownState: {} },
|
||||
);
|
||||
|
||||
expect(queries[1].metrics).toEqual([countDistinctMetric]);
|
||||
});
|
||||
|
||||
test('overrides aggregate-mode totals to AVG for a simple metric when totals_aggregate is set', () => {
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
getTotalsMetrics,
|
||||
isTimeComparison,
|
||||
timeCompareOperator,
|
||||
TotalsAggregate,
|
||||
toTotalsAggregate,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { TableChartFormData } from './types';
|
||||
@@ -349,8 +349,7 @@ export const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
formData.show_totals &&
|
||||
queryMode === QueryMode.Aggregate
|
||||
) {
|
||||
const totalsAggregate: TotalsAggregate =
|
||||
formData.totals_aggregate === 'AVG' ? 'AVG' : 'SUM';
|
||||
const totalsAggregate = toTotalsAggregate(formData.totals_aggregate);
|
||||
extraQueries.push({
|
||||
...queryObject,
|
||||
columns: [],
|
||||
|
||||
@@ -475,14 +475,18 @@ const config: ControlPanelConfig = {
|
||||
type: 'SelectControl',
|
||||
label: t('Summary aggregation'),
|
||||
description: t(
|
||||
'Aggregation used for the summary row, independent of each ' +
|
||||
"metric's own aggregation. Only applies to simple metrics " +
|
||||
'(a metric built from custom SQL keeps its own aggregation ' +
|
||||
'in the summary row).',
|
||||
'Aggregation used for the summary row. By default each metric ' +
|
||||
'keeps its own aggregation; Sum and Average override it for ' +
|
||||
'the summary row only. The override applies to simple ' +
|
||||
'metrics (a metric built from custom SQL always keeps its ' +
|
||||
'own aggregation). Overriding a count or a distinct count ' +
|
||||
'sums the counted column instead, which fails outright on a ' +
|
||||
'non-numeric column.',
|
||||
),
|
||||
default: 'SUM',
|
||||
default: 'ORIGINAL',
|
||||
clearable: false,
|
||||
choices: [
|
||||
['ORIGINAL', t("Each metric's own")],
|
||||
['SUM', t('Sum')],
|
||||
['AVG', t('Average')],
|
||||
],
|
||||
|
||||
@@ -340,7 +340,7 @@ describe('plugin-chart-table', () => {
|
||||
label: 'sum_sales',
|
||||
};
|
||||
|
||||
test('defaults the totals query metric aggregate to SUM', () => {
|
||||
test("defaults to each metric's own aggregate", () => {
|
||||
const { queries } = buildQueryCached({
|
||||
...basicFormData,
|
||||
query_mode: QueryMode.Aggregate,
|
||||
@@ -350,9 +350,28 @@ describe('plugin-chart-table', () => {
|
||||
});
|
||||
|
||||
expect(queries).toHaveLength(2);
|
||||
expect(queries[1].metrics).toEqual([
|
||||
{ ...simpleMetric, aggregate: 'SUM' },
|
||||
]);
|
||||
expect(queries[1].metrics).toEqual([simpleMetric]);
|
||||
});
|
||||
|
||||
test('keeps COUNT_DISTINCT in the summary row by default', () => {
|
||||
// Overriding this to SUM sums the counted column instead of counting
|
||||
// it, which is meaningless on a numeric id and is rejected outright by
|
||||
// the database on a non-numeric one (e.g. a uuid).
|
||||
const countDistinctMetric = {
|
||||
expressionType: 'SIMPLE' as const,
|
||||
column: { column_name: 'contract_id' },
|
||||
aggregate: 'COUNT_DISTINCT' as const,
|
||||
label: 'contracts',
|
||||
};
|
||||
const { queries } = buildQueryCached({
|
||||
...basicFormData,
|
||||
query_mode: QueryMode.Aggregate,
|
||||
metrics: [countDistinctMetric],
|
||||
groupby: ['category'],
|
||||
show_totals: true,
|
||||
});
|
||||
|
||||
expect(queries[1].metrics).toEqual([countDistinctMetric]);
|
||||
});
|
||||
|
||||
test('overrides simple metric aggregate with totals_aggregate for the summary query only', () => {
|
||||
|
||||
@@ -669,11 +669,11 @@ describe('UploadDataModal Collapse Tabs', () => {
|
||||
useRedux: true,
|
||||
});
|
||||
const generalInfoTab = screen.getByRole('tab', {
|
||||
name: /expanded General information/i,
|
||||
name: /General information/i,
|
||||
});
|
||||
expect(generalInfoTab).toHaveAttribute('aria-expanded', 'true');
|
||||
const fileSettingsTab = screen.getByRole('tab', {
|
||||
name: /collapsed File settings/i,
|
||||
name: /File settings/i,
|
||||
});
|
||||
await userEvent.click(fileSettingsTab);
|
||||
await waitFor(() => {
|
||||
@@ -689,11 +689,11 @@ describe('UploadDataModal Collapse Tabs', () => {
|
||||
useRedux: true,
|
||||
});
|
||||
const generalInfoTab = screen.getByRole('tab', {
|
||||
name: /expanded General information/i,
|
||||
name: /General information/i,
|
||||
});
|
||||
expect(generalInfoTab).toHaveAttribute('aria-expanded', 'true');
|
||||
const fileSettingsTab = screen.getByRole('tab', {
|
||||
name: /collapsed File settings/i,
|
||||
name: /File settings/i,
|
||||
});
|
||||
await userEvent.click(fileSettingsTab);
|
||||
await waitFor(() => {
|
||||
@@ -709,11 +709,11 @@ describe('UploadDataModal Collapse Tabs', () => {
|
||||
useRedux: true,
|
||||
});
|
||||
const generalInfoTab = screen.getByRole('tab', {
|
||||
name: /expanded General information/i,
|
||||
name: /General information/i,
|
||||
});
|
||||
expect(generalInfoTab).toHaveAttribute('aria-expanded', 'true');
|
||||
const fileSettingsTab = screen.getByRole('tab', {
|
||||
name: /collapsed File settings/i,
|
||||
name: /File settings/i,
|
||||
});
|
||||
await userEvent.click(fileSettingsTab);
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -182,7 +182,7 @@ test('With sql role - renders all panels on the page on page load', async () =>
|
||||
|
||||
test('With sql role - renders distinct recent activities', async () => {
|
||||
await renderWelcome();
|
||||
const recentPanel = screen.getByRole('button', { name: 'collapsed Recents' });
|
||||
const recentPanel = screen.getByRole('button', { name: 'Recents' });
|
||||
userEvent.click(recentPanel);
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
|
||||
@@ -32,13 +32,11 @@ from superset.commands.chart.exceptions import (
|
||||
DashboardsForbiddenError,
|
||||
DashboardsNotFoundValidationError,
|
||||
)
|
||||
from superset.commands.exceptions import DatasourceTypeInvalidError
|
||||
from superset.commands.utils import get_datasource_by_id, populate_subjects
|
||||
from superset.daos.chart import ChartDAO
|
||||
from superset.daos.dashboard import DashboardDAO
|
||||
from superset.exceptions import SupersetSecurityException
|
||||
from superset.utils import json
|
||||
from superset.utils.core import DatasourceType
|
||||
from superset.utils.decorators import on_error, transaction
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -73,16 +71,6 @@ class CreateChartCommand(CreateMixin, BaseCommand):
|
||||
|
||||
# Validate/Populate datasource
|
||||
try:
|
||||
# Slice.datasource only ever resolves the ``table`` relationship
|
||||
# (see Slice.datasource in superset/models/slice.py), so a chart
|
||||
# pointed at any other datasource_type would "create"
|
||||
# successfully but could never actually render. Reject those
|
||||
# up front instead of failing later -- either at this lookup
|
||||
# (SavedQuery/Query have no ``.name`` attribute, so accessing it
|
||||
# below raises an unhandled AttributeError) or silently, by
|
||||
# producing a permanently broken chart.
|
||||
if datasource_type != DatasourceType.TABLE:
|
||||
raise DatasourceTypeInvalidError()
|
||||
datasource = get_datasource_by_id(datasource_id, datasource_type)
|
||||
self._properties["datasource_name"] = datasource.name
|
||||
security_manager.raise_for_access(datasource=datasource)
|
||||
|
||||
@@ -35,7 +35,6 @@ from superset.commands.chart.exceptions import (
|
||||
DashboardsNotFoundValidationError,
|
||||
DatasourceTypeUpdateRequiredValidationError,
|
||||
)
|
||||
from superset.commands.exceptions import DatasourceTypeInvalidError
|
||||
from superset.commands.utils import (
|
||||
compute_subjects,
|
||||
get_datasource_by_id,
|
||||
@@ -50,7 +49,6 @@ from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.tags.models import ObjectType
|
||||
from superset.utils import json
|
||||
from superset.utils.core import DatasourceType
|
||||
from superset.utils.decorators import on_error, transaction
|
||||
from superset.versioning.changes.normalization import (
|
||||
register_matching_normalization_context,
|
||||
@@ -223,22 +221,8 @@ class UpdateChartCommand(UpdateMixin, BaseCommand):
|
||||
exceptions.append(ex)
|
||||
|
||||
# Validate/Populate datasource
|
||||
# An empty datasource_type was already flagged above via
|
||||
# DatasourceTypeUpdateRequiredValidationError; skip this block so
|
||||
# we don't clobber that message with DatasourceTypeInvalidError.
|
||||
if datasource_id is not None and datasource_type:
|
||||
if datasource_id is not None:
|
||||
try:
|
||||
# Slice.datasource only ever resolves the ``table``
|
||||
# relationship (see Slice.datasource in
|
||||
# superset/models/slice.py), so repointing a chart at any
|
||||
# other datasource_type would "succeed" but leave the chart
|
||||
# permanently unable to render. Reject those up front
|
||||
# instead of failing later -- either at this lookup
|
||||
# (SavedQuery/Query have no ``.name`` attribute, so
|
||||
# accessing it below raises an unhandled AttributeError) or
|
||||
# silently.
|
||||
if datasource_type != DatasourceType.TABLE:
|
||||
raise DatasourceTypeInvalidError()
|
||||
datasource = get_datasource_by_id(datasource_id, datasource_type)
|
||||
self._properties["datasource_name"] = datasource.name
|
||||
security_manager.raise_for_access(datasource=datasource)
|
||||
|
||||
@@ -324,6 +324,20 @@ def load_configs(
|
||||
)
|
||||
exc.messages = {file_name: exc.messages}
|
||||
exceptions.append(exc)
|
||||
except json.JSONDecodeError as exc:
|
||||
# masked_encrypted_extra comes straight from the imported YAML
|
||||
# (before schema validation) and may not be valid JSON. Convert
|
||||
# the raw decode error into a ValidationError so it flows into
|
||||
# the aggregated CommandInvalidError like every other per-file
|
||||
# validation failure, instead of escaping as an opaque 500.
|
||||
logger.error(
|
||||
"Invalid JSON in masked_encrypted_extra for %s: %s",
|
||||
file_name,
|
||||
exc,
|
||||
)
|
||||
exceptions.append(
|
||||
ValidationError({file_name: {"masked_encrypted_extra": [str(exc)]}})
|
||||
)
|
||||
|
||||
return configs
|
||||
|
||||
|
||||
@@ -379,37 +379,37 @@ msgstr "虛擬"
|
||||
msgid "%s aggregates(s)"
|
||||
msgstr "%s 聚合"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s column"
|
||||
msgid_plural "%s columns"
|
||||
msgstr[0] "%s 列"
|
||||
msgstr[0] "%s 個欄位"
|
||||
|
||||
#, python-format
|
||||
msgid "%s column(s)"
|
||||
msgstr "%s 列"
|
||||
msgstr "%s 個欄位"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s day ago"
|
||||
msgid_plural "%s days ago"
|
||||
msgstr[0] "1 天之前"
|
||||
msgstr[0] "%s 天前"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s hr ago"
|
||||
msgid_plural "%s hr ago"
|
||||
msgstr[0] "%s 行"
|
||||
msgstr[0] "%s 小時前"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s imported"
|
||||
msgstr "數據集已導入"
|
||||
msgstr "已匯入 %s"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s item"
|
||||
msgid_plural "%s items"
|
||||
msgstr[0] "%s 個選項"
|
||||
msgstr[0] "%s 個項目"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s item(s)"
|
||||
msgstr "%s 個選項"
|
||||
msgstr "%s 個項目"
|
||||
|
||||
# Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: ar, cs, de,
|
||||
# es, fa, fr, ja, lv, mi, nl, pl, pt_BR, ru, sk, sl, sr, sr_Latn, tr, uk]
|
||||
@@ -419,15 +419,15 @@ msgid ""
|
||||
"all selected objects."
|
||||
msgstr "%s 個項目無法標記,因為您對所有選取的物件沒有編輯權限。"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s metric"
|
||||
msgid_plural "%s metrics"
|
||||
msgstr[0] "排序指標"
|
||||
msgstr[0] "%s 個指標"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s min ago"
|
||||
msgid_plural "%s min ago"
|
||||
msgstr[0] "月"
|
||||
msgstr[0] "%s 分鐘前"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
@@ -451,47 +451,47 @@ msgstr[0] "%s 個選項"
|
||||
msgid "%s option(s)"
|
||||
msgstr "%s 個選項"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s out of %s column"
|
||||
msgid_plural "%s out of %s columns"
|
||||
msgstr[0] "自定義列"
|
||||
msgstr[0] "已選取 %s/%s 個欄位"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s out of %s metric"
|
||||
msgid_plural "%s out of %s metrics"
|
||||
msgstr[0] "排序指標"
|
||||
msgstr[0] "已選取 %s/%s 個指標"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s out of %s selected"
|
||||
msgstr "%s 已選定"
|
||||
msgstr "已選取 %s/%s 個項目"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s recipients"
|
||||
msgstr "%s 最近"
|
||||
msgstr "%s 收件者"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s record..."
|
||||
msgid_plural "%s records..."
|
||||
msgstr[0] "%s 異常"
|
||||
msgstr[0] "%s 筆記錄..."
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s row"
|
||||
msgid_plural "%s rows"
|
||||
msgstr[0] "%s 行"
|
||||
msgstr[0] "%s 列"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s s ago"
|
||||
msgid_plural "%s s ago"
|
||||
msgstr[0] "30 天之前"
|
||||
msgstr[0] "%s 秒前"
|
||||
|
||||
#, python-format
|
||||
msgid "%s saved metric(s)"
|
||||
msgstr "%s 保存的指標"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s second"
|
||||
msgid_plural "%s seconds"
|
||||
msgstr[0] "5 秒"
|
||||
msgstr[0] "%s 秒"
|
||||
|
||||
# Machine-translated via backfill_po.py (claude-sonnet-4-6) [refs: de, es, sr,
|
||||
# sr_Latn]
|
||||
@@ -511,13 +511,13 @@ msgstr "%s 個語意檢視新增失敗"
|
||||
msgid "%s semantic view(s) failed to add: %s"
|
||||
msgstr "%s 個語意檢視新增失敗:%s"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s tab selected"
|
||||
msgstr "%s 已選定"
|
||||
msgstr "已選取「%s」分頁"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "%s updated"
|
||||
msgstr "上次更新 %s"
|
||||
msgstr "已更新 %s"
|
||||
|
||||
#, python-format
|
||||
msgid "%s%s"
|
||||
@@ -677,13 +677,11 @@ msgstr "每年年初的頻率"
|
||||
msgid "10 minute"
|
||||
msgstr "10 分鐘"
|
||||
|
||||
#, fuzzy
|
||||
msgid "10 seconds"
|
||||
msgstr "30 秒"
|
||||
msgstr "10 秒"
|
||||
|
||||
#, fuzzy
|
||||
msgid "10/90 percentiles"
|
||||
msgstr "9/91 百分位"
|
||||
msgstr "10/90 百分位數"
|
||||
|
||||
#. do-not-translate
|
||||
msgid "10000"
|
||||
@@ -696,9 +694,8 @@ msgstr "週"
|
||||
msgid "104 weeks ago"
|
||||
msgstr "104 週之前"
|
||||
|
||||
#, fuzzy
|
||||
msgid "12 hours"
|
||||
msgstr "1 小時"
|
||||
msgstr "12 小時"
|
||||
|
||||
msgid "15 minute"
|
||||
msgstr "15 分鐘"
|
||||
@@ -761,9 +758,8 @@ msgstr "2/98 百分位"
|
||||
msgid "22"
|
||||
msgstr "22"
|
||||
|
||||
#, fuzzy
|
||||
msgid "24 hours"
|
||||
msgstr "6 小時"
|
||||
msgstr "24 小時"
|
||||
|
||||
#, fuzzy
|
||||
msgid "28 days"
|
||||
@@ -831,9 +827,8 @@ msgstr "5 秒"
|
||||
msgid "5 seconds"
|
||||
msgstr "5 秒"
|
||||
|
||||
#, fuzzy
|
||||
msgid "5/95 percentiles"
|
||||
msgstr "9/91 百分位"
|
||||
msgstr "5/95 百分位數"
|
||||
|
||||
#, fuzzy
|
||||
msgid "52 weeks"
|
||||
|
||||
@@ -35,14 +35,12 @@ from superset.extensions import cache_manager, db, security_manager
|
||||
from superset.models.core import Database, FavStar, FavStarClassName
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.models.sql_lab import SavedQuery
|
||||
from superset.reports.models import ReportSchedule, ReportScheduleType
|
||||
from superset.subjects.models import Subject
|
||||
from superset.subjects.types import SubjectType
|
||||
from superset.tags.models import ObjectType, Tag, TaggedObject, TagType
|
||||
from superset.utils import json
|
||||
from superset.utils.core import get_example_default_schema
|
||||
from superset.utils.database import get_example_database
|
||||
from tests.integration_tests.base_api_tests import ApiEditorsTestCaseMixin
|
||||
from tests.integration_tests.base_tests import (
|
||||
subjects_from_users,
|
||||
@@ -662,47 +660,6 @@ class TestChartApi(ApiEditorsTestCaseMixin, InsertChartMixin, SupersetTestCase):
|
||||
response = json.loads(rv.data.decode("utf-8"))
|
||||
assert response == {"message": {"datasource_id": ["Datasource does not exist"]}}
|
||||
|
||||
def test_create_chart_from_saved_query_rejected_cleanly(self):
|
||||
"""
|
||||
Chart API: creating a chart with datasource_type="saved_query" must
|
||||
fail with a clean validation error, not the unhandled 500 "Fatal
|
||||
error" reported in apache/superset#29697. Slice.datasource only
|
||||
ever resolves the "table" relationship, so even a chart that
|
||||
"created" successfully with this datasource_type could never
|
||||
actually render -- "saved_query" is a real, existing row here
|
||||
(not a bad ID), reproducing the original report exactly rather
|
||||
than a not-found case.
|
||||
"""
|
||||
self.login(ADMIN_USERNAME)
|
||||
example_db = get_example_database()
|
||||
saved_query = SavedQuery(
|
||||
db_id=example_db.id,
|
||||
label="issue-29697-repro",
|
||||
schema=get_example_default_schema(),
|
||||
sql="SELECT 1 AS value",
|
||||
)
|
||||
db.session.add(saved_query)
|
||||
db.session.commit()
|
||||
saved_query_id = saved_query.id
|
||||
|
||||
chart_data = {
|
||||
"slice_name": "issue-29697-repro-chart",
|
||||
"datasource_id": saved_query_id,
|
||||
"datasource_type": "saved_query",
|
||||
"viz_type": "table",
|
||||
}
|
||||
try:
|
||||
rv = self.post_assert_metric("/api/v1/chart/", chart_data, "post")
|
||||
|
||||
assert rv.status_code == 422
|
||||
response = json.loads(rv.data.decode("utf-8"))
|
||||
assert response == {
|
||||
"message": {"datasource_type": ["Datasource type is invalid"]}
|
||||
}
|
||||
finally:
|
||||
db.session.delete(db.session.query(SavedQuery).get(saved_query_id))
|
||||
db.session.commit()
|
||||
|
||||
@pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
|
||||
def test_create_chart_validate_user_is_dashboard_editor(self):
|
||||
"""
|
||||
|
||||
@@ -1,154 +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.
|
||||
"""Unit tests for CreateChartCommand.
|
||||
|
||||
Regression coverage for apache/superset#29697: POST /api/v1/chart/ with
|
||||
datasource_type="saved_query" (or "query") crashes with an unhandled
|
||||
AttributeError -- reported to API clients as an opaque 500 "Fatal error" --
|
||||
because SavedQuery and Query models have no ``.name`` attribute, and because
|
||||
Slice.datasource only ever resolves a ``table``-typed datasource, so even a
|
||||
successfully created chart of another type could never actually render.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from superset.commands.chart.create import CreateChartCommand
|
||||
from superset.commands.chart.exceptions import ChartForbiddenError, ChartInvalidError
|
||||
from superset.commands.exceptions import DatasourceTypeInvalidError
|
||||
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
|
||||
from superset.exceptions import SupersetSecurityException
|
||||
|
||||
|
||||
def _base_mocks(mocker: MockerFixture) -> None:
|
||||
mocker.patch(
|
||||
"superset.commands.chart.create.DashboardDAO.find_by_ids", return_value=[]
|
||||
)
|
||||
mocker.patch(
|
||||
"superset.commands.chart.create.populate_subjects",
|
||||
side_effect=lambda properties, exceptions: None,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("datasource_type", ["saved_query", "query"])
|
||||
def test_create_chart_rejects_non_table_datasource_type(
|
||||
mocker: MockerFixture, datasource_type: str
|
||||
) -> None:
|
||||
"""A chart can only ever query a table-backed datasource -- Slice.datasource
|
||||
only ever resolves the ``table`` relationship, so any other type would
|
||||
produce a chart that "creates" successfully but can never render.
|
||||
|
||||
The two types fail differently before this fix, which is exactly why
|
||||
both are covered here:
|
||||
- "saved_query": SavedQuery has no ``.name`` attribute, so validation
|
||||
crashes with an unhandled AttributeError -- surfaced to API clients as
|
||||
an opaque 500 "Fatal error" (apache/superset#29697).
|
||||
- "query": Query *does* define a synthetic ``.name`` property (used for
|
||||
CTAS table naming, not as a real display name), so this one doesn't
|
||||
crash -- it silently "succeeds" and creates a chart with a nonsense
|
||||
name and a datasource that Slice.datasource can never resolve.
|
||||
|
||||
``get_datasource_by_id`` is mocked with ``spec=`` the real model classes
|
||||
so accessing ``.name`` on the mock behaves exactly like the real ORM
|
||||
objects do if the new guard doesn't stop the code from getting there;
|
||||
``raise_for_access`` is mocked to a no-op so nothing downstream masks
|
||||
that behavior.
|
||||
"""
|
||||
from superset.models.sql_lab import Query, SavedQuery
|
||||
|
||||
_base_mocks(mocker)
|
||||
model_cls = SavedQuery if datasource_type == "saved_query" else Query
|
||||
get_datasource_by_id = mocker.patch(
|
||||
"superset.commands.chart.create.get_datasource_by_id",
|
||||
return_value=mocker.MagicMock(spec=model_cls),
|
||||
)
|
||||
mocker.patch("superset.commands.chart.create.security_manager.raise_for_access")
|
||||
|
||||
with pytest.raises(ChartInvalidError) as exc_info:
|
||||
CreateChartCommand(
|
||||
{
|
||||
"datasource_id": 11,
|
||||
"datasource_type": datasource_type,
|
||||
"slice_name": "some_name",
|
||||
"viz_type": "table",
|
||||
}
|
||||
).validate()
|
||||
|
||||
assert any(
|
||||
isinstance(ex, DatasourceTypeInvalidError) for ex in exc_info.value._exceptions
|
||||
)
|
||||
# The invalid type must be rejected before ever touching the datasource
|
||||
# lookup, not caught incidentally by some downstream failure.
|
||||
get_datasource_by_id.assert_not_called()
|
||||
|
||||
|
||||
def test_create_chart_accepts_table_datasource(mocker: MockerFixture) -> None:
|
||||
"""The one supported datasource_type must keep working."""
|
||||
_base_mocks(mocker)
|
||||
datasource = mocker.MagicMock(name="table_datasource")
|
||||
datasource.name = "my_table"
|
||||
mocker.patch(
|
||||
"superset.commands.chart.create.get_datasource_by_id",
|
||||
return_value=datasource,
|
||||
)
|
||||
mocker.patch("superset.commands.chart.create.security_manager.raise_for_access")
|
||||
|
||||
cmd = CreateChartCommand(
|
||||
{
|
||||
"datasource_id": 11,
|
||||
"datasource_type": "table",
|
||||
"slice_name": "some_name",
|
||||
"viz_type": "table",
|
||||
}
|
||||
)
|
||||
cmd.validate()
|
||||
|
||||
assert cmd._properties["datasource_name"] == "my_table"
|
||||
|
||||
|
||||
def test_create_chart_datasource_access_denied_still_raises_forbidden(
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
"""The invalid-type guard must not shadow the existing access-denied path
|
||||
for a legitimately table-typed datasource the user can't access."""
|
||||
_base_mocks(mocker)
|
||||
datasource = mocker.MagicMock()
|
||||
datasource.name = "my_table"
|
||||
mocker.patch(
|
||||
"superset.commands.chart.create.get_datasource_by_id",
|
||||
return_value=datasource,
|
||||
)
|
||||
mocker.patch(
|
||||
"superset.commands.chart.create.security_manager.raise_for_access",
|
||||
side_effect=SupersetSecurityException(
|
||||
SupersetError(
|
||||
error_type=SupersetErrorType.DATASOURCE_SECURITY_ACCESS_ERROR,
|
||||
message="No access",
|
||||
level=ErrorLevel.ERROR,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
with pytest.raises(ChartForbiddenError):
|
||||
CreateChartCommand(
|
||||
{
|
||||
"datasource_id": 11,
|
||||
"datasource_type": "table",
|
||||
"slice_name": "some_name",
|
||||
"viz_type": "table",
|
||||
}
|
||||
).validate()
|
||||
@@ -17,13 +17,8 @@
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from superset.commands.chart.exceptions import (
|
||||
ChartForbiddenError,
|
||||
ChartInvalidError,
|
||||
DatasourceTypeUpdateRequiredValidationError,
|
||||
)
|
||||
from superset.commands.chart.exceptions import ChartForbiddenError, ChartInvalidError
|
||||
from superset.commands.chart.update import UpdateChartCommand
|
||||
from superset.commands.exceptions import DatasourceTypeInvalidError
|
||||
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
|
||||
from superset.exceptions import SupersetSecurityException
|
||||
from superset.utils import json
|
||||
@@ -243,71 +238,3 @@ def test_update_chart_query_context_without_datasource_is_allowed(
|
||||
1,
|
||||
{"query_context": query_context, "query_context_generation": True},
|
||||
).validate()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("datasource_type", ["saved_query", "query"])
|
||||
def test_update_chart_rejects_repointing_to_non_table_datasource(
|
||||
mocker: MockerFixture, datasource_type: str
|
||||
) -> None:
|
||||
"""Repointing a chart's datasource_id must be rejected the same way
|
||||
CreateChartCommand rejects it (apache/superset#29697): Slice.datasource
|
||||
only ever resolves the ``table`` relationship, so repointing at a
|
||||
saved_query or query datasource would "succeed" but leave the chart
|
||||
permanently unable to render -- or, for saved_query specifically, crash
|
||||
on SavedQuery's missing ``.name`` attribute before that point is even
|
||||
reached. This is a regular (non-query-context) update, so it goes
|
||||
through editorship + compute_subjects, unlike the query-context-only
|
||||
tests above."""
|
||||
find_by_id = mocker.patch("superset.commands.chart.update.ChartDAO.find_by_id")
|
||||
find_by_id.return_value = mocker.MagicMock(id=1, tags=[], dashboards=[])
|
||||
mocker.patch("superset.commands.chart.update.security_manager.raise_for_editorship")
|
||||
mocker.patch(
|
||||
"superset.commands.chart.update.compute_subjects",
|
||||
side_effect=lambda model, properties, exceptions: None,
|
||||
)
|
||||
get_datasource_by_id = mocker.patch(
|
||||
"superset.commands.chart.update.get_datasource_by_id"
|
||||
)
|
||||
|
||||
with pytest.raises(ChartInvalidError) as exc_info:
|
||||
UpdateChartCommand(
|
||||
1, {"datasource_id": 11, "datasource_type": datasource_type}
|
||||
).validate()
|
||||
|
||||
assert any(
|
||||
isinstance(ex, DatasourceTypeInvalidError) for ex in exc_info.value._exceptions
|
||||
)
|
||||
get_datasource_by_id.assert_not_called()
|
||||
|
||||
|
||||
def test_update_chart_missing_datasource_type_keeps_required_error(
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
"""When datasource_id is given without datasource_type, the response
|
||||
must keep reporting DatasourceTypeUpdateRequiredValidationError
|
||||
("Datasource type is required") rather than having it overwritten by
|
||||
DatasourceTypeInvalidError ("Datasource type is invalid") -- both
|
||||
exceptions key their message under ``datasource_type``, and
|
||||
normalized_messages() only keeps the last one written for a given key."""
|
||||
find_by_id = mocker.patch("superset.commands.chart.update.ChartDAO.find_by_id")
|
||||
find_by_id.return_value = mocker.MagicMock(id=1, tags=[], dashboards=[])
|
||||
mocker.patch("superset.commands.chart.update.security_manager.raise_for_editorship")
|
||||
mocker.patch(
|
||||
"superset.commands.chart.update.compute_subjects",
|
||||
side_effect=lambda model, properties, exceptions: None,
|
||||
)
|
||||
get_datasource_by_id = mocker.patch(
|
||||
"superset.commands.chart.update.get_datasource_by_id"
|
||||
)
|
||||
|
||||
with pytest.raises(ChartInvalidError) as exc_info:
|
||||
UpdateChartCommand(1, {"datasource_id": 11}).validate()
|
||||
|
||||
assert any(
|
||||
isinstance(ex, DatasourceTypeUpdateRequiredValidationError)
|
||||
for ex in exc_info.value._exceptions
|
||||
)
|
||||
assert not any(
|
||||
isinstance(ex, DatasourceTypeInvalidError) for ex in exc_info.value._exceptions
|
||||
)
|
||||
get_datasource_by_id.assert_not_called()
|
||||
|
||||
@@ -166,6 +166,108 @@ class TestLoadYaml:
|
||||
load_yaml("test.yaml", 'key: "unterminated string')
|
||||
|
||||
|
||||
class TestLoadConfigs:
|
||||
"""
|
||||
load_configs() merges caller-supplied ``encrypted_extra_secrets`` into the
|
||||
``masked_encrypted_extra`` field of each config, which comes straight from
|
||||
the imported YAML (before schema validation). A malformed value there used
|
||||
to raise a raw simplejson.JSONDecodeError that escaped uncaught (opaque
|
||||
500); it must instead be collected as a ValidationError like every other
|
||||
per-file failure.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _trivial_schema(): # type: ignore[no-untyped-def]
|
||||
from marshmallow import EXCLUDE, Schema
|
||||
|
||||
class TrivialSchema(Schema):
|
||||
class Meta:
|
||||
unknown = EXCLUDE
|
||||
|
||||
return TrivialSchema()
|
||||
|
||||
@patch("superset.commands.importers.v1.utils.db")
|
||||
def test_invalid_json_in_masked_encrypted_extra_is_collected(
|
||||
self, mock_db: object
|
||||
) -> None:
|
||||
"""A non-JSON ``masked_encrypted_extra`` is converted into a
|
||||
ValidationError appended to ``exceptions`` rather than raising."""
|
||||
from marshmallow.exceptions import ValidationError
|
||||
|
||||
from superset.commands.importers.v1.utils import load_configs
|
||||
|
||||
# No existing databases / ssh tunnels in the (mocked) metadata DB.
|
||||
mock_db.session.query.return_value.all.return_value = [] # type: ignore[attr-defined]
|
||||
|
||||
file_name = "databases/db.yaml"
|
||||
contents = {
|
||||
file_name: (
|
||||
"uuid: abc-123\n"
|
||||
"password: secret\n"
|
||||
"masked_encrypted_extra: not valid json\n"
|
||||
)
|
||||
}
|
||||
exceptions: list[ValidationError] = []
|
||||
|
||||
configs = load_configs(
|
||||
contents=contents,
|
||||
schemas={"databases/": self._trivial_schema()},
|
||||
passwords={},
|
||||
exceptions=exceptions,
|
||||
ssh_tunnel_passwords={},
|
||||
ssh_tunnel_private_keys={},
|
||||
ssh_tunnel_priv_key_passwords={},
|
||||
encrypted_extra_secrets={file_name: {"$.foo": "actual_secret"}},
|
||||
)
|
||||
|
||||
# The bad file is not added to configs, and a structured error is
|
||||
# collected instead of a raw JSONDecodeError propagating out.
|
||||
assert file_name not in configs
|
||||
assert len(exceptions) == 1
|
||||
assert isinstance(exceptions[0], ValidationError)
|
||||
assert file_name in exceptions[0].messages
|
||||
assert "masked_encrypted_extra" in exceptions[0].messages[file_name]
|
||||
|
||||
@patch("superset.commands.importers.v1.utils.db")
|
||||
def test_valid_json_in_masked_encrypted_extra_still_merges(
|
||||
self, mock_db: object
|
||||
) -> None:
|
||||
"""Control: valid JSON in ``masked_encrypted_extra`` still has the
|
||||
secrets merged in and produces no exceptions."""
|
||||
from marshmallow.exceptions import ValidationError
|
||||
|
||||
from superset.commands.importers.v1.utils import load_configs
|
||||
from superset.utils import json
|
||||
|
||||
mock_db.session.query.return_value.all.return_value = [] # type: ignore[attr-defined]
|
||||
|
||||
file_name = "databases/db.yaml"
|
||||
contents = {
|
||||
file_name: (
|
||||
"uuid: abc-123\n"
|
||||
"password: secret\n"
|
||||
'masked_encrypted_extra: \'{"foo": "XXXXXXXXXX"}\'\n'
|
||||
)
|
||||
}
|
||||
exceptions: list[ValidationError] = []
|
||||
|
||||
configs = load_configs(
|
||||
contents=contents,
|
||||
schemas={"databases/": self._trivial_schema()},
|
||||
passwords={},
|
||||
exceptions=exceptions,
|
||||
ssh_tunnel_passwords={},
|
||||
ssh_tunnel_private_keys={},
|
||||
ssh_tunnel_priv_key_passwords={},
|
||||
encrypted_extra_secrets={file_name: {"$.foo": "actual_secret"}},
|
||||
)
|
||||
|
||||
assert exceptions == []
|
||||
assert file_name in configs
|
||||
merged = json.loads(configs[file_name]["masked_encrypted_extra"])
|
||||
assert merged == {"foo": "actual_secret"}
|
||||
|
||||
|
||||
class TestLoadConfigsNonMappingYaml:
|
||||
"""A syntactically valid YAML document whose top-level value is a
|
||||
scalar or list (not a mapping) must be reported as a schema validation
|
||||
|
||||
Reference in New Issue
Block a user