You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by be...@apache.org on 2026/02/04 14:58:30 UTC
(superset) branch engine-manager updated (259eeb2a680 -> c00fae53a52)
This is an automated email from the ASF dual-hosted git repository.
beto pushed a change to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
omit 259eeb2a680 ruff format
omit a7504f7b938 Merge remote-tracking branch 'origin/master' into engine-manager
omit 5938195c9b0 Fix connect_args, poolclass, and teardown bugs
omit 4f1c43991b5 Fix tests
omit 951754b913a Fix poolclass check
omit d9e798def9c Fix more tests
omit 1a5c3d0b4f5 Simplify key generation
omit fc23c3cd0b1 Update existing tests
omit 4256dba4659 Hash key
omit 757f17595b1 Small improvements
omit 8e6585c06c4 Cleanup
omit 937ea5531fa Connecting
omit 601e3f9ef31 Add extension
omit 405e4155cf9 Cleanup locks
omit 16ef84ffd48 feat: engine manager
add 12a266fd2f0 fix(trino): update query progress using cursor stats (#36872)
add cedc35e39f5 fix(SQLLab): remove error icon displayed when writing Jinja SQL even when the script is correct (#36422)
add f9fde87e858 feat(native filters): add filter type icons in config modal (#36905)
add 1949d1bb966 feat(dev): add make ports and make open commands (#36906)
add aaa174f8202 docs: add Netlify configuration for PR deploy previews (#36908)
add 9aff89c1b4b chore(frontend): migrate SqlLab and explore JS/JSX files to TypeScript (#36760)
add 9968393e4c3 feat(ci): add container security scan for image built for commits merged to mainline (#36604)
add d7d94ba640e fix(ag-grid): Ag Grid Date Filter timezone correction (#36270)
add 861e5cd013e fix(security): enforce datasource access control in get_samples() (#36550)
add 5edaed2e5b6 fix(alerts): wrong alert trigger with custom query (#35871)
add 7a5441bc7a6 refactor: Remove unimplemented APIs from @apache-superset/core (#36952)
add 0c1edd45688 chore: Bump @apache-superset/core (0.0.1-rc9) (#36953)
add dfdf8e75d8d fix: handle undefined template variables safely in query rendering. (#35009)
add f9be2b816ae feat(docs): add resources admonition with external links (#36761)
add 64ee48f1479 fix(mcp): use chart.query_context for get_chart_data like the API does (#36937)
add 6f67b053754 chore: Update INTHEWILD.yaml for PlaidCloud (#36741)
add 03caa7b3373 docs(INTHEWILD): add Cirrus Assessment (#36871)
add 84279acd2fb feat(mcp): add unified get_schema tool for schema discovery (#36458)
add a6d85dccf8f fix: streaming export losing g context (#36950)
add 16e046b5d99 docs(INTHEWILD): add HOLLYLAND猛玛 (#36968)
add 70b2e520b29 chore(frontend): Update $schema path in oxlint.json (#36859)
add 337eb3baf5c fix(dashboard): prevent PublishedLabel overlap in non-English languages (#36877)
add 047360641a1 feat(mcp): change save_chart default to False for preview-first workflow (#36935)
add 4249b8ee6a2 chore(deps-dev): bump eslint-plugin-react-you-might-not-need-an-effect from 0.5.1 to 0.8.3 in /superset-frontend (#36972)
add 3ac76e116d9 chore: Update INTHEWILD.yaml (#36733)
add e9b91f6ec92 chore(deps): bump ioredis from 5.8.2 to 5.9.0 in /superset-websocket (#36920)
add 77ebc5ac101 chore(deps-dev): bump @typescript-eslint/parser from 8.51.0 to 8.52.0 in /docs (#36918)
add dd7f0c4224d chore(deps-dev): bump typescript-eslint from 8.50.1 to 8.52.0 in /superset-websocket (#36916)
add af185ad8c98 chore(deps-dev): bump jsdom from 27.0.0 to 27.4.0 in /superset-frontend (#36922)
add 4fc8157d6fc chore(deps-dev): bump source-map from 0.7.4 to 0.7.6 in /superset-frontend (#36653)
add 85cf46dc1cd fix(mcp): remove unused ctx parameter from health_check tool (#36992)
add 734d64081f5 chore(deps): bump caniuse-lite from 1.0.30001762 to 1.0.30001763 in /docs (#36970)
add ffbf81e952c chore(deps): bump ws from 8.18.3 to 8.19.0 in /superset-websocket (#36919)
add 07aa7622f75 chore(deps): bump react-resizable from 3.0.5 to 3.1.3 in /superset-frontend (#36911)
add 57f8f502928 chore(deps-dev): bump @typescript-eslint/eslint-plugin from 8.50.1 to 8.51.0 in /docs (#36898)
add 413dfc98ff1 chore(deps): bump @storybook/addon-docs from 8.6.14 to 8.6.15 in /docs (#36824)
add f48322c17d8 chore: bump flask-cors to 6.0.2 (#36640)
add ea90d1f1416 refactor(db-engine-specs): use standard OAuth 2.0 params in base class (#37010)
add f895250cf92 fix(mypy): add overrides for superset-core local dev consistency (#36907)
add 14c0cad0ba2 chore: remove deprecated function (#37021)
add 1e8d648f478 feat: Chart query last run timestamp (#36934)
add 53dddf4db26 feat(embedded-sdk): Add resolvePermalinkUrl callback for custom permalink URLs (#36924)
add ecefba5bf72 fix(mcp): resolve startup failures from circular DAO imports (#37023)
add 1cface15e65 docs: Update INTHEWILD.yaml (#36888)
add 0294c30c9e6 fix(Tabs): prevent infinite rerenders with nested tabs (#37018)
add e9b6791ffb2 refactor: move translations and logging to new core (#36929)
add 4f5789abfe8 fix(reports): Use authenticated user as recipient for chart/dashboard reports (#36981)
add d914b35cc04 fix(translations): post merge fix for new translations addded (#37041)
add 459b4cb23dd perf(date_parser): bound regex quantifiers for deterministic parsing performance (#36983)
add 4f444ae1d23 feat(ag-grid): Server Side Filtering for Column Level Filters (#35683)
add 62c7b48b5c2 fix(extensions-cli): fix dev mode error (#37024)
add 169d27c9e97 fix(extensions): gracefully handle dist directory rebuilding (#37025)
add 911d72c9578 fix(models): prevent SQLAlchemy and_() deprecation warning (#37020)
add d8f7ae83ee6 fix(sqlglot): use Athena dialect for awsathena parsing (#36747)
add dcdcf889697 chore(chart): rollback legend top alignment to the right (#36994)
add f4772a93838 chore(deps-dev): bump webpack-bundle-analyzer from 5.1.0 to 5.1.1 in /superset-frontend (#37006)
add 2a38ce001e0 fix(deckgl): remove visibility condition in deckgl stroke color (#37029)
add 005b2af9853 chore(deps-dev): bump @types/lodash from 4.17.21 to 4.17.23 in /superset-websocket (#37045)
add 4fe20855961 chore(deps): bump caniuse-lite from 1.0.30001763 to 1.0.30001764 in /docs (#37049)
add fac5d2bcb65 feat(db): add dynamic schema support for athena (#36003)
add 22cfc4536b1 fix(export): URL prefix handling for subdirectory deployments (#36771)
add 72c69e2ca66 chore(deps): bump fs-extra from 11.3.0 to 11.3.3 in /superset-frontend (#37001)
add d56bc5826fe chore(deps-dev): bump @applitools/eyes-storybook from 3.60.0 to 3.63.4 in /superset-frontend (#37003)
add adb575be2f1 chore(deps-dev): bump typescript-eslint from 8.50.1 to 8.52.0 in /docs (#36913)
add fa3d4a75caf chore(deps): bump actions/download-artifact from 6 to 7 (#36699)
add 7503ee4e092 feat(sqllab): primary/secondary action extensions (#36644)
add 462fffc23c8 fix(docs): Update references from LLMS.md to AGENTS.md (#37060)
add 481bfa0f680 feat(starrocks): add catalog support for StarRocks database connections (#37026)
add 5f582417958 fix(explore): dispatch NumberControl value on blur to allow field clearing (#37007)
add ad3812edd7b fix(table): keep d3-format semantics when applying currency formatting (#37039)
add 936e37bd024 chore(deps-dev): bump @babel/node from 7.28.0 to 7.28.6 in /superset-frontend (#37089)
add e17bfae6bd1 chore(deps-dev): bump webpack from 5.103.0 to 5.104.1 in /superset-frontend (#37083)
add fc67569cd48 fix(datasets): ui bug fixes (#37058)
add 23a47e2f5a5 chore(deps-dev): bump webpack-dev-server from 5.2.2 to 5.2.3 in /superset-frontend (#37088)
add 3a3cbc29005 chore(deps-dev): bump @types/node from 25.0.3 to 25.0.7 in /superset-websocket (#37081)
add 6c753654278 chore(deps-dev): bump typescript-eslint from 8.52.0 to 8.53.0 in /docs (#37076)
add 5d5012aa9f8 chore(deps-dev): bump @types/lodash from 4.17.21 to 4.17.23 in /superset-frontend (#37077)
add 560da50df8e chore(deps): bump @babel/runtime from 7.28.4 to 7.28.6 in /superset-frontend (#37080)
add 393259bb9ef chore(deps-dev): bump typescript-eslint from 8.52.0 to 8.53.0 in /superset-websocket (#37082)
add 0b4fcce03b1 chore(deps-dev): bump cross-env from 10.0.0 to 10.1.0 in /superset-frontend (#36657)
add 1166df35792 docs(inthewild): Dark mode issues on InTheWild page (#37034)
add 0c490dc1abe docs(exploring-data): add overview to tutorial (#37028)
add fd3eea05571 chore(deps): bump lodash-es from 4.17.21 to 4.17.22 in /superset-frontend (#37048)
add 0404c99e392 fix(dashboard): revert cell hover and active colors to grayscale (#36991)
add e053418c970 fix(Matrixify): Do not clear values when saving (#37090)
add bdcc98743d8 chore(deps-dev): bump cheerio from 1.1.0 to 1.1.2 in /superset-frontend (#37002)
add 91dba9dcbf1 chore(deps-dev): bump @babel/types from 7.28.4 to 7.28.5 in /superset-frontend (#36652)
add 86bc4934235 chore(deps-dev): bump @babel/types from 7.28.4 to 7.28.6 in /superset-frontend (#37087)
add 4f3403b1341 chore(deps): bump antd from 6.1.2 to 6.2.0 in /docs (#37079)
add 35ac4c74fdd feat(dashboard): chart customizations modal and plugins (#36062)
add a2722532438 fix(table chart): time comparison totals returning null (#37111)
add 137ebdee392 fix(prune_logs): improve performance by using id column only for ordering log records when max_rows_per_run is provided (#37138)
add 4c01b5c3242 docs: Add UserGuiding INTHEWILD.yaml (#37030)
add debdfbc835a chore(deps-dev): bump @babel/plugin-transform-modules-commonjs from 7.27.1 to 7.28.6 in /superset-frontend (#37117)
add 64424f1625b chore(deps-dev): bump @types/node from 25.0.7 to 25.0.8 in /superset-websocket (#37116)
add 6ca0f7a9255 chore(deps-dev): bump @babel/cli from 7.28.3 to 7.28.6 in /superset-frontend (#37124)
add 2324b4c9e54 chore(deps-dev): bump @babel/preset-typescript from 7.26.0 to 7.28.5 in /superset-frontend (#36661)
add 6eb4db69307 fix(controls): Only initialize categorical control on numeric x axis (#37115)
add 23b91d22efd fix(charts): properly parse error responses in StatefulChart (#37130)
add 81fdc2bd0e9 feat(dates): adding handling for first of (#37098)
add c867d9379f6 chore(deps-dev): bump ts-loader from 9.5.2 to 9.5.4 in /superset-frontend (#36999)
add 96a3f2a187b chore(deps-dev): bump @babel/preset-react from 7.27.1 to 7.28.5 in /superset-frontend (#37047)
add 68e917c3f68 chore(deps-dev): bump eslint-plugin-testing-library from 7.14.0 to 7.15.4 in /superset-frontend (#37052)
add db61e4f62af chore(deps-dev): bump eslint-plugin-react-you-might-not-need-an-effect from 0.8.3 to 0.8.5 in /superset-frontend (#37054)
add 7f6cdc5616d chore(deps-dev): bump prettier from 3.7.4 to 3.8.0 in /superset-websocket (#37149)
add 4c267b7ee27 chore(deps-dev): bump prettier from 3.7.4 to 3.8.0 in /docs (#37152)
add f69cd43bd05 chore(deps-dev): bump eslint-plugin-prettier from 5.5.4 to 5.5.5 in /docs (#37153)
add c6ad0dbd3ac chore(deps-dev): bump prettier from 3.7.4 to 3.8.0 in /superset-frontend (#37156)
add 89380638b0e chore(deps): bump immer from 11.0.1 to 11.1.3 in /superset-frontend (#37159)
add 82d74d15ecd chore(deps): bump react-intersection-observer from 9.16.0 to 10.0.0 in /superset-frontend (#37162)
add 129b8e10a21 chore(deps-dev): bump @babel/core from 7.28.3 to 7.28.6 in /superset-frontend (#37154)
add e071e0c5a4a chore(deps-dev): bump eslint-plugin-prettier from 5.5.4 to 5.5.5 in /superset-frontend (#37160)
add b142f1956f0 chore(deps): bump chrono-node from 2.7.8 to 2.9.0 in /superset-frontend (#36632)
add 95c14b1fc17 chore(Matrixify): disable matrixify for incompatible viz types (#37163)
add 9555798d37d fix(data-zoom): Data-zoom not rendered properly in Matrixify (#37134)
add ab8df1ab345 fix: Move head_custom_extra above csrf token input (#37173)
add 54f19856de1 fix: HTML detection in tables (#37171)
add c30edaf075c feat: add tab select with save chart to dashboard (#36332)
add 4532ccf638d fix(mcp): push Flask app context in mcp_auth_hook for tool execution (#37190)
add d0783da3e55 fix(mcp): Handle big_number charts and make semantic warnings non-blocking (#37142)
add 2bcb66c2fc0 refactor(mcp): simplify single metric chart type check (#37215)
add 4b1d92e5752 feat: add droppable area to tab empty state (#37210)
add 896947c7874 fix(mcp): restore select_columns filtering in list tools (#37213)
add f4474b2e3e9 feat: Dynamic currency (#36416)
add b941be01cfc chore(deps-dev): bump @types/node from 25.0.8 to 25.0.9 in /superset-websocket (#37193)
add 33ff127370d chore(deps-dev): bump baseline-browser-mapping from 2.9.9 to 2.9.14 in /superset-frontend (#37198)
add f00f7d1c18f chore(deps): bump ioredis from 5.9.0 to 5.9.2 in /superset-websocket (#37194)
add a77c2d550c6 fix(mixed-timeseries): prevent duplicate legend entries (#37217)
add f984dca5cc4 fix(sunburst): make Show Total text theme-aware (#37177)
add 6453980d8d0 chore(deps): bump react-error-boundary from 6.0.0 to 6.1.0 in /superset-frontend (#37206)
add cee5ce13e0e chore(deps-dev): bump @types/node from 25.0.3 to 25.0.9 in /superset-frontend (#37196)
add a06e6eb6808 chore(deps): bump googleapis from 169.0.0 to 170.1.0 in /superset-frontend (#37195)
add 4d462c76bdc chore(deps-dev): bump @types/urijs from 1.19.25 to 1.19.26 in /superset-frontend (#36636)
add 476e454384d chore(deps): update @deck.gl/extensions requirement from ~9.2.2 to ~9.2.5 in /superset-frontend/plugins/legacy-preset-chart-deckgl (#36618)
add 39238ef8a9c feat(ag-grid-table): Enable Time Shift feature for AG Grid Table (#37072)
add 2e29e33dd8e fix(calendar-heatmap): correct month display across timezones (#37064)
add 2900258e051 fix(Dashboard): Auto-apply filters with default values when extraForm… (#36927)
add c0cbbe393a0 feat(Matrixify): add matrixify tag to list view and explore (#37247)
add 409cdad2648 fix(mcp): prevent MCP service from corrupting shared appbuilder singleton (#37252)
add 4393db57d9f fix(native-filters): enable Apply button when selecting Boolean FALSE value (#37017)
add f4597be341c fix(timeseries): x-axis last month was hidden (#37181)
add 1f19ef92cb9 refactor(TimezoneSelector): Enhance timezone selection logic and improve performance (#36486)
add 2d20079a884 fix(mcp): Remove screenshot URL functionality from MCP chart tools (#37228)
add 6e1718910f9 fix(sqllab): show virtual dataset banner only when isDataset param is true (#37184)
add fe16c828cf9 feat(mcp): Add support for AG Grid Interactive Table (ag-grid-table) viz_type (#37191)
add 2187fb4ab44 fix(transpile_query): Fix export_as_csv error: "transpile_to_dialect": ['Unknown field.'] (#37249)
add 50d0508a928 feat(mcp): Add Redis EventStore support for multi-pod deployments (#37216)
add e1fa3745174 feat(mcp): add time_grain parameter to XY chart generation (#37182)
add 3fba9678565 fix(delete-filter): deleted native filters are still shown until [sc-96553] (#37012)
add 25647942fd7 fix(chart): Horizontal bar chart value labels cut off (#36989)
add d0e80d2079b refactor: Redesigns the Results panel toolbar and enables extensions to contribute toolbar actions (#37255)
add 2267b78a10e chore(deps): bump antd from 6.2.0 to 6.2.1 in /docs (#37301)
add 445bc403b8c chore(deps): bump diff in /superset-frontend (#37292)
add 807ff513eff chore(deps): bump fs-extra from 11.3.2 to 11.3.3 in /superset-frontend (#37274)
add 281c0c96722 chore: add paths to backend extension stack traces (#37300)
add 238bebebece fix(extensions): prevent duplicate initialization of LOCAL_EXTENSIONS watcher (#37250)
add 801c84f0ef7 chore(deps-dev): bump typescript-eslint from 8.53.0 to 8.53.1 in /superset-websocket (#37268)
add 3fa7dba0946 chore(deps): bump diff from 5.2.0 to 5.2.2 in /docs (#37291)
add a1d24f1e4a3 chore(deps-dev): bump oxlint from 1.33.0 to 1.41.0 in /superset-frontend (#37279)
add 13013bbd64d chore(deps-dev): bump typescript-eslint from 8.53.0 to 8.53.1 in /docs (#37272)
add 2c1a33fd32c fix(roles): allow Public role to read themes (#37295)
add b460ca94c61 feat(docs): auto-generate database documentation from lib.py (#36805)
add 39d5511b294 chore(deps): bump csstype from 3.1.3 to 3.2.3 in /superset-frontend (#37157)
add a33f96b2fc1 chore(deps): bump geolib from 2.0.24 to 3.3.4 in /superset-frontend (#37205)
add 10ed60b4c11 chore(deps): bump caniuse-lite from 1.0.30001764 to 1.0.30001765 in /docs (#37238)
add 95169807d33 chore(deps): bump hot-shots from 12.1.0 to 13.0.0 in /superset-websocket (#37269)
add dc15feb83d6 chore(deps-dev): bump @babel/types from 7.28.4 to 7.28.6 in /superset-frontend (#37273)
add c564655f39a chore(deps): bump diff from 5.1.0 to 5.2.2 in /superset-frontend/cypress-base (#37296)
add 212559dab2e feat(table): add tooltip to table header (#37179)
add 23fec55e3d2 fix(native-filters): respect filter scope in nested tabs by prioritizing chartsInScope (#37139)
add 0fedfe03d52 feat(mcp): add stacked bar/area chart support (#37188)
add ec367915514 fix(mcp): resolve Flask app context error in MCP tools (#37309)
add dee063a4c5c feat(examples): Modernize example data loading with Parquet and YAML configs (#36538)
add 17d6f4ebc43 fix(table): align group headers correctly when filtering time compari… (#37236)
add d2907b2577c docs: federate scattered markdown files into centralized docs (#36756)
add daec3301274 fix: pin remark-gfm to v3.0.1 for compatibility with react-markdown v8 (#36388)
add 996e0e1e7ac fix(docs): remove broken /docs/databases redirect to nonexistent path (#37316)
add bb3452b43c7 chore(deps-dev): bump mini-css-extract-plugin from 2.9.4 to 2.10.0 in /superset-frontend (#37270)
add c3d5edbae9d chore(deps-dev): bump @babel/preset-env from 7.28.5 to 7.28.6 in /superset-frontend (#37158)
add d4723ef1161 feat: Handle OAuth2 dance with TableSelector (#37315)
add 02495a130fa chore(deps): bump lodash from 4.17.21 to 4.17.23 in /superset-websocket (#37321)
add 5e7fe81cfa5 chore(deps-dev): bump prettier from 3.8.0 to 3.8.1 in /docs (#37329)
add 02890283138 chore(deps-dev): bump baseline-browser-mapping from 2.9.14 to 2.9.17 in /superset-frontend (#37330)
add 3fa5bb41385 fix(echarts): restore dashed line style for time comparison series (#37135)
add aa37e96a028 chore(deps-dev): bump @babel/eslint-parser from 7.28.5 to 7.28.6 in /superset-frontend (#37331)
add febc5d54d52 chore(deps-dev): bump prettier-plugin-packagejson from 2.5.20 to 3.0.0 in /superset-frontend (#37340)
add 7f4c260cbed chore(deps): bump mapbox-gl from 3.17.0 to 3.18.0 in /superset-frontend (#37328)
add b576665f9a3 chore(deps): bump lodash-es from 4.17.22 to 4.17.23 in /superset-frontend (#37347)
add 7888da9e305 chore(deps): bump lodash from 4.17.21 to 4.17.23 in /docs (#37346)
add 2b647d23527 chore(deps-dev): bump @applitools/eyes-storybook from 3.63.4 to 3.63.8 in /superset-frontend (#37336)
add de6ac2a4440 chore(deps-dev): bump lightningcss from 1.30.2 to 1.31.1 in /superset-frontend (#37335)
add cc972cad5ac fix: DB OAuth2 fixes (#37350)
add fadab21493e fix(api): nan is not properly handled for athena connections (#37071)
add 9fabd7f997c docs: show Developer Portal in global navigation (#37313)
add b6308308419 fix(sqllab): add colorEditorSelection token for visible text selection (#36932)
add 87bbd54d0ac feat(examples): Transpile virtual dataset SQL on import (#37311)
add 3ca8c998abf docs(INTHEWILD): add club 25 de agosto (#37110)
add 760227d6305 fix(theme): migrate APP_NAME to brandAppName theme token with backward compatibility (#37370)
add b7a5b24a541 feat(docs): add auto-generated troubleshooting section to database pages (#37345)
add c6821cac6fd chore(deps-dev): bump prettier from 3.8.0 to 3.8.1 in /superset-websocket (#37325)
add b1ad54220ba fix: add sans-serif font fallback to fontFamily (Fixes #37096) (#37172)
add 04c5517206b fix(dataset-editor): add missing Data type label in calculated columns tab (#37165)
add 54919c942a2 fix(time-range-modal): time range modal for out of scope filter is not displayed correctly (#36996)
add f2b54e882dd fix: variable shadowing in test_connection command (#37397)
add 5e3acc20412 chore(deps): bump actions/upload-artifact from 4 to 6 (#37320)
add 01a9541a0ed chore(deps): bump dawidd6/action-download-artifact from 6 to 12 (#37324)
add 8e0c584a921 chore(deps-dev): bump prettier from 3.8.0 to 3.8.1 in /superset-frontend (#37339)
add 5f67fa45cec chore(deps): bump jquery from 3.7.1 to 4.0.0 in /superset-frontend (#37342)
add b81543c18c4 chore(deps): bump lodash from 4.17.21 to 4.17.23 in /superset-frontend/cypress-base (#37349)
add e1ce553b2bc chore(deps-dev): bump @types/node from 25.0.9 to 25.0.10 in /superset-websocket (#37351)
add ef395662aa0 chore(deps-dev): bump @babel/runtime-corejs3 from 7.28.4 to 7.28.6 in /superset-frontend (#37353)
add 7c69ec7f24e chore(deps): bump markdown-to-jsx from 7.7.4 to 9.6.0 in /superset-frontend (#37354)
add 790b79541b6 chore(deps-dev): bump globals from 17.0.0 to 17.1.0 in /superset-websocket (#37380)
add 6a467007217 chore(deps): bump caniuse-lite from 1.0.30001765 to 1.0.30001766 in /docs (#37381)
add eeb4065d7d7 chore(deps): bump unist-util-visit from 5.0.0 to 5.1.0 in /docs (#37383)
add 8951362852c chore(deps-dev): bump globals from 17.0.0 to 17.1.0 in /docs (#37385)
add e14931c368b chore(deps-dev): bump swc-loader from 0.2.6 to 0.2.7 in /superset-frontend (#37387)
add f83f9522218 chore(deps-dev): bump webpack-bundle-analyzer from 5.1.1 to 5.2.0 in /superset-frontend (#37388)
add bbab86a0b17 chore(deps-dev): bump @applitools/eyes-storybook from 3.63.8 to 3.63.9 in /superset-frontend (#37390)
add 56cf7a810ba chore(deps-dev): bump html-webpack-plugin from 5.6.5 to 5.6.6 in /superset-frontend (#37392)
add 429d9b27f6b chore: Update outdated GitHub Actions version (#37305)
add baaa8c5f543 feat(deckgl): add auto zoom option in deck gl multi layer (#37221)
add d823dfd2b91 feat: add interactive column sorting to pivot table (#36050)
add 5747fb1e850 feat(ListView): add pagination to card view and center row count display (#36288)
add e8363cf6061 fix(redshift): normalize table names to lowercase for CSV uploads (#37019)
add d6328fcb42f chore(deps): bump mapbox-gl from 3.18.0 to 3.18.1 in /superset-frontend (#37382)
add 34418d7e0b6 fix(datasets): respect application root in database management link (#36986)
add 39ebf7a7ad1 chore(websocket): sync Node version to LTS v22 (#37102)
add d54e227e25c chore: update old MotherDuck duckdb version to follow the official duckdb one (#36834)
add e4f649e49cd fix(superset-frontend): Fixes for broken functionality when an application root is defined (#36058)
add b99fc582e4e fix(chart): implement geohash decoding (#37027)
add 3580dc6cad7 chore(ts): Migrate Divider.jsx to Divider.tsx [SIP-36] (#36335)
add a60f8d761db chore(deps-dev): bump npm from 11.5.2 to 11.8.0 in /superset-frontend (#37352)
add 3a811d680d4 chore(deps): bump lodash from 4.17.21 to 4.17.23 in /superset-frontend (#37348)
add 3f37cdbf9c6 fix(database): include `configuration_method` in the DB export/import flow (#36958)
add 319a131ec91 fix(charts): missing globalOpacity prop with mapbox (#37168)
add 0ecc69d2f1c chore(deps): bump fastmcp from 2.14.0 to 2.14.3 (#37410)
add 2f81720603d chore(deps-dev): bump @swc/plugin-transform-imports from 10.0.0 to 12.4.0 in /superset-frontend (#37384)
add b09e60c1ec2 fix(deckgl-contour): prevent WebGL freeze by clamping and auto-scaling cellSize (#37244)
add 19d5fa86fc5 chore(deps-dev): bump baseline-browser-mapping from 2.9.17 to 2.9.18 in /superset-frontend (#37426)
add 09b5af5945f chore(deps): bump markdown-to-jsx from 9.6.0 to 9.6.1 in /superset-frontend (#37420)
add 04a9be04ab1 chore(deps-dev): bump @playwright/test from 1.57.0 to 1.58.0 in /superset-frontend (#37425)
add 470c593c3d8 chore(deps-dev): bump @types/node from 25.0.9 to 25.0.10 in /superset-frontend (#37418)
add ce84ab4ce26 chore(deps-dev): bump cheerio from 1.1.2 to 1.2.0 in /superset-frontend (#37423)
add c7cec198272 chore(deps): bump ace-builds from 1.43.5 to 1.43.6 in /superset-frontend (#37424)
add 8aebfe11059 fix: display correct icon for Multi Chart in quick switcher (#37256)
add 95a465ad7c1 feat(sqllab): add getActivePanel API for panel visibility tracking (#37448)
add f2fc5dec110 chore(deps): bump ag-grid-react from 34.3.1 to 35.0.1 in /superset-frontend (#37419)
add 695e295333a chore(deps): bump ag-grid-community from 34.3.1 to 35.0.1 in /superset-frontend (#37386)
add 89b998d6b78 chore(deps): bump react-intersection-observer from 10.0.0 to 10.0.2 in /superset-frontend (#37338)
add 647f21c26ad chore(deps): bump hot-shots from 13.0.0 to 13.1.0 in /superset-websocket (#37436)
add 0fd528c7afc feat(sqllab): Improved query status indicator bar (#36936)
add 4db6f9e04c0 chore(trino): Add progress_text for additional running state info (#36909)
add 26ac832138d chore(deps): update @luma.gl/engine requirement from ~9.2.4 to ~9.2.6 in /superset-frontend/plugins/legacy-preset-chart-deckgl (#37440)
add 290bcc1dbbb feat(cache): use configurable hash algorithm for flask-caching (#37361)
add 10a9b4bb947 fix: update Apache Superset website footer copyright year (#37435)
add 73d4332b511 feat(database): SIP-195 Add MongoDB database engine support (#37368)
add 7fc9974a7cf fix(deps): remove encodable dependency and pin query-string to fix Dependabot CI failures (#37450)
add 56ad429200d chore(deps): update @luma.gl/shadertools requirement from ~9.2.2 to ~9.2.6 in /superset-frontend/plugins/legacy-preset-chart-deckgl (#37237)
add bd419d19af1 chore(deps): update core-js requirement from ^3.38.1 to ^3.48.0 in /superset-frontend/packages/superset-ui-core (#37319)
add 97aea5d128d feat(i18n): complete Māori translation (#37443)
add e28d2782f14 chore(deps): bump core-js from 3.40.0 to 3.48.0 in /superset-frontend/packages/superset-ui-demo (#37439)
add dba75bd897f chore(deps): bump memoize-one from 5.2.1 to 6.0.0 in /superset-frontend/packages/superset-ui-demo (#37151)
add 8f6dd4aba02 chore(deps-dev): bump typescript-eslint from 8.53.1 to 8.54.0 in /docs (#37476)
add 58ab4e78ffe chore(deps): bump antd from 6.2.1 to 6.2.2 in /docs (#37470)
add 1681f74b2ea chore(direnv): add direnv to gitignore (#37481)
add 10a8d8b8ee4 chore(deps-dev): bump @typescript-eslint/parser from 8.53.1 to 8.54.0 in /superset-websocket (#37465)
add e4d71c2a554 docs(readme): sync database logos with auto-generated docs (#37463)
add a7e7cc30a97 fix(ag-grid-table): preserve time grain aggregation when temporal column casing changes (#36990)
add 5fa6925522d fix(dashboard): update chart customization UI text to "Display controls" (#37462)
add 27a4575f3e2 refactor: Move frontend related artifacts into frontend gitignore (#37496)
add 20da4eb86ec fix(sqllab): Over-rendering on result table (#30857)
add 2ec3aaaeea6 feat(dashboard): show dataset column labels in View as table (#37140)
add f787aec5673 docs: add Netlify to CI services footer and improve layout (#37451)
add b223f10ab51 chore(deps-dev): bump @typescript-eslint/eslint-plugin from 8.53.1 to 8.54.0 in /superset-websocket (#37468)
add 3bcd3b1683c chore(deps-dev): bump typescript-eslint from 8.53.1 to 8.54.0 in /superset-websocket (#37466)
add e6d0f97aaba fix(mcp): always filter list responses by columns_requested (#37505)
add 6663709a23a fix(mcp): tools not listed when JWT auth is enabled (#37377)
add 3347b9bf6cf fix(table): only show increase/decrease color options when time comparison enabled (#37362)
add da56bddadad chore(deps-dev): bump globals from 17.1.0 to 17.2.0 in /docs (#37509)
add 43653d1fa1b fix(dashboard): resolve dropdown popup positioning (#36963)
add ec2509a8b48 fix(dashboard-filters): prevent clearing all filters when editing a native filter (#37253)
add abf90de0cad fix(select): prevent bulk action buttons from being cut off in filters (#37453)
add cfeb7ccd318 chore(deps-dev): bump globals from 17.1.0 to 17.2.0 in /superset-websocket (#37508)
add 912538d176f docs: Updating quickstart for 6.0.0 (#37446)
add d4b89de0017 chore(deps-dev): bump oxlint from 1.41.0 to 1.42.0 in /superset-frontend (#37512)
add 073c3c72b42 docs: add HPE logo to In the Wild listing (#37506)
add ad0186093f1 docs: add interactive API reference using docusaurus-openapi-docs (#37434)
add f6f9e083acd fix(docs): replace identicon logos and deduplicate README database wall (#37500)
add bb5be6cf54a fix(matrixify): Rename Tag from 'Matrixify' to 'Matrixified' (#37402)
add 06e4f4ff4c9 fix(dashboard): catch DatasourceNotFound in get_datasets to prevent 404 (#37503)
add 042229bf80a fix(docs): add consistent dev-mode logging for Matomo page views (#37526)
add b3526fc4caa docs(community): redesign community page with card grid layout (#37536)
add 5fedb65bc05 fix(docs): migrate deprecated antd v6 APIs to items prop pattern (#37530)
add 73e095db8e2 docs(components): federate Storybook stories into Developer Portal MDX (#37502)
add 7110fc9cde7 fix(explore): remove extra spacing when Advanced Analytics section is hidden (#37456)
add 675a4c7a66d fix(charts): numerical column for the Point Radius field in mapbox (#36962)
add 6cb3cea960d feat(extensions): Allow replacing editors using extensions (#37499)
add 1501af06fe0 fix(Multilayer): preserve dashboard context for embedded (#37495)
add 810d6ff4807 fix(sqllab): Resolve stale closure bug causing text selection to break (#37550)
add 55ec1152ec0 fix(deckgl): change deck gl Path default line width unit to meters (#37248)
add 0b34363654a fix(mcp): Instance not bound to session error (#37548)
add 5a99588f579 fix(themes): correct action icons size and restore missing tooltips (#37409)
add 3ef33dcb766 feat(playwright): add documentation screenshot generator (#37494)
add 9c0337d092c fix(explore): correct `validationDependancies` typo to `validationDependencies` (#37554)
add c16e8f747ca chore(deps-dev): bump css-loader from 7.1.2 to 7.1.3 in /superset-frontend (#37544)
add d6029f5c8ac chore(deps): bump dependencies to address security vulnerabilities (#37552)
add ab4f646ef60 chore(deps): bump @babel/core from 7.28.5 to 7.28.6 in /docs (#37540)
add c7fd1a2f659 chore(deps-dev): bump @types/node from 25.0.10 to 25.1.0 in /superset-websocket (#37539)
add 2cce0308d4e fix: big number drill to details column data (#37068)
add fa79a467e41 chore(deps): bump googleapis from 170.1.0 to 171.0.0 in /superset-frontend (#37564)
add e67b1f53266 chore(deps-dev): bump baseline-browser-mapping from 2.9.18 to 2.9.19 in /superset-frontend (#37565)
add 652d029a2d9 chore(deps-dev): bump @types/node from 25.0.10 to 25.1.0 in /superset-frontend (#37563)
add 1f43138888e chore(deps): bump babel-loader from 9.2.1 to 10.0.0 in /docs (#37541)
add 66519c3a852 chore(deps-dev): bump fetch-mock from 11.1.5 to 12.6.0 in /superset-frontend/packages/superset-ui-core (#36662)
add 570cc3e5f8b feat(sqllab): treeview table selection ui (#37298)
add 9764a844027 fix(charts): Table chart shows an error on row limit (#37218)
add 1ee14c59931 fix(mcp): improve prompts, resources, and instructions clarity (#37389)
add 6043e7e7e32 fix: more DB OAuth2 fixes (#37398)
add 05c23549976 feat: AWS Cross-Account IAM Authentication for Aurora (#37585)
add 5d20dc57d76 feat(oauth2): add PKCE support for database OAuth2 authentication (#37067)
add 004f02746fc fix(build): Increase ForkTsCheckerWebpackPlugin memory limit to fix OOM error (#37583)
add 70b95ca1b98 fix(build): eliminate PostgreSQL extra installation on Python 3.12-based Superset Docker images (#37587)
add d95a3d84269 chore(deps-dev): bump @applitools/eyes-storybook from 3.63.9 to 3.63.10 in /superset-frontend (#37566)
add 901dca58f79 chore(deps): bump JustinBeckwith/linkinator-action from 2.3 to 2.4 (#37562)
add ae10e105c2c fix(chart): enable cross-filter on bar charts without dimensions (#37407)
add 61bd8f0cf26 chore(deps): bump use-query-params from 1.2.3 to 2.2.2 in /superset-frontend (#36997)
add 4a7cdccdad5 fix: Heatmap does not render correctly on normalization (#37208)
add a6a66ca4832 feat: Dataset folders editor (#36239)
add bd654690918 chore(deps-dev): bump globals from 17.2.0 to 17.3.0 in /docs (#37599)
add 2d35ed23918 chore(deps-dev): bump @babel/runtime-corejs3 from 7.28.6 to 7.29.0 in /superset-frontend (#37605)
add f2b6c395cdf feat: Add PWA file handler for CSV/XLS/Parquet uploads (#36191)
add 11257c05369 fix(examples): skip URI safety check for system imports (#37577)
add be404f9b844 fix(dashboard): Avoid calling loadData for invisible charts on virtual rendering (#37452)
add e9b494163b9 refactor(db): use Dialect instead of Engine in select_star to avoid SSH tunnels (#35540)
add 86f690d17fe fix(dashboard): fix Export as Example with app prefix and enable Dashboard Export E2E tests (#37529)
add 4b0d4975130 test: add new RTL and integration tests for DatasetList (#36681)
add 91131d59966 chore(charts): echarts left padding too big and automation of title (#36993)
add f60c82e4a60 fix: charts row limit warning is missing for server (#37112)
add 3a565a6c16e fix(tests): update DatasetList tests to new fetch-mock API (#37623)
add 0b5e4dd5de4 feat(mcp): add config toggle to disable parse_request decorator (#37617)
add 5914e834361 chore(mcp): remove unused MCP_SERVICE feature flag (#37618)
add 32ee160c75e chore: Properly untrack WebSocket config file for docker (#37624)
add 0d7cc88b2bd chore(deps): bump antd from 6.2.2 to 6.2.3 in /docs (#37629)
add 601fcb3382d chore(deps-dev): bump @babel/preset-env from 7.28.6 to 7.29.0 in /superset-frontend (#37635)
add fea135b46ca chore(deps-dev): bump @playwright/test from 1.58.0 to 1.58.1 in /superset-frontend (#37633)
add 568486a3048 chore(deps): bump @babel/core from 7.28.6 to 7.29.0 in /docs (#37598)
add 7112bce961b chore(deps-dev): bump @types/node from 25.1.0 to 25.2.0 in /superset-websocket (#37597)
add a44980da65a chore(deps-dev): bump globals from 17.2.0 to 17.3.0 in /superset-websocket (#37595)
add 3eb860a6635 chore(deps): bump hot-shots from 13.1.0 to 13.2.0 in /superset-websocket (#37596)
add 66264856a7f chore(deps): bump googleapis from 171.0.0 to 171.1.0 in /superset-frontend (#37630)
add 569606635b0 docs(databases): add Supabase, AlloyDB, and Neon as PostgreSQL-compatible databases (#37589)
add 76889c1a696 feat(db_engine_specs): add Apache Phoenix and Apache IoTDB engine specs (#37590)
add 5ac5480f359 chore(deps): bump caniuse-lite from 1.0.30001766 to 1.0.30001767 in /docs (#37601)
add 6b7b23ed788 fix(timeseries): restore ECharts tooltip after closing drill menu (#37284)
add 2dfc770b0f7 fix(native-filters): update TEMPORAL_RANGE filter subject when Time Column filter is applied (#36985)
add 89a98ab9a42 fix(dataset-editor): include calculated columns in currency code dropdown (#37621)
add 8fd34010776 fix(security): update jspdf to 4.0.0 to address CVE-2025-68428 (#37553)
add 45f883c9cd2 chore(deps-dev): bump webpack from 5.104.1 to 5.105.0 in /docs (#37656)
new 5753dfbb6ed feat: engine manager
new e82e06891b0 Cleanup locks
new baf6e03d16f Add extension
new 929b0337f48 Connecting
new bb5a15dc5a6 Cleanup
new 5f61bb8d76a Small improvements
new be31abeb7ee Hash key
new de8c250f86d Update existing tests
new 688224c4c0c Simplify key generation
new 62d2d82ed85 Fix more tests
new d06ccf51524 Fix poolclass check
new 99935fc0355 Fix tests
new c00fae53a52 Rebase
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (259eeb2a680)
\
N -- N -- N refs/heads/engine-manager (c00fae53a52)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 13 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.claude/settings.json | 15 +
.github/actions/change-detector/label-draft-pr.yml | 2 +-
.github/dependabot.yml | 7 +
.github/workflows/check_db_migration_confict.yml | 2 +-
.github/workflows/docker.yml | 17 +
.github/workflows/superset-docs-deploy.yml | 41 +-
.github/workflows/superset-docs-verify.yml | 64 +-
.github/workflows/superset-frontend.yml | 2 +-
.../workflows/superset-python-integrationtest.yml | 30 +
.gitignore | 6 +-
.pre-commit-config.yaml | 18 +-
.rat-excludes | 13 +-
AGENTS.md | 45 +
Dockerfile | 22 +-
INSTALL.md | 20 +-
LINTING_ARCHITECTURE.md | 121 -
Makefile | 8 +-
README.md | 110 +-
RELEASING/release-notes-1-0/README.md | 2 +-
RESOURCES/FEATURE_FLAGS.md | 103 -
RESOURCES/INTHEWILD.yaml | 51 +-
UPDATING.md | 109 +-
docker-compose-light.yml | 4 -
docker-compose.yml | 5 -
docker/pythonpath_dev/superset_config.py | 2 +-
docker/superset-websocket/config.json | 22 -
docs/.claude/instructions.md | 115 +
docs/.gitignore | 18 +
docs/DOCS_CLAUDE.md | 2 +-
docs/README.md | 6 +-
docs/babel.config.js | 11 +-
.../contributing/development-setup.md | 4 +-
docs/developer_portal/contributing/howtos.md | 79 +-
docs/developer_portal/extensions/architecture.md | 2 -
.../extensions/components/alert.mdx | 131 -
.../extensions/components/index.mdx | 93 -
.../extensions/contribution-types.md | 31 +-
docs/developer_portal/extensions/development.md | 4 -
.../extensions/extension-points/editors.md | 245 +
docs/developer_portal/extensions/registry.md | 1 +
.../guidelines/frontend-style-guidelines.md | 3 +-
docs/developer_portal/index.md | 1 -
docs/developer_portal/sidebars.js | 18 +
docs/developer_portal/testing/e2e-testing.md | 215 +-
docs/developer_portal/testing/storybook.md | 114 +
docs/docs/api.mdx | 597 +-
docs/docs/configuration/alerts-reports.mdx | 5 +
docs/docs/configuration/async-queries-celery.mdx | 4 +
docs/docs/configuration/cache.mdx | 5 +
docs/docs/configuration/configuring-superset.mdx | 6 +-
docs/docs/configuration/databases.mdx | 1902 ---
docs/docs/configuration/feature-flags.mdx | 107 +
docs/docs/configuration/networking-settings.mdx | 2 +-
docs/docs/configuration/sql-templating.mdx | 4 +
docs/docs/configuration/theming.mdx | 8 +
docs/docs/configuration/timezones.mdx | 2 +-
docs/docs/contributing/contributing.mdx | 4 +
docs/docs/contributing/development.mdx | 112 +-
docs/docs/faq.mdx | 4 +-
docs/docs/installation/kubernetes.mdx | 7 +-
docs/docs/installation/upgrading-superset.mdx | 12 +
docs/docs/quickstart.mdx | 10 +-
docs/docs/security/securing_superset.mdx | 5 +
.../creating-your-first-dashboard.mdx | 11 +
docs/docs/using-superset/exploring-data.mdx | 17 +-
docs/docusaurus.config.ts | 71 +-
docs/i18n/en/docusaurus-theme-classic/footer.json | 2 +-
docs/netlify.toml | 58 +
docs/package.json | 52 +-
docs/scripts/convert-api-sidebar.mjs | 123 +
docs/scripts/extract_custom_errors.py | 296 +
docs/scripts/fix-openapi-spec.py | 828 ++
docs/scripts/generate-api-index.mjs | 277 +
docs/scripts/generate-api-tag-pages.mjs | 176 +
docs/scripts/generate-database-docs.mjs | 1026 ++
docs/scripts/generate-superset-components.mjs | 1415 ++
docs/sidebarTutorials.js | 17 +
docs/sidebars.js | 32 +-
docs/src/components/BlurredSection.tsx | 5 +-
docs/src/components/SectionHeader.tsx | 12 +-
docs/src/components/StorybookWrapper.jsx | 465 +-
docs/src/components/databases/DatabaseIndex.tsx | 592 +
docs/src/components/databases/DatabasePage.tsx | 788 +
.../src/components/databases}/index.ts | 9 +-
docs/src/components/databases/types.ts | 255 +
docs/src/data/databases.json | 5793 ++++++++
docs/src/pages/community.tsx | 51 +-
docs/src/pages/inTheWild.tsx | 2 +-
docs/src/pages/index.tsx | 59 +-
docs/src/shims/null-module.js | 118 +
docs/src/shims/react-table.js | 54 +
docs/src/styles/custom.css | 320 +
docs/src/styles/main.less | 46 +-
docs/src/theme.d.ts | 10 +
docs/src/theme/Admonition/Types.js | 63 +
docs/src/theme/Playground/Preview/index.tsx | 107 +
.../src/theme/Playground/Preview/styles.module.css | 7 +-
docs/src/theme/ReactLiveScope/index.tsx | 51 +-
docs/src/theme/Root.js | 11 +-
docs/src/webpack.extend.ts | 97 +-
docs/static/.htaccess | 6 +-
docs/static/feature-flags.json | 392 +
.../static/img}/atomic-design.png | Bin
docs/static/img/community/bluesky-symbol.svg | Bin 0 -> 1487 bytes
docs/static/img/community/globe-symbol.svg | Bin 0 -> 1157 bytes
docs/static/img/community/linkedin-symbol.svg | Bin 0 -> 1347 bytes
docs/static/img/community/x-symbol.svg | Bin 0 -> 1068 bytes
docs/static/img/databases/alloydb.png | Bin 0 -> 88097 bytes
docs/static/img/databases/altinity.png | Bin 0 -> 19819 bytes
docs/static/img/databases/amazon-redshift.jpg | Bin 16973 -> 0 bytes
docs/static/img/databases/apache-druid.jpeg | Bin 214904 -> 0 bytes
docs/static/img/databases/apache-impala.png | Bin 5216 -> 25804 bytes
docs/static/img/databases/apache-iotdb.svg | Bin 0 -> 4646 bytes
docs/static/img/databases/apache-phoenix.png | Bin 0 -> 29188 bytes
docs/static/img/databases/apache-solr.png | Bin 0 -> 5584 bytes
docs/static/img/databases/apache-solr.svg | Bin 0 -> 3682 bytes
docs/static/img/databases/apache-spark.png | Bin 0 -> 26999 bytes
docs/static/img/databases/ascend.webp | Bin 0 -> 35644 bytes
docs/static/img/databases/aws-aurora.jpg | Bin 0 -> 80321 bytes
docs/static/img/databases/aws.png | Bin 0 -> 7868 bytes
docs/static/img/databases/azure.svg | Bin 0 -> 1759 bytes
docs/static/img/databases/celerdata.png | Bin 0 -> 41661 bytes
docs/static/img/databases/cloudflare.png | Bin 0 -> 2841 bytes
docs/static/img/databases/cockroachdb.png | Bin 0 -> 242795 bytes
docs/static/img/databases/cratedb.svg | Bin 0 -> 3000 bytes
docs/static/img/databases/db2.png | Bin 7493 -> 0 bytes
docs/static/img/databases/duckdb.png | Bin 0 -> 8369 bytes
docs/static/img/databases/elasticsearch.png | Bin 0 -> 13770 bytes
docs/static/img/databases/google-biquery.png | Bin 15539 -> 0 bytes
docs/static/img/databases/greenplum.jpeg | Bin 7559 -> 0 bytes
docs/static/img/databases/ibmdb2.png | Bin 14127 -> 0 bytes
docs/static/img/databases/imply.png | Bin 0 -> 15978 bytes
docs/static/img/databases/kusto.png | Bin 0 -> 2493 bytes
docs/static/img/databases/monet.png | Bin 21830 -> 0 bytes
docs/static/img/databases/mongodb.png | Bin 0 -> 22190 bytes
docs/static/img/databases/motherduck.png | Bin 0 -> 82556 bytes
docs/static/img/databases/mssql-server.png | Bin 695548 -> 0 bytes
docs/static/img/databases/mssql.jpg | Bin 38434 -> 0 bytes
docs/static/img/databases/mysql.jpg | Bin 42653 -> 0 bytes
docs/static/img/databases/neon.png | Bin 0 -> 20140 bytes
docs/static/img/databases/oracle-logo.png | Bin 10347 -> 0 bytes
docs/static/img/databases/oracle.png | Bin 8231 -> 0 bytes
docs/static/img/databases/pinot.png | Bin 7127 -> 0 bytes
docs/static/img/databases/postgresql.jpg | Bin 19019 -> 0 bytes
docs/static/img/databases/risingwave.svg | Bin 0 -> 14392 bytes
docs/static/img/databases/sap-hana.jpg | Bin 20343 -> 0 bytes
docs/static/img/databases/shillelagh.png | Bin 0 -> 113600 bytes
docs/static/img/databases/singlestore.png | Bin 0 -> 15978 bytes
docs/static/img/databases/snowflake.png | Bin 15635 -> 0 bytes
docs/static/img/databases/sqlite.jpg | Bin 13006 -> 0 bytes
docs/static/img/databases/starburst.png | Bin 0 -> 20343 bytes
docs/static/img/databases/supabase.svg | Bin 0 -> 5817 bytes
.../superset.svg} | Bin
docs/static/img/databases/trino2.jpg | Bin 36149 -> 0 bytes
docs/static/img/extensions/editors-bundle.png | Bin 0 -> 397539 bytes
docs/static/img/logos/cirrus.svg | Bin 0 -> 1809 bytes
docs/static/img/logos/club25deagosto.svg | Bin 0 -> 15461 bytes
.../logos/hollyland\347\214\233\347\216\233.svg" | Bin 0 -> 77086 bytes
docs/static/img/logos/hpe.png | Bin 0 -> 5212 bytes
docs/static/img/logos/plaidcloud.svg | Bin 0 -> 81228 bytes
docs/static/img/logos/userguiding.svg | Bin 0 -> 5610 bytes
docs/static/img/netlify.png | Bin 0 -> 4618 bytes
docs/static/img/screenshots/dashboard.jpg | Bin 0 -> 136723 bytes
docs/static/img/screenshots/explore.jpg | Bin 651168 -> 106386 bytes
docs/static/img/screenshots/gallery.jpg | Bin 965607 -> 121316 bytes
docs/static/img/screenshots/sql_lab.jpg | Bin 455005 -> 101650 bytes
docs/static/resources/openapi.json | 7414 +++++++++-
docs/tsconfig.json | 2 +
.../configuration/configuring-superset.mdx | 2 +-
.../configuration/networking-settings.mdx | 2 +-
.../version-6.0.0/contributing/development.mdx | 2 +-
docs/versioned_docs/version-6.0.0/intro.md | 4 +-
docs/versioned_docs/version-6.0.0/quickstart.mdx | 2 +-
docs/versions-config.json | 6 +-
docs/yarn.lock | 1963 ++-
helm/superset/Chart.yaml | 2 +-
helm/superset/README.md | 4 +-
helm/superset/values.yaml | 2 +-
pyproject.toml | 32 +-
requirements/base.in | 10 +-
requirements/base.txt | 17 +-
requirements/development.txt | 53 +-
scripts/check-type.js | 5 +
scripts/cypress_run.py | 7 +-
scripts/docker-compose-up.sh | 77 +-
scripts/extract_feature_flags.py | 200 +
superset-core/README.md | 2 +-
superset-embedded-sdk/README.md | 61 +-
superset-embedded-sdk/src/index.ts | 38 +-
superset-extensions-cli/README.md | 2 +-
.../src/superset_extensions_cli/cli.py | 5 -
superset-extensions-cli/tests/test_cli_dev.py | 24 +-
superset-frontend/.gitignore | 4 +
superset-frontend/LINTING.md | 165 -
superset-frontend/cypress-base/cypress.config.ts | 2 +-
.../{filter.test.ts => _skip.filter.test.ts} | 8 +-
.../cypress/e2e/chart_list/list.test.ts | 9 +-
.../cypress/e2e/dashboard/_skip.controls.test.ts | 6 +-
...r.test.ts => _skip.horizontalFilterBar.test.ts} | 1 +
.../cypress/e2e/dashboard/_skip.key_value.test.ts | 2 +-
.../dashboard/{load.test.ts => _skip.load.test.ts} | 0
....ts => _skip.nativeFilters.noInitState.test.ts} | 0
...Filters.test.ts => _skip.nativeFilters.test.ts} | 35 +-
.../dashboard/{tabs.test.ts => _skip.tabs.test.ts} | 2 +-
.../cypress/e2e/dashboard/_skip.url_params.test.ts | 2 +-
.../e2e/dashboard/shared_dashboard_functions.ts | 256 +-
.../cypress-base/cypress/e2e/dashboard/utils.ts | 12 +-
.../cypress/e2e/dashboard_list/list.test.ts | 22 +-
.../cypress/e2e/explore/_skip.AdhocFilters.test.ts | 2 +-
...cMetrics.test.ts => _skip.AdhocMetrics.test.ts} | 0
...cs.test.ts => _skip.advanced_analytics.test.ts} | 0
...notations.test.ts => _skip.annotations.test.ts} | 2 +-
.../explore/{link.test.ts => _skip.link.test.ts} | 0
.../cypress-base/cypress/e2e/explore/chart.test.js | 3 +-
...big_number.test.js => _skip.big_number.test.js} | 0
...otal.test.js => _skip.big_number_total.test.js} | 0
.../{box_plot.test.js => _skip.box_plot.test.js} | 33 +-
.../{bubble.test.js => _skip.bubble.test.js} | 89 +-
.../{compare.test.js => _skip.compare.test.js} | 0
..._chart.test.js => _skip.download_chart.test.js} | 0
.../{gauge.test.js => _skip.gauge.test.js} | 0
.../{graph.test.ts => _skip.graph.test.ts} | 0
.../{pie.test.js => _skip.pie.test.js} | 0
...vot_table.test.js => _skip.pivot_table.test.js} | 0
.../{sunburst.test.js => _skip.sunburst.test.js} | 0
.../{table.test.ts => _skip.table.test.ts} | 0
.../{time_table.js => _skip.time_table.js} | 0
.../{world_map.test.js => _skip.world_map.test.js} | 0
.../e2e/explore/visualizations/shared.helper.js | 24 +
.../cypress-base/cypress/e2e/sqllab/tabs.test.ts | 4 +-
superset-frontend/cypress-base/package-lock.json | 24 +-
superset-frontend/jest.config.js | 2 +-
.../types.py => superset-frontend/netlify.toml | 28 +-
superset-frontend/oxlint.json | 4 +-
superset-frontend/package-lock.json | 6344 ++++----
superset-frontend/package.json | 107 +-
.../packages/generator-superset/package.json | 4 +-
superset-frontend/packages/superset-core/README.md | 2 +-
.../packages/superset-core/package.json | 24 +-
.../superset-core/src/api/contributions.ts | 24 +-
.../packages/superset-core/src/api/editors.ts | 381 +
.../packages/superset-core/src/api/environment.ts | 153 -
.../packages/superset-core/src/api/index.ts | 4 +-
.../packages/superset-core/src/api/sqlLab.ts | 123 +-
.../packages/superset-core/src/index.ts | 1 +
.../superset-core/src/spec/utils/logging.test.ts | 69 +
.../packages/superset-core/src/ui/index.ts | 1 +
.../packages/superset-core/src/ui/theme/types.ts | 8 +
.../src/ui}/translation/README.md | 0
.../src/ui}/translation/Translator.ts | 2 +-
.../src/ui}/translation/TranslatorSingleton.ts | 0
.../src/ui}/translation/index.ts | 1 +
.../src/ui}/translation/types/index.ts | 0
.../src/ui}/translation/types/jed.ts | 0
.../superset-core/src/{ => utils}/index.ts | 4 +-
.../src/utils/logging.ts | 2 +-
.../types/external.d.ts | 0
.../superset-ui-chart-controls/package.json | 2 +-
.../src/components/CertifiedIconWithTooltip.tsx | 2 +-
.../components/ColumnTypeLabel/ColumnTypeLabel.tsx | 9 +-
.../src/components/ControlHeader.tsx | 2 +-
.../src/components/MetricOption.tsx | 2 +-
.../src/components/SQLPopover.tsx | 2 +-
.../src/components/labelUtils.tsx | 2 +-
.../superset-ui-chart-controls/src/constants.ts | 3 +-
.../src/operators/utils/isDerivedSeries.ts | 7 +-
.../src/sections/advancedAnalytics.tsx | 3 +-
.../src/sections/annotationsAndLayers.tsx | 2 +-
.../src/sections/chartTitle.tsx | 6 +-
.../src/sections/echartsTimeSeriesQuery.tsx | 2 +-
.../src/sections/forecastInterval.tsx | 7 +-
.../src/sections/matrixify.tsx | 2 +-
.../src/sections/sections.tsx | 2 +-
.../src/sections/timeComparison.tsx | 3 +-
.../components/RadioButtonControl.tsx | 3 +-
.../src/shared-controls/customControls.tsx | 23 +-
.../src/shared-controls/dndControls.tsx | 3 +-
.../src/shared-controls/matrixifyControls.tsx | 3 +-
.../src/shared-controls/mixins.tsx | 2 +-
.../src/shared-controls/sharedControls.tsx | 5 +-
.../superset-ui-chart-controls/src/types.ts | 1 +
.../src/utils/D3Formatting.ts | 2 +-
.../test/components/ColumnTypeLabel.test.tsx | 4 +
.../test/operators/utils/isDerivedSeries.test.ts | 21 +
.../test/shared-controls/customControls.test.tsx | 57 +
.../test/utils/getColorFormatters.test.ts | 2 +-
.../packages/superset-ui-core/package.json | 24 +-
.../src/chart/components/FallbackComponent.tsx | 7 +-
.../src/chart/components/NoResultsComponent.tsx | 3 +-
.../src/chart/components/StatefulChart.test.tsx | 340 +
.../src/chart/components/StatefulChart.tsx | 37 +-
.../src/chart/components/SuperChartCore.tsx | 2 +-
.../superset-ui-core/src/chart/types/Base.ts | 1 +
.../src/components/AsyncAceEditor/index.tsx | 5 +-
.../AutoComplete/AutoComplete.stories.tsx | 59 +-
.../src/components/Avatar/Avatar.stories.tsx | 19 +
.../src/components/Badge/Badge.stories.tsx | 149 +-
.../components/Breadcrumb/Breadcrumb.stories.tsx | 65 +-
.../src/components/Button/Button.stories.tsx | 44 +-
.../src/components/Button/index.tsx | 2 +-
.../components/ButtonGroup/ButtonGroup.stories.tsx | 98 +-
.../src/components/CachedLabel/TooltipContent.tsx | 2 +-
.../src/components/CachedLabel/index.tsx | 2 +-
.../src/components/Card/Card.stories.tsx | 81 +-
.../CertifiedBadge/CertifiedBadge.stories.tsx | 2 +-
.../src/components/CertifiedBadge/index.tsx | 2 +-
.../src/components/Checkbox/Checkbox.stories.tsx | 69 +
.../src/components/ConfirmModal/index.tsx | 2 +-
.../src/components/CronPicker/index.tsx | 2 +-
.../components/DatePicker/DatePicker.stories.tsx | 44 +-
.../src/components/DatePicker/index.tsx | 5 +-
.../src/components/DeleteModal/index.tsx | 2 +-
.../src/components/Divider/Divider.stories.tsx | 41 +
.../DropdownContainer.stories.tsx | 139 +
.../DropdownContainer/DropdownContainer.tsx | 3 +-
.../src/components/DynamicEditableTitle/index.tsx | 2 +-
.../EditableTitle/EditableTitle.stories.tsx | 61 +
.../src/components/EditableTitle/index.tsx | 2 +-
.../components/EmptyState/EmptyState.stories.tsx | 122 +-
.../src/components/EmptyState/index.tsx | 89 +-
.../src/components/EmptyState/types.ts | 3 +
.../src/components/FaveStar/FaveStar.stories.tsx | 43 +
.../src/components/FaveStar/index.tsx | 2 +-
.../src/components/Flex/Flex.stories.tsx | 94 +-
.../src/components/Form/LabeledErrorBoundInput.tsx | 2 +-
.../src/components/Grid/Grid.stories.tsx | 356 +-
.../components/IconButton/IconButton.stories.tsx | 53 +-
.../components/IconTooltip/IconTooltip.stories.tsx | 24 +
.../src/components/Icons/AntdEnhanced.tsx | 16 +
.../src/components/Icons/Icons.stories.tsx | 179 +
.../src/components/Icons/index.tsx | 3 +
.../src/components/InfoTooltip/index.tsx | 2 +-
.../src/components/Input/Input.stories.tsx | 16 +-
.../src/components/Label/Label.stories.tsx | 33 +
.../components/Label/reusable/DatasetTypeLabel.tsx | 2 +-
.../components/Label/reusable/PublishedLabel.tsx | 2 +-
.../src/components/LastUpdated/index.tsx | 2 +-
.../src/components/Layout/Layout.stories.tsx | 250 +-
.../src/components/List/List.stories.tsx | 31 +-
.../components/ListViewCard/ImageLoader.test.tsx | 25 +-
.../src/components/ListViewCard/ImageLoader.tsx | 2 +-
.../ListViewCard/ListViewCard.stories.tsx | 102 +-
.../src/components/ListViewCard/index.tsx | 4 +-
.../src/components/Loading/Loading.stories.tsx | 105 +-
.../src/components/Menu/Menu.stories.tsx | 99 +-
.../src/components/MetadataBar/ContentConfig.tsx | 3 +-
.../components/MetadataBar/MetadataBar.stories.tsx | 119 +-
.../src/components/Modal/FormModal.tsx | 2 +-
.../src/components/Modal/Modal.stories.tsx | 123 +-
.../src/components/Modal/Modal.tsx | 2 +-
.../ModalTrigger/ModalTrigger.stories.tsx | 119 +-
.../src/components/PageHeaderWithActions/index.tsx | 2 +-
.../src/components/Pagination/index.tsx} | 15 +-
.../src/components/Popover/Popover.stories.tsx | 139 +-
.../components/ProgressBar/ProgressBar.stories.tsx | 171 +-
.../src/components/Radio/Radio.stories.tsx | 75 +-
.../src/components/SafeMarkdown/SafeMarkdown.tsx | 3 +
.../src/components/Select/AsyncSelect.tsx | 2 +-
.../src/components/Select/Select.stories.tsx | 385 +-
.../src/components/Select/Select.test.tsx | 2 +-
.../src/components/Select/Select.tsx | 8 +-
.../src/components/Select/constants.ts | 10 +-
.../src/components/Select/styles.tsx | 1 +
.../src/components/Select/types.ts | 1 +
.../src/components/Select/utils.tsx | 3 +-
.../src/components/Skeleton/Skeleton.stories.tsx | 149 +-
.../src/components/Slider/Slider.stories.tsx | 175 +-
.../src/components/Space/Space.stories.tsx | 89 +-
.../src/components/Steps/Steps.stories.tsx | 143 +-
.../src/components/Switch/Switch.stories.tsx | 120 +-
.../src/components/Table/Table.stories.tsx | 192 +-
.../src/components/Table/TableOverview.stories.tsx | 2 +-
.../Table/cell-renderers/NumericCell/index.tsx | 2 +-
.../src/components/Table/index.tsx | 3 +-
.../src/components/TableView/TableView.stories.tsx | 161 +-
.../src/components/Tabs/Tabs.stories.tsx | 148 +-
.../ThemedAgGridReact/ThemedAgGridReact.test.tsx | 10 +-
.../src/components/Timer/Timer.stories.tsx | 121 +-
.../src/components/Timer/index.tsx | 4 +-
.../TimezoneSelector/TimezoneOptionsCache.test.tsx | 256 +
.../TimezoneSelector/TimezoneOptionsCache.ts | 159 +
.../TimezoneSelector.DaylightSavingTime.test.tsx | 12 +-
.../TimezoneSelector/TimezoneSelector.test.tsx | 36 +-
.../src/components/TimezoneSelector/index.tsx | 239 +-
.../{constants.ts => TimezoneSelector/types.ts} | 17 +-
.../src/components/Tooltip/Tooltip.stories.tsx | 111 +-
.../src/components/Tree/Tree.stories.tsx | 320 +-
.../components/TreeSelect/TreeSelect.stories.tsx | 386 +-
.../src/components/TruncatedList/index.tsx | 3 +-
.../components/Typography/Typography.stories.tsx | 476 +-
.../UnsavedChangesModal.stories.tsx | 64 +-
.../src/components/UnsavedChangesModal/index.tsx | 2 +-
.../src/components/Upload/Upload.stories.tsx | 166 +-
.../src/components/assets/images/loading.svg | Bin 1652 -> 2444 bytes
.../superset-ui-core/src/components/constants.ts | 2 +-
.../superset-ui-core/src/components/index.ts | 19 +
.../src/currency-format/CurrencyFormats.ts} | 6 +-
.../src/currency-format/CurrencyFormatter.ts | 95 +-
.../superset-ui-core/src/currency-format/index.ts | 10 +-
.../superset-ui-core/src/currency-format/types.ts | 14 +-
.../superset-ui-core/src/currency-format/utils.ts | 129 +-
.../packages/superset-ui-core/src/index.ts | 1 -
.../superset-ui-core/src/query/constants.ts | 1 +
.../src/query/extractQueryFields.ts | 2 +-
.../src/query/getClientErrorObject.ts | 2 +-
.../packages/superset-ui-core/src/query/index.ts | 1 +
.../superset-ui-core/src/query/types/Dashboard.ts | 81 +-
.../superset-ui-core/src/query/types/Datasource.ts | 1 +
.../superset-ui-core/src/query/types/Query.ts | 3 +
.../src/query/types/QueryFormData.ts | 5 +-
.../src/query/types/QueryResponse.ts | 12 +-
.../superset-ui-core/src/utils/featureFlags.ts | 4 +-
.../superset-ui-core/src/utils/html.test.tsx | 18 +
.../packages/superset-ui-core/src/utils/html.tsx | 129 +-
.../packages/superset-ui-core/src/utils/index.ts | 2 +-
.../packages/superset-ui-core/src/utils/tooltip.ts | 2 +-
.../superset-ui-core/src/utils/withLabel.ts} | 31 +-
.../superset-ui-core/src/validator/index.ts | 1 +
.../src/validator/legacyValidateInteger.ts | 4 +-
.../src/validator/legacyValidateNumber.ts | 4 +-
.../{legacyValidateNumber.ts => types.ts} | 16 +-
.../src/validator/validateInteger.ts | 4 +-
.../src/validator/validateMapboxStylesUrl.ts | 4 +-
.../src/validator/validateMaxValue.ts | 7 +-
.../src/validator/validateNonEmpty.ts | 4 +-
.../src/validator/validateNumber.ts | 4 +-
.../src/validator/validateServerPagination.ts | 4 +-
.../validator/validateTimeComparisonRangeValues.ts | 14 +-
.../test/chart/clients/ChartClient.test.ts | 7 +-
.../chart/components/FallbackComponent.test.tsx | 9 +-
.../test/components/SafeMarkdown.test.ts | 39 -
.../test/components/SafeMarkdown.test.tsx | 160 +
.../test/connection/SupersetClient.test.ts | 17 +-
.../test/connection/SupersetClientClass.test.ts | 109 +-
.../test/connection/callApi/callApi.test.ts | 141 +-
.../callApi/callApiAndParseWithTimeout.test.ts | 9 +-
.../test/connection/callApi/parseResponse.test.ts | 17 +-
.../test/currency-format/CurrencyFormatter.test.ts | 37 +-
.../currency-format/analyzeCurrencyInData.test.ts} | 19 +-
.../currency-format/hasMixedCurrencies.test.ts} | 18 +-
.../currency-format/normalizeCurrency.test.ts} | 20 +-
.../query/api/legacy/getDatasourceMetadata.test.ts | 5 +-
.../test/query/api/legacy/getFormData.test.ts | 5 +-
.../test/query/api/v1/getChartData.test.ts | 6 +-
.../test/query/api/v1/makeApi.test.ts | 20 +-
.../test/query/extractQueryFields.test.ts | 3 +-
.../test/time-comparison/fetchTimeRange.test.ts | 26 +-
.../test/translation/Translator.test.ts | 201 -
.../test/translation/TranslatorSingleton.test.ts | 86 -
.../test/translation/index.test.ts | 35 -
.../test/translation/languagePacks/en.ts | 37 -
.../test/utils/featureFlag.test.ts | 19 +-
.../superset-ui-core/test/utils/logging.test.ts | 78 -
.../superset-ui-core/test/validator/setup.ts | 2 +-
.../test/validator/validateMaxValue.test.ts | 4 +-
.../packages/superset-ui-demo/README.md | 55 +-
.../packages/superset-ui-demo/package.json | 19 +-
.../storybook/shared/components/VerifyCORS.tsx | 2 +-
.../plugins/plugin-chart-table/Table.stories.tsx | 14 +-
superset-frontend/playwright/README.md | 225 -
.../playwright/components/core/Menu.ts | 217 +
.../playwright/components/core/index.ts | 2 +
.../generators/docs/docs-screenshots.spec.ts | 230 +
.../playwright/generators/playwright.config.ts | 81 +
.../playwright/helpers/api/dataset.ts | 59 +-
.../playwright/pages/DashboardPage.ts | 128 +
.../playwright/tests/experimental/README.md | 7 +
.../tests/experimental/dashboard/export.spec.ts | 87 +
.../experimental/dataset/dataset-list.spec.ts | 94 +-
superset-frontend/playwright/utils/constants.ts | 5 +
superset-frontend/playwright/utils/urls.ts | 4 +
.../legacy-plugin-chart-calendar/src/Calendar.js | 6 +-
.../src/controlPanel.ts | 3 +-
.../legacy-plugin-chart-calendar/src/index.js | 3 +-
.../legacy-plugin-chart-calendar/src/utils.ts | 10 +-
.../src/vendor/cal-heatmap.js | 3 +-
.../test/utils.test.ts | 96 +
.../legacy-plugin-chart-chord/src/controlPanel.ts | 3 +-
.../plugins/legacy-plugin-chart-chord/src/index.js | 3 +-
.../src/controlPanel.ts | 3 +-
.../legacy-plugin-chart-country-map/src/index.js | 3 +-
.../src/controlPanel.ts | 2 +-
.../legacy-plugin-chart-horizon/src/index.js | 3 +-
.../src/ScatterPlotGlowOverlay.jsx | 70 +-
.../src/controlPanel.ts | 3 +-
.../legacy-plugin-chart-map-box/src/index.js | 3 +-
.../src/transformProps.js | 4 +-
.../test/ScatterPlotGlowOverlay.test.tsx | 346 +
.../src/controlPanel.ts | 3 +-
.../legacy-plugin-chart-paired-t-test/src/index.js | 3 +-
.../src/controlPanel.ts | 2 +-
.../src/index.js | 3 +-
.../src/controlPanel.tsx | 3 +-
.../legacy-plugin-chart-partition/src/index.js | 3 +-
.../legacy-plugin-chart-rose/src/controlPanel.tsx | 2 +-
.../plugins/legacy-plugin-chart-rose/src/index.js | 3 +-
.../src/controlPanel.ts | 2 +-
.../legacy-plugin-chart-world-map/src/index.js | 3 +-
.../src/transformProps.js | 13 +-
.../legacy-preset-chart-deckgl/package.json | 10 +-
.../src/DeckGLContainer.tsx | 6 +-
.../src/Multi/Multi.test.tsx | 607 +
.../legacy-preset-chart-deckgl/src/Multi/Multi.tsx | 174 +-
.../src/Multi/controlPanel.test.ts | 351 +
.../src/Multi/controlPanel.ts | 35 +-
.../legacy-preset-chart-deckgl/src/Multi/index.ts | 3 +-
.../src/layers/Arc/controlPanel.ts | 3 +-
.../src/layers/Arc/index.ts | 3 +-
.../src/layers/Contour/Contour.tsx | 17 +-
.../src/layers/Contour/controlPanel.ts | 3 +-
.../src/layers/Contour/getSafeCellSize.test.ts | 67 +
.../src/layers/Contour/getSafeCellSize.ts | 73 +
.../src/layers/Contour/index.ts | 5 +-
.../src/layers/Geojson/controlPanel.ts | 2 +-
.../src/layers/Geojson/index.ts | 3 +-
.../src/layers/Grid/controlPanel.ts | 3 +-
.../src/layers/Grid/index.ts | 3 +-
.../src/layers/Heatmap/Heatmap.tsx | 2 +-
.../src/layers/Heatmap/controlPanel.ts | 2 +-
.../src/layers/Heatmap/index.ts | 3 +-
.../src/layers/Hex/controlPanel.ts | 2 +-
.../src/layers/Hex/index.ts | 3 +-
.../src/layers/Path/Path.test.tsx | 120 +
.../src/layers/Path/controlPanel.ts | 4 +-
.../src/layers/Path/index.ts | 3 +-
.../src/layers/Polygon/Polygon.tsx | 2 +-
.../src/layers/Polygon/controlPanel.ts | 4 +-
.../src/layers/Polygon/index.ts | 3 +-
.../src/layers/Polygon/transformProps.test.ts | 50 +
.../src/layers/Polygon/transformProps.ts | 11 +
.../src/layers/Scatter/Scatter.tsx | 3 +-
.../src/layers/Scatter/controlPanel.ts | 3 +-
.../src/layers/Scatter/index.ts | 3 +-
.../src/layers/Screengrid/Screengrid.tsx | 2 +-
.../src/layers/Screengrid/controlPanel.ts | 3 +-
.../src/layers/Screengrid/index.ts | 3 +-
.../src/utilities/HandlebarsRenderer.tsx | 3 +-
.../src/utilities/Shared_DeckGL.tsx | 4 +-
.../src/utilities/TooltipTemplateControl.tsx | 2 +-
.../src/utilities/sharedDndControls.tsx | 2 +-
.../src/utilities/tooltipUtils.tsx | 3 +-
.../plugins/legacy-preset-chart-nvd3/package.json | 2 +-
.../src/Bubble/controlPanel.ts | 2 +-
.../legacy-preset-chart-nvd3/src/Bubble/index.js | 3 +-
.../src/Bullet/controlPanel.ts | 2 +-
.../legacy-preset-chart-nvd3/src/Bullet/index.js | 3 +-
.../src/Compare/controlPanel.ts | 2 +-
.../legacy-preset-chart-nvd3/src/Compare/index.js | 3 +-
.../legacy-preset-chart-nvd3/src/NVD3Controls.tsx | 2 +-
.../legacy-preset-chart-nvd3/src/NVD3Vis.js | 2 +-
.../src/TimePivot/controlPanel.ts | 2 +-
.../src/TimePivot/index.js | 3 +-
.../src/vendor/superset/AnnotationTypes.js | 2 +-
.../plugin-chart-ag-grid-table/package.json | 2 +-
.../src/AgGridTable/components/CustomHeader.tsx | 82 +-
.../src/AgGridTable/components/Pagination.tsx | 2 +-
.../components/TimeComparisonVisibility.tsx | 2 +-
.../src/AgGridTable/index.tsx | 94 +-
.../src/AgGridTableChart.tsx | 93 +-
.../plugin-chart-ag-grid-table/src/buildQuery.ts | 164 +-
.../plugin-chart-ag-grid-table/src/consts.ts | 15 +
.../src/controlPanel.tsx | 52 +-
.../plugin-chart-ag-grid-table/src/index.ts | 3 +-
.../src/renderers/NumericCellRenderer.tsx | 14 +-
.../src/renderers/TextCellRenderer.tsx | 3 +-
.../src/stateConversion.ts | 185 +-
.../src/styles/index.tsx | 4 +-
.../src/transformProps.ts | 20 +-
.../plugin-chart-ag-grid-table/src/types.ts | 23 +-
.../src/utils/agGridFilterConverter.ts | 726 +
.../src/utils/dateFilterComparator.ts | 58 +-
.../src/utils/filterStateManager.ts | 164 +
.../src/utils/getInitialFilterModel.ts | 49 +-
.../src/utils/useColDefs.ts | 89 +-
.../test/buildQuery.test.ts | 591 +
.../test/controlPanel.test.ts | 77 +
.../test/controlPanel.test.tsx | 208 +
.../test/utils/agGridFilterConverter.test.ts | 863 ++
.../test/utils/dateFilterComparator.test.ts | 124 +
.../test/utils/filterStateManager.test.ts | 658 +
.../test/utils/getInitialFilterModel.test.ts | 412 +
.../plugins/plugin-chart-cartodiagram/package.json | 2 +-
.../src/plugin/controlPanel.ts | 3 +-
.../plugin-chart-cartodiagram/src/plugin/index.ts | 3 +-
.../src/util/controlPanelUtil.tsx | 2 +-
.../plugins/plugin-chart-echarts/package.json | 3 +-
.../BigNumber/BigNumberPeriodOverPeriod/PopKPI.tsx | 2 +-
.../BigNumberPeriodOverPeriod/controlPanel.ts | 2 +-
.../BigNumber/BigNumberPeriodOverPeriod/index.ts | 3 +-
.../BigNumberPeriodOverPeriod/transformProps.ts | 13 +-
.../src/BigNumber/BigNumberTotal/controlPanel.ts | 3 +-
.../src/BigNumber/BigNumberTotal/index.ts | 3 +-
.../src/BigNumber/BigNumberTotal/transformProps.ts | 16 +-
.../src/BigNumber/BigNumberViz.tsx | 8 +-
.../BigNumberWithTrendline/controlPanel.tsx | 3 +-
.../src/BigNumber/BigNumberWithTrendline/index.ts | 3 +-
.../BigNumberWithTrendline/transformProps.test.ts | 1 +
.../BigNumberWithTrendline/transformProps.ts | 13 +-
.../src/BigNumber/sharedControls.ts | 2 +-
.../plugin-chart-echarts/src/BigNumber/types.ts | 3 +-
.../src/BoxPlot/controlPanel.ts | 2 +-
.../plugin-chart-echarts/src/BoxPlot/index.ts | 3 +-
.../src/BoxPlot/transformProps.ts | 6 +-
.../src/Bubble/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Bubble/index.ts | 3 +-
.../src/Funnel/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Funnel/index.ts | 3 +-
.../src/Funnel/transformProps.ts | 11 +-
.../src/Gantt/EchartsGantt.tsx | 2 +-
.../src/Gantt/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Gantt/index.ts | 3 +-
.../src/Gantt/transformProps.ts | 2 +-
.../src/Gauge/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Gauge/index.ts | 3 +-
.../src/Gauge/transformProps.ts | 6 +
.../src/Graph/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Graph/index.ts | 3 +-
.../src/Heatmap/controlPanel.tsx | 3 +-
.../plugin-chart-echarts/src/Heatmap/index.ts | 3 +-
.../src/Heatmap/transformProps.ts | 48 +-
.../src/Histogram/controlPanel.tsx | 9 +-
.../plugin-chart-echarts/src/Histogram/index.ts | 3 +-
.../src/MixedTimeseries/controlPanel.tsx | 3 +-
.../src/MixedTimeseries/index.ts | 3 +-
.../src/MixedTimeseries/transformProps.ts | 61 +-
.../plugin-chart-echarts/src/Pie/controlPanel.tsx | 3 +-
.../plugins/plugin-chart-echarts/src/Pie/index.ts | 3 +-
.../plugin-chart-echarts/src/Pie/transformProps.ts | 15 +-
.../src/Radar/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Radar/index.ts | 3 +-
.../src/Sankey/controlPanel.tsx | 3 +-
.../plugin-chart-echarts/src/Sankey/index.ts | 3 +-
.../src/Sunburst/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Sunburst/index.ts | 3 +-
.../src/Sunburst/transformProps.ts | 14 +-
.../src/Timeseries/Area/controlPanel.tsx | 2 +-
.../src/Timeseries/Area/index.ts | 3 +-
.../src/Timeseries/EchartsTimeseries.test.tsx | 90 +
.../src/Timeseries/EchartsTimeseries.tsx | 77 +-
.../src/Timeseries/Regular/Bar/controlPanel.tsx | 3 +-
.../src/Timeseries/Regular/Bar/index.ts | 3 +-
.../src/Timeseries/Regular/Line/controlPanel.tsx | 2 +-
.../src/Timeseries/Regular/Line/index.ts | 3 +-
.../Timeseries/Regular/Scatter/controlPanel.tsx | 2 +-
.../src/Timeseries/Regular/Scatter/index.ts | 3 +-
.../Timeseries/Regular/SmoothLine/controlPanel.tsx | 2 +-
.../src/Timeseries/Regular/SmoothLine/index.ts | 3 +-
.../src/Timeseries/Step/controlPanel.tsx | 2 +-
.../src/Timeseries/Step/index.ts | 3 +-
.../src/Timeseries/constants.ts | 2 +-
.../plugin-chart-echarts/src/Timeseries/index.ts | 3 +-
.../src/Timeseries/transformProps.ts | 135 +-
.../src/Timeseries/transformers.ts | 11 +-
.../plugin-chart-echarts/src/Tree/controlPanel.tsx | 2 +-
.../plugins/plugin-chart-echarts/src/Tree/index.ts | 2 +-
.../src/Treemap/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Treemap/index.ts | 3 +-
.../src/Treemap/transformProps.ts | 12 +-
.../src/Waterfall/constants.ts | 2 +-
.../src/Waterfall/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Waterfall/index.ts | 3 +-
.../plugins/plugin-chart-echarts/src/constants.ts | 5 +-
.../plugins/plugin-chart-echarts/src/controls.tsx | 3 +-
.../plugin-chart-echarts/src/utils/series.ts | 3 -
.../test/BigNumber/transformProps.test.ts | 59 +
.../test/Heatmap/buildQuery.test.ts | 82 +
.../test/Heatmap/transformProps.test.ts | 68 +
.../test/Timeseries/helpers.ts | 122 +
.../test/Timeseries/transformProps.test.ts | 738 +-
.../test/Timeseries/transformers.test.ts | 245 +-
.../plugins/plugin-chart-handlebars/package.json | 2 +-
.../src/components/Handlebars/HandlebarsViewer.tsx | 2 +-
.../src/plugin/controlPanel.tsx | 2 +-
.../src/plugin/controls/columns.tsx | 3 +-
.../src/plugin/controls/handlebarTemplate.tsx | 3 +-
.../src/plugin/controls/includeTime.ts | 2 +-
.../src/plugin/controls/metrics.tsx | 2 +-
.../src/plugin/controls/orderBy.tsx | 2 +-
.../src/plugin/controls/queryMode.tsx | 3 +-
.../src/plugin/controls/shared.ts | 8 +-
.../src/plugin/controls/style.tsx | 2 +-
.../plugin-chart-handlebars/src/plugin/index.ts | 3 +-
.../plugins/plugin-chart-pivot-table/package.json | 3 +-
.../src/PivotTableChart.tsx | 126 +-
.../src/plugin/controlPanel.tsx | 2 +-
.../plugin-chart-pivot-table/src/plugin/index.ts | 2 +-
.../src/plugin/transformProps.ts | 18 +-
.../src/react-pivottable/Styles.js | 2 +-
.../src/react-pivottable/TableRenderers.jsx | 249 +-
.../src/react-pivottable/utilities.js | 58 +-
.../plugins/plugin-chart-pivot-table/src/types.ts | 2 +
.../test/plugin/transformProps.test.ts | 189 +
.../test/react-pivottable/tableRenders.test.tsx | 591 +
.../plugins/plugin-chart-table/package.json | 2 +-
.../src/DataTable/components/SelectPageSize.tsx | 2 +-
.../plugins/plugin-chart-table/src/Styles.tsx | 4 +-
.../plugins/plugin-chart-table/src/TableChart.tsx | 50 +-
.../plugins/plugin-chart-table/src/consts.ts | 2 +-
.../plugin-chart-table/src/controlPanel.tsx | 47 +-
.../plugins/plugin-chart-table/src/index.ts | 3 +-
.../plugin-chart-table/src/transformProps.ts | 45 +-
.../plugins/plugin-chart-table/src/types.ts | 2 +
.../plugin-chart-table/src/utils/formatValue.ts | 11 +-
.../plugin-chart-table/src/utils/isEqualColumns.ts | 3 +
.../plugin-chart-table/test/TableChart.test.tsx | 55 +
.../plugin-chart-table/test/controlPanel.test.tsx | 208 +
.../plugins/plugin-chart-table/test/testData.ts | 77 +
.../test/utils/formatValue.test.ts | 155 +
.../plugins/plugin-chart-word-cloud/package.json | 3 +-
.../src/chart/WordCloud.tsx | 201 +-
.../src/configureEncodable.ts | 83 -
.../plugins/plugin-chart-word-cloud/src/index.ts | 1 -
.../src/plugin/controlPanel.tsx | 3 +-
.../plugin/controls/ColorSchemeControl/index.tsx | 2 +-
.../src/plugin/controls/RotationControl.tsx | 2 +-
.../plugin-chart-word-cloud/src/plugin/index.ts | 6 +-
superset-frontend/spec/helpers/ProviderWrapper.tsx | 12 +-
.../spec/helpers/jsDomWithFetchAPI.ts | 1 +
superset-frontend/spec/helpers/shim.tsx | 6 +-
superset-frontend/spec/helpers/testing-library.tsx | 7 +-
.../src/SqlLab/actions/sqlLab.test.js | 229 +-
.../src/SqlLab/actions/{sqlLab.js => sqlLab.ts} | 798 +-
.../SqlLab/components/AceEditorWrapper/index.tsx | 227 -
.../src/SqlLab/components/App/index.tsx | 2 +-
.../src/SqlLab/components/AppLayout/index.tsx | 28 +-
.../src/SqlLab/components/ColumnElement/index.tsx | 2 +-
.../EditorAutoSync/EditorAutoSync.test.tsx | 48 +-
.../src/SqlLab/components/EditorAutoSync/index.tsx | 2 +-
.../EditorWrapper.test.tsx} | 102 +-
.../src/SqlLab/components/EditorWrapper/index.tsx | 382 +
.../useAnnotations.test.ts | 34 +-
.../useAnnotations.ts | 3 +-
.../useKeywords.test.ts | 12 +-
.../useKeywords.ts | 9 +-
.../EstimateQueryCostButton.test.tsx | 28 +-
.../components/EstimateQueryCostButton/index.tsx | 11 +-
.../ExploreCtasResultsButton.test.tsx | 4 +-
.../components/ExploreCtasResultsButton/index.tsx | 3 +-
.../ExploreResultsButton.test.tsx | 8 +-
.../components/ExploreResultsButton/index.tsx | 14 +-
.../src/SqlLab/components/HighlightedSql/index.tsx | 2 +-
.../components/KeyboardShortcutButton/index.tsx | 2 +-
.../components/PopEditorTab/PopEditorTab.test.tsx | 14 +-
.../QueryAutoRefresh/QueryAutoRefresh.test.tsx | 6 +-
.../SqlLab/components/QueryAutoRefresh/index.tsx | 2 +-
.../components/QueryHistory/QueryHistory.test.tsx | 8 +-
.../src/SqlLab/components/QueryHistory/index.tsx | 6 +-
.../QueryLimitSelect/QueryLimitSelect.test.tsx | 3 +-
.../SqlLab/components/QueryLimitSelect/index.tsx | 30 +-
.../QueryStatusBar/QueryStatusBar.test.tsx | 161 +
.../src/SqlLab/components/QueryStatusBar/index.tsx | 214 +
.../src/SqlLab/components/QueryTable/index.tsx | 3 +-
.../SqlLab/components/ResultSet/ResultSet.test.tsx | 201 +-
.../src/SqlLab/components/ResultSet/index.tsx | 423 +-
.../RunQueryActionButton.test.tsx | 1 -
.../components/RunQueryActionButton/index.tsx | 40 +-
.../SaveDatasetActionButton.test.tsx | 39 +-
.../components/SaveDatasetActionButton/index.tsx | 49 +-
.../SqlLab/components/SaveDatasetModal/index.tsx | 8 +-
.../SqlLab/components/SaveQuery/SaveQuery.test.tsx | 23 +-
.../src/SqlLab/components/SaveQuery/index.tsx | 26 +-
.../components/ScheduleQueryButton/index.tsx | 2 +-
.../ShareSqlLabQuery/ShareSqlLabQuery.test.tsx | 21 +-
.../SqlLab/components/ShareSqlLabQuery/index.tsx | 13 +-
.../src/SqlLab/components/SouthPane/Results.tsx | 54 +-
.../src/SqlLab/components/SouthPane/index.tsx | 41 +-
.../SqlLab/components/SqlEditor/SqlEditor.test.tsx | 37 +-
.../src/SqlLab/components/SqlEditor/index.tsx | 415 +-
.../SqlEditorLeftBar/SqlEditorLeftBar.test.tsx | 239 +-
.../SqlLab/components/SqlEditorLeftBar/index.tsx | 338 +-
.../SqlLab/components/SqlEditorTabHeader/index.tsx | 3 +-
.../SqlEditorTopBar/SqlEditorTopBar.test.tsx | 106 +
.../SqlLab/components/SqlEditorTopBar/index.tsx | 53 +
.../SqlEditorTopBar/useDatabaseSelector.test.ts | 320 +
.../SqlEditorTopBar/useDatabaseSelector.ts | 126 +
.../components/StatusBar/StatusBar.test.tsx} | 39 +-
.../src/SqlLab/components/StatusBar/index.tsx | 57 +
.../TabbedSqlEditors/TabbedSqlEditors.test.tsx | 2 +-
.../SqlLab/components/TabbedSqlEditors/index.tsx | 43 +-
.../components/TableElement/TableElement.test.tsx | 59 +-
.../src/SqlLab/components/TableElement/index.tsx | 4 +-
.../TableExploreTree/TableExploreTree.test.tsx | 241 +
.../TableExploreTree/TreeNodeRenderer.tsx | 243 +
.../SqlLab/components/TableExploreTree/index.tsx | 335 +
.../components/TableExploreTree}/types.ts | 39 +-
.../components/TableExploreTree/useTreeData.ts | 339 +
.../components/TablePreview/TablePreview.test.tsx | 26 +-
.../src/SqlLab/components/TablePreview/index.tsx | 3 +-
.../TemplateParamsEditor.test.tsx | 4 +-
.../components/TemplateParamsEditor/index.tsx | 16 +-
superset-frontend/src/SqlLab/constants.ts | 6 +-
superset-frontend/src/SqlLab/contributions.ts | 7 +-
.../middlewares/persistSqlLabStateEnhancer.js | 136 -
.../middlewares/persistSqlLabStateEnhancer.ts | 185 +
.../src/SqlLab/reducers/getInitialState.ts | 2 +-
.../src/SqlLab/reducers/{sqlLab.js => sqlLab.ts} | 336 +-
.../src/SqlLab/utils/newQueryTabName.ts | 2 +-
.../src/assets/images/icons/move.svg | Bin 978 -> 1468 bytes
.../src/assets/images/icons/multiple.svg | Bin 978 -> 1287 bytes
.../src/assets/images/icons/sigma.svg | Bin 978 -> 1013 bytes
.../src/assets/images/pwa/icon-192.png | Bin 0 -> 9097 bytes
.../src/assets/images/pwa/icon-512.png | Bin 0 -> 25535 bytes
.../src/assets/images/pwa/screenshot-narrow.png | Bin 0 -> 100187 bytes
.../src/assets/images/pwa/screenshot-wide.png | Bin 0 -> 253025 bytes
...ckglLayerVisibilityCustomizationPlugin.test.tsx | 484 +
.../DeckglLayerVisibilityCustomizationPlugin.tsx | 204 +
.../DeckglLayerVisibility/buildQuery.test.ts} | 47 +-
.../DeckglLayerVisibility/buildQuery.ts} | 16 +-
.../DeckglLayerVisibility/controlPanel.ts | 8 +-
.../components/DeckglLayerVisibility}/index.ts | 18 +-
.../DeckglLayerVisibility/transformProps.test.ts | 100 +
.../DeckglLayerVisibility/transformProps.ts} | 26 +-
.../components/DeckglLayerVisibility/types.ts} | 40 +-
.../useDeckLayerMetadata.test.ts | 195 +
.../DeckglLayerVisibility/useDeckLayerMetadata.ts | 87 +
.../DynamicGroupBy/DynamicGroupByPlugin.tsx | 140 +
.../components/DynamicGroupBy/buildQuery.ts} | 23 +-
.../components/DynamicGroupBy}/controlPanel.ts | 37 +-
.../DynamicGroupBy}/images/thumbnail.png | Bin
.../components/DynamicGroupBy}/index.ts | 16 +-
.../components/DynamicGroupBy}/transformProps.ts | 0
.../components/DynamicGroupBy}/types.ts | 74 +-
.../TimeColumn/TimeColumnFilterPlugin.tsx | 3 +-
.../components/TimeColumn/buildQuery.ts | 0
.../components/TimeColumn/controlPanel.ts | 2 +-
.../components/TimeColumn}/images/thumbnail.png | Bin
.../components/TimeColumn/index.ts | 9 +-
.../components/TimeColumn/transformProps.ts | 0
.../components/TimeColumn/types.ts | 0
.../components/TimeGrain/TimeGrainFilterPlugin.tsx | 63 +-
.../components/TimeGrain/buildQuery.ts | 0
.../components/TimeGrain/controlPanel.ts | 6 +-
.../components/TimeGrain}/images/thumbnail.png | Bin
.../components/TimeGrain/index.ts | 9 +-
.../components/TimeGrain/transformProps.ts | 0
.../components/TimeGrain/types.ts | 0
.../components/common.ts | 0
.../chartCustomizations/components}/index.ts | 10 +-
.../src/chartCustomizations/components/types.ts | 25 +-
.../src/components/AlteredSliceTag/index.tsx | 2 +-
.../src/components/AuditInfo/index.tsx | 2 +-
superset-frontend/src/components/Chart/Chart.tsx | 21 +-
.../ChartContextMenu/ChartContextMenu.test.tsx | 152 +
.../Chart/ChartContextMenu/ChartContextMenu.tsx | 5 +-
.../src/components/Chart/ChartRenderer.jsx | 16 +-
.../components/Chart/DrillBy/DrillByModal.test.tsx | 36 +-
.../src/components/Chart/DrillBy/DrillByModal.tsx | 2 +-
.../Chart/DrillBy/DrillBySubmenu.test.tsx | 2 +-
.../components/Chart/DrillBy/DrillBySubmenu.tsx | 2 +-
.../Chart/DrillBy/useDisplayModeToggle.tsx | 2 +-
.../Chart/DrillBy/useResultsTableView.tsx | 3 +-
.../Chart/DrillDetail/DrillDetailModal.tsx | 2 +-
.../Chart/DrillDetail/DrillDetailPane.test.tsx | 2 +-
.../Chart/DrillDetail/DrillDetailPane.tsx | 2 +-
.../Chart/DrillDetail/DrillDetailTableControls.tsx | 2 +-
.../src/components/Chart/chartAction.js | 2 +-
.../src/components/Chart/chartActions.test.js | 51 +-
.../src/components/Chart/chartReducer.ts | 2 +-
.../Chart/useDrillDetailMenuItems/index.tsx | 2 +-
.../src/components/CopyToClipboard/index.tsx | 2 +-
.../DatabaseSelector/DatabaseSelector.test.tsx | 42 +-
.../src/components/DatabaseSelector/index.tsx | 209 +-
.../src/components/DatabaseSelector/types.ts | 1 +
.../ChangeDatasourceModal.test.jsx | 8 +-
.../Datasource/ChangeDatasourceModal/index.tsx | 3 +-
.../DatasourceModal/DatasourceModal.test.jsx | 26 +-
.../DatasourceModal.useModal.test.tsx | 4 +-
.../Datasource/DatasourceModal/index.tsx | 13 +-
.../FoldersEditor/FoldersEditor.test.tsx | 545 +
.../Datasource/FoldersEditor/TreeItem.styles.ts | 214 +
.../Datasource/FoldersEditor/TreeItem.tsx | 396 +
.../FoldersEditor/VirtualizedTreeItem.tsx | 220 +
.../FoldersEditor/VirtualizedTreeList.tsx | 229 +
.../components/DragOverlayContent.tsx | 75 +
.../components/FoldersToolbarComponent.tsx | 78 +
.../components/ResetConfirmModal.tsx} | 52 +-
.../Datasource/FoldersEditor/components/index.ts} | 15 +-
.../Datasource/FoldersEditor/constants.ts | 60 +
.../FoldersEditor/folderOperations.test.ts | 217 +
.../Datasource/FoldersEditor/folderOperations.ts | 217 +
.../Datasource/FoldersEditor/folderValidation.ts | 109 +
.../FoldersEditor/hooks/useAutoScroll.ts | 191 +
.../FoldersEditor/hooks/useDragHandlers.ts | 663 +
.../FoldersEditor/hooks/useHeightCache.ts | 81 +
.../FoldersEditor/hooks/useItemHeights.ts | 89 +
.../components/Datasource/FoldersEditor/index.tsx | 467 +
.../components/Datasource/FoldersEditor/sensors.ts | 47 +
.../components/Datasource/FoldersEditor/styles.tsx | 90 +
.../Datasource/FoldersEditor/treeUtils.test.ts | 669 +
.../Datasource/FoldersEditor/treeUtils.ts | 332 +
.../Datasource/FoldersEditor}/types.ts | 14 +-
.../components/CollectionTable/index.tsx | 2 +-
.../DatasourceEditor/DatasourceEditor.jsx | 242 +-
.../DatasetUsageTab/DatasetUsageTab.test.tsx | 4 +-
.../components/DatasetUsageTab/index.tsx | 2 +-
.../tests/DatasourceEditor.test.tsx | 95 +-
.../tests/DatasourceEditor.test.utils.tsx | 29 +-
.../tests/DatasourceEditorCurrency.test.tsx | 103 +-
.../src/components/Datasource/types.ts | 6 +
.../src/components/Datasource/utils/index.js | 3 +-
.../src/components/Datasource/utils/utils.test.tsx | 2 +-
.../src/components/DynamicPlugins/index.tsx | 2 +-
.../src/components/ErrorBoundary/index.tsx | 2 +-
.../ErrorMessage/DatabaseErrorMessage.tsx | 3 +-
.../ErrorMessage/DatasetNotFoundErrorMessage.tsx | 2 +-
.../src/components/ErrorMessage/ErrorAlert.tsx | 2 +-
.../ErrorMessage/ErrorMessageWithStackTrace.tsx | 3 +-
.../ErrorMessage/FrontendNetworkErrorMessage.tsx | 2 +-
.../ErrorMessage/InvalidSQLErrorMessage.tsx | 2 +-
.../ErrorMessage/MarshmallowErrorMessage.tsx | 2 +-
.../ErrorMessage/OAuth2RedirectMessage.tsx | 3 +-
.../ErrorMessage/ParameterErrorMessage.tsx | 3 +-
.../ErrorMessage/TimeoutErrorMessage.tsx | 3 +-
.../src/components/FilterableTable/index.tsx | 3 +-
.../src/components/FilterableTable/utils.tsx | 3 +-
.../src/components/GridTable/Header.tsx | 2 +-
.../src/components/GridTable/HeaderMenu.tsx | 2 +-
.../src/components/ImportModal/ErrorAlert.tsx | 2 +-
.../components/ImportModal/ImportErrorAlert.tsx | 2 +-
.../components/ImportModal/ImportModal.test.tsx | 12 +-
.../src/components/ImportModal/index.tsx | 2 +-
.../components/LastQueriedLabel/index.tsx} | 63 +-
.../src/components/ListView/ActionsBar.tsx | 3 +-
.../src/components/ListView/CardSortSelect.tsx | 2 +-
.../src/components/ListView/CrossLinksTooltip.tsx | 2 +-
.../src/components/ListView/Filters/DateRange.tsx | 2 +-
.../components/ListView/Filters/NumericalRange.tsx | 2 +-
.../src/components/ListView/Filters/Search.tsx | 2 +-
.../src/components/ListView/Filters/Select.tsx | 2 +-
.../src/components/ListView/ListView.test.tsx | 23 +-
.../src/components/ListView/ListView.tsx | 45 +-
.../src/components/Modal/StandardModal.tsx | 2 +-
.../src/components/Modal/useModalValidation.tsx | 2 +-
.../src/components/PanelToolbar/index.tsx | 165 +
.../src/components/RowCountLabel/index.tsx | 3 +-
.../components/SQLEditorWithValidation/index.tsx | 53 +-
.../StreamingExportModal/StreamingExportModal.tsx | 2 +-
.../useStreamingExport.test.ts | 959 ++
.../StreamingExportModal/useStreamingExport.ts | 57 +-
.../TableSelector/TableSelector.test.tsx | 2 +-
.../src/components/TableSelector/index.tsx | 40 +-
.../src/components/Tag/utils.test.tsx | 24 +-
superset-frontend/src/components/Tag/utils.tsx | 2 +-
.../ViewListExtension/ViewListExtension.test.tsx | 198 +
.../ViewListExtension/index.tsx} | 32 +-
superset-frontend/src/constants.ts | 7 +
superset-frontend/src/core/authentication/index.ts | 6 +-
superset-frontend/src/core/commands/index.ts | 12 +-
.../src/core/editors/AceEditorProvider.test.tsx | 191 +
.../src/core/editors/AceEditorProvider.tsx | 358 +
.../src/core/editors/EditorHost.test.tsx | 90 +
superset-frontend/src/core/editors/EditorHost.tsx | 128 +
.../src/core/editors/EditorProviders.test.ts | 339 +
.../src/core/editors/EditorProviders.ts | 243 +
superset-frontend/src/core/editors/index.ts | 130 +
superset-frontend/src/core/environment/index.ts | 57 -
superset-frontend/src/core/extensions/index.ts | 11 +-
superset-frontend/src/core/index.ts | 2 +-
superset-frontend/src/core/sqlLab/index.ts | 135 +-
.../dashboard/actions/chartCustomizationActions.ts | 379 +-
.../src/dashboard/actions/dashboardInfo.ts | 3 +-
.../src/dashboard/actions/dashboardLayout.js | 2 +-
.../src/dashboard/actions/dashboardState.js | 16 +-
superset-frontend/src/dashboard/actions/hydrate.js | 10 +-
.../src/dashboard/actions/nativeFilters.ts | 30 +-
.../src/dashboard/actions/sliceEntities.ts | 2 +-
.../components/AddSliceCard/AddSliceCard.tsx | 3 +-
.../src/dashboard/components/AnchorLink/index.tsx | 2 +-
.../components/BuilderComponentPane/index.tsx | 2 +-
.../components/ColorSchemeControlWrapper.tsx | 3 +-
.../src/dashboard/components/ColorSchemeSelect.tsx | 2 +-
.../index.tsx | 208 +-
.../src/dashboard/components/Dashboard.jsx | 2 +-
.../DashboardBuilder/DashboardBuilder.tsx | 3 +-
.../DashboardBuilder/DashboardContainer.test.tsx | 211 +-
.../DashboardBuilder/DashboardContainer.tsx | 152 +-
.../dashboard/components/DashboardBuilder/state.ts | 27 +-
.../src/dashboard/components/DashboardGrid.jsx | 4 +-
.../dashboard/components/EmbeddedModal/index.tsx | 2 +-
.../components/FiltersBadge/DetailsPanel/index.tsx | 2 +-
.../dashboard/components/FiltersBadge/index.tsx | 55 +-
.../src/dashboard/components/Header/index.jsx | 5 +-
.../src/dashboard/components/Header/types.ts | 2 +
.../components/Header/useDashboardMetadataBar.tsx | 2 +-
.../Header/useHeaderActionsDropdownMenu.tsx | 4 +-
.../src/dashboard/components/MissingChart.tsx | 2 +-
.../OverwriteConfirmModal.test.tsx | 24 +-
.../OverwriteConfirm/OverwriteConfirmModal.tsx | 2 +-
.../PropertiesModal/PropertiesModal.test.tsx | 18 +-
.../dashboard/components/PropertiesModal/index.tsx | 20 +-
.../PropertiesModal/sections/AccessSection.tsx | 3 +-
.../PropertiesModal/sections/AdvancedSection.tsx | 37 +-
.../PropertiesModal/sections/BasicInfoSection.tsx | 2 +-
.../sections/CertificationSection.tsx | 2 +-
.../PropertiesModal/sections/RefreshSection.tsx | 2 +-
.../sections/StylingSection.test.tsx | 45 +
.../PropertiesModal/sections/StylingSection.tsx | 62 +-
.../dashboard/components/PublishedStatus/index.tsx | 2 +-
.../RefreshFrequency/RefreshFrequencySelect.tsx | 2 +-
.../dashboard/components/RefreshIntervalModal.tsx | 2 +-
.../src/dashboard/components/SaveModal.tsx | 2 +-
.../src/dashboard/components/SliceAdder.tsx | 2 +-
.../components/SliceHeader/SliceHeader.test.tsx | 189 +-
.../src/dashboard/components/SliceHeader/index.tsx | 30 +-
.../ViewResultsModalTrigger.tsx | 2 +-
.../components/SliceHeaderControls/index.tsx | 26 +-
.../components/SliceHeaderControls/types.ts | 1 +
.../components/SyncDashboardState/index.tsx | 5 +-
.../URLShortLinkButton/URLShortLinkButton.test.tsx | 13 +-
.../components/URLShortLinkButton/index.tsx | 9 +-
.../components/filterscope/FilterScopeSelector.jsx | 3 +-
.../dashboard/components/filterscope/treeIcons.tsx | 2 +-
.../components/gridComponents/Chart/Chart.jsx | 40 +-
.../components/gridComponents/Column/Column.jsx | 3 +-
.../Divider/{Divider.test.jsx => Divider.test.tsx} | 17 +-
.../Divider/{Divider.jsx => Divider.tsx} | 37 +-
.../DynamicComponent/DynamicComponent.tsx | 3 +-
.../gridComponents/Markdown/Markdown.jsx | 37 +-
.../gridComponents/Markdown/Markdown.test.tsx | 25 +-
.../components/gridComponents/Row/Row.tsx | 8 +-
.../components/gridComponents/Tab/Tab.jsx | 111 +-
.../components/gridComponents/Tab/Tab.test.tsx | 172 +-
.../components/gridComponents/Tabs/Tabs.jsx | 4 +-
.../components/gridComponents/new/NewColumn.tsx | 2 +-
.../components/gridComponents/new/NewDivider.tsx | 2 +-
.../components/gridComponents/new/NewHeader.tsx | 2 +-
.../components/gridComponents/new/NewMarkdown.tsx | 2 +-
.../components/gridComponents/new/NewRow.tsx | 2 +-
.../components/gridComponents/new/NewTabs.tsx | 2 +-
.../components/menu/BackgroundStyleDropdown.tsx | 2 +-
.../DownloadMenuItems/DownloadMenuItems.test.tsx | 96 +-
.../components/menu/DownloadMenuItems/index.tsx | 96 +-
.../components/menu/MarkdownModeDropdown.tsx | 2 +-
.../menu/ShareMenuItems/ShareMenuItems.test.tsx | 12 +-
.../components/menu/ShareMenuItems/index.tsx | 9 +-
.../ChartCustomization/ChartCustomizationForm.tsx | 1457 --
.../ChartCustomization/ChartCustomizationModal.tsx | 700 -
.../ChartCustomizationTitleContainer.tsx | 184 -
.../ChartCustomizationTitlePane.tsx | 124 -
.../ChartCustomization/groupBySelectors.ts | 262 -
.../nativeFilters/ChartCustomization/selectors.ts | 47 -
.../useChartCustomizationModal.tsx | 52 -
.../nativeFilters/ChartCustomization/utils.ts | 63 -
.../nativeFilters/ConfigModal/ModalFooter.tsx | 2 +-
.../nativeFilters/ConfigModal/SharedStyles.tsx | 1 -
.../FilterBar/ActionButtons/index.tsx | 17 +-
.../FilterBar/CrossFilters/CrossFilterTitle.tsx | 3 +-
.../ScopingModal/ChartsScopingListPanel.tsx | 2 +-
.../ScopingModal/ScopingModal.test.tsx | 11 +-
.../CrossFilters/ScopingModal/ScopingModal.tsx | 3 +-
.../CrossFilters/ScopingModal/ScopingTreePanel.tsx | 3 +-
.../FilterBar/CrossFilters/VerticalCollapse.tsx | 2 +-
.../index.tsx | 32 +-
.../nativeFilters/FilterBar/FilterBar.test.tsx | 189 +-
.../FilterBarSettings/FilterBarSettings.test.tsx | 4 +-
.../FilterBar/FilterBarSettings/index.tsx | 31 +-
.../useFilterConfigModal.tsx | 33 +-
.../FilterBar/FilterControls/FilterControl.tsx | 280 +-
.../{FilterControl.tsx => FilterControlShared.tsx} | 244 +-
.../FilterControls/FilterControls.test.tsx | 411 +
.../FilterBar/FilterControls/FilterControls.tsx | 229 +-
.../FilterBar/FilterControls/FilterValue.tsx | 74 +-
.../FilterControls}/GroupByFilterCard.tsx | 404 +-
.../FilterBar/FilterControls/types.ts | 19 +-
.../FiltersOutOfScopeCollapsible/index.tsx | 3 +-
.../nativeFilters/FilterBar/Header/Header.test.tsx | 4 +-
.../nativeFilters/FilterBar/Header/index.tsx | 4 +-
.../nativeFilters/FilterBar/Horizontal.tsx | 18 +-
.../FilterBar/HorizontalFilterBar.test.tsx | 2 +
.../nativeFilters/FilterBar/Vertical.tsx | 25 +-
.../components/nativeFilters/FilterBar/index.tsx | 275 +-
.../nativeFilters/FilterBar/keyValue.tsx | 3 +-
.../components/nativeFilters/FilterBar/state.ts | 67 +-
.../components/nativeFilters/FilterBar/types.ts | 7 +
.../FilterBar/useFilterControlFactory.tsx | 5 +-
.../components/nativeFilters/FilterBar/utils.ts | 10 +-
.../nativeFilters/FilterCard/DependenciesRow.tsx | 3 +-
.../nativeFilters/FilterCard/FilterCard.test.tsx | 8 +-
.../nativeFilters/FilterCard/FilterCardContent.tsx | 4 +-
.../nativeFilters/FilterCard/NameRow.tsx | 25 +-
.../nativeFilters/FilterCard/ScopeRow.tsx | 3 +-
.../nativeFilters/FilterCard/TypeRow.tsx | 9 +-
.../components/nativeFilters/FilterCard/types.ts | 5 +-
.../FilterCard/useFilterDependencies.ts | 5 +-
.../nativeFilters/FilterCard/useFilterScope.ts | 5 +-
.../ConfigModalContent/ConfigModalContent.tsx | 164 +
.../CustomizationContentRenderer.tsx | 133 +
.../ConfigModalContent/FilterContentRenderer.tsx | 136 +
.../ConfigModalContent}/index.ts | 13 +-
.../ConfigModalSidebar/ConfigModalSidebar.tsx | 192 +
.../ConfigModalSidebar/ItemSection.tsx | 77 +
.../ConfigModalSidebar}/index.ts | 11 +-
.../FiltersConfigModal/DividerConfigForm.tsx | 65 +-
.../FiltersConfigModal/DraggableFilter.test.tsx | 230 +
.../FiltersConfigModal/DraggableFilter.tsx | 74 +-
.../FiltersConfigModal/FilterTitleContainer.tsx | 19 +-
.../FiltersConfigModal/FilterTitlePane.tsx | 3 +-
.../FiltersConfigForm/ColumnSelect.test.tsx | 2 +-
.../FiltersConfigForm/ColumnSelect.tsx | 2 +-
.../FiltersConfigForm/DatasetSelect.test.tsx | 6 +-
.../FiltersConfigForm/DatasetSelect.tsx | 6 +-
.../FiltersConfigForm/DefaultValue.tsx | 22 +-
.../FiltersConfigForm/DependencyList.tsx | 2 +-
.../FiltersConfigForm/FilterScope/state.ts | 2 +-
.../FiltersConfigForm/FilterScope/utils.ts | 4 +-
.../FiltersConfigForm/FiltersConfigForm.tsx | 377 +-
.../FiltersConfigForm/RemovedFilter.tsx | 2 +-
.../FiltersConfigForm/constants.ts | 11 +
.../FiltersConfigForm/getControlItemsMap.tsx | 15 +-
.../FiltersConfigModal/FiltersConfigForm/state.ts | 17 +-
.../FiltersConfigModal/FiltersConfigModal.test.tsx | 151 +-
.../FiltersConfigModal/FiltersConfigModal.tsx | 957 +-
.../Footer/CancelConfirmationAlert.tsx | 2 +-
.../FiltersConfigModal/Footer/Footer.tsx | 2 +-
...erTitleContainer.tsx => ItemTitleContainer.tsx} | 86 +-
.../FiltersConfigModal/ItemTitlePane.tsx | 93 +
.../FiltersConfigModal/NativeFiltersModal.test.tsx | 9 +-
.../FiltersConfigModal/NewItemDropdown.tsx | 93 +
.../FiltersConfigModal/hooks}/index.ts | 34 +-
.../hooks/useCustomizationOperations.ts | 127 +
.../hooks/useFilterOperations.ts | 248 +
.../hooks/useItemStateManager.ts | 154 +
.../FiltersConfigModal/hooks/useModalSaveLogic.ts | 430 +
.../nativeFilters/FiltersConfigModal/state.ts | 16 +-
.../transformers/customizationTransformer.ts | 163 +
.../transformers/filterTransformer.ts | 172 +
.../FiltersConfigModal/transformers}/index.ts | 4 +-
.../nativeFilters/FiltersConfigModal/types.ts | 80 +-
.../nativeFilters/FiltersConfigModal/utils.test.ts | 139 +
.../nativeFilters/FiltersConfigModal/utils.ts | 162 +-
.../components/nativeFilters/state.test.ts | 211 +
.../dashboard/components/nativeFilters/state.ts | 206 +-
.../dashboard/components/nativeFilters/utils.ts | 33 +-
superset-frontend/src/dashboard/constants.ts | 3 +
.../src/dashboard/containers/DashboardPage.tsx | 38 +-
.../src/dashboard/hooks/useDownloadScreenshot.ts | 9 +-
.../src/dashboard/reducers/dashboardInfo.js | 101 +-
.../dashboard/reducers/groupByCustomizations.ts | 198 -
.../src/dashboard/reducers/nativeFilters.test.ts | 257 +-
.../src/dashboard/reducers/nativeFilters.ts | 66 +-
.../src/dashboard/reducers/sliceEntities.ts | 2 +-
superset-frontend/src/dashboard/types.ts | 20 +-
.../dashboard/util/activeAllDashboardFilters.ts | 5 +-
.../src/dashboard/util/backgroundStyleOptions.ts | 2 +-
.../src/dashboard/util/calculateScopes.ts | 75 +
.../util/charts/getFormDataWithExtraFilters.ts | 119 +-
.../util/getChartIdsInFilterScope.test.ts | 370 +
.../src/dashboard/util/getFilterFieldNodesTree.ts | 2 +-
.../src/dashboard/util/getFilterScopeNodesTree.js | 2 +-
.../util/getFormDataWithExtraFilters.test.ts | 114 +
.../src/dashboard/util/getRelatedCharts.ts | 13 +-
.../src/dashboard/util/getSliceHeaderTooltip.tsx | 2 +-
.../src/dashboard/util/headerStyleOptions.ts | 2 +-
.../src/dashboard/util/newComponentFactory.js | 2 +-
.../dashboard/util/updateComponentParentsList.ts | 2 +-
.../util/useFilterFocusHighlightStyles.test.tsx | 4 +
.../util/useFilterFocusHighlightStyles.ts | 10 +-
superset-frontend/src/dataMask/actions.ts | 3 +
superset-frontend/src/dataMask/reducer.test.ts | 118 +
superset-frontend/src/dataMask/reducer.ts | 180 +-
.../src/embedded/EmbeddedContextProviders.tsx | 11 +-
superset-frontend/src/embedded/api.tsx | 4 +-
superset-frontend/src/embedded/index.tsx | 4 +-
superset-frontend/src/embedded/utils.ts | 3 +-
.../src/explore/actions/datasourcesActions.test.ts | 8 +-
.../src/explore/actions/exploreActions.ts | 3 +-
.../src/explore/actions/saveModalActions.test.ts | 78 +-
.../src/explore/actions/saveModalActions.ts | 2 +-
.../src/explore/components/ChartPills.tsx | 20 +-
.../src/explore/components/ControlHeader.tsx | 2 +-
.../components/ControlPanelsContainer.test.tsx | 212 +-
.../explore/components/ControlPanelsContainer.tsx | 33 +-
.../explore/components/DataTableControl/index.tsx | 20 +-
.../DataTableControl/useTableColumns.test.ts | 28 +
.../components/DataTablesPane/DataTablesPane.tsx | 3 +-
.../components/ResultsPaneOnDashboard.tsx | 4 +-
.../DataTablesPane/components/SamplesPane.tsx | 3 +-
.../components/SingleQueryResultPane.tsx | 4 +-
.../DataTablesPane/components/useResultsPane.tsx | 4 +-
.../DataTablesPane/test/DataTablesPane.test.tsx | 14 +-
.../test/ResultsPaneOnDashboard.test.tsx | 2 +-
.../DataTablesPane/test/SamplesPane.test.tsx | 2 +-
.../src/explore/components/DataTablesPane/types.ts | 4 +
.../DatasourcePanel/DatasourcePanel.test.tsx | 23 +-
.../DatasourcePanel/DatasourcePanelItem.test.tsx | 14 +-
.../DatasourcePanel/DatasourcePanelItem.tsx | 3 +-
.../explore/components/DatasourcePanel/index.tsx | 3 +-
.../transformDatasourceFolders.test.ts | 61 +-
.../DatasourcePanel/transformDatasourceFolders.ts | 8 +-
.../explore/components/DatasourcePanel/types.ts | 18 +-
.../src/explore/components/EmbedCodeContent.jsx | 11 +-
.../ExploreChartHeader/ExploreChartHeader.test.tsx | 31 +-
.../components/ExploreChartHeader/index.jsx | 9 +-
.../ExploreChartHeader/useExploreMetadataBar.tsx | 3 +-
.../ExploreChartPanel/ExploreChartPanel.test.jsx | 6 +-
.../explore/components/ExploreChartPanel/index.tsx | 18 +-
.../ExploreViewContainer.test.tsx | 448 +-
.../components/ExploreViewContainer/index.jsx | 47 +-
.../components/ExportToCSVDropdown/index.tsx | 2 +-
.../PropertiesModal/PropertiesModal.test.tsx | 2 +-
.../explore/components/PropertiesModal/index.tsx | 2 +-
.../explore/components/RunQueryButton/index.tsx | 2 +-
.../src/explore/components/SaveModal.test.jsx | 405 +-
.../src/explore/components/SaveModal.tsx | 296 +-
.../AnnotationLayer.test.tsx | 45 +-
.../{AnnotationLayer.jsx => AnnotationLayer.tsx} | 419 +-
.../{AnnotationTypes.js => AnnotationTypes.ts} | 42 +-
.../controls/AnnotationLayerControl/index.tsx | 2 +-
...undsControl.test.jsx => BoundsControl.test.tsx} | 0
.../explore/components/controls/BoundsControl.tsx | 2 +-
.../{CheckboxControl.jsx => CheckboxControl.tsx} | 52 +-
.../CollectionControl/{index.jsx => index.tsx} | 92 +-
.../ColorBreakpointPopoverControl.tsx | 3 +-
.../controls/ColorBreakpointsControl/index.tsx | 2 +-
.../controls/ColorSchemeControl/index.tsx | 2 +-
.../ColumnConfigControl/ColumnConfigControl.tsx | 4 +-
.../controls/ColumnConfigControl/constants.tsx | 5 +-
.../components/controls/ComparisonRangeLabel.tsx | 2 +-
.../ConditionalFormattingControl.tsx | 2 +-
.../FormattingPopoverContent.tsx | 2 +-
.../controls/ContourControl/ContourOption.tsx | 2 +-
.../ContourControl/ContourPopoverControl.tsx | 3 +-
.../components/controls/ContourControl/index.tsx | 2 +-
.../CurrencyControl/CurrencyControl.test.tsx} | 33 +-
.../controls/CurrencyControl/CurrencyControl.tsx | 88 +-
.../DatasourceControl/DatasourceControl.test.tsx | 250 +-
.../DatasourceControl/{index.jsx => index.tsx} | 153 +-
.../controls/DateFilterControl/DateFilterLabel.tsx | 2 +-
.../DateFilterControl/components/AdvancedFrame.tsx | 3 +-
.../DateFilterControl/components/CalendarFrame.tsx | 2 +-
.../DateFilterControl/components/CommonFrame.tsx | 2 +-
.../components/CurrentCalendarFrame.tsx | 2 +-
.../DateFilterControl/components/CustomFrame.tsx | 3 +-
.../components/DateFunctionTooltip.tsx | 2 +-
.../DateFilterControl/components/DateLabel.tsx | 2 +-
.../tests/DateFilterLabel.test.tsx | 43 +
.../controls/DateFilterControl/utils/constants.ts | 2 +-
.../DndColumnSelectControl/ColumnSelectPopover.tsx | 10 +-
.../ColumnSelectPopoverTrigger.tsx | 2 +-
.../DndAdhocFilterOption.tsx | 2 +-
.../DndColumnMetricSelect.tsx | 19 +-
.../DndColumnSelectControl/DndColumnSelect.tsx | 10 +-
...erTitle.jsx => DndColumnSelectPopoverTitle.tsx} | 13 +-
.../DndFilterSelect.test.tsx | 22 +-
.../DndColumnSelectControl/DndFilterSelect.tsx | 3 +-
.../DndMetricSelect.test.tsx | 14 +-
.../DndColumnSelectControl/DndMetricSelect.tsx | 27 +-
.../DndColumnSelectControl/DndSelectLabel.tsx | 2 +-
.../controls/DndColumnSelectControl/Option.tsx | 2 +-
.../DndColumnSelectControl/utils/optionSelector.ts | 2 +-
.../{AdhocFilter.test.js => AdhocFilter.test.ts} | 0
.../AdhocFilter/{index.js => index.ts} | 110 +-
.../AdhocFilterControl/AdhocFilterControl.test.tsx | 49 +-
.../AdhocFilterControl/{index.jsx => index.tsx} | 164 +-
...er.test.jsx => AdhocFilterEditPopover.test.tsx} | 24 +-
.../{index.jsx => index.tsx} | 162 +-
...AdhocFilterEditPopoverSimpleTabContent.test.tsx | 153 +-
.../index.tsx | 90 +-
.../useAdvancedDataTypes.ts | 3 +-
.../AdhocFilterEditPopoverSqlTabContent.test.tsx | 16 +-
.../AdhocFilterEditPopoverSqlTabContent/index.tsx | 9 +-
.../FilterControl/AdhocFilterOption/index.tsx | 25 +-
.../AdhocFilterPopoverTrigger/index.tsx | 2 +-
.../utils/useDatePickerInAdhocFilter.tsx | 9 +-
.../FilterControl/utils/useGetTimeRangeLabel.tsx | 31 +-
.../FixedOrMetricControl/{index.jsx => index.tsx} | 90 +-
.../controls/LayerConfigsControl/FlatLayerTree.tsx | 2 +-
.../LayerConfigsControl/LayerConfigsControl.tsx | 2 +-
.../LayerConfigsPopoverContent.tsx | 3 +-
.../controls/MapViewControl/ExtentTag.tsx | 2 +-
.../controls/MapViewControl/MapViewControl.tsx | 2 +-
.../MapViewControl/MapViewPopoverContent.tsx | 2 +-
.../controls/MatrixifyDimensionControl.test.tsx | 119 +
.../controls/MatrixifyDimensionControl.tsx | 39 +-
.../{AdhocMetric.test.js => AdhocMetric.test.ts} | 4 +-
.../{AdhocMetric.js => AdhocMetric.ts} | 102 +-
.../{index.jsx => index.tsx} | 174 +-
.../MetricControl/AdhocMetricEditPopoverTitle.tsx | 2 +-
...cOption.test.jsx => AdhocMetricOption.test.tsx} | 27 +-
...AdhocMetricOption.jsx => AdhocMetricOption.tsx} | 46 +-
.../MetricControl/AdhocMetricPopoverTrigger.tsx | 23 +-
...on.test.jsx => FilterDefinitionOption.test.tsx} | 6 +-
...nitionOption.jsx => FilterDefinitionOption.tsx} | 25 +-
...lue.test.jsx => MetricDefinitionValue.test.tsx} | 26 +-
...finitionValue.jsx => MetricDefinitionValue.tsx} | 31 +-
...icsControl.test.jsx => MetricsControl.test.tsx} | 17 +-
.../{MetricsControl.jsx => MetricsControl.tsx} | 96 +-
.../{adhocMetricType.js => adhocMetricType.ts} | 0
.../components/controls/MetricControl/types.ts | 2 +
.../controls/NumberControl/NumberControl.test.tsx | 50 +-
.../components/controls/NumberControl/index.tsx | 14 +-
.../components/controls/OptionControls/index.tsx | 6 +-
.../controls/SelectAsyncControl/index.tsx | 21 +-
...lectControl.test.jsx => SelectControl.test.tsx} | 34 +-
.../{SelectControl.jsx => SelectControl.tsx} | 155 +-
.../{SpatialControl.jsx => SpatialControl.tsx} | 125 +-
...eaControl.test.jsx => TextAreaControl.test.tsx} | 0
.../{TextAreaControl.jsx => TextAreaControl.tsx} | 106 +-
.../components/controls/TextControl/index.tsx | 4 +
.../{index.jsx => index.tsx} | 111 +-
.../explore/components/controls/ViewQuery.test.tsx | 60 +-
.../src/explore/components/controls/ViewQuery.tsx | 3 +-
.../components/controls/ViewQueryModal.test.tsx | 59 +-
.../explore/components/controls/ViewQueryModal.tsx | 2 +-
.../components/controls/ViewQueryModalFooter.tsx | 3 +-
...rtControl.test.jsx => ViewportControl.test.tsx} | 2 +-
.../{ViewportControl.jsx => ViewportControl.tsx} | 90 +-
.../controls/VizTypeControl/FastVizSwitcher.tsx | 6 +-
.../components/controls/VizTypeControl/VizTile.tsx | 2 +-
.../VizTypeControl/VizTypeControl.test.tsx | 29 +
.../controls/VizTypeControl/VizTypeGallery.tsx | 2 +-
.../controls/VizTypeControl/constants.tsx | 6 +
.../components/controls/VizTypeControl/index.tsx | 3 +-
.../components/controls/XAxisSortControl.tsx | 16 +-
.../ZoomConfigControl/ZoomConfigControl.tsx | 2 +-
.../ZoomConfigControl/ZoomConfigsChart.tsx | 2 +-
.../components/controls/withAsyncVerification.tsx | 3 +-
.../DashboardsSubMenu.tsx | 2 +-
.../useExploreAdditionalActionsMenu/index.jsx | 30 +-
superset-frontend/src/explore/constants.ts | 2 +-
.../src/explore/controlPanels/Separator.ts | 3 +-
.../src/explore/controlPanels/sections.tsx | 2 +-
.../src/explore/controlUtils/controlUtils.test.tsx | 2 +-
.../src/explore/controlUtils/getColumnKeywords.tsx | 2 +-
.../src/explore/controlUtils/getControlState.ts | 3 +-
.../explore/controlUtils/standardizedFormData.ts | 24 +-
.../src/explore/{controls.jsx => controls.tsx} | 81 +-
.../src/explore/exploreUtils/exportChart.test.ts | 173 +
.../src/explore/exploreUtils/index.js | 25 +-
superset-frontend/src/explore/fixtures.tsx | 3 +-
.../src/explore/reducers/exploreReducer.js | 4 +-
.../src/explore/{store.test.jsx => store.test.tsx} | 5 +-
.../src/explore/{store.js => store.ts} | 62 +-
superset-frontend/src/explore/types.ts | 19 +
.../src/extensions/ExtensionPlaceholder.tsx | 2 +-
.../src/extensions/ExtensionsList.test.tsx | 16 +-
.../src/extensions/ExtensionsList.tsx | 2 +-
.../src/extensions/ExtensionsManager.test.ts | 4 +-
.../src/extensions/ExtensionsManager.ts | 69 +-
.../src/extensions/ExtensionsStartup.test.tsx | 8 +-
.../src/extensions/ExtensionsStartup.tsx | 12 +-
.../src/features/alerts/AlertReportModal.test.tsx | 77 +-
.../src/features/alerts/AlertReportModal.tsx | 52 +-
.../alerts/components/AlertReportCronScheduler.tsx | 2 +-
.../features/alerts/components/AlertStatusIcon.tsx | 2 +-
.../alerts/components/NotificationMethod.tsx | 2 +-
.../features/allEntities/AllEntitiesTable.test.tsx | 7 -
.../src/features/allEntities/AllEntitiesTable.tsx | 2 +-
.../annotationLayers/AnnotationLayerModal.tsx | 2 +-
.../src/features/annotations/AnnotationModal.tsx | 40 +-
.../src/features/charts/ChartCard.tsx | 3 +-
.../src/features/cssTemplates/CssTemplateModal.tsx | 13 +-
.../src/features/dashboards/DashboardCard.tsx | 2 +-
.../DatabaseConnectionForm/CommonParameters.tsx | 2 +-
.../DatabaseConnectionForm/EncryptedField.tsx | 2 +-
.../DatabaseConnectionForm/TableCatalog.tsx | 2 +-
.../DatabaseConnectionForm/ValidatedInputField.tsx | 2 +-
.../databases/DatabaseModal/ExtraOptions.test.tsx | 2 +-
.../databases/DatabaseModal/ExtraOptions.tsx | 2 +-
.../databases/DatabaseModal/ModalHeader.tsx | 2 +-
.../databases/DatabaseModal/SSHTunnelForm.tsx | 2 +-
.../databases/DatabaseModal/SSHTunnelSwitch.tsx | 3 +-
.../databases/DatabaseModal/SqlAlchemyForm.tsx | 2 +-
.../databases/DatabaseModal/index.test.tsx | 10 +-
.../src/features/databases/DatabaseModal/index.tsx | 3 +-
.../databases/UploadDataModel/ColumnsPreview.tsx | 2 +-
.../UploadDataModel/UploadDataModal.test.tsx | 19 +-
.../features/databases/UploadDataModel/index.tsx | 23 +-
.../AddDataset/DatasetPanel/DatasetPanel.tsx | 27 +-
.../AddDataset/DatasetPanel/MessageContent.tsx | 5 +-
.../datasets/AddDataset/DatasetPanel/index.tsx | 4 +-
.../AddDataset/EditDataset/EditDataset.test.tsx | 2 +-
.../datasets/AddDataset/EditDataset/index.tsx | 2 +-
.../features/datasets/AddDataset/Footer/index.tsx | 2 +-
.../features/datasets/AddDataset/Header/index.tsx | 2 +-
.../AddDataset/LeftPanel/LeftPanel.test.tsx | 6 +-
.../datasets/AddDataset/LeftPanel/index.tsx | 6 +-
.../features/datasets/DatasetSelectLabel/index.tsx | 2 +-
.../datasets/DuplicateDatasetModal.test.tsx | 282 +
.../features/datasets/DuplicateDatasetModal.tsx | 2 +-
.../src/features/datasets/constants.ts | 2 +-
.../datasets/hooks/useDatasetLists.test.ts | 180 +-
.../src/features/datasets/hooks/useDatasetLists.ts | 4 +-
.../datasets/hooks/useGetDatasetRelatedCounts.ts | 4 +-
.../metadataBar/useDatasetMetadataBar.test.tsx | 2 +-
.../datasets/metadataBar/useDatasetMetadataBar.tsx | 2 +-
superset-frontend/src/features/datasets/types.ts | 1 +
.../src/features/groups/GroupListModal.tsx | 2 +-
superset-frontend/src/features/groups/utils.ts | 3 +-
.../src/features/home/ActivityTable.tsx | 2 +-
.../src/features/home/ChartTable.test.tsx | 4 +-
superset-frontend/src/features/home/ChartTable.tsx | 2 +-
.../src/features/home/DashboardTable.test.tsx | 8 +-
.../src/features/home/DashboardTable.tsx | 3 +-
superset-frontend/src/features/home/EmptyState.tsx | 2 +-
.../src/features/home/LanguagePicker.tsx | 2 +-
superset-frontend/src/features/home/Menu.test.tsx | 45 +-
superset-frontend/src/features/home/Menu.tsx | 20 +-
.../src/features/home/RightMenu.test.tsx | 57 +-
superset-frontend/src/features/home/RightMenu.tsx | 5 +-
.../src/features/home/SavedQueries.tsx | 3 +-
superset-frontend/src/features/home/SubMenu.tsx | 2 +-
.../src/features/home/commonMenuData.ts | 2 +-
.../src/features/queries/QueryPreviewModal.tsx | 2 +-
.../features/queries/SavedQueryPreviewModal.tsx | 2 +-
.../src/features/queries/SyntaxHighlighterCopy.tsx | 2 +-
.../ReportModal/HeaderReportDropdown/index.tsx | 2 +-
.../reports/ReportModal/ReportModal.test.tsx | 6 +-
.../src/features/reports/ReportModal/actions.js | 3 +-
.../src/features/reports/ReportModal/index.tsx | 3 +-
.../features/rls/RowLevelSecurityModal.test.tsx | 34 +-
.../src/features/rls/RowLevelSecurityModal.tsx | 3 +-
superset-frontend/src/features/rls/constants.ts | 2 +-
.../src/features/roles/RoleFormItems.tsx | 2 +-
.../src/features/roles/RoleListAddModal.tsx | 2 +-
.../src/features/roles/RoleListDuplicateModal.tsx | 2 +-
.../src/features/roles/RoleListEditModal.tsx | 2 +-
.../src/features/tags/BulkTagModal.test.tsx | 2 +-
.../src/features/tags/BulkTagModal.tsx | 3 +-
superset-frontend/src/features/tags/TagCard.tsx | 3 +-
superset-frontend/src/features/tags/TagModal.tsx | 3 +-
.../src/features/themes/ThemeModal.test.tsx | 35 +-
.../src/features/themes/ThemeModal.tsx | 47 +-
superset-frontend/src/features/themes/api.test.ts | 32 +-
.../src/features/userInfo/UserInfoModal.tsx | 3 +-
.../src/features/users/UserListModal.tsx | 2 +-
superset-frontend/src/features/users/utils.ts | 3 +-
.../filters/components/Range/RangeFilterPlugin.tsx | 2 +-
.../src/filters/components/Range/controlPanel.ts | 2 +-
.../src/filters/components/Range/index.ts | 3 +-
.../components/Select/SelectFilterPlugin.test.tsx | 457 +-
.../components/Select/SelectFilterPlugin.tsx | 6 +-
.../src/filters/components/Select/controlPanel.ts | 3 +-
.../src/filters/components/Select/index.ts | 3 +-
.../src/filters/components/Time/controlPanel.ts | 2 +-
.../src/filters/components/Time/index.ts | 3 +-
.../TimeColumn/TimeColumnFilterPlugin.tsx | 3 +-
.../filters/components/TimeColumn/controlPanel.ts | 2 +-
.../src/filters/components/TimeColumn/index.ts | 3 +-
.../components/TimeGrain/TimeGrainFilterPlugin.tsx | 4 +-
.../filters/components/TimeGrain/controlPanel.ts | 2 +-
.../src/filters/components/TimeGrain/index.ts | 3 +-
.../src/hooks/apiResources/dashboards.test.ts | 2 +-
.../hooks/apiResources/databaseFunctions.test.ts | 12 +-
.../src/hooks/apiResources/datasets.ts | 2 +-
.../src/hooks/apiResources/queries.test.ts | 12 +-
.../src/hooks/apiResources/queryApi.test.ts | 10 +-
.../hooks/apiResources/queryValidations.test.ts | 16 +-
.../src/hooks/apiResources/schemas.test.ts | 46 +-
.../src/hooks/apiResources/sqlEditorTabs.test.ts | 18 +-
.../src/hooks/apiResources/sqlLab.test.ts | 22 +-
.../src/hooks/apiResources/tables.test.ts | 53 +-
superset-frontend/src/hooks/apiResources/tables.ts | 9 +-
superset-frontend/src/hooks/useThemeMenuItems.tsx | 2 +-
.../src/hooks/useUnsavedChangesPrompt/index.ts | 3 +-
.../src/middleware/asyncEvent.test.ts | 38 +-
superset-frontend/src/middleware/asyncEvent.ts | 2 +-
superset-frontend/src/pages/ActionLog/index.tsx | 2 +-
.../pages/AlertReportList/AlertReportList.test.jsx | 17 +-
.../src/pages/AlertReportList/index.tsx | 2 +-
superset-frontend/src/pages/AllEntities/index.tsx | 2 +-
.../AnnotationLayerList.test.jsx | 17 +-
.../src/pages/AnnotationLayerList/index.tsx | 3 +-
.../src/pages/AnnotationList/index.tsx | 3 +-
superset-frontend/src/pages/Chart/Chart.test.tsx | 31 +-
superset-frontend/src/pages/Chart/index.tsx | 2 +-
.../src/pages/ChartCreation/ChartCreation.test.tsx | 12 +-
.../src/pages/ChartCreation/index.tsx | 3 +-
.../pages/ChartList/ChartList.cardview.test.tsx | 5 +-
.../pages/ChartList/ChartList.listview.test.tsx | 33 +-
.../pages/ChartList/ChartList.permissions.test.tsx | 24 +-
.../src/pages/ChartList/ChartList.test.tsx | 113 +-
.../src/pages/ChartList/ChartList.testHelpers.tsx | 52 +-
superset-frontend/src/pages/ChartList/index.tsx | 23 +-
.../pages/CssTemplateList/CssTemplateList.test.jsx | 17 +-
.../src/pages/CssTemplateList/index.tsx | 3 +-
.../src/pages/DashboardList/DashboardList.test.jsx | 15 +-
.../src/pages/DashboardList/index.tsx | 2 +-
superset-frontend/src/pages/DatabaseList/index.tsx | 3 +-
.../DatasetList/DatasetList.behavior.test.tsx | 521 +
.../DatasetList/DatasetList.integration.test.tsx | 258 +
.../DatasetList/DatasetList.listview.test.tsx | 2319 +++
.../DatasetList/DatasetList.permissions.test.tsx | 448 +
.../src/pages/DatasetList/DatasetList.test.tsx | 589 +
.../pages/DatasetList/DatasetList.testHelpers.tsx | 564 +
superset-frontend/src/pages/DatasetList/index.tsx | 3 +-
.../ExecutionLogList/ExecutionLogList.test.tsx | 8 +-
.../src/pages/ExecutionLogList/index.tsx | 2 +-
.../src/pages/FileHandler/index.test.tsx | 368 +
superset-frontend/src/pages/FileHandler/index.tsx | 138 +
.../src/pages/GroupsList/GroupsList.test.tsx | 15 +-
superset-frontend/src/pages/GroupsList/index.tsx | 2 +-
superset-frontend/src/pages/Home/Home.test.tsx | 26 +-
superset-frontend/src/pages/Home/index.tsx | 2 +-
superset-frontend/src/pages/Login/index.tsx | 3 +-
.../src/pages/QueryHistoryList/index.tsx | 3 +-
superset-frontend/src/pages/Register/index.tsx | 3 +-
.../src/pages/RolesList/RolesList.test.tsx | 9 +-
superset-frontend/src/pages/RolesList/index.tsx | 3 +-
.../RowLevelSecurityList.test.tsx | 55 +-
.../src/pages/RowLevelSecurityList/index.tsx | 3 +-
.../pages/SavedQueryList/SavedQueryList.test.tsx | 64 +-
.../src/pages/SavedQueryList/index.tsx | 2 +-
.../src/pages/SqlLab/LocationContext.tsx | 3 +-
superset-frontend/src/pages/SqlLab/SqlLab.test.tsx | 10 +-
superset-frontend/src/pages/Tags/index.tsx | 3 +-
.../src/pages/ThemeList/ThemeList.test.tsx | 4 +-
superset-frontend/src/pages/ThemeList/index.tsx | 25 +-
.../src/pages/UserInfo/UserInfo.test.tsx | 9 +-
superset-frontend/src/pages/UserInfo/index.tsx | 3 +-
.../UserRegistrations/UserRegistrations.test.tsx | 2 +-
.../src/pages/UserRegistrations/index.tsx | 3 +-
.../src/pages/UsersList/UsersList.test.tsx | 12 +-
superset-frontend/src/pages/UsersList/index.tsx | 2 +-
superset-frontend/src/preamble.ts | 10 +-
superset-frontend/src/pwa-manifest.json | 65 +
.../Avatar.stories.tsx => src/service-worker.ts} | 35 +-
superset-frontend/src/setup/setupClient.ts | 3 +-
superset-frontend/src/types/bootstrapTypes.ts | 4 +-
superset-frontend/src/utils/assetUrl.test.ts | 48 +
superset-frontend/src/utils/assetUrl.ts | 13 +-
superset-frontend/src/utils/downloadAsImage.tsx | 2 +-
superset-frontend/src/utils/downloadAsPdf.ts | 3 +-
superset-frontend/src/utils/export.test.ts | 57 +-
superset-frontend/src/utils/export.ts | 8 +-
superset-frontend/src/utils/fetchOptions.ts | 3 +-
.../utils/getChartRequiredFieldsMissingMessage.ts | 2 +-
superset-frontend/src/utils/urlUtils.ts | 67 +-
superset-frontend/src/views/CRUD/hooks.ts | 2 +-
superset-frontend/src/views/CRUD/utils.tsx | 3 +-
.../src/views/RootContextProviders.tsx | 11 +-
superset-frontend/src/views/menu.tsx | 12 +-
superset-frontend/src/views/routes.tsx | 8 +
superset-frontend/src/views/store.ts | 2 -
.../src/visualizations/TimeTable/TimeTable.tsx | 2 +-
.../TimeTable/config/controlPanel/controlPanel.ts | 3 +-
.../src/visualizations/TimeTable/index.ts | 3 +-
.../ExampleComponent/ExampleComponent.tsx | 3 +-
.../src/visualizations/presets/MainPreset.js | 20 +-
superset-frontend/webpack.config.js | 49 +-
superset-websocket/.nvmrc | 2 +-
superset-websocket/Dockerfile | 4 +-
superset-websocket/README.md | 2 +-
superset-websocket/package-lock.json | 777 +-
superset-websocket/package.json | 24 +-
superset/charts/schemas.py | 23 +
superset/cli/examples.py | 89 +-
superset/cli/export_example.py | 234 +
.../{examples/big_data.py => cli/test_loaders.py} | 12 +
superset/commands/dashboard/exceptions.py | 4 +
superset/commands/dashboard/export_example.py | 672 +
superset/commands/dashboard/update.py | 18 +
superset/commands/database/importers/v1/utils.py | 16 +-
superset/commands/database/oauth2.py | 22 +
superset/commands/database/test_connection.py | 18 +-
superset/commands/database/uploaders/base.py | 6 +
superset/commands/database/validate_sql.py | 44 +
superset/commands/dataset/update.py | 13 +-
superset/commands/importers/v1/examples.py | 93 +-
superset/commands/importers/v1/utils.py | 43 +
superset/commands/logs/prune.py | 6 +-
superset/commands/report/alert.py | 66 +-
superset/commands/report/create.py | 35 +
superset/commands/report/exceptions.py | 15 +
superset/commands/report/execute.py | 18 +-
superset/commands/streaming_export/base.py | 55 +-
superset/common/query_actions.py | 65 +
superset/common/query_context_factory.py | 34 +
superset/common/query_context_processor.py | 1 +
superset/common/utils/query_cache_manager.py | 11 +
superset/config.py | 385 +-
superset/connectors/sqla/models.py | 15 +-
superset/daos/dashboard.py | 64 +
superset/dashboards/api.py | 183 +
superset/dashboards/schemas.py | 9 +
superset/databases/api.py | 1 +
superset/databases/schemas.py | 7 +
superset/dataframe.py | 7 +-
superset/datasets/api.py | 2 +
superset/datasets/schemas.py | 6 +
superset/db_engine_specs/METADATA_STATUS.md | 153 +
superset/db_engine_specs/README.md | 154 +
superset/db_engine_specs/arc.py | 80 +
superset/db_engine_specs/ascend.py | 19 +
superset/db_engine_specs/athena.py | 100 +-
superset/db_engine_specs/aurora.py | 38 +
superset/db_engine_specs/aws_iam.py | 660 +
superset/db_engine_specs/base.py | 253 +-
superset/db_engine_specs/bigquery.py | 58 +-
superset/db_engine_specs/clickhouse.py | 118 +-
superset/db_engine_specs/cockroachdb.py | 17 +
superset/db_engine_specs/couchbase.py | 30 +
superset/db_engine_specs/crate.py | 26 +-
superset/db_engine_specs/d1.py | 51 +
superset/db_engine_specs/databend.py | 39 +-
superset/db_engine_specs/databricks.py | 96 +-
superset/db_engine_specs/db2.py | 57 +-
superset/db_engine_specs/denodo.py | 36 +-
superset/db_engine_specs/doris.py | 27 +
superset/db_engine_specs/dremio.py | 36 +-
superset/db_engine_specs/drill.py | 57 +-
superset/db_engine_specs/druid.py | 74 +-
superset/db_engine_specs/duckdb.py | 84 +-
superset/db_engine_specs/dynamodb.py | 27 +-
superset/db_engine_specs/elasticsearch.py | 91 +-
superset/db_engine_specs/exasol.py | 47 +-
superset/db_engine_specs/firebird.py | 22 +-
superset/db_engine_specs/firebolt.py | 39 +-
superset/db_engine_specs/greenplum.py | 55 +
superset/db_engine_specs/gsheets.py | 147 +-
superset/db_engine_specs/hana.py | 18 +
superset/db_engine_specs/hive.py | 24 +-
superset/db_engine_specs/hologres.py | 60 +
superset/db_engine_specs/ibmi.py | 6 +
superset/db_engine_specs/impala.py | 19 +-
superset/db_engine_specs/iotdb.py | 59 +
superset/db_engine_specs/kusto.py | 66 +-
superset/db_engine_specs/kylin.py | 19 +-
superset/db_engine_specs/lib.py | 167 +-
superset/db_engine_specs/lint_metadata.py | 705 +
superset/db_engine_specs/mariadb.py | 15 +
superset/db_engine_specs/monetdb.py | 75 +
superset/db_engine_specs/mongodb.py | 125 +
superset/db_engine_specs/mssql.py | 56 +-
superset/db_engine_specs/mysql.py | 163 +-
superset/db_engine_specs/netezza.py | 14 +
superset/db_engine_specs/oceanbase.py | 13 +
superset/db_engine_specs/ocient.py | 13 +-
superset/db_engine_specs/oracle.py | 17 +-
superset/db_engine_specs/parseable.py | 27 +-
superset/db_engine_specs/phoenix.py | 86 +
superset/db_engine_specs/pinot.py | 36 +-
superset/db_engine_specs/postgres.py | 297 +-
superset/db_engine_specs/presto.py | 32 +-
superset/db_engine_specs/redshift.py | 142 +-
superset/db_engine_specs/risingwave.py | 17 +
superset/db_engine_specs/shillelagh.py | 17 +
superset/db_engine_specs/singlestore.py | 36 +
superset/db_engine_specs/snowflake.py | 58 +-
superset/db_engine_specs/solr.py | 19 +-
superset/db_engine_specs/spark.py | 15 +
superset/db_engine_specs/sqlite.py | 15 +-
superset/db_engine_specs/starrocks.py | 240 +-
superset/db_engine_specs/superset.py | 17 +
superset/db_engine_specs/sybase.py | 54 +
superset/db_engine_specs/tdengine.py | 19 +-
superset/db_engine_specs/teradata.py | 31 +-
superset/db_engine_specs/timescaledb.py | 62 +
superset/db_engine_specs/trino.py | 156 +-
superset/db_engine_specs/vertica.py | 23 +
superset/db_engine_specs/ydb.py | 47 +-
superset/db_engine_specs/yugabytedb.py | 53 +
superset/engines/manager.py | 7 +-
.../metadata.yaml => _shared/database.yaml} | 7 +-
.../examples/{configs => _shared}/metadata.yaml | 0
superset/examples/bart_lines.py | 71 -
.../Vaccine_Candidates_per_Approach__Stage.yaml | 50 -
.../Vaccine_Candidates_per_Country.yaml | 39 -
.../Vaccine_Candidates_per_Country_261.yaml | 59 -
.../Vaccine_Candidates_per_Country__Stage.yaml | 40 -
.../Vaccine_Candidates_per_Country__Stage_749.yaml | 46 -
.../Vaccine_Candidates_per_Phase.yaml | 46 -
.../Vaccine_Candidates_per_Phase_587.yaml | 39 -
.../configs/charts/Featured Charts/Radar.yaml | 100 -
.../configs/charts/Vehicle Sales/Items_Sold.yaml | 73 -
.../Vehicle Sales/Items_by_Product_Line.yaml | 80 -
.../Overall_Sales_By_Product_Line.yaml | 76 -
.../Proportion_of_Revenue_by_Product_Line.yaml | 99 -
.../charts/Vehicle Sales/Quarterly_Sales.yaml | 101 -
.../Quarterly_Sales_By_Product_Line_113.yaml | 103 -
.../charts/Vehicle Sales/Total_Revenue.yaml | 79 -
.../Games_per_Genre_over_time.yaml | 118 -
.../Rise__Fall_of_Video_Game_Consoles.yaml | 133 -
...op_10_Games_Proportion_of_Sales_in_Markets.yaml | 132 -
.../Total_Sales_per_Market_Grouped_by_Genre.yaml | 182 -
.../dashboards/COVID_Vaccine_Dashboard.yaml | 399 -
.../configs/dashboards/Unicode_Test.test.yaml | 52 -
.../configs/datasets/examples/FCC_2018_Survey.yaml | 1493 --
.../datasets/examples/hierarchical_dataset.yaml | 116 -
.../datasets/examples/project_management.yaml | 293 -
.../examples/configs/datasets/examples/users.yaml | 223 -
.../examples/users_channels-uzooNNtSRO.yaml | 77 -
.../configs/datasets/examples/users_channels.yaml | 63 -
.../datasets/examples/video_game_sales.yaml | 156 -
superset/examples/country_map.py | 123 -
superset/examples/data_loading.py | 198 +-
superset/examples/deck.py | 547 -
.../charts/Deck.gl_Arcs.yaml} | 74 +-
.../charts/Deck.gl_Grid.yaml} | 74 +-
.../charts/Deck.gl_Hexagons.yaml} | 73 +-
.../charts/Deck.gl_Path.yaml} | 75 +-
.../deckgl_demo/charts/Deck.gl_Polygons.yaml | 104 +
.../charts/Deck.gl_Scatterplot.yaml} | 70 +-
.../charts/Deck.gl_Screen_grid.yaml} | 72 +-
superset/examples/deckgl_demo/dashboard.yaml | 160 +
.../examples/deckgl_demo/data/bart_lines.parquet | Bin 0 -> 9596 bytes
superset/examples/deckgl_demo/data/flights.parquet | Bin 0 -> 2059330 bytes
.../examples/deckgl_demo/data/long_lat.parquet | Bin 0 -> 11861181 bytes
.../data/sf_population_polygons.parquet | Bin 0 -> 91894 bytes
.../datasets/bart_lines.yaml} | 123 +-
.../datasets/flights.yaml} | 755 +-
.../datasets/long_lat.yaml} | 303 +-
.../datasets/sf_population_polygons.yaml} | 123 +-
superset/examples/energy.py | 147 -
.../charts}/Age_distribution_of_respondents.yaml | 17 +-
.../Are_you_an_ethnic_minority_in_your_city.yaml | 15 +-
.../charts}/Breakdown_of_Developer_Type.yaml | 13 +-
.../charts}/Commute_Time.yaml | 51 +-
.../charts}/Country_of_Citizenship.yaml | 13 +-
...elopers_Is_this_your_first_development_job.yaml | 13 +-
.../charts}/Degrees_vs_Income.yaml | 15 +-
.../charts/Ethnic_Minority_Gender.yaml} | 53 +-
.../charts}/First_Time_Developer.yaml | 15 +-
.../charts/First_Time_Developer_Commute_Time.yaml} | 71 +-
.../charts}/Gender.yaml | 15 +-
.../charts}/Highest_degree_held.yaml | 13 +-
.../charts}/How_do_you_prefer_to_work.yaml | 77 +-
.../How_much_do_you_expect_to_earn_0_-_100k.yaml | 53 +-
.../charts}/Last_Year_Income_Distribution.yaml | 53 +-
.../charts}/Location_of_Current_Developers.yaml | 13 +-
.../charts}/Number_of_Aspiring_Developers.yaml | 13 +-
.../charts}/Preferred_Employment_Style.yaml | 51 +-
.../charts}/Relocation_ability.yaml | 15 +-
.../charts}/Top_15_Languages_Spoken_at_Home.yaml | 13 +-
.../charts}/Work_Location_Preference.yaml | 15 +-
.../dashboard.yaml} | 624 +-
.../examples/fcc_new_coder_survey/data.parquet | Bin 0 -> 2829468 bytes
.../examples/fcc_new_coder_survey/dataset.yaml | 1760 +++
.../charts}/Area.yaml | 137 +-
.../charts}/Bar.yaml | 87 +-
.../charts}/Big_Number.yaml | 38 +-
.../charts}/Big_Number_with_Trendline.yaml | 54 +-
.../charts}/Box_Plot.yaml | 56 +-
.../charts}/Bubble.yaml | 82 +-
.../charts}/Funnel.yaml | 52 +-
.../charts}/Gantt.yaml | 63 +-
.../charts}/Gauge.yaml | 56 +-
.../charts}/Graph.yaml | 60 +-
.../charts}/Heatmap.yaml | 64 +-
.../charts}/Histogram.yaml | 46 +-
.../charts}/Line.yaml | 91 +-
.../charts}/Mixed.yaml | 153 +-
.../charts}/Pie.yaml | 64 +-
.../charts}/Pivot_Table.yaml | 104 +-
.../charts/Radar.yaml} | 105 +-
.../charts}/Sankey.yaml | 38 +-
.../charts}/Scatter_Plot.yaml | 87 +-
.../charts}/Sunburst.yaml | 48 +-
.../charts}/Table.yaml | 64 +-
.../charts}/Tree.yaml | 36 +-
.../charts/TreeMap.yaml} | 43 +-
.../charts}/Waterfall.yaml | 70 +-
.../charts}/Word_Cloud.yaml | 40 +-
.../dashboard.yaml} | 353 +-
.../data/cleaned_sales_data.parquet | Bin 0 -> 91814 bytes
.../data/hierarchical_dataset.parquet | Bin 0 -> 3298 bytes
.../data/project_management.parquet | Bin 0 -> 6348 bytes
.../datasets/cleaned_sales_data.yaml} | 507 +-
.../datasets/hierarchical_dataset.yaml} | 125 +-
.../datasets/project_management.yaml} | 155 +-
superset/examples/flights.py | 76 -
superset/examples/generic_loader.py | 251 +
superset/examples/helpers.py | 157 +-
superset/examples/international_sales/data.parquet | Bin 0 -> 9582 bytes
.../dataset.yaml} | 308 +-
superset/examples/long_lat.py | 127 -
.../Birth_in_France_by_department_in_2016.yaml} | 47 +-
.../charts/Parallel_Coordinates.yaml} | 55 +-
.../charts/Unicode_Cloud.yaml} | 49 +-
superset/examples/misc_charts/dashboard.yaml | 161 +
.../data/birth_france_by_region.parquet | Bin 0 -> 16028 bytes
.../examples/misc_charts/data/unicode_test.parquet | Bin 0 -> 9546 bytes
.../misc_charts/data/wb_health_population.parquet | Bin 0 -> 15010872 bytes
.../datasets/birth_france_by_region.yaml} | 284 +-
.../datasets/unicode_test.yaml} | 131 +-
.../misc_charts/datasets/wb_health_population.yaml | 4319 ++++++
superset/examples/misc_dashboard.py | 145 -
superset/examples/multiformat_time_series.py | 135 -
superset/examples/paris.py | 67 -
superset/examples/random_time_series.py | 102 -
.../charts/Items_Sold.yaml} | 44 +-
.../Number_of_Deals_for_each_Combination.yaml | 22 +-
.../charts/Overall_Sales_By_Product_Line.yaml} | 42 +-
.../Proportion_of_Revenue_by_Product_Line.yaml} | 114 +-
.../charts/Quarterly_Sales.yaml} | 114 +-
.../charts}/Quarterly_Sales_By_Product_Line.yaml | 78 +-
.../charts}/Revenue_by_Deal_Size.yaml | 12 +-
.../Seasonality_of_Revenue_per_Product_Line.yaml | 14 +-
.../charts}/Total_Items_Sold_By_Product_Line.yaml | 13 +-
.../charts/Total_Revenue.yaml} | 66 +-
.../dashboard.yaml} | 990 +-
superset/examples/sales_dashboard/data.parquet | Bin 0 -> 91814 bytes
.../dataset.yaml} | 453 +-
superset/examples/sf_population_polygons.py | 71 -
.../charts}/Cross_Channel_Relationship.yaml | 13 +-
.../Cross_Channel_Relationship_heatmap_v2.yaml} | 23 +-
.../charts}/Members_per_Channel.yaml | 15 +-
.../charts}/Messages_per_Channel.yaml | 102 +-
.../charts}/New_Members_per_Month.yaml | 13 +-
.../charts}/Number_of_Members.yaml | 13 +-
.../charts}/Top_Timezones.yaml | 13 +-
.../charts}/Weekly_Messages.yaml | 13 +-
.../charts}/Weekly_Threads.yaml | 13 +-
.../dashboard.yaml} | 230 +-
.../data/members_channels_2.parquet | Bin 0 -> 29617 bytes
.../examples/slack_dashboard/data/messages.parquet | Bin 0 -> 450917 bytes
.../slack_dashboard/data/messages_channels.parquet | Bin 0 -> 271949 bytes
.../slack_dashboard/data/new_members_daily.parquet | Bin 0 -> 12439 bytes
.../examples/slack_dashboard/data/threads.parquet | Bin 0 -> 30110 bytes
.../examples/slack_dashboard/data/users.parquet | Bin 0 -> 120265 bytes
.../data/users_channels-uzooNNtSRO.parquet | Bin 0 -> 5722 bytes
.../datasets}/members_channels_2.yaml | 83 +-
.../datasets}/messages.yaml | 755 +-
.../datasets}/messages_channels.yaml | 99 +-
.../datasets}/new_members_daily.yaml | 83 +-
.../datasets}/threads.yaml | 275 +-
.../datasets/users.yaml} | 371 +-
.../datasets/users_channels-uzooNNtSRO.yaml} | 95 +-
superset/examples/supported_charts_dashboard.py | 1251 --
superset/examples/tabbed_dashboard.py | 561 -
.../charts/Boy_Name_Cloud.yaml} | 37 +-
.../charts/Boys.yaml} | 48 +-
.../charts/Genders.yaml} | 43 +-
.../charts/Genders_by_State.yaml} | 66 +-
.../charts/Girl_Name_Cloud.yaml} | 37 +-
.../charts/Girls.yaml} | 48 +-
.../charts/Participants.yaml} | 37 +-
.../charts/Pivot_Table_v2.yaml} | 47 +-
.../charts/Top_10_Boy_Name_Share.yaml} | 67 +-
.../charts/Top_10_Girl_Name_Share.yaml} | 67 +-
.../charts/Trends.yaml} | 51 +-
superset/examples/usa_births_names/dashboard.yaml | 265 +
superset/examples/usa_births_names/data.parquet | Bin 0 -> 441750 bytes
.../threads.yaml => usa_births_names/dataset.yaml} | 236 +-
superset/examples/utils.py | 218 +-
.../charts}/Games.yaml | 13 +-
.../charts}/Games_per_Genre.yaml | 122 +-
.../charts}/Most_Dominant_Platforms.yaml | 15 +-
.../charts}/Popular_Genres_Across_Platforms.yaml | 23 +-
.../charts}/Publishers_With_Most_Titles.yaml | 13 +-
...op_10_Games_Proportion_of_Sales_in_Markets.yaml | 137 +
.../Total_Sales_per_Market_Grouped_by_Genre.yaml | 186 +
...es_That_Hit_100k_in_Sales_By_Release_Year.yaml} | 109 +-
.../dashboard.yaml} | 352 +-
superset/examples/video_game_sales/data.parquet | Bin 0 -> 484723 bytes
.../threads.yaml => video_game_sales/dataset.yaml} | 253 +-
.../charts/Box_plot.yaml} | 52 +-
.../charts/Growth_Rate.yaml} | 51 +-
.../charts/Life_Expectancy_VS_Rural.yaml} | 73 +-
.../charts/Most_Populated_Countries.yaml} | 50 +-
.../charts/Rural.yaml} | 48 +-
.../charts/Rural_Breakdown.yaml} | 62 +-
.../charts}/Treemap.yaml | 50 +-
.../charts/World_s_Pop_Growth.yaml} | 47 +-
.../charts/World_s_Population.yaml} | 47 +-
superset/examples/world_health/dashboard.yaml | 205 +
superset/examples/world_health/data.parquet | Bin 0 -> 15010872 bytes
superset/examples/world_health/dataset.yaml | 4319 ++++++
superset/extensions/discovery.py | 22 +-
superset/extensions/engine_manager.py | 5 +-
superset/extensions/local_extensions_watcher.py | 45 +-
superset/extensions/types.py | 3 +
superset/extensions/utils.py | 55 +-
superset/initialization/__init__.py | 15 +-
superset/jinja_context.py | 19 +-
superset/key_value/types.py | 1 +
superset/mcp_service/CLAUDE.md | 6 +-
superset/mcp_service/__main__.py | 2 +-
superset/mcp_service/app.py | 177 +-
superset/mcp_service/auth.py | 184 +-
superset/mcp_service/chart/chart_utils.py | 24 +-
.../chart/prompts/create_chart_guided.py | 219 +-
.../mcp_service/chart/resources/chart_configs.py | 293 +-
superset/mcp_service/chart/schemas.py | 124 +-
superset/mcp_service/chart/tool/__init__.py | 2 -
superset/mcp_service/chart/tool/generate_chart.py | 35 +-
.../chart/tool/get_chart_available_filters.py | 50 -
superset/mcp_service/chart/tool/get_chart_data.py | 112 +-
.../mcp_service/chart/tool/get_chart_preview.py | 63 +-
superset/mcp_service/chart/tool/list_charts.py | 40 +-
superset/mcp_service/chart/tool/update_chart.py | 7 +-
.../mcp_service/chart/tool/update_chart_preview.py | 30 +-
.../chart/validation/runtime/__init__.py | 22 +-
superset/mcp_service/common/schema_discovery.py | 483 +
superset/mcp_service/dashboard/schemas.py | 56 +-
superset/mcp_service/dashboard/tool/__init__.py | 2 -
.../tool/get_dashboard_available_filters.py | 49 -
.../mcp_service/dashboard/tool/list_dashboards.py | 64 +-
superset/mcp_service/dataset/schemas.py | 61 +-
superset/mcp_service/dataset/tool/__init__.py | 2 -
.../dataset/tool/get_dataset_available_filters.py | 49 -
superset/mcp_service/dataset/tool/list_datasets.py | 41 +-
.../mcp_service/docs/tool-search-optimization.md | 2 +-
superset/mcp_service/flask_singleton.py | 105 +-
superset/mcp_service/mcp_config.py | 34 +-
superset/mcp_service/mcp_core.py | 120 +-
superset/mcp_service/server.py | 97 +-
superset/mcp_service/storage.py | 71 +-
superset/mcp_service/system/prompts/quickstart.py | 94 +-
superset/mcp_service/system/resources/__init__.py | 5 +-
.../system/resources/instance_metadata.py | 74 +-
.../system/resources/schema_discovery.py | 196 +
superset/mcp_service/system/tool/__init__.py | 2 +
superset/mcp_service/system/tool/get_schema.py | 167 +
superset/mcp_service/system/tool/health_check.py | 3 +-
superset/mcp_service/utils/schema_utils.py | 149 +-
superset/mcp_service/utils/url_utils.py | 28 -
...00_9787190b3d89_add_currency_column_support.py} | 39 +-
superset/models/core.py | 48 +-
superset/models/helpers.py | 30 +-
superset/reports/schemas.py | 2 +-
superset/security/manager.py | 28 +-
superset/sql/parse.py | 23 +-
superset/sqllab/query_render.py | 23 +-
superset/sqllab/utils.py | 1 +
.../static/service-worker.js | 15 +-
superset/superset_typing.py | 47 +-
superset/templates/superset/spa.html | 24 +-
superset/translations/mi/LC_MESSAGES/messages.po | 14507 +++++++++++++++++++
superset/utils/cache.py | 3 +-
superset/utils/cache_manager.py | 141 +-
superset/utils/core.py | 67 +-
superset/utils/currency.py | 160 +
superset/utils/date_parser.py | 119 +-
superset/utils/excel.py | 2 +-
superset/utils/oauth2.py | 83 +-
superset/views/base.py | 47 +-
superset/views/core.py | 36 +-
superset/views/datasource/utils.py | 6 +
superset/viz.py | 1 +
tests/integration_tests/charts/data/api_tests.py | 26 +-
tests/integration_tests/dashboards/api_tests.py | 404 +
tests/integration_tests/databases/api_tests.py | 110 +
.../integration_tests/databases/commands_tests.py | 7 +-
tests/integration_tests/datasets/commands_tests.py | 3 +
.../datasource/test_validate_expression_api.py | 8 +
tests/integration_tests/explore/api_tests.py | 92 +
tests/integration_tests/fixtures/importexport.py | 1 +
tests/integration_tests/query_context_tests.py | 6 +
tests/integration_tests/reports/alert_tests.py | 8 +-
tests/integration_tests/security_tests.py | 3 +-
tests/integration_tests/sqllab_tests.py | 66 +
tests/integration_tests/tagging_tests.py | 6 +-
.../commands/dashboard/export_example_test.py | 323 +
.../commands/databases/validate_sql_test.py | 279 +
.../commands/importers/v1/examples_test.py | 244 +
tests/unit_tests/commands/report/alert_test.py | 496 +
.../commands/report/test_create_recipients.py | 179 +
.../common/test_query_actions_currency.py | 290 +
.../common/test_query_context_factory.py | 106 +
tests/unit_tests/common/test_time_shifts.py | 33 +
tests/unit_tests/connectors/sqla/models_test.py | 104 +
.../dashboards/test_chart_customizations_dao.py | 339 +
tests/unit_tests/databases/api_test.py | 175 +-
tests/unit_tests/dataframe_test.py | 196 +-
tests/unit_tests/datasets/commands/export_test.py | 2 +
tests/unit_tests/datasets/schema_tests.py | 29 +
tests/unit_tests/db_engine_specs/test_athena.py | 70 +
tests/unit_tests/db_engine_specs/test_aurora.py | 317 +
tests/unit_tests/db_engine_specs/test_aws_iam.py | 1045 ++
tests/unit_tests/db_engine_specs/test_base.py | 321 +-
tests/unit_tests/db_engine_specs/test_bigquery.py | 5 +-
tests/unit_tests/db_engine_specs/test_gsheets.py | 257 +
tests/unit_tests/db_engine_specs/test_hive.py | 4 +-
tests/unit_tests/db_engine_specs/test_mongodb.py | 125 +
tests/unit_tests/db_engine_specs/test_mysql_iam.py | 236 +
tests/unit_tests/db_engine_specs/test_postgres.py | 4 +-
tests/unit_tests/db_engine_specs/test_presto.py | 4 +-
tests/unit_tests/db_engine_specs/test_redshift.py | 30 +
.../db_engine_specs/test_redshift_iam.py | 387 +
tests/unit_tests/db_engine_specs/test_starrocks.py | 116 +-
tests/unit_tests/db_engine_specs/test_trino.py | 480 +-
tests/unit_tests/engines/manager_test.py | 168 +-
tests/unit_tests/extensions/test_engine_manager.py | 160 -
tests/unit_tests/jinja_context_test.py | 54 +
.../mcp_service/chart/test_chart_schemas.py | 53 +
.../mcp_service/chart/test_chart_utils.py | 128 +
.../mcp_service/chart/tool/test_generate_chart.py | 21 +-
.../mcp_service/chart/tool/test_get_chart_data.py | 227 +
.../mcp_service/chart/tool/test_list_charts.py | 48 +-
.../mcp_service/chart/validation}/__init__.py | 0
.../chart/validation/test_runtime_validator.py | 221 +
.../dashboard/tool/test_dashboard_tools.py | 135 +-
.../mcp_service/dataset/tool/test_dataset_tools.py | 186 +-
.../mcp_service/system/tool/test_get_schema.py | 368 +
.../mcp_service/system/tool/test_mcp_core.py | 152 +-
tests/unit_tests/mcp_service/test_mcp_config.py | 85 +-
tests/unit_tests/mcp_service/test_mcp_server.py | 126 +
tests/unit_tests/mcp_service/test_mcp_storage.py | 194 +-
.../mcp_service/test_mcp_tool_registration.py | 42 +-
tests/unit_tests/models/core_test.py | 196 +
tests/unit_tests/sql/execution/conftest.py | 1 -
tests/unit_tests/sql/parse_tests.py | 14 +
tests/unit_tests/sql/transpile_to_dialect_test.py | 51 +
tests/unit_tests/sql_lab_test.py | 57 +-
tests/unit_tests/utils/currency_test.py | 332 +
tests/unit_tests/utils/date_parser_tests.py | 111 +
tests/unit_tests/utils/oauth2_tests.py | 188 +-
tests/unit_tests/utils/test_cache_manager.py | 171 +
tests/unit_tests/utils/test_core.py | 508 +
.../unit_tests/views/datasource}/__init__.py | 0
tests/unit_tests/views/datasource/utils_test.py | 217 +
tests/unit_tests/views/test_base_theme_helpers.py | 302 +
2003 files changed, 139908 insertions(+), 39051 deletions(-)
create mode 100644 .claude/settings.json
delete mode 100644 LINTING_ARCHITECTURE.md
delete mode 100644 RESOURCES/FEATURE_FLAGS.md
delete mode 100644 docker/superset-websocket/config.json
create mode 100644 docs/.claude/instructions.md
delete mode 100644 docs/developer_portal/extensions/components/alert.mdx
delete mode 100644 docs/developer_portal/extensions/components/index.mdx
create mode 100644 docs/developer_portal/extensions/extension-points/editors.md
create mode 100644 docs/developer_portal/testing/storybook.md
delete mode 100644 docs/docs/configuration/databases.mdx
create mode 100644 docs/docs/configuration/feature-flags.mdx
create mode 100644 docs/netlify.toml
create mode 100644 docs/scripts/convert-api-sidebar.mjs
create mode 100644 docs/scripts/extract_custom_errors.py
create mode 100644 docs/scripts/fix-openapi-spec.py
create mode 100644 docs/scripts/generate-api-index.mjs
create mode 100644 docs/scripts/generate-api-tag-pages.mjs
create mode 100644 docs/scripts/generate-database-docs.mjs
create mode 100644 docs/scripts/generate-superset-components.mjs
create mode 100644 docs/src/components/databases/DatabaseIndex.tsx
create mode 100644 docs/src/components/databases/DatabasePage.tsx
copy {superset-frontend/packages/superset-ui-core/src/translation => docs/src/components/databases}/index.ts (86%)
create mode 100644 docs/src/components/databases/types.ts
create mode 100644 docs/src/data/databases.json
create mode 100644 docs/src/shims/null-module.js
create mode 100644 docs/src/shims/react-table.js
create mode 100644 docs/src/theme/Admonition/Types.js
create mode 100644 docs/src/theme/Playground/Preview/index.tsx
copy superset-frontend/packages/superset-core/src/ui/index.ts => docs/src/theme/Playground/Preview/styles.module.css (90%)
create mode 100644 docs/static/feature-flags.json
copy {superset-frontend/packages/superset-ui-core/src/components => docs/static/img}/atomic-design.png (100%)
create mode 100644 docs/static/img/community/bluesky-symbol.svg
create mode 100644 docs/static/img/community/globe-symbol.svg
create mode 100644 docs/static/img/community/linkedin-symbol.svg
create mode 100644 docs/static/img/community/x-symbol.svg
create mode 100644 docs/static/img/databases/alloydb.png
create mode 100644 docs/static/img/databases/altinity.png
delete mode 100644 docs/static/img/databases/amazon-redshift.jpg
delete mode 100644 docs/static/img/databases/apache-druid.jpeg
create mode 100644 docs/static/img/databases/apache-iotdb.svg
create mode 100644 docs/static/img/databases/apache-phoenix.png
create mode 100644 docs/static/img/databases/apache-solr.png
create mode 100644 docs/static/img/databases/apache-solr.svg
create mode 100644 docs/static/img/databases/apache-spark.png
create mode 100644 docs/static/img/databases/ascend.webp
create mode 100644 docs/static/img/databases/aws-aurora.jpg
create mode 100644 docs/static/img/databases/aws.png
create mode 100644 docs/static/img/databases/azure.svg
create mode 100644 docs/static/img/databases/celerdata.png
create mode 100644 docs/static/img/databases/cloudflare.png
create mode 100644 docs/static/img/databases/cockroachdb.png
create mode 100644 docs/static/img/databases/cratedb.svg
delete mode 100644 docs/static/img/databases/db2.png
create mode 100644 docs/static/img/databases/duckdb.png
create mode 100644 docs/static/img/databases/elasticsearch.png
delete mode 100644 docs/static/img/databases/google-biquery.png
delete mode 100644 docs/static/img/databases/greenplum.jpeg
delete mode 100644 docs/static/img/databases/ibmdb2.png
create mode 100644 docs/static/img/databases/imply.png
create mode 100644 docs/static/img/databases/kusto.png
delete mode 100644 docs/static/img/databases/monet.png
create mode 100644 docs/static/img/databases/mongodb.png
create mode 100644 docs/static/img/databases/motherduck.png
delete mode 100644 docs/static/img/databases/mssql-server.png
delete mode 100644 docs/static/img/databases/mssql.jpg
delete mode 100644 docs/static/img/databases/mysql.jpg
create mode 100644 docs/static/img/databases/neon.png
delete mode 100644 docs/static/img/databases/oracle-logo.png
delete mode 100644 docs/static/img/databases/oracle.png
delete mode 100644 docs/static/img/databases/pinot.png
delete mode 100644 docs/static/img/databases/postgresql.jpg
create mode 100644 docs/static/img/databases/risingwave.svg
delete mode 100644 docs/static/img/databases/sap-hana.jpg
create mode 100644 docs/static/img/databases/shillelagh.png
create mode 100644 docs/static/img/databases/singlestore.png
delete mode 100644 docs/static/img/databases/snowflake.png
delete mode 100644 docs/static/img/databases/sqlite.jpg
create mode 100644 docs/static/img/databases/starburst.png
create mode 100644 docs/static/img/databases/supabase.svg
copy docs/static/img/{superset-logo-horiz.svg => databases/superset.svg} (100%)
delete mode 100644 docs/static/img/databases/trino2.jpg
create mode 100644 docs/static/img/extensions/editors-bundle.png
create mode 100644 docs/static/img/logos/cirrus.svg
create mode 100644 docs/static/img/logos/club25deagosto.svg
create mode 100644 "docs/static/img/logos/hollyland\347\214\233\347\216\233.svg"
create mode 100644 docs/static/img/logos/hpe.png
create mode 100644 docs/static/img/logos/plaidcloud.svg
create mode 100644 docs/static/img/logos/userguiding.svg
create mode 100644 docs/static/img/netlify.png
create mode 100644 docs/static/img/screenshots/dashboard.jpg
create mode 100644 scripts/extract_feature_flags.py
delete mode 100644 superset-frontend/LINTING.md
rename superset-frontend/cypress-base/cypress/e2e/chart_list/{filter.test.ts => _skip.filter.test.ts} (90%)
rename superset-frontend/cypress-base/cypress/e2e/dashboard/{horizontalFilterBar.test.ts => _skip.horizontalFilterBar.test.ts} (99%)
rename superset-frontend/cypress-base/cypress/e2e/dashboard/{load.test.ts => _skip.load.test.ts} (100%)
rename superset-frontend/cypress-base/cypress/e2e/dashboard/{nativeFilters.noInitState.test.ts => _skip.nativeFilters.noInitState.test.ts} (100%)
rename superset-frontend/cypress-base/cypress/e2e/dashboard/{nativeFilters.test.ts => _skip.nativeFilters.test.ts} (93%)
rename superset-frontend/cypress-base/cypress/e2e/dashboard/{tabs.test.ts => _skip.tabs.test.ts} (99%)
rename superset-frontend/cypress-base/cypress/e2e/explore/{AdhocMetrics.test.ts => _skip.AdhocMetrics.test.ts} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/{advanced_analytics.test.ts => _skip.advanced_analytics.test.ts} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/{annotations.test.ts => _skip.annotations.test.ts} (97%)
rename superset-frontend/cypress-base/cypress/e2e/explore/{link.test.ts => _skip.link.test.ts} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{big_number.test.js => _skip.big_number.test.js} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{big_number_total.test.js => _skip.big_number_total.test.js} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{box_plot.test.js => _skip.box_plot.test.js} (63%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{bubble.test.js => _skip.bubble.test.js} (51%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{compare.test.js => _skip.compare.test.js} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{download_chart.test.js => _skip.download_chart.test.js} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{gauge.test.js => _skip.gauge.test.js} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{graph.test.ts => _skip.graph.test.ts} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{pie.test.js => _skip.pie.test.js} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{pivot_table.test.js => _skip.pivot_table.test.js} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{sunburst.test.js => _skip.sunburst.test.js} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{table.test.ts => _skip.table.test.ts} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{time_table.js => _skip.time_table.js} (100%)
rename superset-frontend/cypress-base/cypress/e2e/explore/visualizations/{world_map.test.js => _skip.world_map.test.js} (100%)
copy superset/extensions/types.py => superset-frontend/netlify.toml (61%)
create mode 100644 superset-frontend/packages/superset-core/src/api/editors.ts
delete mode 100644 superset-frontend/packages/superset-core/src/api/environment.ts
create mode 100644 superset-frontend/packages/superset-core/src/spec/utils/logging.test.ts
rename superset-frontend/packages/{superset-ui-core/src => superset-core/src/ui}/translation/README.md (100%)
rename superset-frontend/packages/{superset-ui-core/src => superset-core/src/ui}/translation/Translator.ts (98%)
rename superset-frontend/packages/{superset-ui-core/src => superset-core/src/ui}/translation/TranslatorSingleton.ts (100%)
rename superset-frontend/packages/{superset-ui-core/src => superset-core/src/ui}/translation/index.ts (94%)
rename superset-frontend/packages/{superset-ui-core/src => superset-core/src/ui}/translation/types/index.ts (100%)
rename superset-frontend/packages/{superset-ui-core/src => superset-core/src/ui}/translation/types/jed.ts (100%)
copy superset-frontend/packages/superset-core/src/{ => utils}/index.ts (94%)
rename superset-frontend/packages/{superset-ui-core => superset-core}/src/utils/logging.ts (95%)
rename superset-frontend/packages/{superset-ui-core => superset-core}/types/external.d.ts (100%)
create mode 100644 superset-frontend/packages/superset-ui-chart-controls/test/shared-controls/customControls.test.tsx
copy superset-frontend/{src/features/rls/constants.ts => packages/superset-ui-core/src/components/Pagination/index.tsx} (79%)
create mode 100644 superset-frontend/packages/superset-ui-core/src/components/TimezoneSelector/TimezoneOptionsCache.test.tsx
create mode 100644 superset-frontend/packages/superset-ui-core/src/components/TimezoneSelector/TimezoneOptionsCache.ts
copy superset-frontend/packages/superset-ui-core/src/components/{constants.ts => TimezoneSelector/types.ts} (77%)
copy superset-frontend/packages/{superset-core/src/ui/index.ts => superset-ui-core/src/currency-format/CurrencyFormats.ts} (89%)
copy docs/babel.config.js => superset-frontend/packages/superset-ui-core/src/currency-format/types.ts (83%)
copy superset-frontend/{playwright/utils/urls.ts => packages/superset-ui-core/src/utils/withLabel.ts} (52%)
copy superset-frontend/packages/superset-ui-core/src/validator/{legacyValidateNumber.ts => types.ts} (77%)
delete mode 100644 superset-frontend/packages/superset-ui-core/test/components/SafeMarkdown.test.ts
create mode 100644 superset-frontend/packages/superset-ui-core/test/components/SafeMarkdown.test.tsx
copy superset-frontend/{plugins/legacy-plugin-chart-calendar/test/getFormattedUTCTime.ts => packages/superset-ui-core/test/currency-format/analyzeCurrencyInData.test.ts} (56%)
copy superset-frontend/{plugins/legacy-plugin-chart-calendar/test/getFormattedUTCTime.ts => packages/superset-ui-core/test/currency-format/hasMixedCurrencies.test.ts} (59%)
rename superset-frontend/{plugins/legacy-plugin-chart-calendar/test/getFormattedUTCTime.ts => packages/superset-ui-core/test/currency-format/normalizeCurrency.test.ts} (57%)
delete mode 100644 superset-frontend/packages/superset-ui-core/test/translation/Translator.test.ts
delete mode 100644 superset-frontend/packages/superset-ui-core/test/translation/TranslatorSingleton.test.ts
delete mode 100644 superset-frontend/packages/superset-ui-core/test/translation/index.test.ts
delete mode 100644 superset-frontend/packages/superset-ui-core/test/translation/languagePacks/en.ts
delete mode 100644 superset-frontend/packages/superset-ui-core/test/utils/logging.test.ts
delete mode 100644 superset-frontend/playwright/README.md
create mode 100644 superset-frontend/playwright/components/core/Menu.ts
create mode 100644 superset-frontend/playwright/generators/docs/docs-screenshots.spec.ts
create mode 100644 superset-frontend/playwright/generators/playwright.config.ts
create mode 100644 superset-frontend/playwright/pages/DashboardPage.ts
create mode 100644 superset-frontend/playwright/tests/experimental/dashboard/export.spec.ts
create mode 100644 superset-frontend/plugins/legacy-plugin-chart-calendar/test/utils.test.ts
create mode 100644 superset-frontend/plugins/legacy-plugin-chart-map-box/test/ScatterPlotGlowOverlay.test.tsx
create mode 100644 superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/Multi.test.tsx
create mode 100644 superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/controlPanel.test.ts
create mode 100644 superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/getSafeCellSize.test.ts
create mode 100644 superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/getSafeCellSize.ts
create mode 100644 superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Path/Path.test.tsx
create mode 100644 superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/agGridFilterConverter.ts
create mode 100644 superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/filterStateManager.ts
copy docs/src/components/BlurredSection.tsx => superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/getInitialFilterModel.ts (50%)
create mode 100644 superset-frontend/plugins/plugin-chart-ag-grid-table/test/controlPanel.test.ts
create mode 100644 superset-frontend/plugins/plugin-chart-ag-grid-table/test/controlPanel.test.tsx
create mode 100644 superset-frontend/plugins/plugin-chart-ag-grid-table/test/utils/agGridFilterConverter.test.ts
create mode 100644 superset-frontend/plugins/plugin-chart-ag-grid-table/test/utils/dateFilterComparator.test.ts
create mode 100644 superset-frontend/plugins/plugin-chart-ag-grid-table/test/utils/filterStateManager.test.ts
create mode 100644 superset-frontend/plugins/plugin-chart-ag-grid-table/test/utils/getInitialFilterModel.test.ts
create mode 100644 superset-frontend/plugins/plugin-chart-echarts/test/Heatmap/buildQuery.test.ts
create mode 100644 superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/helpers.ts
create mode 100644 superset-frontend/plugins/plugin-chart-pivot-table/test/react-pivottable/tableRenders.test.tsx
create mode 100644 superset-frontend/plugins/plugin-chart-table/test/controlPanel.test.tsx
create mode 100644 superset-frontend/plugins/plugin-chart-table/test/utils/formatValue.test.ts
delete mode 100644 superset-frontend/plugins/plugin-chart-word-cloud/src/configureEncodable.ts
rename superset-frontend/src/SqlLab/actions/{sqlLab.js => sqlLab.ts} (60%)
delete mode 100644 superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
rename superset-frontend/src/SqlLab/components/{AceEditorWrapper/AceEditorWrapper.test.tsx => EditorWrapper/EditorWrapper.test.tsx} (53%)
create mode 100644 superset-frontend/src/SqlLab/components/EditorWrapper/index.tsx
rename superset-frontend/src/SqlLab/components/{AceEditorWrapper => EditorWrapper}/useAnnotations.test.ts (88%)
rename superset-frontend/src/SqlLab/components/{AceEditorWrapper => EditorWrapper}/useAnnotations.ts (96%)
rename superset-frontend/src/SqlLab/components/{AceEditorWrapper => EditorWrapper}/useKeywords.test.ts (95%)
rename superset-frontend/src/SqlLab/components/{AceEditorWrapper => EditorWrapper}/useKeywords.ts (96%)
create mode 100644 superset-frontend/src/SqlLab/components/QueryStatusBar/QueryStatusBar.test.tsx
create mode 100644 superset-frontend/src/SqlLab/components/QueryStatusBar/index.tsx
create mode 100644 superset-frontend/src/SqlLab/components/SqlEditorTopBar/SqlEditorTopBar.test.tsx
create mode 100644 superset-frontend/src/SqlLab/components/SqlEditorTopBar/index.tsx
create mode 100644 superset-frontend/src/SqlLab/components/SqlEditorTopBar/useDatabaseSelector.test.ts
create mode 100644 superset-frontend/src/SqlLab/components/SqlEditorTopBar/useDatabaseSelector.ts
copy superset-frontend/src/{features/datasets/AddDataset/EditDataset/EditDataset.test.tsx => SqlLab/components/StatusBar/StatusBar.test.tsx} (54%)
create mode 100644 superset-frontend/src/SqlLab/components/StatusBar/index.tsx
create mode 100644 superset-frontend/src/SqlLab/components/TableExploreTree/TableExploreTree.test.tsx
create mode 100644 superset-frontend/src/SqlLab/components/TableExploreTree/TreeNodeRenderer.tsx
create mode 100644 superset-frontend/src/SqlLab/components/TableExploreTree/index.tsx
copy superset-frontend/src/{dashboard/components/nativeFilters/FilterCard => SqlLab/components/TableExploreTree}/types.ts (61%)
create mode 100644 superset-frontend/src/SqlLab/components/TableExploreTree/useTreeData.ts
delete mode 100644 superset-frontend/src/SqlLab/middlewares/persistSqlLabStateEnhancer.js
create mode 100644 superset-frontend/src/SqlLab/middlewares/persistSqlLabStateEnhancer.ts
rename superset-frontend/src/SqlLab/reducers/{sqlLab.js => sqlLab.ts} (67%)
copy INSTALL.md => superset-frontend/src/assets/images/icons/move.svg (52%)
copy INSTALL.md => superset-frontend/src/assets/images/icons/multiple.svg (59%)
copy INSTALL.md => superset-frontend/src/assets/images/icons/sigma.svg (75%)
create mode 100644 superset-frontend/src/assets/images/pwa/icon-192.png
create mode 100644 superset-frontend/src/assets/images/pwa/icon-512.png
create mode 100644 superset-frontend/src/assets/images/pwa/screenshot-narrow.png
create mode 100644 superset-frontend/src/assets/images/pwa/screenshot-wide.png
create mode 100644 superset-frontend/src/chartCustomizations/components/DeckglLayerVisibility/DeckglLayerVisibilityCustomizationPlugin.test.tsx
create mode 100644 superset-frontend/src/chartCustomizations/components/DeckglLayerVisibility/DeckglLayerVisibilityCustomizationPlugin.tsx
copy superset-frontend/src/{filters/components/TimeGrain/index.ts => chartCustomizations/components/DeckglLayerVisibility/buildQuery.test.ts} (52%)
copy superset-frontend/src/{utils/assetUrl.ts => chartCustomizations/components/DeckglLayerVisibility/buildQuery.ts} (65%)
copy docs/babel.config.js => superset-frontend/src/chartCustomizations/components/DeckglLayerVisibility/controlPanel.ts (83%)
copy superset-frontend/src/{filters/components/Select => chartCustomizations/components/DeckglLayerVisibility}/index.ts (67%)
create mode 100644 superset-frontend/src/chartCustomizations/components/DeckglLayerVisibility/transformProps.test.ts
copy superset-frontend/{packages/superset-ui-core/src/validator/legacyValidateInteger.ts => src/chartCustomizations/components/DeckglLayerVisibility/transformProps.ts} (72%)
copy superset-frontend/src/{dashboard/components/MissingChart.tsx => chartCustomizations/components/DeckglLayerVisibility/types.ts} (57%)
create mode 100644 superset-frontend/src/chartCustomizations/components/DeckglLayerVisibility/useDeckLayerMetadata.test.ts
create mode 100644 superset-frontend/src/chartCustomizations/components/DeckglLayerVisibility/useDeckLayerMetadata.ts
create mode 100644 superset-frontend/src/chartCustomizations/components/DynamicGroupBy/DynamicGroupByPlugin.tsx
copy superset-frontend/{packages/superset-ui-core/src/validator/legacyValidateNumber.ts => src/chartCustomizations/components/DynamicGroupBy/buildQuery.ts} (74%)
copy superset-frontend/src/{filters/components/Range => chartCustomizations/components/DynamicGroupBy}/controlPanel.ts (70%)
copy superset-frontend/{packages/generator-superset/generators/plugin-chart/templates/src => src/chartCustomizations/components/DynamicGroupBy}/images/thumbnail.png (100%)
copy superset-frontend/src/{filters/components/TimeGrain => chartCustomizations/components/DynamicGroupBy}/index.ts (70%)
copy superset-frontend/src/{filters/components/TimeGrain => chartCustomizations/components/DynamicGroupBy}/transformProps.ts (100%)
rename superset-frontend/src/{dashboard/components/nativeFilters/ChartCustomization => chartCustomizations/components/DynamicGroupBy}/types.ts (51%)
copy superset-frontend/src/{filters => chartCustomizations}/components/TimeColumn/TimeColumnFilterPlugin.tsx (97%)
copy superset-frontend/src/{filters => chartCustomizations}/components/TimeColumn/buildQuery.ts (100%)
copy superset-frontend/src/{filters => chartCustomizations}/components/TimeColumn/controlPanel.ts (97%)
copy superset-frontend/{packages/generator-superset/generators/plugin-chart/templates/src => src/chartCustomizations/components/TimeColumn}/images/thumbnail.png (100%)
copy superset-frontend/src/{filters => chartCustomizations}/components/TimeColumn/index.ts (79%)
copy superset-frontend/src/{filters => chartCustomizations}/components/TimeColumn/transformProps.ts (100%)
copy superset-frontend/src/{filters => chartCustomizations}/components/TimeColumn/types.ts (100%)
copy superset-frontend/src/{filters => chartCustomizations}/components/TimeGrain/TimeGrainFilterPlugin.tsx (70%)
copy superset-frontend/src/{filters => chartCustomizations}/components/TimeGrain/buildQuery.ts (100%)
copy superset-frontend/src/{filters => chartCustomizations}/components/TimeGrain/controlPanel.ts (88%)
copy superset-frontend/{packages/generator-superset/generators/plugin-chart/templates/src => src/chartCustomizations/components/TimeGrain}/images/thumbnail.png (100%)
copy superset-frontend/src/{filters => chartCustomizations}/components/TimeGrain/index.ts (79%)
copy superset-frontend/src/{filters => chartCustomizations}/components/TimeGrain/transformProps.ts (100%)
copy superset-frontend/src/{filters => chartCustomizations}/components/TimeGrain/types.ts (100%)
copy superset-frontend/src/{filters => chartCustomizations}/components/common.ts (100%)
copy superset-frontend/{playwright/components/core => src/chartCustomizations/components}/index.ts (71%)
copy docs/src/theme.d.ts => superset-frontend/src/chartCustomizations/components/types.ts (61%)
create mode 100644 superset-frontend/src/components/Chart/ChartContextMenu/ChartContextMenu.test.tsx
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/FoldersEditor.test.tsx
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/TreeItem.styles.ts
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/TreeItem.tsx
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/VirtualizedTreeItem.tsx
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/VirtualizedTreeList.tsx
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/components/DragOverlayContent.tsx
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/components/FoldersToolbarComponent.tsx
copy superset-frontend/{packages/superset-ui-core/src/components/CertifiedBadge/index.tsx => src/components/Datasource/FoldersEditor/components/ResetConfirmModal.tsx} (52%)
copy superset-frontend/src/{features/rls/constants.ts => components/Datasource/FoldersEditor/components/index.ts} (81%)
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/constants.ts
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/folderOperations.test.ts
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/folderOperations.ts
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/folderValidation.ts
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/hooks/useAutoScroll.ts
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/hooks/useDragHandlers.ts
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/hooks/useHeightCache.ts
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/hooks/useItemHeights.ts
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/index.tsx
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/sensors.ts
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/styles.tsx
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/treeUtils.test.ts
create mode 100644 superset-frontend/src/components/Datasource/FoldersEditor/treeUtils.ts
copy superset-frontend/src/{explore/components/controls/MetricControl => components/Datasource/FoldersEditor}/types.ts (72%)
copy superset-frontend/{packages/superset-ui-core/src/components/IconTooltip/IconTooltip.stories.tsx => src/components/LastQueriedLabel/index.tsx} (54%)
create mode 100644 superset-frontend/src/components/PanelToolbar/index.tsx
create mode 100644 superset-frontend/src/components/ViewListExtension/ViewListExtension.test.tsx
copy superset-frontend/src/{core/extensions/index.ts => components/ViewListExtension/index.tsx} (55%)
create mode 100644 superset-frontend/src/core/editors/AceEditorProvider.test.tsx
create mode 100644 superset-frontend/src/core/editors/AceEditorProvider.tsx
create mode 100644 superset-frontend/src/core/editors/EditorHost.test.tsx
create mode 100644 superset-frontend/src/core/editors/EditorHost.tsx
create mode 100644 superset-frontend/src/core/editors/EditorProviders.test.ts
create mode 100644 superset-frontend/src/core/editors/EditorProviders.ts
create mode 100644 superset-frontend/src/core/editors/index.ts
delete mode 100644 superset-frontend/src/core/environment/index.ts
rename superset-frontend/src/dashboard/components/{GroupByBadge => CustomizationsBadge}/index.tsx (55%)
rename superset-frontend/src/dashboard/components/gridComponents/Divider/{Divider.test.jsx => Divider.test.tsx} (83%)
rename superset-frontend/src/dashboard/components/gridComponents/Divider/{Divider.jsx => Divider.tsx} (77%)
delete mode 100644 superset-frontend/src/dashboard/components/nativeFilters/ChartCustomization/ChartCustomizationForm.tsx
delete mode 100644 superset-frontend/src/dashboard/components/nativeFilters/ChartCustomization/ChartCustomizationModal.tsx
delete mode 100644 superset-frontend/src/dashboard/components/nativeFilters/ChartCustomization/ChartCustomizationTitleContainer.tsx
delete mode 100644 superset-frontend/src/dashboard/components/nativeFilters/ChartCustomization/ChartCustomizationTitlePane.tsx
delete mode 100644 superset-frontend/src/dashboard/components/nativeFilters/ChartCustomization/groupBySelectors.ts
delete mode 100644 superset-frontend/src/dashboard/components/nativeFilters/ChartCustomization/selectors.ts
delete mode 100644 superset-frontend/src/dashboard/components/nativeFilters/ChartCustomization/useChartCustomizationModal.tsx
delete mode 100644 superset-frontend/src/dashboard/components/nativeFilters/ChartCustomization/utils.ts
copy superset-frontend/src/dashboard/components/nativeFilters/FilterBar/{FiltersOutOfScopeCollapsible => CustomizationsOutOfScopeCollapsible}/index.tsx (61%)
copy superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/{FilterControl.tsx => FilterControlShared.tsx} (54%)
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.test.tsx
rename superset-frontend/src/dashboard/components/nativeFilters/{ChartCustomization => FilterBar/FilterControls}/GroupByFilterCard.tsx (59%)
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ConfigModalContent/ConfigModalContent.tsx
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ConfigModalContent/CustomizationContentRenderer.tsx
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ConfigModalContent/FilterContentRenderer.tsx
copy superset-frontend/{playwright/components/core => src/dashboard/components/nativeFilters/FiltersConfigModal/ConfigModalContent}/index.ts (63%)
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ConfigModalSidebar/ConfigModalSidebar.tsx
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ConfigModalSidebar/ItemSection.tsx
copy superset-frontend/{playwright/components/core => src/dashboard/components/nativeFilters/FiltersConfigModal/ConfigModalSidebar}/index.ts (76%)
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/DraggableFilter.test.tsx
copy superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/{FilterTitleContainer.tsx => ItemTitleContainer.tsx} (69%)
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ItemTitlePane.tsx
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/NewItemDropdown.tsx
copy superset-frontend/{packages/superset-ui-core/src => src/dashboard/components/nativeFilters/FiltersConfigModal/hooks}/index.ts (56%)
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/hooks/useCustomizationOperations.ts
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/hooks/useFilterOperations.ts
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/hooks/useItemStateManager.ts
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/hooks/useModalSaveLogic.ts
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/transformers/customizationTransformer.ts
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/transformers/filterTransformer.ts
copy superset-frontend/{packages/superset-core/src => src/dashboard/components/nativeFilters/FiltersConfigModal/transformers}/index.ts (85%)
create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/utils.test.ts
delete mode 100644 superset-frontend/src/dashboard/reducers/groupByCustomizations.ts
create mode 100644 superset-frontend/src/dashboard/util/calculateScopes.ts
create mode 100644 superset-frontend/src/dashboard/util/getChartIdsInFilterScope.test.ts
create mode 100644 superset-frontend/src/dataMask/reducer.test.ts
rename superset-frontend/src/explore/components/controls/AnnotationLayerControl/{AnnotationLayer.jsx => AnnotationLayer.tsx} (74%)
rename superset-frontend/src/explore/components/controls/AnnotationLayerControl/{AnnotationTypes.js => AnnotationTypes.ts} (67%)
rename superset-frontend/src/explore/components/controls/{BoundsControl.test.jsx => BoundsControl.test.tsx} (100%)
rename superset-frontend/src/explore/components/controls/{CheckboxControl.jsx => CheckboxControl.tsx} (68%)
rename superset-frontend/src/explore/components/controls/CollectionControl/{index.jsx => index.tsx} (66%)
rename superset-frontend/{packages/superset-ui-core/test/translation/languagePacks/zh.ts => src/explore/components/controls/CurrencyControl/CurrencyControl.test.tsx} (58%)
rename superset-frontend/src/explore/components/controls/DatasourceControl/{index.jsx => index.tsx} (80%)
rename superset-frontend/src/explore/components/controls/DndColumnSelectControl/{DndColumnSelectPopoverTitle.jsx => DndColumnSelectPopoverTitle.tsx} (89%)
rename superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/{AdhocFilter.test.js => AdhocFilter.test.ts} (100%)
rename superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/{index.js => index.ts} (56%)
rename superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterControl/{index.jsx => index.tsx} (70%)
rename superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopover/{AdhocFilterEditPopover.test.jsx => AdhocFilterEditPopover.test.tsx} (89%)
rename superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopover/{index.jsx => index.tsx} (73%)
rename superset-frontend/src/explore/components/controls/FixedOrMetricControl/{index.jsx => index.tsx} (69%)
rename superset-frontend/src/explore/components/controls/MetricControl/{AdhocMetric.test.js => AdhocMetric.test.ts} (98%)
rename superset-frontend/src/explore/components/controls/MetricControl/{AdhocMetric.js => AdhocMetric.ts} (62%)
rename superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/{index.jsx => index.tsx} (78%)
rename superset-frontend/src/explore/components/controls/MetricControl/{AdhocMetricOption.test.jsx => AdhocMetricOption.test.tsx} (90%)
rename superset-frontend/src/explore/components/controls/MetricControl/{AdhocMetricOption.jsx => AdhocMetricOption.tsx} (63%)
rename superset-frontend/src/explore/components/controls/MetricControl/{FilterDefinitionOption.test.jsx => FilterDefinitionOption.test.tsx} (94%)
rename superset-frontend/src/explore/components/controls/MetricControl/{FilterDefinitionOption.jsx => FilterDefinitionOption.tsx} (77%)
rename superset-frontend/src/explore/components/controls/MetricControl/{MetricDefinitionValue.test.jsx => MetricDefinitionValue.test.tsx} (83%)
rename superset-frontend/src/explore/components/controls/MetricControl/{MetricDefinitionValue.jsx => MetricDefinitionValue.tsx} (67%)
rename superset-frontend/src/explore/components/controls/MetricControl/{MetricsControl.test.jsx => MetricsControl.test.tsx} (92%)
rename superset-frontend/src/explore/components/controls/MetricControl/{MetricsControl.jsx => MetricsControl.tsx} (75%)
rename superset-frontend/src/explore/components/controls/MetricControl/{adhocMetricType.js => adhocMetricType.ts} (100%)
rename superset-frontend/src/explore/components/controls/{SelectControl.test.jsx => SelectControl.test.tsx} (94%)
rename superset-frontend/src/explore/components/controls/{SelectControl.jsx => SelectControl.tsx} (64%)
rename superset-frontend/src/explore/components/controls/{SpatialControl.jsx => SpatialControl.tsx} (71%)
rename superset-frontend/src/explore/components/controls/{TextAreaControl.test.jsx => TextAreaControl.test.tsx} (100%)
rename superset-frontend/src/explore/components/controls/{TextAreaControl.jsx => TextAreaControl.tsx} (68%)
rename superset-frontend/src/explore/components/controls/TimeSeriesColumnControl/{index.jsx => index.tsx} (81%)
rename superset-frontend/src/explore/components/controls/{ViewportControl.test.jsx => ViewportControl.test.tsx} (96%)
rename superset-frontend/src/explore/components/controls/{ViewportControl.jsx => ViewportControl.tsx} (56%)
rename superset-frontend/src/explore/{controls.jsx => controls.tsx} (87%)
create mode 100644 superset-frontend/src/explore/exploreUtils/exportChart.test.ts
rename superset-frontend/src/explore/{store.test.jsx => store.test.tsx} (95%)
rename superset-frontend/src/explore/{store.js => store.ts} (61%)
create mode 100644 superset-frontend/src/features/datasets/DuplicateDatasetModal.test.tsx
create mode 100644 superset-frontend/src/pages/DatasetList/DatasetList.behavior.test.tsx
create mode 100644 superset-frontend/src/pages/DatasetList/DatasetList.integration.test.tsx
create mode 100644 superset-frontend/src/pages/DatasetList/DatasetList.listview.test.tsx
create mode 100644 superset-frontend/src/pages/DatasetList/DatasetList.permissions.test.tsx
create mode 100644 superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
create mode 100644 superset-frontend/src/pages/DatasetList/DatasetList.testHelpers.tsx
create mode 100644 superset-frontend/src/pages/FileHandler/index.test.tsx
create mode 100644 superset-frontend/src/pages/FileHandler/index.tsx
create mode 100644 superset-frontend/src/pwa-manifest.json
copy superset-frontend/{packages/superset-ui-core/src/components/Avatar/Avatar.stories.tsx => src/service-worker.ts} (62%)
create mode 100644 superset-frontend/src/utils/assetUrl.test.ts
create mode 100644 superset/cli/export_example.py
rename superset/{examples/big_data.py => cli/test_loaders.py} (87%)
create mode 100644 superset/commands/dashboard/export_example.py
create mode 100644 superset/db_engine_specs/METADATA_STATUS.md
create mode 100644 superset/db_engine_specs/arc.py
create mode 100644 superset/db_engine_specs/aws_iam.py
create mode 100644 superset/db_engine_specs/d1.py
create mode 100644 superset/db_engine_specs/greenplum.py
create mode 100644 superset/db_engine_specs/hologres.py
create mode 100644 superset/db_engine_specs/iotdb.py
create mode 100644 superset/db_engine_specs/lint_metadata.py
create mode 100644 superset/db_engine_specs/monetdb.py
create mode 100644 superset/db_engine_specs/mongodb.py
create mode 100644 superset/db_engine_specs/phoenix.py
create mode 100644 superset/db_engine_specs/sybase.py
create mode 100644 superset/db_engine_specs/timescaledb.py
create mode 100644 superset/db_engine_specs/yugabytedb.py
copy superset/examples/{configs/metadata.yaml => _shared/database.yaml} (81%)
rename superset/examples/{configs => _shared}/metadata.yaml (100%)
delete mode 100644 superset/examples/bart_lines.py
delete mode 100644 superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Approach__Stage.yaml
delete mode 100644 superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Country.yaml
delete mode 100644 superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Country_261.yaml
delete mode 100644 superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Country__Stage.yaml
delete mode 100644 superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Country__Stage_749.yaml
delete mode 100644 superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Phase.yaml
delete mode 100644 superset/examples/configs/charts/COVID Vaccines/Vaccine_Candidates_per_Phase_587.yaml
delete mode 100644 superset/examples/configs/charts/Featured Charts/Radar.yaml
delete mode 100644 superset/examples/configs/charts/Vehicle Sales/Items_Sold.yaml
delete mode 100644 superset/examples/configs/charts/Vehicle Sales/Items_by_Product_Line.yaml
delete mode 100644 superset/examples/configs/charts/Vehicle Sales/Overall_Sales_By_Product_Line.yaml
delete mode 100644 superset/examples/configs/charts/Vehicle Sales/Proportion_of_Revenue_by_Product_Line.yaml
delete mode 100644 superset/examples/configs/charts/Vehicle Sales/Quarterly_Sales.yaml
delete mode 100644 superset/examples/configs/charts/Vehicle Sales/Quarterly_Sales_By_Product_Line_113.yaml
delete mode 100644 superset/examples/configs/charts/Vehicle Sales/Total_Revenue.yaml
delete mode 100644 superset/examples/configs/charts/Video Game Sales/Games_per_Genre_over_time.yaml
delete mode 100644 superset/examples/configs/charts/Video Game Sales/Rise__Fall_of_Video_Game_Consoles.yaml
delete mode 100644 superset/examples/configs/charts/Video Game Sales/Top_10_Games_Proportion_of_Sales_in_Markets.yaml
delete mode 100644 superset/examples/configs/charts/Video Game Sales/Total_Sales_per_Market_Grouped_by_Genre.yaml
delete mode 100644 superset/examples/configs/dashboards/COVID_Vaccine_Dashboard.yaml
delete mode 100644 superset/examples/configs/dashboards/Unicode_Test.test.yaml
delete mode 100644 superset/examples/configs/datasets/examples/FCC_2018_Survey.yaml
delete mode 100644 superset/examples/configs/datasets/examples/hierarchical_dataset.yaml
delete mode 100644 superset/examples/configs/datasets/examples/project_management.yaml
delete mode 100644 superset/examples/configs/datasets/examples/users.yaml
delete mode 100644 superset/examples/configs/datasets/examples/users_channels-uzooNNtSRO.yaml
delete mode 100644 superset/examples/configs/datasets/examples/users_channels.yaml
delete mode 100644 superset/examples/configs/datasets/examples/video_game_sales.yaml
delete mode 100644 superset/examples/country_map.py
delete mode 100644 superset/examples/deck.py
copy superset/examples/{configs/charts/Featured Charts/Big_Number_with_Trendline.yaml => deckgl_demo/charts/Deck.gl_Arcs.yaml} (52%)
copy superset/examples/{configs/charts/Vehicle Sales/Number_of_Deals_for_each_Combination.yaml => deckgl_demo/charts/Deck.gl_Grid.yaml} (54%)
copy superset/examples/{configs/charts/Vehicle Sales/Number_of_Deals_for_each_Combination.yaml => deckgl_demo/charts/Deck.gl_Hexagons.yaml} (54%)
copy superset/examples/{configs/charts/Featured Charts/Big_Number_with_Trendline.yaml => deckgl_demo/charts/Deck.gl_Path.yaml} (50%)
create mode 100644 superset/examples/deckgl_demo/charts/Deck.gl_Polygons.yaml
copy superset/examples/{configs/charts/Featured Charts/Big_Number_with_Trendline.yaml => deckgl_demo/charts/Deck.gl_Scatterplot.yaml} (54%)
copy superset/examples/{configs/charts/Vehicle Sales/Number_of_Deals_for_each_Combination.yaml => deckgl_demo/charts/Deck.gl_Screen_grid.yaml} (54%)
create mode 100644 superset/examples/deckgl_demo/dashboard.yaml
create mode 100644 superset/examples/deckgl_demo/data/bart_lines.parquet
create mode 100644 superset/examples/deckgl_demo/data/flights.parquet
create mode 100644 superset/examples/deckgl_demo/data/long_lat.parquet
create mode 100644 superset/examples/deckgl_demo/data/sf_population_polygons.parquet
copy superset/examples/{configs/datasets/examples/unicode_test.test.yaml => deckgl_demo/datasets/bart_lines.yaml} (75%)
copy superset/examples/{configs/datasets/examples/messages.yaml => deckgl_demo/datasets/flights.yaml} (62%)
rename superset/examples/{configs/datasets/examples/covid_vaccines.yaml => deckgl_demo/datasets/long_lat.yaml} (58%)
copy superset/examples/{configs/datasets/examples/unicode_test.test.yaml => deckgl_demo/datasets/sf_population_polygons.yaml} (74%)
delete mode 100644 superset/examples/energy.py
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Age_distribution_of_respondents.yaml (90%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Are_you_an_ethnic_minority_in_your_city.yaml (93%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Breakdown_of_Developer_Type.yaml (93%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Commute_Time.yaml (68%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Country_of_Citizenship.yaml (94%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Current_Developers_Is_this_your_first_development_job.yaml (94%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Degrees_vs_Income.yaml (94%)
rename superset/examples/{configs/charts/FCC New Coder Survey/Ethnic_Minority__Gender.yaml => fcc_new_coder_survey/charts/Ethnic_Minority_Gender.yaml} (69%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/First_Time_Developer.yaml (93%)
rename superset/examples/{configs/charts/FCC New Coder Survey/First_Time_Developer__Commute_Time.yaml => fcc_new_coder_survey/charts/First_Time_Developer_Commute_Time.yaml} (62%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Gender.yaml (92%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Highest_degree_held.yaml (95%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/How_do_you_prefer_to_work.yaml (66%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/How_much_do_you_expect_to_earn_0_-_100k.yaml (66%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Last_Year_Income_Distribution.yaml (66%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Location_of_Current_Developers.yaml (95%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Number_of_Aspiring_Developers.yaml (93%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Preferred_Employment_Style.yaml (70%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Relocation_ability.yaml (91%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Top_15_Languages_Spoken_at_Home.yaml (93%)
rename superset/examples/{configs/charts/FCC New Coder Survey => fcc_new_coder_survey/charts}/Work_Location_Preference.yaml (93%)
rename superset/examples/{configs/dashboards/FCC_New_Coder_Survey_2018.yaml => fcc_new_coder_survey/dashboard.yaml} (61%)
create mode 100644 superset/examples/fcc_new_coder_survey/data.parquet
create mode 100644 superset/examples/fcc_new_coder_survey/dataset.yaml
rename superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Area.yaml (69%)
copy superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Bar.yaml (90%)
copy superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Big_Number.yaml (87%)
rename superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Big_Number_with_Trendline.yaml (89%)
copy superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Box_Plot.yaml (86%)
copy superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Bubble.yaml (92%)
rename superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Funnel.yaml (88%)
rename superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Gantt.yaml (98%)
rename superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Gauge.yaml (87%)
rename superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Graph.yaml (89%)
rename superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Heatmap.yaml (89%)
copy superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Histogram.yaml (87%)
copy superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Line.yaml (90%)
rename superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Mixed.yaml (67%)
rename superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Pie.yaml (91%)
rename superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Pivot_Table.yaml (63%)
rename superset/examples/{configs/charts/Featured Charts/Bubble.yaml => featured_charts/charts/Radar.yaml} (67%)
copy superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Sankey.yaml (88%)
rename superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Scatter_Plot.yaml (90%)
copy superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Sunburst.yaml (87%)
rename superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Table.yaml (85%)
copy superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Tree.yaml (95%)
copy superset/examples/{configs/charts/Featured Charts/Treemap.yaml => featured_charts/charts/TreeMap.yaml} (88%)
rename superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Waterfall.yaml (92%)
copy superset/examples/{configs/charts/Featured Charts => featured_charts/charts}/Word_Cloud.yaml (87%)
rename superset/examples/{configs/dashboards/Featured_Charts.yaml => featured_charts/dashboard.yaml} (73%)
create mode 100644 superset/examples/featured_charts/data/cleaned_sales_data.parquet
create mode 100644 superset/examples/featured_charts/data/hierarchical_dataset.parquet
create mode 100644 superset/examples/featured_charts/data/project_management.parquet
rename superset/examples/{configs/datasets/examples/channels.yaml => featured_charts/datasets/cleaned_sales_data.yaml} (67%)
copy superset/examples/{configs/datasets/examples/unicode_test.test.yaml => featured_charts/datasets/hierarchical_dataset.yaml} (72%)
copy superset/examples/{configs/datasets/examples/unicode_test.test.yaml => featured_charts/datasets/project_management.yaml} (59%)
delete mode 100644 superset/examples/flights.py
create mode 100644 superset/examples/generic_loader.py
create mode 100644 superset/examples/international_sales/data.parquet
copy superset/examples/{configs/datasets/examples/threads.yaml => international_sales/dataset.yaml} (55%)
delete mode 100644 superset/examples/long_lat.py
copy superset/examples/{configs/charts/Featured Charts/Tree.yaml => misc_charts/charts/Birth_in_France_by_department_in_2016.yaml} (65%)
copy superset/examples/{configs/charts/Featured Charts/Treemap.yaml => misc_charts/charts/Parallel_Coordinates.yaml} (60%)
copy superset/examples/{configs/charts/Unicode Test/Unicode_Cloud.test.yaml => misc_charts/charts/Unicode_Cloud.yaml} (54%)
create mode 100644 superset/examples/misc_charts/dashboard.yaml
create mode 100644 superset/examples/misc_charts/data/birth_france_by_region.parquet
create mode 100644 superset/examples/misc_charts/data/unicode_test.parquet
create mode 100644 superset/examples/misc_charts/data/wb_health_population.parquet
copy superset/examples/{configs/datasets/examples/threads.yaml => misc_charts/datasets/birth_france_by_region.yaml} (64%)
rename superset/examples/{configs/datasets/examples/unicode_test.test.yaml => misc_charts/datasets/unicode_test.yaml} (81%)
create mode 100644 superset/examples/misc_charts/datasets/wb_health_population.yaml
delete mode 100644 superset/examples/misc_dashboard.py
delete mode 100644 superset/examples/multiformat_time_series.py
delete mode 100644 superset/examples/paris.py
delete mode 100644 superset/examples/random_time_series.py
rename superset/examples/{configs/charts/Vehicle Sales/Total_Items_Sold.yaml => sales_dashboard/charts/Items_Sold.yaml} (71%)
rename superset/examples/{configs/charts/Vehicle Sales => sales_dashboard/charts}/Number_of_Deals_for_each_Combination.yaml (100%)
copy superset/examples/{configs/charts/Vehicle Sales/Seasonality_of_Revenue_per_Product_Line.yaml => sales_dashboard/charts/Overall_Sales_By_Product_Line.yaml} (77%)
rename superset/examples/{configs/charts/Featured Charts/Line.yaml => sales_dashboard/charts/Proportion_of_Revenue_by_Product_Line.yaml} (63%)
rename superset/examples/{configs/charts/Featured Charts/Bar.yaml => sales_dashboard/charts/Quarterly_Sales.yaml} (64%)
rename superset/examples/{configs/charts/Vehicle Sales => sales_dashboard/charts}/Quarterly_Sales_By_Product_Line.yaml (66%)
rename superset/examples/{configs/charts/Vehicle Sales => sales_dashboard/charts}/Revenue_by_Deal_Size.yaml (100%)
copy superset/examples/{configs/charts/Vehicle Sales => sales_dashboard/charts}/Seasonality_of_Revenue_per_Product_Line.yaml (100%)
rename superset/examples/{configs/charts/Vehicle Sales => sales_dashboard/charts}/Total_Items_Sold_By_Product_Line.yaml (94%)
rename superset/examples/{configs/charts/Vehicle Sales/Seasonality_of_Revenue_per_Product_Line.yaml => sales_dashboard/charts/Total_Revenue.yaml} (65%)
rename superset/examples/{configs/dashboards/Sales_Dashboard.yaml => sales_dashboard/dashboard.yaml} (87%)
create mode 100644 superset/examples/sales_dashboard/data.parquet
rename superset/examples/{configs/datasets/examples/cleaned_sales_data.yaml => sales_dashboard/dataset.yaml} (81%)
delete mode 100644 superset/examples/sf_population_polygons.py
rename superset/examples/{configs/charts/Slack Dashboard => slack_dashboard/charts}/Cross_Channel_Relationship.yaml (94%)
rename superset/examples/{configs/charts/Slack Dashboard/Cross_Channel_Relationship_heatmap_2786.yaml => slack_dashboard/charts/Cross_Channel_Relationship_heatmap_v2.yaml} (93%)
rename superset/examples/{configs/charts/Slack Dashboard => slack_dashboard/charts}/Members_per_Channel.yaml (91%)
rename superset/examples/{configs/charts/Slack Dashboard => slack_dashboard/charts}/Messages_per_Channel.yaml (57%)
rename superset/examples/{configs/charts/Slack Dashboard => slack_dashboard/charts}/New_Members_per_Month.yaml (94%)
rename superset/examples/{configs/charts/Slack Dashboard => slack_dashboard/charts}/Number_of_Members.yaml (92%)
rename superset/examples/{configs/charts/Slack Dashboard => slack_dashboard/charts}/Top_Timezones.yaml (93%)
rename superset/examples/{configs/charts/Slack Dashboard => slack_dashboard/charts}/Weekly_Messages.yaml (93%)
rename superset/examples/{configs/charts/Slack Dashboard => slack_dashboard/charts}/Weekly_Threads.yaml (93%)
rename superset/examples/{configs/dashboards/Slack_Dashboard.yaml => slack_dashboard/dashboard.yaml} (62%)
create mode 100644 superset/examples/slack_dashboard/data/members_channels_2.parquet
create mode 100644 superset/examples/slack_dashboard/data/messages.parquet
create mode 100644 superset/examples/slack_dashboard/data/messages_channels.parquet
create mode 100644 superset/examples/slack_dashboard/data/new_members_daily.parquet
create mode 100644 superset/examples/slack_dashboard/data/threads.parquet
create mode 100644 superset/examples/slack_dashboard/data/users.parquet
create mode 100644 superset/examples/slack_dashboard/data/users_channels-uzooNNtSRO.parquet
rename superset/examples/{configs/datasets/examples => slack_dashboard/datasets}/members_channels_2.yaml (84%)
rename superset/examples/{configs/datasets/examples => slack_dashboard/datasets}/messages.yaml (74%)
rename superset/examples/{configs/datasets/examples => slack_dashboard/datasets}/messages_channels.yaml (83%)
rename superset/examples/{configs/datasets/examples => slack_dashboard/datasets}/new_members_daily.yaml (84%)
copy superset/examples/{configs/datasets/examples => slack_dashboard/datasets}/threads.yaml (77%)
rename superset/examples/{configs/datasets/examples/exported_stats.yaml => slack_dashboard/datasets/users.yaml} (61%)
rename superset/examples/{configs/datasets/examples/channel_members.yaml => slack_dashboard/datasets/users_channels-uzooNNtSRO.yaml} (67%)
delete mode 100644 superset/examples/supported_charts_dashboard.py
delete mode 100644 superset/examples/tabbed_dashboard.py
copy superset/examples/{configs/charts/Unicode Test/Unicode_Cloud.test.yaml => usa_births_names/charts/Boy_Name_Cloud.yaml} (68%)
copy superset/examples/{configs/charts/Featured Charts/Big_Number.yaml => usa_births_names/charts/Boys.yaml} (64%)
copy superset/examples/{configs/charts/Featured Charts/Tree.yaml => usa_births_names/charts/Genders.yaml} (66%)
copy superset/examples/{configs/charts/Featured Charts/Treemap.yaml => usa_births_names/charts/Genders_by_State.yaml} (53%)
copy superset/examples/{configs/charts/Unicode Test/Unicode_Cloud.test.yaml => usa_births_names/charts/Girl_Name_Cloud.yaml} (68%)
rename superset/examples/{configs/charts/Featured Charts/Big_Number.yaml => usa_births_names/charts/Girls.yaml} (64%)
copy superset/examples/{configs/charts/Unicode Test/Unicode_Cloud.test.yaml => usa_births_names/charts/Participants.yaml} (66%)
copy superset/examples/{configs/charts/Featured Charts/Tree.yaml => usa_births_names/charts/Pivot_Table_v2.yaml} (66%)
copy superset/examples/{configs/charts/Featured Charts/Box_Plot.yaml => usa_births_names/charts/Top_10_Boy_Name_Share.yaml} (58%)
rename superset/examples/{configs/charts/Featured Charts/Box_Plot.yaml => usa_births_names/charts/Top_10_Girl_Name_Share.yaml} (58%)
copy superset/examples/{configs/charts/Featured Charts/Histogram.yaml => usa_births_names/charts/Trends.yaml} (62%)
create mode 100644 superset/examples/usa_births_names/dashboard.yaml
create mode 100644 superset/examples/usa_births_names/data.parquet
copy superset/examples/{configs/datasets/examples/threads.yaml => usa_births_names/dataset.yaml} (59%)
rename superset/examples/{configs/charts/Video Game Sales => video_game_sales/charts}/Games.yaml (94%)
rename superset/examples/{configs/charts/Video Game Sales => video_game_sales/charts}/Games_per_Genre.yaml (51%)
rename superset/examples/{configs/charts/Video Game Sales => video_game_sales/charts}/Most_Dominant_Platforms.yaml (95%)
rename superset/examples/{configs/charts/Video Game Sales => video_game_sales/charts}/Popular_Genres_Across_Platforms.yaml (92%)
rename superset/examples/{configs/charts/Video Game Sales => video_game_sales/charts}/Publishers_With_Most_Titles.yaml (93%)
create mode 100644 superset/examples/video_game_sales/charts/Top_10_Games_Proportion_of_Sales_in_Markets.yaml
create mode 100644 superset/examples/video_game_sales/charts/Total_Sales_per_Market_Grouped_by_Genre.yaml
rename superset/examples/{configs/charts/Video Game Sales/Number_of_Games_That_Hit_100k_in_Sales_By_Release_Year.yaml => video_game_sales/charts/of_Games_That_Hit_100k_in_Sales_By_Release_Year.yaml} (52%)
rename superset/examples/{configs/dashboards/Video_Game_Sales.yaml => video_game_sales/dashboard.yaml} (55%)
create mode 100644 superset/examples/video_game_sales/data.parquet
rename superset/examples/{configs/datasets/examples/threads.yaml => video_game_sales/dataset.yaml} (65%)
copy superset/examples/{configs/charts/Featured Charts/Histogram.yaml => world_health/charts/Box_plot.yaml} (61%)
rename superset/examples/{configs/charts/Featured Charts/Histogram.yaml => world_health/charts/Growth_Rate.yaml} (61%)
copy superset/examples/{configs/charts/Featured Charts/Treemap.yaml => world_health/charts/Life_Expectancy_VS_Rural.yaml} (52%)
rename superset/examples/{configs/charts/Featured Charts/Word_Cloud.yaml => world_health/charts/Most_Populated_Countries.yaml} (63%)
rename superset/examples/{configs/charts/Unicode Test/Unicode_Cloud.test.yaml => world_health/charts/Rural.yaml} (56%)
rename superset/examples/{configs/charts/Featured Charts/Sunburst.yaml => world_health/charts/Rural_Breakdown.yaml} (57%)
rename superset/examples/{configs/charts/Featured Charts => world_health/charts}/Treemap.yaml (63%)
rename superset/examples/{configs/charts/Featured Charts/Sankey.yaml => world_health/charts/World_s_Pop_Growth.yaml} (63%)
rename superset/examples/{configs/charts/Featured Charts/Tree.yaml => world_health/charts/World_s_Population.yaml} (63%)
create mode 100644 superset/examples/world_health/dashboard.yaml
create mode 100644 superset/examples/world_health/data.parquet
create mode 100644 superset/examples/world_health/dataset.yaml
delete mode 100644 superset/mcp_service/chart/tool/get_chart_available_filters.py
create mode 100644 superset/mcp_service/common/schema_discovery.py
delete mode 100644 superset/mcp_service/dashboard/tool/get_dashboard_available_filters.py
delete mode 100644 superset/mcp_service/dataset/tool/get_dataset_available_filters.py
create mode 100644 superset/mcp_service/system/resources/schema_discovery.py
create mode 100644 superset/mcp_service/system/tool/get_schema.py
copy superset/{db_engine_specs/superset.py => migrations/versions/2025-11-18_14-00_9787190b3d89_add_currency_column_support.py} (52%)
copy superset-frontend/packages/superset-ui-core/src/components/constants.ts => superset/static/service-worker.js (77%)
create mode 100644 superset/translations/mi/LC_MESSAGES/messages.po
create mode 100644 superset/utils/currency.py
create mode 100644 tests/unit_tests/commands/dashboard/export_example_test.py
create mode 100644 tests/unit_tests/commands/databases/validate_sql_test.py
create mode 100644 tests/unit_tests/commands/importers/v1/examples_test.py
create mode 100644 tests/unit_tests/commands/report/alert_test.py
create mode 100644 tests/unit_tests/commands/report/test_create_recipients.py
create mode 100644 tests/unit_tests/common/test_query_actions_currency.py
create mode 100644 tests/unit_tests/dashboards/test_chart_customizations_dao.py
create mode 100644 tests/unit_tests/db_engine_specs/test_aurora.py
create mode 100644 tests/unit_tests/db_engine_specs/test_aws_iam.py
create mode 100644 tests/unit_tests/db_engine_specs/test_mongodb.py
create mode 100644 tests/unit_tests/db_engine_specs/test_mysql_iam.py
create mode 100644 tests/unit_tests/db_engine_specs/test_redshift_iam.py
delete mode 100644 tests/unit_tests/extensions/test_engine_manager.py
create mode 100644 tests/unit_tests/mcp_service/chart/tool/test_get_chart_data.py
copy {superset-core/src/superset_core/api => tests/unit_tests/mcp_service/chart/validation}/__init__.py (100%)
create mode 100644 tests/unit_tests/mcp_service/chart/validation/test_runtime_validator.py
create mode 100644 tests/unit_tests/mcp_service/system/tool/test_get_schema.py
create mode 100644 tests/unit_tests/mcp_service/test_mcp_server.py
create mode 100644 tests/unit_tests/utils/currency_test.py
create mode 100644 tests/unit_tests/utils/test_cache_manager.py
copy {superset-core/src/superset_core/api => tests/unit_tests/views/datasource}/__init__.py (100%)
create mode 100644 tests/unit_tests/views/datasource/utils_test.py
(superset) 02/13: Cleanup locks
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
commit e82e06891b0f7a6b88df2b9508d628e1a0c3635f
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Tue Jul 29 20:07:14 2025 -0400
Cleanup locks
---
superset/engines/manager.py | 115 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 114 insertions(+), 1 deletion(-)
diff --git a/superset/engines/manager.py b/superset/engines/manager.py
index 04ebe48b0b7..01eae105635 100644
--- a/superset/engines/manager.py
+++ b/superset/engines/manager.py
@@ -71,8 +71,13 @@ class EngineManager:
engines, as well as configuring the pool through the database settings.
"""
- def __init__(self, mode: EngineModes = EngineModes.NEW) -> None:
+ def __init__(
+ self,
+ mode: EngineModes = EngineModes.NEW,
+ cleanup_interval: float = 300.0, # 5 minutes default
+ ) -> None:
self.mode = mode
+ self.cleanup_interval = cleanup_interval
self._engines: dict[EngineKey, Engine] = {}
self._engine_locks: dict[EngineKey, threading.Lock] = defaultdict(
@@ -84,6 +89,25 @@ class EngineManager:
threading.Lock
)
+ # Background cleanup thread management
+ self._cleanup_thread: threading.Thread | None = None
+ self._cleanup_stop_event = threading.Event()
+ self._cleanup_thread_lock = threading.Lock()
+
+ def __del__(self) -> None:
+ """
+ Ensure cleanup thread is stopped when the manager is destroyed.
+ """
+ try:
+ self.stop_cleanup_thread()
+ except Exception as ex:
+ # Avoid exceptions during garbage collection, but log if possible
+ try:
+ logger.warning(f"Error stopping cleanup thread: {ex}")
+ except Exception: # noqa: S110
+ # If logging fails during destruction, we can't do anything
+ pass
+
@contextmanager
def get_engine(
self,
@@ -387,6 +411,95 @@ class EngineManager:
return kwargs
+ def start_cleanup_thread(self) -> None:
+ """
+ Start the background cleanup thread.
+
+ The thread will periodically clean up abandoned locks at the configured
+ interval. This is safe to call multiple times - subsequent calls are no-ops.
+ """
+ with self._cleanup_thread_lock:
+ if self._cleanup_thread is None or not self._cleanup_thread.is_alive():
+ self._cleanup_stop_event.clear()
+ self._cleanup_thread = threading.Thread(
+ target=self._cleanup_worker,
+ name=f"EngineManager-cleanup-{id(self)}",
+ daemon=True,
+ )
+ self._cleanup_thread.start()
+ logger.info(
+ f"Started cleanup thread with {self.cleanup_interval}s interval"
+ )
+
+ def stop_cleanup_thread(self) -> None:
+ """
+ Stop the background cleanup thread gracefully.
+
+ This will signal the thread to stop and wait for it to finish.
+ Safe to call even if no thread is running.
+ """
+ with self._cleanup_thread_lock:
+ if self._cleanup_thread is not None and self._cleanup_thread.is_alive():
+ self._cleanup_stop_event.set()
+ self._cleanup_thread.join(timeout=5.0) # 5 second timeout
+ if self._cleanup_thread.is_alive():
+ logger.warning("Cleanup thread did not stop within timeout")
+ else:
+ logger.info("Cleanup thread stopped")
+ self._cleanup_thread = None
+
+ def _cleanup_worker(self) -> None:
+ """
+ Background thread worker that periodically cleans up abandoned locks.
+ """
+ while not self._cleanup_stop_event.is_set():
+ try:
+ self._cleanup_abandoned_locks()
+ except Exception:
+ logger.exception("Error during background cleanup")
+
+ # Use wait() instead of sleep() to allow for immediate shutdown
+ if self._cleanup_stop_event.wait(timeout=self.cleanup_interval):
+ break # Stop event was set
+
+ def cleanup(self) -> None:
+ """
+ Public method to manually trigger cleanup of abandoned locks.
+
+ This can be called periodically by external systems to prevent
+ memory leaks from accumulating locks.
+ """
+ self._cleanup_abandoned_locks()
+
+ def _cleanup_abandoned_locks(self) -> None:
+ """
+ Remove locks for engines and tunnels that no longer exist.
+
+ This prevents memory leaks from accumulating locks in defaultdict
+ when engines/tunnels are disposed outside of normal cleanup paths.
+ """
+ # Clean up engine locks
+ active_engine_keys = set(self._engines.keys())
+ abandoned_engine_locks = set(self._engine_locks.keys()) - active_engine_keys
+ for key in abandoned_engine_locks:
+ self._engine_locks.pop(key, None)
+
+ if abandoned_engine_locks:
+ logger.debug(
+ f"Cleaned up {len(abandoned_engine_locks)} abandoned engine locks"
+ )
+
+ # Clean up tunnel locks
+ active_tunnel_keys = set(self._tunnels.keys())
+ abandoned_tunnel_locks = set(self._tunnel_locks.keys()) - active_tunnel_keys
+ for key in abandoned_tunnel_locks:
+ self._tunnel_locks.pop(key, None)
+
+ if abandoned_tunnel_locks:
+ logger.debug(
+ f"Cleaned up {len(abandoned_tunnel_locks)} abandoned tunnel locks"
+ )
+
def _add_disposal_listener(self, engine: Engine, engine_key: EngineKey) -> None:
@event.listens_for(engine, "engine_disposed")
def on_engine_disposed(engine_instance: Engine) -> None:
(superset) 07/13: Hash key
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
commit be31abeb7ee06f76868f990db86290fdf7b5cc48
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Wed Dec 3 17:26:28 2025 -0500
Hash key
---
superset/engines/manager.py | 144 +++++++++++++--
superset/extensions/engine_manager.py | 11 +-
superset/models/core.py | 8 +-
superset/superset_typing.py | 4 +-
tests/unit_tests/engines/manager_test.py | 295 +++++++++++++++++++++++++++++++
5 files changed, 432 insertions(+), 30 deletions(-)
diff --git a/superset/engines/manager.py b/superset/engines/manager.py
index adbe461e8bd..1d5d0efec65 100644
--- a/superset/engines/manager.py
+++ b/superset/engines/manager.py
@@ -16,6 +16,8 @@
# under the License.
import enum
+import hashlib
+import json
import logging
import threading
from contextlib import contextmanager
@@ -33,7 +35,6 @@ from sshtunnel import SSHTunnelForwarder
from superset.databases.utils import make_url_safe
from superset.superset_typing import DBConnectionMutator, EngineContextManager
from superset.utils.core import get_query_source_from_request, get_user_id, QuerySource
-from superset.utils.json import dumps
if TYPE_CHECKING:
from superset.databases.ssh_tunnel.models import SSHTunnel
@@ -48,7 +49,16 @@ class _LockManager:
Manages per-key locks safely without defaultdict race conditions.
This class provides a thread-safe way to create and manage locks for specific keys,
- avoiding race conditions.
+ avoiding the race conditions that occur when using defaultdict with threading.Lock.
+
+ The implementation uses a two-level locking strategy:
+ 1. A meta-lock to protect the lock dictionary itself
+ 2. Per-key locks to protect specific resources
+
+ This ensures that:
+ - Different keys can be locked concurrently (scalability)
+ - Lock creation is thread-safe (no race conditions)
+ - The same key always gets the same lock instance
"""
def __init__(self) -> None:
@@ -58,6 +68,16 @@ class _LockManager:
def get_lock(self, key: str) -> threading.RLock:
"""
Get or create a lock for the given key.
+
+ This method uses double-checked locking to ensure thread safety:
+ 1. First check without lock (fast path)
+ 2. Acquire meta-lock if needed
+ 3. Double-check inside the lock to prevent race conditions
+
+ This approach minimizes lock contention while ensuring correctness.
+
+ :param key: The key to get a lock for
+ :returns: An RLock instance for the given key
"""
if lock := self._locks.get(key):
return lock
@@ -73,6 +93,11 @@ class _LockManager:
def cleanup(self, active_keys: set[str]) -> None:
"""
Remove locks for keys that are no longer in use.
+
+ This prevents memory leaks from accumulating locks for resources
+ that have been disposed.
+
+ :param active_keys: Set of keys that are still active
"""
with self._meta_lock:
# Find locks to remove
@@ -85,6 +110,64 @@ EngineKey = str
TunnelKey = str
+def _normalize_value(value: Any) -> str:
+ """
+ Normalize a value for consistent hashing.
+
+ Converts various types to a consistent string representation for hashing.
+ Handles special cases like bytes, class objects, and nested structures.
+
+ :param value: The value to normalize
+ :returns: String representation suitable for hashing
+ """
+ if isinstance(value, bytes):
+ # For binary data (like private keys), hash it to avoid encoding issues
+ return hashlib.sha256(value).hexdigest()[:16]
+ elif isinstance(value, type):
+ # For class objects (like pool classes), use the class name
+ return value.__name__
+ elif isinstance(value, dict):
+ # For nested dicts, recursively normalize
+ normalized_dict = {}
+ for k, v in sorted(value.items()):
+ normalized_dict[k] = _normalize_value(v)
+ return json.dumps(normalized_dict, sort_keys=True, separators=(",", ":"))
+ elif isinstance(value, (list, tuple)):
+ # For lists/tuples, normalize each item
+ normalized_list = [_normalize_value(item) for item in value]
+ return json.dumps(normalized_list, separators=(",", ":"))
+ else:
+ # For everything else, convert to string
+ return str(value)
+
+
+def _generate_secure_key(components: dict[str, Any]) -> str:
+ """
+ Generate a secure hash-based key from components.
+
+ Creates a SHA-256 hash of the components to ensure:
+ 1. The key includes all parameters for proper caching
+ 2. Sensitive data is not exposed in logs or errors
+ 3. The key is deterministic for the same inputs
+
+ :param components: Dictionary of components to hash
+ :returns: 32-character hex string representing the secure key
+ """
+ # Create deterministic string representation
+ # Sort keys for consistency
+ key_data = {
+ k: _normalize_value(v) if v is not None else ""
+ for k, v in sorted(components.items())
+ }
+
+ # Create compact JSON representation
+ key_string = json.dumps(key_data, sort_keys=True, separators=(",", ":"))
+
+ # Generate SHA-256 hash and return first 32 hex characters
+ # 32 characters = 128 bits of entropy, sufficient for collision resistance
+ return hashlib.sha256(key_string.encode("utf-8")).hexdigest()[:32]
+
+
class EngineModes(enum.Enum):
# reuse existing engine if available, otherwise create a new one; this mode should
# have a connection pool configured in the database
@@ -231,19 +314,37 @@ class EngineManager:
user_id: int | None,
) -> EngineKey:
"""
- Generate a unique key for the engine based on the database and context.
+ Generate a secure hash-based key for the engine.
+
+ The key includes all parameters (including OAuth tokens and other sensitive
+ data) to ensure proper cache isolation, but uses a one-way hash to prevent
+ credential exposure in logs or errors.
+
+ :returns: 32-character hex string representing the secure key
"""
- uri, keys = self._get_engine_args(
+ # Get all parameters that affect the engine
+ uri, kwargs = self._get_engine_args(
database,
catalog,
schema,
source,
user_id,
)
- keys["uri"] = uri
- keys["source"] = source
- return dumps(keys, sort_keys=True)
+ # Create components for the key
+ # Include all parameters to ensure proper cache isolation
+ key_components = {
+ "database_id": database.id,
+ "catalog": catalog,
+ "schema": schema,
+ "uri": str(uri), # SQLAlchemy URLs mask passwords
+ "source": str(source) if source else None,
+ "user_id": user_id,
+ "kwargs": kwargs, # Includes OAuth tokens and other sensitive params
+ }
+
+ # Generate secure hash-based key
+ return _generate_secure_key(key_components)
def _get_engine_args(
self,
@@ -432,11 +533,20 @@ class EngineManager:
def _get_tunnel_key(self, ssh_tunnel: "SSHTunnel", uri: URL) -> TunnelKey:
"""
- Build a unique key for the SSH tunnel.
+ Generate a secure hash-based key for the SSH tunnel.
+
+ The key includes all tunnel parameters (including passwords and private keys)
+ to ensure proper cache isolation, but uses a one-way hash to prevent
+ credential exposure in logs or errors.
+
+ :returns: 32-character hex string representing the secure key
"""
- keys = self._get_tunnel_kwargs(ssh_tunnel, uri)
+ # Get all tunnel parameters
+ tunnel_kwargs = self._get_tunnel_kwargs(ssh_tunnel, uri)
- return dumps(keys, sort_keys=True)
+ # Generate secure hash-based key
+ # The tunnel_kwargs may contain sensitive data like passwords and private keys
+ return _generate_secure_key(tunnel_kwargs)
def _create_tunnel(self, ssh_tunnel: "SSHTunnel", uri: URL) -> SSHTunnelForwarder:
kwargs = self._get_tunnel_kwargs(ssh_tunnel, uri)
@@ -565,12 +675,10 @@ class EngineManager:
try:
# Remove engine from cache - no per-key locks to clean up anymore
if self._engines.pop(engine_key, None):
- logger.info(
- "Engine disposed and removed from cache: %s", engine_key
- )
+ # Log only first 8 chars of hash for safety
+ # (still enough for debugging, but doesn't expose full key)
+ log_key = engine_key[:8] + "..."
+ logger.info("Engine disposed and removed from cache: %s", log_key)
except Exception as ex:
- logger.error(
- "Error during engine disposal cleanup for %s: %s",
- engine_key,
- str(ex),
- )
+ logger.error("Error during engine disposal cleanup: %s", str(ex))
+ # Don't log engine_key to avoid exposing credential hash
diff --git a/superset/extensions/engine_manager.py b/superset/extensions/engine_manager.py
index 5a4cd0301b5..df391ad5d8e 100644
--- a/superset/extensions/engine_manager.py
+++ b/superset/extensions/engine_manager.py
@@ -17,15 +17,11 @@
import logging
from datetime import timedelta
-from typing import TYPE_CHECKING
from flask import Flask
from superset.engines.manager import EngineManager, EngineModes
-if TYPE_CHECKING:
- pass
-
logger = logging.getLogger(__name__)
@@ -74,7 +70,12 @@ class EngineManagerExtension:
def shutdown_engine_manager() -> None:
if self.engine_manager:
self.engine_manager.stop_cleanup_thread()
- logger.info("Stopped EngineManager cleanup thread")
+ # Use a try-except to handle closed log file handlers during tests
+ try:
+ logger.info("Stopped EngineManager cleanup thread")
+ except ValueError:
+ # Ignore logging errors during test shutdown when file handles are closed
+ pass
app.teardown_appcontext_funcs.append(lambda exc: None)
diff --git a/superset/models/core.py b/superset/models/core.py
index 04d3c533bd4..f20e0fce1e5 100755
--- a/superset/models/core.py
+++ b/superset/models/core.py
@@ -136,9 +136,7 @@ class ConfigurationMethod(StrEnum):
DYNAMIC_FORM = "dynamic_form"
-class Database(
- CoreDatabase, AuditMixinNullable, ImportExportMixin
-): # pylint: disable=too-many-public-methods
+class Database(CoreDatabase, AuditMixinNullable, ImportExportMixin): # pylint: disable=too-many-public-methods
"""An ORM object that stores Database related information"""
__tablename__ = "dbs"
@@ -415,7 +413,9 @@ class Database(
return (
username
if (username := get_username())
- else object_url.username if self.impersonate_user else None
+ else object_url.username
+ if self.impersonate_user
+ else None
)
@contextmanager
diff --git a/superset/superset_typing.py b/superset/superset_typing.py
index 5a69250b8bb..e3252483e84 100644
--- a/superset/superset_typing.py
+++ b/superset/superset_typing.py
@@ -34,6 +34,7 @@ from typing_extensions import NotRequired
from werkzeug.wrappers import Response
if TYPE_CHECKING:
+ from superset.models.core import Database
from superset.utils.core import (
GenericDataType,
QueryObjectFilterClause,
@@ -49,9 +50,6 @@ DBConnectionMutator: TypeAlias = Callable[
]
# Type alias for engine context manager
-if TYPE_CHECKING:
- from superset.models.core import Database
-
EngineContextManager: TypeAlias = Callable[
["Database", str | None, str | None], ContextManager[None]
]
diff --git a/tests/unit_tests/engines/manager_test.py b/tests/unit_tests/engines/manager_test.py
new file mode 100644
index 00000000000..287820eaf1b
--- /dev/null
+++ b/tests/unit_tests/engines/manager_test.py
@@ -0,0 +1,295 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Unit tests for EngineManager."""
+
+import threading
+from collections.abc import Iterator
+from unittest.mock import MagicMock, patch
+
+import pytest
+from sqlalchemy.pool import NullPool
+
+from superset.engines.manager import _LockManager, EngineManager, EngineModes
+
+
+class TestLockManager:
+ """Test the _LockManager class."""
+
+ def test_get_lock_creates_new_lock(self):
+ """Test that get_lock creates a new lock when needed."""
+ manager = _LockManager()
+ lock1 = manager.get_lock("key1")
+
+ assert isinstance(lock1, type(threading.RLock()))
+ assert lock1 is manager.get_lock("key1") # Same lock returned
+
+ def test_get_lock_different_keys_different_locks(self):
+ """Test that different keys get different locks."""
+ manager = _LockManager()
+ lock1 = manager.get_lock("key1")
+ lock2 = manager.get_lock("key2")
+
+ assert lock1 is not lock2
+
+ def test_cleanup_removes_unused_locks(self):
+ """Test that cleanup removes locks for inactive keys."""
+ manager = _LockManager()
+
+ # Create locks
+ lock1 = manager.get_lock("key1")
+ lock2 = manager.get_lock("key2")
+
+ # Cleanup with only key1 active
+ manager.cleanup({"key1"})
+
+ # key2 lock should be removed
+ lock3 = manager.get_lock("key2")
+ assert lock3 is not lock2 # New lock created
+
+ def test_concurrent_lock_creation(self):
+ """Test that concurrent lock creation doesn't create duplicates."""
+ manager = _LockManager()
+ locks_created = []
+ exceptions = []
+
+ def create_lock():
+ try:
+ lock = manager.get_lock("concurrent_key")
+ locks_created.append(lock)
+ except Exception as e:
+ exceptions.append(e)
+
+ # Create multiple threads trying to get the same lock
+ threads = [threading.Thread(target=create_lock) for _ in range(10)]
+ for t in threads:
+ t.start()
+ for t in threads:
+ t.join()
+
+ assert len(exceptions) == 0
+ assert len(locks_created) == 10
+
+ # All should be the same lock
+ first_lock = locks_created[0]
+ for lock in locks_created[1:]:
+ assert lock is first_lock
+
+
+class TestEngineManager:
+ """Test the EngineManager class."""
+
+ @pytest.fixture
+ def engine_manager(self):
+ """Create a mock EngineManager instance."""
+
+ def dummy_context_manager(
+ database: MagicMock, catalog: str | None, schema: str | None
+ ) -> Iterator[None]:
+ yield
+
+ return EngineManager(engine_context_manager=dummy_context_manager)
+
+ @pytest.fixture
+ def mock_database(self):
+ """Create a mock database."""
+ database = MagicMock()
+ database.sqlalchemy_uri_decrypted = "postgresql://user:pass@localhost/test"
+ database.get_extra.return_value = {"engine_params": {"poolclass": NullPool}}
+ database.get_effective_user.return_value = "test_user"
+ database.impersonate_user = False
+ database.update_params_from_encrypted_extra = MagicMock()
+ database.db_engine_spec = MagicMock()
+ database.db_engine_spec.adjust_engine_params.return_value = (MagicMock(), {})
+ database.db_engine_spec.impersonate_user = MagicMock(
+ return_value=(MagicMock(), {})
+ )
+ database.db_engine_spec.validate_database_uri = MagicMock()
+ database.ssh_tunnel = None
+ return database
+
+ @patch("superset.engines.manager.create_engine")
+ @patch("superset.engines.manager.make_url_safe")
+ def test_get_engine_new_mode(
+ self, mock_make_url, mock_create_engine, engine_manager, mock_database
+ ):
+ """Test getting an engine in NEW mode (no caching)."""
+ engine_manager.mode = EngineModes.NEW
+
+ mock_make_url.return_value = MagicMock()
+ mock_engine1 = MagicMock()
+ mock_engine2 = MagicMock()
+ mock_create_engine.side_effect = [mock_engine1, mock_engine2]
+
+ result = engine_manager._get_engine(mock_database, "catalog1", "schema1", None)
+
+ assert result is mock_engine1
+ mock_create_engine.assert_called_once()
+
+ # Calling again should create a new engine (no caching)
+ mock_create_engine.reset_mock()
+ result2 = engine_manager._get_engine(mock_database, "catalog2", "schema2", None)
+
+ assert result2 is mock_engine2 # Different engine
+ mock_create_engine.assert_called_once()
+
+ @patch("superset.engines.manager.create_engine")
+ @patch("superset.engines.manager.make_url_safe")
+ def test_get_engine_singleton_mode_caching(
+ self, mock_make_url, mock_create_engine, engine_manager, mock_database
+ ):
+ """Test that engines are cached in SINGLETON mode."""
+ engine_manager.mode = EngineModes.SINGLETON
+
+ # Use a real engine instead of MagicMock to avoid event listener issues
+ from sqlalchemy import create_engine
+ from sqlalchemy.pool import StaticPool
+
+ real_engine = create_engine("sqlite:///:memory:", poolclass=StaticPool)
+ mock_create_engine.return_value = real_engine
+ mock_make_url.return_value = real_engine
+
+ # Call twice with same params - should be cached
+ result1 = engine_manager._get_engine(mock_database, "catalog1", "schema1", None)
+ result2 = engine_manager._get_engine(mock_database, "catalog1", "schema1", None)
+
+ assert result1 is result2 # Same engine returned (cached)
+ mock_create_engine.assert_called_once() # Only created once
+
+ # Call with different params - should create new engine
+
+ @patch("superset.engines.manager.create_engine")
+ @patch("superset.engines.manager.make_url_safe")
+ def test_concurrent_engine_creation(
+ self, mock_make_url, mock_create_engine, engine_manager, mock_database
+ ):
+ """Test concurrent engine creation doesn't create duplicates."""
+ engine_manager.mode = EngineModes.SINGLETON
+
+ # Use a real engine to avoid event listener issues with MagicMock
+ from sqlalchemy import create_engine
+ from sqlalchemy.pool import StaticPool
+
+ real_engine = create_engine("sqlite:///:memory:", poolclass=StaticPool)
+ mock_make_url.return_value = real_engine
+
+ create_count = [0]
+
+ def counting_create_engine(*args, **kwargs):
+ create_count[0] += 1
+ return real_engine
+
+ mock_create_engine.side_effect = counting_create_engine
+
+ results = []
+ exceptions = []
+
+ def get_engine_thread():
+ try:
+ engine = engine_manager._get_engine(
+ mock_database, "catalog1", "schema1", None
+ )
+ results.append(engine)
+ except Exception as e:
+ exceptions.append(e)
+
+ # Run multiple threads
+ threads = [threading.Thread(target=get_engine_thread) for _ in range(10)]
+ for t in threads:
+ t.start()
+ for t in threads:
+ t.join()
+
+ assert len(exceptions) == 0
+ assert len(results) == 10
+ assert create_count[0] == 1 # Engine created only once
+
+ # All results should be the same engine
+ for engine in results:
+ assert engine is real_engine
+
+ @patch("superset.engines.manager.SSHTunnelForwarder")
+ def test_ssh_tunnel_creation(self, mock_tunnel_class, engine_manager):
+ """Test SSH tunnel creation and caching."""
+ ssh_tunnel = MagicMock()
+ ssh_tunnel.server_address = "ssh.example.com"
+ ssh_tunnel.server_port = 22
+ ssh_tunnel.username = "ssh_user"
+ ssh_tunnel.password = "ssh_pass"
+ ssh_tunnel.private_key = None
+ ssh_tunnel.private_key_password = None
+
+ tunnel_instance = MagicMock()
+ tunnel_instance.is_active = True
+ tunnel_instance.local_bind_address = ("127.0.0.1", 12345)
+ mock_tunnel_class.return_value = tunnel_instance
+
+ uri = MagicMock()
+ uri.host = "db.example.com"
+ uri.port = 5432
+ uri.get_backend_name.return_value = "postgresql"
+
+ result = engine_manager._get_tunnel(ssh_tunnel, uri)
+
+ assert result is tunnel_instance
+ mock_tunnel_class.assert_called_once()
+
+ # Getting same tunnel again should return cached version
+ mock_tunnel_class.reset_mock()
+ result2 = engine_manager._get_tunnel(ssh_tunnel, uri)
+
+ assert result2 is tunnel_instance
+ mock_tunnel_class.assert_not_called()
+
+ @patch("superset.engines.manager.SSHTunnelForwarder")
+ def test_ssh_tunnel_recreation_when_inactive(
+ self, mock_tunnel_class, engine_manager
+ ):
+ """Test that inactive tunnels are replaced."""
+ ssh_tunnel = MagicMock()
+ ssh_tunnel.server_address = "ssh.example.com"
+ ssh_tunnel.server_port = 22
+ ssh_tunnel.username = "ssh_user"
+ ssh_tunnel.password = "ssh_pass"
+ ssh_tunnel.private_key = None
+ ssh_tunnel.private_key_password = None
+
+ # First tunnel is inactive
+ inactive_tunnel = MagicMock()
+ inactive_tunnel.is_active = False
+ inactive_tunnel.local_bind_address = ("127.0.0.1", 12345)
+
+ # Second tunnel is active
+ active_tunnel = MagicMock()
+ active_tunnel.is_active = True
+ active_tunnel.local_bind_address = ("127.0.0.1", 23456)
+
+ mock_tunnel_class.side_effect = [inactive_tunnel, active_tunnel]
+
+ uri = MagicMock()
+ uri.host = "db.example.com"
+ uri.port = 5432
+ uri.get_backend_name.return_value = "postgresql"
+
+ # First call creates inactive tunnel
+ result1 = engine_manager._get_tunnel(ssh_tunnel, uri)
+ assert result1 is inactive_tunnel
+
+ # Second call should create new tunnel since first is inactive
+ result2 = engine_manager._get_tunnel(ssh_tunnel, uri)
+ assert result2 is active_tunnel
+ assert mock_tunnel_class.call_count == 2
(superset) 03/13: Add extension
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
commit baf6e03d16f95820ee480b53a8c5c09e62574a25
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Tue Jul 29 20:18:49 2025 -0400
Add extension
---
superset/config.py | 16 ++++++
superset/engines/manager.py | 47 ++++++++++------
superset/extensions/__init__.py | 2 +
superset/extensions/engine_manager.py | 103 ++++++++++++++++++++++++++++++++++
superset/initialization/__init__.py | 5 ++
5 files changed, 157 insertions(+), 16 deletions(-)
diff --git a/superset/config.py b/superset/config.py
index 9731d80bd58..3b0e3146197 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -260,6 +260,22 @@ SQLALCHEMY_ENGINE_OPTIONS = {}
# SQLALCHEMY_CUSTOM_PASSWORD_STORE = lookup_password
SQLALCHEMY_CUSTOM_PASSWORD_STORE = None
+# ---------------------------------------------------------
+# Engine Manager Configuration
+# ---------------------------------------------------------
+
+# Engine manager mode: "NEW" creates a new engine for every connection (default),
+# "SINGLETON" reuses engines with connection pooling
+ENGINE_MANAGER_MODE = "NEW"
+
+# Cleanup interval for abandoned locks in seconds (default: 5 minutes)
+ENGINE_MANAGER_CLEANUP_INTERVAL = 300.0
+
+# Automatically start cleanup thread for SINGLETON mode (default: True)
+ENGINE_MANAGER_AUTO_START_CLEANUP = True
+
+# ---------------------------------------------------------
+
#
# The EncryptedFieldTypeAdapter is used whenever we're building SqlAlchemy models
# which include sensitive fields that should be app-encrypted BEFORE sending
diff --git a/superset/engines/manager.py b/superset/engines/manager.py
index 01eae105635..9f2b41caad6 100644
--- a/superset/engines/manager.py
+++ b/superset/engines/manager.py
@@ -30,16 +30,12 @@ from sqlalchemy.engine import Engine
from sqlalchemy.engine.url import URL
from sshtunnel import SSHTunnelForwarder
-from superset import is_feature_enabled
-from superset.databases.ssh_tunnel.models import SSHTunnel
from superset.databases.utils import make_url_safe
-from superset.extensions import security_manager
from superset.utils.core import get_query_source_from_request, get_user_id, QuerySource
from superset.utils.json import dumps
-from superset.utils.oauth2 import check_for_oauth2, get_oauth2_access_token
-from superset.utils.ssh_tunnel import get_default_port
if TYPE_CHECKING:
+ from superset.databases.ssh_tunnel.models import SSHTunnel
from superset.models.core import Database
@@ -111,7 +107,7 @@ class EngineManager:
@contextmanager
def get_engine(
self,
- database: Database,
+ database: "Database",
catalog: str | None,
schema: str | None,
source: QuerySource | None,
@@ -125,12 +121,14 @@ class EngineManager:
with customization(database, catalog, schema):
# we need to check for errors indicating that OAuth2 is needed, and
# return the proper exception so it starts the authentication flow
+ from superset.utils.oauth2 import check_for_oauth2
+
with check_for_oauth2(database):
yield self._get_engine(database, catalog, schema, source)
def _get_engine(
self,
- database: Database,
+ database: "Database",
catalog: str | None,
schema: str | None,
source: QuerySource | None,
@@ -177,7 +175,7 @@ class EngineManager:
def _get_engine_key(
self,
- database: Database,
+ database: "Database",
catalog: str | None,
schema: str | None,
source: QuerySource | None,
@@ -200,7 +198,7 @@ class EngineManager:
def _get_engine_args(
self,
- database: Database,
+ database: "Database",
catalog: str | None,
schema: str | None,
source: QuerySource | None,
@@ -242,7 +240,15 @@ class EngineManager:
# get effective username
username = database.get_effective_user(uri)
- if username and is_feature_enabled("IMPERSONATE_WITH_EMAIL_PREFIX"):
+
+ # Import here to avoid circular imports
+ from superset.extensions import security_manager
+ from superset.utils.feature_flag_manager import FeatureFlagManager
+
+ feature_flag_manager = FeatureFlagManager()
+ if username and feature_flag_manager.is_feature_enabled(
+ "IMPERSONATE_WITH_EMAIL_PREFIX"
+ ):
user = security_manager.find_user(username=username)
if user and user.email and "@" in user.email:
username = user.email.split("@")[0]
@@ -250,6 +256,9 @@ class EngineManager:
# update URI/kwargs for user impersonation
if database.impersonate_user:
oauth2_config = database.get_oauth2_config()
+ # Import here to avoid circular imports
+ from superset.utils.oauth2 import get_oauth2_access_token
+
access_token = (
get_oauth2_access_token(
oauth2_config,
@@ -275,6 +284,9 @@ class EngineManager:
# mutate URI
if mutator := current_app.config["DB_CONNECTION_MUTATOR"]:
source = source or get_query_source_from_request()
+ # Import here to avoid circular imports
+ from superset.extensions import security_manager
+
uri, kwargs = mutator(
uri,
kwargs,
@@ -290,7 +302,7 @@ class EngineManager:
def _create_engine(
self,
- database: Database,
+ database: "Database",
catalog: str | None,
schema: str | None,
source: QuerySource | None,
@@ -324,7 +336,7 @@ class EngineManager:
return engine
- def _get_tunnel(self, ssh_tunnel: SSHTunnel, uri: URL) -> SSHTunnelForwarder:
+ def _get_tunnel(self, ssh_tunnel: "SSHTunnel", uri: URL) -> SSHTunnelForwarder:
tunnel_key = self._get_tunnel_key(ssh_tunnel, uri)
# tunnel exists and is healthy
@@ -345,7 +357,7 @@ class EngineManager:
def _replace_tunnel(
self,
tunnel_key: str,
- ssh_tunnel: SSHTunnel,
+ ssh_tunnel: "SSHTunnel",
uri: URL,
old_tunnel: SSHTunnelForwarder | None,
) -> SSHTunnelForwarder:
@@ -371,7 +383,7 @@ class EngineManager:
return new_tunnel
- def _get_tunnel_key(self, ssh_tunnel: SSHTunnel, uri: URL) -> TunnelKey:
+ def _get_tunnel_key(self, ssh_tunnel: "SSHTunnel", uri: URL) -> TunnelKey:
"""
Build a unique key for the SSH tunnel.
"""
@@ -379,15 +391,18 @@ class EngineManager:
return dumps(keys, sort_keys=True)
- def _create_tunnel(self, ssh_tunnel: SSHTunnel, uri: URL) -> SSHTunnelForwarder:
+ def _create_tunnel(self, ssh_tunnel: "SSHTunnel", uri: URL) -> SSHTunnelForwarder:
kwargs = self._get_tunnel_kwargs(ssh_tunnel, uri)
tunnel = SSHTunnelForwarder(**kwargs)
tunnel.start()
return tunnel
- def _get_tunnel_kwargs(self, ssh_tunnel: SSHTunnel, uri: URL) -> dict[str, Any]:
+ def _get_tunnel_kwargs(self, ssh_tunnel: "SSHTunnel", uri: URL) -> dict[str, Any]:
backend = uri.get_backend_name()
+ # Import here to avoid circular imports
+ from superset.utils.ssh_tunnel import get_default_port
+
kwargs = {
"ssh_address_or_host": (ssh_tunnel.server_address, ssh_tunnel.server_port),
"ssh_username": ssh_tunnel.username,
diff --git a/superset/extensions/__init__.py b/superset/extensions/__init__.py
index 628af40cd62..07ffbe743a3 100644
--- a/superset/extensions/__init__.py
+++ b/superset/extensions/__init__.py
@@ -41,6 +41,7 @@ from werkzeug.local import LocalProxy
from superset.async_events.async_query_manager import AsyncQueryManager
from superset.async_events.async_query_manager_factory import AsyncQueryManagerFactory
+from superset.extensions.engine_manager import EngineManagerExtension
from superset.extensions.ssh import SSHManagerFactory
from superset.extensions.stats_logger import BaseStatsLoggerManager
from superset.security.manager import SupersetSecurityManager
@@ -136,6 +137,7 @@ cache_manager = CacheManager()
celery_app = celery.Celery()
csrf = CSRFProtect()
db = get_sqla_class()()
+engine_manager_extension = EngineManagerExtension()
_event_logger: dict[str, Any] = {}
encrypted_field_factory = EncryptedFieldFactory()
event_logger = LocalProxy(lambda: _event_logger.get("event_logger"))
diff --git a/superset/extensions/engine_manager.py b/superset/extensions/engine_manager.py
new file mode 100644
index 00000000000..5c07d1cdd9e
--- /dev/null
+++ b/superset/extensions/engine_manager.py
@@ -0,0 +1,103 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+from typing import TYPE_CHECKING
+
+from flask import Flask
+
+from superset.engines.manager import EngineManager, EngineModes
+
+if TYPE_CHECKING:
+ pass
+
+logger = logging.getLogger(__name__)
+
+
+class EngineManagerExtension:
+ """
+ Flask extension for managing SQLAlchemy engines in Superset.
+
+ This extension creates and configures an EngineManager instance based on
+ Flask configuration, handling startup and shutdown of background cleanup
+ threads as needed.
+ """
+
+ def __init__(self) -> None:
+ self.engine_manager: EngineManager | None = None
+
+ def init_app(self, app: Flask) -> None:
+ """
+ Initialize the EngineManager with Flask app configuration.
+ """
+ # Get configuration values with defaults
+ mode_name = app.config.get("ENGINE_MANAGER_MODE", "NEW")
+ cleanup_interval = app.config.get("ENGINE_MANAGER_CLEANUP_INTERVAL", 300.0)
+ auto_start_cleanup = app.config.get("ENGINE_MANAGER_AUTO_START_CLEANUP", True)
+
+ # Convert mode string to enum
+ try:
+ mode = EngineModes[mode_name.upper()]
+ except KeyError:
+ logger.warning(
+ f"Invalid ENGINE_MANAGER_MODE '{mode_name}', defaulting to NEW"
+ )
+ mode = EngineModes.NEW
+
+ # Create the engine manager
+ self.engine_manager = EngineManager(
+ mode=mode,
+ cleanup_interval=cleanup_interval,
+ )
+
+ # Start cleanup thread if requested and in SINGLETON mode
+ if auto_start_cleanup and mode == EngineModes.SINGLETON:
+ self.engine_manager.start_cleanup_thread()
+ logger.info("Started EngineManager cleanup thread")
+
+ # Register shutdown handler
+ def shutdown_engine_manager() -> None:
+ if self.engine_manager:
+ self.engine_manager.stop_cleanup_thread()
+ logger.info("Stopped EngineManager cleanup thread")
+
+ app.teardown_appcontext_funcs.append(lambda exc: None)
+
+ # Register with atexit for clean shutdown
+ import atexit
+
+ atexit.register(shutdown_engine_manager)
+
+ logger.info(
+ f"Initialized EngineManager with mode={mode.name}, "
+ f"cleanup_interval={cleanup_interval}s"
+ )
+
+ @property
+ def manager(self) -> EngineManager:
+ """
+ Get the EngineManager instance.
+
+ Raises:
+ RuntimeError: If the extension hasn't been initialized with an app.
+ """
+ if self.engine_manager is None:
+ raise RuntimeError(
+ "EngineManager extension not initialized. "
+ "Call init_app() with a Flask app first."
+ )
+ return self.engine_manager
diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py
index 3a34d315bf5..f18149d61f8 100644
--- a/superset/initialization/__init__.py
+++ b/superset/initialization/__init__.py
@@ -49,6 +49,7 @@ from superset.extensions import (
csrf,
db,
encrypted_field_factory,
+ engine_manager_extension,
feature_flag_manager,
machine_auth_provider_factory,
manifest_processor,
@@ -585,6 +586,7 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
self.configure_url_map_converters()
self.configure_data_sources()
self.configure_auth_provider()
+ self.configure_engine_manager()
self.configure_async_queries()
self.configure_ssh_manager()
self.configure_stats_manager()
@@ -761,6 +763,9 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
def configure_auth_provider(self) -> None:
machine_auth_provider_factory.init_app(self.superset_app)
+ def configure_engine_manager(self) -> None:
+ engine_manager_extension.init_app(self.superset_app)
+
def configure_ssh_manager(self) -> None:
ssh_manager_factory.init_app(self.superset_app)
(superset) 10/13: Fix more tests
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 62d2d82ed856e5ca1a0dee62a89a16a327a93e6d
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Wed Dec 3 20:41:32 2025 -0500
Fix more tests
---
.../integration_tests/databases/commands_tests.py | 20 ++++++------
tests/integration_tests/model_tests.py | 37 ++++++++++++++--------
2 files changed, 33 insertions(+), 24 deletions(-)
diff --git a/tests/integration_tests/databases/commands_tests.py b/tests/integration_tests/databases/commands_tests.py
index 2d43ee14d0f..1be5849b1cb 100644
--- a/tests/integration_tests/databases/commands_tests.py
+++ b/tests/integration_tests/databases/commands_tests.py
@@ -986,7 +986,7 @@ class TestTestConnectionDatabaseCommand(SupersetTestCase):
with pytest.raises(DatabaseSecurityUnsafeError) as excinfo: # noqa: PT012
command_without_db_name.run()
- assert str(excinfo.value) == ("Stopped an unsafe database connection")
+ assert str(excinfo.value) == ("Stopped an unsafe database connection")
mock_event_logger.assert_called()
@@ -1008,9 +1008,9 @@ class TestTestConnectionDatabaseCommand(SupersetTestCase):
with pytest.raises(SupersetErrorsException) as excinfo: # noqa: PT012
command_without_db_name.run()
- assert str(excinfo.value) == (
- "Connection failed, please check your connection settings"
- )
+ assert str(excinfo.value) == (
+ "Connection failed, please check your connection settings"
+ )
mock_event_logger.assert_called()
@@ -1147,7 +1147,7 @@ class TestTablesDatabaseCommand(SupersetTestCase):
with pytest.raises(DatabaseNotFoundError) as excinfo: # noqa: PT012
command.run()
- assert str(excinfo.value) == ("Database not found.")
+ assert str(excinfo.value) == ("Database not found.")
@patch("superset.daos.database.DatabaseDAO.find_by_id")
@patch("superset.security.manager.SupersetSecurityManager.can_access_database")
@@ -1166,7 +1166,7 @@ class TestTablesDatabaseCommand(SupersetTestCase):
command = TablesDatabaseCommand(database.id, None, "main", False)
with pytest.raises(SupersetException) as excinfo: # noqa: PT012
command.run()
- assert str(excinfo.value) == "Test Error"
+ assert str(excinfo.value) == "Test Error"
@patch("superset.daos.database.DatabaseDAO.find_by_id")
@patch("superset.security.manager.SupersetSecurityManager.can_access_database")
@@ -1182,10 +1182,10 @@ class TestTablesDatabaseCommand(SupersetTestCase):
command = TablesDatabaseCommand(database.id, None, "main", False)
with pytest.raises(DatabaseTablesUnexpectedError) as excinfo: # noqa: PT012
command.run()
- assert (
- str(excinfo.value)
- == "Unexpected error occurred, please check your logs for details"
- )
+ assert (
+ str(excinfo.value)
+ == "Unexpected error occurred, please check your logs for details"
+ )
@patch("superset.daos.database.DatabaseDAO.find_by_id")
@patch("superset.security.manager.SupersetSecurityManager.can_access_database")
diff --git a/tests/integration_tests/model_tests.py b/tests/integration_tests/model_tests.py
index e80fea7db88..4e9987fe3f3 100644
--- a/tests/integration_tests/model_tests.py
+++ b/tests/integration_tests/model_tests.py
@@ -145,7 +145,7 @@ class TestDatabaseModel(SupersetTestCase):
username = make_url(engine.url).username
assert example_user.username != username
- @mock.patch("superset.models.core.create_engine")
+ @mock.patch("superset.engines.manager.create_engine")
@unittest.skipUnless(
SupersetTestCase.is_module_installed("pyhive"), "pyhive not installed"
)
@@ -172,7 +172,8 @@ class TestDatabaseModel(SupersetTestCase):
database_name="test_database", sqlalchemy_uri=uri, extra=extra
)
model.impersonate_user = True
- model._get_sqla_engine()
+ with model.get_sqla_engine():
+ pass
call_args = mocked_create_engine.call_args
assert str(call_args[0][0]) == "presto://gamma@localhost/"
@@ -185,7 +186,8 @@ class TestDatabaseModel(SupersetTestCase):
}
model.impersonate_user = False
- model._get_sqla_engine()
+ with model.get_sqla_engine():
+ pass
call_args = mocked_create_engine.call_args
assert str(call_args[0][0]) == "presto://localhost/"
@@ -199,13 +201,14 @@ class TestDatabaseModel(SupersetTestCase):
@unittest.skipUnless(
SupersetTestCase.is_module_installed("mysqlclient"), "mysqlclient not installed"
)
- @mock.patch("superset.models.core.create_engine")
+ @mock.patch("superset.engines.manager.create_engine")
def test_adjust_engine_params_mysql(self, mocked_create_engine):
model = Database(
database_name="test_database1",
sqlalchemy_uri="mysql://user:password@localhost",
)
- model._get_sqla_engine()
+ with model.get_sqla_engine():
+ pass
call_args = mocked_create_engine.call_args
assert str(call_args[0][0]) == "mysql://user:password@localhost"
@@ -215,13 +218,14 @@ class TestDatabaseModel(SupersetTestCase):
database_name="test_database2",
sqlalchemy_uri="mysql+mysqlconnector://user:password@localhost",
)
- model._get_sqla_engine()
+ with model.get_sqla_engine():
+ pass
call_args = mocked_create_engine.call_args
assert str(call_args[0][0]) == "mysql+mysqlconnector://user:password@localhost"
assert call_args[1]["connect_args"]["allow_local_infile"] == 0
- @mock.patch("superset.models.core.create_engine")
+ @mock.patch("superset.engines.manager.create_engine")
def test_impersonate_user_trino(self, mocked_create_engine):
principal_user = security_manager.find_user(username="gamma")
@@ -230,7 +234,8 @@ class TestDatabaseModel(SupersetTestCase):
database_name="test_database", sqlalchemy_uri="trino://localhost"
)
model.impersonate_user = True
- model._get_sqla_engine()
+ with model.get_sqla_engine():
+ pass
call_args = mocked_create_engine.call_args
assert str(call_args[0][0]) == "trino://localhost/"
@@ -242,7 +247,8 @@ class TestDatabaseModel(SupersetTestCase):
)
model.impersonate_user = True
- model._get_sqla_engine()
+ with model.get_sqla_engine():
+ pass
call_args = mocked_create_engine.call_args
assert (
@@ -251,7 +257,7 @@ class TestDatabaseModel(SupersetTestCase):
)
assert call_args[1]["connect_args"]["user"] == "gamma"
- @mock.patch("superset.models.core.create_engine")
+ @mock.patch("superset.engines.manager.create_engine")
@unittest.skipUnless(
SupersetTestCase.is_module_installed("pyhive"), "pyhive not installed"
)
@@ -281,7 +287,8 @@ class TestDatabaseModel(SupersetTestCase):
database_name="test_database", sqlalchemy_uri=uri, extra=extra
)
model.impersonate_user = True
- model._get_sqla_engine()
+ with model.get_sqla_engine():
+ pass
call_args = mocked_create_engine.call_args
assert str(call_args[0][0]) == "hive://localhost"
@@ -294,7 +301,8 @@ class TestDatabaseModel(SupersetTestCase):
}
model.impersonate_user = False
- model._get_sqla_engine()
+ with model.get_sqla_engine():
+ pass
call_args = mocked_create_engine.call_args
assert str(call_args[0][0]) == "hive://localhost"
@@ -376,7 +384,7 @@ class TestDatabaseModel(SupersetTestCase):
df = main_db.get_df("USE superset; SELECT ';';", None, None)
assert df.iat[0, 0] == ";"
- @mock.patch("superset.models.core.create_engine")
+ @mock.patch("superset.engines.manager.create_engine")
def test_get_sqla_engine(self, mocked_create_engine):
model = Database(
database_name="test_database",
@@ -387,7 +395,8 @@ class TestDatabaseModel(SupersetTestCase):
)
mocked_create_engine.side_effect = Exception()
with self.assertRaises(SupersetException): # noqa: PT027
- model._get_sqla_engine()
+ with model.get_sqla_engine():
+ pass
class TestSqlaTableModel(SupersetTestCase):
(superset) 06/13: Small improvements
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 5f61bb8d76a8e52c78f66c59ee8a941163591492
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Wed Dec 3 15:59:46 2025 -0500
Small improvements
---
superset/config.py | 11 ++-
superset/engines/manager.py | 169 +++++++++++++++++++-------------
superset/extensions/__init__.py | 2 -
superset/extensions/engine_manager.py | 17 +++-
superset/extensions/ssh.py | 94 ------------------
superset/initialization/__init__.py | 5 -
superset/models/core.py | 17 ++--
superset/superset_typing.py | 31 +++++-
tests/unit_tests/extensions/ssh_test.py | 36 -------
9 files changed, 163 insertions(+), 219 deletions(-)
diff --git a/superset/config.py b/superset/config.py
index ee4dc92feca..4110d902ffa 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -56,7 +56,11 @@ from superset.engines.manager import EngineModes
from superset.jinja_context import BaseTemplateProcessor
from superset.key_value.types import JsonKeyValueCodec
from superset.stats_logger import DummyStatsLogger
-from superset.superset_typing import CacheConfig
+from superset.superset_typing import (
+ CacheConfig,
+ DBConnectionMutator,
+ EngineContextManager,
+)
from superset.tasks.types import ExecutorType
from superset.themes.types import Theme
from superset.utils import core as utils
@@ -826,7 +830,6 @@ DEFAULT_FEATURE_FLAGS: dict[str, bool] = {
# FIREWALL (only port 22 is open)
# ----------------------------------------------------------------------
-SSH_TUNNEL_MANAGER_CLASS = "superset.extensions.ssh.SSHManager"
SSH_TUNNEL_LOCAL_BIND_ADDRESS = "127.0.0.1"
#: Timeout (seconds) for tunnel connection (open_channel timeout)
SSH_TUNNEL_TIMEOUT_SEC = 10.0
@@ -1701,7 +1704,7 @@ def engine_context_manager( # pylint: disable=unused-argument
yield None
-ENGINE_CONTEXT_MANAGER = engine_context_manager
+ENGINE_CONTEXT_MANAGER: EngineContextManager = engine_context_manager
# A callable that allows altering the database connection URL and params
# on the fly, at runtime. This allows for things like impersonation or
@@ -1718,7 +1721,7 @@ ENGINE_CONTEXT_MANAGER = engine_context_manager
#
# Note that the returned uri and params are passed directly to sqlalchemy's
# as such `create_engine(url, **params)`
-DB_CONNECTION_MUTATOR = None
+DB_CONNECTION_MUTATOR: DBConnectionMutator | None = None
# A callable that is invoked for every invocation of DB Engine Specs
diff --git a/superset/engines/manager.py b/superset/engines/manager.py
index cbe855fd1fd..adbe461e8bd 100644
--- a/superset/engines/manager.py
+++ b/superset/engines/manager.py
@@ -18,13 +18,12 @@
import enum
import logging
import threading
-from collections import defaultdict
from contextlib import contextmanager
from datetime import timedelta
from io import StringIO
-from typing import Any, TYPE_CHECKING
+from typing import Any, Iterator, TYPE_CHECKING
-from flask import current_app
+import sshtunnel
from paramiko import RSAKey
from sqlalchemy import create_engine, event, pool
from sqlalchemy.engine import Engine
@@ -32,6 +31,7 @@ from sqlalchemy.engine.url import URL
from sshtunnel import SSHTunnelForwarder
from superset.databases.utils import make_url_safe
+from superset.superset_typing import DBConnectionMutator, EngineContextManager
from superset.utils.core import get_query_source_from_request, get_user_id, QuerySource
from superset.utils.json import dumps
@@ -43,6 +43,44 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
+class _LockManager:
+ """
+ Manages per-key locks safely without defaultdict race conditions.
+
+ This class provides a thread-safe way to create and manage locks for specific keys,
+ avoiding race conditions.
+ """
+
+ def __init__(self) -> None:
+ self._locks: dict[str, threading.RLock] = {}
+ self._meta_lock = threading.Lock()
+
+ def get_lock(self, key: str) -> threading.RLock:
+ """
+ Get or create a lock for the given key.
+ """
+ if lock := self._locks.get(key):
+ return lock
+
+ with self._meta_lock:
+ # Double-check inside the lock
+ lock = self._locks.get(key)
+ if lock is None:
+ lock = threading.RLock()
+ self._locks[key] = lock
+ return lock
+
+ def cleanup(self, active_keys: set[str]) -> None:
+ """
+ Remove locks for keys that are no longer in use.
+ """
+ with self._meta_lock:
+ # Find locks to remove
+ locks_to_remove = self._locks.keys() - active_keys
+ for key in locks_to_remove:
+ self._locks.pop(key, None)
+
+
EngineKey = str
TunnelKey = str
@@ -71,21 +109,27 @@ class EngineManager:
def __init__(
self,
+ engine_context_manager: EngineContextManager,
+ db_connection_mutator: DBConnectionMutator | None = None,
mode: EngineModes = EngineModes.NEW,
cleanup_interval: timedelta = timedelta(minutes=5),
+ local_bind_address: str = "127.0.0.1",
+ tunnel_timeout: timedelta = timedelta(seconds=30),
+ ssh_timeout: timedelta = timedelta(seconds=1),
) -> None:
+ self.engine_context_manager = engine_context_manager
+ self.db_connection_mutator = db_connection_mutator
self.mode = mode
self.cleanup_interval = cleanup_interval
+ self.local_bind_address = local_bind_address
- self._engines: dict[EngineKey, Engine] = {}
- self._engine_locks: dict[EngineKey, threading.Lock] = defaultdict(
- threading.Lock
- )
+ sshtunnel.TUNNEL_TIMEOUT = tunnel_timeout.total_seconds()
+ sshtunnel.SSH_TIMEOUT = ssh_timeout.total_seconds()
+ self._engines: dict[EngineKey, Engine] = {}
+ self._engine_locks = _LockManager()
self._tunnels: dict[TunnelKey, SSHTunnelForwarder] = {}
- self._tunnel_locks: dict[TunnelKey, threading.Lock] = defaultdict(
- threading.Lock
- )
+ self._tunnel_locks = _LockManager()
# Background cleanup thread management
self._cleanup_thread: threading.Thread | None = None
@@ -113,14 +157,13 @@ class EngineManager:
catalog: str | None,
schema: str | None,
source: QuerySource | None,
- ) -> Engine:
+ ) -> Iterator[Engine]:
"""
Context manager to get a SQLAlchemy engine.
"""
# users can wrap the engine in their own context manager for different
# reasons
- customization = current_app.config["ENGINE_CONTEXT_MANAGER"]
- with customization(database, catalog, schema):
+ with self.engine_context_manager(database, catalog, schema):
# we need to check for errors indicating that OAuth2 is needed, and
# return the proper exception so it starts the authentication flow
from superset.utils.oauth2 import check_for_oauth2
@@ -158,22 +201,26 @@ class EngineManager:
user_id,
)
- if engine_key not in self._engines:
- with self._engine_locks[engine_key]:
- # double-checked locking to ensure thread safety and prevent unnecessary
- # engine creation
- if engine_key not in self._engines:
- engine = self._create_engine(
- database,
- catalog,
- schema,
- source,
- user_id,
- )
- self._engines[engine_key] = engine
- self._add_disposal_listener(engine, engine_key)
+ if engine := self._engines.get(engine_key):
+ return engine
+
+ lock = self._engine_locks.get_lock(engine_key)
+ with lock:
+ # Double-check inside the lock
+ if engine := self._engines.get(engine_key):
+ return engine
- return self._engines[engine_key]
+ # Create and cache the engine
+ engine = self._create_engine(
+ database,
+ catalog,
+ schema,
+ source,
+ user_id,
+ )
+ self._engines[engine_key] = engine
+ self._add_disposal_listener(engine, engine_key)
+ return engine
def _get_engine_key(
self,
@@ -284,12 +331,12 @@ class EngineManager:
database.update_params_from_encrypted_extra(kwargs)
# mutate URI
- if mutator := current_app.config["DB_CONNECTION_MUTATOR"]:
+ if self.db_connection_mutator:
source = source or get_query_source_from_request()
# Import here to avoid circular imports
from superset.extensions import security_manager
- uri, kwargs = mutator(
+ uri, kwargs = self.db_connection_mutator(
uri,
kwargs,
username,
@@ -340,20 +387,19 @@ class EngineManager:
def _get_tunnel(self, ssh_tunnel: "SSHTunnel", uri: URL) -> SSHTunnelForwarder:
tunnel_key = self._get_tunnel_key(ssh_tunnel, uri)
- # tunnel exists and is healthy
- if tunnel_key in self._tunnels:
- tunnel = self._tunnels[tunnel_key]
- if tunnel.is_active:
- return tunnel
+ tunnel = self._tunnels.get(tunnel_key)
+ if tunnel is not None and tunnel.is_active:
+ return tunnel
- # create or recreate tunnel
- with self._tunnel_locks[tunnel_key]:
- existing_tunnel = self._tunnels.get(tunnel_key)
- if existing_tunnel and existing_tunnel.is_active:
- return existing_tunnel
+ lock = self._tunnel_locks.get_lock(tunnel_key)
+ with lock:
+ # Double-check inside the lock
+ tunnel = self._tunnels.get(tunnel_key)
+ if tunnel is not None and tunnel.is_active:
+ return tunnel
- # replace inactive or missing tunnel
- return self._replace_tunnel(tunnel_key, ssh_tunnel, uri, existing_tunnel)
+ # Create or replace tunnel
+ return self._replace_tunnel(tunnel_key, ssh_tunnel, uri, tunnel)
def _replace_tunnel(
self,
@@ -400,15 +446,15 @@ class EngineManager:
return tunnel
def _get_tunnel_kwargs(self, ssh_tunnel: "SSHTunnel", uri: URL) -> dict[str, Any]:
- backend = uri.get_backend_name()
# Import here to avoid circular imports
from superset.utils.ssh_tunnel import get_default_port
+ backend = uri.get_backend_name()
kwargs = {
"ssh_address_or_host": (ssh_tunnel.server_address, ssh_tunnel.server_port),
"ssh_username": ssh_tunnel.username,
"remote_bind_address": (uri.host, uri.port or get_default_port(backend)),
- "local_bind_address": (ssh_tunnel.local_bind_address,),
+ "local_bind_address": (self.local_bind_address,),
"debug_level": logging.getLogger("flask_appbuilder").level,
}
@@ -492,45 +538,36 @@ class EngineManager:
def _cleanup_abandoned_locks(self) -> None:
"""
- Remove locks for engines and tunnels that no longer exist.
+ Clean up locks for engines and tunnels that no longer exist.
- This prevents memory leaks from accumulating locks in defaultdict
- when engines/tunnels are disposed outside of normal cleanup paths.
+ This prevents memory leaks from accumulating locks when engines/tunnels
+ are disposed outside of normal cleanup paths.
"""
- # Clean up engine locks
+ # Clean up engine locks for inactive engines
active_engine_keys = set(self._engines.keys())
- abandoned_engine_locks = set(self._engine_locks.keys()) - active_engine_keys
- for key in abandoned_engine_locks:
- self._engine_locks.pop(key, None)
-
- if abandoned_engine_locks:
- logger.debug(
- "Cleaned up %d abandoned engine locks",
- len(abandoned_engine_locks),
- )
+ self._engine_locks.cleanup(active_engine_keys)
- # Clean up tunnel locks
+ # Clean up tunnel locks for inactive tunnels
active_tunnel_keys = set(self._tunnels.keys())
- abandoned_tunnel_locks = set(self._tunnel_locks.keys()) - active_tunnel_keys
- for key in abandoned_tunnel_locks:
- self._tunnel_locks.pop(key, None)
+ self._tunnel_locks.cleanup(active_tunnel_keys)
- if abandoned_tunnel_locks:
+ # Log for debugging
+ if active_engine_keys or active_tunnel_keys:
logger.debug(
- "Cleaned up %d abandoned tunnel locks",
- len(abandoned_tunnel_locks),
+ "EngineManager resources - Engines: %d, Tunnels: %d",
+ len(active_engine_keys),
+ len(active_tunnel_keys),
)
def _add_disposal_listener(self, engine: Engine, engine_key: EngineKey) -> None:
@event.listens_for(engine, "engine_disposed")
def on_engine_disposed(engine_instance: Engine) -> None:
try:
- # `pop` is atomic -- no lock needed
+ # Remove engine from cache - no per-key locks to clean up anymore
if self._engines.pop(engine_key, None):
logger.info(
"Engine disposed and removed from cache: %s", engine_key
)
- self._engine_locks.pop(engine_key, None)
except Exception as ex:
logger.error(
"Error during engine disposal cleanup for %s: %s",
diff --git a/superset/extensions/__init__.py b/superset/extensions/__init__.py
index 07ffbe743a3..a396e207ee9 100644
--- a/superset/extensions/__init__.py
+++ b/superset/extensions/__init__.py
@@ -42,7 +42,6 @@ from werkzeug.local import LocalProxy
from superset.async_events.async_query_manager import AsyncQueryManager
from superset.async_events.async_query_manager_factory import AsyncQueryManagerFactory
from superset.extensions.engine_manager import EngineManagerExtension
-from superset.extensions.ssh import SSHManagerFactory
from superset.extensions.stats_logger import BaseStatsLoggerManager
from superset.security.manager import SupersetSecurityManager
from superset.utils.cache_manager import CacheManager
@@ -148,6 +147,5 @@ migrate = Migrate()
profiling = ProfilingExtension()
results_backend_manager = ResultsBackendManager()
security_manager: SupersetSecurityManager = LocalProxy(lambda: appbuilder.sm)
-ssh_manager_factory = SSHManagerFactory()
stats_logger_manager = BaseStatsLoggerManager()
talisman = Talisman()
diff --git a/superset/extensions/engine_manager.py b/superset/extensions/engine_manager.py
index c341364e2a5..5a4cd0301b5 100644
--- a/superset/extensions/engine_manager.py
+++ b/superset/extensions/engine_manager.py
@@ -16,6 +16,7 @@
# under the License.
import logging
+from datetime import timedelta
from typing import TYPE_CHECKING
from flask import Flask
@@ -44,13 +45,25 @@ class EngineManagerExtension:
"""
Initialize the EngineManager with Flask app configuration.
"""
- # Get configuration values with defaults
+ engine_context_manager = app.config["ENGINE_CONTEXT_MANAGER"]
+ db_connection_mutator = app.config["DB_CONNECTION_MUTATOR"]
mode = app.config["ENGINE_MANAGER_MODE"]
cleanup_interval = app.config["ENGINE_MANAGER_CLEANUP_INTERVAL"]
+ local_bind_address = app.config["SSH_TUNNEL_LOCAL_BIND_ADDRESS"]
+ tunnel_timeout = timedelta(seconds=app.config["SSH_TUNNEL_TIMEOUT_SEC"])
+ ssh_timeout = timedelta(seconds=app.config["SSH_TUNNEL_PACKET_TIMEOUT_SEC"])
auto_start_cleanup = app.config["ENGINE_MANAGER_AUTO_START_CLEANUP"]
# Create the engine manager
- self.engine_manager = EngineManager(mode, cleanup_interval)
+ self.engine_manager = EngineManager(
+ engine_context_manager,
+ db_connection_mutator,
+ mode,
+ cleanup_interval,
+ local_bind_address,
+ tunnel_timeout,
+ ssh_timeout,
+ )
# Start cleanup thread if requested and in SINGLETON mode
if auto_start_cleanup and mode == EngineModes.SINGLETON:
diff --git a/superset/extensions/ssh.py b/superset/extensions/ssh.py
deleted file mode 100644
index 74fb44cfd75..00000000000
--- a/superset/extensions/ssh.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import logging
-from io import StringIO
-from typing import TYPE_CHECKING
-
-import sshtunnel
-from flask import Flask
-from paramiko import RSAKey
-
-from superset.commands.database.ssh_tunnel.exceptions import SSHTunnelDatabasePortError
-from superset.databases.utils import make_url_safe
-from superset.utils.class_utils import load_class_from_name
-
-if TYPE_CHECKING:
- from superset.databases.ssh_tunnel.models import SSHTunnel
-
-
-class SSHManager:
- def __init__(self, app: Flask) -> None:
- super().__init__()
- self.local_bind_address = app.config["SSH_TUNNEL_LOCAL_BIND_ADDRESS"]
- sshtunnel.TUNNEL_TIMEOUT = app.config["SSH_TUNNEL_TIMEOUT_SEC"]
- sshtunnel.SSH_TIMEOUT = app.config["SSH_TUNNEL_PACKET_TIMEOUT_SEC"]
-
- def build_sqla_url(
- self, sqlalchemy_url: str, server: sshtunnel.SSHTunnelForwarder
- ) -> str:
- # override any ssh tunnel configuration object
- url = make_url_safe(sqlalchemy_url)
- return url.set(
- host=server.local_bind_address[0],
- port=server.local_bind_port,
- )
-
- def create_tunnel(
- self,
- ssh_tunnel: "SSHTunnel",
- sqlalchemy_database_uri: str,
- ) -> sshtunnel.SSHTunnelForwarder:
- from superset.utils.ssh_tunnel import get_default_port
-
- url = make_url_safe(sqlalchemy_database_uri)
- backend = url.get_backend_name()
- port = url.port or get_default_port(backend)
- if not port:
- raise SSHTunnelDatabasePortError()
- params = {
- "ssh_address_or_host": (ssh_tunnel.server_address, ssh_tunnel.server_port),
- "ssh_username": ssh_tunnel.username,
- "remote_bind_address": (url.host, port),
- "local_bind_address": (self.local_bind_address,),
- "debug_level": logging.getLogger("flask_appbuilder").level,
- }
-
- if ssh_tunnel.password:
- params["ssh_password"] = ssh_tunnel.password
- elif ssh_tunnel.private_key:
- private_key_file = StringIO(ssh_tunnel.private_key)
- private_key = RSAKey.from_private_key(
- private_key_file, ssh_tunnel.private_key_password
- )
- params["ssh_pkey"] = private_key
-
- return sshtunnel.open_tunnel(**params)
-
-
-class SSHManagerFactory:
- def __init__(self) -> None:
- self._ssh_manager = None
-
- def init_app(self, app: Flask) -> None:
- self._ssh_manager = load_class_from_name(
- app.config["SSH_TUNNEL_MANAGER_CLASS"]
- )(app)
-
- @property
- def instance(self) -> SSHManager:
- return self._ssh_manager # type: ignore
diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py
index f18149d61f8..4cefa0c7337 100644
--- a/superset/initialization/__init__.py
+++ b/superset/initialization/__init__.py
@@ -56,7 +56,6 @@ from superset.extensions import (
migrate,
profiling,
results_backend_manager,
- ssh_manager_factory,
stats_logger_manager,
talisman,
)
@@ -588,7 +587,6 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
self.configure_auth_provider()
self.configure_engine_manager()
self.configure_async_queries()
- self.configure_ssh_manager()
self.configure_stats_manager()
# Hook that provides administrators a handle on the Flask APP
@@ -766,9 +764,6 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
def configure_engine_manager(self) -> None:
engine_manager_extension.init_app(self.superset_app)
- def configure_ssh_manager(self) -> None:
- ssh_manager_factory.init_app(self.superset_app)
-
def configure_stats_manager(self) -> None:
stats_logger_manager.init_app(self.superset_app)
diff --git a/superset/models/core.py b/superset/models/core.py
index c6dcd9ad05b..04d3c533bd4 100755
--- a/superset/models/core.py
+++ b/superset/models/core.py
@@ -30,7 +30,7 @@ from copy import deepcopy
from datetime import datetime
from functools import lru_cache
from inspect import signature
-from typing import Any, Callable, cast, Optional, TYPE_CHECKING
+from typing import Any, Callable, cast, Iterator, Optional, TYPE_CHECKING
import numpy
import pandas as pd
@@ -136,7 +136,9 @@ class ConfigurationMethod(StrEnum):
DYNAMIC_FORM = "dynamic_form"
-class Database(CoreDatabase, AuditMixinNullable, ImportExportMixin): # pylint: disable=too-many-public-methods
+class Database(
+ CoreDatabase, AuditMixinNullable, ImportExportMixin
+): # pylint: disable=too-many-public-methods
"""An ORM object that stores Database related information"""
__tablename__ = "dbs"
@@ -413,9 +415,7 @@ class Database(CoreDatabase, AuditMixinNullable, ImportExportMixin): # pylint:
return (
username
if (username := get_username())
- else object_url.username
- if self.impersonate_user
- else None
+ else object_url.username if self.impersonate_user else None
)
@contextmanager
@@ -424,7 +424,7 @@ class Database(CoreDatabase, AuditMixinNullable, ImportExportMixin): # pylint:
catalog: str | None = None,
schema: str | None = None,
source: utils.QuerySource | None = None,
- ) -> Engine:
+ ) -> Iterator[Engine]:
"""
Context manager for a SQLAlchemy engine.
@@ -437,12 +437,13 @@ class Database(CoreDatabase, AuditMixinNullable, ImportExportMixin): # pylint:
# Use the engine manager to get the engine
engine_manager = engine_manager_extension.manager
- return engine_manager.get_engine(
+ with engine_manager.get_engine(
database=self,
catalog=catalog,
schema=schema,
source=source,
- )
+ ) as engine:
+ yield engine
def add_database_to_signature(
self,
diff --git a/superset/superset_typing.py b/superset/superset_typing.py
index 02e294a08cf..5a69250b8bb 100644
--- a/superset/superset_typing.py
+++ b/superset/superset_typing.py
@@ -18,17 +18,44 @@ from __future__ import annotations
from collections.abc import Hashable, Sequence
from datetime import datetime
-from typing import Any, Literal, TYPE_CHECKING, TypeAlias, TypedDict
+from typing import (
+ Any,
+ Callable,
+ ContextManager,
+ Literal,
+ TYPE_CHECKING,
+ TypeAlias,
+ TypedDict,
+)
+from sqlalchemy.engine.url import URL
from sqlalchemy.sql.type_api import TypeEngine
from typing_extensions import NotRequired
from werkzeug.wrappers import Response
if TYPE_CHECKING:
- from superset.utils.core import GenericDataType, QueryObjectFilterClause
+ from superset.utils.core import (
+ GenericDataType,
+ QueryObjectFilterClause,
+ QuerySource,
+ )
SQLType: TypeAlias = TypeEngine | type[TypeEngine]
+# Type alias for database connection mutator function
+DBConnectionMutator: TypeAlias = Callable[
+ [URL, dict[str, Any], str | None, Any, "QuerySource | None"],
+ tuple[URL, dict[str, Any]],
+]
+
+# Type alias for engine context manager
+if TYPE_CHECKING:
+ from superset.models.core import Database
+
+EngineContextManager: TypeAlias = Callable[
+ ["Database", str | None, str | None], ContextManager[None]
+]
+
class LegacyMetric(TypedDict):
label: str | None
diff --git a/tests/unit_tests/extensions/ssh_test.py b/tests/unit_tests/extensions/ssh_test.py
deleted file mode 100644
index a36f0fe03eb..00000000000
--- a/tests/unit_tests/extensions/ssh_test.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-from unittest.mock import Mock
-
-import sshtunnel
-
-from superset.extensions.ssh import SSHManagerFactory
-
-
-def test_ssh_tunnel_timeout_setting() -> None:
- app = Mock()
- app.config = {
- "SSH_TUNNEL_MAX_RETRIES": 2,
- "SSH_TUNNEL_LOCAL_BIND_ADDRESS": "test",
- "SSH_TUNNEL_TIMEOUT_SEC": 123.0,
- "SSH_TUNNEL_PACKET_TIMEOUT_SEC": 321.0,
- "SSH_TUNNEL_MANAGER_CLASS": "superset.extensions.ssh.SSHManager",
- }
- factory = SSHManagerFactory()
- factory.init_app(app)
- assert sshtunnel.TUNNEL_TIMEOUT == 123.0
- assert sshtunnel.SSH_TIMEOUT == 321.0
(superset) 11/13: Fix poolclass check
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
commit d06ccf51524dfe34596cad26ca330473c4e7add4
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Dec 4 09:40:04 2025 -0500
Fix poolclass check
---
superset/engines/manager.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/superset/engines/manager.py b/superset/engines/manager.py
index 452882108aa..ed6700d4884 100644
--- a/superset/engines/manager.py
+++ b/superset/engines/manager.py
@@ -323,7 +323,7 @@ class EngineManager:
kwargs = extra.get("engine_params", {})
# get pool class
- if self.mode == EngineModes.NEW or "poolclass" not in extra:
+ if self.mode == EngineModes.NEW or "poolclass" not in kwargs:
kwargs["poolclass"] = pool.NullPool
else:
pools = {
(superset) 05/13: Cleanup
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
commit bb5a15dc5a60c7894a3b38d6bcdea5018ca9864f
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Wed Dec 3 14:57:02 2025 -0500
Cleanup
---
superset/config.py | 5 +++--
superset/engines/manager.py | 31 +++++++++++++++++++------------
superset/extensions/engine_manager.py | 25 +++++++------------------
superset/models/core.py | 12 ++++--------
4 files changed, 33 insertions(+), 40 deletions(-)
diff --git a/superset/config.py b/superset/config.py
index 3b0e3146197..ee4dc92feca 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -52,6 +52,7 @@ from superset.advanced_data_type.plugins.internet_address import internet_addres
from superset.advanced_data_type.plugins.internet_port import internet_port
from superset.advanced_data_type.types import AdvancedDataType
from superset.constants import CHANGE_ME_SECRET_KEY
+from superset.engines.manager import EngineModes
from superset.jinja_context import BaseTemplateProcessor
from superset.key_value.types import JsonKeyValueCodec
from superset.stats_logger import DummyStatsLogger
@@ -266,10 +267,10 @@ SQLALCHEMY_CUSTOM_PASSWORD_STORE = None
# Engine manager mode: "NEW" creates a new engine for every connection (default),
# "SINGLETON" reuses engines with connection pooling
-ENGINE_MANAGER_MODE = "NEW"
+ENGINE_MANAGER_MODE = EngineModes.NEW
# Cleanup interval for abandoned locks in seconds (default: 5 minutes)
-ENGINE_MANAGER_CLEANUP_INTERVAL = 300.0
+ENGINE_MANAGER_CLEANUP_INTERVAL = timedelta(minutes=5)
# Automatically start cleanup thread for SINGLETON mode (default: True)
ENGINE_MANAGER_AUTO_START_CLEANUP = True
diff --git a/superset/engines/manager.py b/superset/engines/manager.py
index 37be4641cb7..cbe855fd1fd 100644
--- a/superset/engines/manager.py
+++ b/superset/engines/manager.py
@@ -20,6 +20,7 @@ import logging
import threading
from collections import defaultdict
from contextlib import contextmanager
+from datetime import timedelta
from io import StringIO
from typing import Any, TYPE_CHECKING
@@ -71,7 +72,7 @@ class EngineManager:
def __init__(
self,
mode: EngineModes = EngineModes.NEW,
- cleanup_interval: float = 300.0, # 5 minutes default
+ cleanup_interval: timedelta = timedelta(minutes=5),
) -> None:
self.mode = mode
self.cleanup_interval = cleanup_interval
@@ -100,7 +101,7 @@ class EngineManager:
except Exception as ex:
# Avoid exceptions during garbage collection, but log if possible
try:
- logger.warning(f"Error stopping cleanup thread: {ex}")
+ logger.warning("Error stopping cleanup thread: %s", ex)
except Exception: # noqa: S110
# If logging fails during destruction, we can't do anything
pass
@@ -212,6 +213,10 @@ class EngineManager:
needed, since it needs to connect to the tunnel instead of the original DB. But
that information is only available after the tunnel is created.
"""
+ # Import here to avoid circular imports
+ from superset.extensions import security_manager
+ from superset.utils.feature_flag_manager import FeatureFlagManager
+
uri = make_url_safe(database.sqlalchemy_uri_decrypted)
extra = database.get_extra(source)
@@ -242,10 +247,6 @@ class EngineManager:
# get effective username
username = database.get_effective_user(uri)
- # Import here to avoid circular imports
- from superset.extensions import security_manager
- from superset.utils.feature_flag_manager import FeatureFlagManager
-
feature_flag_manager = FeatureFlagManager()
if username and feature_flag_manager.is_feature_enabled(
"IMPERSONATE_WITH_EMAIL_PREFIX"
@@ -322,7 +323,6 @@ class EngineManager:
user_id,
)
- tunnel = None
if database.ssh_tunnel:
tunnel = self._get_tunnel(database.ssh_tunnel, uri)
uri = uri.set(
@@ -444,7 +444,8 @@ class EngineManager:
)
self._cleanup_thread.start()
logger.info(
- f"Started cleanup thread with {self.cleanup_interval}s interval"
+ "Started cleanup thread with %ds interval",
+ self.cleanup_interval.total_seconds(),
)
def stop_cleanup_thread(self) -> None:
@@ -475,7 +476,9 @@ class EngineManager:
logger.exception("Error during background cleanup")
# Use wait() instead of sleep() to allow for immediate shutdown
- if self._cleanup_stop_event.wait(timeout=self.cleanup_interval):
+ if self._cleanup_stop_event.wait(
+ timeout=self.cleanup_interval.total_seconds()
+ ):
break # Stop event was set
def cleanup(self) -> None:
@@ -502,7 +505,8 @@ class EngineManager:
if abandoned_engine_locks:
logger.debug(
- f"Cleaned up {len(abandoned_engine_locks)} abandoned engine locks"
+ "Cleaned up %d abandoned engine locks",
+ len(abandoned_engine_locks),
)
# Clean up tunnel locks
@@ -513,7 +517,8 @@ class EngineManager:
if abandoned_tunnel_locks:
logger.debug(
- f"Cleaned up {len(abandoned_tunnel_locks)} abandoned tunnel locks"
+ "Cleaned up %d abandoned tunnel locks",
+ len(abandoned_tunnel_locks),
)
def _add_disposal_listener(self, engine: Engine, engine_key: EngineKey) -> None:
@@ -522,7 +527,9 @@ class EngineManager:
try:
# `pop` is atomic -- no lock needed
if self._engines.pop(engine_key, None):
- logger.info(f"Engine disposed and removed from cache: {engine_key}")
+ logger.info(
+ "Engine disposed and removed from cache: %s", engine_key
+ )
self._engine_locks.pop(engine_key, None)
except Exception as ex:
logger.error(
diff --git a/superset/extensions/engine_manager.py b/superset/extensions/engine_manager.py
index 5c07d1cdd9e..c341364e2a5 100644
--- a/superset/extensions/engine_manager.py
+++ b/superset/extensions/engine_manager.py
@@ -45,24 +45,12 @@ class EngineManagerExtension:
Initialize the EngineManager with Flask app configuration.
"""
# Get configuration values with defaults
- mode_name = app.config.get("ENGINE_MANAGER_MODE", "NEW")
- cleanup_interval = app.config.get("ENGINE_MANAGER_CLEANUP_INTERVAL", 300.0)
- auto_start_cleanup = app.config.get("ENGINE_MANAGER_AUTO_START_CLEANUP", True)
-
- # Convert mode string to enum
- try:
- mode = EngineModes[mode_name.upper()]
- except KeyError:
- logger.warning(
- f"Invalid ENGINE_MANAGER_MODE '{mode_name}', defaulting to NEW"
- )
- mode = EngineModes.NEW
+ mode = app.config["ENGINE_MANAGER_MODE"]
+ cleanup_interval = app.config["ENGINE_MANAGER_CLEANUP_INTERVAL"]
+ auto_start_cleanup = app.config["ENGINE_MANAGER_AUTO_START_CLEANUP"]
# Create the engine manager
- self.engine_manager = EngineManager(
- mode=mode,
- cleanup_interval=cleanup_interval,
- )
+ self.engine_manager = EngineManager(mode, cleanup_interval)
# Start cleanup thread if requested and in SINGLETON mode
if auto_start_cleanup and mode == EngineModes.SINGLETON:
@@ -83,8 +71,9 @@ class EngineManagerExtension:
atexit.register(shutdown_engine_manager)
logger.info(
- f"Initialized EngineManager with mode={mode.name}, "
- f"cleanup_interval={cleanup_interval}s"
+ "Initialized EngineManager with mode=%s, cleanup_interval=%ds",
+ mode,
+ cleanup_interval.total_seconds(),
)
@property
diff --git a/superset/models/core.py b/superset/models/core.py
index 18a44e66cd5..c6dcd9ad05b 100755
--- a/superset/models/core.py
+++ b/superset/models/core.py
@@ -136,9 +136,7 @@ class ConfigurationMethod(StrEnum):
DYNAMIC_FORM = "dynamic_form"
-class Database(
- CoreDatabase, AuditMixinNullable, ImportExportMixin
-): # pylint: disable=too-many-public-methods
+class Database(CoreDatabase, AuditMixinNullable, ImportExportMixin): # pylint: disable=too-many-public-methods
"""An ORM object that stores Database related information"""
__tablename__ = "dbs"
@@ -415,7 +413,9 @@ class Database(
return (
username
if (username := get_username())
- else object_url.username if self.impersonate_user else None
+ else object_url.username
+ if self.impersonate_user
+ else None
)
@contextmanager
@@ -431,9 +431,6 @@ class Database(
This method will return a context manager for a SQLAlchemy engine. The engine
manager handles connection pooling, SSH tunnels, and other connection details
based on the configured mode (NEW or SINGLETON).
-
- Note: The nullpool parameter is kept for backwards compatibility but is ignored.
- Pool configuration is now read from the database's extra configuration.
"""
# Import here to avoid circular imports
from superset.extensions import engine_manager_extension
@@ -470,7 +467,6 @@ class Database(
self,
catalog: str | None = None,
schema: str | None = None,
- nullpool: bool = True, # Kept for backwards compatibility, but ignored
source: utils.QuerySource | None = None,
) -> Connection:
with self.get_sqla_engine(
(superset) 13/13: Rebase
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
commit c00fae53a5253f06c89f2c88953f0f1c57176770
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Wed Feb 4 09:58:15 2026 -0500
Rebase
---
UPDATING.md | 35 ++++++++++++++++++++++++++++++
superset/engines/manager.py | 5 +++--
tests/unit_tests/engines/manager_test.py | 9 ++++----
tests/unit_tests/sql/execution/conftest.py | 1 -
4 files changed, 42 insertions(+), 8 deletions(-)
diff --git a/UPDATING.md b/UPDATING.md
index 0f22ef36f03..635a7d2f3a0 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -24,6 +24,41 @@ assists people when migrating to a new version.
## Next
+### Engine Manager for Connection Pooling
+
+A new `EngineManager` class has been introduced to centralize SQLAlchemy engine creation and management. This enables connection pooling for analytics databases and provides a more flexible architecture for engine configuration.
+
+#### Breaking Changes
+
+1. **Removed `SSH_TUNNEL_MANAGER_CLASS` config**: SSH tunnel handling is now integrated into the EngineManager. If you have custom SSH tunnel managers, you'll need to migrate to the new architecture.
+
+2. **Removed `nullpool` parameter**: The `get_sqla_engine()` and `get_raw_connection()` methods on the `Database` model no longer accept a `nullpool` parameter. Pool configuration is now controlled through the engine manager.
+
+3. **Removed `_get_sqla_engine()` method**: The private `_get_sqla_engine()` method has been removed from the `Database` model. All engine creation now goes through the `EngineManager`.
+
+#### New Configuration Options
+
+```python
+# Engine manager mode:
+# - EngineModes.NEW: Creates a new engine for every connection (default, original behavior)
+# - EngineModes.SINGLETON: Reuses engines with connection pooling
+from superset.engines.manager import EngineModes
+ENGINE_MANAGER_MODE = EngineModes.NEW
+
+# Cleanup interval for abandoned locks (default: 5 minutes)
+from datetime import timedelta
+ENGINE_MANAGER_CLEANUP_INTERVAL = timedelta(minutes=5)
+
+# Automatically start cleanup thread for SINGLETON mode (default: True)
+ENGINE_MANAGER_AUTO_START_CLEANUP = True
+```
+
+#### Migration Guide
+
+- If you were using the `nullpool` parameter, remove it from your calls
+- If you had a custom `SSH_TUNNEL_MANAGER_CLASS`, refactor to use the new EngineManager architecture
+- If you need connection pooling, set `ENGINE_MANAGER_MODE = EngineModes.SINGLETON` and configure the pool in your database's `extra` JSON field
+
### WebSocket config for GAQ with Docker
[35896](https://github.com/apache/superset/pull/35896) and [37624](https://github.com/apache/superset/pull/37624) updated documentation on how to run and configure Superset with Docker. Specifically for the WebSocket configuration, a new `docker/superset-websocket/config.example.json` was added to the repo, so that users could copy it to create a `docker/superset-websocket/config.json` file. The existing `docker/superset-websocket/config.json` was removed and git-ignored, so if you're us [...]
diff --git a/superset/engines/manager.py b/superset/engines/manager.py
index ed6700d4884..1beb41929af 100644
--- a/superset/engines/manager.py
+++ b/superset/engines/manager.py
@@ -320,7 +320,8 @@ class EngineManager:
uri = make_url_safe(database.sqlalchemy_uri_decrypted)
extra = database.get_extra(source)
- kwargs = extra.get("engine_params", {})
+ # Make a copy to avoid mutating the original extra dict
+ kwargs = dict(extra.get("engine_params", {}))
# get pool class
if self.mode == EngineModes.NEW or "poolclass" not in kwargs:
@@ -336,7 +337,7 @@ class EngineManager:
kwargs["poolclass"] = pools.get(extra["poolclass"], pool.QueuePool)
# update URI for specific catalog/schema
- connect_args = extra.setdefault("connect_args", {})
+ connect_args = dict(extra.get("connect_args", {}))
uri, connect_args = database.db_engine_spec.adjust_engine_params(
uri,
connect_args,
diff --git a/tests/unit_tests/engines/manager_test.py b/tests/unit_tests/engines/manager_test.py
index 871624f2a42..dc5248d663a 100644
--- a/tests/unit_tests/engines/manager_test.py
+++ b/tests/unit_tests/engines/manager_test.py
@@ -22,7 +22,6 @@ from collections.abc import Iterator
from unittest.mock import MagicMock, patch
import pytest
-from sqlalchemy.pool import NullPool
from superset.engines.manager import _LockManager, EngineManager, EngineModes
@@ -51,7 +50,7 @@ class TestLockManager:
manager = _LockManager()
# Create locks
- lock1 = manager.get_lock("key1")
+ _ = manager.get_lock("key1") # noqa: F841
lock2 = manager.get_lock("key2")
# Cleanup with only key1 active
@@ -111,7 +110,7 @@ class TestEngineManager:
"""Create a mock database."""
database = MagicMock()
database.sqlalchemy_uri_decrypted = "postgresql://user:pass@localhost/test"
- database.get_extra.return_value = {"engine_params": {"poolclass": NullPool}}
+ database.get_extra.return_value = {"engine_params": {}}
database.get_effective_user.return_value = "test_user"
database.impersonate_user = False
database.update_params_from_encrypted_extra = MagicMock()
@@ -231,7 +230,7 @@ class TestEngineManager:
ssh_tunnel.server_address = "ssh.example.com"
ssh_tunnel.server_port = 22
ssh_tunnel.username = "ssh_user"
- ssh_tunnel.password = "ssh_pass"
+ ssh_tunnel.password = "ssh_pass" # noqa: S105
ssh_tunnel.private_key = None
ssh_tunnel.private_key_password = None
@@ -266,7 +265,7 @@ class TestEngineManager:
ssh_tunnel.server_address = "ssh.example.com"
ssh_tunnel.server_port = 22
ssh_tunnel.username = "ssh_user"
- ssh_tunnel.password = "ssh_pass"
+ ssh_tunnel.password = "ssh_pass" # noqa: S105
ssh_tunnel.private_key = None
ssh_tunnel.private_key_password = None
diff --git a/tests/unit_tests/sql/execution/conftest.py b/tests/unit_tests/sql/execution/conftest.py
index 630f6884529..fba41e6e376 100644
--- a/tests/unit_tests/sql/execution/conftest.py
+++ b/tests/unit_tests/sql/execution/conftest.py
@@ -202,7 +202,6 @@ def setup_mock_raw_connection(
def _raw_connection(
catalog: str | None = None,
schema: str | None = None,
- nullpool: bool = True,
source: Any | None = None,
):
yield mock_connection
(superset) 09/13: Simplify key generation
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 688224c4c0c27d0b6b2b5eb23b8507f91ae5d915
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Wed Dec 3 20:23:18 2025 -0500
Simplify key generation
---
superset/engines/manager.py | 112 +++++++++++---------------------------------
1 file changed, 28 insertions(+), 84 deletions(-)
diff --git a/superset/engines/manager.py b/superset/engines/manager.py
index 1d5d0efec65..452882108aa 100644
--- a/superset/engines/manager.py
+++ b/superset/engines/manager.py
@@ -17,7 +17,6 @@
import enum
import hashlib
-import json
import logging
import threading
from contextlib import contextmanager
@@ -110,62 +109,22 @@ EngineKey = str
TunnelKey = str
-def _normalize_value(value: Any) -> str:
+def _generate_cache_key(*args: Any) -> str:
"""
- Normalize a value for consistent hashing.
+ Generate a deterministic cache key from arbitrary arguments.
- Converts various types to a consistent string representation for hashing.
- Handles special cases like bytes, class objects, and nested structures.
+ Uses repr() for serialization and SHA-256 for hashing. The resulting key
+ is a 32-character hex string that:
+ 1. Is deterministic for the same inputs
+ 2. Does not expose sensitive data (everything is hashed)
+ 3. Has sufficient entropy to avoid collisions
- :param value: The value to normalize
- :returns: String representation suitable for hashing
+ :param args: Arguments to include in the cache key
+ :returns: 32-character hex string
"""
- if isinstance(value, bytes):
- # For binary data (like private keys), hash it to avoid encoding issues
- return hashlib.sha256(value).hexdigest()[:16]
- elif isinstance(value, type):
- # For class objects (like pool classes), use the class name
- return value.__name__
- elif isinstance(value, dict):
- # For nested dicts, recursively normalize
- normalized_dict = {}
- for k, v in sorted(value.items()):
- normalized_dict[k] = _normalize_value(v)
- return json.dumps(normalized_dict, sort_keys=True, separators=(",", ":"))
- elif isinstance(value, (list, tuple)):
- # For lists/tuples, normalize each item
- normalized_list = [_normalize_value(item) for item in value]
- return json.dumps(normalized_list, separators=(",", ":"))
- else:
- # For everything else, convert to string
- return str(value)
-
-
-def _generate_secure_key(components: dict[str, Any]) -> str:
- """
- Generate a secure hash-based key from components.
-
- Creates a SHA-256 hash of the components to ensure:
- 1. The key includes all parameters for proper caching
- 2. Sensitive data is not exposed in logs or errors
- 3. The key is deterministic for the same inputs
-
- :param components: Dictionary of components to hash
- :returns: 32-character hex string representing the secure key
- """
- # Create deterministic string representation
- # Sort keys for consistency
- key_data = {
- k: _normalize_value(v) if v is not None else ""
- for k, v in sorted(components.items())
- }
-
- # Create compact JSON representation
- key_string = json.dumps(key_data, sort_keys=True, separators=(",", ":"))
-
- # Generate SHA-256 hash and return first 32 hex characters
- # 32 characters = 128 bits of entropy, sufficient for collision resistance
- return hashlib.sha256(key_string.encode("utf-8")).hexdigest()[:32]
+ # Use repr() which works with most Python objects and is deterministic
+ serialized = repr(args).encode("utf-8")
+ return hashlib.sha256(serialized).hexdigest()[:32]
class EngineModes(enum.Enum):
@@ -314,15 +273,13 @@ class EngineManager:
user_id: int | None,
) -> EngineKey:
"""
- Generate a secure hash-based key for the engine.
+ Generate a cache key for the engine.
- The key includes all parameters (including OAuth tokens and other sensitive
- data) to ensure proper cache isolation, but uses a one-way hash to prevent
- credential exposure in logs or errors.
+ The key is a hash of all parameters that affect the engine, ensuring
+ proper cache isolation without exposing sensitive data.
- :returns: 32-character hex string representing the secure key
+ :returns: 32-character hex string
"""
- # Get all parameters that affect the engine
uri, kwargs = self._get_engine_args(
database,
catalog,
@@ -331,20 +288,15 @@ class EngineManager:
user_id,
)
- # Create components for the key
- # Include all parameters to ensure proper cache isolation
- key_components = {
- "database_id": database.id,
- "catalog": catalog,
- "schema": schema,
- "uri": str(uri), # SQLAlchemy URLs mask passwords
- "source": str(source) if source else None,
- "user_id": user_id,
- "kwargs": kwargs, # Includes OAuth tokens and other sensitive params
- }
-
- # Generate secure hash-based key
- return _generate_secure_key(key_components)
+ return _generate_cache_key(
+ database.id,
+ catalog,
+ schema,
+ str(uri),
+ source,
+ user_id,
+ kwargs,
+ )
def _get_engine_args(
self,
@@ -533,20 +485,12 @@ class EngineManager:
def _get_tunnel_key(self, ssh_tunnel: "SSHTunnel", uri: URL) -> TunnelKey:
"""
- Generate a secure hash-based key for the SSH tunnel.
-
- The key includes all tunnel parameters (including passwords and private keys)
- to ensure proper cache isolation, but uses a one-way hash to prevent
- credential exposure in logs or errors.
+ Generate a cache key for the SSH tunnel.
- :returns: 32-character hex string representing the secure key
+ :returns: 32-character hex string
"""
- # Get all tunnel parameters
tunnel_kwargs = self._get_tunnel_kwargs(ssh_tunnel, uri)
-
- # Generate secure hash-based key
- # The tunnel_kwargs may contain sensitive data like passwords and private keys
- return _generate_secure_key(tunnel_kwargs)
+ return _generate_cache_key(tunnel_kwargs)
def _create_tunnel(self, ssh_tunnel: "SSHTunnel", uri: URL) -> SSHTunnelForwarder:
kwargs = self._get_tunnel_kwargs(ssh_tunnel, uri)
(superset) 01/13: feat: engine manager
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 5753dfbb6ed79f09b07a70b50cf25a21343dba1f
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Tue Jul 29 19:01:09 2025 -0400
feat: engine manager
---
superset/engines/__init__.py | 16 ++
superset/engines/manager.py | 403 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 419 insertions(+)
diff --git a/superset/engines/__init__.py b/superset/engines/__init__.py
new file mode 100644
index 00000000000..13a83393a91
--- /dev/null
+++ b/superset/engines/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/superset/engines/manager.py b/superset/engines/manager.py
new file mode 100644
index 00000000000..04ebe48b0b7
--- /dev/null
+++ b/superset/engines/manager.py
@@ -0,0 +1,403 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import enum
+import logging
+import threading
+from collections import defaultdict
+from contextlib import contextmanager
+from io import StringIO
+from typing import Any, TYPE_CHECKING
+
+from flask import current_app
+from paramiko import RSAKey
+from sqlalchemy import create_engine, event, pool
+from sqlalchemy.engine import Engine
+from sqlalchemy.engine.url import URL
+from sshtunnel import SSHTunnelForwarder
+
+from superset import is_feature_enabled
+from superset.databases.ssh_tunnel.models import SSHTunnel
+from superset.databases.utils import make_url_safe
+from superset.extensions import security_manager
+from superset.utils.core import get_query_source_from_request, get_user_id, QuerySource
+from superset.utils.json import dumps
+from superset.utils.oauth2 import check_for_oauth2, get_oauth2_access_token
+from superset.utils.ssh_tunnel import get_default_port
+
+if TYPE_CHECKING:
+ from superset.models.core import Database
+
+
+logger = logging.getLogger(__name__)
+
+
+EngineKey = str
+TunnelKey = str
+
+
+class EngineModes(enum.Enum):
+ # reuse existing engine if available, otherwise create a new one; this mode should
+ # have a connection pool configured in the database
+ SINGLETON = enum.auto()
+
+ # always create a new engine for every connection; this mode will use a NullPool
+ # and is the default behavior for Superset
+ NEW = enum.auto()
+
+
+class EngineManager:
+ """
+ A manager for SQLAlchemy engines.
+
+ This class handles the creation and management of SQLAlchemy engines, allowing them
+ to be configured with connection pools and reused across requests. The default mode
+ is the default behavior for Superset, where we create a new engine for every
+ connection, using a NullPool. The `SINGLETON` mode allows for reusing of the
+ engines, as well as configuring the pool through the database settings.
+ """
+
+ def __init__(self, mode: EngineModes = EngineModes.NEW) -> None:
+ self.mode = mode
+
+ self._engines: dict[EngineKey, Engine] = {}
+ self._engine_locks: dict[EngineKey, threading.Lock] = defaultdict(
+ threading.Lock
+ )
+
+ self._tunnels: dict[TunnelKey, SSHTunnelForwarder] = {}
+ self._tunnel_locks: dict[TunnelKey, threading.Lock] = defaultdict(
+ threading.Lock
+ )
+
+ @contextmanager
+ def get_engine(
+ self,
+ database: Database,
+ catalog: str | None,
+ schema: str | None,
+ source: QuerySource | None,
+ ) -> Engine:
+ """
+ Context manager to get a SQLAlchemy engine.
+ """
+ # users can wrap the engine in their own context manager for different
+ # reasons
+ customization = current_app.config["ENGINE_CONTEXT_MANAGER"]
+ with customization(database, catalog, schema):
+ # we need to check for errors indicating that OAuth2 is needed, and
+ # return the proper exception so it starts the authentication flow
+ with check_for_oauth2(database):
+ yield self._get_engine(database, catalog, schema, source)
+
+ def _get_engine(
+ self,
+ database: Database,
+ catalog: str | None,
+ schema: str | None,
+ source: QuerySource | None,
+ ) -> Engine:
+ """
+ Get a specific engine, or create it if none exists.
+ """
+ source = source or get_query_source_from_request()
+ user_id = get_user_id()
+
+ if self.mode == EngineModes.NEW:
+ return self._create_engine(
+ database,
+ catalog,
+ schema,
+ source,
+ user_id,
+ )
+
+ engine_key = self._get_engine_key(
+ database,
+ catalog,
+ schema,
+ source,
+ user_id,
+ )
+
+ if engine_key not in self._engines:
+ with self._engine_locks[engine_key]:
+ # double-checked locking to ensure thread safety and prevent unnecessary
+ # engine creation
+ if engine_key not in self._engines:
+ engine = self._create_engine(
+ database,
+ catalog,
+ schema,
+ source,
+ user_id,
+ )
+ self._engines[engine_key] = engine
+ self._add_disposal_listener(engine, engine_key)
+
+ return self._engines[engine_key]
+
+ def _get_engine_key(
+ self,
+ database: Database,
+ catalog: str | None,
+ schema: str | None,
+ source: QuerySource | None,
+ user_id: int | None,
+ ) -> EngineKey:
+ """
+ Generate a unique key for the engine based on the database and context.
+ """
+ uri, keys = self._get_engine_args(
+ database,
+ catalog,
+ schema,
+ source,
+ user_id,
+ )
+ keys["uri"] = uri
+ keys["source"] = source
+
+ return dumps(keys, sort_keys=True)
+
+ def _get_engine_args(
+ self,
+ database: Database,
+ catalog: str | None,
+ schema: str | None,
+ source: QuerySource | None,
+ user_id: int | None,
+ ) -> tuple[URL, dict[str, Any]]:
+ """
+ Build the almost final SQLAlchemy URI and engine kwargs.
+
+ "Almost" final because we may still need to mutate the URI if an SSH tunnel is
+ needed, since it needs to connect to the tunnel instead of the original DB. But
+ that information is only available after the tunnel is created.
+ """
+ uri = make_url_safe(database.sqlalchemy_uri_decrypted)
+
+ extra = database.get_extra(source)
+ kwargs = extra.get("engine_params", {})
+
+ # get pool class
+ if self.mode == EngineModes.NEW or "poolclass" not in extra:
+ kwargs["poolclass"] = pool.NullPool
+ else:
+ pools = {
+ "queue": pool.QueuePool,
+ "singleton": pool.SingletonThreadPool,
+ "assertion": pool.AssertionPool,
+ "null": pool.NullPool,
+ "static": pool.StaticPool,
+ }
+ kwargs["poolclass"] = pools.get(extra["poolclass"], pool.QueuePool)
+
+ # update URI for specific catalog/schema
+ connect_args = extra.setdefault("connect_args", {})
+ uri, connect_args = database.db_engine_spec.adjust_engine_params(
+ uri,
+ connect_args,
+ catalog,
+ schema,
+ )
+
+ # get effective username
+ username = database.get_effective_user(uri)
+ if username and is_feature_enabled("IMPERSONATE_WITH_EMAIL_PREFIX"):
+ user = security_manager.find_user(username=username)
+ if user and user.email and "@" in user.email:
+ username = user.email.split("@")[0]
+
+ # update URI/kwargs for user impersonation
+ if database.impersonate_user:
+ oauth2_config = database.get_oauth2_config()
+ access_token = (
+ get_oauth2_access_token(
+ oauth2_config,
+ database.id,
+ user_id,
+ database.db_engine_spec,
+ )
+ if oauth2_config and user_id
+ else None
+ )
+
+ uri, kwargs = database.db_engine_spec.impersonate_user(
+ database,
+ username,
+ access_token,
+ uri,
+ kwargs,
+ )
+
+ # update kwargs from params stored encrupted at rest
+ database.update_params_from_encrypted_extra(kwargs)
+
+ # mutate URI
+ if mutator := current_app.config["DB_CONNECTION_MUTATOR"]:
+ source = source or get_query_source_from_request()
+ uri, kwargs = mutator(
+ uri,
+ kwargs,
+ username,
+ security_manager,
+ source,
+ )
+
+ # validate final URI
+ database.db_engine_spec.validate_database_uri(uri)
+
+ return uri, kwargs
+
+ def _create_engine(
+ self,
+ database: Database,
+ catalog: str | None,
+ schema: str | None,
+ source: QuerySource | None,
+ user_id: int | None,
+ ) -> Engine:
+ """
+ Create the actual engine.
+
+ This should be the only place in Superset where a SQLAlchemy engine is created,
+ """
+ uri, kwargs = self._get_engine_args(
+ database,
+ catalog,
+ schema,
+ source,
+ user_id,
+ )
+
+ tunnel = None
+ if database.ssh_tunnel:
+ tunnel = self._get_tunnel(database.ssh_tunnel, uri)
+ uri = uri.set(
+ host=tunnel.local_bind_address[0],
+ port=tunnel.local_bind_port,
+ )
+
+ try:
+ engine = create_engine(uri, **kwargs)
+ except Exception as ex:
+ raise database.db_engine_spec.get_dbapi_mapped_exception(ex) from ex
+
+ return engine
+
+ def _get_tunnel(self, ssh_tunnel: SSHTunnel, uri: URL) -> SSHTunnelForwarder:
+ tunnel_key = self._get_tunnel_key(ssh_tunnel, uri)
+
+ # tunnel exists and is healthy
+ if tunnel_key in self._tunnels:
+ tunnel = self._tunnels[tunnel_key]
+ if tunnel.is_active:
+ return tunnel
+
+ # create or recreate tunnel
+ with self._tunnel_locks[tunnel_key]:
+ existing_tunnel = self._tunnels.get(tunnel_key)
+ if existing_tunnel and existing_tunnel.is_active:
+ return existing_tunnel
+
+ # replace inactive or missing tunnel
+ return self._replace_tunnel(tunnel_key, ssh_tunnel, uri, existing_tunnel)
+
+ def _replace_tunnel(
+ self,
+ tunnel_key: str,
+ ssh_tunnel: SSHTunnel,
+ uri: URL,
+ old_tunnel: SSHTunnelForwarder | None,
+ ) -> SSHTunnelForwarder:
+ """
+ Replace tunnel with proper cleanup.
+
+ This function assumes caller holds lock.
+ """
+ if old_tunnel:
+ try:
+ old_tunnel.stop()
+ except Exception:
+ logger.exception("Error stopping old tunnel")
+
+ try:
+ new_tunnel = self._create_tunnel(ssh_tunnel, uri)
+ self._tunnels[tunnel_key] = new_tunnel
+ except Exception:
+ # Remove failed tunnel from cache
+ self._tunnels.pop(tunnel_key, None)
+ logger.exception("Failed to create tunnel")
+ raise
+
+ return new_tunnel
+
+ def _get_tunnel_key(self, ssh_tunnel: SSHTunnel, uri: URL) -> TunnelKey:
+ """
+ Build a unique key for the SSH tunnel.
+ """
+ keys = self._get_tunnel_kwargs(ssh_tunnel, uri)
+
+ return dumps(keys, sort_keys=True)
+
+ def _create_tunnel(self, ssh_tunnel: SSHTunnel, uri: URL) -> SSHTunnelForwarder:
+ kwargs = self._get_tunnel_kwargs(ssh_tunnel, uri)
+ tunnel = SSHTunnelForwarder(**kwargs)
+ tunnel.start()
+
+ return tunnel
+
+ def _get_tunnel_kwargs(self, ssh_tunnel: SSHTunnel, uri: URL) -> dict[str, Any]:
+ backend = uri.get_backend_name()
+ kwargs = {
+ "ssh_address_or_host": (ssh_tunnel.server_address, ssh_tunnel.server_port),
+ "ssh_username": ssh_tunnel.username,
+ "remote_bind_address": (uri.host, uri.port or get_default_port(backend)),
+ "local_bind_address": (ssh_tunnel.local_bind_address,),
+ "debug_level": logging.getLogger("flask_appbuilder").level,
+ }
+
+ if ssh_tunnel.password:
+ kwargs["ssh_password"] = ssh_tunnel.password
+ elif ssh_tunnel.private_key:
+ private_key_file = StringIO(ssh_tunnel.private_key)
+ private_key = RSAKey.from_private_key(
+ private_key_file,
+ ssh_tunnel.private_key_password,
+ )
+ kwargs["ssh_pkey"] = private_key
+
+ if self.mode == EngineModes.NEW:
+ kwargs["keepalive"] = 0 # disable
+
+ return kwargs
+
+ def _add_disposal_listener(self, engine: Engine, engine_key: EngineKey) -> None:
+ @event.listens_for(engine, "engine_disposed")
+ def on_engine_disposed(engine_instance: Engine) -> None:
+ try:
+ # `pop` is atomic -- no lock needed
+ if self._engines.pop(engine_key, None):
+ logger.info(f"Engine disposed and removed from cache: {engine_key}")
+ self._engine_locks.pop(engine_key, None)
+ except Exception as ex:
+ logger.error(
+ "Error during engine disposal cleanup for %s: %s",
+ engine_key,
+ str(ex),
+ )
(superset) 08/13: Update existing tests
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
commit de8c250f86d67a008dfec9d8d3fa72e7e1cd261e
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Wed Dec 3 20:00:29 2025 -0500
Update existing tests
---
superset/extensions/engine_manager.py | 6 -
tests/integration_tests/conftest.py | 1 -
.../integration_tests/databases/commands_tests.py | 22 +-
tests/unit_tests/engines/manager_test.py | 232 +++++++++++++++++++++
tests/unit_tests/initialization_test.py | 2 +-
tests/unit_tests/models/core_test.py | 167 ---------------
6 files changed, 244 insertions(+), 186 deletions(-)
diff --git a/superset/extensions/engine_manager.py b/superset/extensions/engine_manager.py
index df391ad5d8e..e15ead09b43 100644
--- a/superset/extensions/engine_manager.py
+++ b/superset/extensions/engine_manager.py
@@ -70,12 +70,6 @@ class EngineManagerExtension:
def shutdown_engine_manager() -> None:
if self.engine_manager:
self.engine_manager.stop_cleanup_thread()
- # Use a try-except to handle closed log file handlers during tests
- try:
- logger.info("Stopped EngineManager cleanup thread")
- except ValueError:
- # Ignore logging errors during test shutdown when file handles are closed
- pass
app.teardown_appcontext_funcs.append(lambda exc: None)
diff --git a/tests/integration_tests/conftest.py b/tests/integration_tests/conftest.py
index 4f6ce10b0f9..95f1015e85f 100644
--- a/tests/integration_tests/conftest.py
+++ b/tests/integration_tests/conftest.py
@@ -170,7 +170,6 @@ def example_db_provider() -> Callable[[], Database]:
return self._db
def _load_lazy_data_to_decouple_from_session(self) -> None:
- self._db._get_sqla_engine() # type: ignore
self._db.backend # type: ignore # noqa: B018
def remove(self) -> None:
diff --git a/tests/integration_tests/databases/commands_tests.py b/tests/integration_tests/databases/commands_tests.py
index 27c1ce56542..2d43ee14d0f 100644
--- a/tests/integration_tests/databases/commands_tests.py
+++ b/tests/integration_tests/databases/commands_tests.py
@@ -897,7 +897,7 @@ class TestImportDatabasesCommand(SupersetTestCase):
class TestTestConnectionDatabaseCommand(SupersetTestCase):
- @patch("superset.models.core.Database._get_sqla_engine")
+ @patch("superset.models.core.Database.get_sqla_engine")
@patch("superset.commands.database.test_connection.event_logger.log_with_context")
@patch("superset.utils.core.g")
def test_connection_db_exception(
@@ -906,19 +906,19 @@ class TestTestConnectionDatabaseCommand(SupersetTestCase):
"""Test to make sure event_logger is called when an exception is raised"""
database = get_example_database()
mock_g.user = security_manager.find_user("admin")
- mock_get_sqla_engine.side_effect = Exception("An error has occurred!")
+ mock_get_sqla_engine.__enter__.side_effect = Exception("An error has occurred!")
db_uri = database.sqlalchemy_uri_decrypted
json_payload = {"sqlalchemy_uri": db_uri}
command_without_db_name = TestConnectionDatabaseCommand(json_payload)
with pytest.raises(DatabaseTestConnectionUnexpectedError) as excinfo: # noqa: PT012
command_without_db_name.run()
- assert str(excinfo.value) == (
- "Unexpected error occurred, please check your logs for details"
- )
+ assert str(excinfo.value) == (
+ "Unexpected error occurred, please check your logs for details"
+ )
mock_event_logger.assert_called()
- @patch("superset.models.core.Database._get_sqla_engine")
+ @patch("superset.models.core.Database.get_sqla_engine")
@patch("superset.commands.database.test_connection.event_logger.log_with_context")
@patch("superset.utils.core.g")
def test_connection_do_ping_exception(
@@ -927,7 +927,7 @@ class TestTestConnectionDatabaseCommand(SupersetTestCase):
"""Test to make sure do_ping exceptions gets captured"""
database = get_example_database()
mock_g.user = security_manager.find_user("admin")
- mock_get_sqla_engine.return_value.dialect.do_ping.side_effect = Exception(
+ mock_get_sqla_engine.__enter__().dialect.do_ping.side_effect = Exception(
"An error has occurred!"
)
db_uri = database.sqlalchemy_uri_decrypted
@@ -967,7 +967,7 @@ class TestTestConnectionDatabaseCommand(SupersetTestCase):
== SupersetErrorType.CONNECTION_DATABASE_TIMEOUT
)
- @patch("superset.models.core.Database._get_sqla_engine")
+ @patch("superset.models.core.Database.get_sqla_engine")
@patch("superset.commands.database.test_connection.event_logger.log_with_context")
@patch("superset.utils.core.g")
def test_connection_superset_security_connection(
@@ -977,7 +977,7 @@ class TestTestConnectionDatabaseCommand(SupersetTestCase):
connection exc is raised"""
database = get_example_database()
mock_g.user = security_manager.find_user("admin")
- mock_get_sqla_engine.side_effect = SupersetSecurityException(
+ mock_get_sqla_engine.__enter__.side_effect = SupersetSecurityException(
SupersetError(error_type=500, message="test", level="info")
)
db_uri = database.sqlalchemy_uri_decrypted
@@ -990,7 +990,7 @@ class TestTestConnectionDatabaseCommand(SupersetTestCase):
mock_event_logger.assert_called()
- @patch("superset.models.core.Database._get_sqla_engine")
+ @patch("superset.models.core.Database.get_sqla_engine")
@patch("superset.commands.database.test_connection.event_logger.log_with_context")
@patch("superset.utils.core.g")
def test_connection_db_api_exc(
@@ -999,7 +999,7 @@ class TestTestConnectionDatabaseCommand(SupersetTestCase):
"""Test to make sure event_logger is called when DBAPIError is raised"""
database = get_example_database()
mock_g.user = security_manager.find_user("admin")
- mock_get_sqla_engine.side_effect = DBAPIError(
+ mock_get_sqla_engine.__enter__.side_effect = DBAPIError(
statement="error", params={}, orig={}
)
db_uri = database.sqlalchemy_uri_decrypted
diff --git a/tests/unit_tests/engines/manager_test.py b/tests/unit_tests/engines/manager_test.py
index 287820eaf1b..871624f2a42 100644
--- a/tests/unit_tests/engines/manager_test.py
+++ b/tests/unit_tests/engines/manager_test.py
@@ -96,7 +96,9 @@ class TestEngineManager:
@pytest.fixture
def engine_manager(self):
"""Create a mock EngineManager instance."""
+ from contextlib import contextmanager
+ @contextmanager
def dummy_context_manager(
database: MagicMock, catalog: str | None, schema: str | None
) -> Iterator[None]:
@@ -293,3 +295,233 @@ class TestEngineManager:
result2 = engine_manager._get_tunnel(ssh_tunnel, uri)
assert result2 is active_tunnel
assert mock_tunnel_class.call_count == 2
+
+ @patch("superset.engines.manager.create_engine")
+ @patch("superset.engines.manager.make_url_safe")
+ def test_get_engine_args_basic(
+ self, mock_make_url, mock_create_engine, engine_manager
+ ):
+ """Test _get_engine_args returns correct URI and kwargs."""
+ from sqlalchemy.engine.url import make_url
+
+ from superset.engines.manager import EngineModes
+
+ engine_manager.mode = EngineModes.NEW
+
+ mock_uri = make_url("trino://")
+ mock_make_url.return_value = mock_uri
+
+ database = MagicMock()
+ database.id = 1
+ database.sqlalchemy_uri_decrypted = "trino://"
+ database.get_extra.return_value = {
+ "engine_params": {},
+ "connect_args": {"source": "Apache Superset"},
+ }
+ database.get_effective_user.return_value = "alice"
+ database.impersonate_user = False
+ database.update_params_from_encrypted_extra = MagicMock()
+ database.db_engine_spec = MagicMock()
+ database.db_engine_spec.adjust_engine_params.return_value = (
+ mock_uri,
+ {"source": "Apache Superset"},
+ )
+ database.db_engine_spec.validate_database_uri = MagicMock()
+
+ uri, kwargs = engine_manager._get_engine_args(database, None, None, None, None)
+
+ assert str(uri) == "trino://"
+ assert "connect_args" in database.get_extra.return_value
+
+ @patch("superset.engines.manager.create_engine")
+ @patch("superset.engines.manager.make_url_safe")
+ def test_get_engine_args_user_impersonation(
+ self, mock_make_url, mock_create_engine, engine_manager
+ ):
+ """Test user impersonation in _get_engine_args."""
+ from sqlalchemy.engine.url import make_url
+
+ from superset.engines.manager import EngineModes
+
+ engine_manager.mode = EngineModes.NEW
+
+ mock_uri = make_url("trino://")
+ mock_make_url.return_value = mock_uri
+
+ database = MagicMock()
+ database.id = 1
+ database.sqlalchemy_uri_decrypted = "trino://"
+ database.get_extra.return_value = {
+ "engine_params": {},
+ "connect_args": {"source": "Apache Superset"},
+ }
+ database.get_effective_user.return_value = "alice"
+ database.impersonate_user = True
+ database.get_oauth2_config.return_value = None
+ database.update_params_from_encrypted_extra = MagicMock()
+ database.db_engine_spec = MagicMock()
+ database.db_engine_spec.adjust_engine_params.return_value = (
+ mock_uri,
+ {"source": "Apache Superset"},
+ )
+ database.db_engine_spec.impersonate_user.return_value = (
+ mock_uri,
+ {"connect_args": {"user": "alice", "source": "Apache Superset"}},
+ )
+ database.db_engine_spec.validate_database_uri = MagicMock()
+
+ uri, kwargs = engine_manager._get_engine_args(database, None, None, None, None)
+
+ # Verify impersonate_user was called
+ database.db_engine_spec.impersonate_user.assert_called_once()
+ call_args = database.db_engine_spec.impersonate_user.call_args
+ assert call_args[0][0] is database # database
+ assert call_args[0][1] == "alice" # username
+ assert call_args[0][2] is None # access_token (no OAuth2)
+
+ @patch("superset.engines.manager.create_engine")
+ @patch("superset.engines.manager.make_url_safe")
+ def test_get_engine_args_user_impersonation_email_prefix(
+ self,
+ mock_make_url,
+ mock_create_engine,
+ engine_manager,
+ ):
+ """Test user impersonation with IMPERSONATE_WITH_EMAIL_PREFIX feature flag."""
+ from sqlalchemy.engine.url import make_url
+
+ from superset.engines.manager import EngineModes
+
+ engine_manager.mode = EngineModes.NEW
+
+ mock_uri = make_url("trino://")
+ mock_make_url.return_value = mock_uri
+
+ # Mock user with email
+ mock_user = MagicMock()
+ mock_user.email = "alice.doe@example.org"
+
+ database = MagicMock()
+ database.id = 1
+ database.sqlalchemy_uri_decrypted = "trino://"
+ database.get_extra.return_value = {
+ "engine_params": {},
+ "connect_args": {"source": "Apache Superset"},
+ }
+ database.get_effective_user.return_value = "alice"
+ database.impersonate_user = True
+ database.get_oauth2_config.return_value = None
+ database.update_params_from_encrypted_extra = MagicMock()
+ database.db_engine_spec = MagicMock()
+ database.db_engine_spec.adjust_engine_params.return_value = (
+ mock_uri,
+ {"source": "Apache Superset"},
+ )
+ database.db_engine_spec.impersonate_user.return_value = (
+ mock_uri,
+ {"connect_args": {"user": "alice.doe", "source": "Apache Superset"}},
+ )
+ database.db_engine_spec.validate_database_uri = MagicMock()
+
+ with (
+ patch(
+ "superset.utils.feature_flag_manager.FeatureFlagManager.is_feature_enabled",
+ return_value=True,
+ ),
+ patch(
+ "superset.extensions.security_manager.find_user",
+ return_value=mock_user,
+ ),
+ ):
+ uri, kwargs = engine_manager._get_engine_args(
+ database, None, None, None, None
+ )
+
+ # Verify impersonate_user was called with the email prefix
+ database.db_engine_spec.impersonate_user.assert_called_once()
+ call_args = database.db_engine_spec.impersonate_user.call_args
+ assert call_args[0][1] == "alice.doe" # username from email prefix
+
+ @patch("superset.engines.manager.create_engine")
+ @patch("superset.engines.manager.make_url_safe")
+ def test_engine_context_manager_called(
+ self, mock_make_url, mock_create_engine, engine_manager, mock_database
+ ):
+ """Test that the engine context manager is properly called."""
+ from sqlalchemy.engine.url import make_url
+
+ mock_uri = make_url("trino://")
+ mock_make_url.return_value = mock_uri
+ mock_engine = MagicMock()
+ mock_create_engine.return_value = mock_engine
+
+ # Track context manager calls
+ context_manager_calls = []
+
+ def tracking_context_manager(database, catalog, schema):
+ from contextlib import contextmanager
+
+ @contextmanager
+ def inner():
+ context_manager_calls.append(("enter", database, catalog, schema))
+ yield
+ context_manager_calls.append(("exit", database, catalog, schema))
+
+ return inner()
+
+ engine_manager.engine_context_manager = tracking_context_manager
+
+ with engine_manager.get_engine(mock_database, "catalog1", "schema1", None):
+ pass
+
+ assert len(context_manager_calls) == 2
+ assert context_manager_calls[0][0] == "enter"
+ assert context_manager_calls[0][1] is mock_database
+ assert context_manager_calls[0][2] == "catalog1"
+ assert context_manager_calls[0][3] == "schema1"
+ assert context_manager_calls[1][0] == "exit"
+
+ @patch("superset.utils.oauth2.check_for_oauth2")
+ @patch("superset.engines.manager.create_engine")
+ @patch("superset.engines.manager.make_url_safe")
+ def test_engine_oauth2_error_handling(
+ self,
+ mock_make_url,
+ mock_create_engine,
+ mock_check_for_oauth2,
+ engine_manager,
+ mock_database,
+ ):
+ """Test that OAuth2 errors are properly propagated from get_engine."""
+ from contextlib import contextmanager
+
+ from sqlalchemy.engine.url import make_url
+
+ mock_uri = make_url("trino://")
+ mock_make_url.return_value = mock_uri
+
+ # Simulate OAuth2 error during engine creation
+ class OAuth2TestError(Exception):
+ pass
+
+ oauth_error = OAuth2TestError("OAuth2 required")
+ mock_create_engine.side_effect = oauth_error
+
+ # Make get_dbapi_mapped_exception return the original exception
+ mock_database.db_engine_spec.get_dbapi_mapped_exception.return_value = (
+ oauth_error
+ )
+
+ # Mock check_for_oauth2 to re-raise the exception
+ @contextmanager
+ def mock_oauth2_context(database):
+ try:
+ yield
+ except OAuth2TestError:
+ raise
+
+ mock_check_for_oauth2.return_value = mock_oauth2_context(mock_database)
+
+ with pytest.raises(OAuth2TestError, match="OAuth2 required"):
+ with engine_manager.get_engine(mock_database, "catalog1", "schema1", None):
+ pass
diff --git a/tests/unit_tests/initialization_test.py b/tests/unit_tests/initialization_test.py
index 01fde0967c9..93fdf4d352e 100644
--- a/tests/unit_tests/initialization_test.py
+++ b/tests/unit_tests/initialization_test.py
@@ -123,7 +123,7 @@ class TestSupersetAppInitializer:
patch.object(app_initializer, "configure_data_sources"),
patch.object(app_initializer, "configure_auth_provider"),
patch.object(app_initializer, "configure_async_queries"),
- patch.object(app_initializer, "configure_ssh_manager"),
+ patch.object(app_initializer, "configure_engine_manager"),
patch.object(app_initializer, "configure_stats_manager"),
patch.object(app_initializer, "init_views"),
):
diff --git a/tests/unit_tests/models/core_test.py b/tests/unit_tests/models/core_test.py
index 7d7aa96ea19..b2a48df0592 100644
--- a/tests/unit_tests/models/core_test.py
+++ b/tests/unit_tests/models/core_test.py
@@ -19,7 +19,6 @@
from datetime import datetime
import pytest
-from flask import current_app
from pytest_mock import MockerFixture
from sqlalchemy import (
Column,
@@ -29,7 +28,6 @@ from sqlalchemy import (
Table as SqlalchemyTable,
)
from sqlalchemy.engine.reflection import Inspector
-from sqlalchemy.engine.url import make_url
from sqlalchemy.orm.session import Session
from sqlalchemy.sql import Select
@@ -525,60 +523,6 @@ def test_get_all_materialized_view_names_in_schema_needs_oauth2(
assert excinfo.value.error.error_type == SupersetErrorType.OAUTH2_REDIRECT
-def test_get_sqla_engine(mocker: MockerFixture) -> None:
- """
- Test `_get_sqla_engine`.
- """
- from superset.models.core import Database
-
- user = mocker.MagicMock()
- user.email = "alice.doe@example.org"
- mocker.patch(
- "superset.models.core.security_manager.find_user",
- return_value=user,
- )
- mocker.patch("superset.models.core.get_username", return_value="alice")
-
- create_engine = mocker.patch("superset.models.core.create_engine")
-
- database = Database(database_name="my_db", sqlalchemy_uri="trino://")
- database._get_sqla_engine(nullpool=False)
-
- create_engine.assert_called_with(
- make_url("trino:///"),
- connect_args={"source": "Apache Superset"},
- )
-
-
-def test_get_sqla_engine_user_impersonation(mocker: MockerFixture) -> None:
- """
- Test user impersonation in `_get_sqla_engine`.
- """
- from superset.models.core import Database
-
- user = mocker.MagicMock()
- user.email = "alice.doe@example.org"
- mocker.patch(
- "superset.models.core.security_manager.find_user",
- return_value=user,
- )
- mocker.patch("superset.models.core.get_username", return_value="alice")
-
- create_engine = mocker.patch("superset.models.core.create_engine")
-
- database = Database(
- database_name="my_db",
- sqlalchemy_uri="trino://",
- impersonate_user=True,
- )
- database._get_sqla_engine(nullpool=False)
-
- create_engine.assert_called_with(
- make_url("trino:///"),
- connect_args={"user": "alice", "source": "Apache Superset"},
- )
-
-
def test_add_database_to_signature():
args = ["param1", "param2"]
@@ -604,36 +548,6 @@ def test_add_database_to_signature():
assert args3 == ["param1", "param2", database]
-@with_feature_flags(IMPERSONATE_WITH_EMAIL_PREFIX=True)
-def test_get_sqla_engine_user_impersonation_email(mocker: MockerFixture) -> None:
- """
- Test user impersonation in `_get_sqla_engine` with `username_from_email`.
- """
- from superset.models.core import Database
-
- user = mocker.MagicMock()
- user.email = "alice.doe@example.org"
- mocker.patch(
- "superset.models.core.security_manager.find_user",
- return_value=user,
- )
- mocker.patch("superset.models.core.get_username", return_value="alice")
-
- create_engine = mocker.patch("superset.models.core.create_engine")
-
- database = Database(
- database_name="my_db",
- sqlalchemy_uri="trino://",
- impersonate_user=True,
- )
- database._get_sqla_engine(nullpool=False)
-
- create_engine.assert_called_with(
- make_url("trino:///"),
- connect_args={"user": "alice.doe", "source": "Apache Superset"},
- )
-
-
def test_is_oauth2_enabled() -> None:
"""
Test the `is_oauth2_enabled` method.
@@ -753,37 +667,6 @@ def test_get_oauth2_config_redirect_uri_from_config(
assert config["redirect_uri"] == custom_redirect_uri
-def test_raw_connection_oauth_engine(mocker: MockerFixture) -> None:
- """
- Test that we can start OAuth2 from `raw_connection()` errors.
-
- With OAuth2, some databases will raise an exception when the engine is first created
- (eg, BigQuery). Others, like, Snowflake, when the connection is created. And
- finally, GSheets will raise an exception when the query is executed.
-
- This tests verifies that when calling `raw_connection()` the OAuth2 flow is
- triggered when the engine is created.
- """
- g = mocker.patch("superset.db_engine_specs.base.g")
- g.user = mocker.MagicMock()
- g.user.id = 42
-
- database = Database(
- id=1,
- database_name="my_db",
- sqlalchemy_uri="sqlite://",
- encrypted_extra=json.dumps(oauth2_client_info),
- )
- database.db_engine_spec.oauth2_exception = OAuth2Error # type: ignore
- _get_sqla_engine = mocker.patch.object(database, "_get_sqla_engine")
- _get_sqla_engine.side_effect = OAuth2Error("OAuth2 required")
-
- with pytest.raises(OAuth2RedirectError) as excinfo:
- with database.get_raw_connection() as conn:
- conn.cursor()
- assert str(excinfo.value) == "You don't have permission to access the data."
-
-
def test_raw_connection_oauth_connection(mocker: MockerFixture) -> None:
"""
Test that we can start OAuth2 from `raw_connection()` errors.
@@ -879,56 +762,6 @@ def test_get_schema_access_for_file_upload() -> None:
assert database.get_schema_access_for_file_upload() == {"public"}
-def test_engine_context_manager(mocker: MockerFixture, app_context: None) -> None:
- """
- Test the engine context manager.
- """
- from unittest.mock import MagicMock
-
- engine_context_manager = MagicMock()
- mocker.patch.dict(
- current_app.config,
- {"ENGINE_CONTEXT_MANAGER": engine_context_manager},
- )
- _get_sqla_engine = mocker.patch.object(Database, "_get_sqla_engine")
-
- database = Database(database_name="my_db", sqlalchemy_uri="trino://")
- with database.get_sqla_engine("catalog", "schema"):
- pass
-
- engine_context_manager.assert_called_once_with(database, "catalog", "schema")
- engine_context_manager().__enter__.assert_called_once()
- engine_context_manager().__exit__.assert_called_once_with(None, None, None)
- _get_sqla_engine.assert_called_once_with(
- catalog="catalog",
- schema="schema",
- nullpool=True,
- source=None,
- sqlalchemy_uri="trino://",
- )
-
-
-def test_engine_oauth2(mocker: MockerFixture) -> None:
- """
- Test that we handle OAuth2 when `create_engine` fails.
- """
- database = Database(database_name="my_db", sqlalchemy_uri="trino://")
- mocker.patch.object(database, "_get_sqla_engine", side_effect=Exception)
- mocker.patch.object(database, "is_oauth2_enabled", return_value=True)
- mocker.patch.object(database.db_engine_spec, "needs_oauth2", return_value=True)
- start_oauth2_dance = mocker.patch.object(
- database.db_engine_spec,
- "start_oauth2_dance",
- side_effect=OAuth2Error("OAuth2 required"),
- )
-
- with pytest.raises(OAuth2Error):
- with database.get_sqla_engine("catalog", "schema"):
- pass
-
- start_oauth2_dance.assert_called_with(database)
-
-
def test_purge_oauth2_tokens(session: Session) -> None:
"""
Test the `purge_oauth2_tokens` method.
(superset) 04/13: Connecting
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 929b0337f483b93af8ac94d2d41969c420e29960
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Tue Jul 29 20:28:33 2025 -0400
Connecting
---
superset/engines/manager.py | 7 ++-
superset/models/core.py | 145 +++++++-------------------------------------
2 files changed, 25 insertions(+), 127 deletions(-)
diff --git a/superset/engines/manager.py b/superset/engines/manager.py
index 9f2b41caad6..37be4641cb7 100644
--- a/superset/engines/manager.py
+++ b/superset/engines/manager.py
@@ -62,9 +62,10 @@ class EngineManager:
This class handles the creation and management of SQLAlchemy engines, allowing them
to be configured with connection pools and reused across requests. The default mode
- is the default behavior for Superset, where we create a new engine for every
- connection, using a NullPool. The `SINGLETON` mode allows for reusing of the
- engines, as well as configuring the pool through the database settings.
+ is the original behavior for Superset, where we create a new engine for every
+ connection, using a NullPool. The `SINGLETON` mode, on the other hand, allows for
+ reusing of the engines, as well as configuring the pool through the database
+ settings.
"""
def __init__(
diff --git a/superset/models/core.py b/superset/models/core.py
index b9b7b605911..18a44e66cd5 100755
--- a/superset/models/core.py
+++ b/superset/models/core.py
@@ -25,7 +25,7 @@ import builtins
import logging
import textwrap
from ast import literal_eval
-from contextlib import closing, contextmanager, nullcontext, suppress
+from contextlib import closing, contextmanager, suppress
from copy import deepcopy
from datetime import datetime
from functools import lru_cache
@@ -35,14 +35,12 @@ from typing import Any, Callable, cast, Optional, TYPE_CHECKING
import numpy
import pandas as pd
import sqlalchemy as sqla
-import sshtunnel
from flask import current_app as app, g, has_app_context
from flask_appbuilder import Model
from marshmallow.exceptions import ValidationError
from sqlalchemy import (
Boolean,
Column,
- create_engine,
DateTime,
ForeignKey,
Integer,
@@ -57,7 +55,6 @@ from sqlalchemy.engine.url import URL
from sqlalchemy.exc import NoSuchModuleError
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship
-from sqlalchemy.pool import NullPool
from sqlalchemy.schema import UniqueConstraint
from sqlalchemy.sql import ColumnElement, expression, Select
from superset_core.api.models import Database as CoreDatabase
@@ -72,7 +69,6 @@ from superset.extensions import (
encrypted_field_factory,
event_logger,
security_manager,
- ssh_manager_factory,
)
from superset.models.helpers import AuditMixinNullable, ImportExportMixin, UUIDMixin
from superset.result_set import SupersetResultSet
@@ -84,10 +80,9 @@ from superset.superset_typing import (
)
from superset.utils import cache as cache_util, core as utils, json
from superset.utils.backports import StrEnum
-from superset.utils.core import get_query_source_from_request, get_username
+from superset.utils.core import get_username
from superset.utils.oauth2 import (
check_for_oauth2,
- get_oauth2_access_token,
OAuth2ClientConfigSchema,
)
@@ -141,7 +136,9 @@ class ConfigurationMethod(StrEnum):
DYNAMIC_FORM = "dynamic_form"
-class Database(CoreDatabase, AuditMixinNullable, ImportExportMixin): # pylint: disable=too-many-public-methods
+class Database(
+ CoreDatabase, AuditMixinNullable, ImportExportMixin
+): # pylint: disable=too-many-public-methods
"""An ORM object that stores Database related information"""
__tablename__ = "dbs"
@@ -418,137 +415,38 @@ class Database(CoreDatabase, AuditMixinNullable, ImportExportMixin): # pylint:
return (
username
if (username := get_username())
- else object_url.username
- if self.impersonate_user
- else None
+ else object_url.username if self.impersonate_user else None
)
@contextmanager
- def get_sqla_engine( # pylint: disable=too-many-arguments
+ def get_sqla_engine(
self,
catalog: str | None = None,
schema: str | None = None,
- nullpool: bool = True,
source: utils.QuerySource | None = None,
) -> Engine:
"""
Context manager for a SQLAlchemy engine.
- This method will return a context manager for a SQLAlchemy engine. Using the
- context manager (as opposed to the engine directly) is important because we need
- to potentially establish SSH tunnels before the connection is created, and clean
- them up once the engine is no longer used.
- """
-
- sqlalchemy_uri = self.sqlalchemy_uri_decrypted
-
- ssh_context_manager = (
- ssh_manager_factory.instance.create_tunnel(
- ssh_tunnel=self.ssh_tunnel,
- sqlalchemy_database_uri=sqlalchemy_uri,
- )
- if self.ssh_tunnel
- else nullcontext()
- )
-
- with ssh_context_manager as ssh_context:
- if ssh_context:
- logger.info(
- "[SSH] Successfully created tunnel w/ %s tunnel_timeout + %s "
- "ssh_timeout at %s",
- sshtunnel.TUNNEL_TIMEOUT,
- sshtunnel.SSH_TIMEOUT,
- ssh_context.local_bind_address,
- )
- sqlalchemy_uri = ssh_manager_factory.instance.build_sqla_url(
- sqlalchemy_uri,
- ssh_context,
- )
+ This method will return a context manager for a SQLAlchemy engine. The engine
+ manager handles connection pooling, SSH tunnels, and other connection details
+ based on the configured mode (NEW or SINGLETON).
- engine_context_manager = app.config["ENGINE_CONTEXT_MANAGER"]
- with engine_context_manager(self, catalog, schema):
- with check_for_oauth2(self):
- yield self._get_sqla_engine(
- catalog=catalog,
- schema=schema,
- nullpool=nullpool,
- source=source,
- sqlalchemy_uri=sqlalchemy_uri,
- )
+ Note: The nullpool parameter is kept for backwards compatibility but is ignored.
+ Pool configuration is now read from the database's extra configuration.
+ """
+ # Import here to avoid circular imports
+ from superset.extensions import engine_manager_extension
- def _get_sqla_engine( # pylint: disable=too-many-locals # noqa: C901
- self,
- catalog: str | None = None,
- schema: str | None = None,
- nullpool: bool = True,
- source: utils.QuerySource | None = None,
- sqlalchemy_uri: str | None = None,
- ) -> Engine:
- sqlalchemy_url = make_url_safe(
- sqlalchemy_uri if sqlalchemy_uri else self.sqlalchemy_uri_decrypted
- )
- self.db_engine_spec.validate_database_uri(sqlalchemy_url)
-
- extra = self.get_extra(source)
- engine_kwargs = extra.get("engine_params", {})
- if nullpool:
- engine_kwargs["poolclass"] = NullPool
- connect_args = engine_kwargs.setdefault("connect_args", {})
-
- # modify URL/args for a specific catalog/schema
- sqlalchemy_url, connect_args = self.db_engine_spec.adjust_engine_params(
- uri=sqlalchemy_url,
- connect_args=connect_args,
+ # Use the engine manager to get the engine
+ engine_manager = engine_manager_extension.manager
+ return engine_manager.get_engine(
+ database=self,
catalog=catalog,
schema=schema,
+ source=source,
)
- effective_username = self.get_effective_user(sqlalchemy_url)
- if effective_username and is_feature_enabled("IMPERSONATE_WITH_EMAIL_PREFIX"):
- user = security_manager.find_user(username=effective_username)
- if user and user.email:
- effective_username = user.email.split("@")[0]
-
- oauth2_config = self.get_oauth2_config()
- access_token = (
- get_oauth2_access_token(
- oauth2_config,
- self.id,
- g.user.id,
- self.db_engine_spec,
- )
- if oauth2_config and hasattr(g, "user") and hasattr(g.user, "id")
- else None
- )
- masked_url = self.get_password_masked_url(sqlalchemy_url)
- logger.debug("Database._get_sqla_engine(). Masked URL: %s", str(masked_url))
-
- if self.impersonate_user:
- sqlalchemy_url, engine_kwargs = self.db_engine_spec.impersonate_user(
- self,
- effective_username,
- access_token,
- sqlalchemy_url,
- engine_kwargs,
- )
-
- self.update_params_from_encrypted_extra(engine_kwargs)
-
- if DB_CONNECTION_MUTATOR := app.config["DB_CONNECTION_MUTATOR"]: # noqa: N806
- source = source or get_query_source_from_request()
-
- sqlalchemy_url, engine_kwargs = DB_CONNECTION_MUTATOR(
- sqlalchemy_url,
- engine_kwargs,
- effective_username,
- security_manager,
- source,
- )
- try:
- return create_engine(sqlalchemy_url, **engine_kwargs)
- except Exception as ex:
- raise self.db_engine_spec.get_dbapi_mapped_exception(ex) from ex
-
def add_database_to_signature(
self,
func: Callable[..., None],
@@ -572,13 +470,12 @@ class Database(CoreDatabase, AuditMixinNullable, ImportExportMixin): # pylint:
self,
catalog: str | None = None,
schema: str | None = None,
- nullpool: bool = True,
+ nullpool: bool = True, # Kept for backwards compatibility, but ignored
source: utils.QuerySource | None = None,
) -> Connection:
with self.get_sqla_engine(
catalog=catalog,
schema=schema,
- nullpool=nullpool,
source=source,
) as engine:
with check_for_oauth2(self):
(superset) 12/13: Fix tests
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch engine-manager
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 99935fc0355bb9b72bf8ac6a99ced82a66aefd12
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Dec 4 09:49:19 2025 -0500
Fix tests
---
.../integration_tests/databases/commands_tests.py | 29 +++++++++++++++-------
1 file changed, 20 insertions(+), 9 deletions(-)
diff --git a/tests/integration_tests/databases/commands_tests.py b/tests/integration_tests/databases/commands_tests.py
index 1be5849b1cb..f300aa45604 100644
--- a/tests/integration_tests/databases/commands_tests.py
+++ b/tests/integration_tests/databases/commands_tests.py
@@ -906,7 +906,9 @@ class TestTestConnectionDatabaseCommand(SupersetTestCase):
"""Test to make sure event_logger is called when an exception is raised"""
database = get_example_database()
mock_g.user = security_manager.find_user("admin")
- mock_get_sqla_engine.__enter__.side_effect = Exception("An error has occurred!")
+ mock_get_sqla_engine.return_value.__enter__.side_effect = Exception(
+ "An error has occurred!"
+ )
db_uri = database.sqlalchemy_uri_decrypted
json_payload = {"sqlalchemy_uri": db_uri}
command_without_db_name = TestConnectionDatabaseCommand(json_payload)
@@ -927,7 +929,7 @@ class TestTestConnectionDatabaseCommand(SupersetTestCase):
"""Test to make sure do_ping exceptions gets captured"""
database = get_example_database()
mock_g.user = security_manager.find_user("admin")
- mock_get_sqla_engine.__enter__().dialect.do_ping.side_effect = Exception(
+ mock_get_sqla_engine.return_value.__enter__.return_value.dialect.do_ping.side_effect = Exception(
"An error has occurred!"
)
db_uri = database.sqlalchemy_uri_decrypted
@@ -977,7 +979,7 @@ class TestTestConnectionDatabaseCommand(SupersetTestCase):
connection exc is raised"""
database = get_example_database()
mock_g.user = security_manager.find_user("admin")
- mock_get_sqla_engine.__enter__.side_effect = SupersetSecurityException(
+ mock_get_sqla_engine.return_value.__enter__.side_effect = SupersetSecurityException(
SupersetError(error_type=500, message="test", level="info")
)
db_uri = database.sqlalchemy_uri_decrypted
@@ -999,7 +1001,7 @@ class TestTestConnectionDatabaseCommand(SupersetTestCase):
"""Test to make sure event_logger is called when DBAPIError is raised"""
database = get_example_database()
mock_g.user = security_manager.find_user("admin")
- mock_get_sqla_engine.__enter__.side_effect = DBAPIError(
+ mock_get_sqla_engine.return_value.__enter__.side_effect = DBAPIError(
statement="error", params={}, orig={}
)
db_uri = database.sqlalchemy_uri_decrypted
@@ -1169,23 +1171,32 @@ class TestTablesDatabaseCommand(SupersetTestCase):
assert str(excinfo.value) == "Test Error"
@patch("superset.daos.database.DatabaseDAO.find_by_id")
+ @patch("superset.models.core.Database.get_all_materialized_view_names_in_schema")
+ @patch("superset.models.core.Database.get_all_view_names_in_schema")
+ @patch("superset.models.core.Database.get_all_table_names_in_schema")
@patch("superset.security.manager.SupersetSecurityManager.can_access_database")
@patch("superset.utils.core.g")
def test_database_tables_exception(
- self, mock_g, mock_can_access_database, mock_find_by_id
+ self,
+ mock_g,
+ mock_can_access_database,
+ mock_get_tables,
+ mock_get_views,
+ mock_get_mvs,
+ mock_find_by_id,
):
database = get_example_database()
mock_find_by_id.return_value = database
+ mock_get_tables.return_value = {("table1", "main", None)}
+ mock_get_views.return_value = set()
+ mock_get_mvs.return_value = []
mock_can_access_database.side_effect = Exception("Test Error")
mock_g.user = security_manager.find_user("admin")
command = TablesDatabaseCommand(database.id, None, "main", False)
with pytest.raises(DatabaseTablesUnexpectedError) as excinfo: # noqa: PT012
command.run()
- assert (
- str(excinfo.value)
- == "Unexpected error occurred, please check your logs for details"
- )
+ assert str(excinfo.value) == "Test Error"
@patch("superset.daos.database.DatabaseDAO.find_by_id")
@patch("superset.security.manager.SupersetSecurityManager.can_access_database")