mirror of
https://github.com/apache/superset.git
synced 2026-09-01 13:01:33 +00:00
Compare commits
41
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05043fb961 | ||
|
|
fe1fddde05 | ||
|
|
7e67deead7 | ||
|
|
6e02603098 | ||
|
|
4518f6999c | ||
|
|
aff847b3af | ||
|
|
b24aca0304 | ||
|
|
2c453035e4 | ||
|
|
4fe11869fc | ||
|
|
a0a49f9300 | ||
|
|
29d2fac485 | ||
|
|
0c5da6cb5d | ||
|
|
da6947d295 | ||
|
|
2db8f809ba | ||
|
|
5912fad745 | ||
|
|
dc41c45bec | ||
|
|
88ee90c579 | ||
|
|
bbb2279644 | ||
|
|
1958df6b83 | ||
|
|
58bd3bfcf0 | ||
|
|
d6eb6e08d0 | ||
|
|
96cb6030c8 | ||
|
|
94d47113ea | ||
|
|
f756cee01b | ||
|
|
e8926f177d | ||
|
|
16f4516903 | ||
|
|
000d353ef3 | ||
|
|
83b6f672ff | ||
|
|
0dc48e9b41 | ||
|
|
fe9eef9198 | ||
|
|
8a8248b575 | ||
|
|
42d9a78777 | ||
|
|
31a15c5162 | ||
|
|
67b21c45df | ||
|
|
b280ab9e1f | ||
|
|
c42be77c25 | ||
|
|
160917eae8 | ||
|
|
68b84acd93 | ||
|
|
0aa48b6564 | ||
|
|
0fc1955049 | ||
|
|
8a704d293b |
@@ -42,7 +42,7 @@ body:
|
||||
options:
|
||||
- master / latest-dev
|
||||
- "5.0.0"
|
||||
- "4.1.2"
|
||||
- "4.1.3"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
name: Claude PR Assistant
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
check-permissions:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude'))
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
allowed: ${{ steps.check.outputs.allowed }}
|
||||
steps:
|
||||
- name: Check if user is allowed
|
||||
id: check
|
||||
run: |
|
||||
# List of allowed users
|
||||
ALLOWED_USERS="mistercrunch,rusackas"
|
||||
|
||||
# Get the commenter's username
|
||||
COMMENTER="${{ github.event.comment.user.login }}"
|
||||
|
||||
echo "Checking permissions for user: $COMMENTER"
|
||||
|
||||
# Check if user is in allowed list
|
||||
if [[ ",$ALLOWED_USERS," == *",$COMMENTER,"* ]]; then
|
||||
echo "allowed=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ User $COMMENTER is allowed to use Claude"
|
||||
else
|
||||
echo "allowed=false" >> $GITHUB_OUTPUT
|
||||
echo "❌ User $COMMENTER is not allowed to use Claude"
|
||||
fi
|
||||
|
||||
deny-access:
|
||||
needs: check-permissions
|
||||
if: needs.check-permissions.outputs.allowed == 'false'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Comment access denied
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const message = `👋 Hi @${{ github.event.comment.user.login || github.event.review.user.login || github.event.issue.user.login }}!
|
||||
|
||||
Thanks for trying to use Claude Code, but currently only certain team members have access to this feature.
|
||||
|
||||
If you believe you should have access, please contact a project maintainer.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: message
|
||||
});
|
||||
|
||||
claude-code-action:
|
||||
needs: check-permissions
|
||||
if: needs.check-permissions.outputs.allowed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude PR Action
|
||||
uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
timeout_minutes: "60"
|
||||
@@ -92,6 +92,7 @@ scripts/*.zip
|
||||
# IntelliJ
|
||||
*.iml
|
||||
venv
|
||||
.venv
|
||||
@eaDir/
|
||||
|
||||
# PyCharm
|
||||
@@ -127,3 +128,4 @@ docker/*local*
|
||||
test-report.html
|
||||
superset/static/stats/statistics.html
|
||||
.aider*
|
||||
CLAUDE.local.md
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
## Change Log
|
||||
|
||||
### 4.1.3 (Thu May 29 02:31:07 2025 -0500)
|
||||
|
||||
**Database Migrations**
|
||||
|
||||
**Features**
|
||||
|
||||
**Fixes**
|
||||
|
||||
- [#33522](https://github.com/apache/superset/pull/33522) fix(Sqllab): Autocomplete got stuck in UI when open it too fast (@rebenitez1802)
|
||||
- [#33425](https://github.com/apache/superset/pull/33425) fix(table-chart): time shift is not working (@justinpark)
|
||||
- [#32414](https://github.com/apache/superset/pull/32414) fix(api): Added uuid to list api calls (@withnale)
|
||||
- [#33354](https://github.com/apache/superset/pull/33354) fix: loading examples from raw.githubusercontent.com fails with 429 errors (@mistercrunch)
|
||||
- [#32382](https://github.com/apache/superset/pull/32382) fix(pinot): revert join and subquery flags (@yuribogomolov)
|
||||
- [#32473](https://github.com/apache/superset/pull/32473) fix(plugin-chart-echarts): remove erroneous upper bound value (@villebro)
|
||||
- [#33048](https://github.com/apache/superset/pull/33048) fix: improve error type on parse error (@justinpark)
|
||||
- [#32968](https://github.com/apache/superset/pull/32968) fix(pivot-table): Revert "fix(Pivot Table): Fix column width to respect currency config (#31414)" (@justinpark)
|
||||
- [#32795](https://github.com/apache/superset/pull/32795) fix(log): store navigation path to get correct logging path (@justinpark)
|
||||
- [#33216](https://github.com/apache/superset/pull/33216) fix: Downgrade to marshmallow<4 (@amotl)
|
||||
- [#32866](https://github.com/apache/superset/pull/32866) fix: make packages PEP 625 compliant (@sadpandajoe)
|
||||
- [#32035](https://github.com/apache/superset/pull/32035) fix(fe/dashboard-list): display modifier info for `Last modified` data (@hainenber)
|
||||
- [#32708](https://github.com/apache/superset/pull/32708) fix(logging): missing path in event data (@justinpark)
|
||||
- [#32699](https://github.com/apache/superset/pull/32699) fix: Signature of Celery pruner jobs (@michael-s-molina)
|
||||
- [#32681](https://github.com/apache/superset/pull/32681) fix(log): Update recent_activity by event name (@justinpark)
|
||||
- [#32608](https://github.com/apache/superset/pull/32608) fix(welcome): perf on distinct recent activities (@justinpark)
|
||||
- [#32572](https://github.com/apache/superset/pull/32572) fix: Log table retention policy (@michael-s-molina)
|
||||
- [#32406](https://github.com/apache/superset/pull/32406) fix(model/helper): represent RLS filter clause in proper textual SQL string (@hainenber)
|
||||
- [#32240](https://github.com/apache/superset/pull/32240) fix: upgrade to 3.11.11-slim-bookworm to address critical vulnerabilities (@gpchandran)
|
||||
- [#30858](https://github.com/apache/superset/pull/30858) fix(chart data): removing query from /chart/data payload when accessing as guest user (@fisjac)
|
||||
|
||||
**Others**
|
||||
|
||||
- [#33612](https://github.com/apache/superset/pull/33612) chore: update Dockerfile - Upgrade to 3.11.12 (@gpchandran)
|
||||
- [#33435](https://github.com/apache/superset/pull/33435) docs: CVEs fixed on 4.1.2 (@sha174n)
|
||||
- [#33339](https://github.com/apache/superset/pull/33339) chore(🦾): bump python h11 0.14.0 -> 0.16.0 (@github-actions[bot])
|
||||
- [#32745](https://github.com/apache/superset/pull/32745) chore(🦾): bump python sqlglot 26.1.3 -> 26.11.1 (@github-actions[bot])
|
||||
- [#32782](https://github.com/apache/superset/pull/32782) chore: Revert "chore: bump base image in Dockerfile with `ARG PY_VER=3.11.11-slim-bookworm`" (@sadpandajoe)
|
||||
- [#32780](https://github.com/apache/superset/pull/32780) chore: bump base image in Dockerfile with `ARG PY_VER=3.11.11-slim-bookworm` (@gpchandran)
|
||||
+1
-1
@@ -23,7 +23,7 @@ This file documents any backwards-incompatible changes in Superset and
|
||||
assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
|
||||
- [34204](https://github.com/apache/superset/pull/33603) OpenStreetView has been promoted as the new default for Deck.gl visualization since it can be enabled by default without requiring an API key. If you have Mapbox set up and want to disable OpenStreeView in your environment, please follow the steps documented here [https://superset.apache.org/docs/configuration/map-tiles].
|
||||
- [33116](https://github.com/apache/superset/pull/33116) In Echarts Series charts (e.g. Line, Area, Bar, etc.) charts, the `x_axis_sort_series` and `x_axis_sort_series_ascending` form data items have been renamed with `x_axis_sort` and `x_axis_sort_asc`.
|
||||
There's a migration added that can potentially affect a significant number of existing charts.
|
||||
- [32317](https://github.com/apache/superset/pull/32317) The horizontal filter bar feature is now out of testing/beta development and its feature flag `HORIZONTAL_FILTER_BAR` has been removed.
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Map Tiles
|
||||
sidebar_position: 12
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Map tiles
|
||||
|
||||
Superset uses OSM and Mapbox tiles by default. OSM is free but you still need setting your MAPBOX_API_KEY if you want to use mapbox maps.
|
||||
|
||||
## Setting map tiles
|
||||
|
||||
Map tiles can be set with `DECKGL_BASE_MAP` in your `superset_config.py` or `superset_config_docker.py`
|
||||
For adding your own map tiles, you can use the following format.
|
||||
|
||||
```python
|
||||
DECKGL_BASE_MAP = [
|
||||
['tile://https://your_personal_url/{z}/{x}/{y}.png', 'MyTile']
|
||||
]
|
||||
```
|
||||
Openstreetmap tiles url can be added without prefix.
|
||||
```python
|
||||
DECKGL_BASE_MAP = [
|
||||
['https://c.tile.openstreetmap.org/{z}/{x}/{y}.png', 'OpenStreetMap']
|
||||
]
|
||||
```
|
||||
|
||||
Default values are:
|
||||
```python
|
||||
DECKGL_BASE_MAP = [
|
||||
['https://tile.openstreetmap.org/{z}/{x}/{y}.png', 'Streets (OSM)'],
|
||||
['https://tile.osm.ch/osm-swiss-style/{z}/{x}/{y}.png', 'Topography (OSM)'],
|
||||
['mapbox://styles/mapbox/streets-v9', 'Streets'],
|
||||
['mapbox://styles/mapbox/dark-v9', 'Dark'],
|
||||
['mapbox://styles/mapbox/light-v9', 'Light'],
|
||||
['mapbox://styles/mapbox/satellite-streets-v9', 'Satellite Streets'],
|
||||
['mapbox://styles/mapbox/satellite-v9', 'Satellite'],
|
||||
['mapbox://styles/mapbox/outdoors-v9', 'Outdoors'],
|
||||
]
|
||||
```
|
||||
|
||||
It is possible to set only mapbox by removing osm tiles and other way around.
|
||||
|
||||
:::warning
|
||||
Setting `DECKGL_BASE_MAP` overwrite default values
|
||||
:::
|
||||
|
||||
After defining your map tiles, set them in these variables:
|
||||
- `CORS_OPTIONS`
|
||||
- `connect-src` of `TALISMAN_CONFIG` and `TALISMAN_CONFIG_DEV` variables.
|
||||
|
||||
```python
|
||||
ENABLE_CORS = True
|
||||
CORS_OPTIONS: dict[Any, Any] = {
|
||||
"origins": [
|
||||
"https://tile.openstreetmap.org",
|
||||
"https://tile.osm.ch",
|
||||
"https://your_personal_url/{z}/{x}/{y}.png",
|
||||
]
|
||||
}
|
||||
|
||||
.
|
||||
.
|
||||
|
||||
TALISMAN_CONFIG = {
|
||||
"content_security_policy": {
|
||||
...
|
||||
"connect-src": [
|
||||
"'self'",
|
||||
"https://api.mapbox.com",
|
||||
"https://events.mapbox.com",
|
||||
"https://tile.openstreetmap.org",
|
||||
"https://tile.osm.ch",
|
||||
"https://your_personal_url/{z}/{x}/{y}.png",
|
||||
],
|
||||
...
|
||||
}
|
||||
```
|
||||
@@ -78,7 +78,7 @@ Affecting the Docker build process:
|
||||
|
||||
- **SUPERSET_BUILD_TARGET (default=dev):** which --target to build, either `lean` or `dev` are commonly used
|
||||
- **INCLUDE_FIREFOX (default=false):** whether to include the Firefox headless browser in the build
|
||||
- **INCLUDE_CHROMIUM (default=false):** whether to include the Firefox headless browser in the build
|
||||
- **INCLUDE_CHROMIUM (default=false):** whether to include the Chromium headless browser in the build
|
||||
- **BUILD_TRANSLATIONS(default=false):** whether to compile the translations from the .po files available
|
||||
- **SUPERSET_LOAD_EXAMPLES (default=yes):** whether to load the examples into the database upon startup,
|
||||
save some precious time on startup by `SUPERSET_LOAD_EXAMPLES=no docker compose up`
|
||||
@@ -614,9 +614,6 @@ act --job test-python-38 --secret GITHUB_TOKEN=$GITHUB_TOKEN --event pull_reques
|
||||
|
||||
There is also a utility script included in the Superset codebase to run Python integration tests. The [readme can be found here](https://github.com/apache/superset/tree/master/scripts/tests).
|
||||
|
||||
There is also a utility script included in the Superset codebase to run python integration tests. The [readme can be
|
||||
found here](https://github.com/apache/superset/tree/master/scripts/tests)
|
||||
|
||||
To run all integration tests, for example, run this script from the root directory:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/* eslint-env node */
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
const typescriptEslintParser = require('@typescript-eslint/parser');
|
||||
const typescriptEslintPlugin = require('@typescript-eslint/eslint-plugin');
|
||||
const eslintConfigPrettier = require('eslint-config-prettier');
|
||||
const prettierEslintPlugin = require('eslint-plugin-prettier');
|
||||
const js = require('@eslint/js');
|
||||
const ts = require('typescript-eslint');
|
||||
const react = require('eslint-plugin-react');
|
||||
const globals = require('globals');
|
||||
const { defineConfig, globalIgnores } = require('eslint/config');
|
||||
|
||||
module.exports = defineConfig([
|
||||
globalIgnores(['build/**/*', '.docusaurus/**/*', 'node_modules/**/*']),
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
eslintConfigPrettier,
|
||||
{
|
||||
files: ['eslint.config.js'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
}
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
parser: typescriptEslintParser,
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
},
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
typescript: typescriptEslintPlugin,
|
||||
react,
|
||||
prettier: prettierEslintPlugin,
|
||||
},
|
||||
rules: {
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'react/prop-types': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
}
|
||||
])
|
||||
+8
-5
@@ -40,15 +40,18 @@
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.8.1",
|
||||
"@docusaurus/tsconfig": "^3.8.1",
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@types/react": "^19.1.8",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||
"@typescript-eslint/parser": "^5.0.0",
|
||||
"eslint": "^8.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
||||
"@typescript-eslint/parser": "^8.37.0",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-prettier": "^5.5.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"prettier": "^2.0.0",
|
||||
"globals": "^16.3.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.37.0",
|
||||
"webpack": "^5.99.9"
|
||||
},
|
||||
"browserslist": {
|
||||
|
||||
@@ -111,7 +111,7 @@ const StyledTitleContainer = styled('div')`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Link as React.ComponentType<any>)`
|
||||
const StyledButton = styled(Link)`
|
||||
border-radius: 10px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
|
||||
+12
-8
@@ -16,7 +16,6 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/* eslint-disable no-undef */
|
||||
import { useEffect } from 'react';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
|
||||
@@ -50,7 +49,9 @@ export default function Root({ children }) {
|
||||
|
||||
// Handle route changes for SPA
|
||||
const handleRouteChange = () => {
|
||||
devMode && console.log('Route changed to:', window.location.pathname);
|
||||
if (devMode) {
|
||||
console.log('Route changed to:', window.location.pathname);
|
||||
}
|
||||
|
||||
// Short timeout to ensure the page has fully rendered
|
||||
setTimeout(() => {
|
||||
@@ -58,11 +59,9 @@ export default function Root({ children }) {
|
||||
const currentTitle = document.title;
|
||||
const currentPath = window.location.pathname;
|
||||
|
||||
devMode &&
|
||||
console.log('Tracking page view:', currentPath, currentTitle);
|
||||
|
||||
// For testing: impersonate real domain - ONLY FOR DEVELOPMENT
|
||||
if (devMode) {
|
||||
console.log('Tracking page view:', currentPath, currentTitle);
|
||||
window._paq.push(['setDomains', ['superset.apache.org']]);
|
||||
window._paq.push([
|
||||
'setCustomUrl',
|
||||
@@ -85,17 +84,22 @@ export default function Root({ children }) {
|
||||
'routeDidUpdate',
|
||||
];
|
||||
|
||||
devMode && console.log('Setting up Docusaurus route listeners');
|
||||
if (devMode) {
|
||||
console.log('Setting up Docusaurus route listeners');
|
||||
}
|
||||
possibleEvents.forEach(eventName => {
|
||||
document.addEventListener(eventName, () => {
|
||||
devMode &&
|
||||
if (devMode) {
|
||||
console.log(`Docusaurus route update detected via ${eventName}`);
|
||||
}
|
||||
handleRouteChange();
|
||||
});
|
||||
});
|
||||
|
||||
// Also set up manual history tracking as fallback
|
||||
devMode && console.log('Setting up manual history tracking as fallback');
|
||||
if (devMode) {
|
||||
console.log('Setting up manual history tracking as fallback');
|
||||
}
|
||||
const originalPushState = window.history.pushState;
|
||||
window.history.pushState = function () {
|
||||
originalPushState.apply(this, arguments);
|
||||
|
||||
+284
-198
@@ -2157,30 +2157,71 @@
|
||||
dependencies:
|
||||
eslint-visitor-keys "^3.4.3"
|
||||
|
||||
"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1":
|
||||
"@eslint-community/eslint-utils@^4.7.0":
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a"
|
||||
integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==
|
||||
dependencies:
|
||||
eslint-visitor-keys "^3.4.3"
|
||||
|
||||
"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1":
|
||||
version "4.12.1"
|
||||
resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0"
|
||||
integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
|
||||
|
||||
"@eslint/eslintrc@^2.1.4":
|
||||
version "2.1.4"
|
||||
resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz"
|
||||
integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==
|
||||
"@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==
|
||||
dependencies:
|
||||
"@eslint/object-schema" "^2.1.6"
|
||||
debug "^4.3.1"
|
||||
minimatch "^3.1.2"
|
||||
|
||||
"@eslint/config-helpers@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.3.0.tgz#3e09a90dfb87e0005c7694791e58e97077271286"
|
||||
integrity sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==
|
||||
|
||||
"@eslint/core@^0.15.0", "@eslint/core@^0.15.1":
|
||||
version "0.15.1"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.1.tgz#d530d44209cbfe2f82ef86d6ba08760196dd3b60"
|
||||
integrity sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.15"
|
||||
|
||||
"@eslint/eslintrc@^3.3.1":
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964"
|
||||
integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==
|
||||
dependencies:
|
||||
ajv "^6.12.4"
|
||||
debug "^4.3.2"
|
||||
espree "^9.6.0"
|
||||
globals "^13.19.0"
|
||||
espree "^10.0.1"
|
||||
globals "^14.0.0"
|
||||
ignore "^5.2.0"
|
||||
import-fresh "^3.2.1"
|
||||
js-yaml "^4.1.0"
|
||||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@eslint/js@8.57.1":
|
||||
version "8.57.1"
|
||||
resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz"
|
||||
integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
|
||||
"@eslint/js@9.31.0", "@eslint/js@^9.31.0":
|
||||
version "9.31.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.31.0.tgz#adb1f39953d8c475c4384b67b67541b0d7206ed8"
|
||||
integrity sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==
|
||||
|
||||
"@eslint/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/plugin-kit@^0.3.1":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz#32926b59bd407d58d817941e48b2a7049359b1fd"
|
||||
integrity sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==
|
||||
dependencies:
|
||||
"@eslint/core" "^0.15.1"
|
||||
levn "^0.4.1"
|
||||
|
||||
"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
|
||||
version "9.3.0"
|
||||
@@ -2194,24 +2235,33 @@
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
|
||||
"@humanwhocodes/config-array@^0.13.0":
|
||||
version "0.13.0"
|
||||
resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz"
|
||||
integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==
|
||||
"@humanfs/core@^0.19.1":
|
||||
version "0.19.1"
|
||||
resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77"
|
||||
integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==
|
||||
|
||||
"@humanfs/node@^0.16.6":
|
||||
version "0.16.6"
|
||||
resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e"
|
||||
integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==
|
||||
dependencies:
|
||||
"@humanwhocodes/object-schema" "^2.0.3"
|
||||
debug "^4.3.1"
|
||||
minimatch "^3.0.5"
|
||||
"@humanfs/core" "^0.19.1"
|
||||
"@humanwhocodes/retry" "^0.3.0"
|
||||
|
||||
"@humanwhocodes/module-importer@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz"
|
||||
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
|
||||
|
||||
"@humanwhocodes/object-schema@^2.0.3":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz"
|
||||
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
|
||||
"@humanwhocodes/retry@^0.3.0":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a"
|
||||
integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==
|
||||
|
||||
"@humanwhocodes/retry@^0.4.2":
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba"
|
||||
integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==
|
||||
|
||||
"@iconify/types@^2.0.0":
|
||||
version "2.0.0"
|
||||
@@ -2353,7 +2403,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
|
||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||
|
||||
"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
|
||||
"@nodelib/fs.walk@^1.2.3":
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
|
||||
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
|
||||
@@ -2361,6 +2411,11 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@pkgr/core@^0.2.4":
|
||||
version "0.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.7.tgz#eb5014dfd0b03e7f3ba2eeeff506eed89b028058"
|
||||
integrity sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==
|
||||
|
||||
"@pnpm/config.env-replace@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c"
|
||||
@@ -3599,11 +3654,6 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/semver@^7.3.12":
|
||||
version "7.5.8"
|
||||
resolved "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz"
|
||||
integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==
|
||||
|
||||
"@types/send@*":
|
||||
version "0.17.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a"
|
||||
@@ -3674,91 +3724,105 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.0.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz"
|
||||
integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==
|
||||
"@typescript-eslint/eslint-plugin@8.37.0", "@typescript-eslint/eslint-plugin@^8.37.0":
|
||||
version "8.37.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz#332392883f936137cd6252c8eb236d298e514e70"
|
||||
integrity sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==
|
||||
dependencies:
|
||||
"@eslint-community/regexpp" "^4.4.0"
|
||||
"@typescript-eslint/scope-manager" "5.62.0"
|
||||
"@typescript-eslint/type-utils" "5.62.0"
|
||||
"@typescript-eslint/utils" "5.62.0"
|
||||
debug "^4.3.4"
|
||||
"@eslint-community/regexpp" "^4.10.0"
|
||||
"@typescript-eslint/scope-manager" "8.37.0"
|
||||
"@typescript-eslint/type-utils" "8.37.0"
|
||||
"@typescript-eslint/utils" "8.37.0"
|
||||
"@typescript-eslint/visitor-keys" "8.37.0"
|
||||
graphemer "^1.4.0"
|
||||
ignore "^5.2.0"
|
||||
natural-compare-lite "^1.4.0"
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
ignore "^7.0.0"
|
||||
natural-compare "^1.4.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/parser@^5.0.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz"
|
||||
integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==
|
||||
"@typescript-eslint/parser@8.37.0", "@typescript-eslint/parser@^8.37.0":
|
||||
version "8.37.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.37.0.tgz#b87f6b61e25ad5cc5bbf8baf809b8da889c89804"
|
||||
integrity sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "5.62.0"
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
"@typescript-eslint/typescript-estree" "5.62.0"
|
||||
"@typescript-eslint/scope-manager" "8.37.0"
|
||||
"@typescript-eslint/types" "8.37.0"
|
||||
"@typescript-eslint/typescript-estree" "8.37.0"
|
||||
"@typescript-eslint/visitor-keys" "8.37.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz"
|
||||
integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==
|
||||
"@typescript-eslint/project-service@8.37.0":
|
||||
version "8.37.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.37.0.tgz#0594352e32a4ac9258591b88af77b5653800cdfe"
|
||||
integrity sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
"@typescript-eslint/visitor-keys" "5.62.0"
|
||||
|
||||
"@typescript-eslint/type-utils@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz"
|
||||
integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "5.62.0"
|
||||
"@typescript-eslint/utils" "5.62.0"
|
||||
"@typescript-eslint/tsconfig-utils" "^8.37.0"
|
||||
"@typescript-eslint/types" "^8.37.0"
|
||||
debug "^4.3.4"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/types@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz"
|
||||
integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==
|
||||
|
||||
"@typescript-eslint/typescript-estree@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz"
|
||||
integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==
|
||||
"@typescript-eslint/scope-manager@8.37.0":
|
||||
version "8.37.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz#a31a3c80ca2ef4ed58de13742debb692e7d4c0a4"
|
||||
integrity sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
"@typescript-eslint/visitor-keys" "5.62.0"
|
||||
"@typescript-eslint/types" "8.37.0"
|
||||
"@typescript-eslint/visitor-keys" "8.37.0"
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@8.37.0", "@typescript-eslint/tsconfig-utils@^8.37.0":
|
||||
version "8.37.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz#47a2760d265c6125f8e7864bc5c8537cad2bd053"
|
||||
integrity sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==
|
||||
|
||||
"@typescript-eslint/type-utils@8.37.0":
|
||||
version "8.37.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz#2a682e4c6ff5886712dad57e9787b5e417124507"
|
||||
integrity sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.37.0"
|
||||
"@typescript-eslint/typescript-estree" "8.37.0"
|
||||
"@typescript-eslint/utils" "8.37.0"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/types@8.37.0", "@typescript-eslint/types@^8.37.0":
|
||||
version "8.37.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.37.0.tgz#09517aa9625eb3c68941dde3ac8835740587b6ff"
|
||||
integrity sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.37.0":
|
||||
version "8.37.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz#a07e4574d8e6e4355a558f61323730c987f5fcbc"
|
||||
integrity sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service" "8.37.0"
|
||||
"@typescript-eslint/tsconfig-utils" "8.37.0"
|
||||
"@typescript-eslint/types" "8.37.0"
|
||||
"@typescript-eslint/visitor-keys" "8.37.0"
|
||||
debug "^4.3.4"
|
||||
fast-glob "^3.3.2"
|
||||
is-glob "^4.0.3"
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
minimatch "^9.0.4"
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/utils@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz"
|
||||
integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==
|
||||
"@typescript-eslint/utils@8.37.0":
|
||||
version "8.37.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.37.0.tgz#189ea59b2709f5d898614611f091a776751ee335"
|
||||
integrity sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.2.0"
|
||||
"@types/json-schema" "^7.0.9"
|
||||
"@types/semver" "^7.3.12"
|
||||
"@typescript-eslint/scope-manager" "5.62.0"
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
"@typescript-eslint/typescript-estree" "5.62.0"
|
||||
eslint-scope "^5.1.1"
|
||||
semver "^7.3.7"
|
||||
"@eslint-community/eslint-utils" "^4.7.0"
|
||||
"@typescript-eslint/scope-manager" "8.37.0"
|
||||
"@typescript-eslint/types" "8.37.0"
|
||||
"@typescript-eslint/typescript-estree" "8.37.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz"
|
||||
integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==
|
||||
"@typescript-eslint/visitor-keys@8.37.0":
|
||||
version "8.37.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz#cdb6a6bd3e8d6dd69bd70c1bdda36e2d18737455"
|
||||
integrity sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
"@typescript-eslint/types" "8.37.0"
|
||||
eslint-visitor-keys "^4.2.1"
|
||||
|
||||
"@ungap/structured-clone@^1.0.0", "@ungap/structured-clone@^1.2.0":
|
||||
"@ungap/structured-clone@^1.0.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
|
||||
integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
|
||||
@@ -3914,11 +3978,16 @@ acorn-walk@^8.0.0:
|
||||
dependencies:
|
||||
acorn "^8.11.0"
|
||||
|
||||
acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.8.2, acorn@^8.9.0:
|
||||
acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.8.2:
|
||||
version "8.14.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb"
|
||||
integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==
|
||||
|
||||
acorn@^8.15.0:
|
||||
version "8.15.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
|
||||
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
|
||||
|
||||
address@^1.0.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e"
|
||||
@@ -5006,7 +5075,7 @@ cosmiconfig@^8.1.3, cosmiconfig@^8.3.5:
|
||||
parse-json "^5.2.0"
|
||||
path-type "^4.0.0"
|
||||
|
||||
cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
cross-spawn@^7.0.3, cross-spawn@^7.0.6:
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
||||
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
|
||||
@@ -5704,13 +5773,6 @@ doctrine@^2.1.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
doctrine@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz"
|
||||
integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
docusaurus-plugin-less@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/docusaurus-plugin-less/-/docusaurus-plugin-less-2.0.2.tgz"
|
||||
@@ -6104,12 +6166,13 @@ eslint-config-prettier@^10.1.5:
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz#00c18d7225043b6fbce6a665697377998d453782"
|
||||
integrity sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==
|
||||
|
||||
eslint-plugin-prettier@^4.0.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz"
|
||||
integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==
|
||||
eslint-plugin-prettier@^5.5.1:
|
||||
version "5.5.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz#470820964de9aedb37e9ce62c3266d2d26d08d15"
|
||||
integrity sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==
|
||||
dependencies:
|
||||
prettier-linter-helpers "^1.0.0"
|
||||
synckit "^0.11.7"
|
||||
|
||||
eslint-plugin-react@^7.37.5:
|
||||
version "7.37.5"
|
||||
@@ -6135,7 +6198,7 @@ eslint-plugin-react@^7.37.5:
|
||||
string.prototype.matchall "^4.0.12"
|
||||
string.prototype.repeat "^1.0.0"
|
||||
|
||||
eslint-scope@5.1.1, eslint-scope@^5.1.1:
|
||||
eslint-scope@5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
||||
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
|
||||
@@ -6143,80 +6206,82 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1:
|
||||
esrecurse "^4.3.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-scope@^7.2.2:
|
||||
version "7.2.2"
|
||||
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz"
|
||||
integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
|
||||
eslint-scope@^8.4.0:
|
||||
version "8.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82"
|
||||
integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==
|
||||
dependencies:
|
||||
esrecurse "^4.3.0"
|
||||
estraverse "^5.2.0"
|
||||
|
||||
eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3:
|
||||
eslint-visitor-keys@^3.4.3:
|
||||
version "3.4.3"
|
||||
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
|
||||
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
||||
|
||||
eslint@^8.0.0:
|
||||
version "8.57.1"
|
||||
resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz"
|
||||
integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
|
||||
eslint-visitor-keys@^4.2.1:
|
||||
version "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.31.0:
|
||||
version "9.31.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.31.0.tgz#9a488e6da75bbe05785cd62e43c5ea99356d21ba"
|
||||
integrity sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.2.0"
|
||||
"@eslint-community/regexpp" "^4.6.1"
|
||||
"@eslint/eslintrc" "^2.1.4"
|
||||
"@eslint/js" "8.57.1"
|
||||
"@humanwhocodes/config-array" "^0.13.0"
|
||||
"@eslint-community/regexpp" "^4.12.1"
|
||||
"@eslint/config-array" "^0.21.0"
|
||||
"@eslint/config-helpers" "^0.3.0"
|
||||
"@eslint/core" "^0.15.0"
|
||||
"@eslint/eslintrc" "^3.3.1"
|
||||
"@eslint/js" "9.31.0"
|
||||
"@eslint/plugin-kit" "^0.3.1"
|
||||
"@humanfs/node" "^0.16.6"
|
||||
"@humanwhocodes/module-importer" "^1.0.1"
|
||||
"@nodelib/fs.walk" "^1.2.8"
|
||||
"@ungap/structured-clone" "^1.2.0"
|
||||
"@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.2"
|
||||
cross-spawn "^7.0.6"
|
||||
debug "^4.3.2"
|
||||
doctrine "^3.0.0"
|
||||
escape-string-regexp "^4.0.0"
|
||||
eslint-scope "^7.2.2"
|
||||
eslint-visitor-keys "^3.4.3"
|
||||
espree "^9.6.1"
|
||||
esquery "^1.4.2"
|
||||
eslint-scope "^8.4.0"
|
||||
eslint-visitor-keys "^4.2.1"
|
||||
espree "^10.4.0"
|
||||
esquery "^1.5.0"
|
||||
esutils "^2.0.2"
|
||||
fast-deep-equal "^3.1.3"
|
||||
file-entry-cache "^6.0.1"
|
||||
file-entry-cache "^8.0.0"
|
||||
find-up "^5.0.0"
|
||||
glob-parent "^6.0.2"
|
||||
globals "^13.19.0"
|
||||
graphemer "^1.4.0"
|
||||
ignore "^5.2.0"
|
||||
imurmurhash "^0.1.4"
|
||||
is-glob "^4.0.0"
|
||||
is-path-inside "^3.0.3"
|
||||
js-yaml "^4.1.0"
|
||||
json-stable-stringify-without-jsonify "^1.0.1"
|
||||
levn "^0.4.1"
|
||||
lodash.merge "^4.6.2"
|
||||
minimatch "^3.1.2"
|
||||
natural-compare "^1.4.0"
|
||||
optionator "^0.9.3"
|
||||
strip-ansi "^6.0.1"
|
||||
text-table "^0.2.0"
|
||||
|
||||
espree@^9.6.0, espree@^9.6.1:
|
||||
version "9.6.1"
|
||||
resolved "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz"
|
||||
integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
|
||||
espree@^10.0.1, espree@^10.4.0:
|
||||
version "10.4.0"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837"
|
||||
integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==
|
||||
dependencies:
|
||||
acorn "^8.9.0"
|
||||
acorn "^8.15.0"
|
||||
acorn-jsx "^5.3.2"
|
||||
eslint-visitor-keys "^3.4.1"
|
||||
eslint-visitor-keys "^4.2.1"
|
||||
|
||||
esprima@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
||||
|
||||
esquery@^1.4.2:
|
||||
esquery@^1.5.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz"
|
||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7"
|
||||
integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
|
||||
dependencies:
|
||||
estraverse "^5.1.0"
|
||||
@@ -6411,7 +6476,7 @@ fast-diff@^1.1.2:
|
||||
resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz"
|
||||
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
|
||||
|
||||
fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0:
|
||||
fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.2:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
|
||||
integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
|
||||
@@ -6484,12 +6549,12 @@ figures@^3.2.0:
|
||||
dependencies:
|
||||
escape-string-regexp "^1.0.5"
|
||||
|
||||
file-entry-cache@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz"
|
||||
integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
|
||||
file-entry-cache@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f"
|
||||
integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==
|
||||
dependencies:
|
||||
flat-cache "^3.0.4"
|
||||
flat-cache "^4.0.0"
|
||||
|
||||
file-loader@^6.2.0:
|
||||
version "6.2.0"
|
||||
@@ -6548,14 +6613,13 @@ find-up@^6.3.0:
|
||||
locate-path "^7.1.0"
|
||||
path-exists "^5.0.0"
|
||||
|
||||
flat-cache@^3.0.4:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz"
|
||||
integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==
|
||||
flat-cache@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c"
|
||||
integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==
|
||||
dependencies:
|
||||
flatted "^3.2.9"
|
||||
keyv "^4.5.3"
|
||||
rimraf "^3.0.2"
|
||||
keyv "^4.5.4"
|
||||
|
||||
flat@^5.0.2:
|
||||
version "5.0.2"
|
||||
@@ -6590,12 +6654,14 @@ form-data-encoder@^2.1.2:
|
||||
integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4"
|
||||
integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
es-set-tostringtag "^2.1.0"
|
||||
hasown "^2.0.2"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
format@^0.2.0:
|
||||
@@ -6781,18 +6847,21 @@ globals@^11.1.0:
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
|
||||
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
|
||||
|
||||
globals@^13.19.0:
|
||||
version "13.24.0"
|
||||
resolved "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz"
|
||||
integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==
|
||||
dependencies:
|
||||
type-fest "^0.20.2"
|
||||
globals@^14.0.0:
|
||||
version "14.0.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e"
|
||||
integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
|
||||
|
||||
globals@^15.14.0:
|
||||
version "15.15.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8"
|
||||
integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==
|
||||
|
||||
globals@^16.3.0:
|
||||
version "16.3.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-16.3.0.tgz#66118e765ddaf9e2d880f7e17658543f93f1f667"
|
||||
integrity sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==
|
||||
|
||||
globalthis@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz"
|
||||
@@ -7285,6 +7354,11 @@ ignore@^5.2.0, ignore@^5.2.4:
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
|
||||
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
|
||||
|
||||
ignore@^7.0.0:
|
||||
version "7.0.5"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9"
|
||||
integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==
|
||||
|
||||
image-size@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/image-size/-/image-size-2.0.2.tgz#84a7b43704db5736f364bf0d1b029821299b4bdc"
|
||||
@@ -7619,7 +7693,7 @@ is-obj@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
|
||||
integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
|
||||
|
||||
is-path-inside@^3.0.2, is-path-inside@^3.0.3:
|
||||
is-path-inside@^3.0.2:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
||||
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
|
||||
@@ -7921,7 +7995,7 @@ katex@^0.16.9:
|
||||
dependencies:
|
||||
commander "^8.3.0"
|
||||
|
||||
keyv@^4.5.3:
|
||||
keyv@^4.5.3, keyv@^4.5.4:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
|
||||
integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
|
||||
@@ -8949,7 +9023,7 @@ minimalistic-assert@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
||||
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
|
||||
|
||||
minimatch@3.1.2, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
minimatch@3.1.2, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
@@ -8963,6 +9037,13 @@ minimatch@^7.4.3:
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimatch@^9.0.4:
|
||||
version "9.0.5"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
|
||||
integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist@^1.2.0:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
@@ -9011,11 +9092,6 @@ nanoid@^3.3.11, nanoid@^3.3.8:
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
|
||||
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
|
||||
|
||||
natural-compare-lite@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz"
|
||||
integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
|
||||
@@ -10165,10 +10241,10 @@ prettier-linter-helpers@^1.0.0:
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@^2.0.0:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz"
|
||||
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
|
||||
prettier@^3.6.2:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393"
|
||||
integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==
|
||||
|
||||
pretty-error@^4.0.0:
|
||||
version "4.0.0"
|
||||
@@ -11438,6 +11514,11 @@ semver@^7.3.5, semver@^7.3.7, semver@^7.5.4:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f"
|
||||
integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
|
||||
|
||||
semver@^7.6.0:
|
||||
version "7.7.2"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
|
||||
integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
|
||||
|
||||
send@0.19.0:
|
||||
version "0.19.0"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
|
||||
@@ -12058,6 +12139,13 @@ swagger-ui-react@^5.26.0:
|
||||
xml-but-prettier "^1.0.1"
|
||||
zenscroll "^4.0.2"
|
||||
|
||||
synckit@^0.11.7:
|
||||
version "0.11.8"
|
||||
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.8.tgz#b2aaae998a4ef47ded60773ad06e7cb821f55457"
|
||||
integrity sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==
|
||||
dependencies:
|
||||
"@pkgr/core" "^0.2.4"
|
||||
|
||||
tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
|
||||
@@ -12084,11 +12172,6 @@ terser@^5.10.0, terser@^5.15.1, terser@^5.31.1:
|
||||
commander "^2.20.0"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
text-table@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
|
||||
|
||||
throttle-debounce@^5.0.0, throttle-debounce@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz"
|
||||
@@ -12180,6 +12263,11 @@ trough@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f"
|
||||
integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==
|
||||
|
||||
ts-api-utils@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91"
|
||||
integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==
|
||||
|
||||
ts-dedent@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
|
||||
@@ -12195,23 +12283,11 @@ ts-toolbelt@^9.6.0:
|
||||
resolved "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz"
|
||||
integrity sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==
|
||||
|
||||
tslib@^1.8.1:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.3, tslib@^2.3.0, tslib@^2.6.0:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
|
||||
tsutils@^3.21.0:
|
||||
version "3.21.0"
|
||||
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"
|
||||
integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
|
||||
dependencies:
|
||||
tslib "^1.8.1"
|
||||
|
||||
type-check@^0.4.0, type-check@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz"
|
||||
@@ -12306,6 +12382,16 @@ types-ramda@^0.30.0:
|
||||
dependencies:
|
||||
ts-toolbelt "^9.6.0"
|
||||
|
||||
typescript-eslint@^8.37.0:
|
||||
version "8.37.0"
|
||||
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.37.0.tgz#2235ddfa40cdbdadb1afb05f8bda688a2294b4c2"
|
||||
integrity sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin" "8.37.0"
|
||||
"@typescript-eslint/parser" "8.37.0"
|
||||
"@typescript-eslint/typescript-estree" "8.37.0"
|
||||
"@typescript-eslint/utils" "8.37.0"
|
||||
|
||||
typescript@~5.8.3:
|
||||
version "5.8.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
|
||||
|
||||
@@ -29,7 +29,7 @@ maintainers:
|
||||
- name: craig-rueda
|
||||
email: craig@craigrueda.com
|
||||
url: https://github.com/craig-rueda
|
||||
version: 0.14.2
|
||||
version: 0.14.3
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
version: 13.4.4
|
||||
|
||||
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
|
||||
|
||||
# superset
|
||||
|
||||

|
||||

|
||||
|
||||
Apache Superset is a modern, enterprise-ready business intelligence web application
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ metadata:
|
||||
chart: {{ template "superset.chart" . }}
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
{{- if .Values.extraLabels }}
|
||||
{{- toYaml .Values.extraLabels | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if .Values.extraLabels }}
|
||||
{{- toYaml .Values.extraLabels | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if .Values.init.jobAnnotations }}
|
||||
annotations: {{- toYaml .Values.init.jobAnnotations | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -44,10 +44,10 @@ spec:
|
||||
{{- if or .Values.extraLabels .Values.init.podLabels }}
|
||||
labels:
|
||||
{{- if .Values.extraLabels }}
|
||||
{{- toYaml .Values.extraLabels | nindent 8 }}
|
||||
{{- toYaml .Values.extraLabels | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.init.podLabels }}
|
||||
{{- toYaml .Values.init.podLabels | nindent 8 }}
|
||||
{{- toYaml .Values.init.podLabels | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
spec:
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{{- with .Values.supersetCeleryBeat.podDisruptionBudget }}
|
||||
{{- if .enabled -}}
|
||||
{{- if and .minAvailable .maxUnavailable }}
|
||||
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
|
||||
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
|
||||
{{- end}}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
@@ -35,12 +35,12 @@ metadata:
|
||||
{{- toYaml $.Values.extraLabels | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .minAvailable }}
|
||||
{{- if .minAvailable }}
|
||||
minAvailable: {{ .minAvailable }}
|
||||
{{- end }}
|
||||
{{- if .maxUnavailable }}
|
||||
{{- end }}
|
||||
{{- if .maxUnavailable }}
|
||||
maxUnavailable: {{ .maxUnavailable }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "supersetCeleryBeat.selectorLabels" $ | nindent 6 }}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{{- with .Values.supersetCeleryFlower.podDisruptionBudget }}
|
||||
{{- if .enabled -}}
|
||||
{{- if and .minAvailable .maxUnavailable }}
|
||||
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
|
||||
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
|
||||
{{- end}}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
@@ -35,12 +35,12 @@ metadata:
|
||||
{{- toYaml $.Values.extraLabels | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .minAvailable }}
|
||||
{{- if .minAvailable }}
|
||||
minAvailable: {{ .minAvailable }}
|
||||
{{- end }}
|
||||
{{- if .maxUnavailable }}
|
||||
{{- end }}
|
||||
{{- if .maxUnavailable }}
|
||||
maxUnavailable: {{ .maxUnavailable }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "supersetCeleryFlower.selectorLabels" $ | nindent 6 }}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{{- with .Values.supersetWorker.podDisruptionBudget }}
|
||||
{{- if .enabled -}}
|
||||
{{- if and .minAvailable .maxUnavailable }}
|
||||
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
|
||||
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
|
||||
{{- end}}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
@@ -35,12 +35,12 @@ metadata:
|
||||
{{- toYaml $.Values.extraLabels | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .minAvailable }}
|
||||
{{- if .minAvailable }}
|
||||
minAvailable: {{ .minAvailable }}
|
||||
{{- end }}
|
||||
{{- if .maxUnavailable }}
|
||||
{{- end }}
|
||||
{{- if .maxUnavailable }}
|
||||
maxUnavailable: {{ .maxUnavailable }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "supersetWorker.selectorLabels" $ | nindent 6 }}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{{- with .Values.supersetWebsockets.podDisruptionBudget }}
|
||||
{{- if .enabled -}}
|
||||
{{- if and .minAvailable .maxUnavailable }}
|
||||
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
|
||||
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
|
||||
{{- end}}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
@@ -35,12 +35,12 @@ metadata:
|
||||
{{- toYaml $.Values.extraLabels | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .minAvailable }}
|
||||
{{- if .minAvailable }}
|
||||
minAvailable: {{ .minAvailable }}
|
||||
{{- end }}
|
||||
{{- if .maxUnavailable }}
|
||||
{{- end }}
|
||||
{{- if .maxUnavailable }}
|
||||
maxUnavailable: {{ .maxUnavailable }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "supersetWebsockets.selectorLabels" $ | nindent 6 }}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{{- with .Values.supersetNode.podDisruptionBudget }}
|
||||
{{- if .enabled -}}
|
||||
{{- if and .minAvailable .maxUnavailable }}
|
||||
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
|
||||
{{- fail "Only one of minAvailable or maxUnavailable should be set" }}
|
||||
{{- end}}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
@@ -35,12 +35,12 @@ metadata:
|
||||
{{- toYaml $.Values.extraLabels | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .minAvailable }}
|
||||
{{- if .minAvailable }}
|
||||
minAvailable: {{ .minAvailable }}
|
||||
{{- end }}
|
||||
{{- if .maxUnavailable }}
|
||||
{{- end }}
|
||||
{{- if .maxUnavailable }}
|
||||
maxUnavailable: {{ .maxUnavailable }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "supersetNode.selectorLabels" $ | nindent 6 }}
|
||||
|
||||
@@ -148,6 +148,7 @@ hive = [
|
||||
impala = ["impyla>0.16.2, <0.17"]
|
||||
kusto = ["sqlalchemy-kusto>=3.0.0, <4"]
|
||||
kylin = ["kylinpy>=2.8.1, <2.9"]
|
||||
metricflow = ["shillelagh[dbtmetricflowapi]>=1.4.2, <2"]
|
||||
motherduck = ["duckdb==0.10.2", "duckdb-engine>=0.12.1, <0.13"]
|
||||
mssql = ["pymssql>=2.2.8, <3"]
|
||||
mysql = ["mysqlclient>=2.1.0, <3"]
|
||||
|
||||
Generated
+326
-34
@@ -3251,20 +3251,6 @@
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios/node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||
@@ -3658,6 +3644,19 @@
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -4064,6 +4063,20 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.19",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz",
|
||||
@@ -4124,12 +4137,57 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
@@ -4590,6 +4648,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-readdir-recursive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
|
||||
@@ -4617,10 +4691,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
@@ -4640,6 +4717,30 @@
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-package-type": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
|
||||
@@ -4649,6 +4750,19 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
@@ -4711,6 +4825,18 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -4739,6 +4865,45 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@@ -6927,6 +7092,15 @@
|
||||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
@@ -10565,19 +10739,6 @@
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-jest": {
|
||||
@@ -10862,6 +11023,16 @@
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -11140,6 +11311,17 @@
|
||||
"integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
|
||||
"dev": true
|
||||
},
|
||||
"dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.5.19",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz",
|
||||
@@ -11183,12 +11365,45 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true
|
||||
},
|
||||
"es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true
|
||||
},
|
||||
"es-module-lexer": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
|
||||
"dev": true
|
||||
},
|
||||
"es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
@@ -11503,6 +11718,19 @@
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"dev": true
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"fs-readdir-recursive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
|
||||
@@ -11523,9 +11751,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true
|
||||
},
|
||||
"gensync": {
|
||||
@@ -11540,12 +11768,40 @@
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"get-package-type": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
|
||||
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
@@ -11588,6 +11844,12 @@
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"dev": true
|
||||
},
|
||||
"gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -11609,6 +11871,30 @@
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-symbols": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@@ -13219,6 +13505,12 @@
|
||||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true
|
||||
},
|
||||
"merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
|
||||
@@ -56,7 +56,7 @@ module.exports = {
|
||||
],
|
||||
coverageReporters: ['lcov', 'json-summary', 'html', 'text'],
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!d3-(interpolate|color|time)|remark-gfm|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|@rjsf/*.|sinon|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|geostyler|geostyler-.*|react-error-boundary|react-json-tree|react-base16-styling|lodash-es)',
|
||||
'node_modules/(?!d3-(interpolate|color|time|scale)|@mapbox/tiny-sdf|remark-gfm|(?!@ngrx|(?!deck.gl)|d3-scale)|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|@rjsf/*.|sinon|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|geostyler|geostyler-.*|react-error-boundary|react-json-tree|react-base16-styling|lodash-es)',
|
||||
],
|
||||
preset: 'ts-jest',
|
||||
transform: {
|
||||
|
||||
Generated
+1332
-73
File diff suppressed because it is too large
Load Diff
@@ -104,12 +104,12 @@
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "file:./plugins/legacy-plugin-chart-world-map",
|
||||
"@superset-ui/legacy-preset-chart-deckgl": "file:./plugins/legacy-preset-chart-deckgl",
|
||||
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
|
||||
"@superset-ui/plugin-chart-ag-grid-table": "file:./plugins/plugin-chart-ag-grid-table",
|
||||
"@superset-ui/plugin-chart-cartodiagram": "file:./plugins/plugin-chart-cartodiagram",
|
||||
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
|
||||
"@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
|
||||
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
|
||||
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
|
||||
"@superset-ui/plugin-chart-ag-grid-table": "file:./plugins/plugin-chart-ag-grid-table",
|
||||
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
|
||||
"@superset-ui/switchboard": "file:./packages/superset-ui-switchboard",
|
||||
"@types/d3-format": "^3.0.1",
|
||||
@@ -203,6 +203,7 @@
|
||||
"use-event-callback": "^0.1.0",
|
||||
"use-immer": "^0.9.0",
|
||||
"use-query-params": "^1.1.9",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -90,6 +90,7 @@ export interface Dataset {
|
||||
database?: Record<string, unknown>;
|
||||
normalize_columns?: boolean;
|
||||
always_filter_main_dttm?: boolean;
|
||||
extra?: object | string;
|
||||
}
|
||||
|
||||
export interface ControlPanelState {
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ interface HandleSelectProps {
|
||||
}
|
||||
|
||||
const menuItemStyles = (theme: any) => css`
|
||||
&.antd5-menu-item {
|
||||
&.ant-menu-item {
|
||||
height: auto;
|
||||
line-height: 1.4;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
Radio as Antd5Radio,
|
||||
Radio as AntRadio,
|
||||
type CheckboxOptionType,
|
||||
type RadioGroupProps,
|
||||
} from 'antd';
|
||||
@@ -40,19 +40,19 @@ const RadioGroup = ({
|
||||
...props
|
||||
}: RadioGroupWrapperProps) => {
|
||||
const content = options.map((option: CheckboxOptionType) => (
|
||||
<Antd5Radio key={option.value} value={option.value}>
|
||||
<AntRadio key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</Antd5Radio>
|
||||
</AntRadio>
|
||||
));
|
||||
return (
|
||||
<Antd5Radio.Group {...props}>
|
||||
<AntRadio.Group {...props}>
|
||||
{spaceConfig ? <Space {...spaceConfig}>{content}</Space> : content}
|
||||
</Antd5Radio.Group>
|
||||
</AntRadio.Group>
|
||||
);
|
||||
};
|
||||
export const Radio = Object.assign(Antd5Radio, {
|
||||
export const Radio = Object.assign(AntRadio, {
|
||||
GroupWrapper: RadioGroup,
|
||||
Button: Antd5Radio.Button,
|
||||
Button: AntRadio.Button,
|
||||
});
|
||||
export type {
|
||||
RadioChangeEvent,
|
||||
|
||||
@@ -19,28 +19,28 @@
|
||||
import { Tooltip } from 'antd';
|
||||
import { Dropdown, Icons } from '@superset-ui/core/components';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { ThemeMode } from '../../theme/types';
|
||||
import { ThemeAlgorithm, ThemeMode } from '../../theme/types';
|
||||
|
||||
export interface ThemeSelectProps {
|
||||
changeThemeMode: (newMode: ThemeMode) => void;
|
||||
setThemeMode: (newMode: ThemeMode) => void;
|
||||
tooltipTitle?: string;
|
||||
themeMode: ThemeMode;
|
||||
}
|
||||
|
||||
const ThemeSelect: React.FC<ThemeSelectProps> = ({
|
||||
changeThemeMode,
|
||||
setThemeMode,
|
||||
tooltipTitle = 'Select theme',
|
||||
themeMode,
|
||||
}) => {
|
||||
const handleSelect = (mode: ThemeMode) => {
|
||||
changeThemeMode(mode);
|
||||
setThemeMode(mode);
|
||||
};
|
||||
|
||||
const themeIconMap: Record<ThemeMode, React.ReactNode> = {
|
||||
[ThemeMode.LIGHT]: <Icons.SunOutlined />,
|
||||
[ThemeMode.DARK]: <Icons.MoonOutlined />,
|
||||
const themeIconMap: Record<ThemeAlgorithm | ThemeMode, React.ReactNode> = {
|
||||
[ThemeAlgorithm.DEFAULT]: <Icons.SunOutlined />,
|
||||
[ThemeAlgorithm.DARK]: <Icons.MoonOutlined />,
|
||||
[ThemeMode.SYSTEM]: <Icons.FormatPainterOutlined />,
|
||||
[ThemeMode.COMPACT]: <Icons.CompressOutlined />,
|
||||
[ThemeAlgorithm.COMPACT]: <Icons.CompressOutlined />,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -49,9 +49,9 @@ const ThemeSelect: React.FC<ThemeSelectProps> = ({
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: ThemeMode.LIGHT,
|
||||
key: ThemeMode.DEFAULT,
|
||||
label: t('Light'),
|
||||
onClick: () => handleSelect(ThemeMode.LIGHT),
|
||||
onClick: () => handleSelect(ThemeMode.DEFAULT),
|
||||
icon: <Icons.SunOutlined />,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
import { theme as antdThemeImport } from 'antd';
|
||||
import { Theme } from './Theme';
|
||||
import { AnyThemeConfig } from './types';
|
||||
import { AnyThemeConfig, ThemeAlgorithm } from './types';
|
||||
|
||||
// Mock emotion's cache to avoid actual DOM operations
|
||||
jest.mock('@emotion/cache', () => ({
|
||||
@@ -44,7 +44,7 @@ describe('Theme', () => {
|
||||
const parsedJson = JSON.parse(jsonString);
|
||||
|
||||
expect(parsedJson.token?.colorPrimary).toBe('#ff0000');
|
||||
expect(parsedJson.algorithm).toBe('dark');
|
||||
expect(parsedJson.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -91,7 +91,7 @@ describe('Theme', () => {
|
||||
|
||||
// Verify dark mode by using the serialized config from the public method
|
||||
const serialized = theme.toSerializedConfig();
|
||||
expect(serialized.algorithm).toBe('dark');
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -137,7 +137,7 @@ describe('Theme', () => {
|
||||
|
||||
// Verify the algorithm was updated
|
||||
const serialized = theme.toSerializedConfig();
|
||||
expect(serialized.algorithm).toBe('dark');
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -150,7 +150,7 @@ describe('Theme', () => {
|
||||
|
||||
// Verify dark algorithm is used
|
||||
const serialized = theme.toSerializedConfig();
|
||||
expect(serialized.algorithm).toBe('dark');
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
|
||||
it('switches to default algorithm when toggling dark mode off', () => {
|
||||
@@ -164,7 +164,7 @@ describe('Theme', () => {
|
||||
|
||||
// Verify default algorithm is used
|
||||
const serialized = theme.toSerializedConfig();
|
||||
expect(serialized.algorithm).toBe('default');
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DEFAULT);
|
||||
});
|
||||
|
||||
it('preserves other algorithms when toggling dark mode', () => {
|
||||
@@ -181,10 +181,11 @@ describe('Theme', () => {
|
||||
|
||||
// Verify default algorithm replaces dark but compact is preserved
|
||||
const serialized = theme.toSerializedConfig();
|
||||
|
||||
expect(Array.isArray(serialized.algorithm)).toBe(true);
|
||||
expect(serialized.algorithm).toContain('default');
|
||||
expect(serialized.algorithm).toContain('compact');
|
||||
expect(serialized.algorithm).not.toContain('dark');
|
||||
expect(serialized.algorithm).toContain(ThemeAlgorithm.DEFAULT);
|
||||
expect(serialized.algorithm).toContain(ThemeAlgorithm.COMPACT);
|
||||
expect(serialized.algorithm).not.toContain(ThemeAlgorithm.DARK);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -218,7 +219,7 @@ describe('Theme', () => {
|
||||
const serialized = theme.toSerializedConfig();
|
||||
|
||||
expect(serialized.token?.colorPrimary).toBe('#ff0000');
|
||||
expect(serialized.algorithm).toBe('dark');
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
/* eslint-disable theme-colors/no-literal-colors */
|
||||
import { SerializableThemeConfig } from './types';
|
||||
import { type SerializableThemeConfig, ThemeAlgorithm } from './types';
|
||||
|
||||
const exampleThemes: Record<string, SerializableThemeConfig> = {
|
||||
superset: {
|
||||
@@ -27,11 +27,11 @@ const exampleThemes: Record<string, SerializableThemeConfig> = {
|
||||
},
|
||||
supersetDark: {
|
||||
token: {},
|
||||
algorithm: 'dark',
|
||||
algorithm: ThemeAlgorithm.DARK,
|
||||
},
|
||||
supersetCompact: {
|
||||
token: {},
|
||||
algorithm: 'compact',
|
||||
algorithm: ThemeAlgorithm.COMPACT,
|
||||
},
|
||||
funky: {
|
||||
token: {
|
||||
@@ -43,7 +43,7 @@ const exampleThemes: Record<string, SerializableThemeConfig> = {
|
||||
borderRadius: 12,
|
||||
fontFamily: 'Comic Sans MS, cursive',
|
||||
},
|
||||
algorithm: 'default',
|
||||
algorithm: ThemeAlgorithm.DEFAULT,
|
||||
},
|
||||
funkyDark: {
|
||||
token: {
|
||||
@@ -55,7 +55,7 @@ const exampleThemes: Record<string, SerializableThemeConfig> = {
|
||||
borderRadius: 12,
|
||||
fontFamily: 'Comic Sans MS, cursive',
|
||||
},
|
||||
algorithm: 'dark',
|
||||
algorithm: ThemeAlgorithm.DARK,
|
||||
},
|
||||
};
|
||||
export default exampleThemes;
|
||||
|
||||
@@ -16,17 +16,17 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import emotionStyled from '@emotion/styled';
|
||||
import emotionStyled, { CreateStyled } from '@emotion/styled';
|
||||
import { useTheme as useThemeBasic } from '@emotion/react';
|
||||
// import { theme as antdThemeImport } from 'antd';
|
||||
import { Theme } from './Theme';
|
||||
import type {
|
||||
SupersetTheme,
|
||||
SerializableThemeConfig,
|
||||
AnyThemeConfig,
|
||||
ThemeStorage,
|
||||
ThemeControllerOptions,
|
||||
ThemeContextType,
|
||||
import {
|
||||
type SupersetTheme,
|
||||
type SerializableThemeConfig,
|
||||
type AnyThemeConfig,
|
||||
type ThemeStorage,
|
||||
type ThemeControllerOptions,
|
||||
type ThemeContextType,
|
||||
ThemeAlgorithm,
|
||||
} from './types';
|
||||
|
||||
export {
|
||||
@@ -56,10 +56,12 @@ export function useTheme() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
const styled = emotionStyled;
|
||||
const styled: CreateStyled = emotionStyled;
|
||||
|
||||
// launching in in dark mode for now while iterating
|
||||
const themeObject = Theme.fromConfig({ algorithm: 'default' });
|
||||
const themeObject: Theme = Theme.fromConfig({
|
||||
algorithm: ThemeAlgorithm.DEFAULT,
|
||||
});
|
||||
|
||||
const { theme } = themeObject;
|
||||
const supersetTheme = theme;
|
||||
|
||||
@@ -33,6 +33,41 @@ import { Theme } from '.';
|
||||
export type AntdTokens = ReturnType<typeof antdThemeImport.getDesignToken>;
|
||||
export type AntdThemeConfig = ThemeConfig;
|
||||
|
||||
/**
|
||||
* Theme algorithms supported by Antd.
|
||||
* They can be used individually or in combination.
|
||||
* - DEFAULT: Default light theme
|
||||
* - DARK: Dark theme
|
||||
* - COMPACT: Compact theme (smaller spacing)
|
||||
*/
|
||||
export enum ThemeAlgorithm {
|
||||
DEFAULT = 'default',
|
||||
DARK = 'dark',
|
||||
COMPACT = 'compact',
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the current theme mode of the app.
|
||||
* It can be one of the following:
|
||||
* - DEFAULT: Light theme
|
||||
* - DARK: Dark theme
|
||||
* - SYSTEM: System theme (auto-detects based on system settings)
|
||||
*/
|
||||
export enum ThemeMode {
|
||||
DEFAULT = 'default',
|
||||
DARK = 'dark',
|
||||
SYSTEM = 'system',
|
||||
}
|
||||
|
||||
/**
|
||||
* All valid algorithm values that can be used in theme config.
|
||||
*/
|
||||
export type ThemeAlgorithmOption =
|
||||
| ThemeAlgorithm.DEFAULT
|
||||
| ThemeAlgorithm.DARK
|
||||
| ThemeAlgorithm.COMPACT
|
||||
| ThemeAlgorithm[];
|
||||
|
||||
/**
|
||||
* A serializable version of Ant Design's ThemeConfig
|
||||
* Compatible with theme editor exports
|
||||
@@ -40,11 +75,7 @@ export type AntdThemeConfig = ThemeConfig;
|
||||
export type SerializableThemeConfig = {
|
||||
token?: Record<string, any>;
|
||||
components?: Record<string, any>;
|
||||
algorithm?:
|
||||
| 'default'
|
||||
| 'dark'
|
||||
| 'compact'
|
||||
| ('default' | 'dark' | 'compact')[];
|
||||
algorithm?: ThemeAlgorithmOption;
|
||||
hashed?: boolean;
|
||||
inherit?: boolean;
|
||||
};
|
||||
@@ -358,13 +389,6 @@ export type AllowedAntdTokenKeys = Extract<
|
||||
keyof AntdTokens
|
||||
>;
|
||||
|
||||
export enum ThemeMode {
|
||||
LIGHT = 'light',
|
||||
DARK = 'dark',
|
||||
SYSTEM = 'system',
|
||||
COMPACT = 'compact',
|
||||
}
|
||||
|
||||
export type SharedAntdTokens = Pick<AntdTokens, AllowedAntdTokenKeys>;
|
||||
|
||||
/** The final shape for our custom theme object, combining old theme + shared antd + superset specifics. */
|
||||
@@ -379,7 +403,7 @@ export interface ThemeStorage {
|
||||
}
|
||||
|
||||
export interface ThemeControllerOptions {
|
||||
themeObject: Theme;
|
||||
themeObject?: Theme;
|
||||
storage?: ThemeStorage;
|
||||
storageKey?: string;
|
||||
modeStorageKey?: string;
|
||||
@@ -393,6 +417,6 @@ export interface ThemeContextType {
|
||||
theme: Theme;
|
||||
themeMode: ThemeMode;
|
||||
setTheme: (config: AnyThemeConfig) => void;
|
||||
changeThemeMode: (newMode: ThemeMode) => void;
|
||||
setThemeMode: (newMode: ThemeMode) => void;
|
||||
resetTheme: () => void;
|
||||
}
|
||||
|
||||
@@ -28,9 +28,10 @@ import {
|
||||
genDeprecatedColorVariations,
|
||||
} from './utils';
|
||||
import {
|
||||
AnyThemeConfig,
|
||||
SerializableThemeConfig,
|
||||
AntdThemeConfig,
|
||||
type AnyThemeConfig,
|
||||
type SerializableThemeConfig,
|
||||
type AntdThemeConfig,
|
||||
ThemeAlgorithm,
|
||||
} from './types';
|
||||
|
||||
// Mock tinycolor2 for consistent testing
|
||||
@@ -50,22 +51,25 @@ describe('Theme utilities', () => {
|
||||
const config: AnyThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
};
|
||||
|
||||
expect(isSerializableConfig(config)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when algorithm is a string', () => {
|
||||
const config: AnyThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: 'dark',
|
||||
algorithm: ThemeAlgorithm.DARK,
|
||||
};
|
||||
|
||||
expect(isSerializableConfig(config)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when algorithm is an array of strings', () => {
|
||||
const config: AnyThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: ['dark', 'compact'],
|
||||
algorithm: [ThemeAlgorithm.DARK, ThemeAlgorithm.COMPACT],
|
||||
};
|
||||
|
||||
expect(isSerializableConfig(config)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -74,15 +78,19 @@ describe('Theme utilities', () => {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
};
|
||||
|
||||
expect(isSerializableConfig(config)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when algorithm is an array containing a function', () => {
|
||||
const config: AnyThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
// @ts-ignore
|
||||
algorithm: [antdThemeImport.darkAlgorithm, 'compact'],
|
||||
algorithm: [
|
||||
antdThemeImport.darkAlgorithm,
|
||||
antdThemeImport.compactAlgorithm,
|
||||
],
|
||||
};
|
||||
|
||||
expect(isSerializableConfig(config)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -91,18 +99,22 @@ describe('Theme utilities', () => {
|
||||
it('converts string algorithm to function reference', () => {
|
||||
const config: SerializableThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: 'dark',
|
||||
algorithm: ThemeAlgorithm.DARK,
|
||||
};
|
||||
|
||||
const result = deserializeThemeConfig(config);
|
||||
|
||||
expect(result.algorithm).toBe(antdThemeImport.darkAlgorithm);
|
||||
});
|
||||
|
||||
it('converts array of string algorithms to function references', () => {
|
||||
const config: SerializableThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: ['dark', 'compact'],
|
||||
algorithm: [ThemeAlgorithm.DARK, ThemeAlgorithm.COMPACT],
|
||||
};
|
||||
|
||||
const result = deserializeThemeConfig(config);
|
||||
|
||||
expect(Array.isArray(result.algorithm)).toBe(true);
|
||||
expect(result.algorithm).toContain(antdThemeImport.darkAlgorithm);
|
||||
expect(result.algorithm).toContain(antdThemeImport.compactAlgorithm);
|
||||
@@ -111,10 +123,12 @@ describe('Theme utilities', () => {
|
||||
it('preserves other configuration properties', () => {
|
||||
const config: SerializableThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: 'dark',
|
||||
algorithm: ThemeAlgorithm.DARK,
|
||||
hashed: true,
|
||||
};
|
||||
|
||||
const result = deserializeThemeConfig(config);
|
||||
|
||||
expect(result.token).toEqual({ colorPrimary: '#ff0000' });
|
||||
expect(result.hashed).toBe(true);
|
||||
});
|
||||
@@ -123,25 +137,31 @@ describe('Theme utilities', () => {
|
||||
const config: SerializableThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
};
|
||||
|
||||
const result = deserializeThemeConfig(config);
|
||||
expect(result.algorithm).toBeUndefined();
|
||||
|
||||
expect(result.algorithm).toBe(antdThemeImport.defaultAlgorithm);
|
||||
});
|
||||
|
||||
it('converts default algorithm string to function reference', () => {
|
||||
const config: SerializableThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: 'default',
|
||||
algorithm: ThemeAlgorithm.DEFAULT,
|
||||
};
|
||||
|
||||
const result = deserializeThemeConfig(config);
|
||||
|
||||
expect(result.algorithm).toBe(antdThemeImport.defaultAlgorithm);
|
||||
});
|
||||
|
||||
it('converts compact algorithm string to function reference', () => {
|
||||
const config: SerializableThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: 'compact',
|
||||
algorithm: ThemeAlgorithm.COMPACT,
|
||||
};
|
||||
|
||||
const result = deserializeThemeConfig(config);
|
||||
|
||||
expect(result.algorithm).toBe(antdThemeImport.compactAlgorithm);
|
||||
});
|
||||
});
|
||||
@@ -152,8 +172,10 @@ describe('Theme utilities', () => {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
};
|
||||
|
||||
const result = serializeThemeConfig(config);
|
||||
expect(result.algorithm).toBe('dark');
|
||||
|
||||
expect(result.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
|
||||
it('converts array of function algorithms to strings', () => {
|
||||
@@ -164,10 +186,12 @@ describe('Theme utilities', () => {
|
||||
antdThemeImport.compactAlgorithm,
|
||||
],
|
||||
};
|
||||
|
||||
const result = serializeThemeConfig(config);
|
||||
|
||||
expect(Array.isArray(result.algorithm)).toBe(true);
|
||||
expect(result.algorithm).toContain('dark');
|
||||
expect(result.algorithm).toContain('compact');
|
||||
expect(result.algorithm).toContain(ThemeAlgorithm.DARK);
|
||||
expect(result.algorithm).toContain(ThemeAlgorithm.COMPACT);
|
||||
});
|
||||
|
||||
it('preserves other configuration properties', () => {
|
||||
@@ -176,7 +200,9 @@ describe('Theme utilities', () => {
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
hashed: true,
|
||||
};
|
||||
|
||||
const result = serializeThemeConfig(config);
|
||||
|
||||
expect(result.token).toEqual({ colorPrimary: '#ff0000' });
|
||||
expect(result.hashed).toBe(true);
|
||||
});
|
||||
@@ -185,7 +211,9 @@ describe('Theme utilities', () => {
|
||||
const config: AntdThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
};
|
||||
|
||||
const result = serializeThemeConfig(config);
|
||||
|
||||
expect(result.algorithm).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -196,8 +224,10 @@ describe('Theme utilities', () => {
|
||||
// @ts-ignore
|
||||
algorithm: unknownAlgorithm,
|
||||
};
|
||||
|
||||
const result = serializeThemeConfig(config);
|
||||
expect(result.algorithm).toBe('default');
|
||||
|
||||
expect(result.algorithm).toBe(ThemeAlgorithm.DEFAULT);
|
||||
});
|
||||
|
||||
it('converts default algorithm function to string', () => {
|
||||
@@ -205,8 +235,10 @@ describe('Theme utilities', () => {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: antdThemeImport.defaultAlgorithm,
|
||||
};
|
||||
|
||||
const result = serializeThemeConfig(config);
|
||||
expect(result.algorithm).toBe('default');
|
||||
|
||||
expect(result.algorithm).toBe(ThemeAlgorithm.DEFAULT);
|
||||
});
|
||||
|
||||
it('converts compact algorithm function to string', () => {
|
||||
@@ -214,8 +246,10 @@ describe('Theme utilities', () => {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: antdThemeImport.compactAlgorithm,
|
||||
};
|
||||
|
||||
const result = serializeThemeConfig(config);
|
||||
expect(result.algorithm).toBe('compact');
|
||||
|
||||
expect(result.algorithm).toBe(ThemeAlgorithm.COMPACT);
|
||||
});
|
||||
|
||||
it('defaults each unknown algorithm in array to "default"', () => {
|
||||
@@ -225,9 +259,14 @@ describe('Theme utilities', () => {
|
||||
// @ts-ignore
|
||||
algorithm: [antdThemeImport.darkAlgorithm, unknownAlgorithm],
|
||||
};
|
||||
|
||||
const result = serializeThemeConfig(config);
|
||||
|
||||
expect(Array.isArray(result.algorithm)).toBe(true);
|
||||
expect(result.algorithm).toEqual(['dark', 'default']);
|
||||
expect(result.algorithm).toEqual([
|
||||
ThemeAlgorithm.DARK,
|
||||
ThemeAlgorithm.DEFAULT,
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles mixed known and unknown algorithms in array', () => {
|
||||
@@ -244,13 +283,15 @@ describe('Theme utilities', () => {
|
||||
unknownAlgorithm2,
|
||||
],
|
||||
};
|
||||
|
||||
const result = serializeThemeConfig(config);
|
||||
|
||||
expect(Array.isArray(result.algorithm)).toBe(true);
|
||||
expect(result.algorithm).toEqual([
|
||||
'dark',
|
||||
'default',
|
||||
'compact',
|
||||
'default',
|
||||
ThemeAlgorithm.DARK,
|
||||
ThemeAlgorithm.DEFAULT,
|
||||
ThemeAlgorithm.COMPACT,
|
||||
ThemeAlgorithm.DEFAULT,
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -261,16 +302,20 @@ describe('Theme utilities', () => {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
};
|
||||
|
||||
const result = normalizeThemeConfig(config);
|
||||
|
||||
expect(result).toBe(config);
|
||||
});
|
||||
|
||||
it('deserializes serializable configs', () => {
|
||||
const config: SerializableThemeConfig = {
|
||||
token: { colorPrimary: '#ff0000' },
|
||||
algorithm: 'dark',
|
||||
algorithm: ThemeAlgorithm.DARK,
|
||||
};
|
||||
|
||||
const result = normalizeThemeConfig(config);
|
||||
|
||||
expect(result.algorithm).toBe(antdThemeImport.darkAlgorithm);
|
||||
});
|
||||
});
|
||||
@@ -278,14 +323,18 @@ describe('Theme utilities', () => {
|
||||
describe('getAntdConfig', () => {
|
||||
it('returns config with default algorithm for light mode', () => {
|
||||
const seed = { colorPrimary: '#ff0000' };
|
||||
|
||||
const result = getAntdConfig(seed, false);
|
||||
|
||||
expect(result.token).toBe(seed);
|
||||
expect(result.algorithm).toBe(antdThemeImport.defaultAlgorithm);
|
||||
});
|
||||
|
||||
it('returns config with dark algorithm for dark mode', () => {
|
||||
const seed = { colorPrimary: '#ff0000' };
|
||||
|
||||
const result = getAntdConfig(seed, true);
|
||||
|
||||
expect(result.token).toBe(seed);
|
||||
expect(result.algorithm).toBe(antdThemeImport.darkAlgorithm);
|
||||
});
|
||||
@@ -301,7 +350,9 @@ describe('Theme utilities', () => {
|
||||
colorInfo: '#info',
|
||||
otherToken: 'ignore-me',
|
||||
};
|
||||
|
||||
const result = getSystemColors(tokens);
|
||||
|
||||
expect(result).toEqual({
|
||||
colorPrimary: '#primary',
|
||||
colorError: '#error',
|
||||
@@ -315,6 +366,7 @@ describe('Theme utilities', () => {
|
||||
describe('genDeprecatedColorVariations', () => {
|
||||
it('generates color variations for light mode', () => {
|
||||
const result = genDeprecatedColorVariations('#base-color', false);
|
||||
|
||||
expect(result.base).toBe('#base-color');
|
||||
expect(result.light1).toBe('#mixed-color');
|
||||
expect(result.dark1).toBe('#mixed-color');
|
||||
@@ -322,6 +374,7 @@ describe('Theme utilities', () => {
|
||||
|
||||
it('generates color variations for dark mode', () => {
|
||||
const result = genDeprecatedColorVariations('#base-color', true);
|
||||
|
||||
expect(result.base).toBe('#base-color');
|
||||
expect(result.light1).toBe('#mixed-color');
|
||||
expect(result.dark1).toBe('#mixed-color');
|
||||
@@ -337,7 +390,9 @@ describe('Theme utilities', () => {
|
||||
colorSuccess: '#success',
|
||||
colorInfo: '#info',
|
||||
};
|
||||
|
||||
const result = getDeprecatedColors(systemColors, false);
|
||||
|
||||
expect(result.primary.base).toBe('#primary');
|
||||
expect(result.error.base).toBe('#error');
|
||||
expect(result.warning.base).toBe('#warning');
|
||||
|
||||
@@ -19,13 +19,14 @@
|
||||
import { theme as antdThemeImport } from 'antd';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import {
|
||||
AntdThemeConfig,
|
||||
AnyThemeConfig,
|
||||
SerializableThemeConfig,
|
||||
DeprecatedColorVariations,
|
||||
DeprecatedThemeColors,
|
||||
SystemColors,
|
||||
SupersetTheme,
|
||||
type AntdThemeConfig,
|
||||
type AnyThemeConfig,
|
||||
type SerializableThemeConfig,
|
||||
type DeprecatedColorVariations,
|
||||
type DeprecatedThemeColors,
|
||||
type SystemColors,
|
||||
type SupersetTheme,
|
||||
ThemeAlgorithm,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
@@ -38,9 +39,8 @@ export function isSerializableConfig(
|
||||
|
||||
if (algorithm === undefined) return true;
|
||||
|
||||
if (Array.isArray(algorithm)) {
|
||||
if (Array.isArray(algorithm))
|
||||
return (algorithm as unknown[]).every(alg => typeof alg === 'string');
|
||||
}
|
||||
|
||||
return typeof algorithm === 'string';
|
||||
}
|
||||
@@ -60,9 +60,21 @@ export function deserializeThemeConfig(
|
||||
|
||||
let resolvedAlgorithm;
|
||||
if (Array.isArray(algorithm)) {
|
||||
resolvedAlgorithm = algorithm.map(alg => algorithmMap[alg]);
|
||||
} else if (algorithm) {
|
||||
const validAlgorithms = algorithm
|
||||
.map((alg: ThemeAlgorithm) => algorithmMap[alg])
|
||||
.filter(Boolean);
|
||||
|
||||
// If we have valid algorithms, use them; otherwise fallback to default
|
||||
if (validAlgorithms.length > 0) {
|
||||
resolvedAlgorithm =
|
||||
validAlgorithms.length === 1 ? validAlgorithms[0] : validAlgorithms;
|
||||
} else {
|
||||
resolvedAlgorithm = antdThemeImport.defaultAlgorithm;
|
||||
}
|
||||
} else if (algorithm && algorithmMap[algorithm]) {
|
||||
resolvedAlgorithm = algorithmMap[algorithm];
|
||||
} else {
|
||||
resolvedAlgorithm = antdThemeImport.defaultAlgorithm;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -83,19 +95,21 @@ export function serializeThemeConfig(
|
||||
|
||||
if (Array.isArray(algorithm)) {
|
||||
serializedAlgorithm = algorithm.map(alg => {
|
||||
if (alg === antdThemeImport.defaultAlgorithm) return 'default';
|
||||
if (alg === antdThemeImport.darkAlgorithm) return 'dark';
|
||||
if (alg === antdThemeImport.compactAlgorithm) return 'compact';
|
||||
return 'default'; // Fallback
|
||||
}) as ('default' | 'dark' | 'compact')[];
|
||||
if (alg === antdThemeImport.defaultAlgorithm)
|
||||
return ThemeAlgorithm.DEFAULT;
|
||||
if (alg === antdThemeImport.darkAlgorithm) return ThemeAlgorithm.DARK;
|
||||
if (alg === antdThemeImport.compactAlgorithm)
|
||||
return ThemeAlgorithm.COMPACT;
|
||||
return ThemeAlgorithm.DEFAULT; // Fallback
|
||||
}) as ThemeAlgorithm[];
|
||||
} else if (algorithm) {
|
||||
if (algorithm === antdThemeImport.defaultAlgorithm)
|
||||
serializedAlgorithm = 'default';
|
||||
serializedAlgorithm = ThemeAlgorithm.DEFAULT;
|
||||
else if (algorithm === antdThemeImport.darkAlgorithm)
|
||||
serializedAlgorithm = 'dark';
|
||||
serializedAlgorithm = ThemeAlgorithm.DARK;
|
||||
else if (algorithm === antdThemeImport.compactAlgorithm)
|
||||
serializedAlgorithm = 'compact';
|
||||
else serializedAlgorithm = 'default'; // Fallback
|
||||
serializedAlgorithm = ThemeAlgorithm.COMPACT;
|
||||
else serializedAlgorithm = ThemeAlgorithm.DEFAULT; // Fallback
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -109,9 +123,8 @@ export function serializeThemeConfig(
|
||||
* This automatically detects and converts serializable configs
|
||||
*/
|
||||
export function normalizeThemeConfig(config: AnyThemeConfig): AntdThemeConfig {
|
||||
if (isSerializableConfig(config)) {
|
||||
return deserializeThemeConfig(config);
|
||||
}
|
||||
if (isSerializableConfig(config)) return deserializeThemeConfig(config);
|
||||
|
||||
return config as AntdThemeConfig;
|
||||
}
|
||||
|
||||
@@ -164,7 +177,7 @@ export function getDeprecatedColors(
|
||||
systemColors: SystemColors,
|
||||
isDark: boolean,
|
||||
): DeprecatedThemeColors {
|
||||
const sc = systemColors;
|
||||
const sc: SystemColors = systemColors;
|
||||
return {
|
||||
primary: genDeprecatedColorVariations(sc.colorPrimary, isDark),
|
||||
error: genDeprecatedColorVariations(sc.colorError, isDark),
|
||||
|
||||
@@ -86,20 +86,33 @@ export default class Translator {
|
||||
}
|
||||
|
||||
translate(input: string, ...args: unknown[]): string {
|
||||
return this.i18n.translate(input).fetch(...args);
|
||||
try {
|
||||
return this.i18n.translate(input).fetch(...args);
|
||||
} catch (err) {
|
||||
logging.warn(`Translation failed for key "${input}" with args:`, args);
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
translateWithNumber(key: string, ...args: unknown[]): string {
|
||||
const [plural, num, ...rest] = args;
|
||||
if (typeof plural === 'number') {
|
||||
try {
|
||||
const [plural, num, ...rest] = args;
|
||||
if (typeof plural === 'number') {
|
||||
return this.i18n
|
||||
.translate(key)
|
||||
.ifPlural(plural, key)
|
||||
.fetch(plural, num, ...rest);
|
||||
}
|
||||
return this.i18n
|
||||
.translate(key)
|
||||
.ifPlural(plural, key)
|
||||
.fetch(plural, num, ...args);
|
||||
.ifPlural(num as number, plural as string)
|
||||
.fetch(...rest);
|
||||
} catch (err) {
|
||||
logging.warn(
|
||||
`Plural translation failed for key "${key}" with args:`,
|
||||
args,
|
||||
);
|
||||
}
|
||||
return this.i18n
|
||||
.translate(key)
|
||||
.ifPlural(num as number, plural as string)
|
||||
.fetch(...rest);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ export enum FeatureFlag {
|
||||
EnableTemplateProcessing = 'ENABLE_TEMPLATE_PROCESSING',
|
||||
EscapeMarkdownHtml = 'ESCAPE_MARKDOWN_HTML',
|
||||
EstimateQueryCost = 'ESTIMATE_QUERY_COST',
|
||||
FilterBarClosedByDefault = 'FILTERBAR_CLOSED_BY_DEFAULT',
|
||||
GlobalAsyncQueries = 'GLOBAL_ASYNC_QUERIES',
|
||||
ListviewsDefaultCardView = 'LISTVIEWS_DEFAULT_CARD_VIEW',
|
||||
ScheduledQueries = 'SCHEDULED_QUERIES',
|
||||
|
||||
+16
-7
@@ -19,18 +19,27 @@
|
||||
|
||||
import { t } from '../translation';
|
||||
|
||||
const VALIDE_OSM_URLS = ['https://tile.osm', 'https://tile.openstreetmap'];
|
||||
|
||||
/**
|
||||
* Validate a [Mapbox styles URL](https://docs.mapbox.com/help/glossary/style-url/)
|
||||
* @param v
|
||||
*/
|
||||
export default function validateMapboxStylesUrl(v: unknown) {
|
||||
if (
|
||||
typeof v === 'string' &&
|
||||
v.trim().length > 0 &&
|
||||
v.trim().startsWith('mapbox://styles/')
|
||||
) {
|
||||
return false;
|
||||
if (typeof v === 'string') {
|
||||
const trimmed_v = v.trim();
|
||||
if (
|
||||
typeof v === 'string' &&
|
||||
trimmed_v.length > 0 &&
|
||||
(trimmed_v.startsWith('mapbox://styles/') ||
|
||||
trimmed_v.startsWith('tile://http') ||
|
||||
VALIDE_OSM_URLS.some(s => trimmed_v.startsWith(s)))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return t('is expected to be a Mapbox URL');
|
||||
return t(
|
||||
'is expected to be a Mapbox/OSM URL (eg. mapbox://styles/...) or a tile server URL (eg. tile://http...)',
|
||||
);
|
||||
}
|
||||
|
||||
+6
-1
@@ -29,6 +29,11 @@ describe('validateMapboxStylesUrl', () => {
|
||||
'mapbox://styles/foobar/clp2dr5r4008a01pcg4ad45m8',
|
||||
),
|
||||
).toEqual(false);
|
||||
expect(
|
||||
validateMapboxStylesUrl(
|
||||
'tile://https://c.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
),
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
[
|
||||
@@ -40,7 +45,7 @@ describe('validateMapboxStylesUrl', () => {
|
||||
].forEach(value => {
|
||||
it(`should not validate ${value}`, () => {
|
||||
expect(validateMapboxStylesUrl(value)).toEqual(
|
||||
'is expected to be a Mapbox URL',
|
||||
'is expected to be a Mapbox/OSM URL (eg. mapbox://styles/...) or a tile server URL (eg. tile://http...)',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+122
-58
File diff suppressed because one or more lines are too long
+43
-119
File diff suppressed because one or more lines are too long
@@ -42,4 +42,8 @@ export default styled(WorldMapComponent)`
|
||||
background-color: ${({ theme }) => theme.colors.grayscale.light5};
|
||||
}
|
||||
}
|
||||
.hoverinfo {
|
||||
background-color: ${({ theme }) => theme.colorBgElevated};
|
||||
color: ${({ theme }) => theme.colorTextSecondary};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -24,10 +24,17 @@
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"@deck.gl/aggregation-layers": "^9.0.38",
|
||||
"@deck.gl/core": "^9.0.37",
|
||||
"@deck.gl/layers": "^9.0.38",
|
||||
"@deck.gl/react": "^9.1.4",
|
||||
"@deck.gl/aggregation-layers": "^9.1.13",
|
||||
"@deck.gl/core": "^9.1.13",
|
||||
"@deck.gl/geo-layers": "^9.1.13",
|
||||
"@deck.gl/layers": "^9.1.13",
|
||||
"@deck.gl/react": "^9.1.13",
|
||||
"@luma.gl/constants": "^9.1.9",
|
||||
"@luma.gl/core": "^9.1.9",
|
||||
"@luma.gl/engine": "^9.1.9",
|
||||
"@luma.gl/shadertools": "^9.1.9",
|
||||
"@luma.gl/webgl": "^9.1.9",
|
||||
"@mapbox/tiny-sdf": "^2.0.6",
|
||||
"@mapbox/geojson-extent": "^1.0.1",
|
||||
"@math.gl/web-mercator": "^4.1.0",
|
||||
"@types/d3-array": "^2.0.0",
|
||||
|
||||
@@ -39,6 +39,12 @@ import { JsonObject, JsonValue, styled, usePrevious } from '@superset-ui/core';
|
||||
import Tooltip, { TooltipProps } from './components/Tooltip';
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
import { Viewport } from './utils/fitViewport';
|
||||
import {
|
||||
MAPBOX_LAYER_PREFIX,
|
||||
OSM_LAYER_KEYWORDS,
|
||||
TILE_LAYER_PREFIX,
|
||||
buildTileLayer,
|
||||
} from './utils';
|
||||
|
||||
const TICK = 250; // milliseconds
|
||||
|
||||
@@ -102,6 +108,20 @@ export const DeckGLContainer = memo(
|
||||
);
|
||||
|
||||
const layers = useCallback(() => {
|
||||
if (
|
||||
(props.mapStyle?.startsWith(TILE_LAYER_PREFIX) ||
|
||||
OSM_LAYER_KEYWORDS.some(tilek => props.mapStyle?.includes(tilek))) &&
|
||||
props.layers.some(
|
||||
l => typeof l !== 'function' && l?.id === 'tile-layer',
|
||||
) === false
|
||||
) {
|
||||
props.layers.unshift(
|
||||
buildTileLayer(
|
||||
(props.mapStyle ?? '').replace(TILE_LAYER_PREFIX, ''),
|
||||
'tile-layer',
|
||||
),
|
||||
);
|
||||
}
|
||||
// Support for layer factory
|
||||
if (props.layers.some(l => typeof l === 'function')) {
|
||||
return props.layers.map(l =>
|
||||
@@ -110,7 +130,7 @@ export const DeckGLContainer = memo(
|
||||
}
|
||||
|
||||
return props.layers as Layer[];
|
||||
}, [props.layers]);
|
||||
}, [props.layers, props.mapStyle]);
|
||||
|
||||
const { children = null, height, width } = props;
|
||||
|
||||
@@ -134,11 +154,13 @@ export const DeckGLContainer = memo(
|
||||
glContextRef.current = context.gl;
|
||||
}}
|
||||
>
|
||||
<StaticMap
|
||||
preserveDrawingBuffer
|
||||
mapStyle={props.mapStyle || 'light'}
|
||||
mapboxApiAccessToken={props.mapboxApiAccessToken}
|
||||
/>
|
||||
{props.mapStyle?.startsWith(MAPBOX_LAYER_PREFIX) && (
|
||||
<StaticMap
|
||||
preserveDrawingBuffer
|
||||
mapStyle={props.mapStyle || 'light'}
|
||||
mapboxApiAccessToken={props.mapboxApiAccessToken}
|
||||
/>
|
||||
)}
|
||||
</DeckGL>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
+27
-9
@@ -48,6 +48,30 @@ const sequentialSchemeRegistry = getSequentialSchemeRegistry();
|
||||
|
||||
export const DEFAULT_DECKGL_COLOR = { r: 158, g: 158, b: 158, a: 1 };
|
||||
|
||||
let deckglTiles: string[][];
|
||||
|
||||
export const DEFAULT_DECKGL_TILES = [
|
||||
['https://tile.openstreetmap.org/{z}/{x}/{y}.png', 'Streets (OSM)'],
|
||||
['https://tile.osm.ch/osm-swiss-style/{z}/{x}/{y}.png', 'Topography (OSM)'],
|
||||
['mapbox://styles/mapbox/streets-v9', 'Streets (Mapbox)'],
|
||||
['mapbox://styles/mapbox/dark-v9', 'Dark (Mapbox)'],
|
||||
['mapbox://styles/mapbox/light-v9', 'Light (Mapbox)'],
|
||||
['mapbox://styles/mapbox/satellite-streets-v9', 'Satellite Streets (Mapbox)'],
|
||||
['mapbox://styles/mapbox/satellite-v9', 'Satellite (Mapbox)'],
|
||||
['mapbox://styles/mapbox/outdoors-v9', 'Outdoors (Mapbox)'],
|
||||
];
|
||||
|
||||
const getDeckGLTiles = () => {
|
||||
if (!deckglTiles) {
|
||||
const appContainer = document.getElementById('app');
|
||||
const { common } = JSON.parse(
|
||||
appContainer?.getAttribute('data-bootstrap') || '{}',
|
||||
);
|
||||
deckglTiles = common?.deckgl_tiles ?? DEFAULT_DECKGL_TILES;
|
||||
}
|
||||
return deckglTiles;
|
||||
};
|
||||
|
||||
const DEFAULT_VIEWPORT = {
|
||||
longitude: 6.85236157047845,
|
||||
latitude: 31.222656842808707,
|
||||
@@ -395,17 +419,11 @@ export const mapboxStyle = {
|
||||
renderTrigger: true,
|
||||
freeForm: true,
|
||||
validators: [validateMapboxStylesUrl],
|
||||
choices: [
|
||||
['mapbox://styles/mapbox/streets-v9', t('Streets')],
|
||||
['mapbox://styles/mapbox/dark-v9', t('Dark')],
|
||||
['mapbox://styles/mapbox/light-v9', t('Light')],
|
||||
['mapbox://styles/mapbox/satellite-streets-v9', t('Satellite Streets')],
|
||||
['mapbox://styles/mapbox/satellite-v9', t('Satellite')],
|
||||
['mapbox://styles/mapbox/outdoors-v9', t('Outdoors')],
|
||||
],
|
||||
default: 'mapbox://styles/mapbox/light-v9',
|
||||
choices: getDeckGLTiles(),
|
||||
default: getDeckGLTiles()[0][0],
|
||||
description: t(
|
||||
'Base layer map style. See Mapbox documentation: %s',
|
||||
'Mapbox base layer map style (see Mapbox documentation: %s) or tile server URL.',
|
||||
'https://docs.mapbox.com/help/glossary/style-url/',
|
||||
),
|
||||
},
|
||||
|
||||
@@ -26,11 +26,17 @@ import {
|
||||
SequentialScheme,
|
||||
} from '@superset-ui/core';
|
||||
import { Color } from '@deck.gl/core';
|
||||
import { GeoBoundingBox, TileLayer } from '@deck.gl/geo-layers';
|
||||
import { BitmapLayer, PathLayer } from '@deck.gl/layers';
|
||||
import { hexToRGB } from './utils/colors';
|
||||
import { ColorBreakpointType } from './types';
|
||||
|
||||
const DEFAULT_NUM_BUCKETS = 10;
|
||||
|
||||
export const MAPBOX_LAYER_PREFIX = 'mapbox://';
|
||||
export const TILE_LAYER_PREFIX = 'tile://';
|
||||
export const OSM_LAYER_KEYWORDS = ['openstreetmap', 'osm'];
|
||||
|
||||
export type Buckets = {
|
||||
break_points: string[];
|
||||
num_buckets: string;
|
||||
@@ -215,3 +221,42 @@ export function getColorBreakpointsBuckets(
|
||||
|
||||
return buckets;
|
||||
}
|
||||
|
||||
export function buildTileLayer(url: string, id: string) {
|
||||
interface TileLayerProps {
|
||||
id: string;
|
||||
data: string;
|
||||
minZoom: number;
|
||||
maxZoom: number;
|
||||
tileSize: number;
|
||||
renderSubLayers: (props: any) => (BitmapLayer | PathLayer)[];
|
||||
}
|
||||
|
||||
interface RenderSubLayerProps {
|
||||
tile: {
|
||||
bbox: GeoBoundingBox;
|
||||
};
|
||||
data: any;
|
||||
}
|
||||
|
||||
return new TileLayer({
|
||||
data: url,
|
||||
id,
|
||||
minZoom: 0,
|
||||
maxZoom: 19,
|
||||
tileSize: 256,
|
||||
|
||||
renderSubLayers: (props: RenderSubLayerProps): BitmapLayer[] => {
|
||||
const { west, north, east, south } = props.tile.bbox as GeoBoundingBox;
|
||||
|
||||
// Ajouter une BitmapLayer
|
||||
const bitmapLayer = new BitmapLayer(props, {
|
||||
data: undefined,
|
||||
image: props.data,
|
||||
bounds: [west, south, east, north],
|
||||
});
|
||||
|
||||
return [bitmapLayer];
|
||||
},
|
||||
} as TileLayerProps);
|
||||
}
|
||||
|
||||
@@ -209,12 +209,18 @@ export default function EchartsTimeseries({
|
||||
}),
|
||||
);
|
||||
groupBy.forEach((dimension, i) => {
|
||||
const val = labelMap[seriesName][i];
|
||||
const dimensionValues = labelMap[seriesName] ?? [];
|
||||
|
||||
// Skip the metric values at the beginning and get the actual dimension value
|
||||
// If we have multiple metrics, they come first, then the dimension values
|
||||
const metricsCount = dimensionValues.length - groupBy.length;
|
||||
const val = dimensionValues[metricsCount + i];
|
||||
|
||||
drillByFilters.push({
|
||||
col: dimension,
|
||||
op: '==',
|
||||
val,
|
||||
formattedVal: formatSeriesName(values[i], {
|
||||
formattedVal: formatSeriesName(val, {
|
||||
timeFormatter: getTimeFormatter(formData.dateFormat),
|
||||
numberFormatter: getNumberFormatter(formData.numberFormat),
|
||||
coltype: coltypeMapping?.[getColumnLabel(dimension)],
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
set -e
|
||||
|
||||
export NODE_NO_WARNINGS=1
|
||||
|
||||
for file in $( find ../superset/translations/** -name '*.po' );
|
||||
do
|
||||
extension=${file##*.}
|
||||
@@ -29,7 +31,7 @@ do
|
||||
if [ $extension == "po" ]
|
||||
then
|
||||
echo "po2json --domain superset --format jed1.x $file $filename.json"
|
||||
po2json --domain superset --format jed1.x $file $filename.json
|
||||
po2json --domain superset --format jed1.x --fuzzy $file $filename.json
|
||||
prettier --write $filename.json
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -926,7 +926,7 @@ const SqlEditor: FC<Props> = ({
|
||||
css={css`
|
||||
margin-bottom: ${theme.sizeUnit * 2}px;
|
||||
padding-top: ${theme.sizeUnit * 4}px;
|
||||
.antd5-alert-action {
|
||||
.ant-alert-action {
|
||||
align-self: center;
|
||||
}
|
||||
`}
|
||||
|
||||
@@ -353,7 +353,7 @@ test('Sends the correct db when changing the database', async () => {
|
||||
await waitFor(() =>
|
||||
expect(props.onDbChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: `mysql-test-mysql-2`,
|
||||
value: 2,
|
||||
database_name: 'test-mysql',
|
||||
backend: 'mysql',
|
||||
}),
|
||||
|
||||
@@ -319,11 +319,14 @@ export function DatabaseSelector({
|
||||
value: { label: string; value: number },
|
||||
database: DatabaseValue,
|
||||
) {
|
||||
setCurrentDb(database);
|
||||
// the database id is actually stored in the value property; the ID is used
|
||||
// for the DOM, so it can't be an integer
|
||||
const databaseWithId = { ...database, id: database.value };
|
||||
setCurrentDb(databaseWithId);
|
||||
setCurrentCatalog(undefined);
|
||||
setCurrentSchema(undefined);
|
||||
if (onDbChange) {
|
||||
onDbChange(database);
|
||||
onDbChange(databaseWithId);
|
||||
}
|
||||
if (onCatalogChange) {
|
||||
onCatalogChange(undefined);
|
||||
|
||||
@@ -717,7 +717,9 @@ class DatasourceEditor extends PureComponent {
|
||||
}
|
||||
|
||||
onDatasourceTypeChange(datasourceType) {
|
||||
this.setState({ datasourceType });
|
||||
// Call onChange after setting datasourceType to ensure
|
||||
// SQL is cleared when switching to a physical dataset
|
||||
this.setState({ datasourceType }, this.onChange);
|
||||
}
|
||||
|
||||
setColumns(obj) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
screen,
|
||||
waitFor,
|
||||
userEvent,
|
||||
cleanup,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import mockDatasource from 'spec/fixtures/mockDatasource';
|
||||
import { isFeatureEnabled } from '@superset-ui/core';
|
||||
@@ -244,4 +245,36 @@ describe('DatasourceEditor Source Tab', () => {
|
||||
expect(physicalRadioBtn).toBeDisabled();
|
||||
expect(virtualRadioBtn).toBeDisabled();
|
||||
});
|
||||
|
||||
it('calls onChange with empty SQL when switching to physical dataset', async () => {
|
||||
// Clean previous render
|
||||
cleanup();
|
||||
|
||||
props.onChange.mockClear();
|
||||
|
||||
await asyncRender({
|
||||
...props,
|
||||
datasource: {
|
||||
...props.datasource,
|
||||
table_name: 'Vehicle Sales +',
|
||||
datasourceType: 'virtual',
|
||||
sql: 'SELECT * FROM users',
|
||||
},
|
||||
});
|
||||
|
||||
// Enable edit mode
|
||||
const getLockBtn = screen.getByRole('img', { name: /lock/i });
|
||||
await userEvent.click(getLockBtn);
|
||||
|
||||
// Switch to physical dataset
|
||||
const physicalRadioBtn = screen.getByRole('radio', {
|
||||
name: /physical \(table or view\)/i,
|
||||
});
|
||||
await userEvent.click(physicalRadioBtn);
|
||||
|
||||
// Assert that the latest onChange call has empty SQL
|
||||
expect(props.onChange).toHaveBeenCalled();
|
||||
const updatedDatasource = props.onChange.mock.calls[0];
|
||||
expect(updatedDatasource[0].sql).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,3 +39,10 @@ export interface TagType {
|
||||
css?: SerializedStyles;
|
||||
closable?: boolean;
|
||||
}
|
||||
|
||||
export enum TagTypeEnum {
|
||||
Custom = 1,
|
||||
Type = 2,
|
||||
Owner = 3,
|
||||
FavoritedBy = 4,
|
||||
}
|
||||
|
||||
@@ -47,6 +47,15 @@ export const TagsList = ({
|
||||
onDelete,
|
||||
maxTags,
|
||||
}: TagsListProps) => {
|
||||
const sortedTags = useMemo(
|
||||
() =>
|
||||
tags.toSorted((a, b) => {
|
||||
const nameA = a.name ?? '';
|
||||
const nameB = b.name ?? '';
|
||||
return nameA.localeCompare(nameB);
|
||||
}),
|
||||
[tags],
|
||||
);
|
||||
const [tempMaxTags, setTempMaxTags] = useState<number | undefined>(maxTags);
|
||||
|
||||
const handleDelete = (index: number) => {
|
||||
@@ -58,21 +67,23 @@ export const TagsList = ({
|
||||
const collapse = () => setTempMaxTags(maxTags);
|
||||
|
||||
const tagsIsLong: boolean | null = useMemo(
|
||||
() => (tempMaxTags ? tags.length > tempMaxTags : null),
|
||||
[tags.length, tempMaxTags],
|
||||
() => (tempMaxTags ? sortedTags.length > tempMaxTags : null),
|
||||
[sortedTags.length, tempMaxTags],
|
||||
);
|
||||
|
||||
const extraTags: number | null = useMemo(
|
||||
() =>
|
||||
typeof tempMaxTags === 'number' ? tags.length - tempMaxTags + 1 : null,
|
||||
[tagsIsLong, tags.length, tempMaxTags],
|
||||
typeof tempMaxTags === 'number'
|
||||
? sortedTags.length - tempMaxTags + 1
|
||||
: null,
|
||||
[tagsIsLong, sortedTags.length, tempMaxTags],
|
||||
);
|
||||
|
||||
return (
|
||||
<TagsDiv className="tag-list">
|
||||
{tagsIsLong && typeof tempMaxTags === 'number' ? (
|
||||
<>
|
||||
{tags.slice(0, tempMaxTags - 1).map((tag: TagType, index) => (
|
||||
{sortedTags.slice(0, tempMaxTags - 1).map((tag: TagType, index) => (
|
||||
<Tag
|
||||
id={tag.id}
|
||||
key={tag.id}
|
||||
@@ -82,17 +93,17 @@ export const TagsList = ({
|
||||
editable={editable}
|
||||
/>
|
||||
))}
|
||||
{tags.length > tempMaxTags ? (
|
||||
{sortedTags.length > tempMaxTags ? (
|
||||
<Tag
|
||||
name={`+${extraTags}...`}
|
||||
onClick={expand}
|
||||
toolTipTitle={tags.map(t => t.name).join(', ')}
|
||||
toolTipTitle={sortedTags.map(t => t.name).join(', ')}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{tags.map((tag: TagType, index) => (
|
||||
{sortedTags.map((tag: TagType, index) => (
|
||||
<Tag
|
||||
id={tag.id}
|
||||
key={tag.id}
|
||||
@@ -103,7 +114,7 @@ export const TagsList = ({
|
||||
/>
|
||||
))}
|
||||
{maxTags ? (
|
||||
tags.length > maxTags ? (
|
||||
sortedTags.length > maxTags ? (
|
||||
<Tag name="..." onClick={collapse} />
|
||||
) : null
|
||||
) : null}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { DEFAULT_D3_FORMAT, DEFAULT_D3_TIME_FORMAT } from '@superset-ui/core';
|
||||
|
||||
import { BootstrapData, CommonBootstrapData } from './types/bootstrapTypes';
|
||||
|
||||
export const DATETIME_WITH_TIME_ZONE = 'YYYY-MM-DD HH:mm:ssZ';
|
||||
@@ -141,7 +140,11 @@ export const DEFAULT_COMMON_BOOTSTRAP_DATA: CommonBootstrapData = {
|
||||
},
|
||||
extra_categorical_color_schemes: [],
|
||||
extra_sequential_color_schemes: [],
|
||||
theme: {},
|
||||
theme: {
|
||||
default: {},
|
||||
dark: {},
|
||||
settings: {},
|
||||
},
|
||||
menu_data: {
|
||||
menu: [],
|
||||
brand: {
|
||||
|
||||
@@ -294,6 +294,7 @@ export function saveDashboardRequest(data, id, saveType) {
|
||||
owners,
|
||||
roles,
|
||||
slug,
|
||||
tags,
|
||||
} = data;
|
||||
|
||||
const hasId = item => item.id !== undefined;
|
||||
@@ -315,6 +316,9 @@ export function saveDashboardRequest(data, id, saveType) {
|
||||
? undefined
|
||||
: ensureIsArray(roles).map(r => (hasId(r) ? r.id : r)),
|
||||
slug: slug || null,
|
||||
tags: !isFeatureEnabled(FeatureFlag.TaggingSystem)
|
||||
? undefined
|
||||
: ensureIsArray(tags || []).map(r => (hasId(r) ? r.id : r)),
|
||||
metadata: {
|
||||
...data.metadata,
|
||||
color_namespace: getColorNamespace(data.metadata?.color_namespace),
|
||||
@@ -445,6 +449,7 @@ export function saveDashboardRequest(data, id, saveType) {
|
||||
slug: cleanedData.slug,
|
||||
owners: cleanedData.owners,
|
||||
roles: cleanedData.roles,
|
||||
tags: cleanedData.tags || [],
|
||||
json_metadata: safeStringify({
|
||||
...(cleanedData?.metadata || {}),
|
||||
default_filters: safeStringify(serializedFilters),
|
||||
|
||||
@@ -23,6 +23,11 @@ import {
|
||||
within,
|
||||
screen,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import { FeatureFlag } from '@superset-ui/core';
|
||||
import {
|
||||
OPEN_FILTER_BAR_WIDTH,
|
||||
CLOSED_FILTER_BAR_WIDTH,
|
||||
} from 'src/dashboard/constants';
|
||||
import DashboardBuilder from 'src/dashboard/components/DashboardBuilder/DashboardBuilder';
|
||||
import useStoredSidebarWidth from 'src/components/ResizableSidebar/useStoredSidebarWidth';
|
||||
import {
|
||||
@@ -268,6 +273,46 @@ describe('DashboardBuilder', () => {
|
||||
expect(filterbar).toHaveStyleRule('width', `${expectedValue}px`);
|
||||
});
|
||||
|
||||
it('filter panel state when featureflag is true', () => {
|
||||
window.featureFlags = {
|
||||
[FeatureFlag.FilterBarClosedByDefault]: true,
|
||||
};
|
||||
const setter = jest.fn();
|
||||
(useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
|
||||
CLOSED_FILTER_BAR_WIDTH,
|
||||
setter,
|
||||
]);
|
||||
const { getByTestId } = setup({
|
||||
dashboardInfo: {
|
||||
...mockState.dashboardInfo,
|
||||
dash_edit_perm: true,
|
||||
},
|
||||
});
|
||||
|
||||
const filterbar = getByTestId('dashboard-filters-panel');
|
||||
expect(filterbar).toHaveStyleRule('width', `${CLOSED_FILTER_BAR_WIDTH}px`);
|
||||
});
|
||||
|
||||
it('filter panel state when featureflag is false', () => {
|
||||
window.featureFlags = {
|
||||
[FeatureFlag.FilterBarClosedByDefault]: false,
|
||||
};
|
||||
const setter = jest.fn();
|
||||
(useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
|
||||
OPEN_FILTER_BAR_WIDTH,
|
||||
setter,
|
||||
]);
|
||||
const { getByTestId } = setup({
|
||||
dashboardInfo: {
|
||||
...mockState.dashboardInfo,
|
||||
dash_edit_perm: true,
|
||||
},
|
||||
});
|
||||
|
||||
const filterbar = getByTestId('dashboard-filters-panel');
|
||||
expect(filterbar).toHaveStyleRule('width', `${OPEN_FILTER_BAR_WIDTH}px`);
|
||||
});
|
||||
|
||||
it('should not render the filter bar when nativeFiltersEnabled is false', () => {
|
||||
jest.spyOn(useNativeFiltersModule, 'useNativeFilters').mockReturnValue({
|
||||
showDashboard: true,
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import { getUrlParam } from 'src/utils/urlUtils';
|
||||
import { RootState } from 'src/dashboard/types';
|
||||
import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core';
|
||||
import {
|
||||
useFilters,
|
||||
useNativeFiltersDataMask,
|
||||
@@ -71,6 +72,8 @@ export const useNativeFilters = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
(isFeatureEnabled(FeatureFlag.FilterBarClosedByDefault) &&
|
||||
expandFilters === null) ||
|
||||
expandFilters === false ||
|
||||
(filterValues.length === 0 && nativeFiltersEnabled)
|
||||
) {
|
||||
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
DASHBOARD_POSITION_DATA_LIMIT,
|
||||
DASHBOARD_HEADER_ID,
|
||||
} from 'src/dashboard/util/constants';
|
||||
import { TagTypeEnum } from 'src/components/Tag/TagType';
|
||||
import setPeriodicRunner, {
|
||||
stopPeriodicRender,
|
||||
} from 'src/dashboard/util/setPeriodicRunner';
|
||||
@@ -415,6 +416,9 @@ const Header = () => {
|
||||
owners: dashboardInfo.owners,
|
||||
roles: dashboardInfo.roles,
|
||||
slug,
|
||||
tags: (dashboardInfo.tags || []).filter(
|
||||
item => item.type === TagTypeEnum.Custom || !item.type,
|
||||
),
|
||||
metadata: {
|
||||
...dashboardInfo?.metadata,
|
||||
color_namespace: currentColorNamespace,
|
||||
@@ -459,6 +463,7 @@ const Header = () => {
|
||||
dashboardInfo.metadata,
|
||||
dashboardInfo.owners,
|
||||
dashboardInfo.roles,
|
||||
dashboardInfo.tags,
|
||||
dashboardTitle,
|
||||
layout,
|
||||
refreshFrequency,
|
||||
@@ -525,6 +530,7 @@ const Header = () => {
|
||||
certification_details: updates.certificationDetails,
|
||||
owners: updates.owners,
|
||||
roles: updates.roles,
|
||||
tags: updates.tags,
|
||||
});
|
||||
boundActionCreators.setUnsavedChanges(true);
|
||||
boundActionCreators.dashboardTitleChanged(updates.title);
|
||||
|
||||
@@ -383,7 +383,7 @@ const PropertiesModal = ({
|
||||
|
||||
currentJsonMetadata = jsonStringify(metadata);
|
||||
|
||||
const moreOnSubmitProps: { roles?: Roles } = {};
|
||||
const moreOnSubmitProps: { roles?: Roles; tags?: TagType[] } = {};
|
||||
const morePutProps: {
|
||||
roles?: number[];
|
||||
tags?: (string | number | undefined)[];
|
||||
@@ -393,6 +393,7 @@ const PropertiesModal = ({
|
||||
morePutProps.roles = (roles || []).map(r => r.id);
|
||||
}
|
||||
if (isFeatureEnabled(FeatureFlag.TaggingSystem)) {
|
||||
moreOnSubmitProps.tags = tags;
|
||||
morePutProps.tags = tags.map(tag => tag.id);
|
||||
}
|
||||
const onSubmitProps = {
|
||||
@@ -600,6 +601,10 @@ const PropertiesModal = ({
|
||||
setTags(parsedTags);
|
||||
};
|
||||
|
||||
const handleClearTags = () => {
|
||||
setTags([]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
show={show}
|
||||
@@ -725,6 +730,7 @@ const PropertiesModal = ({
|
||||
value={tagsAsSelectValues}
|
||||
options={loadTags}
|
||||
onChange={handleChangeTags}
|
||||
onClear={handleClearTags}
|
||||
allowClear
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
@@ -59,6 +59,7 @@ type SliceHeaderProps = SliceHeaderControlsProps & {
|
||||
formData: object;
|
||||
width: number;
|
||||
height: number;
|
||||
exportPivotExcel?: (arg0: string) => void;
|
||||
};
|
||||
|
||||
const annotationsLoading = t('Annotation layers are still loading.');
|
||||
@@ -167,6 +168,7 @@ const SliceHeader = forwardRef<HTMLDivElement, SliceHeaderProps>(
|
||||
formData,
|
||||
width,
|
||||
height,
|
||||
exportPivotExcel = () => ({}),
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
@@ -344,6 +346,7 @@ const SliceHeader = forwardRef<HTMLDivElement, SliceHeaderProps>(
|
||||
formData={formData}
|
||||
exploreUrl={exploreUrl}
|
||||
crossFiltersEnabled={isCrossFiltersEnabled}
|
||||
exportPivotExcel={exportPivotExcel}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
+15
@@ -33,6 +33,7 @@ const createProps = (viz_type = VizType.Sunburst) =>
|
||||
exportFullCSV: jest.fn(),
|
||||
exportXLSX: jest.fn(),
|
||||
exportFullXLSX: jest.fn(),
|
||||
exportPivotExcel: jest.fn(),
|
||||
forceRefresh: jest.fn(),
|
||||
handleToggleFullSize: jest.fn(),
|
||||
toggleExpandSlice: jest.fn(),
|
||||
@@ -254,6 +255,20 @@ test('Should not show export full Excel if report is not table', async () => {
|
||||
expect(screen.queryByText('Export to full Excel')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Should export to pivoted Excel if report is pivot table', async () => {
|
||||
const props = createProps(VizType.PivotTable);
|
||||
renderWrapper(props);
|
||||
openMenu();
|
||||
expect(props.exportPivotExcel).toHaveBeenCalledTimes(0);
|
||||
userEvent.hover(screen.getByText('Download'));
|
||||
userEvent.click(await screen.findByText('Export to Pivoted Excel'));
|
||||
expect(props.exportPivotExcel).toHaveBeenCalledTimes(1);
|
||||
expect(props.exportPivotExcel).toHaveBeenCalledWith(
|
||||
'.pvtTable',
|
||||
props.slice.slice_name,
|
||||
);
|
||||
});
|
||||
|
||||
test('Should "Show chart description"', () => {
|
||||
const props = createProps();
|
||||
renderWrapper(props);
|
||||
|
||||
@@ -128,6 +128,7 @@ export interface SliceHeaderControlsProps {
|
||||
exportXLSX?: (sliceId: number) => void;
|
||||
exportFullXLSX?: (sliceId: number) => void;
|
||||
handleToggleFullSize: () => void;
|
||||
exportPivotExcel?: (tableSelector: string, sliceName: string) => void;
|
||||
|
||||
addDangerToast: (message: string) => void;
|
||||
addSuccessToast: (message: string) => void;
|
||||
@@ -255,6 +256,10 @@ const SliceHeaderControls = (
|
||||
});
|
||||
break;
|
||||
}
|
||||
case MenuKeys.ExportPivotXlsx: {
|
||||
props.exportPivotExcel?.('.pvtTable', props.slice.slice_name);
|
||||
break;
|
||||
}
|
||||
case MenuKeys.CrossFilterScoping: {
|
||||
openScopingModal();
|
||||
break;
|
||||
@@ -468,6 +473,15 @@ const SliceHeaderControls = (
|
||||
{t('Export to Excel')}
|
||||
</Menu.Item>
|
||||
|
||||
{isPivotTable && (
|
||||
<Menu.Item
|
||||
key={MenuKeys.ExportPivotXlsx}
|
||||
icon={<Icons.FileOutlined css={dropdownIconsStyles} />}
|
||||
>
|
||||
{t('Export to Pivoted Excel')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
{isFeatureEnabled(FeatureFlag.AllowFullCsvExport) &&
|
||||
props.supersetCanCSV &&
|
||||
isTable && (
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
import { postFormData } from 'src/explore/exploreUtils/formData';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import { enforceSharedLabelsColorsArray } from 'src/utils/colorScheme';
|
||||
import exportPivotExcel from 'src/utils/downloadAsPivotExcel';
|
||||
|
||||
import SliceHeader from '../SliceHeader';
|
||||
import MissingChart from '../MissingChart';
|
||||
@@ -213,11 +214,15 @@ const Chart = props => {
|
||||
|
||||
const getHeaderHeight = useCallback(() => {
|
||||
if (headerRef.current) {
|
||||
const computedStyle = getComputedStyle(
|
||||
const computedMarginBottom = getComputedStyle(
|
||||
headerRef.current,
|
||||
).getPropertyValue('margin-bottom');
|
||||
const marginBottom = parseInt(computedStyle, 10) || 0;
|
||||
return headerRef.current.offsetHeight + marginBottom;
|
||||
const marginBottom = parseInt(computedMarginBottom, 10) || 0;
|
||||
const computedHeight = getComputedStyle(
|
||||
headerRef.current,
|
||||
).getPropertyValue('height');
|
||||
const height = parseInt(computedHeight, 10) || DEFAULT_HEADER_HEIGHT;
|
||||
return height + marginBottom;
|
||||
}
|
||||
return DEFAULT_HEADER_HEIGHT;
|
||||
}, [headerRef]);
|
||||
@@ -471,6 +476,7 @@ const Chart = props => {
|
||||
formData={formData}
|
||||
width={width}
|
||||
height={getHeaderHeight()}
|
||||
exportPivotExcel={exportPivotExcel}
|
||||
/>
|
||||
|
||||
{/*
|
||||
|
||||
@@ -27,6 +27,7 @@ export default function DownloadAsImage({
|
||||
text,
|
||||
logEvent,
|
||||
dashboardTitle,
|
||||
...props
|
||||
}: {
|
||||
text: string;
|
||||
dashboardTitle: string;
|
||||
@@ -50,6 +51,7 @@ export default function DownloadAsImage({
|
||||
onClick={e => {
|
||||
onDownloadImage(e.domEvent);
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{text}
|
||||
</Menu.Item>
|
||||
|
||||
@@ -27,6 +27,7 @@ export default function DownloadAsPdf({
|
||||
text,
|
||||
logEvent,
|
||||
dashboardTitle,
|
||||
...props
|
||||
}: {
|
||||
text: string;
|
||||
dashboardTitle: string;
|
||||
@@ -50,6 +51,7 @@ export default function DownloadAsPdf({
|
||||
onClick={e => {
|
||||
onDownloadPdf(e.domEvent);
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{text}
|
||||
</Menu.Item>
|
||||
|
||||
@@ -289,4 +289,5 @@ export enum MenuKeys {
|
||||
ToggleFullscreen = 'toggle_fullscreen',
|
||||
ManageEmbedded = 'manage_embedded',
|
||||
ManageEmailReports = 'manage_email_reports',
|
||||
ExportPivotXlsx = 'export_pivot_xlsx',
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import Chart, { Slice } from 'src/types/Chart';
|
||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||
import { type TagType } from 'src/components';
|
||||
import { TagTypeEnum } from 'src/components/Tag/TagType';
|
||||
import { loadTags } from 'src/components/Tag/utils';
|
||||
|
||||
export type PropertiesModalProps = {
|
||||
@@ -125,7 +126,7 @@ function PropertiesModal({
|
||||
);
|
||||
if (isFeatureEnabled(FeatureFlag.TaggingSystem)) {
|
||||
const customTags = chart.tags?.filter(
|
||||
(tag: TagType) => tag.type === 1,
|
||||
(tag: TagType) => tag.type === TagTypeEnum.Custom,
|
||||
);
|
||||
setTags(customTags);
|
||||
}
|
||||
|
||||
+27
@@ -479,3 +479,30 @@ test('should show missing dataset state', () => {
|
||||
),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show forbidden dataset state', () => {
|
||||
// @ts-ignore
|
||||
delete window.location;
|
||||
// @ts-ignore
|
||||
window.location = { search: '?slice_id=152' };
|
||||
const error = {
|
||||
error_type: 'TABLE_SECURITY_ACCESS_ERROR',
|
||||
statusText: 'FORBIDDEN',
|
||||
message: 'You do not have access to the following tables: blocked_table',
|
||||
extra: {
|
||||
datasource: 152,
|
||||
datasource_name: 'forbidden dataset',
|
||||
},
|
||||
};
|
||||
const props = createProps({
|
||||
datasource: {
|
||||
...fallbackExploreInitialData.dataset,
|
||||
extra: {
|
||||
error,
|
||||
},
|
||||
},
|
||||
});
|
||||
render(<DatasourceControl {...props} />, { useRedux: true, useRouter: true });
|
||||
expect(screen.getByText(error.message)).toBeInTheDocument();
|
||||
expect(screen.getByText(error.statusText)).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
userHasPermission,
|
||||
isUserAdmin,
|
||||
} from 'src/dashboard/util/permissionUtils';
|
||||
import { ErrorMessageWithStackTrace } from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||
import ViewQueryModalFooter from 'src/explore/components/controls/ViewQueryModalFooter';
|
||||
import ViewQuery from 'src/explore/components/controls/ViewQuery';
|
||||
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
|
||||
@@ -281,7 +282,17 @@ class DatasourceControl extends PureComponent {
|
||||
showSaveDatasetModal,
|
||||
} = this.state;
|
||||
const { datasource, onChange, theme } = this.props;
|
||||
const isMissingDatasource = !datasource?.id;
|
||||
let extra;
|
||||
if (datasource?.extra) {
|
||||
if (typeof datasource.extra === 'string') {
|
||||
try {
|
||||
extra = JSON.parse(datasource.extra);
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
} else {
|
||||
extra = datasource.extra; // eslint-disable-line prefer-destructuring
|
||||
}
|
||||
}
|
||||
const isMissingDatasource = !datasource?.id || Boolean(extra?.error);
|
||||
let isMissingParams = false;
|
||||
if (isMissingDatasource) {
|
||||
const datasourceId = getUrlParam(URL_PARAMS.datasourceId);
|
||||
@@ -386,20 +397,10 @@ class DatasourceControl extends PureComponent {
|
||||
|
||||
const { health_check_message: healthCheckMessage } = datasource;
|
||||
|
||||
let extra;
|
||||
if (datasource?.extra) {
|
||||
if (typeof datasource.extra === 'string') {
|
||||
try {
|
||||
extra = JSON.parse(datasource.extra);
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
} else {
|
||||
extra = datasource.extra; // eslint-disable-line prefer-destructuring
|
||||
}
|
||||
}
|
||||
|
||||
const titleText = isMissingDatasource
|
||||
? t('Missing dataset')
|
||||
: getDatasourceTitle(datasource);
|
||||
const titleText =
|
||||
isMissingDatasource && !datasource.name
|
||||
? t('Missing dataset')
|
||||
: getDatasourceTitle(datasource);
|
||||
|
||||
const tooltip = titleText;
|
||||
|
||||
@@ -452,31 +453,44 @@ class DatasourceControl extends PureComponent {
|
||||
)}
|
||||
{isMissingDatasource && !isMissingParams && (
|
||||
<div className="error-alert">
|
||||
<ErrorAlert
|
||||
type="warning"
|
||||
errorType={t('Missing dataset')}
|
||||
descriptionPre={false}
|
||||
descriptionDetailsCollapsed={false}
|
||||
descriptionDetails={
|
||||
<>
|
||||
<p>
|
||||
{t(
|
||||
'The dataset linked to this chart may have been deleted.',
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<Button
|
||||
buttonStyle="warning"
|
||||
onClick={() =>
|
||||
this.handleMenuItemClick({ key: CHANGE_DATASET })
|
||||
}
|
||||
>
|
||||
{t('Swap dataset')}
|
||||
</Button>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{extra?.error ? (
|
||||
<div className="error-alert">
|
||||
<ErrorMessageWithStackTrace
|
||||
title={extra.error.statusText || extra.error.message}
|
||||
subtitle={
|
||||
extra.error.statusText ? extra.error.message : undefined
|
||||
}
|
||||
error={extra.error}
|
||||
source="explore"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<ErrorAlert
|
||||
type="warning"
|
||||
errorType={t('Missing dataset')}
|
||||
descriptionPre={false}
|
||||
descriptionDetailsCollapsed={false}
|
||||
descriptionDetails={
|
||||
<>
|
||||
<p>
|
||||
{t(
|
||||
'The dataset linked to this chart may have been deleted.',
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<Button
|
||||
buttonStyle="warning"
|
||||
onClick={() =>
|
||||
this.handleMenuItemClick({ key: CHANGE_DATASET })
|
||||
}
|
||||
>
|
||||
{t('Swap dataset')}
|
||||
</Button>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{showEditDatasourceModal && (
|
||||
|
||||
+4
-1
@@ -90,7 +90,10 @@ const DndFilterSelect = (props: DndFilterSelectProps) => {
|
||||
let extra = {};
|
||||
if (datasource?.extra) {
|
||||
try {
|
||||
extra = JSON.parse(datasource.extra);
|
||||
extra =
|
||||
typeof datasource.extra === 'string'
|
||||
? JSON.parse(datasource.extra)
|
||||
: datasource.extra;
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
}
|
||||
return extra;
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
LOG_ACTIONS_CHART_DOWNLOAD_AS_CSV_PIVOTED,
|
||||
LOG_ACTIONS_CHART_DOWNLOAD_AS_XLS,
|
||||
} from 'src/logger/LogUtils';
|
||||
import exportPivotExcel from 'src/utils/downloadAsPivotExcel';
|
||||
import ViewQueryModal from '../controls/ViewQueryModal';
|
||||
import EmbedCodeContent from '../EmbedCodeContent';
|
||||
import DashboardsSubMenu from './DashboardsSubMenu';
|
||||
@@ -67,6 +68,7 @@ const MENU_KEYS = {
|
||||
DELETE_REPORT: 'delete_report',
|
||||
VIEW_QUERY: 'view_query',
|
||||
RUN_IN_SQL_LAB: 'run_in_sql_lab',
|
||||
EXPORT_TO_PIVOT_XLSX: 'export_to_pivot_xlsx',
|
||||
};
|
||||
|
||||
const VIZ_TYPES_PIVOTABLE = [VizType.PivotTable];
|
||||
@@ -248,6 +250,16 @@ export const useExploreAdditionalActionsMenu = (
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case MENU_KEYS.EXPORT_TO_PIVOT_XLSX:
|
||||
exportPivotExcel('.pvtTable', slice?.slice_name ?? t('pivoted_xlsx'));
|
||||
setIsDropdownVisible(false);
|
||||
dispatch(
|
||||
logEvent(LOG_ACTIONS_CHART_DOWNLOAD_AS_XLS, {
|
||||
chartId: slice?.slice_id,
|
||||
chartName: slice?.slice_name,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case MENU_KEYS.DOWNLOAD_AS_IMAGE:
|
||||
downloadAsImage(
|
||||
'.panel-body .chart-container',
|
||||
@@ -365,6 +377,13 @@ export const useExploreAdditionalActionsMenu = (
|
||||
>
|
||||
{t('Export to Excel')}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key={MENU_KEYS.EXPORT_TO_PIVOT_XLSX}
|
||||
icon={<Icons.FileOutlined />}
|
||||
disabled={!canDownloadCSV}
|
||||
>
|
||||
{t('Export to Pivoted Excel')}
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.SubMenu title={t('Share')} key={MENU_KEYS.SHARE_SUBMENU}>
|
||||
<Menu.Item key={MENU_KEYS.COPY_PERMALINK}>
|
||||
|
||||
@@ -159,8 +159,8 @@ export const fallbackExploreInitialData: ExplorePageInitialData = {
|
||||
verbose_map: {},
|
||||
main_dttm_col: '',
|
||||
owners: [],
|
||||
datasource_name: 'missing_datasource',
|
||||
name: 'missing_datasource',
|
||||
datasource_name: '',
|
||||
name: '',
|
||||
description: null,
|
||||
},
|
||||
slice: null,
|
||||
|
||||
@@ -69,7 +69,7 @@ export type Datasource = Dataset & {
|
||||
catalog?: string | null;
|
||||
schema?: string;
|
||||
is_sqllab_view?: boolean;
|
||||
extra?: string;
|
||||
extra?: string | object;
|
||||
};
|
||||
|
||||
export interface ExplorePageInitialData {
|
||||
|
||||
@@ -191,4 +191,32 @@ describe('ExtraOptions Component', () => {
|
||||
fireEvent.change(input, { target: { value: '1000' } });
|
||||
expect(onExtraInputChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders the collaps tab correctly and resets to default tab after closing', () => {
|
||||
const { rerender } = renderComponent();
|
||||
const sqlLabTab = screen.getByRole('tab', {
|
||||
name: /SQL Lab./i,
|
||||
});
|
||||
|
||||
expect(sqlLabTab).toHaveAttribute('aria-expanded', 'false');
|
||||
fireEvent.click(sqlLabTab);
|
||||
expect(sqlLabTab).toHaveAttribute('aria-expanded', 'true');
|
||||
const customDb = {
|
||||
...defaultDb,
|
||||
expose_in_sqllab: false,
|
||||
};
|
||||
|
||||
rerender(
|
||||
<ExtraOptions
|
||||
db={customDb as unknown as DatabaseObject}
|
||||
onInputChange={onInputChange}
|
||||
onTextChange={onTextChange}
|
||||
onEditorChange={onEditorChange}
|
||||
onExtraInputChange={onExtraInputChange}
|
||||
onExtraEditorChange={onExtraEditorChange}
|
||||
extraExtension={undefined}
|
||||
/>,
|
||||
);
|
||||
expect(sqlLabTab).toHaveAttribute('aria-expanded', 'false');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ChangeEvent, EventHandler } from 'react';
|
||||
import { ChangeEvent, EventHandler, useState, useEffect } from 'react';
|
||||
import cx from 'classnames';
|
||||
import {
|
||||
t,
|
||||
@@ -88,12 +88,21 @@ const ExtraOptions = ({
|
||||
const isAllowRunAsyncDisabled = isFeatureEnabled(
|
||||
FeatureFlag.ForceSqlLabRunAsync,
|
||||
);
|
||||
const [activeKey, setActiveKey] = useState<string[] | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!expandableModalIsOpen && activeKey !== undefined) {
|
||||
setActiveKey(undefined);
|
||||
}
|
||||
}, [expandableModalIsOpen, activeKey]);
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
expandIconPosition="end"
|
||||
accordion
|
||||
modalMode
|
||||
activeKey={activeKey}
|
||||
onChange={key => setActiveKey(key)}
|
||||
items={[
|
||||
{
|
||||
key: 'sql-lab',
|
||||
|
||||
@@ -132,7 +132,7 @@ function GroupListModal({
|
||||
value: role.id,
|
||||
label: role.name,
|
||||
}))}
|
||||
getPopupContainer={trigger => trigger.closest('.antd5-modal-content')}
|
||||
getPopupContainer={trigger => trigger.closest('.ant-modal-content')}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem name="users" label={t('Users')}>
|
||||
|
||||
@@ -187,7 +187,7 @@ const RightMenu = ({
|
||||
const {
|
||||
theme: themeEditorTheme,
|
||||
setTheme,
|
||||
changeThemeMode,
|
||||
setThemeMode,
|
||||
themeMode,
|
||||
} = useThemeContext();
|
||||
const dropdownItems: MenuObjectProps[] = [
|
||||
@@ -501,10 +501,7 @@ const RightMenu = ({
|
||||
)}
|
||||
{isFeatureEnabled(FeatureFlag.ThemeEnableDarkThemeSwitch) && (
|
||||
<span>
|
||||
<ThemeSelect
|
||||
changeThemeMode={changeThemeMode}
|
||||
themeMode={themeMode}
|
||||
/>
|
||||
<ThemeSelect setThemeMode={setThemeMode} themeMode={themeMode} />
|
||||
</span>
|
||||
)}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import { JsonObject, SupersetClient } from '@superset-ui/core';
|
||||
import rison from 'rison';
|
||||
import { TagType } from 'src/components';
|
||||
import { TagTypeEnum } from 'src/components/Tag/TagType';
|
||||
|
||||
export const OBJECT_TYPES_VALUES = Object.freeze([
|
||||
'dashboard',
|
||||
@@ -93,7 +94,11 @@ export function fetchTags(
|
||||
endpoint: `/api/v1/${objectType}/${objectId}`,
|
||||
})
|
||||
.then(({ json }) =>
|
||||
callback(json.result.tags.filter((tag: TagType) => tag.type === 1)),
|
||||
callback(
|
||||
json.result.tags.filter(
|
||||
(tag: TagType) => tag.type === TagTypeEnum.Custom,
|
||||
),
|
||||
),
|
||||
)
|
||||
.catch(response => error(response));
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ const Wrapper = styled.div`
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.antd5-input-number {
|
||||
.ant-input-number {
|
||||
min-width: 80px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -196,10 +196,10 @@ function ActionLogList() {
|
||||
}: any) => (
|
||||
<Typography.Text
|
||||
css={css`
|
||||
.antd5-typography-copy {
|
||||
.ant-typography-copy {
|
||||
visibility: hidden;
|
||||
}
|
||||
&:hover .antd5-typography-copy {
|
||||
&:hover .ant-typography-copy {
|
||||
visibility: visible;
|
||||
}
|
||||
`}
|
||||
@@ -224,10 +224,10 @@ function ActionLogList() {
|
||||
}: any) => (
|
||||
<Typography.Text
|
||||
css={css`
|
||||
.antd5-typography-copy {
|
||||
.ant-typography-copy {
|
||||
visibility: hidden;
|
||||
}
|
||||
&:hover .antd5-typography-copy {
|
||||
&:hover .ant-typography-copy {
|
||||
visibility: visible;
|
||||
}
|
||||
`}
|
||||
|
||||
@@ -31,6 +31,7 @@ import getFormDataWithExtraFilters from 'src/dashboard/util/charts/getFormDataWi
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import { JsonObject, VizType } from '@superset-ui/core';
|
||||
import { useUnsavedChangesPrompt } from 'src/hooks/useUnsavedChangesPrompt';
|
||||
import { getParsedExploreURLParams } from 'src/explore/exploreUtils/getParsedExploreURLParams';
|
||||
import ChartPage from '.';
|
||||
|
||||
jest.mock('src/hooks/useUnsavedChangesPrompt', () => ({
|
||||
@@ -49,6 +50,9 @@ jest.mock(
|
||||
),
|
||||
);
|
||||
jest.mock('src/dashboard/util/charts/getFormDataWithExtraFilters');
|
||||
jest.mock('src/explore/exploreUtils/getParsedExploreURLParams', () => ({
|
||||
getParsedExploreURLParams: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('ChartPage', () => {
|
||||
beforeEach(() => {
|
||||
@@ -89,6 +93,91 @@ describe('ChartPage', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('displays the dataset name and error when it is prohibited', async () => {
|
||||
const chartApiRoute = `glob:*/api/v1/chart/*`;
|
||||
const exploreApiRoute = 'glob:*/api/v1/explore/*';
|
||||
const expectedDatasourceName = 'failed datasource name';
|
||||
(getParsedExploreURLParams as jest.Mock).mockReturnValue(
|
||||
new Map([['datasource_id', 1]]),
|
||||
);
|
||||
fetchMock.get(exploreApiRoute, () => {
|
||||
class Extra {
|
||||
datasource = 123;
|
||||
|
||||
datasource_name = expectedDatasourceName;
|
||||
}
|
||||
class SupersetSecurityError {
|
||||
message = 'You do not have a permission to the table';
|
||||
|
||||
extra = new Extra();
|
||||
}
|
||||
throw new SupersetSecurityError();
|
||||
});
|
||||
fetchMock.get(chartApiRoute, 200);
|
||||
const { getByTestId } = render(<ChartPage />, {
|
||||
useRouter: true,
|
||||
useRedux: true,
|
||||
useDnd: true,
|
||||
});
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(getByTestId('mock-explore-chart-panel')).toHaveTextContent(
|
||||
JSON.stringify({ datasource_name: expectedDatasourceName }).slice(
|
||||
1,
|
||||
-1,
|
||||
),
|
||||
),
|
||||
{
|
||||
timeout: 5000,
|
||||
},
|
||||
);
|
||||
expect(fetchMock.calls(chartApiRoute).length).toEqual(0);
|
||||
expect(fetchMock.calls(exploreApiRoute).length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
test('fetches the chart api when explore metadata is prohibited and access from the chart link', async () => {
|
||||
const expectedChartId = 7;
|
||||
const expectedChartName = 'Unauthorized dataset owned chart name';
|
||||
(getParsedExploreURLParams as jest.Mock).mockReturnValue(
|
||||
new Map([['slice_id', expectedChartId]]),
|
||||
);
|
||||
const chartApiRoute = `glob:*/api/v1/chart/${expectedChartId}`;
|
||||
const exploreApiRoute = 'glob:*/api/v1/explore/*';
|
||||
|
||||
fetchMock.get(exploreApiRoute, () => {
|
||||
class Extra {
|
||||
datasource = 123;
|
||||
}
|
||||
class SupersetSecurityError {
|
||||
message = 'You do not have a permission to the table';
|
||||
|
||||
extra = new Extra();
|
||||
}
|
||||
throw new SupersetSecurityError();
|
||||
});
|
||||
fetchMock.get(chartApiRoute, {
|
||||
result: {
|
||||
id: expectedChartId,
|
||||
slice_name: expectedChartName,
|
||||
url: 'chartid',
|
||||
},
|
||||
});
|
||||
const { getByTestId, getByText } = render(<ChartPage />, {
|
||||
useRouter: true,
|
||||
useRedux: true,
|
||||
useDnd: true,
|
||||
});
|
||||
await waitFor(() => expect(fetchMock.calls(chartApiRoute).length).toBe(1), {
|
||||
timeout: 5000,
|
||||
});
|
||||
expect(fetchMock.calls(exploreApiRoute).length).toBeGreaterThanOrEqual(1);
|
||||
expect(getByTestId('mock-explore-chart-panel')).toBeInTheDocument();
|
||||
expect(getByTestId('mock-explore-chart-panel')).toHaveTextContent(
|
||||
JSON.stringify({ datasource: 123 }).slice(1, -1),
|
||||
);
|
||||
expect(getByText(expectedChartName)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('with dashboardContextFormData', () => {
|
||||
const dashboardPageId = 'mockPageId';
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import { ExploreResponsePayload, SaveActionType } from 'src/explore/types';
|
||||
import { fallbackExploreInitialData } from 'src/explore/fixtures';
|
||||
import { getItem, LocalStorageKeys } from 'src/utils/localStorageHelpers';
|
||||
import { getFormDataWithDashboardContext } from 'src/explore/controlUtils/getFormDataWithDashboardContext';
|
||||
import type Chart from 'src/types/Chart';
|
||||
|
||||
const isValidResult = (rv: JsonObject): boolean =>
|
||||
rv?.result?.form_data && rv?.result?.dataset;
|
||||
@@ -49,41 +50,31 @@ const hasDatasetId = (rv: JsonObject): boolean =>
|
||||
isDefined(rv?.result?.dataset?.id);
|
||||
|
||||
const fetchExploreData = async (exploreUrlParams: URLSearchParams) => {
|
||||
try {
|
||||
const rv = await makeApi<{}, ExploreResponsePayload>({
|
||||
method: 'GET',
|
||||
endpoint: 'api/v1/explore/',
|
||||
})(exploreUrlParams);
|
||||
if (isValidResult(rv)) {
|
||||
if (hasDatasetId(rv)) {
|
||||
return rv;
|
||||
}
|
||||
// Since there's no dataset id but the API responded with a valid payload,
|
||||
// we assume the dataset was deleted, so we preserve some values from previous
|
||||
// state so if the user decide to swap the datasource, the chart config remains
|
||||
fallbackExploreInitialData.form_data = {
|
||||
...rv.result.form_data,
|
||||
...fallbackExploreInitialData.form_data,
|
||||
};
|
||||
if (rv.result?.slice) {
|
||||
fallbackExploreInitialData.slice = rv.result.slice;
|
||||
}
|
||||
const rv = await makeApi<{}, ExploreResponsePayload>({
|
||||
method: 'GET',
|
||||
endpoint: 'api/v1/explore/',
|
||||
})(exploreUrlParams);
|
||||
if (isValidResult(rv)) {
|
||||
if (hasDatasetId(rv)) {
|
||||
return rv;
|
||||
}
|
||||
let message = t('Failed to load chart data');
|
||||
const responseError = rv?.result?.message;
|
||||
if (responseError) {
|
||||
message = `${message}:\n${responseError}`;
|
||||
// Since there's no dataset id but the API responded with a valid payload,
|
||||
// we assume the dataset was deleted, so we preserve some values from previous
|
||||
// state so if the user decide to swap the datasource, the chart config remains
|
||||
fallbackExploreInitialData.form_data = {
|
||||
...rv.result.form_data,
|
||||
...fallbackExploreInitialData.form_data,
|
||||
};
|
||||
if (rv.result?.slice) {
|
||||
fallbackExploreInitialData.slice = rv.result.slice;
|
||||
}
|
||||
throw new Error(message);
|
||||
} catch (err) {
|
||||
// todo: encapsulate the error handler
|
||||
const clientError = await getClientErrorObject(err);
|
||||
throw new Error(
|
||||
clientError.message ||
|
||||
clientError.error ||
|
||||
t('Failed to load chart data.'),
|
||||
);
|
||||
}
|
||||
let message = t('Failed to load chart data');
|
||||
const responseError = rv?.result?.message;
|
||||
if (responseError) {
|
||||
message = `${message}:\n${responseError}`;
|
||||
}
|
||||
throw new Error(message);
|
||||
};
|
||||
|
||||
const getDashboardPageContext = (pageId?: string | null) => {
|
||||
@@ -166,9 +157,62 @@ export default function ExplorePage() {
|
||||
}),
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch(err => Promise.all([getClientErrorObject(err), err]))
|
||||
.then(resolved => {
|
||||
const [clientError, err] = resolved || [];
|
||||
if (!err) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const errorMesage =
|
||||
clientError?.message ||
|
||||
clientError?.error ||
|
||||
t('Failed to load chart data.');
|
||||
dispatch(addDangerToast(errorMesage));
|
||||
|
||||
if (err.extra?.datasource) {
|
||||
const exploreData = {
|
||||
...fallbackExploreInitialData,
|
||||
dataset: {
|
||||
...fallbackExploreInitialData.dataset,
|
||||
id: err.extra?.datasource,
|
||||
name: err.extra?.datasource_name,
|
||||
extra: {
|
||||
error: err,
|
||||
},
|
||||
},
|
||||
};
|
||||
const chartId = exploreUrlParams.get('slice_id');
|
||||
return (
|
||||
chartId
|
||||
? makeApi<void, { result: Chart }>({
|
||||
method: 'GET',
|
||||
endpoint: `api/v1/chart/${chartId}`,
|
||||
})()
|
||||
: Promise.reject()
|
||||
)
|
||||
.then(
|
||||
({ result: { id, url, owners, form_data: _, ...data } }) => {
|
||||
const slice = {
|
||||
...data,
|
||||
datasource: err.extra?.datasource_name,
|
||||
slice_id: id,
|
||||
slice_url: url,
|
||||
owners: owners?.map(({ id }) => id),
|
||||
};
|
||||
dispatch(
|
||||
hydrateExplore({
|
||||
...exploreData,
|
||||
slice,
|
||||
}),
|
||||
);
|
||||
},
|
||||
)
|
||||
.catch(() => {
|
||||
dispatch(hydrateExplore(exploreData));
|
||||
});
|
||||
}
|
||||
dispatch(hydrateExplore(fallbackExploreInitialData));
|
||||
dispatch(addDangerToast(err.message));
|
||||
return Promise.resolve();
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoaded(true);
|
||||
|
||||
@@ -71,6 +71,7 @@ import PropertiesModal from 'src/explore/components/PropertiesModal';
|
||||
import Chart from 'src/types/Chart';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import { nativeFilterGate } from 'src/dashboard/components/nativeFilters/utils';
|
||||
import { TagTypeEnum } from 'src/components/Tag/TagType';
|
||||
import { loadTags } from 'src/components/Tag/utils';
|
||||
import ChartCard from 'src/features/charts/ChartCard';
|
||||
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
|
||||
@@ -417,7 +418,8 @@ function ChartList(props: ChartListProps) {
|
||||
<TagsList
|
||||
tags={tags.filter((tag: TagType) =>
|
||||
tag.type
|
||||
? tag.type === 1 || tag.type === 'TagTypes.custom'
|
||||
? tag.type === TagTypeEnum.Custom ||
|
||||
tag.type === 'TagTypes.custom'
|
||||
: true,
|
||||
)}
|
||||
maxTags={3}
|
||||
|
||||
@@ -67,6 +67,7 @@ import {
|
||||
Dashboard as CRUDDashboard,
|
||||
QueryObjectColumns,
|
||||
} from 'src/views/CRUD/types';
|
||||
import { TagTypeEnum } from 'src/components/Tag/TagType';
|
||||
import { loadTags } from 'src/components/Tag/utils';
|
||||
import DashboardCard from 'src/features/dashboards/DashboardCard';
|
||||
import { DashboardStatus } from 'src/features/dashboards/types';
|
||||
@@ -376,7 +377,8 @@ function DashboardList(props: DashboardListProps) {
|
||||
<TagsList
|
||||
tags={tags.filter(
|
||||
(tag: TagType) =>
|
||||
tag.type === 'TagTypes.custom' || tag.type === 1,
|
||||
tag.type === 'TagTypes.custom' ||
|
||||
tag.type === TagTypeEnum.Custom,
|
||||
)}
|
||||
maxTags={3}
|
||||
/>
|
||||
|
||||
@@ -62,7 +62,7 @@ const StyledCard = styled(Card)`
|
||||
margin-top: ${theme.marginXL}px;
|
||||
color: ${theme.colorBgContainer};
|
||||
background: ${theme.colorBgBase};
|
||||
.antd5-form-item-label label {
|
||||
.ant-form-item-label label {
|
||||
color: ${theme.colorPrimary};
|
||||
}
|
||||
`}
|
||||
|
||||
@@ -45,7 +45,7 @@ const StyledCard = styled(Card)`
|
||||
width: 50%;
|
||||
margin-top: ${theme.marginXL}px;
|
||||
background: ${theme.colorBgBase};
|
||||
.antd5-form-item-label label {
|
||||
.ant-form-item-label label {
|
||||
color: ${theme.colorPrimary};
|
||||
}
|
||||
`}
|
||||
|
||||
@@ -58,6 +58,7 @@ import handleResourceExport from 'src/utils/export';
|
||||
import SubMenu, { ButtonProps, SubMenuProps } from 'src/features/home/SubMenu';
|
||||
import { commonMenuData } from 'src/features/home/commonMenuData';
|
||||
import { QueryObjectColumns, SavedQueryObject } from 'src/views/CRUD/types';
|
||||
import { TagTypeEnum } from 'src/components/Tag/TagType';
|
||||
import { loadTags } from 'src/components/Tag/utils';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
|
||||
@@ -393,7 +394,11 @@ function SavedQueryList({
|
||||
},
|
||||
}: any) => (
|
||||
// Only show custom type tags
|
||||
<TagsList tags={tags.filter((tag: TagType) => tag.type === 1)} />
|
||||
<TagsList
|
||||
tags={tags.filter(
|
||||
(tag: TagType) => tag.type === TagTypeEnum.Custom,
|
||||
)}
|
||||
/>
|
||||
),
|
||||
Header: t('Tags'),
|
||||
accessor: 'tags',
|
||||
|
||||
@@ -54,11 +54,11 @@ const DescriptionsContainer = styled.div`
|
||||
|
||||
const StyledLayout = styled.div`
|
||||
${({ theme }) => css`
|
||||
.antd5-row {
|
||||
.ant-row {
|
||||
margin: 0px ${theme.sizeUnit * 3}px ${theme.sizeUnit * 6}px
|
||||
${theme.sizeUnit * 3}px;
|
||||
}
|
||||
&& .menu > .antd5-menu {
|
||||
&& .menu > .ant-menu {
|
||||
padding: 0px;
|
||||
}
|
||||
&& .nav-right {
|
||||
|
||||
@@ -19,7 +19,13 @@
|
||||
import { setConfig as setHotLoaderConfig } from 'react-hot-loader';
|
||||
import dayjs from 'dayjs';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { configure, makeApi, initFeatureFlags } from '@superset-ui/core';
|
||||
import {
|
||||
configure,
|
||||
makeApi,
|
||||
initFeatureFlags,
|
||||
SupersetClient,
|
||||
LanguagePack,
|
||||
} from '@superset-ui/core';
|
||||
import setupClient from './setup/setupClient';
|
||||
import setupColors from './setup/setupColors';
|
||||
import setupFormatters from './setup/setupFormatters';
|
||||
@@ -27,57 +33,67 @@ import setupDashboardComponents from './setup/setupDashboardComponents';
|
||||
import { User } from './types/bootstrapTypes';
|
||||
import getBootstrapData, { applicationRoot } from './utils/getBootstrapData';
|
||||
|
||||
configure();
|
||||
|
||||
// Set hot reloader config
|
||||
if (process.env.WEBPACK_MODE === 'development') {
|
||||
setHotLoaderConfig({ logLevel: 'debug', trackTailUpdates: false });
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-mutable-exports
|
||||
// Grab initial bootstrap data
|
||||
const bootstrapData = getBootstrapData();
|
||||
|
||||
// Configure translation
|
||||
if (typeof window !== 'undefined') {
|
||||
configure({ languagePack: bootstrapData.common.language_pack });
|
||||
dayjs.locale(bootstrapData.common.locale);
|
||||
} else {
|
||||
configure();
|
||||
}
|
||||
|
||||
// Configure feature flags
|
||||
initFeatureFlags(bootstrapData.common.feature_flags);
|
||||
|
||||
// Setup SupersetClient
|
||||
// Setup SupersetClient early so we can fetch language pack
|
||||
setupClient({ appRoot: applicationRoot() });
|
||||
|
||||
setupColors(
|
||||
bootstrapData.common.extra_categorical_color_schemes,
|
||||
bootstrapData.common.extra_sequential_color_schemes,
|
||||
);
|
||||
// Load language pack before anything else
|
||||
(async () => {
|
||||
const lang = bootstrapData.common.locale || 'en';
|
||||
if (lang !== 'en') {
|
||||
try {
|
||||
// Second call to configure to set the language pack
|
||||
const { json } = await SupersetClient.get({
|
||||
endpoint: `/superset/language_pack/${lang}/`,
|
||||
});
|
||||
configure({ languagePack: json as LanguagePack });
|
||||
dayjs.locale(lang);
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
'Failed to fetch language pack, falling back to default.',
|
||||
err,
|
||||
);
|
||||
configure();
|
||||
dayjs.locale('en');
|
||||
}
|
||||
}
|
||||
|
||||
// Setup number formatters
|
||||
setupFormatters(
|
||||
bootstrapData.common.d3_format,
|
||||
bootstrapData.common.d3_time_format,
|
||||
);
|
||||
// Continue with rest of setup
|
||||
initFeatureFlags(bootstrapData.common.feature_flags);
|
||||
|
||||
setupDashboardComponents();
|
||||
setupColors(
|
||||
bootstrapData.common.extra_categorical_color_schemes,
|
||||
bootstrapData.common.extra_sequential_color_schemes,
|
||||
);
|
||||
|
||||
const getMe = makeApi<void, User>({
|
||||
method: 'GET',
|
||||
endpoint: '/api/v1/me/',
|
||||
});
|
||||
setupFormatters(
|
||||
bootstrapData.common.d3_format,
|
||||
bootstrapData.common.d3_time_format,
|
||||
);
|
||||
|
||||
/**
|
||||
* When you re-open the window, we check if you are still logged in.
|
||||
* If your session expired or you signed out, we'll redirect to login.
|
||||
* If you aren't logged in in the first place (!isActive), then we shouldn't do this.
|
||||
*/
|
||||
if (bootstrapData.user?.isActive) {
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
// we only care about the tab becoming visible, not vice versa
|
||||
if (document.visibilityState !== 'visible') return;
|
||||
setupDashboardComponents();
|
||||
|
||||
getMe().catch(() => {
|
||||
// ignore error, SupersetClient will redirect to login on a 401
|
||||
});
|
||||
const getMe = makeApi<void, User>({
|
||||
method: 'GET',
|
||||
endpoint: '/api/v1/me/',
|
||||
});
|
||||
}
|
||||
|
||||
if (bootstrapData.user?.isActive) {
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
getMe().catch(() => {
|
||||
// SupersetClient will redirect to login on 401
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,596 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
Theme,
|
||||
AnyThemeConfig,
|
||||
ThemeStorage,
|
||||
ThemeControllerOptions,
|
||||
themeObject as supersetThemeObject,
|
||||
} from '@superset-ui/core';
|
||||
import { SupersetTheme, ThemeMode } from '@superset-ui/core/theme/types';
|
||||
import {
|
||||
getAntdConfig,
|
||||
normalizeThemeConfig,
|
||||
} from '@superset-ui/core/theme/utils';
|
||||
import type {
|
||||
BootstrapThemeData,
|
||||
BootstrapThemeDataConfig,
|
||||
SerializableThemeSettings,
|
||||
} from 'src/types/bootstrapTypes';
|
||||
import getBootstrapData from 'src/utils/getBootstrapData';
|
||||
|
||||
const DEFAULT_THEME_SETTINGS = {
|
||||
enforced: false,
|
||||
allowSwitching: true,
|
||||
allowOSPreference: true,
|
||||
} as const;
|
||||
|
||||
const STORAGE_KEYS = {
|
||||
THEME_MODE: 'superset-theme-mode',
|
||||
} as const;
|
||||
|
||||
const MEDIA_QUERY_DARK_SCHEME = '(prefers-color-scheme: dark)';
|
||||
|
||||
export class LocalStorageAdapter implements ThemeStorage {
|
||||
getItem(key: string): string | null {
|
||||
try {
|
||||
return localStorage.getItem(key);
|
||||
} catch (error) {
|
||||
console.warn('Failed to read from localStorage:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
setItem(key: string, value: string): void {
|
||||
try {
|
||||
localStorage.setItem(key, value);
|
||||
} catch (error) {
|
||||
console.warn('Failed to write to localStorage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
removeItem(key: string): void {
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
} catch (error) {
|
||||
console.warn('Failed to remove from localStorage:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ThemeController {
|
||||
private themeObject: Theme;
|
||||
|
||||
private storage: ThemeStorage;
|
||||
|
||||
private modeStorageKey: string;
|
||||
|
||||
private defaultTheme: AnyThemeConfig;
|
||||
|
||||
private darkTheme: AnyThemeConfig | null;
|
||||
|
||||
private themeSettings: SerializableThemeSettings;
|
||||
|
||||
private systemMode: ThemeMode.DARK | ThemeMode.DEFAULT;
|
||||
|
||||
private currentMode: ThemeMode;
|
||||
|
||||
private readonly hasBootstrapThemes: boolean;
|
||||
|
||||
private onChangeCallbacks: Set<(theme: Theme) => void> = new Set();
|
||||
|
||||
private mediaQuery: MediaQueryList;
|
||||
|
||||
constructor(options: ThemeControllerOptions = {}) {
|
||||
const {
|
||||
storage = new LocalStorageAdapter(),
|
||||
modeStorageKey = STORAGE_KEYS.THEME_MODE,
|
||||
themeObject = supersetThemeObject,
|
||||
defaultTheme = (supersetThemeObject.theme as AnyThemeConfig) ?? {},
|
||||
onChange = null,
|
||||
} = options;
|
||||
|
||||
this.storage = storage;
|
||||
this.modeStorageKey = modeStorageKey;
|
||||
this.themeObject = themeObject;
|
||||
|
||||
// Initialize bootstrap data and themes
|
||||
const {
|
||||
bootstrapDefaultTheme,
|
||||
bootstrapDarkTheme,
|
||||
bootstrapThemeSettings,
|
||||
hasBootstrapThemes,
|
||||
}: BootstrapThemeData = this.loadBootstrapData();
|
||||
|
||||
this.hasBootstrapThemes = hasBootstrapThemes;
|
||||
this.themeSettings = bootstrapThemeSettings || {};
|
||||
|
||||
// Set themes based on bootstrap data availability
|
||||
if (this.hasBootstrapThemes) {
|
||||
this.darkTheme = bootstrapDarkTheme || bootstrapDefaultTheme || null;
|
||||
this.defaultTheme =
|
||||
bootstrapDefaultTheme || bootstrapDarkTheme || defaultTheme;
|
||||
} else {
|
||||
this.darkTheme = null;
|
||||
this.defaultTheme = defaultTheme;
|
||||
}
|
||||
|
||||
// Initialize system theme detection
|
||||
this.systemMode = ThemeController.getSystemPreferredMode();
|
||||
|
||||
// Only initialize media query listener if OS preference is allowed
|
||||
if (this.shouldInitializeMediaQueryListener())
|
||||
this.initializeMediaQueryListener();
|
||||
|
||||
// Initialize theme and mode
|
||||
this.currentMode = this.determineInitialMode();
|
||||
const initialTheme =
|
||||
this.getThemeForMode(this.currentMode) || this.defaultTheme;
|
||||
|
||||
// Setup change callback
|
||||
if (onChange) this.onChangeCallbacks.add(onChange);
|
||||
|
||||
// Apply initial theme and persist mode
|
||||
this.applyTheme(initialTheme);
|
||||
this.persistMode();
|
||||
}
|
||||
|
||||
// Public Methods
|
||||
|
||||
/**
|
||||
* Cleans up listeners and references. Should be called when the controller is no longer needed.
|
||||
*/
|
||||
public destroy(): void {
|
||||
if (this.mediaQuery)
|
||||
this.mediaQuery.removeEventListener(
|
||||
'change',
|
||||
this.handleSystemThemeChange,
|
||||
);
|
||||
|
||||
this.onChangeCallbacks.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user can update the theme.
|
||||
*/
|
||||
public canSetTheme(): boolean {
|
||||
return !this.themeSettings?.enforced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user can update the theme mode.
|
||||
*/
|
||||
public canSetMode(): boolean {
|
||||
return this.isModeUpdatable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current theme object.
|
||||
*/
|
||||
public getTheme(): Theme {
|
||||
return this.themeObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current theme mode.
|
||||
*/
|
||||
public getCurrentMode(): ThemeMode {
|
||||
return this.currentMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets new theme.
|
||||
* @param theme - The new theme to apply
|
||||
* @throws {Error} If the user does not have permission to update the theme
|
||||
*/
|
||||
public setTheme(theme: AnyThemeConfig): void {
|
||||
this.validateThemeUpdatePermission();
|
||||
|
||||
const normalizedTheme = this.normalizeTheme(theme);
|
||||
this.currentMode = this.determineInitialMode();
|
||||
|
||||
this.updateTheme(normalizedTheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the theme mode (light, dark, or system).
|
||||
* @param mode - The new theme mode to apply
|
||||
* @throws {Error} If the user does not have permission to update the theme mode
|
||||
*/
|
||||
public setThemeMode(mode: ThemeMode): void {
|
||||
this.validateModeUpdatePermission(mode);
|
||||
|
||||
if (this.currentMode === mode) return;
|
||||
|
||||
const theme: AnyThemeConfig | null = this.getThemeForMode(mode);
|
||||
if (!theme) {
|
||||
console.warn(`Theme for mode ${mode} not found, falling back to default`);
|
||||
this.fallbackToDefaultMode();
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentMode = mode;
|
||||
this.updateTheme(theme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the theme to the default theme.
|
||||
*/
|
||||
public resetTheme(): void {
|
||||
this.currentMode = ThemeMode.DEFAULT;
|
||||
const defaultTheme: AnyThemeConfig =
|
||||
this.getThemeForMode(ThemeMode.DEFAULT) || this.defaultTheme;
|
||||
|
||||
this.updateTheme(defaultTheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles system theme changes with error recovery.
|
||||
*/
|
||||
private handleSystemThemeChange = (): void => {
|
||||
try {
|
||||
const newSystemMode: ThemeMode.DEFAULT | ThemeMode.DARK =
|
||||
ThemeController.getSystemPreferredMode();
|
||||
|
||||
// Update systemMode regardless of current mode
|
||||
const oldSystemMode: ThemeMode.DEFAULT | ThemeMode.DARK = this.systemMode;
|
||||
this.systemMode = newSystemMode;
|
||||
|
||||
// Only update theme if currently in SYSTEM mode and the preference changed
|
||||
if (
|
||||
this.currentMode === ThemeMode.SYSTEM &&
|
||||
oldSystemMode !== newSystemMode
|
||||
) {
|
||||
const newTheme: AnyThemeConfig | null = this.getThemeForMode(
|
||||
ThemeMode.SYSTEM,
|
||||
);
|
||||
|
||||
if (newTheme) this.updateTheme(newTheme);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to handle system theme change:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the theme.
|
||||
* @param theme - The new theme to apply
|
||||
*/
|
||||
private updateTheme(theme?: AnyThemeConfig): void {
|
||||
try {
|
||||
// If no config provided, use current mode to get theme
|
||||
const config: AnyThemeConfig =
|
||||
theme || this.getThemeForMode(this.currentMode) || this.defaultTheme;
|
||||
|
||||
// Normalize the theme
|
||||
const normalizedTheme = this.normalizeTheme(config);
|
||||
|
||||
this.applyTheme(normalizedTheme);
|
||||
this.persistMode();
|
||||
this.notifyListeners();
|
||||
} catch (error) {
|
||||
console.error('Failed to update theme:', error);
|
||||
this.fallbackToDefaultMode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback to default mode with error recovery.
|
||||
*/
|
||||
private fallbackToDefaultMode(): void {
|
||||
this.currentMode = ThemeMode.DEFAULT;
|
||||
|
||||
// Get the default theme which will have the correct algorithm
|
||||
const defaultTheme: AnyThemeConfig =
|
||||
this.getThemeForMode(ThemeMode.DEFAULT) || this.defaultTheme;
|
||||
|
||||
this.applyTheme(defaultTheme);
|
||||
this.persistMode();
|
||||
this.notifyListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to be called when the theme changes.
|
||||
* @param callback - The callback to be called on theme change
|
||||
* @returns A function to unsubscribe from the theme change events
|
||||
*/
|
||||
public onChange(callback: (theme: Theme) => void): () => void {
|
||||
this.onChangeCallbacks.add(callback);
|
||||
return () => this.onChangeCallbacks.delete(callback);
|
||||
}
|
||||
|
||||
// Private Helper Methods
|
||||
|
||||
/**
|
||||
* Determines whether the MediaQueryList listener for system theme changes should be initialized.
|
||||
* This checks if OS preference detection is enabled in the theme settings.
|
||||
* @returns {boolean} True if the media query listener should be initialized, false otherwise
|
||||
*/
|
||||
private shouldInitializeMediaQueryListener(): boolean {
|
||||
const { allowOSPreference = DEFAULT_THEME_SETTINGS.allowOSPreference } =
|
||||
this.themeSettings || {};
|
||||
|
||||
return allowOSPreference === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes media query listeners if OS preference is allowed
|
||||
*/
|
||||
private initializeMediaQueryListener(): void {
|
||||
try {
|
||||
this.mediaQuery = window.matchMedia(MEDIA_QUERY_DARK_SCHEME);
|
||||
this.mediaQuery.addEventListener('change', this.handleSystemThemeChange);
|
||||
} catch (error) {
|
||||
console.warn('Failed to initialize media query listener:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and validates bootstrap theme data.
|
||||
*/
|
||||
private loadBootstrapData(): BootstrapThemeData {
|
||||
const {
|
||||
common: { theme = {} as BootstrapThemeDataConfig },
|
||||
} = getBootstrapData();
|
||||
|
||||
const {
|
||||
default: defaultTheme,
|
||||
dark: darkTheme,
|
||||
settings: themeSettings,
|
||||
} = theme;
|
||||
|
||||
const hasValidDefault: boolean = this.isNonEmptyObject(defaultTheme);
|
||||
const hasValidDark: boolean = this.isNonEmptyObject(darkTheme);
|
||||
const hasValidSettings: boolean = this.isNonEmptyObject(themeSettings);
|
||||
|
||||
return {
|
||||
bootstrapDefaultTheme: hasValidDefault ? defaultTheme : null,
|
||||
bootstrapDarkTheme: hasValidDark ? darkTheme : null,
|
||||
bootstrapThemeSettings: hasValidSettings ? themeSettings : null,
|
||||
hasBootstrapThemes: hasValidDefault || hasValidDark,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an object is non-empty (has at least one property).
|
||||
*/
|
||||
private isNonEmptyObject(
|
||||
obj: Record<string, any> | undefined | null,
|
||||
): boolean {
|
||||
return Boolean(
|
||||
obj && typeof obj === 'object' && Object.keys(obj).length > 0,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if mode updates are allowed.
|
||||
*/
|
||||
private isModeUpdatable(): boolean {
|
||||
const {
|
||||
enforced = DEFAULT_THEME_SETTINGS.enforced,
|
||||
allowSwitching = DEFAULT_THEME_SETTINGS.allowSwitching,
|
||||
} = this.themeSettings || {};
|
||||
|
||||
return !enforced && allowSwitching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the theme configuration to ensure it has a valid algorithm.
|
||||
* @param theme - The theme configuration to normalize
|
||||
* @returns An object with normalized mode and algorithm.
|
||||
*/
|
||||
private normalizeTheme(theme: AnyThemeConfig): AnyThemeConfig {
|
||||
const normalizedTheme = normalizeThemeConfig(theme);
|
||||
return normalizedTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate theme configuration for a given mode.
|
||||
* @param mode - The theme mode to get the configuration for
|
||||
* @returns The theme configuration for the specified mode or null if not available
|
||||
*/
|
||||
private getThemeForMode(mode: ThemeMode): AnyThemeConfig | null {
|
||||
const { allowOSPreference = DEFAULT_THEME_SETTINGS.allowOSPreference } =
|
||||
this.themeSettings;
|
||||
|
||||
let resolvedMode: ThemeMode = mode;
|
||||
|
||||
if (mode === ThemeMode.SYSTEM) {
|
||||
if (!allowOSPreference) return null;
|
||||
resolvedMode = ThemeController.getSystemPreferredMode();
|
||||
}
|
||||
|
||||
if (!this.hasBootstrapThemes) {
|
||||
const baseTheme = this.defaultTheme.token as Partial<SupersetTheme>;
|
||||
return getAntdConfig(baseTheme, resolvedMode === ThemeMode.DARK);
|
||||
}
|
||||
|
||||
// Handle bootstrap themes using existing normalization
|
||||
const selectedTheme: AnyThemeConfig =
|
||||
resolvedMode === ThemeMode.DARK
|
||||
? this.darkTheme || this.defaultTheme
|
||||
: this.defaultTheme;
|
||||
|
||||
return selectedTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the initial theme mode with error recovery.
|
||||
*/
|
||||
private determineInitialMode(): ThemeMode {
|
||||
const {
|
||||
enforced = DEFAULT_THEME_SETTINGS.enforced,
|
||||
allowOSPreference = DEFAULT_THEME_SETTINGS.allowOSPreference,
|
||||
allowSwitching = DEFAULT_THEME_SETTINGS.allowSwitching,
|
||||
} = this.themeSettings;
|
||||
|
||||
// Enforced mode always takes precedence
|
||||
if (enforced) {
|
||||
this.storage.removeItem(this.modeStorageKey);
|
||||
return ThemeMode.DEFAULT;
|
||||
}
|
||||
|
||||
// When OS preference is allowed but switching is not
|
||||
// This means the user MUST follow OS preference and cannot override it
|
||||
if (allowOSPreference && !allowSwitching) {
|
||||
// Clear any saved preference since switching is not allowed
|
||||
this.storage.removeItem(this.modeStorageKey);
|
||||
return ThemeMode.SYSTEM;
|
||||
}
|
||||
|
||||
// Try to restore saved mode
|
||||
const savedMode: ThemeMode | null = this.loadSavedMode();
|
||||
if (savedMode && this.isValidThemeMode(savedMode)) return savedMode;
|
||||
|
||||
// Fallback to system preference if allowed and available
|
||||
if (allowOSPreference && this.getThemeForMode(this.systemMode))
|
||||
return ThemeMode.SYSTEM;
|
||||
|
||||
return ThemeMode.DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely loads saved theme mode from storage.
|
||||
*/
|
||||
private loadSavedMode(): ThemeMode | null {
|
||||
try {
|
||||
const stored: string | null = this.storage.getItem(this.modeStorageKey);
|
||||
if (stored && Object.values(ThemeMode).includes(stored as ThemeMode))
|
||||
return stored as ThemeMode;
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.warn('Failed to load saved theme mode:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a theme mode is valid and supported.
|
||||
* This checks if the mode is one of the known ThemeMode values.
|
||||
* @param mode - The theme mode to validate
|
||||
* @returns {boolean} True if the mode is valid, false otherwise
|
||||
*/
|
||||
private isValidThemeMode(mode: ThemeMode): boolean {
|
||||
if (!Object.values(ThemeMode).includes(mode)) return false;
|
||||
|
||||
// Validate that we have the required theme data for the mode
|
||||
switch (mode) {
|
||||
case ThemeMode.DARK:
|
||||
return !!(this.darkTheme || this.defaultTheme);
|
||||
case ThemeMode.DEFAULT:
|
||||
return !!this.defaultTheme;
|
||||
case ThemeMode.SYSTEM:
|
||||
return this.themeSettings?.allowOSPreference !== false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates permission to update theme.
|
||||
*/
|
||||
private validateThemeUpdatePermission(): void {
|
||||
if (!this.canSetTheme())
|
||||
throw new Error('User does not have permission to update the theme');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates permission to update mode.
|
||||
* @param newMode - The new mode to validate
|
||||
* @throws {Error} If the user does not have permission to update the theme mode
|
||||
* @throws {Error} If the new mode is SYSTEM and OS preference is not allowed
|
||||
*/
|
||||
private validateModeUpdatePermission(newMode: ThemeMode): void {
|
||||
const {
|
||||
allowOSPreference = DEFAULT_THEME_SETTINGS.allowOSPreference,
|
||||
allowSwitching = DEFAULT_THEME_SETTINGS.allowSwitching,
|
||||
} = this.themeSettings;
|
||||
|
||||
// If OS preference is allowed but switching is not,
|
||||
// don't allow any mode changes
|
||||
if (allowOSPreference && !allowSwitching)
|
||||
throw new Error(
|
||||
'Theme mode changes are not allowed when OS preference is enforced',
|
||||
);
|
||||
|
||||
// Check if user can set a new theme mode
|
||||
if (!this.canSetMode())
|
||||
throw new Error('User does not have permission to update the theme mode');
|
||||
|
||||
// Check if user has permissions to set OS preference as a theme mode
|
||||
if (newMode === ThemeMode.SYSTEM && !allowOSPreference)
|
||||
throw new Error('System theme mode is not allowed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the current theme configuration.
|
||||
* This method sets the theme on the themeObject and applies it to Theme.
|
||||
* It also handles any errors that may occur during the application of the theme.
|
||||
* @param theme - The theme configuration to apply
|
||||
*/
|
||||
private applyTheme(theme: AnyThemeConfig): void {
|
||||
try {
|
||||
const normalizedConfig = normalizeThemeConfig(theme);
|
||||
this.themeObject.setConfig(normalizedConfig);
|
||||
} catch (error) {
|
||||
console.error('Failed to apply theme:', error);
|
||||
this.fallbackToDefaultMode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists the current theme mode to storage.
|
||||
*/
|
||||
private persistMode(): void {
|
||||
try {
|
||||
this.storage.setItem(this.modeStorageKey, this.currentMode);
|
||||
} catch (error) {
|
||||
console.warn('Failed to persist theme mode:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all registered listeners about theme changes.
|
||||
*/
|
||||
private notifyListeners(): void {
|
||||
this.onChangeCallbacks.forEach(callback => {
|
||||
try {
|
||||
callback(this.themeObject);
|
||||
} catch (error) {
|
||||
console.error('Error in theme change callback:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the system's preferred theme mode.
|
||||
* @returns {ThemeMode.DARK | ThemeMode.DEFAULT} The system's preferred theme mode
|
||||
*/
|
||||
static getSystemPreferredMode(): ThemeMode.DARK | ThemeMode.DEFAULT {
|
||||
try {
|
||||
return window.matchMedia(MEDIA_QUERY_DARK_SCHEME).matches
|
||||
? ThemeMode.DARK
|
||||
: ThemeMode.DEFAULT;
|
||||
} catch (error) {
|
||||
console.warn('Failed to detect system theme preference:', error);
|
||||
return ThemeMode.DEFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Theme,
|
||||
AnyThemeConfig,
|
||||
ThemeStorage,
|
||||
ThemeControllerOptions,
|
||||
themeObject,
|
||||
} from '@superset-ui/core';
|
||||
import { ThemeMode } from '@superset-ui/core/theme/types';
|
||||
|
||||
export class LocalStorageAdapter implements ThemeStorage {
|
||||
getItem(key: string): string | null {
|
||||
return localStorage.getItem(key);
|
||||
}
|
||||
|
||||
setItem(key: string, value: string): void {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
|
||||
removeItem(key: string): void {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
export class ThemeController {
|
||||
private themeObject: Theme;
|
||||
|
||||
private storage: ThemeStorage;
|
||||
|
||||
private storageKey: string;
|
||||
|
||||
private modeStorageKey: string;
|
||||
|
||||
private defaultTheme: AnyThemeConfig;
|
||||
|
||||
private systemMode: ThemeMode.DARK | ThemeMode.LIGHT;
|
||||
|
||||
private currentMode: ThemeMode;
|
||||
|
||||
private customizations: AnyThemeConfig = {};
|
||||
|
||||
private onChangeCallbacks: Set<(theme: Theme) => void> = new Set();
|
||||
|
||||
private canUpdateThemeFn: () => boolean;
|
||||
|
||||
private canUpdateModeFn: () => boolean;
|
||||
|
||||
private mediaQuery: MediaQueryList;
|
||||
|
||||
constructor(options: ThemeControllerOptions = { themeObject }) {
|
||||
this.storage = options.storage || new LocalStorageAdapter();
|
||||
this.storageKey = options.storageKey || 'superset-theme';
|
||||
this.modeStorageKey = options.modeStorageKey || `${this.storageKey}-mode`;
|
||||
this.defaultTheme = options.defaultTheme || {};
|
||||
this.themeObject = options.themeObject;
|
||||
|
||||
// Load customizations from storage
|
||||
const savedThemeJson = this.storage.getItem(this.storageKey);
|
||||
if (savedThemeJson) {
|
||||
try {
|
||||
this.customizations = JSON.parse(savedThemeJson);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse saved theme:', e);
|
||||
this.storage.removeItem(this.storageKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine initial mode
|
||||
this.systemMode = ThemeController.getSystemMode();
|
||||
const savedMode = this.storage.getItem(this.modeStorageKey) as ThemeMode;
|
||||
this.currentMode = savedMode || ThemeMode.SYSTEM;
|
||||
|
||||
// Apply the initial theme and mode
|
||||
this.applyTheme();
|
||||
|
||||
if (options.onChange) {
|
||||
this.onChangeCallbacks.add(options.onChange);
|
||||
}
|
||||
this.canUpdateThemeFn = options.canUpdateTheme || (() => true);
|
||||
this.canUpdateModeFn = options.canUpdateMode || (() => true);
|
||||
|
||||
// Listen for system theme changes to enable dynamic `SYSTEM` mode
|
||||
this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
this.mediaQuery.addEventListener('change', this.handleSystemThemeChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up listeners. Should be called when the controller is no longer needed.
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.mediaQuery.removeEventListener('change', this.handleSystemThemeChange);
|
||||
}
|
||||
|
||||
public canUpdateTheme(): boolean {
|
||||
return this.canUpdateThemeFn();
|
||||
}
|
||||
|
||||
public canUpdateMode(): boolean {
|
||||
return this.canUpdateModeFn();
|
||||
}
|
||||
|
||||
public getTheme(): Theme {
|
||||
return this.themeObject;
|
||||
}
|
||||
|
||||
public getCurrentMode(): ThemeMode {
|
||||
return this.currentMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets new theme customizations (e.g., from a JSON editor).
|
||||
* This method updates the theme's appearance but preserves the current mode.
|
||||
*/
|
||||
public setTheme(newCustomizations: AnyThemeConfig): void {
|
||||
if (!this.canUpdateTheme()) {
|
||||
throw new Error('User does not have permission to update the theme');
|
||||
}
|
||||
this.customizations = newCustomizations;
|
||||
|
||||
if (!newCustomizations.algorithm) {
|
||||
this.currentMode = ThemeMode.LIGHT;
|
||||
}
|
||||
|
||||
if (newCustomizations?.algorithm) {
|
||||
this.currentMode = newCustomizations.algorithm as ThemeMode;
|
||||
}
|
||||
|
||||
this.applyTheme();
|
||||
this.persist();
|
||||
this.notifyListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the theme mode (light, dark, or system).
|
||||
* This is for the mode switch.
|
||||
*/
|
||||
public changeThemeMode(newMode: ThemeMode): void {
|
||||
if (!this.canUpdateMode()) {
|
||||
throw new Error('User does not have permission to update the theme mode');
|
||||
}
|
||||
if (this.currentMode === newMode) return;
|
||||
|
||||
this.currentMode = newMode;
|
||||
|
||||
this.applyTheme();
|
||||
this.persist();
|
||||
this.notifyListeners();
|
||||
}
|
||||
|
||||
public resetTheme(): void {
|
||||
this.customizations = this.defaultTheme;
|
||||
this.applyTheme();
|
||||
this.persist();
|
||||
this.notifyListeners();
|
||||
}
|
||||
|
||||
public onChange(callback: (theme: Theme) => void): () => void {
|
||||
this.onChangeCallbacks.add(callback);
|
||||
return () => {
|
||||
this.onChangeCallbacks.delete(callback);
|
||||
};
|
||||
}
|
||||
|
||||
private handleSystemThemeChange = (): void => {
|
||||
const newSystemMode = ThemeController.getSystemMode();
|
||||
if (this.systemMode === newSystemMode) return;
|
||||
|
||||
this.systemMode = newSystemMode;
|
||||
// If the current mode is SYSTEM, we need to re-apply the theme
|
||||
if (this.currentMode === ThemeMode.SYSTEM) {
|
||||
this.applyTheme();
|
||||
this.notifyListeners();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Centralized method to apply the current customizations and mode.
|
||||
*/
|
||||
private applyTheme(): void {
|
||||
const newConfig = { ...this.customizations };
|
||||
|
||||
switch (this.currentMode) {
|
||||
case ThemeMode.DARK:
|
||||
newConfig.algorithm = 'dark';
|
||||
break;
|
||||
case ThemeMode.LIGHT:
|
||||
newConfig.algorithm = 'default';
|
||||
break;
|
||||
case ThemeMode.SYSTEM:
|
||||
newConfig.algorithm =
|
||||
this.systemMode === ThemeMode.DARK ? 'dark' : 'default';
|
||||
break;
|
||||
case ThemeMode.COMPACT:
|
||||
newConfig.algorithm = 'compact';
|
||||
break;
|
||||
default:
|
||||
newConfig.algorithm = 'default';
|
||||
break;
|
||||
}
|
||||
|
||||
this.themeObject.setConfig(newConfig);
|
||||
}
|
||||
|
||||
private persist(): void {
|
||||
this.storage.setItem(this.modeStorageKey, this.currentMode);
|
||||
|
||||
const { algorithm, ...persistedCustomizations } = this.customizations;
|
||||
this.storage.setItem(
|
||||
this.storageKey,
|
||||
JSON.stringify(persistedCustomizations),
|
||||
);
|
||||
}
|
||||
|
||||
private notifyListeners(): void {
|
||||
this.onChangeCallbacks.forEach(callback => {
|
||||
try {
|
||||
callback(this.themeObject);
|
||||
} catch (e) {
|
||||
console.error('Error in theme change callback:', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static getSystemMode(): ThemeMode.DARK | ThemeMode.LIGHT {
|
||||
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
return isDark ? ThemeMode.DARK : ThemeMode.LIGHT;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
@@ -62,8 +61,8 @@ export function SupersetThemeProvider({
|
||||
[themeController],
|
||||
);
|
||||
|
||||
const changeThemeMode = useCallback(
|
||||
(newMode: ThemeMode) => themeController.changeThemeMode(newMode),
|
||||
const setThemeMode = useCallback(
|
||||
(newMode: ThemeMode) => themeController.setThemeMode(newMode),
|
||||
[themeController],
|
||||
);
|
||||
|
||||
@@ -77,10 +76,10 @@ export function SupersetThemeProvider({
|
||||
theme: currentTheme,
|
||||
themeMode: currentThemeMode,
|
||||
setTheme,
|
||||
changeThemeMode,
|
||||
setThemeMode,
|
||||
resetTheme,
|
||||
}),
|
||||
[currentTheme, currentThemeMode, setTheme, changeThemeMode, resetTheme],
|
||||
[currentTheme, currentThemeMode, setTheme, setThemeMode, resetTheme],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -96,9 +95,10 @@ export function SupersetThemeProvider({
|
||||
* React hook to use the theme context
|
||||
*/
|
||||
export function useThemeContext(): ThemeContextType {
|
||||
const context = useContext(ThemeContext);
|
||||
if (!context) {
|
||||
const context: ThemeContextType | null = useContext(ThemeContext);
|
||||
|
||||
if (!context)
|
||||
throw new Error('useThemeContext must be used within a ThemeProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ReactNode } from 'react';
|
||||
import { Theme } from '@superset-ui/core';
|
||||
import { ThemeContextType, ThemeMode } from '@superset-ui/core/theme/types';
|
||||
import { act, render, screen } from '@superset-ui/core/spec';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { SupersetThemeProvider, useThemeContext } from '../ThemeProvider';
|
||||
import { ThemeController } from '../ThemeController';
|
||||
|
||||
jest.mock('../ThemeController');
|
||||
const MockThemeController = ThemeController as jest.MockedClass<
|
||||
typeof ThemeController
|
||||
>;
|
||||
|
||||
// Mock theme objects
|
||||
const mockTheme = {
|
||||
SupersetThemeProvider: ({ children }: { children: ReactNode }) => (
|
||||
<div data-test="superset-theme-provider">{children}</div>
|
||||
),
|
||||
theme: {
|
||||
token: {
|
||||
colorPrimary: '#1890ff',
|
||||
colorBgBase: '#ffffff',
|
||||
},
|
||||
},
|
||||
} as unknown as Theme;
|
||||
|
||||
const mockDarkTheme = {
|
||||
SupersetThemeProvider: ({ children }: { children: ReactNode }) => (
|
||||
<div data-test="superset-dark-theme-provider">{children}</div>
|
||||
),
|
||||
theme: {
|
||||
token: {
|
||||
colorPrimary: '#1890ff',
|
||||
colorBgBase: '#000000',
|
||||
},
|
||||
},
|
||||
} as unknown as Theme;
|
||||
|
||||
const createWrapper =
|
||||
(controller: jest.Mocked<ThemeController>) =>
|
||||
({ children }: { children: ReactNode }) => (
|
||||
<SupersetThemeProvider themeController={controller}>
|
||||
{children}
|
||||
</SupersetThemeProvider>
|
||||
);
|
||||
|
||||
describe('SupersetThemeProvider', () => {
|
||||
let mockThemeController: jest.Mocked<ThemeController>;
|
||||
let mockOnChangeCallback: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mockOnChangeCallback = jest.fn();
|
||||
mockThemeController = {
|
||||
getTheme: jest.fn().mockReturnValue(mockTheme),
|
||||
getCurrentMode: jest.fn().mockReturnValue(ThemeMode.DEFAULT),
|
||||
setTheme: jest.fn(),
|
||||
setThemeMode: jest.fn(),
|
||||
resetTheme: jest.fn(),
|
||||
onChange: jest.fn().mockReturnValue(jest.fn()),
|
||||
canUpdateTheme: jest.fn().mockReturnValue(true),
|
||||
canUpdateMode: jest.fn().mockReturnValue(true),
|
||||
destroy: jest.fn(),
|
||||
} as unknown as jest.Mocked<ThemeController>;
|
||||
|
||||
mockThemeController.onChange.mockImplementation(callback => {
|
||||
mockOnChangeCallback.mockImplementation(callback);
|
||||
return jest.fn();
|
||||
});
|
||||
|
||||
MockThemeController.mockImplementation(() => mockThemeController);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Provider Initialization', () => {
|
||||
it('should render children within theme provider wrapper', () => {
|
||||
render(
|
||||
<SupersetThemeProvider themeController={mockThemeController}>
|
||||
<div data-test="test-child">Hello Superset</div>
|
||||
</SupersetThemeProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('superset-theme-provider')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('test-child')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should initialize theme state from controller', () => {
|
||||
const wrapper = createWrapper(mockThemeController);
|
||||
|
||||
const { result } = renderHook((): ThemeContextType => useThemeContext(), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
expect(mockThemeController.getTheme).toHaveBeenCalled();
|
||||
expect(mockThemeController.getCurrentMode).toHaveBeenCalled();
|
||||
expect(result.current.theme).toBe(mockTheme);
|
||||
expect(result.current.themeMode).toBe(ThemeMode.DEFAULT);
|
||||
});
|
||||
|
||||
it('should register onChange listener on mount', () => {
|
||||
const wrapper = createWrapper(mockThemeController);
|
||||
|
||||
renderHook((): ThemeContextType => useThemeContext(), { wrapper });
|
||||
|
||||
expect(mockThemeController.onChange).toHaveBeenCalledWith(
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('should unregister onChange listener on unmount', () => {
|
||||
const unsubscribeMock = jest.fn();
|
||||
mockThemeController.onChange.mockReturnValue(unsubscribeMock);
|
||||
|
||||
const wrapper = createWrapper(mockThemeController);
|
||||
|
||||
const { unmount } = renderHook(
|
||||
(): ThemeContextType => useThemeContext(),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
unmount();
|
||||
|
||||
expect(unsubscribeMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Theme State Updates', () => {
|
||||
it('should update theme state when controller notifies change', () => {
|
||||
const wrapper = createWrapper(mockThemeController);
|
||||
|
||||
const { result } = renderHook((): ThemeContextType => useThemeContext(), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
// Initial state
|
||||
expect(result.current.themeMode).toBe(ThemeMode.DEFAULT);
|
||||
expect(result.current.theme).toBe(mockTheme);
|
||||
|
||||
// Simulate theme change from controller
|
||||
act(() => {
|
||||
mockThemeController.getCurrentMode.mockReturnValue(ThemeMode.DARK);
|
||||
mockOnChangeCallback(mockDarkTheme);
|
||||
});
|
||||
|
||||
// State should update
|
||||
expect(result.current.themeMode).toBe(ThemeMode.DARK);
|
||||
expect(result.current.theme).toBe(mockDarkTheme);
|
||||
});
|
||||
|
||||
it('should update both theme and mode when controller changes', () => {
|
||||
const wrapper = createWrapper(mockThemeController);
|
||||
|
||||
const { result } = renderHook((): ThemeContextType => useThemeContext(), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
act(() => {
|
||||
mockThemeController.getCurrentMode.mockReturnValue(ThemeMode.SYSTEM);
|
||||
mockOnChangeCallback(mockDarkTheme);
|
||||
});
|
||||
|
||||
expect(result.current.themeMode).toBe(ThemeMode.SYSTEM);
|
||||
expect(result.current.theme).toBe(mockDarkTheme);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Theme Actions', () => {
|
||||
it('should call setTheme when invoked', () => {
|
||||
const wrapper = createWrapper(mockThemeController);
|
||||
|
||||
const { result } = renderHook((): ThemeContextType => useThemeContext(), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
const customTheme = {
|
||||
token: {
|
||||
colorPrimary: '#ff0000',
|
||||
colorBgBase: '#000000',
|
||||
},
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.setTheme(customTheme);
|
||||
});
|
||||
|
||||
expect(mockThemeController.setTheme).toHaveBeenCalledWith(customTheme);
|
||||
});
|
||||
|
||||
it('should call setThemeMode when invoked', () => {
|
||||
const wrapper = createWrapper(mockThemeController);
|
||||
|
||||
const { result } = renderHook((): ThemeContextType => useThemeContext(), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.setThemeMode(ThemeMode.DARK);
|
||||
});
|
||||
|
||||
expect(mockThemeController.setThemeMode).toHaveBeenCalledWith(
|
||||
ThemeMode.DARK,
|
||||
);
|
||||
});
|
||||
|
||||
it('should call resetTheme when invoked', () => {
|
||||
const wrapper = createWrapper(mockThemeController);
|
||||
|
||||
const { result } = renderHook((): ThemeContextType => useThemeContext(), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.resetTheme();
|
||||
});
|
||||
|
||||
expect(mockThemeController.resetTheme).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,17 +1,3 @@
|
||||
import {
|
||||
ColorSchemeConfig,
|
||||
FeatureFlagMap,
|
||||
JsonObject,
|
||||
LanguagePack,
|
||||
Locale,
|
||||
SequentialSchemeConfig,
|
||||
} from '@superset-ui/core';
|
||||
import { FormatLocaleDefinition } from 'd3-format';
|
||||
import { TimeLocaleDefinition } from 'd3-time-format';
|
||||
import { isPlainObject } from 'lodash';
|
||||
import { Languages } from 'src/features/home/LanguagePicker';
|
||||
import type { FlashMessage } from 'src/components';
|
||||
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
@@ -30,6 +16,24 @@ import type { FlashMessage } from 'src/components';
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
ColorSchemeConfig,
|
||||
FeatureFlagMap,
|
||||
JsonObject,
|
||||
LanguagePack,
|
||||
Locale,
|
||||
SequentialSchemeConfig,
|
||||
} from '@superset-ui/core';
|
||||
import { FormatLocaleDefinition } from 'd3-format';
|
||||
import { TimeLocaleDefinition } from 'd3-time-format';
|
||||
import { isPlainObject } from 'lodash';
|
||||
import { Languages } from 'src/features/home/LanguagePicker';
|
||||
import type { FlashMessage } from 'src/components';
|
||||
import type {
|
||||
AnyThemeConfig,
|
||||
SerializableThemeConfig,
|
||||
} from '@superset-ui/core/theme/types';
|
||||
|
||||
export type User = {
|
||||
createdOn?: string;
|
||||
email?: string;
|
||||
@@ -143,6 +147,18 @@ export interface MenuData {
|
||||
};
|
||||
}
|
||||
|
||||
export interface SerializableThemeSettings {
|
||||
enforced?: boolean;
|
||||
allowSwitching?: boolean;
|
||||
allowOSPreference?: boolean;
|
||||
}
|
||||
|
||||
export interface BootstrapThemeDataConfig {
|
||||
default: SerializableThemeConfig | {};
|
||||
dark: SerializableThemeConfig | {};
|
||||
settings: SerializableThemeSettings | {};
|
||||
}
|
||||
|
||||
export interface CommonBootstrapData {
|
||||
application_root: string;
|
||||
static_assets_prefix: string;
|
||||
@@ -153,7 +169,7 @@ export interface CommonBootstrapData {
|
||||
language_pack: LanguagePack;
|
||||
extra_categorical_color_schemes: ColorSchemeConfig[];
|
||||
extra_sequential_color_schemes: SequentialSchemeConfig[];
|
||||
theme: JsonObject;
|
||||
theme: BootstrapThemeDataConfig;
|
||||
menu_data: MenuData;
|
||||
d3_format: Partial<FormatLocaleDefinition>;
|
||||
d3_time_format: Partial<TimeLocaleDefinition>;
|
||||
@@ -169,6 +185,13 @@ export interface BootstrapData {
|
||||
requested_query?: JsonObject;
|
||||
}
|
||||
|
||||
export interface BootstrapThemeData {
|
||||
bootstrapDefaultTheme: AnyThemeConfig | null;
|
||||
bootstrapDarkTheme: AnyThemeConfig | null;
|
||||
bootstrapThemeSettings: SerializableThemeSettings | null;
|
||||
hasBootstrapThemes: boolean;
|
||||
}
|
||||
|
||||
export function isUser(user: any): user is User {
|
||||
return isPlainObject(user) && 'username' in user;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-env node */
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
@@ -17,31 +16,13 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
module.exports = {
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'react', 'prettier'],
|
||||
rules: {
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'react/prop-types': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
ignorePatterns: ['build/**/*', '.docusaurus/**/*', 'node_modules/**/*'],
|
||||
};
|
||||
import { utils, writeFile } from 'xlsx';
|
||||
|
||||
export default function exportPivotExcel(
|
||||
tableSelector: string,
|
||||
fileName: string,
|
||||
) {
|
||||
const table = document.querySelector(tableSelector);
|
||||
const workbook = utils.table_to_book(table);
|
||||
writeFile(workbook, `${fileName}.xlsx`);
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
import { Route } from 'react-router-dom';
|
||||
import { getExtensionsRegistry, themeObject } from '@superset-ui/core';
|
||||
import { getExtensionsRegistry } from '@superset-ui/core';
|
||||
import { Provider as ReduxProvider } from 'react-redux';
|
||||
import { QueryParamProvider } from 'use-query-params';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
@@ -32,17 +32,14 @@ import { store } from './store';
|
||||
import '../preamble';
|
||||
|
||||
const { common } = getBootstrapData();
|
||||
|
||||
if (Object.keys(common?.theme || {}).length > 0) {
|
||||
themeObject.setConfig(common.theme);
|
||||
}
|
||||
const themeController = new ThemeController({ themeObject });
|
||||
|
||||
const themeController = new ThemeController();
|
||||
const extensionsRegistry = getExtensionsRegistry();
|
||||
|
||||
export const RootContextProviders: React.FC = ({ children }) => {
|
||||
const RootContextProviderExtension = extensionsRegistry.get(
|
||||
'root.context.provider',
|
||||
);
|
||||
|
||||
return (
|
||||
<SupersetThemeProvider themeController={themeController}>
|
||||
<ReduxProvider store={store}>
|
||||
|
||||
+15
-15
@@ -13,7 +13,7 @@
|
||||
"express": "~5.1.0",
|
||||
"http-errors": "~2.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"morgan": "~1.10.0",
|
||||
"morgan": "~1.10.1",
|
||||
"pug": "~3.0.3"
|
||||
}
|
||||
},
|
||||
@@ -863,15 +863,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/morgan": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
|
||||
"integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
|
||||
"integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==",
|
||||
"dependencies": {
|
||||
"basic-auth": "~2.0.1",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
"on-finished": "~2.3.0",
|
||||
"on-headers": "~1.0.2"
|
||||
"on-headers": "~1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
@@ -936,9 +936,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/on-headers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
||||
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
|
||||
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
@@ -2045,15 +2045,15 @@
|
||||
}
|
||||
},
|
||||
"morgan": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
|
||||
"integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
|
||||
"integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==",
|
||||
"requires": {
|
||||
"basic-auth": "~2.0.1",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
"on-finished": "~2.3.0",
|
||||
"on-headers": "~1.0.2"
|
||||
"on-headers": "~1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
@@ -2100,9 +2100,9 @@
|
||||
}
|
||||
},
|
||||
"on-headers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
||||
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
|
||||
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user