Compare commits

..

1 Commits

Author SHA1 Message Date
Evan
f342aebe2a fix(db_engine_specs): map var_string to STRING for MySQL/StarRocks
The MySQL and StarRocks column type mappings did not recognize the
`var_string` native type (returned for varchar-backed columns), so
these columns fell through unmatched and were not classified as
GenericDataType.STRING. As a result, controls that gate on column
type (e.g. the "Force categorical" / y-axis sort control) were
incorrectly hidden for such columns.

Add a `^var_string` regex mapping resolving to GenericDataType.STRING
in both engine specs, matching the workaround confirmed by the issue
reporter, plus regression cases in the parameterized column-spec tests.

Fixes #30282

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 16:39:05 -07:00
1302 changed files with 26622 additions and 83574 deletions

View File

@@ -32,6 +32,8 @@ runs:
elif [ "$INPUT_PYTHON_VERSION" = "next" ]; then
# currently disabled in GHA matrixes because of library compatibility issues
RESOLVED_VERSION="3.12"
elif [ "$INPUT_PYTHON_VERSION" = "previous" ]; then
RESOLVED_VERSION="3.10"
elif printf '%s' "$INPUT_PYTHON_VERSION" | grep -Eq '^[0-9]+\.[0-9]+(\.[0-9]+)?$'; then
RESOLVED_VERSION="$INPUT_PYTHON_VERSION"
else

299
.github/dependabot.yml vendored
View File

@@ -11,15 +11,7 @@ updates:
- package-ecosystem: "npm"
ignore:
- dependency-name: "@rjsf/*"
# TODO: remove below entries once the application supports React >= 19.0.0
- dependency-name: "react"
update-types: ["version-update:semver-major"]
- dependency-name: "react-dom"
update-types: ["version-update:semver-major"]
- dependency-name: "@types/react"
update-types: ["version-update:semver-major"]
- dependency-name: "@types/react-dom"
update-types: ["version-update:semver-major"]
# TODO: remove below entries until React >= 19.0.0
- dependency-name: "react-icons"
# JSDOM v30 doesn't play well with Jest v30
# Source: https://jestjs.io/blog#known-issues
@@ -54,6 +46,7 @@ updates:
cooldown:
default-days: 7
- package-ecosystem: "pip"
directory: "/"
open-pull-requests-limit: 10
@@ -110,3 +103,291 @@ updates:
versioning-strategy: increase
cooldown:
default-days: 7
# Now for all of our plugins and packages!
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/legacy-plugin-chart-calendar/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/legacy-plugin-chart-partition/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/legacy-plugin-chart-world-map/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/plugin-chart-pivot-table/"
ignore:
# TODO: remove below entries until React >= 19.0.0
- dependency-name: "react-icons"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/legacy-plugin-chart-chord/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/legacy-plugin-chart-horizon/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/legacy-plugin-chart-rose/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/legacy-preset-chart-deckgl/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/plugin-chart-table/"
ignore:
# TODO: remove below entries until React >= 19.0.0
- dependency-name: "react-icons"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/legacy-plugin-chart-country-map/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/legacy-plugin-chart-map-box/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/legacy-preset-chart-nvd3/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/plugin-chart-word-cloud/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/legacy-plugin-chart-paired-t-test/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/plugin-chart-echarts/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/plugin-chart-ag-grid-table/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/plugin-chart-cartodiagram/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/legacy-plugin-chart-parallel-coordinates/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/plugins/plugin-chart-handlebars/"
ignore:
# `just-handlerbars-helpers` library in plugin-chart-handlebars requires `currencyformatter`` to be < 2
- dependency-name: "currencyformatter.js"
update-types: ["version-update:semver-major"]
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/packages/generator-superset/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/packages/superset-ui-chart-controls/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/packages/superset-ui-core/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7
- package-ecosystem: "npm"
directory: "/superset-frontend/packages/superset-ui-switchboard/"
schedule:
interval: "daily"
labels:
- npm
- dependabot
open-pull-requests-limit: 5
versioning-strategy: increase
cooldown:
default-days: 7

View File

@@ -46,7 +46,7 @@ jobs:
- name: Login to Docker Hub
if: steps.check.outputs.python
continue-on-error: true
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

View File

@@ -64,7 +64,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -75,6 +75,6 @@ jobs:
# queries: security-extended,security-and-quality
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
category: "/language:${{matrix.language}}"

View File

@@ -31,7 +31,7 @@ jobs:
# token, which makes npm attempt token auth and skip the OIDC
# trusted-publishing exchange. With no .npmrc auth line, npm authenticates
# via OIDC against the default registry (registry.npmjs.org).
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: "./superset-embedded-sdk/.nvmrc"
- run: npm ci

View File

@@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: "./superset-embedded-sdk/.nvmrc"
registry-url: "https://registry.npmjs.org"

View File

@@ -37,7 +37,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: "temurin"
java-version: "11"

View File

@@ -14,7 +14,7 @@ jobs:
pull-requests: write
runs-on: ubuntu-24.04
steps:
- uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
with:
sync-labels: true

View File

@@ -23,7 +23,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: "temurin"
java-version: "11"

View File

@@ -7,11 +7,6 @@ on:
- "[0-9].[0-9]*"
pull_request:
types: [synchronize, opened, reopened, ready_for_review]
# Nightly full-tree sweep. Per-PR runs only lint changed files, so a change
# that invalidates an untouched file (e.g. a type change that breaks an
# importing test) can pass every PR yet leave master red. This catches that.
schedule:
- cron: "0 6 * * *"
permissions:
contents: read
@@ -30,16 +25,13 @@ jobs:
# Run the full version spread on push (master/release) and nightly,
# but only the current version on PRs — lint/format/type results
# rarely differ across patch versions, so 3x per PR is wasteful.
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "previous", "next"]') }}
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
submodules: recursive
# Full history so we can diff a PR/push against its base commit to
# determine changed files (see "Determine changed files" below).
fetch-depth: 0
- name: Setup Python
uses: ./.github/actions/setup-backend/
@@ -53,7 +45,7 @@ jobs:
run: go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.14.2
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: "superset-frontend/.nvmrc"
cache: "npm"
@@ -77,82 +69,19 @@ jobs:
restore-keys: |
pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-
- name: Determine changed files
- name: Get changed files
id: changed_files
env:
EVENT_NAME: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
BEFORE_SHA: ${{ github.event.before }}
run: |
set -euo pipefail
# Scheduled runs check the whole tree (see the pre-commit step).
if [ "${EVENT_NAME}" = "schedule" ]; then
echo "mode=all" >> "$GITHUB_OUTPUT"
exit 0
fi
# Resolve the commit to diff against.
base=""
if [ "${EVENT_NAME}" = "pull_request" ]; then
base="${BASE_SHA}"
elif [ -n "${BEFORE_SHA:-}" ] && \
[ "${BEFORE_SHA}" != "0000000000000000000000000000000000000000" ]; then
base="${BEFORE_SHA}"
fi
# Fail closed: if the diff base can't be resolved, check every file
# instead of silently checking nothing. Previously an empty file list
# made `pre-commit run --files` a no-op that still reported success,
# which let unlinted code reach master.
if [ -z "${base}" ] || ! git cat-file -e "${base}^{commit}" 2>/dev/null; then
echo "::notice::Could not resolve a diff base; falling back to --all-files."
echo "mode=all" >> "$GITHUB_OUTPUT"
exit 0
fi
# Files present in HEAD that changed since the base (drop deletions).
files="$(git diff --name-only --diff-filter=ACMRT "${base}...HEAD")"
if [ -z "${files}" ]; then
echo "mode=none" >> "$GITHUB_OUTPUT"
else
echo "mode=files" >> "$GITHUB_OUTPUT"
{
echo "files<<__CHANGED_FILES_EOF__"
echo "${files}"
echo "__CHANGED_FILES_EOF__"
} >> "$GITHUB_OUTPUT"
fi
uses: ./.github/actions/file-changes-action
with:
output: " "
- name: pre-commit
env:
MODE: ${{ steps.changed_files.outputs.mode }}
CHANGED_FILES: ${{ steps.changed_files.outputs.files }}
run: |
set +e # Don't exit immediately on failure
export SKIP=type-checking-frontend
case "${MODE}" in
all)
echo " Running pre-commit on all files."
pre-commit run --all-files
;;
files)
echo " Running pre-commit on changed files:"
echo "${CHANGED_FILES}"
# shellcheck disable=SC2086
pre-commit run --files ${CHANGED_FILES}
;;
none)
echo " No source files changed; nothing for pre-commit to check."
exit 0
;;
*)
echo "⚠️ Unrecognized changed-files mode '${MODE}'; checking all files."
pre-commit run --all-files
;;
esac
pre-commit run --files $CHANGED_FILES
PRE_COMMIT_EXIT_CODE=$?
git diff --quiet --exit-code
GIT_DIFF_EXIT_CODE=$?

View File

@@ -50,7 +50,7 @@ jobs:
- name: Install Node.js
if: env.HAS_TAGS
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: "./superset-frontend/.nvmrc"

View File

@@ -117,7 +117,7 @@ jobs:
build: "true"
- name: Use Node.js 20
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 20

View File

@@ -66,12 +66,12 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: "./docs/.nvmrc"
- name: Setup Python
uses: ./.github/actions/setup-backend/
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: "zulu"
java-version: "21"

View File

@@ -78,7 +78,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: "./docs/.nvmrc"
- name: yarn install
@@ -118,7 +118,7 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: "./docs/.nvmrc"
- name: yarn install

View File

@@ -128,7 +128,7 @@ jobs:
with:
run: testdata
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: "./superset-frontend/.nvmrc"
cache: "npm"
@@ -238,7 +238,7 @@ jobs:
with:
run: playwright_testdata
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: "./superset-frontend/.nvmrc"
cache: "npm"

View File

@@ -25,7 +25,7 @@ jobs:
matrix:
# Full version spread on push (master/release) + nightly; current only
# on PRs to cut runner cost (cross-version breaks are caught at merge).
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["previous", "current", "next"]') }}
defaults:
run:
working-directory: superset-extensions-cli

View File

@@ -114,7 +114,7 @@ jobs:
with:
run: playwright_testdata
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: "./superset-frontend/.nvmrc"
cache: "npm"

View File

@@ -135,7 +135,7 @@ jobs:
matrix:
# Full version spread on push (master/release) + nightly; current only
# on PRs to cut runner cost (cross-version breaks are caught at merge).
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "previous", "next"]') }}
env:
PYTHONPATH: ${{ github.workspace }}
SUPERSET_CONFIG: tests.integration_tests.superset_test_config

View File

@@ -50,15 +50,9 @@ jobs:
matrix:
# Full version spread on push (master/release) + nightly; current only
# on PRs to cut runner cost (cross-version breaks are caught at merge).
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["previous", "current", "next"]') }}
env:
PYTHONPATH: ${{ github.workspace }}
# Promotes the SQLAlchemy 2.0 deprecation warnings already locked in as
# errors via pytest.ini's `filterwarnings` to actually run in CI, so a
# regression on those fails the build instead of relying on a
# contributor remembering to set this locally. See the migration
# battleplan: https://github.com/apache/superset/discussions/40273
SQLALCHEMY_WARN_20: "1"
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

View File

@@ -38,7 +38,7 @@ jobs:
- name: Setup Node.js
if: steps.check.outputs.frontend
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: "./superset-frontend/.nvmrc"
cache: "npm"

View File

@@ -38,7 +38,7 @@ jobs:
- name: Login to Docker Hub
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
continue-on-error: true
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

View File

@@ -75,7 +75,7 @@ jobs:
- name: Use Node.js 20
# zizmor: ignore[cache-poisoning] - node only runs the supersetbot CLI; no dependency cache is enabled
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 20
package-manager-cache: false
@@ -133,7 +133,7 @@ jobs:
- name: Use Node.js 20
# zizmor: ignore[cache-poisoning] - node only runs the supersetbot CLI; no dependency cache is enabled
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 20
package-manager-cache: false

View File

@@ -37,7 +37,7 @@ jobs:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: "./superset-frontend/.nvmrc"

3
.gitmodules vendored
View File

@@ -21,6 +21,9 @@
[submodule ".github/actions/pr-lint-action"]
path = .github/actions/pr-lint-action
url = https://github.com/morrisoncole/pr-lint-action
[submodule ".github/actions/file-changes-action"]
path = .github/actions/file-changes-action
url = https://github.com/trilom/file-changes-action
[submodule ".github/actions/cached-dependencies"]
path = .github/actions/cached-dependencies
url = https://github.com/apache-superset/cached-dependencies

View File

@@ -63,7 +63,7 @@ repos:
hooks:
- id: prettier-frontend
name: prettier (frontend)
entry: bash -c 'cd superset-frontend && files=(); for f in "$@"; do files+=("${f#superset-frontend/}"); done; npx prettier --write -- "${files[@]}"' --
entry: bash -c 'cd superset-frontend && for file in "$@"; do npx prettier --write "${file#superset-frontend/}"; done'
language: system
pass_filenames: true
files: ^superset-frontend/.*\.(js|jsx|ts|tsx|css|scss|sass|json)$
@@ -81,12 +81,6 @@ repos:
language: system
pass_filenames: true
files: ^superset-frontend/.*\.(js|jsx|ts|tsx)$
- id: stylelint-frontend
name: stylelint (frontend css-in-js)
entry: ./scripts/stylelint.sh
language: system
pass_filenames: true
files: ^superset-frontend/.*\.(js|jsx|ts|tsx)$
- id: eslint-docs
name: eslint (docs)
entry: bash -c 'cd docs && FILES=$(printf "%s\n" "$@" | sed "s|^docs/||" | tr "\n" " ") && yarn eslint --fix --quiet $FILES'

View File

@@ -7,7 +7,6 @@
.codecov.yml
.eslintrc
.eslintignore
.stylelintignore
.flake8
.nvmrc
.prettierrc

View File

@@ -619,34 +619,14 @@ categories:
- name: Symmetrics
url: https://www.symmetrics.fyi
Government / Public authority:
- name: Centre Régional Auvergnat de l'Information Géographique (France)
url: https://www.craig.fr/
logo: craig.png
Government:
- name: City of Ann Arbor, MI
url: https://www.a2gov.org/
contributors: ["@sfirke"]
- name: Métropole Européenne de Lille (France)
url: https://data.lillemetropole.fr/accueil/
logo: mel.jpg
- name: NRLM - Sarathi, India
url: https://pib.gov.in/PressReleasePage.aspx?PRID=1999586
- name: Région Bretagne (France)
url: https://geobretagne.fr/
logo: geobretagne.svg
- name: Région Hauts-de-France (France)
url: https://www.geo2france.fr/
logo: geo2france.png
- name: Rennes Métropole (France)
url: https://metropole.rennes.fr/
logo: rennes-ville-metropole_noir.svg
- name: RIS3 Strategy of CZ, MIT CR
url: https://www.ris3.cz/
contributors: ["@RIS3CZ"]
@@ -702,10 +682,6 @@ categories:
url: https://automattic.com/
contributors: ["@Khrol", "@Usiel"]
- name: CamptoCamp
url: https://camptocamp.com/
logo: camptocamp.svg
- name: Dropbox
url: https://www.dropbox.com/
contributors: ["@bkyryliuk"]
@@ -714,10 +690,6 @@ categories:
url: https://flowbird.com
contributors: ["@EmmanuelCbd"]
- name: geOrchestra
url: https://www.georchestra.org
logo: georchestra.svg
- name: GEOTAB
url: https://www.geotab.com
contributors: ["@JZ6"]
@@ -734,10 +706,6 @@ categories:
url: https://www.increff.com/
contributors: ["@ishansinghania"]
- name: jDev
url: https://jdev.fr/
logo: jDev.svg
- name: Let's Roam
url: https://www.letsroam.com/
@@ -748,11 +716,6 @@ categories:
url: https://1beat.com/
contributors: ["@GuyAttia"]
- name: pi-Geosolutions
url: https://www.pi-geosolutions.fr/
logo: pi-Geosolutions.png
contributors: ["@jeanpommier"]
- name: X
url: https://x.com/

View File

@@ -24,94 +24,6 @@ assists people when migrating to a new version.
## Next
### Owners, dashboard roles, and RLS roles replaced by Subjects
Superset now uses subject-based access assignments for dashboards, charts, datasets,
alerts/reports, and Row Level Security. A Subject can represent a user, role, or group.
This is a breaking API and metadata change:
- `owners` is replaced by `editors` for dashboards, charts, datasets, and alerts/reports.
- Dashboard `roles` and the `DASHBOARD_RBAC` feature flag are replaced by dashboard/chart
`viewers`, enabled with `ENABLE_VIEWERS`.
- RLS `roles` is replaced by `subjects`.
- The legacy `dashboard_user`, `slice_user`, `sqlatable_user`, `report_schedule_user`,
`dashboard_roles`, and `rls_filter_roles` tables are migrated into subject junction tables
and dropped on upgrade.
For deployments that previously used `DASHBOARD_RBAC` and later disabled it: remove stale rows
from the legacy `dashboard_roles` table before upgrading, otherwise those role assignments will
become active dashboard Viewers after migration.
API clients and automation should send and read `editors`, `viewers`, and `subjects` instead
of the legacy fields.
Subject pickers support users, groups, and roles, but only users and groups are selectable by
default. Roles remain supported as Subject types for backwards compatibility with RLS role
assignments and the previous `DASHBOARD_RBAC` model, but they are not recommended for new
resource-specific assignments. Prefer groups for membership-based access and keep roles focused
on capability grants. Existing Role subject assignments remain effective after migration even when
Roles are hidden from the default dropdown values; configure the relevant `SUBJECTS_RELATED_TYPES_*`
setting to make Roles selectable when editing subject lists. See the [Security documentation](docs/admin_docs/security/security.mdx#subjects)
for the full Subject model and picker configuration guidance.
To make roles selectable everywhere:
```python
from superset.subjects.types import SubjectType
SUBJECTS_RELATED_TYPES = [
SubjectType.USER,
SubjectType.GROUP,
SubjectType.ROLE,
]
```
To make roles selectable for RLS while other pickers keep the user and group default, use the
RLS-specific override:
```python
from superset.subjects.types import SubjectType
SUBJECTS_RELATED_TYPES_RLS = [
SubjectType.USER,
SubjectType.GROUP,
SubjectType.ROLE,
]
```
Entity-specific `SUBJECTS_RELATED_TYPES_*` settings replace `SUBJECTS_RELATED_TYPES` for that
picker.
Deployments using `EXTRA_OWNERS_RESOLVER` must migrate to `EXTRA_EDITORS_RESOLVER`. The new
resolver should return editor Subjects, subject IDs, or dicts with an `id` key instead of FAB
User objects. API responses expose these dynamic assignments as `extra_editors` instead of
`extra_owners`.
`DASHBOARD_RBAC` has been removed. To preserve the previous Dashboard RBAC behavior, enable both
subject viewers and viewer datasource bypass:
```python
FEATURE_FLAGS = {
"ENABLE_VIEWERS": True,
}
VIEWER_PROMISCUOUS_MODE = True
```
Enabling only `ENABLE_VIEWERS` allows assigning dashboard/chart viewer subjects, but viewers still
need normal datasource permissions unless `VIEWER_PROMISCUOUS_MODE` is also enabled.
For backwards compatibility, enabling `ENABLE_VIEWERS` does not change access for dashboards or
charts that have no assigned viewers. Those resources continue to use the implicit dataset-access
model: users who can access the underlying dataset can still see published dashboards that use that
dataset and charts backed by that dataset.
Assigning one or more viewers opts that resource into explicit viewer access for non-editors. To
return a resource to the implicit dataset-access model, remove all viewers from it. Explicit Viewers
are the intended model going forward; deprecating and removing implicit viewership can be considered
in a later major version.
- [41044](https://github.com/apache/superset/issues/41044): Removes the deprecated `AVOID_COLORS_COLLISION` feature flag (it defaulted to `True`). Color-collision avoidance is now permanently enabled; any config override setting it to `False` is ignored.
- [39925](https://github.com/apache/superset/pull/39925): URL prefixing for `SUPERSET_APP_ROOT` subdirectory deployments is now handled automatically by helpers in `src/utils/navigationUtils` (`openInNewTab`, `redirect`, `getShareableUrl`, `<AppLink>`). Direct imports of `ensureAppRoot` / `makeUrl` from `src/utils/pathUtils` are forbidden outside `navigationUtils.ts` (enforced by a static-invariant test); contributors writing new code should use the focused helpers instead. No runtime behaviour change for existing callers — all 19 prior call sites have been migrated and four pre-existing double-prefix and missing-prefix bugs are fixed as part of the migration.
- [39925](https://github.com/apache/superset/pull/39925): `SupersetClient.getUrl()` now strips a single leading application-root segment from the supplied `endpoint` before building the request URL, so a caller that accidentally pre-prefixes its endpoint (for example by wrapping it with `ensureAppRoot` before passing it to the client) no longer produces a doubled `/superset/superset/...` URL under subdirectory deployment. The strip is **single-pass** — a genuine `/superset/superset/<slug>` route is preserved, not collapsed — and **silent** (no console warning); the static-invariant test remains the primary signal for pre-prefixing at the call site, and this runtime strip is a safety net beneath it. Code that intentionally targeted a literal `/<app_root>/<app_root>/...` endpoint through `getUrl` (a configuration that has no legitimate use under the prefixing model) would have its first redundant segment removed.
@@ -153,16 +65,6 @@ virtual-dataset SQL) raises a parse error in SQL Lab and dashboard-generated
queries. Deployments that legitimately run queries above this size should raise
the value, and `SQL_MAX_PARSE_LENGTH = None` disables the check entirely.
### Ant Design upgraded from v5 to v6
The frontend now builds against Ant Design 6, and `@superset-ui/core` / `@apache-superset/core` peer-depend on `antd ^6`. Custom plugins, extensions, and themes that interact with Ant Design need review:
- **Internal DOM classes were renamed**, so any custom CSS targeting `.ant-*` internals silently stops matching. Notable renames: `.ant-tabs-content-holder``.ant-tabs-body-holder`, `.ant-tabs-content``.ant-tabs-body`, `.ant-tabs-tabpane``.ant-tabs-content`; `.ant-select-selector``.ant-select-content`, `.ant-select-selection-placeholder``.ant-select-placeholder`, `.ant-select-arrow``.ant-select-suffix`; `.ant-tooltip-inner``.ant-tooltip-container`; `.ant-popover-inner``.ant-popover-container`; `.ant-steps-item-tail``.ant-steps-item-rail`.
- **Some component props changed or were removed** — e.g. `Select` no longer accepts `dropdownAlign`, `visible`/`onVisibleChange` are `open`/`onOpenChange`, `Dropdown` `overlay` is `menu`, `Steps.Step` children are the `items` prop, and `styles.body` on Tooltip/Popover is `styles.container`.
- **CSS variables are on by default** in antd 6, and `ThemeConfig.cssVar` no longer accepts a boolean; Superset theme configs using `cssVar: true`/`false` are coerced (`true``{}`, `false` → omitted).
Theme tokens are unaffected — antd 6 removed none of the tokens Superset exposes, so existing theme configurations continue to work. See the [Ant Design v6 migration guide](https://ant.design/docs/react/migration-v6) for the complete upstream list.
### Guest-token RLS rules reject unknown fields
The `rls` rules passed to `POST /api/v1/security/guest_token/` are now validated strictly: a rule may only contain `dataset` and `clause`. Previously unknown fields were silently dropped, so a mistyped or legacy scope key (most commonly `datasource` instead of `dataset`) produced a rule with no `dataset`, which is treated as a *global* rule applied to every dataset the embedded resource can reach. Such a request now returns HTTP 400 identifying the offending field instead of issuing a token with an unintended global rule. Integrators that were sending extra fields in RLS rules must remove them; valid dataset-scoped (`{"dataset": 41, "clause": "..."}`) and global (`{"clause": "..."}`) rules are unaffected.
@@ -192,10 +94,6 @@ helm upgrade <release-name> superset/superset
Alternatively, perform a fresh install. This is a one-time migration; subsequent upgrades are unaffected.
### Time-series tooltips follow the selected time grain
Tooltips on the Time-series and Mixed Time-series ECharts plugins now respect the chart's time grain (and any dashboard-level time-grain override delivered via `extra_form_data`) when the tooltip time format is left on Adaptive formatting (the default). Tooltips read grain-appropriate labels such as `Jan 2021` (month), `2021 Q1` (quarter), `2021` (year), and weekly date ranges, becoming grain-aware like the x-axis, though the two are formatted independently and their labels may not always match exactly. Only a custom, explicitly-set tooltip time format (a d3 format string) is unaffected — that always wins over the grain.
### Pivot table First/Last aggregations follow data order
The pivot table chart's `First` and `Last` aggregations now return the first and last value in data (query result) order, instead of effectively returning the minimum and maximum. Existing pivot tables that use these aggregations for totals/subtotals may show different values after upgrading. For deterministic results, ensure the underlying query has a stable sort order.
@@ -227,17 +125,6 @@ ALTER TABLE tagged_object DROP CONSTRAINT <constraint_name>;
-- MySQL: find names via `SHOW CREATE TABLE tagged_object;`
ALTER TABLE tagged_object DROP FOREIGN KEY <constraint_name>;
```
### Entity version-history infrastructure (gated off by default)
Introduces the schema and SQLAlchemy-Continuum wiring that captures version history for charts, dashboards, and datasets, plus read-only `GET /api/v1/{chart,dashboard,dataset}/<uuid>/versions/` endpoints. This ships **inert**: a new config flag `ENABLE_VERSIONING_CAPTURE` defaults to `False`, so no save writes any version rows and the endpoints return empty. It is an operational kill-switch (a release toggle that becomes a permanent ops switch), not a feature flag — set it to `True` to enable capture once validated. The migration is additive; existing entity `PUT` responses gain `old_version_uuid` / `new_version_uuid` body fields and an `ETag` header (both null/absent when capture is off).
A few save- and import-path internals change **unconditionally** (independent of the flag), because the versioned mappers must behave correctly whether or not capture is enabled:
- `DatasetDAO` column/metric updates move from bulk operations to per-row ORM operations, and a metadata refresh now preserves column primary keys via a natural-key (`column_name`) upsert instead of delete-and-reinsert — so charts that reference dataset columns by id keep working across a refresh (previously such references could be invalidated).
- `ImportExportMixin.reset_ownership` stamps the current user onto `created_by`/`changed_by` when a request context is present (previously left null for the column default to fill).
- `UpdateDashboardCommand` runs its body under `no_autoflush`.
These are behavior changes that take effect on upgrade regardless of `ENABLE_VERSIONING_CAPTURE`; no operator action is required.
### Webhook alerts/reports block private/internal hosts by default
@@ -433,7 +320,7 @@ With the flag enabled: `DELETE /api/v1/dataset/<id>` no longer hard-deletes the
**Side-effect change for operators.** Because the row is no longer physically deleted, FAB `ab_view_menu` / permission-view rows tied to the dataset are also preserved. Downstream automation that relied on `DELETE /api/v1/dataset/<id>` cleaning up those rows must now react to the new `POST /api/v1/dataset/<uuid>/restore` lifecycle, or call the eventual hard-delete endpoint.
**New endpoint** — `POST /api/v1/dataset/<uuid>/restore` clears `deleted_at` and returns the dataset to active state. Requires `can_write on Dataset` and editorship of the row (or admin). Soft-deleted datasets can also be surfaced in the list endpoint via the new `dataset_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to datasets they can edit — the same audience that can restore them.
**New endpoint** — `POST /api/v1/dataset/<uuid>/restore` clears `deleted_at` and returns the dataset to active state. Requires `can_write on Dataset` and ownership of the row (or admin). Soft-deleted datasets can also be surfaced in the list endpoint via the new `dataset_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to datasets they own — the same audience that can restore them.
**Permissions migration:** existing role grants of `can_write on Dataset` cover the new restore endpoint automatically; no role migration is required.
@@ -443,7 +330,7 @@ With the flag enabled: `DELETE /api/v1/dataset/<id>` no longer hard-deletes the
**SQL Lab / dataset-creation flows:** creating a dataset over a table whose dataset sits in the trash is refused. The SQL Lab "save as dataset" flow (`get_or_create_dataset`) and file uploads return a **422 naming the hidden twin and the restore endpoint**; the plain create, update, and duplicate paths currently fail with the generic "already exists" 422. In all cases the remediation is the same: restore the hidden dataset (or use a different table name). Perm-string maintenance also covers hidden rows: renaming a database rewrites `perm`/`schema_perm`/`catalog_perm` on soft-deleted datasets and their charts, so a later restore does not resurrect stale permission strings.
**Importer behavior:** importing a dataset YAML whose UUID matches an existing **soft-deleted** dataset is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dataset imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dataset's exact UUID is an explicit request to bring it back. The restore preserves the original PK, the chart back-reference, `table_columns`, and `sql_metrics`. Non-editors get `ImportFailedError`. Callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
**Importer behavior:** importing a dataset YAML whose UUID matches an existing **soft-deleted** dataset is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dataset imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dataset's exact UUID is an explicit request to bring it back. The restore preserves the original PK, the chart back-reference, `table_columns`, and `sql_metrics`. Non-owners get `ImportFailedError`. Callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
**Uniqueness-validation changes that apply regardless of the feature flag:** two dataset uniqueness checks were tightened alongside this work and are active even with `SOFT_DELETE` off. (1) Create/update uniqueness treats a dataset whose `catalog` is `NULL` as belonging to the database's default catalog, so a legacy twin pair (`catalog=NULL` vs. `catalog=<default>`, same database/schema/name) that older versions allowed now fails validation with "already exists" when either row is edited — resolve by renaming or removing one of the twins. (2) Duplicating a dataset now checks name collisions scoped to the target (database, catalog, schema) instead of globally by name alone: duplicates into other databases that were previously blocked are now allowed.
@@ -457,7 +344,7 @@ With the flag enabled: `DELETE /api/v1/chart/<id>` no longer hard-deletes the ch
**Operational notes:** a report schedule whose target chart is soft-deleted now fails its runs with an explicit error ("The chart this report targets was deleted...") until the chart is restored or the report re-pointed — chart deletion is blocked while a report references the chart, but a validate/commit race or a flag toggle can still produce this state. Dashboards **preserve** their membership rows for soft-deleted charts: saving a dashboard does not sever a trashed member, and restoring the chart re-attaches it to its dashboards.
**New endpoint** — `POST /api/v1/chart/<uuid>/restore` clears `deleted_at` and returns the chart to active state. Requires `can_write on Chart` and editorship of the row (or admin). Soft-deleted charts can also be surfaced in the list endpoint via the new `chart_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to charts they can edit — the same audience that can restore them.
**New endpoint** — `POST /api/v1/chart/<uuid>/restore` clears `deleted_at` and returns the chart to active state. Requires `can_write on Chart` and ownership of the row (or admin). Soft-deleted charts can also be surfaced in the list endpoint via the new `chart_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to charts they own — the same audience that can restore them.
**Permissions migration:** existing role grants of `can_write on Chart` cover the new restore endpoint automatically; no role migration is required.
@@ -465,7 +352,7 @@ With the flag enabled: `DELETE /api/v1/chart/<id>` no longer hard-deletes the ch
**Rollback note:** if the application code is rolled back after charts have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted charts silently become live, active charts with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) *before* downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
**Importer behavior:** importing a chart YAML whose UUID matches an existing **soft-deleted** chart is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active chart imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted chart's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all out-of-archive references (`dashboard_slices` junctions, `report.chart_id`, tag rows). The operation is permission-gated: non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
**Importer behavior:** importing a chart YAML whose UUID matches an existing **soft-deleted** chart is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active chart imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted chart's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all out-of-archive references (`dashboard_slices` junctions, `report.chart_id`, tag rows). The operation is permission-gated: non-owners get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
- [39914](https://github.com/apache/superset/pull/39914) `ALERT_REPORT_SLACK_V2` now defaults to `True` and the legacy Slack v1 integration (`Slack` recipient type, `files.upload` API) is deprecated for removal in the next major. Slack blocked new apps from `files.upload` in May 2024 and fully retired the method for all apps on November 12, 2025; because the v1 path sends files through `files.upload`, v1 file-bearing sends now fail at the API level — only text-only `chat_postMessage` still works via the legacy path. Grant your Slack bot the `channels:read` and `groups:read` scopes so existing `Slack` recipients can be auto-upgraded to `SlackV2` on next send. Operators who explicitly override the flag to `False`, or whose Slack bot is missing those scopes, will see deprecation warnings while text-only sends continue through the legacy path.
@@ -477,7 +364,7 @@ With the flag enabled: `DELETE /api/v1/chart/<id>` no longer hard-deletes the ch
With the flag enabled: `DELETE /api/v1/dashboard/<id>` no longer hard-deletes the dashboard (the bulk-delete endpoint behaves the same way). The row is marked with a `deleted_at` timestamp and hidden from the dashboard API's list, detail, and lookup endpoints, which return 404 for soft-deleted dashboards. The embedded-dashboard iframe URL (`/embedded/<uuid>`) keeps rendering because it reads only `embedded.allowed_domains` and `embedded.dashboard_id` (the FK column) without dereferencing the parent dashboard; the frontend's subsequent dashboard-API fetch is what sees the 404 and surfaces "dashboard not found" to the user.
**New endpoint** — `POST /api/v1/dashboard/<uuid>/restore` clears `deleted_at` and returns the dashboard to active state. Requires `can_write on Dashboard` and editorship of the row (or admin). Soft-deleted dashboards can also be surfaced in the list endpoint via the new `dashboard_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to dashboards they can edit — the same audience that can restore them.
**New endpoint** — `POST /api/v1/dashboard/<uuid>/restore` clears `deleted_at` and returns the dashboard to active state. Requires `can_write on Dashboard` and ownership of the row (or admin). Soft-deleted dashboards can also be surfaced in the list endpoint via the new `dashboard_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to dashboards they own — the same audience that can restore them.
**Permissions migration:** existing role grants of `can_write on Dashboard` cover the new restore endpoint automatically; no role migration is required.
@@ -489,7 +376,7 @@ The partial-index replacement is dialect-dependent: PostgreSQL uses a native `WH
**Slug semantics:** on PostgreSQL and MySQL 8.0.13+, the slug of a soft-deleted dashboard is **free for reuse**. A new active dashboard can claim it immediately. Restoring a soft-deleted dashboard whose slug has since been claimed returns **422 with a clean error** (`DashboardSlugConflictError`) — rename one of the dashboards and retry; the restore is not silently rejected by a database-level constraint violation.
**Importer behavior:** importing a dashboard YAML whose UUID matches an existing **soft-deleted** dashboard is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dashboard imported without `overwrite=true` is returned unchanged (the import never mutates it), but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dashboard's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all pre-deletion relationship rows (`dashboard_slices` junctions, editor/viewer subjects, tags). Callers whose imports must never mutate existing state should treat bundles that may contain previously deleted UUIDs accordingly. The operation is permission-gated: it requires `can_write` and editorship of the deleted row (or admin) — non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
**Importer behavior:** importing a dashboard YAML whose UUID matches an existing **soft-deleted** dashboard is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dashboard imported without `overwrite=true` is returned unchanged (the import never mutates it), but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dashboard's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all pre-deletion relationship rows (`dashboard_slices` junctions, role grants, owners, tags) — including role grants that were implicitly revoked by the deletion. Callers whose imports must never mutate existing state should treat bundles that may contain previously deleted UUIDs accordingly. The operation is permission-gated: it requires `can_write` and ownership of the deleted row (or admin) — non-owners get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
### Granular Export Controls

View File

@@ -135,7 +135,7 @@ Superset sends an HTTP POST with `Content-Type: application/json`:
}
```
When a report includes file attachments (CSV, Excel, PDF, or PNG screenshots), the request is sent as `multipart/form-data` instead. In that case, each top-level payload field (`name`, `text`, `description`, `url`) becomes its own form field, and nested structures like `header` are serialized as a JSON-encoded string in their own field. Every attachment is added as a repeated form field named `files`:
When a report includes file attachments (CSV, PDF, or PNG screenshots), the request is sent as `multipart/form-data` instead. In that case, each top-level payload field (`name`, `text`, `description`, `url`) becomes its own form field, and nested structures like `header` are serialized as a JSON-encoded string in their own field. Every attachment is added as a repeated form field named `files`:
```
POST /webhook HTTP/1.1
@@ -287,10 +287,8 @@ to specify on behalf of which username to render the dashboards. In general, das
are not accessible to unauthorized requests, that is why the worker needs to take over credentials
of an existing user to take a snapshot.
By default, Alerts and Reports are executed as a physical user represented by the alert/report editors
(giving priority to the last modifier, then the creator, then the first direct user editor, then a
deterministic user from editor roles or groups). To use a fixed user account, just change the config
as follows (`admin` in this example):
By default, Alerts and Reports are executed as the owner of the alert/report object. To use a fixed user account,
just change the config as follows (`admin` in this example):
```python
from superset.tasks.types import FixedExecutor

View File

@@ -139,10 +139,6 @@ from superset.tasks.types import FixedExecutor
THUMBNAIL_EXECUTORS = [FixedExecutor("admin")]
```
When using `ExecutorType.EDITOR`, thumbnails are rendered as a physical user represented by
the dashboard or chart editors. Superset prioritizes the last modifier, then the creator,
then the first direct user editor, then a deterministic user from editor roles or groups.
For this feature you will need a cache system and celery workers. All thumbnails are stored on cache
and are processed asynchronously by the workers.
@@ -183,10 +179,10 @@ WEBDRIVER_BASEURL = "https://superset.company.com"
To control which user account is used for rendering thumbnails and warming up caches, configure
`THUMBNAIL_EXECUTORS` and `CACHE_WARMUP_EXECUTORS`. Each accepts a list of executor types (which
resolve to an editor, creator, modifier, or the currently-logged-in user) and/or a
`FixedExecutor` pinned to a specific username. By default, thumbnails render as the current user
(`ExecutorType.CURRENT_USER`) and cache warmup uses editor-based execution
(`ExecutorType.EDITOR`) where executor-based cache warmup is used.
resolve to an owner, creator, modifier, or the currently-logged-in user) and/or a `FixedExecutor`
pinned to a specific username. By default, thumbnails render as the current user
(`ExecutorType.CURRENT_USER`) and cache warmup runs as the chart/dashboard owner
(`ExecutorType.OWNER`).
To force both to run as a dedicated service account (`admin` in this example):

View File

@@ -529,7 +529,6 @@ MCP_RESPONSE_SIZE_CONFIG = {
"enabled": True,
"token_limit": 25000,
"warn_threshold_pct": 80,
"max_list_items": 100,
"excluded_tools": [
"health_check",
"get_chart_preview",
@@ -544,7 +543,6 @@ MCP_RESPONSE_SIZE_CONFIG = {
| `enabled` | `True` | Enable response size checking |
| `token_limit` | `25000` | Maximum estimated token count per response |
| `warn_threshold_pct` | `80` | Warn when response exceeds this percentage of the limit |
| `max_list_items` | `100` | Cap on list-field length (e.g. `charts`, `native_filters`) applied to the `get_*_info` tools before falling back to more aggressive truncation. Raised from a hardcoded 30 in earlier versions; the higher default only keeps more data before the same token-budget fallback kicks in, so it's not a breaking change, but tenants that tuned workflows around the old 30-item cap should lower this value explicitly. |
| `excluded_tools` | See above | Tools exempt from size checking (e.g., tools that return URLs, not data) |
### Caching

View File

@@ -60,9 +60,9 @@ There are two approaches to making dashboards publicly accessible:
**Option 2: Dashboard-level access (selective control)**
1. Set `PUBLIC_ROLE_LIKE = "Public"` in `superset_config.py`
2. Add the `'ENABLE_VIEWERS': True` [Feature Flag](/admin-docs/configuration/feature-flags)
3. Edit each dashboard's properties and add the "Public" role subject as a viewer
4. Only published dashboards with the Public role subject explicitly assigned are visible to anonymous users
2. Add the `'DASHBOARD_RBAC': True` [Feature Flag](/admin-docs/configuration/feature-flags)
3. Edit each dashboard's properties and add the "Public" role
4. Only dashboards with the Public role explicitly assigned are visible to anonymous users
See the [Public role documentation](/admin-docs/security/#public) for more details.

View File

@@ -176,7 +176,7 @@ https://superset.apache.org/admin-docs/configuration/configuring-superset/#rotat
### **Appendix C: Secrets Register and Rotation Schedule**
`SUPERSET_SECRET_KEY` is not the only security-critical secret in a Superset deployment. Maintain an inventory of all such secrets, store each in a secrets manager (not in `superset_config.py` or version control), assign a responsible maintainer, and rotate them on a defined cadence as well as after any suspected compromise.
`SUPERSET_SECRET_KEY` is not the only security-critical secret in a Superset deployment. Maintain an inventory of all such secrets, store each in a secrets manager (not in `superset_config.py` or version control), assign an owner, and rotate them on a defined cadence as well as after any suspected compromise.
| Secret | Purpose | Risk if leaked | Suggested rotation |
|---|---|---|---|

View File

@@ -91,24 +91,24 @@ PUBLIC_ROLE_LIKE = "Public"
# PUBLIC_ROLE_LIKE = "Gamma"
```
If you prefer to manually configure the Public role's permissions, or use subject-based
dashboard viewer assignments, you do not need to set `PUBLIC_ROLE_LIKE`.
If you prefer to manually configure the Public role's permissions (or use `DASHBOARD_RBAC` to
grant access at the dashboard level), you do not need to set `PUBLIC_ROLE_LIKE`.
**Important notes:**
- **Data access is still required by default:** The Public role only grants UI/API permissions.
You must also grant access to specific datasets necessary to view a dashboard. As with other
roles, this can be done in two ways:
- **Data access is still required:** The Public role only grants UI/API permissions. You must
also grant access to specific datasets necessary to view a dashboard. As with other roles,
this can be done in two ways:
- **Dataset-based access:** Dashboards only appear in the list and are accessible if the user
has permission to at least one of their datasets. Grant dataset access by editing the Public
role in the Superset UI (Menu -> Security -> List Roles -> Public) and adding the relevant
data sources. All published dashboards using those datasets become visible.
- **Without `DASHBOARD_RBAC`:** Dashboards only appear in the list and are accessible if
the user has permission to at least one of their datasets. Grant dataset access by editing
the Public role in the Superset UI (Menu Security List Roles Public) and adding the
relevant data sources. All published dashboards using those datasets become visible.
- **Subject-based dashboard access:** When `ENABLE_VIEWERS` is enabled, anonymous users can
see published dashboards where the "Public" role subject has been explicitly added as a
viewer. If `VIEWER_PROMISCUOUS_MODE` is enabled, viewer access can bypass dataset
permission checks for dashboard rendering.
- **With `DASHBOARD_RBAC` enabled:** Anonymous users will only see dashboards where the
"Public" role has been explicitly added in the dashboard's properties. Dataset permissions
are not required—DASHBOARD_RBAC handles the cascading permissions check. This provides
fine-grained control over which dashboards are publicly visible.
- **Role synchronization:** Built-in role permissions (Admin, Alpha, Gamma, sql_lab, and Public
when `PUBLIC_ROLE_LIKE = "Public"`) are synchronized when you run `superset init`. Any manual
@@ -131,76 +131,10 @@ tables in the **Permissions** dropdown. To select the data sources you want to a
You can then confirm with users assigned to the **Gamma** role that they see the
objects (dashboards and slices) associated with the tables you just extended them.
### Subjects
A **subject** is a unified identity that can be granted access to Superset resources such as
dashboards, charts, and datasets. Subjects come in three types:
- **User** — an individual Superset user
- **Group** — a FAB group
- **Role** — a FAB role (e.g. Alpha, Gamma, or any custom role)
Subjects are used throughout Superset to assign **editors** (who can modify a resource) and
**viewers** (who can view it). For new resource-specific assignments, Superset defaults to
**Users** and **Groups** in subject pickers. Groups are the recommended way to grant access to a
set of people because they represent organizational membership, while roles remain focused on
capability grants such as "can read dashboards" or "can write charts".
Role subjects are still supported for backwards compatibility with features that previously used
roles directly, including Row Level Security role assignments and `DASHBOARD_RBAC`. Existing role
subject assignments continue to be enforced even when roles are not exposed in the default picker
configuration. However, when an administrator edits a subject list, roles are not available as new
dropdown values unless that picker has been configured to expose role subjects. Roles are not
recommended for new subject assignments, because using roles for both permissions and resource
membership couples two separate concerns.
Administrators can control which subject types are available in pickers with
`SUBJECTS_RELATED_TYPES`. By default, this setting exposes only users and groups:
```python
from superset.subjects.types import SubjectType
SUBJECTS_RELATED_TYPES = [
SubjectType.USER,
SubjectType.GROUP,
]
```
Set `SUBJECTS_RELATED_TYPES = None` to expose all subject types, including roles. To expose roles
only for a specific entity, set that entity's override:
```python
from superset.subjects.types import SubjectType
SUBJECTS_RELATED_TYPES = [
SubjectType.USER,
SubjectType.GROUP,
]
# Leave dashboard, chart, and alert/report overrides unset so they inherit
# the users + groups default. Expose roles only in the RLS subject picker for
# compatibility with existing role-based RLS workflows.
SUBJECTS_RELATED_TYPES_RLS = [
SubjectType.USER,
SubjectType.GROUP,
SubjectType.ROLE,
]
```
Available per-entity overrides are:
- `SUBJECTS_RELATED_TYPES_DASHBOARDS` for dashboard editor/viewer pickers
- `SUBJECTS_RELATED_TYPES_CHARTS` for chart editor/viewer pickers
- `SUBJECTS_RELATED_TYPES_RLS` for Row Level Security subject pickers
- `SUBJECTS_RELATED_TYPES_ALERT_REPORTS` for alert/report editor pickers
When an override is set, it replaces `SUBJECTS_RELATED_TYPES` for that picker. When it is `None`,
the picker inherits the global default.
### Dashboard Access Control
Access to dashboards is managed via editors (subjects that have edit permissions to the dashboard).
Non-editor user access can be managed in two ways. Note that dashboards must be published to be
Access to dashboards is managed via owners (users that have edit permissions to the dashboard).
Non-owner user access can be managed in two ways. Note that dashboards must be published to be
visible to other users.
#### Dataset-Based Access (Default)
@@ -212,40 +146,30 @@ to a role (Menu → Security → List Roles).
This is the simplest approach but provides all-or-nothing access based on dataset permissions—
if a user has access to a dataset, they can see all published dashboards using that dataset.
#### Dashboard and Chart-Level Access
#### Dashboard-Level Access (DASHBOARD_RBAC)
For fine-grained control over which dashboards and charts specific users or groups can access,
enable subject-based viewers:
For fine-grained control over which dashboards specific roles can access, enable the
`DASHBOARD_RBAC` feature flag:
```python
FEATURE_FLAGS = {
"ENABLE_VIEWERS": True,
"DASHBOARD_RBAC": True,
}
```
With this enabled, you can assign viewer subjects to each dashboard or chart in its properties.
Users will see resources where they are assigned directly, or indirectly through a group subject.
Role subjects can also be enabled for compatibility, but groups are recommended for new viewer
assignments.
`ENABLE_VIEWERS` can be adopted incrementally. Dashboards and charts with no assigned viewers keep
the existing implicit dataset-access behavior: users who can access the underlying dataset can still
see published dashboards that use that dataset and charts backed by that dataset. Assigning one or
more viewers changes that specific resource to explicit viewer access for non-editors. Removing all
viewers returns the resource to the dataset-based fallback. Explicit Viewers are the preferred model
going forward; the implicit fallback may be deprecated and removed in a later major version.
With this enabled, you can assign specific roles to each dashboard in its properties. Users
will only see dashboards where their role is explicitly added.
**Important considerations:**
- Viewer access uses normal dataset checks unless `VIEWER_PROMISCUOUS_MODE` is enabled
- With `VIEWER_PROMISCUOUS_MODE`, dashboard viewer access can bypass dataset-level checks for
charts and datasets in that dashboard
- Dashboards and charts without assigned viewers fall back to dataset-based access
- Dashboard access **bypasses** dataset-level checks—granting a role access to a dashboard
implicitly grants read access to all charts and datasets in that dashboard
- Dashboards without any assigned roles fall back to dataset-based access
- The dashboard must still be published to be visible
This feature is particularly useful for:
- Making specific dashboards public while keeping others private
- Granting access to dashboards without exposing the underlying datasets for other uses
- Creating dashboard-specific access patterns that don't align with dataset permissions
- Creating dashboard-specific access patterns that don't align with dataset ownership
### SQL Execution Security Considerations
@@ -377,16 +301,16 @@ based on the roles and permissions that were attributed.
### Row Level Security
Using Row Level Security filters (under the **Security** menu) you can create filters
that are assigned to a particular dataset, as well as a set of subjects.
that are assigned to a particular dataset, as well as a set of roles.
If you want members of the Finance team to only have access to
rows where `department = "finance"`, you could:
- Create a Row Level Security filter with that clause (`department = "finance"`)
- Then assign the clause to the **Finance** role subject and the dataset it applies to
- Then assign the clause to the **Finance** role and the dataset it applies to
The **clause** field, which can contain arbitrary text, is then added to the generated
SQL statement's WHERE clause. So you could even do something like create a filter
for the last 30 days and apply it to a specific subject, with a clause
for the last 30 days and apply it to a specific role, with a clause
like `date_field > DATE_SUB(NOW(), INTERVAL 30 DAY)`. It can also support
multiple conditions: `client_id = 6` AND `advertiser="foo"`, etc.
@@ -397,14 +321,12 @@ RLS clauses also support **Jinja templating** when `ENABLE_TEMPLATE_PROCESSING`
There are two types of RLS filters:
- **Regular** — The filter clause is applied when the querying user matches one of the
subjects assigned to the filter. Use this to restrict what specific users, roles, or groups
can see.
- **Base** — The filter clause is applied to **all** users _except_ those matching the assigned
subjects. Use this to define a default restriction that privileged subjects (e.g. the Admin
role subject) are exempt from. For example, a Base filter with clause `1 = 0` and the Admin
role subject would hide all rows from everyone except Admin - useful as a deny-by-default
baseline.
- **Regular** — The filter clause is applied when the querying user belongs to one of the
roles assigned to the filter. Use this to restrict what specific roles can see.
- **Base** — The filter clause is applied to **all** users _except_ those in the assigned
roles. Use this to define a default restriction that privileged roles (e.g. Admin) are
exempt from. For example, a Base filter with clause `1 = 0` and the Admin role would
hide all rows from everyone except Admin — useful as a deny-by-default baseline.
#### Group Keys and Filter Combination
@@ -494,20 +416,21 @@ GET /api/v1/rowlevelsecurity/
GET /api/v1/rowlevelsecurity/?q=(filters:!((col:tables,opr:rel_m_m,value:<dataset_id>)))
```
**Filter RLS rules by subject:**
**Filter RLS rules by role:**
```
GET /api/v1/rowlevelsecurity/?q=(filters:!((col:subjects,opr:rel_m_m,value:<subject_id>)))
GET /api/v1/rowlevelsecurity/?q=(filters:!((col:roles,opr:rel_m_m,value:<role_id>)))
```
**View details of a specific rule** (including clause, assigned datasets, and subjects):
**View details of a specific rule** (including clause, assigned datasets, and roles):
```
GET /api/v1/rowlevelsecurity/<id>
```
The response includes the filter's `name`, `filter_type` (Regular or Base), `clause`,
`group_key`, assigned `tables` (with id, schema, and table\_name), and assigned `subjects`.
`group_key`, assigned `tables` (with id, schema, and table\_name), and assigned `roles`
(with id and name).
:::tip Auditing RLS for virtual datasets
To find all RLS rules that could affect a particular virtual dataset, query the list

View File

@@ -26,7 +26,7 @@ import { StoryWithControls } from '../../../src/components/StorybookWrapper';
# MetadataBar
MetadataBar displays a row of metadata items (SQL info, editors, last modified, tags, dashboards, etc.) that collapse responsively based on available width.
MetadataBar displays a row of metadata items (SQL info, owners, last modified, tags, dashboards, etc.) that collapse responsively based on available width.
## Live Example
@@ -43,9 +43,9 @@ MetadataBar displays a row of metadata items (SQL info, editors, last modified,
title: "Click to view query"
},
{
type: "editor",
type: "owner",
createdBy: "Jane Smith",
editors: [
owners: [
"John Doe",
"Mary Wilson"
],
@@ -104,9 +104,9 @@ function Demo() {
const items = [
{ type: 'sql', title: 'Click to view query' },
{
type: 'editor',
type: 'owner',
createdBy: 'Jane Smith',
editors: ['John Doe', 'Mary Wilson'],
owners: ['John Doe', 'Mary Wilson'],
createdOn: 'a week ago',
},
{
@@ -125,7 +125,7 @@ function Demo() {
```tsx live
function MinimalMetadata() {
const items = [
{ type: 'editor', createdBy: 'Admin', editors: ['Admin'], createdOn: 'yesterday' },
{ type: 'owner', createdBy: 'Admin', owners: ['Admin'], createdOn: 'yesterday' },
{ type: 'lastModified', value: '2 hours ago', modifiedBy: 'Admin' },
];
return <MetadataBar items={items} />;
@@ -138,7 +138,7 @@ function MinimalMetadata() {
function FullMetadata() {
const items = [
{ type: 'sql', title: 'SELECT * FROM ...' },
{ type: 'editor', createdBy: 'Jane Smith', editors: ['Jane Smith', 'John Doe', 'Bob Wilson'], createdOn: '2 weeks ago' },
{ type: 'owner', createdBy: 'Jane Smith', owners: ['Jane Smith', 'John Doe', 'Bob Wilson'], createdOn: '2 weeks ago' },
{ type: 'lastModified', value: '3 days ago', modifiedBy: 'John Doe' },
{ type: 'tags', values: ['production', 'finance', 'quarterly'] },
{ type: 'dashboards', title: 'Used in 12 dashboards' },
@@ -158,7 +158,7 @@ function FullMetadata() {
| `createdBy` | `string` | `"Jane Smith"` | - |
| `modifiedBy` | `string` | `"Jane Smith"` | - |
| `description` | `string` | `"To preview the list of dashboards go to More settings."` | - |
| `items` | `any` | `[{"type":"sql","title":"Click to view query"},{"type":"editor","createdBy":"Jane Smith","editors":["John Doe","Mary Wilson"],"createdOn":"a week ago"},{"type":"lastModified","value":"a week ago","modifiedBy":"Jane Smith"},{"type":"tags","values":["management","research","poc"]},{"type":"dashboards","title":"Added to 3 dashboards","description":"To preview the list of dashboards go to More settings."}]` | Items displayed in the metadata bar. The `editor` type shows editors/creators. |
| `items` | `any` | `[{"type":"sql","title":"Click to view query"},{"type":"owner","createdBy":"Jane Smith","owners":["John Doe","Mary Wilson"],"createdOn":"a week ago"},{"type":"lastModified","value":"a week ago","modifiedBy":"Jane Smith"},{"type":"tags","values":["management","research","poc"]},{"type":"dashboards","title":"Added to 3 dashboards","description":"To preview the list of dashboards go to More settings."}]` | - |
## Import

View File

@@ -128,7 +128,7 @@ from superset.dashboards.commands.create import CreateDashboardCommand
def test_create_dashboard_success():
properties = {
"dashboard_title": "Test Dashboard",
"editors": [1]
"owners": [1]
}
command = CreateDashboardCommand(properties)
@@ -275,11 +275,9 @@ def handle_validation_error(error):
from typing import List, Optional, Dict, Any
from superset.models.dashboard import Dashboard
def get_dashboards_by_editor(editor_id: int) -> List[Dashboard]:
"""Get all dashboards editable by a specific subject"""
return db.session.query(Dashboard).filter(
Dashboard.editors.any(id=editor_id)
).all()
def get_dashboards_by_owner(owner_id: int) -> List[Dashboard]:
"""Get all dashboards owned by a specific user"""
return db.session.query(Dashboard).filter_by(owner_id=owner_id).all()
def create_dashboard(properties: Dict[str, Any]) -> Optional[Dashboard]:
"""Create a new dashboard with the given properties"""

View File

@@ -153,10 +153,10 @@ class DashboardDAO:
return db.session.query(Dashboard).filter_by(slug=slug).first()
@classmethod
def find_by_editor(cls, editor_id: int) -> List[Dashboard]:
"""Find all dashboards editable by a subject"""
return db.session.query(Dashboard).filter(
Dashboard.editors.any(id=editor_id)
def find_by_owner(cls, owner_id: int) -> List[Dashboard]:
"""Find all dashboards owned by a user"""
return db.session.query(Dashboard).filter_by(
created_by_fk=owner_id
).all()
@classmethod

View File

@@ -120,7 +120,7 @@ SMTP_MAIL_FROM = "superset@localhost"
# Must match where your frontend is running
WEBDRIVER_BASEURL = "http://localhost:9000/"
ALERT_REPORTS_EXECUTORS = [ExecutorType.EDITOR]
ALERT_REPORTS_EXECUTE_AS = [ExecutorType.OWNER]
FEATURE_FLAGS = {
"ALERT_REPORTS": True,

View File

@@ -181,23 +181,6 @@ value in milliseconds in the JSON Metadata field:
Here, the entire dashboard will refresh at once if periodic refresh is on. The stagger time of 2.5
seconds is ignored.
The manual **Refresh dashboard** button can also stagger its chart requests, controlled by the
`SUPERSET_DASHBOARD_MANUAL_REFRESH_STAGGER_MS` server config in `superset_config.py`. This defaults
to `0`, which preserves the original behavior where every chart request fires at the same time when
the button is clicked. To opt in to staggering, set a positive number of milliseconds; the window
then becomes the larger of this value and the per-dashboard `stagger_time` metadata, which itself
defaults to `5000` milliseconds when it is not set explicitly. Setting `stagger_refresh` to `false`
in a dashboard's JSON Metadata also disables staggering on the manual-refresh path, so its charts
fire all at once even when the server config is positive. If the backend does not provide a value at
all, for example an older backend that predates this config or a value explicitly set to `None`, the
frontend falls back to a built-in default of `5000` milliseconds.
```python
# superset_config.py
# 0 (the default) fires every chart at once; set a positive value to stagger.
SUPERSET_DASHBOARD_MANUAL_REFRESH_STAGGER_MS = 0
```
**Why does flask fab or superset freeze/hang/not responding when started (my home directory is
NFS mounted)?**

View File

@@ -211,8 +211,8 @@ slices and dashboards of your own.
### Manage access to Dashboards
Access to dashboards is managed via editors and permissions. Non-editor access can be controlled
through dataset permissions, or through dashboard viewers when `ENABLE_VIEWERS` is enabled.
Access to dashboards is managed via owners and permissions. Non-owner access can be controlled
through dataset permissions or dashboard-level roles (using the `DASHBOARD_RBAC` feature flag).
For detailed information on configuring dashboard access, see the
[Dashboard Access Control](/admin-docs/security/#dashboard-access-control) section in the
@@ -228,9 +228,7 @@ title of your dashboard.
<img src={useBaseUrl("/img/tutorial/publish_button_dashboard.png" )} />
:::warning
Draft dashboards are only visible to dashboard editors and admins. Published dashboards are visible
to users with access to the underlying datasets, or, when `ENABLE_VIEWERS` is enabled, to users
who have viewer access directly or through an assigned role or group subject.
Draft dashboards are only visible to the dashboard owners and admins. Published dashboards are visible to all users with access to the underlying datasets or if RBAC is enabled, to the roles that have been granted access to the dashboard.
:::
### Mark a Dashboard as Favorite

View File

@@ -63,9 +63,9 @@
"@swc/core": "^1.15.43",
"antd": "^6.5.0",
"baseline-browser-mapping": "^2.10.40",
"caniuse-lite": "^1.0.30001803",
"docusaurus-plugin-openapi-docs": "^5.1.1",
"docusaurus-theme-openapi-docs": "^5.1.1",
"caniuse-lite": "^1.0.30001799",
"docusaurus-plugin-openapi-docs": "^5.1.0",
"docusaurus-theme-openapi-docs": "^5.1.0",
"js-yaml": "^5.2.0",
"json-bigint": "^1.0.0",
"prism-react-renderer": "^2.4.1",
@@ -90,15 +90,15 @@
"@types/js-yaml": "^4.0.9",
"@types/react": "^19.1.8",
"@typescript-eslint/eslint-plugin": "^8.59.3",
"@typescript-eslint/parser": "^8.63.0",
"@typescript-eslint/parser": "^8.61.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react": "^7.37.5",
"globals": "^17.7.0",
"prettier": "^3.9.5",
"prettier": "^3.9.1",
"typescript": "~6.0.3",
"typescript-eslint": "^8.63.0",
"typescript-eslint": "^8.62.0",
"webpack": "^5.108.2"
},
"browserslist": {

View File

@@ -182,13 +182,6 @@
"description": "Allows users to add a superset:// DB that can query across databases. Experimental with potential security/performance risks. See SUPERSET_META_DB_LIMIT.",
"docs": "https://superset.apache.org/user-docs/databases/supported/superset-meta-database"
},
{
"name": "ENABLE_VIEWERS",
"default": false,
"lifecycle": "testing",
"description": "Subject-based viewer access control for dashboards and charts. When enabled, resources can have explicit viewer Subject assignments.",
"category": "security"
},
{
"name": "ESTIMATE_QUERY_COST",
"default": false,
@@ -263,6 +256,14 @@
"description": "Enables CSS Templates in Settings menu and dashboard forms",
"category": "runtime_config"
},
{
"name": "DASHBOARD_RBAC",
"default": false,
"lifecycle": "stable",
"description": "Role-based access control for dashboards",
"docs": "https://superset.apache.org/docs/using-superset/creating-your-first-dashboard",
"category": "runtime_config"
},
{
"name": "DASHBOARD_VIRTUALIZATION",
"default": true,
@@ -400,6 +401,12 @@
}
],
"deprecated": [
{
"name": "AVOID_COLORS_COLLISION",
"default": true,
"lifecycle": "deprecated",
"description": "Avoid color collisions in charts by using distinct colors"
},
{
"name": "DRILL_TO_DETAIL",
"default": true,

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -1,108 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="76.919907mm"
height="26.149284mm"
viewBox="0 0 76.919907 26.149284"
version="1.1"
id="svg855"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
sodipodi:docname="georchestra-logo.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview857"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
fit-margin-top="2"
fit-margin-right="2"
fit-margin-bottom="2"
fit-margin-left="2"
inkscape:zoom="4.0920134"
inkscape:cx="120.47859"
inkscape:cy="47.775992"
inkscape:window-width="2400"
inkscape:window-height="1271"
inkscape:window-x="2391"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs852" />
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-73.097929,-135.79112)">
<g
id="g53648"
transform="matrix(1.1048988,0,0,1.1048988,-54.141498,96.632368)">
<path
id="path164290-3-6-4"
style="fill:#85127e;fill-opacity:0.4;stroke-width:0.606168;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
d="m 126.99257,37.251152 a 10.023325,10.023325 0 0 0 -10.0231,10.023355 10.023325,10.023325 0 0 0 10.0231,10.023083 10.023325,10.023325 0 0 0 9.62187,-7.232846 3.0318022,3.5371027 0 0 1 -2.01469,-3.330589 3.0318022,3.5371027 0 0 1 1.70117,-3.175791 10.023325,10.023325 0 0 0 -9.30835,-6.307212 z"
inkscape:export-filename="C:\Users\Agathe\Desktop\georchestra_univers.png"
inkscape:export-xdpi="200"
inkscape:export-ydpi="200" />
<path
id="path20251-5-9-8-2"
style="fill:#85127e;fill-opacity:1;stroke-width:0.337452;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
d="m 138.54037,41.724815 a 4.988567,4.988567 0 0 0 -4.9887,4.988712 4.988567,4.988567 0 0 0 4.9887,4.98844 4.988567,4.988567 0 0 0 4.98844,-4.98844 4.988567,4.988567 0 0 0 -4.98844,-4.988712 z m 0,2.683748 a 2.3048444,2.3048444 0 0 1 2.30472,2.304964 2.3048444,2.3048444 0 0 1 -2.30472,2.304704 2.3048444,2.3048444 0 0 1 -2.30496,-2.304704 2.3048444,2.3048444 0 0 1 2.30496,-2.304964 z"
inkscape:export-filename="C:\Users\Agathe\Desktop\georchestra_univers.png"
inkscape:export-xdpi="200"
inkscape:export-ydpi="200" />
<g
aria-label="ge rchestra"
id="text10089-26-6-9"
style="font-size:9.62958px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto, Normal';letter-spacing:0px;fill:#85127e;stroke-width:0.601849">
<path
d="m 123.84851,51.576041 q -0.66682,0 -1.14556,-0.177819 -0.47875,-0.177819 -0.76941,-0.475324 -0.29067,-0.297505 -0.40351,-0.653142 l 1.11478,-0.270148 q 0.0752,0.140203 0.21886,0.287245 0.14362,0.150463 0.38641,0.249631 0.24621,0.102588 0.61895,0.102588 0.52662,0 0.872,-0.25305 0.34537,-0.25305 0.34537,-0.817283 v -0.995103 h -0.0615 q -0.0957,0.191498 -0.28041,0.393254 -0.18123,0.201756 -0.48216,0.33854 -0.2975,0.136784 -0.74889,0.136784 -0.60527,0 -1.09769,-0.283827 -0.489,-0.287246 -0.77967,-0.854899 -0.28724,-0.571072 -0.28724,-1.42939 0,-0.865158 0.28724,-1.460167 0.29067,-0.59843 0.78309,-0.906193 0.49242,-0.311183 1.09769,-0.311183 0.46165,0 0.75915,0.157301 0.30092,0.153882 0.47874,0.372736 0.17782,0.215434 0.27015,0.406932 h 0.0684 v -0.868577 h 1.2208 v 5.337987 q 0,0.659982 -0.32144,1.09769 -0.32144,0.441128 -0.87884,0.656563 -0.55739,0.218854 -1.26525,0.218854 z m 0.0103,-3.104992 q 0.39325,0 0.67024,-0.191498 0.27699,-0.191497 0.42061,-0.550555 0.14362,-0.359057 0.14362,-0.861738 0,-0.495841 -0.14362,-0.868577 -0.1402,-0.372736 -0.41719,-0.577911 -0.27357,-0.208596 -0.67366,-0.208596 -0.41377,0 -0.69076,0.215435 -0.27699,0.215434 -0.41719,0.59159 -0.1402,0.372736 -0.1402,0.848059 0,0.482163 0.1402,0.84464 0.14362,0.359058 0.42061,0.560814 0.28041,0.198337 0.68734,0.198337 z"
id="path1811" />
<path
d="m 129.91145,49.620032 q -0.78993,0 -1.36442,-0.328281 -0.57107,-0.3317 -0.87883,-0.936969 -0.30777,-0.608688 -0.30777,-1.43281 0,-0.810444 0.30777,-1.422552 0.31118,-0.615527 0.86857,-0.957486 0.5574,-0.345379 1.30971,-0.345379 0.48558,0 0.91645,0.157301 0.43429,0.153882 0.76599,0.478743 0.33512,0.324862 0.52662,0.827542 0.19149,0.499261 0.19149,1.19002 v 0.379575 h -4.30527 v -0.834382 h 3.11867 q -0.003,-0.355638 -0.15388,-0.632625 -0.15046,-0.280406 -0.42061,-0.441127 -0.26673,-0.160721 -0.62236,-0.160721 -0.37958,0 -0.66683,0.184658 -0.28724,0.181238 -0.44796,0.478743 -0.1573,0.294085 -0.16072,0.646303 v 0.728374 q 0,0.458226 0.16756,0.786507 0.16756,0.324862 0.46848,0.499261 0.30093,0.17098 0.70444,0.17098 0.27015,0 0.489,-0.07523 0.21885,-0.07865 0.37957,-0.229113 0.16073,-0.150462 0.2428,-0.372736 l 1.15582,0.129944 q -0.10943,0.458226 -0.41719,0.800186 -0.30435,0.33854 -0.77967,0.526617 -0.47532,0.184658 -1.08743,0.184658 z"
id="path1813" />
<path
d="m 145.2449,49.517445 v -5.252498 h 1.20027 v 0.875416 h 0.0547 q 0.14362,-0.454806 0.49242,-0.701017 0.35222,-0.24963 0.8036,-0.24963 0.10259,0 0.22912,0.01026 0.12994,0.0068 0.21543,0.02394 v 1.138725 q -0.0787,-0.02736 -0.24963,-0.04787 -0.16756,-0.02394 -0.32486,-0.02394 -0.33854,0 -0.60869,0.147043 -0.26673,0.143623 -0.42061,0.400092 -0.15388,0.25647 -0.15388,0.59159 v 3.087895 z"
style="font-weight:600;font-family:inter;-inkscape-font-specification:'inter Semi-Bold'"
id="path1815" />
<path
d="m 151.13685,49.620032 q -0.7865,0 -1.35074,-0.345379 -0.56081,-0.345379 -0.86516,-0.954067 -0.30092,-0.612107 -0.30092,-1.408873 0,-0.800185 0.30776,-1.412293 0.30777,-0.615527 0.86858,-0.957486 0.56423,-0.345379 1.33364,-0.345379 0.63947,0 1.13189,0.235952 0.49584,0.232532 0.78993,0.659982 0.29408,0.424029 0.33512,0.991682 h -1.18318 q -0.0718,-0.379575 -0.34196,-0.632625 -0.26673,-0.256469 -0.7147,-0.256469 -0.37957,0 -0.66682,0.205175 -0.28725,0.201756 -0.44797,0.581331 -0.1573,0.379575 -0.1573,0.909613 0,0.536876 0.1573,0.92329 0.1573,0.382995 0.44113,0.59159 0.28725,0.205176 0.67366,0.205176 0.27357,0 0.489,-0.102588 0.21886,-0.106007 0.3659,-0.304344 0.14704,-0.198336 0.20176,-0.482163 h 1.18318 q -0.0445,0.557394 -0.32829,0.988263 -0.28382,0.42745 -0.77282,0.670241 -0.48901,0.239371 -1.14899,0.239371 z"
style="font-weight:600;font-family:inter;-inkscape-font-specification:'inter Semi-Bold'"
id="path1817" />
<path
d="m 155.64387,46.439809 v 3.077636 h -1.2379 v -7.003331 h 1.21054 v 2.643347 h 0.0615 q 0.18466,-0.444547 0.57107,-0.701017 0.38984,-0.259889 0.99169,-0.259889 0.54713,0 0.95406,0.229113 0.40694,0.229113 0.62921,0.67024 0.22569,0.441128 0.22569,1.077173 v 3.344364 h -1.23789 v -3.152867 q 0,-0.530037 -0.27357,-0.824122 -0.27015,-0.297505 -0.75915,-0.297505 -0.32828,0 -0.58817,0.143623 -0.25647,0.140203 -0.40351,0.406932 -0.14362,0.266728 -0.14362,0.646303 z"
style="font-weight:600;font-family:inter;-inkscape-font-specification:'inter Semi-Bold'"
id="path1819" />
<path
d="m 162.6335,49.620032 q -0.78992,0 -1.36441,-0.328281 -0.57108,-0.3317 -0.87884,-0.936969 -0.30776,-0.608688 -0.30776,-1.43281 0,-0.810444 0.30776,-1.422552 0.31118,-0.615527 0.86858,-0.957486 0.55739,-0.345379 1.3097,-0.345379 0.48558,0 0.91645,0.157301 0.43429,0.153882 0.76599,0.478743 0.33512,0.324862 0.52662,0.827542 0.1915,0.499261 0.1915,1.19002 v 0.379575 h -4.30527 v -0.834382 h 3.11867 q -0.003,-0.355638 -0.15388,-0.632625 -0.15047,-0.280406 -0.42061,-0.441127 -0.26673,-0.160721 -0.62237,-0.160721 -0.37958,0 -0.66682,0.184658 -0.28725,0.181238 -0.44797,0.478743 -0.1573,0.294085 -0.16072,0.646303 v 0.728374 q 0,0.458226 0.16756,0.786507 0.16756,0.324862 0.46849,0.499261 0.30092,0.17098 0.70443,0.17098 0.27015,0 0.489,-0.07523 0.21886,-0.07865 0.37958,-0.229113 0.16072,-0.150462 0.24279,-0.372736 l 1.15582,0.129944 q -0.10942,0.458226 -0.41719,0.800186 -0.30434,0.33854 -0.77967,0.526617 -0.47532,0.184658 -1.08743,0.184658 z"
style="font-weight:600;font-family:inter;-inkscape-font-specification:'inter Semi-Bold'"
id="path1821" />
<path
d="m 170.16686,45.653302 -1.12846,0.123106 q -0.0479,-0.17098 -0.16756,-0.321442 -0.11627,-0.150462 -0.31461,-0.242792 -0.19833,-0.09233 -0.48558,-0.09233 -0.38641,0 -0.64972,0.167561 -0.25989,0.16756 -0.25647,0.434288 -0.003,0.229113 0.16756,0.372736 0.1744,0.143623 0.57449,0.235952 l 0.89593,0.191497 q 0.74548,0.160721 1.10795,0.50952 0.3659,0.348799 0.36932,0.913032 -0.003,0.495841 -0.29067,0.875416 -0.28382,0.376156 -0.78992,0.588171 -0.5061,0.212014 -1.16267,0.212014 -0.96432,0 -1.55249,-0.403512 -0.58817,-0.406932 -0.70102,-1.131886 l 1.20712,-0.116266 q 0.0821,0.355638 0.3488,0.536876 0.26673,0.181239 0.69417,0.181239 0.44113,0 0.70786,-0.181239 0.27015,-0.181238 0.27015,-0.447966 0,-0.225694 -0.1744,-0.372736 -0.17098,-0.147043 -0.53346,-0.225694 l -0.89593,-0.188077 q -0.75573,-0.157302 -1.11821,-0.530038 -0.36248,-0.376155 -0.35906,-0.950647 -0.003,-0.485582 0.26331,-0.84122 0.27015,-0.359058 0.74889,-0.553975 0.48217,-0.198336 1.11137,-0.198336 0.92329,0 1.45333,0.393253 0.53346,0.393254 0.65998,1.063495 z"
style="font-weight:600;font-family:inter;-inkscape-font-specification:'inter Semi-Bold'"
id="path1823" />
<path
d="m 173.925,44.264947 v 0.957486 h -3.01951 v -0.957486 z m -2.27403,-1.258411 h 1.23789 v 4.931056 q 0,0.24963 0.0752,0.382994 0.0787,0.129945 0.20518,0.177819 0.12652,0.04788 0.2804,0.04788 0.11627,0 0.21202,-0.0171 0.0992,-0.0171 0.15046,-0.03078 l 0.2086,0.967746 q -0.0992,0.0342 -0.28383,0.07523 -0.18124,0.04104 -0.44455,0.04787 -0.46506,0.01368 -0.8378,-0.140203 -0.37274,-0.157302 -0.59159,-0.485583 -0.21543,-0.328281 -0.21201,-0.820703 z"
style="font-weight:600;font-family:inter;-inkscape-font-specification:'inter Semi-Bold'"
id="path1825" />
<path
d="m 174.96112,49.517445 v -5.252498 h 1.20028 v 0.875416 h 0.0547 q 0.14363,-0.454806 0.49242,-0.701017 0.35222,-0.24963 0.80361,-0.24963 0.10259,0 0.22911,0.01026 0.12995,0.0068 0.21544,0.02394 v 1.138725 q -0.0787,-0.02736 -0.24963,-0.04787 -0.16756,-0.02394 -0.32487,-0.02394 -0.33853,0 -0.60868,0.147043 -0.26673,0.143623 -0.42061,0.400092 -0.15388,0.25647 -0.15388,0.59159 v 3.087895 z"
style="font-weight:600;font-family:inter;-inkscape-font-specification:'inter Semi-Bold'"
id="path1827" />
<path
d="m 180.19309,49.623452 q -0.49926,0 -0.89936,-0.177819 -0.39667,-0.181238 -0.6292,-0.533457 -0.22911,-0.352218 -0.22911,-0.868577 0,-0.444547 0.16414,-0.735213 0.16414,-0.290665 0.44796,-0.465065 0.28383,-0.174399 0.63947,-0.263309 0.35905,-0.09233 0.74205,-0.133364 0.46164,-0.04787 0.74889,-0.08549 0.28725,-0.04104 0.41719,-0.123105 0.13337,-0.08549 0.13337,-0.263309 v -0.02052 q 0,-0.386415 -0.22912,-0.59843 -0.22911,-0.212014 -0.65998,-0.212014 -0.45481,0 -0.72153,0.198336 -0.26331,0.198337 -0.35564,0.468485 L 178.6064,45.64646 q 0.13678,-0.478743 0.45138,-0.800185 0.3146,-0.324862 0.76941,-0.485583 0.45481,-0.16414 1.00536,-0.16414 0.37958,0 0.75573,0.08891 0.37616,0.08891 0.68734,0.294085 0.31118,0.201757 0.49926,0.550555 0.1915,0.348799 0.1915,0.871997 v 3.515344 h -1.19002 V 48.79591 h -0.041 q -0.11284,0.218854 -0.31802,0.410351 -0.20175,0.188078 -0.50952,0.304344 -0.30434,0.112847 -0.71469,0.112847 z m 0.32144,-0.909612 q 0.37274,0 0.6463,-0.147043 0.27357,-0.150462 0.42061,-0.396673 0.15047,-0.246211 0.15047,-0.536876 v -0.618947 q -0.0581,0.04787 -0.19834,0.08891 -0.13679,0.04104 -0.30776,0.07181 -0.17098,0.03078 -0.33854,0.05471 -0.16756,0.02394 -0.29067,0.04104 -0.27699,0.03761 -0.49584,0.123105 -0.21886,0.08549 -0.34538,0.239372 -0.12653,0.150462 -0.12653,0.389834 0,0.341959 0.24963,0.516358 0.24964,0.1744 0.63605,0.1744 z"
style="font-weight:600;font-family:inter;-inkscape-font-specification:'inter Semi-Bold'"
id="path1829" />
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="178" height="34" version="1.0" viewBox="27 27 367 70"><switch><foreignObject width="1" height="1" x="0" y="0" requiredExtensions="http://ns.adobe.com/AdobeIllustrator/10.0/"/><g><path d="M221.8 47.9c1.3 4.4 5 7.1 10.9 7.1 6 0 10.1-2.8 10.1-7.9 0-4.2-2.6-6.8-9.9-7.8-4.1-.6-5.5-1.6-5.5-3.6 0-2.2 1.8-3.2 4.5-3.2 3.3 0 5.1 1.5 5.8 4.4l4.2-1.2c-1.1-4.6-4.5-7.2-10.1-7.2-5.5 0-9.1 2.6-9.1 7.5 0 3.5 1.8 6.4 9 7.4 4.7.6 6.2 1.7 6.2 3.9 0 2.4-1.9 3.6-5.1 3.6-3.8 0-6-1.7-7.1-4.7zm-21.1-8.3c.5-4 2.9-6.8 7.1-6.8 3.9 0 6.5 2.4 6.5 6.8zm18.3 1.1c0-7.9-4.4-12.1-11.1-12.1-7.6 0-11.9 5.3-11.9 13.2 0 8 4.3 13.2 12.1 13.2 5.6 0 9.2-2.7 10.5-7l-4.3-1.4c-.9 3.2-3.5 4.1-6.2 4.1-4.9 0-7.1-3.1-7.4-7.2H219zm-44.7 13.8V35.9c2.5 2.4 9.6 12.8 12.4 18.6h4.6V29h-4.5v17.1c-3.3-5.9-7.9-12-12.8-17.1h-4.3v25.5zm-27.5 0V35.9c2.5 2.4 9.6 12.8 12.4 18.6h4.6V29h-4.5v17.1c-3.3-5.9-7.9-12-12.7-17.1h-4.3v25.5zm-27.4-14.9c.5-4 2.9-6.8 7.1-6.8 3.9 0 6.5 2.4 6.5 6.8zm18.2 1.1c0-7.9-4.4-12.1-11.1-12.1-7.6 0-11.9 5.3-11.9 13.2 0 8 4.3 13.2 12.1 13.2 5.6 0 9.2-2.7 10.5-7l-4.3-1.4c-.9 3.2-3.5 4.1-6.2 4.1-4.9 0-7.1-3.1-7.4-7.2h18.3zM101 33.2c3.6 0 5.5 1.7 5.5 4.6 0 2.6-1.6 4.4-5 4.4h-6.1v-9zm4.4 12.3c4.5-1.5 5.9-4.9 5.9-8.2 0-5.2-3.6-8.3-9.8-8.3H90.9v25.5h4.5v-8.3h5.2l1.7 2.8c3 4.9 4.2 5.7 9 5.7v-4.3c-2.5 0-3.4-.8-5.3-3.9zM383.9 71.9c2.8 0 4.8 1.8 4.8 5.1h-9.9c.3-3 2-5.1 5.1-5.1m8.8 6c0-6.1-3.4-9.5-8.8-9.5-6 0-9.4 4.1-9.4 10.3 0 6.1 3.4 10.3 9.7 10.3 4.5 0 7.2-2.1 8.2-5.6l-3.5-1.2c-.6 2-2.3 3-4.7 3-3.5 0-5.1-2.2-5.4-5.2h14v-2.1zm-21.5-15.5H367v26.2h4.2zm-17.4 22.7c-3.3 0-5.3-2.4-5.3-6.4s2.1-6.4 5.3-6.4 5.3 2.4 5.3 6.4c0 3.9-2.1 6.4-5.3 6.4m0 3.8c5.8 0 9.7-3.9 9.7-10.2s-3.9-10.2-9.7-10.2c-5.9 0-9.8 3.9-9.8 10.2.1 6.3 3.9 10.2 9.8 10.2m-22-3.7c-3.3 0-5.4-2.5-5.4-6.5s2.1-6.5 5.4-6.5 5.4 2.5 5.4 6.5c.1 4-2.1 6.5-5.4 6.5M326.4 96V85.5h.2c1.2 2.4 3.7 3.5 6.2 3.5 5 0 8.9-3.9 8.9-10.3s-3.9-10.3-8.9-10.3c-2.5 0-5.1 1.1-6.2 3.5h-.2v-3.1h-4.2V96zm-17.3-10.9c-3.3 0-5.3-2.4-5.3-6.4s2.1-6.4 5.3-6.4 5.3 2.4 5.3 6.4c.1 3.9-2 6.4-5.3 6.4m0 3.8c5.8 0 9.7-3.9 9.7-10.2s-3.9-10.2-9.7-10.2c-5.9 0-9.8 3.9-9.8 10.2.1 6.3 4 10.2 9.8 10.2m-10.8-20.3c-.5-.1-1-.1-1.5-.1-2.5 0-4.4.8-5.4 3.5h-.2v-3.2H287v19.7h4.2v-9.7c0-5.6 2.4-6.1 4.7-6.1.8 0 1.5.1 2.3.2v-4.3zm-17.6 16.2c-3 0-3.4-.9-3.4-6.3v-5.9h5.7v-3.7h-5.7v-4.8H273v4.8h-3.7v3.7h3.7v7c0 7 1.8 9.1 7 9.1 1 0 2.3 0 3.5-.1v-3.8zM259 71.9c2.8 0 4.8 1.8 4.8 5.1h-9.9c.4-3 2.1-5.1 5.1-5.1m8.9 6c0-6.1-3.4-9.5-8.8-9.5-6 0-9.4 4.1-9.4 10.3 0 6.1 3.4 10.3 9.7 10.3 4.5 0 7.2-2.1 8.2-5.6l-3.5-1.2c-.6 2-2.3 3-4.7 3-3.5 0-5.1-2.2-5.4-5.2h14v-2.1zm-3.9-17-9.1 3.2 1.1 3.1 9.1-3.1zm-20 2.2h-4.4c-2.9 5.9-5.5 11.8-7.6 18.6h-.3c-2.1-6.8-4.8-12.7-7.6-18.6h-4.4l-3.1 25.5h4.5l2-17.6c2.4 5.4 4.5 10.9 6.3 16.8h5q2.7-8.7 6.3-16.8l2 17.6h4.5zm-41.2 21.7c-3 0-3.4-.9-3.4-6.3v-5.9h5.7v-3.7h-5.7v-4.8h-4.2v4.8h-3.7v3.7h3.7v7c0 7 1.8 9.1 7 9.1 1 0 2.3 0 3.5-.1v-3.8zm-21.7-12.9c2.8 0 4.8 1.8 4.8 5.1H176c.4-3 2.1-5.1 5.1-5.1m8.9 6c0-6.1-3.4-9.5-8.8-9.5-6 0-9.4 4.1-9.4 10.3 0 6.1 3.4 10.3 9.7 10.3 4.5 0 7.2-2.1 8.2-5.6l-3.5-1.2c-.6 2-2.3 3-4.7 3-3.5 0-5.1-2.2-5.4-5.2h14v-2.1zm-37.3-6c2.8 0 4.8 1.8 4.8 5.1h-9.9c.3-3 2-5.1 5.1-5.1m8.8 6c0-6.1-3.4-9.5-8.8-9.5-6 0-9.3 4.1-9.3 10.3 0 6.1 3.4 10.3 9.7 10.3 4.5 0 7.2-2.1 8.2-5.6l-3.5-1.2c-.6 2-2.3 3-4.7 3-3.5 0-5.1-2.2-5.4-5.2h14v-2.1zm-21.9-15.5h-4.2v26.2h4.2zm-9.1 0h-4.2v26.2h4.2zm-9.4 6.4h-4.2v19.7h4.2zm-2-2.2c1.4 0 2.6-1.2 2.6-2.7 0-1.4-1.2-2.6-2.6-2.6-1.5 0-2.7 1.2-2.7 2.6s1.2 2.7 2.7 2.7m-14.3 22 9-25.5H109l-7.2 21h-.3l-7.2-21h-4.8l9 25.5zM57.4 74.6c-3.7-5.3-6.2-6.9-11.4-7.7v4.9c3.6.5 5.3 2.4 7.6 5.7 6.2 8.9 9.7 12.5 22.3 11.9v-4.7c-9.7.7-13-2.3-18.5-10.1m3.1-2.4c5.3 7.3 8.1 9.8 15.4 9.2v-4.7c-5.4.6-8-2.1-11.7-7.4-4.5-6.4-9.6-9.9-18.2-10.3v4.8c6.1.2 10.1 2.3 14.5 8.4M28.3 88.8h4.9V47.9c-1.5.5-3.4 1.1-4.9 1.8zm13.6-43.2c-1.5.2-3.4.5-4.9.9v42.2h4.9zm26 4.7c0-8.1-6.4-13.5-19-13.5-6.6 0-14.5 1.5-20.6 4v5c5.6-2.5 13.7-4.3 20.6-4.3 9.1 0 14.1 3.1 14.1 8.9 0 3.4-1.7 6.4-4.8 8.3 1.3.7 2.7 1.8 3.8 2.8 3.8-2.4 5.9-6.5 5.9-11.2m-18.4-22c-7 0-14.2 1.5-21.1 3.8v5C36 34.3 43.2 33 49.5 33c14.3 0 22.7 6.5 22.7 16.8 0 6-2.6 11.1-7.5 14.5 1 1.1 2.1 2.4 3 3.5 6.1-4.2 9.4-10.9 9.4-18-.1-13-10.6-21.5-27.6-21.5"/></g></switch></svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -5504,110 +5504,100 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@8.63.0", "@typescript-eslint/eslint-plugin@^8.59.3":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz#0d85d0ec1a28b0e35f484cc8220a8bf084019e71"
integrity sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==
"@typescript-eslint/eslint-plugin@8.62.1", "@typescript-eslint/eslint-plugin@^8.59.3":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz#1736dcdca6cae3359d818456a47d18b674761f7f"
integrity sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==
dependencies:
"@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.63.0"
"@typescript-eslint/type-utils" "8.63.0"
"@typescript-eslint/utils" "8.63.0"
"@typescript-eslint/visitor-keys" "8.63.0"
"@typescript-eslint/scope-manager" "8.62.1"
"@typescript-eslint/type-utils" "8.62.1"
"@typescript-eslint/utils" "8.62.1"
"@typescript-eslint/visitor-keys" "8.62.1"
ignore "^7.0.5"
natural-compare "^1.4.0"
ts-api-utils "^2.5.0"
"@typescript-eslint/parser@8.63.0", "@typescript-eslint/parser@^8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.63.0.tgz#2993338c379903e6afc72c3532ae6e15b97334d4"
integrity sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==
"@typescript-eslint/parser@8.62.1", "@typescript-eslint/parser@^8.61.0":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.62.1.tgz#d3f7ba18f1bf78bfb7256fea021d1927b48e7080"
integrity sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==
dependencies:
"@typescript-eslint/scope-manager" "8.63.0"
"@typescript-eslint/types" "8.63.0"
"@typescript-eslint/typescript-estree" "8.63.0"
"@typescript-eslint/visitor-keys" "8.63.0"
"@typescript-eslint/scope-manager" "8.62.1"
"@typescript-eslint/types" "8.62.1"
"@typescript-eslint/typescript-estree" "8.62.1"
"@typescript-eslint/visitor-keys" "8.62.1"
debug "^4.4.3"
"@typescript-eslint/project-service@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.63.0.tgz#01a3d0550a860127444a9939749ab434591cd71a"
integrity sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==
"@typescript-eslint/project-service@8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.62.1.tgz#78d880eb1cf6859b5ec263d04f95403e9f90ae47"
integrity sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.63.0"
"@typescript-eslint/types" "^8.63.0"
"@typescript-eslint/tsconfig-utils" "^8.62.1"
"@typescript-eslint/types" "^8.62.1"
debug "^4.4.3"
"@typescript-eslint/scope-manager@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz#c9cf7ecd234f7ec346f62e5c7d28dfaf4d507b4d"
integrity sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==
"@typescript-eslint/scope-manager@8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz#7ee65e9a6eb3ccdc4816593a4ff38840306de88a"
integrity sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==
dependencies:
"@typescript-eslint/types" "8.63.0"
"@typescript-eslint/visitor-keys" "8.63.0"
"@typescript-eslint/types" "8.62.1"
"@typescript-eslint/visitor-keys" "8.62.1"
"@typescript-eslint/tsconfig-utils@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz#f7e1cf9a029bb71f4027ffa4194ba82afb564cd3"
integrity sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==
"@typescript-eslint/tsconfig-utils@8.62.1", "@typescript-eslint/tsconfig-utils@^8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz#e2b5f24fe721044189cb7e81117c96d75979d627"
integrity sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==
"@typescript-eslint/tsconfig-utils@^8.63.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz#c62ac8ea9173c3cac8b38b8e66e30a046b548851"
integrity sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==
"@typescript-eslint/type-utils@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz#9c00f362140186c588da86b3e10c212800b64b85"
integrity sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==
"@typescript-eslint/type-utils@8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz#ebd30b13bacb13070917259a23309cf644121f9a"
integrity sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==
dependencies:
"@typescript-eslint/types" "8.63.0"
"@typescript-eslint/typescript-estree" "8.63.0"
"@typescript-eslint/utils" "8.63.0"
"@typescript-eslint/types" "8.62.1"
"@typescript-eslint/typescript-estree" "8.62.1"
"@typescript-eslint/utils" "8.62.1"
debug "^4.4.3"
ts-api-utils "^2.5.0"
"@typescript-eslint/types@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.63.0.tgz#6b32b0a5913520554d81a986acfffba2d32b6963"
integrity sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==
"@typescript-eslint/types@8.62.1", "@typescript-eslint/types@^8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.62.1.tgz#c58be954e483b2fc98275374d5bcb40b99842dc1"
integrity sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==
"@typescript-eslint/types@^8.63.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.64.0.tgz#b41f8ef5dd40616908658b991197a9d486cda60b"
integrity sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==
"@typescript-eslint/typescript-estree@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz#a6f9c9cd290e98203ad29850b0d72529dc83be73"
integrity sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==
"@typescript-eslint/typescript-estree@8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz#98c1bb17635d5b026b24193a8d29188ac64380ff"
integrity sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==
dependencies:
"@typescript-eslint/project-service" "8.63.0"
"@typescript-eslint/tsconfig-utils" "8.63.0"
"@typescript-eslint/types" "8.63.0"
"@typescript-eslint/visitor-keys" "8.63.0"
"@typescript-eslint/project-service" "8.62.1"
"@typescript-eslint/tsconfig-utils" "8.62.1"
"@typescript-eslint/types" "8.62.1"
"@typescript-eslint/visitor-keys" "8.62.1"
debug "^4.4.3"
minimatch "^10.2.2"
semver "^7.7.3"
tinyglobby "^0.2.15"
ts-api-utils "^2.5.0"
"@typescript-eslint/utils@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.63.0.tgz#b6a6c8aff1cebd1de4410b3a42b7ec9ba6703f23"
integrity sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==
"@typescript-eslint/utils@8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.62.1.tgz#1622b75c7e6df308181dd0b44855dc4228da0457"
integrity sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==
dependencies:
"@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.63.0"
"@typescript-eslint/types" "8.63.0"
"@typescript-eslint/typescript-estree" "8.63.0"
"@typescript-eslint/scope-manager" "8.62.1"
"@typescript-eslint/types" "8.62.1"
"@typescript-eslint/typescript-estree" "8.62.1"
"@typescript-eslint/visitor-keys@8.63.0":
version "8.63.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz#2853a8e33b52f23570338f96cc89cb223daabd44"
integrity sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==
"@typescript-eslint/visitor-keys@8.62.1":
version "8.62.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz#499657d77ffafb8a99eb1d6c97847ca430234722"
integrity sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==
dependencies:
"@typescript-eslint/types" "8.63.0"
"@typescript-eslint/types" "8.62.1"
eslint-visitor-keys "^5.0.0"
"@ungap/structured-clone@^1.0.0":
@@ -6577,10 +6567,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001799, caniuse-lite@^1.0.30001803:
version "1.0.30001803"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001803.tgz#b2a5d696e042bc8304dcd4942c39fe330fbbcb24"
integrity sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001799:
version "1.0.30001800"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz#b896c773e1c39400809415162bb5320371291b36"
integrity sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==
ccount@^2.0.0:
version "2.0.1"
@@ -7829,10 +7819,10 @@ doctrine@^2.1.0:
dependencies:
esutils "^2.0.2"
docusaurus-plugin-openapi-docs@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-5.1.1.tgz#78959b32222095408460aee3ee3e889082766d69"
integrity sha512-msmXMA6DuKUgGq2eGlsk++Gq58nvXAFghq4VAH4OCCLqNqtY3lGLJeHa5hMPM12x8gH3wBsZhLWRpC3CZi/mAQ==
docusaurus-plugin-openapi-docs@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-5.1.0.tgz#9732f81f45a5bc126bcafcb150332b7623ddece7"
integrity sha512-ocRemE3KmdhhPKaow5hja1m1NLIPfNlfRYFt7pja+nG26Wlp0MEC9ERS99gSWEWaxU+txDFBpUXsxo7nGlk8ZA==
dependencies:
"@apidevtools/json-schema-ref-parser" "^15.3.3"
"@redocly/openapi-core" "^2.25.2"
@@ -7850,10 +7840,10 @@ docusaurus-plugin-openapi-docs@^5.1.1:
swagger2openapi "^7.0.8"
xml-formatter "^3.6.6"
docusaurus-theme-openapi-docs@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/docusaurus-theme-openapi-docs/-/docusaurus-theme-openapi-docs-5.1.1.tgz#6e404a60942c74dbf31ecabb3bb8276d91de811c"
integrity sha512-oGpHomo51qfKD8KM6gEfZRw98o8IRFpOhe3QnMljqy+L69vownXblJEMxZlXUMJML922NbN98tf3Bh4zKXqZTQ==
docusaurus-theme-openapi-docs@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/docusaurus-theme-openapi-docs/-/docusaurus-theme-openapi-docs-5.1.0.tgz#4b9b7024130bd299a56bffe238c584280851012f"
integrity sha512-SLHg1lodIM5AEGo0EJx4pJ+9szDDrN8Q+hNk5qqTVJ60k/zKjHAnwGCzVDT25K1CCMDPaUzuuruj43d7TRoQKw==
dependencies:
"@hookform/error-message" "^2.0.1"
"@reduxjs/toolkit" "^2.8.2"
@@ -13008,10 +12998,10 @@ prettier-linter-helpers@^1.0.1:
dependencies:
fast-diff "^1.1.2"
prettier@^3.9.5:
version "3.9.5"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.9.5.tgz#4fec97736e33b9d0b620b48914fe93b530e835ad"
integrity sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==
prettier@^3.9.1:
version "3.9.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.9.4.tgz#a9c477cf1614376bd1f6bbc593d8c0d414bcec87"
integrity sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==
pretty-error@^4.0.0:
version "4.0.0"
@@ -15272,15 +15262,15 @@ types-ramda@^0.30.1:
dependencies:
ts-toolbelt "^9.6.0"
typescript-eslint@^8.63.0:
version "8.63.0"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.63.0.tgz#1c2b65c989572af7113fbae7f54c7ffab0fbeb12"
integrity sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==
typescript-eslint@^8.62.0:
version "8.62.1"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.62.1.tgz#eb93fd94d527aa04ec5b844fb0b4ada613cc7d3f"
integrity sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==
dependencies:
"@typescript-eslint/eslint-plugin" "8.63.0"
"@typescript-eslint/parser" "8.63.0"
"@typescript-eslint/typescript-estree" "8.63.0"
"@typescript-eslint/utils" "8.63.0"
"@typescript-eslint/eslint-plugin" "8.62.1"
"@typescript-eslint/parser" "8.62.1"
"@typescript-eslint/typescript-estree" "8.62.1"
"@typescript-eslint/utils" "8.62.1"
typescript@~6.0.3:
version "6.0.3"

View File

@@ -29,7 +29,7 @@ maintainers:
- name: craig-rueda
email: craig@craigrueda.com
url: https://github.com/craig-rueda
version: 0.20.0 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
version: 0.19.0 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
dependencies:
- name: postgresql
version: 16.7.27

View File

@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
# superset
![Version: 0.20.0](https://img.shields.io/badge/Version-0.20.0-informational?style=flat-square)
![Version: 0.19.0](https://img.shields.io/badge/Version-0.19.0-informational?style=flat-square)
Apache Superset is a modern, enterprise-ready business intelligence web application
@@ -74,42 +74,10 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
|-----|------|---------|-------------|
| affinity | object | `{}` | |
| bootstrapScript | string | see `values.yaml` | Install additional packages and do any other bootstrap configuration in this script For production clusters it's recommended to build own image with this step done in CI |
| cache | object | `{"asyncQueries":{"keyPrefix":"qc-","timeout":86400},"cacheDb":null,"cacheUrl":null,"celeryDb":null,"celeryUrl":null,"defaultTimeout":86400,"driver":"","enabled":true,"host":null,"keyPrefix":"superset_","password":null,"port":null,"resultsBackendKeyPrefix":"superset_results","sentinel":null,"ssl":{"ca_certs":null,"certfile":null,"enabled":false,"keyfile":null,"ssl_cert_reqs":"required"},"user":""}` | Redis cache configuration for Superset Redis is optional but recommended for caching and Celery. If redis.enabled (chart dependency) is true, defaults point to the chart's Redis instance. |
| cache.asyncQueries | object | `{"keyPrefix":"qc-","timeout":86400}` | Async queries configuration |
| cache.cacheDb | string | `nil` | Redis database number for cache (default: 1 when unset; legacy redis_cache_db is honored) |
| cache.cacheUrl | string | `nil` | Full Redis cache URL (overrides host/port/user/pass if set) |
| cache.celeryDb | string | `nil` | Redis database number for Celery (default: 0 when unset; legacy redis_celery_db is honored) |
| cache.celeryUrl | string | `nil` | Full Redis Celery URL (overrides host/port/user/pass if set) |
| cache.defaultTimeout | int | `86400` | Default cache timeout in seconds |
| cache.driver | string | `""` | Custom Redis driver (e.g. TLS/managed variants); overrides the redis proto in URLs when set. Ports the legacy supersetNode.connections.redis_driver escape hatch. |
| cache.enabled | bool | `true` | Enable Redis-based features (cache, Celery). Set to false to disable Redis usage entirely. |
| cache.host | string | `nil` | Redis host (default: {{ .Release.Name }}-redis-headless) |
| cache.keyPrefix | string | `"superset_"` | Cache key prefix |
| cache.password | string | `nil` | Redis password |
| cache.port | string | `nil` | Redis port (default: 6379 when unset; legacy supersetNode.connections.redis_port is honored) |
| cache.resultsBackendKeyPrefix | string | `"superset_results"` | Results backend key prefix |
| cache.sentinel | string | `nil` | Redis Sentinel configuration (optional) |
| cache.ssl | object | `{"ca_certs":null,"certfile":null,"enabled":false,"keyfile":null,"ssl_cert_reqs":"required"}` | Redis SSL configuration |
| cache.user | string | `""` | Redis user (optional, for Redis ACL) |
| cluster | object | `{"databaseServiceName":null,"domain":".svc.cluster.local","redisServiceName":null,"websocketServiceName":null}` | Kubernetes cluster configuration Used for constructing service URLs between chart components |
| cluster.databaseServiceName | string | `nil` | Database service name (default: {{ .Release.Name }}-postgresql) Override if using a different service name for the database |
| cluster.domain | string | `".svc.cluster.local"` | Kubernetes cluster domain (default: .svc.cluster.local) Override if using a custom cluster domain |
| cluster.redisServiceName | string | `nil` | Redis service name (default: {{ .Release.Name }}-redis-headless) Override if using a different service name for Redis |
| cluster.websocketServiceName | string | `nil` | WebSocket service name (default: {{ .Release.Name }}-ws) Override if using a different service name for the WebSocket service |
| config | object | `{}` | Superset configuration properties Set any configuration property from superset/config.py here See https://github.com/apache/superset/blob/master/superset/config.py for all available options |
| configFromSecret | string | `"{{ template \"superset.fullname\" . }}-config"` | The name of the secret which we will use to generate a superset_config.py file Note: this secret must have the key superset_config.py in it and can include other files as well |
| configMountPath | string | `"/app/pythonpath"` | |
| configOverrides | object | `{}` | A dictionary of overrides to append at the end of superset_config.py - the name does not matter WARNING: the order is not guaranteed Files can be passed as helm --set-file configOverrides.my-override=my-file.py |
| configOverridesFiles | object | `{}` | Same as above but the values are files |
| database | object | `{"driver":null,"host":null,"name":null,"password":null,"port":null,"ssl":{"enabled":false,"mode":"require"},"uri":null,"user":null}` | Database connection configuration for the Superset metadata database |
| database.driver | string | `nil` | Database driver used when uri is not set (default: postgresql+psycopg2 when unset; legacy supersetNode.connections.db_type is honored when this is unset) |
| database.host | string | `nil` | Database host (default: {{ .Release.Name }}-postgresql) |
| database.name | string | `nil` | Database name (default: superset, resolved via superset.db.name) |
| database.password | string | `nil` | Database password (default: superset, resolved via superset.db.password) ⚠️ CHANGE THIS for production |
| database.port | string | `nil` | Database port (default: 5432 when unset; legacy supersetNode.connections.db_port is honored) |
| database.ssl | object | `{"enabled":false,"mode":"require"}` | Database SSL configuration |
| database.uri | string | `nil` | Full database URI (overrides host/port/user/pass/name if set) Example: "postgresql+psycopg2://user:pass@host:5432/dbname" |
| database.user | string | `nil` | Database user (default: superset, resolved via superset.db.user) |
| envFromSecret | string | `"{{ template \"superset.fullname\" . }}-env"` | The name of the secret which we will use to populate env vars in deployed pods This can be useful for secret keys, etc. |
| envFromSecrets | list | `[]` | This can be a list of templated strings |
| extraConfigMountPath | string | `"/app/configs"` | |
@@ -121,9 +89,7 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| extraSecrets | object | `{}` | Extra files to be mounted as Secrets on the path specified in `configMountPath` |
| extraVolumeMounts | list | `[]` | |
| extraVolumes | list | `[]` | |
| featureFlags | object | `{}` | Feature flags configuration See https://github.com/apache/superset/blob/master/RESOURCES/FEATURE_FLAGS.md |
| fullnameOverride | string | `nil` | Provide a name to override the full names of resources |
| globalPodAnnotations | object | `{}` | Global pod annotations to be added to all pods Use this to set annotations that apply to all Superset components Component-specific podAnnotations will be merged with these global annotations |
| hostAliases | list | `[]` | Custom hostAliases for all superset pods # https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/ |
| image.pullPolicy | string | `"IfNotPresent"` | |
| image.repository | string | `"apachesuperset.docker.scarf.sh/apache/superset"` | |
@@ -150,7 +116,7 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| init.enabled | bool | `true` | |
| init.extraContainers | list | `[]` | Launch additional containers into init job pod |
| init.initContainers | list | a container waiting for postgres | List of initContainers |
| init.initscript | string | unused; kept for backwards-compatibility only | DEPRECATED: this field is no longer used by the chart. The init script is rendered entirely from the internal `superset.initScript` template (which runs `superset db upgrade`, `superset init`, admin creation, and examples). Any customization placed here is silently ignored. See UPGRADING.md. |
| init.initscript | string | a script to create admin user and initialize roles | A Superset init script |
| init.jobAnnotations."helm.sh/hook" | string | `"post-install,post-upgrade"` | |
| init.jobAnnotations."helm.sh/hook-delete-policy" | string | `"before-hook-creation"` | |
| init.loadExamples | bool | `false` | |
@@ -177,7 +143,6 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| service.type | string | `"ClusterIP"` | |
| serviceAccount.annotations | object | `{}` | |
| serviceAccount.create | bool | `false` | Create custom service account for Superset. If create: true and serviceAccountName is not provided, `superset.fullname` will be used. |
| serviceAccount.name | string | `""` | Service account name to use (if not specified, defaults to release name + chart name) |
| serviceAccountName | string | `nil` | Specify service account name to be used |
| supersetCeleryBeat.affinity | object | `{}` | Affinity to be added to supersetCeleryBeat deployment |
| supersetCeleryBeat.command | list | a `celery beat` command | Command |
@@ -249,7 +214,20 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| supersetNode.autoscaling.minReplicas | int | `1` | |
| supersetNode.autoscaling.targetCPUUtilizationPercentage | int | `80` | |
| supersetNode.command | list | See `values.yaml` | Startup command |
| supersetNode.connections | object | `{}` | |
| supersetNode.connections.db_host | string | `"{{ .Release.Name }}-postgresql"` | |
| supersetNode.connections.db_name | string | `"superset"` | |
| supersetNode.connections.db_pass | string | `"superset"` | |
| supersetNode.connections.db_port | string | `"5432"` | |
| supersetNode.connections.db_type | string | `"postgresql"` | Database type for Superset metadata (Supported types: "postgresql", "mysql") |
| supersetNode.connections.db_user | string | `"superset"` | |
| supersetNode.connections.redis_cache_db | string | `"1"` | |
| supersetNode.connections.redis_celery_db | string | `"0"` | |
| supersetNode.connections.redis_driver | string | `""` | |
| supersetNode.connections.redis_host | string | `"{{ .Release.Name }}-redis-headless"` | Change in case of bringing your own redis and then also set redis.enabled:false |
| supersetNode.connections.redis_port | string | `"6379"` | |
| supersetNode.connections.redis_ssl.enabled | bool | `false` | |
| supersetNode.connections.redis_ssl.ssl_cert_reqs | string | `"CERT_NONE"` | |
| supersetNode.connections.redis_user | string | `""` | |
| supersetNode.containerSecurityContext | object | `{}` | |
| supersetNode.deploymentAdditionalPodSpec | object | `{}` | Custom pod spec to be added to supersetNode deployment |
| supersetNode.deploymentAnnotations | object | `{}` | Annotations to be added to supersetNode deployment |
@@ -356,11 +334,6 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
| supersetWorker.deploymentLabels | object | `{}` | Labels to be added to supersetWorker deployment |
| supersetWorker.extraContainers | list | `[]` | Launch additional containers into supersetWorker pod |
| supersetWorker.forceReload | bool | `false` | If true, forces deployment to reload on each upgrade |
| supersetWorker.healthCheck | object | `{"enabled":false,"livenessFile":"/tmp/celery_worker_alive","livenessHeartbeatInterval":10,"readinessFile":"/tmp/celery_worker_ready"}` | Celery worker file-based health check (worker writes readiness/liveness files via signals; point supersetWorker.readinessProbe/livenessProbe at these files to use them) |
| supersetWorker.healthCheck.enabled | bool | `false` | Enable the file-based Celery worker health check |
| supersetWorker.healthCheck.livenessFile | string | `"/tmp/celery_worker_alive"` | Liveness file (touched periodically by a heartbeat thread) |
| supersetWorker.healthCheck.livenessHeartbeatInterval | int | `10` | Seconds between liveness heartbeats |
| supersetWorker.healthCheck.readinessFile | string | `"/tmp/celery_worker_ready"` | Readiness file (created when the worker is ready, removed on shutdown) |
| supersetWorker.initContainers | list | a container waiting for postgres and redis | Init container |
| supersetWorker.lifecycle | object | `{}` | Container lifecycle hooks for the worker pod |
| supersetWorker.livenessProbe.exec.command | list | a `celery inspect ping` command | Liveness probe command |

View File

@@ -1,161 +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.
-->
# Upgrading the Superset Helm Chart
## Upgrading to chart 0.20.0
Chart 0.20.0 introduces a structured connection schema. The old keys listed below are **DEPRECATED** — they still work (auto-mapped to the new keys at render time) and a deprecation warning is printed in `helm install`/`helm upgrade` NOTES, but they will be removed in a future release. Migrate as soon as possible.
### 1. Connection schema: `supersetNode.connections.*` → `database.*` / `cache.*`
**Before (deprecated — still honored):**
```yaml
supersetNode:
connections:
db_host: "pg-host"
db_port: "5432"
db_user: "superset"
db_pass: "superset"
db_name: "superset"
redis_host: "redis-host"
redis_port: "6379"
redis_cache_db: "1"
redis_celery_db: "0"
redis_driver: "rediss"
```
**After (new structured keys — recommended):**
```yaml
database:
host: "pg-host"
port: 5432
user: "superset"
password: "superset"
name: "superset"
cache:
host: "redis-host"
port: 6379
cacheDb: 1
celeryDb: 0
driver: "rediss" # optional: custom Redis driver / TLS variant
```
Key-by-key mapping summary:
| Old key | New key |
|---|---|
| `supersetNode.connections.db_host` | `database.host` |
| `supersetNode.connections.db_port` | `database.port` |
| `supersetNode.connections.db_user` | `database.user` |
| `supersetNode.connections.db_pass` | `database.password` |
| `supersetNode.connections.db_name` | `database.name` |
| `supersetNode.connections.redis_host` | `cache.host` |
| `supersetNode.connections.redis_port` | `cache.port` |
| `supersetNode.connections.redis_cache_db` | `cache.cacheDb` |
| `supersetNode.connections.redis_celery_db` | `cache.celeryDb` |
| `supersetNode.connections.redis_driver` | `cache.driver` |
### 2. Service account: root `serviceAccountName` → `serviceAccount.name`
**Before (deprecated — still honored):**
```yaml
serviceAccountName: my-sa
```
**After (new key — recommended):**
```yaml
serviceAccount:
name: my-sa
```
### 3. Init script: `init.initscript` is deprecated and replaced by the built-in template
> **This is a behavior change.** The chart no longer uses `init.initscript`. The init script is rendered
> entirely from an internal chart template (`superset.initScript`), which runs the full initialization
> sequence:
>
> 1. `superset db upgrade` — applies all pending database schema migrations
> 2. `superset init` — initializes roles and permissions
> 3. Admin user creation (when `init.createAdmin: true`)
> 4. Example data loading (when `init.loadExamples: true`)
> 5. Datasource import (when `import_datasources.yaml` is present)
>
> A future PR will optionally split the database migration step into a dedicated upgrade Job for
> zero-downtime deployments. Until that PR lands, migrations run as part of the init Job above.
If you customized `init.initscript` in your `values.yaml`, that customization is silently ignored.
Move any customizations to `config` or `configOverrides`:
```yaml
# Move custom Python config here:
config:
MY_SETTING: "value"
configOverrides:
my_custom_override: |
# Python snippet appended to superset_config.py
MY_SETTING = "value"
```
If your use case cannot be covered by `config` or `configOverrides`, please open an issue so the maintainers
can evaluate extending the template.
### 4. New top-level sections: `config.*` and `featureFlags.*`
Two new sections provide direct Superset configuration passthrough without needing raw `configOverrides`.
**`config.*` — direct Superset config properties:**
```yaml
config:
SECRET_KEY: "$(SUPERSET_SECRET_KEY)"
ROW_LIMIT: 50000
WTF_CSRF_ENABLED: true
SQLALCHEMY_POOL_SIZE: 10
```
Each key is injected verbatim into `superset_config.py`. String values are quoted; non-string values
(integers, booleans) are rendered as-is.
**`featureFlags.*` — feature flag overrides:**
```yaml
featureFlags:
ALERT_REPORTS: true
DASHBOARD_RBAC: true
ENABLE_TEMPLATE_PROCESSING: false
```
This is equivalent to setting `FEATURE_FLAGS = {...}` in `superset_config.py`, but is more readable and
schema-validated.
### 5. `values.schema.json` — early validation
The chart now ships a `values.schema.json` that validates the values you provide. Wrong types (e.g., a string
where an integer is expected) or unknown keys in structured sections will cause `helm install`/`helm upgrade`
to fail immediately with a descriptive error instead of producing a broken deployment.
If you get a validation error after upgrading, check that your overridden values match the types documented in
`values.yaml` and the schema.

View File

@@ -35,9 +35,3 @@
echo "Visit http://127.0.0.1:8088 to use your application"
kubectl port-forward service/superset 8088:8088 --namespace {{ .Release.Namespace }}
{{- end }}
{{- $warnings := include "superset.deprecationWarnings" . }}
{{- if trim $warnings }}
⚠️ DEPRECATION WARNINGS
{{ $warnings }}
{{- end }}

View File

@@ -47,12 +47,11 @@ If release name contains chart name it will be used as a full name.
Create the name of the service account to use
*/}}
{{- define "superset.serviceAccountName" -}}
{{- $name := coalesce .Values.serviceAccount.name .Values.serviceAccountName -}}
{{- if .Values.serviceAccount.create -}}
{{- default (include "superset.fullname" .) $name -}}
{{- else -}}
{{- default "default" $name -}}
{{- end -}}
{{- if .Values.serviceAccount.create -}}
{{- default (include "superset.fullname" .) .Values.serviceAccountName -}}
{{- else -}}
{{- default "default" .Values.serviceAccountName -}}
{{- end -}}
{{- end -}}
{{/*
@@ -106,572 +105,80 @@ app.kubernetes.io/component: {{ .component }}
{{- end -}}
{{/*
Coalescing resolvers for DB and Redis connection parameters.
Each resolver checks (in order):
1. New top-level database.* / cache.* values
2. Legacy supersetNode.connections.* keys (deprecation path, using safe index to avoid errors on absent maps)
3. cluster.* service name overrides
4. Hard-coded defaults derived from the release name
Call with root context: {{ include "superset.db.host" . }}
*/}}
{{/*
Helper to safely read .Values.supersetNode.connections.<key> without erroring when maps are absent.
*/}}
{{- define "_superset.legacyConn" -}}
{{- $sn := index .Values "supersetNode" | default dict -}}
{{- $conn := index $sn "connections" | default dict -}}
{{- $conn | toJson -}}
{{- end -}}
{{- define "superset.db.host" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- tpl (coalesce .Values.database.host (index $conn "db_host") .Values.cluster.databaseServiceName (printf "%s-postgresql" .Release.Name)) $ -}}
{{- end -}}
{{- define "superset.db.port" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- if .Values.database.port -}}
{{- .Values.database.port | toString -}}
{{- else -}}
{{- coalesce (index $conn "db_port") "5432" -}}
{{- end -}}
{{- end -}}
{{- define "superset.db.user" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- coalesce .Values.database.user (index $conn "db_user") "superset" -}}
{{- end -}}
{{- define "superset.db.password" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- coalesce .Values.database.password (index $conn "db_pass") "superset" -}}
{{- end -}}
{{- define "superset.db.name" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- coalesce .Values.database.name (index $conn "db_name") "superset" -}}
{{- end -}}
{{- define "superset.db.driver" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- coalesce .Values.database.driver (index $conn "db_type") "postgresql+psycopg2" -}}
{{- end -}}
{{- define "superset.redis.host" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- tpl (coalesce .Values.cache.host (index $conn "redis_host") .Values.cluster.redisServiceName (printf "%s-redis-headless" .Release.Name)) $ -}}
{{- end -}}
{{- define "superset.redis.port" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- if .Values.cache.port -}}
{{- .Values.cache.port | toString -}}
{{- else -}}
{{- coalesce (index $conn "redis_port") "6379" -}}
{{- end -}}
{{- end -}}
{{- define "superset.redis.user" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- coalesce .Values.cache.user (index $conn "redis_user") "" -}}
{{- end -}}
{{- define "superset.redis.password" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- coalesce .Values.cache.password (index $conn "redis_password") "" -}}
{{- end -}}
{{- define "superset.redis.cacheDb" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- if not (kindIs "invalid" .Values.cache.cacheDb) -}}
{{- .Values.cache.cacheDb | toString -}}
{{- else -}}
{{- coalesce (index $conn "redis_cache_db") "1" -}}
{{- end -}}
{{- end -}}
{{- define "superset.redis.celeryDb" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- if not (kindIs "invalid" .Values.cache.celeryDb) -}}
{{- .Values.cache.celeryDb | toString -}}
{{- else -}}
{{- coalesce (index $conn "redis_celery_db") "0" -}}
{{- end -}}
{{- end -}}
{{- define "superset.redis.proto" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- if .Values.cache.driver }}{{ .Values.cache.driver }}{{- else if index $conn "redis_driver" }}{{ index $conn "redis_driver" }}{{- else if .Values.cache.ssl.enabled }}rediss{{- else }}redis{{- end -}}
{{- end -}}
{{- define "superset.config" }}
{{- /* SECURITY: Validate admin password is set if admin creation is enabled */}}
{{- if and .Values.init.createAdmin (or (not .Values.init.adminUser.password) (eq .Values.init.adminUser.password "")) }}
{{- fail "SECURITY ERROR: init.createAdmin is true but init.adminUser.password is empty. You must set a secure password using --set init.adminUser.password='your-password' or via external secret." }}
{{- end }}
{{- define "superset-config" }}
import os
import json
from urllib.parse import quote
{{- if or .Values.config.cacheConfig .Values.config.dataCacheConfig .Values.config.resultsBackend .Values.config.celeryConfig .Values.cache.enabled }}
from flask_caching.backends.rediscache import RedisCache
{{- end }}
def env(key, default=None):
return os.getenv(key, default)
{{- /* Database Configuration - Superset always requires a database */}}
{{- if .Values.database.uri }}
SQLALCHEMY_DATABASE_URI = {{ .Values.database.uri | quote }}
# Redis Base URL
{{- if .Values.supersetNode.connections.redis_password }}
REDIS_BASE_URL=f"{env('REDIS_DRIVER') or env('REDIS_PROTO')}://{env('REDIS_USER', '')}:{env('REDIS_PASSWORD')}@{env('REDIS_HOST')}:{env('REDIS_PORT')}"
{{- else }}
{{- $driver := include "superset.db.driver" . }}
{{- $sslParams := "" }}
{{- if and (hasKey .Values.database "ssl") .Values.database.ssl.enabled }}
{{- $sslMode := .Values.database.ssl.mode | default "require" }}
{{- $sslParams = printf "?sslmode=%s" $sslMode }}
{{- end }}
SQLALCHEMY_DATABASE_URI = f"{{ $driver }}://{quote(env('DB_USER', ''), safe='')}:{quote(env('DB_PASS', ''), safe='')}@{env('DB_HOST')}:{env('DB_PORT')}/{env('DB_NAME')}{{ $sslParams }}"
{{- end }}
{{- if hasKey .Values.config "SQLALCHEMY_TRACK_MODIFICATIONS" }}
SQLALCHEMY_TRACK_MODIFICATIONS = {{ .Values.config.SQLALCHEMY_TRACK_MODIFICATIONS | toString | title }}
{{- else }}
SQLALCHEMY_TRACK_MODIFICATIONS = False
REDIS_BASE_URL=f"{env('REDIS_DRIVER') or env('REDIS_PROTO')}://{env('REDIS_HOST')}:{env('REDIS_PORT')}"
{{- end }}
{{- /* Redis Configuration - only if Redis cache is configured */}}
{{- if .Values.cache.enabled }}
{{- if .Values.cache.cacheUrl }}
CACHE_REDIS_URL = {{ .Values.cache.cacheUrl | quote }}
{{- else }}
{{- $useSSL := and (hasKey .Values.cache "ssl") .Values.cache.ssl.enabled }}
_redis_user = quote(env('REDIS_USER', ''), safe='')
_redis_password = quote(env('REDIS_PASSWORD', ''), safe='')
_redis_auth = f"{_redis_user}:{_redis_password}@" if (_redis_user or _redis_password) else ""
REDIS_BASE_URL = f"{{ include "superset.redis.proto" . }}://{_redis_auth}{env('REDIS_HOST')}:{env('REDIS_PORT')}"
{{- if $useSSL }}
{{- $sslCertReqs := .Values.cache.ssl.ssl_cert_reqs | default "required" }}
REDIS_URL_PARAMS = f"?ssl_cert_reqs={{ $sslCertReqs }}"
# Redis URL Params
{{- if .Values.supersetNode.connections.redis_ssl.enabled }}
REDIS_URL_PARAMS = f"?ssl_cert_reqs={env('REDIS_SSL_CERT_REQS')}"
{{- else }}
REDIS_URL_PARAMS = ""
{{- end }}
{{- $cacheDb := include "superset.redis.cacheDb" . }}
CACHE_REDIS_URL = f"{REDIS_BASE_URL}/{{ $cacheDb }}{REDIS_URL_PARAMS}"
{{- end }}
{{- if .Values.cache.celeryUrl }}
CELERY_REDIS_URL = {{ .Values.cache.celeryUrl | quote }}
{{- else if not .Values.cache.cacheUrl }}
{{- $celeryDb := include "superset.redis.celeryDb" . }}
CELERY_REDIS_URL = f"{REDIS_BASE_URL}/{{ $celeryDb }}{REDIS_URL_PARAMS}"
{{- else }}
{{- if or .Values.config.celeryConfig (not .Values.cache.enabled) }}
{{- /* Custom celeryConfig provided or cache disabled - OK */}}
{{- else }}
{{- fail "CONFIGURATION ERROR: cache.cacheUrl is set but cache.celeryUrl is not set. When using cacheUrl, you must also set celeryUrl for Celery to work. Alternatively, set config.celeryConfig to provide a custom Celery configuration." }}
{{- end }}
{{- end }}
{{- end }}
{{- end}}
{{- /* Cache Configuration */}}
{{- if .Values.config.cacheConfig }}
CACHE_CONFIG = json.loads({{ .Values.config.cacheConfig | toJson | quote }})
{{- else if .Values.cache.enabled }}
# Build Redis URLs
CACHE_REDIS_URL = f"{REDIS_BASE_URL}/{env('REDIS_DB', 1)}{REDIS_URL_PARAMS}"
CELERY_REDIS_URL = f"{REDIS_BASE_URL}/{env('REDIS_CELERY_DB', 0)}{REDIS_URL_PARAMS}"
MAPBOX_API_KEY = env('MAPBOX_API_KEY', '')
CACHE_CONFIG = {
'CACHE_TYPE': 'RedisCache',
'CACHE_DEFAULT_TIMEOUT': {{ .Values.cache.defaultTimeout | default (.Values.config.cacheDefaultTimeout | default 86400) | int }},
'CACHE_KEY_PREFIX': {{ .Values.cache.keyPrefix | default "superset_" | quote }},
'CACHE_REDIS_URL': CACHE_REDIS_URL,
'CACHE_TYPE': 'RedisCache',
'CACHE_DEFAULT_TIMEOUT': 300,
'CACHE_KEY_PREFIX': 'superset_',
'CACHE_REDIS_URL': CACHE_REDIS_URL,
}
{{- end }}
{{- if .Values.config.dataCacheConfig }}
DATA_CACHE_CONFIG = json.loads({{ .Values.config.dataCacheConfig | toJson | quote }})
{{- else if .Values.config.cacheConfig }}
DATA_CACHE_CONFIG = CACHE_CONFIG
{{- else if .Values.cache.enabled }}
DATA_CACHE_CONFIG = CACHE_CONFIG
{{- end }}
{{- /* SQLLAB_ASYNC_TIME_LIMIT_SEC - Required for async_queries module import (default: 6 hours) */}}
{{- if .Values.config.SQLLAB_ASYNC_TIME_LIMIT_SEC }}
SQLLAB_ASYNC_TIME_LIMIT_SEC = {{ .Values.config.SQLLAB_ASYNC_TIME_LIMIT_SEC | int }}
{{- else }}
from datetime import timedelta
SQLLAB_ASYNC_TIME_LIMIT_SEC = int(timedelta(hours=6).total_seconds())
{{- end }}
{{- /* Celery Configuration */}}
{{- if .Values.config.celeryConfig }}
{{- if kindIs "string" .Values.config.celeryConfig }}
{{ .Values.config.celeryConfig }}
{{- else }}
class CeleryConfig:
{{- range $key, $value := .Values.config.celeryConfig }}
{{ $key }} = json.loads({{ $value | toJson | quote }})
{{- end }}
{{- if hasKey .Values.config.celeryConfig "imports" }}
CELERY_IMPORTS = CeleryConfig.imports
{{- else }}
CELERY_IMPORTS = ()
{{- end }}
CELERY_CONFIG = CeleryConfig
{{- end }}
{{- else if .Values.cache.enabled }}
from celery.schedules import crontab
from datetime import timedelta
if os.getenv("SQLALCHEMY_DATABASE_URI"):
SQLALCHEMY_DATABASE_URI = os.getenv("SQLALCHEMY_DATABASE_URI")
else:
{{- if eq .Values.supersetNode.connections.db_type "postgresql" }}
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
{{- else if eq .Values.supersetNode.connections.db_type "mysql" }}
SQLALCHEMY_DATABASE_URI = f"mysql+mysqldb://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
{{- else }}
{{ fail (printf "Unsupported database type: %s. Please use 'postgresql' or 'mysql'." .Values.supersetNode.connections.db_type) }}
{{- end }}
class CeleryConfig:
imports = (
"superset.sql_lab",
"superset.tasks.scheduler",
"superset.tasks.thumbnails",
"superset.tasks.cache",
)
broker_connection_retry_on_startup = True
worker_prefetch_multiplier = 10
task_acks_late = True
broker_url = CELERY_REDIS_URL
result_backend = CELERY_REDIS_URL
task_annotations = {
"sql_lab.get_sql_results": {
"rate_limit": "100/s",
},
}
beat_schedule = {
"reports.scheduler": {
"task": "reports.scheduler",
"schedule": crontab(minute="*", hour="*"),
"options": {"expires": int(timedelta(weeks=1).total_seconds())},
},
"reports.prune_log": {
"task": "reports.prune_log",
"schedule": crontab(minute=0, hour=0),
},
}
imports = ("superset.sql_lab", )
broker_url = CELERY_REDIS_URL
result_backend = CELERY_REDIS_URL
CELERY_IMPORTS = CeleryConfig.imports
CELERY_CONFIG = CeleryConfig
{{- end }}
{{- /* Celery Worker Health Check - File-based health probes for Kubernetes */}}
{{- if and .Values.supersetWorker.healthCheck .Values.supersetWorker.healthCheck.enabled }}
# Celery Worker Health Check Configuration
import threading
from celery import bootsteps
from celery.signals import worker_ready, worker_shutdown, worker_init
_readiness_file = {{ .Values.supersetWorker.healthCheck.readinessFile | default "/tmp/celery_worker_ready" | quote }}
_liveness_file = {{ .Values.supersetWorker.healthCheck.livenessFile | default "/tmp/celery_worker_alive" | quote }}
_heartbeat_interval = {{ .Values.supersetWorker.healthCheck.livenessHeartbeatInterval | default 10 | int }}
_liveness_thread = None
_liveness_stop_event = None
@worker_ready.connect
def create_ready_file(sender, **kwargs):
try:
open(_readiness_file, 'w').close()
except Exception as e:
print(f"Warning: Could not create readiness file: {e}")
@worker_shutdown.connect
def remove_ready_file(sender, **kwargs):
global _liveness_thread, _liveness_stop_event
if _liveness_stop_event:
_liveness_stop_event.set()
if _liveness_thread:
_liveness_thread.join(timeout=5)
try:
if os.path.exists(_readiness_file):
os.remove(_readiness_file)
if os.path.exists(_liveness_file):
os.remove(_liveness_file)
except Exception as e:
print(f"Warning: Could not remove health check files: {e}")
@worker_init.connect
def start_liveness_heartbeat(sender, **kwargs):
global _liveness_thread, _liveness_stop_event
_liveness_stop_event = threading.Event()
def update_liveness():
while not _liveness_stop_event.is_set():
try:
with open(_liveness_file, 'w') as f:
f.write(str(os.getpid()))
except Exception as e:
print(f"Warning: Could not update liveness file: {e}")
_liveness_stop_event.wait(_heartbeat_interval)
_liveness_thread = threading.Thread(target=update_liveness, daemon=True)
_liveness_thread.start()
{{- else }}
CELERY_WORKER_HEALTH_CHECK_ENABLED = False
{{- end }}
{{- /* Results Backend */}}
{{- $redisHostForBackend := include "superset.redis.host" . }}
{{- $redisPortForBackend := include "superset.redis.port" . }}
{{- $redisPasswordForBackend := include "superset.redis.password" . }}
{{- if .Values.config.resultsBackend }}
{{- if kindIs "string" .Values.config.resultsBackend }}
RESULTS_BACKEND = {{ .Values.config.resultsBackend }}
{{- else }}
RESULTS_BACKEND = RedisCache(
host={{ $redisHostForBackend | quote }},
{{- if $redisPasswordForBackend }}
password={{ $redisPasswordForBackend | quote }},
{{- end }}
port={{ $redisPortForBackend | int }},
key_prefix={{ .Values.cache.resultsBackendKeyPrefix | default "superset_results" | quote }},
{{- if and (hasKey .Values.cache "ssl") .Values.cache.ssl.enabled }}
ssl=True,
ssl_cert_reqs={{ .Values.cache.ssl.ssl_cert_reqs | default "required" | quote }},
{{- end }}
host=env('REDIS_HOST'),
{{- if .Values.supersetNode.connections.redis_password }}
password=env('REDIS_PASSWORD'),
{{- end }}
port=env('REDIS_PORT'),
key_prefix='superset_results',
{{- if .Values.supersetNode.connections.redis_ssl.enabled }}
ssl=True,
ssl_cert_reqs=env('REDIS_SSL_CERT_REQS'),
{{- end }}
)
{{- end }}
{{- else if .Values.cache.enabled }}
RESULTS_BACKEND = RedisCache(
host={{ $redisHostForBackend | quote }},
{{- if $redisPasswordForBackend }}
password={{ $redisPasswordForBackend | quote }},
{{- end }}
port={{ $redisPortForBackend | int }},
key_prefix={{ .Values.cache.resultsBackendKeyPrefix | default "superset_results" | quote }},
{{- if and (hasKey .Values.cache "ssl") .Values.cache.ssl.enabled }}
ssl=True,
ssl_cert_reqs={{ .Values.cache.ssl.ssl_cert_reqs | default "required" | quote }},
{{- end }}
)
{{- end }}
{{- /* Global Async Queries Cache Backend */}}
{{- $redisUserForGaq := include "superset.redis.user" . }}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_CACHE_BACKEND }}
GLOBAL_ASYNC_QUERIES_CACHE_BACKEND = json.loads({{ .Values.config.GLOBAL_ASYNC_QUERIES_CACHE_BACKEND | toJson | quote }})
{{- else if .Values.cache.enabled }}
GLOBAL_ASYNC_QUERIES_CACHE_BACKEND = {
"CACHE_TYPE": "RedisCache",
"CACHE_REDIS_HOST": {{ $redisHostForBackend | quote }},
"CACHE_REDIS_PORT": {{ $redisPortForBackend | int }},
{{- if $redisUserForGaq }}
"CACHE_REDIS_USER": {{ $redisUserForGaq | quote }},
{{- end }}
{{- if $redisPasswordForBackend }}
"CACHE_REDIS_PASSWORD": {{ $redisPasswordForBackend | quote }},
{{- else }}
"CACHE_REDIS_PASSWORD": "",
{{- end }}
"CACHE_REDIS_DB": {{ .Values.cache.asyncQueries.db | default .Values.cache.cacheDb | default 0 | int }},
"CACHE_KEY_PREFIX": {{ .Values.cache.asyncQueries.keyPrefix | default "qc-" | quote }},
"CACHE_DEFAULT_TIMEOUT": {{ .Values.cache.asyncQueries.timeout | default 86400 | int }},
{{- if and .Values.cache.sentinel .Values.cache.sentinel.enabled }}
{{- if .Values.cache.sentinel.sentinels }}
"CACHE_REDIS_SENTINELS": {{ .Values.cache.sentinel.sentinels | toJson }},
{{- else }}
{{- fail "CONFIGURATION ERROR: cache.sentinel.enabled is true but cache.sentinel.sentinels is not set. You must provide Sentinel host(s) in cache.sentinel.sentinels (e.g., [['sentinel-host', 26379]])." }}
{{- end }}
"CACHE_REDIS_SENTINEL_MASTER": {{ .Values.cache.sentinel.master | default "mymaster" | quote }},
{{- if .Values.cache.sentinel.password }}
"CACHE_REDIS_SENTINEL_PASSWORD": {{ .Values.cache.sentinel.password | quote }},
{{- else }}
"CACHE_REDIS_SENTINEL_PASSWORD": None,
{{- end }}
{{- end }}
{{- if and (hasKey .Values.cache "ssl") .Values.cache.ssl.enabled }}
"CACHE_REDIS_SSL": True,
"CACHE_REDIS_SSL_CERTFILE": {{ if .Values.cache.ssl.certfile }}{{ .Values.cache.ssl.certfile | quote }}{{ else }}None{{ end }},
"CACHE_REDIS_SSL_KEYFILE": {{ if .Values.cache.ssl.keyfile }}{{ .Values.cache.ssl.keyfile | quote }}{{ else }}None{{ end }},
"CACHE_REDIS_SSL_CERT_REQS": {{ .Values.cache.ssl.ssl_cert_reqs | default "required" | quote }},
"CACHE_REDIS_SSL_CA_CERTS": {{ if .Values.cache.ssl.ca_certs }}{{ .Values.cache.ssl.ca_certs | quote }}{{ else }}None{{ end }},
{{- else }}
"CACHE_REDIS_SSL": False,
"CACHE_REDIS_SSL_CERTFILE": None,
"CACHE_REDIS_SSL_KEYFILE": None,
"CACHE_REDIS_SSL_CERT_REQS": {{ .Values.cache.ssl.ssl_cert_reqs | default "required" | quote }},
"CACHE_REDIS_SSL_CA_CERTS": None,
{{- end }}
}
{{- end }}
{{- /* Global Async Queries Results Backend */}}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND }}
GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND = json.loads({{ .Values.config.GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND | toJson | quote }})
{{- else if .Values.cache.enabled }}
GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND = {
"backend": "redis",
"host": {{ $redisHostForBackend | quote }},
"port": {{ $redisPortForBackend | int }},
"prefix": {{ .Values.cache.asyncQueries.keyPrefix | default "qc-" | quote }},
"db": {{ .Values.cache.asyncQueries.db | default .Values.cache.cacheDb | default 0 | int }},
{{- if $redisPasswordForBackend }}
"password": {{ $redisPasswordForBackend | quote }},
{{- end }}
}
{{- end }}
{{- /* Feature Flags */}}
{{- if .Values.featureFlags }}
FEATURE_FLAGS = {
{{- range $key, $value := .Values.featureFlags }}
{{- if kindIs "bool" $value }}
"{{ $key }}": {{ if $value }}True{{ else }}False{{ end }},
{{- else if kindIs "string" $value }}
"{{ $key }}": {{ $value | quote }},
{{- else if kindIs "float64" $value }}
"{{ $key }}": {{ $value }},
{{- else if kindIs "int" $value }}
"{{ $key }}": {{ $value }},
{{- else if kindIs "invalid" $value }}
"{{ $key }}": None,
{{- else }}
"{{ $key }}": json.loads({{ $value | toJson | quote }}),
{{- end }}
{{- end }}
}
{{- end }}
{{- /* FAB Security API - Required for List Roles view in 6.0.0+ */}}
{{- if not (hasKey .Values.config "FAB_ADD_SECURITY_API") }}
FAB_ADD_SECURITY_API = True
{{- end }}
{{- if not (hasKey .Values.config "FAB_ADD_SECURITY_VIEWS") }}
FAB_ADD_SECURITY_VIEWS = True
{{- end }}
{{- /* Global Async Queries Transport - Auto-configure for websockets if enabled */}}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_TRANSPORT }}
GLOBAL_ASYNC_QUERIES_TRANSPORT = {{ .Values.config.GLOBAL_ASYNC_QUERIES_TRANSPORT | quote }}
{{- else if .Values.supersetWebsockets.enabled }}
GLOBAL_ASYNC_QUERIES_TRANSPORT = "ws"
{{- else }}
GLOBAL_ASYNC_QUERIES_TRANSPORT = "polling"
{{- end }}
{{- /* Global Async Queries WebSocket URL */}}
{{- $wsUrl := "" }}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL }}
{{- $wsUrl = .Values.config.GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL }}
GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL = {{ $wsUrl | quote }}
{{- else if and .Values.supersetWebsockets.enabled .Values.supersetWebsockets.websocketUrl }}
{{- $wsUrl = .Values.supersetWebsockets.websocketUrl }}
GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL = {{ $wsUrl | quote }}
{{- else if .Values.supersetWebsockets.enabled }}
{{- $wsServiceName := .Values.cluster.websocketServiceName }}
{{- if not $wsServiceName }}
{{- $wsServiceName = printf "%s-ws" (include "superset.fullname" .) }}
{{- end }}
{{- $wsPort := .Values.supersetWebsockets.service.port | default 8080 }}
{{- $wsPath := "/ws" }}
{{- $clusterDomain := .Values.cluster.domain | default ".svc.cluster.local" }}
{{- $wsUrl = printf "ws://%s.%s%s:%d%s" $wsServiceName .Release.Namespace $clusterDomain $wsPort $wsPath }}
GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL = {{ $wsUrl | quote }}
{{- end }}
{{- /* Global Async Queries JWT Secret */}}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_JWT_SECRET }}
GLOBAL_ASYNC_QUERIES_JWT_SECRET = {{ .Values.config.GLOBAL_ASYNC_QUERIES_JWT_SECRET | quote }}
{{- else if and .Values.supersetWebsockets.enabled .Values.supersetWebsockets.config.jwtSecret }}
GLOBAL_ASYNC_QUERIES_JWT_SECRET = {{ .Values.supersetWebsockets.config.jwtSecret | quote }}
{{- end }}
{{- /* Global Async Queries JWT Cookie Settings */}}
{{- if hasKey .Values.config "GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE" }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE = {{ .Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE | toString | title }}
{{- else if and .Values.supersetWebsockets.enabled (or (hasPrefix "wss://" $wsUrl) .Values.ingress.tls) }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE = True
{{- else if .Values.supersetWebsockets.enabled }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE = False
{{- end }}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE = {{ .Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE | quote }}
{{- else if .Values.supersetWebsockets.enabled }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE = "Lax"
{{- end }}
{{- if .Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME = {{ .Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME | quote }}
{{- else if and .Values.supersetWebsockets.enabled .Values.supersetWebsockets.config.jwtCookieName }}
GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME = {{ .Values.supersetWebsockets.config.jwtCookieName | quote }}
{{- end }}
{{- /* Content Security Policy (CSP) */}}
{{- if and .Values.supersetWebsockets.enabled $wsUrl (not (hasKey .Values.config "TALISMAN_CONFIG")) }}
TALISMAN_CONFIG = {
"content_security_policy": {
"base-uri": ["'self'"],
"default-src": ["'self'"],
"img-src": [
"'self'",
"blob:",
"data:",
"https://apachesuperset.gateway.scarf.sh",
"https://static.scarf.sh/",
"ows.terrestris.de",
"https://cdn.document360.io",
],
"worker-src": ["'self'", "blob:"],
"connect-src": [
"'self'",
{{ $wsUrl | quote }},
"https://api.mapbox.com",
"https://events.mapbox.com",
"https://tile.openstreetmap.org",
"https://tile.osm.ch",
],
"object-src": "'none'",
"style-src": [
"'self'",
"'unsafe-inline'",
],
"script-src": ["'self'", "'strict-dynamic'"],
},
"content_security_policy_nonce_in": ["script-src"],
{{- if or (hasPrefix "wss://" $wsUrl) .Values.ingress.tls }}
"force_https": True,
"session_cookie_secure": True,
{{- else }}
"force_https": False,
"session_cookie_secure": False,
{{- end }}
}
{{- end }}
{{- /* General Configuration - iterate through all config values */}}
{{- range $key, $value := .Values.config }}
{{- if and (ne $key "cacheConfig") (ne $key "dataCacheConfig") (ne $key "celeryConfig") (ne $key "resultsBackend") (ne $key "GLOBAL_ASYNC_QUERIES_CACHE_BACKEND") (ne $key "GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND") (ne $key "GLOBAL_ASYNC_QUERIES_TRANSPORT") (ne $key "GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL") (ne $key "GLOBAL_ASYNC_QUERIES_JWT_SECRET") (ne $key "GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE") (ne $key "GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE") (ne $key "GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME") (ne $key "TALISMAN_CONFIG") (ne $key "SQLLAB_ASYNC_TIME_LIMIT_SEC") (ne $key "SQLALCHEMY_TRACK_MODIFICATIONS") }}
{{- if kindIs "map" $value }}
{{ $key }} = json.loads({{ $value | toJson | quote }})
{{- else if kindIs "slice" $value }}
{{ $key }} = json.loads({{ $value | toJson | quote }})
{{- else if kindIs "bool" $value }}
{{ $key }} = {{ if $value }}True{{ else }}False{{ end }}
{{- else if kindIs "string" $value }}
{{- if or (hasPrefix "f\"" $value) (hasPrefix "F\"" $value) (hasPrefix "r\"" $value) (hasPrefix "R\"" $value) (hasPrefix "b\"" $value) (hasPrefix "B\"" $value) }}
{{ $key }} = {{ $value }}
{{- else }}
{{ $key }} = {{ $value | quote }}
{{- end }}
{{- else if kindIs "invalid" $value }}
{{ $key }} = None
{{- else }}
{{ $key }} = {{ $value | toJson }}
{{- end }}
{{- end }}
{{- end }}
{{- /* Custom Config Overrides */}}
{{- if .Values.configOverrides }}
# Custom Overrides
{{ if .Values.configOverrides }}
# Overrides
{{- range $key, $value := .Values.configOverrides }}
# {{ $key }}
{{ tpl $value $ }}
{{- end }}
{{- end }}
{{- if .Values.configOverridesFiles }}
{{ if .Values.configOverridesFiles }}
# Overrides from files
{{- $files := .Files }}
{{- range $key, $value := .Values.configOverridesFiles }}
@@ -680,57 +187,7 @@ TALISMAN_CONFIG = {
{{- end }}
{{- end }}
{{- end -}}
{{- define "superset.initScript" -}}
#!/bin/sh
set -eu
echo "Upgrading DB schema..."
superset db upgrade
echo "Initializing roles and permissions..."
superset init
echo "Init job: Creating admin user and loading initial data..."
{{- if .Values.init.createAdmin }}
echo "Creating admin user (if not present)..."
if superset fab list-users 2>/dev/null | grep -qF {{ printf "username:%s" .Values.init.adminUser.username | squote }}; then
echo "Admin user already exists, skipping."
else
superset fab create-admin \
--username {{ .Values.init.adminUser.username | squote }} \
--firstname {{ .Values.init.adminUser.firstname | squote }} \
--lastname {{ .Values.init.adminUser.lastname | squote }} \
--email {{ .Values.init.adminUser.email | squote }} \
--password {{ .Values.init.adminUser.password | squote }}
fi
{{- else }}
echo "Skipping admin creation (init.createAdmin=false)"
{{- end }}
{{- if .Values.init.loadExamples }}
echo "Loading examples..."
superset load_examples
{{- else }}
echo "Skipping examples (init.loadExamples=false)"
{{- end }}
if [ -f "{{ .Values.extraConfigMountPath }}/import_datasources.yaml" ]; then
echo "Importing database connections..."
superset import_datasources -p {{ .Values.extraConfigMountPath }}/import_datasources.yaml
fi
echo "Init job complete."
{{- end -}}
{{/*
Deprecation warnings returns a newline-separated list of active deprecation messages,
or empty string when no deprecated keys are set. Rendered in NOTES.txt after install/upgrade.
*/}}
{{- define "superset.deprecationWarnings" -}}
{{- $conn := include "_superset.legacyConn" . | fromJson -}}
{{- if gt (len (keys $conn)) 0 }}
- supersetNode.connections.* is deprecated; use database.* and cache.* (auto-mapped for now). See UPGRADING.md
{{- end }}
{{- if .Values.serviceAccountName }}
- root serviceAccountName is deprecated; use serviceAccount.name (auto-mapped for now). See UPGRADING.md
{{- end }}
{{- end -}}
{{- define "supersetNode.selectorLabels" -}}
app.kubernetes.io/name: {{ include "superset.name" . }}

View File

@@ -37,12 +37,9 @@ spec:
template:
metadata:
annotations:
checksum/superset_config.py: {{ include "superset.config" . | sha256sum }}
checksum/superset_config.py: {{ include "superset-config" . | sha256sum }}
checksum/superset_bootstrap.sh: {{ tpl .Values.bootstrapScript . | sha256sum }}
checksum/database: {{ .Values.database | toYaml | sha256sum }}
checksum/redis: {{ .Values.cache | toYaml | sha256sum }}
checksum/config: {{ .Values.config | toYaml | sha256sum }}
checksum/featureFlags: {{ .Values.featureFlags | toYaml | sha256sum }}
checksum/connections: {{ .Values.supersetNode.connections | toYaml | sha256sum }}
checksum/extraConfigs: {{ .Values.extraConfigs | toYaml | sha256sum }}
checksum/extraSecrets: {{ .Values.extraSecrets | toYaml | sha256sum }}
checksum/extraSecretEnv: {{ .Values.extraSecretEnv | toYaml | sha256sum }}
@@ -55,9 +52,6 @@ spec:
{{- if .Values.supersetCeleryBeat.podAnnotations }}
{{- toYaml .Values.supersetCeleryBeat.podAnnotations | nindent 8 }}
{{- end }}
{{- with .Values.globalPodAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "supersetCeleryBeat.selectorLabels" . | nindent 8 }}
{{- if .Values.supersetCeleryBeat.podLabels }}

View File

@@ -36,14 +36,11 @@ spec:
template:
metadata:
annotations:
checksum/config: {{ include "superset.config" . | sha256sum }}
checksum/config: {{ include "superset-config" . | sha256sum }}
checksum/secrets: {{ tpl (toJson .Values.extraSecretEnv) . | sha256sum }}
{{- if .Values.supersetCeleryFlower.podAnnotations }}
{{- toYaml .Values.supersetCeleryFlower.podAnnotations | nindent 8 }}
{{- end }}
{{- with .Values.globalPodAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "supersetCeleryFlower.selectorLabels" . | nindent 8 }}
{{- if .Values.supersetCeleryFlower.podLabels }}

View File

@@ -43,12 +43,9 @@ spec:
template:
metadata:
annotations:
checksum/superset_config.py: {{ include "superset.config" . | sha256sum }}
checksum/superset_config.py: {{ include "superset-config" . | sha256sum }}
checksum/superset_bootstrap.sh: {{ tpl .Values.bootstrapScript . | sha256sum }}
checksum/database: {{ .Values.database | toYaml | sha256sum }}
checksum/redis: {{ .Values.cache | toYaml | sha256sum }}
checksum/config: {{ .Values.config | toYaml | sha256sum }}
checksum/featureFlags: {{ .Values.featureFlags | toYaml | sha256sum }}
checksum/connections: {{ .Values.supersetNode.connections | toYaml | sha256sum }}
checksum/extraConfigs: {{ .Values.extraConfigs | toYaml | sha256sum }}
checksum/extraSecrets: {{ .Values.extraSecrets | toYaml | sha256sum }}
checksum/extraSecretEnv: {{ .Values.extraSecretEnv | toYaml | sha256sum }}
@@ -61,9 +58,6 @@ spec:
{{- if .Values.supersetWorker.podAnnotations }}
{{- toYaml .Values.supersetWorker.podAnnotations | nindent 8 }}
{{- end }}
{{- with .Values.globalPodAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "supersetWorker.selectorLabels" . | nindent 8 }}
{{- if .Values.supersetWorker.podLabels }}

View File

@@ -44,9 +44,6 @@ spec:
{{- if .Values.supersetWebsockets.podAnnotations }}
{{- toYaml .Values.supersetWebsockets.podAnnotations | nindent 8 }}
{{- end }}
{{- with .Values.globalPodAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "supersetWebsockets.selectorLabels" . | nindent 8 }}
{{- if .Values.supersetWebsockets.podLabels }}

View File

@@ -44,13 +44,10 @@ spec:
metadata:
annotations:
# Force reload on config changes
checksum/superset_config.py: {{ include "superset.config" . | sha256sum }}
checksum/superset_init.sh: {{ include "superset.initScript" . | sha256sum }}
checksum/superset_config.py: {{ include "superset-config" . | sha256sum }}
checksum/superset_init.sh: {{ tpl .Values.init.initscript . | sha256sum }}
checksum/superset_bootstrap.sh: {{ tpl .Values.bootstrapScript . | sha256sum }}
checksum/database: {{ .Values.database | toYaml | sha256sum }}
checksum/redis: {{ .Values.cache | toYaml | sha256sum }}
checksum/config: {{ .Values.config | toYaml | sha256sum }}
checksum/featureFlags: {{ .Values.featureFlags | toYaml | sha256sum }}
checksum/connections: {{ .Values.supersetNode.connections | toYaml | sha256sum }}
checksum/extraConfigs: {{ .Values.extraConfigs | toYaml | sha256sum }}
checksum/extraSecrets: {{ .Values.extraSecrets | toYaml | sha256sum }}
checksum/extraSecretEnv: {{ .Values.extraSecretEnv | toYaml | sha256sum }}
@@ -63,9 +60,6 @@ spec:
{{- if .Values.supersetNode.podAnnotations }}
{{- toYaml .Values.supersetNode.podAnnotations | nindent 8 }}
{{- end }}
{{- with .Values.globalPodAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "supersetNode.selectorLabels" . | nindent 8 }}
{{- if .Values.supersetNode.podLabels }}

View File

@@ -26,28 +26,24 @@ metadata:
{{- include "superset.labels" . | nindent 4 }}
type: Opaque
stringData:
DB_HOST: {{ include "superset.db.host" . | quote }}
DB_PORT: {{ include "superset.db.port" . | quote }}
DB_USER: {{ include "superset.db.user" . | quote }}
DB_PASS: {{ include "superset.db.password" . | quote }}
DB_NAME: {{ include "superset.db.name" . | quote }}
{{- if .Values.database.ssl.enabled }}
DB_SSL_MODE: {{ .Values.database.ssl.mode | default "require" | quote }}
{{- end }}
{{- if .Values.cache.enabled }}
REDIS_HOST: {{ include "superset.redis.host" . | quote }}
REDIS_PORT: {{ include "superset.redis.port" . | quote }}
REDIS_USER: {{ include "superset.redis.user" . | quote }}
{{- if include "superset.redis.password" . }}
REDIS_PASSWORD: {{ include "superset.redis.password" . | quote }}
{{- end }}
REDIS_DB: {{ include "superset.redis.cacheDb" . | quote }}
REDIS_CELERY_DB: {{ include "superset.redis.celeryDb" . | quote }}
REDIS_PROTO: {{ include "superset.redis.proto" . | quote }}
{{- if .Values.cache.driver }}
REDIS_DRIVER: {{ .Values.cache.driver | quote }}
REDIS_HOST: {{ tpl .Values.supersetNode.connections.redis_host . | quote }}
REDIS_USER: {{ .Values.supersetNode.connections.redis_user | quote }}
{{- if .Values.supersetNode.connections.redis_password }}
REDIS_PASSWORD: {{ .Values.supersetNode.connections.redis_password | quote }}
{{- end }}
REDIS_PORT: {{ .Values.supersetNode.connections.redis_port | quote }}
REDIS_PROTO: {{ if .Values.supersetNode.connections.redis_ssl.enabled }}"rediss"{{ else }}"redis"{{ end }}
REDIS_DRIVER: {{ .Values.supersetNode.connections.redis_driver | quote }}
REDIS_DB: {{ .Values.supersetNode.connections.redis_cache_db | quote }}
REDIS_CELERY_DB: {{ .Values.supersetNode.connections.redis_celery_db | quote }}
{{- if .Values.supersetNode.connections.redis_ssl.enabled }}
REDIS_SSL_CERT_REQS: {{ .Values.supersetNode.connections.redis_ssl.ssl_cert_reqs | default "CERT_NONE" | quote }}
{{- end }}
DB_HOST: {{ tpl .Values.supersetNode.connections.db_host . | quote }}
DB_PORT: {{ .Values.supersetNode.connections.db_port | quote }}
DB_USER: {{ .Values.supersetNode.connections.db_user | quote }}
DB_PASS: {{ .Values.supersetNode.connections.db_pass | quote }}
DB_NAME: {{ .Values.supersetNode.connections.db_name | quote }}
{{- if .Values.extraSecretEnv }}
{{- range $key, $value := .Values.extraSecretEnv }}
{{ $key }}: {{ $value | quote }}

View File

@@ -27,9 +27,9 @@ metadata:
type: Opaque
stringData:
superset_config.py: |
{{- include "superset.config" . | nindent 4 }}
{{- include "superset-config" . | nindent 4 }}
superset_init.sh: |
{{- include "superset.initScript" . | nindent 4 }}
{{- tpl .Values.init.initscript . | nindent 4 }}
superset_bootstrap.sh: |
{{- tpl .Values.bootstrapScript . | nindent 4 }}

View File

@@ -1,132 +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.
#
suite: config checksums + global pod annotations
templates:
- deployment.yaml
- deployment-worker.yaml
- deployment-beat.yaml
- deployment-flower.yaml
- deployment-ws.yaml
tests:
- it: main deployment has new config checksums, not the old connections checksum
template: deployment.yaml
asserts:
- exists:
path: 'spec.template.metadata.annotations["checksum/database"]'
- exists:
path: 'spec.template.metadata.annotations["checksum/config"]'
- notExists:
path: 'spec.template.metadata.annotations["checksum/connections"]'
- it: main deployment applies globalPodAnnotations
template: deployment.yaml
set:
globalPodAnnotations:
team: data
asserts:
- equal:
path: 'spec.template.metadata.annotations.team'
value: data
- it: globalPodAnnotations coexist with per-component podAnnotations on deployment.yaml
template: deployment.yaml
set:
globalPodAnnotations:
global: "yes"
supersetNode:
podAnnotations:
component: "yes"
asserts:
- equal:
path: 'spec.template.metadata.annotations.global'
value: "yes"
- equal:
path: 'spec.template.metadata.annotations.component'
value: "yes"
- it: worker deployment has config and database checksums
template: deployment-worker.yaml
asserts:
- exists:
path: 'spec.template.metadata.annotations["checksum/database"]'
- exists:
path: 'spec.template.metadata.annotations["checksum/config"]'
- it: worker deployment applies globalPodAnnotations
template: deployment-worker.yaml
set:
globalPodAnnotations:
team: data
asserts:
- equal:
path: 'spec.template.metadata.annotations.team'
value: data
- it: beat deployment has config and database checksums
template: deployment-beat.yaml
set:
supersetCeleryBeat:
enabled: true
asserts:
- exists:
path: 'spec.template.metadata.annotations["checksum/database"]'
- exists:
path: 'spec.template.metadata.annotations["checksum/config"]'
- it: beat deployment applies globalPodAnnotations
template: deployment-beat.yaml
set:
supersetCeleryBeat:
enabled: true
globalPodAnnotations:
team: data
asserts:
- equal:
path: 'spec.template.metadata.annotations.team'
value: data
- it: flower deployment has config checksum
template: deployment-flower.yaml
set:
supersetCeleryFlower:
enabled: true
asserts:
- exists:
path: 'spec.template.metadata.annotations["checksum/config"]'
- it: flower deployment applies globalPodAnnotations
template: deployment-flower.yaml
set:
supersetCeleryFlower:
enabled: true
globalPodAnnotations:
team: data
asserts:
- equal:
path: 'spec.template.metadata.annotations.team'
value: data
- it: websockets deployment has wsconfig checksum
template: deployment-ws.yaml
set:
supersetWebsockets:
enabled: true
asserts:
- exists:
path: 'spec.template.metadata.annotations["checksum/wsconfig"]'
- it: websockets deployment applies globalPodAnnotations
template: deployment-ws.yaml
set:
supersetWebsockets:
enabled: true
globalPodAnnotations:
team: data
asserts:
- equal:
path: 'spec.template.metadata.annotations.team'
value: data

View File

@@ -1,94 +0,0 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
suite: superset_config.py rendering
templates:
- secret-superset-config.yaml
tests:
- it: builds SQLALCHEMY_DATABASE_URI from env with percent-encoded creds
set: {database: {host: pg.svc, port: 5432, user: su, password: pw, name: sset}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'SQLALCHEMY_DATABASE_URI = f"postgresql\+psycopg2://\{quote\(env\(.DB_USER'}
- it: honors database.uri override
set: {database.uri: "postgresql://x:y@h:5432/d"}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'SQLALCHEMY_DATABASE_URI = "postgresql://x:y@h:5432/d"'}
- it: imports required for env/json handling are present
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'import json'}
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'from urllib\.parse import quote'}
- it: redis creds are percent-encoded and read from env
set: {cache: {enabled: true, password: "p@ss"}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'quote\(env\(.REDIS_PASSWORD'}
- it: config map values render via json.loads
set: {config: {MY_MAP: {enabled: true}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'MY_MAP = json\.loads\('}
- it: config nil values render as None
set: {config: {MY_NIL: null}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'MY_NIL = None'}
- it: supersetWorker healthCheck enabled renders readiness file path
set: {supersetWorker: {healthCheck: {enabled: true}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: '/tmp/celery_worker_ready'}
- it: SQLALCHEMY_TRACK_MODIFICATIONS defaults False
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'SQLALCHEMY_TRACK_MODIFICATIONS = False'}
- it: renders feature flags
set: {featureFlags: {ALERT_REPORTS: true}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: '"ALERT_REPORTS": True'}
- it: SQLALCHEMY_TRACK_MODIFICATIONS user-set true renders Python True
set: {config: {SQLALCHEMY_TRACK_MODIFICATIONS: true}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'SQLALCHEMY_TRACK_MODIFICATIONS = True'}
- it: celeryConfig without imports emits a safe empty CELERY_IMPORTS (no AttributeError)
set: {config: {celeryConfig: {broker_url: "redis://x"}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'CELERY_IMPORTS = \(\)'}
- notMatchRegex: {path: 'stringData["superset_config.py"]', pattern: 'CELERY_IMPORTS = CeleryConfig\.imports'}
- it: legacy db_type maps to the SQLAlchemy driver (deprecation path)
set: {supersetNode: {connections: {db_type: "postgresql+pg8000"}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'SQLALCHEMY_DATABASE_URI = f"postgresql\+pg8000://'}
- it: init script single-quotes the admin username (no shell injection)
set: {init: {createAdmin: true, adminUser: {password: "x"}}}
asserts:
- matchRegex: {path: 'stringData["superset_init.sh"]', pattern: "grep -qF 'username:"}
- notMatchRegex: {path: 'stringData["superset_init.sh"]', pattern: 'create-admin[\s\S]*\|\| true'}
- it: sentinel disabled does not fail render
set: {cache: {enabled: true, sentinel: {enabled: false}}}
asserts:
- hasDocuments:
count: 1
- it: ssl certfile path is quoted
set: {cache: {enabled: true, ssl: {enabled: true, certfile: "/etc/ssl/cert.pem"}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: '"CACHE_REDIS_SSL_CERTFILE": "/etc/ssl/cert.pem"'}
- it: explicit JWT cookie secure false is respected
set: {supersetWebsockets: {enabled: true}, config: {GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE: false}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE = False'}
- it: celeryConfig non-string values render as valid Python via json.loads
set: {config: {celeryConfig: {broker_url: "redis://x", task_acks_late: true}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: 'task_acks_late = json\.loads\('}
- it: featureFlags non-scalar values render as valid Python via json.loads
set: {featureFlags: {SOME_MAP: {nested: true}}}
asserts:
- matchRegex: {path: 'stringData["superset_config.py"]', pattern: '"SOME_MAP": json\.loads\('}

View File

@@ -1,28 +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.
#
suite: deprecation path does not fail render
templates:
- secret-superset-config.yaml
tests:
- it: legacy supersetNode.connections still renders (no fail)
set: {supersetNode.connections.db_host: legacy}
asserts:
- hasDocuments: {count: 1}
- it: legacy root serviceAccountName still renders (no fail)
set: {serviceAccountName: my-sa}
asserts:
- hasDocuments: {count: 1}

View File

@@ -1,63 +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.
#
suite: superset_init.sh rendering
templates:
- secret-superset-config.yaml
tests:
- it: renders superset db upgrade in init script
asserts:
- matchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'superset db upgrade'
- it: renders superset init in init script
asserts:
- matchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'superset init'
- it: renders db upgrade before admin creation
set:
init:
createAdmin: true
adminUser:
username: admin
firstname: Admin
lastname: User
email: admin@example.com
password: adminpass
asserts:
- matchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'superset db upgrade'
- matchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'superset init'
- matchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'fab create-admin'
- it: admin creation is idempotent and does not swallow real failures
set:
init:
createAdmin: true
adminUser:
password: "x"
asserts:
- matchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'superset fab list-users'
- notMatchRegex:
path: 'stringData["superset_init.sh"]'
pattern: 'create-admin.*\|\| true'

View File

@@ -1,71 +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.
#
suite: secret-env schema + deprecation mapping
templates:
- secret-env.yaml
tests:
- it: emits DB_* and REDIS_* from new schema
set:
database: {host: pg.svc, port: 5432, user: su, password: pw, name: superset}
cache: {enabled: true, host: rd.svc, port: 6379, cacheDb: 1, celeryDb: 0}
asserts:
- equal: {path: 'stringData.DB_HOST', value: pg.svc}
- equal: {path: 'stringData.DB_PORT', value: "5432"}
- equal: {path: 'stringData.REDIS_HOST', value: rd.svc}
- it: maps ALL legacy supersetNode.connections.* keys (deprecation path, no fail)
set:
cache:
enabled: true
supersetNode:
connections:
db_host: legacy-pg
db_port: "5555"
db_user: legacy-user
db_pass: legacy-pass
db_name: legacy-name
redis_host: legacy-rd
redis_port: "6380"
redis_cache_db: "7"
redis_celery_db: "8"
redis_driver: rediss
asserts:
- equal: {path: 'stringData.DB_HOST', value: legacy-pg}
- equal: {path: 'stringData.DB_PORT', value: "5555"}
- equal: {path: 'stringData.DB_USER', value: legacy-user}
- equal: {path: 'stringData.DB_PASS', value: legacy-pass}
- equal: {path: 'stringData.DB_NAME', value: legacy-name}
- equal: {path: 'stringData.REDIS_HOST', value: legacy-rd}
- equal: {path: 'stringData.REDIS_PORT', value: "6380"}
- equal: {path: 'stringData.REDIS_DB', value: "7"}
- equal: {path: 'stringData.REDIS_CELERY_DB', value: "8"}
- equal: {path: 'stringData.REDIS_PROTO', value: rediss}
- it: new-schema values still win over legacy when both are set
set:
database: {host: new-host, port: 1234}
supersetNode:
connections: {db_host: old-host, db_port: "9999"}
asserts:
- equal: {path: 'stringData.DB_HOST', value: new-host}
- equal: {path: 'stringData.DB_PORT', value: "1234"}
- it: emits REDIS_DRIVER when cache.driver set
set: {cache: {enabled: true, driver: "rediss+managed"}}
asserts:
- equal: {path: 'stringData.REDIS_DRIVER', value: "rediss+managed"}
- it: omits redis vars when cache disabled
set: {cache.enabled: false}
asserts:
- notExists: {path: 'stringData.REDIS_HOST'}

View File

@@ -1,28 +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.
#
suite: serviceAccountName resolution
templates:
- serviceaccount.yaml
tests:
- it: uses serviceAccount.name when set
set: {serviceAccount: {create: true, name: my-sa}}
asserts:
- equal: {path: 'metadata.name', value: my-sa}
- it: falls back to legacy root serviceAccountName (deprecation path)
set: {serviceAccount: {create: true}, serviceAccountName: legacy-sa}
asserts:
- equal: {path: 'metadata.name', value: legacy-sa}

View File

@@ -1,30 +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.
#
suite: values defaults
templates:
- secret-env.yaml
tests:
- it: renders with new database defaults
set:
database.host: db.example
cache.enabled: true
asserts:
- hasDocuments:
count: 1
- equal:
path: 'stringData.DB_HOST'
value: db.example

View File

@@ -1,140 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"additionalProperties": true,
"properties": {
"database": {
"type": "object",
"additionalProperties": true,
"properties": {
"uri": {
"type": ["string", "null"]
},
"driver": {
"type": ["string", "null"]
},
"host": {
"type": ["string", "null"]
},
"port": {
"type": ["integer", "string", "null"]
},
"user": {
"type": ["string", "null"]
},
"password": {
"type": ["string", "null"]
},
"name": {
"type": ["string", "null"]
},
"ssl": {
"type": "object",
"additionalProperties": true,
"properties": {
"enabled": {
"type": "boolean"
},
"mode": {
"type": "string"
}
}
}
}
},
"cache": {
"type": "object",
"additionalProperties": true,
"properties": {
"enabled": {
"type": "boolean"
},
"driver": {
"type": "string"
},
"host": {
"type": ["string", "null"]
},
"port": {
"type": ["integer", "string", "null"]
},
"cacheDb": {
"type": ["integer", "string", "null"]
},
"celeryDb": {
"type": ["integer", "string", "null"]
},
"ssl": {
"type": "object",
"additionalProperties": true,
"properties": {
"enabled": {
"type": "boolean"
}
}
}
}
},
"cluster": {
"type": "object",
"additionalProperties": true,
"properties": {
"domain": {
"type": "string"
},
"databaseServiceName": {
"type": ["string", "null"]
},
"redisServiceName": {
"type": ["string", "null"]
},
"websocketServiceName": {
"type": ["string", "null"]
}
}
},
"config": {
"type": "object",
"additionalProperties": true
},
"featureFlags": {
"type": "object",
"additionalProperties": true
},
"serviceAccount": {
"type": "object",
"additionalProperties": true,
"properties": {
"create": {
"type": "boolean"
},
"name": {
"type": "string"
},
"annotations": {
"type": "object",
"additionalProperties": true
}
}
},
"globalPodAnnotations": {
"type": "object",
"additionalProperties": true
},
"image": {
"type": "object",
"additionalProperties": true,
"properties": {
"repository": {
"type": "string"
},
"tag": {
"type": ["string", "null"]
},
"pullPolicy": {
"type": "string"
}
}
}
}
}

View File

@@ -28,30 +28,9 @@ nameOverride: ~
# -- Provide a name to override the full names of resources
fullnameOverride: ~
# -- Kubernetes cluster configuration
# Used for constructing service URLs between chart components
cluster:
# -- Kubernetes cluster domain (default: .svc.cluster.local)
# Override if using a custom cluster domain
domain: ".svc.cluster.local"
# -- Database service name (default: {{ .Release.Name }}-postgresql)
# Override if using a different service name for the database
databaseServiceName: ~
# -- Redis service name (default: {{ .Release.Name }}-redis-headless)
# Override if using a different service name for Redis
redisServiceName: ~
# -- WebSocket service name (default: {{ .Release.Name }}-ws)
# Override if using a different service name for the WebSocket service
websocketServiceName: ~
# -- Labels to be added to all resources
extraLabels: {}
# -- Global pod annotations to be added to all pods
# Use this to set annotations that apply to all Superset components
# Component-specific podAnnotations will be merged with these global annotations
globalPodAnnotations: {}
# -- User ID directive. This user must have enough permissions to run the bootstrap script
# Running containers as root is not recommended in production. Change this to another UID - e.g. 1000 to be more secure
runAsUser: 0
@@ -67,8 +46,6 @@ serviceAccountName: ~
serviceAccount:
# -- Create custom service account for Superset. If create: true and serviceAccountName is not provided, `superset.fullname` will be used.
create: false
# -- Service account name to use (if not specified, defaults to release name + chart name)
name: ""
annotations: {}
# -- Install additional packages and do any other bootstrap configuration in this script
@@ -267,89 +244,6 @@ hostAliases: []
# - nodns.my.lan
# ip: 18.27.36.45
# -- Database connection configuration for the Superset metadata database
database:
# -- Full database URI (overrides host/port/user/pass/name if set)
# Example: "postgresql+psycopg2://user:pass@host:5432/dbname"
uri: ~
# -- Database driver used when uri is not set (default: postgresql+psycopg2 when unset;
# legacy supersetNode.connections.db_type is honored when this is unset)
driver: ~
# -- Database host (default: {{ .Release.Name }}-postgresql)
host: ~
# -- Database port (default: 5432 when unset; legacy supersetNode.connections.db_port is honored)
port: ~
# -- Database user (default: superset, resolved via superset.db.user)
user: ~
# -- Database password (default: superset, resolved via superset.db.password)
# ⚠️ CHANGE THIS for production
password: ~
# -- Database name (default: superset, resolved via superset.db.name)
name: ~
# -- Database SSL configuration
ssl:
enabled: false
mode: require
# -- Redis cache configuration for Superset
# Redis is optional but recommended for caching and Celery.
# If redis.enabled (chart dependency) is true, defaults point to the chart's Redis instance.
cache:
# -- Enable Redis-based features (cache, Celery). Set to false to disable Redis usage entirely.
enabled: true
# -- Full Redis cache URL (overrides host/port/user/pass if set)
cacheUrl: ~
# -- Full Redis Celery URL (overrides host/port/user/pass if set)
celeryUrl: ~
# -- Redis host (default: {{ .Release.Name }}-redis-headless)
host: ~
# -- Redis port (default: 6379 when unset; legacy supersetNode.connections.redis_port is honored)
port: ~
# -- Redis user (optional, for Redis ACL)
user: ""
# -- Redis password
password: ~
# -- Redis database number for cache (default: 1 when unset; legacy redis_cache_db is honored)
cacheDb: ~
# -- Redis database number for Celery (default: 0 when unset; legacy redis_celery_db is honored)
celeryDb: ~
# -- Cache key prefix
keyPrefix: "superset_"
# -- Default cache timeout in seconds
defaultTimeout: 86400
# -- Results backend key prefix
resultsBackendKeyPrefix: "superset_results"
# -- Async queries configuration
asyncQueries:
keyPrefix: "qc-"
timeout: 86400
# -- Redis Sentinel configuration (optional)
sentinel: ~
# -- Redis SSL configuration
ssl:
enabled: false
ssl_cert_reqs: required
certfile: ~
keyfile: ~
ca_certs: ~
# -- Custom Redis driver (e.g. TLS/managed variants); overrides the redis proto in URLs when set.
# Ports the legacy supersetNode.connections.redis_driver escape hatch.
driver: ""
# -- Superset configuration properties
# Set any configuration property from superset/config.py here
# See https://github.com/apache/superset/blob/master/superset/config.py for all available options
config: {}
# SECRET_KEY: "your-secret-key-here"
# ROW_LIMIT: 50000
# DEBUG: false
# -- Feature flags configuration
# See https://github.com/apache/superset/blob/master/RESOURCES/FEATURE_FLAGS.md
featureFlags: {}
# ALERT_REPORTS: true
# DASHBOARD_RBAC: true
# Superset node configuration
supersetNode:
replicas:
@@ -376,8 +270,28 @@ supersetNode:
- "/bin/sh"
- "-c"
- ". {{ .Values.configMountPath }}/superset_bootstrap.sh; exec /usr/bin/run-server.sh"
# DEPRECATED: use database.* and cache.* instead (still honored if set). See UPGRADING.md
connections: {}
connections:
# -- Change in case of bringing your own redis and then also set redis.enabled:false
redis_host: "{{ .Release.Name }}-redis-headless"
redis_port: "6379"
redis_user: ""
# redis_password: superset
redis_cache_db: "1"
redis_celery_db: "0"
# Or SSL port is usually 6380
# Update following for using Redis with SSL
redis_ssl:
enabled: false
ssl_cert_reqs: CERT_NONE
redis_driver: ""
# You need to change below configuration incase bringing own PostgresSQL instance and also set postgresql.enabled:false
# -- Database type for Superset metadata (Supported types: "postgresql", "mysql")
db_type: "postgresql"
db_host: "{{ .Release.Name }}-postgresql"
db_port: "5432"
db_user: superset
db_pass: superset
db_name: superset
env: {}
# -- If true, forces deployment to reload on each upgrade
forceReload: false
@@ -594,17 +508,6 @@ supersetWorker:
startupProbe: {}
# -- No startup/readiness probes by default since we don't really care about its startup time (it doesn't serve traffic)
readinessProbe: {}
# -- Celery worker file-based health check (worker writes readiness/liveness files via signals;
# point supersetWorker.readinessProbe/livenessProbe at these files to use them)
healthCheck:
# -- Enable the file-based Celery worker health check
enabled: false
# -- Readiness file (created when the worker is ready, removed on shutdown)
readinessFile: "/tmp/celery_worker_ready"
# -- Liveness file (touched periodically by a heartbeat thread)
livenessFile: "/tmp/celery_worker_alive"
# -- Seconds between liveness heartbeats
livenessHeartbeatInterval: 10
# -- Set priorityClassName for supersetWorker pods
priorityClassName: ~
@@ -967,11 +870,8 @@ init:
requests:
cpu: "250m"
memory: "128Mi"
# -- DEPRECATED: this field is no longer used by the chart.
# The init script is rendered entirely from the internal `superset.initScript` template
# (which runs `superset db upgrade`, `superset init`, admin creation, and examples).
# Any customization placed here is silently ignored. See UPGRADING.md.
# @default -- unused; kept for backwards-compatibility only
# -- A Superset init script
# @default -- a script to create admin user and initialize roles
initscript: |-
#!/bin/sh
set -eu

View File

@@ -43,9 +43,9 @@ dependencies = [
# ``google-auth`` 2.53+ dropped it, so Superset must declare it
# explicitly to keep fresh ``pip install apache-superset`` working
# without the ``base.txt`` lock file (#40962).
"cachetools>=7.1.4, <8",
"celery>=5.6.3, <6.0.0",
"click>=8.4.2",
"cachetools>=6.2.1, <7",
"celery>=5.3.6, <6.0.0",
"click>=8.4.0",
"click-option-group",
"colorama",
"flask-cors>=6.0.5, <7.0",
@@ -67,7 +67,7 @@ dependencies = [
"gunicorn>=26.0.0, <27; sys_platform != 'win32'",
"hashids>=1.3.1, <2",
# holidays>=0.45 required for security fix
"holidays>=0.99, <1",
"holidays>=0.45, <1",
"humanize",
"isodate",
"jsonpath-ng>=1.8.0, <2",
@@ -106,11 +106,10 @@ dependencies = [
"shillelagh[gsheetsapi]>=1.4.4, <2.0",
"sshtunnel>=0.4.0, <0.5",
"simplejson>=4.1.1",
"slack_sdk>=3.43.0, <4",
"slack_sdk>=3.42.0, <4",
"sqlalchemy>=1.4, <2",
"sqlalchemy-continuum>=1.6.0, <2.0.0",
"sqlalchemy-utils>=0.42.1, <0.43", # expanding lowerbound to work with pydoris
"sqlglot>=30.12.0, <31",
"sqlglot>=30.8.0, <31",
# newer pandas needs 0.9+
"tabulate>=0.10.0, <1.0",
"typing-extensions>=4, <5",
@@ -130,7 +129,7 @@ bigquery = [
"sqlalchemy-bigquery>=1.17.0",
"google-cloud-bigquery>=3.42.1",
]
clickhouse = ["clickhouse-connect>=1.4.2, <2.0"]
clickhouse = ["clickhouse-connect>=1.1.1, <2.0"]
cockroachdb = ["cockroachdb>=0.3.5, <0.4"]
crate = ["sqlalchemy-cratedb>=0.41.0, <1"]
d1 = [
@@ -155,17 +154,17 @@ elasticsearch = ["elasticsearch-dbapi>=0.2.13, <0.3.0"]
exasol = ["sqlalchemy-exasol>=2.4.0, <8.0"]
excel = ["xlrd>=2.0.2, <2.1"]
fastmcp = [
"fastmcp>=3.4.3,<4.0",
"fastmcp>=3.4.2,<4.0",
# tiktoken backs the response-size-guard token estimator. Without
# it, the middleware falls back to a coarser character-based
# heuristic that under-counts JSON-heavy MCP responses.
"tiktoken>=0.13.0,<1.0",
]
firebird = ["sqlalchemy-firebird>=0.8.0, <2.2"]
firebird = ["sqlalchemy-firebird>=0.7.0, <2.2"]
firebolt = ["firebolt-sqlalchemy>=1.0.0, <2"]
gevent = ["gevent>=26.4.0"]
gsheets = ["shillelagh[gsheetsapi]>=1.4.4, <2"]
hana = ["hdbcli==2.29.23", "sqlalchemy_hana==3.0.3"]
hana = ["hdbcli==2.28.21", "sqlalchemy_hana==0.4.0"]
hive = [
"pyhive[hive]>=0.6.5;python_version<'3.11'",
"pyhive[hive_pure_sasl]>=0.7.0",
@@ -173,7 +172,7 @@ hive = [
"thrift>=0.23.0, <1.0.0",
"thrift_sasl>=0.4.3, < 1.0.0",
]
impala = ["impyla>=0.24.0, <0.25"]
impala = ["impyla>0.16.2, <0.23"]
kusto = ["sqlalchemy-kusto>=3.1.2, <4"]
kylin = ["kylinpy>=2.8.4, <2.9"]
mssql = ["pymssql>=2.3.13, <3"]
@@ -189,17 +188,17 @@ ocient = [
oracle = ["oracledb>=2.0.0, <5"]
parseable = ["sqlalchemy-parseable>=0.1.6,<0.2.0"]
pinot = ["pinotdb>=5.0.0, <10.0.0"]
playwright = ["playwright>=1.61.0, <2"]
playwright = ["playwright>=1.60.0, <2"]
postgres = ["psycopg2-binary==2.9.12"]
presto = ["pyhive[presto]>=0.6.5"]
trino = ["trino>=0.338.0"]
trino = ["trino>=0.337.0"]
prophet = ["prophet>=1.1.6, <2"]
redshift = ["sqlalchemy-redshift>=0.8.1, <0.9"]
risingwave = ["sqlalchemy-risingwave"]
shillelagh = ["shillelagh[all]>=1.4.4, <2"]
singlestore = ["sqlalchemy-singlestoredb>=1.2.1, <2"]
snowflake = ["snowflake-sqlalchemy>=1.10.2, <2"]
sqlite = ["syntaqlite>=0.7.0,<0.8.0"]
sqlite = ["syntaqlite>=0.1.0,<0.5.0"]
spark = [
"pyhive[hive]>=0.6.5;python_version<'3.11'",
"pyhive[hive_pure_sasl]>=0.7",
@@ -210,12 +209,12 @@ tdengine = [
"taospy>=2.8.9",
"taos-ws-py>=0.6.9"
]
teradata = ["teradatasql>=20.0.0.62"]
teradata = ["teradatasql>=16.20.0.23"]
thumbnails = [] # deprecated, will be removed in 7.0
vertica = ["sqlalchemy-vertica-python>= 0.6.3, < 0.7"]
netezza = ["nzalchemy>=11.0.2, < 11.2"]
starrocks = ["starrocks>=1.3.3, <2"]
doris = ["pydoris>=1.2.0, <2.0.0"]
doris = ["pydoris>=1.0.0, <2.0.0"]
oceanbase = ["oceanbase_py>=0.0.1.2"]
ydb = ["ydb-sqlalchemy>=0.1.22", "ydb-sqlglot-plugin>=0.2.8"]
development = [
@@ -236,7 +235,7 @@ development = [
"pyfakefs",
"pyinstrument>=5.1.2,<6",
"pylint",
"pytest<10.0.0", # cap below the next major until validated; the earlier <8 pin (current_app proxy timing) no longer reproduces
"pytest<8.0.0", # hairy issue with pytest >=8 where current_app proxies are not set in time
"pytest-asyncio",
"pytest-cov",
"pytest-mock",
@@ -244,7 +243,7 @@ development = [
"ruff",
"sqloxide",
"statsd",
"syntaqlite>=0.7.0,<0.8.0",
"syntaqlite>=0.4.2,<0.5.0",
]
[project.urls]

View File

@@ -35,12 +35,13 @@ filterwarnings =
# error:"SqlMetric" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
# error:"TableColumn" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
# error:"TaggedObject" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
error:The autoload parameter is deprecated:sqlalchemy.exc.RemovedIn20Warning
error:The current statement is being autocommitted using implicit autocommit:sqlalchemy.exc.RemovedIn20Warning
error:The connection.execute\(\) method:sqlalchemy.exc.RemovedIn20Warning
# error:The ``as_declarative\(\)`` function is now available:sqlalchemy.exc.RemovedIn20Warning
# error:The autoload parameter is deprecated:sqlalchemy.exc.RemovedIn20Warning
# error:The connection.execute\(\) method:sqlalchemy.exc.RemovedIn20Warning
# error:The current statement is being autocommitted using implicit autocommit:sqlalchemy.exc.RemovedIn20Warning
error:The ``declarative_base\(\)`` function is now available:sqlalchemy.exc.RemovedIn20Warning
error:The Engine.execute\(\) method is considered legacy:sqlalchemy.exc.RemovedIn20Warning
# error:The `database` package is deprecated:sqlalchemy.exc.RemovedIn20Warning
# error:The ``declarative_base\(\)`` function is now available:sqlalchemy.exc.RemovedIn20Warning
# error:The Engine.execute\(\) method is considered legacy:sqlalchemy.exc.RemovedIn20Warning
error:The legacy calling style of select\(\) is deprecated:sqlalchemy.exc.RemovedIn20Warning
error:The "whens" argument to case:sqlalchemy.exc.RemovedIn20Warning
# error:"User" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning

View File

@@ -44,11 +44,11 @@ cachelib==0.13.0
# via
# flask-caching
# flask-session
cachetools==7.1.4
cachetools==6.2.1
# via apache-superset (pyproject.toml)
cattrs==25.1.1
# via requests-cache
celery==5.6.3
celery==5.5.2
# via apache-superset (pyproject.toml)
certifi==2026.5.20
# via
@@ -60,7 +60,7 @@ cffi==2.0.0
# pynacl
charset-normalizer==3.4.2
# via requests
click==8.4.2
click==8.4.1
# via
# apache-superset (pyproject.toml)
# celery
@@ -175,7 +175,7 @@ h11==0.16.0
# via wsproto
hashids==1.3.1
# via apache-superset (pyproject.toml)
holidays==0.100
holidays==0.82
# via apache-superset (pyproject.toml)
humanize==4.12.3
# via apache-superset (pyproject.toml)
@@ -207,7 +207,7 @@ jsonschema-specifications==2025.4.1
# via
# jsonschema
# openapi-schema-validator
kombu==5.6.2
kombu==5.5.3
# via celery
limits==5.1.0
# via flask-limiter
@@ -272,7 +272,6 @@ packaging==25.0
# apispec
# deprecation
# gunicorn
# kombu
# limits
# shillelagh
pandas==2.1.4
@@ -390,7 +389,7 @@ six==1.17.0
# python-dateutil
# rfc3339-validator
# wtforms-json
slack-sdk==3.43.0
slack-sdk==3.42.0
# via apache-superset (pyproject.toml)
sniffio==1.3.1
# via trio
@@ -405,16 +404,13 @@ sqlalchemy==1.4.54
# flask-sqlalchemy
# marshmallow-sqlalchemy
# shillelagh
# sqlalchemy-continuum
# sqlalchemy-utils
sqlalchemy-continuum==1.6.0
# via apache-superset (pyproject.toml)
sqlalchemy-utils==0.42.1
# via
# apache-superset (pyproject.toml)
# apache-superset-core
# flask-appbuilder
sqlglot==30.12.0
sqlglot==30.8.0
# via
# apache-superset (pyproject.toml)
# apache-superset-core
@@ -448,8 +444,6 @@ tzdata==2025.2
# via
# kombu
# pandas
tzlocal==5.4.4
# via celery
url-normalize==2.2.1
# via requests-cache
urllib3==2.7.0

View File

@@ -97,7 +97,7 @@ cachelib==0.13.0
# -c requirements/base-constraint.txt
# flask-caching
# flask-session
cachetools==7.1.4
cachetools==6.2.1
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -108,7 +108,7 @@ cattrs==25.1.1
# via
# -c requirements/base-constraint.txt
# requests-cache
celery==5.6.3
celery==5.5.2
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -130,7 +130,7 @@ charset-normalizer==3.4.2
# via
# -c requirements/base-constraint.txt
# requests
click==8.4.2
click==8.4.1
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -238,9 +238,9 @@ et-xmlfile==2.0.0
# openpyxl
exceptiongroup==1.3.0
# via fastmcp-slim
fastmcp==3.4.4
fastmcp==3.4.2
# via apache-superset
fastmcp-slim==3.4.4
fastmcp-slim==3.4.2
# via fastmcp
filelock==3.20.3
# via
@@ -407,7 +407,7 @@ hashids==1.3.1
# via
# -c requirements/base-constraint.txt
# apache-superset
holidays==0.100
holidays==0.82
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -503,7 +503,7 @@ keyring==25.7.0
# via py-key-value-aio
kiwisolver==1.4.7
# via matplotlib
kombu==5.6.2
kombu==5.5.3
# via
# -c requirements/base-constraint.txt
# celery
@@ -635,7 +635,6 @@ packaging==25.0
# fastmcp-slim
# google-cloud-bigquery
# gunicorn
# kombu
# limits
# matplotlib
# pytest
@@ -959,7 +958,7 @@ six==1.17.0
# python-dateutil
# rfc3339-validator
# wtforms-json
slack-sdk==3.43.0
slack-sdk==3.42.0
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -984,21 +983,16 @@ sqlalchemy==1.4.54
# marshmallow-sqlalchemy
# shillelagh
# sqlalchemy-bigquery
# sqlalchemy-continuum
# sqlalchemy-utils
sqlalchemy-bigquery==1.17.0
# via apache-superset
sqlalchemy-continuum==1.6.0
# via
# -c requirements/base-constraint.txt
# apache-superset
sqlalchemy-utils==0.42.1
# via
# -c requirements/base-constraint.txt
# apache-superset
# apache-superset-core
# flask-appbuilder
sqlglot==30.12.0
sqlglot==30.8.0
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -1017,7 +1011,7 @@ starlette==1.3.1
# mcp
statsd==4.0.1
# via apache-superset
syntaqlite==0.7.1
syntaqlite==0.4.2
# via apache-superset
tabulate==0.10.0
# via
@@ -1033,7 +1027,7 @@ tqdm==4.67.1
# via
# cmdstanpy
# prophet
trino==0.338.0
trino==0.337.0
# via apache-superset
trio==0.33.0
# via
@@ -1078,11 +1072,8 @@ tzdata==2025.2
# -c requirements/base-constraint.txt
# kombu
# pandas
tzlocal==5.4.4
# via
# -c requirements/base-constraint.txt
# celery
# trino
tzlocal==5.2
# via trino
uncalled-for==0.2.0
# via fastmcp-slim
url-normalize==2.2.1

View File

@@ -153,11 +153,10 @@ def main( # noqa: C901
)
print(f"Migration goes from {down_revision} to {revision}")
with db.engine.connect() as conn:
current_revision = conn.execute(
text("SELECT version_num FROM alembic_version")
).scalar()
print(f"Current version of the DB is {current_revision}")
current_revision = db.engine.execute(
text("SELECT version_num FROM alembic_version")
).scalar()
print(f"Current version of the DB is {current_revision}")
if current_revision != down_revision:
if not force:

View File

@@ -1,49 +0,0 @@
#!/usr/bin/env bash
# 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.
set -e
script_dir="$(dirname "$(realpath "$0")")"
root_dir="$(dirname "$script_dir")"
frontend_dir=superset-frontend
if [[ ! -d "$root_dir/$frontend_dir" ]]; then
echo "Error: $frontend_dir directory not found in $root_dir" >&2
exit 1
fi
cd "$root_dir/$frontend_dir"
# Filter files to only include JS/TS files and remove the frontend dir prefix
js_ts_files=()
for file in "$@"; do
# Remove superset-frontend/ prefix if present
cleaned_file="${file#$frontend_dir/}"
# Only include JS/TS files (Emotion styles live in these)
if [[ "$cleaned_file" =~ \.(js|jsx|ts|tsx)$ ]]; then
js_ts_files+=("$cleaned_file")
fi
done
# Only run if we have JS/TS files to lint. --allow-empty-input keeps the hook
# green when every passed file is covered by .stylelintignore.
if [ ${#js_ts_files[@]} -gt 0 ]; then
npx stylelint --allow-empty-input "${js_ts_files[@]}"
else
echo "No JavaScript/TypeScript files to lint for styles"
fi

View File

@@ -92,33 +92,4 @@ do
fi
done
# A few UI labels ("% calculation" etc.) parse as accidentally-valid
# %-format directives (a space flag plus a conversion character), so
# babel auto-flags them python-format on every write and msgfmt then
# fatals on their translations. The app never %-formats them; strip the
# flag AFTER the update pass (babel re-adds it during the update, so
# this must run last). Line-targeted so canonical wrapping is untouched.
python3 - <<'PYEOF'
import glob
TARGETS = {'msgid "% calculation"\n', 'msgid "% of parent"\n', 'msgid "% of total"\n'}
paths = ["superset/translations/messages.pot"] + sorted(
glob.glob("superset/translations/*/LC_MESSAGES/messages.po")
)
for path in paths:
lines = open(path, encoding="utf-8").readlines()
changed = False
for i, line in enumerate(lines):
if line in TARGETS and i > 0 and lines[i - 1].startswith("#,"):
tokens = [t.strip() for t in lines[i - 1][2:].split(",")]
if "python-format" in tokens:
tokens = [t for t in tokens if t != "python-format"]
if "no-python-format" not in tokens:
tokens.append("no-python-format")
lines[i - 1] = "#, " + ", ".join(tokens) + "\n"
changed = True
if changed:
open(path, "w", encoding="utf-8").writelines(lines)
PYEOF
cd $CURRENT_DIR

View File

@@ -138,22 +138,6 @@ def _lang_name(code: str) -> str:
return LANGUAGE_NAMES.get(code, code)
# An ISO 639-1/639-2 code, optionally followed by an ISO 3166 region
# (``_BR``, two uppercase) or an ISO 15924 script (``_Latn``, titlecase). The
# script subtag matters for catalogs like ``sr_Latn`` (Serbian in Latin script);
# without it the code was rejected and the catalog silently skipped.
_LANG_CODE_RE: re.Pattern[str] = re.compile(r"[a-z]{2,3}(_([A-Z]{2}|[A-Z][a-z]{3}))?")
def _is_valid_lang_code(lang: str) -> bool:
"""Validate a language code before it lands, unsanitized, in a filesystem path.
Guards against path traversal while allowing the region and script subtags
Superset actually ships (e.g. ``pt_BR``, ``sr_Latn``).
"""
return bool(_LANG_CODE_RE.fullmatch(lang))
def _plural_key(msgid: str, msgid_plural: str) -> str:
"""Build the translation index key used for pluralized entries."""
return f"{msgid}\x00{msgid_plural}"
@@ -683,11 +667,13 @@ def backfill(
mark_fuzzy: bool = True,
) -> None:
"""Backfill missing translations in the target language's .po file."""
if not _is_valid_lang_code(lang):
# Defense against path traversal: ``lang`` lands in a filesystem path
# without further sanitization, so reject anything that isn't an
# ISO 639-1/639-2 code with an optional ISO 3166 region (e.g. ``pt_BR``).
if not re.fullmatch(r"[a-z]{2,3}(_[A-Z]{2})?", lang):
print(
f"Invalid language code: {lang!r} "
"(expected ISO 639 code, optionally with a _<REGION> or _<Script> "
"subtag, e.g. 'fr', 'pt_BR', or 'sr_Latn')",
"(expected ISO 639 code, optionally with _<REGION>, e.g. 'fr' or 'pt_BR')",
file=sys.stderr,
)
sys.exit(1)

View File

@@ -42,7 +42,7 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"flask-appbuilder>=5.2.0,<6",
"flask-appbuilder>=5.0.2,<6",
"isodate>=0.7.0",
"pyarrow>=16.0.0",
"pydantic>=2.8.0",

View File

@@ -42,10 +42,7 @@ from superset_core.common.models import (
Dashboard,
Database,
Dataset,
Group,
KeyValue,
Role,
Subject,
Tag,
User,
)
@@ -193,34 +190,6 @@ class UserDAO(BaseDAO[User]):
id_column_name = "id"
class RoleDAO(BaseDAO[Role]):
"""
Abstract Role DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
class GroupDAO(BaseDAO[Group]):
"""
Abstract Group DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
class TagDAO(BaseDAO[Tag]):
"""
Abstract Tag DAO interface.
@@ -249,16 +218,13 @@ class KeyValueDAO(BaseDAO[KeyValue]):
id_column_name = "id"
class SubjectDAO(BaseDAO[Subject]):
"""
Abstract Subject DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
uuid_column_name = "uuid"
__all__ = [
"BaseDAO",
"DatasetDAO",
"DatabaseDAO",
"ChartDAO",
"DashboardDAO",
"UserDAO",
"TagDAO",
"KeyValueDAO",
]

View File

@@ -285,38 +285,6 @@ class User(CoreModel):
active: bool
class Role(CoreModel):
"""
Abstract Role model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
name: str | None
class Group(CoreModel):
"""
Abstract Group model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
name: str | None
label: str | None
description: str | None
class Tag(CoreModel):
"""
Abstract Tag model interface.
@@ -352,33 +320,6 @@ class KeyValue(CoreModel):
changed_by_fk: int | None
class Subject(CoreModel):
"""
Abstract Subject model interface.
A Subject is a unified representation of a User, Role, or Group. Host
implementations will replace this class during initialization with a
concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
uuid: UUID | None
label: str | None
secondary_label: str | None
active: bool | None
extra_search: str | None
type: int
# Convenience references to the underlying principal. Exactly one is set,
# matching ``type`` (User, Role, or Group respectively).
user: User | None
role: Role | None
group: Group | None
def get_session() -> scoped_session:
"""
Retrieve the SQLAlchemy session to directly interface with the
@@ -398,11 +339,8 @@ __all__ = [
"Chart",
"Dashboard",
"User",
"Role",
"Group",
"Tag",
"KeyValue",
"Subject",
"CoreModel",
"get_session",
]

View File

@@ -158,7 +158,7 @@ class Filter:
type: PredicateType
column: Dimension | Metric | None
operator: Operator
value: FilterValues | tuple[FilterValues, ...] | frozenset[FilterValues]
value: FilterValues | frozenset[FilterValues]
class OrderDirection(enum.Enum):

View File

@@ -96,26 +96,6 @@ describe("guest token refresh", () => {
expect(timing).toBe(DEFAULT_TOKEN_EXP_MS - REFRESH_TIMING_BUFFER_MS);
});
it("falls back to default timing for a completely malformed token", () => {
const timing = getGuestTokenRefreshTiming("not-a-jwt");
expect(timing).toBe(DEFAULT_TOKEN_EXP_MS - REFRESH_TIMING_BUFFER_MS);
});
it("falls back to default timing for an empty string token", () => {
const timing = getGuestTokenRefreshTiming("");
expect(timing).toBe(DEFAULT_TOKEN_EXP_MS - REFRESH_TIMING_BUFFER_MS);
});
it("falls back to default timing for a token with invalid base64 payload", () => {
const timing = getGuestTokenRefreshTiming(
"header.!!!invalid-base64!!!.signature",
);
expect(timing).toBe(DEFAULT_TOKEN_EXP_MS - REFRESH_TIMING_BUFFER_MS);
});
it("exposes a positive retry delay for failed token refreshes", () => {
// The refresh loop reschedules itself after this delay when a fetch
// fails or times out, so it must be a sane positive value.

View File

@@ -25,20 +25,16 @@ export const DEFAULT_TOKEN_REFRESH_RETRY_MS = 10000; // wait before retrying a f
// when do we refresh the guest token?
export function getGuestTokenRefreshTiming(currentGuestToken: string) {
try {
const parsedJwt = jwtDecode<Record<string, any>>(currentGuestToken);
// if exp is int, it is in seconds, but Date() takes milliseconds
const exp = new Date(
/[^0-9\.]/g.test(parsedJwt.exp)
? parsedJwt.exp
: parseFloat(parsedJwt.exp) * 1000,
);
const isValidDate = exp.toString() !== "Invalid Date";
const ttl = isValidDate
? Math.max(MIN_REFRESH_WAIT_MS, exp.getTime() - Date.now())
: DEFAULT_TOKEN_EXP_MS;
return ttl - REFRESH_TIMING_BUFFER_MS;
} catch {
return DEFAULT_TOKEN_EXP_MS - REFRESH_TIMING_BUFFER_MS;
}
const parsedJwt = jwtDecode<Record<string, any>>(currentGuestToken);
// if exp is int, it is in seconds, but Date() takes milliseconds
const exp = new Date(
/[^0-9\.]/g.test(parsedJwt.exp)
? parsedJwt.exp
: parseFloat(parsedJwt.exp) * 1000,
);
const isValidDate = exp.toString() !== "Invalid Date";
const ttl = isValidDate
? Math.max(MIN_REFRESH_WAIT_MS, exp.getTime() - Date.now())
: DEFAULT_TOKEN_EXP_MS;
return ttl - REFRESH_TIMING_BUFFER_MS;
}

View File

@@ -22,7 +22,7 @@ import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import reducerIndex from 'spec/helpers/reducerIndex';
import { Global } from '@emotion/react';
import { App, Layout } from 'antd';
import { App, Layout, Space, Content } from 'antd';
import 'src/theme.ts';
import './storybook.css';

View File

@@ -17,38 +17,45 @@
* under the License.
*/
import { useState, useCallback, ReactNode } from 'react';
import { Component, ReactNode } from 'react';
export type Props = {
children: ReactNode;
expandableWhat?: string;
};
export default function Expandable({ children, expandableWhat }: Props) {
const [open, setOpen] = useState(false);
type State = {
open: boolean;
};
const handleToggle = useCallback(() => {
setOpen(prevOpen => !prevOpen);
}, []);
export default class Expandable extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { open: false };
this.handleToggle = this.handleToggle.bind(this);
}
const label = expandableWhat
? `${open ? 'Hide' : 'Show'} ${expandableWhat}`
: open
? 'Hide'
: 'Show';
handleToggle() {
this.setState(({ open }) => ({ open: !open }));
}
return (
<div>
<button
type="button"
className="btn btn-primary btn-sm"
onClick={handleToggle}
>
{label}
</button>
<br />
<br />
{open ? children : null}
</div>
);
render() {
const { open } = this.state;
const { children, expandableWhat } = this.props;
return (
<div>
<button
type="button"
className="btn btn-primary btn-sm"
onClick={this.handleToggle}
>
{`${open ? 'Hide' : 'Show'} ${expandableWhat}`}
</button>
<br />
<br />
{open ? children : null}
</div>
);
}
}

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import { useState, useEffect, useCallback, useRef, ReactNode } from 'react';
import { Component, ReactNode } from 'react';
import { t } from '@apache-superset/core/translation';
import {
SupersetClient,
@@ -36,6 +36,12 @@ export type Props = {
postPayload?: string;
};
type State = {
didVerify: boolean;
error?: Error | SupersetApiError;
payload?: object;
};
export const renderError = (error: Error) => (
<div>
The following error occurred, make sure you have <br />
@@ -48,37 +54,29 @@ export const renderError = (error: Error) => (
</div>
);
export default function VerifyCORS({
children,
endpoint,
host,
method,
postPayload,
}: Props): JSX.Element {
const [didVerify, setDidVerify] = useState(false);
const [error, setError] = useState<Error | SupersetApiError | undefined>(
undefined,
);
const [payload, setPayload] = useState<object | undefined>(undefined);
export default class VerifyCORS extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { didVerify: false };
this.handleVerify = this.handleVerify.bind(this);
}
const prevPropsRef = useRef({ endpoint, host, postPayload, method });
useEffect(() => {
const prevProps = prevPropsRef.current;
componentDidUpdate(prevProps: Props) {
const { endpoint, host, postPayload, method } = this.props;
if (
(didVerify || error) &&
(this.state.didVerify || this.state.error) &&
(prevProps.endpoint !== endpoint ||
prevProps.host !== host ||
prevProps.postPayload !== postPayload ||
prevProps.method !== method)
) {
setDidVerify(false);
setError(undefined);
// eslint-disable-next-line react/no-did-update-set-state
this.setState({ didVerify: false, error: undefined });
}
prevPropsRef.current = { endpoint, host, postPayload, method };
}, [endpoint, host, postPayload, method, didVerify, error]);
}
const handleVerify = useCallback(() => {
handleVerify() {
const { endpoint, host, postPayload, method } = this.props;
SupersetClient.reset();
SupersetClient.configure({
credentials: 'include',
@@ -96,40 +94,43 @@ export default function VerifyCORS({
}
return { error: 'Must provide valid endpoint and payload.' };
})
.then(result => {
setDidVerify(true);
setError(undefined);
setPayload(result);
})
.catch(err => setError(err));
}, [endpoint, host, method, postPayload]);
.then(result =>
this.setState({ didVerify: true, error: undefined, payload: result }),
)
.catch(error => this.setState({ error }));
}
return didVerify ? (
<>{children({ payload })}</>
) : (
<div className="row">
<div className="col-md-10">
This example requires CORS requests from this domain. <br />
<br />
1) enable CORS requests in your Superset App from{' '}
{`${window.location.origin}`}
<br />
2) configure your Superset App host name below <br />
3) click below to verify authentication. You may debug CORS further
using the `@superset-ui/connection` story. <br />
<br />
<Button type="primary" size="small" onClick={handleVerify}>
{t('Verify')}
</Button>
<br />
<br />
</div>
render() {
const { didVerify, error, payload } = this.state;
const { children } = this.props;
{error && (
<div className="col-md-8">
<ErrorMessage error={error} />
return didVerify ? (
children({ payload })
) : (
<div className="row">
<div className="col-md-10">
This example requires CORS requests from this domain. <br />
<br />
1) enable CORS requests in your Superset App from{' '}
{`${window.location.origin}`}
<br />
2) configure your Superset App host name below <br />
3) click below to verify authentication. You may debug CORS further
using the `@superset-ui/connection` story. <br />
<br />
<Button type="primary" size="small" onClick={this.handleVerify}>
{t('Verify')}
</Button>
<br />
<br />
</div>
)}
</div>
);
{error && (
<div className="col-md-8">
<ErrorMessage error={error} />
</div>
)}
</div>
);
}
}

View File

@@ -1,9 +0,0 @@
# Files whose Emotion template literals nest a `css` tag inside an
# interpolation (e.g. styled(X)`${props => css`...`}`) or otherwise use a
# structure that postcss-styled-syntax cannot parse. Stylelint reports a
# CssSyntaxError on these rather than a real lint finding, so they are
# excluded until the syntax gains nested-template support.
src/components/ListView/Filters/Base.ts
src/features/databases/DatabaseModal/styles.ts
src/features/databases/UploadDataModel/styles.ts
packages/superset-ui-core/src/components/Typography/index.tsx

View File

@@ -471,19 +471,19 @@ export function applyAdvancedTimeRangeFilterOnDashboard(
endRange?: string,
) {
cy.get('.control-label').contains('Range type').should('be.visible');
cy.get('.ant-popover-content .ant-select-content')
cy.get('.ant-popover-content .ant-select-selector')
.should('be.visible')
.click();
cy.get(`[label="Advanced"]`).should('be.visible').click();
cy.get('.section-title').contains('Advanced Time Range').should('be.visible');
if (startRange) {
cy.get('.ant-popover-content')
cy.get('.ant-popover-inner-content')
.find('[class^=ant-input]')
.first()
.type(`${startRange}`);
}
if (endRange) {
cy.get('.ant-popover-content')
cy.get('.ant-popover-inner-content')
.find('[class^=ant-input]')
.last()
.type(`${endRange}`);
@@ -514,14 +514,15 @@ export function inputNativeFilterDefaultValue(
)
.eq(1)
.within(() => {
cy.get('.ant-select-input').type(`${defaultValue}{enter}`, {
force: true,
});
cy.get('.ant-select-selection-search-input').type(
`${defaultValue}{enter}`,
{ force: true },
);
});
});
} else {
cy.getBySel('default-input').within(() => {
cy.get('.ant-select-input').click();
cy.get('.ant-select-selection-search-input').click();
cy.get('.ant-select-item-option-content').contains(defaultValue).click();
});
}

View File

@@ -53,11 +53,13 @@ export function saveChartToDashboard(chartName: string, dashboardName: string) {
.then($modal => {
cy.wait(500);
cy.wrap($modal)
.find('.ant-select-input[aria-label*="Select a dashboard"]')
.find(
'.ant-select-selection-search-input[aria-label*="Select a dashboard"]',
)
.type(dashboardName, { force: true });
cy.get(`.ant-select-item-option[title="${dashboardName}"]`).click();
cy.wrap($modal)
.find(`.ant-select-item-option[title="${dashboardName}"]`)
.click();
cy.getBySel('btn-modal-save').click();
cy.wait('@update');
});

View File

@@ -2,6 +2,7 @@
{
"slice_name": "1 - Sample chart",
"description": "chart description",
"owners": [1],
"viz_type": "echarts_timeseries_line",
"cache_timeout": 1000,
"datasource_id": 2,
@@ -11,6 +12,7 @@
{
"slice_name": "2 - Sample chart",
"description": "chart description",
"owners": [1],
"viz_type": "echarts_timeseries_line",
"cache_timeout": 1000,
"datasource_id": 2,
@@ -20,6 +22,7 @@
{
"slice_name": "3 - Sample chart",
"description": "chart description",
"owners": [1],
"viz_type": "echarts_timeseries_line",
"cache_timeout": 1000,
"datasource_id": 2,
@@ -29,6 +32,7 @@
{
"slice_name": "4 - Sample chart",
"description": "chart description",
"owners": [1],
"viz_type": "echarts_timeseries_line",
"cache_timeout": 1000,
"datasource_id": 2,

View File

@@ -52,7 +52,7 @@ export const securityAccess = {
};
export const homePage = {
homeSection: {
sectionArea: '.ant-collapse-body',
sectionArea: '.ant-collapse-content-box',
sectionElement: '.ant-card-meta-title',
},
sections: {
@@ -91,7 +91,7 @@ export const databasesPage = {
preferredBlockSheets: '.preferred > :nth-child(6)',
supportedDatabasesText: '.control-label',
orChoose: '.available-label',
dbDropdown: '.ant-select-input',
dbDropdown: '[class="ant-select-selection-search-input"]',
dbDropdownMenu: '.rc-virtual-list-holder-inner',
dbDropdownMenuItem: '[class="ant-select-item-option-content"]',
infoAlert: '.ant-alert',
@@ -277,7 +277,7 @@ export const chartListView = {
header: {
cardView: '[aria-label="card-view"]',
listView: '[aria-label="list-view"]',
sort: '.ant-select-input[aria-label="Sort"]',
sort: '[class="ant-select-selection-search-input"][aria-label="Sort"]',
sortRecentlyModifiedMenuOption: '[label="Recently modified"]',
sortAlphabeticalMenuOption: '[label="Alphabetical"]',
sortDropdown: '.Select__menu',
@@ -338,20 +338,20 @@ export const nativeFilters = {
filtersPanel: {
filterName: dataTestLocator('filters-config-modal__name-input'),
datasetName: dataTestLocator('filters-config-modal__datasource-input'),
filterInfoInput: '.ant-select-input',
filterInfoInput: '.ant-select-selection-search-input',
inputDropdown: '.ant-select-item-option-content',
columnEmptyInput: '.ant-select-placeholder',
columnEmptyInput: '.ant-select-selection-placeholder',
filterTypeInput: dataTestLocator('filters-config-modal__filter-type'),
fieldInput: dataTestLocator('field-input'),
filterTypeItem: '.ant-select-content-has-value, .ant-select-selection-item',
filterTypeItem: '.ant-select-selection-item',
filterGear: dataTestLocator('filterbar-orientation-icon'),
},
filterFromDashboardView: {
filterValueInput: '.ant-select-input',
filterValueInput: '[class="ant-select-selection-search-input"]',
expand: dataTestLocator('filter-bar__expand-button'),
collapse: dataTestLocator('filter-bar__collapse-button'),
filterName: dataTestLocator('filter-control-name'),
filterContent: '.ant-select-content-has-value, .ant-select-selection-item',
filterContent: '.ant-select-selection-item',
createFilterButton: dataTestLocator('filter-bar__create-filter'),
timeRangeFilterContent: dataTestLocator('time-range-trigger'),
},
@@ -365,8 +365,8 @@ export const nativeFilters = {
checkedCheckbox: '.ant-checkbox-wrapper-checked',
infoTooltip: '[aria-label="Show info tooltip"]',
parentFilterInput: dataTestLocator('parent-filter-input'),
filterPlaceholder: '.ant-select-placeholder',
collapsedSectionContainer: '[class="ant-collapse-body"]',
filterPlaceholder: '.ant-select-selection-placeholder',
collapsedSectionContainer: '[class="ant-collapse-content-box"]',
},
filtersList: {
list: '.ant-tabs-nav-list',
@@ -376,7 +376,7 @@ export const nativeFilters = {
removeIcon: '[aria-label="delete"]',
},
filterItem: dataTestLocator('form-item-value'),
filterItemDropdown: '.ant-select-input',
filterItemDropdown: '.ant-select-selection-search',
applyFilter: dataTestLocator('filter-bar__apply-button'),
defaultInput: dataTestLocator('default-input'),
filterIcon: dataTestLocator('filter-icon'),
@@ -484,7 +484,7 @@ export const exploreView = {
saveModal: {
modal: '.ant-modal-content',
chartNameInput: dataTestLocator('new-chart-name'),
dashboardNameInput: '.ant-select-input',
dashboardNameInput: '.ant-select-selection-search-input',
addToDashboardInput: dataTestLocator(
'save-chart-modal-select-dashboard-form',
),
@@ -495,7 +495,7 @@ export const exploreView = {
},
controlPanel: {
panel: dataTestLocator('control-tabs'),
categoryArea: '.ant-collapse-body',
categoryArea: '.ant-collapse-content-box',
dragField: dataTestLocator('datasource'),
metricsField: dataTestLocator('metrics'),
optionField: dataTestLocator('option-label'),
@@ -594,7 +594,7 @@ export const exploreView = {
},
};
export const createChartView = {
chooseDatasetInput: '.ant-select-input',
chooseDatasetInput: '.ant-select-selection-search-input',
chooseDatasetOption: '.ant-select-item-option-content',
chooseDatasetList: '.rc-virtual-list-holder-inner',
tableVizType: "[alt='Table']",
@@ -641,7 +641,7 @@ export const dashboardView = {
secondTabSalesDashboard: dataTestLocator('dragdroppable-object'),
},
timeRangeModal: {
rangeTypeField: '.ant-select-content-has-value, .ant-select-selection-item',
rangeTypeField: '.ant-select-selection-item',
startTimeInputNumber: '.ant-input-number-input',
datePicker: '.ant-picker-input',
applyButton: dataTestLocator('date-filter-control__apply-button'),

View File

@@ -75,7 +75,7 @@ module.exports = {
// @ant-design/colors and @ant-design/fast-color are allowed through because
// @ant-design/icons >= 6.3 deep-imports the ESM build of @ant-design/colors
// from its CJS output, so babel-jest must transform those files.
'node_modules/(?!@ant-design/(colors|fast-color)|@formatjs/.*|d3-(array|interpolate|color|time|scale|time-format|format)|internmap|@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|uuid|@rjsf/*.|@x0k/.*|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-.*|(?!geostyler)lodash|react-error-boundary|react-json-tree|react-base16-styling|lodash-es|rbush|quickselect|react-diff-viewer-continued|storybook/*.|json-stringify-pretty-compact)',
'node_modules/(?!@ant-design/(colors|fast-color)|@formatjs/.*|d3-(array|interpolate|color|time|scale|time-format|format)|internmap|@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|uuid|@rjsf/*.|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-.*|(?!geostyler)lodash|react-error-boundary|react-json-tree|react-base16-styling|lodash-es|rbush|quickselect|react-diff-viewer-continued|storybook/*.|json-stringify-pretty-compact)',
],
preset: 'ts-jest',
transform: {

View File

@@ -23,7 +23,7 @@
},
"settings": {
"react": {
"version": "18.3.1"
"version": "17.0.2"
}
},
"rules": {
@@ -52,6 +52,10 @@
// require-default-props, sort-comp, static-property-placement
// (prefer-stateless-function / function-component-definition
// are represented by react/prefer-function-component below)
// jsx-a11y: interactive-supports-focus,
// no-interactive-element-to-noninteractive-role,
// no-noninteractive-element-interactions,
// no-noninteractive-element-to-interactive-role
// typescript: naming-convention
// unicorn: prevent-abbreviations
@@ -124,7 +128,8 @@
"import/no-mutable-exports": "error",
"import/no-amd": "error",
"import/first": "error",
"import/no-duplicates": "error",
// TODO: Consolidate duplicate imports in DatasetList and other files
"import/no-duplicates": "warn",
"import/no-absolute-path": "error",
"import/no-dynamic-require": "error",
"import/no-webpack-loader-syntax": "error",
@@ -191,7 +196,6 @@
// === JSX-a11y rules ===
"jsx-a11y/alt-text": "error",
"jsx-a11y/anchor-ambiguous-text": "error",
"jsx-a11y/anchor-has-content": "error",
"jsx-a11y/anchor-is-valid": "error",
"jsx-a11y/aria-activedescendant-has-tabindex": "error",
@@ -199,28 +203,21 @@
"jsx-a11y/aria-proptypes": "error",
"jsx-a11y/aria-role": ["error", { "ignoreNonDOM": false }],
"jsx-a11y/aria-unsupported-elements": "error",
"jsx-a11y/autocomplete-valid": "error",
"jsx-a11y/click-events-have-key-events": "error",
"jsx-a11y/control-has-associated-label": "error",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/heading-has-content": "error",
"jsx-a11y/html-has-lang": "error",
"jsx-a11y/iframe-has-title": "error",
"jsx-a11y/img-redundant-alt": "error",
"jsx-a11y/interactive-supports-focus": "error",
"jsx-a11y/label-has-associated-control": "error",
"jsx-a11y/lang": "error",
"jsx-a11y/media-has-caption": "error",
"jsx-a11y/mouse-events-have-key-events": "error",
"jsx-a11y/mouse-events-have-key-events": "off",
"jsx-a11y/no-access-key": "error",
"jsx-a11y/no-aria-hidden-on-focusable": "error",
"jsx-a11y/no-autofocus": ["error", { "ignoreNonDOM": true }],
"jsx-a11y/no-distracting-elements": "error",
"jsx-a11y/no-interactive-element-to-noninteractive-role": "error",
"jsx-a11y/no-noninteractive-element-interactions": "error",
"jsx-a11y/no-noninteractive-element-to-interactive-role": "error",
"jsx-a11y/no-noninteractive-tabindex": "error",
"jsx-a11y/no-redundant-roles": "error",
"jsx-a11y/no-static-element-interactions": "error",
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/role-has-required-aria-props": "error",
"jsx-a11y/role-supports-aria-props": "error",
"jsx-a11y/scope": "error",
@@ -235,14 +232,7 @@
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrors": "none"
}
],
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/prefer-optional-chain": "error",
// === Unicorn rules (bonus coverage) ===
@@ -292,16 +282,6 @@
"rules": {
"jsx-a11y/no-redundant-roles": "off"
}
},
{
// The pivot table intentionally exposes `role="grid"` on its <table>
// for interactive grid semantics (asserted by tableRenders.test.tsx).
"files": [
"plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx"
],
"rules": {
"jsx-a11y/no-noninteractive-element-to-interactive-role": "off"
}
}
],
"ignorePatterns": [

File diff suppressed because it is too large Load Diff

View File

@@ -57,9 +57,7 @@
"lint:all": "npx oxlint --config oxlint.json && npm run type",
"lint-fix": "npx oxlint --config oxlint.json --fix --quiet",
"lint-fix:all": "npx oxlint --config oxlint.json --fix",
"lint:full": "npm run lint && npm run check:custom-rules && npm run stylelint",
"stylelint": "stylelint \"{src,packages/*/src,plugins/*/src}/**/*.{ts,tsx}\"",
"stylelint-fix": "stylelint --fix \"{src,packages/*/src,plugins/*/src}/**/*.{ts,tsx}\"",
"lint:full": "npm run lint && npm run check:custom-rules",
"check:custom-rules": "node scripts/check-custom-rules.js",
"check:storybook-coverage": "node scripts/check-storybook-coverage.js",
"ensure-oxc": "echo 'OXC linter is ready' && npx oxlint --version",
@@ -109,7 +107,6 @@
"@deck.gl/mesh-layers": "~9.2.5",
"@deck.gl/react": "~9.2.5",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@emotion/cache": "^11.4.0",
@@ -130,9 +127,10 @@
"@luma.gl/shadertools": "~9.2.5",
"@luma.gl/webgl": "~9.2.5",
"@reduxjs/toolkit": "^1.9.3",
"@rjsf/core": "^6.6.2",
"@rjsf/utils": "^6.6.2",
"@rjsf/validator-ajv8": "^6.6.2",
"@rjsf/antd": "^5.24.13",
"@rjsf/core": "^5.24.13",
"@rjsf/utils": "^5.24.3",
"@rjsf/validator-ajv8": "^5.24.13",
"@scarf/scarf": "^1.4.0",
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
"@superset-ui/core": "file:./packages/superset-ui-core",
@@ -166,9 +164,9 @@
"@visx/scale": "^4.0.0",
"@visx/tooltip": "^4.0.0",
"@visx/xychart": "^4.0.0",
"ag-grid-community": "36.0.0",
"ag-grid-react": "36.0.0",
"antd": "^6.0.0",
"ag-grid-community": "35.3.1",
"ag-grid-react": "35.3.1",
"antd": "^5.26.0",
"chrono-node": "^2.9.1",
"classnames": "^2.2.5",
"content-disposition": "^2.0.1",
@@ -187,7 +185,7 @@
"geostyler-style": "11.0.2",
"geostyler-wfs-parser": "^3.0.1",
"google-auth-library": "^10.9.0",
"immer": "^11.1.11",
"immer": "^11.1.8",
"interweave": "^13.1.1",
"jquery": "^4.0.0",
"js-levenshtein": "^1.1.6",
@@ -195,7 +193,7 @@
"json-stringify-pretty-compact": "^4.0.0",
"lodash": "^4.18.1",
"lodash-es": "^4.18.1",
"mapbox-gl": "^3.26.0",
"mapbox-gl": "^3.25.0",
"markdown-to-jsx": "^9.8.2",
"match-sorter": "^8.3.0",
"memoize-one": "^6.0.0",
@@ -206,9 +204,9 @@
"query-string": "9.4.1",
"re-resizable": "^6.11.2",
"react": "^18.3.0",
"react-arborist": "^3.13.2",
"react-arborist": "^3.12.0",
"react-checkbox-tree": "^1.8.0",
"react-diff-viewer-continued": "^4.3.0",
"react-diff-viewer-continued": "^4.2.2",
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^18.3.0",
@@ -263,7 +261,7 @@
"@babel/types": "^7.29.7",
"@emotion/babel-plugin": "^11.13.5",
"@emotion/jest": "^11.14.2",
"@formatjs/intl-durationformat": "^0.10.16",
"@formatjs/intl-durationformat": "^0.10.15",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@playwright/test": "^1.61.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
@@ -273,7 +271,7 @@
"@storybook/test-runner": "0.24.4",
"@svgr/webpack": "^8.1.0",
"@swc/core": "^1.15.43",
"@swc/plugin-emotion": "^14.15.0",
"@swc/plugin-emotion": "^14.14.0",
"@swc/plugin-transform-imports": "^12.5.0",
"@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^6.9.1",
@@ -287,7 +285,7 @@
"@types/json-bigint": "^1.0.4",
"@types/lodash-es": "^4.17.12",
"@types/mousetrap": "^1.6.15",
"@types/node": "^26.1.1",
"@types/node": "^26.0.1",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@types/react-loadable": "^5.5.11",
@@ -300,13 +298,13 @@
"@types/rison": "0.1.0",
"@types/tinycolor2": "^1.4.3",
"@types/unzipper": "^0.10.11",
"@typescript-eslint/eslint-plugin": "^8.63.0",
"@typescript-eslint/parser": "^8.63.0",
"@typescript-eslint/eslint-plugin": "^8.62.1",
"@typescript-eslint/parser": "^8.62.1",
"babel-jest": "^30.4.1",
"babel-loader": "^10.1.1",
"babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"baseline-browser-mapping": "^2.10.42",
"baseline-browser-mapping": "^2.10.40",
"cheerio": "1.2.0",
"concurrently": "^10.0.3",
"copy-webpack-plugin": "^14.0.0",
@@ -316,6 +314,7 @@
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-cypress": "^6.4.2",
"eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings",
"eslint-plugin-icons": "file:eslint-rules/eslint-plugin-icons",
"eslint-plugin-import": "^2.32.0",
@@ -345,10 +344,9 @@
"mini-css-extract-plugin": "^2.10.2",
"minimizer-webpack-plugin": "^5.6.1",
"open-cli": "^9.0.0",
"oxlint": "^1.73.0",
"oxlint": "^1.72.0",
"po2json": "^0.4.5",
"postcss-styled-syntax": "^0.7.2",
"prettier": "3.9.5",
"prettier": "3.9.4",
"prettier-plugin-packagejson": "^3.0.2",
"process": "^0.11.10",
"react-dnd-test-backend": "^16.0.1",
@@ -360,21 +358,20 @@
"speed-measure-webpack-plugin": "^1.6.0",
"storybook": "10.4.6",
"style-loader": "^4.0.0",
"stylelint": "^17.14.0",
"swc-loader": "^0.2.7",
"ts-jest": "^29.4.11",
"tscw-config": "^1.1.2",
"tsx": "^4.23.0",
"tsx": "^4.22.4",
"typescript": "5.4.5",
"unzipper": "^0.12.5",
"vm-browserify": "^1.1.2",
"wait-on": "^9.0.10",
"webpack": "^5.108.4",
"webpack": "^5.108.3",
"webpack-bundle-analyzer": "^5.3.0",
"webpack-cli": "^7.0.3",
"webpack-dev-server": "^5.2.5",
"webpack-manifest-plugin": "^6.0.1",
"webpack-sources": "^3.5.1",
"webpack-sources": "^3.5.0",
"webpack-visualizer-plugin2": "^2.0.0"
},
"peerDependencies": {
@@ -390,9 +387,6 @@
},
"overrides": {
"uuid": "$uuid",
"@great-expectations/jsonforms-antd-renderers": {
"antd": "$antd"
},
"core-js": "^3.38.1",
"dompurify": "^3.4.11",
"esbuild": "^0.28.1",

Some files were not shown because too many files have changed in this diff Show More