Compare commits

...
Author SHA1 Message Date
Elizabeth ThompsonandClaude 482bef1507 fix(playwright): Use actual viewport height for tiled screenshot scroll calculations
The configured SCREENSHOT_TILED_VIEWPORT_HEIGHT (default 2000px) was larger than
Playwright's actual browser viewport (~768-1200px), causing scroll increments to
skip content between tiles. Now we fetch the actual viewport height and use
min(configured, actual) for scroll calculations to ensure complete content coverage.

Added regression test to verify tiles properly cover all content when configured
viewport exceeds actual viewport size.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 11:38:24 -07:00
Elizabeth ThompsonandClaude 20c04a4663 fix(playwright): Simplify tiled screenshot clip calculation to fix multi-tile captures
Fixes issue where Playwright tiled screenshots only captured the first frame
and failed to capture subsequent tiles on large dashboards (50+ charts).

**Problem:**
The previous fix prevented errors but used overly complex tile_content_height
calculations that caused incorrect clip dimensions for tiles after the first one.
Customer testing revealed screenshots would not capture content past the first tile.

**Solution:**
Simplified the clip height calculation by:
- Removing tile_content_height from the clip height logic
- Directly calculating visible portion of element in viewport
- Maintaining proper handling for elements scrolled above viewport

After scrolling to position each tile, we now simply capture what's visible
of the element rather than trying to match a calculated content height.

**Testing:**
- All 21 unit tests pass including edge cases
- Negative element positions (scrolled above viewport)
- Elements extending beyond viewport boundaries
- Elements completely out of view

Based on customer feedback from Brandon Sovran who confirmed this approach
successfully captures massive pages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 16:43:32 -07:00
Elizabeth ThompsonandClaude 782f5eab16 fix(playwright): Fix tiled screenshot clip bounds validation
Fixes issue where Playwright tiled screenshots fail with "Clipped area is either empty or outside the resulting image" error on large dashboards (50+ charts).

The problem occurred when:
- Dashboard elements scroll above viewport (negative Y coordinates)
- Clip regions extend beyond viewport boundaries
- Element dimensions result in invalid clip calculations

Changes:
- Add viewport dimension tracking to clip calculations
- Clamp coordinates to viewport bounds (prevent negative x/y)
- Calculate visible portions for partially scrolled elements
- Validate clip dimensions before screenshot (skip invalid tiles)
- Add comprehensive test coverage for edge cases

Tested with 21 unit tests including:
- Negative element positions
- Elements beyond viewport bounds
- Invalid clip dimensions
- Zero-width elements
- Elements completely scrolled out of view

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 14:14:51 -07:00
Richard Fogaca NienkotterandGitHub 1234533c67 fix: edit dataset modal visual fixes (#35799) 2025-10-22 22:43:13 +03:00
Mehmet Salih YavuzandGitHub 7f0c0aea94 fix(ThemeController): replace fetch with SupersetClient for proper auth (#35794) 2025-10-22 19:54:28 +03:00
Fabian HalkivahaandGitHub d9dcbb68b7 chore(docs): use native docusauros admonition notation (#35791) 2025-10-22 11:39:52 -04:00
Mehmet Salih YavuzandGitHub 98fba1eefe fix(security): Add active property to guest user (#35454) 2025-10-22 12:51:19 +03:00
GeidōandGitHub bad03b1e76 fix(Actions): Improper spacing (#35724) 2025-10-21 20:10:00 +02:00
GeidōandGitHub fcfafebb29 fix(Themes): Local label inconsistent behaviors (#35663) 2025-10-21 20:09:33 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
47e82b02ed chore(deps-dev): bump ts-jest from 29.4.0 to 29.4.5 in /superset-frontend (#35732)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 10:54:09 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a463d66c80 chore(deps-dev): bump typescript-eslint from 8.46.1 to 8.46.2 in /superset-websocket (#35757)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 10:53:02 -07:00
Michael S. MolinaandGitHub 337da13ba7 fix: Changes ResultSet to include sqlEditorImmutableId when fetching results (#35773) 2025-10-21 14:24:39 -03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4a3453999a chore(deps-dev): bump eslint from 9.37.0 to 9.38.0 in /docs (#35727)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 10:21:37 -07:00
SkinnyPigeonGitHubbito-code-review[bot] <188872107+bito-code-review[bot]@users.noreply.github.com>Beto Dealmeida
58758de93d feat(reports): allow custom na values (#35481)
Co-authored-by: bito-code-review[bot] <188872107+bito-code-review[bot]@users.noreply.github.com>
Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>
2025-10-21 13:05:57 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b4a8acc584 chore(deps-dev): bump @babel/compat-data from 7.28.0 to 7.28.4 in /superset-frontend (#35730)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 09:57:51 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
08f89690e9 chore(deps-dev): bump html-webpack-plugin from 5.6.3 to 5.6.4 in /superset-frontend (#35755)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 09:56:55 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f02899d38d chore(deps-dev): bump @types/node from 24.8.1 to 24.9.1 in /superset-websocket (#35761)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 09:56:13 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
86583f1121 chore(deps-dev): bump @typescript-eslint/parser from 8.46.1 to 8.46.2 in /superset-websocket (#35759)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 09:55:05 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
26cbd71099 chore(deps-dev): bump prettier-plugin-packagejson from 2.5.8 to 2.5.19 in /superset-frontend (#35760)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 09:49:49 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
500ce7a02a chore(deps): bump ace-builds from 1.43.3 to 1.43.4 in /superset-frontend (#35763)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 09:48:51 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6d8ceed10e chore(deps-dev): bump typescript-eslint from 8.46.1 to 8.46.2 in /docs (#35764)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 09:46:51 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
68d65f727f chore(deps): bump antd from 5.27.5 to 5.27.6 in /docs (#35765)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 09:46:10 -07:00
Yuvraj Singh ChauhanandGitHub f165785003 docs: update links in CONTRIBUTING.md to point to the correct Developer Portal URLs (#35723) 2025-10-20 17:03:43 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8e31c93119 chore(deps): bump @docusaurus/preset-classic from 3.9.1 to 3.9.2 in /docs (#35744)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 13:29:16 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4974c08f7d chore(deps): bump @docusaurus/theme-mermaid from 3.9.1 to 3.9.2 in /docs (#35741)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 13:09:24 -07:00
Michael S. MolinaandGitHub fa90ba976c fix: Extension events not fired in SQL Lab tabs (#35753) 2025-10-20 16:41:24 -03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
35c3d8dfbc chore(deps): bump @docusaurus/plugin-client-redirects from 3.9.1 to 3.9.2 in /docs (#35739)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 12:11:29 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ee23815aff chore(deps): bump @docusaurus/core from 3.9.1 to 3.9.2 in /docs (#35738)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 11:39:53 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7c946ae3db chore(deps-dev): bump @docusaurus/tsconfig from 3.9.1 to 3.9.2 in /docs (#35733)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 11:37:10 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3926f5c55c chore(deps-dev): bump @babel/eslint-parser from 7.26.5 to 7.28.4 in /superset-frontend (#35746)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 11:35:48 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fdc03d4bf3 chore(deps): update xss requirement from ^1.0.14 to ^1.0.15 in /superset-frontend/packages/superset-ui-core (#35747)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 11:35:10 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
24f0aed8a7 chore(deps): bump swagger-ui-react from 5.29.4 to 5.29.5 in /docs (#35734)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 10:54:06 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
00d2f577df chore(deps-dev): bump eslint from 9.37.0 to 9.38.0 in /superset-websocket (#35735)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 10:53:41 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c35fc71bc5 chore(deps-dev): bump @eslint/js from 9.37.0 to 9.38.0 in /docs (#35736)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 10:53:02 -07:00
1b6d57c3f3 test(tasks): Add tests for log_task_failure signal handler (#35721)
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-20 09:23:18 -07:00
d089a96163 fix(playwright): Download dashboard correctly (#35484)
Co-authored-by: Erkka Tahvanainen <erkka.tahvanainen@confidently.fi>
2025-10-20 16:57:01 +02:00
Elizabeth ThompsonandGitHub 0b3fe3d60c test: add comprehensive unhappy path tests for export (#35718) 2025-10-17 16:41:00 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
0eeb184b6a chore(deps): bump @luma.gl/constants from 9.1.9 to 9.2.2 in /superset-frontend (#35696)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-17 13:28:25 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8e7edce616 chore(deps-dev): bump @types/node from 24.6.2 to 24.8.1 in /superset-frontend (#35705)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-17 12:59:06 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
754201b3d0 chore(deps): bump dayjs from 1.11.13 to 1.11.18 in /superset-frontend (#35697)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-17 10:29:46 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
925401b4e1 chore(deps-dev): bump @types/node from 24.7.2 to 24.8.1 in /superset-websocket (#35698)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-17 10:29:13 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8368ea4094 chore(deps-dev): bump eslint-plugin-prettier from 5.2.3 to 5.5.4 in /superset-frontend (#35700)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-17 10:28:45 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e8a6fb24ae chore(deps): bump react-reverse-portal from 2.1.2 to 2.3.0 in /superset-frontend (#35706)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-17 10:27:41 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
311b7a72dc chore(deps-dev): bump copy-webpack-plugin from 13.0.0 to 13.0.1 in /superset-frontend (#35707)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-17 10:27:11 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
aa496def53 chore(deps-dev): bump @babel/cli from 7.27.2 to 7.28.3 in /superset-frontend (#35708)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-17 10:26:47 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
aea4375255 chore(deps): update @fontsource/fira-code requirement from ^5.2.6 to ^5.2.7 in /superset-frontend/packages/superset-ui-core (#35709)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-17 10:26:15 -07:00
9ab0a0179d fix(charts): update axis title labels to sentence case (#35694)
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-17 09:42:04 -07:00
Fabian HalkivahaandGitHub 3db613dab5 chore(docs): switch to markdown headlines for available macros (#35685) 2025-10-17 10:16:49 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
de5ca79805 chore(deps): update chalk requirement from ^5.4.1 to ^5.6.2 in /superset-frontend/packages/generator-superset (#35391)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 14:08:40 -07:00
Sam FirkeandGitHub aede3bb5ba fix(auth): redirect anonymous attempts to view dashboard with next (#35345) 2025-10-16 16:33:37 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
408f84aea6 chore(deps): update dompurify requirement from ^3.2.7 to ^3.3.0 in /superset-frontend/plugins/legacy-preset-chart-nvd3 (#35632)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 12:59:53 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
92c07aaf54 chore(deps-dev): bump @playwright/test from 1.55.0 to 1.56.0 in /superset-frontend (#35676)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 12:58:35 -07:00
f405174fcf fix(dataset): render default URL description properly in settings (#35669)
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-16 14:27:24 -04:00
Richard Fogaca NienkotterandGitHub 8c125d2553 feat: use in deck.gl custom tooltip instead of SafeMarkdown (#35665) 2025-10-16 11:13:38 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fb8fca4c64 chore(deps): bump antd from 5.27.4 to 5.27.5 in /docs (#35654)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 10:05:35 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
dc0c055518 chore(deps-dev): bump webpack from 5.102.0 to 5.102.1 in /superset-frontend (#35675)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 10:04:08 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
09349cb1e7 chore(deps): bump caniuse-lite from 1.0.30001750 to 1.0.30001751 in /docs (#35677)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 09:39:02 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ca29adb0cb chore(deps): bump fuse.js from 7.0.0 to 7.1.0 in /superset-frontend (#35678)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 09:35:07 -07:00
73 changed files with 2477 additions and 1118 deletions
+7 -7
View File
@@ -25,14 +25,14 @@ little bit helps, and credit will always be given.
All developer and contribution documentation has moved to the Apache Superset Developer Portal:
**[📚 View the Developer Portal →](https://superset.apache.org/docs/developer-portal/)**
**[📚 View the Developer Portal →](https://superset.apache.org/developer_portal/)**
The Developer Portal includes comprehensive guides for:
- [Contributing Overview](https://superset.apache.org/docs/developer-portal/contributing/overview)
- [Development Setup](https://superset.apache.org/docs/developer-portal/contributing/development-setup)
- [Submitting Pull Requests](https://superset.apache.org/docs/developer-portal/contributing/submitting-pr)
- [Contribution Guidelines](https://superset.apache.org/docs/developer-portal/contributing/guidelines)
- [Code Review Process](https://superset.apache.org/docs/developer-portal/contributing/code-review)
- [Development How-tos](https://superset.apache.org/docs/developer-portal/contributing/howtos)
- [Contributing Overview](https://superset.apache.org/developer_portal/contributing/overview)
- [Development Setup](https://superset.apache.org/developer_portal/contributing/development-setup)
- [Submitting Pull Requests](https://superset.apache.org/developer_portal/contributing/submitting-pr)
- [Contribution Guidelines](https://superset.apache.org/developer_portal/contributing/guidelines)
- [Code Review Process](https://superset.apache.org/developer_portal/contributing/code-review)
- [Development How-tos](https://superset.apache.org/developer_portal/contributing/howtos)
Source for the Developer Portal documentation is [located here](https://github.com/apache/superset/tree/master/docs/developer_portal).
+21 -19
View File
@@ -12,11 +12,13 @@ version: 1
SQL Lab and Explore supports [Jinja templating](https://jinja.palletsprojects.com/en/2.11.x/) in queries.
To enable templating, the `ENABLE_TEMPLATE_PROCESSING` [feature flag](/docs/configuration/configuring-superset#feature-flags) needs to be enabled in `superset_config.py`.
> #### ⚠️ Security Warning
>
> While powerful, this feature executes template code on the server. Within the Superset security model, this is **intended functionality**, as users with permissions to edit charts and virtual datasets are considered **trusted users**.
>
> If you grant these permissions to untrusted users, this feature can be exploited as a **Server-Side Template Injection (SSTI)** vulnerability. Do not enable `ENABLE_TEMPLATE_PROCESSING` unless you fully understand and accept the associated security risks.
:::warning[Security Warning]
While powerful, this feature executes template code on the server. Within the Superset security model, this is **intended functionality**, as users with permissions to edit charts and virtual datasets are considered **trusted users**.
If you grant these permissions to untrusted users, this feature can be exploited as a **Server-Side Template Injection (SSTI)** vulnerability. Do not enable `ENABLE_TEMPLATE_PROCESSING` unless you fully understand and accept the associated security risks.
:::
When templating is enabled, python code can be embedded in virtual datasets and
in Custom SQL in the filter and metric controls in Explore. By default, the following variables are
@@ -182,7 +184,7 @@ The available validators and names can be found in
In this section, we'll walkthrough the pre-defined Jinja macros in Superset.
**Current Username**
### Current Username
The `{{ current_username() }}` macro returns the `username` of the currently logged in user.
@@ -197,7 +199,7 @@ cache key by adding the following parameter to your Jinja code:
{{ current_username(add_to_cache_keys=False) }}
```
**Current User ID**
### Current User ID
The `{{ current_user_id() }}` macro returns the account ID of the currently logged in user.
@@ -212,7 +214,7 @@ cache key by adding the following parameter to your Jinja code:
{{ current_user_id(add_to_cache_keys=False) }}
```
**Current User Email**
### Current User Email
The `{{ current_user_email() }}` macro returns the email address of the currently logged in user.
@@ -227,7 +229,7 @@ cache key by adding the following parameter to your Jinja code:
{{ current_user_email(add_to_cache_keys=False) }}
```
**Current User Roles**
### Current User Roles
The `{{ current_user_roles() }}` macro returns an array of roles for the logged in user.
@@ -257,7 +259,7 @@ Will be rendered as:
SELECT * FROM users WHERE role IN ('admin', 'viewer')
```
**Current User RLS Rules**
### Current User RLS Rules
The `{{ current_user_rls_rules() }}` macro returns an array of RLS rules applied to the current dataset for the logged in user.
@@ -265,7 +267,7 @@ If you have caching enabled in your Superset configuration, then the list of RLS
by Superset when calculating the cache key. A cache key is a unique identifier that determines if there's a
cache hit in the future and Superset can retrieve cached data.
**Custom URL Parameters**
### Custom URL Parameters
The `{{ url_param('custom_variable') }}` macro lets you define arbitrary URL
parameters and reference them in your SQL code.
@@ -299,7 +301,7 @@ Here's a concrete example:
WHERE country_code = 'US'
```
**Explicitly Including Values in Cache Key**
### Explicitly Including Values in Cache Key
The `{{ cache_key_wrapper() }}` function explicitly instructs Superset to add a value to the
accumulated list of values used in the calculation of the cache key.
@@ -311,7 +313,7 @@ in the cache key. You can gain more context
Note that this function powers the caching of the `user_id` and `username` values
in the `current_user_id()` and `current_username()` function calls (if you have caching enabled).
**Filter Values**
### Filter Values
You can retrieve the value for a specific filter as a list using `{{ filter_values() }}`.
@@ -332,7 +334,7 @@ GROUP BY action
There `where_in` filter converts the list of values from `filter_values('action_type')` into a string suitable for an `IN` expression.
**Filters for a Specific Column**
### Filters for a Specific Column
The `{{ get_filters() }}` macro returns the filters applied to a given column. In addition to
returning the values (similar to how `filter_values()` does), the `get_filters()` macro
@@ -394,7 +396,7 @@ Here's a concrete example:
order by lineage, level
```
**Time Filter**
### Time Filter
The `{{ get_time_filter() }}` macro returns the time filter applied to a specific column. This is useful if you want
to handle time filters inside the virtual dataset, as by default the time filter is placed on the outer query. This can
@@ -469,7 +471,7 @@ WHERE
AND dttm < {{ time_filter.to_expr }}
```
**Datasets**
### Datasets
It's possible to query physical and virtual datasets using the `dataset` macro. This is useful if you've defined computed columns and metrics on your datasets, and want to reuse the definition in adhoc SQL Lab queries.
@@ -493,7 +495,7 @@ Since metrics are aggregations, the resulting SQL expression will be grouped by
SELECT * FROM {{ dataset(42, include_metrics=True, columns=["ds", "category"]) }} LIMIT 10
```
**Metrics**
### Metrics
The `{{ metric('metric_key', dataset_id) }}` macro can be used to retrieve the metric SQL syntax from a dataset. This can be useful for different purposes:
@@ -511,7 +513,7 @@ The parameter can be used in SQL Lab, or when fetching a metric from another dat
Superset supports [builtin filters from the Jinja2 templating package](https://jinja.palletsprojects.com/en/stable/templates/#builtin-filters). Custom filters have also been implemented:
**Where In**
### Where In
Parses a list into a SQL-compatible statement. This is useful with macros that return an array (for example the `filter_values` macro):
```
@@ -528,7 +530,7 @@ Dashboard filter without any value applied
{{ filter_values('column')|where_in(default_to_none=True) }} => None
```
**To Datetime**
### To Datetime
Loads a string as a `datetime` object. This is useful when performing date operations. For example:
```
+11 -11
View File
@@ -28,10 +28,10 @@
},
"dependencies": {
"@ant-design/icons": "^6.1.0",
"@docusaurus/core": "3.9.1",
"@docusaurus/plugin-client-redirects": "3.9.1",
"@docusaurus/preset-classic": "3.9.1",
"@docusaurus/theme-mermaid": "^3.9.1",
"@docusaurus/core": "3.9.2",
"@docusaurus/plugin-client-redirects": "3.9.2",
"@docusaurus/preset-classic": "3.9.2",
"@docusaurus/theme-mermaid": "^3.9.2",
"@emotion/core": "^10.0.27",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^10.0.27",
@@ -49,8 +49,8 @@
"@storybook/preview-api": "^8.6.11",
"@storybook/theming": "^8.6.11",
"@superset-ui/core": "^0.20.4",
"antd": "^5.27.4",
"caniuse-lite": "^1.0.30001750",
"antd": "^5.27.6",
"caniuse-lite": "^1.0.30001751",
"docusaurus-plugin-less": "^2.0.2",
"json-bigint": "^1.0.0",
"less": "^4.4.2",
@@ -63,25 +63,25 @@
"remark-import-partial": "^0.0.2",
"reselect": "^5.1.1",
"storybook": "^8.6.11",
"swagger-ui-react": "^5.29.4",
"swagger-ui-react": "^5.29.5",
"tinycolor2": "^1.4.2",
"ts-loader": "^9.5.4"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.9.1",
"@docusaurus/tsconfig": "^3.9.1",
"@eslint/js": "^9.37.0",
"@docusaurus/tsconfig": "^3.9.2",
"@eslint/js": "^9.38.0",
"@types/react": "^19.1.8",
"@typescript-eslint/eslint-plugin": "^8.37.0",
"@typescript-eslint/parser": "^8.46.0",
"eslint": "^9.37.0",
"eslint": "^9.38.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.4.0",
"prettier": "^3.6.2",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.1",
"typescript-eslint": "^8.46.2",
"webpack": "^5.102.1"
},
"browserslist": {
+355 -357
View File
@@ -1593,10 +1593,10 @@
marked "^16.3.0"
zod "^4.1.8"
"@docusaurus/babel@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/babel/-/babel-3.9.1.tgz#5297195ab34df9e184e3e2fe20de1a2e1b2a22e8"
integrity sha512-/uoi3oG+wvbVWNBRfPrzrEslOSeLxrQEyWMywK51TLDFTANqIRivzkMusudh5bdDty8fXzCYUT+tg5t697jYqg==
"@docusaurus/babel@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/babel/-/babel-3.9.2.tgz#f956c638baeccf2040e482c71a742bc7e35fdb22"
integrity sha512-GEANdi/SgER+L7Japs25YiGil/AUDnFFHaCGPBbundxoWtCkA2lmy7/tFmgED4y1htAy6Oi4wkJEQdGssnw9MA==
dependencies:
"@babel/core" "^7.25.9"
"@babel/generator" "^7.25.9"
@@ -1608,23 +1608,23 @@
"@babel/runtime" "^7.25.9"
"@babel/runtime-corejs3" "^7.25.9"
"@babel/traverse" "^7.25.9"
"@docusaurus/logger" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/logger" "3.9.2"
"@docusaurus/utils" "3.9.2"
babel-plugin-dynamic-import-node "^2.3.3"
fs-extra "^11.1.1"
tslib "^2.6.0"
"@docusaurus/bundler@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/bundler/-/bundler-3.9.1.tgz#6b78c152cf364d706249f6978f8e3fedf576b118"
integrity sha512-E1c9DgNmAz4NqbNtiJVp4UgjLtr8O01IgtXD/NDQ4PZaK8895cMiTOgb3k7mN0qX8A3lb8vqyrPJ842+yMpuUg==
"@docusaurus/bundler@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/bundler/-/bundler-3.9.2.tgz#0ca82cda4acf13a493e3f66061aea351e9d356cf"
integrity sha512-ZOVi6GYgTcsZcUzjblpzk3wH1Fya2VNpd5jtHoCCFcJlMQ1EYXZetfAnRHLcyiFeBABaI1ltTYbOBtH/gahGVA==
dependencies:
"@babel/core" "^7.25.9"
"@docusaurus/babel" "3.9.1"
"@docusaurus/cssnano-preset" "3.9.1"
"@docusaurus/logger" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/babel" "3.9.2"
"@docusaurus/cssnano-preset" "3.9.2"
"@docusaurus/logger" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils" "3.9.2"
babel-loader "^9.2.1"
clean-css "^5.3.3"
copy-webpack-plugin "^11.0.0"
@@ -1644,18 +1644,18 @@
webpack "^5.95.0"
webpackbar "^6.0.1"
"@docusaurus/core@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.9.1.tgz#be4d859464fee8889794d8527f884e931d591f2e"
integrity sha512-FWDk1LIGD5UR5Zmm9rCrXRoxZUgbwuP6FBA7rc50DVfzqDOMkeMe3NyJhOsA2dF0zBE3VbHEIMmTjKwTZJwbaA==
"@docusaurus/core@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.9.2.tgz#cc970f29b85a8926d63c84f8cffdcda43ed266ff"
integrity sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw==
dependencies:
"@docusaurus/babel" "3.9.1"
"@docusaurus/bundler" "3.9.1"
"@docusaurus/logger" "3.9.1"
"@docusaurus/mdx-loader" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/utils-common" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/babel" "3.9.2"
"@docusaurus/bundler" "3.9.2"
"@docusaurus/logger" "3.9.2"
"@docusaurus/mdx-loader" "3.9.2"
"@docusaurus/utils" "3.9.2"
"@docusaurus/utils-common" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
boxen "^6.2.1"
chalk "^4.1.2"
chokidar "^3.5.3"
@@ -1692,32 +1692,32 @@
webpack-dev-server "^5.2.2"
webpack-merge "^6.0.1"
"@docusaurus/cssnano-preset@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.9.1.tgz#fa57c81a3f41e4d118115f86c85a71aed6b90f49"
integrity sha512-2y7+s7RWQMqBg+9ejeKwvZs7Bdw/hHIVJIodwMXbs2kr+S48AhcmAfdOh6Cwm0unJb0hJUshN0ROwRoQMwl3xg==
"@docusaurus/cssnano-preset@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.9.2.tgz#523aab65349db3c51a77f2489048d28527759428"
integrity sha512-8gBKup94aGttRduABsj7bpPFTX7kbwu+xh3K9NMCF5K4bWBqTFYW+REKHF6iBVDHRJ4grZdIPbvkiHd/XNKRMQ==
dependencies:
cssnano-preset-advanced "^6.1.2"
postcss "^8.5.4"
postcss-sort-media-queries "^5.2.0"
tslib "^2.6.0"
"@docusaurus/logger@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.9.1.tgz#0209c4c1044ee35d89dbf676e3cbb5dc8b59c82b"
integrity sha512-C9iFzXwHzwvGlisE4bZx+XQE0JIqlGAYAd5LzpR7fEDgjctu7yL8bE5U4nTNywXKHURDzMt4RJK8V6+stFHVkA==
"@docusaurus/logger@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.9.2.tgz#6ec6364b90f5a618a438cc9fd01ac7376869f92a"
integrity sha512-/SVCc57ByARzGSU60c50rMyQlBuMIJCjcsJlkphxY6B0GV4UH3tcA1994N8fFfbJ9kX3jIBe/xg3XP5qBtGDbA==
dependencies:
chalk "^4.1.2"
tslib "^2.6.0"
"@docusaurus/mdx-loader@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.9.1.tgz#0ef77bee13450c83c18338f8e5f1753ed2e9ee3f"
integrity sha512-/1PY8lqry8jCt0qZddJSpc0U2sH6XC27kVJZfpA7o2TiQ3mdBQyH5AVbj/B2m682B1ounE+XjI0LdpOkAQLPoA==
"@docusaurus/mdx-loader@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.9.2.tgz#78d238de6c6203fa811cc2a7e90b9b79e111408c"
integrity sha512-wiYoGwF9gdd6rev62xDU8AAM8JuLI/hlwOtCzMmYcspEkzecKrP8J8X+KpYnTlACBUUtXNJpSoCwFWJhLRevzQ==
dependencies:
"@docusaurus/logger" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/logger" "3.9.2"
"@docusaurus/utils" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
"@mdx-js/mdx" "^3.0.0"
"@slorber/remark-comment" "^1.0.0"
escape-html "^1.0.3"
@@ -1740,12 +1740,12 @@
vfile "^6.0.1"
webpack "^5.88.1"
"@docusaurus/module-type-aliases@3.9.1", "@docusaurus/module-type-aliases@^3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.1.tgz#201959a22e7b30881cf879a21d2ae5b26415b705"
integrity sha512-YBce3GbJGGcMbJTyHcnEOMvdXqg41pa5HsrMCGA5Rm4z0h0tHS6YtEldj0mlfQRhCG7Y0VD66t2tb87Aom+11g==
"@docusaurus/module-type-aliases@3.9.2", "@docusaurus/module-type-aliases@^3.9.1":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.2.tgz#993c7cb0114363dea5ef6855e989b3ad4b843a34"
integrity sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew==
dependencies:
"@docusaurus/types" "3.9.1"
"@docusaurus/types" "3.9.2"
"@types/history" "^4.7.11"
"@types/react" "*"
"@types/react-router-config" "*"
@@ -1753,34 +1753,34 @@
react-helmet-async "npm:@slorber/react-helmet-async@1.3.0"
react-loadable "npm:@docusaurus/react-loadable@6.0.0"
"@docusaurus/plugin-client-redirects@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.9.1.tgz#06e07487f1596c62536d50afac81535d5fe60a20"
integrity sha512-+1InCGvAnw46H+TnVqxaYlJC0qy9AY5gTMgTx2ZFryjAsImJNs3i1pEYW/iUTVbOdtWRj3E/87E4ehbBIaA1TA==
"@docusaurus/plugin-client-redirects@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.9.2.tgz#9c27025c72aeeedeb783a94720163911567da0e8"
integrity sha512-lUgMArI9vyOYMzLRBUILcg9vcPTCyyI2aiuXq/4npcMVqOr6GfmwtmBYWSbNMlIUM0147smm4WhpXD0KFboffw==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/logger" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/utils-common" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/logger" "3.9.2"
"@docusaurus/utils" "3.9.2"
"@docusaurus/utils-common" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
eta "^2.2.0"
fs-extra "^11.1.1"
lodash "^4.17.21"
tslib "^2.6.0"
"@docusaurus/plugin-content-blog@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.9.1.tgz#bf6619847065360d52abc5bf1da307f5ce2a19f8"
integrity sha512-vT6kIimpJLWvW9iuWzH4u7VpTdsGlmn4yfyhq0/Kb1h4kf9uVouGsTmrD7WgtYBUG1P+TSmQzUUQa+ALBSRTig==
"@docusaurus/plugin-content-blog@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.9.2.tgz#d5ce51eb7757bdab0515e2dd26a793ed4e119df9"
integrity sha512-3I2HXy3L1QcjLJLGAoTvoBnpOwa6DPUa3Q0dMK19UTY9mhPkKQg/DYhAGTiBUKcTR0f08iw7kLPqOhIgdV3eVQ==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/logger" "3.9.1"
"@docusaurus/mdx-loader" "3.9.1"
"@docusaurus/theme-common" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/utils-common" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/logger" "3.9.2"
"@docusaurus/mdx-loader" "3.9.2"
"@docusaurus/theme-common" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils" "3.9.2"
"@docusaurus/utils-common" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
cheerio "1.0.0-rc.12"
feed "^4.2.2"
fs-extra "^11.1.1"
@@ -1792,20 +1792,20 @@
utility-types "^3.10.0"
webpack "^5.88.1"
"@docusaurus/plugin-content-docs@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.1.tgz#e3e75d4aa310689c262c18e10010788e53f101ec"
integrity sha512-DyLk9BIA6I9gPIuia8XIL+XIEbNnExam6AHzRsfrEq4zJr7k/DsWW7oi4aJMepDnL7jMRhpVcdsCxdjb0/A9xg==
"@docusaurus/plugin-content-docs@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz#cd8f2d1c06e53c3fa3d24bdfcb48d237bf2d6b2e"
integrity sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/logger" "3.9.1"
"@docusaurus/mdx-loader" "3.9.1"
"@docusaurus/module-type-aliases" "3.9.1"
"@docusaurus/theme-common" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/utils-common" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/logger" "3.9.2"
"@docusaurus/mdx-loader" "3.9.2"
"@docusaurus/module-type-aliases" "3.9.2"
"@docusaurus/theme-common" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils" "3.9.2"
"@docusaurus/utils-common" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
"@types/react-router-config" "^5.0.7"
combine-promises "^1.1.0"
fs-extra "^11.1.1"
@@ -1816,142 +1816,142 @@
utility-types "^3.10.0"
webpack "^5.88.1"
"@docusaurus/plugin-content-pages@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.9.1.tgz#044b8adbd2a673ff22630a74b3e0ce482761655d"
integrity sha512-/1wFzRnXYASI+Nv9ck9IVPIMw0O5BGQ8ZVhDzEwhkL+tl44ycvSnY6PIe6rW2HLxsw61Z3WFwAiU8+xMMtMZpg==
"@docusaurus/plugin-content-pages@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.9.2.tgz#22db6c88ade91cec0a9e87a00b8089898051b08d"
integrity sha512-s4849w/p4noXUrGpPUF0BPqIAfdAe76BLaRGAGKZ1gTDNiGxGcpsLcwJ9OTi1/V8A+AzvsmI9pkjie2zjIQZKA==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/mdx-loader" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/mdx-loader" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
fs-extra "^11.1.1"
tslib "^2.6.0"
webpack "^5.88.1"
"@docusaurus/plugin-css-cascade-layers@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.9.1.tgz#958a04679279e787d14fd3cc423ad35c580dc6fc"
integrity sha512-/QyW2gRCk/XE3ttCK/ERIgle8KJ024dBNKMu6U5SmpJvuT2il1n5jR/48Pp/9wEwut8WVml4imNm6X8JsL5A0Q==
"@docusaurus/plugin-css-cascade-layers@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.9.2.tgz#358c85f63f1c6a11f611f1b8889d9435c11b22f8"
integrity sha512-w1s3+Ss+eOQbscGM4cfIFBlVg/QKxyYgj26k5AnakuHkKxH6004ZtuLe5awMBotIYF2bbGDoDhpgQ4r/kcj4rQ==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
tslib "^2.6.0"
"@docusaurus/plugin-debug@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.9.1.tgz#5dbe01771176697f427b89a1ff023a3967c3e674"
integrity sha512-qPeAuk0LccC251d7jg2MRhNI+o7niyqa924oEM/AxnZJvIpMa596aAxkRImiAqNN6+gtLE1Hkrz/RHUH2HDGsA==
"@docusaurus/plugin-debug@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.9.2.tgz#b5df4db115583f5404a252dbf66f379ff933e53c"
integrity sha512-j7a5hWuAFxyQAkilZwhsQ/b3T7FfHZ+0dub6j/GxKNFJp2h9qk/P1Bp7vrGASnvA9KNQBBL1ZXTe7jlh4VdPdA==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils" "3.9.2"
fs-extra "^11.1.1"
react-json-view-lite "^2.3.0"
tslib "^2.6.0"
"@docusaurus/plugin-google-analytics@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.9.1.tgz#226e39ed6d0a5eb3978dc5189bc9676235756446"
integrity sha512-k4Qq2HphqOrIU/CevGPdEO1yJnWUI8m0zOJsYt5NfMJwNsIn/gDD6gv/DKD+hxHndQT5pacsfBd4BWHZVNVroQ==
"@docusaurus/plugin-google-analytics@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.9.2.tgz#857fe075fdeccdf6959e62954d9efe39769fa247"
integrity sha512-mAwwQJ1Us9jL/lVjXtErXto4p4/iaLlweC54yDUK1a97WfkC6Z2k5/769JsFgwOwOP+n5mUQGACXOEQ0XDuVUw==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
tslib "^2.6.0"
"@docusaurus/plugin-google-gtag@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.9.1.tgz#971b075898d46d1a59482b2873ccb9aa2e679910"
integrity sha512-n9BURBiQyJKI/Ecz35IUjXYwXcgNCSq7/eA07+ZYcDiSyH2p/EjPf8q/QcZG3CyEJPZ/SzGkDHePfcVPahY4Gg==
"@docusaurus/plugin-google-gtag@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.9.2.tgz#df75b1a90ae9266b0471909ba0265f46d5dcae62"
integrity sha512-YJ4lDCphabBtw19ooSlc1MnxtYGpjFV9rEdzjLsUnBCeis2djUyCozZaFhCg6NGEwOn7HDDyMh0yzcdRpnuIvA==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
"@types/gtag.js" "^0.0.12"
tslib "^2.6.0"
"@docusaurus/plugin-google-tag-manager@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.9.1.tgz#b26770d8bb07cedc1e01305cd9d66c2e4ce6d654"
integrity sha512-rZAQZ25ZuXaThBajxzLjXieTDUCMmBzfAA6ThElQ3o7Q+LEpOjCIrwGFau0KLY9HeG6x91+FwwsAM8zeApYDrg==
"@docusaurus/plugin-google-tag-manager@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.9.2.tgz#d1a3cf935acb7d31b84685e92d70a1d342946677"
integrity sha512-LJtIrkZN/tuHD8NqDAW1Tnw0ekOwRTfobWPsdO15YxcicBo2ykKF0/D6n0vVBfd3srwr9Z6rzrIWYrMzBGrvNw==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
tslib "^2.6.0"
"@docusaurus/plugin-sitemap@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.9.1.tgz#e84717c1e52f3a61f9fea414ef98ebe025e7ffd2"
integrity sha512-k/bf5cXDxAJUYTzqatgFJwmZsLUbIgl6S8AdZMKGG2Mv2wcOHt+EQNN9qPyWZ5/9cFj+Q8f8DN+KQheBMYLong==
"@docusaurus/plugin-sitemap@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.9.2.tgz#e1d9f7012942562cc0c6543d3cb2cdc4ae713dc4"
integrity sha512-WLh7ymgDXjG8oPoM/T4/zUP7KcSuFYRZAUTl8vR6VzYkfc18GBM4xLhcT+AKOwun6kBivYKUJf+vlqYJkm+RHw==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/logger" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/utils-common" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/logger" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils" "3.9.2"
"@docusaurus/utils-common" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
fs-extra "^11.1.1"
sitemap "^7.1.1"
tslib "^2.6.0"
"@docusaurus/plugin-svgr@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-svgr/-/plugin-svgr-3.9.1.tgz#394ad2b8da3af587a0f68167252b4bf99fb72351"
integrity sha512-TeZOXT2PSdTNR1OpDJMkYqFyX7MMhbd4t16hQByXksgZQCXNyw3Dio+KaDJ2Nj+LA4WkOvsk45bWgYG5MAaXSQ==
"@docusaurus/plugin-svgr@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-svgr/-/plugin-svgr-3.9.2.tgz#62857ed79d97c0150d25f7e7380fdee65671163a"
integrity sha512-n+1DE+5b3Lnf27TgVU5jM1d4x5tUh2oW5LTsBxJX4PsAPV0JGcmI6p3yLYtEY0LRVEIJh+8RsdQmRE66wSV8mw==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
"@svgr/core" "8.1.0"
"@svgr/webpack" "^8.1.0"
tslib "^2.6.0"
webpack "^5.88.1"
"@docusaurus/preset-classic@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.9.1.tgz#58d86664b5c9779578092556a0e6ae5ccebbd6c0"
integrity sha512-ZHga2xsxxsyd0dN1BpLj8S889Eu9eMBuj2suqxdw/vaaXu/FjJ8KEGbcaeo6nHPo8VQcBBnPEdkBtSDm2TfMNw==
"@docusaurus/preset-classic@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.9.2.tgz#85cc4f91baf177f8146c9ce896dfa1f0fd377050"
integrity sha512-IgyYO2Gvaigi21LuDIe+nvmN/dfGXAiMcV/murFqcpjnZc7jxFAxW+9LEjdPt61uZLxG4ByW/oUmX/DDK9t/8w==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/plugin-content-blog" "3.9.1"
"@docusaurus/plugin-content-docs" "3.9.1"
"@docusaurus/plugin-content-pages" "3.9.1"
"@docusaurus/plugin-css-cascade-layers" "3.9.1"
"@docusaurus/plugin-debug" "3.9.1"
"@docusaurus/plugin-google-analytics" "3.9.1"
"@docusaurus/plugin-google-gtag" "3.9.1"
"@docusaurus/plugin-google-tag-manager" "3.9.1"
"@docusaurus/plugin-sitemap" "3.9.1"
"@docusaurus/plugin-svgr" "3.9.1"
"@docusaurus/theme-classic" "3.9.1"
"@docusaurus/theme-common" "3.9.1"
"@docusaurus/theme-search-algolia" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/plugin-content-blog" "3.9.2"
"@docusaurus/plugin-content-docs" "3.9.2"
"@docusaurus/plugin-content-pages" "3.9.2"
"@docusaurus/plugin-css-cascade-layers" "3.9.2"
"@docusaurus/plugin-debug" "3.9.2"
"@docusaurus/plugin-google-analytics" "3.9.2"
"@docusaurus/plugin-google-gtag" "3.9.2"
"@docusaurus/plugin-google-tag-manager" "3.9.2"
"@docusaurus/plugin-sitemap" "3.9.2"
"@docusaurus/plugin-svgr" "3.9.2"
"@docusaurus/theme-classic" "3.9.2"
"@docusaurus/theme-common" "3.9.2"
"@docusaurus/theme-search-algolia" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/theme-classic@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.9.1.tgz#790fb1b8058d0572632211023ead238c1a6450e0"
integrity sha512-LrAIu/mQ04nG6s1cssC0TMmICD8twFIIn/hJ5Pd9uIPQvtKnyAKEn12RefopAul5KfMo9kixPaqogV5jIJr26w==
"@docusaurus/theme-classic@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.9.2.tgz#6e514f99a0ff42b80afcf42d5e5d042618311ce0"
integrity sha512-IGUsArG5hhekXd7RDb11v94ycpJpFdJPkLnt10fFQWOVxAtq5/D7hT6lzc2fhyQKaaCE62qVajOMKL7OiAFAIA==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/logger" "3.9.1"
"@docusaurus/mdx-loader" "3.9.1"
"@docusaurus/module-type-aliases" "3.9.1"
"@docusaurus/plugin-content-blog" "3.9.1"
"@docusaurus/plugin-content-docs" "3.9.1"
"@docusaurus/plugin-content-pages" "3.9.1"
"@docusaurus/theme-common" "3.9.1"
"@docusaurus/theme-translations" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/utils-common" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/logger" "3.9.2"
"@docusaurus/mdx-loader" "3.9.2"
"@docusaurus/module-type-aliases" "3.9.2"
"@docusaurus/plugin-content-blog" "3.9.2"
"@docusaurus/plugin-content-docs" "3.9.2"
"@docusaurus/plugin-content-pages" "3.9.2"
"@docusaurus/theme-common" "3.9.2"
"@docusaurus/theme-translations" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils" "3.9.2"
"@docusaurus/utils-common" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
"@mdx-js/react" "^3.0.0"
clsx "^2.0.0"
infima "0.2.0-alpha.45"
@@ -1965,15 +1965,15 @@
tslib "^2.6.0"
utility-types "^3.10.0"
"@docusaurus/theme-common@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.9.1.tgz#095cbeab489d51380143951508571888f4d2928d"
integrity sha512-j9adi961F+6Ps9d0jcb5BokMcbjXAAJqKkV43eo8nh4YgmDj7KUNDX4EnOh/MjTQeO06oPY5cxp3yUXdW/8Ggw==
"@docusaurus/theme-common@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.9.2.tgz#487172c6fef9815c2746ef62a71e4f5b326f9ba5"
integrity sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag==
dependencies:
"@docusaurus/mdx-loader" "3.9.1"
"@docusaurus/module-type-aliases" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/utils-common" "3.9.1"
"@docusaurus/mdx-loader" "3.9.2"
"@docusaurus/module-type-aliases" "3.9.2"
"@docusaurus/utils" "3.9.2"
"@docusaurus/utils-common" "3.9.2"
"@types/history" "^4.7.11"
"@types/react" "*"
"@types/react-router-config" "*"
@@ -1983,32 +1983,32 @@
tslib "^2.6.0"
utility-types "^3.10.0"
"@docusaurus/theme-mermaid@^3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.9.1.tgz#92de20580489e05b3da6716503fda17e5a337a4d"
integrity sha512-aKMFlQfxueVBPdCdrNSshG12fOkJXSn1sb6EhI/sGn3UpiTEiazJm4QLP6NoF78mqq8O5Ar2Yll+iHWLvCsuZQ==
"@docusaurus/theme-mermaid@^3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.9.2.tgz#f065e4b4b319560ddd8c3be65ce9dd19ce1d5cc8"
integrity sha512-5vhShRDq/ntLzdInsQkTdoKWSzw8d1jB17sNPYhA/KvYYFXfuVEGHLM6nrf8MFbV8TruAHDG21Fn3W4lO8GaDw==
dependencies:
"@docusaurus/core" "3.9.1"
"@docusaurus/module-type-aliases" "3.9.1"
"@docusaurus/theme-common" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/module-type-aliases" "3.9.2"
"@docusaurus/theme-common" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
mermaid ">=11.6.0"
tslib "^2.6.0"
"@docusaurus/theme-search-algolia@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.1.tgz#2f2ad5212201a1bed3acf8527ae6d81a079e654e"
integrity sha512-WjM28bzlgfT6nHlEJemkwyGVpvGsZWPireV/w+wZ1Uo64xCZ8lNOb4xwQRukDaLSed3oPBN0gSnu06l5VuCXHg==
"@docusaurus/theme-search-algolia@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.2.tgz#420fd5b27fc1673b48151fdc9fe7167ba135ed50"
integrity sha512-GBDSFNwjnh5/LdkxCKQHkgO2pIMX1447BxYUBG2wBiajS21uj64a+gH/qlbQjDLxmGrbrllBrtJkUHxIsiwRnw==
dependencies:
"@docsearch/react" "^3.9.0 || ^4.1.0"
"@docusaurus/core" "3.9.1"
"@docusaurus/logger" "3.9.1"
"@docusaurus/plugin-content-docs" "3.9.1"
"@docusaurus/theme-common" "3.9.1"
"@docusaurus/theme-translations" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/utils-validation" "3.9.1"
"@docusaurus/core" "3.9.2"
"@docusaurus/logger" "3.9.2"
"@docusaurus/plugin-content-docs" "3.9.2"
"@docusaurus/theme-common" "3.9.2"
"@docusaurus/theme-translations" "3.9.2"
"@docusaurus/utils" "3.9.2"
"@docusaurus/utils-validation" "3.9.2"
algoliasearch "^5.37.0"
algoliasearch-helper "^3.26.0"
clsx "^2.0.0"
@@ -2018,23 +2018,23 @@
tslib "^2.6.0"
utility-types "^3.10.0"
"@docusaurus/theme-translations@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.9.1.tgz#189f1942d0178bc0da659db88c07682c7d7191ee"
integrity sha512-mUQd49BSGKTiM6vP9+JFgRJL28lMIN3PUvXjF3rzuOHMByUZUBNwCt26Z23GkKiSIOrRkjKoaBNTipR/MHdYSQ==
"@docusaurus/theme-translations@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.9.2.tgz#238cd69c2da92d612be3d3b4f95944c1d0f1e041"
integrity sha512-vIryvpP18ON9T9rjgMRFLr2xJVDpw1rtagEGf8Ccce4CkTrvM/fRB8N2nyWYOW5u3DdjkwKw5fBa+3tbn9P4PA==
dependencies:
fs-extra "^11.1.1"
tslib "^2.6.0"
"@docusaurus/tsconfig@^3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/tsconfig/-/tsconfig-3.9.1.tgz#a39cb74021f16dd4794db9182de812303817528e"
integrity sha512-stdzM1dNDgRO0OvxeznXlE3N1igUoeHPNJjiKqyffLizgpVgNXJBAWeG6fuoYiCH4udGUBqy2dyM+1+kG2/UPQ==
"@docusaurus/tsconfig@^3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/tsconfig/-/tsconfig-3.9.2.tgz#7f440e0ae665b841e1d487749037f26a0275f9c1"
integrity sha512-j6/Fp4Rlpxsc632cnRnl5HpOWeb6ZKssDj6/XzzAzVGXXfm9Eptx3rxCC+fDzySn9fHTS+CWJjPineCR1bB5WQ==
"@docusaurus/types@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.9.1.tgz#e4fdaf0b91ea014a6aae0d8b62d59f3f020117b6"
integrity sha512-ElekJ29sk39s5LTEZMByY1c2oH9FMtw7KbWFU3BtuQ1TytfIK39HhUivDEJvm5KCLyEnnfUZlvSNDXeyk0vzAA==
"@docusaurus/types@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.9.2.tgz#e482cf18faea0d1fa5ce0e3f1e28e0f32d2593eb"
integrity sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==
dependencies:
"@mdx-js/mdx" "^3.0.0"
"@types/history" "^4.7.11"
@@ -2047,36 +2047,36 @@
webpack "^5.95.0"
webpack-merge "^5.9.0"
"@docusaurus/utils-common@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.9.1.tgz#202778391caed923c2527a166a3aae3a22b2dcad"
integrity sha512-4M1u5Q8Zn2CYL2TJ864M51FV4YlxyGyfC3x+7CLuR6xsyTVNBNU4QMcPgsTHRS9J2+X6Lq7MyH6hiWXyi/sXUQ==
"@docusaurus/utils-common@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.9.2.tgz#e89bfcf43d66359f43df45293fcdf22814847460"
integrity sha512-I53UC1QctruA6SWLvbjbhCpAw7+X7PePoe5pYcwTOEXD/PxeP8LnECAhTHHwWCblyUX5bMi4QLRkxvyZ+IT8Aw==
dependencies:
"@docusaurus/types" "3.9.1"
"@docusaurus/types" "3.9.2"
tslib "^2.6.0"
"@docusaurus/utils-validation@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.9.1.tgz#8f9816b31ffb647539881f3c153d46f54e6399f7"
integrity sha512-5bzab5si3E1udrlZuVGR17857Lfwe8iFPoy5AvMP9PXqDfoyIKT7gDQgAmxdRDMurgHaJlyhXEHHdzDKkOxxZQ==
"@docusaurus/utils-validation@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.9.2.tgz#04aec285604790806e2fc5aa90aa950dc7ba75ae"
integrity sha512-l7yk3X5VnNmATbwijJkexdhulNsQaNDwoagiwujXoxFbWLcxHQqNQ+c/IAlzrfMMOfa/8xSBZ7KEKDesE/2J7A==
dependencies:
"@docusaurus/logger" "3.9.1"
"@docusaurus/utils" "3.9.1"
"@docusaurus/utils-common" "3.9.1"
"@docusaurus/logger" "3.9.2"
"@docusaurus/utils" "3.9.2"
"@docusaurus/utils-common" "3.9.2"
fs-extra "^11.2.0"
joi "^17.9.2"
js-yaml "^4.1.0"
lodash "^4.17.21"
tslib "^2.6.0"
"@docusaurus/utils@3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.9.1.tgz#9b78849a2be5e3023580b800409aae36a0da6dc8"
integrity sha512-YAL4yhhWLl9DXuf5MVig260a6INz4MehrBGFU/CZu8yXmRiYEuQvRFWh9ZsjfAOyaG7za1MNmBVZ4VVAi/CiJA==
"@docusaurus/utils@3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.9.2.tgz#ffab7922631c7e0febcb54e6d499f648bf8a89eb"
integrity sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ==
dependencies:
"@docusaurus/logger" "3.9.1"
"@docusaurus/types" "3.9.1"
"@docusaurus/utils-common" "3.9.1"
"@docusaurus/logger" "3.9.2"
"@docusaurus/types" "3.9.2"
"@docusaurus/utils-common" "3.9.2"
escape-string-regexp "^4.0.0"
execa "5.1.1"
file-loader "^6.2.0"
@@ -2428,19 +2428,19 @@
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0"
integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
"@eslint/config-array@^0.21.0":
version "0.21.0"
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.0.tgz#abdbcbd16b124c638081766392a4d6b509f72636"
integrity sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==
"@eslint/config-array@^0.21.1":
version "0.21.1"
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713"
integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==
dependencies:
"@eslint/object-schema" "^2.1.6"
"@eslint/object-schema" "^2.1.7"
debug "^4.3.1"
minimatch "^3.1.2"
"@eslint/config-helpers@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.0.tgz#e9f94ba3b5b875e32205cb83fece18e64486e9e6"
integrity sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==
"@eslint/config-helpers@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.1.tgz#7d173a1a35fe256f0989a0fdd8d911ebbbf50037"
integrity sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==
dependencies:
"@eslint/core" "^0.16.0"
@@ -2466,15 +2466,15 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@9.37.0", "@eslint/js@^9.37.0":
version "9.37.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.37.0.tgz#0cfd5aa763fe5d1ee60bedf84cd14f54bcf9e21b"
integrity sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==
"@eslint/js@9.38.0", "@eslint/js@^9.38.0":
version "9.38.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.38.0.tgz#f7aa9c7577577f53302c1d795643589d7709ebd1"
integrity sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==
"@eslint/object-schema@^2.1.6":
version "2.1.6"
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f"
integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==
"@eslint/object-schema@^2.1.7":
version "2.1.7"
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad"
integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==
"@eslint/plugin-kit@^0.4.0":
version "0.4.0"
@@ -2796,14 +2796,13 @@
classnames "^2.3.2"
rc-util "^5.24.4"
"@rc-component/qrcode@~1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@rc-component/qrcode/-/qrcode-1.0.0.tgz#48a8de5eb11d0e65926f1377c4b1ef4c888997f5"
integrity sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==
"@rc-component/qrcode@~1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@rc-component/qrcode/-/qrcode-1.0.1.tgz#98e0a79dc95f26fe211b59d04ef3312bc70dedbe"
integrity sha512-g8eeeaMyFXVlq8cZUeaxCDhfIYjpao0l9cvm5gFwKXy/Vm1yDWV7h2sjH5jHYzdFedlVKBpATFB1VKMrHzwaWQ==
dependencies:
"@babel/runtime" "^7.24.7"
classnames "^2.3.2"
rc-util "^5.38.0"
"@rc-component/tour@~1.15.1":
version "1.15.1"
@@ -4336,79 +4335,79 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@8.46.1", "@typescript-eslint/eslint-plugin@^8.37.0":
version "8.46.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz#20876354024140aabc8b400bc95735fdcade17d5"
integrity sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==
"@typescript-eslint/eslint-plugin@8.46.2", "@typescript-eslint/eslint-plugin@^8.37.0":
version "8.46.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz#dc4ab93ee3d7e6c8e38820a0d6c7c93c7183e2dc"
integrity sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==
dependencies:
"@eslint-community/regexpp" "^4.10.0"
"@typescript-eslint/scope-manager" "8.46.1"
"@typescript-eslint/type-utils" "8.46.1"
"@typescript-eslint/utils" "8.46.1"
"@typescript-eslint/visitor-keys" "8.46.1"
"@typescript-eslint/scope-manager" "8.46.2"
"@typescript-eslint/type-utils" "8.46.2"
"@typescript-eslint/utils" "8.46.2"
"@typescript-eslint/visitor-keys" "8.46.2"
graphemer "^1.4.0"
ignore "^7.0.0"
natural-compare "^1.4.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/parser@8.46.1", "@typescript-eslint/parser@^8.46.0":
version "8.46.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.46.1.tgz#81751f46800fc6b01ce1a72760cd17f06e7f395b"
integrity sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==
"@typescript-eslint/parser@8.46.2", "@typescript-eslint/parser@^8.46.0":
version "8.46.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.46.2.tgz#dd938d45d581ac8ffa9d8a418a50282b306f7ebf"
integrity sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==
dependencies:
"@typescript-eslint/scope-manager" "8.46.1"
"@typescript-eslint/types" "8.46.1"
"@typescript-eslint/typescript-estree" "8.46.1"
"@typescript-eslint/visitor-keys" "8.46.1"
"@typescript-eslint/scope-manager" "8.46.2"
"@typescript-eslint/types" "8.46.2"
"@typescript-eslint/typescript-estree" "8.46.2"
"@typescript-eslint/visitor-keys" "8.46.2"
debug "^4.3.4"
"@typescript-eslint/project-service@8.46.1":
version "8.46.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.46.1.tgz#07be0e6f27fa90a17d8e5f6996ee02329c9a8c2e"
integrity sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==
"@typescript-eslint/project-service@8.46.2":
version "8.46.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.46.2.tgz#ab2f02a0de4da6a7eeb885af5e059be57819d608"
integrity sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.46.1"
"@typescript-eslint/types" "^8.46.1"
"@typescript-eslint/tsconfig-utils" "^8.46.2"
"@typescript-eslint/types" "^8.46.2"
debug "^4.3.4"
"@typescript-eslint/scope-manager@8.46.1":
version "8.46.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.46.1.tgz#590dd2e65e95af646bdaf50adeae9af39e25e8c1"
integrity sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==
"@typescript-eslint/scope-manager@8.46.2":
version "8.46.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz#7d37df2493c404450589acb3b5d0c69cc0670a88"
integrity sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==
dependencies:
"@typescript-eslint/types" "8.46.1"
"@typescript-eslint/visitor-keys" "8.46.1"
"@typescript-eslint/types" "8.46.2"
"@typescript-eslint/visitor-keys" "8.46.2"
"@typescript-eslint/tsconfig-utils@8.46.1", "@typescript-eslint/tsconfig-utils@^8.46.1":
version "8.46.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.1.tgz#24405888560175c6c209c39df11ac06a2efef9d7"
integrity sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==
"@typescript-eslint/tsconfig-utils@8.46.2", "@typescript-eslint/tsconfig-utils@^8.46.2":
version "8.46.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz#d110451cb93bbd189865206ea37ef677c196828c"
integrity sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==
"@typescript-eslint/type-utils@8.46.1":
version "8.46.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.46.1.tgz#14d4307dd6045f6b48a888cde1513d6ec305537f"
integrity sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw==
"@typescript-eslint/type-utils@8.46.2":
version "8.46.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz#802d027864e6fb752e65425ed09f3e089fb4d384"
integrity sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==
dependencies:
"@typescript-eslint/types" "8.46.1"
"@typescript-eslint/typescript-estree" "8.46.1"
"@typescript-eslint/utils" "8.46.1"
"@typescript-eslint/types" "8.46.2"
"@typescript-eslint/typescript-estree" "8.46.2"
"@typescript-eslint/utils" "8.46.2"
debug "^4.3.4"
ts-api-utils "^2.1.0"
"@typescript-eslint/types@8.46.1", "@typescript-eslint/types@^8.46.1":
version "8.46.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.46.1.tgz#4c5479538ec10b5508b8e982e172911c987446d8"
integrity sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==
"@typescript-eslint/types@8.46.2", "@typescript-eslint/types@^8.46.2":
version "8.46.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.46.2.tgz#2bad7348511b31e6e42579820e62b73145635763"
integrity sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==
"@typescript-eslint/typescript-estree@8.46.1":
version "8.46.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.1.tgz#1c146573b942ebe609c156c217ceafdc7a88e6ed"
integrity sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==
"@typescript-eslint/typescript-estree@8.46.2":
version "8.46.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz#ab547a27e4222bb6a3281cb7e98705272e2c7d08"
integrity sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==
dependencies:
"@typescript-eslint/project-service" "8.46.1"
"@typescript-eslint/tsconfig-utils" "8.46.1"
"@typescript-eslint/types" "8.46.1"
"@typescript-eslint/visitor-keys" "8.46.1"
"@typescript-eslint/project-service" "8.46.2"
"@typescript-eslint/tsconfig-utils" "8.46.2"
"@typescript-eslint/types" "8.46.2"
"@typescript-eslint/visitor-keys" "8.46.2"
debug "^4.3.4"
fast-glob "^3.3.2"
is-glob "^4.0.3"
@@ -4416,22 +4415,22 @@
semver "^7.6.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/utils@8.46.1":
version "8.46.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.46.1.tgz#c572184d9227d66b10a954b90249a20c48b22452"
integrity sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ==
"@typescript-eslint/utils@8.46.2":
version "8.46.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.46.2.tgz#b313d33d67f9918583af205bd7bcebf20f231732"
integrity sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==
dependencies:
"@eslint-community/eslint-utils" "^4.7.0"
"@typescript-eslint/scope-manager" "8.46.1"
"@typescript-eslint/types" "8.46.1"
"@typescript-eslint/typescript-estree" "8.46.1"
"@typescript-eslint/scope-manager" "8.46.2"
"@typescript-eslint/types" "8.46.2"
"@typescript-eslint/typescript-estree" "8.46.2"
"@typescript-eslint/visitor-keys@8.46.1":
version "8.46.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.1.tgz#da35f1d58ec407419d68847cfd358b32746ac315"
integrity sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==
"@typescript-eslint/visitor-keys@8.46.2":
version "8.46.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz#803fa298948c39acf810af21bdce6f8babfa9738"
integrity sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==
dependencies:
"@typescript-eslint/types" "8.46.1"
"@typescript-eslint/types" "8.46.2"
eslint-visitor-keys "^4.2.1"
"@ungap/structured-clone@^1.0.0":
@@ -4746,10 +4745,10 @@ ansi-styles@^6.1.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
antd@^5.27.4:
version "5.27.4"
resolved "https://registry.yarnpkg.com/antd/-/antd-5.27.4.tgz#13c97deb12e6aeb43adecd23f3dbe3139a62e579"
integrity sha512-rhArohoAUCxhkPjGI/BXthOrrjaElL4Fb7d4vEHnIR3DpxFXfegd4rN21IgGdiF+Iz4EFuUZu8MdS8NuJHLSVQ==
antd@^5.27.6:
version "5.27.6"
resolved "https://registry.yarnpkg.com/antd/-/antd-5.27.6.tgz#6b7c7a87b5c696395d2aab2fdbd8409a342813e1"
integrity sha512-70HrjVbzDXvtiUQ5MP1XdNudr/wGAk9Ivaemk6f36yrAeJurJSmZ8KngOIilolLRHdGuNc6/Vk+4T1OZpSjpag==
dependencies:
"@ant-design/colors" "^7.2.1"
"@ant-design/cssinjs" "^1.23.0"
@@ -4760,7 +4759,7 @@ antd@^5.27.4:
"@babel/runtime" "^7.26.0"
"@rc-component/color-picker" "~2.0.1"
"@rc-component/mutate-observer" "^1.1.0"
"@rc-component/qrcode" "~1.0.0"
"@rc-component/qrcode" "~1.0.1"
"@rc-component/tour" "~1.15.1"
"@rc-component/trigger" "^2.3.0"
classnames "^2.5.1"
@@ -4790,7 +4789,7 @@ antd@^5.27.4:
rc-slider "~11.1.9"
rc-steps "~6.0.1"
rc-switch "~4.1.0"
rc-table "~7.53.0"
rc-table "~7.54.0"
rc-tabs "~15.7.0"
rc-textarea "~1.10.2"
rc-tooltip "~6.4.0"
@@ -5305,10 +5304,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001746, caniuse-lite@^1.0.30001750:
version "1.0.30001750"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz#c229f82930033abd1502c6f73035356cf528bfbc"
integrity sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001746, caniuse-lite@^1.0.30001751:
version "1.0.30001751"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz#dacd5d9f4baeea841641640139d2b2a4df4226ad"
integrity sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==
ccount@^2.0.0:
version "2.0.1"
@@ -6997,24 +6996,23 @@ eslint-visitor-keys@^4.2.1:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1"
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
eslint@^9.37.0:
version "9.37.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.37.0.tgz#ac0222127f76b09c0db63036f4fe289562072d74"
integrity sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==
eslint@^9.38.0:
version "9.38.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.38.0.tgz#3957d2af804e5cf6cc503c618f60acc71acb2e7e"
integrity sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==
dependencies:
"@eslint-community/eslint-utils" "^4.8.0"
"@eslint-community/regexpp" "^4.12.1"
"@eslint/config-array" "^0.21.0"
"@eslint/config-helpers" "^0.4.0"
"@eslint/config-array" "^0.21.1"
"@eslint/config-helpers" "^0.4.1"
"@eslint/core" "^0.16.0"
"@eslint/eslintrc" "^3.3.1"
"@eslint/js" "9.37.0"
"@eslint/js" "9.38.0"
"@eslint/plugin-kit" "^0.4.0"
"@humanfs/node" "^0.16.6"
"@humanwhocodes/module-importer" "^1.0.1"
"@humanwhocodes/retry" "^0.4.2"
"@types/estree" "^1.0.6"
"@types/json-schema" "^7.0.15"
ajv "^6.12.4"
chalk "^4.0.0"
cross-spawn "^7.0.6"
@@ -11729,10 +11727,10 @@ rc-switch@~4.1.0:
classnames "^2.2.1"
rc-util "^5.30.0"
rc-table@~7.53.0:
version "7.53.1"
resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.53.1.tgz#b891aa39e9d1d944711f018692d2c52013afc90f"
integrity sha512-firAd7Z+liqIDS5TubJ1qqcoBd6YcANLKWQDZhFf3rfoOTt/UNPj4n3O+2vhl+z4QMqwPEUVAil661WHA8H8Aw==
rc-table@~7.54.0:
version "7.54.0"
resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.54.0.tgz#dedd4ea18d1189f2acdf90a80f04d8ca0111e16a"
integrity sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==
dependencies:
"@babel/runtime" "^7.10.1"
"@rc-component/context" "^1.4.0"
@@ -13259,10 +13257,10 @@ swagger-client@^3.35.7:
ramda "^0.30.1"
ramda-adjunct "^5.1.0"
swagger-ui-react@^5.29.4:
version "5.29.4"
resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.29.4.tgz#ff061f301b46849a93c53b2490f7cebbea401832"
integrity sha512-lBBRq75dHWnuN1uuxGOvJkoYr8F+AuZpOSUdHez9st7GlHKTPiBz5bOFONXPzbLKDWrwsPTQ/zArBSDjfqtVow==
swagger-ui-react@^5.29.5:
version "5.29.5"
resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.29.5.tgz#8c6eafebb75972c15a9f3e24627caec10cc32cbe"
integrity sha512-D0YbsDhi4F38HsY5p1DjzuNduU/fVQxtqm3v0o2dRTF5BbLJYRSgjMZ79jejG4q3nNw4kuouCKKiq5xqCLjWrQ==
dependencies:
"@babel/runtime-corejs3" "^7.27.1"
"@scarf/scarf" "=1.4.0"
@@ -13590,15 +13588,15 @@ types-ramda@^0.30.1:
dependencies:
ts-toolbelt "^9.6.0"
typescript-eslint@^8.46.1:
version "8.46.1"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.46.1.tgz#baeb322ee83ca566a8cf1f6403847694a3acd44a"
integrity sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==
typescript-eslint@^8.46.2:
version "8.46.2"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.46.2.tgz#da1adec683ba93a1b6c3850a4efb0922ffbc627d"
integrity sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg==
dependencies:
"@typescript-eslint/eslint-plugin" "8.46.1"
"@typescript-eslint/parser" "8.46.1"
"@typescript-eslint/typescript-estree" "8.46.1"
"@typescript-eslint/utils" "8.46.1"
"@typescript-eslint/eslint-plugin" "8.46.2"
"@typescript-eslint/parser" "8.46.2"
"@typescript-eslint/typescript-estree" "8.46.2"
"@typescript-eslint/utils" "8.46.2"
typescript@~5.9.3:
version "5.9.3"
+177 -264
View File
@@ -62,14 +62,14 @@
"content-disposition": "^0.5.4",
"d3-color": "^3.1.0",
"d3-scale": "^2.1.2",
"dayjs": "^1.11.13",
"dayjs": "^1.11.18",
"dom-to-image-more": "^3.6.0",
"dom-to-pdf": "^0.3.2",
"echarts": "^5.6.0",
"eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings",
"fast-glob": "^3.3.2",
"fs-extra": "^11.2.0",
"fuse.js": "^7.0.0",
"fuse.js": "^7.1.0",
"geolib": "^2.0.24",
"geostyler": "^14.1.3",
"geostyler-data": "^1.1.0",
@@ -112,7 +112,7 @@
"react-loadable": "^5.5.0",
"react-redux": "^7.2.9",
"react-resize-detector": "^7.1.2",
"react-reverse-portal": "^2.1.2",
"react-reverse-portal": "^2.3.0",
"react-router-dom": "^5.3.4",
"react-search-input": "^0.11.3",
"react-sortable-hoc": "^2.0.0",
@@ -139,10 +139,10 @@
},
"devDependencies": {
"@applitools/eyes-storybook": "^3.60.0",
"@babel/cli": "^7.27.2",
"@babel/compat-data": "^7.28.0",
"@babel/cli": "^7.28.3",
"@babel/compat-data": "^7.28.4",
"@babel/core": "^7.28.3",
"@babel/eslint-parser": "^7.25.9",
"@babel/eslint-parser": "^7.28.4",
"@babel/node": "^7.22.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
@@ -161,7 +161,7 @@
"@hot-loader/react-dom": "^17.0.2",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@mihkeleidast/storybook-addon-source": "^1.0.1",
"@playwright/test": "^1.49.1",
"@playwright/test": "^1.56.0",
"@storybook/addon-actions": "8.1.11",
"@storybook/addon-controls": "8.1.11",
"@storybook/addon-essentials": "8.1.11",
@@ -184,7 +184,7 @@
"@types/json-bigint": "^1.0.4",
"@types/math-expression-evaluator": "^1.3.3",
"@types/mousetrap": "^1.6.15",
"@types/node": "^24.6.2",
"@types/node": "^24.8.1",
"@types/react": "^17.0.83",
"@types/react-dom": "^17.0.26",
"@types/react-json-tree": "^0.13.0",
@@ -210,7 +210,7 @@
"babel-plugin-lodash": "^3.3.4",
"babel-plugin-typescript-to-proptypes": "^2.0.0",
"cheerio": "1.1.0",
"copy-webpack-plugin": "^13.0.0",
"copy-webpack-plugin": "^13.0.1",
"cross-env": "^10.0.0",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.2",
@@ -228,7 +228,7 @@
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-no-only-tests": "^3.3.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-prefer-function-component": "^3.3.0",
@@ -239,7 +239,7 @@
"fetch-mock": "^11.1.5",
"fork-ts-checker-webpack-plugin": "^9.1.0",
"history": "^5.3.0",
"html-webpack-plugin": "^5.6.3",
"html-webpack-plugin": "^5.6.4",
"imports-loader": "^5.0.0",
"jest": "^30.0.2",
"jest-environment-jsdom": "^29.7.0",
@@ -251,7 +251,7 @@
"open-cli": "^8.0.0",
"po2json": "^0.4.5",
"prettier": "3.6.2",
"prettier-plugin-packagejson": "^2.5.3",
"prettier-plugin-packagejson": "^2.5.19",
"process": "^0.11.10",
"react-resizable": "^3.0.5",
"redux-mock-store": "^1.5.4",
@@ -262,13 +262,13 @@
"storybook": "8.1.11",
"style-loader": "^4.0.0",
"thread-loader": "^4.0.4",
"ts-jest": "^29.4.0",
"ts-jest": "^29.4.5",
"ts-loader": "^9.5.1",
"tscw-config": "^1.1.2",
"tsx": "^4.20.3",
"typescript": "5.4.5",
"vm-browserify": "^1.1.2",
"webpack": "^5.102.0",
"webpack": "^5.102.1",
"webpack-bundle-analyzer": "^4.10.1",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.2",
@@ -1106,13 +1106,13 @@
}
},
"node_modules/@babel/cli": {
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.27.2.tgz",
"integrity": "sha512-cfd7DnGlhH6OIyuPSSj3vcfIdnbXukhAyKY8NaZrFadC7pXyL9mOL5WgjcptiEJLi5k3j8aYvLIVCzezrWTaiA==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.28.3.tgz",
"integrity": "sha512-n1RU5vuCX0CsaqaXm9I0KUCNKNQMy5epmzl/xdSSm70bSqhg9GWhgeosypyQLc0bK24+Xpk1WGzZlI9pJtkZdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.25",
"@jridgewell/trace-mapping": "^0.3.28",
"commander": "^6.2.0",
"convert-source-map": "^2.0.0",
"fs-readdir-recursive": "^1.1.0",
@@ -1150,9 +1150,9 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
"integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz",
"integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1201,9 +1201,9 @@
}
},
"node_modules/@babel/eslint-parser": {
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.26.5.tgz",
"integrity": "sha512-Kkm8C8uxI842AwQADxl0GbcG1rupELYLShazYEZO/2DYjhyWXJIOUVOE3tBYm6JXzUCNJOZEzqc4rCW/jsEQYQ==",
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.4.tgz",
"integrity": "sha512-Aa+yDiH87980jR6zvRfFuCR1+dLb00vBydhTL+zI992Rz/wQhSvuxjmOOuJOgO3XmakO6RykRGD2S1mq1AtgHA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4125,6 +4125,12 @@
"mjolnir.js": "^3.0.0"
}
},
"node_modules/@deck.gl/core/node_modules/@luma.gl/constants": {
"version": "9.1.10",
"resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.1.10.tgz",
"integrity": "sha512-O4Nx8UbWmrHHZ7ihKB8WiscX1cz05l1KvKorYTgq+xeXwz2Beh3MkXBMnA46uuyEtimN945OEdYshZnbh80jyw==",
"license": "MIT"
},
"node_modules/@deck.gl/extensions": {
"version": "9.1.13",
"resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-9.1.13.tgz",
@@ -9000,9 +9006,9 @@
}
},
"node_modules/@luma.gl/constants": {
"version": "9.1.9",
"resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.1.9.tgz",
"integrity": "sha512-yc9fml04OeTTcwK+7gmDMxoLQ67j4ZiAFXjmYvPomYyBVzS0NZxTDuwcCBmnxjLOiroOZW8FRRrVc/yOiFug2w==",
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.2.2.tgz",
"integrity": "sha512-XURMF0gSh0ImZltYa/PCe9KgmopQJiOA6y1m1PxDxJY8OCLma7ZJyvomLn7TQBvPtWTYZsibTW7blu7RwThsaQ==",
"license": "MIT"
},
"node_modules/@luma.gl/core": {
@@ -9078,6 +9084,12 @@
"@luma.gl/core": "^9.1.0"
}
},
"node_modules/@luma.gl/webgl/node_modules/@luma.gl/constants": {
"version": "9.1.9",
"resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.1.9.tgz",
"integrity": "sha512-yc9fml04OeTTcwK+7gmDMxoLQ67j4ZiAFXjmYvPomYyBVzS0NZxTDuwcCBmnxjLOiroOZW8FRRrVc/yOiFug2w==",
"license": "MIT"
},
"node_modules/@mapbox/extent": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@mapbox/extent/-/extent-0.4.0.tgz",
@@ -10577,26 +10589,26 @@
}
},
"node_modules/@pkgr/core": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
"integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/@playwright/test": {
"version": "1.55.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz",
"integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==",
"version": "1.56.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.0.tgz",
"integrity": "sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.55.0"
"playwright": "1.56.0"
},
"bin": {
"playwright": "cli.js"
@@ -16371,12 +16383,12 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.6.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz",
"integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==",
"version": "24.8.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.8.1.tgz",
"integrity": "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.13.0"
"undici-types": "~7.14.0"
}
},
"node_modules/@types/node-forge": {
@@ -19140,9 +19152,9 @@
}
},
"node_modules/ace-builds": {
"version": "1.43.1",
"resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.43.1.tgz",
"integrity": "sha512-n9/n+zBhbbkEJjU0FJ4wWAZBDl5G8WYzg4+uIjSER/U3wSSSSVo52W4sco4Jryg11JAJvorExxMr3GDINqtjdA==",
"version": "1.43.4",
"resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.43.4.tgz",
"integrity": "sha512-8hAxVfo2ImICd69BWlZwZlxe9rxDGDjuUhh+WeWgGDvfBCE+r3lkynkQvIovDz4jcMi8O7bsEaFygaDT+h9sBA==",
"license": "BSD-3-Clause",
"peer": true
},
@@ -20893,9 +20905,9 @@
"license": "MIT"
},
"node_modules/browserslist": {
"version": "4.26.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz",
"integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==",
"version": "4.26.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
"integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
"dev": true,
"funding": [
{
@@ -20913,9 +20925,9 @@
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001741",
"electron-to-chromium": "^1.5.218",
"baseline-browser-mapping": "^2.8.9",
"caniuse-lite": "^1.0.30001746",
"electron-to-chromium": "^1.5.227",
"node-releases": "^2.0.21",
"update-browserslist-db": "^1.1.3"
},
@@ -22723,9 +22735,9 @@
}
},
"node_modules/copy-webpack-plugin": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.0.tgz",
"integrity": "sha512-FgR/h5a6hzJqATDGd9YG41SeDViH+0bkHn6WNXCi5zKAZkeESeSxLySSsFLHqLEVCh0E+rITmCf0dusXWYukeQ==",
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz",
"integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -23925,9 +23937,9 @@
}
},
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
"version": "1.11.18",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz",
"integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
"license": "MIT"
},
"node_modules/debounce": {
@@ -24352,13 +24364,16 @@
}
},
"node_modules/detect-indent": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.1.tgz",
"integrity": "sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==",
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.2.tgz",
"integrity": "sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/detect-newline": {
@@ -26343,14 +26358,14 @@
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz",
"integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==",
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz",
"integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.9.1"
"synckit": "^0.11.7"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -26361,7 +26376,7 @@
"peerDependencies": {
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
"eslint-config-prettier": "*",
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
@@ -28414,9 +28429,9 @@
}
},
"node_modules/fuse.js": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz",
"integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz",
"integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=10"
@@ -29526,9 +29541,9 @@
}
},
"node_modules/git-hooks-list": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-3.1.0.tgz",
"integrity": "sha512-LF8VeHeR7v+wAbXqfgRlTSX/1BJR9Q1vEMR8JAz1cEg6GX07+zyj3sAdDvYjj/xnlIfVuGgj4qBei1K3hKH+PA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-4.1.1.tgz",
"integrity": "sha512-cmP497iLq54AZnv4YRAEMnEyQ1eIn4tGKbmswqwmFV4GBnAqE8NLtWxxdXa++AalfgL5EBH4IxTPyquEuGY/jA==",
"dev": true,
"license": "MIT",
"funding": {
@@ -30871,9 +30886,9 @@
}
},
"node_modules/html-webpack-plugin": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz",
"integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==",
"version": "5.6.4",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.4.tgz",
"integrity": "sha512-V/PZeWsqhfpE27nKeX9EO2sbR+D17A+tLf6qU+ht66jdUsN0QLKJN27Z+1+gHrVMKgndBahes0PU6rRihDgHTw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -35729,19 +35744,6 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-snapshot/node_modules/@pkgr/core": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz",
"integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/jest-snapshot/node_modules/@sinclair/typebox": {
"version": "0.34.37",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz",
@@ -35934,22 +35936,6 @@
"node": ">=8"
}
},
"node_modules/jest-snapshot/node_modules/synckit": {
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz",
"integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.4"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"node_modules/jest-util": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
@@ -46079,13 +46065,13 @@
"license": "MIT"
},
"node_modules/playwright": {
"version": "1.55.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz",
"integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==",
"version": "1.56.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.0.tgz",
"integrity": "sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.55.0"
"playwright-core": "1.56.0"
},
"bin": {
"playwright": "cli.js"
@@ -46098,9 +46084,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.55.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz",
"integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==",
"version": "1.56.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0.tgz",
"integrity": "sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -46863,14 +46849,14 @@
}
},
"node_modules/prettier-plugin-packagejson": {
"version": "2.5.8",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.8.tgz",
"integrity": "sha512-BaGOF63I0IJZoudxpuQe17naV93BRtK8b3byWktkJReKEMX9CC4qdGUzThPDVO/AUhPzlqDiAXbp18U6X8wLKA==",
"version": "2.5.19",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.19.tgz",
"integrity": "sha512-Qsqp4+jsZbKMpEGZB1UP1pxeAT8sCzne2IwnKkr+QhUe665EXUo3BAvTf1kAPCqyMv9kg3ZmO0+7eOni/C6Uag==",
"dev": true,
"license": "MIT",
"dependencies": {
"sort-package-json": "2.14.0",
"synckit": "0.9.2"
"sort-package-json": "3.4.0",
"synckit": "0.11.11"
},
"peerDependencies": {
"prettier": ">= 1.16.0"
@@ -48969,13 +48955,13 @@
}
},
"node_modules/react-reverse-portal": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/react-reverse-portal/-/react-reverse-portal-2.1.2.tgz",
"integrity": "sha512-li4puNtBmMMJhtI+IVxeSX0RvK1ft8qjPSbCih4OKQ/YUIcROc31Nmo22gv94hTx8EUfR7fzZY47RuZF2YRMdQ==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-reverse-portal/-/react-reverse-portal-2.3.0.tgz",
"integrity": "sha512-kvbPfLPKg6Y3S6tVq83us2RghvDpOS4GcJxbI7cZ0V0tuzUaSzblRIhVnKLOucfqF4lN/i9oWvEmpEi6bAOYlQ==",
"license": "Apache-2.0",
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
"react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-router": {
@@ -51689,9 +51675,9 @@
}
},
"node_modules/schema-utils": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -53001,23 +52987,25 @@
"license": "MIT"
},
"node_modules/sort-package-json": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.14.0.tgz",
"integrity": "sha512-xBRdmMjFB/KW3l51mP31dhlaiFmqkHLfWTfZAno8prb/wbDxwBPWFpxB16GZbiPbYr3wL41H8Kx22QIDWRe8WQ==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.4.0.tgz",
"integrity": "sha512-97oFRRMM2/Js4oEA9LJhjyMlde+2ewpZQf53pgue27UkbEXfHJnDzHlUxQ/DWUkzqmp7DFwJp8D+wi/TYeQhpA==",
"dev": true,
"license": "MIT",
"dependencies": {
"detect-indent": "^7.0.1",
"detect-newline": "^4.0.0",
"get-stdin": "^9.0.0",
"git-hooks-list": "^3.0.0",
"detect-newline": "^4.0.1",
"git-hooks-list": "^4.0.0",
"is-plain-obj": "^4.1.0",
"semver": "^7.6.0",
"semver": "^7.7.1",
"sort-object-keys": "^1.1.3",
"tinyglobby": "^0.2.9"
"tinyglobby": "^0.2.12"
},
"bin": {
"sort-package-json": "cli.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/sort-package-json/node_modules/detect-newline": {
@@ -53046,6 +53034,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/sort-package-json/node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/source-list-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
@@ -54355,26 +54356,25 @@
"license": "MIT"
},
"node_modules/synckit": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
"integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.1.0",
"tslib": "^2.6.2"
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
"url": "https://opencollective.com/synckit"
}
},
"node_modules/tapable": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz",
"integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -55359,19 +55359,19 @@
}
},
"node_modules/ts-jest": {
"version": "29.4.0",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.0.tgz",
"integrity": "sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==",
"version": "29.4.5",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz",
"integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"bs-logger": "^0.2.6",
"ejs": "^3.1.10",
"fast-json-stable-stringify": "^2.1.0",
"handlebars": "^4.7.8",
"json5": "^2.2.3",
"lodash.memoize": "^4.1.2",
"make-error": "^1.3.6",
"semver": "^7.7.2",
"semver": "^7.7.3",
"type-fest": "^4.41.0",
"yargs-parser": "^21.1.1"
},
@@ -55412,9 +55412,9 @@
}
},
"node_modules/ts-jest/node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"dev": true,
"license": "ISC",
"bin": {
@@ -56413,9 +56413,9 @@
}
},
"node_modules/undici-types": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz",
"integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==",
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz",
"integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
"license": "MIT"
},
"node_modules/unicode-canonical-property-names-ecmascript": {
@@ -57554,9 +57554,9 @@
}
},
"node_modules/webpack": {
"version": "5.102.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.0.tgz",
"integrity": "sha512-hUtqAR3ZLVEYDEABdBioQCIqSoguHbFn1K7WlPPWSuXmx0031BD73PSE35jKyftdSh4YLDoQNgK4pqBt5Q82MA==",
"version": "5.102.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz",
"integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -57568,7 +57568,7 @@
"@webassemblyjs/wasm-parser": "^1.14.1",
"acorn": "^8.15.0",
"acorn-import-phases": "^1.0.3",
"browserslist": "^4.24.5",
"browserslist": "^4.26.3",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.17.3",
"es-module-lexer": "^1.2.1",
@@ -57580,8 +57580,8 @@
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
"schema-utils": "^4.3.2",
"tapable": "^2.2.3",
"schema-utils": "^4.3.3",
"tapable": "^2.3.0",
"terser-webpack-plugin": "^5.3.11",
"watchpack": "^2.4.4",
"webpack-sources": "^3.3.3"
@@ -59296,7 +59296,7 @@
"version": "0.20.3",
"license": "Apache-2.0",
"dependencies": {
"chalk": "^5.4.1",
"chalk": "^5.6.2",
"lodash-es": "^4.17.21",
"yeoman-generator": "^7.5.1",
"yosay": "^3.0.0"
@@ -59891,19 +59891,6 @@
"@octokit/openapi-types": "^25.1.0"
}
},
"packages/generator-superset/node_modules/@pkgr/core": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"packages/generator-superset/node_modules/@sinclair/typebox": {
"version": "0.34.38",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz",
@@ -59977,9 +59964,9 @@
}
},
"packages/generator-superset/node_modules/chalk": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
@@ -61279,22 +61266,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/generator-superset/node_modules/synckit": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"packages/generator-superset/node_modules/universal-user-agent": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
@@ -61343,7 +61314,7 @@
"version": "0.0.1-rc5",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/cli": "^7.28.3",
"@babel/core": "^7.28.3",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
@@ -64067,11 +64038,11 @@
"@ant-design/icons": "^5.2.6",
"@apache-superset/core": "*",
"@babel/runtime": "^7.28.4",
"@fontsource/fira-code": "^5.2.6",
"@fontsource/fira-code": "^5.2.7",
"@fontsource/inter": "^5.2.6",
"@types/json-bigint": "^1.0.4",
"@visx/responsive": "^3.12.0",
"ace-builds": "^1.43.3",
"ace-builds": "^1.43.4",
"ag-grid-community": "34.2.0",
"ag-grid-react": "34.2.0",
"brace": "^0.11.1",
@@ -64083,7 +64054,7 @@
"d3-scale": "^4.0.2",
"d3-time": "^3.1.0",
"d3-time-format": "^4.1.0",
"dayjs": "^1.11.13",
"dayjs": "^1.11.18",
"dompurify": "^3.2.4",
"fetch-retry": "^6.0.0",
"handlebars": "^4.7.8",
@@ -64107,7 +64078,7 @@
"reselect": "^5.1.1",
"rison": "^0.1.1",
"seedrandom": "^3.0.5",
"xss": "^1.0.14"
"xss": "^1.0.15"
},
"devDependencies": {
"@emotion/styled": "^11.14.1",
@@ -64119,7 +64090,7 @@
"@types/jquery": "^3.5.33",
"@types/lodash": "^4.17.20",
"@types/math-expression-evaluator": "^1.3.3",
"@types/node": "^24.6.2",
"@types/node": "^24.8.1",
"@types/prop-types": "^15.7.15",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/react-table": "^7.7.20",
@@ -65950,7 +65921,7 @@
"@deck.gl/geo-layers": "^9.1.13",
"@deck.gl/layers": "^9.1.13",
"@deck.gl/react": "^9.1.14",
"@luma.gl/constants": "^9.1.9",
"@luma.gl/constants": "^9.2.2",
"@luma.gl/core": "^9.1.9",
"@luma.gl/engine": "^9.1.9",
"@luma.gl/shadertools": "^9.1.9",
@@ -65964,7 +65935,7 @@
"d3-array": "^1.2.4",
"d3-color": "^1.4.1",
"d3-scale": "^3.0.0",
"dayjs": "^1.11.13",
"dayjs": "^1.11.18",
"handlebars": "^4.7.8",
"lodash": "^4.17.21",
"mousetrap": "^1.6.5",
@@ -66008,6 +65979,12 @@
"@luma.gl/engine": "~9.1.9"
}
},
"plugins/legacy-preset-chart-deckgl/node_modules/@deck.gl/aggregation-layers/node_modules/@luma.gl/constants": {
"version": "9.1.10",
"resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.1.10.tgz",
"integrity": "sha512-O4Nx8UbWmrHHZ7ihKB8WiscX1cz05l1KvKorYTgq+xeXwz2Beh3MkXBMnA46uuyEtimN945OEdYshZnbh80jyw==",
"license": "MIT"
},
"plugins/legacy-preset-chart-deckgl/node_modules/@mapbox/tiny-sdf": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz",
@@ -66098,7 +66075,7 @@
"d3": "^3.5.17",
"d3-tip": "^0.9.1",
"dayjs": "^1.11.18",
"dompurify": "^3.2.7",
"dompurify": "^3.3.0",
"fast-safe-stringify": "^2.1.1",
"lodash": "^4.17.21",
"nvd3-fork": "^2.0.5",
@@ -66111,16 +66088,10 @@
"react": "^17.0.2"
}
},
"plugins/legacy-preset-chart-nvd3/node_modules/dayjs": {
"version": "1.11.18",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz",
"integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
"license": "MIT"
},
"plugins/legacy-preset-chart-nvd3/node_modules/dompurify": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz",
"integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -66192,7 +66163,7 @@
"dependencies": {
"@types/react-redux": "^7.1.34",
"d3-array": "^1.2.0",
"dayjs": "^1.11.13",
"dayjs": "^1.11.18",
"lodash": "^4.17.21"
},
"peerDependencies": {
@@ -66538,19 +66509,6 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"plugins/plugin-chart-handlebars/node_modules/@pkgr/core": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"plugins/plugin-chart-handlebars/node_modules/@sinclair/typebox": {
"version": "0.34.37",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz",
@@ -67447,22 +67405,6 @@
"license": "BSD-3-Clause",
"peer": true
},
"plugins/plugin-chart-handlebars/node_modules/synckit": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"plugins/plugin-chart-pivot-table": {
"name": "@superset-ui/plugin-chart-pivot-table",
"version": "0.20.3",
@@ -67800,19 +67742,6 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"plugins/plugin-chart-pivot-table/node_modules/@pkgr/core": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"plugins/plugin-chart-pivot-table/node_modules/@sinclair/typebox": {
"version": "0.34.41",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz",
@@ -68672,22 +68601,6 @@
"source-map": "^0.6.0"
}
},
"plugins/plugin-chart-pivot-table/node_modules/synckit": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"plugins/plugin-chart-table": {
"name": "@superset-ui/plugin-chart-table",
"version": "0.20.3",
+14 -14
View File
@@ -135,14 +135,14 @@
"content-disposition": "^0.5.4",
"d3-color": "^3.1.0",
"d3-scale": "^2.1.2",
"dayjs": "^1.11.13",
"dayjs": "^1.11.18",
"dom-to-image-more": "^3.6.0",
"dom-to-pdf": "^0.3.2",
"echarts": "^5.6.0",
"eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings",
"fast-glob": "^3.3.2",
"fs-extra": "^11.2.0",
"fuse.js": "^7.0.0",
"fuse.js": "^7.1.0",
"geolib": "^2.0.24",
"geostyler": "^14.1.3",
"geostyler-data": "^1.1.0",
@@ -185,7 +185,7 @@
"react-loadable": "^5.5.0",
"react-redux": "^7.2.9",
"react-resize-detector": "^7.1.2",
"react-reverse-portal": "^2.1.2",
"react-reverse-portal": "^2.3.0",
"react-router-dom": "^5.3.4",
"react-search-input": "^0.11.3",
"react-sortable-hoc": "^2.0.0",
@@ -212,10 +212,10 @@
},
"devDependencies": {
"@applitools/eyes-storybook": "^3.60.0",
"@babel/cli": "^7.27.2",
"@babel/compat-data": "^7.28.0",
"@babel/cli": "^7.28.3",
"@babel/compat-data": "^7.28.4",
"@babel/core": "^7.28.3",
"@babel/eslint-parser": "^7.25.9",
"@babel/eslint-parser": "^7.28.4",
"@babel/node": "^7.22.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
@@ -234,7 +234,7 @@
"@hot-loader/react-dom": "^17.0.2",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@mihkeleidast/storybook-addon-source": "^1.0.1",
"@playwright/test": "^1.49.1",
"@playwright/test": "^1.56.0",
"@storybook/addon-actions": "8.1.11",
"@storybook/addon-controls": "8.1.11",
"@storybook/addon-essentials": "8.1.11",
@@ -257,7 +257,7 @@
"@types/json-bigint": "^1.0.4",
"@types/math-expression-evaluator": "^1.3.3",
"@types/mousetrap": "^1.6.15",
"@types/node": "^24.6.2",
"@types/node": "^24.8.1",
"@types/react": "^17.0.83",
"@types/react-dom": "^17.0.26",
"@types/react-json-tree": "^0.13.0",
@@ -283,7 +283,7 @@
"babel-plugin-lodash": "^3.3.4",
"babel-plugin-typescript-to-proptypes": "^2.0.0",
"cheerio": "1.1.0",
"copy-webpack-plugin": "^13.0.0",
"copy-webpack-plugin": "^13.0.1",
"cross-env": "^10.0.0",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.2",
@@ -301,7 +301,7 @@
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-no-only-tests": "^3.3.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-prefer-function-component": "^3.3.0",
@@ -312,7 +312,7 @@
"fetch-mock": "^11.1.5",
"fork-ts-checker-webpack-plugin": "^9.1.0",
"history": "^5.3.0",
"html-webpack-plugin": "^5.6.3",
"html-webpack-plugin": "^5.6.4",
"imports-loader": "^5.0.0",
"jest": "^30.0.2",
"jest-environment-jsdom": "^29.7.0",
@@ -324,7 +324,7 @@
"open-cli": "^8.0.0",
"po2json": "^0.4.5",
"prettier": "3.6.2",
"prettier-plugin-packagejson": "^2.5.3",
"prettier-plugin-packagejson": "^2.5.19",
"process": "^0.11.10",
"react-resizable": "^3.0.5",
"redux-mock-store": "^1.5.4",
@@ -335,13 +335,13 @@
"storybook": "8.1.11",
"style-loader": "^4.0.0",
"thread-loader": "^4.0.4",
"ts-jest": "^29.4.0",
"ts-jest": "^29.4.5",
"ts-loader": "^9.5.1",
"tscw-config": "^1.1.2",
"tsx": "^4.20.3",
"typescript": "5.4.5",
"vm-browserify": "^1.1.2",
"webpack": "^5.102.0",
"webpack": "^5.102.1",
"webpack-bundle-analyzer": "^4.10.1",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.2",
@@ -28,7 +28,7 @@
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest"
},
"dependencies": {
"chalk": "^5.4.1",
"chalk": "^5.6.2",
"lodash-es": "^4.17.21",
"yeoman-generator": "^7.5.1",
"yosay": "^3.0.0"
@@ -11,7 +11,7 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/cli": "^7.28.3",
"@babel/core": "^7.28.3",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
@@ -27,10 +27,10 @@
"@apache-superset/core": "*",
"@ant-design/icons": "^5.2.6",
"@babel/runtime": "^7.28.4",
"@fontsource/fira-code": "^5.2.6",
"@fontsource/fira-code": "^5.2.7",
"@fontsource/inter": "^5.2.6",
"@types/json-bigint": "^1.0.4",
"ace-builds": "^1.43.3",
"ace-builds": "^1.43.4",
"ag-grid-community": "34.2.0",
"ag-grid-react": "34.2.0",
"brace": "^0.11.1",
@@ -38,7 +38,7 @@
"csstype": "^3.1.3",
"core-js": "^3.38.1",
"d3-format": "^1.3.2",
"dayjs": "^1.11.13",
"dayjs": "^1.11.18",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-time": "^3.1.0",
@@ -67,7 +67,7 @@
"rison": "^0.1.1",
"seedrandom": "^3.0.5",
"@visx/responsive": "^3.12.0",
"xss": "^1.0.14"
"xss": "^1.0.15"
},
"devDependencies": {
"@emotion/styled": "^11.14.1",
@@ -81,7 +81,7 @@
"@types/jquery": "^3.5.33",
"@types/lodash": "^4.17.20",
"@types/math-expression-evaluator": "^1.3.3",
"@types/node": "^24.6.2",
"@types/node": "^24.8.1",
"@types/prop-types": "^15.7.15",
"@types/rison": "0.1.0",
"@types/seedrandom": "^3.0.8",
@@ -28,6 +28,7 @@ export const StyledHeader = styled.span<{ headerPosition: string }>`
text-overflow: ellipsis;
white-space: nowrap;
margin-right: ${headerPosition === 'left' ? theme.sizeUnit * 2 : 0}px;
font-size: ${theme.fontSizeSM}px;
`}
`;
@@ -324,6 +324,7 @@ export type Query = {
schema?: string;
sql: string;
sqlEditorId: string;
sqlEditorImmutableId: string;
state: QueryState;
tab: string | null;
tempSchema: string | null;
@@ -373,6 +374,7 @@ export const testQuery: Query = {
dbId: 1,
sql: 'SELECT * FROM something',
sqlEditorId: 'dfsadfs',
sqlEditorImmutableId: 'immutableId2353',
tab: 'unimportant',
tempTable: '',
ctas: false,
@@ -402,7 +402,7 @@ export interface ThemeContextType {
setTheme: (config: AnyThemeConfig) => void;
setThemeMode: (newMode: ThemeMode) => void;
resetTheme: () => void;
setTemporaryTheme: (config: AnyThemeConfig) => void;
setTemporaryTheme: (config: AnyThemeConfig, themeId?: number | null) => void;
clearLocalOverrides: () => void;
getCurrentCrudThemeId: () => string | null;
hasDevOverride: () => boolean;
@@ -410,6 +410,7 @@ export interface ThemeContextType {
canSetTheme: () => boolean;
canDetectOSPreference: () => boolean;
createDashboardThemeProvider: (themeId: string) => Promise<Theme | null>;
getAppliedThemeId: () => number | null;
}
/**
@@ -29,7 +29,7 @@
"@deck.gl/geo-layers": "^9.1.13",
"@deck.gl/layers": "^9.1.13",
"@deck.gl/react": "^9.1.14",
"@luma.gl/constants": "^9.1.9",
"@luma.gl/constants": "^9.2.2",
"@luma.gl/core": "^9.1.9",
"@luma.gl/engine": "^9.1.9",
"@luma.gl/shadertools": "^9.1.9",
@@ -43,7 +43,7 @@
"d3-array": "^1.2.4",
"d3-color": "^1.4.1",
"d3-scale": "^3.0.0",
"dayjs": "^1.11.13",
"dayjs": "^1.11.18",
"handlebars": "^4.7.8",
"lodash": "^4.17.21",
"mousetrap": "^1.6.5",
@@ -16,10 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
import { useEffect, useState, memo } from 'react';
import { styled, t } from '@superset-ui/core';
import { useEffect, useState, memo, useMemo } from 'react';
import { styled, t, sanitizeHtml } from '@superset-ui/core';
import { extendedDayjs as dayjs } from '@superset-ui/core/utils/dates';
import { SafeMarkdown } from '@superset-ui/core/components';
import Handlebars from 'handlebars';
import { isPlainObject } from 'lodash';
@@ -45,8 +44,6 @@ export const HandlebarsRenderer: React.FC<HandlebarsRendererProps> = memo(
appContainer?.getAttribute('data-bootstrap') || '{}',
);
const htmlSanitization = common?.conf?.HTML_SANITIZATION ?? true;
const htmlSchemaOverrides =
common?.conf?.HTML_SANITIZATION_SCHEMA_EXTENSIONS || {};
useEffect(() => {
try {
@@ -60,6 +57,12 @@ export const HandlebarsRenderer: React.FC<HandlebarsRendererProps> = memo(
}
}, [templateSource, data]);
const htmlContent = useMemo(
() =>
htmlSanitization ? sanitizeHtml(renderedTemplate) : renderedTemplate,
[renderedTemplate, htmlSanitization],
);
if (error) {
return <ErrorContainer>{error}</ErrorContainer>;
}
@@ -73,13 +76,9 @@ export const HandlebarsRenderer: React.FC<HandlebarsRendererProps> = memo(
fontSize: '12px',
lineHeight: '1.4',
}}
>
<SafeMarkdown
source={renderedTemplate || ''}
htmlSanitization={htmlSanitization}
htmlSchemaOverrides={htmlSchemaOverrides}
/>
</div>
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: htmlContent }}
/>
);
}
@@ -35,7 +35,7 @@
"lodash": "^4.17.21",
"dayjs": "^1.11.18",
"nvd3-fork": "^2.0.5",
"dompurify": "^3.2.7",
"dompurify": "^3.3.0",
"prop-types": "^15.8.1",
"urijs": "^1.19.11"
},
@@ -26,7 +26,7 @@
"dependencies": {
"@types/react-redux": "^7.1.34",
"d3-array": "^1.2.0",
"dayjs": "^1.11.13",
"dayjs": "^1.11.18",
"lodash": "^4.17.21"
},
"peerDependencies": {
@@ -142,7 +142,7 @@ const config: ControlPanelConfig = {
type: 'SelectControl',
freeForm: true,
clearable: true,
label: t('X AXIS TITLE MARGIN'),
label: t('X axis title margin'),
renderTrigger: true,
default: sections.TITLE_MARGIN_OPTIONS[1],
choices: formatSelectOptions(sections.TITLE_MARGIN_OPTIONS),
@@ -214,7 +214,7 @@ const config: ControlPanelConfig = {
type: 'SelectControl',
freeForm: true,
clearable: true,
label: t('Y AXIS TITLE MARGIN'),
label: t('Y axis title margin'),
renderTrigger: true,
default: sections.TITLE_MARGIN_OPTIONS[1],
choices: formatSelectOptions(sections.TITLE_MARGIN_OPTIONS),
@@ -81,7 +81,7 @@ function createAxisTitleControl(axis: 'x' | 'y'): ControlSetRow[] {
type: 'SelectControl',
freeForm: true,
clearable: true,
label: t('AXIS TITLE MARGIN'),
label: t('Axis title margin'),
renderTrigger: true,
default: sections.TITLE_MARGIN_OPTIONS[0],
choices: formatSelectOptions(sections.TITLE_MARGIN_OPTIONS),
@@ -114,7 +114,7 @@ function createAxisTitleControl(axis: 'x' | 'y'): ControlSetRow[] {
type: 'SelectControl',
freeForm: true,
clearable: true,
label: t('AXIS TITLE MARGIN'),
label: t('Axis title margin'),
renderTrigger: true,
default: sections.TITLE_MARGIN_OPTIONS[1],
choices: formatSelectOptions(sections.TITLE_MARGIN_OPTIONS),
@@ -132,7 +132,7 @@ function createAxisTitleControl(axis: 'x' | 'y'): ControlSetRow[] {
type: 'SelectControl',
freeForm: true,
clearable: false,
label: t('AXIS TITLE POSITION'),
label: t('Axis title position'),
renderTrigger: true,
default: sections.TITLE_POSITION_OPTIONS[0][0],
choices: sections.TITLE_POSITION_OPTIONS,
@@ -394,7 +394,7 @@ export function runQueryFromSqlEditor(
dbId: qe.dbId,
sql: qe.selectedText || qe.sql,
sqlEditorId: qe.tabViewId ?? qe.id,
immutableId: qe.immutableId,
sqlEditorImmutableId: qe.immutableId,
tab: qe.name,
catalog: qe.catalog,
schema: qe.schema,
@@ -602,4 +602,42 @@ describe('ResultSet', () => {
);
expect(queryByTestId('copy-to-clipboard-button')).not.toBeInTheDocument();
});
test('should include sqlEditorImmutableId in query object when fetching results', async () => {
const queryWithResultsKey = {
...queries[0],
resultsKey: 'test-results-key',
sqlEditorImmutableId: 'test-immutable-id-123',
};
const store = mockStore({
...initialState,
user,
sqlLab: {
...initialState.sqlLab,
queries: {
[queryWithResultsKey.id]: queryWithResultsKey,
},
},
});
setup({ ...mockedProps, queryId: queryWithResultsKey.id }, store);
await waitFor(() => {
// Check that REQUEST_QUERY_RESULTS action was dispatched
const actions = store.getActions();
const requestAction = actions.find(
action => action.type === 'REQUEST_QUERY_RESULTS',
);
expect(requestAction).toBeDefined();
// Verify sqlEditorImmutableId is present in the query object
expect(requestAction?.query?.sqlEditorImmutableId).toBe(
'test-immutable-id-123',
);
});
// Verify the API was called
const resultsCalls = fetchMock.calls('glob:*/api/v1/sqllab/results/*');
expect(resultsCalls).toHaveLength(1);
});
});
@@ -198,6 +198,7 @@ const ResultSet = ({
'sql',
'executedSql',
'sqlEditorId',
'sqlEditorImmutableId',
'templateParams',
'schema',
'rows',
+6
View File
@@ -238,6 +238,7 @@ export const queries = [
ctas: false,
cached: false,
id: 'BkA1CLrJg',
sqlEditorImmutableId: 'BkA1CLrJg_immutable',
progress: 100,
startDttm: 1476910566092.96,
state: QueryState.Success,
@@ -297,6 +298,7 @@ export const queries = [
ctas: false,
cached: false,
id: 'S1zeAISkx',
sqlEditorImmutableId: 'S1zeAISkx_immutable',
progress: 100,
startDttm: 1476910570802.2,
state: QueryState.Success,
@@ -331,6 +333,7 @@ export const queryWithNoQueryLimit = {
ctas: false,
cached: false,
id: 'BkA1CLrJg',
sqlEditorImmutableId: 'BkA1CLrJg_immutable',
progress: 100,
startDttm: 1476910566092.96,
state: QueryState.Success,
@@ -589,6 +592,7 @@ const baseQuery: QueryResponse = {
ctas: false,
cached: false,
id: 'BkA1CLrJg',
sqlEditorImmutableId: 'BkA1CLrJg_immutable',
progress: 100,
startDttm: 1476910566092.96,
state: QueryState.Success,
@@ -672,6 +676,7 @@ export const runningQuery: QueryResponse = {
cached: false,
ctas: false,
id: 'ryhMUZCGb',
sqlEditorImmutableId: 'ryhMUZCGb_immutable',
progress: 90,
state: QueryState.Running,
startDttm: Date.now() - 500,
@@ -683,6 +688,7 @@ export const successfulQuery: QueryResponse = {
cached: false,
ctas: false,
id: 'ryhMUZCGb',
sqlEditorImmutableId: 'ryhMUZCGb_immutable',
progress: 100,
state: QueryState.Success,
startDttm: Date.now() - 500,
@@ -1019,10 +1019,16 @@ class DatasourceEditor extends PureComponent {
<Field
fieldKey="default_endpoint"
label={t('Default URL')}
description={t(
`Default URL to redirect to when accessing from the dataset list page.
Accepts relative URLs such as <span style=„white-space: nowrap;”>/superset/dashboard/{id}/</span>`,
)}
description={
<>
{t(
'Default URL to redirect to when accessing from the dataset list page. Accepts relative URLs such as',
)}{' '}
<Typography.Text code>
/superset/dashboard/{'{id}'}/
</Typography.Text>
</>
}
control={<TextControl controlId="default_endpoint" />}
/>
<Field
+7 -10
View File
@@ -30,7 +30,6 @@ import {
} from 'src/SqlLab/actions/sqlLab';
import { RootState, store } from 'src/views/store';
import { AnyListenerPredicate } from '@reduxjs/toolkit';
import memoizeOne from 'memoize-one';
import type { SqlLabRootState } from 'src/SqlLab/types';
import { Disposable } from '../models';
import { createActionListener } from '../utils';
@@ -198,13 +197,10 @@ const getActiveEditorImmutableId = () => {
return activeEditor?.immutableId;
};
// Memoized version to avoid repeated store lookups when active editor hasn't changed
const getActiveEditorId = memoizeOne(getActiveEditorImmutableId);
const predicate = (actionType: string): AnyListenerPredicate<RootState> => {
// Capture the immutable ID of the active editor at the time the listener is created
// This ID never changes for a tab, ensuring stable event routing
const registrationImmutableId = getActiveEditorId();
const registrationImmutableId = getActiveEditorImmutableId();
return action => {
if (action.type !== actionType) return false;
@@ -212,14 +208,15 @@ const predicate = (actionType: string): AnyListenerPredicate<RootState> => {
// If we don't have a registration ID, don't filter events
if (!registrationImmutableId) return true;
// For query events, use the immutableId directly from the action payload
if (action.query?.immutableId) {
return action.query.immutableId === registrationImmutableId;
// For query events, use the sqlEditorImmutableId directly from the action payload
if (action.query?.sqlEditorImmutableId) {
return action.query.sqlEditorImmutableId === registrationImmutableId;
}
// For tab events, we need to find the immutable ID of the affected tab
if (action.queryEditor?.id) {
const queryEditor = findQueryEditor(action.queryEditor.id);
const queryEditorId = action.queryEditor?.id || action.query?.sqlEditorId;
if (queryEditorId) {
const queryEditor = findQueryEditor(queryEditorId);
return queryEditor?.immutableId === registrationImmutableId;
}
@@ -208,8 +208,7 @@ class TextAreaControl extends Component {
buttonSize="small"
style={{ marginTop: this.props.theme.sizeUnit }}
>
{t('Edit')} <strong>{this.props.language}</strong>{' '}
{t('in modal')}
{t('Edit %s in modal', this.props.language)}
</Button>
}
modalBody={this.renderModalBody(true)}
@@ -66,6 +66,7 @@ export const mapQueryResponse = (
): Omit<
Query,
| 'tempSchema'
| 'sqlEditorImmutableId'
| 'started'
| 'time'
| 'duration'
@@ -103,6 +103,9 @@ const Actions = styled.div`
}
}
color: ${theme.colorTextDisabled};
&:hover {
cursor: not-allowed;
}
.ant-menu-item:hover {
cursor: default;
}
@@ -479,7 +482,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
<span
role="button"
tabIndex={0}
className={allowEdit ? 'action-button' : 'disabled'}
className={`action-button ${allowEdit ? '' : 'disabled'}`}
onClick={allowEdit ? handleEdit : undefined}
>
<Icons.EditOutlined iconSize="l" />
+24 -4
View File
@@ -27,7 +27,7 @@ import {
Typography,
Icons,
} from '@superset-ui/core/components';
import { useState, useEffect } from 'react';
import { useState, useEffect, useMemo } from 'react';
import { capitalize } from 'lodash/fp';
import { addDangerToast } from 'src/components/MessageToasts/actions';
import { useDispatch } from 'react-redux';
@@ -82,6 +82,26 @@ export default function Login() {
const dispatch = useDispatch();
const bootstrapData = getBootstrapData();
const nextUrl = useMemo(() => {
try {
const params = new URLSearchParams(window.location.search);
return params.get('next') || '';
} catch (_error) {
return '';
}
}, []);
const loginEndpoint = useMemo(
() => (nextUrl ? `/login/?next=${encodeURIComponent(nextUrl)}` : '/login/'),
[nextUrl],
);
const buildProviderLoginUrl = (providerName: string) => {
const base = `/login/${providerName}`;
return nextUrl
? `${base}${base.includes('?') ? '&' : '?'}next=${encodeURIComponent(nextUrl)}`
: base;
};
const authType: AuthType = bootstrapData.common.conf.AUTH_TYPE;
const providers: Provider[] = bootstrapData.common.conf.AUTH_PROVIDERS;
@@ -109,7 +129,7 @@ export default function Login() {
sessionStorage.setItem('login_attempted', 'true');
// Use standard form submission for Flask-AppBuilder compatibility
SupersetClient.postForm('/login/', values, '');
SupersetClient.postForm(loginEndpoint, values, '');
};
const getAuthIconElement = (
@@ -146,7 +166,7 @@ export default function Login() {
{providers.map((provider: OIDProvider) => (
<Form.Item<LoginForm>>
<Button
href={`/login/${provider.name}`}
href={buildProviderLoginUrl(provider.name)}
block
iconPosition="start"
icon={getAuthIconElement(provider.name)}
@@ -164,7 +184,7 @@ export default function Login() {
{providers.map((provider: OAuthProvider) => (
<Form.Item<LoginForm>>
<Button
href={`/login/${provider.name}`}
href={buildProviderLoginUrl(provider.name)}
block
iconPosition="start"
icon={getAuthIconElement(provider.name)}
@@ -62,6 +62,7 @@ jest.mock('src/views/CRUD/hooks', () => ({
// Mock the useThemeContext hook
const mockSetTemporaryTheme = jest.fn();
const mockGetAppliedThemeId = jest.fn();
jest.mock('src/theme/ThemeProvider', () => ({
...jest.requireActual('src/theme/ThemeProvider'),
useThemeContext: jest.fn(),
@@ -141,10 +142,13 @@ beforeEach(() => {
});
// Mock useThemeContext
mockGetAppliedThemeId.mockReturnValue(null);
(useThemeContext as jest.Mock).mockReturnValue({
getCurrentCrudThemeId: jest.fn().mockReturnValue('1'),
appliedTheme: { theme_name: 'Light Theme', id: 1 },
setTemporaryTheme: mockSetTemporaryTheme,
hasDevOverride: jest.fn().mockReturnValue(false),
getAppliedThemeId: mockGetAppliedThemeId,
});
fetchMock.reset();
@@ -460,7 +464,7 @@ test('shows create theme button when user has permissions', async () => {
expect(addButton).toBeInTheDocument();
});
test('clicking apply button calls setTemporaryTheme with parsed theme data', async () => {
test('clicking apply button calls setTemporaryTheme with parsed theme data and ID', async () => {
render(
<ThemesList
user={mockUser}
@@ -483,8 +487,106 @@ test('clicking apply button calls setTemporaryTheme with parsed theme data', asy
await userEvent.click(applyButtons[0]);
await waitFor(() => {
expect(mockSetTemporaryTheme).toHaveBeenCalledWith({
colors: { primary: '#ffffff' },
});
expect(mockSetTemporaryTheme).toHaveBeenCalledWith(
{
colors: { primary: '#ffffff' },
},
1, // theme ID
);
});
});
test('applying a local theme calls setTemporaryTheme with theme ID', async () => {
render(
<ThemesList
user={mockUser}
addDangerToast={jest.fn()}
addSuccessToast={jest.fn()}
/>,
{
useRedux: true,
useRouter: true,
useQueryParams: true,
useTheme: true,
},
);
await screen.findByText('Custom Theme');
// Find and click the apply button for the first theme
const applyButtons = await screen.findAllByTestId('apply-action');
await userEvent.click(applyButtons[0]);
// Check that setTemporaryTheme was called with both theme config and ID
await waitFor(() => {
expect(mockSetTemporaryTheme).toHaveBeenCalledWith(
{ colors: { primary: '#ffffff' } },
1, // theme ID
);
});
});
test('component loads successfully with applied theme ID set', async () => {
// This test verifies that having a stored theme ID doesn't break the component
// Mock hasDevOverride to return true since we have a dev override set
mockGetAppliedThemeId.mockReturnValue(1);
(useThemeContext as jest.Mock).mockReturnValue({
getCurrentCrudThemeId: jest.fn().mockReturnValue('1'),
appliedTheme: { theme_name: 'Light Theme', id: 1 },
setTemporaryTheme: mockSetTemporaryTheme,
hasDevOverride: jest.fn().mockReturnValue(true),
getAppliedThemeId: mockGetAppliedThemeId,
});
render(
<ThemesList
user={mockUser}
addDangerToast={jest.fn()}
addSuccessToast={jest.fn()}
/>,
{
useRedux: true,
useRouter: true,
useQueryParams: true,
useTheme: true,
},
);
// Wait for list to load and verify it renders successfully
await screen.findByText('Custom Theme');
// Verify the component called getAppliedThemeId
expect(mockGetAppliedThemeId).toHaveBeenCalled();
});
test('component loads successfully and preserves applied theme state', async () => {
// Mock hasDevOverride to return true and getAppliedThemeId to return a theme
mockGetAppliedThemeId.mockReturnValue(1);
(useThemeContext as jest.Mock).mockReturnValue({
getCurrentCrudThemeId: jest.fn().mockReturnValue('1'),
appliedTheme: { theme_name: 'Light Theme', id: 1 },
setTemporaryTheme: mockSetTemporaryTheme,
hasDevOverride: jest.fn().mockReturnValue(true),
getAppliedThemeId: mockGetAppliedThemeId,
});
render(
<ThemesList
user={mockUser}
addDangerToast={jest.fn()}
addSuccessToast={jest.fn()}
/>,
{
useRedux: true,
useRouter: true,
useQueryParams: true,
useTheme: true,
},
);
// Wait for list to load
await screen.findByText('Custom Theme');
// Verify getAppliedThemeId is called during component mount
expect(mockGetAppliedThemeId).toHaveBeenCalled();
});
+42 -24
View File
@@ -17,7 +17,7 @@
* under the License.
*/
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { t, SupersetClient, styled } from '@superset-ui/core';
import {
Tag,
@@ -110,15 +110,27 @@ function ThemesList({
refreshData,
toggleBulkSelect,
} = useListViewResource<ThemeObject>('theme', t('Themes'), addDangerToast);
const { setTemporaryTheme, getCurrentCrudThemeId } = useThemeContext();
const { setTemporaryTheme, hasDevOverride, getAppliedThemeId } =
useThemeContext();
const [themeModalOpen, setThemeModalOpen] = useState<boolean>(false);
const [currentTheme, setCurrentTheme] = useState<ThemeObject | null>(null);
const [preparingExport, setPreparingExport] = useState<boolean>(false);
const [importingTheme, showImportModal] = useState<boolean>(false);
const [appliedThemeId, setAppliedThemeId] = useState<number | null>(null);
const [appliedThemeId, setLocalAppliedThemeId] = useState<number | null>(
null,
);
const { showConfirm, ConfirmModal } = useConfirmModal();
useEffect(() => {
if (hasDevOverride()) {
const storedThemeId = getAppliedThemeId();
setLocalAppliedThemeId(storedThemeId);
} else {
setLocalAppliedThemeId(null);
}
}, [hasDevOverride, getAppliedThemeId]);
const canCreate = hasPerm('can_write');
const canEdit = hasPerm('can_write');
const canDelete = hasPerm('can_write');
@@ -201,8 +213,11 @@ function ThemesList({
if (themeObj.json_data) {
try {
const themeConfig = JSON.parse(themeObj.json_data);
setTemporaryTheme(themeConfig);
setAppliedThemeId(themeObj.id || null);
const themeId = themeObj.id || null;
setTemporaryTheme(themeConfig, themeId);
setLocalAppliedThemeId(themeId);
addSuccessToast(t('Local theme set to "%s"', themeObj.theme_name));
} catch (error) {
addDangerToast(
@@ -217,23 +232,26 @@ function ThemesList({
function handleThemeModalApply() {
// Clear any previously applied theme ID when applying from modal
// since the modal theme might not have an ID yet (unsaved theme)
setAppliedThemeId(null);
setLocalAppliedThemeId(null);
}
const handleBulkThemeExport = async (themesToExport: ThemeObject[]) => {
const ids = themesToExport
.map(({ id }) => id)
.filter((id): id is number => id !== undefined);
setPreparingExport(true);
try {
await handleResourceExport('theme', ids, () => {
const handleBulkThemeExport = useCallback(
async (themesToExport: ThemeObject[]) => {
const ids = themesToExport
.map(({ id }) => id)
.filter((id): id is number => id !== undefined);
setPreparingExport(true);
try {
await handleResourceExport('theme', ids, () => {
setPreparingExport(false);
});
} catch (error) {
setPreparingExport(false);
});
} catch (error) {
setPreparingExport(false);
addDangerToast(t('There was an issue exporting the selected themes'));
}
};
addDangerToast(t('There was an issue exporting the selected themes'));
}
},
[addDangerToast],
);
const openThemeImportModal = () => {
showImportModal(true);
@@ -346,11 +364,10 @@ function ThemesList({
() => [
{
Cell: ({ row: { original } }: any) => {
const currentCrudThemeId = getCurrentCrudThemeId();
const isCurrentTheme =
(currentCrudThemeId &&
original.id?.toString() === currentCrudThemeId) ||
(appliedThemeId && original.id === appliedThemeId);
hasDevOverride() &&
appliedThemeId &&
original.id === appliedThemeId;
return (
<FlexRowContainer>
@@ -520,11 +537,12 @@ function ThemesList({
canDelete,
canApply,
canExport,
getCurrentCrudThemeId,
hasDevOverride,
appliedThemeId,
canSetSystemThemes,
addDangerToast,
handleThemeApply,
handleBulkThemeExport,
handleSetSystemDefault,
handleUnsetSystemDefault,
handleSetSystemDark,
+61 -15
View File
@@ -22,6 +22,7 @@ import {
type ThemeControllerOptions,
type ThemeStorage,
isThemeConfigDark,
makeApi,
Theme,
ThemeMode,
themeObject as supersetThemeObject,
@@ -37,6 +38,7 @@ const STORAGE_KEYS = {
THEME_MODE: 'superset-theme-mode',
CRUD_THEME_ID: 'superset-crud-theme-id',
DEV_THEME_OVERRIDE: 'superset-dev-theme-override',
APPLIED_THEME_ID: 'superset-applied-theme-id',
} as const;
const MEDIA_QUERY_DARK_SCHEME = '(prefers-color-scheme: dark)';
@@ -224,14 +226,14 @@ export class ThemeController {
return this.dashboardThemes.get(themeId)!;
}
// Fetch theme config from API
const response = await fetch(`/api/v1/theme/${themeId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Fetch theme config from API using SupersetClient for proper auth
const getTheme = makeApi<void, { result: { json_data: string } }>({
method: 'GET',
endpoint: `/api/v1/theme/${themeId}`,
});
const data = await response.json();
const themeConfig = JSON.parse(data.result.json_data);
const { result } = await getTheme();
const themeConfig = JSON.parse(result.json_data);
if (themeConfig) {
// Controller creates and owns the dashboard theme
@@ -303,7 +305,12 @@ export class ThemeController {
public setThemeMode(mode: ThemeMode): void {
this.validateModeUpdatePermission(mode);
if (this.currentMode === mode) return;
if (
this.currentMode === mode &&
!this.devThemeOverride &&
!this.crudThemeId
)
return;
// Clear any local overrides when explicitly selecting a theme mode
// This ensures the selected mode takes effect and provides clear UX
@@ -367,8 +374,12 @@ export class ThemeController {
* Sets a temporary theme override for development purposes.
* This does not persist the theme but allows live preview.
* @param theme - The theme configuration to apply temporarily
* @param themeId - Optional theme ID to track which theme was applied (for UI display)
*/
public setTemporaryTheme(theme: AnyThemeConfig): void {
public setTemporaryTheme(
theme: AnyThemeConfig,
themeId?: number | null,
): void {
this.validateThemeUpdatePermission();
this.devThemeOverride = theme;
@@ -377,6 +388,11 @@ export class ThemeController {
JSON.stringify(theme),
);
// Store the theme ID if provided
if (themeId !== undefined) {
this.setAppliedThemeId(themeId);
}
const mergedTheme = this.getThemeForMode(this.currentMode);
if (mergedTheme) this.updateTheme(mergedTheme);
}
@@ -392,6 +408,7 @@ export class ThemeController {
this.storage.removeItem(STORAGE_KEYS.DEV_THEME_OVERRIDE);
this.storage.removeItem(STORAGE_KEYS.CRUD_THEME_ID);
this.storage.removeItem(STORAGE_KEYS.APPLIED_THEME_ID);
// Clear dashboard themes cache
this.dashboardThemes.clear();
@@ -413,6 +430,34 @@ export class ThemeController {
return this.devThemeOverride !== null;
}
/**
* Gets the applied theme ID (for UI display purposes).
*/
public getAppliedThemeId(): number | null {
try {
const storedId = this.storage.getItem(STORAGE_KEYS.APPLIED_THEME_ID);
return storedId ? parseInt(storedId, 10) : null;
} catch (error) {
console.warn('Failed to get applied theme ID:', error);
return null;
}
}
/**
* Sets the applied theme ID (for UI display purposes).
*/
public setAppliedThemeId(themeId: number | null): void {
try {
if (themeId !== null) {
this.storage.setItem(STORAGE_KEYS.APPLIED_THEME_ID, themeId.toString());
} else {
this.storage.removeItem(STORAGE_KEYS.APPLIED_THEME_ID);
}
} catch (error) {
console.warn('Failed to set applied theme ID:', error);
}
}
/**
* Checks if OS preference detection is allowed.
* Allowed when dark theme is available (including base dark theme)
@@ -817,13 +862,14 @@ export class ThemeController {
themeId: string,
): Promise<AnyThemeConfig | null> {
try {
const response = await fetch(`/api/v1/theme/${themeId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Use SupersetClient for proper authentication handling
const getTheme = makeApi<void, { result: { json_data: string } }>({
method: 'GET',
endpoint: `/api/v1/theme/${themeId}`,
});
const data = await response.json();
const themeConfig = JSON.parse(data.result.json_data);
const { result } = await getTheme();
const themeConfig = JSON.parse(result.json_data);
return themeConfig;
} catch (error) {
@@ -117,6 +117,11 @@ export function SupersetThemeProvider({
[themeController],
);
const getAppliedThemeId = useCallback(
() => themeController.getAppliedThemeId(),
[themeController],
);
const contextValue = useMemo(
() => ({
theme: currentTheme,
@@ -132,6 +137,7 @@ export function SupersetThemeProvider({
canSetTheme,
canDetectOSPreference,
createDashboardThemeProvider,
getAppliedThemeId,
}),
[
currentTheme,
@@ -147,6 +153,7 @@ export function SupersetThemeProvider({
canSetTheme,
canDetectOSPreference,
createDashboardThemeProvider,
getAppliedThemeId,
],
);
@@ -1137,4 +1137,236 @@ describe('ThemeController', () => {
);
});
});
test('setThemeMode clears dev override and crud theme from storage', () => {
mockGetBootstrapData.mockReturnValue(
createMockBootstrapData({
default: DEFAULT_THEME,
dark: DARK_THEME,
}),
);
mockLocalStorage.getItem.mockReturnValue(null);
const controller = new ThemeController({
storage: mockLocalStorage,
themeObject: mockThemeObject,
});
// Simulate having overrides after initialization using Reflect to access private properties
Reflect.set(controller, 'devThemeOverride', {
token: { colorPrimary: '#ff0000' },
});
Reflect.set(controller, 'crudThemeId', '123');
jest.clearAllMocks();
// Change theme mode - should clear the overrides
controller.setThemeMode(ThemeMode.DARK);
// Verify both storage keys were removed
expect(mockLocalStorage.removeItem).toHaveBeenCalledWith(
'superset-dev-theme-override',
);
expect(mockLocalStorage.removeItem).toHaveBeenCalledWith(
'superset-crud-theme-id',
);
});
test('setThemeMode can be called with same mode when overrides exist', () => {
mockGetBootstrapData.mockReturnValue(
createMockBootstrapData({
default: DEFAULT_THEME,
dark: DARK_THEME,
}),
);
mockLocalStorage.getItem.mockReturnValue(null);
const controller = new ThemeController({
storage: mockLocalStorage,
themeObject: mockThemeObject,
});
jest.clearAllMocks();
// Simulate having dev override after initialization using Reflect
Reflect.set(controller, 'devThemeOverride', {
token: { colorPrimary: '#ff0000' },
});
// Call setThemeMode with DEFAULT mode - should clear override
controller.setThemeMode(ThemeMode.DEFAULT);
// Verify override was removed even though mode is the same
expect(mockLocalStorage.removeItem).toHaveBeenCalledWith(
'superset-dev-theme-override',
);
expect(mockLocalStorage.removeItem).toHaveBeenCalledWith(
'superset-crud-theme-id',
);
// Theme should still be updated to clear the override
expect(mockSetConfig).toHaveBeenCalled();
});
test('setThemeMode with no override and same mode does not trigger update', () => {
mockGetBootstrapData.mockReturnValue(
createMockBootstrapData({
default: DEFAULT_THEME,
dark: DARK_THEME,
}),
);
const controller = new ThemeController({
storage: mockLocalStorage,
themeObject: mockThemeObject,
});
// Set mode to DEFAULT
controller.setThemeMode(ThemeMode.DEFAULT);
jest.clearAllMocks();
// Call again with same mode and no override - should skip
controller.setThemeMode(ThemeMode.DEFAULT);
// Should not trigger any updates
expect(mockSetConfig).not.toHaveBeenCalled();
expect(mockLocalStorage.removeItem).not.toHaveBeenCalled();
});
test('hasDevOverride returns true when dev override is set', () => {
mockGetBootstrapData.mockReturnValue(
createMockBootstrapData({
default: DEFAULT_THEME,
dark: DARK_THEME,
}),
);
mockLocalStorage.getItem.mockReturnValue(null);
const controller = new ThemeController({
storage: mockLocalStorage,
themeObject: mockThemeObject,
});
// Simulate dev override after initialization using Reflect
Reflect.set(controller, 'devThemeOverride', {
token: { colorPrimary: '#ff0000' },
});
expect(controller.hasDevOverride()).toBe(true);
});
test('hasDevOverride returns false when no dev override in storage', () => {
mockGetBootstrapData.mockReturnValue(
createMockBootstrapData({
default: DEFAULT_THEME,
dark: DARK_THEME,
}),
);
mockLocalStorage.getItem.mockReturnValue(null);
const controller = new ThemeController({
storage: mockLocalStorage,
themeObject: mockThemeObject,
});
expect(controller.hasDevOverride()).toBe(false);
});
test('clearLocalOverrides removes dev override, crud theme, and applied theme ID', () => {
mockGetBootstrapData.mockReturnValue(
createMockBootstrapData({
default: DEFAULT_THEME,
dark: DARK_THEME,
}),
);
mockLocalStorage.getItem.mockReturnValue(null);
const controller = new ThemeController({
storage: mockLocalStorage,
themeObject: mockThemeObject,
});
jest.clearAllMocks();
// Clear overrides
controller.clearLocalOverrides();
// Verify all storage keys are removed
expect(mockLocalStorage.removeItem).toHaveBeenCalledWith(
'superset-dev-theme-override',
);
expect(mockLocalStorage.removeItem).toHaveBeenCalledWith(
'superset-crud-theme-id',
);
expect(mockLocalStorage.removeItem).toHaveBeenCalledWith(
'superset-applied-theme-id',
);
// Should reset to default theme
expect(mockSetConfig).toHaveBeenCalled();
});
test('getAppliedThemeId returns stored theme ID', () => {
mockLocalStorage.getItem.mockImplementation((key: string) => {
if (key === 'superset-applied-theme-id') {
return '42';
}
return null;
});
const controller = new ThemeController({
storage: mockLocalStorage,
themeObject: mockThemeObject,
});
expect(controller.getAppliedThemeId()).toBe(42);
});
test('getAppliedThemeId returns null when no theme ID is stored', () => {
mockLocalStorage.getItem.mockReturnValue(null);
const controller = new ThemeController({
storage: mockLocalStorage,
themeObject: mockThemeObject,
});
expect(controller.getAppliedThemeId()).toBeNull();
});
test('setAppliedThemeId stores theme ID in storage', () => {
const controller = new ThemeController({
storage: mockLocalStorage,
themeObject: mockThemeObject,
});
jest.clearAllMocks();
controller.setAppliedThemeId(123);
expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
'superset-applied-theme-id',
'123',
);
});
test('setAppliedThemeId removes theme ID when null is passed', () => {
const controller = new ThemeController({
storage: mockLocalStorage,
themeObject: mockThemeObject,
});
jest.clearAllMocks();
controller.setAppliedThemeId(null);
expect(mockLocalStorage.removeItem).toHaveBeenCalledWith(
'superset-applied-theme-id',
);
});
});
+120
View File
@@ -276,3 +276,123 @@ test('handles various resource types', async () => {
expect(doneMock).toHaveBeenCalledTimes(5);
});
test('handles network errors and logs them', async () => {
const networkError = new Error('Network request failed');
(SupersetClient.get as jest.Mock).mockRejectedValue(networkError);
const doneMock = jest.fn();
await expect(
handleResourceExport('dashboard', [1], doneMock),
).rejects.toThrow('Network request failed');
expect(logging.error).toHaveBeenCalledWith(
'Resource export failed:',
networkError,
);
expect(doneMock).toHaveBeenCalled();
});
test('handles 404 errors when resource not found', async () => {
const notFoundError = new Error('Not found');
(SupersetClient.get as jest.Mock).mockRejectedValue(notFoundError);
const doneMock = jest.fn();
await expect(
handleResourceExport('dashboard', [999], doneMock),
).rejects.toThrow('Not found');
expect(doneMock).toHaveBeenCalled();
});
test('handles empty response from server', async () => {
const emptyBlob = new Blob([], { type: 'application/zip' });
mockResponse = {
headers: new Headers({
'Content-Disposition': 'attachment; filename="empty.zip"',
}),
blob: jest.fn().mockResolvedValue(emptyBlob),
} as unknown as Response;
(SupersetClient.get as jest.Mock).mockResolvedValue(mockResponse);
const doneMock = jest.fn();
await handleResourceExport('dashboard', [1], doneMock);
expect(window.URL.createObjectURL).toHaveBeenCalledWith(emptyBlob);
expect(doneMock).toHaveBeenCalled();
});
test('cleans up blob URL even when download fails', async () => {
const mockAnchor = document.createElement('a');
mockAnchor.click = jest.fn().mockImplementation(() => {
throw new Error('Click failed');
});
createElementSpy.mockRestore();
createElementSpy = jest
.spyOn(document, 'createElement')
.mockReturnValue(mockAnchor);
const doneMock = jest.fn();
await expect(
handleResourceExport('dashboard', [1], doneMock),
).rejects.toThrow('Click failed');
// Verify cleanup still happens
expect(window.URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock-url');
expect(doneMock).toHaveBeenCalled();
});
test('handles malformed Content-Disposition header', async () => {
mockResponse = {
headers: new Headers({
'Content-Disposition': 'not-a-valid-header',
}),
blob: jest.fn().mockResolvedValue(mockBlob),
} as unknown as Response;
(SupersetClient.get as jest.Mock).mockResolvedValue(mockResponse);
(contentDisposition.parse as jest.Mock).mockImplementationOnce(() => {
throw new Error('Parse error');
});
const doneMock = jest.fn();
await handleResourceExport('dataset', [5], doneMock);
// Should fall back to default filename
const anchor = document.createElement('a');
expect(anchor.download).toBe('dataset_export.zip');
expect(logging.warn).toHaveBeenCalledWith(
'Failed to parse Content-Disposition header:',
expect.any(Error),
);
});
test('handles missing headers object', async () => {
mockResponse = {
headers: new Headers(),
blob: jest.fn().mockResolvedValue(mockBlob),
} as unknown as Response;
(SupersetClient.get as jest.Mock).mockResolvedValue(mockResponse);
const doneMock = jest.fn();
await handleResourceExport('chart', [7], doneMock);
const anchor = document.createElement('a');
expect(anchor.download).toBe('chart_export.zip');
expect(doneMock).toHaveBeenCalled();
});
test('handles export with empty IDs array', async () => {
const doneMock = jest.fn();
await handleResourceExport('dashboard', [], doneMock);
expect(SupersetClient.get).toHaveBeenCalledWith(
expect.objectContaining({
endpoint: '/api/v1/dashboard/export/?q=!()',
}),
);
});
+179 -179
View File
@@ -25,12 +25,12 @@
"@types/jest": "^29.5.14",
"@types/jsonwebtoken": "^9.0.10",
"@types/lodash": "^4.17.20",
"@types/node": "^24.7.2",
"@types/node": "^24.9.1",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.26.0",
"@typescript-eslint/parser": "^8.46.1",
"eslint": "^9.37.0",
"@typescript-eslint/parser": "^8.46.2",
"eslint": "^9.38.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-lodash": "^8.0.0",
"globals": "^16.4.0",
@@ -40,7 +40,7 @@
"ts-node": "^10.9.2",
"tscw-config": "^1.1.2",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.1"
"typescript-eslint": "^8.46.2"
},
"engines": {
"node": "^20.19.4",
@@ -750,12 +750,13 @@
}
},
"node_modules/@eslint/config-array": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
"integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/object-schema": "^2.1.6",
"@eslint/object-schema": "^2.1.7",
"debug": "^4.3.1",
"minimatch": "^3.1.2"
},
@@ -764,9 +765,9 @@
}
},
"node_modules/@eslint/config-helpers": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz",
"integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==",
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz",
"integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -847,9 +848,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.37.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz",
"integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==",
"version": "9.38.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz",
"integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -860,10 +861,11 @@
}
},
"node_modules/@eslint/object-schema": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
"integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
@@ -1857,13 +1859,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.7.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz",
"integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==",
"version": "24.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
"integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.14.0"
"undici-types": "~7.16.0"
}
},
"node_modules/@types/stack-utils": {
@@ -1909,17 +1911,17 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz",
"integrity": "sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz",
"integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.46.1",
"@typescript-eslint/type-utils": "8.46.1",
"@typescript-eslint/utils": "8.46.1",
"@typescript-eslint/visitor-keys": "8.46.1",
"@typescript-eslint/scope-manager": "8.46.2",
"@typescript-eslint/type-utils": "8.46.2",
"@typescript-eslint/utils": "8.46.2",
"@typescript-eslint/visitor-keys": "8.46.2",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -1933,7 +1935,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.46.1",
"@typescript-eslint/parser": "^8.46.2",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
@@ -1949,16 +1951,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.1.tgz",
"integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz",
"integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.1",
"@typescript-eslint/types": "8.46.1",
"@typescript-eslint/typescript-estree": "8.46.1",
"@typescript-eslint/visitor-keys": "8.46.1",
"@typescript-eslint/scope-manager": "8.46.2",
"@typescript-eslint/types": "8.46.2",
"@typescript-eslint/typescript-estree": "8.46.2",
"@typescript-eslint/visitor-keys": "8.46.2",
"debug": "^4.3.4"
},
"engines": {
@@ -1974,14 +1976,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.1.tgz",
"integrity": "sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz",
"integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.46.1",
"@typescript-eslint/types": "^8.46.1",
"@typescript-eslint/tsconfig-utils": "^8.46.2",
"@typescript-eslint/types": "^8.46.2",
"debug": "^4.3.4"
},
"engines": {
@@ -1996,14 +1998,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.1.tgz",
"integrity": "sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz",
"integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.1",
"@typescript-eslint/visitor-keys": "8.46.1"
"@typescript-eslint/types": "8.46.2",
"@typescript-eslint/visitor-keys": "8.46.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2014,9 +2016,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.1.tgz",
"integrity": "sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz",
"integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2031,15 +2033,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.1.tgz",
"integrity": "sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz",
"integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.1",
"@typescript-eslint/typescript-estree": "8.46.1",
"@typescript-eslint/utils": "8.46.1",
"@typescript-eslint/types": "8.46.2",
"@typescript-eslint/typescript-estree": "8.46.2",
"@typescript-eslint/utils": "8.46.2",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -2056,9 +2058,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.1.tgz",
"integrity": "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz",
"integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2070,16 +2072,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.1.tgz",
"integrity": "sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz",
"integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.46.1",
"@typescript-eslint/tsconfig-utils": "8.46.1",
"@typescript-eslint/types": "8.46.1",
"@typescript-eslint/visitor-keys": "8.46.1",
"@typescript-eslint/project-service": "8.46.2",
"@typescript-eslint/tsconfig-utils": "8.46.2",
"@typescript-eslint/types": "8.46.2",
"@typescript-eslint/visitor-keys": "8.46.2",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -2125,16 +2127,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.1.tgz",
"integrity": "sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz",
"integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.46.1",
"@typescript-eslint/types": "8.46.1",
"@typescript-eslint/typescript-estree": "8.46.1"
"@typescript-eslint/scope-manager": "8.46.2",
"@typescript-eslint/types": "8.46.2",
"@typescript-eslint/typescript-estree": "8.46.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2149,13 +2151,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.1.tgz",
"integrity": "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz",
"integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.1",
"@typescript-eslint/types": "8.46.2",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -2839,25 +2841,24 @@
}
},
"node_modules/eslint": {
"version": "9.37.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz",
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
"version": "9.38.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz",
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.4.0",
"@eslint/config-array": "^0.21.1",
"@eslint/config-helpers": "^0.4.1",
"@eslint/core": "^0.16.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.37.0",
"@eslint/js": "9.38.0",
"@eslint/plugin-kit": "^0.4.0",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.6",
@@ -6305,16 +6306,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.1.tgz",
"integrity": "sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.2.tgz",
"integrity": "sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.46.1",
"@typescript-eslint/parser": "8.46.1",
"@typescript-eslint/typescript-estree": "8.46.1",
"@typescript-eslint/utils": "8.46.1"
"@typescript-eslint/eslint-plugin": "8.46.2",
"@typescript-eslint/parser": "8.46.2",
"@typescript-eslint/typescript-estree": "8.46.2",
"@typescript-eslint/utils": "8.46.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6343,9 +6344,9 @@
}
},
"node_modules/undici-types": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz",
"integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},
@@ -7163,20 +7164,20 @@
"dev": true
},
"@eslint/config-array": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
"integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
"dev": true,
"requires": {
"@eslint/object-schema": "^2.1.6",
"@eslint/object-schema": "^2.1.7",
"debug": "^4.3.1",
"minimatch": "^3.1.2"
}
},
"@eslint/config-helpers": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz",
"integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==",
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz",
"integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==",
"dev": true,
"requires": {
"@eslint/core": "^0.16.0"
@@ -7232,15 +7233,15 @@
}
},
"@eslint/js": {
"version": "9.37.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz",
"integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==",
"version": "9.38.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz",
"integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==",
"dev": true
},
"@eslint/object-schema": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
"integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
"dev": true
},
"@eslint/plugin-kit": {
@@ -8056,12 +8057,12 @@
"dev": true
},
"@types/node": {
"version": "24.7.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz",
"integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==",
"version": "24.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
"integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
"dev": true,
"requires": {
"undici-types": "~7.14.0"
"undici-types": "~7.16.0"
}
},
"@types/stack-utils": {
@@ -8106,16 +8107,16 @@
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz",
"integrity": "sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz",
"integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==",
"dev": true,
"requires": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.46.1",
"@typescript-eslint/type-utils": "8.46.1",
"@typescript-eslint/utils": "8.46.1",
"@typescript-eslint/visitor-keys": "8.46.1",
"@typescript-eslint/scope-manager": "8.46.2",
"@typescript-eslint/type-utils": "8.46.2",
"@typescript-eslint/utils": "8.46.2",
"@typescript-eslint/visitor-keys": "8.46.2",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -8131,75 +8132,75 @@
}
},
"@typescript-eslint/parser": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.1.tgz",
"integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz",
"integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "8.46.1",
"@typescript-eslint/types": "8.46.1",
"@typescript-eslint/typescript-estree": "8.46.1",
"@typescript-eslint/visitor-keys": "8.46.1",
"@typescript-eslint/scope-manager": "8.46.2",
"@typescript-eslint/types": "8.46.2",
"@typescript-eslint/typescript-estree": "8.46.2",
"@typescript-eslint/visitor-keys": "8.46.2",
"debug": "^4.3.4"
}
},
"@typescript-eslint/project-service": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.1.tgz",
"integrity": "sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz",
"integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==",
"dev": true,
"requires": {
"@typescript-eslint/tsconfig-utils": "^8.46.1",
"@typescript-eslint/types": "^8.46.1",
"@typescript-eslint/tsconfig-utils": "^8.46.2",
"@typescript-eslint/types": "^8.46.2",
"debug": "^4.3.4"
}
},
"@typescript-eslint/scope-manager": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.1.tgz",
"integrity": "sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz",
"integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "8.46.1",
"@typescript-eslint/visitor-keys": "8.46.1"
"@typescript-eslint/types": "8.46.2",
"@typescript-eslint/visitor-keys": "8.46.2"
}
},
"@typescript-eslint/tsconfig-utils": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.1.tgz",
"integrity": "sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz",
"integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==",
"dev": true,
"requires": {}
},
"@typescript-eslint/type-utils": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.1.tgz",
"integrity": "sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz",
"integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "8.46.1",
"@typescript-eslint/typescript-estree": "8.46.1",
"@typescript-eslint/utils": "8.46.1",
"@typescript-eslint/types": "8.46.2",
"@typescript-eslint/typescript-estree": "8.46.2",
"@typescript-eslint/utils": "8.46.2",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
}
},
"@typescript-eslint/types": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.1.tgz",
"integrity": "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz",
"integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.1.tgz",
"integrity": "sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz",
"integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==",
"dev": true,
"requires": {
"@typescript-eslint/project-service": "8.46.1",
"@typescript-eslint/tsconfig-utils": "8.46.1",
"@typescript-eslint/types": "8.46.1",
"@typescript-eslint/visitor-keys": "8.46.1",
"@typescript-eslint/project-service": "8.46.2",
"@typescript-eslint/tsconfig-utils": "8.46.2",
"@typescript-eslint/types": "8.46.2",
"@typescript-eslint/visitor-keys": "8.46.2",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -8229,24 +8230,24 @@
}
},
"@typescript-eslint/utils": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.1.tgz",
"integrity": "sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz",
"integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.46.1",
"@typescript-eslint/types": "8.46.1",
"@typescript-eslint/typescript-estree": "8.46.1"
"@typescript-eslint/scope-manager": "8.46.2",
"@typescript-eslint/types": "8.46.2",
"@typescript-eslint/typescript-estree": "8.46.2"
}
},
"@typescript-eslint/visitor-keys": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.1.tgz",
"integrity": "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz",
"integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==",
"dev": true,
"requires": {
"@typescript-eslint/types": "8.46.1",
"@typescript-eslint/types": "8.46.2",
"eslint-visitor-keys": "^4.2.1"
},
"dependencies": {
@@ -8742,24 +8743,23 @@
"dev": true
},
"eslint": {
"version": "9.37.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz",
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
"version": "9.38.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz",
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.4.0",
"@eslint/config-array": "^0.21.1",
"@eslint/config-helpers": "^0.4.1",
"@eslint/core": "^0.16.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.37.0",
"@eslint/js": "9.38.0",
"@eslint/plugin-kit": "^0.4.0",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.6",
@@ -11259,15 +11259,15 @@
"dev": true
},
"typescript-eslint": {
"version": "8.46.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.1.tgz",
"integrity": "sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==",
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.2.tgz",
"integrity": "sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg==",
"dev": true,
"requires": {
"@typescript-eslint/eslint-plugin": "8.46.1",
"@typescript-eslint/parser": "8.46.1",
"@typescript-eslint/typescript-estree": "8.46.1",
"@typescript-eslint/utils": "8.46.1"
"@typescript-eslint/eslint-plugin": "8.46.2",
"@typescript-eslint/parser": "8.46.2",
"@typescript-eslint/typescript-estree": "8.46.2",
"@typescript-eslint/utils": "8.46.2"
}
},
"uglify-js": {
@@ -11278,9 +11278,9 @@
"optional": true
},
"undici-types": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz",
"integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true
},
"unix-dgram": {
+4 -4
View File
@@ -33,12 +33,12 @@
"@types/jest": "^29.5.14",
"@types/jsonwebtoken": "^9.0.10",
"@types/lodash": "^4.17.20",
"@types/node": "^24.7.2",
"@types/node": "^24.9.1",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.26.0",
"@typescript-eslint/parser": "^8.46.1",
"eslint": "^9.37.0",
"@typescript-eslint/parser": "^8.46.2",
"eslint": "^9.38.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-lodash": "^8.0.0",
"globals": "^16.4.0",
@@ -48,7 +48,7 @@
"ts-node": "^10.9.2",
"tscw-config": "^1.1.2",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.1"
"typescript-eslint": "^8.46.2"
},
"engines": {
"node": "^20.19.4",
+10 -1
View File
@@ -30,6 +30,7 @@ from typing import Any, Optional, TYPE_CHECKING, Union
import numpy as np
import pandas as pd
from flask import current_app
from flask_babel import gettext as __
from superset.common.chart_data import ChartDataResultFormat
@@ -340,7 +341,15 @@ def apply_client_processing( # noqa: C901
if query["result_format"] == ChartDataResultFormat.JSON:
df = pd.DataFrame.from_dict(data)
elif query["result_format"] == ChartDataResultFormat.CSV:
df = pd.read_csv(StringIO(data))
# Use custom NA values configuration for
# reports to avoid unwanted conversions
# This allows users to control which values should be treated as null/NA
na_values = current_app.config["REPORTS_CSV_NA_NAMES"]
df = pd.read_csv(
StringIO(data),
keep_default_na=na_values is None,
na_values=na_values,
)
# convert all columns to verbose (label) name
if datasource:
+9
View File
@@ -1354,6 +1354,15 @@ ALLOWED_USER_CSV_SCHEMA_FUNC = allowed_schemas_for_csv_upload
# Values that should be treated as nulls for the csv uploads.
CSV_DEFAULT_NA_NAMES = list(STR_NA_VALUES)
# Values that should be treated as nulls for scheduled reports CSV processing.
# If not set or None, defaults to standard pandas NA handling behavior.
# Set to a custom list to control which values should be treated as null.
# Examples:
# REPORTS_CSV_NA_NAMES = None # Use default pandas NA handling (backwards compatible)
# REPORTS_CSV_NA_NAMES = [] # Disable all automatic NA conversion
# REPORTS_CSV_NA_NAMES = ["", "NULL", "null"] # Only treat these specific values as NA
REPORTS_CSV_NA_NAMES: list[str] | None = None
# Chunk size for reading CSV files during uploads
# Smaller values use less memory but may be slower for large files
READ_CSV_CHUNK_SIZE = 1000
+1
View File
@@ -59,6 +59,7 @@ class GuestUser(AnonymousUserMixin):
"""
is_guest_user = True
active = True
@property
def is_authenticated(self) -> bool:
@@ -751,10 +751,10 @@ msgstr "AQE"
msgid "AUG"
msgstr "اغسطس"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "هامش عنوان المحور"
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "موضع عنوان المحور"
msgid "About"
@@ -13452,7 +13452,7 @@ msgstr "اكتب وصفًا لاستعلامك"
msgid "Write a handlebars template to render the data"
msgstr "اكتب قالب المقاود لعرض البيانات"
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "هامش عنوان المحور X"
msgid "X Axis"
@@ -13501,7 +13501,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr "حدود Y 2"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "هامش عنوان المحور Y"
msgid "Y Axis"
@@ -780,10 +780,10 @@ msgstr "AQE"
msgid "AUG"
msgstr "AGO"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "MARGE DEL TÍTOL DE L'EIX"
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "POSICIÓ DEL TÍTOL DE L'EIX"
msgid "About"
@@ -13231,7 +13231,7 @@ msgstr "Escriu una descripció per la teva consulta"
msgid "Write a handlebars template to render the data"
msgstr "Escriu una plantilla handlebars per renderitzar les dades"
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "MARGE DEL TÍTOL DE L'EIX X"
msgid "X Axis"
@@ -13279,7 +13279,7 @@ msgstr "XYZ"
msgid "Y 2 bounds"
msgstr "Límits Y 2"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "MARGE DEL TÍTOL DE L'EIX Y"
msgid "Y Axis"
@@ -794,10 +794,10 @@ msgstr "AQE"
msgid "AUG"
msgstr "AUG"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "ABSTAND DES ACHSENTITELS"
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "Y-ACHSE TITEL POSITION"
msgid "About"
@@ -13868,7 +13868,7 @@ msgstr "Beschreibung Ihrer Anfrage"
msgid "Write a handlebars template to render the data"
msgstr "Handlebars-Template zur Darstellung der Daten verfassen"
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "X AXIS TITLE MARGIN"
msgid "X Axis"
@@ -13917,7 +13917,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr "Y 2 Grenzen"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "Y-ACHSE TITEL RAND"
msgid "Y Axis"
@@ -689,10 +689,10 @@ msgstr ""
msgid "AUG"
msgstr ""
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr ""
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr ""
msgid "About"
@@ -12368,7 +12368,7 @@ msgstr ""
msgid "Write a handlebars template to render the data"
msgstr ""
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr ""
msgid "X Axis"
@@ -12416,7 +12416,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr ""
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr ""
msgid "Y Axis"
@@ -689,10 +689,10 @@ msgstr "AQE"
msgid "AUG"
msgstr "AGO"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "MARGEN DEL TÍTULO DEL EJE"
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "POSICIÓN DEL TÍTULO DEL EJE"
msgid "About"
@@ -12368,7 +12368,7 @@ msgstr "Escribe una descripción para tu consulta"
msgid "Write a handlebars template to render the data"
msgstr "Elabora una plantilla de Handlebars para renderizar los datos"
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "MARGEN DEL TÍTULO DEL EJE X"
msgid "X Axis"
@@ -12416,7 +12416,7 @@ msgstr "XYZ"
msgid "Y 2 bounds"
msgstr "Límites de Y 2"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "MARGEN DEL TÍTULO DEL EJE Y"
msgid "Y Axis"
@@ -767,10 +767,10 @@ msgstr ""
msgid "AUG"
msgstr "آگوست"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "حاشیه عنوان محور"
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "محل عنوان محور"
msgid "About"
@@ -13425,7 +13425,7 @@ msgstr "توضیحی برای کوئری خود بنویسید."
msgid "Write a handlebars template to render the data"
msgstr "یک الگوی هندل‌بارز برای نمایش داده‌ها بنویسید."
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "حاشیه عنوان محور ایکس"
msgid "X Axis"
@@ -13474,7 +13474,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr "بازه‌های Y ۲"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "حاشیه عنوان محور Y"
msgid "Y Axis"
@@ -852,10 +852,10 @@ msgstr "AQE"
msgid "AUG"
msgstr "AUG"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "MARGE DU TITRE DE L'AXE"
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "POSITION DU TITRE DE L'AXE"
msgid "About"
@@ -16095,7 +16095,7 @@ msgstr "Écrire une description pour votre requête"
msgid "Write a handlebars template to render the data"
msgstr "Écrire un modèle handlebar pour afficher les données"
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "MARGE DU TITRE DE L'AXE DES ABCISSES"
msgid "X Axis"
@@ -16148,7 +16148,7 @@ msgstr "XYZ"
msgid "Y 2 bounds"
msgstr "Limites ordonnées 2"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "MARGE DU TITRE DE L'AXE DES ORDONNÉES"
msgid "Y Axis"
@@ -718,11 +718,11 @@ msgstr ""
msgid "AUG"
msgstr ""
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr ""
#, fuzzy
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "Testa la Connessione"
msgid "About"
@@ -13284,7 +13284,7 @@ msgstr ""
msgid "Write a handlebars template to render the data"
msgstr ""
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr ""
msgid "X Axis"
@@ -13336,7 +13336,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr ""
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr ""
msgid "Y Axis"
@@ -725,10 +725,10 @@ msgstr "AQE"
msgid "AUG"
msgstr "8月"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "軸のタイトルの余白"
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "軸タイトルの位置"
msgid "About"
@@ -12622,7 +12622,7 @@ msgstr "クエリの説明を書いてください"
msgid "Write a handlebars template to render the data"
msgstr "データをレンダリングするハンドルバー テンプレートを作成する"
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "X 軸のタイトルマージン"
msgid "X Axis"
@@ -12670,7 +12670,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr "Y 2 バウンド"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "Y 軸のタイトルマージン"
msgid "Y Axis"
@@ -715,10 +715,10 @@ msgstr ""
msgid "AUG"
msgstr ""
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr ""
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr ""
msgid "About"
@@ -13159,7 +13159,7 @@ msgstr ""
msgid "Write a handlebars template to render the data"
msgstr ""
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr ""
msgid "X Axis"
@@ -13209,7 +13209,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr ""
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr ""
msgid "Y Axis"
+4 -4
View File
@@ -695,10 +695,10 @@ msgstr ""
msgid "AUG"
msgstr ""
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr ""
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr ""
msgid "About"
@@ -12353,7 +12353,7 @@ msgstr ""
msgid "Write a handlebars template to render the data"
msgstr ""
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr ""
msgid "X Axis"
@@ -12401,7 +12401,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr ""
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr ""
msgid "Y Axis"
@@ -757,10 +757,10 @@ msgstr "AQE"
msgid "AUG"
msgstr "AUG"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "AXIS TITEL MARGIN"
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "AXIS TITEL POSITIE"
msgid "About"
@@ -13691,7 +13691,7 @@ msgstr "Schrijf een omschrijving voor uw query"
msgid "Write a handlebars template to render the data"
msgstr "Schrijf een handlebars sjabloon om de gegevens weer te geven"
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "X AXIS TITEL MARGE"
msgid "X Axis"
@@ -13740,7 +13740,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr "Y 2 grenzen"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "Y AXIS TITEL MARGE"
msgid "Y Axis"
@@ -796,11 +796,11 @@ msgstr "AQE"
msgid "AUG"
msgstr "SIE"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "MARGINES TYTUŁU OSI"
#, fuzzy
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "POZYCJA TYTUŁU OSI"
msgid "About"
@@ -14280,7 +14280,7 @@ msgstr "Napisz opis swojego zapytania"
msgid "Write a handlebars template to render the data"
msgstr "Napisz szablon handlebars do renderowania danych"
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "MARGINES TYTUŁU OSI X"
msgid "X Axis"
@@ -14335,7 +14335,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr "Granice osi Y 2"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "MARGINES TYTUŁU OSI Y"
msgid "Y Axis"
@@ -726,10 +726,10 @@ msgstr ""
msgid "AUG"
msgstr ""
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr ""
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr ""
msgid "About"
@@ -13486,7 +13486,7 @@ msgstr "Escreva uma descrição para sua consulta"
msgid "Write a handlebars template to render the data"
msgstr ""
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr ""
msgid "X Axis"
@@ -13537,7 +13537,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr ""
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr ""
msgid "Y Axis"
@@ -769,10 +769,10 @@ msgstr "AQE"
msgid "AUG"
msgstr "AGO"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "MARGEM DO EIXO DO TÍTULO "
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "POSIÇÃO DO EIXO DO TÍTULO"
msgid "About"
@@ -13862,7 +13862,7 @@ msgstr "Escreva uma descrição para sua consulta"
msgid "Write a handlebars template to render the data"
msgstr "Escreva um modelo de guidão para renderizar os dados"
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "MARGEM DO TÍTULO DO EIXO X"
msgid "X Axis"
@@ -13912,7 +13912,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr "Y 2 limites"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "MARGEM DO TÍTULO DO EIXO Y"
msgid "Y Axis"
@@ -799,10 +799,10 @@ msgstr "Асинхронные запросы"
msgid "AUG"
msgstr "АВГ"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "Отступ заголовка оси"
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "Положение заголовка оси"
msgid "About"
@@ -14035,7 +14035,7 @@ msgstr "Заполните описание к вашему запросу"
msgid "Write a handlebars template to render the data"
msgstr "Напишите шаблон Handlebars для отображения данных"
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "Отступ заголовка оси X"
msgid "X Axis"
@@ -14089,7 +14089,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr "Границы оси Y 2"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "Отступ заголовка оси Y"
msgid "Y Axis"
@@ -693,10 +693,10 @@ msgstr ""
msgid "AUG"
msgstr ""
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr ""
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr ""
msgid "About"
@@ -12502,7 +12502,7 @@ msgstr ""
msgid "Write a handlebars template to render the data"
msgstr ""
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr ""
msgid "X Axis"
@@ -12550,7 +12550,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr ""
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr ""
msgid "Y Axis"
@@ -787,10 +787,10 @@ msgstr "AQE"
msgid "AUG"
msgstr "AVG"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "OBROBA OZNAKE OSI"
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "POLOŽAJ OZNAKE OSI"
msgid "About"
@@ -13457,7 +13457,7 @@ msgstr "Dodajte opis vaše poizvedbe"
msgid "Write a handlebars template to render the data"
msgstr "Napišite Handlebars-predlogo za prikaz podatkov"
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "OBROBA NASLOVA X-OSI"
msgid "X Axis"
@@ -13506,7 +13506,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr "Meje Y-osi 2"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "OBROBA NASLOVA Y-OSI"
msgid "Y Axis"
@@ -691,10 +691,10 @@ msgstr ""
msgid "AUG"
msgstr "AĞU"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr ""
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr ""
msgid "About"
@@ -12543,7 +12543,7 @@ msgstr ""
msgid "Write a handlebars template to render the data"
msgstr ""
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr ""
msgid "X Axis"
@@ -12591,7 +12591,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr ""
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr ""
msgid "Y Axis"
@@ -759,10 +759,10 @@ msgstr "AQE"
msgid "AUG"
msgstr "Серпень"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "ЗАВДАННЯ ВІСІВ"
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "Позиція заголовка вісь"
msgid "About"
@@ -13662,7 +13662,7 @@ msgstr "Напишіть опис свого запиту"
msgid "Write a handlebars template to render the data"
msgstr "Напишіть шаблон ручки для надання даних"
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr ""
msgid "X Axis"
@@ -13712,7 +13712,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr "Y 2 межі"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "Y Exis title Margin"
msgid "Y Axis"
@@ -730,11 +730,11 @@ msgstr "异步执行查询"
msgid "AUG"
msgstr "八月"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "轴标题边距"
#, fuzzy
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "轴标题的位置"
msgid "About"
@@ -13364,7 +13364,7 @@ msgstr "为您的查询写一段描述"
msgid "Write a handlebars template to render the data"
msgstr ""
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "X 轴标题边距"
msgid "X Axis"
@@ -13416,7 +13416,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr "Y 界限"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "Y 轴标题边距"
msgid "Y Axis"
@@ -729,11 +729,11 @@ msgstr "異步執行查詢"
msgid "AUG"
msgstr "八月"
msgid "AXIS TITLE MARGIN"
msgid "Axis title margin"
msgstr "軸標題邊距"
#, fuzzy
msgid "AXIS TITLE POSITION"
msgid "Axis title position"
msgstr "軸標題的位置"
msgid "About"
@@ -13378,7 +13378,7 @@ msgstr "為您的查詢寫一段描述"
msgid "Write a handlebars template to render the data"
msgstr ""
msgid "X AXIS TITLE MARGIN"
msgid "X axis title margin"
msgstr "X 軸標題邊距"
msgid "X Axis"
@@ -13430,7 +13430,7 @@ msgstr ""
msgid "Y 2 bounds"
msgstr "Y 界限"
msgid "Y AXIS TITLE MARGIN"
msgid "Y axis title margin"
msgstr "Y 軸標題邊距"
msgid "Y Axis"
+64 -17
View File
@@ -120,15 +120,26 @@ def take_tiled_screenshot(
dashboard_top,
)
# Calculate number of tiles needed
num_tiles = max(1, (dashboard_height + viewport_height - 1) // viewport_height)
# Get actual viewport height to ensure we don't skip content
actual_viewport_height = page.viewport_size["height"]
tile_height = min(viewport_height, actual_viewport_height)
logger.info(
"Viewport: configured=%s, actual=%s, using tile_height=%s",
viewport_height,
actual_viewport_height,
tile_height,
)
# Calculate number of tiles needed based on actual tile height
num_tiles = max(1, (dashboard_height + tile_height - 1) // tile_height)
logger.info("Taking %s screenshot tiles", num_tiles)
screenshot_tiles = []
for i in range(num_tiles):
# Calculate scroll position to show this tile's content
scroll_y = dashboard_top + (i * viewport_height)
scroll_y = dashboard_top + (i * tile_height)
# Scroll the window to the desired position
page.evaluate(f"window.scrollTo(0, {scroll_y})")
@@ -139,29 +150,65 @@ def take_tiled_screenshot(
# Wait for scroll to settle and content to load
page.wait_for_timeout(2000) # 2 second wait per tile
# Get the current element position after scroll
current_element_box = page.evaluate(f"""() => {{
# Get the current element position after scroll and viewport size
viewport_info = page.evaluate(f"""() => {{
const el = document.querySelector(".{element_name}");
const rect = el.getBoundingClientRect();
return {{
x: rect.left,
y: rect.top,
width: rect.width,
height: rect.height
elementX: rect.left,
elementY: rect.top,
elementWidth: rect.width,
elementHeight: rect.height,
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight
}};
}}""")
# Calculate what portion of the element we want to capture for this tile
tile_start_in_element = i * viewport_height
remaining_content = dashboard_height - tile_start_in_element
tile_content_height = min(viewport_height, remaining_content)
# Ensure clip coordinates are within viewport bounds
# If element.top is negative, it's scrolled above viewport - start from y=0
clip_y = max(0, viewport_info["elementY"])
# If element.left is negative, start from x=0
clip_x = max(0, viewport_info["elementX"])
# Calculate clip dimensions - capture what's visible of the element
# Handle elements scrolled above viewport: if elementY is negative,
# only the portion from (elementY + elementHeight) is visible
if viewport_info["elementY"] < 0:
# Element extends from above viewport - calculate visible portion
visible_height = (
viewport_info["elementY"] + viewport_info["elementHeight"]
)
clip_height = min(visible_height, viewport_info["viewportHeight"])
else:
# Element is within viewport
clip_height = min(
viewport_info["elementHeight"],
viewport_info["viewportHeight"] - clip_y,
)
clip_width = min(
viewport_info["elementWidth"], viewport_info["viewportWidth"] - clip_x
)
# Validate clip region before taking screenshot
if clip_width <= 0 or clip_height <= 0:
logger.warning(
"Skipping tile %s/%s - invalid clip dimensions: %sx%s at (%s, %s)",
i + 1,
num_tiles,
clip_width,
clip_height,
clip_x,
clip_y,
)
continue
# Clip to capture only the current tile portion of the element
clip = {
"x": current_element_box["x"],
"y": current_element_box["y"],
"width": current_element_box["width"],
"height": min(tile_content_height, current_element_box["height"]),
"x": clip_x,
"y": clip_y,
"width": clip_width,
"height": clip_height,
}
# Take screenshot with clipping to capture only this tile's content
+17 -3
View File
@@ -216,6 +216,13 @@ class WebDriverPlaywright(WebDriverProxy):
return error_messages
@staticmethod
def _get_screenshot(page: Page, element: Locator, element_name: str) -> bytes:
if element_name == "standalone":
return page.screenshot(full_page=True)
else:
return element.screenshot()
def get_screenshot( # pylint: disable=too-many-locals, too-many-statements # noqa: C901
self, url: str, element_name: str, user: User
) -> bytes | None:
@@ -363,11 +370,18 @@ class WebDriverPlaywright(WebDriverProxy):
"falling back to standard screenshot"
)
)
img = element.screenshot()
img = WebDriverPlaywright._get_screenshot(
page, element, element_name
)
else:
img = element.screenshot()
img = WebDriverPlaywright._get_screenshot(
page, element, element_name
)
else:
img = element.screenshot()
img = WebDriverPlaywright._get_screenshot(
page, element, element_name
)
except PlaywrightTimeout:
# raise again for the finally block, but handled above
pass
+13 -4
View File
@@ -46,7 +46,6 @@ from sqlalchemy.exc import SQLAlchemyError
from werkzeug.utils import safe_join
from superset import (
appbuilder,
db,
event_logger,
is_feature_enabled,
@@ -80,6 +79,7 @@ from superset.models.slice import Slice
from superset.models.sql_lab import Query
from superset.models.user_attributes import UserAttribute
from superset.superset_typing import FlaskResponse
from superset.tasks.utils import get_current_user
from superset.utils import core as utils, json
from superset.utils.cache import etag_cache
from superset.utils.core import (
@@ -108,6 +108,7 @@ from superset.views.utils import (
get_form_data,
get_viz,
loads_request_json,
redirect_to_login,
sanitize_datasource_data,
)
from superset.viz import BaseViz
@@ -765,13 +766,21 @@ class Superset(BaseSupersetView):
dashboard = Dashboard.get(dashboard_id_or_slug)
if not dashboard:
if not get_current_user():
return redirect_to_login()
abort(404)
# Redirect anonymous users to login for unpublished dashboards,
# in the edge case where a dataset has been shared with public
if not get_current_user() and not dashboard.published:
return redirect_to_login()
try:
dashboard.raise_for_access()
except SupersetSecurityException:
# Return 404 to avoid revealing dashboard existence
return Response(status=404)
if not get_current_user():
return redirect_to_login()
abort(404)
add_extra_log_payload(
dashboard_id=dashboard.id,
dashboard_version="v2",
@@ -882,7 +891,7 @@ class Superset(BaseSupersetView):
def welcome(self) -> FlaskResponse:
"""Personalized welcome page"""
if not g.user or not get_user_id():
return redirect(appbuilder.get_url_for_login)
return redirect_to_login()
if welcome_dashboard_id := (
db.session.query(UserAttribute.welcome_dashboard_id)
+12 -5
View File
@@ -25,7 +25,6 @@ from typing import Any, Callable, cast
from flask import (
Flask,
redirect,
request,
Response,
send_file,
@@ -34,7 +33,6 @@ from flask_wtf.csrf import CSRFError
from sqlalchemy import exc
from werkzeug.exceptions import HTTPException
from superset import appbuilder
from superset.commands.exceptions import CommandException, CommandInvalidError
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
from superset.exceptions import (
@@ -46,6 +44,7 @@ from superset.exceptions import (
from superset.superset_typing import FlaskResponse
from superset.utils import core as utils, json
from superset.utils.log import get_logger_from_status
from superset.views.utils import redirect_to_login
if typing.TYPE_CHECKING:
from superset.views.base import BaseSupersetView
@@ -153,7 +152,7 @@ def set_app_error_handlers(app: Flask) -> None: # noqa: C901
if request.is_json:
return show_http_exception(ex)
return redirect(appbuilder.get_url_for_login)
return redirect_to_login()
@app.errorhandler(HTTPException)
def show_http_exception(ex: HTTPException) -> FlaskResponse:
@@ -165,7 +164,11 @@ def set_app_error_handlers(app: Flask) -> None: # noqa: C901
and ex.code in {404, 500}
):
path = files("superset") / f"static/assets/{ex.code}.html"
return send_file(path, max_age=0), ex.code
# Try to serve HTML file; fall back to JSON if not built
try:
return send_file(path, max_age=0), ex.code
except FileNotFoundError:
pass
return json_error_response(
[
@@ -189,7 +192,11 @@ def set_app_error_handlers(app: Flask) -> None: # noqa: C901
if "text/html" in request.accept_mimetypes and not app.config["DEBUG"]:
path = files("superset") / "static/assets/500.html"
return send_file(path, max_age=0), 500
# Try to serve HTML file; fall back to JSON if not built
try:
return send_file(path, max_age=0), 500
except FileNotFoundError:
pass
extra = ex.normalized_messages() if isinstance(ex, CommandInvalidError) else {}
return json_error_response(
+31 -3
View File
@@ -19,16 +19,17 @@ import logging
from collections import defaultdict
from functools import wraps
from typing import Any, Callable, DefaultDict, Optional, Union
from urllib import parse
import msgpack
import pyarrow as pa
from flask import current_app as app, g, has_request_context, request
from flask import current_app as app, g, has_request_context, redirect, request
from flask_appbuilder.security.sqla import models as ab_models
from flask_appbuilder.security.sqla.models import User
from flask_babel import _
from sqlalchemy.exc import NoResultFound
from superset import dataframe, db, result_set, viz
from superset import appbuilder, dataframe, db, result_set, viz
from superset.common.db_query_status import QueryStatus
from superset.daos.datasource import DatasourceDAO
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
@@ -44,7 +45,7 @@ from superset.models.core import Database
from superset.models.dashboard import Dashboard
from superset.models.slice import Slice
from superset.models.sql_lab import Query
from superset.superset_typing import FormData
from superset.superset_typing import FlaskResponse, FormData
from superset.utils import json
from superset.utils.core import DatasourceType
from superset.utils.decorators import stats_timing
@@ -58,6 +59,33 @@ if not feature_flag_manager.is_feature_enabled("ENABLE_JAVASCRIPT_CONTROLS"):
REJECTED_FORM_DATA_KEYS = ["js_tooltip", "js_onclick_href", "js_data_mutator"]
def redirect_to_login(next_target: str | None = None) -> FlaskResponse:
"""Return a redirect response to the login view, preserving target URL.
When ``next_target`` is ``None`` the current request path (including query
string) is used, provided a request context is available. The resulting URL
always remains relative, mirroring Flask-AppBuilder expectations.
"""
login_url = appbuilder.get_url_for_login
parsed = parse.urlparse(login_url)
query = parse.parse_qs(parsed.query, keep_blank_values=True)
target = next_target
if target is None and has_request_context():
if request.query_string:
target = request.full_path.rstrip("?")
else:
target = request.path
if target:
query["next"] = [target]
encoded_query = parse.urlencode(query, doseq=True)
redirect_url = parse.urlunparse(parsed._replace(query=encoded_query))
return redirect(redirect_url)
def sanitize_datasource_data(datasource_data: dict[str, Any]) -> dict[str, Any]:
if datasource_data:
datasource_database = datasource_data.get("database")
+68 -6
View File
@@ -18,8 +18,8 @@
"""Unit tests for Superset"""
import re
import unittest
from random import random
from urllib.parse import parse_qs, urlparse
import pytest
from flask import Response, escape, url_for
@@ -52,7 +52,7 @@ from tests.integration_tests.fixtures.world_bank_dashboard import (
load_world_bank_data, # noqa: F401
)
from .base_tests import SupersetTestCase
from .base_tests import DEFAULT_PASSWORD, SupersetTestCase
class TestDashboard(SupersetTestCase):
@@ -186,6 +186,72 @@ class TestDashboard(SupersetTestCase):
# Cleanup
self.revoke_public_access_to_table(table)
@pytest.mark.usefixtures(
"load_energy_table_with_slice",
"load_dashboard",
)
def test_anonymous_user_redirects_to_login_with_next(self):
self.logout()
target_path = f"/superset/dashboard/{pytest.hidden_dash_slug}/"
response = self.client.get(target_path, follow_redirects=False)
assert response.status_code == 302
redirect_location = response.headers["Location"]
parsed = urlparse(redirect_location)
assert parsed.path.rstrip("/") == "/login"
next_values = parse_qs(parsed.query).get("next")
assert next_values is not None
assert next_values[0].endswith(target_path)
login_target = (
f"{parsed.path}{'?' + parsed.query if parsed.query else ''}"
if parsed.scheme or parsed.netloc
else redirect_location
)
login_response = self.client.post(
login_target,
data={"username": ADMIN_USERNAME, "password": DEFAULT_PASSWORD},
follow_redirects=False,
)
assert login_response.status_code == 302
assert login_response.headers["Location"].endswith(target_path)
target_response: Response = self.client.get(target_path, follow_redirects=False)
assert target_response.status_code == 200
def test_anonymous_user_redirects_to_login_for_missing_dashboard(self):
self.logout()
target_path = "/superset/dashboard/nonexistent-dashboard/"
response = self.client.get(target_path, follow_redirects=False)
assert response.status_code == 302
parsed = urlparse(response.headers["Location"])
assert parsed.path.rstrip("/") == "/login"
next_values = parse_qs(parsed.query).get("next")
assert next_values is not None
assert next_values[0].endswith(target_path)
@pytest.mark.usefixtures(
"public_role_like_gamma",
"load_energy_table_with_slice",
"load_dashboard",
)
def test_authenticated_user_without_access_gets_404(self):
self.login(GAMMA_USERNAME)
target_path = f"/superset/dashboard/{pytest.hidden_dash_slug}/"
response = self.client.get(
target_path,
follow_redirects=False,
headers={"Accept": "text/html"},
)
assert response.status_code == 404
@pytest.mark.usefixtures(
"public_role_like_gamma",
"load_energy_table_with_slice",
@@ -248,7 +314,3 @@ class TestDashboard(SupersetTestCase):
db.session.commit()
assert f"/superset/dashboard/{slug}/" not in resp
if __name__ == "__main__":
unittest.main()
@@ -108,7 +108,7 @@ class TestDashboardRoleBasedSecurity(BaseTestDashboardSecurity):
# act
response = self.get_dashboard_view_response(dashboard_to_access)
assert response.status_code == 404
assert response.status_code == 404 # Authenticated users without access get 404
request_payload = get_query_context("birth_names")
rv = self.post_assert_metric(CHART_DATA_URI, request_payload, "data")
@@ -221,7 +221,8 @@ class TestDashboardRoleBasedSecurity(BaseTestDashboardSecurity):
response = self.get_dashboard_view_response(dashboard_to_access)
# assert
assert response.status_code == 404
# Anonymous users are redirected to login instead of getting 404
assert response.status_code == 302
@pytest.mark.usefixtures("public_role_like_gamma")
def test_get_dashboard_view__public_user_with_dashboard_permission_can_not_access_draft( # noqa: E501
@@ -234,7 +235,8 @@ class TestDashboardRoleBasedSecurity(BaseTestDashboardSecurity):
response = self.get_dashboard_view_response(dashboard_to_access)
# assert
assert response.status_code == 404
# Anonymous users are redirected to login for unpublished dashboards
assert response.status_code == 302
# post
revoke_access_to_dashboard(dashboard_to_access, "Public") # noqa: F405
@@ -190,6 +190,8 @@ def delete_all_inserted_objects() -> None:
def delete_all_inserted_dashboards():
try:
# Expire all objects to ensure fresh state after potential rollbacks
db.session.expire_all()
dashboards_to_delete: list[Dashboard] = (
db.session.query(Dashboard)
.filter(Dashboard.id.in_(inserted_dashboards_ids))
@@ -16,7 +16,7 @@
# under the License.
from random import randint
from unittest.mock import patch
from unittest.mock import MagicMock, patch
import pytest
from flask_appbuilder.security.sqla.models import User
@@ -25,7 +25,7 @@ from freezegun.api import FakeDatetime
from superset.extensions import db
from superset.reports.models import ReportScheduleType
from superset.tasks.scheduler import execute, scheduler
from superset.tasks.scheduler import execute, log_task_failure, scheduler
from tests.integration_tests.reports.utils import insert_report_schedule
from tests.integration_tests.test_app import app
@@ -201,3 +201,48 @@ def test_execute_task_with_command_exception(
db.session.delete(report_schedule)
db.session.commit()
@patch("superset.tasks.scheduler.logger")
def test_log_task_failure_with_sender(logger_mock):
"""
Test that log_task_failure logs correctly when sender is provided
"""
mock_task = MagicMock()
mock_task.name = "test.task.name"
mock_exception = Exception("Test error")
mock_einfo = MagicMock()
log_task_failure(
sender=mock_task,
task_id="test-task-id",
exception=mock_exception,
einfo=mock_einfo,
)
logger_mock.exception.assert_called_once_with(
"Celery task %s failed: %s",
"test.task.name",
mock_exception,
exc_info=mock_einfo,
)
@patch("superset.tasks.scheduler.logger")
def test_log_task_failure_without_sender(logger_mock):
"""
Test that log_task_failure logs correctly when sender is None
"""
mock_exception = Exception("Test error")
mock_einfo = MagicMock()
log_task_failure(
sender=None,
task_id="test-task-id",
exception=mock_exception,
einfo=mock_einfo,
)
logger_mock.exception.assert_called_once_with(
"Celery task %s failed: %s", "Unknown", mock_exception, exc_info=mock_einfo
)
@@ -24,6 +24,7 @@ from sqlalchemy.orm.session import Session
from superset.charts.client_processing import apply_client_processing, pivot_df, table
from superset.common.chart_data import ChartDataResultFormat
from superset.utils.core import GenericDataType
from tests.conftest import with_config
def test_pivot_df_no_cols_no_rows_single_metric():
@@ -2653,3 +2654,137 @@ def test_pivot_multi_level_index():
| ('Total (Sum)', '', '') | 210 | 105 | 0 |
""".strip()
)
@with_config({"REPORTS_CSV_NA_NAMES": []})
def test_apply_client_processing_csv_format_preserves_na_strings():
"""
Test that apply_client_processing preserves "NA" when REPORTS_CSV_NA_NAMES is [].
This ensures that scheduled reports can be configured to
preserve strings like "NA" as literal values.
"""
# CSV data with "NA" string that should be preserved
csv_data = "first_name,last_name\nJeff,Smith\nAlice,NA"
result = {
"queries": [
{
"result_format": ChartDataResultFormat.CSV,
"data": csv_data,
}
]
}
form_data = {
"datasource": "1__table",
"viz_type": "table",
"slice_id": 1,
"url_params": {},
"metrics": [],
"groupby": [],
"columns": ["first_name", "last_name"],
"extra_form_data": {},
"force": False,
"result_format": "csv",
"result_type": "results",
}
# Test with REPORTS_CSV_NA_NAMES set to empty list (disable NA conversion)
processed_result = apply_client_processing(result, form_data)
# Verify the CSV data still contains "NA" as string, not converted to null
output_data = processed_result["queries"][0]["data"]
assert "NA" in output_data
# The "NA" should be preserved in the output CSV
lines = output_data.strip().split("\n")
assert "Alice,NA" in lines[2] # Second data row should preserve "NA"
@with_config({"REPORTS_CSV_NA_NAMES": ["MISSING"]})
def test_apply_client_processing_csv_format_custom_na_values():
"""
Test that apply_client_processing respects custom NA values configuration.
"""
csv_data = "name,status\nJeff,MISSING\nAlice,OK"
result = {
"queries": [
{
"result_format": ChartDataResultFormat.CSV,
"data": csv_data,
}
]
}
form_data = {
"datasource": "1__table",
"viz_type": "table",
"slice_id": 1,
"url_params": {},
"metrics": [],
"groupby": [],
"columns": ["name", "status"],
"extra_form_data": {},
"force": False,
"result_format": "csv",
"result_type": "results",
}
# Test with custom NA values - only "MISSING" should be treated as NA
processed_result = apply_client_processing(result, form_data)
output_data = processed_result["queries"][0]["data"]
lines = output_data.strip().split("\n")
assert len(lines) >= 3 # header + 2 data rows
assert "Jeff," in lines[1] # First data row should have empty status after "Jeff,"
assert "Alice,OK" in lines[2] # Second data row should preserve "OK"
@with_config({"REPORTS_CSV_NA_NAMES": []})
def test_apply_client_processing_csv_format_default_na_behavior():
"""
Test that apply_client_processing uses default pandas NA behavior
when REPORTS_CSV_NA_NAMES is not configured.
This ensures backwards compatibility.
"""
# CSV data with "NA" string that should be converted to null in default behavior
csv_data = "first_name,last_name\nJeff,Smith\nAlice,NA"
result = {
"queries": [
{
"result_format": ChartDataResultFormat.CSV,
"data": csv_data,
}
]
}
form_data = {
"datasource": "1__table",
"viz_type": "table",
"slice_id": 1,
"url_params": {},
"metrics": [],
"groupby": [],
"columns": ["first_name", "last_name"],
"extra_form_data": {},
"force": False,
"result_format": "csv",
"result_type": "results",
}
processed_result = apply_client_processing(result, form_data)
# Verify the CSV data has "NA" converted to empty (default pandas behavior)
output_data = processed_result["queries"][0]["data"]
lines = output_data.strip().split("\n")
assert len(lines) >= 3 # header + 2 data rows
# The "NA" should be converted to empty by default pandas behavior
assert (
"Alice," in lines[2]
) # Second data row should have empty last_name (NA converted to null)
+54
View File
@@ -127,6 +127,21 @@ def fake_get_chart_csv_data_hierarchical(chart_url, auth_cookies=None):
return json.dumps(fake_result).encode("utf-8")
def fake_get_chart_csv_data_with_na_values(chart_url, auth_cookies=None):
# Return JSON with data containing "NA" string value that will be treated as null
fake_result = {
"result": [
{
"data": {"first_name": ["Jeff", "Alice"], "last_name": ["Smith", "NA"]},
"coltypes": [GenericDataType.STRING, GenericDataType.STRING],
"colnames": ["first_name", "last_name"],
"indexnames": ["idx1", "idx2"],
}
]
}
return json.dumps(fake_result).encode("utf-8")
def test_df_to_escaped_csv():
df = pd.DataFrame(
data={
@@ -263,3 +278,42 @@ def test_get_chart_dataframe_with_hierarchical_columns(monkeypatch: pytest.Monke
| ('idx',) | 2 |
"""
assert markdown_str.strip() == expected_markdown_str.strip()
def test_get_chart_dataframe_preserves_na_string_values(
monkeypatch: pytest.MonkeyPatch,
):
"""
Test that get_chart_dataframe currently preserves rows containing "NA"
string values.
This test verifies the existing behavior before implementing custom NA handling.
"""
monkeypatch.setattr(
csv, "get_chart_csv_data", fake_get_chart_csv_data_with_na_values
)
df = get_chart_dataframe("http://dummy-url")
assert df is not None
# Verify the DataFrame structure
expected_columns = pd.MultiIndex.from_tuples([("first_name",), ("last_name",)])
pd.testing.assert_index_equal(df.columns, expected_columns)
expected_index = pd.MultiIndex.from_tuples([("idx1",), ("idx2",)])
pd.testing.assert_index_equal(df.index, expected_index)
# Check that we have both rows initially
assert len(df) == 2
# Verify the data contains the "NA" string value (not converted to NaN)
pd.testing.assert_series_equal(
df[("first_name",)],
pd.Series(["Jeff", "Alice"], name=("first_name",), index=df.index),
)
pd.testing.assert_series_equal(
df[("last_name",)],
pd.Series(["Smith", "NA"], name=("last_name",), index=df.index),
)
last_name_values = df[("last_name",)].values
assert last_name_values[0] == "Smith"
assert last_name_values[1] == "NA"
+460 -37
View File
@@ -110,24 +110,42 @@ class TestTakeTiledScreenshot:
"""Create a mock Playwright page object."""
page = MagicMock()
# Mock viewport size
page.viewport_size = {"width": 1024, "height": 768}
# Mock element locator
element = MagicMock()
page.locator.return_value = element
# Mock element info - simulating a 5000px tall dashboard
element_info = {"height": 5000, "top": 100, "left": 50, "width": 800}
element_box = {"x": 50, "y": 200, "width": 800, "height": 600}
viewport_info = {
"elementX": 50,
"elementY": 200,
"elementWidth": 800,
"elementHeight": 600,
"viewportWidth": 1024,
"viewportHeight": 768,
}
# For 3 tiles (5000px / 2000px = 2.5, rounded up to 3):
# 1 initial call + 3 scroll + 3 element box + 1 reset scroll = 8 calls
# For 7 tiles (5000px / 768px actual viewport = 6.5, rounded up to 7):
# 1 initial call + 7 scroll + 7 viewport info + 1 reset scroll = 16 calls
page.evaluate.side_effect = [
element_info, # Initial call for dashboard dimensions
None, # First scroll call
element_box, # First element box call
None, # Second scroll call
element_box, # Second element box call
None, # Third scroll call
element_box, # Third element box call
None,
viewport_info, # Tile 1
None,
viewport_info, # Tile 2
None,
viewport_info, # Tile 3
None,
viewport_info, # Tile 4
None,
viewport_info, # Tile 5
None,
viewport_info, # Tile 6
None,
viewport_info, # Tile 7
None, # Final reset scroll call
]
@@ -150,8 +168,8 @@ class TestTakeTiledScreenshot:
assert result == b"combined_screenshot"
# Should have called screenshot method multiple times
# (3 tiles for 5000px height)
assert mock_page.screenshot.call_count == 3
# (7 tiles for 5000px height with 768px actual viewport)
assert mock_page.screenshot.call_count == 7
# Should have called combine function
mock_combine.assert_called_once()
@@ -171,16 +189,23 @@ class TestTakeTiledScreenshot:
"""Test that tiles are calculated correctly."""
# Mock dashboard height of 3500px with viewport of 2000px
element_info = {"height": 3500, "top": 100, "left": 50, "width": 800}
element_box = {"x": 50, "y": 200, "width": 800, "height": 600}
viewport_info = {
"elementX": 50,
"elementY": 200,
"elementWidth": 800,
"elementHeight": 600,
"viewportWidth": 1024,
"viewportHeight": 768,
}
# For 2 tiles (3500px / 2000px = 1.75, rounded up to 2):
# 1 initial call + 2 scroll + 2 element box + 1 reset scroll = 6 calls
# 1 initial call + 2 scroll + 2 viewport info + 1 reset scroll = 6 calls
mock_page.evaluate.side_effect = [
element_info,
None, # First scroll call
element_box, # First element box call
viewport_info, # First viewport info call
None, # Second scroll call
element_box, # Second element box call
viewport_info, # Second viewport info call
None, # Reset scroll call
]
@@ -198,38 +223,57 @@ class TestTakeTiledScreenshot:
"""Test that scroll positions are calculated correctly."""
# Override the fixture's side_effect for this specific test
element_info = {"height": 5000, "top": 100, "left": 50, "width": 800}
element_box = {"x": 50, "y": 200, "width": 800, "height": 600}
viewport_info = {
"elementX": 50,
"elementY": 200,
"elementWidth": 800,
"elementHeight": 600,
"viewportWidth": 1024,
"viewportHeight": 768,
}
mock_page.evaluate.side_effect = [
element_info, # Initial call for dashboard dimensions
None, # First scroll call
element_box, # First element box call
None, # Second scroll call
element_box, # Second element box call
None, # Third scroll call
element_box, # Third element box call
None,
viewport_info, # Tile 1
None,
viewport_info, # Tile 2
None,
viewport_info, # Tile 3
None,
viewport_info, # Tile 4
None,
viewport_info, # Tile 5
None,
viewport_info, # Tile 6
None,
viewport_info, # Tile 7
None, # Reset scroll call
]
with patch("superset.utils.screenshot_utils.combine_screenshot_tiles"):
take_tiled_screenshot(mock_page, "dashboard", viewport_height=2000)
# Check scroll positions (dashboard_top = 100)
# Check scroll positions (dashboard_top = 100, tile_height = 768)
scroll_calls = [
call
for call in mock_page.evaluate.call_args_list
if "scrollTo" in str(call)
]
# Should have scrolled to positions: 100, 2100, 4100
# Should have scrolled to positions: 100, 868, 1636, 2404, 3172, 3940, 4708
expected_scrolls = [
"window.scrollTo(0, 100)",
"window.scrollTo(0, 2100)",
"window.scrollTo(0, 4100)",
"window.scrollTo(0, 868)",
"window.scrollTo(0, 1636)",
"window.scrollTo(0, 2404)",
"window.scrollTo(0, 3172)",
"window.scrollTo(0, 3940)",
"window.scrollTo(0, 4708)",
]
actual_scrolls = [call[0][0] for call in scroll_calls]
assert len(actual_scrolls) == 4 # 3 tile scrolls + 1 reset
assert len(actual_scrolls) == 8 # 7 tile scrolls + 1 reset
for expected in expected_scrolls:
assert expected in actual_scrolls
@@ -237,16 +281,31 @@ class TestTakeTiledScreenshot:
"""Test that scroll position is reset after screenshot."""
# Override the fixture's side_effect for this specific test
element_info = {"height": 5000, "top": 100, "left": 50, "width": 800}
element_box = {"x": 50, "y": 200, "width": 800, "height": 600}
viewport_info = {
"elementX": 50,
"elementY": 200,
"elementWidth": 800,
"elementHeight": 600,
"viewportWidth": 1024,
"viewportHeight": 768,
}
mock_page.evaluate.side_effect = [
element_info, # Initial call for dashboard dimensions
None, # First scroll call
element_box, # First element box call
None, # Second scroll call
element_box, # Second element box call
None, # Third scroll call
element_box, # Third element box call
None,
viewport_info, # Tile 1
None,
viewport_info, # Tile 2
None,
viewport_info, # Tile 3
None,
viewport_info, # Tile 4
None,
viewport_info, # Tile 5
None,
viewport_info, # Tile 6
None,
viewport_info, # Tile 7
None, # Reset scroll call
]
@@ -268,7 +327,7 @@ class TestTakeTiledScreenshot:
"Dashboard: %sx%spx at (%s, %s)", 800, 5000, 50, 100
)
# Should log number of tiles with lazy logging format
mock_logger.info.assert_any_call("Taking %s screenshot tiles", 3)
mock_logger.info.assert_any_call("Taking %s screenshot tiles", 7)
def test_exception_handling_returns_none(self):
"""Test that exceptions are handled and None is returned."""
@@ -289,8 +348,8 @@ class TestTakeTiledScreenshot:
with patch("superset.utils.screenshot_utils.combine_screenshot_tiles"):
take_tiled_screenshot(mock_page, "dashboard", viewport_height=2000)
# Should have called wait_for_timeout for each tile (3 tiles)
assert mock_page.wait_for_timeout.call_count == 3
# Should have called wait_for_timeout for each tile (7 tiles)
assert mock_page.wait_for_timeout.call_count == 7
# Each wait should be 2000ms (2 seconds)
for call in mock_page.wait_for_timeout.call_args_list:
@@ -315,3 +374,367 @@ class TestTakeTiledScreenshot:
assert clip["width"] == 800
# Height should be min of viewport_height and remaining content
assert clip["height"] <= 600 # Element height from mock
def test_negative_element_position_clipped_to_zero(self):
"""Test that negative element positions are clipped to viewport bounds."""
mock_page = MagicMock()
mock_page.viewport_size = {"width": 1024, "height": 768}
# Mock element locator
element = MagicMock()
mock_page.locator.return_value = element
# Simulate element scrolled above viewport (negative Y position)
element_info = {"height": 3000, "top": 100, "left": 0, "width": 800}
viewport_info = {
"elementX": 0,
"elementY": -200, # Element is scrolled 200px above viewport
"elementWidth": 800,
"elementHeight": 600,
"viewportWidth": 1024,
"viewportHeight": 768,
}
# For 4 tiles (3000px / 768px = 3.9, rounded up to 4):
# 1 initial + 4 * (scroll + viewport info) + 1 reset = 10 calls
mock_page.evaluate.side_effect = [
element_info,
None,
viewport_info, # Tile 1
None,
viewport_info, # Tile 2
None,
viewport_info, # Tile 3
None,
viewport_info, # Tile 4
None, # Reset scroll
]
mock_page.screenshot.return_value = b"screenshot"
with patch("superset.utils.screenshot_utils.combine_screenshot_tiles"):
result = take_tiled_screenshot(mock_page, "dashboard", viewport_height=2000)
# Should complete successfully
assert result is not None
# Check that clip Y was adjusted to 0 (not negative)
screenshot_calls = mock_page.screenshot.call_args_list
for call in screenshot_calls:
clip = call[1]["clip"]
assert clip["y"] >= 0, "Clip Y should never be negative"
assert clip["x"] >= 0, "Clip X should never be negative"
def test_element_extends_beyond_viewport(self):
"""Test clipping when element extends beyond viewport boundaries."""
mock_page = MagicMock()
mock_page.viewport_size = {"width": 1024, "height": 768}
element = MagicMock()
mock_page.locator.return_value = element
element_info = {"height": 2000, "top": 0, "left": 0, "width": 1200}
# Element is wider than viewport
viewport_info = {
"elementX": 0,
"elementY": 100,
"elementWidth": 1200, # Wider than viewport
"elementHeight": 800,
"viewportWidth": 1024, # Viewport width
"viewportHeight": 768,
}
# For 3 tiles (2000px / 768px = 2.6, rounded up to 3):
# 1 initial + 3 * (scroll + viewport info) + 1 reset = 8 calls
mock_page.evaluate.side_effect = [
element_info,
None,
viewport_info, # Tile 1
None,
viewport_info, # Tile 2
None,
viewport_info, # Tile 3
None, # Reset scroll
]
mock_page.screenshot.return_value = b"screenshot"
with patch("superset.utils.screenshot_utils.combine_screenshot_tiles"):
result = take_tiled_screenshot(mock_page, "dashboard", viewport_height=2000)
assert result is not None
# Check that clip width was constrained to viewport
clip = mock_page.screenshot.call_args_list[0][1]["clip"]
assert clip["width"] <= 1024, "Clip width should not exceed viewport"
def test_invalid_clip_dimensions_skipped(self):
"""Test that tiles with invalid dimensions are skipped with a warning."""
mock_page = MagicMock()
mock_page.viewport_size = {"width": 1024, "height": 768}
element = MagicMock()
mock_page.locator.return_value = element
element_info = {"height": 4000, "top": 0, "left": 0, "width": 800}
# First tile: valid
valid_viewport_info = {
"elementX": 0,
"elementY": 100,
"elementWidth": 800,
"elementHeight": 600,
"viewportWidth": 1024,
"viewportHeight": 768,
}
# Second tile: invalid (negative height after calculation)
invalid_viewport_info = {
"elementX": 0,
"elementY": -1000, # Far above viewport
"elementWidth": 800,
"elementHeight": 100, # Not enough visible height
"viewportWidth": 1024,
"viewportHeight": 768,
}
# For 6 tiles (4000px / 768px = 5.2, rounded up to 6):
# 1 initial + 6 * (scroll + viewport info) + 1 reset = 14 calls
mock_page.evaluate.side_effect = [
element_info,
None,
valid_viewport_info, # Tile 1 - valid
None,
invalid_viewport_info, # Tile 2 - invalid, should be skipped
None,
valid_viewport_info, # Tile 3 - valid
None,
valid_viewport_info, # Tile 4 - valid
None,
valid_viewport_info, # Tile 5 - valid
None,
valid_viewport_info, # Tile 6 - valid
None, # Reset scroll
]
mock_page.screenshot.return_value = b"screenshot"
with patch("superset.utils.screenshot_utils.logger") as mock_logger:
with patch("superset.utils.screenshot_utils.combine_screenshot_tiles"):
result = take_tiled_screenshot(
mock_page, "dashboard", viewport_height=2000
)
# Should complete but with warning
assert result is not None
# Should have logged a warning about skipping tile
mock_logger.warning.assert_called_once()
warning_msg = mock_logger.warning.call_args[0][0]
assert "Skipping tile" in warning_msg
assert "invalid clip dimensions" in warning_msg
# Should have taken 5 screenshots (6 tiles - 1 invalid)
assert mock_page.screenshot.call_count == 5
def test_viewport_bounds_with_offset_element(self):
"""Test proper clipping for element with positive offset from viewport edge."""
mock_page = MagicMock()
mock_page.viewport_size = {"width": 1024, "height": 768}
element = MagicMock()
mock_page.locator.return_value = element
element_info = {"height": 2000, "top": 500, "left": 200, "width": 600}
# Element starts 200px from left edge
viewport_info = {
"elementX": 200, # Offset from left
"elementY": 150,
"elementWidth": 600,
"elementHeight": 500,
"viewportWidth": 1024,
"viewportHeight": 768,
}
# For 3 tiles (2000px / 768px = 2.6, rounded up to 3):
# 1 initial + 3 * (scroll + viewport info) + 1 reset = 8 calls
mock_page.evaluate.side_effect = [
element_info,
None,
viewport_info, # Tile 1
None,
viewport_info, # Tile 2
None,
viewport_info, # Tile 3
None, # Reset scroll
]
mock_page.screenshot.return_value = b"screenshot"
with patch("superset.utils.screenshot_utils.combine_screenshot_tiles"):
result = take_tiled_screenshot(mock_page, "dashboard", viewport_height=2000)
assert result is not None
# Check clip respects element position
clip = mock_page.screenshot.call_args_list[0][1]["clip"]
assert clip["x"] == 200, "Should preserve element X offset"
assert clip["y"] == 150, "Should preserve element Y offset"
assert clip["width"] == 600, "Should use element width"
def test_zero_width_element_skipped(self):
"""Test that elements with zero or negative width are skipped."""
mock_page = MagicMock()
mock_page.viewport_size = {"width": 1024, "height": 768}
element = MagicMock()
mock_page.locator.return_value = element
element_info = {"height": 2000, "top": 0, "left": 0, "width": 0}
viewport_info = {
"elementX": 0,
"elementY": 100,
"elementWidth": 0, # Zero width
"elementHeight": 600,
"viewportWidth": 1024,
"viewportHeight": 768,
}
# For 3 tiles (2000px / 768px = 2.6, rounded up to 3):
# 1 initial + 3 * (scroll + viewport info) + 1 reset = 8 calls
# All tiles will be skipped due to zero width
mock_page.evaluate.side_effect = [
element_info,
None,
viewport_info, # Tile 1 - skipped
None,
viewport_info, # Tile 2 - skipped
None,
viewport_info, # Tile 3 - skipped
None, # Reset scroll
]
with patch("superset.utils.screenshot_utils.logger") as mock_logger:
with patch("superset.utils.screenshot_utils.combine_screenshot_tiles"):
result = take_tiled_screenshot(
mock_page, "dashboard", viewport_height=2000
)
# Should handle gracefully
assert result is not None
# Should have logged warnings about invalid dimensions
# (3 times, once per tile)
assert mock_logger.warning.call_count == 3
for call in mock_logger.warning.call_args_list:
warning_msg = call[0][0]
assert "invalid clip dimensions" in warning_msg
# Should not have taken any screenshots
assert mock_page.screenshot.call_count == 0
def test_element_completely_above_viewport(self):
"""Test element that is completely scrolled above the viewport."""
mock_page = MagicMock()
mock_page.viewport_size = {"width": 1024, "height": 768}
element = MagicMock()
mock_page.locator.return_value = element
element_info = {"height": 2000, "top": 0, "left": 0, "width": 800}
# Element completely above viewport
viewport_info = {
"elementX": 0,
"elementY": -800, # Completely above viewport
"elementWidth": 800,
"elementHeight": 600,
"viewportWidth": 1024,
"viewportHeight": 768,
}
# For 3 tiles (2000px / 768px = 2.6, rounded up to 3):
# 1 initial + 3 * (scroll + viewport info) + 1 reset = 8 calls
# All tiles will be skipped because element is completely above viewport
mock_page.evaluate.side_effect = [
element_info,
None,
viewport_info, # Tile 1 - skipped
None,
viewport_info, # Tile 2 - skipped
None,
viewport_info, # Tile 3 - skipped
None, # Reset scroll
]
with patch("superset.utils.screenshot_utils.logger") as mock_logger:
with patch("superset.utils.screenshot_utils.combine_screenshot_tiles"):
result = take_tiled_screenshot(
mock_page, "dashboard", viewport_height=2000
)
# Should handle gracefully
assert result is not None
# Should have skipped all 3 tiles with warnings
assert mock_logger.warning.call_count == 3
# Should not have taken screenshots
assert mock_page.screenshot.call_count == 0
def test_scroll_increment_respects_actual_viewport_height(self):
"""When config viewport height > actual viewport, we still cover every tile."""
mock_page = MagicMock()
mock_page.viewport_size = {"width": 1600, "height": 1200}
element = MagicMock()
mock_page.locator.return_value = element
element_info = {"height": 3600, "top": 0, "left": 0, "width": 800}
viewport_info = {
"elementX": 0,
"elementY": 0,
"elementWidth": 800,
"elementHeight": 1200,
"viewportWidth": 1600,
"viewportHeight": 1200,
}
mock_page.evaluate.side_effect = [
element_info, # Initial call for dashboard dimensions
None, # First scroll
viewport_info, # First viewport info
None, # Second scroll
viewport_info, # Second viewport info
None, # Third scroll
viewport_info, # Third viewport info
None, # Reset scroll
]
mock_page.screenshot.return_value = b"screenshot"
with patch("superset.utils.screenshot_utils.combine_screenshot_tiles"):
take_tiled_screenshot(mock_page, "dashboard", viewport_height=2000)
# We expect three tiles (01200, 12002400, 24003600)
# even though config says 2000.
assert mock_page.screenshot.call_count == 3
scroll_calls = [
call
for call in mock_page.evaluate.call_args_list
if "scrollTo" in str(call)
]
actual_scrolls = [call[0][0] for call in scroll_calls]
# Should have scrolled to positions: 0, 1200, 2400, plus final reset to 0
assert len(actual_scrolls) == 4 # 3 tile scrolls + 1 reset
assert actual_scrolls == [
"window.scrollTo(0, 0)",
"window.scrollTo(0, 1200)",
"window.scrollTo(0, 2400)",
"window.scrollTo(0, 0)", # Reset scroll
]