mirror of
https://github.com/apache/superset.git
synced 2026-07-18 12:45:44 +00:00
Compare commits
221 Commits
enxdev/fea
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
054bf559c1 | ||
|
|
bda62cb28d | ||
|
|
45354994a6 | ||
|
|
0f61d9a3fb | ||
|
|
b432f8c917 | ||
|
|
25f6c6c80b | ||
|
|
ce8d4397cd | ||
|
|
68297886d4 | ||
|
|
91f814abea | ||
|
|
6a330c251b | ||
|
|
5bf26f77f8 | ||
|
|
5750d82426 | ||
|
|
c84a154b52 | ||
|
|
5b837e844e | ||
|
|
a99c98c6fe | ||
|
|
f8cfa459ef | ||
|
|
eef3dac72f | ||
|
|
73aa8ef280 | ||
|
|
f3255c46ac | ||
|
|
9f230bcfc0 | ||
|
|
c05ac138fb | ||
|
|
c0781ba316 | ||
|
|
905b20a3f2 | ||
|
|
fc506c06da | ||
|
|
69b144ec16 | ||
|
|
9ccd365652 | ||
|
|
60fb1c6f01 | ||
|
|
929ec58276 | ||
|
|
bef03fb850 | ||
|
|
8d36dca9e8 | ||
|
|
9bb0e376b3 | ||
|
|
778800ba82 | ||
|
|
ba309fd016 | ||
|
|
7eab41f904 | ||
|
|
dbef0c3fee | ||
|
|
157ef61fd8 | ||
|
|
f467d36a24 | ||
|
|
9f8e1508ca | ||
|
|
20e6dfd37d | ||
|
|
5067230484 | ||
|
|
c45b9002e6 | ||
|
|
edca579625 | ||
|
|
30b93ab743 | ||
|
|
9e38c1dd13 | ||
|
|
b459601ceb | ||
|
|
6f154377d7 | ||
|
|
5b57eb38d2 | ||
|
|
33bc35d135 | ||
|
|
2dfd08fdb3 | ||
|
|
661535e390 | ||
|
|
059f6944eb | ||
|
|
3b7647eb6e | ||
|
|
e84f870787 | ||
|
|
de300c70b9 | ||
|
|
2f7afe4b47 | ||
|
|
aea4585c6d | ||
|
|
f697a0c24d | ||
|
|
13d38a9cbd | ||
|
|
8603048518 | ||
|
|
635b18103d | ||
|
|
d57569c54a | ||
|
|
409605de70 | ||
|
|
8ce6d42942 | ||
|
|
2bbb7d0638 | ||
|
|
1b0c6aaed3 | ||
|
|
1f786f1949 | ||
|
|
c540f782a3 | ||
|
|
6fe4655ba2 | ||
|
|
689fc34ac2 | ||
|
|
e564389a01 | ||
|
|
da518d7a10 | ||
|
|
c6da740ce2 | ||
|
|
e28b259de0 | ||
|
|
beb9d53687 | ||
|
|
90f9238f8a | ||
|
|
f38fff2a19 | ||
|
|
cec9afb165 | ||
|
|
753113d169 | ||
|
|
5c8e14e9dc | ||
|
|
e66d58361a | ||
|
|
7bc1895050 | ||
|
|
071c431580 | ||
|
|
53c4603c8c | ||
|
|
f010affbc2 | ||
|
|
6c2b7aceb5 | ||
|
|
2af66b2c9b | ||
|
|
a540f56f5c | ||
|
|
aa85455a5c | ||
|
|
d80267d00b | ||
|
|
7953382d10 | ||
|
|
abafe195bf | ||
|
|
b4373f60b3 | ||
|
|
6dcd95e14a | ||
|
|
ad8f2cf268 | ||
|
|
9db88203e1 | ||
|
|
13121fcd58 | ||
|
|
be768efc0f | ||
|
|
d68e84e731 | ||
|
|
0a3c263606 | ||
|
|
bd9ba24266 | ||
|
|
4446967d0f | ||
|
|
c04a0295ef | ||
|
|
e3a0d0e41e | ||
|
|
256cc71c87 | ||
|
|
4b07d43050 | ||
|
|
ef3a9e925e | ||
|
|
76bb5f8e69 | ||
|
|
1f41899deb | ||
|
|
c4bee525ea | ||
|
|
0dcf67fa27 | ||
|
|
4e10a96253 | ||
|
|
24959d1656 | ||
|
|
7075e9f253 | ||
|
|
d5e75c4813 | ||
|
|
cc32d16e0c | ||
|
|
258f4c035b | ||
|
|
d49365757c | ||
|
|
68881a60ca | ||
|
|
41efdb3082 | ||
|
|
e86dcf7b82 | ||
|
|
35d05cc278 | ||
|
|
115ce12ff7 | ||
|
|
ccfccdf237 | ||
|
|
42523f8cc4 | ||
|
|
e165762bb7 | ||
|
|
8afad27c84 | ||
|
|
e420812eb7 | ||
|
|
993a43396a | ||
|
|
a2a71760ed | ||
|
|
836dce9b05 | ||
|
|
2857b29ab9 | ||
|
|
3c648ca264 | ||
|
|
25ce83cc58 | ||
|
|
ce8219672e | ||
|
|
3e957b63d4 | ||
|
|
9818da445a | ||
|
|
8262aa7d6b | ||
|
|
d4a31d1d78 | ||
|
|
18f1dd394b | ||
|
|
62ccdfacc2 | ||
|
|
c5131bff47 | ||
|
|
521e51429a | ||
|
|
64df96be00 | ||
|
|
d43a0d7633 | ||
|
|
873566c827 | ||
|
|
e063f5093c | ||
|
|
1627fab741 | ||
|
|
5dd060b714 | ||
|
|
68ebc22e1e | ||
|
|
8f75f1a353 | ||
|
|
2fac66d1a3 | ||
|
|
029d49539b | ||
|
|
3c6982252b | ||
|
|
1c7a3f395f | ||
|
|
f7b7bad9a8 | ||
|
|
0cf217cdea | ||
|
|
96bf1a1e4c | ||
|
|
9180ace1dd | ||
|
|
bb5d7dbb17 | ||
|
|
73925922a9 | ||
|
|
8f339545ad | ||
|
|
c762b75ddc | ||
|
|
c0e5f5226d | ||
|
|
d380663e71 | ||
|
|
98c3ec7a71 | ||
|
|
12f28ce235 | ||
|
|
e403d9b074 | ||
|
|
0ca51d9796 | ||
|
|
db1912c6d9 | ||
|
|
2a18a556b0 | ||
|
|
a1d1d69f5e | ||
|
|
13c5a32402 | ||
|
|
958d1ab256 | ||
|
|
b8c0171976 | ||
|
|
49dc0acd82 | ||
|
|
5e6b29d1a3 | ||
|
|
189f258e0c | ||
|
|
f5deda7864 | ||
|
|
b237aefb1e | ||
|
|
0ecf34d80e | ||
|
|
a03cabffa7 | ||
|
|
e852147182 | ||
|
|
df209cedbf | ||
|
|
b641008da6 | ||
|
|
678fcba8b4 | ||
|
|
5c12f216d5 | ||
|
|
7a752d1c22 | ||
|
|
51ce2b11ad | ||
|
|
3818152191 | ||
|
|
4dde4d2c70 | ||
|
|
35712ff977 | ||
|
|
bc80d138fd | ||
|
|
bd61e09b4e | ||
|
|
5b8e94de26 | ||
|
|
a6ce2fd76b | ||
|
|
2112fbd379 | ||
|
|
1631d52b65 | ||
|
|
1ddc3e45d7 | ||
|
|
2a3d65f61a | ||
|
|
169c05f8ce | ||
|
|
38707866c0 | ||
|
|
e2ba378b69 | ||
|
|
597c9c6fea | ||
|
|
be600f78fc | ||
|
|
ea76d0d785 | ||
|
|
0ff52d46e0 | ||
|
|
a899e1db41 | ||
|
|
1bfb7c60e7 | ||
|
|
2380d576d5 | ||
|
|
43bfc85941 | ||
|
|
bea5f8d4a4 | ||
|
|
baff395d98 | ||
|
|
1fd43ffe52 | ||
|
|
afc7dbeff2 | ||
|
|
c1d4401d29 | ||
|
|
a4418a27c5 | ||
|
|
30e1ab3203 | ||
|
|
3dd5e69714 | ||
|
|
227b3a48c1 | ||
|
|
74c3f3b66b | ||
|
|
ea5950558b |
1
.github/actions/file-changes-action
vendored
1
.github/actions/file-changes-action
vendored
Submodule .github/actions/file-changes-action deleted from a6ca26c142
2
.github/actions/setup-backend/action.yml
vendored
2
.github/actions/setup-backend/action.yml
vendored
@@ -32,8 +32,6 @@ 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
|
||||
|
||||
28
.github/dependabot.yml
vendored
28
.github/dependabot.yml
vendored
@@ -10,8 +10,15 @@ updates:
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
ignore:
|
||||
- dependency-name: "@rjsf/*"
|
||||
# TODO: remove below entries until React >= 19.0.0
|
||||
# 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"]
|
||||
- dependency-name: "react-icons"
|
||||
# JSDOM v30 doesn't play well with Jest v30
|
||||
# Source: https://jestjs.io/blog#known-issues
|
||||
@@ -41,6 +48,23 @@ updates:
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
groups:
|
||||
rjsf:
|
||||
patterns:
|
||||
- "@rjsf/*"
|
||||
typescript-eslint:
|
||||
patterns:
|
||||
- "@typescript-eslint/*"
|
||||
- "typescript-eslint"
|
||||
babel:
|
||||
patterns:
|
||||
- "@babel/*"
|
||||
deckgl:
|
||||
patterns:
|
||||
- "@deck.gl/*"
|
||||
lumagl:
|
||||
patterns:
|
||||
- "@luma.gl/*"
|
||||
open-pull-requests-limit: 30
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
|
||||
2
.github/workflows/check-python-deps.yml
vendored
2
.github/workflows/check-python-deps.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Login to Docker Hub
|
||||
if: steps.check.outputs.python
|
||||
continue-on-error: true
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
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@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
2
.github/workflows/embedded-sdk-release.yml
vendored
2
.github/workflows/embedded-sdk-release.yml
vendored
@@ -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
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: "./superset-embedded-sdk/.nvmrc"
|
||||
- run: npm ci
|
||||
|
||||
2
.github/workflows/embedded-sdk-test.yml
vendored
2
.github/workflows/embedded-sdk-test.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: "./superset-embedded-sdk/.nvmrc"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
2
.github/workflows/generate-FOSSA-report.yml
vendored
2
.github/workflows/generate-FOSSA-report.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "11"
|
||||
|
||||
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
|
||||
- uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
|
||||
with:
|
||||
sync-labels: true
|
||||
|
||||
|
||||
2
.github/workflows/license-check.yml
vendored
2
.github/workflows/license-check.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "11"
|
||||
|
||||
10
.github/workflows/pr-lint.yml
vendored
10
.github/workflows/pr-lint.yml
vendored
@@ -8,10 +8,16 @@ on:
|
||||
# Possible values: https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-event-pull_request
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
# Serialize runs per PR without cancelling: the `edited` trigger means a PR
|
||||
# opened and then edited has two queued runs for the same head SHA. On
|
||||
# first-time-contributor PRs those runs start together when a maintainer
|
||||
# approves workflows, and cancel-in-progress lets the older run cancel the
|
||||
# newer one — leaving a permanently-cancelled required check on the head SHA
|
||||
# that blocks merging until manually re-run. This job takes seconds, so let
|
||||
# queued runs complete instead.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
lint-check:
|
||||
|
||||
85
.github/workflows/pre-commit.yml
vendored
85
.github/workflows/pre-commit.yml
vendored
@@ -7,6 +7,11 @@ 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
|
||||
@@ -25,13 +30,16 @@ 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", "previous", "next"]') }}
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "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/
|
||||
@@ -45,7 +53,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
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: "superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
@@ -69,19 +77,82 @@ jobs:
|
||||
restore-keys: |
|
||||
pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-
|
||||
|
||||
- name: Get changed files
|
||||
- name: Determine changed files
|
||||
id: changed_files
|
||||
uses: ./.github/actions/file-changes-action
|
||||
with:
|
||||
output: " "
|
||||
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
|
||||
|
||||
- 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
|
||||
pre-commit run --files $CHANGED_FILES
|
||||
|
||||
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_EXIT_CODE=$?
|
||||
git diff --quiet --exit-code
|
||||
GIT_DIFF_EXIT_CODE=$?
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Install Node.js
|
||||
if: env.HAS_TAGS
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
build: "true"
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
||||
4
.github/workflows/superset-docs-deploy.yml
vendored
4
.github/workflows/superset-docs-deploy.yml
vendored
@@ -66,12 +66,12 @@ jobs:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: "./docs/.nvmrc"
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||
with:
|
||||
distribution: "zulu"
|
||||
java-version: "21"
|
||||
|
||||
4
.github/workflows/superset-docs-verify.yml
vendored
4
.github/workflows/superset-docs-verify.yml
vendored
@@ -78,7 +78,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
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
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: "./docs/.nvmrc"
|
||||
- name: yarn install
|
||||
|
||||
4
.github/workflows/superset-e2e.yml
vendored
4
.github/workflows/superset-e2e.yml
vendored
@@ -128,7 +128,7 @@ jobs:
|
||||
with:
|
||||
run: testdata
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
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
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
|
||||
@@ -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('["previous", "current", "next"]') }}
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: superset-extensions-cli
|
||||
|
||||
9
.github/workflows/superset-helm-lint.yml
vendored
9
.github/workflows/superset-helm-lint.yml
vendored
@@ -2,17 +2,20 @@ name: "Helm: lint and test charts"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
types: [opened, reopened, synchronize]
|
||||
paths:
|
||||
- "helm/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
# Serialize runs per PR without cancelling: when a first-time contributor's
|
||||
# queued runs are approved together, cancel-in-progress lets an older run
|
||||
# cancel a newer one, leaving a permanently-cancelled required check on the
|
||||
# head SHA. Queued runs are cheap here, so let them all complete.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
lint-test:
|
||||
|
||||
2
.github/workflows/superset-playwright.yml
vendored
2
.github/workflows/superset-playwright.yml
vendored
@@ -114,7 +114,7 @@ jobs:
|
||||
with:
|
||||
run: playwright_testdata
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
|
||||
@@ -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", "previous", "next"]') }}
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
|
||||
|
||||
@@ -50,9 +50,15 @@ 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('["previous", "current", "next"]') }}
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["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
|
||||
|
||||
2
.github/workflows/superset-translations.yml
vendored
2
.github/workflows/superset-translations.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.check.outputs.frontend
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
|
||||
continue-on-error: true
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
4
.github/workflows/tag-release.yml
vendored
4
.github/workflows/tag-release.yml
vendored
@@ -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
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
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
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20
|
||||
package-manager-cache: false
|
||||
|
||||
2
.github/workflows/tech-debt.yml
vendored
2
.github/workflows/tech-debt.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -21,9 +21,6 @@
|
||||
[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
|
||||
|
||||
@@ -31,9 +31,10 @@ repos:
|
||||
types-simplejson,
|
||||
types-python-dateutil,
|
||||
types-requests,
|
||||
# types-redis 4.6.0.5 is failing mypy
|
||||
# because of https://github.com/python/typeshed/pull/10531
|
||||
types-redis==4.6.0.4,
|
||||
# types-redis is intentionally absent: redis-py ships its own
|
||||
# bundled types (py.typed, since 4.6+/5.x), while the stub
|
||||
# package is unmaintained (frozen at the 4.6.0.x API surface).
|
||||
# Installing the stub would shadow the accurate inline types.
|
||||
types-pytz,
|
||||
types-croniter,
|
||||
types-PyYAML,
|
||||
@@ -63,7 +64,7 @@ repos:
|
||||
hooks:
|
||||
- id: prettier-frontend
|
||||
name: prettier (frontend)
|
||||
entry: bash -c 'cd superset-frontend && for file in "$@"; do npx prettier --write "${file#superset-frontend/}"; done'
|
||||
entry: bash -c 'cd superset-frontend && files=(); for f in "$@"; do files+=("${f#superset-frontend/}"); done; npx prettier --write -- "${files[@]}"' --
|
||||
language: system
|
||||
pass_filenames: true
|
||||
files: ^superset-frontend/.*\.(js|jsx|ts|tsx|css|scss|sass|json)$
|
||||
@@ -81,6 +82,12 @@ 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'
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
.codecov.yml
|
||||
.eslintrc
|
||||
.eslintignore
|
||||
.stylelintignore
|
||||
.flake8
|
||||
.nvmrc
|
||||
.prettierrc
|
||||
|
||||
@@ -641,7 +641,7 @@ categories:
|
||||
|
||||
- name: Région Hauts-de-France (France)
|
||||
url: https://www.geo2france.fr/
|
||||
logo: geo2france.svg
|
||||
logo: geo2france.png
|
||||
|
||||
- name: Rennes Métropole (France)
|
||||
url: https://metropole.rennes.fr/
|
||||
|
||||
28
UPDATING.md
28
UPDATING.md
@@ -112,6 +112,8 @@ in a later major version.
|
||||
|
||||
- [41044](https://github.com/apache/superset/issues/41044): Removes the deprecated `AVOID_COLORS_COLLISION` feature flag (it defaulted to `True`). Color-collision avoidance is now permanently enabled; any config override setting it to `False` is ignored.
|
||||
|
||||
- [41813](https://github.com/apache/superset/pull/41813): `redis` (the Python client, `redis-py`) is bumped from 5.3.1 to 8.0.1. redis-py 8 changes several connection defaults; Superset's own Redis-backed features (`GLOBAL_ASYNC_QUERIES_CACHE_BACKEND`, `DISTRIBUTED_COORDINATION_CONFIG`, and the MCP Redis store) explicitly pin the pre-upgrade behavior so this bump is a no-op for them: the wire protocol stays RESP2 (not the new RESP3 default, which requires Redis/Sentinel 6+ to speak `HELLO`) and there is still no socket timeout by default (redis-py 8 defaults to 5s, which could otherwise newly time out large cached payloads or slow networks). The no-timeout default can now be overridden via two new config keys, `CACHE_REDIS_SOCKET_TIMEOUT` / `CACHE_REDIS_SOCKET_CONNECT_TIMEOUT`, on any `CacheConfig` dict using `CACHE_TYPE: RedisCache` or `RedisSentinelCache`. Separately, redis-py 6+ changed the default for `ssl_check_hostname` from `False` to `True` for SSL connections using `ssl_cert_reqs="required"` (the default) — this is a security improvement, so it has **not** been reverted; deployments with `CACHE_REDIS_SSL=True` whose certificates lack a hostname matching the connection address should set `CACHE_REDIS_SSL_CERT_REQS="none"` (disables cert verification entirely, matching hostname-check bypass) or replace the certificate. General-purpose cache/results backends configured via `CACHE_CONFIG` / `DATA_CACHE_CONFIG` / `RESULTS_BACKEND` with `CACHE_TYPE: RedisCache` go through `flask-caching`'s own Redis backend (outside Superset's code) and are subject to the same new defaults; pass `socket_timeout` / `protocol` via `CACHE_OPTIONS` there if needed. Celery broker and result-backend connections (built by `kombu`, also outside Superset's code) keep their no-socket-timeout behavior (`kombu` passes `socket_timeout=None` explicitly) but do **not** pin the wire protocol, so they follow redis-py's RESP3 default — which requires a Redis server new enough to speak `HELLO` (Redis 6+). Deployments using a pre-6.0 Redis server (EOL) as a Celery broker should upgrade the server before taking this bump.
|
||||
|
||||
- [39925](https://github.com/apache/superset/pull/39925): URL prefixing for `SUPERSET_APP_ROOT` subdirectory deployments is now handled automatically by helpers in `src/utils/navigationUtils` (`openInNewTab`, `redirect`, `getShareableUrl`, `<AppLink>`). Direct imports of `ensureAppRoot` / `makeUrl` from `src/utils/pathUtils` are forbidden outside `navigationUtils.ts` (enforced by a static-invariant test); contributors writing new code should use the focused helpers instead. No runtime behaviour change for existing callers — all 19 prior call sites have been migrated and four pre-existing double-prefix and missing-prefix bugs are fixed as part of the migration.
|
||||
|
||||
- [39925](https://github.com/apache/superset/pull/39925): `SupersetClient.getUrl()` now strips a single leading application-root segment from the supplied `endpoint` before building the request URL, so a caller that accidentally pre-prefixes its endpoint (for example by wrapping it with `ensureAppRoot` before passing it to the client) no longer produces a doubled `/superset/superset/...` URL under subdirectory deployment. The strip is **single-pass** — a genuine `/superset/superset/<slug>` route is preserved, not collapsed — and **silent** (no console warning); the static-invariant test remains the primary signal for pre-prefixing at the call site, and this runtime strip is a safety net beneath it. Code that intentionally targeted a literal `/<app_root>/<app_root>/...` endpoint through `getUrl` (a configuration that has no legitimate use under the prefixing model) would have its first redundant segment removed.
|
||||
@@ -153,6 +155,16 @@ 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.
|
||||
@@ -182,6 +194,10 @@ 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.
|
||||
@@ -213,6 +229,17 @@ ALTER TABLE tagged_object DROP CONSTRAINT <constraint_name>;
|
||||
-- MySQL: find names via `SHOW CREATE TABLE tagged_object;`
|
||||
ALTER TABLE tagged_object DROP FOREIGN KEY <constraint_name>;
|
||||
```
|
||||
### Entity version-history infrastructure (gated off by default)
|
||||
|
||||
Introduces the schema and SQLAlchemy-Continuum wiring that captures version history for charts, dashboards, and datasets, plus read-only `GET /api/v1/{chart,dashboard,dataset}/<uuid>/versions/` endpoints. This ships **inert**: a new config flag `ENABLE_VERSIONING_CAPTURE` defaults to `False`, so no save writes any version rows and the endpoints return empty. It is an operational kill-switch (a release toggle that becomes a permanent ops switch), not a feature flag — set it to `True` to enable capture once validated. The migration is additive; existing entity `PUT` responses gain `old_version_uuid` / `new_version_uuid` body fields and an `ETag` header (both null/absent when capture is off).
|
||||
|
||||
A few save- and import-path internals change **unconditionally** (independent of the flag), because the versioned mappers must behave correctly whether or not capture is enabled:
|
||||
|
||||
- `DatasetDAO` column/metric updates move from bulk operations to per-row ORM operations, and a metadata refresh now preserves column primary keys via a natural-key (`column_name`) upsert instead of delete-and-reinsert — so charts that reference dataset columns by id keep working across a refresh (previously such references could be invalidated).
|
||||
- `ImportExportMixin.reset_ownership` stamps the current user onto `created_by`/`changed_by` when a request context is present (previously left null for the column default to fill).
|
||||
- `UpdateDashboardCommand` runs its body under `no_autoflush`.
|
||||
|
||||
These are behavior changes that take effect on upgrade regardless of `ENABLE_VERSIONING_CAPTURE`; no operator action is required.
|
||||
|
||||
### Webhook alerts/reports block private/internal hosts by default
|
||||
|
||||
@@ -710,6 +737,7 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
|
||||
|
||||
---
|
||||
|
||||
- [38358](https://github.com/apache/superset/pull/38358): Switched CrateDB PyPI package from `crate[sqlalchemy]` to `sqlalchemy-cratedb`.
|
||||
- [35621](https://github.com/apache/superset/pull/35621): The default hash algorithm has changed from MD5 to SHA-256 for improved security and FedRAMP compliance. This affects cache keys for thumbnails, dashboard digests, chart digests, and filter option names. Existing cached data will be invalidated upon upgrade. To opt out of this change and maintain backward compatibility, set `HASH_ALGORITHM = "md5"` in your `superset_config.py`.
|
||||
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ Superset sends an HTTP POST with `Content-Type: application/json`:
|
||||
}
|
||||
```
|
||||
|
||||
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`:
|
||||
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`:
|
||||
|
||||
```
|
||||
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 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, XLSX, 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.
|
||||
|
||||
|
||||
@@ -529,6 +529,7 @@ MCP_RESPONSE_SIZE_CONFIG = {
|
||||
"enabled": True,
|
||||
"token_limit": 25000,
|
||||
"warn_threshold_pct": 80,
|
||||
"max_list_items": 100,
|
||||
"excluded_tools": [
|
||||
"health_check",
|
||||
"get_chart_preview",
|
||||
@@ -543,6 +544,7 @@ 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
|
||||
|
||||
@@ -181,6 +181,23 @@ value in milliseconds in the JSON Metadata field:
|
||||
Here, the entire dashboard will refresh at once if periodic refresh is on. The stagger time of 2.5
|
||||
seconds is ignored.
|
||||
|
||||
The manual **Refresh dashboard** button can also stagger its chart requests, controlled by the
|
||||
`SUPERSET_DASHBOARD_MANUAL_REFRESH_STAGGER_MS` server config in `superset_config.py`. This defaults
|
||||
to `0`, which preserves the original behavior where every chart request fires at the same time when
|
||||
the button is clicked. To opt in to staggering, set a positive number of milliseconds; the window
|
||||
then becomes the larger of this value and the per-dashboard `stagger_time` metadata, which itself
|
||||
defaults to `5000` milliseconds when it is not set explicitly. Setting `stagger_refresh` to `false`
|
||||
in a dashboard's JSON Metadata also disables staggering on the manual-refresh path, so its charts
|
||||
fire all at once even when the server config is positive. If the backend does not provide a value at
|
||||
all, for example an older backend that predates this config or a value explicitly set to `None`, the
|
||||
frontend falls back to a built-in default of `5000` milliseconds.
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
# 0 (the default) fires every chart at once; set a positive value to stagger.
|
||||
SUPERSET_DASHBOARD_MANUAL_REFRESH_STAGGER_MS = 0
|
||||
```
|
||||
|
||||
**Why does ‘flask fab’ or superset freeze/hang/not responding when started (my home directory is
|
||||
NFS mounted)?**
|
||||
|
||||
|
||||
@@ -63,9 +63,9 @@
|
||||
"@swc/core": "^1.15.43",
|
||||
"antd": "^6.5.0",
|
||||
"baseline-browser-mapping": "^2.10.40",
|
||||
"caniuse-lite": "^1.0.30001799",
|
||||
"docusaurus-plugin-openapi-docs": "^5.1.0",
|
||||
"docusaurus-theme-openapi-docs": "^5.1.0",
|
||||
"caniuse-lite": "^1.0.30001803",
|
||||
"docusaurus-plugin-openapi-docs": "^5.1.1",
|
||||
"docusaurus-theme-openapi-docs": "^5.1.1",
|
||||
"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.61.0",
|
||||
"@typescript-eslint/parser": "^8.63.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.1",
|
||||
"prettier": "^3.9.5",
|
||||
"typescript": "~6.0.3",
|
||||
"typescript-eslint": "^8.62.0",
|
||||
"typescript-eslint": "^8.63.0",
|
||||
"webpack": "^5.108.2"
|
||||
},
|
||||
"browserslist": {
|
||||
|
||||
@@ -3672,7 +3672,7 @@
|
||||
"drivers": [
|
||||
{
|
||||
"name": "crate",
|
||||
"pypi_package": "crate[sqlalchemy]",
|
||||
"pypi_package": "sqlalchemy-cratedb",
|
||||
"connection_string": "crate://{host}:{port}",
|
||||
"is_recommended": true
|
||||
}
|
||||
|
||||
@@ -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 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, XLSX, 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.
|
||||
|
||||
|
||||
184
docs/yarn.lock
184
docs/yarn.lock
@@ -5504,100 +5504,110 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@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==
|
||||
"@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==
|
||||
dependencies:
|
||||
"@eslint-community/regexpp" "^4.12.2"
|
||||
"@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"
|
||||
"@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"
|
||||
ignore "^7.0.5"
|
||||
natural-compare "^1.4.0"
|
||||
ts-api-utils "^2.5.0"
|
||||
|
||||
"@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==
|
||||
"@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==
|
||||
dependencies:
|
||||
"@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"
|
||||
"@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"
|
||||
debug "^4.4.3"
|
||||
|
||||
"@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==
|
||||
"@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==
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils" "^8.62.1"
|
||||
"@typescript-eslint/types" "^8.62.1"
|
||||
"@typescript-eslint/tsconfig-utils" "^8.63.0"
|
||||
"@typescript-eslint/types" "^8.63.0"
|
||||
debug "^4.4.3"
|
||||
|
||||
"@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==
|
||||
"@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==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.62.1"
|
||||
"@typescript-eslint/visitor-keys" "8.62.1"
|
||||
"@typescript-eslint/types" "8.63.0"
|
||||
"@typescript-eslint/visitor-keys" "8.63.0"
|
||||
|
||||
"@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.63.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz#f7e1cf9a029bb71f4027ffa4194ba82afb564cd3"
|
||||
integrity sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==
|
||||
|
||||
"@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==
|
||||
"@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==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.62.1"
|
||||
"@typescript-eslint/typescript-estree" "8.62.1"
|
||||
"@typescript-eslint/utils" "8.62.1"
|
||||
"@typescript-eslint/types" "8.63.0"
|
||||
"@typescript-eslint/typescript-estree" "8.63.0"
|
||||
"@typescript-eslint/utils" "8.63.0"
|
||||
debug "^4.4.3"
|
||||
ts-api-utils "^2.5.0"
|
||||
|
||||
"@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.63.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.63.0.tgz#6b32b0a5913520554d81a986acfffba2d32b6963"
|
||||
integrity sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==
|
||||
|
||||
"@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==
|
||||
"@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==
|
||||
dependencies:
|
||||
"@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"
|
||||
"@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"
|
||||
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.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==
|
||||
"@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==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.9.1"
|
||||
"@typescript-eslint/scope-manager" "8.62.1"
|
||||
"@typescript-eslint/types" "8.62.1"
|
||||
"@typescript-eslint/typescript-estree" "8.62.1"
|
||||
"@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.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==
|
||||
"@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==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.62.1"
|
||||
"@typescript-eslint/types" "8.63.0"
|
||||
eslint-visitor-keys "^5.0.0"
|
||||
|
||||
"@ungap/structured-clone@^1.0.0":
|
||||
@@ -6567,10 +6577,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:
|
||||
version "1.0.30001800"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz#b896c773e1c39400809415162bb5320371291b36"
|
||||
integrity sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==
|
||||
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==
|
||||
|
||||
ccount@^2.0.0:
|
||||
version "2.0.1"
|
||||
@@ -7819,10 +7829,10 @@ doctrine@^2.1.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
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==
|
||||
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==
|
||||
dependencies:
|
||||
"@apidevtools/json-schema-ref-parser" "^15.3.3"
|
||||
"@redocly/openapi-core" "^2.25.2"
|
||||
@@ -7840,10 +7850,10 @@ docusaurus-plugin-openapi-docs@^5.1.0:
|
||||
swagger2openapi "^7.0.8"
|
||||
xml-formatter "^3.6.6"
|
||||
|
||||
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==
|
||||
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==
|
||||
dependencies:
|
||||
"@hookform/error-message" "^2.0.1"
|
||||
"@reduxjs/toolkit" "^2.8.2"
|
||||
@@ -12998,10 +13008,10 @@ prettier-linter-helpers@^1.0.1:
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
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==
|
||||
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==
|
||||
|
||||
pretty-error@^4.0.0:
|
||||
version "4.0.0"
|
||||
@@ -15262,15 +15272,15 @@ types-ramda@^0.30.1:
|
||||
dependencies:
|
||||
ts-toolbelt "^9.6.0"
|
||||
|
||||
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==
|
||||
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==
|
||||
dependencies:
|
||||
"@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-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@~6.0.3:
|
||||
version "6.0.3"
|
||||
|
||||
@@ -29,7 +29,7 @@ maintainers:
|
||||
- name: craig-rueda
|
||||
email: craig@craigrueda.com
|
||||
url: https://github.com/craig-rueda
|
||||
version: 0.19.0 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
|
||||
version: 0.21.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
|
||||
|
||||
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
|
||||
|
||||
# superset
|
||||
|
||||

|
||||

|
||||
|
||||
Apache Superset is a modern, enterprise-ready business intelligence web application
|
||||
|
||||
@@ -74,10 +74,42 @@ 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"` | |
|
||||
@@ -89,7 +121,9 @@ 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"` | |
|
||||
@@ -115,8 +149,9 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
|
||||
| init.createAdmin | bool | `true` | |
|
||||
| init.enabled | bool | `true` | |
|
||||
| init.extraContainers | list | `[]` | Launch additional containers into init job pod |
|
||||
| init.extraInitContainers | list | `[]` | Extra init containers appended after init job initContainers |
|
||||
| init.initContainers | list | a container waiting for postgres | List of initContainers |
|
||||
| init.initscript | string | a script to create admin user and initialize roles | A Superset init script |
|
||||
| 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.jobAnnotations."helm.sh/hook" | string | `"post-install,post-upgrade"` | |
|
||||
| init.jobAnnotations."helm.sh/hook-delete-policy" | string | `"before-hook-creation"` | |
|
||||
| init.loadExamples | bool | `false` | |
|
||||
@@ -143,6 +178,7 @@ 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 |
|
||||
@@ -151,6 +187,7 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
|
||||
| supersetCeleryBeat.deploymentAnnotations | object | `{}` | Annotations to be added to supersetCeleryBeat deployment |
|
||||
| supersetCeleryBeat.enabled | bool | `false` | This is only required if you intend to use alerts and reports |
|
||||
| supersetCeleryBeat.extraContainers | list | `[]` | Launch additional containers into supersetCeleryBeat pods |
|
||||
| supersetCeleryBeat.extraInitContainers | list | `[]` | Extra init containers appended after supersetCeleryBeat initContainers |
|
||||
| supersetCeleryBeat.forceReload | bool | `false` | If true, forces deployment to reload on each upgrade |
|
||||
| supersetCeleryBeat.initContainers | list | a container waiting for postgres | List of init containers |
|
||||
| supersetCeleryBeat.podAnnotations | object | `{}` | Annotations to be added to supersetCeleryBeat pods |
|
||||
@@ -170,6 +207,7 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
|
||||
| supersetCeleryFlower.deploymentAnnotations | object | `{}` | Annotations to be added to supersetCeleryFlower deployment |
|
||||
| supersetCeleryFlower.enabled | bool | `false` | Enables a Celery flower deployment (management UI to monitor celery jobs) WARNING: on superset 1.x, this requires a Superset image that has `flower<1.0.0` installed (which is NOT the case of the default images) flower>=1.0.0 requires Celery 5+ which Superset 1.5 does not support |
|
||||
| supersetCeleryFlower.extraContainers | list | `[]` | Launch additional containers into supersetCeleryFlower pods |
|
||||
| supersetCeleryFlower.extraInitContainers | list | `[]` | Extra init containers appended after supersetCeleryFlower initContainers |
|
||||
| supersetCeleryFlower.initContainers | list | a container waiting for postgres and redis | List of init containers |
|
||||
| supersetCeleryFlower.livenessProbe.failureThreshold | int | `3` | |
|
||||
| supersetCeleryFlower.livenessProbe.httpGet.path | string | `"/api/workers"` | |
|
||||
@@ -214,26 +252,14 @@ 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.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.connections | object | `{}` | |
|
||||
| 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 |
|
||||
| supersetNode.deploymentLabels | object | `{}` | Labels to be added to supersetNode deployment |
|
||||
| supersetNode.env | object | `{}` | |
|
||||
| supersetNode.extraContainers | list | `[]` | Launch additional containers into supersetNode pod |
|
||||
| supersetNode.extraInitContainers | list | `[]` | Extra init containers appended after supersetNode initContainers |
|
||||
| supersetNode.forceReload | bool | `false` | If true, forces deployment to reload on each upgrade |
|
||||
| supersetNode.initContainers | list | a container waiting for postgres | Init containers |
|
||||
| supersetNode.lifecycle | object | `{}` | Container lifecycle hooks, e.g. a preStop sleep so the Service/Ingress stops routing to the pod before gunicorn receives SIGTERM |
|
||||
@@ -333,7 +359,13 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
|
||||
| supersetWorker.deploymentAnnotations | object | `{}` | Annotations to be added to supersetWorker deployment |
|
||||
| supersetWorker.deploymentLabels | object | `{}` | Labels to be added to supersetWorker deployment |
|
||||
| supersetWorker.extraContainers | list | `[]` | Launch additional containers into supersetWorker pod |
|
||||
| supersetWorker.extraInitContainers | list | `[]` | Extra init containers appended after supersetWorker initContainers |
|
||||
| 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 |
|
||||
|
||||
161
helm/superset/UPGRADING.md
Normal file
161
helm/superset/UPGRADING.md
Normal file
@@ -0,0 +1,161 @@
|
||||
<!--
|
||||
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.
|
||||
@@ -35,3 +35,9 @@
|
||||
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 }}
|
||||
|
||||
@@ -47,11 +47,12 @@ 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" -}}
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
{{- default (include "superset.fullname" .) .Values.serviceAccountName -}}
|
||||
{{- else -}}
|
||||
{{- default "default" .Values.serviceAccountName -}}
|
||||
{{- end -}}
|
||||
{{- $name := coalesce .Values.serviceAccount.name .Values.serviceAccountName -}}
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
{{- default (include "superset.fullname" .) $name -}}
|
||||
{{- else -}}
|
||||
{{- default "default" $name -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
@@ -105,80 +106,572 @@ app.kubernetes.io/component: {{ .component }}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
{{- define "superset-config" }}
|
||||
{{/*
|
||||
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 }}
|
||||
|
||||
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)
|
||||
|
||||
# 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')}"
|
||||
{{- /* Database Configuration - Superset always requires a database */}}
|
||||
{{- if .Values.database.uri }}
|
||||
SQLALCHEMY_DATABASE_URI = {{ .Values.database.uri | quote }}
|
||||
{{- else }}
|
||||
REDIS_BASE_URL=f"{env('REDIS_DRIVER') or env('REDIS_PROTO')}://{env('REDIS_HOST')}:{env('REDIS_PORT')}"
|
||||
{{- $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
|
||||
{{- end }}
|
||||
|
||||
# Redis URL Params
|
||||
{{- if .Values.supersetNode.connections.redis_ssl.enabled }}
|
||||
REDIS_URL_PARAMS = f"?ssl_cert_reqs={env('REDIS_SSL_CERT_REQS')}"
|
||||
{{- /* 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 }}"
|
||||
{{- else }}
|
||||
REDIS_URL_PARAMS = ""
|
||||
{{- end}}
|
||||
{{- 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 }}
|
||||
|
||||
# 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 Configuration */}}
|
||||
{{- if .Values.config.cacheConfig }}
|
||||
CACHE_CONFIG = json.loads({{ .Values.config.cacheConfig | toJson | quote }})
|
||||
{{- else if .Values.cache.enabled }}
|
||||
CACHE_CONFIG = {
|
||||
'CACHE_TYPE': 'RedisCache',
|
||||
'CACHE_DEFAULT_TIMEOUT': 300,
|
||||
'CACHE_KEY_PREFIX': 'superset_',
|
||||
'CACHE_REDIS_URL': CACHE_REDIS_URL,
|
||||
'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,
|
||||
}
|
||||
{{- 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 }}
|
||||
|
||||
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 }}
|
||||
{{- /* 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
|
||||
|
||||
class CeleryConfig:
|
||||
imports = ("superset.sql_lab", )
|
||||
broker_url = CELERY_REDIS_URL
|
||||
result_backend = CELERY_REDIS_URL
|
||||
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),
|
||||
},
|
||||
}
|
||||
|
||||
CELERY_IMPORTS = CeleryConfig.imports
|
||||
CELERY_CONFIG = CeleryConfig
|
||||
RESULTS_BACKEND = RedisCache(
|
||||
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 }}
|
||||
|
||||
{{ if .Values.configOverrides }}
|
||||
# Overrides
|
||||
{{- /* 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 }}
|
||||
)
|
||||
{{- 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
|
||||
{{- 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 }}
|
||||
@@ -187,7 +680,57 @@ RESULTS_BACKEND = RedisCache(
|
||||
{{- 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" . }}
|
||||
|
||||
@@ -37,9 +37,12 @@ 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/connections: {{ .Values.supersetNode.connections | toYaml | 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/extraConfigs: {{ .Values.extraConfigs | toYaml | sha256sum }}
|
||||
checksum/extraSecrets: {{ .Values.extraSecrets | toYaml | sha256sum }}
|
||||
checksum/extraSecretEnv: {{ .Values.extraSecretEnv | toYaml | sha256sum }}
|
||||
@@ -52,6 +55,9 @@ 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 }}
|
||||
@@ -69,8 +75,14 @@ spec:
|
||||
{{- if .Values.supersetCeleryBeat.podSecurityContext }}
|
||||
{{- toYaml .Values.supersetCeleryBeat.podSecurityContext | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if or .Values.supersetCeleryBeat.initContainers .Values.supersetCeleryBeat.extraInitContainers }}
|
||||
initContainers:
|
||||
{{- if .Values.supersetCeleryBeat.initContainers }}
|
||||
initContainers: {{- tpl (toYaml .Values.supersetCeleryBeat.initContainers) . | nindent 6 }}
|
||||
{{- tpl (toYaml .Values.supersetCeleryBeat.initContainers) . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- if .Values.supersetCeleryBeat.extraInitContainers }}
|
||||
{{- tpl (toYaml .Values.supersetCeleryBeat.extraInitContainers) . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.hostAliases }}
|
||||
hostAliases: {{- toYaml . | nindent 6 }}
|
||||
|
||||
@@ -36,11 +36,14 @@ 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 }}
|
||||
@@ -58,8 +61,14 @@ spec:
|
||||
{{- if .Values.supersetCeleryFlower.podSecurityContext }}
|
||||
{{- toYaml .Values.supersetCeleryFlower.podSecurityContext | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if or .Values.supersetCeleryFlower.initContainers .Values.supersetCeleryFlower.extraInitContainers }}
|
||||
initContainers:
|
||||
{{- if .Values.supersetCeleryFlower.initContainers }}
|
||||
initContainers: {{- tpl (toYaml .Values.supersetCeleryFlower.initContainers) . | nindent 6 }}
|
||||
{{- tpl (toYaml .Values.supersetCeleryFlower.initContainers) . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- if .Values.supersetCeleryFlower.extraInitContainers }}
|
||||
{{- tpl (toYaml .Values.supersetCeleryFlower.extraInitContainers) . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.hostAliases }}
|
||||
hostAliases: {{- toYaml . | nindent 6 }}
|
||||
|
||||
@@ -43,9 +43,12 @@ 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/connections: {{ .Values.supersetNode.connections | toYaml | 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/extraConfigs: {{ .Values.extraConfigs | toYaml | sha256sum }}
|
||||
checksum/extraSecrets: {{ .Values.extraSecrets | toYaml | sha256sum }}
|
||||
checksum/extraSecretEnv: {{ .Values.extraSecretEnv | toYaml | sha256sum }}
|
||||
@@ -58,6 +61,9 @@ 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 }}
|
||||
@@ -75,8 +81,14 @@ spec:
|
||||
{{- if .Values.supersetWorker.podSecurityContext }}
|
||||
{{- toYaml .Values.supersetWorker.podSecurityContext | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if or .Values.supersetWorker.initContainers .Values.supersetWorker.extraInitContainers }}
|
||||
initContainers:
|
||||
{{- if .Values.supersetWorker.initContainers }}
|
||||
initContainers: {{- tpl (toYaml .Values.supersetWorker.initContainers) . | nindent 6 }}
|
||||
{{- tpl (toYaml .Values.supersetWorker.initContainers) . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- if .Values.supersetWorker.extraInitContainers }}
|
||||
{{- tpl (toYaml .Values.supersetWorker.extraInitContainers) . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.hostAliases }}
|
||||
hostAliases: {{- toYaml . | nindent 6 }}
|
||||
|
||||
@@ -44,6 +44,9 @@ 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 }}
|
||||
|
||||
@@ -44,10 +44,13 @@ spec:
|
||||
metadata:
|
||||
annotations:
|
||||
# Force reload on config changes
|
||||
checksum/superset_config.py: {{ include "superset-config" . | sha256sum }}
|
||||
checksum/superset_init.sh: {{ tpl .Values.init.initscript . | sha256sum }}
|
||||
checksum/superset_config.py: {{ include "superset.config" . | sha256sum }}
|
||||
checksum/superset_init.sh: {{ include "superset.initScript" . | sha256sum }}
|
||||
checksum/superset_bootstrap.sh: {{ tpl .Values.bootstrapScript . | sha256sum }}
|
||||
checksum/connections: {{ .Values.supersetNode.connections | toYaml | 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/extraConfigs: {{ .Values.extraConfigs | toYaml | sha256sum }}
|
||||
checksum/extraSecrets: {{ .Values.extraSecrets | toYaml | sha256sum }}
|
||||
checksum/extraSecretEnv: {{ .Values.extraSecretEnv | toYaml | sha256sum }}
|
||||
@@ -60,6 +63,9 @@ 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 }}
|
||||
@@ -77,8 +83,14 @@ spec:
|
||||
{{- if .Values.supersetNode.podSecurityContext }}
|
||||
{{- toYaml .Values.supersetNode.podSecurityContext | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if or .Values.supersetNode.initContainers .Values.supersetNode.extraInitContainers }}
|
||||
initContainers:
|
||||
{{- if .Values.supersetNode.initContainers }}
|
||||
initContainers: {{- tpl (toYaml .Values.supersetNode.initContainers) . | nindent 6 }}
|
||||
{{- tpl (toYaml .Values.supersetNode.initContainers) . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- if .Values.supersetNode.extraInitContainers }}
|
||||
{{- tpl (toYaml .Values.supersetNode.extraInitContainers) . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.hostAliases }}
|
||||
hostAliases: {{- toYaml . | nindent 6 }}
|
||||
|
||||
@@ -56,8 +56,14 @@ spec:
|
||||
{{- if .Values.init.podSecurityContext }}
|
||||
{{- toYaml .Values.init.podSecurityContext | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if or .Values.init.initContainers .Values.init.extraInitContainers }}
|
||||
initContainers:
|
||||
{{- if .Values.init.initContainers }}
|
||||
initContainers: {{- tpl (toYaml .Values.init.initContainers) . | nindent 6 }}
|
||||
{{- tpl (toYaml .Values.init.initContainers) . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- if .Values.init.extraInitContainers }}
|
||||
{{- tpl (toYaml .Values.init.extraInitContainers) . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.hostAliases }}
|
||||
hostAliases: {{- toYaml . | nindent 6 }}
|
||||
|
||||
@@ -26,24 +26,28 @@ metadata:
|
||||
{{- include "superset.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
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 }}
|
||||
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 }}
|
||||
{{- 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 }}
|
||||
|
||||
@@ -27,9 +27,9 @@ metadata:
|
||||
type: Opaque
|
||||
stringData:
|
||||
superset_config.py: |
|
||||
{{- include "superset-config" . | nindent 4 }}
|
||||
{{- include "superset.config" . | nindent 4 }}
|
||||
superset_init.sh: |
|
||||
{{- tpl .Values.init.initscript . | nindent 4 }}
|
||||
{{- include "superset.initScript" . | nindent 4 }}
|
||||
superset_bootstrap.sh: |
|
||||
{{- tpl .Values.bootstrapScript . | nindent 4 }}
|
||||
|
||||
|
||||
132
helm/superset/tests/checksum_test.yaml
Normal file
132
helm/superset/tests/checksum_test.yaml
Normal file
@@ -0,0 +1,132 @@
|
||||
#
|
||||
# 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
|
||||
94
helm/superset/tests/config_test.yaml
Normal file
94
helm/superset/tests/config_test.yaml
Normal file
@@ -0,0 +1,94 @@
|
||||
#
|
||||
# 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\('}
|
||||
28
helm/superset/tests/deprecation_test.yaml
Normal file
28
helm/superset/tests/deprecation_test.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
#
|
||||
# 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}
|
||||
168
helm/superset/tests/init_containers_test.yaml
Normal file
168
helm/superset/tests/init_containers_test.yaml
Normal file
@@ -0,0 +1,168 @@
|
||||
#
|
||||
# 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: init containers + extraInitContainers
|
||||
templates:
|
||||
- deployment.yaml
|
||||
- deployment-worker.yaml
|
||||
- deployment-beat.yaml
|
||||
- deployment-flower.yaml
|
||||
- init-job.yaml
|
||||
tests:
|
||||
- it: renders only the default wait-for container by default
|
||||
template: deployment.yaml
|
||||
asserts:
|
||||
- lengthEqual:
|
||||
path: spec.template.spec.initContainers
|
||||
count: 1
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[0].name
|
||||
value: wait-for-postgres
|
||||
|
||||
- it: appends extraInitContainers after the default initContainers
|
||||
template: deployment.yaml
|
||||
set:
|
||||
supersetNode.extraInitContainers:
|
||||
- name: prepare-assets
|
||||
image: busybox
|
||||
command: ["sh", "-c", "echo preparing"]
|
||||
asserts:
|
||||
- lengthEqual:
|
||||
path: spec.template.spec.initContainers
|
||||
count: 2
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[0].name
|
||||
value: wait-for-postgres
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[1].name
|
||||
value: prepare-assets
|
||||
|
||||
- it: templates values inside extraInitContainers
|
||||
template: deployment.yaml
|
||||
set:
|
||||
image.repository: example/superset
|
||||
supersetNode.extraInitContainers:
|
||||
- name: prepare-assets
|
||||
image: "{{ .Values.image.repository }}:test"
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[1].image
|
||||
value: example/superset:test
|
||||
|
||||
- it: keeps replace semantics for initContainers while appending extras
|
||||
template: deployment.yaml
|
||||
set:
|
||||
supersetNode.initContainers:
|
||||
- name: custom-wait
|
||||
image: busybox
|
||||
supersetNode.extraInitContainers:
|
||||
- name: prepare-assets
|
||||
image: busybox
|
||||
asserts:
|
||||
- lengthEqual:
|
||||
path: spec.template.spec.initContainers
|
||||
count: 2
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[0].name
|
||||
value: custom-wait
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[1].name
|
||||
value: prepare-assets
|
||||
|
||||
- it: renders only extras when initContainers is cleared
|
||||
template: deployment.yaml
|
||||
set:
|
||||
supersetNode.initContainers: []
|
||||
supersetNode.extraInitContainers:
|
||||
- name: prepare-assets
|
||||
image: busybox
|
||||
asserts:
|
||||
- lengthEqual:
|
||||
path: spec.template.spec.initContainers
|
||||
count: 1
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[0].name
|
||||
value: prepare-assets
|
||||
|
||||
- it: appends worker extraInitContainers after wait-for-postgres-redis
|
||||
template: deployment-worker.yaml
|
||||
set:
|
||||
supersetWorker.extraInitContainers:
|
||||
- name: prepare-assets
|
||||
image: busybox
|
||||
asserts:
|
||||
- lengthEqual:
|
||||
path: spec.template.spec.initContainers
|
||||
count: 2
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[0].name
|
||||
value: wait-for-postgres-redis
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[1].name
|
||||
value: prepare-assets
|
||||
|
||||
- it: appends beat extraInitContainers after wait-for-postgres-redis
|
||||
template: deployment-beat.yaml
|
||||
set:
|
||||
supersetCeleryBeat.enabled: true
|
||||
supersetCeleryBeat.extraInitContainers:
|
||||
- name: prepare-assets
|
||||
image: busybox
|
||||
asserts:
|
||||
- lengthEqual:
|
||||
path: spec.template.spec.initContainers
|
||||
count: 2
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[0].name
|
||||
value: wait-for-postgres-redis
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[1].name
|
||||
value: prepare-assets
|
||||
|
||||
- it: appends flower extraInitContainers after wait-for-postgres-redis
|
||||
template: deployment-flower.yaml
|
||||
set:
|
||||
supersetCeleryFlower.enabled: true
|
||||
supersetCeleryFlower.extraInitContainers:
|
||||
- name: prepare-assets
|
||||
image: busybox
|
||||
asserts:
|
||||
- lengthEqual:
|
||||
path: spec.template.spec.initContainers
|
||||
count: 2
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[0].name
|
||||
value: wait-for-postgres-redis
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[1].name
|
||||
value: prepare-assets
|
||||
|
||||
- it: appends init job extraInitContainers after wait-for-postgres
|
||||
template: init-job.yaml
|
||||
set:
|
||||
init.extraInitContainers:
|
||||
- name: prepare-assets
|
||||
image: busybox
|
||||
asserts:
|
||||
- lengthEqual:
|
||||
path: spec.template.spec.initContainers
|
||||
count: 2
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[0].name
|
||||
value: wait-for-postgres
|
||||
- equal:
|
||||
path: spec.template.spec.initContainers[1].name
|
||||
value: prepare-assets
|
||||
63
helm/superset/tests/initscript_test.yaml
Normal file
63
helm/superset/tests/initscript_test.yaml
Normal file
@@ -0,0 +1,63 @@
|
||||
#
|
||||
# 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'
|
||||
71
helm/superset/tests/secret_env_test.yaml
Normal file
71
helm/superset/tests/secret_env_test.yaml
Normal file
@@ -0,0 +1,71 @@
|
||||
#
|
||||
# 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'}
|
||||
28
helm/superset/tests/serviceaccount_test.yaml
Normal file
28
helm/superset/tests/serviceaccount_test.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
#
|
||||
# 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}
|
||||
30
helm/superset/tests/values_defaults_test.yaml
Normal file
30
helm/superset/tests/values_defaults_test.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
#
|
||||
# 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
|
||||
140
helm/superset/values.schema.json
Normal file
140
helm/superset/values.schema.json
Normal file
@@ -0,0 +1,140 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,30 @@ 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
|
||||
@@ -46,6 +67,8 @@ 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
|
||||
@@ -244,6 +267,89 @@ 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:
|
||||
@@ -270,28 +376,8 @@ supersetNode:
|
||||
- "/bin/sh"
|
||||
- "-c"
|
||||
- ". {{ .Values.configMountPath }}/superset_bootstrap.sh; exec /usr/bin/run-server.sh"
|
||||
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
|
||||
# DEPRECATED: use database.* and cache.* instead (still honored if set). See UPGRADING.md
|
||||
connections: {}
|
||||
env: {}
|
||||
# -- If true, forces deployment to reload on each upgrade
|
||||
forceReload: false
|
||||
@@ -329,6 +415,8 @@ supersetNode:
|
||||
cpu: "250m"
|
||||
memory: "128Mi"
|
||||
|
||||
# -- Extra init containers appended after supersetNode initContainers
|
||||
extraInitContainers: []
|
||||
# -- Launch additional containers into supersetNode pod
|
||||
extraContainers: []
|
||||
# -- Annotations to be added to supersetNode deployment
|
||||
@@ -456,6 +544,8 @@ supersetWorker:
|
||||
requests:
|
||||
cpu: "250m"
|
||||
memory: "128Mi"
|
||||
# -- Extra init containers appended after supersetWorker initContainers
|
||||
extraInitContainers: []
|
||||
# -- Launch additional containers into supersetWorker pod
|
||||
extraContainers: []
|
||||
# -- Annotations to be added to supersetWorker deployment
|
||||
@@ -508,6 +598,17 @@ 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: ~
|
||||
|
||||
@@ -566,6 +667,8 @@ supersetCeleryBeat:
|
||||
requests:
|
||||
cpu: "250m"
|
||||
memory: "128Mi"
|
||||
# -- Extra init containers appended after supersetCeleryBeat initContainers
|
||||
extraInitContainers: []
|
||||
# -- Launch additional containers into supersetCeleryBeat pods
|
||||
extraContainers: []
|
||||
# -- Annotations to be added to supersetCeleryBeat deployment
|
||||
@@ -683,6 +786,8 @@ supersetCeleryFlower:
|
||||
requests:
|
||||
cpu: "250m"
|
||||
memory: "128Mi"
|
||||
# -- Extra init containers appended after supersetCeleryFlower initContainers
|
||||
extraInitContainers: []
|
||||
# -- Launch additional containers into supersetCeleryFlower pods
|
||||
extraContainers: []
|
||||
# -- Annotations to be added to supersetCeleryFlower deployment
|
||||
@@ -870,8 +975,13 @@ init:
|
||||
requests:
|
||||
cpu: "250m"
|
||||
memory: "128Mi"
|
||||
# -- A Superset init script
|
||||
# @default -- a script to create admin user and initialize roles
|
||||
# -- Extra init containers appended after init job initContainers
|
||||
extraInitContainers: []
|
||||
# -- 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
|
||||
initscript: |-
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
@@ -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>=6.2.1, <7",
|
||||
"cachetools>=7.1.4, <8",
|
||||
"celery>=5.6.3, <6.0.0",
|
||||
"click>=8.4.0",
|
||||
"click>=8.4.2",
|
||||
"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.45, <1",
|
||||
"holidays>=0.99, <1",
|
||||
"humanize",
|
||||
"isodate",
|
||||
"jsonpath-ng>=1.8.0, <2",
|
||||
@@ -100,16 +100,17 @@ dependencies = [
|
||||
"pyarrow>=24.0.0, <25", # before upgrading pyarrow, check that all db dependencies support this, see e.g. https://github.com/apache/superset/pull/34693
|
||||
"pyyaml>=6.0.3, <7.0.0",
|
||||
"PyJWT>=2.4.0, <3.0",
|
||||
"redis>=5.0.0, <6.0",
|
||||
"redis>=5.0.0, <9.0",
|
||||
"rison>=2.0.1, <3.0",
|
||||
"selenium>=4.45.0, <5.0",
|
||||
"shillelagh[gsheetsapi]>=1.4.4, <2.0",
|
||||
"sshtunnel>=0.4.0, <0.5",
|
||||
"simplejson>=4.1.1",
|
||||
"slack_sdk>=3.42.0, <4",
|
||||
"slack_sdk>=3.43.0, <4",
|
||||
"sqlalchemy>=1.4, <2",
|
||||
"sqlalchemy-continuum>=1.6.0, <2.0.0",
|
||||
"sqlalchemy-utils>=0.42.1, <0.43", # expanding lowerbound to work with pydoris
|
||||
"sqlglot>=30.8.0, <31",
|
||||
"sqlglot>=30.12.0, <31",
|
||||
# newer pandas needs 0.9+
|
||||
"tabulate>=0.10.0, <1.0",
|
||||
"typing-extensions>=4, <5",
|
||||
@@ -129,7 +130,7 @@ bigquery = [
|
||||
"sqlalchemy-bigquery>=1.17.0",
|
||||
"google-cloud-bigquery>=3.42.1",
|
||||
]
|
||||
clickhouse = ["clickhouse-connect>=1.1.1, <2.0"]
|
||||
clickhouse = ["clickhouse-connect>=1.4.2, <2.0"]
|
||||
cockroachdb = ["cockroachdb>=0.3.5, <0.4"]
|
||||
crate = ["sqlalchemy-cratedb>=0.41.0, <1"]
|
||||
d1 = [
|
||||
@@ -154,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.2,<4.0",
|
||||
"fastmcp>=3.4.3,<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.7.0, <2.2"]
|
||||
firebird = ["sqlalchemy-firebird>=0.8.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.28.21", "sqlalchemy_hana==0.4.0"]
|
||||
hana = ["hdbcli==2.29.25", "sqlalchemy_hana==4.6.0"]
|
||||
hive = [
|
||||
"pyhive[hive]>=0.6.5;python_version<'3.11'",
|
||||
"pyhive[hive_pure_sasl]>=0.7.0",
|
||||
@@ -172,7 +173,7 @@ hive = [
|
||||
"thrift>=0.23.0, <1.0.0",
|
||||
"thrift_sasl>=0.4.3, < 1.0.0",
|
||||
]
|
||||
impala = ["impyla>0.16.2, <0.23"]
|
||||
impala = ["impyla>=0.24.0, <0.25"]
|
||||
kusto = ["sqlalchemy-kusto>=3.1.2, <4"]
|
||||
kylin = ["kylinpy>=2.8.4, <2.9"]
|
||||
mssql = ["pymssql>=2.3.13, <3"]
|
||||
@@ -191,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.337.0"]
|
||||
trino = ["trino>=0.338.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.1.0,<0.5.0"]
|
||||
sqlite = ["syntaqlite>=0.7.0,<0.8.0"]
|
||||
spark = [
|
||||
"pyhive[hive]>=0.6.5;python_version<'3.11'",
|
||||
"pyhive[hive_pure_sasl]>=0.7",
|
||||
@@ -209,12 +210,12 @@ tdengine = [
|
||||
"taospy>=2.8.9",
|
||||
"taos-ws-py>=0.6.9"
|
||||
]
|
||||
teradata = ["teradatasql>=16.20.0.23"]
|
||||
teradata = ["teradatasql>=20.0.0.62"]
|
||||
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.0.0, <2.0.0"]
|
||||
doris = ["pydoris>=1.2.0, <2.0.0"]
|
||||
oceanbase = ["oceanbase_py>=0.0.1.2"]
|
||||
ydb = ["ydb-sqlalchemy>=0.1.22", "ydb-sqlglot-plugin>=0.2.8"]
|
||||
development = [
|
||||
@@ -243,7 +244,7 @@ development = [
|
||||
"ruff",
|
||||
"sqloxide",
|
||||
"statsd",
|
||||
"syntaqlite>=0.4.2,<0.5.0",
|
||||
"syntaqlite>=0.7.0,<0.8.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
||||
11
pytest.ini
11
pytest.ini
@@ -35,13 +35,12 @@ 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 ``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 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 current statement is being autocommitted using implicit autocommit: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 ``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
|
||||
|
||||
@@ -44,7 +44,7 @@ cachelib==0.13.0
|
||||
# via
|
||||
# flask-caching
|
||||
# flask-session
|
||||
cachetools==6.2.1
|
||||
cachetools==7.1.4
|
||||
# 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.1
|
||||
click==8.4.2
|
||||
# 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.82
|
||||
holidays==0.100
|
||||
# via apache-superset (pyproject.toml)
|
||||
humanize==4.12.3
|
||||
# via apache-superset (pyproject.toml)
|
||||
@@ -321,7 +321,6 @@ pyjwt==2.13.0
|
||||
# apache-superset (pyproject.toml)
|
||||
# flask-appbuilder
|
||||
# flask-jwt-extended
|
||||
# redis
|
||||
pynacl==1.6.2
|
||||
# via paramiko
|
||||
pyopenssl==26.3.0
|
||||
@@ -353,7 +352,7 @@ pyyaml==6.0.3
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# apispec
|
||||
redis==5.3.1
|
||||
redis==8.0.1
|
||||
# via apache-superset (pyproject.toml)
|
||||
referencing==0.36.2
|
||||
# via
|
||||
@@ -390,7 +389,7 @@ six==1.17.0
|
||||
# python-dateutil
|
||||
# rfc3339-validator
|
||||
# wtforms-json
|
||||
slack-sdk==3.42.0
|
||||
slack-sdk==3.43.0
|
||||
# via apache-superset (pyproject.toml)
|
||||
sniffio==1.3.1
|
||||
# via trio
|
||||
@@ -405,13 +404,16 @@ sqlalchemy==1.4.54
|
||||
# flask-sqlalchemy
|
||||
# marshmallow-sqlalchemy
|
||||
# shillelagh
|
||||
# sqlalchemy-continuum
|
||||
# sqlalchemy-utils
|
||||
sqlalchemy-continuum==1.6.0
|
||||
# via apache-superset (pyproject.toml)
|
||||
sqlalchemy-utils==0.42.1
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# apache-superset-core
|
||||
# flask-appbuilder
|
||||
sqlglot==30.8.0
|
||||
sqlglot==30.12.0
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# apache-superset-core
|
||||
|
||||
@@ -97,7 +97,7 @@ cachelib==0.13.0
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask-caching
|
||||
# flask-session
|
||||
cachetools==6.2.1
|
||||
cachetools==7.1.4
|
||||
# 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.1
|
||||
click==8.4.2
|
||||
# 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.2
|
||||
fastmcp==3.4.4
|
||||
# via apache-superset
|
||||
fastmcp-slim==3.4.2
|
||||
fastmcp-slim==3.4.4
|
||||
# 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.82
|
||||
holidays==0.100
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -784,7 +784,6 @@ pyjwt==2.13.0
|
||||
# flask-appbuilder
|
||||
# flask-jwt-extended
|
||||
# mcp
|
||||
# redis
|
||||
pylint==3.3.7
|
||||
# via apache-superset
|
||||
pynacl==1.6.2
|
||||
@@ -870,7 +869,7 @@ pyyaml==6.0.3
|
||||
# fastmcp-slim
|
||||
# jsonschema-path
|
||||
# pre-commit
|
||||
redis==5.3.1
|
||||
redis==8.0.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -959,7 +958,7 @@ six==1.17.0
|
||||
# python-dateutil
|
||||
# rfc3339-validator
|
||||
# wtforms-json
|
||||
slack-sdk==3.42.0
|
||||
slack-sdk==3.43.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -984,16 +983,21 @@ sqlalchemy==1.4.54
|
||||
# marshmallow-sqlalchemy
|
||||
# shillelagh
|
||||
# sqlalchemy-bigquery
|
||||
# sqlalchemy-continuum
|
||||
# sqlalchemy-utils
|
||||
sqlalchemy-bigquery==1.17.0
|
||||
# via apache-superset
|
||||
sqlalchemy-continuum==1.6.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
sqlalchemy-utils==0.42.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
# apache-superset-core
|
||||
# flask-appbuilder
|
||||
sqlglot==30.8.0
|
||||
sqlglot==30.12.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -1012,7 +1016,7 @@ starlette==1.3.1
|
||||
# mcp
|
||||
statsd==4.0.1
|
||||
# via apache-superset
|
||||
syntaqlite==0.4.2
|
||||
syntaqlite==0.7.1
|
||||
# via apache-superset
|
||||
tabulate==0.10.0
|
||||
# via
|
||||
@@ -1028,7 +1032,7 @@ tqdm==4.67.1
|
||||
# via
|
||||
# cmdstanpy
|
||||
# prophet
|
||||
trino==0.337.0
|
||||
trino==0.338.0
|
||||
# via apache-superset
|
||||
trio==0.33.0
|
||||
# via
|
||||
|
||||
16
scripts/__init__.py
Normal file
16
scripts/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
@@ -153,10 +153,11 @@ def main( # noqa: C901
|
||||
)
|
||||
|
||||
print(f"Migration goes from {down_revision} to {revision}")
|
||||
current_revision = db.engine.execute(
|
||||
text("SELECT version_num FROM alembic_version")
|
||||
).scalar()
|
||||
print(f"Current version of the DB is {current_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}")
|
||||
|
||||
if current_revision != down_revision:
|
||||
if not force:
|
||||
|
||||
@@ -20,9 +20,21 @@ 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": [
|
||||
@@ -57,15 +69,34 @@ GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
|
||||
|
||||
|
||||
def fetch_files_github_api(url: str): # type: ignore
|
||||
"""Fetches data using GitHub API."""
|
||||
"""Fetches data using GitHub API, retrying on transient failures."""
|
||||
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}")
|
||||
with urlopen(req) as response: # noqa: S310
|
||||
body = response.read()
|
||||
return json.loads(body)
|
||||
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)
|
||||
|
||||
|
||||
def fetch_changed_files_pr(repo: str, pr_number: str) -> List[str]:
|
||||
|
||||
49
scripts/stylelint.sh
Executable file
49
scripts/stylelint.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/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
|
||||
@@ -92,4 +92,33 @@ 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
|
||||
|
||||
@@ -138,6 +138,22 @@ 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}"
|
||||
@@ -667,13 +683,11 @@ def backfill(
|
||||
mark_fuzzy: bool = True,
|
||||
) -> None:
|
||||
"""Backfill missing translations in the target language's .po file."""
|
||||
# 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):
|
||||
if not _is_valid_lang_code(lang):
|
||||
print(
|
||||
f"Invalid language code: {lang!r} "
|
||||
"(expected ISO 639 code, optionally with _<REGION>, e.g. 'fr' or 'pt_BR')",
|
||||
"(expected ISO 639 code, optionally with a _<REGION> or _<Script> "
|
||||
"subtag, e.g. 'fr', 'pt_BR', or 'sr_Latn')",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -42,7 +42,10 @@ from superset_core.common.models import (
|
||||
Dashboard,
|
||||
Database,
|
||||
Dataset,
|
||||
Group,
|
||||
KeyValue,
|
||||
Role,
|
||||
Subject,
|
||||
Tag,
|
||||
User,
|
||||
)
|
||||
@@ -190,6 +193,34 @@ class UserDAO(BaseDAO[User]):
|
||||
id_column_name = "id"
|
||||
|
||||
|
||||
class RoleDAO(BaseDAO[Role]):
|
||||
"""
|
||||
Abstract Role DAO interface.
|
||||
|
||||
Host implementations will replace this class during initialization
|
||||
with a concrete implementation providing actual functionality.
|
||||
"""
|
||||
|
||||
# Class variables that will be set by host implementation
|
||||
model_cls = None
|
||||
base_filter = None
|
||||
id_column_name = "id"
|
||||
|
||||
|
||||
class GroupDAO(BaseDAO[Group]):
|
||||
"""
|
||||
Abstract Group DAO interface.
|
||||
|
||||
Host implementations will replace this class during initialization
|
||||
with a concrete implementation providing actual functionality.
|
||||
"""
|
||||
|
||||
# Class variables that will be set by host implementation
|
||||
model_cls = None
|
||||
base_filter = None
|
||||
id_column_name = "id"
|
||||
|
||||
|
||||
class TagDAO(BaseDAO[Tag]):
|
||||
"""
|
||||
Abstract Tag DAO interface.
|
||||
@@ -218,13 +249,16 @@ class KeyValueDAO(BaseDAO[KeyValue]):
|
||||
id_column_name = "id"
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BaseDAO",
|
||||
"DatasetDAO",
|
||||
"DatabaseDAO",
|
||||
"ChartDAO",
|
||||
"DashboardDAO",
|
||||
"UserDAO",
|
||||
"TagDAO",
|
||||
"KeyValueDAO",
|
||||
]
|
||||
class SubjectDAO(BaseDAO[Subject]):
|
||||
"""
|
||||
Abstract Subject DAO interface.
|
||||
|
||||
Host implementations will replace this class during initialization
|
||||
with a concrete implementation providing actual functionality.
|
||||
"""
|
||||
|
||||
# Class variables that will be set by host implementation
|
||||
model_cls = None
|
||||
base_filter = None
|
||||
id_column_name = "id"
|
||||
uuid_column_name = "uuid"
|
||||
|
||||
@@ -285,6 +285,38 @@ class User(CoreModel):
|
||||
active: bool
|
||||
|
||||
|
||||
class Role(CoreModel):
|
||||
"""
|
||||
Abstract Role model interface.
|
||||
|
||||
Host implementations will replace this class during initialization
|
||||
with concrete implementation providing actual functionality.
|
||||
"""
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
# Type hints for expected attributes (no actual field definitions)
|
||||
id: int
|
||||
name: str | None
|
||||
|
||||
|
||||
class Group(CoreModel):
|
||||
"""
|
||||
Abstract Group model interface.
|
||||
|
||||
Host implementations will replace this class during initialization
|
||||
with concrete implementation providing actual functionality.
|
||||
"""
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
# Type hints for expected attributes (no actual field definitions)
|
||||
id: int
|
||||
name: str | None
|
||||
label: str | None
|
||||
description: str | None
|
||||
|
||||
|
||||
class Tag(CoreModel):
|
||||
"""
|
||||
Abstract Tag model interface.
|
||||
@@ -320,6 +352,33 @@ class KeyValue(CoreModel):
|
||||
changed_by_fk: int | None
|
||||
|
||||
|
||||
class Subject(CoreModel):
|
||||
"""
|
||||
Abstract Subject model interface.
|
||||
|
||||
A Subject is a unified representation of a User, Role, or Group. Host
|
||||
implementations will replace this class during initialization with a
|
||||
concrete implementation providing actual functionality.
|
||||
"""
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
# Type hints for expected attributes (no actual field definitions)
|
||||
id: int
|
||||
uuid: UUID | None
|
||||
label: str | None
|
||||
secondary_label: str | None
|
||||
active: bool | None
|
||||
extra_search: str | None
|
||||
type: int
|
||||
|
||||
# Convenience references to the underlying principal. Exactly one is set,
|
||||
# matching ``type`` (User, Role, or Group respectively).
|
||||
user: User | None
|
||||
role: Role | None
|
||||
group: Group | None
|
||||
|
||||
|
||||
def get_session() -> scoped_session:
|
||||
"""
|
||||
Retrieve the SQLAlchemy session to directly interface with the
|
||||
@@ -339,8 +398,11 @@ __all__ = [
|
||||
"Chart",
|
||||
"Dashboard",
|
||||
"User",
|
||||
"Role",
|
||||
"Group",
|
||||
"Tag",
|
||||
"KeyValue",
|
||||
"Subject",
|
||||
"CoreModel",
|
||||
"get_session",
|
||||
]
|
||||
|
||||
@@ -158,7 +158,7 @@ class Filter:
|
||||
type: PredicateType
|
||||
column: Dimension | Metric | None
|
||||
operator: Operator
|
||||
value: FilterValues | frozenset[FilterValues]
|
||||
value: FilterValues | tuple[FilterValues, ...] | frozenset[FilterValues]
|
||||
|
||||
|
||||
class OrderDirection(enum.Enum):
|
||||
|
||||
@@ -96,6 +96,26 @@ 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.
|
||||
|
||||
@@ -25,16 +25,20 @@ 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) {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, Space, Content } from 'antd';
|
||||
import { App, Layout } from 'antd';
|
||||
|
||||
import 'src/theme.ts';
|
||||
import './storybook.css';
|
||||
|
||||
@@ -17,45 +17,38 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Component, ReactNode } from 'react';
|
||||
import { useState, useCallback, ReactNode } from 'react';
|
||||
|
||||
export type Props = {
|
||||
children: ReactNode;
|
||||
expandableWhat?: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
open: boolean;
|
||||
};
|
||||
export default function Expandable({ children, expandableWhat }: Props) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
export default class Expandable extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { open: false };
|
||||
this.handleToggle = this.handleToggle.bind(this);
|
||||
}
|
||||
const handleToggle = useCallback(() => {
|
||||
setOpen(prevOpen => !prevOpen);
|
||||
}, []);
|
||||
|
||||
handleToggle() {
|
||||
this.setState(({ open }) => ({ open: !open }));
|
||||
}
|
||||
const label = expandableWhat
|
||||
? `${open ? 'Hide' : 'Show'} ${expandableWhat}`
|
||||
: open
|
||||
? 'Hide'
|
||||
: 'Show';
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={handleToggle}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
<br />
|
||||
<br />
|
||||
{open ? children : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Component, ReactNode } from 'react';
|
||||
import { useState, useEffect, useCallback, useRef, ReactNode } from 'react';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import {
|
||||
SupersetClient,
|
||||
@@ -36,12 +36,6 @@ 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 />
|
||||
@@ -54,29 +48,37 @@ export const renderError = (error: Error) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
export default class VerifyCORS extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { didVerify: false };
|
||||
this.handleVerify = this.handleVerify.bind(this);
|
||||
}
|
||||
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);
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const { endpoint, host, postPayload, method } = this.props;
|
||||
const prevPropsRef = useRef({ endpoint, host, postPayload, method });
|
||||
|
||||
useEffect(() => {
|
||||
const prevProps = prevPropsRef.current;
|
||||
if (
|
||||
(this.state.didVerify || this.state.error) &&
|
||||
(didVerify || error) &&
|
||||
(prevProps.endpoint !== endpoint ||
|
||||
prevProps.host !== host ||
|
||||
prevProps.postPayload !== postPayload ||
|
||||
prevProps.method !== method)
|
||||
) {
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
this.setState({ didVerify: false, error: undefined });
|
||||
setDidVerify(false);
|
||||
setError(undefined);
|
||||
}
|
||||
}
|
||||
prevPropsRef.current = { endpoint, host, postPayload, method };
|
||||
}, [endpoint, host, postPayload, method, didVerify, error]);
|
||||
|
||||
handleVerify() {
|
||||
const { endpoint, host, postPayload, method } = this.props;
|
||||
const handleVerify = useCallback(() => {
|
||||
SupersetClient.reset();
|
||||
SupersetClient.configure({
|
||||
credentials: 'include',
|
||||
@@ -94,43 +96,40 @@ export default class VerifyCORS extends Component<Props, State> {
|
||||
}
|
||||
return { error: 'Must provide valid endpoint and payload.' };
|
||||
})
|
||||
.then(result =>
|
||||
this.setState({ didVerify: true, error: undefined, payload: result }),
|
||||
)
|
||||
.catch(error => this.setState({ error }));
|
||||
}
|
||||
.then(result => {
|
||||
setDidVerify(true);
|
||||
setError(undefined);
|
||||
setPayload(result);
|
||||
})
|
||||
.catch(err => setError(err));
|
||||
}, [endpoint, host, method, postPayload]);
|
||||
|
||||
render() {
|
||||
const { didVerify, error, payload } = this.state;
|
||||
const { children } = this.props;
|
||||
|
||||
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>
|
||||
|
||||
{error && (
|
||||
<div className="col-md-8">
|
||||
<ErrorMessage error={error} />
|
||||
</div>
|
||||
)}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
{error && (
|
||||
<div className="col-md-8">
|
||||
<ErrorMessage error={error} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
9
superset-frontend/.stylelintignore
Normal file
9
superset-frontend/.stylelintignore
Normal file
@@ -0,0 +1,9 @@
|
||||
# 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
|
||||
@@ -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-selector')
|
||||
cy.get('.ant-popover-content .ant-select-content')
|
||||
.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-inner-content')
|
||||
cy.get('.ant-popover-content')
|
||||
.find('[class^=ant-input]')
|
||||
.first()
|
||||
.type(`${startRange}`);
|
||||
}
|
||||
if (endRange) {
|
||||
cy.get('.ant-popover-inner-content')
|
||||
cy.get('.ant-popover-content')
|
||||
.find('[class^=ant-input]')
|
||||
.last()
|
||||
.type(`${endRange}`);
|
||||
@@ -514,15 +514,14 @@ export function inputNativeFilterDefaultValue(
|
||||
)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('.ant-select-selection-search-input').type(
|
||||
`${defaultValue}{enter}`,
|
||||
{ force: true },
|
||||
);
|
||||
cy.get('.ant-select-input').type(`${defaultValue}{enter}`, {
|
||||
force: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
cy.getBySel('default-input').within(() => {
|
||||
cy.get('.ant-select-selection-search-input').click();
|
||||
cy.get('.ant-select-input').click();
|
||||
cy.get('.ant-select-item-option-content').contains(defaultValue).click();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,9 +53,7 @@ export function saveChartToDashboard(chartName: string, dashboardName: string) {
|
||||
.then($modal => {
|
||||
cy.wait(500);
|
||||
cy.wrap($modal)
|
||||
.find(
|
||||
'.ant-select-selection-search-input[aria-label*="Select a dashboard"]',
|
||||
)
|
||||
.find('.ant-select-input[aria-label*="Select a dashboard"]')
|
||||
.type(dashboardName, { force: true });
|
||||
|
||||
cy.get(`.ant-select-item-option[title="${dashboardName}"]`).click();
|
||||
|
||||
@@ -52,7 +52,7 @@ export const securityAccess = {
|
||||
};
|
||||
export const homePage = {
|
||||
homeSection: {
|
||||
sectionArea: '.ant-collapse-content-box',
|
||||
sectionArea: '.ant-collapse-body',
|
||||
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: '[class="ant-select-selection-search-input"]',
|
||||
dbDropdown: '.ant-select-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: '[class="ant-select-selection-search-input"][aria-label="Sort"]',
|
||||
sort: '.ant-select-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-selection-search-input',
|
||||
filterInfoInput: '.ant-select-input',
|
||||
inputDropdown: '.ant-select-item-option-content',
|
||||
columnEmptyInput: '.ant-select-selection-placeholder',
|
||||
columnEmptyInput: '.ant-select-placeholder',
|
||||
filterTypeInput: dataTestLocator('filters-config-modal__filter-type'),
|
||||
fieldInput: dataTestLocator('field-input'),
|
||||
filterTypeItem: '.ant-select-selection-item',
|
||||
filterTypeItem: '.ant-select-content-has-value, .ant-select-selection-item',
|
||||
filterGear: dataTestLocator('filterbar-orientation-icon'),
|
||||
},
|
||||
filterFromDashboardView: {
|
||||
filterValueInput: '[class="ant-select-selection-search-input"]',
|
||||
filterValueInput: '.ant-select-input',
|
||||
expand: dataTestLocator('filter-bar__expand-button'),
|
||||
collapse: dataTestLocator('filter-bar__collapse-button'),
|
||||
filterName: dataTestLocator('filter-control-name'),
|
||||
filterContent: '.ant-select-selection-item',
|
||||
filterContent: '.ant-select-content-has-value, .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-selection-placeholder',
|
||||
collapsedSectionContainer: '[class="ant-collapse-content-box"]',
|
||||
filterPlaceholder: '.ant-select-placeholder',
|
||||
collapsedSectionContainer: '[class="ant-collapse-body"]',
|
||||
},
|
||||
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-selection-search',
|
||||
filterItemDropdown: '.ant-select-input',
|
||||
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-selection-search-input',
|
||||
dashboardNameInput: '.ant-select-input',
|
||||
addToDashboardInput: dataTestLocator(
|
||||
'save-chart-modal-select-dashboard-form',
|
||||
),
|
||||
@@ -495,7 +495,7 @@ export const exploreView = {
|
||||
},
|
||||
controlPanel: {
|
||||
panel: dataTestLocator('control-tabs'),
|
||||
categoryArea: '.ant-collapse-content-box',
|
||||
categoryArea: '.ant-collapse-body',
|
||||
dragField: dataTestLocator('datasource'),
|
||||
metricsField: dataTestLocator('metrics'),
|
||||
optionField: dataTestLocator('option-label'),
|
||||
@@ -594,7 +594,7 @@ export const exploreView = {
|
||||
},
|
||||
};
|
||||
export const createChartView = {
|
||||
chooseDatasetInput: '.ant-select-selection-search-input',
|
||||
chooseDatasetInput: '.ant-select-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-selection-item',
|
||||
rangeTypeField: '.ant-select-content-has-value, .ant-select-selection-item',
|
||||
startTimeInputNumber: '.ant-input-number-input',
|
||||
datePicker: '.ant-picker-input',
|
||||
applyButton: dataTestLocator('date-filter-control__apply-button'),
|
||||
|
||||
@@ -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/*.|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/*.|@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|@x0k/json-schema-merge)',
|
||||
],
|
||||
preset: 'ts-jest',
|
||||
transform: {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "17.0.2"
|
||||
"version": "18.3.1"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
@@ -52,10 +52,6 @@
|
||||
// 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
|
||||
|
||||
@@ -128,8 +124,7 @@
|
||||
"import/no-mutable-exports": "error",
|
||||
"import/no-amd": "error",
|
||||
"import/first": "error",
|
||||
// TODO: Consolidate duplicate imports in DatasetList and other files
|
||||
"import/no-duplicates": "warn",
|
||||
"import/no-duplicates": "error",
|
||||
"import/no-absolute-path": "error",
|
||||
"import/no-dynamic-require": "error",
|
||||
"import/no-webpack-loader-syntax": "error",
|
||||
@@ -196,6 +191,7 @@
|
||||
|
||||
// === 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",
|
||||
@@ -203,21 +199,28 @@
|
||||
"jsx-a11y/aria-proptypes": "error",
|
||||
"jsx-a11y/aria-role": ["error", { "ignoreNonDOM": false }],
|
||||
"jsx-a11y/aria-unsupported-elements": "error",
|
||||
"jsx-a11y/click-events-have-key-events": "off",
|
||||
"jsx-a11y/autocomplete-valid": "error",
|
||||
"jsx-a11y/click-events-have-key-events": "error",
|
||||
"jsx-a11y/control-has-associated-label": "error",
|
||||
"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": "off",
|
||||
"jsx-a11y/mouse-events-have-key-events": "error",
|
||||
"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": "off",
|
||||
"jsx-a11y/no-static-element-interactions": "error",
|
||||
"jsx-a11y/role-has-required-aria-props": "error",
|
||||
"jsx-a11y/role-supports-aria-props": "error",
|
||||
"jsx-a11y/scope": "error",
|
||||
@@ -232,7 +235,14 @@
|
||||
"@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": "warn",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"caughtErrors": "none"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/prefer-optional-chain": "error",
|
||||
|
||||
// === Unicorn rules (bonus coverage) ===
|
||||
@@ -282,6 +292,16 @@
|
||||
"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": [
|
||||
|
||||
3096
superset-frontend/package-lock.json
generated
3096
superset-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -57,7 +57,9 @@
|
||||
"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",
|
||||
"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}\"",
|
||||
"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",
|
||||
@@ -107,6 +109,7 @@
|
||||
"@deck.gl/mesh-layers": "~9.2.5",
|
||||
"@deck.gl/react": "~9.2.5",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@emotion/cache": "^11.4.0",
|
||||
@@ -127,10 +130,9 @@
|
||||
"@luma.gl/shadertools": "~9.2.5",
|
||||
"@luma.gl/webgl": "~9.2.5",
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"@rjsf/antd": "^5.24.13",
|
||||
"@rjsf/core": "^5.24.13",
|
||||
"@rjsf/utils": "^5.24.3",
|
||||
"@rjsf/validator-ajv8": "^5.24.13",
|
||||
"@rjsf/core": "^6.6.2",
|
||||
"@rjsf/utils": "^6.6.2",
|
||||
"@rjsf/validator-ajv8": "^6.6.2",
|
||||
"@scarf/scarf": "^1.4.0",
|
||||
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
|
||||
"@superset-ui/core": "file:./packages/superset-ui-core",
|
||||
@@ -164,9 +166,9 @@
|
||||
"@visx/scale": "^4.0.0",
|
||||
"@visx/tooltip": "^4.0.0",
|
||||
"@visx/xychart": "^4.0.0",
|
||||
"ag-grid-community": "35.3.1",
|
||||
"ag-grid-react": "35.3.1",
|
||||
"antd": "^5.26.0",
|
||||
"ag-grid-community": "36.0.0",
|
||||
"ag-grid-react": "36.0.0",
|
||||
"antd": "^6.0.0",
|
||||
"chrono-node": "^2.9.1",
|
||||
"classnames": "^2.2.5",
|
||||
"content-disposition": "^2.0.1",
|
||||
@@ -185,7 +187,7 @@
|
||||
"geostyler-style": "11.0.2",
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"google-auth-library": "^10.9.0",
|
||||
"immer": "^11.1.9",
|
||||
"immer": "^11.1.11",
|
||||
"interweave": "^13.1.1",
|
||||
"jquery": "^4.0.0",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
@@ -193,7 +195,7 @@
|
||||
"json-stringify-pretty-compact": "^4.0.0",
|
||||
"lodash": "^4.18.1",
|
||||
"lodash-es": "^4.18.1",
|
||||
"mapbox-gl": "^3.25.0",
|
||||
"mapbox-gl": "^3.26.0",
|
||||
"markdown-to-jsx": "^9.8.2",
|
||||
"match-sorter": "^8.3.0",
|
||||
"memoize-one": "^6.0.0",
|
||||
@@ -204,9 +206,9 @@
|
||||
"query-string": "9.4.1",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react": "^18.3.0",
|
||||
"react-arborist": "^3.12.0",
|
||||
"react-arborist": "^3.13.2",
|
||||
"react-checkbox-tree": "^1.8.0",
|
||||
"react-diff-viewer-continued": "^4.2.2",
|
||||
"react-diff-viewer-continued": "^4.3.0",
|
||||
"react-dnd": "^11.1.3",
|
||||
"react-dnd-html5-backend": "^11.1.3",
|
||||
"react-dom": "^18.3.0",
|
||||
@@ -261,7 +263,7 @@
|
||||
"@babel/types": "^7.29.7",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/jest": "^11.14.2",
|
||||
"@formatjs/intl-durationformat": "^0.10.15",
|
||||
"@formatjs/intl-durationformat": "^0.10.16",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@playwright/test": "^1.61.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
|
||||
@@ -271,7 +273,7 @@
|
||||
"@storybook/test-runner": "0.24.4",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@swc/core": "^1.15.43",
|
||||
"@swc/plugin-emotion": "^14.14.1",
|
||||
"@swc/plugin-emotion": "^14.15.0",
|
||||
"@swc/plugin-transform-imports": "^12.5.0",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
@@ -285,7 +287,7 @@
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^26.1.0",
|
||||
"@types/node": "^26.1.1",
|
||||
"@types/react": "^18.3.0",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-loadable": "^5.5.11",
|
||||
@@ -298,13 +300,13 @@
|
||||
"@types/rison": "0.1.0",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
"@types/unzipper": "^0.10.11",
|
||||
"@typescript-eslint/eslint-plugin": "^8.62.1",
|
||||
"@typescript-eslint/parser": "^8.62.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.63.0",
|
||||
"@typescript-eslint/parser": "^8.63.0",
|
||||
"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.40",
|
||||
"baseline-browser-mapping": "^2.10.42",
|
||||
"cheerio": "1.2.0",
|
||||
"concurrently": "^10.0.3",
|
||||
"copy-webpack-plugin": "^14.0.0",
|
||||
@@ -343,9 +345,10 @@
|
||||
"mini-css-extract-plugin": "^2.10.2",
|
||||
"minimizer-webpack-plugin": "^5.6.1",
|
||||
"open-cli": "^9.0.0",
|
||||
"oxlint": "^1.72.0",
|
||||
"oxlint": "^1.73.0",
|
||||
"po2json": "^0.4.5",
|
||||
"prettier": "3.9.4",
|
||||
"postcss-styled-syntax": "^0.7.2",
|
||||
"prettier": "3.9.5",
|
||||
"prettier-plugin-packagejson": "^3.0.2",
|
||||
"process": "^0.11.10",
|
||||
"react-dnd-test-backend": "^16.0.1",
|
||||
@@ -357,20 +360,21 @@
|
||||
"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.22.4",
|
||||
"tsx": "^4.23.0",
|
||||
"typescript": "5.4.5",
|
||||
"unzipper": "^0.12.5",
|
||||
"vm-browserify": "^1.1.2",
|
||||
"wait-on": "^9.0.10",
|
||||
"webpack": "^5.108.3",
|
||||
"webpack": "^5.108.4",
|
||||
"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.0",
|
||||
"webpack-sources": "^3.5.1",
|
||||
"webpack-visualizer-plugin2": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -386,6 +390,9 @@
|
||||
},
|
||||
"overrides": {
|
||||
"uuid": "$uuid",
|
||||
"@great-expectations/jsonforms-antd-renderers": {
|
||||
"antd": "$antd"
|
||||
},
|
||||
"core-js": "^3.38.1",
|
||||
"dompurify": "^3.4.11",
|
||||
"esbuild": "^0.28.1",
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*",
|
||||
"lodash": "^4.18.1",
|
||||
"antd": "^5.26.0",
|
||||
"antd": "^6.0.0",
|
||||
"jed": "^1.1.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -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,8 +105,18 @@ export const GlobalStyles = () => {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// Override geostyler CSS that hides AntD ColorPicker alpha input
|
||||
// See: https://github.com/apache/superset/issues/34721
|
||||
/* 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 */
|
||||
.ant-color-picker .ant-color-picker-alpha-input {
|
||||
display: block;
|
||||
}
|
||||
@@ -117,7 +127,7 @@ export const GlobalStyles = () => {
|
||||
}
|
||||
|
||||
.superset-explore-popover.ant-popover
|
||||
.ant-popover-inner:has(.ant-popover-title) {
|
||||
.ant-popover-container:has(.ant-popover-title) {
|
||||
padding-top: 0;
|
||||
}
|
||||
.superset-explore-popover.ant-popover .ant-popover-title {
|
||||
@@ -126,7 +136,7 @@ export const GlobalStyles = () => {
|
||||
line-height: 1;
|
||||
}
|
||||
.superset-explore-popover.ant-popover
|
||||
.ant-popover-inner:has(.ant-popover-title)
|
||||
.ant-popover-container:has(.ant-popover-title)
|
||||
.ant-tabs-tab {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,34 @@
|
||||
*/
|
||||
import { theme as antdThemeImport } from 'antd';
|
||||
import { Theme } from './Theme';
|
||||
import { AnyThemeConfig, ThemeAlgorithm } from './types';
|
||||
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;
|
||||
}
|
||||
|
||||
// Mock emotion's cache to avoid actual DOM operations
|
||||
jest.mock('@emotion/cache', () => ({
|
||||
@@ -338,10 +365,10 @@ test('Theme edge cases correctly applies base theme tokens in dark mode', () =>
|
||||
algorithm: antdThemeImport.defaultAlgorithm,
|
||||
};
|
||||
|
||||
const baseThemeDark: AnyThemeConfig = {
|
||||
const baseThemeDark = {
|
||||
...baseTheme,
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
};
|
||||
} as AnyThemeConfig;
|
||||
|
||||
// Simulate light mode with base theme
|
||||
const lightTheme = Theme.fromConfig({}, baseTheme);
|
||||
@@ -473,10 +500,10 @@ test('Theme base theme integration handles base theme with dark algorithm correc
|
||||
},
|
||||
};
|
||||
|
||||
const baseThemeDark: AnyThemeConfig = {
|
||||
const baseThemeDark = {
|
||||
...baseTheme,
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
};
|
||||
} as AnyThemeConfig;
|
||||
|
||||
const userDarkTheme: AnyThemeConfig = {
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
@@ -525,7 +552,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();
|
||||
@@ -604,7 +631,9 @@ test('Theme base theme integration handles cssVar, hashed and inherit properties
|
||||
|
||||
// User properties override/add to base
|
||||
expect(serialized.inherit).toBe(true);
|
||||
expect(serialized.cssVar).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.hashed).toBe(false);
|
||||
|
||||
// Tokens are still merged
|
||||
@@ -909,3 +938,81 @@ 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);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,730 @@
|
||||
// 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,
|
||||
}
|
||||
`;
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { theme as antdThemeImport } from 'antd';
|
||||
import { theme as antdThemeImport, type MappingAlgorithm } from 'antd';
|
||||
import {
|
||||
type AntdThemeConfig,
|
||||
type AnyThemeConfig,
|
||||
@@ -48,8 +48,8 @@ export function isSerializableConfig(
|
||||
export function deserializeThemeConfig(
|
||||
config: SerializableThemeConfig,
|
||||
): AntdThemeConfig {
|
||||
const { algorithm, ...rest } = config;
|
||||
const algorithmMap: Record<string, any> = {
|
||||
const { algorithm, cssVar, ...rest } = config;
|
||||
const algorithmMap: Record<string, MappingAlgorithm> = {
|
||||
default: antdThemeImport.defaultAlgorithm,
|
||||
dark: antdThemeImport.darkAlgorithm,
|
||||
compact: antdThemeImport.compactAlgorithm,
|
||||
@@ -74,9 +74,17 @@ 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 } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"lodash-es": "^4.18.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
"@ant-design/icons": "^5.6.1 || ^6.0.0",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@visx/responsive": "^4.0.0",
|
||||
"ace-builds": "^1.44.0",
|
||||
"ag-grid-community": "35.3.1",
|
||||
"ag-grid-react": "35.3.1",
|
||||
"ag-grid-community": "36.0.0",
|
||||
"ag-grid-react": "36.0.0",
|
||||
"brace": "^0.11.1",
|
||||
"classnames": "^2.5.1",
|
||||
"core-js": "^3.49.0",
|
||||
@@ -54,7 +54,7 @@
|
||||
"react-ace": "^14.0.1",
|
||||
"react-draggable": "^4.7.0",
|
||||
"react-error-boundary": "^6.1.2",
|
||||
"react-js-cron": "^5.2.0",
|
||||
"react-js-cron": "^6.0.2",
|
||||
"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.0",
|
||||
"@types/node": "^26.1.1",
|
||||
"@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": "^5.26.0",
|
||||
"antd": "^6.0.0",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0",
|
||||
|
||||
@@ -92,7 +92,7 @@ const MatrixNoDataComponent = () => {
|
||||
* Individual grid cell component - memoized to prevent unnecessary re-renders
|
||||
*/
|
||||
const MatrixifyGridCell = memo(
|
||||
({ cell, rowHeight, datasource, hooks }: MatrixifyGridCellProps) => {
|
||||
({ cell, hooks }: MatrixifyGridCellProps) => {
|
||||
// Use computed title from template (will be empty string if no template)
|
||||
const cellLabel = cell.title || '';
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { ThemeProvider } from '@apache-superset/core/theme';
|
||||
import { supersetTheme } from '@apache-superset/core/theme';
|
||||
import { ThemeProvider, supersetTheme } from '@apache-superset/core/theme';
|
||||
import MatrixifyGridRenderer from './MatrixifyGridRenderer';
|
||||
import type { MatrixifyMode } from '../../types/matrixify';
|
||||
import { generateMatrixifyGrid } from './MatrixifyGridGenerator';
|
||||
|
||||
@@ -118,7 +118,6 @@ interface MatrixifyGridRendererProps {
|
||||
function MatrixifyGridRenderer({
|
||||
formData,
|
||||
datasource,
|
||||
width,
|
||||
height,
|
||||
hooks,
|
||||
}: MatrixifyGridRendererProps) {
|
||||
@@ -249,7 +248,7 @@ function MatrixifyGridRenderer({
|
||||
{/* Row cells for this column group */}
|
||||
{row
|
||||
.slice(colGroup.startIdx, colGroup.endIdx)
|
||||
.map((cell, colIdx) =>
|
||||
.map(cell =>
|
||||
cell ? (
|
||||
<MatrixifyGridCell
|
||||
key={cell.id}
|
||||
|
||||
@@ -32,8 +32,7 @@ import {
|
||||
wordCloudFormData,
|
||||
} from '../../../../superset-ui-core/test/chart/fixtures/formData';
|
||||
|
||||
import { Expandable } from '@storybook-shared';
|
||||
import { VerifyCORS, renderError } from '@storybook-shared';
|
||||
import { Expandable, VerifyCORS, renderError } from '@storybook-shared';
|
||||
|
||||
const BIG_NUMBER = bigNumberFormData.viz_type;
|
||||
const SANKEY = sankeyFormData.viz_type;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { render, screen, userEvent } from '@superset-ui/core/spec';
|
||||
import { render, screen, userEvent, fireEvent } from '@superset-ui/core/spec';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import { ActionButton } from '.';
|
||||
|
||||
@@ -45,6 +45,18 @@ 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} />);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* 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';
|
||||
@@ -55,6 +56,7 @@ export const ActionButton = ({
|
||||
className="action-button"
|
||||
data-test={label}
|
||||
onClick={onClick}
|
||||
onKeyDown={onClick ? handleKeyboardActivation(onClick) : undefined}
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user