mirror of
https://github.com/apache/superset.git
synced 2026-06-19 22:49:18 +00:00
Compare commits
31 Commits
theme_uuid
...
refactor_q
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
398842a4d8 | ||
|
|
aff7f54b1a | ||
|
|
288da4a050 | ||
|
|
2d8ae42d42 | ||
|
|
990174bb1c | ||
|
|
6f8a79693d | ||
|
|
b8a71e4754 | ||
|
|
13e7ba18ed | ||
|
|
c5887630ab | ||
|
|
c11efecdad | ||
|
|
3dc97b11f8 | ||
|
|
b81487e177 | ||
|
|
72e33ba811 | ||
|
|
b0715bd8bb | ||
|
|
0348b6c313 | ||
|
|
453b3da9f6 | ||
|
|
7c6c0c0451 | ||
|
|
bf43704200 | ||
|
|
fef0676954 | ||
|
|
7485af5e6c | ||
|
|
825b9e784a | ||
|
|
7191ae55c8 | ||
|
|
17725ebc83 | ||
|
|
1a7a381bd5 | ||
|
|
daf207e5c2 | ||
|
|
72294c569f | ||
|
|
792dd08d38 | ||
|
|
1e40e7d02b | ||
|
|
7e98c75f01 | ||
|
|
b18de05ea4 | ||
|
|
9300652277 |
2
.github/workflows/welcome-new-users.yml
vendored
2
.github/workflows/welcome-new-users.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Welcome Message
|
||||
uses: actions/first-interaction@v1
|
||||
uses: actions/first-interaction@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
repo-token: ${{ github.token }}
|
||||
|
||||
@@ -28,6 +28,9 @@ const globals = require('globals');
|
||||
const { defineConfig, globalIgnores } = require('eslint/config');
|
||||
|
||||
module.exports = defineConfig([
|
||||
{
|
||||
files: ['**/*.{js,jsx,ts,tsx}'],
|
||||
},
|
||||
globalIgnores(['build/**/*', '.docusaurus/**/*', 'node_modules/**/*']),
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
@@ -36,7 +39,7 @@ module.exports = defineConfig([
|
||||
files: ['eslint.config.js'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
@@ -68,5 +71,5 @@ module.exports = defineConfig([
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
}
|
||||
])
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids",
|
||||
"typecheck": "tsc",
|
||||
"eslint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
||||
"eslint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
@@ -26,33 +26,33 @@
|
||||
"@emotion/styled": "^10.0.27",
|
||||
"@saucelabs/theme-github-codeblock": "^0.3.0",
|
||||
"@superset-ui/style": "^0.14.23",
|
||||
"antd": "^5.26.3",
|
||||
"antd": "^5.26.7",
|
||||
"docusaurus-plugin-less": "^2.0.2",
|
||||
"less": "^4.3.0",
|
||||
"less": "^4.4.0",
|
||||
"less-loader": "^12.3.0",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-github-btn": "^1.4.0",
|
||||
"react-svg-pan-zoom": "^3.13.1",
|
||||
"swagger-ui-react": "^5.26.0"
|
||||
"swagger-ui-react": "^5.27.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.8.1",
|
||||
"@docusaurus/tsconfig": "^3.8.1",
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@eslint/js": "^9.32.0",
|
||||
"@types/react": "^19.1.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
||||
"@typescript-eslint/parser": "^8.37.0",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^16.3.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.37.0",
|
||||
"webpack": "^5.99.9"
|
||||
"webpack": "^5.101.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
133
docs/yarn.lock
133
docs/yarn.lock
@@ -2205,11 +2205,16 @@
|
||||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@eslint/js@9.31.0", "@eslint/js@^9.31.0":
|
||||
"@eslint/js@9.31.0":
|
||||
version "9.31.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.31.0.tgz#adb1f39953d8c475c4384b67b67541b0d7206ed8"
|
||||
integrity sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==
|
||||
|
||||
"@eslint/js@^9.32.0":
|
||||
version "9.32.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.32.0.tgz#a02916f58bd587ea276876cb051b579a3d75d091"
|
||||
integrity sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==
|
||||
|
||||
"@eslint/object-schema@^2.1.6":
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f"
|
||||
@@ -2512,10 +2517,10 @@
|
||||
classnames "^2.3.2"
|
||||
rc-util "^5.24.4"
|
||||
|
||||
"@rc-component/trigger@^2.0.0", "@rc-component/trigger@^2.1.1", "@rc-component/trigger@^2.2.7":
|
||||
version "2.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@rc-component/trigger/-/trigger-2.2.7.tgz#a2b97ecbb93280a3c424e51fa415b371b355d76a"
|
||||
integrity sha512-Qggj4Z0AA2i5dJhzlfFSmg1Qrziu8dsdHOihROL5Kl18seO2Eh/ZaTYt2c8a/CyGaTChnFry7BEYew1+/fhSbA==
|
||||
"@rc-component/trigger@^2.0.0", "@rc-component/trigger@^2.1.1", "@rc-component/trigger@^2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@rc-component/trigger/-/trigger-2.3.0.tgz#9499ada078daca9dd99d01f0f0743ee1ab9e398b"
|
||||
integrity sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.2"
|
||||
"@rc-component/portal" "^1.1.0"
|
||||
@@ -3422,10 +3427,10 @@
|
||||
dependencies:
|
||||
"@types/estree" "*"
|
||||
|
||||
"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6":
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8"
|
||||
integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==
|
||||
"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6", "@types/estree@^1.0.8":
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
|
||||
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
||||
|
||||
"@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0":
|
||||
version "5.0.6"
|
||||
@@ -3966,6 +3971,11 @@ accepts@~1.3.4, accepts@~1.3.8:
|
||||
mime-types "~2.1.34"
|
||||
negotiator "0.6.3"
|
||||
|
||||
acorn-import-phases@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz#16eb850ba99a056cb7cbfe872ffb8972e18c8bd7"
|
||||
integrity sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==
|
||||
|
||||
acorn-jsx@^5.0.0, acorn-jsx@^5.3.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||
@@ -3978,12 +3988,7 @@ acorn-walk@^8.0.0:
|
||||
dependencies:
|
||||
acorn "^8.11.0"
|
||||
|
||||
acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.8.2:
|
||||
version "8.14.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb"
|
||||
integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==
|
||||
|
||||
acorn@^8.15.0:
|
||||
acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0, acorn@^8.8.2:
|
||||
version "8.15.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
|
||||
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
|
||||
@@ -4107,10 +4112,10 @@ ansi-styles@^6.1.0:
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
|
||||
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
|
||||
|
||||
antd@^5.26.3:
|
||||
version "5.26.3"
|
||||
resolved "https://registry.yarnpkg.com/antd/-/antd-5.26.3.tgz#cbbb7e1b48a972dc7b6ee8b6948f51cc91c263f8"
|
||||
integrity sha512-M/s9Q39h/+G7AWnS6fbNxmAI9waTH4ti022GVEXBLq2j810V1wJ3UOQps13nEilzDNcyxnFN/EIbqIgS7wSYaA==
|
||||
antd@^5.26.7:
|
||||
version "5.26.7"
|
||||
resolved "https://registry.yarnpkg.com/antd/-/antd-5.26.7.tgz#e2f7e37330b27eec0de7a7789767975373f61602"
|
||||
integrity sha512-iCyXN6+i2CUVEOSzzJKfbKeg115qoJhGvSkCh5uzAf9hANwHUOJQhsMn+KtN+Lx/2NQ6wfM7nGZ+7NPNO5Pn1w==
|
||||
dependencies:
|
||||
"@ant-design/colors" "^7.2.1"
|
||||
"@ant-design/cssinjs" "^1.23.0"
|
||||
@@ -4123,7 +4128,7 @@ antd@^5.26.3:
|
||||
"@rc-component/mutate-observer" "^1.1.0"
|
||||
"@rc-component/qrcode" "~1.0.0"
|
||||
"@rc-component/tour" "~1.15.1"
|
||||
"@rc-component/trigger" "^2.2.7"
|
||||
"@rc-component/trigger" "^2.3.0"
|
||||
classnames "^2.5.1"
|
||||
copy-to-clipboard "^3.3.3"
|
||||
dayjs "^1.11.11"
|
||||
@@ -4153,7 +4158,7 @@ antd@^5.26.3:
|
||||
rc-switch "~4.1.0"
|
||||
rc-table "~7.51.1"
|
||||
rc-tabs "~15.6.1"
|
||||
rc-textarea "~1.10.0"
|
||||
rc-textarea "~1.10.1"
|
||||
rc-tooltip "~6.4.0"
|
||||
rc-tree "~5.13.1"
|
||||
rc-tree-select "~5.27.0"
|
||||
@@ -4508,17 +4513,7 @@ braces@^3.0.3, braces@~3.0.2:
|
||||
dependencies:
|
||||
fill-range "^7.1.1"
|
||||
|
||||
browserslist@^4.0.0, browserslist@^4.23.0, browserslist@^4.24.0, browserslist@^4.24.4:
|
||||
version "4.24.4"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b"
|
||||
integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001688"
|
||||
electron-to-chromium "^1.5.73"
|
||||
node-releases "^2.0.19"
|
||||
update-browserslist-db "^1.1.1"
|
||||
|
||||
browserslist@^4.25.0:
|
||||
browserslist@^4.0.0, browserslist@^4.23.0, browserslist@^4.24.0, browserslist@^4.24.4, browserslist@^4.25.0:
|
||||
version "4.25.0"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.0.tgz#986aa9c6d87916885da2b50d8eb577ac8d133b2c"
|
||||
integrity sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==
|
||||
@@ -4620,7 +4615,7 @@ caniuse-api@^3.0.0:
|
||||
lodash.memoize "^4.1.2"
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001688, caniuse-lite@^1.0.30001702:
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702:
|
||||
version "1.0.30001714"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001714.tgz#cfd27ff07e6fa20a0f45c7a10d28a0ffeaba2122"
|
||||
integrity sha512-mtgapdwDLSSBnCI3JokHM7oEQBLxiJKVRtg10AxM1AyeiKcM96f0Mkbqeq+1AbiCtvMcHRulAAEMu693JrSWqg==
|
||||
@@ -5903,11 +5898,6 @@ electron-to-chromium@^1.5.160:
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.170.tgz#9f6697de4339e24da8b234e4492a9ecb91f5989c"
|
||||
integrity sha512-GP+M7aeluQo9uAyiTCxgIj/j+PrWhMlY7LFVj8prlsPljd0Fdg9AprlfUi+OCSFWy9Y5/2D/Jrj9HS8Z4rpKWA==
|
||||
|
||||
electron-to-chromium@^1.5.73:
|
||||
version "1.5.138"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.138.tgz#319e775179bd0889ed96a04d4390d355fb315a44"
|
||||
integrity sha512-FWlQc52z1dXqm+9cCJ2uyFgJkESd+16j6dBEjsgDNuHjBpuIzL8/lRc0uvh1k8RNI6waGo6tcy2DvwkTBJOLDg==
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
@@ -5952,10 +5942,10 @@ encodeurl@~2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
|
||||
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
|
||||
|
||||
enhanced-resolve@^5.17.1:
|
||||
version "5.18.1"
|
||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz#728ab082f8b7b6836de51f1637aab5d3b9568faf"
|
||||
integrity sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==
|
||||
enhanced-resolve@^5.17.2:
|
||||
version "5.18.2"
|
||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz#7903c5b32ffd4b2143eeb4b92472bd68effd5464"
|
||||
integrity sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.4"
|
||||
tapable "^2.2.0"
|
||||
@@ -6161,10 +6151,10 @@ escape-string-regexp@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
|
||||
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
|
||||
|
||||
eslint-config-prettier@^10.1.5:
|
||||
version "10.1.5"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz#00c18d7225043b6fbce6a665697377998d453782"
|
||||
integrity sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==
|
||||
eslint-config-prettier@^10.1.8:
|
||||
version "10.1.8"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz#15734ce4af8c2778cc32f0b01b37b0b5cd1ecb97"
|
||||
integrity sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==
|
||||
|
||||
eslint-plugin-prettier@^5.5.1:
|
||||
version "5.5.1"
|
||||
@@ -8063,10 +8053,10 @@ less-loader@^12.3.0:
|
||||
resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-12.3.0.tgz#d4a00361568be86a97da3df4f16954b0d4c15340"
|
||||
integrity sha512-0M6+uYulvYIWs52y0LqN4+QM9TqWAohYSNTo4htE8Z7Cn3G/qQMEmktfHmyJT23k+20kU9zHH2wrfFXkxNLtVw==
|
||||
|
||||
less@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/less/-/less-4.3.0.tgz#ef0cfc260a9ca8079ed8d0e3512bda8a12c82f2a"
|
||||
integrity sha512-X9RyH9fvemArzfdP8Pi3irr7lor2Ok4rOttDXBhlwDg+wKQsXOXgHWduAJE1EsF7JJx0w0bcO6BC6tCKKYnXKA==
|
||||
less@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/less/-/less-4.4.0.tgz#deaf881f4880ee80691beae925b8fac699d3a76d"
|
||||
integrity sha512-kdTwsyRuncDfjEs0DlRILWNvxhDG/Zij4YLO4TMJgDLW+8OzpfkdPnRgrsRuY1o+oaxJGWsps5f/RVBgGmmN0w==
|
||||
dependencies:
|
||||
copy-anything "^2.0.1"
|
||||
parse-node-version "^1.0.1"
|
||||
@@ -10714,10 +10704,10 @@ rc-tabs@~15.6.1:
|
||||
rc-resize-observer "^1.0.0"
|
||||
rc-util "^5.34.1"
|
||||
|
||||
rc-textarea@~1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-1.10.0.tgz#f8f962ef83be0b8e35db97cf03dbfb86ddd9c46c"
|
||||
integrity sha512-ai9IkanNuyBS4x6sOL8qu/Ld40e6cEs6pgk93R+XLYg0mDSjNBGey6/ZpDs5+gNLD7urQ14po3V6Ck2dJLt9SA==
|
||||
rc-textarea@~1.10.0, rc-textarea@~1.10.1:
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-1.10.2.tgz#459e3574a95c32939c6793045a1e4db04cb514cc"
|
||||
integrity sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.1"
|
||||
classnames "^2.2.1"
|
||||
@@ -12100,10 +12090,10 @@ swagger-client@^3.35.5:
|
||||
ramda "^0.30.1"
|
||||
ramda-adjunct "^5.1.0"
|
||||
|
||||
swagger-ui-react@^5.26.0:
|
||||
version "5.26.0"
|
||||
resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.26.0.tgz#b15a903d556cc0ec2a56a969beb9d5bc9ea52910"
|
||||
integrity sha512-4e6bP9bdJyh+SqQW0lxulPn/SDno4+oWrKXsuon5Z9kjtV0zeoWEJ1c70Qxp8kN/c3caFwec8OyxDNhvo14pkw==
|
||||
swagger-ui-react@^5.27.1:
|
||||
version "5.27.1"
|
||||
resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.27.1.tgz#315b59970c33933a5f62ca0f702789741dcedc7c"
|
||||
integrity sha512-wwDoavIeJI/Pwiavn32FMJ5dfptz0BAOKjSrj7EdU22QdP3gdk9+MZHdzzjxWURmVj0kc0XoQfsFgjln0toJaw==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.27.1"
|
||||
"@scarf/scarf" "=1.4.0"
|
||||
@@ -12525,7 +12515,7 @@ unraw@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/unraw/-/unraw-3.0.0.tgz"
|
||||
integrity sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==
|
||||
|
||||
update-browserslist-db@^1.1.1, update-browserslist-db@^1.1.3:
|
||||
update-browserslist-db@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420"
|
||||
integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==
|
||||
@@ -12794,26 +12784,27 @@ webpack-merge@^6.0.1:
|
||||
flat "^5.0.2"
|
||||
wildcard "^2.0.1"
|
||||
|
||||
webpack-sources@^3.2.3:
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
|
||||
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
|
||||
webpack-sources@^3.3.3:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.3.tgz#d4bf7f9909675d7a070ff14d0ef2a4f3c982c723"
|
||||
integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==
|
||||
|
||||
webpack@^5.88.1, webpack@^5.95.0, webpack@^5.99.9:
|
||||
version "5.99.9"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.99.9.tgz#d7de799ec17d0cce3c83b70744b4aedb537d8247"
|
||||
integrity sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==
|
||||
webpack@^5.101.0, webpack@^5.88.1, webpack@^5.95.0:
|
||||
version "5.101.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.101.0.tgz#4b81407ffad9857f81ff03f872e3369b9198cc9d"
|
||||
integrity sha512-B4t+nJqytPeuZlHuIKTbalhljIFXeNRqrUGAQgTGlfOl2lXXKXw+yZu6bicycP+PUlM44CxBjCFD6aciKFT3LQ==
|
||||
dependencies:
|
||||
"@types/eslint-scope" "^3.7.7"
|
||||
"@types/estree" "^1.0.6"
|
||||
"@types/estree" "^1.0.8"
|
||||
"@types/json-schema" "^7.0.15"
|
||||
"@webassemblyjs/ast" "^1.14.1"
|
||||
"@webassemblyjs/wasm-edit" "^1.14.1"
|
||||
"@webassemblyjs/wasm-parser" "^1.14.1"
|
||||
acorn "^8.14.0"
|
||||
acorn "^8.15.0"
|
||||
acorn-import-phases "^1.0.3"
|
||||
browserslist "^4.24.0"
|
||||
chrome-trace-event "^1.0.2"
|
||||
enhanced-resolve "^5.17.1"
|
||||
enhanced-resolve "^5.17.2"
|
||||
es-module-lexer "^1.2.1"
|
||||
eslint-scope "5.1.1"
|
||||
events "^3.2.0"
|
||||
@@ -12827,7 +12818,7 @@ webpack@^5.88.1, webpack@^5.95.0, webpack@^5.99.9:
|
||||
tapable "^2.1.1"
|
||||
terser-webpack-plugin "^5.3.11"
|
||||
watchpack "^2.4.1"
|
||||
webpack-sources "^3.2.3"
|
||||
webpack-sources "^3.3.3"
|
||||
|
||||
webpackbar@^6.0.1:
|
||||
version "6.0.1"
|
||||
|
||||
@@ -174,6 +174,8 @@ function Echart(
|
||||
if (!chartRef.current) {
|
||||
chartRef.current = init(divRef.current, null, { locale });
|
||||
}
|
||||
// did mount
|
||||
handleSizeChange({ width, height });
|
||||
setDidMount(true);
|
||||
});
|
||||
}, [locale]);
|
||||
@@ -235,9 +237,6 @@ function Echart(
|
||||
echartOptions,
|
||||
);
|
||||
chartRef.current?.setOption(themedEchartOptions, true);
|
||||
|
||||
// did mount
|
||||
handleSizeChange({ width, height });
|
||||
}
|
||||
}, [didMount, echartOptions, eventHandlers, zrEventHandlers, theme]);
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ def _get_samples(
|
||||
query_obj = copy.copy(query_obj)
|
||||
query_obj.is_timeseries = False
|
||||
query_obj.orderby = []
|
||||
query_obj.metrics = None
|
||||
query_obj.metrics = []
|
||||
query_obj.post_processing = []
|
||||
qry_obj_cols = []
|
||||
for o in datasource.columns:
|
||||
@@ -168,7 +168,7 @@ def _get_drill_detail(
|
||||
query_obj = copy.copy(query_obj)
|
||||
query_obj.is_timeseries = False
|
||||
query_obj.orderby = []
|
||||
query_obj.metrics = None
|
||||
query_obj.metrics = []
|
||||
query_obj.post_processing = []
|
||||
qry_obj_cols = []
|
||||
for o in datasource.columns:
|
||||
|
||||
@@ -86,6 +86,8 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
|
||||
apply_fetch_values_predicate: bool
|
||||
columns: list[Column]
|
||||
datasource: BaseDatasource | None
|
||||
columns_by_name: dict[str, Any]
|
||||
metrics_by_name: dict[str, Any]
|
||||
extras: dict[str, Any]
|
||||
filter: list[QueryObjectFilterClause]
|
||||
from_dttm: datetime | None
|
||||
@@ -94,7 +96,7 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
|
||||
inner_to_dttm: datetime | None
|
||||
is_rowcount: bool
|
||||
is_timeseries: bool
|
||||
metrics: list[Metric] | None
|
||||
metrics: list[Metric]
|
||||
order_desc: bool
|
||||
orderby: list[OrderBy]
|
||||
post_processing: list[dict[str, Any]]
|
||||
@@ -141,6 +143,30 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
|
||||
self.apply_fetch_values_predicate = apply_fetch_values_predicate or False
|
||||
self.columns = columns or []
|
||||
self.datasource = datasource
|
||||
|
||||
# Build datasource mappings for easy lookup
|
||||
self.columns_by_name: dict[str, Any] = {}
|
||||
self.metrics_by_name: dict[str, Any] = {}
|
||||
|
||||
if datasource:
|
||||
try:
|
||||
if hasattr(datasource, "columns") and datasource.columns is not None:
|
||||
self.columns_by_name = {
|
||||
col.column_name: col for col in datasource.columns
|
||||
}
|
||||
except (TypeError, AttributeError):
|
||||
# Handle mocked datasources or other non-iterable cases
|
||||
pass
|
||||
|
||||
try:
|
||||
if hasattr(datasource, "metrics") and datasource.metrics is not None:
|
||||
self.metrics_by_name = {
|
||||
metric.metric_name: metric for metric in datasource.metrics
|
||||
}
|
||||
except (TypeError, AttributeError):
|
||||
# Handle mocked datasources or other non-iterable cases
|
||||
pass
|
||||
|
||||
self.extras = extras or {}
|
||||
self.filter = filters or []
|
||||
self.granularity = granularity
|
||||
@@ -192,9 +218,12 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
|
||||
def is_str_or_adhoc(metric: Metric) -> bool:
|
||||
return isinstance(metric, str) or is_adhoc_metric(metric)
|
||||
|
||||
self.metrics = metrics and [
|
||||
# Track whether metrics was originally None (for need_groupby logic)
|
||||
self._metrics_is_not_none = metrics is not None
|
||||
|
||||
self.metrics = [
|
||||
x if is_str_or_adhoc(x) else x["label"] # type: ignore
|
||||
for x in metrics
|
||||
for x in (metrics or [])
|
||||
]
|
||||
|
||||
def _set_post_processing(
|
||||
@@ -226,15 +255,20 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
|
||||
field.new_name,
|
||||
)
|
||||
value = kwargs[field.old_name]
|
||||
if value:
|
||||
if hasattr(self, field.new_name):
|
||||
if value is not None:
|
||||
# Only override if the new field is not already populated with data
|
||||
current_value = getattr(self, field.new_name, None)
|
||||
if (
|
||||
current_value
|
||||
): # If field already has truthy data, don't override
|
||||
logger.warning(
|
||||
"The field `%s` is already populated, "
|
||||
"replacing value with contents from `%s`.",
|
||||
"not replacing with contents from deprecated `%s`.",
|
||||
field.new_name,
|
||||
field.old_name,
|
||||
)
|
||||
setattr(self, field.new_name, value)
|
||||
else:
|
||||
setattr(self, field.new_name, value)
|
||||
|
||||
def _move_deprecated_extra_fields(self, kwargs: dict[str, Any]) -> None:
|
||||
# move deprecated extras fields to extras
|
||||
@@ -247,8 +281,8 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
|
||||
field.new_name,
|
||||
)
|
||||
value = kwargs[field.old_name]
|
||||
if value:
|
||||
if hasattr(self.extras, field.new_name):
|
||||
if value is not None and value != "": # Don't add empty string values
|
||||
if field.new_name in self.extras:
|
||||
logger.warning(
|
||||
"The field `%s` is already populated in "
|
||||
"`extras`, replacing value with contents "
|
||||
@@ -262,7 +296,7 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
|
||||
def metric_names(self) -> list[str]:
|
||||
"""Return metrics names (labels), coerce adhoc metrics to strings."""
|
||||
return get_metric_names(
|
||||
self.metrics or [],
|
||||
self.metrics,
|
||||
(
|
||||
self.datasource.verbose_map
|
||||
if self.datasource and hasattr(self.datasource, "verbose_map")
|
||||
@@ -276,6 +310,428 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
|
||||
and metrics are non-empty, otherwise returns column labels."""
|
||||
return get_column_names(self.columns)
|
||||
|
||||
@property
|
||||
def time_grain(self) -> str | None:
|
||||
"""Get time grain from extras."""
|
||||
return (self.extras or {}).get("time_grain_sqla")
|
||||
|
||||
@property
|
||||
def need_groupby(self) -> bool:
|
||||
"""Determine if GROUP BY is needed based on metrics and columns."""
|
||||
# GROUP BY is needed when there are metrics or when metrics is explicitly
|
||||
# provided (even as empty list). When metrics=None, columns are just for
|
||||
# selection without aggregation, so no GROUP BY needed.
|
||||
return self._metrics_is_not_none
|
||||
|
||||
@property
|
||||
def groupby(self) -> list[Column]:
|
||||
"""Alias for columns (for backward compatibility/clarity)."""
|
||||
return self.columns or []
|
||||
|
||||
def get_series_limit_prequery_obj(
|
||||
self,
|
||||
granularity: str | None,
|
||||
inner_from_dttm: datetime | None,
|
||||
inner_to_dttm: datetime | None,
|
||||
orderby: list[OrderBy] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build prequery object for series limit queries.
|
||||
|
||||
This is used to determine top groups when series_limit is set.
|
||||
|
||||
Args:
|
||||
granularity: The time column name
|
||||
inner_from_dttm: Inner from datetime (if different from main query)
|
||||
inner_to_dttm: Inner to datetime (if different from main query)
|
||||
orderby: Optional orderby to override (for series_limit_metric)
|
||||
|
||||
Returns:
|
||||
Dictionary suitable for passing to query()
|
||||
"""
|
||||
from superset.utils.core import get_non_base_axis_columns
|
||||
|
||||
return {
|
||||
"is_timeseries": False,
|
||||
"row_limit": self.series_limit,
|
||||
"metrics": self.metrics,
|
||||
"granularity": granularity,
|
||||
"groupby": self.groupby,
|
||||
"from_dttm": inner_from_dttm or self.from_dttm,
|
||||
"to_dttm": inner_to_dttm or self.to_dttm,
|
||||
"filter": self.filter,
|
||||
"orderby": orderby or [],
|
||||
"extras": self.extras or {},
|
||||
"columns": get_non_base_axis_columns(self.columns),
|
||||
"order_desc": True,
|
||||
}
|
||||
|
||||
def build_select_expressions( # noqa: C901
|
||||
self,
|
||||
granularity: str | None,
|
||||
series_column_labels: set[str],
|
||||
datasource: Any, # BaseDatasource
|
||||
template_processor: Any,
|
||||
) -> tuple[list[Any], dict[str, Any], dict[str, Any]]:
|
||||
"""Build SELECT expressions for the query.
|
||||
|
||||
Args:
|
||||
granularity: The time column name
|
||||
series_column_labels: Labels of series columns
|
||||
datasource: The datasource being queried
|
||||
template_processor: Template processor for SQL templating
|
||||
|
||||
Returns:
|
||||
Tuple of (select_exprs, groupby_all_columns, groupby_series_columns)
|
||||
"""
|
||||
from sqlalchemy import literal_column
|
||||
|
||||
from superset.utils.core import (
|
||||
DTTM_ALIAS,
|
||||
is_adhoc_column,
|
||||
)
|
||||
|
||||
select_exprs = []
|
||||
groupby_all_columns = {}
|
||||
groupby_series_columns = {}
|
||||
|
||||
# Filter out the pseudo column __timestamp from columns
|
||||
columns = [col for col in self.columns if col != DTTM_ALIAS]
|
||||
|
||||
if self.need_groupby:
|
||||
# dedup columns while preserving order
|
||||
columns = self.groupby or self.columns
|
||||
for selected in columns:
|
||||
if isinstance(selected, str):
|
||||
# if groupby field/expr equals granularity field/expr
|
||||
if selected == granularity:
|
||||
table_col = self.columns_by_name[selected]
|
||||
outer = table_col.get_timestamp_expression(
|
||||
time_grain=self.time_grain,
|
||||
label=selected,
|
||||
template_processor=template_processor,
|
||||
)
|
||||
# if groupby field equals a selected column
|
||||
elif selected in self.columns_by_name:
|
||||
outer = datasource.convert_tbl_column_to_sqla_col(
|
||||
self.columns_by_name[selected],
|
||||
template_processor=template_processor,
|
||||
)
|
||||
else:
|
||||
# Import here to avoid circular imports
|
||||
from superset.models.helpers import validate_adhoc_subquery
|
||||
|
||||
selected = validate_adhoc_subquery(
|
||||
selected,
|
||||
datasource.database,
|
||||
datasource.catalog,
|
||||
datasource.schema,
|
||||
datasource.database.db_engine_spec.engine,
|
||||
)
|
||||
outer = literal_column(f"({selected})")
|
||||
outer = datasource.make_sqla_column_compatible(outer, selected)
|
||||
else:
|
||||
outer = datasource.adhoc_column_to_sqla(
|
||||
col=selected,
|
||||
template_processor=template_processor,
|
||||
)
|
||||
groupby_all_columns[outer.name] = outer
|
||||
if (
|
||||
self.is_timeseries and not series_column_labels
|
||||
) or outer.name in series_column_labels:
|
||||
groupby_series_columns[outer.name] = outer
|
||||
select_exprs.append(outer)
|
||||
elif self.columns:
|
||||
with datasource.database.get_sqla_engine() as engine:
|
||||
quote = engine.dialect.identifier_preparer.quote
|
||||
|
||||
for selected in self.columns:
|
||||
if is_adhoc_column(selected):
|
||||
_sql = selected["sqlExpression"]
|
||||
_column_label = selected["label"]
|
||||
elif isinstance(selected, str):
|
||||
_sql = quote(selected)
|
||||
_column_label = selected
|
||||
|
||||
# Import here to avoid circular imports
|
||||
from superset.models.helpers import validate_adhoc_subquery
|
||||
|
||||
selected = validate_adhoc_subquery(
|
||||
_sql,
|
||||
datasource.database,
|
||||
datasource.catalog,
|
||||
datasource.schema,
|
||||
datasource.database.db_engine_spec.engine,
|
||||
)
|
||||
|
||||
select_exprs.append(
|
||||
datasource.convert_tbl_column_to_sqla_col(
|
||||
self.columns_by_name[selected],
|
||||
template_processor=template_processor,
|
||||
label=_column_label,
|
||||
)
|
||||
if selected in self.columns_by_name
|
||||
else datasource.make_sqla_column_compatible(
|
||||
literal_column(selected), _column_label
|
||||
)
|
||||
)
|
||||
|
||||
return select_exprs, groupby_all_columns, groupby_series_columns
|
||||
|
||||
def build_filter_clauses( # noqa: C901
|
||||
self,
|
||||
datasource: Any, # BaseDatasource
|
||||
template_processor: Any,
|
||||
time_filters: list[Any],
|
||||
removed_filters: set[str],
|
||||
applied_adhoc_filters_columns: list[Any],
|
||||
rejected_adhoc_filters_columns: list[Any],
|
||||
is_timeseries: bool,
|
||||
dttm_col: Any,
|
||||
) -> tuple[list[Any], list[Any]]:
|
||||
"""Build WHERE and HAVING filter clauses for the query.
|
||||
|
||||
Args:
|
||||
datasource: The datasource being queried
|
||||
template_processor: Template processor for SQL templating
|
||||
time_filters: Time-based filters to apply
|
||||
removed_filters: Set of filter column names handled by Jinja templates
|
||||
applied_adhoc_filters_columns: List to track applied adhoc filters
|
||||
rejected_adhoc_filters_columns: List to track rejected adhoc filters
|
||||
is_timeseries: Whether this is a timeseries query
|
||||
dttm_col: The datetime column object
|
||||
|
||||
Returns:
|
||||
Tuple of (where_clause_and, having_clause_and)
|
||||
"""
|
||||
from flask import current_app
|
||||
from sqlalchemy import or_
|
||||
|
||||
from superset import feature_flag_manager
|
||||
from superset.common.utils.time_range_utils import (
|
||||
get_since_until_from_time_range,
|
||||
)
|
||||
from superset.exceptions import QueryObjectValidationError
|
||||
from superset.utils.core import (
|
||||
DTTM_ALIAS,
|
||||
FilterOperator,
|
||||
GenericDataType,
|
||||
get_column_name,
|
||||
is_adhoc_column,
|
||||
)
|
||||
|
||||
where_clause_and = []
|
||||
having_clause_and = []
|
||||
|
||||
# Process regular filters
|
||||
for flt in self.filter:
|
||||
if not all(flt.get(s) for s in ["col", "op"]):
|
||||
continue
|
||||
flt_col = flt["col"]
|
||||
val = flt.get("val")
|
||||
flt_grain = flt.get("grain")
|
||||
op = FilterOperator(flt["op"].upper())
|
||||
col_obj = None
|
||||
sqla_col = None
|
||||
|
||||
if flt_col == DTTM_ALIAS and is_timeseries and dttm_col:
|
||||
col_obj = dttm_col
|
||||
elif is_adhoc_column(flt_col):
|
||||
try:
|
||||
sqla_col = datasource.adhoc_column_to_sqla(
|
||||
flt_col, force_type_check=True
|
||||
)
|
||||
applied_adhoc_filters_columns.append(flt_col)
|
||||
except Exception: # ColumnNotFoundException
|
||||
rejected_adhoc_filters_columns.append(flt_col)
|
||||
continue
|
||||
else:
|
||||
col_obj = self.columns_by_name.get(str(flt_col))
|
||||
filter_grain = flt.get("grain")
|
||||
|
||||
if get_column_name(flt_col) in removed_filters:
|
||||
# Skip generating SQLA filter when the jinja template handles it.
|
||||
continue
|
||||
|
||||
if col_obj or sqla_col is not None:
|
||||
db_engine_spec = datasource.database.db_engine_spec
|
||||
|
||||
if sqla_col is not None:
|
||||
pass
|
||||
elif col_obj and filter_grain:
|
||||
sqla_col = col_obj.get_timestamp_expression(
|
||||
time_grain=filter_grain, template_processor=template_processor
|
||||
)
|
||||
elif col_obj:
|
||||
sqla_col = datasource.convert_tbl_column_to_sqla_col(
|
||||
tbl_column=col_obj, template_processor=template_processor
|
||||
)
|
||||
|
||||
col_type = col_obj.type if col_obj else None
|
||||
col_spec = db_engine_spec.get_column_spec(native_type=col_type)
|
||||
is_list_target = op in (
|
||||
FilterOperator.IN,
|
||||
FilterOperator.NOT_IN,
|
||||
)
|
||||
|
||||
col_advanced_data_type = col_obj.advanced_data_type if col_obj else ""
|
||||
|
||||
if col_spec and not col_advanced_data_type:
|
||||
target_generic_type = col_spec.generic_type
|
||||
else:
|
||||
target_generic_type = GenericDataType.STRING
|
||||
|
||||
eq = datasource.filter_values_handler(
|
||||
values=val,
|
||||
operator=op,
|
||||
target_generic_type=target_generic_type,
|
||||
target_native_type=col_type,
|
||||
is_list_target=is_list_target,
|
||||
db_engine_spec=db_engine_spec,
|
||||
)
|
||||
|
||||
# Get ADVANCED_DATA_TYPES from config when needed
|
||||
ADVANCED_DATA_TYPES = current_app.config.get("ADVANCED_DATA_TYPES", {}) # noqa: N806
|
||||
|
||||
if (
|
||||
col_advanced_data_type != ""
|
||||
and feature_flag_manager.is_feature_enabled(
|
||||
"ENABLE_ADVANCED_DATA_TYPES"
|
||||
)
|
||||
and col_advanced_data_type in ADVANCED_DATA_TYPES
|
||||
and eq is not None
|
||||
):
|
||||
where_clause_and.append(
|
||||
datasource._apply_advanced_data_type_filter(
|
||||
sqla_col, col_advanced_data_type, op, eq
|
||||
)
|
||||
)
|
||||
elif is_list_target:
|
||||
assert isinstance(eq, (tuple, list))
|
||||
if len(eq) == 0:
|
||||
raise QueryObjectValidationError(
|
||||
"Filter value list cannot be empty"
|
||||
)
|
||||
if len(eq) > len(
|
||||
eq_without_none := [x for x in eq if x is not None]
|
||||
):
|
||||
is_null_cond = sqla_col.is_(None)
|
||||
if eq:
|
||||
cond = or_(is_null_cond, sqla_col.in_(eq_without_none))
|
||||
else:
|
||||
cond = is_null_cond
|
||||
else:
|
||||
cond = sqla_col.in_(eq)
|
||||
if op == FilterOperator.NOT_IN:
|
||||
cond = ~cond
|
||||
where_clause_and.append(cond)
|
||||
elif op in {
|
||||
FilterOperator.IS_NULL,
|
||||
FilterOperator.IS_NOT_NULL,
|
||||
}:
|
||||
where_clause_and.append(
|
||||
db_engine_spec.handle_null_filter(sqla_col, op)
|
||||
)
|
||||
elif op == FilterOperator.IS_TRUE:
|
||||
where_clause_and.append(
|
||||
db_engine_spec.handle_boolean_filter(sqla_col, op, True)
|
||||
)
|
||||
elif op == FilterOperator.IS_FALSE:
|
||||
where_clause_and.append(
|
||||
db_engine_spec.handle_boolean_filter(sqla_col, op, False)
|
||||
)
|
||||
else:
|
||||
if (
|
||||
op
|
||||
not in {
|
||||
FilterOperator.EQUALS,
|
||||
FilterOperator.NOT_EQUALS,
|
||||
}
|
||||
and eq is None
|
||||
):
|
||||
raise QueryObjectValidationError(
|
||||
"Must specify a value for filters with comparison operators"
|
||||
)
|
||||
if op in {
|
||||
FilterOperator.EQUALS,
|
||||
FilterOperator.NOT_EQUALS,
|
||||
FilterOperator.GREATER_THAN,
|
||||
FilterOperator.LESS_THAN,
|
||||
FilterOperator.GREATER_THAN_OR_EQUALS,
|
||||
FilterOperator.LESS_THAN_OR_EQUALS,
|
||||
}:
|
||||
where_clause_and.append(
|
||||
db_engine_spec.handle_comparison_filter(sqla_col, op, eq)
|
||||
)
|
||||
elif op in {
|
||||
FilterOperator.ILIKE,
|
||||
FilterOperator.LIKE,
|
||||
}:
|
||||
if target_generic_type != GenericDataType.STRING:
|
||||
import sqlalchemy as sa
|
||||
|
||||
sqla_col = sa.cast(sqla_col, sa.String)
|
||||
|
||||
if op == FilterOperator.LIKE:
|
||||
where_clause_and.append(sqla_col.like(eq))
|
||||
else:
|
||||
where_clause_and.append(sqla_col.ilike(eq))
|
||||
elif op in {FilterOperator.NOT_LIKE}:
|
||||
if target_generic_type != GenericDataType.STRING:
|
||||
import sqlalchemy as sa
|
||||
|
||||
sqla_col = sa.cast(sqla_col, sa.String)
|
||||
|
||||
where_clause_and.append(sqla_col.not_like(eq))
|
||||
elif (
|
||||
op == FilterOperator.TEMPORAL_RANGE
|
||||
and isinstance(eq, str)
|
||||
and col_obj is not None
|
||||
):
|
||||
_since, _until = get_since_until_from_time_range(
|
||||
time_range=eq,
|
||||
time_shift=self.time_shift,
|
||||
extras=self.extras or {},
|
||||
)
|
||||
where_clause_and.append(
|
||||
datasource.get_time_filter(
|
||||
time_col=col_obj,
|
||||
start_dttm=_since,
|
||||
end_dttm=_until,
|
||||
time_grain=flt_grain,
|
||||
label=sqla_col.key,
|
||||
template_processor=template_processor,
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise QueryObjectValidationError(
|
||||
f"Invalid filter operation type: {op}"
|
||||
)
|
||||
|
||||
# Process WHERE and HAVING extras
|
||||
if self.extras:
|
||||
where = self.extras.get("where")
|
||||
if where:
|
||||
where = datasource._process_sql_expression(
|
||||
expression=where,
|
||||
database_id=datasource.database_id,
|
||||
engine=datasource.database.backend,
|
||||
schema=datasource.schema,
|
||||
template_processor=template_processor,
|
||||
)
|
||||
where_clause_and += [datasource.text(where)]
|
||||
having = self.extras.get("having")
|
||||
if having:
|
||||
having = datasource._process_sql_expression(
|
||||
expression=having,
|
||||
database_id=datasource.database_id,
|
||||
engine=datasource.database.backend,
|
||||
schema=datasource.schema,
|
||||
template_processor=template_processor,
|
||||
)
|
||||
having_clause_and += [datasource.text(having)]
|
||||
|
||||
return where_clause_and, having_clause_and
|
||||
|
||||
def validate(
|
||||
self, raise_exceptions: bool | None = True
|
||||
) -> QueryObjectValidationError | None:
|
||||
@@ -350,7 +806,7 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
|
||||
"inner_to_dttm": self.inner_to_dttm,
|
||||
"is_rowcount": self.is_rowcount,
|
||||
"is_timeseries": self.is_timeseries,
|
||||
"metrics": self.metrics,
|
||||
"metrics": self.metrics if self.metrics else None,
|
||||
"order_desc": self.order_desc,
|
||||
"orderby": self.orderby,
|
||||
"row_limit": self.row_limit,
|
||||
|
||||
@@ -71,6 +71,7 @@ from sqlalchemy.types import JSON
|
||||
from superset import db, is_feature_enabled, security_manager
|
||||
from superset.commands.dataset.exceptions import DatasetNotFoundError
|
||||
from superset.common.db_query_status import QueryStatus
|
||||
from superset.common.query_object import QueryObject
|
||||
from superset.connectors.sqla.utils import (
|
||||
get_columns_description,
|
||||
get_physical_table_metadata,
|
||||
@@ -720,7 +721,7 @@ class AnnotationDatasource(BaseDatasource):
|
||||
def query(self, query_obj: QueryObjectDict) -> QueryResult:
|
||||
error_message = None
|
||||
qry = db.session.query(Annotation)
|
||||
qry = qry.filter(Annotation.layer_id == query_obj["filter"][0]["val"])
|
||||
qry = qry.filter(Annotation.layer_id == query_obj["filters"][0]["val"])
|
||||
if query_obj["from_dttm"]:
|
||||
qry = qry.filter(Annotation.start_dttm >= query_obj["from_dttm"])
|
||||
if query_obj["to_dttm"]:
|
||||
@@ -1514,18 +1515,19 @@ class SqlaTable(
|
||||
def _get_series_orderby(
|
||||
self,
|
||||
series_limit_metric: Metric,
|
||||
metrics_by_name: dict[str, SqlMetric],
|
||||
columns_by_name: dict[str, TableColumn],
|
||||
query_obj: QueryObject,
|
||||
template_processor: BaseTemplateProcessor | None = None,
|
||||
) -> Column:
|
||||
if utils.is_adhoc_metric(series_limit_metric):
|
||||
assert isinstance(series_limit_metric, dict)
|
||||
ob = self.adhoc_metric_to_sqla(series_limit_metric, columns_by_name)
|
||||
ob = self.adhoc_metric_to_sqla(
|
||||
series_limit_metric, query_obj.columns_by_name
|
||||
)
|
||||
elif (
|
||||
isinstance(series_limit_metric, str)
|
||||
and series_limit_metric in metrics_by_name
|
||||
and series_limit_metric in query_obj.metrics_by_name
|
||||
):
|
||||
ob = metrics_by_name[series_limit_metric].get_sqla_col(
|
||||
ob = query_obj.metrics_by_name[series_limit_metric].get_sqla_col(
|
||||
template_processor=template_processor
|
||||
)
|
||||
else:
|
||||
@@ -1857,7 +1859,10 @@ class SqlaTable(
|
||||
"""
|
||||
extra_cache_keys = super().get_extra_cache_keys(query_obj)
|
||||
if self.has_extra_cache_key_calls(query_obj):
|
||||
sqla_query = self.get_sqla_query(**query_obj)
|
||||
from superset.common.query_object import QueryObject
|
||||
|
||||
query_object = QueryObject(datasource=self, **query_obj)
|
||||
sqla_query = self.get_sqla_query(query_object)
|
||||
extra_cache_keys += sqla_query.extra_cache_keys
|
||||
return list(set(extra_cache_keys))
|
||||
|
||||
|
||||
@@ -940,7 +940,7 @@ def dataset_macro(
|
||||
metrics = [metric.metric_name for metric in dataset.metrics]
|
||||
query_obj = {
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
"metrics": metrics if include_metrics else None,
|
||||
"columns": columns,
|
||||
"from_dttm": from_dttm,
|
||||
|
||||
@@ -37,7 +37,7 @@ from superset.migrations.shared.security_converge import (
|
||||
)
|
||||
from superset.models.core import Database
|
||||
|
||||
logger = logging.getLogger("alembic")
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
Base: Type[Any] = declarative_base()
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ from superset.migrations.shared.utils import paginated_update, try_load_json
|
||||
from superset.utils import json
|
||||
from superset.utils.date_parser import get_since_until
|
||||
|
||||
logger = logging.getLogger("alembic")
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from sqlalchemy import (
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import Load, relationship, Session
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ YELLOW = "\033[33m"
|
||||
RED = "\033[31m"
|
||||
LRED = "\033[91m"
|
||||
|
||||
logger = logging.getLogger("alembic")
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
DEFAULT_BATCH_SIZE = int(os.environ.get("BATCH_SIZE", 1000))
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ from superset.utils.core import generic_find_constraint_name
|
||||
revision = "1226819ee0e3"
|
||||
down_revision = "956a063c52b3"
|
||||
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
naming_convention = {
|
||||
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s"
|
||||
@@ -61,7 +63,7 @@ def upgrade():
|
||||
["datasource_name"],
|
||||
)
|
||||
except: # noqa: E722
|
||||
logging.warning("Could not find or drop constraint on `columns`")
|
||||
logger.warning("Could not find or drop constraint on `columns`")
|
||||
|
||||
|
||||
def downgrade():
|
||||
|
||||
@@ -38,6 +38,8 @@ from superset.utils.core import generic_find_constraint_name
|
||||
revision = "3b626e2a6783"
|
||||
down_revision = "eca4694defa7"
|
||||
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
def upgrade():
|
||||
# cleanup after: https://github.com/airbnb/superset/pull/1078
|
||||
@@ -60,7 +62,7 @@ def upgrade():
|
||||
batch_op.drop_column("druid_datasource_id")
|
||||
batch_op.drop_column("table_id")
|
||||
except Exception as ex:
|
||||
logging.warning(str(ex))
|
||||
logger.warning(str(ex))
|
||||
|
||||
# fixed issue: https://github.com/airbnb/superset/issues/466
|
||||
try:
|
||||
@@ -69,18 +71,18 @@ def upgrade():
|
||||
None, "datasources", ["datasource_name"], ["datasource_name"]
|
||||
)
|
||||
except Exception as ex:
|
||||
logging.warning(str(ex))
|
||||
logger.warning(str(ex))
|
||||
try:
|
||||
with op.batch_alter_table("query") as batch_op:
|
||||
batch_op.create_unique_constraint("client_id", ["client_id"])
|
||||
except Exception as ex:
|
||||
logging.warning(str(ex))
|
||||
logger.warning(str(ex))
|
||||
|
||||
try:
|
||||
with op.batch_alter_table("query") as batch_op:
|
||||
batch_op.drop_column("name")
|
||||
except Exception as ex:
|
||||
logging.warning(str(ex))
|
||||
logger.warning(str(ex))
|
||||
|
||||
|
||||
def downgrade():
|
||||
@@ -88,7 +90,7 @@ def downgrade():
|
||||
with op.batch_alter_table("tables") as batch_op:
|
||||
batch_op.create_index("table_name", ["table_name"], unique=True)
|
||||
except Exception as ex:
|
||||
logging.warning(str(ex))
|
||||
logger.warning(str(ex))
|
||||
|
||||
try:
|
||||
with op.batch_alter_table("slices") as batch_op:
|
||||
@@ -113,7 +115,7 @@ def downgrade():
|
||||
)
|
||||
batch_op.create_foreign_key("slices_ibfk_2", "tables", ["table_id"], ["id"])
|
||||
except Exception as ex:
|
||||
logging.warning(str(ex))
|
||||
logger.warning(str(ex))
|
||||
|
||||
try:
|
||||
fk_columns = generic_find_constraint_name(
|
||||
@@ -125,11 +127,11 @@ def downgrade():
|
||||
with op.batch_alter_table("columns") as batch_op:
|
||||
batch_op.drop_constraint(fk_columns, type_="foreignkey")
|
||||
except Exception as ex:
|
||||
logging.warning(str(ex))
|
||||
logger.warning(str(ex))
|
||||
|
||||
op.add_column("query", sa.Column("name", sa.String(length=256), nullable=True))
|
||||
try:
|
||||
with op.batch_alter_table("query") as batch_op:
|
||||
batch_op.drop_constraint("client_id", type_="unique")
|
||||
except Exception as ex:
|
||||
logging.warning(str(ex))
|
||||
logger.warning(str(ex))
|
||||
|
||||
@@ -31,6 +31,8 @@ import logging # noqa: E402
|
||||
import sqlalchemy as sa # noqa: E402
|
||||
from alembic import op # noqa: E402
|
||||
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column("tables", sa.Column("params", sa.Text(), nullable=True))
|
||||
@@ -40,4 +42,4 @@ def downgrade():
|
||||
try:
|
||||
op.drop_column("tables", "params")
|
||||
except Exception as ex:
|
||||
logging.warning(str(ex))
|
||||
logger.warning(str(ex))
|
||||
|
||||
@@ -31,6 +31,8 @@ import logging # noqa: E402
|
||||
import sqlalchemy as sa # noqa: E402
|
||||
from alembic import op # noqa: E402
|
||||
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column(
|
||||
@@ -44,7 +46,7 @@ def upgrade():
|
||||
op.create_unique_constraint(None, "dbs", ["verbose_name"])
|
||||
op.create_unique_constraint(None, "clusters", ["verbose_name"])
|
||||
except Exception:
|
||||
logging.info("Constraint not created, expected when using sqlite")
|
||||
logger.info("Constraint not created, expected when using sqlite")
|
||||
|
||||
|
||||
def downgrade():
|
||||
@@ -52,4 +54,4 @@ def downgrade():
|
||||
op.drop_column("dbs", "verbose_name")
|
||||
op.drop_column("clusters", "verbose_name")
|
||||
except Exception as ex:
|
||||
logging.exception(ex)
|
||||
logger.exception(ex)
|
||||
|
||||
@@ -37,6 +37,8 @@ from superset.utils.core import (
|
||||
revision = "4736ec66ce19"
|
||||
down_revision = "f959a6652acd"
|
||||
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
conv = {
|
||||
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
||||
"uq": "uq_%(table_name)s_%(column_0_name)s",
|
||||
@@ -119,13 +121,13 @@ def upgrade():
|
||||
type_="unique",
|
||||
)
|
||||
except Exception as ex:
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
"Constraint drop failed, you may want to do this "
|
||||
"manually on your database. For context, this is a known "
|
||||
"issue around nondeterministic constraint names on Postgres "
|
||||
"and perhaps more databases through SQLAlchemy."
|
||||
)
|
||||
logging.exception(ex)
|
||||
logger.exception(ex)
|
||||
|
||||
|
||||
def downgrade():
|
||||
|
||||
@@ -22,12 +22,16 @@ Create Date: 2018-05-09 23:45:14.296283
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import sqlalchemy as sa # noqa: E402
|
||||
from alembic import op # noqa: E402
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "e502db2af7be"
|
||||
down_revision = "5ccf602336a0"
|
||||
|
||||
import sqlalchemy as sa # noqa: E402
|
||||
from alembic import op # noqa: E402
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
def upgrade():
|
||||
@@ -38,4 +42,4 @@ def downgrade():
|
||||
try:
|
||||
op.drop_column("tables", "template_params")
|
||||
except Exception as ex:
|
||||
logging.warning(str(ex)) # noqa: F821
|
||||
logger.warning(str(ex))
|
||||
|
||||
@@ -35,6 +35,8 @@ from superset.utils import json
|
||||
revision = "fb13d49b72f9"
|
||||
down_revision = "de021a1ca60d"
|
||||
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
@@ -49,7 +51,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade_slice(slc):
|
||||
params = json.loads(slc.params)
|
||||
logging.info(f"Upgrading {slc.slice_name}")
|
||||
logger.info(f"Upgrading {slc.slice_name}")
|
||||
cols = params.get("groupby")
|
||||
metric = params.get("metric")
|
||||
if cols:
|
||||
@@ -79,8 +81,8 @@ def upgrade():
|
||||
for slc in filter_box_slices.all():
|
||||
try:
|
||||
upgrade_slice(slc)
|
||||
except Exception:
|
||||
logging.exception(e) # noqa: F821
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
session.commit()
|
||||
session.close()
|
||||
@@ -94,7 +96,7 @@ def downgrade():
|
||||
for slc in filter_box_slices.all():
|
||||
try:
|
||||
params = json.loads(slc.params)
|
||||
logging.info(f"Downgrading {slc.slice_name}")
|
||||
logger.info(f"Downgrading {slc.slice_name}")
|
||||
flts = params.get("filter_configs")
|
||||
if not flts:
|
||||
continue
|
||||
@@ -102,7 +104,7 @@ def downgrade():
|
||||
params["groupby"] = [o.get("column") for o in flts]
|
||||
slc.params = json.dumps(params, sort_keys=True)
|
||||
except Exception as ex:
|
||||
logging.exception(ex)
|
||||
logger.exception(ex)
|
||||
|
||||
session.commit()
|
||||
session.close()
|
||||
|
||||
@@ -41,6 +41,8 @@ from superset.utils.core import ( # noqa: E402
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
class Slice(Base):
|
||||
__tablename__ = "slices"
|
||||
@@ -63,7 +65,7 @@ def upgrade():
|
||||
if source != target:
|
||||
slc.params = json.dumps(target, sort_keys=True)
|
||||
except Exception as ex:
|
||||
logging.warn(ex)
|
||||
logger.warning(ex)
|
||||
|
||||
session.commit()
|
||||
session.close()
|
||||
|
||||
@@ -37,6 +37,8 @@ from superset.utils.dashboard_filter_scopes_converter import convert_filter_scop
|
||||
revision = "3325d4caccc8"
|
||||
down_revision = "e96dbf2cfef0"
|
||||
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
@@ -86,7 +88,7 @@ def upgrade():
|
||||
if filters:
|
||||
filter_scopes = convert_filter_scopes(json_metadata, filters)
|
||||
json_metadata["filter_scopes"] = filter_scopes
|
||||
logging.info(
|
||||
logger.info(
|
||||
f"Adding filter_scopes for dashboard {dashboard.id}: {json.dumps(filter_scopes)}" # noqa: E501
|
||||
)
|
||||
|
||||
@@ -100,7 +102,7 @@ def upgrade():
|
||||
else:
|
||||
dashboard.json_metadata = None
|
||||
except Exception as ex:
|
||||
logging.exception(f"dashboard {dashboard.id} has error: {ex}")
|
||||
logger.exception(f"dashboard {dashboard.id} has error: {ex}")
|
||||
|
||||
session.commit()
|
||||
session.close()
|
||||
|
||||
@@ -38,6 +38,8 @@ from sqlalchemy_utils import UUIDType # noqa: E402
|
||||
|
||||
from superset import db # noqa: E402
|
||||
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
add_uuid_column_to_import_mixin = import_module(
|
||||
"superset.migrations.versions."
|
||||
"2020-09-28_17-57_b56500de1855_add_uuid_column_to_import_mixin",
|
||||
@@ -52,9 +54,9 @@ def has_uuid_column(table_name, bind):
|
||||
columns = {column["name"] for column in inspector.get_columns(table_name)}
|
||||
has_uuid_column = "uuid" in columns
|
||||
if has_uuid_column:
|
||||
logging.info("Table %s already has uuid column, skipping...", table_name)
|
||||
logger.info("Table %s already has uuid column, skipping...", table_name)
|
||||
else:
|
||||
logging.info("Table %s doesn't have uuid column, adding...", table_name)
|
||||
logger.info("Table %s doesn't have uuid column, adding...", table_name)
|
||||
return has_uuid_column
|
||||
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ down_revision = "31b2a1039d4a"
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
class Database(Base):
|
||||
__tablename__ = "dbs"
|
||||
@@ -56,7 +58,7 @@ def upgrade():
|
||||
try:
|
||||
extra = json.loads(database.extra)
|
||||
except json.JSONDecodeError as ex:
|
||||
logging.warning(str(ex))
|
||||
logger.warning(str(ex))
|
||||
continue
|
||||
|
||||
schemas_allowed_for_csv_upload = extra.get("schemas_allowed_for_csv_upload")
|
||||
|
||||
@@ -35,6 +35,8 @@ from sqlalchemy import func # noqa: E402
|
||||
from superset import db # noqa: E402
|
||||
from superset.connectors.sqla.models import SqlaTable # noqa: E402
|
||||
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.batch_alter_table("tables") as batch_op:
|
||||
@@ -62,12 +64,12 @@ def remove_value_if_too_long():
|
||||
for row in rows:
|
||||
row.fetch_values_predicate = None
|
||||
|
||||
logging.info("%d values deleted", len(rows))
|
||||
logger.info("%d values deleted", len(rows))
|
||||
|
||||
session.commit()
|
||||
session.close()
|
||||
except Exception as ex:
|
||||
logging.warning(ex)
|
||||
logger.warning(ex)
|
||||
|
||||
|
||||
def downgrade():
|
||||
|
||||
@@ -37,7 +37,7 @@ from superset.utils import json # noqa: E402
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
logger = logging.getLogger("alembic")
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
class Dashboard(Base):
|
||||
|
||||
@@ -37,6 +37,8 @@ from superset.utils import json # noqa: E402
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
class Database(Base):
|
||||
__tablename__ = "dbs"
|
||||
@@ -52,7 +54,7 @@ def upgrade():
|
||||
try:
|
||||
extra = json.loads(database.extra)
|
||||
except json.JSONDecodeError as ex:
|
||||
logging.warning(str(ex))
|
||||
logger.warning(str(ex))
|
||||
continue
|
||||
|
||||
if "schemas_allowed_for_csv_upload" in extra:
|
||||
@@ -74,7 +76,7 @@ def downgrade():
|
||||
try:
|
||||
extra = json.loads(database.extra)
|
||||
except json.JSONDecodeError as ex:
|
||||
logging.warning(str(ex))
|
||||
logger.warning(str(ex))
|
||||
continue
|
||||
|
||||
if "schemas_allowed_for_file_upload" in extra:
|
||||
|
||||
@@ -37,7 +37,7 @@ from superset.utils import json # noqa: E402
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
logger = logging.getLogger("alembic")
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
class Slice(Base):
|
||||
|
||||
@@ -38,7 +38,7 @@ from superset.utils import json # noqa: E402
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
logger = logging.getLogger("alembic")
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
class Slice(Base):
|
||||
|
||||
@@ -37,7 +37,7 @@ from superset.utils import json # noqa: E402
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
class Slice(Base): # type: ignore
|
||||
|
||||
@@ -38,7 +38,7 @@ from superset.migrations.shared.utils import paginated_update # noqa: E402
|
||||
from superset.utils import json # noqa: E402
|
||||
|
||||
Base = declarative_base()
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
class Dashboard(Base):
|
||||
|
||||
@@ -41,7 +41,7 @@ from superset.utils.date_parser import get_since_until
|
||||
revision = "f84fde59123a"
|
||||
down_revision = "9621c6d56ffb"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = logging.getLogger("alembic.env")
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ from superset.utils.core import generic_find_uq_constraint_name
|
||||
revision = "df3d7e2eb9a4"
|
||||
down_revision = "48cbb571fa3a"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
|
||||
def upgrade():
|
||||
|
||||
@@ -32,8 +32,7 @@ from sqlalchemy.ext.declarative import declarative_base
|
||||
from superset import db
|
||||
from superset.migrations.shared.utils import paginated_update
|
||||
|
||||
logger = logging.getLogger("alembic")
|
||||
logger.setLevel(logging.INFO)
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "363a9b1e8992"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -70,7 +70,7 @@ SQLGLOT_DIALECTS = {
|
||||
# "denodo": ???
|
||||
"dremio": Dremio,
|
||||
"drill": Dialects.DRILL,
|
||||
"druid": Dialects.DRUID,
|
||||
# "druid": Dialects.DRUID, # DRUID dialect not available in current sqlglot version
|
||||
"duckdb": Dialects.DUCKDB,
|
||||
# "dynamodb": ???
|
||||
# "elasticsearch": ???
|
||||
|
||||
@@ -411,7 +411,7 @@ class BaseViz: # pylint: disable=too-many-public-methods
|
||||
"groupby": groupby,
|
||||
"metrics": metrics,
|
||||
"row_limit": row_limit,
|
||||
"filter": self.form_data.get("filters", []),
|
||||
"filters": self.form_data.get("filters", []),
|
||||
"timeseries_limit": limit,
|
||||
"extras": extras,
|
||||
"timeseries_limit_metric": timeseries_limit_metric,
|
||||
|
||||
@@ -29,8 +29,8 @@ query_birth_names = {
|
||||
"row_limit": 100,
|
||||
"granularity": "ds",
|
||||
"time_range": "100 years ago : now",
|
||||
"timeseries_limit": 0,
|
||||
"timeseries_limit_metric": None,
|
||||
"series_limit": 0,
|
||||
"series_limit_metric": None,
|
||||
"order_desc": True,
|
||||
"filters": [
|
||||
{"col": "gender", "op": "==", "val": "boy"},
|
||||
|
||||
@@ -902,6 +902,9 @@ class TestPostChartDataApi(BaseTestChartDataApi):
|
||||
request_payload["queries"][0]["columns"] = ["foo", "bar", "state"]
|
||||
request_payload["queries"][0]["where"] = "':abc' != ':xyz:qwerty'"
|
||||
request_payload["queries"][0]["orderby"] = None
|
||||
request_payload["queries"][0]["granularity"] = (
|
||||
None # Virtual table has no time column
|
||||
)
|
||||
request_payload["queries"][0]["metrics"] = [
|
||||
{
|
||||
"expressionType": AdhocMetricExpressionType.SQL,
|
||||
@@ -1012,7 +1015,7 @@ class TestGetChartDataApi(BaseTestChartDataApi):
|
||||
"orderby": [["sum__num", False]],
|
||||
"annotation_layers": [],
|
||||
"row_limit": 50000,
|
||||
"timeseries_limit": 0,
|
||||
"series_limit": 0,
|
||||
"order_desc": True,
|
||||
"url_params": {},
|
||||
"custom_params": {},
|
||||
@@ -1065,7 +1068,7 @@ class TestGetChartDataApi(BaseTestChartDataApi):
|
||||
"orderby": [["sum__num", False]],
|
||||
"annotation_layers": [],
|
||||
"row_limit": 50000,
|
||||
"timeseries_limit": 0,
|
||||
"series_limit": 0,
|
||||
"order_desc": True,
|
||||
"url_params": {},
|
||||
"custom_params": {},
|
||||
@@ -1119,7 +1122,7 @@ class TestGetChartDataApi(BaseTestChartDataApi):
|
||||
"orderby": [["sum__num", False]],
|
||||
"annotation_layers": [],
|
||||
"row_limit": 50000,
|
||||
"timeseries_limit": 0,
|
||||
"series_limit": 0,
|
||||
"order_desc": True,
|
||||
"url_params": {},
|
||||
"custom_params": {},
|
||||
|
||||
@@ -124,17 +124,6 @@ class TestDatasource(SupersetTestCase):
|
||||
else:
|
||||
return
|
||||
|
||||
query_obj = {
|
||||
"columns": ["metric"],
|
||||
"filter": [],
|
||||
"from_dttm": datetime.now() - timedelta(days=1),
|
||||
"granularity": "additional_dttm",
|
||||
"orderby": [],
|
||||
"to_dttm": datetime.now() + timedelta(days=1),
|
||||
"series_columns": [],
|
||||
"row_limit": 1000,
|
||||
"row_offset": 0,
|
||||
}
|
||||
table = SqlaTable(
|
||||
table_name="dummy_sql_table",
|
||||
database=database,
|
||||
@@ -149,13 +138,28 @@ class TestDatasource(SupersetTestCase):
|
||||
sql=sql,
|
||||
)
|
||||
|
||||
from superset.common.query_object import QueryObject
|
||||
|
||||
query_obj = QueryObject(
|
||||
columns=["metric"],
|
||||
filters=[],
|
||||
from_dttm=datetime.now() - timedelta(days=1),
|
||||
granularity="additional_dttm",
|
||||
orderby=[],
|
||||
to_dttm=datetime.now() + timedelta(days=1),
|
||||
series_columns=[],
|
||||
row_limit=1000,
|
||||
row_offset=0,
|
||||
datasource=table,
|
||||
)
|
||||
|
||||
with create_and_cleanup_table(table):
|
||||
table.always_filter_main_dttm = False
|
||||
result = str(table.get_sqla_query(**query_obj).sqla_query.whereclause)
|
||||
result = str(table.get_sqla_query(query_obj).sqla_query.whereclause)
|
||||
assert "default_dttm" not in result and "additional_dttm" in result # noqa: PT018
|
||||
|
||||
table.always_filter_main_dttm = True
|
||||
result = str(table.get_sqla_query(**query_obj).sqla_query.whereclause)
|
||||
result = str(table.get_sqla_query(query_obj).sqla_query.whereclause)
|
||||
assert "default_dttm" in result and "additional_dttm" in result # noqa: PT018
|
||||
|
||||
def test_external_metadata_for_virtual_table(self):
|
||||
@@ -584,7 +588,11 @@ def test_get_samples_with_incorrect_cc(test_client, login_as_admin, virtual_data
|
||||
)
|
||||
rv = test_client.post(uri, json={})
|
||||
assert rv.status_code == 422
|
||||
assert rv.json["errors"][0]["error_type"] == "INVALID_SQL_ERROR"
|
||||
# The error handling returns a simple error message for CommandInvalidError
|
||||
assert "error" in rv.json
|
||||
assert (
|
||||
"DUMMY CC" in rv.json["error"]
|
||||
) # Check the error mentions the problematic column
|
||||
|
||||
|
||||
@with_feature_flags(ALLOW_ADHOC_SUBQUERY=True)
|
||||
|
||||
@@ -186,6 +186,6 @@ def _get_energy_slices():
|
||||
"xscale_interval": "1",
|
||||
"yscale_interval": "1",
|
||||
},
|
||||
"query_context": '{"datasource":{"id":12,"type":"table"},"force":false,"queries":[{"time_range":" : ","filters":[],"extras":{"time_grain_sqla":null,"having":"","where":""},"applied_time_extras":{},"columns":[],"metrics":[],"annotation_layers":[],"row_limit":5000,"timeseries_limit":0,"order_desc":true,"url_params":{},"custom_params":{},"custom_form_data":{}}],"result_format":"json","result_type":"full"}', # noqa: E501
|
||||
"query_context": '{"datasource":{"id":12,"type":"table"},"force":false,"queries":[{"time_range":" : ","filters":[],"extras":{"time_grain_sqla":null,"having":"","where":""},"applied_time_extras":{},"columns":[],"metrics":[],"annotation_layers":[],"row_limit":5000,"series_limit":0,"order_desc":true,"url_params":{},"custom_params":{},"custom_form_data":{}}],"result_format":"json","result_type":"full"}', # noqa: E501
|
||||
},
|
||||
]
|
||||
|
||||
@@ -591,7 +591,7 @@ chart_config: dict[str, Any] = {
|
||||
},
|
||||
"viz_type": "deck_path",
|
||||
},
|
||||
"query_context": '{"datasource":{"id":12,"type":"table"},"force":false,"queries":[{"time_range":" : ","filters":[],"extras":{"time_grain_sqla":null,"having":"","where":""},"applied_time_extras":{},"columns":[],"metrics":[],"annotation_layers":[],"row_limit":5000,"timeseries_limit":0,"order_desc":true,"url_params":{},"custom_params":{},"custom_form_data":{}}],"result_format":"json","result_type":"full"}', # noqa: E501
|
||||
"query_context": '{"datasource":{"id":12,"type":"table"},"force":false,"queries":[{"time_range":" : ","filters":[],"extras":{"time_grain_sqla":null,"having":"","where":""},"applied_time_extras":{},"columns":[],"metrics":[],"annotation_layers":[],"row_limit":5000,"series_limit":0,"order_desc":true,"url_params":{},"custom_params":{},"custom_form_data":{}}],"result_format":"json","result_type":"full"}', # noqa: E501
|
||||
"cache_timeout": None,
|
||||
"uuid": "0c23747a-6528-4629-97bf-e4b78d3b9df1",
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -294,14 +294,17 @@ class TestQueryContext(SupersetTestCase):
|
||||
payload = get_query_context("birth_names")
|
||||
columns = payload["queries"][0]["columns"]
|
||||
payload["queries"][0]["groupby"] = columns
|
||||
payload["queries"][0]["timeseries_limit"] = 99
|
||||
payload["queries"][0]["timeseries_limit_metric"] = "sum__num"
|
||||
payload["queries"][0]["series_limit"] = 99
|
||||
payload["queries"][0]["series_limit_metric"] = "sum__num"
|
||||
del payload["queries"][0]["columns"]
|
||||
# Remove granularity so granularity_sqla can be used
|
||||
del payload["queries"][0]["granularity"]
|
||||
payload["queries"][0]["granularity_sqla"] = "timecol"
|
||||
payload["queries"][0]["having_filters"] = [{"col": "a", "op": "==", "val": "b"}]
|
||||
query_context = ChartDataQueryContextSchema().load(payload)
|
||||
assert len(query_context.queries) == 1
|
||||
query_object = query_context.queries[0]
|
||||
# granularity should be set from granularity_sqla since granularity was not set
|
||||
assert query_object.granularity == "timecol"
|
||||
assert query_object.columns == columns
|
||||
assert query_object.series_limit == 99
|
||||
@@ -520,7 +523,7 @@ class TestQueryContext(SupersetTestCase):
|
||||
payload["queries"][0]["metrics"] = ["sum__num"]
|
||||
payload["queries"][0]["groupby"] = ["name"]
|
||||
payload["queries"][0]["is_timeseries"] = True
|
||||
payload["queries"][0]["timeseries_limit"] = 5
|
||||
payload["queries"][0]["series_limit"] = 5
|
||||
payload["queries"][0]["time_offsets"] = ["1 year ago", "1 year later"]
|
||||
payload["queries"][0]["time_range"] = "1990 : 1991"
|
||||
query_context = ChartDataQueryContextSchema().load(payload)
|
||||
@@ -556,7 +559,7 @@ class TestQueryContext(SupersetTestCase):
|
||||
# due to "name" is random generated, each time_offset slice will be empty
|
||||
payload["queries"][0]["groupby"] = ["name"]
|
||||
payload["queries"][0]["is_timeseries"] = True
|
||||
payload["queries"][0]["timeseries_limit"] = 5
|
||||
payload["queries"][0]["series_limit"] = 5
|
||||
payload["queries"][0]["time_offsets"] = []
|
||||
payload["queries"][0]["time_range"] = "1990 : 1991"
|
||||
payload["queries"][0]["granularity"] = "ds"
|
||||
@@ -609,7 +612,7 @@ class TestQueryContext(SupersetTestCase):
|
||||
payload["queries"][0]["metrics"] = ["sum__num"]
|
||||
payload["queries"][0]["groupby"] = ["state"]
|
||||
payload["queries"][0]["is_timeseries"] = True
|
||||
payload["queries"][0]["timeseries_limit"] = 5
|
||||
payload["queries"][0]["series_limit"] = 5
|
||||
payload["queries"][0]["time_offsets"] = []
|
||||
payload["queries"][0]["time_range"] = "1980 : 1991"
|
||||
payload["queries"][0]["granularity"] = "ds"
|
||||
@@ -638,9 +641,11 @@ class TestQueryContext(SupersetTestCase):
|
||||
def test_time_offsets_accuracy(self):
|
||||
payload = get_query_context("birth_names")
|
||||
payload["queries"][0]["metrics"] = ["sum__num"]
|
||||
payload["queries"][0]["groupby"] = ["state"]
|
||||
payload["queries"][0]["columns"] = [
|
||||
"state"
|
||||
] # Use columns instead of deprecated groupby
|
||||
payload["queries"][0]["is_timeseries"] = True
|
||||
payload["queries"][0]["timeseries_limit"] = 5
|
||||
payload["queries"][0]["series_limit"] = 5
|
||||
payload["queries"][0]["time_offsets"] = []
|
||||
payload["queries"][0]["time_range"] = "1980 : 1991"
|
||||
payload["queries"][0]["granularity"] = "ds"
|
||||
@@ -713,10 +718,10 @@ class TestQueryContext(SupersetTestCase):
|
||||
"sqlExpression": "ds",
|
||||
"label": "ds",
|
||||
"expressionType": "SQL",
|
||||
}
|
||||
},
|
||||
"name", # Add name to columns instead of using deprecated groupby
|
||||
]
|
||||
payload["queries"][0]["metrics"] = ["sum__num"]
|
||||
payload["queries"][0]["groupby"] = ["name"]
|
||||
payload["queries"][0]["is_timeseries"] = True
|
||||
payload["queries"][0]["row_limit"] = 100
|
||||
payload["queries"][0]["row_offset"] = 10
|
||||
|
||||
@@ -33,6 +33,7 @@ from sqlalchemy.sql import text
|
||||
from sqlalchemy.sql.elements import TextClause
|
||||
|
||||
from superset import db
|
||||
from superset.common.query_object import QueryObject
|
||||
from superset.connectors.sqla.models import SqlaTable, TableColumn, SqlMetric
|
||||
from superset.constants import EMPTY_STRING, NULL_STRING
|
||||
from superset.db_engine_specs.bigquery import BigQueryEngineSpec
|
||||
@@ -162,7 +163,7 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
"count_timegrain",
|
||||
],
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
"extras": {"time_grain_sqla": "P1D"},
|
||||
}
|
||||
|
||||
@@ -186,7 +187,8 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
sqla_query = table.get_sqla_query(**base_query_obj)
|
||||
query_object = QueryObject(datasource=table, **base_query_obj)
|
||||
sqla_query = table.get_sqla_query(query_object)
|
||||
query = table.database.compile_sqla_query(sqla_query.sqla_query)
|
||||
|
||||
# assert virtual dataset
|
||||
@@ -234,12 +236,13 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
},
|
||||
],
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
"extras": {"time_grain_sqla": "P1D"},
|
||||
}
|
||||
mock_dataset_id_from_context.return_value = table.id
|
||||
|
||||
sqla_query = table.get_sqla_query(**base_query_obj)
|
||||
query_object = QueryObject(datasource=table, **base_query_obj)
|
||||
sqla_query = table.get_sqla_query(query_object)
|
||||
query = table.database.compile_sqla_query(sqla_query.sqla_query)
|
||||
|
||||
database = table.database
|
||||
@@ -267,7 +270,7 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
}
|
||||
],
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
}
|
||||
|
||||
table = SqlaTable(
|
||||
@@ -275,8 +278,9 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
query_object = QueryObject(datasource=table, **base_query_obj)
|
||||
with pytest.raises(QueryObjectValidationError):
|
||||
table.get_sqla_query(**base_query_obj)
|
||||
table.get_sqla_query(query_object)
|
||||
# Cleanup
|
||||
db.session.delete(table)
|
||||
db.session.commit()
|
||||
@@ -310,7 +314,7 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
"groupby": ["gender"],
|
||||
"metrics": ["count"],
|
||||
"is_timeseries": False,
|
||||
"filter": [
|
||||
"filters": [
|
||||
{
|
||||
"col": filter_.column,
|
||||
"op": filter_.operator,
|
||||
@@ -319,7 +323,8 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
],
|
||||
"extras": {},
|
||||
}
|
||||
sqla_query = table.get_sqla_query(**query_obj)
|
||||
query_object = QueryObject(datasource=table, **query_obj)
|
||||
sqla_query = table.get_sqla_query(query_object)
|
||||
sql = table.database.compile_sqla_query(sqla_query.sqla_query)
|
||||
if isinstance(filter_.expected, list):
|
||||
assert any([candidate in sql for candidate in filter_.expected]) # noqa: C419
|
||||
@@ -344,7 +349,7 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
"groupby": ["boolean_gender"],
|
||||
"metrics": ["count"],
|
||||
"is_timeseries": False,
|
||||
"filter": [
|
||||
"filters": [
|
||||
{
|
||||
"col": "boolean_gender",
|
||||
"op": FilterOperator.IN,
|
||||
@@ -353,7 +358,8 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
],
|
||||
"extras": {},
|
||||
}
|
||||
sqla_query = table.get_sqla_query(**query_obj)
|
||||
query_object = QueryObject(datasource=table, **query_obj)
|
||||
sqla_query = table.get_sqla_query(query_object)
|
||||
sql = table.database.compile_sqla_query(sqla_query.sqla_query)
|
||||
dialect = table.database.get_dialect()
|
||||
operand = "(true, false)"
|
||||
@@ -371,7 +377,7 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
"groupby": ["user"],
|
||||
"metrics": [],
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
"extras": {},
|
||||
}
|
||||
|
||||
@@ -383,8 +389,9 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
)
|
||||
# TODO(villebro): make it work with presto
|
||||
if get_example_database().backend != "presto":
|
||||
query_object = QueryObject(datasource=table, **query_obj)
|
||||
with pytest.raises(QueryObjectValidationError):
|
||||
table.get_sqla_query(**query_obj)
|
||||
table.get_sqla_query(query_object)
|
||||
|
||||
def test_query_format_strip_trailing_semicolon(self):
|
||||
query_obj = {
|
||||
@@ -394,7 +401,7 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
"groupby": ["user"],
|
||||
"metrics": [],
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
"extras": {},
|
||||
}
|
||||
|
||||
@@ -403,7 +410,8 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
sql="SELECT * from test_table;",
|
||||
database=get_example_database(),
|
||||
)
|
||||
sqlaq = table.get_sqla_query(**query_obj)
|
||||
query_object = QueryObject(datasource=table, **query_obj)
|
||||
sqlaq = table.get_sqla_query(query_object)
|
||||
sql = table.database.compile_sqla_query(sqlaq.sqla_query)
|
||||
assert sql[-1] != ";"
|
||||
|
||||
@@ -415,7 +423,7 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
"groupby": ["grp"],
|
||||
"metrics": [],
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
}
|
||||
|
||||
table = SqlaTable(
|
||||
@@ -425,8 +433,9 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
)
|
||||
|
||||
query_obj = dict(**base_query_obj, extras={})
|
||||
query_object = QueryObject(datasource=table, **query_obj)
|
||||
with pytest.raises(QueryObjectValidationError):
|
||||
table.get_sqla_query(**query_obj)
|
||||
table.get_sqla_query(query_object)
|
||||
|
||||
def test_dml_statement_raises_exception(self):
|
||||
base_query_obj = {
|
||||
@@ -436,7 +445,7 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
"groupby": ["grp"],
|
||||
"metrics": [],
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
}
|
||||
|
||||
table = SqlaTable(
|
||||
@@ -446,8 +455,9 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
)
|
||||
|
||||
query_obj = dict(**base_query_obj, extras={})
|
||||
query_object = QueryObject(datasource=table, **query_obj)
|
||||
with pytest.raises(QueryObjectValidationError):
|
||||
table.get_sqla_query(**query_obj)
|
||||
table.get_sqla_query(query_object)
|
||||
|
||||
def test_fetch_metadata_for_updated_virtual_table(self):
|
||||
table = SqlaTable(
|
||||
@@ -507,7 +517,7 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
}
|
||||
],
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
"extras": {},
|
||||
}
|
||||
|
||||
@@ -516,7 +526,8 @@ class TestDatabaseModel(SupersetTestCase):
|
||||
db.session.add(database)
|
||||
db.session.add(table)
|
||||
db.session.commit()
|
||||
sqlaq = table.get_sqla_query(**query_obj)
|
||||
query_object = QueryObject(datasource=table, **query_obj)
|
||||
sqlaq = table.get_sqla_query(query_object)
|
||||
assert sqlaq.labels_expected == ["user", "COUNT_DISTINCT(user)"]
|
||||
sql = table.database.compile_sqla_query(sqlaq.sqla_query)
|
||||
assert "COUNT_DISTINCT_user__00db1" in sql
|
||||
@@ -585,7 +596,7 @@ def test_filter_on_text_column(text_column_table):
|
||||
result_object = table.query(
|
||||
{
|
||||
"metrics": ["count"],
|
||||
"filter": [{"col": "foo", "val": [NULL_STRING], "op": "IN"}],
|
||||
"filters": [{"col": "foo", "val": [NULL_STRING], "op": "IN"}],
|
||||
"is_timeseries": False,
|
||||
}
|
||||
)
|
||||
@@ -595,7 +606,7 @@ def test_filter_on_text_column(text_column_table):
|
||||
result_object = table.query(
|
||||
{
|
||||
"metrics": ["count"],
|
||||
"filter": [{"col": "foo", "val": [None], "op": "IN"}],
|
||||
"filters": [{"col": "foo", "val": [None], "op": "IN"}],
|
||||
"is_timeseries": False,
|
||||
}
|
||||
)
|
||||
@@ -605,7 +616,7 @@ def test_filter_on_text_column(text_column_table):
|
||||
result_object = table.query(
|
||||
{
|
||||
"metrics": ["count"],
|
||||
"filter": [{"col": "foo", "val": [EMPTY_STRING], "op": "IN"}],
|
||||
"filters": [{"col": "foo", "val": [EMPTY_STRING], "op": "IN"}],
|
||||
"is_timeseries": False,
|
||||
}
|
||||
)
|
||||
@@ -615,7 +626,7 @@ def test_filter_on_text_column(text_column_table):
|
||||
result_object = table.query(
|
||||
{
|
||||
"metrics": ["count"],
|
||||
"filter": [{"col": "foo", "val": [""], "op": "IN"}],
|
||||
"filters": [{"col": "foo", "val": [""], "op": "IN"}],
|
||||
"is_timeseries": False,
|
||||
}
|
||||
)
|
||||
@@ -625,7 +636,7 @@ def test_filter_on_text_column(text_column_table):
|
||||
result_object = table.query(
|
||||
{
|
||||
"metrics": ["count"],
|
||||
"filter": [
|
||||
"filters": [
|
||||
{
|
||||
"col": "foo",
|
||||
"val": [EMPTY_STRING, NULL_STRING, "null", "foo"],
|
||||
@@ -641,7 +652,7 @@ def test_filter_on_text_column(text_column_table):
|
||||
result_object = table.query(
|
||||
{
|
||||
"metrics": ["count"],
|
||||
"filter": [
|
||||
"filters": [
|
||||
{
|
||||
"col": "foo",
|
||||
"val": ['"text in double quotes"'],
|
||||
@@ -657,7 +668,7 @@ def test_filter_on_text_column(text_column_table):
|
||||
result_object = table.query(
|
||||
{
|
||||
"metrics": ["count"],
|
||||
"filter": [
|
||||
"filters": [
|
||||
{
|
||||
"col": "foo",
|
||||
"val": ["'text in single quotes'"],
|
||||
@@ -673,7 +684,7 @@ def test_filter_on_text_column(text_column_table):
|
||||
result_object = table.query(
|
||||
{
|
||||
"metrics": ["count"],
|
||||
"filter": [
|
||||
"filters": [
|
||||
{
|
||||
"col": "foo",
|
||||
"val": ['double quotes " in text'],
|
||||
@@ -689,7 +700,7 @@ def test_filter_on_text_column(text_column_table):
|
||||
result_object = table.query(
|
||||
{
|
||||
"metrics": ["count"],
|
||||
"filter": [
|
||||
"filters": [
|
||||
{
|
||||
"col": "foo",
|
||||
"val": ["single quotes ' in text"],
|
||||
@@ -726,7 +737,7 @@ def test_should_generate_closed_and_open_time_filter_range(login_as_admin):
|
||||
{
|
||||
"metrics": ["count"],
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
"from_dttm": datetime(2022, 1, 1),
|
||||
"to_dttm": datetime(2023, 1, 1),
|
||||
"granularity": "datetime_col",
|
||||
@@ -763,7 +774,7 @@ def test_none_operand_in_filter(login_as_admin, physical_dataset):
|
||||
result = physical_dataset.query(
|
||||
{
|
||||
"metrics": ["count"],
|
||||
"filter": [{"col": "col4", "val": None, "op": expected["operator"]}],
|
||||
"filters": [{"col": "col4", "val": None, "op": expected["operator"]}],
|
||||
"is_timeseries": False,
|
||||
}
|
||||
)
|
||||
@@ -782,7 +793,7 @@ def test_none_operand_in_filter(login_as_admin, physical_dataset):
|
||||
physical_dataset.query(
|
||||
{
|
||||
"metrics": ["count"],
|
||||
"filter": [{"col": "col4", "val": None, "op": flt.value}],
|
||||
"filters": [{"col": "col4", "val": None, "op": flt.value}],
|
||||
"is_timeseries": False,
|
||||
}
|
||||
)
|
||||
@@ -871,7 +882,7 @@ def test_extra_cache_keys(
|
||||
"groupby": ["id", "username", "email"],
|
||||
"metrics": [],
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
}
|
||||
|
||||
query_obj = dict(**base_query_obj, extras={})
|
||||
@@ -917,7 +928,7 @@ def test_extra_cache_keys_in_sql_expression(
|
||||
"groupby": ["id", "username", "email"],
|
||||
"metrics": [],
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
}
|
||||
|
||||
query_obj = dict(**base_query_obj, extras={"where": sql_expression})
|
||||
@@ -960,7 +971,7 @@ def test_extra_cache_keys_in_adhoc_metrics_and_columns(
|
||||
"metrics": [],
|
||||
"columns": [],
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
}
|
||||
|
||||
items: dict[str, Any] = {
|
||||
@@ -1014,7 +1025,7 @@ def test_extra_cache_keys_in_dataset_metrics_and_columns(
|
||||
"columns": ["username"],
|
||||
"metrics": ["variable_profit"],
|
||||
"is_timeseries": False,
|
||||
"filter": [],
|
||||
"filters": [],
|
||||
}
|
||||
|
||||
extra_cache_keys = table.get_extra_cache_keys(query_obj)
|
||||
@@ -1118,7 +1129,7 @@ def test__temporal_range_operator_in_adhoc_filter(physical_dataset):
|
||||
result = physical_dataset.query(
|
||||
{
|
||||
"columns": ["col1", "col2"],
|
||||
"filter": [
|
||||
"filters": [
|
||||
{
|
||||
"col": "col5",
|
||||
"val": "2000-01-05 : 2000-01-06",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
115
tests/unit_tests/queries/test_query_object_prequery.py
Normal file
115
tests/unit_tests/queries/test_query_object_prequery.py
Normal file
@@ -0,0 +1,115 @@
|
||||
# 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.
|
||||
from datetime import datetime
|
||||
|
||||
from superset.common.query_object import QueryObject
|
||||
|
||||
|
||||
def test_get_series_limit_prequery_obj():
|
||||
"""
|
||||
Test get_series_limit_prequery_obj method
|
||||
"""
|
||||
# Create a QueryObject with series limit settings
|
||||
query_object = QueryObject(
|
||||
columns=["country", "year"],
|
||||
metrics=["sum__sales"],
|
||||
series_limit=10,
|
||||
from_dttm=datetime(2020, 1, 1),
|
||||
to_dttm=datetime(2021, 1, 1),
|
||||
filters=[{"col": "region", "op": "IN", "val": ["US", "EU"]}],
|
||||
extras={"time_grain_sqla": "P1D"},
|
||||
order_desc=False,
|
||||
)
|
||||
|
||||
# Test basic prequery object creation
|
||||
prequery_obj = query_object.get_series_limit_prequery_obj(
|
||||
granularity="ds",
|
||||
inner_from_dttm=None,
|
||||
inner_to_dttm=None,
|
||||
)
|
||||
|
||||
assert prequery_obj["is_timeseries"] is False
|
||||
assert prequery_obj["row_limit"] == 10
|
||||
assert prequery_obj["metrics"] == ["sum__sales"]
|
||||
assert prequery_obj["granularity"] == "ds"
|
||||
assert prequery_obj["groupby"] == ["country", "year"]
|
||||
assert prequery_obj["from_dttm"] == datetime(2020, 1, 1)
|
||||
assert prequery_obj["to_dttm"] == datetime(2021, 1, 1)
|
||||
assert prequery_obj["filter"] == [
|
||||
{"col": "region", "op": "IN", "val": ["US", "EU"]}
|
||||
]
|
||||
assert prequery_obj["orderby"] == []
|
||||
assert prequery_obj["extras"] == {"time_grain_sqla": "P1D"}
|
||||
assert prequery_obj["order_desc"] is True # Always True for prequery
|
||||
|
||||
|
||||
def test_get_series_limit_prequery_obj_with_overrides():
|
||||
"""
|
||||
Test get_series_limit_prequery_obj with inner dates and orderby override
|
||||
"""
|
||||
query_object = QueryObject(
|
||||
columns=["country"],
|
||||
metrics=["count"],
|
||||
series_limit=5,
|
||||
from_dttm=datetime(2020, 1, 1),
|
||||
to_dttm=datetime(2021, 1, 1),
|
||||
)
|
||||
|
||||
# Test with inner dates and custom orderby
|
||||
inner_from = datetime(2020, 6, 1)
|
||||
inner_to = datetime(2020, 12, 31)
|
||||
custom_orderby = [("sum__revenue", False)]
|
||||
|
||||
prequery_obj = query_object.get_series_limit_prequery_obj(
|
||||
granularity="date_col",
|
||||
inner_from_dttm=inner_from,
|
||||
inner_to_dttm=inner_to,
|
||||
orderby=custom_orderby,
|
||||
)
|
||||
|
||||
assert prequery_obj["from_dttm"] == inner_from
|
||||
assert prequery_obj["to_dttm"] == inner_to
|
||||
assert prequery_obj["orderby"] == custom_orderby
|
||||
|
||||
|
||||
def test_get_series_limit_prequery_obj_base_axis_filtering():
|
||||
"""
|
||||
Test that base axis columns are filtered out in prequery
|
||||
"""
|
||||
# Mock the x-axis column with proper structure for base axis
|
||||
query_object = QueryObject(
|
||||
columns=[
|
||||
{
|
||||
"label": "__timestamp",
|
||||
"sqlExpression": "__timestamp",
|
||||
"columnType": "BASE_AXIS",
|
||||
},
|
||||
"country",
|
||||
"city",
|
||||
],
|
||||
metrics=["revenue"],
|
||||
series_limit=20,
|
||||
)
|
||||
|
||||
prequery_obj = query_object.get_series_limit_prequery_obj(
|
||||
granularity=None,
|
||||
inner_from_dttm=None,
|
||||
inner_to_dttm=None,
|
||||
)
|
||||
|
||||
# The columns in prequery should exclude the base axis column
|
||||
assert prequery_obj["columns"] == ["country", "city"]
|
||||
Reference in New Issue
Block a user