mirror of
https://github.com/apache/superset.git
synced 2026-05-17 22:05:14 +00:00
Compare commits
13 Commits
msyavuz/ch
...
fix/playwr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d2b655c22 | ||
|
|
29b94ced71 | ||
|
|
736a51c13f | ||
|
|
34c28f7b76 | ||
|
|
62c86abcd1 | ||
|
|
caa357e0d2 | ||
|
|
cc21683118 | ||
|
|
114d88468b | ||
|
|
48c0bea906 | ||
|
|
a46925d431 | ||
|
|
0df9cc986a | ||
|
|
ade901ed04 | ||
|
|
1e2d0b5f5b |
127
superset-frontend/package-lock.json
generated
127
superset-frontend/package-lock.json
generated
@@ -102,7 +102,7 @@
|
||||
"geostyler-style": "11.0.2",
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"google-auth-library": "^10.6.2",
|
||||
"immer": "^11.1.7",
|
||||
"immer": "^11.1.8",
|
||||
"interweave": "^13.1.1",
|
||||
"jquery": "^4.0.0",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
@@ -120,13 +120,13 @@
|
||||
"pretty-ms": "^9.3.0",
|
||||
"query-string": "9.3.1",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react": "^18.3.0",
|
||||
"react-arborist": "^3.5.0",
|
||||
"react": "^18.2.0",
|
||||
"react-arborist": "^3.6.1",
|
||||
"react-checkbox-tree": "^1.8.0",
|
||||
"react-diff-viewer-continued": "^4.2.2",
|
||||
"react-dnd": "^11.1.3",
|
||||
"react-dnd-html5-backend": "^11.1.3",
|
||||
"react-dom": "^18.3.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-google-recaptcha": "^3.1.0",
|
||||
"react-intersection-observer": "^10.0.3",
|
||||
"react-json-tree": "^0.20.0",
|
||||
@@ -209,8 +209,8 @@
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/react": "^18.3.0",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@types/react-loadable": "^5.5.11",
|
||||
"@types/react-redux": "^7.1.10",
|
||||
"@types/react-resizable": "^3.0.8",
|
||||
@@ -274,7 +274,6 @@
|
||||
"prettier": "3.8.3",
|
||||
"prettier-plugin-packagejson": "^3.0.2",
|
||||
"process": "^0.11.10",
|
||||
"react-dnd-test-backend": "^11.1.3",
|
||||
"react-refresh": "^0.18.0",
|
||||
"react-resizable": "^3.1.3",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
@@ -23973,9 +23972,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fast-xml-builder": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz",
|
||||
"integrity": "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz",
|
||||
"integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -23984,7 +23983,8 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-expression-matcher": "^1.1.3"
|
||||
"path-expression-matcher": "^1.5.0",
|
||||
"xml-naming": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
@@ -27207,9 +27207,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.7.tgz",
|
||||
"integrity": "sha512-LFVFtAROHcDy1er5UI6nodRFnZ2SgdCXhfNSI+DpObO8N7Pur/muBGsjzH5wpnFHCYhYVQxZskCkV4koQ//3/Q==",
|
||||
"version": "11.1.8",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.8.tgz",
|
||||
"integrity": "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -41344,9 +41344,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-arborist": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-arborist/-/react-arborist-3.5.0.tgz",
|
||||
"integrity": "sha512-FdXOICSt7P2h+Pxin1ULN02b4qrXJznNcshgwwWVtuYMLWSJcD245PQ4HOSj/Lr2T1uEegmnEm5Lbns2hUUsqg==",
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/react-arborist/-/react-arborist-3.6.1.tgz",
|
||||
"integrity": "sha512-h2/sPz6PXL79h7mOWjCA6Y5WNUKmA0kL8Uh6RYZQbYk7UOFBd86Jeoga4RjHMBYpOWpBPYrOJOE3HbIPUETp8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-dnd": "^14.0.3",
|
||||
@@ -41562,16 +41562,6 @@
|
||||
"dnd-core": "^11.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dnd-test-backend": {
|
||||
"version": "11.1.3",
|
||||
"resolved": "https://registry.npmjs.org/react-dnd-test-backend/-/react-dnd-test-backend-11.1.3.tgz",
|
||||
"integrity": "sha512-5qFm+NI2GdWIUfiYun0A8Gv0xjbq0NGOPS+f6z3x/3nTuliApjmqcM1lfTgePoz1FDG47ZofF58N8y96If62+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dnd-core": "^11.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-docgen": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-7.1.1.tgz",
|
||||
@@ -49474,6 +49464,21 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/xml-naming": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz",
|
||||
"integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xml-utils": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.10.2.tgz",
|
||||
@@ -50377,8 +50382,8 @@
|
||||
"jed": "^1.1.1",
|
||||
"lodash": "^4.18.1",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*"
|
||||
}
|
||||
@@ -50404,9 +50409,9 @@
|
||||
"ace-builds": "^1.4.14",
|
||||
"brace": "^0.11.1",
|
||||
"memoize-one": "^5.1.1",
|
||||
"react": "^18.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"packages/superset-ui-core": {
|
||||
@@ -50490,8 +50495,8 @@
|
||||
"@types/tinycolor2": "*",
|
||||
"antd": "^5.26.0",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*"
|
||||
}
|
||||
@@ -50656,7 +50661,7 @@
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-calendar/node_modules/d3-array": {
|
||||
@@ -50717,7 +50722,7 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-country-map/node_modules/d3-array": {
|
||||
@@ -50745,7 +50750,7 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-horizon/node_modules/d3-array": {
|
||||
@@ -50779,7 +50784,7 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-parallel-coordinates": {
|
||||
@@ -50794,7 +50799,7 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-partition": {
|
||||
@@ -50812,8 +50817,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-rose": {
|
||||
@@ -50830,7 +50835,7 @@
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-world-map": {
|
||||
@@ -50848,7 +50853,7 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-world-map/node_modules/d3-array": {
|
||||
@@ -50935,7 +50940,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"dayjs": "^1.11.19",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-ag-grid-table": {
|
||||
@@ -50963,8 +50968,8 @@
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/react": "*",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-ag-grid-table/node_modules/d3-array": {
|
||||
@@ -51008,8 +51013,8 @@
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"ol": "^10.8.0",
|
||||
"polished": "*",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-echarts": {
|
||||
@@ -51022,7 +51027,7 @@
|
||||
"acorn": "^8.16.0",
|
||||
"d3-array": "^3.2.4",
|
||||
"lodash": "^4.18.1",
|
||||
"zod": "^4.4.3"
|
||||
"zod": "^4.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
@@ -51031,7 +51036,7 @@
|
||||
"dayjs": "^1.11.19",
|
||||
"echarts": "*",
|
||||
"memoize-one": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-echarts/node_modules/acorn": {
|
||||
@@ -51089,9 +51094,9 @@
|
||||
"dayjs": "^1.11.19",
|
||||
"handlebars": "^4.7.8",
|
||||
"lodash": "^4.18.1",
|
||||
"react": "^18.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-handlebars/node_modules/just-handlebars-helpers": {
|
||||
@@ -51122,8 +51127,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"lodash": "^4.18.1",
|
||||
"prop-types": "*",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-point-cluster-map": {
|
||||
@@ -51141,8 +51146,8 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-point-cluster-map/node_modules/react-map-gl": {
|
||||
@@ -51195,8 +51200,8 @@
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/react": "*",
|
||||
"match-sorter": "^8.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-table/node_modules/d3-array": {
|
||||
@@ -51223,7 +51228,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/d3-scale": "^4.0.9",
|
||||
"d3-cloud": "^1.2.8",
|
||||
"d3-cloud": "^1.2.9",
|
||||
"d3-scale": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -51235,7 +51240,7 @@
|
||||
"@superset-ui/core": "*",
|
||||
"@types/lodash": "*",
|
||||
"@types/react": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-word-cloud/node_modules/@types/d3-scale": {
|
||||
@@ -51304,8 +51309,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"dayjs": "^1.11.19",
|
||||
"mapbox-gl": ">=1.0.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"mapbox-gl": {
|
||||
|
||||
@@ -109,20 +109,20 @@
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@fontsource/fira-code": "^5.2.7",
|
||||
"@fontsource/ibm-plex-mono": "^5.2.7",
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@googleapis/sheets": "^13.0.1",
|
||||
"@great-expectations/jsonforms-antd-renderers": "^2.2.10",
|
||||
"@jsonforms/core": "^3.7.0",
|
||||
"@jsonforms/react": "^3.7.0",
|
||||
"@jsonforms/vanilla-renderers": "^3.7.0",
|
||||
"@luma.gl/constants": "~9.2.5",
|
||||
"@luma.gl/core": "~9.2.5",
|
||||
"@luma.gl/engine": "~9.2.5",
|
||||
"@luma.gl/gltf": "~9.2.5",
|
||||
"@luma.gl/shadertools": "~9.2.5",
|
||||
"@luma.gl/webgl": "~9.2.5",
|
||||
"@fontsource/fira-code": "^5.2.7",
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@great-expectations/jsonforms-antd-renderers": "^2.2.10",
|
||||
"@jsonforms/core": "^3.7.0",
|
||||
"@jsonforms/react": "^3.7.0",
|
||||
"@jsonforms/vanilla-renderers": "^3.7.0",
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"@rjsf/antd": "^5.24.13",
|
||||
"@rjsf/core": "^5.24.13",
|
||||
@@ -140,16 +140,16 @@
|
||||
"@superset-ui/legacy-plugin-chart-partition": "file:./plugins/legacy-plugin-chart-partition",
|
||||
"@superset-ui/legacy-plugin-chart-rose": "file:./plugins/legacy-plugin-chart-rose",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "file:./plugins/legacy-plugin-chart-world-map",
|
||||
"@superset-ui/preset-chart-deckgl": "file:./plugins/preset-chart-deckgl",
|
||||
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
|
||||
"@superset-ui/plugin-chart-ag-grid-table": "file:./plugins/plugin-chart-ag-grid-table",
|
||||
"@superset-ui/plugin-chart-cartodiagram": "file:./plugins/plugin-chart-cartodiagram",
|
||||
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
|
||||
"@superset-ui/plugin-chart-point-cluster-map": "file:./plugins/plugin-chart-point-cluster-map",
|
||||
"@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
|
||||
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
|
||||
"@superset-ui/plugin-chart-point-cluster-map": "file:./plugins/plugin-chart-point-cluster-map",
|
||||
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
|
||||
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
|
||||
"@superset-ui/preset-chart-deckgl": "file:./plugins/preset-chart-deckgl",
|
||||
"@superset-ui/switchboard": "file:./packages/superset-ui-switchboard",
|
||||
"@types/d3-format": "^3.0.1",
|
||||
"@types/d3-selection": "^3.0.11",
|
||||
@@ -183,7 +183,7 @@
|
||||
"geostyler-style": "11.0.2",
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"google-auth-library": "^10.6.2",
|
||||
"immer": "^11.1.7",
|
||||
"immer": "^11.1.8",
|
||||
"interweave": "^13.1.1",
|
||||
"jquery": "^4.0.0",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
@@ -201,13 +201,13 @@
|
||||
"pretty-ms": "^9.3.0",
|
||||
"query-string": "9.3.1",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react": "^18.3.0",
|
||||
"react-arborist": "^3.5.0",
|
||||
"react": "^18.2.0",
|
||||
"react-arborist": "^3.6.1",
|
||||
"react-checkbox-tree": "^1.8.0",
|
||||
"react-diff-viewer-continued": "^4.2.2",
|
||||
"react-dnd": "^11.1.3",
|
||||
"react-dnd-html5-backend": "^11.1.3",
|
||||
"react-dom": "^18.3.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-google-recaptcha": "^3.1.0",
|
||||
"react-intersection-observer": "^10.0.3",
|
||||
"react-json-tree": "^0.20.0",
|
||||
@@ -290,8 +290,8 @@
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/react": "^18.3.0",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@types/react-loadable": "^5.5.11",
|
||||
"@types/react-redux": "^7.1.10",
|
||||
"@types/react-resizable": "^3.0.8",
|
||||
@@ -355,7 +355,6 @@
|
||||
"prettier": "3.8.3",
|
||||
"prettier-plugin-packagejson": "^3.0.2",
|
||||
"process": "^0.11.10",
|
||||
"react-dnd-test-backend": "^11.1.3",
|
||||
"react-refresh": "^0.18.0",
|
||||
"react-resizable": "^3.1.3",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
|
||||
@@ -97,8 +97,8 @@
|
||||
"@fontsource/ibm-plex-mono": "^5.2.7",
|
||||
"@fontsource/inter": "^5.2.6",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*",
|
||||
"lodash": "^4.18.1",
|
||||
|
||||
@@ -50,24 +50,20 @@ test('should pipe to `console` methods', () => {
|
||||
});
|
||||
|
||||
test('should use noop functions when console unavailable', () => {
|
||||
const originalConsole = window.console;
|
||||
Object.assign(window, { console: undefined });
|
||||
try {
|
||||
const { logging } = require('@apache-superset/core/utils');
|
||||
const { logging } = require('@apache-superset/core/utils');
|
||||
|
||||
expect(() => {
|
||||
logging.debug();
|
||||
logging.log();
|
||||
logging.info();
|
||||
logging.warn('warn');
|
||||
logging.error('error');
|
||||
logging.trace();
|
||||
logging.table([
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
]);
|
||||
}).not.toThrow();
|
||||
} finally {
|
||||
Object.assign(window, { console: originalConsole });
|
||||
}
|
||||
expect(() => {
|
||||
logging.debug();
|
||||
logging.log();
|
||||
logging.info();
|
||||
logging.warn('warn');
|
||||
logging.error('error');
|
||||
logging.trace();
|
||||
logging.table([
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
]);
|
||||
}).not.toThrow();
|
||||
Object.assign(window, { console });
|
||||
});
|
||||
|
||||
@@ -40,9 +40,9 @@
|
||||
"ace-builds": "^1.4.14",
|
||||
"brace": "^0.11.1",
|
||||
"memoize-one": "^5.1.1",
|
||||
"react": "^18.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
"react-js-cron": "^5.2.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-resize-detector": "^7.1.2",
|
||||
"react-syntax-highlighter": "^16.1.0",
|
||||
"react-syntax-highlighter": "^16.1.1",
|
||||
"react-ultimate-pagination": "^1.3.2",
|
||||
"regenerator-runtime": "^0.14.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
@@ -100,8 +100,8 @@
|
||||
"@types/tinycolor2": "*",
|
||||
"antd": "^5.26.0",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*"
|
||||
},
|
||||
|
||||
@@ -17,16 +17,15 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { forwardRef } from 'react';
|
||||
import { Avatar as AntdAvatar } from 'antd';
|
||||
import type { AvatarProps, GroupProps as AvatarGroupProps } from './types';
|
||||
|
||||
export const Avatar = forwardRef<HTMLSpanElement, AvatarProps>((props, ref) => (
|
||||
<AntdAvatar ref={ref} {...props} />
|
||||
));
|
||||
export function Avatar(props: AvatarProps) {
|
||||
return <AntdAvatar {...props} />;
|
||||
}
|
||||
|
||||
export const AvatarGroup = (props: AvatarGroupProps) => (
|
||||
<AntdAvatar.Group {...props} />
|
||||
);
|
||||
export function AvatarGroup(props: AvatarGroupProps) {
|
||||
return <AntdAvatar.Group {...props} />;
|
||||
}
|
||||
|
||||
export type { AvatarProps, AvatarGroupProps };
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Children, ReactElement, Fragment, forwardRef, Ref } from 'react';
|
||||
import { Children, ReactElement, Fragment } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { Button as AntdButton } from 'antd';
|
||||
import { useTheme } from '@apache-superset/core/theme';
|
||||
@@ -100,7 +100,7 @@ const BUTTON_STYLE_MAP: Record<
|
||||
link: { type: 'link' },
|
||||
};
|
||||
|
||||
function ButtonInner(props: ButtonProps, ref: Ref<HTMLElement>) {
|
||||
export function Button(props: ButtonProps) {
|
||||
const {
|
||||
tooltip,
|
||||
placement,
|
||||
@@ -160,7 +160,6 @@ function ButtonInner(props: ButtonProps, ref: Ref<HTMLElement>) {
|
||||
|
||||
const button = (
|
||||
<AntdButton
|
||||
ref={ref as Ref<HTMLButtonElement & HTMLAnchorElement>}
|
||||
href={disabled ? undefined : href}
|
||||
disabled={disabled}
|
||||
type={antdType}
|
||||
@@ -236,6 +235,4 @@ function ButtonInner(props: ButtonProps, ref: Ref<HTMLElement>) {
|
||||
return button;
|
||||
}
|
||||
|
||||
export const Button = forwardRef<HTMLElement, ButtonProps>(ButtonInner);
|
||||
|
||||
export type { ButtonProps, OnClickHandler };
|
||||
|
||||
@@ -75,10 +75,7 @@ export const DropdownButton = ({
|
||||
id={`${kebabCase(tooltip)}-tooltip`}
|
||||
title={tooltip}
|
||||
>
|
||||
{/* antd Dropdown.Button is a plain function component without
|
||||
forwardRef; wrap in a span so the Tooltip can attach a ref to a
|
||||
real DOM node and skip the findDOMNode fallback. */}
|
||||
<span>{button}</span>
|
||||
{button}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -240,10 +240,7 @@ export function EditableTitle({
|
||||
t("You don't have the rights to alter this title.")
|
||||
}
|
||||
>
|
||||
{/* Wrap in span so the Tooltip can attach a ref to a DOM element.
|
||||
antd's Input.TextArea forwards a non-DOM imperative handle, which
|
||||
triggers a React 18 findDOMNode deprecation warning. */}
|
||||
<span>{titleComponent}</span>
|
||||
{titleComponent}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,54 +16,47 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { forwardRef } from 'react';
|
||||
import { Tooltip } from '../Tooltip';
|
||||
import { Button } from '../Button';
|
||||
import type { IconTooltipProps } from './types';
|
||||
|
||||
export const IconTooltip = forwardRef<HTMLElement, IconTooltipProps>(
|
||||
(
|
||||
{
|
||||
children = null,
|
||||
className = '',
|
||||
onClick = () => undefined,
|
||||
placement = 'top',
|
||||
style = {},
|
||||
tooltip = null,
|
||||
mouseEnterDelay = 0.3,
|
||||
mouseLeaveDelay = 0.15,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const iconTooltip = (
|
||||
<Button
|
||||
ref={ref}
|
||||
onClick={onClick}
|
||||
style={{
|
||||
padding: 0,
|
||||
...style,
|
||||
}}
|
||||
buttonStyle="link"
|
||||
className={`IconTooltip ${className}`}
|
||||
export const IconTooltip = ({
|
||||
children = null,
|
||||
className = '',
|
||||
onClick = () => undefined,
|
||||
placement = 'top',
|
||||
style = {},
|
||||
tooltip = null,
|
||||
mouseEnterDelay = 0.3,
|
||||
mouseLeaveDelay = 0.15,
|
||||
}: IconTooltipProps) => {
|
||||
const iconTooltip = (
|
||||
<Button
|
||||
onClick={onClick}
|
||||
style={{
|
||||
padding: 0,
|
||||
...style,
|
||||
}}
|
||||
buttonStyle="link"
|
||||
className={`IconTooltip ${className}`}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
if (tooltip) {
|
||||
return (
|
||||
<Tooltip
|
||||
id="tooltip"
|
||||
title={tooltip}
|
||||
placement={placement}
|
||||
mouseEnterDelay={mouseEnterDelay}
|
||||
mouseLeaveDelay={mouseLeaveDelay}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
{iconTooltip}
|
||||
</Tooltip>
|
||||
);
|
||||
if (tooltip) {
|
||||
return (
|
||||
<Tooltip
|
||||
id="tooltip"
|
||||
title={tooltip}
|
||||
placement={placement}
|
||||
mouseEnterDelay={mouseEnterDelay}
|
||||
mouseLeaveDelay={mouseLeaveDelay}
|
||||
>
|
||||
{iconTooltip}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
return iconTooltip;
|
||||
},
|
||||
);
|
||||
}
|
||||
return iconTooltip;
|
||||
};
|
||||
|
||||
export type { IconTooltipProps };
|
||||
|
||||
@@ -165,7 +165,7 @@ import {
|
||||
SlackOutlined,
|
||||
ApiOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { ForwardRefExoticComponent, RefAttributes, forwardRef } from 'react';
|
||||
import { FC } from 'react';
|
||||
import { IconType } from './types';
|
||||
import { BaseIconComponent } from './BaseIcon';
|
||||
|
||||
@@ -323,25 +323,19 @@ type AntdIconNames = keyof typeof AntdIcons;
|
||||
|
||||
export const antdEnhancedIcons: Record<
|
||||
AntdIconNames,
|
||||
ForwardRefExoticComponent<IconType & RefAttributes<HTMLSpanElement>>
|
||||
FC<IconType>
|
||||
> = Object.keys(AntdIcons)
|
||||
.filter(key => !EXCLUDED_ICONS.some(excluded => key.includes(excluded)))
|
||||
.reduce(
|
||||
(acc, key) => {
|
||||
acc[key as AntdIconNames] = forwardRef<HTMLSpanElement, IconType>(
|
||||
(props, ref) => (
|
||||
<BaseIconComponent
|
||||
ref={ref}
|
||||
component={AntdIcons[key as AntdIconNames]}
|
||||
fileName={key}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
acc[key as AntdIconNames] = (props: IconType) => (
|
||||
<BaseIconComponent
|
||||
component={AntdIcons[key as AntdIconNames]}
|
||||
fileName={key}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<
|
||||
AntdIconNames,
|
||||
ForwardRefExoticComponent<IconType & RefAttributes<HTMLSpanElement>>
|
||||
>,
|
||||
{} as Record<AntdIconNames, FC<IconType>>,
|
||||
);
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { FC, SVGProps, forwardRef, useEffect, useRef, useState } from 'react';
|
||||
import { FC, SVGProps, useEffect, useRef, useState } from 'react';
|
||||
import TransparentIcon from './svgs/transparent.svg';
|
||||
import { IconType } from './types';
|
||||
import { BaseIconComponent } from './BaseIcon';
|
||||
|
||||
const AsyncIcon = forwardRef<HTMLSpanElement, IconType>((props, ref) => {
|
||||
const AsyncIcon = (props: IconType) => {
|
||||
const [, setLoaded] = useState(false);
|
||||
const ImportedSVG = useRef<FC<SVGProps<SVGSVGElement>>>();
|
||||
const { fileName, customIcons, iconSize, iconColor, viewBox, ...restProps } =
|
||||
@@ -46,7 +46,6 @@ const AsyncIcon = forwardRef<HTMLSpanElement, IconType>((props, ref) => {
|
||||
|
||||
return (
|
||||
<BaseIconComponent
|
||||
ref={ref}
|
||||
component={ImportedSVG.current || TransparentIcon}
|
||||
fileName={fileName}
|
||||
customIcons={customIcons}
|
||||
@@ -56,6 +55,6 @@ const AsyncIcon = forwardRef<HTMLSpanElement, IconType>((props, ref) => {
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default AsyncIcon;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { forwardRef, type ComponentType } from 'react';
|
||||
import { css, useTheme, getFontSize } from '@apache-superset/core/theme';
|
||||
import { AntdIconType, BaseIconProps, CustomIconType, IconType } from './types';
|
||||
|
||||
@@ -36,78 +35,65 @@ const genAriaLabel = (fileName: string) => {
|
||||
return name.toLowerCase();
|
||||
};
|
||||
|
||||
export const BaseIconComponent = forwardRef<
|
||||
HTMLSpanElement | SVGSVGElement,
|
||||
export const BaseIconComponent: React.FC<
|
||||
BaseIconProps & Omit<IconType, 'component'>
|
||||
>(
|
||||
(
|
||||
{
|
||||
component: Component,
|
||||
iconColor,
|
||||
iconSize,
|
||||
viewBox,
|
||||
customIcons,
|
||||
fileName,
|
||||
...rest
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const theme = useTheme();
|
||||
const whatRole = rest?.onClick ? 'button' : 'img';
|
||||
const ariaLabel = genAriaLabel(fileName || '');
|
||||
const style = {
|
||||
color: iconColor,
|
||||
fontSize: iconSize
|
||||
? `${getFontSize(theme, iconSize)}px`
|
||||
: `${theme.fontSize}px`,
|
||||
cursor: rest?.onClick ? 'pointer' : undefined,
|
||||
};
|
||||
> = ({
|
||||
component: Component,
|
||||
iconColor,
|
||||
iconSize,
|
||||
viewBox,
|
||||
customIcons,
|
||||
fileName,
|
||||
...rest
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const whatRole = rest?.onClick ? 'button' : 'img';
|
||||
const ariaLabel = genAriaLabel(fileName || '');
|
||||
const style = {
|
||||
color: iconColor,
|
||||
fontSize: iconSize
|
||||
? `${getFontSize(theme, iconSize)}px`
|
||||
: `${theme.fontSize}px`,
|
||||
cursor: rest?.onClick ? 'pointer' : undefined,
|
||||
};
|
||||
|
||||
const AntdComponent = Component as ComponentType<
|
||||
Record<string, unknown> & {
|
||||
ref?: React.Ref<HTMLSpanElement | SVGSVGElement>;
|
||||
}
|
||||
>;
|
||||
return customIcons ? (
|
||||
<span
|
||||
ref={ref as React.Ref<HTMLSpanElement>}
|
||||
role={whatRole}
|
||||
aria-label={ariaLabel}
|
||||
data-test={ariaLabel}
|
||||
css={[
|
||||
css`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
line-height: 0;
|
||||
vertical-align: middle;
|
||||
`,
|
||||
]}
|
||||
>
|
||||
<Component
|
||||
viewBox={viewBox || '0 0 24 24'}
|
||||
style={style}
|
||||
width={
|
||||
iconSize
|
||||
? `${getFontSize(theme, iconSize) || theme.fontSize}px`
|
||||
: `${theme.fontSize}px`
|
||||
}
|
||||
height={
|
||||
iconSize
|
||||
? `${getFontSize(theme, iconSize) || theme.fontSize}px`
|
||||
: `${theme.fontSize}px`
|
||||
}
|
||||
{...(rest as CustomIconType)}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
<AntdComponent
|
||||
ref={ref}
|
||||
role={whatRole}
|
||||
return customIcons ? (
|
||||
<span
|
||||
role={whatRole}
|
||||
aria-label={ariaLabel}
|
||||
data-test={ariaLabel}
|
||||
css={[
|
||||
css`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
line-height: 0;
|
||||
vertical-align: middle;
|
||||
`,
|
||||
]}
|
||||
>
|
||||
<Component
|
||||
viewBox={viewBox || '0 0 24 24'}
|
||||
style={style}
|
||||
aria-label={ariaLabel}
|
||||
data-test={ariaLabel}
|
||||
{...(rest as AntdIconType)}
|
||||
width={
|
||||
iconSize
|
||||
? `${getFontSize(theme, iconSize) || theme.fontSize}px`
|
||||
: `${theme.fontSize}px`
|
||||
}
|
||||
height={
|
||||
iconSize
|
||||
? `${getFontSize(theme, iconSize) || theme.fontSize}px`
|
||||
: `${theme.fontSize}px`
|
||||
}
|
||||
{...(rest as CustomIconType)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
</span>
|
||||
) : (
|
||||
<Component
|
||||
role={whatRole}
|
||||
style={style}
|
||||
aria-label={ariaLabel}
|
||||
data-test={ariaLabel}
|
||||
{...(rest as AntdIconType)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,16 +17,12 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { ForwardRefExoticComponent, RefAttributes, forwardRef } from 'react';
|
||||
import { FC } from 'react';
|
||||
import { antdEnhancedIcons } from './AntdEnhanced';
|
||||
import AsyncIcon from './AsyncIcon';
|
||||
|
||||
import type { IconType } from './types';
|
||||
|
||||
type IconComponent = ForwardRefExoticComponent<
|
||||
IconType & RefAttributes<HTMLSpanElement>
|
||||
>;
|
||||
|
||||
/**
|
||||
* Filename is going to be inferred from the icon name.
|
||||
* i.e. BigNumberChartTile => assets/images/icons/big_number_chart_tile
|
||||
@@ -62,17 +58,15 @@ const customIcons = [
|
||||
'Undo',
|
||||
] as const;
|
||||
|
||||
type CustomIconType = Record<(typeof customIcons)[number], IconComponent>;
|
||||
type CustomIconType = Record<(typeof customIcons)[number], FC<IconType>>;
|
||||
|
||||
const iconOverrides: CustomIconType = {} as CustomIconType;
|
||||
customIcons.forEach(customIcon => {
|
||||
const fileName = customIcon
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
||||
.toLowerCase();
|
||||
iconOverrides[customIcon] = forwardRef<HTMLSpanElement, IconType>(
|
||||
(props, ref) => (
|
||||
<AsyncIcon ref={ref} customIcons fileName={fileName} {...props} />
|
||||
),
|
||||
iconOverrides[customIcon] = (props: IconType) => (
|
||||
<AsyncIcon customIcons fileName={fileName} {...props} />
|
||||
);
|
||||
});
|
||||
|
||||
@@ -80,7 +74,7 @@ export type IconNameType =
|
||||
| keyof typeof antdEnhancedIcons
|
||||
| keyof typeof iconOverrides;
|
||||
|
||||
type IconComponentType = Record<IconNameType, IconComponent>;
|
||||
type IconComponentType = Record<IconNameType, FC<IconType>>;
|
||||
|
||||
export const Icons: IconComponentType = {
|
||||
...antdEnhancedIcons,
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { forwardRef } from 'react';
|
||||
import { Tag } from '@superset-ui/core/components/Tag';
|
||||
import { css } from '@emotion/react';
|
||||
import { useTheme, getColorVariants } from '@apache-superset/core/theme';
|
||||
@@ -24,7 +23,7 @@ import { DatasetTypeLabel } from './reusable/DatasetTypeLabel';
|
||||
import { PublishedLabel } from './reusable/PublishedLabel';
|
||||
import type { LabelProps } from './types';
|
||||
|
||||
export const Label = forwardRef<HTMLSpanElement, LabelProps>((props, ref) => {
|
||||
export function Label(props: LabelProps) {
|
||||
const theme = useTheme();
|
||||
// Use Ant Design's motion duration instead of deprecated transitionTiming
|
||||
const {
|
||||
@@ -72,7 +71,6 @@ export const Label = forwardRef<HTMLSpanElement, LabelProps>((props, ref) => {
|
||||
|
||||
return (
|
||||
<Tag
|
||||
ref={ref}
|
||||
onClick={onClick}
|
||||
role={onClick ? 'button' : undefined}
|
||||
style={style}
|
||||
@@ -83,6 +81,6 @@ export const Label = forwardRef<HTMLSpanElement, LabelProps>((props, ref) => {
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
});
|
||||
}
|
||||
export { DatasetTypeLabel, PublishedLabel };
|
||||
export type { LabelType } from './types';
|
||||
|
||||
@@ -357,9 +357,6 @@ const CustomModal = ({
|
||||
disabled={!draggable || dragDisabled}
|
||||
bounds={bounds}
|
||||
onStart={(event, uiData) => onDragStart(event, uiData)}
|
||||
// Pass nodeRef so react-draggable does not fall back to
|
||||
// ReactDOM.findDOMNode (deprecated in React 18+ Strict Mode).
|
||||
nodeRef={draggableRef}
|
||||
{...draggableConfig}
|
||||
>
|
||||
{resizable ? (
|
||||
|
||||
@@ -16,15 +16,11 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { forwardRef } from 'react';
|
||||
import { Popover as AntdPopover } from 'antd';
|
||||
import { PopoverProps as AntdPopoverProps } from 'antd/es/popover';
|
||||
import type { TooltipRef } from 'antd/es/tooltip';
|
||||
|
||||
export interface PopoverProps extends AntdPopoverProps {
|
||||
forceRender?: boolean;
|
||||
}
|
||||
|
||||
export const Popover = forwardRef<TooltipRef, PopoverProps>((props, ref) => (
|
||||
<AntdPopover ref={ref} {...props} />
|
||||
));
|
||||
export const Popover = (props: PopoverProps) => <AntdPopover {...props} />;
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { MouseEventHandler } from 'react';
|
||||
import { MouseEventHandler, forwardRef } from 'react';
|
||||
import { SupersetTheme } from '@apache-superset/core/theme';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import type { IconType } from '@superset-ui/core/components/Icons/types';
|
||||
import { Tooltip } from '../Tooltip';
|
||||
|
||||
export interface RefreshLabelProps {
|
||||
@@ -31,19 +32,25 @@ const RefreshLabel = ({
|
||||
onClick,
|
||||
tooltipContent,
|
||||
disabled,
|
||||
}: RefreshLabelProps) => (
|
||||
<Tooltip title={tooltipContent}>
|
||||
<Icons.SyncOutlined
|
||||
iconSize="l"
|
||||
role="button"
|
||||
onClick={disabled ? undefined : onClick}
|
||||
css={(theme: SupersetTheme) => ({
|
||||
cursor: 'pointer',
|
||||
color: theme.colorIcon,
|
||||
'&:hover': { color: theme.colorPrimary },
|
||||
})}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}: RefreshLabelProps) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const IconWithoutRef = forwardRef((props: IconType, ref: any) => (
|
||||
<Icons.SyncOutlined iconSize="l" {...props} />
|
||||
));
|
||||
|
||||
return (
|
||||
<Tooltip title={tooltipContent}>
|
||||
<IconWithoutRef
|
||||
role="button"
|
||||
onClick={disabled ? undefined : onClick}
|
||||
css={(theme: SupersetTheme) => ({
|
||||
cursor: 'pointer',
|
||||
color: theme.colorIcon,
|
||||
'&:hover': { color: theme.colorPrimary },
|
||||
})}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default RefreshLabel;
|
||||
|
||||
@@ -16,22 +16,17 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { forwardRef } from 'react';
|
||||
import { Tooltip as AntdTooltip } from 'antd';
|
||||
import type { TooltipRef } from 'antd/es/tooltip';
|
||||
|
||||
import type { TooltipProps, TooltipPlacement } from './types';
|
||||
|
||||
export const Tooltip = forwardRef<TooltipRef, TooltipProps>(
|
||||
({ overlayStyle, ...props }, ref) => (
|
||||
<AntdTooltip
|
||||
ref={ref}
|
||||
styles={{
|
||||
body: { overflow: 'hidden', textOverflow: 'ellipsis' },
|
||||
root: overlayStyle ?? {},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
export const Tooltip = ({ overlayStyle, ...props }: TooltipProps) => (
|
||||
<AntdTooltip
|
||||
styles={{
|
||||
body: { overflow: 'hidden', textOverflow: 'ellipsis' },
|
||||
root: overlayStyle ?? {},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
export type { TooltipProps, TooltipPlacement };
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -34,6 +34,6 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -36,6 +36,6 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -39,6 +39,6 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,6 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"dayjs": "^1.11.19",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/react": "*",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"ol": "^10.8.0",
|
||||
"polished": "*",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"acorn": "^8.16.0",
|
||||
"d3-array": "^3.2.4",
|
||||
"lodash": "^4.18.1",
|
||||
"zod": "^4.4.3"
|
||||
"zod": "^4.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
@@ -38,7 +38,7 @@
|
||||
"dayjs": "^1.11.19",
|
||||
"echarts": "*",
|
||||
"memoize-one": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
"handlebars": "^4.7.8",
|
||||
"lodash": "^4.18.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"react": "^18.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"lodash": "^4.18.1",
|
||||
"prop-types": "*",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.29.0",
|
||||
|
||||
@@ -29,15 +29,15 @@
|
||||
"@math.gl/web-mercator": "^4.1.0",
|
||||
"mapbox-gl": "^3.23.1",
|
||||
"maplibre-gl": "^5.24.0",
|
||||
"react-map-gl": "^8.1.0",
|
||||
"react-map-gl": "^8.1.1",
|
||||
"supercluster": "^8.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/react": "*",
|
||||
"match-sorter": "^8.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/d3-scale": "^4.0.9",
|
||||
"d3-cloud": "^1.2.8",
|
||||
"d3-cloud": "^1.2.9",
|
||||
"d3-scale": "^4.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -39,7 +39,7 @@
|
||||
"@superset-ui/core": "*",
|
||||
"@types/lodash": "*",
|
||||
"@types/react": "*",
|
||||
"react": "^18.3.0"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/d3-cloud": "^1.2.9"
|
||||
|
||||
@@ -67,8 +67,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"dayjs": "^1.11.19",
|
||||
"mapbox-gl": ">=1.0.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"mapbox-gl": {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { AriaAttributes, Ref } from 'react';
|
||||
import { AriaAttributes } from 'react';
|
||||
import 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
import jQuery from 'jquery';
|
||||
@@ -98,39 +98,31 @@ jest.mock('rehype-raw', () => () => jest.fn());
|
||||
// Tests should override this when needed
|
||||
jest.mock('@superset-ui/core/components/Icons/AsyncIcon', () => ({
|
||||
__esModule: true,
|
||||
// eslint-disable-next-line global-require
|
||||
default: require('react').forwardRef(
|
||||
(
|
||||
{
|
||||
fileName,
|
||||
role,
|
||||
'aria-label': ariaLabel,
|
||||
onClick,
|
||||
...rest
|
||||
}: {
|
||||
fileName: string;
|
||||
role?: string;
|
||||
'aria-label'?: AriaAttributes['aria-label'];
|
||||
onClick?: () => void;
|
||||
},
|
||||
ref: Ref<HTMLSpanElement>,
|
||||
) => {
|
||||
// Simple mock that provides the essential attributes for testing
|
||||
const label =
|
||||
ariaLabel || fileName?.replace(/_/g, '-').toLowerCase() || '';
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<span
|
||||
ref={ref}
|
||||
role={role || (onClick ? 'button' : 'img')}
|
||||
aria-label={label}
|
||||
data-test={label}
|
||||
onClick={onClick}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
},
|
||||
),
|
||||
default: ({
|
||||
fileName,
|
||||
role,
|
||||
'aria-label': ariaLabel,
|
||||
onClick,
|
||||
...rest
|
||||
}: {
|
||||
fileName: string;
|
||||
role?: string;
|
||||
'aria-label'?: AriaAttributes['aria-label'];
|
||||
onClick?: () => void;
|
||||
}) => {
|
||||
// Simple mock that provides the essential attributes for testing
|
||||
const label = ariaLabel || fileName?.replace(/_/g, '-').toLowerCase() || '';
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<span
|
||||
role={role || (onClick ? 'button' : 'img')}
|
||||
aria-label={label}
|
||||
data-test={label}
|
||||
onClick={onClick}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
},
|
||||
StyledIcon: ({
|
||||
component: Component,
|
||||
role,
|
||||
|
||||
@@ -37,7 +37,6 @@ import { BrowserRouter } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { TestBackend } from 'react-dnd-test-backend';
|
||||
import reducerIndex from 'spec/helpers/reducerIndex';
|
||||
import { QueryParamProvider } from 'use-query-params';
|
||||
import { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5';
|
||||
@@ -47,11 +46,7 @@ import userEvent from '@testing-library/user-event';
|
||||
|
||||
type Options = Omit<RenderOptions, 'queries'> & {
|
||||
useRedux?: boolean;
|
||||
// `true` -> HTML5Backend (default; matches browser).
|
||||
// `'test'` -> TestBackend, drive drags programmatically via
|
||||
// `react-dnd-test-backend` `getInstance()` — avoids the jsdom
|
||||
// HTML5-drag-event / preventDefault / zero-rect gaps.
|
||||
useDnd?: boolean | 'test';
|
||||
useDnd?: boolean;
|
||||
useQueryParams?: boolean;
|
||||
useRouter?: boolean;
|
||||
useTheme?: boolean;
|
||||
@@ -102,9 +97,8 @@ export function createWrapper(options?: Options) {
|
||||
}
|
||||
|
||||
if (useDnd) {
|
||||
const backend = useDnd === 'test' ? TestBackend : HTML5Backend;
|
||||
// @ts-expect-error react-dnd types not updated for React 18
|
||||
result = <DndProvider backend={backend}>{result}</DndProvider>;
|
||||
result = <DndProvider backend={HTML5Backend}>{result}</DndProvider>;
|
||||
}
|
||||
|
||||
if (useRedux || store) {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { act } from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { QueryState } from '@superset-ui/core';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
@@ -189,19 +189,15 @@ const SqlEditorLeftBar = ({ queryEditorId }: SqlEditorLeftBarProps) => {
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
>
|
||||
{/* Wrap in a span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>
|
||||
<DatabaseSelector
|
||||
key={`db-selector-${db ? db.id : 'no-db'}:${catalog ?? 'no-catalog'}:${
|
||||
schema ?? 'no-schema'
|
||||
}`}
|
||||
{...dbSelectorProps}
|
||||
emptyState={<EmptyState />}
|
||||
sqlLabMode
|
||||
onOpenModal={openSelectorModal}
|
||||
/>
|
||||
</span>
|
||||
<DatabaseSelector
|
||||
key={`db-selector-${db ? db.id : 'no-db'}:${catalog ?? 'no-catalog'}:${
|
||||
schema ?? 'no-schema'
|
||||
}`}
|
||||
{...dbSelectorProps}
|
||||
emptyState={<EmptyState />}
|
||||
sqlLabMode
|
||||
onOpenModal={openSelectorModal}
|
||||
/>
|
||||
</Popover>
|
||||
<StyledDivider />
|
||||
<TableExploreTree queryEditorId={activeQEId} />
|
||||
|
||||
@@ -98,10 +98,7 @@ class CopyToClip extends Component<CopyToClipboardProps> {
|
||||
trigger={['hover']}
|
||||
arrow={{ pointAtCenter: true }}
|
||||
>
|
||||
{/* Wrap in a span so antd Tooltip has a real DOM ref target;
|
||||
avoids findDOMNode fallback when copyNode is a function
|
||||
component without forwardRef. */}
|
||||
<span>{this.getDecoratedCopyNode()}</span>
|
||||
{this.getDecoratedCopyNode()}
|
||||
</Tooltip>
|
||||
) : (
|
||||
this.getDecoratedCopyNode()
|
||||
|
||||
@@ -17,20 +17,21 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { forwardRef, PropsWithoutRef, Ref, RefAttributes } from 'react';
|
||||
import { PropsWithoutRef, RefAttributes } from 'react';
|
||||
import { Link, LinkProps } from 'react-router-dom';
|
||||
import { isUrlExternal, parseUrl } from 'src/utils/urlUtils';
|
||||
|
||||
type GenericLinkProps<S> = PropsWithoutRef<LinkProps<S>> &
|
||||
RefAttributes<HTMLAnchorElement>;
|
||||
|
||||
const GenericLinkInner = <S,>(
|
||||
{ to, component, replace, innerRef, children, ...rest }: GenericLinkProps<S>,
|
||||
ref: Ref<HTMLAnchorElement>,
|
||||
) => {
|
||||
export const GenericLink = <S,>({
|
||||
to,
|
||||
component,
|
||||
replace,
|
||||
innerRef,
|
||||
children,
|
||||
...rest
|
||||
}: PropsWithoutRef<LinkProps<S>> & RefAttributes<HTMLAnchorElement>) => {
|
||||
if (typeof to === 'string' && isUrlExternal(to)) {
|
||||
return (
|
||||
<a ref={ref} data-test="external-link" href={parseUrl(to)} {...rest}>
|
||||
<a data-test="external-link" href={parseUrl(to)} {...rest}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
@@ -41,14 +42,10 @@ const GenericLinkInner = <S,>(
|
||||
to={to}
|
||||
component={component}
|
||||
replace={replace}
|
||||
innerRef={innerRef ?? ref}
|
||||
innerRef={innerRef}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const GenericLink = forwardRef(GenericLinkInner) as <S>(
|
||||
props: GenericLinkProps<S> & { ref?: Ref<HTMLAnchorElement> },
|
||||
) => ReturnType<typeof GenericLinkInner>;
|
||||
|
||||
@@ -193,57 +193,52 @@ export default function getControlItemsMap({
|
||||
t('Populate "Default value" to enable this control')
|
||||
}
|
||||
>
|
||||
{/* Wrap in span so antd Tooltip can attach a ref without
|
||||
relying on findDOMNode (deprecated in React 18+). */}
|
||||
<span>
|
||||
<StyledRowFormItem
|
||||
expanded={expanded}
|
||||
key={controlItem.name}
|
||||
name={['filters', filterId, 'controlValues', controlItem.name]}
|
||||
initialValue={initialValue}
|
||||
valuePropName="checked"
|
||||
colon={false}
|
||||
<StyledRowFormItem
|
||||
expanded={expanded}
|
||||
key={controlItem.name}
|
||||
name={['filters', filterId, 'controlValues', controlItem.name]}
|
||||
initialValue={initialValue}
|
||||
valuePropName="checked"
|
||||
colon={false}
|
||||
>
|
||||
<Checkbox
|
||||
disabled={controlItem.config.affectsDataMask && disabled}
|
||||
onChange={checked => {
|
||||
if (controlItem.config.requiredFirst) {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
requiredFirst: {
|
||||
...formFilter?.requiredFirst,
|
||||
[controlItem.name]: checked,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (controlItem.config.resetConfig) {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
defaultDataMask: null,
|
||||
});
|
||||
}
|
||||
formChanged();
|
||||
forceUpdate();
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
disabled={controlItem.config.affectsDataMask && disabled}
|
||||
onChange={checked => {
|
||||
if (controlItem.config.requiredFirst) {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
requiredFirst: {
|
||||
...formFilter?.requiredFirst,
|
||||
[controlItem.name]: checked,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (controlItem.config.resetConfig) {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
defaultDataMask: null,
|
||||
});
|
||||
}
|
||||
formChanged();
|
||||
forceUpdate();
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{typeof controlItem.config.label === 'function'
|
||||
? (controlItem.config.label as Function)()
|
||||
: controlItem.config.label}
|
||||
|
||||
{controlItem.config.description && (
|
||||
<InfoTooltip
|
||||
placement="top"
|
||||
tooltip={
|
||||
typeof controlItem.config.description === 'function'
|
||||
? (controlItem.config.description as Function)()
|
||||
: (controlItem.config
|
||||
.description as React.ReactNode)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Checkbox>
|
||||
</StyledRowFormItem>
|
||||
</span>
|
||||
<>
|
||||
{typeof controlItem.config.label === 'function'
|
||||
? (controlItem.config.label as Function)()
|
||||
: controlItem.config.label}
|
||||
|
||||
{controlItem.config.description && (
|
||||
<InfoTooltip
|
||||
placement="top"
|
||||
tooltip={
|
||||
typeof controlItem.config.description === 'function'
|
||||
? (controlItem.config.description as Function)()
|
||||
: (controlItem.config.description as React.ReactNode)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Checkbox>
|
||||
</StyledRowFormItem>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -188,9 +188,7 @@ function CollectionControl({
|
||||
// Two items can collide when keyAccessor returns falsy and the index
|
||||
// fallback is used — breaking dnd-kit reordering and React reconciliation.
|
||||
// Assign a stable nanoid per item ref when no key is available.
|
||||
const generatedIdsRef = useRef<WeakMap<CollectionItem, string>>(
|
||||
new WeakMap(),
|
||||
);
|
||||
const generatedIdsRef = useRef<WeakMap<CollectionItem, string>>(new WeakMap());
|
||||
const itemIds = useMemo(
|
||||
() =>
|
||||
value.map(item => {
|
||||
|
||||
@@ -54,9 +54,7 @@ const ColorBreakpointsPopoverTrigger = ({
|
||||
onOpenChange={setVisibility}
|
||||
destroyOnHidden
|
||||
>
|
||||
{/* Wrap in span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>{props.children}</span>
|
||||
{props.children}
|
||||
</ControlPopover>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -52,9 +52,7 @@ const ContourPopoverTrigger = ({
|
||||
onOpenChange={setVisibility}
|
||||
destroyOnHidden
|
||||
>
|
||||
{/* Wrap in span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>{props.children}</span>
|
||||
{props.children}
|
||||
</ControlPopover>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -369,21 +369,16 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||
overlayClassName="time-range-popover"
|
||||
>
|
||||
<Tooltip placement="top" title={tooltipTitle}>
|
||||
{/* Wrap in a span so the Popover gets a stable DOM ref target;
|
||||
DateLabel forwards its ref to an inner span used for measuring
|
||||
text truncation, which would otherwise become the popover's
|
||||
positioning anchor in React 18. */}
|
||||
<span data-test={DateFilterTestKey.PopoverOverlay}>
|
||||
<DateLabel
|
||||
name={name}
|
||||
aria-labelledby={`filter-name-${props.name}`}
|
||||
aria-describedby={`date-label-${props.name}`}
|
||||
label={actualTimeRange}
|
||||
isActive={show}
|
||||
isPlaceholder={actualTimeRange === NO_TIME_RANGE}
|
||||
ref={labelRef}
|
||||
/>
|
||||
</span>
|
||||
<DateLabel
|
||||
name={name}
|
||||
aria-labelledby={`filter-name-${props.name}`}
|
||||
aria-describedby={`date-label-${props.name}`}
|
||||
label={actualTimeRange}
|
||||
isActive={show}
|
||||
isPlaceholder={actualTimeRange === NO_TIME_RANGE}
|
||||
data-test={DateFilterTestKey.PopoverOverlay}
|
||||
ref={labelRef}
|
||||
/>
|
||||
</Tooltip>
|
||||
</ControlPopover>
|
||||
);
|
||||
|
||||
@@ -201,9 +201,7 @@ const ColumnSelectPopoverTriggerInner = ({
|
||||
title={popoverTitle}
|
||||
destroyOnHidden
|
||||
>
|
||||
{/* Wrap in span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>{children}</span>
|
||||
{children}
|
||||
</ControlPopover>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { act } from 'react';
|
||||
import { getInstance as getTestBackend } from 'react-dnd-test-backend';
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
@@ -213,9 +211,7 @@ test('can drop only selected metrics', () => {
|
||||
test('can drag and reorder items', async () => {
|
||||
const values = ['column_a', 'metric_a', 'column_b'];
|
||||
render(<DndColumnMetricSelect {...defaultProps} value={values} multi />, {
|
||||
// TestBackend bypasses the HTML5 drag-event pipeline (which jsdom
|
||||
// doesn't fully implement) and lets us drive drags via simulate* calls.
|
||||
useDnd: 'test',
|
||||
useDnd: true,
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
@@ -228,24 +224,10 @@ test('can drag and reorder items', async () => {
|
||||
expect(within(firstItem).getByText('column_a')).toBeVisible();
|
||||
expect(within(lastItem).getByText('Column B')).toBeVisible();
|
||||
|
||||
const sourceId = firstItem
|
||||
.querySelector('[data-drag-source-id]')!
|
||||
.getAttribute('data-drag-source-id')!;
|
||||
const targetId = lastItem
|
||||
.querySelector('[data-drop-target-id]')!
|
||||
.getAttribute('data-drop-target-id')!;
|
||||
const backend = getTestBackend()!;
|
||||
act(() => {
|
||||
backend.simulateBeginDrag([sourceId]);
|
||||
// Pass a clientOffset past the hover midpoint so OptionWrapper.hover /
|
||||
// OptionControls.hover fire the reorder instead of bailing on the
|
||||
// jsdom zero-rect.
|
||||
backend.simulateHover([targetId], {
|
||||
clientOffset: { x: 0, y: 100 },
|
||||
} as any);
|
||||
backend.simulateDrop();
|
||||
backend.simulateEndDrag();
|
||||
});
|
||||
fireEvent.dragStart(firstItem);
|
||||
fireEvent.dragEnter(lastItem);
|
||||
fireEvent.dragOver(lastItem);
|
||||
fireEvent.drop(lastItem);
|
||||
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { act } from 'react';
|
||||
import { getInstance as getTestBackend } from 'react-dnd-test-backend';
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
@@ -316,9 +314,7 @@ test('update adhoc metric name when column label in dataset changes', () => {
|
||||
test('can drag metrics', async () => {
|
||||
const metricValues = ['metric_a', 'metric_b', adhocMetricB];
|
||||
render(<DndMetricSelect {...defaultProps} value={metricValues} multi />, {
|
||||
// TestBackend bypasses the HTML5 drag-event pipeline (which jsdom
|
||||
// doesn't fully implement) and lets us drive drags via simulate* calls.
|
||||
useDnd: 'test',
|
||||
useDnd: true,
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
@@ -336,23 +332,10 @@ test('can drag metrics', async () => {
|
||||
fireEvent.mouseOver(within(firstMetric).getByText('metric_a'));
|
||||
expect(await screen.findByText('Metric name')).toBeInTheDocument();
|
||||
|
||||
const sourceId = firstMetric
|
||||
.querySelector('[data-drag-source-id]')!
|
||||
.getAttribute('data-drag-source-id')!;
|
||||
const targetId = lastMetric
|
||||
.querySelector('[data-drop-target-id]')!
|
||||
.getAttribute('data-drop-target-id')!;
|
||||
const backend = getTestBackend()!;
|
||||
act(() => {
|
||||
backend.simulateBeginDrag([sourceId]);
|
||||
// Pass a clientOffset past the hover midpoint so OptionControls.hover
|
||||
// fires onMoveLabel instead of bailing on the jsdom zero-rect.
|
||||
backend.simulateHover([targetId], {
|
||||
clientOffset: { x: 0, y: 100 },
|
||||
} as any);
|
||||
backend.simulateDrop();
|
||||
backend.simulateEndDrag();
|
||||
});
|
||||
fireEvent.dragStart(firstMetric);
|
||||
fireEvent.dragEnter(lastMetric);
|
||||
fireEvent.dragOver(lastMetric);
|
||||
fireEvent.drop(lastMetric);
|
||||
|
||||
expect(within(firstMetric).getByText('SUM(Column B)')).toBeVisible();
|
||||
expect(within(lastMetric).getByText('metric_a')).toBeVisible();
|
||||
|
||||
@@ -67,24 +67,18 @@ export default function OptionWrapper(
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const labelRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [{ isDragging, dragSourceId }, drag] = useDrag({
|
||||
const [{ isDragging }, drag] = useDrag({
|
||||
item: {
|
||||
type,
|
||||
dragIndex: index,
|
||||
},
|
||||
collect: (monitor: DragSourceMonitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
// Exposed via `data-drag-source-id` so tests using react-dnd-test-backend
|
||||
// can drive drags programmatically without DOM-event simulation.
|
||||
dragSourceId: monitor.getHandlerId(),
|
||||
}),
|
||||
});
|
||||
|
||||
const [{ dropTargetId }, drop] = useDrop({
|
||||
const [, drop] = useDrop({
|
||||
accept: type,
|
||||
collect: (monitor: DropTargetMonitor) => ({
|
||||
dropTargetId: monitor.getHandlerId(),
|
||||
}),
|
||||
|
||||
hover: (item: OptionItemInterface, monitor: DropTargetMonitor) => {
|
||||
if (!ref.current) {
|
||||
@@ -188,12 +182,7 @@ export default function OptionWrapper(
|
||||
drag(drop(ref));
|
||||
|
||||
return (
|
||||
<DragContainer
|
||||
ref={ref}
|
||||
data-drag-source-id={dragSourceId ?? undefined}
|
||||
data-drop-target-id={dropTargetId ?? undefined}
|
||||
{...rest}
|
||||
>
|
||||
<DragContainer ref={ref} {...rest}>
|
||||
<Option
|
||||
index={index}
|
||||
clickClose={clickClose}
|
||||
|
||||
@@ -112,9 +112,7 @@ class AdhocFilterPopoverTrigger extends PureComponent<
|
||||
onOpenChange={togglePopover}
|
||||
destroyOnHidden
|
||||
>
|
||||
{/* Wrap in span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>{this.props.children}</span>
|
||||
{this.props.children}
|
||||
</ControlPopover>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -274,9 +274,7 @@ class AdhocMetricPopoverTrigger extends PureComponent<
|
||||
title={popoverTitle}
|
||||
destroyOnHidden
|
||||
>
|
||||
{/* Wrap in span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>{this.props.children}</span>
|
||||
{this.props.children}
|
||||
</ControlPopover>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -32,6 +32,13 @@ import MetricDefinitionValue from './MetricDefinitionValue';
|
||||
import AdhocMetric from './AdhocMetric';
|
||||
import AdhocMetricPopoverTrigger from './AdhocMetricPopoverTrigger';
|
||||
|
||||
const defaultProps = {
|
||||
onChange: () => {},
|
||||
clearable: true,
|
||||
savedMetrics: [],
|
||||
columns: [],
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function getOptionsForSavedMetrics(
|
||||
savedMetrics: any,
|
||||
@@ -115,11 +122,11 @@ export interface MetricsControlProps {
|
||||
}
|
||||
|
||||
const MetricsControl = ({
|
||||
onChange = () => {},
|
||||
onChange,
|
||||
multi,
|
||||
value: propsValue,
|
||||
columns = [],
|
||||
savedMetrics = [],
|
||||
columns,
|
||||
savedMetrics,
|
||||
datasource,
|
||||
...props
|
||||
}: MetricsControlProps) => {
|
||||
@@ -344,4 +351,6 @@ const MetricsControl = ({
|
||||
);
|
||||
};
|
||||
|
||||
MetricsControl.defaultProps = defaultProps;
|
||||
|
||||
export default MetricsControl;
|
||||
|
||||
@@ -275,11 +275,8 @@ export const OptionControlLabel = ({
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const labelRef = useRef<HTMLDivElement>(null);
|
||||
const hasMetricName = savedMetric?.metric_name;
|
||||
const [{ dropTargetId }, drop] = useDrop({
|
||||
const [, drop] = useDrop({
|
||||
accept: type,
|
||||
collect: (monitor: DropTargetMonitor) => ({
|
||||
dropTargetId: monitor.getHandlerId(),
|
||||
}),
|
||||
drop() {
|
||||
if (!multi) {
|
||||
return;
|
||||
@@ -331,7 +328,7 @@ export const OptionControlLabel = ({
|
||||
item.dragIndex = hoverIndex;
|
||||
},
|
||||
});
|
||||
const [{ isDragging, dragSourceId }, drag] = useDrag({
|
||||
const [{ isDragging }, drag] = useDrag({
|
||||
item: {
|
||||
type,
|
||||
dragIndex: index,
|
||||
@@ -339,9 +336,6 @@ export const OptionControlLabel = ({
|
||||
},
|
||||
collect: monitor => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
// Exposed via `data-drag-source-id` so tests using react-dnd-test-backend
|
||||
// can drive drags programmatically without DOM-event simulation.
|
||||
dragSourceId: monitor.getHandlerId(),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -430,13 +424,5 @@ export const OptionControlLabel = ({
|
||||
);
|
||||
|
||||
drag(drop(ref));
|
||||
return (
|
||||
<DragContainer
|
||||
ref={ref}
|
||||
data-drag-source-id={dragSourceId ?? undefined}
|
||||
data-drop-target-id={dropTargetId ?? undefined}
|
||||
>
|
||||
{getOptionControlContent()}
|
||||
</DragContainer>
|
||||
);
|
||||
return <DragContainer ref={ref}>{getOptionControlContent()}</DragContainer>;
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from 'spec/helpers/testing-library';
|
||||
import { VizType } from '@superset-ui/core';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { act } from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import handleResourceExport from 'src/utils/export';
|
||||
import { LocalStorageKeys } from 'src/utils/localStorageHelpers';
|
||||
import ChartTable from './ChartTable';
|
||||
|
||||
@@ -111,15 +111,12 @@ const createController = (
|
||||
...options,
|
||||
});
|
||||
|
||||
// Shared console spies — re-installed in beforeEach so each test starts
|
||||
// with a fresh call count and a clean implementation.
|
||||
let consoleSpy: jest.SpyInstance;
|
||||
let consoleErrorSpy: jest.SpyInstance;
|
||||
// Shared console spies
|
||||
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
// Setup DOM environment
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
|
||||
30
superset-websocket/package-lock.json
generated
30
superset-websocket/package-lock.json
generated
@@ -23,7 +23,7 @@
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/node": "^25.7.0",
|
||||
"@types/node": "^25.8.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.3",
|
||||
"@typescript-eslint/parser": "^8.59.3",
|
||||
@@ -1798,13 +1798,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.7.0.tgz",
|
||||
"integrity": "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==",
|
||||
"version": "25.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.8.0.tgz",
|
||||
"integrity": "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.21.0"
|
||||
"undici-types": ">=7.24.0 <7.24.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/stack-utils": {
|
||||
@@ -6237,9 +6237,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.21.0.tgz",
|
||||
"integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==",
|
||||
"version": "7.24.6",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
||||
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -7894,12 +7894,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "25.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.7.0.tgz",
|
||||
"integrity": "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==",
|
||||
"version": "25.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.8.0.tgz",
|
||||
"integrity": "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"undici-types": "~7.21.0"
|
||||
"undici-types": ">=7.24.0 <7.24.7"
|
||||
}
|
||||
},
|
||||
"@types/stack-utils": {
|
||||
@@ -11063,9 +11063,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.21.0.tgz",
|
||||
"integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==",
|
||||
"version": "7.24.6",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
||||
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
|
||||
"dev": true
|
||||
},
|
||||
"unix-dgram": {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/node": "^25.7.0",
|
||||
"@types/node": "^25.8.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.3",
|
||||
"@typescript-eslint/parser": "^8.59.3",
|
||||
|
||||
@@ -454,7 +454,7 @@ def print_report(reports: list[MetadataReport], verbose: bool = False) -> None:
|
||||
print("=" * 60)
|
||||
|
||||
all_fields = {**REQUIRED_FIELDS, **RECOMMENDED_FIELDS, **OPTIONAL_FIELDS}
|
||||
field_counts: dict[str, int] = dict.fromkeys(all_fields, 0)
|
||||
field_counts: dict[str, int] = {f: 0 for f in all_fields}
|
||||
|
||||
for r in reports:
|
||||
for field in r.present_fields:
|
||||
|
||||
@@ -353,10 +353,10 @@ class OAuth2RedirectError(SupersetErrorException):
|
||||
|
||||
See the `OAuth2RedirectMessage.tsx` component for more details of how this
|
||||
information is handled.
|
||||
|
||||
TODO (betodealmeida): change status to 403.
|
||||
"""
|
||||
|
||||
status = 403
|
||||
|
||||
def __init__(self, url: str, tab_id: str, redirect_uri: str):
|
||||
super().__init__(
|
||||
SupersetError(
|
||||
|
||||
@@ -26,7 +26,6 @@ import logging
|
||||
import math
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from superset.commands.chart.data.get_data_command import ChartDataCommand
|
||||
from superset.mcp_service.chart.schemas import (
|
||||
ASCIIPreview,
|
||||
ChartError,
|
||||
@@ -78,6 +77,7 @@ def generate_preview_from_form_data(
|
||||
"""
|
||||
try:
|
||||
# Execute query to get data
|
||||
from superset.commands.chart.data.get_data_command import ChartDataCommand
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.extensions import db
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ ALLOWED_TAGS = {
|
||||
"ul",
|
||||
}.union(TABLE_TAGS)
|
||||
|
||||
ALLOWED_TABLE_ATTRIBUTES = dict.fromkeys(TABLE_TAGS, TABLE_ATTRIBUTES)
|
||||
ALLOWED_TABLE_ATTRIBUTES = {tag: TABLE_ATTRIBUTES for tag in TABLE_TAGS}
|
||||
ALLOWED_ATTRIBUTES = {
|
||||
"a": {"href", "title"},
|
||||
"abbr": {"title"},
|
||||
|
||||
@@ -94,7 +94,7 @@ class SlackMixin:
|
||||
# need to truncate the data
|
||||
for i in range(len(df) - 1):
|
||||
truncated_df = df[: i + 1].fillna("")
|
||||
truncated_row = pd.Series(dict.fromkeys(df.columns, "..."))
|
||||
truncated_row = pd.Series({k: "..." for k in df.columns})
|
||||
truncated_df = pd.concat(
|
||||
[truncated_df, truncated_row.to_frame().T], ignore_index=True
|
||||
)
|
||||
@@ -104,7 +104,7 @@ class SlackMixin:
|
||||
if len(message) > MAXIMUM_MESSAGE_SIZE:
|
||||
# Decrement i and build a message that is under the limit
|
||||
truncated_df = df[:i].fillna("")
|
||||
truncated_row = pd.Series(dict.fromkeys(df.columns, "..."))
|
||||
truncated_row = pd.Series({k: "..." for k in df.columns})
|
||||
truncated_df = pd.concat(
|
||||
[truncated_df, truncated_row.to_frame().T], ignore_index=True
|
||||
)
|
||||
|
||||
@@ -10403,7 +10403,7 @@ msgstr "Běží"
|
||||
|
||||
#, fuzzy, python-format
|
||||
msgid "Running block %(block_num)s out of %(block_count)s"
|
||||
msgstr "Spouští se příkaz %(statement_num)s z %(statement_count)s"
|
||||
msgstr "Spouští se příkaz %(block_num)s z %(block_count)s"
|
||||
|
||||
msgid "SAT"
|
||||
msgstr "SO"
|
||||
|
||||
@@ -28,6 +28,11 @@ logger = logging.getLogger(__name__)
|
||||
# Time to wait after scrolling for content to settle and load (in milliseconds)
|
||||
SCROLL_SETTLE_TIMEOUT_MS = 1000
|
||||
|
||||
try:
|
||||
from playwright.sync_api import TimeoutError as PlaywrightTimeout
|
||||
except ImportError:
|
||||
PlaywrightTimeout = Exception
|
||||
|
||||
if TYPE_CHECKING:
|
||||
try:
|
||||
from playwright.sync_api import Page
|
||||
@@ -80,7 +85,10 @@ def combine_screenshot_tiles(screenshot_tiles: list[bytes]) -> bytes:
|
||||
|
||||
|
||||
def take_tiled_screenshot(
|
||||
page: "Page", element_name: str, tile_height: int
|
||||
page: "Page",
|
||||
element_name: str,
|
||||
tile_height: int,
|
||||
load_wait: int = 60,
|
||||
) -> bytes | None:
|
||||
"""
|
||||
Take a tiled screenshot of a large dashboard by scrolling and capturing sections.
|
||||
@@ -89,6 +97,7 @@ def take_tiled_screenshot(
|
||||
page: Playwright page object
|
||||
element_name: CSS class name of the element to screenshot
|
||||
tile_height: Height of each tile in pixels
|
||||
load_wait: Seconds to wait for charts to load per tile (default 60)
|
||||
|
||||
Returns:
|
||||
Combined screenshot bytes or None if failed
|
||||
@@ -139,6 +148,31 @@ def take_tiled_screenshot(
|
||||
)
|
||||
# Wait for scroll to settle and content to load
|
||||
page.wait_for_timeout(SCROLL_SETTLE_TIMEOUT_MS)
|
||||
# Wait for any loading spinners visible in the current viewport to clear.
|
||||
# Only check viewport-visible spinners to avoid blocking on
|
||||
# virtualization placeholders rendered for off-screen charts.
|
||||
try:
|
||||
page.wait_for_function(
|
||||
"""() => {
|
||||
const els = document.querySelectorAll('.loading');
|
||||
for (const el of els) {
|
||||
const r = el.getBoundingClientRect();
|
||||
if (r.top < window.innerHeight && r.bottom > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}""",
|
||||
timeout=load_wait * 1000,
|
||||
)
|
||||
except PlaywrightTimeout:
|
||||
logger.warning(
|
||||
"Timed out waiting for visible spinners to clear on tile %s/%s "
|
||||
"(load_wait=%ss)",
|
||||
i + 1,
|
||||
num_tiles,
|
||||
load_wait,
|
||||
)
|
||||
|
||||
# Calculate what portion of the element we want to capture for this tile
|
||||
tile_start_in_element = i * tile_height
|
||||
|
||||
@@ -295,21 +295,6 @@ class WebDriverPlaywright(WebDriverProxy):
|
||||
url,
|
||||
)
|
||||
raise
|
||||
try:
|
||||
# charts took too long to load
|
||||
logger.debug(
|
||||
"Wait for loading element of charts to be gone at url: %s", url
|
||||
)
|
||||
page.wait_for_function(
|
||||
"() => document.querySelectorAll('.loading').length === 0",
|
||||
timeout=self._screenshot_load_wait * 1000,
|
||||
)
|
||||
except PlaywrightTimeout:
|
||||
logger.warning(
|
||||
"Timed out waiting for charts to load at url %s", url
|
||||
)
|
||||
raise
|
||||
|
||||
selenium_animation_wait = app.config[
|
||||
"SCREENSHOT_SELENIUM_ANIMATION_WAIT"
|
||||
]
|
||||
@@ -368,7 +353,12 @@ class WebDriverPlaywright(WebDriverProxy):
|
||||
page.set_viewport_size(
|
||||
{"height": tile_height, "width": viewport_width}
|
||||
)
|
||||
img = take_tiled_screenshot(page, element_name, tile_height)
|
||||
img = take_tiled_screenshot(
|
||||
page,
|
||||
element_name,
|
||||
tile_height,
|
||||
load_wait=self._screenshot_load_wait,
|
||||
)
|
||||
if img is None:
|
||||
logger.warning(
|
||||
(
|
||||
@@ -380,10 +370,50 @@ class WebDriverPlaywright(WebDriverProxy):
|
||||
page, element, element_name
|
||||
)
|
||||
else:
|
||||
# Standard screenshot captures the full element including
|
||||
# below-the-fold content, so wait for all spinners globally.
|
||||
try:
|
||||
logger.debug(
|
||||
"Wait for loading element of charts to be gone"
|
||||
" at url: %s",
|
||||
url,
|
||||
)
|
||||
page.wait_for_function(
|
||||
"() => document.querySelectorAll("
|
||||
"'.loading').length === 0",
|
||||
timeout=self._screenshot_load_wait * 1000,
|
||||
)
|
||||
except PlaywrightTimeout:
|
||||
logger.warning(
|
||||
"Timed out waiting for charts to load at url %s "
|
||||
"(SCREENSHOT_LOAD_WAIT=%ss)",
|
||||
url,
|
||||
self._screenshot_load_wait,
|
||||
)
|
||||
raise
|
||||
img = WebDriverPlaywright._get_screenshot(
|
||||
page, element, element_name
|
||||
)
|
||||
else:
|
||||
# Standard screenshot captures the full element including
|
||||
# below-the-fold content, so wait for all spinners globally.
|
||||
try:
|
||||
logger.debug(
|
||||
"Wait for loading element of charts to be gone at url: %s",
|
||||
url,
|
||||
)
|
||||
page.wait_for_function(
|
||||
"() => document.querySelectorAll('.loading').length === 0",
|
||||
timeout=self._screenshot_load_wait * 1000,
|
||||
)
|
||||
except PlaywrightTimeout:
|
||||
logger.warning(
|
||||
"Timed out waiting for charts to load at url %s "
|
||||
"(SCREENSHOT_LOAD_WAIT=%ss)",
|
||||
url,
|
||||
self._screenshot_load_wait,
|
||||
)
|
||||
raise
|
||||
img = WebDriverPlaywright._get_screenshot(
|
||||
page, element, element_name
|
||||
)
|
||||
|
||||
@@ -2250,7 +2250,7 @@ def test_catalogs_with_oauth2(
|
||||
security_manager.get_catalogs_accessible_by_user.return_value = {"db2"}
|
||||
|
||||
response = client.get("/api/v1/database/1/catalogs/")
|
||||
assert response.status_code == 500
|
||||
assert response.status_code == 403
|
||||
assert response.json == {
|
||||
"errors": [
|
||||
{
|
||||
@@ -2351,7 +2351,7 @@ def test_schemas_with_oauth2(
|
||||
security_manager.get_schemas_accessible_by_user.return_value = {"schema2"}
|
||||
|
||||
response = client.get("/api/v1/database/1/schemas/")
|
||||
assert response.status_code == 500
|
||||
assert response.status_code == 403
|
||||
assert response.json == {
|
||||
"errors": [
|
||||
{
|
||||
|
||||
@@ -19,6 +19,47 @@
|
||||
Tests for preview_utils query context column building.
|
||||
"""
|
||||
|
||||
import ast
|
||||
import inspect
|
||||
from pathlib import Path
|
||||
|
||||
from superset.mcp_service.chart import preview_utils
|
||||
|
||||
|
||||
def _imports_chart_data_command(node: ast.Import | ast.ImportFrom) -> bool:
|
||||
blocked_module = "superset.commands.chart.data.get_data_command"
|
||||
|
||||
if isinstance(node, ast.Import):
|
||||
return any(
|
||||
alias.name == blocked_module or alias.name.startswith(f"{blocked_module}.")
|
||||
for alias in node.names
|
||||
)
|
||||
|
||||
module = node.module or ""
|
||||
return (
|
||||
module == blocked_module
|
||||
or module.startswith(f"{blocked_module}.")
|
||||
or (
|
||||
module == "superset.commands.chart.data"
|
||||
and any(alias.name == "get_data_command" for alias in node.names)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_preview_utils_does_not_top_level_import_chart_data_command():
|
||||
"""preview_utils constants should stay safe to import before app setup."""
|
||||
source_path = inspect.getsourcefile(preview_utils) or preview_utils.__file__
|
||||
source = Path(source_path).read_text(encoding="utf-8")
|
||||
tree = ast.parse(source)
|
||||
top_level_imports = [
|
||||
node for node in tree.body if isinstance(node, (ast.Import, ast.ImportFrom))
|
||||
]
|
||||
|
||||
assert preview_utils.SUPPORTED_FORM_DATA_PREVIEW_FORMATS == frozenset(
|
||||
{"ascii", "table", "vega_lite"}
|
||||
)
|
||||
assert not any(_imports_chart_data_command(node) for node in top_level_imports)
|
||||
|
||||
|
||||
class TestPreviewUtilsColumnBuilding:
|
||||
"""Tests for x_axis + groupby column building in generate_preview_from_form_data.
|
||||
|
||||
@@ -1007,12 +1007,12 @@ class TestChartDataCommandValidation:
|
||||
mock_dataset = MagicMock()
|
||||
mock_dataset.id = 10
|
||||
|
||||
# ChartDataCommand is module-level import in preview_utils;
|
||||
# db and QueryContextFactory are local imports inside the function.
|
||||
# ChartDataCommand, db, and QueryContextFactory are local imports inside
|
||||
# the function so preview_utils stays safe to import before app setup.
|
||||
with (
|
||||
patch("superset.extensions.db") as mock_db,
|
||||
patch(
|
||||
"superset.mcp_service.chart.preview_utils.ChartDataCommand",
|
||||
"superset.commands.chart.data.get_data_command.ChartDataCommand",
|
||||
return_value=mock_command,
|
||||
),
|
||||
patch(
|
||||
@@ -1061,7 +1061,7 @@ class TestChartDataCommandValidation:
|
||||
with (
|
||||
patch("superset.extensions.db") as mock_db,
|
||||
patch(
|
||||
"superset.mcp_service.chart.preview_utils.ChartDataCommand",
|
||||
"superset.commands.chart.data.get_data_command.ChartDataCommand",
|
||||
return_value=mock_command,
|
||||
),
|
||||
patch(
|
||||
|
||||
@@ -320,3 +320,54 @@ class TestTakeTiledScreenshot:
|
||||
# Each wait should use the scroll settle timeout constant
|
||||
for call in mock_page.wait_for_timeout.call_args_list:
|
||||
assert call[0][0] == SCROLL_SETTLE_TIMEOUT_MS
|
||||
|
||||
def test_per_tile_spinner_wait_uses_viewport_check(self, mock_page):
|
||||
"""wait_for_function polls viewport-visible spinners after each scroll."""
|
||||
with patch("superset.utils.screenshot_utils.combine_screenshot_tiles"):
|
||||
take_tiled_screenshot(
|
||||
mock_page, "dashboard", tile_height=2000, load_wait=30
|
||||
)
|
||||
|
||||
# 3 tiles → 3 wait_for_function calls, one per tile
|
||||
assert mock_page.wait_for_function.call_count == 3
|
||||
|
||||
# Each call uses viewport-scoped JS and the load_wait timeout
|
||||
for call in mock_page.wait_for_function.call_args_list:
|
||||
js = call[0][0]
|
||||
assert "getBoundingClientRect" in js
|
||||
assert "window.innerHeight" in js
|
||||
assert call[1]["timeout"] == 30 * 1000
|
||||
|
||||
def test_per_tile_spinner_timeout_logs_warning_and_continues(self, mock_page):
|
||||
"""A per-tile spinner timeout logs a warning but still takes the screenshot."""
|
||||
from superset.utils.screenshot_utils import PlaywrightTimeout
|
||||
|
||||
timeout = PlaywrightTimeout()
|
||||
mock_page.wait_for_function.side_effect = timeout
|
||||
|
||||
with patch("superset.utils.screenshot_utils.logger") as mock_logger:
|
||||
with patch("superset.utils.screenshot_utils.combine_screenshot_tiles"):
|
||||
result = take_tiled_screenshot(
|
||||
mock_page, "dashboard", tile_height=2000, load_wait=30
|
||||
)
|
||||
|
||||
# Screenshot should still proceed (non-fatal)
|
||||
assert result is not None
|
||||
# Warning logged for each tile that timed out
|
||||
assert mock_logger.warning.call_count == 3
|
||||
mock_logger.warning.assert_any_call(
|
||||
"Timed out waiting for visible spinners to clear on tile %s/%s "
|
||||
"(load_wait=%ss)",
|
||||
1,
|
||||
3,
|
||||
30,
|
||||
)
|
||||
|
||||
def test_load_wait_default_is_sixty_seconds(self):
|
||||
"""load_wait defaults to 60 to match SCREENSHOT_LOAD_WAIT config default."""
|
||||
import inspect
|
||||
|
||||
from superset.utils.screenshot_utils import take_tiled_screenshot
|
||||
|
||||
sig = inspect.signature(take_tiled_screenshot)
|
||||
assert sig.parameters["load_wait"].default == 60
|
||||
|
||||
@@ -744,8 +744,9 @@ class TestWebDriverPlaywrightErrorHandling:
|
||||
|
||||
assert exc_info.value is timeout
|
||||
mock_logger.warning.assert_any_call(
|
||||
"Timed out waiting for charts to load at url %s",
|
||||
"Timed out waiting for charts to load at url %s (SCREENSHOT_LOAD_WAIT=%ss)",
|
||||
"http://example.com",
|
||||
60,
|
||||
)
|
||||
|
||||
@patch("superset.utils.webdriver.PLAYWRIGHT_AVAILABLE", True)
|
||||
|
||||
Reference in New Issue
Block a user