Compare commits

..

1 Commits

Author SHA1 Message Date
Amin Ghadersohi
b652b996a4 chore(deps): raise SQLAlchemy lower bound to >=1.4.43
The python-oracledb (oracle+oracledb) dialect that the Oracle engine
spec now relies on was added in SQLAlchemy 1.4.43. The old `>=1.4`
floor allowed 1.4.0-1.4.42, where that dialect does not exist, so a
downstream resolver could install a SQLAlchemy that cannot connect to
Oracle. The lockfile already resolves to 1.4.54, so this is a no-op for
the pinned environment and only constrains library installs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 02:47:45 -07:00
782 changed files with 14895 additions and 44269 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

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

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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
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

@@ -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

@@ -153,16 +153,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 +182,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.

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
@@ -361,7 +361,7 @@ This is the best source of information about the problem. In a docker compose d
### Check web browser and webdriver installation
To take a screenshot, the worker visits the dashboard or chart using a headless browser, then takes a screenshot. If you are able to send a chart as CSV, XLSX, or text but can't send as PNG, your problem may lie with the browser.
To take a screenshot, the worker visits the dashboard or chart using a headless browser, then takes a screenshot. If you are able to send a chart as CSV or text but can't send as PNG, your problem may lie with the browser.
If you are handling the installation of the headless browser on your own, do your own verification to ensure that the headless browser opens successfully in the worker environment.

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

@@ -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

@@ -291,7 +291,7 @@ This is the best source of information about the problem. In a docker compose d
### Check web browser and webdriver installation
To take a screenshot, the worker visits the dashboard or chart using a headless browser, then takes a screenshot. If you are able to send a chart as CSV, XLSX, or text but can't send as PNG, your problem may lie with the browser.
To take a screenshot, the worker visits the dashboard or chart using a headless browser, then takes a screenshot. If you are able to send a chart as CSV or text but can't send as PNG, your problem may lie with the browser.
Superset docker images that have a tag ending with `-dev` have the Firefox headless browser and geckodriver already installed. You can test that these are installed and in the proper path by entering your Superset worker and running `firefox --headless` and then `geckodriver`. Both commands should start those applications.

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",
"cachetools>=6.2.1, <7",
"celery>=5.6.3, <6.0.0",
"click>=8.4.2",
"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",
@@ -107,10 +107,10 @@ dependencies = [
"sshtunnel>=0.4.0, <0.5",
"simplejson>=4.1.1",
"slack_sdk>=3.43.0, <4",
"sqlalchemy>=1.4, <2",
"sqlalchemy>=1.4.43, <2", # 1.4.43 adds the python-oracledb (oracle+oracledb) dialect
"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 +130,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 +155,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 +173,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"]
@@ -192,14 +192,14 @@ pinot = ["pinotdb>=5.0.0, <10.0.0"]
playwright = ["playwright>=1.61.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",
@@ -215,7 +215,7 @@ 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 = [
@@ -244,7 +244,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,7 +44,7 @@ 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
@@ -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)
@@ -414,7 +414,7 @@ sqlalchemy-utils==0.42.1
# 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

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
@@ -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
@@ -998,7 +998,7 @@ sqlalchemy-utils==0.42.1
# 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 +1017,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 +1033,7 @@ tqdm==4.67.1
# via
# cmdstanpy
# prophet
trino==0.338.0
trino==0.337.0
# via apache-superset
trio==0.33.0
# via

View File

@@ -1,16 +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.

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

@@ -20,21 +20,9 @@ import json
import os
import re
import subprocess
import time
from typing import List, Optional
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
# The change detector gates the entire CI matrix, so a single transient GitHub
# API hiccup should not fail the build. Retry server errors and network blips
# with exponential backoff before giving up.
MAX_RETRIES: int = 4
RETRY_BACKOFF_SECONDS: int = 2
REQUEST_TIMEOUT_SECONDS: int = 30
# GitHub returns 429 (and 403 for secondary rate limits) when throttling, which
# is transient and worth retrying alongside 5xx server errors.
RETRYABLE_STATUS_CODES: frozenset[int] = frozenset({403, 429})
# Define patterns for each group of files you're interested in
PATTERNS = {
"python": [
@@ -69,34 +57,15 @@ GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
def fetch_files_github_api(url: str): # type: ignore
"""Fetches data using GitHub API, retrying on transient failures."""
"""Fetches data using GitHub API."""
req = Request(url) # noqa: S310
req.add_header("Authorization", f"Bearer {GITHUB_TOKEN}")
req.add_header("Accept", "application/vnd.github.v3+json")
print(f"Fetching from {url}")
for attempt in range(1, MAX_RETRIES + 1):
try:
with urlopen(req, timeout=REQUEST_TIMEOUT_SECONDS) as response: # noqa: S310
body = response.read()
return json.loads(body)
except (HTTPError, URLError) as err:
# Retry transient failures: network errors (URLError has no status
# code), 5xx server errors, and GitHub rate-limit responses. Other
# 4xx client errors are deterministic, so re-raise immediately. Also
# re-raise once the retry budget is exhausted.
status = getattr(err, "code", None)
is_transient = (
status is None or status >= 500 or status in RETRYABLE_STATUS_CODES
)
if not is_transient or attempt == MAX_RETRIES:
raise
wait = RETRY_BACKOFF_SECONDS * 2 ** (attempt - 1)
print(
f"Attempt {attempt}/{MAX_RETRIES} failed ({err}); "
f"retrying in {wait}s..."
)
time.sleep(wait)
with urlopen(req) as response: # noqa: S310
body = response.read()
return json.loads(body)
def fetch_changed_files_pr(repo: str, pr_number: str) -> List[str]:

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

@@ -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,7 +53,9 @@ 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();

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",
@@ -130,9 +128,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",
@@ -168,7 +167,7 @@
"@visx/xychart": "^4.0.0",
"ag-grid-community": "36.0.0",
"ag-grid-react": "36.0.0",
"antd": "^6.0.0",
"antd": "^5.26.0",
"chrono-node": "^2.9.1",
"classnames": "^2.2.5",
"content-disposition": "^2.0.1",
@@ -187,7 +186,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.9",
"interweave": "^13.1.1",
"jquery": "^4.0.0",
"js-levenshtein": "^1.1.6",
@@ -195,7 +194,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 +205,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 +262,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 +272,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.1",
"@swc/plugin-transform-imports": "^12.5.0",
"@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^6.9.1",
@@ -287,7 +286,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.1.0",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@types/react-loadable": "^5.5.11",
@@ -300,13 +299,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.41",
"cheerio": "1.2.0",
"concurrently": "^10.0.3",
"copy-webpack-plugin": "^14.0.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.5",
"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",

View File

@@ -110,7 +110,7 @@
"react-loadable": "^5.5.0",
"tinycolor2": "*",
"lodash": "^4.18.1",
"antd": "^6.0.0",
"antd": "^5.26.0",
"jed": "^1.1.1"
},
"scripts": {

View File

@@ -38,7 +38,7 @@ export const GlobalStyles = () => {
<Global
key={`global-${theme.colorLink}`}
styles={css`
/* SPA */
// SPA
html {
color-scheme: ${isDark ? 'dark' : 'light'};
}
@@ -93,7 +93,7 @@ export const GlobalStyles = () => {
}
}
/* Overriding bootstrap styles */
// Overriding bootstrap styles
#app {
flex: 1 1 auto;
position: relative;
@@ -105,18 +105,8 @@ export const GlobalStyles = () => {
cursor: pointer;
}
/* antd 6 removed the Tag's default trailing margin (v5 shipped */
/* margin-inline-end: 8px on every tag) in favor of parents spacing */
/* tags via flex/Space gaps. The app's layouts predate that and rely */
/* on the v5 default (e.g. the dashboard header's Published tag), */
/* so restore it for visual parity. Remove once Tag-adjacent layouts */
/* declare their own gaps. */
.ant-tag {
margin-inline-end: ${theme.marginXS}px;
}
/* Override geostyler CSS that hides AntD ColorPicker alpha input */
/* See: https://github.com/apache/superset/issues/34721 */
// Override geostyler CSS that hides AntD ColorPicker alpha input
// See: https://github.com/apache/superset/issues/34721
.ant-color-picker .ant-color-picker-alpha-input {
display: block;
}
@@ -127,7 +117,7 @@ export const GlobalStyles = () => {
}
.superset-explore-popover.ant-popover
.ant-popover-container:has(.ant-popover-title) {
.ant-popover-inner:has(.ant-popover-title) {
padding-top: 0;
}
.superset-explore-popover.ant-popover .ant-popover-title {
@@ -136,7 +126,7 @@ export const GlobalStyles = () => {
line-height: 1;
}
.superset-explore-popover.ant-popover
.ant-popover-container:has(.ant-popover-title)
.ant-popover-inner:has(.ant-popover-title)
.ant-tabs-tab {
padding-top: 0;
}

View File

@@ -18,34 +18,7 @@
*/
import { theme as antdThemeImport } from 'antd';
import { Theme } from './Theme';
import { AnyThemeConfig, ThemeAlgorithm, allowedAntdTokens } from './types';
/**
* A fixed, Superset-representative base theme used by the characterization
* tests below. Mirrors the real THEME_DEFAULT token set so the snapshots are
* stable and meaningful. Do NOT churn this casually — it is the baseline the
* Ant Design v6 upgrade is measured against.
*/
const SUPERSET_BASE_THEME: AnyThemeConfig = {
token: {
colorPrimary: '#2893B3',
colorError: '#e04355',
colorWarning: '#fcc700',
colorSuccess: '#5ac189',
colorInfo: '#66bcfe',
fontFamily: "'Inter', Helvetica, Arial",
fontFamilyCode: "'IBM Plex Mono', 'Courier New', monospace",
},
};
/** Collect the computed value of every allow-listed antd token, sorted. */
function pickAllowedTokens(theme: Theme): Record<string, unknown> {
const out: Record<string, unknown> = {};
[...allowedAntdTokens].sort().forEach(key => {
out[key] = (theme.theme as unknown as Record<string, unknown>)[key];
});
return out;
}
import { AnyThemeConfig, ThemeAlgorithm } from './types';
// Mock emotion's cache to avoid actual DOM operations
jest.mock('@emotion/cache', () => ({
@@ -365,10 +338,10 @@ test('Theme edge cases correctly applies base theme tokens in dark mode', () =>
algorithm: antdThemeImport.defaultAlgorithm,
};
const baseThemeDark = {
const baseThemeDark: AnyThemeConfig = {
...baseTheme,
algorithm: antdThemeImport.darkAlgorithm,
} as AnyThemeConfig;
};
// Simulate light mode with base theme
const lightTheme = Theme.fromConfig({}, baseTheme);
@@ -500,10 +473,10 @@ test('Theme base theme integration handles base theme with dark algorithm correc
},
};
const baseThemeDark = {
const baseThemeDark: AnyThemeConfig = {
...baseTheme,
algorithm: antdThemeImport.darkAlgorithm,
} as AnyThemeConfig;
};
const userDarkTheme: AnyThemeConfig = {
algorithm: antdThemeImport.darkAlgorithm,
@@ -552,7 +525,7 @@ test('Theme base theme integration works with real-world Superset base theme con
const darkTheme = Theme.fromConfig(themeDark, {
...supersetBaseTheme,
algorithm: antdThemeImport.darkAlgorithm,
} as AnyThemeConfig);
});
expect(darkTheme.theme.fontFamily).toBe("'Inter', Helvetica, Arial");
const darkSerialized = darkTheme.toSerializedConfig();
@@ -631,9 +604,7 @@ test('Theme base theme integration handles cssVar, hashed and inherit properties
// User properties override/add to base
expect(serialized.inherit).toBe(true);
// Ant Design v6 dropped boolean `cssVar`; Superset normalizes `true` to the
// object form `{}` at the antd boundary (CSS variables are on by default).
expect(serialized.cssVar).toEqual({});
expect(serialized.cssVar).toBe(true);
expect(serialized.hashed).toBe(false);
// Tokens are still merged
@@ -938,81 +909,3 @@ test('colorLink is preserved in setConfig when explicitly set', () => {
expect(theme.theme.colorPrimary).toBe('#f759ab');
expect(theme.theme.colorLink).toBe('#ff0000');
});
// ---------------------------------------------------------------------------
// Characterization tests — theming safety net for the Ant Design v6 upgrade.
//
// These lock the bridge between Superset's theme and Ant Design's design
// tokens (getDesignToken -> allowedAntdTokens -> SupersetTheme). When antd
// renames or removes a token, the completeness tests fail loudly instead of
// letting a token silently resolve to `undefined`, and the snapshots make any
// value drift reviewable. See packages/superset-core/src/theme/types.ts.
// ---------------------------------------------------------------------------
test('every allowed antd token resolves to a defined value in light mode', () => {
const theme = Theme.fromConfig(
{ algorithm: antdThemeImport.defaultAlgorithm },
SUPERSET_BASE_THEME,
);
const missing = allowedAntdTokens.filter(
key =>
(theme.theme as unknown as Record<string, unknown>)[key] === undefined,
);
// A non-empty list means antd dropped/renamed a token we still allow-list.
expect(missing).toEqual([]);
});
test('every allowed antd token resolves to a defined value in dark mode', () => {
const theme = Theme.fromConfig(
{ algorithm: antdThemeImport.darkAlgorithm },
SUPERSET_BASE_THEME,
);
const missing = allowedAntdTokens.filter(
key =>
(theme.theme as unknown as Record<string, unknown>)[key] === undefined,
);
expect(missing).toEqual([]);
});
test('allowed antd token key set is stable', () => {
// Locks the *set* of tokens Superset exposes (independent of their values),
// so additions/removals to allowedAntdTokens are an explicit, reviewed diff.
expect([...allowedAntdTokens].sort()).toMatchSnapshot();
});
test('computed light-mode token values match snapshot', () => {
const theme = Theme.fromConfig(
{ algorithm: antdThemeImport.defaultAlgorithm },
SUPERSET_BASE_THEME,
);
expect(pickAllowedTokens(theme)).toMatchSnapshot();
});
test('computed dark-mode token values match snapshot', () => {
const theme = Theme.fromConfig(
{ algorithm: antdThemeImport.darkAlgorithm },
SUPERSET_BASE_THEME,
);
expect(pickAllowedTokens(theme)).toMatchSnapshot();
});
test('dark mode actually diverges from light mode for background tokens', () => {
const light = Theme.fromConfig(
{ algorithm: antdThemeImport.defaultAlgorithm },
SUPERSET_BASE_THEME,
);
const dark = Theme.fromConfig(
{ algorithm: antdThemeImport.darkAlgorithm },
SUPERSET_BASE_THEME,
);
// Guards the dark algorithm wiring: base surface and text must invert.
expect(dark.theme.colorBgBase).not.toBe(light.theme.colorBgBase);
expect(dark.theme.colorTextBase).not.toBe(light.theme.colorTextBase);
});

View File

@@ -1,730 +0,0 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`allowed antd token key set is stable 1`] = `
[
"borderRadius",
"borderRadiusLG",
"borderRadiusOuter",
"borderRadiusSM",
"borderRadiusXS",
"boxShadow",
"boxShadowDrawerLeft",
"boxShadowDrawerRight",
"boxShadowDrawerUp",
"boxShadowPopoverArrow",
"boxShadowSecondary",
"boxShadowTabsOverflowBottom",
"boxShadowTabsOverflowLeft",
"boxShadowTabsOverflowRight",
"boxShadowTabsOverflowTop",
"boxShadowTertiary",
"colorBgBase",
"colorBgBlur",
"colorBgContainer",
"colorBgContainerDisabled",
"colorBgElevated",
"colorBgLayout",
"colorBgMask",
"colorBgSpotlight",
"colorBgTextActive",
"colorBgTextHover",
"colorBorder",
"colorBorderBg",
"colorBorderSecondary",
"colorError",
"colorErrorActive",
"colorErrorBg",
"colorErrorBgActive",
"colorErrorBgHover",
"colorErrorBorder",
"colorErrorBorderHover",
"colorErrorHover",
"colorErrorOutline",
"colorErrorText",
"colorErrorTextActive",
"colorErrorTextHover",
"colorFill",
"colorFillAlter",
"colorFillContent",
"colorFillContentHover",
"colorFillQuaternary",
"colorFillSecondary",
"colorFillTertiary",
"colorHighlight",
"colorIcon",
"colorIconHover",
"colorInfo",
"colorInfoActive",
"colorInfoBg",
"colorInfoBgHover",
"colorInfoBorder",
"colorInfoBorderHover",
"colorInfoHover",
"colorInfoText",
"colorInfoTextActive",
"colorInfoTextHover",
"colorLink",
"colorLinkActive",
"colorLinkHover",
"colorPrimary",
"colorPrimaryActive",
"colorPrimaryBg",
"colorPrimaryBgHover",
"colorPrimaryBorder",
"colorPrimaryBorderHover",
"colorPrimaryHover",
"colorPrimaryText",
"colorPrimaryTextActive",
"colorPrimaryTextHover",
"colorSplit",
"colorSuccess",
"colorSuccessActive",
"colorSuccessBg",
"colorSuccessBgHover",
"colorSuccessBorder",
"colorSuccessBorderHover",
"colorSuccessHover",
"colorSuccessText",
"colorSuccessTextActive",
"colorSuccessTextHover",
"colorText",
"colorTextBase",
"colorTextDescription",
"colorTextDisabled",
"colorTextHeading",
"colorTextLabel",
"colorTextLightSolid",
"colorTextPlaceholder",
"colorTextQuaternary",
"colorTextSecondary",
"colorTextTertiary",
"colorWarning",
"colorWarningActive",
"colorWarningBg",
"colorWarningBgHover",
"colorWarningBorder",
"colorWarningBorderHover",
"colorWarningHover",
"colorWarningOutline",
"colorWarningText",
"colorWarningTextActive",
"colorWarningTextHover",
"colorWhite",
"controlHeight",
"controlHeightLG",
"controlHeightSM",
"controlHeightXS",
"controlInteractiveSize",
"controlItemBgActive",
"controlItemBgActiveDisabled",
"controlItemBgActiveHover",
"controlItemBgHover",
"controlOutline",
"controlOutlineWidth",
"controlPaddingHorizontal",
"controlPaddingHorizontalSM",
"controlTmpOutline",
"fontFamily",
"fontFamilyCode",
"fontHeight",
"fontHeightLG",
"fontHeightSM",
"fontSize",
"fontSizeHeading1",
"fontSizeHeading2",
"fontSizeHeading3",
"fontSizeHeading4",
"fontSizeHeading5",
"fontSizeIcon",
"fontSizeLG",
"fontSizeSM",
"fontSizeXL",
"fontWeightStrong",
"lineHeight",
"lineHeightHeading1",
"lineHeightHeading2",
"lineHeightHeading3",
"lineHeightHeading4",
"lineHeightHeading5",
"lineHeightLG",
"lineHeightSM",
"lineType",
"lineWidth",
"lineWidthBold",
"lineWidthFocus",
"linkDecoration",
"linkFocusDecoration",
"linkHoverDecoration",
"margin",
"marginLG",
"marginMD",
"marginSM",
"marginXL",
"marginXS",
"marginXXL",
"marginXXS",
"motion",
"motionBase",
"motionDurationFast",
"motionDurationMid",
"motionDurationSlow",
"motionEaseInBack",
"motionEaseInOut",
"motionEaseInOutCirc",
"motionEaseInQuint",
"motionEaseOut",
"motionEaseOutBack",
"motionEaseOutCirc",
"motionEaseOutQuint",
"motionUnit",
"opacityImage",
"opacityLoading",
"padding",
"paddingContentHorizontal",
"paddingContentHorizontalLG",
"paddingContentHorizontalSM",
"paddingContentVertical",
"paddingContentVerticalLG",
"paddingContentVerticalSM",
"paddingLG",
"paddingMD",
"paddingSM",
"paddingXL",
"paddingXS",
"paddingXXS",
"screenLG",
"screenLGMax",
"screenLGMin",
"screenMD",
"screenMDMax",
"screenMDMin",
"screenSM",
"screenSMMax",
"screenSMMin",
"screenXL",
"screenXLMax",
"screenXLMin",
"screenXS",
"screenXSMax",
"screenXSMin",
"screenXXL",
"screenXXLMin",
"size",
"sizeLG",
"sizeMD",
"sizeMS",
"sizePopupArrow",
"sizeSM",
"sizeStep",
"sizeUnit",
"sizeXL",
"sizeXS",
"sizeXXL",
"sizeXXS",
"wireframe",
"zIndexBase",
"zIndexPopupBase",
]
`;
exports[`computed dark-mode token values match snapshot 1`] = `
{
"borderRadius": 6,
"borderRadiusLG": 8,
"borderRadiusOuter": 4,
"borderRadiusSM": 4,
"borderRadiusXS": 2,
"boxShadow": "
0 6px 16px 0 rgba(255,255,255,0.016),
0 3px 6px -4px rgba(255,255,255,0.024),
0 9px 28px 8px rgba(255,255,255,0.010000000000000002)
",
"boxShadowDrawerLeft": "
6px 0 16px 0 rgba(255,255,255,0.016),
3px 0 6px -4px rgba(255,255,255,0.024),
9px 0 28px 8px rgba(255,255,255,0.010000000000000002)
",
"boxShadowDrawerRight": "
-6px 0 16px 0 rgba(255,255,255,0.016),
-3px 0 6px -4px rgba(255,255,255,0.024),
-9px 0 28px 8px rgba(255,255,255,0.010000000000000002)
",
"boxShadowDrawerUp": "
0 6px 16px 0 rgba(255,255,255,0.016),
0 3px 6px -4px rgba(255,255,255,0.024),
0 9px 28px 8px rgba(255,255,255,0.010000000000000002)
",
"boxShadowPopoverArrow": "2px 2px 5px rgba(255,255,255,0.010000000000000002)",
"boxShadowSecondary": "
0 6px 16px 0 rgba(255,255,255,0.016),
0 3px 6px -4px rgba(255,255,255,0.024),
0 9px 28px 8px rgba(255,255,255,0.010000000000000002)
",
"boxShadowTabsOverflowBottom": "inset 0 -10px 8px -8px rgba(255,255,255,0.016)",
"boxShadowTabsOverflowLeft": "inset 10px 0 8px -8px rgba(255,255,255,0.016)",
"boxShadowTabsOverflowRight": "inset -10px 0 8px -8px rgba(255,255,255,0.016)",
"boxShadowTabsOverflowTop": "inset 0 10px 8px -8px rgba(255,255,255,0.016)",
"boxShadowTertiary": "
0 1px 2px 0 rgba(255,255,255,0.010000000000000002),
0 1px 6px -1px rgba(255,255,255,0.006),
0 2px 4px 0 rgba(255,255,255,0.006)
",
"colorBgBase": "#000",
"colorBgBlur": "rgba(255,255,255,0.04)",
"colorBgContainer": "#141414",
"colorBgContainerDisabled": "rgba(255,255,255,0.08)",
"colorBgElevated": "#1f1f1f",
"colorBgLayout": "#000000",
"colorBgMask": "rgba(0,0,0,0.45)",
"colorBgSpotlight": "#424242",
"colorBgTextActive": "rgba(255,255,255,0.18)",
"colorBgTextHover": "rgba(255,255,255,0.12)",
"colorBorder": "#424242",
"colorBorderBg": "#141414",
"colorBorderSecondary": "#303030",
"colorError": "#e04355",
"colorErrorActive": "#99333e",
"colorErrorBg": "#271619",
"colorErrorBgActive": "#512228",
"colorErrorBgHover": "#3e1b20",
"colorErrorBorder": "#512228",
"colorErrorBorderHover": "#702931",
"colorErrorHover": "#d7646e",
"colorErrorOutline": "rgba(231,42,76,0.09)",
"colorErrorText": "#c13c4b",
"colorErrorTextActive": "#99333e",
"colorErrorTextHover": "#d7646e",
"colorFill": "rgba(255,255,255,0.18)",
"colorFillAlter": "rgba(255,255,255,0.04)",
"colorFillContent": "rgba(255,255,255,0.12)",
"colorFillContentHover": "rgba(255,255,255,0.18)",
"colorFillQuaternary": "rgba(255,255,255,0.04)",
"colorFillSecondary": "rgba(255,255,255,0.12)",
"colorFillTertiary": "rgba(255,255,255,0.08)",
"colorHighlight": "#c13c4b",
"colorIcon": "rgba(255,255,255,0.45)",
"colorIconHover": "rgba(255,255,255,0.85)",
"colorInfo": "#66bcfe",
"colorInfoActive": "#4981ac",
"colorInfoBg": "#19222c",
"colorInfoBgHover": "#223545",
"colorInfoBorder": "#2d465a",
"colorInfoBorderHover": "#39607d",
"colorInfoHover": "#39607d",
"colorInfoText": "#5aa3db",
"colorInfoTextActive": "#4981ac",
"colorInfoTextHover": "#83bfe8",
"colorLink": "#2893B3",
"colorLinkActive": "#21677b",
"colorLinkHover": "#1d4d5c",
"colorPrimary": "#2893B3",
"colorPrimaryActive": "#21677b",
"colorPrimaryBg": "#1a3a44",
"colorPrimaryBgHover": "#1d4d5c",
"colorPrimaryBorder": "#1a3a44",
"colorPrimaryBorderHover": "#1d4d5c",
"colorPrimaryHover": "#4499ae",
"colorPrimaryText": "#25809b",
"colorPrimaryTextActive": "#21677b",
"colorPrimaryTextHover": "#4499ae",
"colorSplit": "rgba(253,253,253,0.12)",
"colorSuccess": "#5ac189",
"colorSuccessActive": "#428460",
"colorSuccessBg": "#17231d",
"colorSuccessBgHover": "#1f362a",
"colorSuccessBorder": "#294837",
"colorSuccessBorderHover": "#346249",
"colorSuccessHover": "#346249",
"colorSuccessText": "#50a777",
"colorSuccessTextActive": "#428460",
"colorSuccessTextHover": "#77bc94",
"colorText": "rgba(255,255,255,0.85)",
"colorTextBase": "#fff",
"colorTextDescription": "rgba(255,255,255,0.45)",
"colorTextDisabled": "rgba(255,255,255,0.25)",
"colorTextHeading": "rgba(255,255,255,0.85)",
"colorTextLabel": "rgba(255,255,255,0.65)",
"colorTextLightSolid": "#fff",
"colorTextPlaceholder": "rgba(255,255,255,0.25)",
"colorTextQuaternary": "rgba(255,255,255,0.25)",
"colorTextSecondary": "rgba(255,255,255,0.65)",
"colorTextTertiary": "rgba(255,255,255,0.45)",
"colorWarning": "#fcc700",
"colorWarningActive": "#ab8807",
"colorWarningBg": "#2b2411",
"colorWarningBgHover": "#45370f",
"colorWarningBorder": "#5a4a0e",
"colorWarningBorderHover": "#7c650b",
"colorWarningHover": "#7c650b",
"colorWarningOutline": "rgba(173,127,0,0.15)",
"colorWarningText": "#d9ac03",
"colorWarningTextActive": "#ab8807",
"colorWarningTextHover": "#e8c427",
"colorWhite": "#fff",
"controlHeight": 32,
"controlHeightLG": 40,
"controlHeightSM": 24,
"controlHeightXS": 16,
"controlInteractiveSize": 16,
"controlItemBgActive": "#1a3a44",
"controlItemBgActiveDisabled": "rgba(255,255,255,0.18)",
"controlItemBgActiveHover": "#1d4d5c",
"controlItemBgHover": "rgba(255,255,255,0.08)",
"controlOutline": "rgba(49,201,249,0.21)",
"controlOutlineWidth": 2,
"controlPaddingHorizontal": 12,
"controlPaddingHorizontalSM": 8,
"controlTmpOutline": "rgba(255,255,255,0.04)",
"fontFamily": "'Inter', Helvetica, Arial",
"fontFamilyCode": "'IBM Plex Mono', 'Courier New', monospace",
"fontHeight": 22,
"fontHeightLG": 24,
"fontHeightSM": 20,
"fontSize": 14,
"fontSizeHeading1": 38,
"fontSizeHeading2": 30,
"fontSizeHeading3": 24,
"fontSizeHeading4": 20,
"fontSizeHeading5": 16,
"fontSizeIcon": 12,
"fontSizeLG": 16,
"fontSizeSM": 12,
"fontSizeXL": 20,
"fontWeightStrong": 600,
"lineHeight": 1.5714285714285714,
"lineHeightHeading1": 1.2105263157894737,
"lineHeightHeading2": 1.2666666666666666,
"lineHeightHeading3": 1.3333333333333333,
"lineHeightHeading4": 1.4,
"lineHeightHeading5": 1.5,
"lineHeightLG": 1.5,
"lineHeightSM": 1.6666666666666667,
"lineType": "solid",
"lineWidth": 1,
"lineWidthBold": 2,
"lineWidthFocus": 3,
"linkDecoration": "none",
"linkFocusDecoration": "none",
"linkHoverDecoration": "none",
"margin": 16,
"marginLG": 24,
"marginMD": 20,
"marginSM": 12,
"marginXL": 32,
"marginXS": 8,
"marginXXL": 48,
"marginXXS": 4,
"motion": true,
"motionBase": 0,
"motionDurationFast": "0.1s",
"motionDurationMid": "0.2s",
"motionDurationSlow": "0.3s",
"motionEaseInBack": "cubic-bezier(0.71, -0.46, 0.88, 0.6)",
"motionEaseInOut": "cubic-bezier(0.645, 0.045, 0.355, 1)",
"motionEaseInOutCirc": "cubic-bezier(0.78, 0.14, 0.15, 0.86)",
"motionEaseInQuint": "cubic-bezier(0.755, 0.05, 0.855, 0.06)",
"motionEaseOut": "cubic-bezier(0.215, 0.61, 0.355, 1)",
"motionEaseOutBack": "cubic-bezier(0.12, 0.4, 0.29, 1.46)",
"motionEaseOutCirc": "cubic-bezier(0.08, 0.82, 0.17, 1)",
"motionEaseOutQuint": "cubic-bezier(0.23, 1, 0.32, 1)",
"motionUnit": 0.1,
"opacityImage": 1,
"opacityLoading": 0.65,
"padding": 16,
"paddingContentHorizontal": 16,
"paddingContentHorizontalLG": 24,
"paddingContentHorizontalSM": 16,
"paddingContentVertical": 12,
"paddingContentVerticalLG": 16,
"paddingContentVerticalSM": 8,
"paddingLG": 24,
"paddingMD": 20,
"paddingSM": 12,
"paddingXL": 32,
"paddingXS": 8,
"paddingXXS": 4,
"screenLG": 992,
"screenLGMax": 1199,
"screenLGMin": 992,
"screenMD": 768,
"screenMDMax": 991,
"screenMDMin": 768,
"screenSM": 576,
"screenSMMax": 767,
"screenSMMin": 576,
"screenXL": 1200,
"screenXLMax": 1599,
"screenXLMin": 1200,
"screenXS": 480,
"screenXSMax": 575,
"screenXSMin": 480,
"screenXXL": 1600,
"screenXXLMin": 1600,
"size": 16,
"sizeLG": 24,
"sizeMD": 20,
"sizeMS": 16,
"sizePopupArrow": 16,
"sizeSM": 12,
"sizeStep": 4,
"sizeUnit": 4,
"sizeXL": 32,
"sizeXS": 8,
"sizeXXL": 48,
"sizeXXS": 4,
"wireframe": false,
"zIndexBase": 0,
"zIndexPopupBase": 1000,
}
`;
exports[`computed light-mode token values match snapshot 1`] = `
{
"borderRadius": 6,
"borderRadiusLG": 8,
"borderRadiusOuter": 4,
"borderRadiusSM": 4,
"borderRadiusXS": 2,
"boxShadow": "
0 6px 16px 0 rgba(0,0,0,0.08),
0 3px 6px -4px rgba(0,0,0,0.12),
0 9px 28px 8px rgba(0,0,0,0.05)
",
"boxShadowDrawerLeft": "
6px 0 16px 0 rgba(0,0,0,0.08),
3px 0 6px -4px rgba(0,0,0,0.12),
9px 0 28px 8px rgba(0,0,0,0.05)
",
"boxShadowDrawerRight": "
-6px 0 16px 0 rgba(0,0,0,0.08),
-3px 0 6px -4px rgba(0,0,0,0.12),
-9px 0 28px 8px rgba(0,0,0,0.05)
",
"boxShadowDrawerUp": "
0 6px 16px 0 rgba(0,0,0,0.08),
0 3px 6px -4px rgba(0,0,0,0.12),
0 9px 28px 8px rgba(0,0,0,0.05)
",
"boxShadowPopoverArrow": "2px 2px 5px rgba(0,0,0,0.05)",
"boxShadowSecondary": "
0 6px 16px 0 rgba(0,0,0,0.08),
0 3px 6px -4px rgba(0,0,0,0.12),
0 9px 28px 8px rgba(0,0,0,0.05)
",
"boxShadowTabsOverflowBottom": "inset 0 -10px 8px -8px rgba(0,0,0,0.08)",
"boxShadowTabsOverflowLeft": "inset 10px 0 8px -8px rgba(0,0,0,0.08)",
"boxShadowTabsOverflowRight": "inset -10px 0 8px -8px rgba(0,0,0,0.08)",
"boxShadowTabsOverflowTop": "inset 0 10px 8px -8px rgba(0,0,0,0.08)",
"boxShadowTertiary": "
0 1px 2px 0 rgba(0,0,0,0.05),
0 1px 6px -1px rgba(0,0,0,0.03),
0 2px 4px 0 rgba(0,0,0,0.03)
",
"colorBgBase": "#fff",
"colorBgBlur": "transparent",
"colorBgContainer": "#ffffff",
"colorBgContainerDisabled": "rgba(0,0,0,0.04)",
"colorBgElevated": "#ffffff",
"colorBgLayout": "#f5f5f5",
"colorBgMask": "rgba(0,0,0,0.45)",
"colorBgSpotlight": "rgba(0,0,0,0.85)",
"colorBgTextActive": "rgba(0,0,0,0.15)",
"colorBgTextHover": "rgba(0,0,0,0.06)",
"colorBorder": "#d9d9d9",
"colorBorderBg": "#ffffff",
"colorBorderSecondary": "#f0f0f0",
"colorError": "#e04355",
"colorErrorActive": "#ba2f43",
"colorErrorBg": "#fff0f0",
"colorErrorBgActive": "#ffc7c8",
"colorErrorBgHover": "#fff0f0",
"colorErrorBorder": "#ffc7c8",
"colorErrorBorderHover": "#fa9ba0",
"colorErrorHover": "#ed6d78",
"colorErrorOutline": "rgba(255,5,5,0.06)",
"colorErrorText": "#e04355",
"colorErrorTextActive": "#ba2f43",
"colorErrorTextHover": "#ed6d78",
"colorFill": "rgba(0,0,0,0.15)",
"colorFillAlter": "rgba(0,0,0,0.02)",
"colorFillContent": "rgba(0,0,0,0.06)",
"colorFillContentHover": "rgba(0,0,0,0.15)",
"colorFillQuaternary": "rgba(0,0,0,0.02)",
"colorFillSecondary": "rgba(0,0,0,0.06)",
"colorFillTertiary": "rgba(0,0,0,0.04)",
"colorHighlight": "#e04355",
"colorIcon": "rgba(0,0,0,0.45)",
"colorIconHover": "rgba(0,0,0,0.88)",
"colorInfo": "#66bcfe",
"colorInfoActive": "#4c97d9",
"colorInfoBg": "#f0fbff",
"colorInfoBgHover": "#f0faff",
"colorInfoBorder": "#e0f5ff",
"colorInfoBorderHover": "#b8e5ff",
"colorInfoHover": "#b8e5ff",
"colorInfoText": "#66bcfe",
"colorInfoTextActive": "#4c97d9",
"colorInfoTextHover": "#8fd2ff",
"colorLink": "#2893B3",
"colorLinkActive": "#186d8c",
"colorLinkHover": "#6ebccc",
"colorPrimary": "#2893B3",
"colorPrimaryActive": "#186d8c",
"colorPrimaryBg": "#e4f1f2",
"colorPrimaryBgHover": "#c5e2e6",
"colorPrimaryBorder": "#98d0d9",
"colorPrimaryBorderHover": "#6ebccc",
"colorPrimaryHover": "#49a8bf",
"colorPrimaryText": "#2893b3",
"colorPrimaryTextActive": "#186d8c",
"colorPrimaryTextHover": "#49a8bf",
"colorSplit": "rgba(5,5,5,0.06)",
"colorSuccess": "#5ac189",
"colorSuccessActive": "#419c6d",
"colorSuccessBg": "#f0fff4",
"colorSuccessBgHover": "#e6f5eb",
"colorSuccessBorder": "#dae8df",
"colorSuccessBorderHover": "#addbbf",
"colorSuccessHover": "#addbbf",
"colorSuccessText": "#5ac189",
"colorSuccessTextActive": "#419c6d",
"colorSuccessTextHover": "#82cfa2",
"colorText": "rgba(0,0,0,0.88)",
"colorTextBase": "#000",
"colorTextDescription": "rgba(0,0,0,0.45)",
"colorTextDisabled": "rgba(0,0,0,0.25)",
"colorTextHeading": "rgba(0,0,0,0.88)",
"colorTextLabel": "rgba(0,0,0,0.65)",
"colorTextLightSolid": "#fff",
"colorTextPlaceholder": "rgba(0,0,0,0.25)",
"colorTextQuaternary": "rgba(0,0,0,0.25)",
"colorTextSecondary": "rgba(0,0,0,0.65)",
"colorTextTertiary": "rgba(0,0,0,0.45)",
"colorWarning": "#fcc700",
"colorWarningActive": "#d6a100",
"colorWarningBg": "#fffee6",
"colorWarningBgHover": "#fff7a3",
"colorWarningBorder": "#fff07a",
"colorWarningBorderHover": "#ffe552",
"colorWarningHover": "#ffe552",
"colorWarningOutline": "rgba(255,245,5,0.1)",
"colorWarningText": "#fcc700",
"colorWarningTextActive": "#d6a100",
"colorWarningTextHover": "#ffd829",
"colorWhite": "#fff",
"controlHeight": 32,
"controlHeightLG": 40,
"controlHeightSM": 24,
"controlHeightXS": 16,
"controlInteractiveSize": 16,
"controlItemBgActive": "#e4f1f2",
"controlItemBgActiveDisabled": "rgba(0,0,0,0.15)",
"controlItemBgActiveHover": "#c5e2e6",
"controlItemBgHover": "rgba(0,0,0,0.04)",
"controlOutline": "rgba(10,128,137,0.11)",
"controlOutlineWidth": 2,
"controlPaddingHorizontal": 12,
"controlPaddingHorizontalSM": 8,
"controlTmpOutline": "rgba(0,0,0,0.02)",
"fontFamily": "'Inter', Helvetica, Arial",
"fontFamilyCode": "'IBM Plex Mono', 'Courier New', monospace",
"fontHeight": 22,
"fontHeightLG": 24,
"fontHeightSM": 20,
"fontSize": 14,
"fontSizeHeading1": 38,
"fontSizeHeading2": 30,
"fontSizeHeading3": 24,
"fontSizeHeading4": 20,
"fontSizeHeading5": 16,
"fontSizeIcon": 12,
"fontSizeLG": 16,
"fontSizeSM": 12,
"fontSizeXL": 20,
"fontWeightStrong": 600,
"lineHeight": 1.5714285714285714,
"lineHeightHeading1": 1.2105263157894737,
"lineHeightHeading2": 1.2666666666666666,
"lineHeightHeading3": 1.3333333333333333,
"lineHeightHeading4": 1.4,
"lineHeightHeading5": 1.5,
"lineHeightLG": 1.5,
"lineHeightSM": 1.6666666666666667,
"lineType": "solid",
"lineWidth": 1,
"lineWidthBold": 2,
"lineWidthFocus": 3,
"linkDecoration": "none",
"linkFocusDecoration": "none",
"linkHoverDecoration": "none",
"margin": 16,
"marginLG": 24,
"marginMD": 20,
"marginSM": 12,
"marginXL": 32,
"marginXS": 8,
"marginXXL": 48,
"marginXXS": 4,
"motion": true,
"motionBase": 0,
"motionDurationFast": "0.1s",
"motionDurationMid": "0.2s",
"motionDurationSlow": "0.3s",
"motionEaseInBack": "cubic-bezier(0.71, -0.46, 0.88, 0.6)",
"motionEaseInOut": "cubic-bezier(0.645, 0.045, 0.355, 1)",
"motionEaseInOutCirc": "cubic-bezier(0.78, 0.14, 0.15, 0.86)",
"motionEaseInQuint": "cubic-bezier(0.755, 0.05, 0.855, 0.06)",
"motionEaseOut": "cubic-bezier(0.215, 0.61, 0.355, 1)",
"motionEaseOutBack": "cubic-bezier(0.12, 0.4, 0.29, 1.46)",
"motionEaseOutCirc": "cubic-bezier(0.08, 0.82, 0.17, 1)",
"motionEaseOutQuint": "cubic-bezier(0.23, 1, 0.32, 1)",
"motionUnit": 0.1,
"opacityImage": 1,
"opacityLoading": 0.65,
"padding": 16,
"paddingContentHorizontal": 16,
"paddingContentHorizontalLG": 24,
"paddingContentHorizontalSM": 16,
"paddingContentVertical": 12,
"paddingContentVerticalLG": 16,
"paddingContentVerticalSM": 8,
"paddingLG": 24,
"paddingMD": 20,
"paddingSM": 12,
"paddingXL": 32,
"paddingXS": 8,
"paddingXXS": 4,
"screenLG": 992,
"screenLGMax": 1199,
"screenLGMin": 992,
"screenMD": 768,
"screenMDMax": 991,
"screenMDMin": 768,
"screenSM": 576,
"screenSMMax": 767,
"screenSMMin": 576,
"screenXL": 1200,
"screenXLMax": 1599,
"screenXLMin": 1200,
"screenXS": 480,
"screenXSMax": 575,
"screenXSMin": 480,
"screenXXL": 1600,
"screenXXLMin": 1600,
"size": 16,
"sizeLG": 24,
"sizeMD": 20,
"sizeMS": 16,
"sizePopupArrow": 16,
"sizeSM": 12,
"sizeStep": 4,
"sizeUnit": 4,
"sizeXL": 32,
"sizeXS": 8,
"sizeXXL": 48,
"sizeXXS": 4,
"wireframe": false,
"zIndexBase": 0,
"zIndexPopupBase": 1000,
}
`;

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { theme as antdThemeImport, type MappingAlgorithm } from 'antd';
import { theme as antdThemeImport } from 'antd';
import {
type AntdThemeConfig,
type AnyThemeConfig,
@@ -48,8 +48,8 @@ export function isSerializableConfig(
export function deserializeThemeConfig(
config: SerializableThemeConfig,
): AntdThemeConfig {
const { algorithm, cssVar, ...rest } = config;
const algorithmMap: Record<string, MappingAlgorithm> = {
const { algorithm, ...rest } = config;
const algorithmMap: Record<string, any> = {
default: antdThemeImport.defaultAlgorithm,
dark: antdThemeImport.darkAlgorithm,
compact: antdThemeImport.compactAlgorithm,
@@ -74,17 +74,9 @@ export function deserializeThemeConfig(
resolvedAlgorithm = antdThemeImport.defaultAlgorithm;
}
// Ant Design v6 dropped `boolean` from ThemeConfig['cssVar'] (it is now
// object-only, and CSS variables are enabled by default). Superset keeps a
// boolean-friendly serializable API, so coerce at the antd boundary:
// `true` -> `{}` (defaults), `false`/undefined -> omit (antd default).
const normalizedCssVar =
typeof cssVar === 'boolean' ? (cssVar ? {} : undefined) : cssVar;
return {
...rest,
algorithm: resolvedAlgorithm,
...(normalizedCssVar !== undefined ? { cssVar: normalizedCssVar } : {}),
};
}

View File

@@ -31,7 +31,7 @@
"lodash-es": "^4.18.1"
},
"peerDependencies": {
"@ant-design/icons": "^5.6.1 || ^6.0.0",
"@ant-design/icons": "^5.6.1",
"@emotion/react": "^11.4.1",
"@superset-ui/core": "*",
"@testing-library/dom": "^9.3.4",

View File

@@ -54,7 +54,7 @@
"react-ace": "^14.0.1",
"react-draggable": "^4.7.0",
"react-error-boundary": "^6.1.2",
"react-js-cron": "^6.0.2",
"react-js-cron": "^5.2.0",
"react-markdown": "^10.1.0",
"react-resize-detector": "^7.1.2",
"react-syntax-highlighter": "^16.1.1",
@@ -78,7 +78,7 @@
"@types/d3-time-format": "^4.0.3",
"@types/jquery": "^4.0.1",
"@types/lodash": "^4.17.24",
"@types/node": "^26.1.1",
"@types/node": "^26.1.0",
"@types/prop-types": "^15.7.15",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/react-table": "^7.7.20",
@@ -100,7 +100,7 @@
"@types/react-loadable": "*",
"@types/react-window": "^1.8.8",
"@types/tinycolor2": "*",
"antd": "^6.0.0",
"antd": "^5.26.0",
"nanoid": "^5.0.9",
"react": "^18.3.0",
"react-dom": "^18.3.0",

View File

@@ -92,7 +92,7 @@ const MatrixNoDataComponent = () => {
* Individual grid cell component - memoized to prevent unnecessary re-renders
*/
const MatrixifyGridCell = memo(
({ cell, hooks }: MatrixifyGridCellProps) => {
({ cell, rowHeight, datasource, hooks }: MatrixifyGridCellProps) => {
// Use computed title from template (will be empty string if no template)
const cellLabel = cell.title || '';

View File

@@ -19,7 +19,8 @@
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { ThemeProvider, supersetTheme } from '@apache-superset/core/theme';
import { ThemeProvider } from '@apache-superset/core/theme';
import { supersetTheme } from '@apache-superset/core/theme';
import MatrixifyGridRenderer from './MatrixifyGridRenderer';
import type { MatrixifyMode } from '../../types/matrixify';
import { generateMatrixifyGrid } from './MatrixifyGridGenerator';

View File

@@ -118,6 +118,7 @@ interface MatrixifyGridRendererProps {
function MatrixifyGridRenderer({
formData,
datasource,
width,
height,
hooks,
}: MatrixifyGridRendererProps) {
@@ -248,7 +249,7 @@ function MatrixifyGridRenderer({
{/* Row cells for this column group */}
{row
.slice(colGroup.startIdx, colGroup.endIdx)
.map(cell =>
.map((cell, colIdx) =>
cell ? (
<MatrixifyGridCell
key={cell.id}

View File

@@ -32,7 +32,8 @@ import {
wordCloudFormData,
} from '../../../../superset-ui-core/test/chart/fixtures/formData';
import { Expandable, VerifyCORS, renderError } from '@storybook-shared';
import { Expandable } from '@storybook-shared';
import { VerifyCORS, renderError } from '@storybook-shared';
const BIG_NUMBER = bigNumberFormData.viz_type;
const SANKEY = sankeyFormData.viz_type;

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { render, screen, userEvent, fireEvent } from '@superset-ui/core/spec';
import { render, screen, userEvent } from '@superset-ui/core/spec';
import { Icons } from '@superset-ui/core/components/Icons';
import { ActionButton } from '.';
@@ -45,18 +45,6 @@ test('calls onClick when clicked', async () => {
expect(onClick).toHaveBeenCalledTimes(1);
});
test('calls onClick when activated with the keyboard', () => {
const onClick = jest.fn();
render(<ActionButton {...defaultProps} onClick={onClick} />);
const button = screen.getByRole('button');
fireEvent.keyDown(button, { key: 'Enter' });
expect(onClick).toHaveBeenCalledTimes(1);
fireEvent.keyDown(button, { key: ' ' });
expect(onClick).toHaveBeenCalledTimes(2);
});
test('renders with tooltip when tooltip prop is provided', async () => {
const tooltipText = 'This is a tooltip';
render(<ActionButton {...defaultProps} tooltip={tooltipText} />);

View File

@@ -17,7 +17,6 @@
* under the License.
*/
import { handleKeyboardActivation } from '../../utils';
import type { ReactElement, ReactNode } from 'react';
import { Tooltip, type TooltipPlacement } from '@superset-ui/core/components';
import { css, useTheme } from '@apache-superset/core/theme';
@@ -56,7 +55,6 @@ export const ActionButton = ({
className="action-button"
data-test={label}
onClick={onClick}
onKeyDown={onClick ? handleKeyboardActivation(onClick) : undefined}
>
{icon}
</span>

View File

@@ -444,6 +444,7 @@ export function AsyncAceEditor(
/* Adjust tooltip styles */
.ace_tooltip {
margin-left: ${token.margin}px;
padding: ${token.sizeUnit * 2}px;
background-color: ${token.colorBgElevated} !important;
color: ${token.colorText} !important;
border: 1px solid ${token.colorBorderSecondary};

View File

@@ -17,13 +17,13 @@
* under the License.
*/
import { fireEvent, render } from '@superset-ui/core/spec';
import { Button } from '.';
import {
ButtonGallery,
SIZES as buttonSizes,
STYLES as buttonStyles,
} from './Button.stories';
import {
Button,
getSecondaryButtonStyle,
getSecondaryButtonHoverStyles,
} from './index';

View File

@@ -82,10 +82,8 @@ test('collapses on click', async () => {
await userEvent.click(screen.getAllByRole('button')[0]);
// antd v6 moved the hidden state to the panel wrapper element
// (ant-collapse-panel-hidden) instead of the content wrapper.
expect(screen.getByText('Content 1').parentElement).toHaveClass(
'ant-collapse-panel-hidden',
expect(screen.getByText('Content 1').parentNode).toHaveClass(
'ant-collapse-content-hidden',
);
expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
});

View File

@@ -52,10 +52,10 @@ const StyledCollapse = styled((props: CollapseProps) => (
`
border-bottom: 1px solid ${theme.colorBorderSecondary};
`}
.ant-collapse-panel {
.ant-collapse-content {
color: ${({ theme }) => theme.colorText};
.ant-collapse-body {
.ant-collapse-content-box {
.loading.inline {
margin: ${({ theme }) => theme.sizeUnit * 12}px auto;
display: block;

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import { render, waitFor } from '@superset-ui/core/spec';
import { render } from '@superset-ui/core/spec';
import * as ReactCronPicker from 'react-js-cron';
import { CronPicker } from '.';
@@ -37,25 +37,3 @@ test('Should send correct props to ReactCronPicker', () => {
expect.anything(),
);
});
// The invalid-cron error styling was silently dead under antd 6 until
// react-js-cron was bumped to v6 (its v5 stylesheet targeted antd 5 Select
// classes that no longer exist). Pin the error contract: an invalid value
// must fire onError and apply the vendor's error class — the hook that both
// the vendor stylesheet and Superset's overrides style the error state
// through.
test('flags an invalid cron value with the error state', async () => {
const onError = jest.fn();
const { container } = render(
<CronPicker value="not a cron" setValue={jest.fn()} onError={onError} />,
);
await waitFor(() =>
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ type: expect.any(String) }),
),
);
expect(
// eslint-disable-next-line testing-library/no-node-access
container.querySelector('.react-js-cron-error'),
).toBeInTheDocument();
});

View File

@@ -175,7 +175,7 @@ export const CronPicker = styled((props: CronProps) => (
.react-js-cron-select.ant-select {
width: 100%;
.ant-select-content {
.ant-select-selector {
flex-wrap: nowrap;
}
}
@@ -188,12 +188,12 @@ export const CronPicker = styled((props: CronProps) => (
}
}
.react-js-cron-custom-select .ant-select-placeholder {
.react-js-cron-custom-select .ant-select-selection-placeholder {
flex: auto;
border-radius: ${theme.borderRadius}px;
}
.react-js-cron-custom-select .ant-select-content-item {
.react-js-cron-custom-select .ant-select-selection-overflow-item {
align-self: center;
}

View File

@@ -338,9 +338,9 @@ export const DropdownContainer = forwardRef(
<>
<Global
styles={css`
.ant-popover-container {
/* Some OS versions only show the scroll when hovering. */
/* These settings will make the scroll always visible. */
.ant-popover-inner {
// Some OS versions only show the scroll when hovering.
// These settings will make the scroll always visible.
::-webkit-scrollbar {
-webkit-appearance: none;
width: 14px;
@@ -361,8 +361,7 @@ export const DropdownContainer = forwardRef(
<Popover
styles={{
// antd v6 renamed the inner content slot `body` -> `container`
container: {
body: {
maxHeight: `${MAX_HEIGHT}px`,
overflow: showOverflow ? 'auto' : 'visible',
},

View File

@@ -60,7 +60,6 @@ export const FaveStar = ({
className="fave-unfave-icon"
data-test="fave-unfave-icon"
role="button"
tabIndex={0}
>
{isStarred ? (
<Icons.StarFilled

View File

@@ -28,10 +28,6 @@ const StyledInput = styled(Input)`
margin: ${({ theme }) => `${theme.sizeUnit}px 0 ${theme.sizeUnit * 2}px`};
`;
const StyledTextArea = styled(Input.TextArea)`
margin: ${({ theme }) => `${theme.sizeUnit}px 0 ${theme.sizeUnit * 2}px`};
`;
const StyledInputPassword = styled(Input.Password)`
margin: ${({ theme }) => `${theme.sizeUnit}px 0 ${theme.sizeUnit * 2}px`};
`;
@@ -66,8 +62,6 @@ export const LabeledErrorBoundInput = ({
get_url,
description,
isValidating = false,
renderAsTextArea,
textAreaCss,
...props
}: LabeledErrorBoundInputProps) => {
const hasError = !!errorMessage;
@@ -104,8 +98,6 @@ export const LabeledErrorBoundInput = ({
}
role="textbox"
/>
) : renderAsTextArea ? (
<StyledTextArea css={textAreaCss} {...props} {...validationMethods} />
) : (
<StyledInput {...props} {...validationMethods} />
)}

View File

@@ -16,10 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import type { SerializedStyles } from '@emotion/react';
export type { FormProps, FormInstance, FormItemProps } from 'antd/es/form';
export type { SerializedStyles };
export interface LabeledErrorBoundInputProps {
label?: string;
@@ -34,7 +31,5 @@ export interface LabeledErrorBoundInputProps {
classname?: string;
visibilityToggle?: boolean;
isValidating?: boolean;
renderAsTextArea?: boolean;
textAreaCss?: SerializedStyles;
[x: string]: any;
}

View File

@@ -63,11 +63,11 @@ const IconBlock = styled.div`
span {
margin-top: ${({ theme }) =>
2 * theme.sizeUnit}px; /* Add spacing between icon and name */
2 * theme.sizeUnit}px; // Add spacing between icon and name
font-size: ${({ theme }) =>
theme.fontSizeSM}; /* Optional: adjust font size for elegance */
theme.fontSizeSM}; // Optional: adjust font size for elegance
color: ${({ theme }) =>
theme.colorText}; /* Optional: subtle color for the name */
theme.colorText}; // Optional: subtle color for the name
}
`;

View File

@@ -18,7 +18,6 @@
*/
import { fireEvent, render } from '@superset-ui/core/spec';
import { Icons } from '../Icons';
import { Label } from '.';
import { LabelGallery, options } from './Label.stories';
@@ -40,21 +39,6 @@ test('renders with monospace prop', () => {
expect(getByText('monospace text')).toBeInTheDocument();
});
// Regression: Ant Design v6's Tag clones the element passed via its `icon` prop
// and overwrites that element's inline `style`, which silently dropped the
// icon's own color. Label wraps the icon in a span so the wrapper (not the
// icon) is Tag's clone target and the icon keeps its explicit color.
test('preserves a custom icon color (antd v6 Tag icon-style regression)', () => {
const { container } = render(
<Label icon={<Icons.CheckCircleOutlined iconColor="#aabbcc" />}>
labeled
</Label>,
);
expect(container.querySelector('[role="img"]')).toHaveStyle({
color: '#aabbcc',
});
});
// test stories from the storybook!
test('renders all the storybook gallery variants', () => {
const { container } = render(<LabelGallery />);

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