mirror of
https://github.com/apache/superset.git
synced 2026-05-13 03:45:12 +00:00
Compare commits
24 Commits
dependabot
...
fix-no-top
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98f6d627ed | ||
|
|
1d1a0e6fec | ||
|
|
494c29f5bf | ||
|
|
ad7075d2aa | ||
|
|
3e1cfc6d69 | ||
|
|
fcf3f6c0d5 | ||
|
|
14ba666594 | ||
|
|
1c795418d2 | ||
|
|
6271272e60 | ||
|
|
2cf4a2c31f | ||
|
|
2adb6f64eb | ||
|
|
5a453fe95d | ||
|
|
245fffca79 | ||
|
|
372b50e19d | ||
|
|
d83b0c5ce3 | ||
|
|
ff323ba328 | ||
|
|
78fada7f43 | ||
|
|
b71c556560 | ||
|
|
7d6513f946 | ||
|
|
d25c07586d | ||
|
|
93066ade52 | ||
|
|
3fe59024ef | ||
|
|
0b442ef9ae | ||
|
|
5bb88b2b74 |
@@ -69,7 +69,7 @@
|
||||
"@superset-ui/core": "^0.20.4",
|
||||
"@swc/core": "^1.15.33",
|
||||
"antd": "^6.3.7",
|
||||
"baseline-browser-mapping": "^2.10.27",
|
||||
"baseline-browser-mapping": "^2.10.29",
|
||||
"caniuse-lite": "^1.0.30001792",
|
||||
"docusaurus-plugin-openapi-docs": "^5.0.2",
|
||||
"docusaurus-theme-openapi-docs": "^5.0.2",
|
||||
|
||||
@@ -261,6 +261,15 @@
|
||||
js-tokens "^4.0.0"
|
||||
picocolors "^1.1.1"
|
||||
|
||||
"@babel/code-frame@^7.29.0":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c"
|
||||
integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.28.5"
|
||||
js-tokens "^4.0.0"
|
||||
picocolors "^1.1.1"
|
||||
|
||||
"@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.0":
|
||||
version "7.28.0"
|
||||
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz"
|
||||
@@ -303,6 +312,17 @@
|
||||
"@jridgewell/trace-mapping" "^0.3.28"
|
||||
jsesc "^3.0.2"
|
||||
|
||||
"@babel/generator@^7.29.0":
|
||||
version "7.29.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50"
|
||||
integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.29.0"
|
||||
"@babel/types" "^7.29.0"
|
||||
"@jridgewell/gen-mapping" "^0.3.12"
|
||||
"@jridgewell/trace-mapping" "^0.3.28"
|
||||
jsesc "^3.0.2"
|
||||
|
||||
"@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3":
|
||||
version "7.27.3"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz"
|
||||
@@ -404,6 +424,11 @@
|
||||
resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz"
|
||||
integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==
|
||||
|
||||
"@babel/helper-plugin-utils@^7.28.6":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8"
|
||||
integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==
|
||||
|
||||
"@babel/helper-remap-async-to-generator@^7.27.1":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz"
|
||||
@@ -435,11 +460,6 @@
|
||||
resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz"
|
||||
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.27.1":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz"
|
||||
integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.28.5":
|
||||
version "7.28.5"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz"
|
||||
@@ -474,6 +494,13 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.28.6"
|
||||
|
||||
"@babel/parser@^7.29.0":
|
||||
version "7.29.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.3.tgz#116f70a77958307fceac27747573032f8a62f88e"
|
||||
integrity sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==
|
||||
dependencies:
|
||||
"@babel/types" "^7.29.0"
|
||||
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz"
|
||||
@@ -758,14 +785,14 @@
|
||||
"@babel/helper-plugin-utils" "^7.27.1"
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs@^7.27.1":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz"
|
||||
integrity sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==
|
||||
version "7.29.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz#f621105da99919c15cf4bde6fcc7346ef95e7b20"
|
||||
integrity sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==
|
||||
dependencies:
|
||||
"@babel/helper-module-transforms" "^7.27.1"
|
||||
"@babel/helper-plugin-utils" "^7.27.1"
|
||||
"@babel/helper-validator-identifier" "^7.27.1"
|
||||
"@babel/traverse" "^7.27.1"
|
||||
"@babel/helper-module-transforms" "^7.28.6"
|
||||
"@babel/helper-plugin-utils" "^7.28.6"
|
||||
"@babel/helper-validator-identifier" "^7.28.5"
|
||||
"@babel/traverse" "^7.29.0"
|
||||
|
||||
"@babel/plugin-transform-modules-umd@^7.27.1":
|
||||
version "7.27.1"
|
||||
@@ -1163,6 +1190,19 @@
|
||||
"@babel/types" "^7.28.6"
|
||||
debug "^4.3.1"
|
||||
|
||||
"@babel/traverse@^7.29.0":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a"
|
||||
integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.29.0"
|
||||
"@babel/generator" "^7.29.0"
|
||||
"@babel/helper-globals" "^7.28.0"
|
||||
"@babel/parser" "^7.29.0"
|
||||
"@babel/template" "^7.28.6"
|
||||
"@babel/types" "^7.29.0"
|
||||
debug "^4.3.1"
|
||||
|
||||
"@babel/types@^7.21.3", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.4.4":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz"
|
||||
@@ -1171,6 +1211,14 @@
|
||||
"@babel/helper-string-parser" "^7.27.1"
|
||||
"@babel/helper-validator-identifier" "^7.28.5"
|
||||
|
||||
"@babel/types@^7.29.0":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7"
|
||||
integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.27.1"
|
||||
"@babel/helper-validator-identifier" "^7.28.5"
|
||||
|
||||
"@braintree/sanitize-url@^7.0.4":
|
||||
version "7.1.1"
|
||||
resolved "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz"
|
||||
@@ -5794,10 +5842,10 @@ base64-js@^1.3.1, base64-js@^1.5.1:
|
||||
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
baseline-browser-mapping@^2.10.27, baseline-browser-mapping@^2.9.0, baseline-browser-mapping@^2.9.19:
|
||||
version "2.10.27"
|
||||
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz#fee941c2a0b42cdf83c6427e4c830b1d0bdab2c3"
|
||||
integrity sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==
|
||||
baseline-browser-mapping@^2.10.29, baseline-browser-mapping@^2.9.0, baseline-browser-mapping@^2.9.19:
|
||||
version "2.10.29"
|
||||
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz#47bdc13027af28d341f367a4f35a07ce872e27b4"
|
||||
integrity sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==
|
||||
|
||||
batch@0.6.1:
|
||||
version "0.6.1"
|
||||
@@ -8118,9 +8166,9 @@ fast-safe-stringify@^2.0.7:
|
||||
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
|
||||
|
||||
fast-uri@^3.0.1:
|
||||
version "3.0.6"
|
||||
resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz"
|
||||
integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.2.tgz#8af3d4fc9d3e71b11572cc2673b514a7d1a8c8ec"
|
||||
integrity sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==
|
||||
|
||||
fastq@^1.6.0:
|
||||
version "1.19.1"
|
||||
|
||||
@@ -71,7 +71,7 @@ dependencies = [
|
||||
"marshmallow>=3.0, <4",
|
||||
"marshmallow-union>=0.1",
|
||||
"msgpack>=1.0.0, <1.2",
|
||||
"nh3>=0.2.11, <0.3",
|
||||
"nh3>=0.2.11, <0.4",
|
||||
"numpy>1.23.5, <2.3",
|
||||
"packaging",
|
||||
# --------------------------
|
||||
@@ -131,10 +131,10 @@ d1 = [
|
||||
]
|
||||
databend = ["databend-sqlalchemy>=0.3.2, <1.0"]
|
||||
databricks = [
|
||||
"databricks-sql-connector==4.1.2",
|
||||
"databricks-sql-connector==4.2.6",
|
||||
"databricks-sqlalchemy==1.0.5",
|
||||
]
|
||||
db2 = ["ibm-db-sa>0.3.8, <=0.4.0"]
|
||||
db2 = ["ibm-db-sa>0.3.8, <=0.4.4"]
|
||||
denodo = ["denodo-sqlalchemy>=1.0.6,<2.1.0"]
|
||||
dremio = ["sqlalchemy-dremio>=1.2.1, <4"]
|
||||
drill = ["sqlalchemy-drill>=1.1.4, <2"]
|
||||
@@ -152,7 +152,7 @@ fastmcp = [
|
||||
# heuristic that under-counts JSON-heavy MCP responses.
|
||||
"tiktoken>=0.7.0,<1.0",
|
||||
]
|
||||
firebird = ["sqlalchemy-firebird>=0.7.0, <0.8"]
|
||||
firebird = ["sqlalchemy-firebird>=0.7.0, <2.2"]
|
||||
firebolt = ["firebolt-sqlalchemy>=1.0.0, <2"]
|
||||
gevent = ["gevent>=23.9.1"]
|
||||
gsheets = ["shillelagh[gsheetsapi]>=1.4.4, <2"]
|
||||
@@ -179,7 +179,7 @@ ocient = [
|
||||
]
|
||||
oracle = ["cx-Oracle>8.0.0, <8.4"]
|
||||
parseable = ["sqlalchemy-parseable>=0.1.3,<0.2.0"]
|
||||
pinot = ["pinotdb>=5.0.0, <6.0.0"]
|
||||
pinot = ["pinotdb>=5.0.0, <10.0.0"]
|
||||
playwright = ["playwright>=1.37.0, <2"]
|
||||
postgres = ["psycopg2-binary==2.9.12"]
|
||||
presto = ["pyhive[presto]>=0.6.5"]
|
||||
@@ -224,7 +224,7 @@ development = [
|
||||
"progress>=1.5,<2",
|
||||
"psutil",
|
||||
"pyfakefs",
|
||||
"pyinstrument>=4.0.2,<5",
|
||||
"pyinstrument>=4.0.2,<6",
|
||||
"pylint",
|
||||
"pytest<8.0.0", # hairy issue with pytest >=8 where current_app proxies are not set in time
|
||||
"pytest-asyncio",
|
||||
|
||||
351
superset-frontend/package-lock.json
generated
351
superset-frontend/package-lock.json
generated
@@ -222,7 +222,7 @@
|
||||
"@types/rison": "0.1.0",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
"@types/unzipper": "^0.10.11",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.2",
|
||||
"@typescript-eslint/parser": "^8.58.2",
|
||||
"babel-jest": "^30.0.2",
|
||||
"babel-loader": "^10.1.1",
|
||||
@@ -290,7 +290,7 @@
|
||||
"typescript": "5.4.5",
|
||||
"unzipper": "^0.12.3",
|
||||
"vm-browserify": "^1.1.2",
|
||||
"wait-on": "^9.0.5",
|
||||
"wait-on": "^9.0.6",
|
||||
"webpack": "^5.106.2",
|
||||
"webpack-bundle-analyzer": "^5.3.0",
|
||||
"webpack-cli": "^6.0.1",
|
||||
@@ -14358,17 +14358,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz",
|
||||
"integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz",
|
||||
"integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.59.1",
|
||||
"@typescript-eslint/type-utils": "8.59.1",
|
||||
"@typescript-eslint/utils": "8.59.1",
|
||||
"@typescript-eslint/visitor-keys": "8.59.1",
|
||||
"@typescript-eslint/scope-manager": "8.59.2",
|
||||
"@typescript-eslint/type-utils": "8.59.2",
|
||||
"@typescript-eslint/utils": "8.59.2",
|
||||
"@typescript-eslint/visitor-keys": "8.59.2",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -14381,20 +14381,42 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.59.1",
|
||||
"@typescript-eslint/parser": "^8.59.2",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz",
|
||||
"integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==",
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.2.tgz",
|
||||
"integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.59.1",
|
||||
"@typescript-eslint/visitor-keys": "8.59.1"
|
||||
"@typescript-eslint/tsconfig-utils": "^8.59.2",
|
||||
"@typescript-eslint/types": "^8.59.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz",
|
||||
"integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.59.2",
|
||||
"@typescript-eslint/visitor-keys": "8.59.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -14404,10 +14426,27 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz",
|
||||
"integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz",
|
||||
"integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz",
|
||||
"integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -14419,16 +14458,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz",
|
||||
"integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz",
|
||||
"integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.59.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.59.1",
|
||||
"@typescript-eslint/types": "8.59.1",
|
||||
"@typescript-eslint/visitor-keys": "8.59.1",
|
||||
"@typescript-eslint/project-service": "8.59.2",
|
||||
"@typescript-eslint/tsconfig-utils": "8.59.2",
|
||||
"@typescript-eslint/types": "8.59.2",
|
||||
"@typescript-eslint/visitor-keys": "8.59.2",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -14447,16 +14486,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz",
|
||||
"integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.2.tgz",
|
||||
"integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.59.1",
|
||||
"@typescript-eslint/types": "8.59.1",
|
||||
"@typescript-eslint/typescript-estree": "8.59.1"
|
||||
"@typescript-eslint/scope-manager": "8.59.2",
|
||||
"@typescript-eslint/types": "8.59.2",
|
||||
"@typescript-eslint/typescript-estree": "8.59.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -14471,13 +14510,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz",
|
||||
"integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz",
|
||||
"integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.59.1",
|
||||
"@typescript-eslint/types": "8.59.2",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -14499,9 +14538,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -14551,16 +14590,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz",
|
||||
"integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.2.tgz",
|
||||
"integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.59.1",
|
||||
"@typescript-eslint/types": "8.59.1",
|
||||
"@typescript-eslint/typescript-estree": "8.59.1",
|
||||
"@typescript-eslint/visitor-keys": "8.59.1",
|
||||
"@typescript-eslint/scope-manager": "8.59.2",
|
||||
"@typescript-eslint/types": "8.59.2",
|
||||
"@typescript-eslint/typescript-estree": "8.59.2",
|
||||
"@typescript-eslint/visitor-keys": "8.59.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -14575,15 +14614,37 @@
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz",
|
||||
"integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==",
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.2.tgz",
|
||||
"integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.59.1",
|
||||
"@typescript-eslint/visitor-keys": "8.59.1"
|
||||
"@typescript-eslint/tsconfig-utils": "^8.59.2",
|
||||
"@typescript-eslint/types": "^8.59.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz",
|
||||
"integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.59.2",
|
||||
"@typescript-eslint/visitor-keys": "8.59.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -14593,10 +14654,27 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz",
|
||||
"integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz",
|
||||
"integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz",
|
||||
"integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -14608,16 +14686,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz",
|
||||
"integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz",
|
||||
"integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.59.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.59.1",
|
||||
"@typescript-eslint/types": "8.59.1",
|
||||
"@typescript-eslint/visitor-keys": "8.59.1",
|
||||
"@typescript-eslint/project-service": "8.59.2",
|
||||
"@typescript-eslint/tsconfig-utils": "8.59.2",
|
||||
"@typescript-eslint/types": "8.59.2",
|
||||
"@typescript-eslint/visitor-keys": "8.59.2",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -14636,13 +14714,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz",
|
||||
"integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz",
|
||||
"integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.59.1",
|
||||
"@typescript-eslint/types": "8.59.2",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -14664,9 +14742,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/brace-expansion": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -14777,15 +14855,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz",
|
||||
"integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz",
|
||||
"integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.59.1",
|
||||
"@typescript-eslint/typescript-estree": "8.59.1",
|
||||
"@typescript-eslint/utils": "8.59.1",
|
||||
"@typescript-eslint/types": "8.59.2",
|
||||
"@typescript-eslint/typescript-estree": "8.59.2",
|
||||
"@typescript-eslint/utils": "8.59.2",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -14801,15 +14879,37 @@
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz",
|
||||
"integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==",
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.2.tgz",
|
||||
"integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.59.1",
|
||||
"@typescript-eslint/visitor-keys": "8.59.1"
|
||||
"@typescript-eslint/tsconfig-utils": "^8.59.2",
|
||||
"@typescript-eslint/types": "^8.59.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz",
|
||||
"integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.59.2",
|
||||
"@typescript-eslint/visitor-keys": "8.59.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -14819,10 +14919,27 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz",
|
||||
"integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz",
|
||||
"integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz",
|
||||
"integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -14834,16 +14951,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz",
|
||||
"integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz",
|
||||
"integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.59.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.59.1",
|
||||
"@typescript-eslint/types": "8.59.1",
|
||||
"@typescript-eslint/visitor-keys": "8.59.1",
|
||||
"@typescript-eslint/project-service": "8.59.2",
|
||||
"@typescript-eslint/tsconfig-utils": "8.59.2",
|
||||
"@typescript-eslint/types": "8.59.2",
|
||||
"@typescript-eslint/visitor-keys": "8.59.2",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -14862,16 +14979,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz",
|
||||
"integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.2.tgz",
|
||||
"integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.59.1",
|
||||
"@typescript-eslint/types": "8.59.1",
|
||||
"@typescript-eslint/typescript-estree": "8.59.1"
|
||||
"@typescript-eslint/scope-manager": "8.59.2",
|
||||
"@typescript-eslint/types": "8.59.2",
|
||||
"@typescript-eslint/typescript-estree": "8.59.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -14886,13 +15003,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz",
|
||||
"integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==",
|
||||
"version": "8.59.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz",
|
||||
"integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.59.1",
|
||||
"@typescript-eslint/types": "8.59.2",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -14914,9 +15031,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -16882,13 +16999,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
|
||||
"integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz",
|
||||
"integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.11",
|
||||
"follow-redirects": "^1.16.0",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^2.1.0"
|
||||
}
|
||||
@@ -31577,9 +31694,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/joi": {
|
||||
"version": "18.1.2",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-18.1.2.tgz",
|
||||
"integrity": "sha512-rF5MAmps5esSlhCA+N1b6IYHDw9j/btzGaqfgie522jS02Ju/HXBxamlXVlKEHAxoMKQL77HWI8jlqWsFuekZA==",
|
||||
"version": "18.2.1",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-18.2.1.tgz",
|
||||
"integrity": "sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
@@ -47858,14 +47975,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wait-on": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.5.tgz",
|
||||
"integrity": "sha512-qgnbHDfDTRIp73ANEJNRW/7kn8CrDUcvZz18xotJQku/P4saTGkbIzvnMZebPmVvVNUiRq1qWAPyqCH+W4H8KA==",
|
||||
"version": "9.0.6",
|
||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.6.tgz",
|
||||
"integrity": "sha512-KR+Te+NBg6DmPVil4anyIO72mpt/QDHjRo3nVFkwRgb26oweUp3DDW2szO3EeUY4cqafWy4rQuOOeEk4n+7Oeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.15.0",
|
||||
"joi": "^18.1.2",
|
||||
"axios": "^1.16.0",
|
||||
"joi": "^18.2.1",
|
||||
"lodash": "^4.18.1",
|
||||
"minimist": "^1.2.8",
|
||||
"rxjs": "^7.8.2"
|
||||
@@ -50893,7 +51010,7 @@
|
||||
"@deck.gl/extensions": "~9.2.9",
|
||||
"@deck.gl/geo-layers": "~9.2.5",
|
||||
"@deck.gl/layers": "~9.2.5",
|
||||
"@deck.gl/mapbox": "^9.3.2",
|
||||
"@deck.gl/mapbox": "~9.3.2",
|
||||
"@deck.gl/mesh-layers": "~9.2.5",
|
||||
"@luma.gl/constants": "~9.2.5",
|
||||
"@luma.gl/core": "~9.2.5",
|
||||
|
||||
@@ -303,7 +303,7 @@
|
||||
"@types/rison": "0.1.0",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
"@types/unzipper": "^0.10.11",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.2",
|
||||
"@typescript-eslint/parser": "^8.58.2",
|
||||
"babel-jest": "^30.0.2",
|
||||
"babel-loader": "^10.1.1",
|
||||
@@ -371,7 +371,7 @@
|
||||
"typescript": "5.4.5",
|
||||
"unzipper": "^0.12.3",
|
||||
"vm-browserify": "^1.1.2",
|
||||
"wait-on": "^9.0.5",
|
||||
"wait-on": "^9.0.6",
|
||||
"webpack": "^5.106.2",
|
||||
"webpack-bundle-analyzer": "^5.3.0",
|
||||
"webpack-cli": "^6.0.1",
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* 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 { testWithAssets, expect } from '../../helpers/fixtures';
|
||||
import { apiPost, apiPut } from '../../helpers/api/requests';
|
||||
import { apiPostDashboard } from '../../helpers/api/dashboard';
|
||||
import { DashboardPage } from '../../pages/DashboardPage';
|
||||
|
||||
const DATASET_NAME = 'birth_names';
|
||||
const FILTER_COLUMN = 'gender';
|
||||
|
||||
async function findDatasetIdByName(page: any, name: string): Promise<number> {
|
||||
const rison = `(filters:!((col:table_name,opr:eq,value:'${name}')))`;
|
||||
const resp = await page.request.get(`api/v1/dataset/?q=${rison}`);
|
||||
const body = await resp.json();
|
||||
if (!body.result?.length) {
|
||||
throw new Error(`Dataset ${name} not found`);
|
||||
}
|
||||
return body.result[0].id;
|
||||
}
|
||||
|
||||
testWithAssets(
|
||||
'Clear all filters waits for Apply (sc-105059)',
|
||||
async ({ page, testAssets }) => {
|
||||
const datasetId = await findDatasetIdByName(page, DATASET_NAME);
|
||||
|
||||
// Create a chart that the dashboard filter will target
|
||||
const chartParams = {
|
||||
datasource: `${datasetId}__table`,
|
||||
viz_type: 'big_number_total',
|
||||
metric: 'count',
|
||||
adhoc_filters: [],
|
||||
header_font_size: 0.4,
|
||||
subheader_font_size: 0.15,
|
||||
};
|
||||
const chartResp = await apiPost(page, 'api/v1/chart/', {
|
||||
slice_name: `clear_all_repro_${Date.now()}`,
|
||||
viz_type: 'big_number_total',
|
||||
datasource_id: datasetId,
|
||||
datasource_type: 'table',
|
||||
params: JSON.stringify(chartParams),
|
||||
});
|
||||
expect(chartResp.ok()).toBe(true);
|
||||
const chart = await chartResp.json();
|
||||
const chartId: number = chart.id ?? chart.result?.id;
|
||||
testAssets.trackChart(chartId);
|
||||
|
||||
// Create dashboard with chart in position_json and a native filter in json_metadata
|
||||
const filterId = `NATIVE_FILTER-${Math.random().toString(36).slice(2, 10)}`;
|
||||
const chartLayoutKey = `CHART-${chartId}`;
|
||||
const positionJson = {
|
||||
DASHBOARD_VERSION_KEY: 'v2',
|
||||
ROOT_ID: { type: 'ROOT', id: 'ROOT_ID', children: ['GRID_ID'] },
|
||||
GRID_ID: {
|
||||
type: 'GRID',
|
||||
id: 'GRID_ID',
|
||||
children: ['ROW-1'],
|
||||
parents: ['ROOT_ID'],
|
||||
},
|
||||
'ROW-1': {
|
||||
type: 'ROW',
|
||||
id: 'ROW-1',
|
||||
children: [chartLayoutKey],
|
||||
parents: ['ROOT_ID', 'GRID_ID'],
|
||||
meta: { background: 'BACKGROUND_TRANSPARENT' },
|
||||
},
|
||||
[chartLayoutKey]: {
|
||||
type: 'CHART',
|
||||
id: chartLayoutKey,
|
||||
children: [],
|
||||
parents: ['ROOT_ID', 'GRID_ID', 'ROW-1'],
|
||||
meta: {
|
||||
chartId,
|
||||
width: 6,
|
||||
height: 50,
|
||||
sliceName: 'clear_all_repro',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const jsonMetadata = {
|
||||
native_filter_configuration: [
|
||||
{
|
||||
id: filterId,
|
||||
name: 'Gender',
|
||||
filterType: 'filter_select',
|
||||
type: 'NATIVE_FILTER',
|
||||
targets: [
|
||||
{
|
||||
datasetId,
|
||||
column: { name: FILTER_COLUMN },
|
||||
},
|
||||
],
|
||||
controlValues: {
|
||||
multiSelect: false,
|
||||
enableEmptyFilter: false,
|
||||
defaultToFirstItem: false,
|
||||
inverseSelection: false,
|
||||
searchAllOptions: false,
|
||||
},
|
||||
defaultDataMask: { filterState: {}, extraFormData: {} },
|
||||
cascadeParentIds: [],
|
||||
scope: { rootPath: ['ROOT_ID'], excluded: [] },
|
||||
chartsInScope: [chartId],
|
||||
},
|
||||
],
|
||||
chart_configuration: {},
|
||||
cross_filters_enabled: false,
|
||||
global_chart_configuration: {
|
||||
scope: { rootPath: ['ROOT_ID'], excluded: [] },
|
||||
chartsInScope: [chartId],
|
||||
},
|
||||
};
|
||||
|
||||
const dashResp = await apiPostDashboard(page, {
|
||||
dashboard_title: `clear_all_repro_${Date.now()}`,
|
||||
published: true,
|
||||
position_json: JSON.stringify(positionJson),
|
||||
json_metadata: JSON.stringify(jsonMetadata),
|
||||
});
|
||||
expect(dashResp.ok()).toBe(true);
|
||||
const dashBody = await dashResp.json();
|
||||
const dashboardId: number = dashBody.result?.id ?? dashBody.id;
|
||||
testAssets.trackDashboard(dashboardId);
|
||||
|
||||
// Associate chart with the dashboard so it actually renders
|
||||
const linkResp = await apiPut(page, `api/v1/chart/${chartId}`, {
|
||||
dashboards: [dashboardId],
|
||||
});
|
||||
expect(linkResp.ok()).toBe(true);
|
||||
|
||||
// Visit dashboard
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.gotoById(dashboardId);
|
||||
await dashboardPage.waitForLoad();
|
||||
await dashboardPage.waitForChartsToLoad();
|
||||
|
||||
// The Gender select should be visible in the filter bar
|
||||
const filterCombobox = page
|
||||
.locator('[data-test="form-item-value"]')
|
||||
.first()
|
||||
.locator('[role="combobox"]');
|
||||
await filterCombobox.click();
|
||||
await page
|
||||
.locator('.ant-select-item-option', { hasText: /^boy$/ })
|
||||
.first()
|
||||
.click();
|
||||
// Close the dropdown
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
const applyBtn = page.locator(
|
||||
'[data-test="filter-bar__apply-button"], [data-test="filterbar-action-buttons"] button[type="submit"]',
|
||||
);
|
||||
|
||||
// Wait for chart data to come back after Apply
|
||||
const firstApplyResponse = page.waitForResponse(
|
||||
r =>
|
||||
r.url().includes('/api/v1/chart/data') &&
|
||||
r.request().method() === 'POST',
|
||||
{ timeout: 10_000 },
|
||||
);
|
||||
await applyBtn.first().click();
|
||||
await firstApplyResponse;
|
||||
await dashboardPage.waitForChartsToLoad();
|
||||
|
||||
// Now track POST /api/v1/chart/data requests around Clear All
|
||||
const postsAfterClearAll: string[] = [];
|
||||
const handler = (req: any) => {
|
||||
if (
|
||||
req.url().includes('/api/v1/chart/data') &&
|
||||
req.method() === 'POST'
|
||||
) {
|
||||
postsAfterClearAll.push(req.url());
|
||||
}
|
||||
};
|
||||
page.on('request', handler);
|
||||
|
||||
const clearBtn = page.locator('[data-test="filter-bar__clear-button"]');
|
||||
await clearBtn.click();
|
||||
|
||||
// Allow time for any debounced reload to fire if the bug is present
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
page.off('request', handler);
|
||||
|
||||
// BUG: on master, the Clear All triggers an immediate dispatch which
|
||||
// re-runs the chart query before the user clicks Apply. After the fix,
|
||||
// no chart/data request should fire until Apply is clicked.
|
||||
expect(
|
||||
postsAfterClearAll,
|
||||
'Clear All must not reload charts until Apply is clicked',
|
||||
).toEqual([]);
|
||||
|
||||
// After Apply, the chart should reload
|
||||
const applyAfterClearPromise = page.waitForResponse(
|
||||
r =>
|
||||
r.url().includes('/api/v1/chart/data') &&
|
||||
r.request().method() === 'POST',
|
||||
{ timeout: 10_000 },
|
||||
);
|
||||
await applyBtn.first().click();
|
||||
await applyAfterClearPromise;
|
||||
},
|
||||
);
|
||||
@@ -191,6 +191,8 @@ export function DatabaseSelector({
|
||||
}: DatabaseSelectorProps) {
|
||||
const showCatalogSelector = !!db?.allow_multi_catalog;
|
||||
const [currentDb, setCurrentDb] = useState<DatabaseValue | undefined>();
|
||||
const showSchemaSelector =
|
||||
(db?.supports_schemas ?? currentDb?.supports_schemas) !== false;
|
||||
const [errorPayload, setErrorPayload] = useState<SupersetError | null>();
|
||||
const [currentCatalog, setCurrentCatalog] = useState<
|
||||
CatalogOption | null | undefined
|
||||
@@ -260,6 +262,12 @@ export function DatabaseSelector({
|
||||
database_name: row.database_name,
|
||||
backend: row.backend,
|
||||
allow_multi_catalog: row.allow_multi_catalog,
|
||||
supports_schemas:
|
||||
(
|
||||
row as DatabaseObject & {
|
||||
engine_information?: { supports_schemas?: boolean };
|
||||
}
|
||||
).engine_information?.supports_schemas !== false,
|
||||
order,
|
||||
}));
|
||||
|
||||
@@ -597,7 +605,7 @@ export function DatabaseSelector({
|
||||
{renderDatabaseSelect()}
|
||||
{renderError()}
|
||||
{showCatalogSelector && renderCatalogSelect()}
|
||||
{renderSchemaSelect()}
|
||||
{showSchemaSelector && renderSchemaSelect()}
|
||||
</DatabaseSelectorWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export type DatabaseValue = {
|
||||
id: number;
|
||||
database_name: string;
|
||||
backend?: string;
|
||||
supports_schemas?: boolean;
|
||||
};
|
||||
|
||||
export type DatabaseObject = {
|
||||
@@ -31,6 +32,7 @@ export type DatabaseObject = {
|
||||
database_name: string;
|
||||
backend?: string;
|
||||
allow_multi_catalog?: boolean;
|
||||
supports_schemas?: boolean;
|
||||
};
|
||||
|
||||
export interface DatabaseSelectorProps {
|
||||
|
||||
@@ -260,6 +260,52 @@ test('table multi select retain all the values selected', async () => {
|
||||
expect(selections[1]).toHaveTextContent('table_c');
|
||||
});
|
||||
|
||||
test('calls onTableSelectChange for schema-less database without schema', async () => {
|
||||
fetchMock.get(catalogApiRoute, { result: [] });
|
||||
fetchMock.get(schemaApiRoute, { result: [] });
|
||||
fetchMock.get(tablesApiRoute, getTableMockFunction());
|
||||
|
||||
const callback = jest.fn();
|
||||
const props = createProps({
|
||||
database: {
|
||||
id: 1,
|
||||
database_name: 'ydb',
|
||||
backend: 'ydb',
|
||||
supports_schemas: false,
|
||||
},
|
||||
schema: undefined,
|
||||
onTableSelectChange: callback,
|
||||
});
|
||||
|
||||
render(<TableSelector {...props} />, { useRedux: true, store });
|
||||
|
||||
const tableSelect = screen.getByRole('combobox', {
|
||||
name: 'Select table or type to search tables',
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(tableSelect);
|
||||
});
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(screen.getByText('table_a')).toBeInTheDocument();
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByText('table_a'));
|
||||
});
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(callback).toHaveBeenCalled();
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
}, 15000);
|
||||
|
||||
test('TableOption renders correct icons for different table types', () => {
|
||||
// Test regular table
|
||||
const tableTable = {
|
||||
|
||||
@@ -190,6 +190,7 @@ const TableSelector: FunctionComponent<TableSelectorProps> = ({
|
||||
dbId: database?.id,
|
||||
catalog: currentCatalog,
|
||||
schema: currentSchema,
|
||||
supportsSchemas: database?.supports_schemas,
|
||||
onSuccess: (data, isFetched) => {
|
||||
setErrorPayload(null);
|
||||
if (isFetched) {
|
||||
@@ -247,7 +248,8 @@ const TableSelector: FunctionComponent<TableSelectorProps> = ({
|
||||
const internalTableChange = (
|
||||
selectedOptions: TableOption | TableOption[] | undefined,
|
||||
) => {
|
||||
if (currentSchema) {
|
||||
setTableSelectValue(selectedOptions);
|
||||
if (currentSchema || database?.supports_schemas === false) {
|
||||
onTableSelectChange?.(
|
||||
Array.isArray(selectedOptions)
|
||||
? selectedOptions.map(option => option?.value)
|
||||
@@ -255,8 +257,6 @@ const TableSelector: FunctionComponent<TableSelectorProps> = ({
|
||||
currentCatalog,
|
||||
currentSchema,
|
||||
);
|
||||
} else {
|
||||
setTableSelectValue(selectedOptions);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -302,7 +302,8 @@ const TableSelector: FunctionComponent<TableSelectorProps> = ({
|
||||
);
|
||||
|
||||
function renderTableSelect() {
|
||||
const disabled = (currentSchema && !formMode && readOnly) || !currentSchema;
|
||||
const disabled =
|
||||
readOnly || (database?.supports_schemas !== false && !currentSchema);
|
||||
|
||||
const label = t('Table');
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
screen,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import { FeatureFlag } from '@superset-ui/core';
|
||||
import { supersetTheme } from '@apache-superset/core/theme';
|
||||
import {
|
||||
OPEN_FILTER_BAR_WIDTH,
|
||||
CLOSED_FILTER_BAR_WIDTH,
|
||||
@@ -487,6 +488,47 @@ test('should render ParentSize wrapper with height 100% for tabs', async () => {
|
||||
expect(tabPanels.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('should apply min-height to the top-level tab drop target so tabs can be dropped on dashboards with content', () => {
|
||||
(useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
|
||||
100,
|
||||
jest.fn(),
|
||||
]);
|
||||
(fetchFaveStar as jest.Mock).mockReturnValue({ type: 'mock-action' });
|
||||
(setActiveTab as jest.Mock).mockReturnValue({ type: 'mock-action' });
|
||||
|
||||
const { getByTestId } = render(<DashboardBuilder />, {
|
||||
useRedux: true,
|
||||
store: storeWithState({
|
||||
...mockState,
|
||||
dashboardLayout: undoableDashboardLayout,
|
||||
dashboardState: { ...mockState.dashboardState, editMode: true },
|
||||
}),
|
||||
useDnd: true,
|
||||
useTheme: true,
|
||||
});
|
||||
|
||||
const headerWrapper = getByTestId('dashboard-header-wrapper');
|
||||
|
||||
// The Droppable inside the header should have the empty-droptarget class
|
||||
// when there are no top-level tabs and edit mode is active. Without this
|
||||
// class (and its associated min-height CSS rule), the drop target has zero
|
||||
// height and users cannot drag tabs onto dashboards that already have
|
||||
// content.
|
||||
const droptarget = headerWrapper.querySelector('.empty-droptarget');
|
||||
expect(droptarget).toBeInTheDocument();
|
||||
|
||||
// Verify the StyledHeader CSS defines a non-zero min-height for
|
||||
// .empty-droptarget, derived from theme.sizeUnit * 4 to stay in sync
|
||||
// with the source rule in DashboardBuilder.tsx.
|
||||
expect(headerWrapper).toHaveStyleRule(
|
||||
'min-height',
|
||||
`${supersetTheme.sizeUnit * 4}px`,
|
||||
{
|
||||
target: '.empty-droptarget',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should maintain layout when switching between tabs', async () => {
|
||||
(useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
|
||||
100,
|
||||
|
||||
@@ -100,6 +100,10 @@ const StyledHeader = styled.div<{ filterBarWidth: number }>`
|
||||
z-index: 99;
|
||||
max-width: calc(100vw - ${filterBarWidth}px);
|
||||
|
||||
.empty-droptarget {
|
||||
min-height: ${theme.sizeUnit * 4}px;
|
||||
}
|
||||
|
||||
.empty-droptarget:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
|
||||
@@ -763,6 +763,123 @@ test('Should show row count warning for table chart with server pagination when
|
||||
mockUseUiConfig.mockRestore();
|
||||
});
|
||||
|
||||
test('Should show row count warning for non-table chart when row limit is reached', () => {
|
||||
const props = createProps({
|
||||
formData: {
|
||||
...createProps().formData,
|
||||
viz_type: VizType.Bar,
|
||||
row_limit: 10,
|
||||
},
|
||||
slice: {
|
||||
...createProps().slice,
|
||||
form_data: {
|
||||
...createProps().slice.form_data,
|
||||
viz_type: VizType.Bar,
|
||||
row_limit: 10,
|
||||
},
|
||||
viz_type: VizType.Bar,
|
||||
},
|
||||
});
|
||||
const barChartState = {
|
||||
...initialState,
|
||||
charts: {
|
||||
[props.slice.slice_id]: {
|
||||
id: MOCKED_CHART_ID,
|
||||
chartStatus: 'rendered',
|
||||
queriesResponse: [
|
||||
{
|
||||
sql_rowcount: 10,
|
||||
data: Array(10).fill({}),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockUseUiConfig = useUiConfig as jest.MockedFunction<
|
||||
typeof useUiConfig
|
||||
>;
|
||||
mockUseUiConfig.mockReturnValue({
|
||||
hideTitle: false,
|
||||
hideTab: false,
|
||||
hideNav: false,
|
||||
hideChartControls: false,
|
||||
emitDataMasks: false,
|
||||
showRowLimitWarning: true,
|
||||
});
|
||||
|
||||
render(<SliceHeader {...props} />, {
|
||||
useRedux: true,
|
||||
useRouter: true,
|
||||
initialState: barChartState,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('warning')).toBeInTheDocument();
|
||||
|
||||
mockUseUiConfig.mockRestore();
|
||||
});
|
||||
|
||||
test('Should show row count warning for ag-grid table chart with server pagination when limit is reached', () => {
|
||||
const props = createProps({
|
||||
formData: {
|
||||
...createProps().formData,
|
||||
viz_type: VizType.TableAgGrid,
|
||||
row_limit: 10,
|
||||
server_pagination: true,
|
||||
},
|
||||
slice: {
|
||||
...createProps().slice,
|
||||
form_data: {
|
||||
...createProps().slice.form_data,
|
||||
viz_type: VizType.TableAgGrid,
|
||||
row_limit: 10,
|
||||
server_pagination: true,
|
||||
},
|
||||
viz_type: VizType.TableAgGrid,
|
||||
},
|
||||
});
|
||||
const agGridWithPaginationState = {
|
||||
...initialState,
|
||||
charts: {
|
||||
[props.slice.slice_id]: {
|
||||
id: MOCKED_CHART_ID,
|
||||
chartStatus: 'rendered',
|
||||
queriesResponse: [
|
||||
{
|
||||
sql_rowcount: 10,
|
||||
data: Array(10).fill({}),
|
||||
},
|
||||
{
|
||||
data: [{ rowcount: 50 }],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockUseUiConfig = useUiConfig as jest.MockedFunction<
|
||||
typeof useUiConfig
|
||||
>;
|
||||
mockUseUiConfig.mockReturnValue({
|
||||
hideTitle: false,
|
||||
hideTab: false,
|
||||
hideNav: false,
|
||||
hideChartControls: false,
|
||||
emitDataMasks: false,
|
||||
showRowLimitWarning: true,
|
||||
});
|
||||
|
||||
render(<SliceHeader {...props} />, {
|
||||
useRedux: true,
|
||||
useRouter: true,
|
||||
initialState: agGridWithPaginationState,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('warning')).toBeInTheDocument();
|
||||
|
||||
mockUseUiConfig.mockRestore();
|
||||
});
|
||||
|
||||
test('Should NOT show row count warning for table chart with server pagination when limit is NOT reached', () => {
|
||||
const props = createProps({
|
||||
formData: {
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { getExtensionsRegistry, QueryData } from '@superset-ui/core';
|
||||
import { getExtensionsRegistry, QueryData, VizType } from '@superset-ui/core';
|
||||
import {
|
||||
css,
|
||||
styled,
|
||||
@@ -206,9 +206,12 @@ const SliceHeader = forwardRef<HTMLDivElement, SliceHeaderProps>(
|
||||
|
||||
const rowLimit = Number(formData.row_limit ?? 0);
|
||||
|
||||
const isTableChart = formData.viz_type === 'table';
|
||||
const countFromSecondQuery =
|
||||
isTableChart && secondQueryResponse?.data?.[0]?.rowcount;
|
||||
const isTableChart =
|
||||
formData.viz_type === VizType.Table ||
|
||||
formData.viz_type === VizType.TableAgGrid;
|
||||
const countFromSecondQuery = isTableChart
|
||||
? secondQueryResponse?.data?.[0]?.rowcount
|
||||
: undefined;
|
||||
|
||||
const sqlRowCount =
|
||||
countFromSecondQuery != null
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
RefObject,
|
||||
} from 'react';
|
||||
import type { ChartCustomization, JsonObject } from '@superset-ui/core';
|
||||
import { VizType } from '@superset-ui/core';
|
||||
import { styled } from '@apache-superset/core/theme';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { debounce } from 'lodash';
|
||||
@@ -495,7 +496,9 @@ const Chart = (props: ChartProps) => {
|
||||
const resultType = isPivot ? 'post_processed' : 'full';
|
||||
|
||||
let actualRowCount: number | undefined;
|
||||
const isTableViz = (formData as JsonObject)?.viz_type === 'table';
|
||||
const vizType = (formData as JsonObject)?.viz_type;
|
||||
const isTableViz =
|
||||
vizType === VizType.Table || vizType === VizType.TableAgGrid;
|
||||
|
||||
if (
|
||||
isTableViz &&
|
||||
|
||||
@@ -486,7 +486,7 @@ test('FilterBar renders correctly when filter has complete extraFormData', async
|
||||
expect(screen.getByTestId(getTestId('filter-icon'))).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('handleClearAll dispatches updateDataMask with value undefined for filter_select', async () => {
|
||||
test('Clear All stages filter_select clear without dispatching until Apply', async () => {
|
||||
const filterId = 'NATIVE_FILTER-clear-select';
|
||||
const updateDataMaskSpy = jest.spyOn(dataMaskActions, 'updateDataMask');
|
||||
const selectFilter = createFilter({
|
||||
@@ -513,7 +513,9 @@ test('handleClearAll dispatches updateDataMask with value undefined for filter_s
|
||||
activeTabs: ['ROOT_ID'],
|
||||
},
|
||||
dataMask: {
|
||||
[filterId]: createDataMask(filterId, ['East']),
|
||||
[filterId]: createDataMask(filterId, ['East'], {
|
||||
filters: [{ col: 'region', op: 'IN', val: ['East'] }],
|
||||
}),
|
||||
},
|
||||
nativeFilters: {
|
||||
filters: { [filterId]: selectFilter },
|
||||
@@ -533,14 +535,24 @@ test('handleClearAll dispatches updateDataMask with value undefined for filter_s
|
||||
userEvent.click(clearBtn);
|
||||
});
|
||||
|
||||
// Clear All must not dispatch — staging only
|
||||
expect(updateDataMaskSpy).not.toHaveBeenCalled();
|
||||
|
||||
// Apply commits the staged clear
|
||||
const applyBtn = screen.getByTestId(getTestId('apply-button'));
|
||||
expect(applyBtn).not.toBeDisabled();
|
||||
await act(async () => {
|
||||
userEvent.click(applyBtn);
|
||||
});
|
||||
expect(updateDataMaskSpy).toHaveBeenCalledWith(filterId, {
|
||||
filterState: { value: undefined },
|
||||
id: filterId,
|
||||
filterState: { value: undefined, validateStatus: undefined },
|
||||
extraFormData: {},
|
||||
});
|
||||
updateDataMaskSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('handleClearAll dispatches updateDataMask with [null, null] for filter_range', async () => {
|
||||
test('Clear All stages filter_range clear with [null, null], dispatched on Apply', async () => {
|
||||
fetchMock.post('glob:*/api/v1/chart/data', {
|
||||
result: [{ data: [{ min: 0, max: 100 }] }],
|
||||
});
|
||||
@@ -570,7 +582,9 @@ test('handleClearAll dispatches updateDataMask with [null, null] for filter_rang
|
||||
activeTabs: ['ROOT_ID'],
|
||||
},
|
||||
dataMask: {
|
||||
[filterId]: createDataMask(filterId, [10, 50]),
|
||||
[filterId]: createDataMask(filterId, [10, 50], {
|
||||
filters: [{ col: 'age', op: '>=', val: 10 }],
|
||||
}),
|
||||
},
|
||||
nativeFilters: {
|
||||
filters: { [filterId]: rangeFilter },
|
||||
@@ -590,14 +604,21 @@ test('handleClearAll dispatches updateDataMask with [null, null] for filter_rang
|
||||
userEvent.click(clearBtn);
|
||||
});
|
||||
|
||||
expect(updateDataMaskSpy).not.toHaveBeenCalled();
|
||||
|
||||
const applyBtn = screen.getByTestId(getTestId('apply-button'));
|
||||
await act(async () => {
|
||||
userEvent.click(applyBtn);
|
||||
});
|
||||
expect(updateDataMaskSpy).toHaveBeenCalledWith(filterId, {
|
||||
filterState: { value: [null, null] },
|
||||
id: filterId,
|
||||
filterState: { value: [null, null], validateStatus: undefined },
|
||||
extraFormData: {},
|
||||
});
|
||||
updateDataMaskSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('handleClearAll only dispatches for filters present in dataMask', async () => {
|
||||
test('Clear All + Apply only dispatches for filters present in dataMask', async () => {
|
||||
const idInMask = 'NATIVE_FILTER-has-value';
|
||||
const idNotInMask = 'NATIVE_FILTER-no-value';
|
||||
const updateDataMaskSpy = jest.spyOn(dataMaskActions, 'updateDataMask');
|
||||
@@ -631,7 +652,9 @@ test('handleClearAll only dispatches for filters present in dataMask', async ()
|
||||
activeTabs: ['ROOT_ID'],
|
||||
},
|
||||
dataMask: {
|
||||
[idInMask]: createDataMask(idInMask, ['v']),
|
||||
[idInMask]: createDataMask(idInMask, ['v'], {
|
||||
filters: [{ col: 'x', op: 'IN', val: ['v'] }],
|
||||
}),
|
||||
},
|
||||
nativeFilters: {
|
||||
filters: {
|
||||
@@ -652,10 +675,16 @@ test('handleClearAll only dispatches for filters present in dataMask', async ()
|
||||
await act(async () => {
|
||||
userEvent.click(clearBtn);
|
||||
});
|
||||
expect(updateDataMaskSpy).not.toHaveBeenCalled();
|
||||
|
||||
const applyBtn = screen.getByTestId(getTestId('apply-button'));
|
||||
await act(async () => {
|
||||
userEvent.click(applyBtn);
|
||||
});
|
||||
expect(updateDataMaskSpy).toHaveBeenCalledTimes(1);
|
||||
expect(updateDataMaskSpy).toHaveBeenCalledWith(idInMask, {
|
||||
filterState: { value: undefined },
|
||||
id: idInMask,
|
||||
filterState: { value: undefined, validateStatus: undefined },
|
||||
extraFormData: {},
|
||||
});
|
||||
updateDataMaskSpy.mockRestore();
|
||||
@@ -790,18 +819,86 @@ test('FilterBar Clear All only clears in-scope filters, not out-of-scope ones',
|
||||
await act(async () => {
|
||||
userEvent.click(clearButton);
|
||||
});
|
||||
expect(updateDataMaskSpy).not.toHaveBeenCalled();
|
||||
|
||||
// Verify only the in-scope filter was cleared, not the out-of-scope ones
|
||||
const clearedFilterIds = updateDataMaskSpy.mock.calls.map(call => call[0]);
|
||||
expect(clearedFilterIds).toContain(inScopeFilterId);
|
||||
expect(clearedFilterIds).not.toContain(outOfScopeRequiredFilterId);
|
||||
expect(clearedFilterIds).not.toContain(outOfScopeNonRequiredFilterId);
|
||||
// After Apply: only the in-scope filter was cleared. Out-of-scope filters
|
||||
// retain their original values (Apply re-dispatches them unchanged).
|
||||
const applyButton = screen.getByTestId(getTestId('apply-button'));
|
||||
await act(async () => {
|
||||
userEvent.click(applyButton);
|
||||
});
|
||||
|
||||
// Verify the in-scope filter was cleared with the correct value
|
||||
expect(updateDataMaskSpy).toHaveBeenCalledWith(inScopeFilterId, {
|
||||
filterState: { value: undefined },
|
||||
id: inScopeFilterId,
|
||||
filterState: { value: undefined, validateStatus: undefined },
|
||||
extraFormData: {},
|
||||
});
|
||||
|
||||
// Out-of-scope filters keep their existing values; not cleared
|
||||
const outOfScopeRequiredCall = updateDataMaskSpy.mock.calls.find(
|
||||
call => call[0] === outOfScopeRequiredFilterId,
|
||||
);
|
||||
expect(outOfScopeRequiredCall?.[1]?.filterState?.value).toEqual(['value2']);
|
||||
const outOfScopeNonRequiredCall = updateDataMaskSpy.mock.calls.find(
|
||||
call => call[0] === outOfScopeNonRequiredFilterId,
|
||||
);
|
||||
expect(outOfScopeNonRequiredCall?.[1]?.filterState?.value).toEqual([
|
||||
'value3',
|
||||
]);
|
||||
|
||||
updateDataMaskSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('Clear All on a required filter disables Apply via validateStatus', async () => {
|
||||
const filterId = 'NATIVE_FILTER-required-clear';
|
||||
const updateDataMaskSpy = jest.spyOn(dataMaskActions, 'updateDataMask');
|
||||
const requiredFilter = createFilter({
|
||||
id: filterId,
|
||||
name: 'Required Region',
|
||||
filterType: 'filter_select',
|
||||
targets: [{ datasetId: 7, column: { name: 'region' } }],
|
||||
controlValues: { enableEmptyFilter: true },
|
||||
chartsInScope: [18],
|
||||
});
|
||||
const state = {
|
||||
...stateWithoutNativeFilters,
|
||||
dashboardInfo: {
|
||||
id: 1,
|
||||
dash_edit_perm: true,
|
||||
filterBarOrientation: FilterBarOrientation.Vertical,
|
||||
metadata: {
|
||||
native_filter_configuration: [requiredFilter],
|
||||
chart_configuration: {},
|
||||
},
|
||||
},
|
||||
dashboardState: {
|
||||
...stateWithoutNativeFilters.dashboardState,
|
||||
activeTabs: ['ROOT_ID'],
|
||||
},
|
||||
dataMask: {
|
||||
[filterId]: createDataMask(filterId, ['East'], {
|
||||
filters: [{ col: 'region', op: 'IN', val: ['East'] }],
|
||||
}),
|
||||
},
|
||||
nativeFilters: {
|
||||
filters: { [filterId]: requiredFilter },
|
||||
filtersState: {},
|
||||
},
|
||||
};
|
||||
|
||||
const props = createOpenedBarProps();
|
||||
renderFilterBar(props, state);
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(300);
|
||||
});
|
||||
|
||||
const clearBtn = screen.getByTestId(getTestId('clear-button'));
|
||||
await act(async () => {
|
||||
userEvent.click(clearBtn);
|
||||
});
|
||||
|
||||
// No dispatch yet; Apply should be disabled because the required filter is empty
|
||||
expect(updateDataMaskSpy).not.toHaveBeenCalled();
|
||||
expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
|
||||
updateDataMaskSpy.mockRestore();
|
||||
});
|
||||
|
||||
@@ -498,17 +498,20 @@ const FilterBar: FC<FiltersBarProps> = ({
|
||||
// Range filters use [null, null] as the cleared value; others use undefined
|
||||
const clearedValue =
|
||||
filterType === 'filter_range' ? [null, null] : undefined;
|
||||
const clearedDataMask = {
|
||||
filterState: { value: clearedValue },
|
||||
extraFormData: {},
|
||||
};
|
||||
const isRequired = !!filter.controlValues?.enableEmptyFilter;
|
||||
if (dataMaskSelected[id]) {
|
||||
dispatch(updateDataMask(id, clearedDataMask));
|
||||
// Stage the cleared value locally; do NOT dispatch to Redux here.
|
||||
// Persistence happens when the user clicks Apply.
|
||||
setDataMaskSelected(draft => {
|
||||
if (draft[id].filterState?.value !== undefined) {
|
||||
draft[id].filterState!.value = clearedValue;
|
||||
}
|
||||
draft[id].extraFormData = {};
|
||||
if (draft[id].filterState) {
|
||||
draft[id].filterState!.validateStatus = isRequired
|
||||
? 'error'
|
||||
: undefined;
|
||||
}
|
||||
});
|
||||
newClearAllTriggers[id] = true;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { forwardRef, RefObject } from 'react';
|
||||
import { QueryData } from '@superset-ui/core';
|
||||
import { QueryData, VizType } from '@superset-ui/core';
|
||||
import { css, SupersetTheme } from '@apache-superset/core/theme';
|
||||
import {
|
||||
CachedLabel,
|
||||
@@ -68,7 +68,9 @@ export const ChartPills = forwardRef(
|
||||
const firstQueryResponse = queriesResponse?.[0];
|
||||
|
||||
// For table charts with server pagination, check second query for total count
|
||||
const isTableChart = formData?.viz_type === 'table';
|
||||
const isTableChart =
|
||||
formData?.viz_type === VizType.Table ||
|
||||
formData?.viz_type === VizType.TableAgGrid;
|
||||
const hasCountQuery = queriesResponse && queriesResponse.length > 1;
|
||||
const countFromSecondQuery = hasCountQuery
|
||||
? queriesResponse[1]?.data?.[0]?.rowcount
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { render, waitFor } from 'spec/helpers/testing-library';
|
||||
import { SupersetClient } from '@superset-ui/core';
|
||||
import DatasetPanelWrapper from 'src/features/datasets/AddDataset/DatasetPanel';
|
||||
|
||||
jest.mock(
|
||||
'@superset-ui/core/components/Icons/AsyncIcon',
|
||||
() =>
|
||||
({ fileName }: { fileName: string }) => (
|
||||
<span role="img" aria-label={fileName.replace('_', '-')} />
|
||||
),
|
||||
);
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('fetches table metadata for schema-less database without schema', async () => {
|
||||
const getSpy = jest.spyOn(SupersetClient, 'get').mockResolvedValue({
|
||||
json: {
|
||||
name: 'my_table',
|
||||
columns: [{ name: 'id', type: 'INTEGER', longType: 'INTEGER' }],
|
||||
},
|
||||
} as any);
|
||||
|
||||
render(
|
||||
<DatasetPanelWrapper
|
||||
tableName="my_table"
|
||||
dbId={1}
|
||||
database={{ supports_schemas: false }}
|
||||
/>,
|
||||
{ useRouter: true },
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
endpoint: expect.stringContaining('/api/v1/database/1/table_metadata/'),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -22,6 +22,7 @@ import { SupersetClient } from '@superset-ui/core';
|
||||
import { logging } from '@apache-superset/core/utils';
|
||||
import { DatasetObject } from 'src/features/datasets/AddDataset/types';
|
||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||
import { type DatabaseObject } from 'src/components';
|
||||
import { toQueryString } from 'src/utils/urlUtils';
|
||||
import DatasetPanel from './DatasetPanel';
|
||||
import { ITableColumn, IDatabaseTable, isIDatabaseTable } from './types';
|
||||
@@ -39,9 +40,9 @@ interface IColumnProps {
|
||||
*/
|
||||
tableName: string;
|
||||
/**
|
||||
* Name of the schema
|
||||
* Name of the schema (optional for databases that don't support schemas)
|
||||
*/
|
||||
schema: string;
|
||||
schema?: string | null;
|
||||
}
|
||||
|
||||
export interface IDatasetPanelWrapperProps {
|
||||
@@ -58,6 +59,10 @@ export interface IDatasetPanelWrapperProps {
|
||||
*/
|
||||
catalog?: string | null;
|
||||
schema?: string | null;
|
||||
/**
|
||||
* The selected database object (used to check engine capabilities)
|
||||
*/
|
||||
database?: Partial<DatabaseObject> | null;
|
||||
setHasColumns?: Function;
|
||||
datasets?: DatasetObject[] | undefined;
|
||||
}
|
||||
@@ -67,6 +72,7 @@ const DatasetPanelWrapper = ({
|
||||
dbId,
|
||||
catalog,
|
||||
schema,
|
||||
database,
|
||||
setHasColumns,
|
||||
datasets,
|
||||
}: IDatasetPanelWrapperProps) => {
|
||||
@@ -128,12 +134,13 @@ const DatasetPanelWrapper = ({
|
||||
|
||||
useEffect(() => {
|
||||
tableNameRef.current = tableName;
|
||||
if (tableName && schema && dbId) {
|
||||
getTableMetadata({ tableName, dbId, schema });
|
||||
const schemaRequired = database?.supports_schemas !== false;
|
||||
if (tableName && dbId && (schema || !schemaRequired)) {
|
||||
getTableMetadata({ tableName, dbId, schema: schema || undefined });
|
||||
}
|
||||
// getTableMetadata is a const and should not be in dependency array
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [tableName, dbId, schema]);
|
||||
}, [tableName, dbId, schema, database]);
|
||||
|
||||
return (
|
||||
<DatasetPanel
|
||||
|
||||
@@ -358,6 +358,66 @@ test('useDatasetsList skips fetching when db.id is undefined', () => {
|
||||
expect(result.current.datasetNames).toEqual([]);
|
||||
});
|
||||
|
||||
test('useDatasetsList fetches datasets for schema-less databases without schema filter', async () => {
|
||||
const schemalessDb = {
|
||||
id: 2,
|
||||
database_name: 'ydb',
|
||||
owners: [1] as [number],
|
||||
supports_schemas: false,
|
||||
};
|
||||
|
||||
const getSpy = jest.spyOn(SupersetClient, 'get').mockResolvedValue({
|
||||
json: {
|
||||
count: 1,
|
||||
result: [{ id: 10, table_name: 'my_table', schema: null }],
|
||||
},
|
||||
} as unknown as JsonResponse);
|
||||
|
||||
const { result } = renderHook(() => useDatasetsList(schemalessDb, null));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.datasets).toHaveLength(1);
|
||||
});
|
||||
|
||||
expect(result.current.datasetNames).toEqual(['my_table']);
|
||||
expect(getSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Verify the API was called without a schema filter
|
||||
const callArg = getSpy.mock.calls[0]?.[0]?.endpoint;
|
||||
expect(callArg).toBeDefined();
|
||||
|
||||
const risonParam = new URL(callArg!, 'http://localhost').searchParams.get(
|
||||
'q',
|
||||
);
|
||||
expect(risonParam).toBeTruthy();
|
||||
const decoded = rison.decode(risonParam!) as {
|
||||
filters: Array<{ col: string; opr: string; value: unknown }>;
|
||||
};
|
||||
|
||||
// Only database filter and sql filter — no schema filter
|
||||
const schemaFilter = decoded.filters.find(f => f.col === 'schema');
|
||||
expect(schemaFilter).toBeUndefined();
|
||||
|
||||
const dbFilter = decoded.filters.find(f => f.col === 'database');
|
||||
expect(dbFilter).toEqual({ col: 'database', opr: 'rel_o_m', value: 2 });
|
||||
});
|
||||
|
||||
test('useDatasetsList skips fetching when schema-less database id is undefined', () => {
|
||||
const getSpy = jest.spyOn(SupersetClient, 'get');
|
||||
|
||||
const schemalessDb = {
|
||||
database_name: 'ydb',
|
||||
owners: [1] as [number],
|
||||
supports_schemas: false,
|
||||
} as typeof mockDb & { supports_schemas: boolean };
|
||||
|
||||
const { result } = renderHook(() => useDatasetsList(schemalessDb, null));
|
||||
|
||||
// No db.id — should NOT call API even for schema-less DB
|
||||
expect(getSpy).not.toHaveBeenCalled();
|
||||
expect(result.current.datasets).toEqual([]);
|
||||
});
|
||||
|
||||
test('useDatasetsList encodes schemas with spaces and special characters in endpoint URL', async () => {
|
||||
const getSpy = jest.spyOn(SupersetClient, 'get').mockResolvedValue({
|
||||
json: { count: 0, result: [] },
|
||||
|
||||
@@ -37,7 +37,8 @@ const useDatasetsList = (
|
||||
schema: string | null | undefined,
|
||||
) => {
|
||||
const [datasets, setDatasets] = useState<DatasetObject[]>([]);
|
||||
const encodedSchema = schema ? encodeURIComponent(schema) : undefined;
|
||||
const supportsSchemas = db?.supports_schemas !== false;
|
||||
const encodedSchema = schema ? encodeURIComponent(schema) : null;
|
||||
|
||||
const getDatasetsList = useCallback(async (filters: object[]) => {
|
||||
let results: DatasetObject[] = [];
|
||||
@@ -77,14 +78,16 @@ const useDatasetsList = (
|
||||
useEffect(() => {
|
||||
const filters = [
|
||||
{ col: 'database', opr: 'rel_o_m', value: db?.id },
|
||||
{ col: 'schema', opr: 'eq', value: encodedSchema },
|
||||
...(supportsSchemas
|
||||
? [{ col: 'schema', opr: 'eq', value: encodedSchema }]
|
||||
: []),
|
||||
{ col: 'sql', opr: 'dataset_is_null_or_empty', value: true },
|
||||
];
|
||||
|
||||
if (schema && db?.id !== undefined) {
|
||||
if (db?.id !== undefined && (schema || !supportsSchemas)) {
|
||||
getDatasetsList(filters);
|
||||
}
|
||||
}, [db?.id, schema, encodedSchema, getDatasetsList]);
|
||||
}, [db?.id, schema, encodedSchema, supportsSchemas, getDatasetsList]);
|
||||
|
||||
const datasetNames = useMemo(
|
||||
() => datasets?.map(dataset => dataset.table_name),
|
||||
|
||||
@@ -240,6 +240,35 @@ describe('useTables hook', () => {
|
||||
expect(fetchMock.callHistory.calls(tableApiRoute).length).toBe(1);
|
||||
});
|
||||
|
||||
test('fetches tables without schema when supportsSchemas is false', async () => {
|
||||
const expectDbId = 'db1';
|
||||
const tableApiRoute = `glob:*/api/v1/database/${expectDbId}/tables/?q=*`;
|
||||
fetchMock.get(tableApiRoute, fakeApiResult);
|
||||
fetchMock.get(`glob:*/api/v1/database/${expectDbId}/catalogs/*`, {
|
||||
count: 0,
|
||||
result: [],
|
||||
});
|
||||
fetchMock.get(`glob:*/api/v1/database/${expectDbId}/schemas/*`, {
|
||||
result: fakeSchemaApiResult,
|
||||
});
|
||||
const { result, waitFor } = renderHook(
|
||||
() =>
|
||||
useTables({
|
||||
dbId: expectDbId,
|
||||
supportsSchemas: false,
|
||||
}),
|
||||
{
|
||||
wrapper: createWrapper({
|
||||
useRedux: true,
|
||||
store,
|
||||
}),
|
||||
},
|
||||
);
|
||||
// Tables are fetched even though no schema is provided or validated against schemaOptions
|
||||
await waitFor(() => expect(result.current.data).toEqual(expectedData));
|
||||
expect(fetchMock.callHistory.calls(tableApiRoute).length).toBe(1);
|
||||
});
|
||||
|
||||
test('returns refreshed data after expires', async () => {
|
||||
const expectDbId = 'db1';
|
||||
const expectedSchema = 'schema1';
|
||||
|
||||
@@ -96,7 +96,9 @@ type TableMetadataResponse = {
|
||||
|
||||
export type TableExtendedMetadata = Record<string, string>;
|
||||
|
||||
type Params = Omit<FetchTablesQueryParams, 'forceRefresh'>;
|
||||
type Params = Omit<FetchTablesQueryParams, 'forceRefresh'> & {
|
||||
supportsSchemas?: boolean;
|
||||
};
|
||||
|
||||
const tableApi = api.injectEndpoints({
|
||||
endpoints: builder => ({
|
||||
@@ -166,7 +168,14 @@ export const {
|
||||
} = tableApi;
|
||||
|
||||
export function useTables(options: Params) {
|
||||
const { dbId, catalog, schema, onSuccess, onError } = options || {};
|
||||
const {
|
||||
dbId,
|
||||
catalog,
|
||||
schema,
|
||||
supportsSchemas = true,
|
||||
onSuccess,
|
||||
onError,
|
||||
} = options || {};
|
||||
const isMountedRef = useRef(false);
|
||||
const { currentData: schemaOptions, isFetching } = useSchemas({
|
||||
dbId,
|
||||
@@ -177,9 +186,9 @@ export function useTables(options: Params) {
|
||||
[schemaOptions],
|
||||
);
|
||||
|
||||
const enabled = Boolean(
|
||||
dbId && schema && !isFetching && schemaOptionsMap.has(schema),
|
||||
);
|
||||
const enabled = supportsSchemas
|
||||
? Boolean(dbId && schema && !isFetching && schemaOptionsMap.has(schema))
|
||||
: Boolean(dbId);
|
||||
|
||||
const result = useTablesQuery(
|
||||
{ dbId, catalog, schema, forceRefresh: false },
|
||||
|
||||
@@ -122,6 +122,7 @@ export default function AddDataset() {
|
||||
dbId={dataset?.db?.id}
|
||||
catalog={dataset?.catalog}
|
||||
schema={dataset?.schema}
|
||||
database={dataset?.db}
|
||||
setHasColumns={setHasColumns}
|
||||
datasets={datasets}
|
||||
/>
|
||||
|
||||
20
superset-frontend/src/types/emotion-jest.d.ts
vendored
Normal file
20
superset-frontend/src/types/emotion-jest.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/// <reference types="@emotion/jest" />
|
||||
@@ -44,7 +44,7 @@ class TablesDatabaseCommand(BaseCommand):
|
||||
self,
|
||||
db_id: int,
|
||||
catalog_name: str | None,
|
||||
schema_name: str,
|
||||
schema_name: str | None,
|
||||
force: bool,
|
||||
):
|
||||
self._db_id = db_id
|
||||
@@ -55,6 +55,8 @@ class TablesDatabaseCommand(BaseCommand):
|
||||
def run(self) -> dict[str, Any]:
|
||||
self.validate()
|
||||
self._catalog_name = self._catalog_name or self._model.get_default_catalog()
|
||||
if not self._model.db_engine_spec.supports_schemas:
|
||||
self._schema_name = None
|
||||
try:
|
||||
tables = security_manager.get_datasources_accessible_by_user(
|
||||
database=self._model,
|
||||
|
||||
@@ -1067,6 +1067,9 @@ class EngineInformationSchema(Schema):
|
||||
supports_oauth2 = fields.Boolean(
|
||||
metadata={"description": "The database supports OAuth2"}
|
||||
)
|
||||
supports_schemas = fields.Boolean(
|
||||
metadata={"description": "The database uses schemas to organize tables"}
|
||||
)
|
||||
|
||||
|
||||
class DatabaseConnectionSchema(Schema):
|
||||
|
||||
@@ -577,6 +577,10 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
|
||||
# Does the DB engine spec support cross-catalog queries?
|
||||
supports_cross_catalog_queries = False
|
||||
|
||||
# Does the DB engine support schemas? When set to False the schema selector is
|
||||
# hidden in the dataset creation UI and schema is not required for table access.
|
||||
supports_schemas = True
|
||||
|
||||
# Does the engine supports OAuth 2.0? This requires logic to be added to one of the
|
||||
# the user impersonation methods to handle personal tokens.
|
||||
supports_oauth2 = False
|
||||
@@ -2523,6 +2527,7 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
|
||||
"disable_ssh_tunneling": cls.disable_ssh_tunneling,
|
||||
"supports_dynamic_catalog": cls.supports_dynamic_catalog,
|
||||
"supports_oauth2": cls.supports_oauth2,
|
||||
"supports_schemas": cls.supports_schemas,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -51,6 +51,7 @@ class YDBEngineSpec(BaseEngineSpec):
|
||||
disable_ssh_tunneling = False
|
||||
|
||||
supports_file_upload = False
|
||||
supports_schemas = False
|
||||
|
||||
allows_alias_in_orderby = True
|
||||
|
||||
|
||||
@@ -3427,6 +3427,7 @@ class TestDatabaseApi(SupersetTestCase):
|
||||
"supports_dynamic_catalog": True,
|
||||
"disable_ssh_tunneling": False,
|
||||
"supports_oauth2": False,
|
||||
"supports_schemas": True,
|
||||
},
|
||||
"supports_oauth2": False,
|
||||
},
|
||||
@@ -3455,6 +3456,7 @@ class TestDatabaseApi(SupersetTestCase):
|
||||
"supports_dynamic_catalog": True,
|
||||
"disable_ssh_tunneling": True,
|
||||
"supports_oauth2": False,
|
||||
"supports_schemas": True,
|
||||
},
|
||||
"supports_oauth2": False,
|
||||
},
|
||||
@@ -3513,6 +3515,7 @@ class TestDatabaseApi(SupersetTestCase):
|
||||
"supports_dynamic_catalog": False,
|
||||
"disable_ssh_tunneling": False,
|
||||
"supports_oauth2": False,
|
||||
"supports_schemas": True,
|
||||
},
|
||||
"supports_oauth2": False,
|
||||
},
|
||||
@@ -3558,6 +3561,7 @@ class TestDatabaseApi(SupersetTestCase):
|
||||
"supports_dynamic_catalog": False,
|
||||
"disable_ssh_tunneling": True,
|
||||
"supports_oauth2": True,
|
||||
"supports_schemas": True,
|
||||
},
|
||||
"supports_oauth2": True,
|
||||
},
|
||||
@@ -3616,6 +3620,7 @@ class TestDatabaseApi(SupersetTestCase):
|
||||
"supports_dynamic_catalog": False,
|
||||
"disable_ssh_tunneling": False,
|
||||
"supports_oauth2": False,
|
||||
"supports_schemas": True,
|
||||
},
|
||||
"supports_oauth2": False,
|
||||
},
|
||||
@@ -3630,6 +3635,7 @@ class TestDatabaseApi(SupersetTestCase):
|
||||
"supports_dynamic_catalog": False,
|
||||
"disable_ssh_tunneling": False,
|
||||
"supports_oauth2": False,
|
||||
"supports_schemas": True,
|
||||
},
|
||||
"supports_oauth2": False,
|
||||
},
|
||||
@@ -3664,6 +3670,7 @@ class TestDatabaseApi(SupersetTestCase):
|
||||
"supports_dynamic_catalog": False,
|
||||
"disable_ssh_tunneling": False,
|
||||
"supports_oauth2": False,
|
||||
"supports_schemas": True,
|
||||
},
|
||||
"supports_oauth2": False,
|
||||
},
|
||||
@@ -3678,6 +3685,7 @@ class TestDatabaseApi(SupersetTestCase):
|
||||
"supports_dynamic_catalog": False,
|
||||
"disable_ssh_tunneling": False,
|
||||
"supports_oauth2": False,
|
||||
"supports_schemas": True,
|
||||
},
|
||||
"supports_oauth2": False,
|
||||
},
|
||||
|
||||
@@ -148,6 +148,78 @@ def test_tables_with_catalog(
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def database_without_schema_support(mocker: MockerFixture) -> MagicMock:
|
||||
"""
|
||||
Mock a database that does not support schemas (e.g. YDB).
|
||||
"""
|
||||
mocker.patch("superset.commands.database.tables.db")
|
||||
|
||||
database = mocker.MagicMock()
|
||||
database.database_name = "test_database"
|
||||
database.get_default_catalog.return_value = None
|
||||
database.db_engine_spec.supports_schemas = False
|
||||
database.get_all_table_names_in_schema.return_value = {
|
||||
("table1", None, None),
|
||||
("table2", None, None),
|
||||
}
|
||||
database.get_all_view_names_in_schema.return_value = set()
|
||||
database.get_all_materialized_view_names_in_schema.return_value = set()
|
||||
|
||||
DatabaseDAO = mocker.patch("superset.commands.database.tables.DatabaseDAO") # noqa: N806
|
||||
DatabaseDAO.find_by_id.return_value = database
|
||||
|
||||
return database
|
||||
|
||||
|
||||
def test_tables_without_schema_support(
|
||||
mocker: MockerFixture,
|
||||
database_without_schema_support: MagicMock,
|
||||
) -> None:
|
||||
"""
|
||||
Test that schema is overridden to None for databases that don't support schemas.
|
||||
Any schema name passed to the command is ignored.
|
||||
"""
|
||||
get_datasources_accessible_by_user = mocker.patch.object(
|
||||
security_manager,
|
||||
"get_datasources_accessible_by_user",
|
||||
side_effect=[
|
||||
{
|
||||
DatasourceName("table1", None), # type: ignore[arg-type]
|
||||
DatasourceName("table2", None), # type: ignore[arg-type]
|
||||
},
|
||||
set(), # Empty set for views
|
||||
set(), # Empty set for materialized views
|
||||
],
|
||||
)
|
||||
|
||||
db = mocker.patch("superset.commands.database.tables.db")
|
||||
db.session.query().filter().options().all.return_value = []
|
||||
|
||||
# Schema name should be overridden to None when supports_schemas=False
|
||||
payload = TablesDatabaseCommand(1, None, "any_schema", False).run()
|
||||
|
||||
assert payload["count"] == 2
|
||||
assert {item["value"] for item in payload["result"]} == {"table1", "table2"}
|
||||
|
||||
# Verify schema was set to None when calling the underlying DB methods
|
||||
database_without_schema_support.get_all_table_names_in_schema.assert_called_with(
|
||||
catalog=None,
|
||||
schema=None,
|
||||
force=False,
|
||||
cache=database_without_schema_support.table_cache_enabled,
|
||||
cache_timeout=database_without_schema_support.table_cache_timeout,
|
||||
)
|
||||
|
||||
# Verify security_manager was called with schema=None
|
||||
get_datasources_accessible_by_user.assert_any_call(
|
||||
database=database_without_schema_support,
|
||||
catalog=None,
|
||||
schema=None,
|
||||
datasource_names=mocker.ANY,
|
||||
)
|
||||
|
||||
|
||||
def test_tables_without_catalog(
|
||||
mocker: MockerFixture,
|
||||
database_without_catalog: MockerFixture,
|
||||
|
||||
@@ -243,6 +243,7 @@ def test_database_connection(
|
||||
"supports_dynamic_catalog": False,
|
||||
"supports_file_upload": True,
|
||||
"supports_oauth2": True,
|
||||
"supports_schemas": True,
|
||||
},
|
||||
"expose_in_sqllab": True,
|
||||
"extra": '{\n "metadata_params": {},\n "engine_params": {},\n "metadata_cache_timeout": {},\n "schemas_allowed_for_file_upload": []\n}\n', # noqa: E501
|
||||
@@ -332,6 +333,7 @@ def test_database_connection(
|
||||
"supports_dynamic_catalog": False,
|
||||
"supports_file_upload": True,
|
||||
"supports_oauth2": True,
|
||||
"supports_schemas": True,
|
||||
},
|
||||
"expose_in_sqllab": True,
|
||||
"force_ctas_schema": None,
|
||||
|
||||
Reference in New Issue
Block a user