Compare commits

..

1 Commits

Author SHA1 Message Date
Evan Rusackas
eb36d7b043 feat(glyph): single-file chart definition pattern across all plugins
Introduces @superset-ui/glyph-core's defineChart() and migrates ECharts,
deckgl, nvd3, legacy, and other plugins from the multi-file pattern
(controlPanel/transformProps/buildQuery/types/component) to single-file
chart definitions. Includes the SIP design doc, GlyphOptionsPanel explore
UI, and the supporting test suites.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:21:13 -07:00
792 changed files with 35845 additions and 108132 deletions

View File

@@ -42,7 +42,7 @@ runs:
fi
echo "python-version=$RESOLVED_VERSION" >> "$GITHUB_OUTPUT"
- name: Set up Python ${{ steps.set-python-version.outputs.python-version }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ steps.set-python-version.outputs.python-version }}
cache: ${{ inputs.cache }}

View File

@@ -114,7 +114,7 @@ testdata() {
say "::group::Load test data"
# must specify PYTHONPATH to make `tests.superset_test_config` importable
export PYTHONPATH="$GITHUB_WORKSPACE"
uv pip install --system -e .
pip install -e .
superset db upgrade
superset load_test_users
superset load_examples --load-test-data
@@ -127,7 +127,7 @@ playwright_testdata() {
say "::group::Load all examples for Playwright tests"
# must specify PYTHONPATH to make `tests.superset_test_config` importable
export PYTHONPATH="$GITHUB_WORKSPACE"
uv pip install --system -e .
pip install -e .
superset db upgrade
superset load_test_users
superset load_examples

View File

@@ -31,7 +31,7 @@ jobs:
checks: write
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: true
ref: master
@@ -40,7 +40,7 @@ jobs:
uses: ./.github/actions/setup-supersetbot/
- name: Set up Python ${{ inputs.python-version }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.10"

View File

@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive

View File

@@ -25,7 +25,7 @@ jobs:
pull-requests: write
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check and notify

View File

@@ -26,7 +26,7 @@ jobs:
frontend: ${{ steps.check.outputs.frontend }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check for file changes
@@ -47,7 +47,6 @@ jobs:
permissions:
actions: read
contents: read
pull-requests: read
security-events: write
strategy:
@@ -58,7 +57,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

View File

@@ -27,7 +27,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout Repository"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: "Dependency Review"
@@ -43,7 +43,7 @@ jobs:
# the latest version. It's MIT: https://github.com/nbubna/store/blob/master/LICENSE-MIT
# pkg:npm/node-forge@1.3.1
# selecting BSD-3-Clause licensing terms for node-forge to ensure compatibility with Apache
allow-dependencies-licenses: pkg:npm/rgbcolor, pkg:npm/jszip@3.10.1
allow-dependencies-licenses: pkg:npm/store2@2.14.2, pkg:npm/node-forge@1.3.1, pkg:npm/rgbcolor, pkg:npm/jszip@3.10.1
python-dependency-liccheck:
# NOTE: Configuration for liccheck lives in our pyproject.yml.
@@ -51,7 +51,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: "Checkout Repository"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

View File

@@ -30,7 +30,7 @@ jobs:
docker: ${{ steps.check.outputs.docker }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check for file changes
@@ -71,28 +71,10 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Free up disk space
shell: bash
run: |
# Reclaim large preinstalled toolchains we don't use. The image
# build, and especially the docker-compose sanity check (which
# rebuilds from scratch whenever the registry cache image
# apache/superset-cache is unavailable), can otherwise exhaust the
# runner's root disk and fail with "no space left on device".
echo "Disk before cleanup:"; df -h /
sudo rm -rf \
/usr/share/dotnet \
/usr/local/lib/android \
/opt/ghc \
/usr/local/.ghcup \
/opt/hostedtoolcache/CodeQL \
/usr/local/share/boost || true
echo "Disk after cleanup:"; df -h /
- name: Setup Docker Environment
uses: ./.github/actions/setup-docker
with:
@@ -119,27 +101,13 @@ jobs:
PUSH_OR_LOAD="--load"
fi
# Retry to absorb transient Docker Hub registry errors (base-image
# pull timeouts, 504/401 on push, ECONNRESET) that otherwise fail
# the whole job. buildx reuses the buildkit layer cache from the
# failed attempt, so a retry mostly re-does just the failed push.
for attempt in 1 2 3; do
if supersetbot docker \
$PUSH_OR_LOAD \
--preset "$BUILD_PRESET" \
--context "$EVENT" \
--context-ref "$RELEASE" $FORCE_LATEST \
--extra-flags "--build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG" \
$PLATFORM_ARG; then
break
fi
if [ "$attempt" -eq 3 ]; then
echo "::error::supersetbot docker build failed after 3 attempts"
exit 1
fi
echo "::warning::Build attempt ${attempt} failed; retrying in 30s..."
sleep 30
done
supersetbot docker \
$PUSH_OR_LOAD \
--preset "$BUILD_PRESET" \
--context "$EVENT" \
--context-ref "$RELEASE" $FORCE_LATEST \
--extra-flags "--build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG" \
$PLATFORM_ARG
# in the context of push (using multi-platform build), we need to pull the image locally
- name: Docker pull
@@ -177,24 +145,9 @@ jobs:
timeout-minutes: 30
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Free up disk space
shell: bash
run: |
# The sanity check rebuilds the image from scratch whenever the
# registry cache image apache/superset-cache is unavailable, which
# can exhaust the runner's root disk ("no space left on device").
echo "Disk before cleanup:"; df -h /
sudo rm -rf \
/usr/share/dotnet \
/usr/local/lib/android \
/opt/ghc \
/usr/local/.ghcup \
/opt/hostedtoolcache/CodeQL \
/usr/local/share/boost || true
echo "Disk after cleanup:"; df -h /
- name: Setup Docker Environment
uses: ./.github/actions/setup-docker
with:

View File

@@ -10,29 +10,37 @@ permissions:
contents: read
jobs:
build:
# Publishing uses npm trusted publishing (OIDC), so there is no NPM_TOKEN to
# gate on. Restrict to the canonical repo: forks cannot mint a valid OIDC
# token for this package and must not publish.
if: github.repository == 'apache/superset'
config:
runs-on: ubuntu-24.04
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
steps:
- name: "Check for secrets"
id: check
shell: bash
run: |
if [ -n "${NPM_TOKEN}" ]; then
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
fi
env:
NPM_TOKEN: ${{ (secrets.NPM_TOKEN != '') || '' }}
build:
needs: config
if: needs.config.outputs.has-secrets
runs-on: ubuntu-24.04
permissions:
contents: read
id-token: write # required for npm trusted publishing (OIDC)
defaults:
run:
working-directory: superset-embedded-sdk
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
# Note: registry-url is intentionally omitted. When set, actions/setup-node
# writes an .npmrc with `_authToken=${NODE_AUTH_TOKEN}` and a placeholder
# 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
with:
node-version-file: "./superset-embedded-sdk/.nvmrc"
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm run ci:release
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -21,7 +21,7 @@ jobs:
run:
working-directory: superset-embedded-sdk
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6

View File

@@ -32,12 +32,12 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
distribution: "temurin"
java-version: "11"

View File

@@ -27,7 +27,7 @@ jobs:
security-events: write
steps:
- name: Checkout Repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

View File

@@ -16,7 +16,7 @@ jobs:
issues: write
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive

View File

@@ -18,12 +18,12 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
distribution: "temurin"
java-version: "11"

View File

@@ -21,7 +21,7 @@ jobs:
pull-requests: write
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive

View File

@@ -28,7 +28,7 @@ jobs:
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "previous", "next"]') }}
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive

View File

@@ -33,7 +33,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
# pulls all commits (needed for lerna / semantic release to correctly version)

View File

@@ -152,7 +152,7 @@ jobs:
- name: Checkout PR code (only if build needed)
if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.build_needed == 'true'
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ steps.check.outputs.target_sha }}
persist-credentials: false

View File

@@ -41,7 +41,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -61,7 +61,7 @@ jobs:
- name: superset init
if: steps.check.outputs.python
run: |
uv pip install --system -e .
pip install -e .
superset db upgrade
superset load_test_users
- name: superset load_examples

View File

@@ -60,7 +60,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.event.workflow_run.head_sha || github.sha }}"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
persist-credentials: false
@@ -71,7 +71,7 @@ jobs:
node-version-file: "./docs/.nvmrc"
- name: Setup Python
uses: ./.github/actions/setup-backend/
- uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
distribution: "zulu"
java-version: "21"

View File

@@ -28,7 +28,7 @@ jobs:
name: Link Checking
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
# Do not bump this linkinator-action version without opening
@@ -73,7 +73,7 @@ jobs:
working-directory: docs
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -112,7 +112,7 @@ jobs:
working-directory: docs
steps:
- name: "Checkout PR head: ${{ github.event.workflow_run.head_sha }}"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.event.workflow_run.head_sha }}
persist-credentials: false

View File

@@ -38,7 +38,7 @@ jobs:
frontend: ${{ steps.check.outputs.frontend }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check for file changes
@@ -49,7 +49,7 @@ jobs:
cypress-matrix:
needs: changes
if: (needs.changes.outputs.python == 'true' || needs.changes.outputs.frontend == 'true') && github.event.pull_request.draft == false
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.frontend == 'true'
# Somehow one test flakes on 24.04 for unknown reasons, this is the only GHA left on 22.04
runs-on: ubuntu-22.04
timeout-minutes: 30
@@ -97,21 +97,21 @@ jobs:
# Conditional checkout based on context
- name: Checkout for push or pull_request event
if: github.event_name == 'push' || github.event_name == 'pull_request'
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Checkout using ref (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
ref: ${{ github.event.inputs.ref }}
submodules: recursive
- name: Checkout using PR ID (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
@@ -207,21 +207,21 @@ jobs:
# Conditional checkout based on context (same as Cypress workflow)
- name: Checkout for push or pull_request event
if: github.event_name == 'push' || github.event_name == 'pull_request'
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Checkout using ref (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
ref: ${{ github.event.inputs.ref }}
submodules: recursive
- name: Checkout using PR ID (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge

View File

@@ -31,7 +31,7 @@ jobs:
working-directory: superset-extensions-cli
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive

View File

@@ -27,7 +27,7 @@ jobs:
should-run: ${{ steps.check.outputs.frontend }}
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
fetch-depth: 0
@@ -110,7 +110,7 @@ jobs:
id-token: write
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
fetch-depth: 0

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive

View File

@@ -29,7 +29,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.ref || github.ref_name }}
persist-credentials: true

View File

@@ -34,7 +34,7 @@ jobs:
frontend: ${{ steps.check.outputs.frontend }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check for file changes
@@ -83,21 +83,21 @@ jobs:
# Conditional checkout based on context (same as Cypress workflow)
- name: Checkout for push or pull_request event
if: github.event_name == 'push' || github.event_name == 'pull_request'
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Checkout using ref (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
ref: ${{ github.event.inputs.ref }}
submodules: recursive
- name: Checkout using PR ID (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge

View File

@@ -1,11 +1,6 @@
# Python integration tests
name: Python-Integration
# Least-privilege default for GITHUB_TOKEN. Jobs that need more (e.g. OIDC for
# codecov uploads) opt in via their own job-level `permissions:` block.
permissions:
contents: read
on:
push:
branches:
@@ -29,7 +24,7 @@ jobs:
python: ${{ steps.check.outputs.python }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check for file changes
@@ -72,7 +67,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -157,7 +152,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -207,7 +202,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive

View File

@@ -25,7 +25,7 @@ jobs:
python: ${{ steps.check.outputs.python }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check for file changes
@@ -72,7 +72,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -127,7 +127,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -149,7 +149,7 @@ jobs:
run: celery-worker
- name: Python unit tests (PostgreSQL)
run: |
uv pip install --system -e .[hive]
pip install -e .[hive]
./scripts/python_tests.sh -m 'chart_data_flow or sql_json_flow'
- name: Upload code coverage
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0

View File

@@ -1,11 +1,6 @@
# Python unit tests
name: Python-Unit
# Least-privilege default for GITHUB_TOKEN. Jobs that need more (e.g. OIDC for
# codecov uploads) opt in via their own job-level `permissions:` block.
permissions:
contents: read
on:
push:
branches:
@@ -30,7 +25,7 @@ jobs:
python: ${{ steps.check.outputs.python }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check for file changes
@@ -55,7 +50,7 @@ jobs:
PYTHONPATH: ${{ github.workspace }}
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive

View File

@@ -25,7 +25,7 @@ jobs:
pull-requests: read
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -61,7 +61,7 @@ jobs:
pull-requests: read
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive

View File

@@ -25,7 +25,7 @@ jobs:
timeout-minutes: 20
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install dependencies

View File

@@ -38,7 +38,7 @@ jobs:
});
- name: "Checkout ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

View File

@@ -1,60 +0,0 @@
name: Sync requirements for Python dependency PRs
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: write
pull-requests: read
jobs:
sync-python-dep-requirements:
# This action is limited for (1) PRs authored by Dependabot and (2) upstream repo due to write back to remote
if: github.repository == 'apache/superset' && github.event.pull_request.user.login == 'dependabot[bot]' && github.event.pull_request.head.repo.fork == false
runs-on: ubuntu-26.04
steps:
- name: Fetch Dependabot metadata
id: dependabot-metadata
shell: bash
env:
BRANCH_NAME: ${{ github.head_ref }}
run: |
# Get current branch name, extract the package ecosystem and return as GHA step output
packageEcosystem=$(echo "$BRANCH_NAME" | cut -d'/' -f2)
echo "package-ecosystem=$packageEcosystem" >> $GITHUB_OUTPUT
# zizmor: ignore[artipacked] - required persisted credentials to push synced requirement changes back to remote
- name: Checkout source code
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: true
# Authenticate the Docker daemon so the python:slim pull in
# uv-pip-compile.sh uses our (much higher) authenticated rate limit
# instead of the shared-runner anonymous one.
- 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
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Sync requirements in containerized environment
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
run: ./scripts/uv-pip-compile.sh
- name: Push changes to remote PRs
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add requirements
git diff --cached --quiet && exit 0
git commit --signoff --message "build(deps): sync pinned requirements for Dependabot pip PRs"
git push origin "HEAD:refs/heads/${GITHUB_EVENT_PULL_REQUEST_HEAD_REF}"
env:
GITHUB_EVENT_PULL_REQUEST_HEAD_REF: ${{ github.event.pull_request.head.ref }}

View File

@@ -54,7 +54,7 @@ jobs:
fail-fast: false
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
fetch-depth: 0
@@ -120,7 +120,7 @@ jobs:
pull-requests: write
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
fetch-depth: 0

View File

@@ -32,7 +32,7 @@ jobs:
name: Generate Reports
steps:
- name: Checkout Repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

View File

@@ -14,6 +14,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Install pre-push hooks too, not just pre-commit:
# pre-commit install --hook-type pre-push
# (or this default applies automatically on `pre-commit install`)
default_install_hook_types: [pre-commit, pre-push]
repos:
- repo: https://github.com/MarcoGorelli/auto-walrus
rev: 0.3.4
@@ -94,6 +98,19 @@ repos:
files: ^superset-frontend\/.*\.(js|jsx|ts|tsx)$
exclude: ^superset-frontend/cypress-base\/
require_serial: true
- id: full-type-check-frontend
name: Full Type-Check (Frontend, pre-push)
# The pre-commit (per-file) variant catches most issues but `tsc <file>`
# bypasses some project-wide invariants. This runs the same check CI
# runs (`npm run type` -> `tsc --noEmit -p tsconfig.json`) so cross-file
# type errors are surfaced before they reach origin.
entry: bash -c 'cd superset-frontend && npm run type'
language: system
files: ^superset-frontend\/.*\.(js|jsx|ts|tsx)$
exclude: ^superset-frontend/cypress-base\/
pass_filenames: false
stages: [pre-push]
require_serial: true
# blacklist unsafe functions like make_url (see #19526)
- repo: https://github.com/skorokithakis/blacklist-pre-commit-hook
rev: e2f070289d8eddcaec0b580d3bde29437e7c8221

View File

@@ -50,4 +50,3 @@ under the License.
- [4.1.4](./CHANGELOG/4.1.4.md)
- [5.0.0](./CHANGELOG/5.0.0.md)
- [6.0.0](./CHANGELOG/6.0.0.md)
- [6.1.0](./CHANGELOG/6.1.0.md)

File diff suppressed because it is too large Load Diff

View File

@@ -23,14 +23,11 @@ PYTHON=`command -v python3.11 || command -v python3.10`
install: superset pre-commit
superset:
# Bootstrap uv (the project's installer) into the active environment
pip install uv
# Install external dependencies
uv pip install -r requirements/development.txt
pip install -r requirements/development.txt
# Install Superset in editable (development) mode
uv pip install -e .
pip install -e .
# Create an admin user in your metadata database
superset fab create-admin \
@@ -55,14 +52,11 @@ superset:
update: update-py update-js
update-py:
# Bootstrap uv (the project's installer) into the active environment
pip install uv
# Install external dependencies
uv pip install -r requirements/development.txt
pip install -r requirements/development.txt
# Install Superset in editable (development) mode
uv pip install -e .
pip install -e .
# Initialize the database
superset db upgrade
@@ -85,8 +79,7 @@ activate:
pre-commit:
# setup pre commit dependencies
pip install uv
uv pip install -r requirements/development.txt
pip3 install -r requirements/development.txt
pre-commit install
format: py-format js-format

View File

@@ -83,9 +83,6 @@ categories:
- name: Clark.de
url: https://clark.de/
- name: Cover Genius
url: https://covergenius.com/
- name: EnquiryLabs
url: https://www.enquirylabs.co.uk
@@ -95,10 +92,6 @@ categories:
- name: KarrotPay
url: https://www.daangnpay.com/
- name: NICE Actimize
url: https://www.niceactimize.com/
contributors: ["@stevensuting"]
- name: Remita
url: https://remita.net
contributors: ["@mujibishola"]
@@ -119,6 +112,9 @@ categories:
url: https://xendit.co/
contributors: ["@LieAlbertTriAdrian"]
- name: Cover Genius
url: https://covergenius.com/
Gaming:
- name: Popoko VM Games Studio
url: https://popoko.live
@@ -300,6 +296,7 @@ categories:
logo: hifadih.png
contributors: ["@saintLaurent00"]
# Logo approved by @anmol-hpe on behalf of HPE
- name: HPE
url: https://www.hpe.com/in/en/home.html
logo: hpe.png
@@ -399,10 +396,6 @@ categories:
url: https://www.techaudit.info
contributors: ["@ETselikov"]
- name: Tech Solution
url: https://www.tech-solution.com.ar/
contributors: ["@danteGiuliano", "@LeandroVallejos", "@McJaben", "@xJeree", "@zeo-return-null"]
- name: Tenable
url: https://www.tenable.com
contributors: ["@dflionis"]
@@ -432,10 +425,6 @@ categories:
logo: userguiding.svg
contributors: ["@tzercin"]
- name: Value Ad
url: https://bestpair.info/
contributors: ["@stevensuting"]
- name: Virtuoso QA
url: https://www.virtuosoqa.com
@@ -520,6 +509,10 @@ categories:
url: https://www.sunbird.org/
contributors: ["@eksteporg"]
- name: The GRAPH Network
url: https://thegraphnetwork.org/
contributors: ["@fccoelho"]
- name: Udemy
url: https://www.udemy.com/
contributors: ["@sungjuly"]
@@ -528,24 +521,7 @@ categories:
url: https://www.vipkid.com.cn/
contributors: ["@illpanda"]
Social Organization:
- name: Living Goods
url: https://www.livinggoods.org
contributors: ["@chelule"]
- name: One Acre Fund
url: https://oneacrefund.org/
contributors: ["@stevensuting"]
- name: Quest Alliance
url: https://www.questalliance.net/
contributors: ["@stevensuting"]
- name: The GRAPH Network
url: https://thegraphnetwork.org/
contributors: ["@fccoelho"]
- name: Wikimedia Foundation
- name: WikiMedia Foundation
url: https://wikimediafoundation.org
contributors: ["@vg"]
@@ -558,10 +534,6 @@ categories:
url: https://www.douroeci.com/
contributors: ["@nunohelibeires"]
- name: Rogow
url: https://rogow.com.br/
contributors: ["@nilmonto"]
- name: Safaricom
url: https://www.safaricom.co.ke/
contributors: ["@mmutiso"]
@@ -574,10 +546,11 @@ categories:
url: https://wattbewerb.de/
contributors: ["@wattbewerb"]
Healthcare:
- name: 2070Health
url: https://2070health.com/
- name: Rogow
url: https://rogow.com.br/
contributors: ["@nilmonto"]
Healthcare:
- name: Amino
url: https://amino.com
contributors: ["@shkr"]
@@ -590,6 +563,10 @@ categories:
url: https://www.getcare.io/
contributors: ["@alandao2021"]
- name: Living Goods
url: https://www.livinggoods.org
contributors: ["@chelule"]
- name: Maieutical Labs
url: https://maieuticallabs.it
contributors: ["@xrmx"]
@@ -608,10 +585,10 @@ categories:
- name: WeSure
url: https://www.wesure.cn/
HR / Staffing:
- name: bluquist
url: https://bluquist.com/
- name: 2070Health
url: https://2070health.com/
HR / Staffing:
- name: Swile
url: https://www.swile.co/
contributors: ["@PaoloTerzi"]
@@ -619,18 +596,21 @@ categories:
- name: Symmetrics
url: https://www.symmetrics.fyi
- name: bluquist
url: https://bluquist.com/
Government:
- name: City of Ann Arbor, MI
url: https://www.a2gov.org/
contributors: ["@sfirke"]
- name: NRLM - Sarathi, India
url: https://pib.gov.in/PressReleasePage.aspx?PRID=1999586
- name: RIS3 Strategy of CZ, MIT CR
url: https://www.ris3.cz/
contributors: ["@RIS3CZ"]
- name: NRLM - Sarathi, India
url: https://pib.gov.in/PressReleasePage.aspx?PRID=1999586
Mobile Software:
- name: VLMedia
url: https://www.vlmedia.com.tr

View File

@@ -24,10 +24,6 @@ assists people when migrating to a new version.
## Next
### 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.
### `thumbnail_url` removed from dashboard list API response
The `thumbnail_url` field has been removed from `GET /api/v1/dashboard/` list responses. External consumers relying on this field must now construct the thumbnail URL client-side using `id` and `changed_on_utc`:
@@ -227,9 +223,6 @@ Added a new combined datasource list endpoint at `GET /api/v1/datasource/` to se
- The endpoint is available to users with at least one of `can_read` on `Dataset` or `SemanticView`.
- Semantic views are included only when the `SEMANTIC_LAYERS` feature flag is enabled.
- The endpoint enforces strict `order_column` validation and returns `400` for invalid sort columns.
## 6.1.0
### ClickHouse minimum driver version bump
The minimum required version of `clickhouse-connect` has been raised to `>=0.13.0`. If you are using the ClickHouse connector, please upgrade your `clickhouse-connect` package. The `_mutate_label` workaround that appended hash suffixes to column aliases has also been removed, as it is no longer needed with modern versions of the driver.

View File

@@ -111,6 +111,8 @@ services:
superset-init-light:
condition: service_completed_successfully
volumes: *superset-volumes
ports:
- "${SUPERSET_PORT:-8088}:8088"
environment:
DATABASE_HOST: db-light
DATABASE_DB: superset_light
@@ -162,7 +164,7 @@ services:
environment:
# set this to false if you have perf issues running the npm i; npm run dev in-docker
# if you do so, you have to run this manually on the host, which should perform better!
BUILD_SUPERSET_FRONTEND_IN_DOCKER: true
BUILD_SUPERSET_FRONTEND_IN_DOCKER: false
NPM_RUN_PRUNE: false
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
DISABLE_TS_CHECKER: "${DISABLE_TS_CHECKER:-true}"

View File

@@ -34,6 +34,14 @@ x-superset-volumes: &superset-volumes
- superset_home:/app/superset_home
- ./tests:/app/tests
- superset_data:/app/data
# Python package metadata for the editable `uv pip install -e .` that
# docker-bootstrap.sh runs at container start. Without these bind mounts
# the editable install reads stale metadata baked into the image at
# build time and may conflict with apache-superset-core's current pins.
- ./pyproject.toml:/app/pyproject.toml
- ./setup.py:/app/setup.py
- ./MANIFEST.in:/app/MANIFEST.in
- ./README.md:/app/README.md
x-common-build: &common-build
context: .
target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean`
@@ -72,23 +80,20 @@ services:
- -c
- |
url="http://host.docker.internal:9000/static/assets/manifest.json"
max_attempts=300 # ~10 minutes at 2s intervals; first build can be slow
echo "Waiting for webpack dev server at $$url..."
max_attempts=150 # ~5 minutes at 2s intervals
echo "Waiting for webpack dev server at $url..."
attempt=0
until curl -sf --max-time 5 -H "Host: localhost" -o /dev/null "$$url"; do
attempt=$$((attempt + 1))
if [ "$$attempt" -ge "$$max_attempts" ]; then
echo "ERROR: webpack dev server did not serve $$url after $$max_attempts attempts." >&2
until curl -sf --max-time 5 -o /dev/null "$url"; do
attempt=$((attempt + 1))
if [ "$attempt" -ge "$max_attempts" ]; then
echo "ERROR: webpack dev server did not serve $url after $max_attempts attempts (~5 minutes)." >&2
echo "Is the dev server running? With BUILD_SUPERSET_FRONTEND_IN_DOCKER=false you must start it on the host (e.g. 'npm run dev' in superset-frontend)." >&2
exit 1
fi
if [ $$((attempt % 15)) -eq 0 ]; then
echo "Still waiting for webpack dev server... ($$attempt/$$max_attempts)"
fi
sleep 2
done
echo "Webpack dev server is ready; starting nginx."
exec /docker-entrypoint.sh nginx -g 'daemon off;'
exec nginx -g 'daemon off;'
redis:
image: redis:7

View File

@@ -71,29 +71,27 @@ case "${1}" in
worker)
echo "Starting Celery worker..."
# setting up only 2 workers by default to contain memory usage in dev environments
celery --app=superset.tasks.celery_app:app worker -O fair -l INFO --concurrency=${CELERYD_CONCURRENCY:-2} ${WORKER_LOG_FILE:+--logfile=$WORKER_LOG_FILE}
celery --app=superset.tasks.celery_app:app worker -O fair -l INFO --concurrency=${CELERYD_CONCURRENCY:-2}
;;
beat)
echo "Starting Celery beat..."
rm -f /tmp/celerybeat.pid
celery --app=superset.tasks.celery_app:app beat --pidfile /tmp/celerybeat.pid -l INFO -s "${SUPERSET_HOME}"/celerybeat-schedule ${BEAT_LOG_FILE:+--logfile=$BEAT_LOG_FILE}
celery --app=superset.tasks.celery_app:app beat --pidfile /tmp/celerybeat.pid -l INFO -s "${SUPERSET_HOME}"/celerybeat-schedule
;;
app)
echo "Starting web app (using development server)..."
# Default to Flask debug mode in this dev compose entrypoint so the Talisman
# dev CSP (which permits 'unsafe-eval' required by React Refresh / HMR) is
# served. Operators can still set FLASK_DEBUG=false in docker/.env-local
# to exercise the production-like CSP and error handling.
: "${FLASK_DEBUG:=1}"
export FLASK_DEBUG
# Werkzeug's interactive debugger (/console) is a separate, security-sensitive
# feature and must be opted into explicitly via SUPERSET_DEBUG_ENABLED=true.
# Environment-based debugger control for security
# Only enable Werkzeug interactive debugger when explicitly requested
# Modern Werkzeug (3.0+) includes PIN protection, but defense-in-depth approach
# Override FLASK_DEBUG so the effective state matches SUPERSET_DEBUG_ENABLED even
# when FLASK_DEBUG=true is inherited from docker/.env or .flaskenv
if [[ "${SUPERSET_DEBUG_ENABLED:-}" == "true" ]]; then
export FLASK_DEBUG=1
DEBUGGER_FLAG="--debugger"
echo " ⚠️ Werkzeug debugger enabled (requires PIN for /console access)"
else
export FLASK_DEBUG=0
DEBUGGER_FLAG="--no-debugger"
echo " 🔒 Werkzeug debugger disabled (set SUPERSET_DEBUG_ENABLED=true to enable)"
fi

View File

@@ -19,7 +19,7 @@
#
HYPHEN_SYMBOL='-'
exec gunicorn \
gunicorn \
--bind "${SUPERSET_BIND_ADDRESS:-0.0.0.0}:${SUPERSET_PORT:-8088}" \
--access-logfile "${ACCESS_LOG_FILE:-$HYPHEN_SYMBOL}" \
--error-logfile "${ERROR_LOG_FILE:-$HYPHEN_SYMBOL}" \

View File

@@ -455,51 +455,6 @@ def FLASK_APP_MUTATOR(app: Flask) -> None:
app.before_request_funcs.setdefault(None, []).append(make_session_permanent)
```
## Customizing the landing page (index view)
The page served at `/` is rendered by an index view. By default Superset registers
`SupersetIndexView`, which redirects to `/superset/welcome/` and also adds the
`/lang/<locale>` locale handler. You can replace it with your own view, for example
to send users straight to a specific dashboard or to a chart list.
Set `FAB_INDEX_VIEW` to the **importable dotted path** of your view class. Flask-AppBuilder
resolves this during app initialization and uses it in place of the default:
```python
# my_overrides.py — must be importable on the PYTHONPATH
from flask import redirect
from superset.initialization import SupersetIndexView
from superset.superset_typing import FlaskResponse
from flask_appbuilder import expose
class MyIndexView(SupersetIndexView):
@expose("/")
def index(self) -> FlaskResponse:
return redirect("/chart/list/")
```
```python
# superset_config.py
FAB_INDEX_VIEW = "my_overrides.MyIndexView"
```
A few things that commonly trip people up:
- **Subclass `SupersetIndexView`, not Flask-AppBuilder's bare `IndexView`.** Subclassing
keeps Superset's `/lang/<locale>` locale handling; replacing it with a bare `IndexView`
silently drops that behavior.
- **The class must be importable as a real module.** `FAB_INDEX_VIEW` is resolved by
importing the dotted path, which is independent of how `superset_config.py` itself is
loaded. Superset only copies **uppercase** names out of `superset_config.py` into its
runtime config, so a `FAB_INDEX_VIEW = "superset_config.MyIndexView"` reference only works
if `superset_config` is itself importable by that name on the `PYTHONPATH`. If you load
config via `SUPERSET_CONFIG_PATH` (an arbitrary file path), put the view in a separate
importable module instead and reference that module.
- **Don't set `appbuilder.indexview` from `FLASK_APP_MUTATOR`.** The mutator runs after
routes are already registered, so the assignment has no effect on the `/` route. Use
`FAB_INDEX_VIEW` instead.
## Feature Flags
To support a diverse set of users, Superset has some features that are not enabled by default. For

View File

@@ -22,24 +22,31 @@ level dependencies.
**Debian and Ubuntu**
The following command will ensure that the required dependencies are installed (tested on Ubuntu 20.04, 22.04, and 24.04):
Ubuntu **24.04** uses python 3.12 per default, which currently is not supported by Superset. You need to add a second python installation of 3.11 and install the required additional dependencies.
```bash
sudo apt-get install build-essential libssl-dev libffi-dev python3-dev python3-pip python3-venv libsasl2-dev libldap2-dev libpq-dev default-libmysqlclient-dev pkg-config
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install python3.11 python3.11-dev python3.11-venv build-essential libssl-dev libffi-dev libsasl2-dev libldap2-dev default-libmysqlclient-dev
```
Refer to the
[pyproject.toml](https://github.com/apache/superset/blob/master/pyproject.toml) file for the list of
Python versions officially supported by Superset, and install a matching `python3` interpreter for
your distribution. The `libpq-dev` package is only needed if you intend to connect to (or use) a
PostgreSQL database; you can omit it otherwise.
In Ubuntu **20.04 and 22.04** the following command will ensure that the required dependencies are installed:
```bash
sudo apt-get install build-essential libssl-dev libffi-dev python3-dev python3-pip libsasl2-dev libldap2-dev default-libmysqlclient-dev
```
In Ubuntu **before 20.04** the following command will ensure that the required dependencies are installed:
```bash
sudo apt-get install build-essential libssl-dev libffi-dev python-dev python-pip libsasl2-dev libldap2-dev default-libmysqlclient-dev
```
**Fedora and RHEL-derivative Linux distributions**
Install the following packages using the `yum` package manager:
```bash
sudo yum install gcc gcc-c++ libffi-devel python3-devel python3-pip python3-wheel openssl-devel cyrus-sasl-devel openldap-devel
sudo yum install gcc gcc-c++ libffi-devel python-devel python-pip python-wheel openssl-devel cyrus-sasl-devel openldap-devel
```
In more recent versions of CentOS and Fedora, you may need to install a slightly different set of packages using `dnf`:

View File

@@ -28,19 +28,14 @@
# Skip builds when no docs changes (exit 0 = skip, non-zero = build).
# Checks for changes in docs/ and README.md (which gets pulled into docs).
#
# $CACHED_COMMIT_REF is the last *deployed* commit; it is set on incremental
# builds (notably the master production deploy) and empty on a context's
# first build (every deploy preview). The production path diffs against it
# and skips correctly.
#
# Deploy previews need different handling: Netlify checks out a *merge*
# commit, so $COMMIT_REF (the PR head SHA) is frequently not resolvable in
# the clone, and on a shallow clone `git merge-base` can fail too -- so the
# previous logic fell through to a build on every PR, even non-docs ones.
# Instead, always diff the checked-out HEAD against its merge-base with
# master, deepening the shallow clone until that merge-base resolves. If it
# genuinely can't be determined, exit non-zero to build (fail safe).
ignore = 'if [ -n "$CACHED_COMMIT_REF" ]; then git diff --quiet "$CACHED_COMMIT_REF" HEAD -- . ../README.md; else git fetch --no-tags origin master >/dev/null 2>&1 || true; i=0; while [ "$i" -lt 10 ] && ! git merge-base origin/master HEAD >/dev/null 2>&1; do git fetch --deepen=200 origin master >/dev/null 2>&1 || break; i=$((i+1)); done; BASE="$(git merge-base origin/master HEAD 2>/dev/null || true)"; if [ -z "$BASE" ]; then exit 1; fi; git diff --quiet "$BASE" HEAD -- . ../README.md; fi'
# $CACHED_COMMIT_REF is the last *deployed* commit. On a PR's first build it
# is empty, so the original `git diff` errored and Netlify fell back to
# building -- which is why every PR built a docs preview once even with no
# docs changes. When it is empty we instead diff the whole branch against its
# merge-base with master, so non-docs PRs are skipped from the very first
# build. Subsequent builds (and the master production build) keep the cheaper
# incremental $CACHED_COMMIT_REF diff. Any failure exits non-zero -> build.
ignore = 'if [ -n "$CACHED_COMMIT_REF" ]; then git diff --quiet "$CACHED_COMMIT_REF" "$COMMIT_REF" -- . ../README.md; else git fetch origin master --depth=100 >/dev/null 2>&1; git diff --quiet "$(git merge-base origin/master "$COMMIT_REF" 2>/dev/null || echo origin/master)" "$COMMIT_REF" -- . ../README.md; fi'
[build.environment]
# Node version matching docs/.nvmrc

View File

@@ -71,9 +71,9 @@
"@storybook/theming": "^8.6.15",
"@superset-ui/core": "^0.20.4",
"@swc/core": "^1.15.41",
"antd": "^6.4.4",
"baseline-browser-mapping": "^2.10.37",
"caniuse-lite": "^1.0.30001799",
"antd": "^6.4.3",
"baseline-browser-mapping": "^2.10.35",
"caniuse-lite": "^1.0.30001797",
"docusaurus-plugin-openapi-docs": "^5.0.2",
"docusaurus-theme-openapi-docs": "^5.0.2",
"js-yaml": "^4.2.0",
@@ -107,9 +107,9 @@
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react": "^7.37.5",
"globals": "^17.6.0",
"prettier": "^3.8.4",
"prettier": "^3.8.3",
"typescript": "~6.0.3",
"typescript-eslint": "^8.61.1",
"typescript-eslint": "^8.61.0",
"webpack": "^5.107.2"
},
"browserslist": {

View File

@@ -1808,10 +1808,6 @@ If you enable DML in the meta database users will be able to run DML queries on
Second, you might want to change the value of `SUPERSET_META_DB_LIMIT`. The default value is 1000, and defines how many are read from each database before any aggregations and joins are executed. You can also set this value `None` if you only have small tables.
:::warning
`SUPERSET_META_DB_LIMIT` is applied to **each** underlying table *before* the in-memory join runs, not to the final result. If any table involved in a join has more rows than the limit, the meta database will read only the first `SUPERSET_META_DB_LIMIT` rows of that table, which means matching rows can be silently dropped and the join can return **incomplete or even empty** results with no error. If you join tables larger than the limit, raise `SUPERSET_META_DB_LIMIT` to comfortably exceed your largest joined table, or set it to `None` when working only with small tables, to get correct results.
:::
Additionally, you might want to restrict the databases to with the meta database has access to. This can be done in the database configuration, under "Advanced" -> "Other" -> "ENGINE PARAMETERS" and adding:
```json

View File

@@ -212,7 +212,7 @@
resolved "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz"
integrity sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==
"@ant-design/icons@^6.2.5":
"@ant-design/icons@^6.2.3", "@ant-design/icons@^6.2.5":
version "6.2.5"
resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-6.2.5.tgz#31c142aa6ce5eaf99598aaead222f4c459693512"
integrity sha512-0hKtoKqTjGFOndUyJLJmC9Cg6k4rEO7rLo6xmgbNJH+/ZX1C57RVals2v1j1knHl9n7Q+sBOveTvn931wLOCKw==
@@ -3162,21 +3162,21 @@
resolved "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz"
integrity sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==
"@rc-component/async-validator@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@rc-component/async-validator/-/async-validator-6.0.0.tgz#1c20b8864f69bac63b7876b1321697c2ea71c68d"
integrity sha512-D3AGQwdyE58gmvx6waVSXJ80JGO+IY5L2O8HDnSOex7JNlzB3GuN/4hyHNTdhy2qtOhkpbIjmeAN3tL993wKbA==
"@rc-component/async-validator@^5.1.0":
version "5.1.0"
resolved "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz"
integrity sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==
dependencies:
"@babel/runtime" "^7.24.4"
"@rc-component/cascader@~1.16.1":
version "1.16.1"
resolved "https://registry.yarnpkg.com/@rc-component/cascader/-/cascader-1.16.1.tgz#94193ee55009219999a46e005a0f8589c8c021a2"
integrity sha512-wxLopwM+EBed0zNNGdnGE4coYoqcO+XD42fHgn+pDvO+XzhNFbdgSlSNXdKocIYqccvqgWvoxDPNb0OVRdi59A==
"@rc-component/cascader@~1.15.0":
version "1.15.0"
resolved "https://registry.yarnpkg.com/@rc-component/cascader/-/cascader-1.15.0.tgz#554cba8e01e94a1288547cec96422b2cfc73ff40"
integrity sha512-ZzpMtwFCRo3fbXHuDnncARJMZQjdqA2w7aDuPofNQt+aDx39st1hgfIpEwTBLhe2Hqsvs/zOr8RTtgxTkCPySw==
dependencies:
"@rc-component/select" "~1.7.1"
"@rc-component/tree" "~1.3.2"
"@rc-component/util" "^1.11.1"
"@rc-component/select" "~1.6.0"
"@rc-component/tree" "~1.3.0"
"@rc-component/util" "^1.4.0"
clsx "^2.1.1"
"@rc-component/checkbox@~2.0.0":
@@ -3242,13 +3242,13 @@
"@rc-component/util" "^1.2.1"
clsx "^2.1.1"
"@rc-component/form@~1.8.3":
version "1.8.5"
resolved "https://registry.yarnpkg.com/@rc-component/form/-/form-1.8.5.tgz#20571cfd401dc38c74c38cdf4722ddc6c23a9806"
integrity sha512-d24EYtvUOBhxEtSd/EqIu9DaMuqrWF2IRIvAFCTM6NQ/GJIYNr8DvEpUSUlv2uPxEJ0ZPwYQ+wwlGIAaiHvdrw==
"@rc-component/form@~1.8.1":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@rc-component/form/-/form-1.8.1.tgz#d811fb52df41bf72297938ebfe5cf4a4774588d4"
integrity sha512-8O7TB55Fi2mWIGvSnwZjk8jFqVNYyKDAswglwGShcbndxqzKz4cHwNtNaLjZlAeRge9wcB0LL8IWsC/Bl18raQ==
dependencies:
"@rc-component/async-validator" "^6.0.0"
"@rc-component/util" "^1.11.1"
"@rc-component/async-validator" "^5.1.0"
"@rc-component/util" "^1.6.2"
clsx "^2.1.1"
"@rc-component/image@~1.9.0":
@@ -3270,13 +3270,13 @@
"@rc-component/util" "^1.4.0"
clsx "^2.1.1"
"@rc-component/input@~1.3.0", "@rc-component/input@~1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@rc-component/input/-/input-1.3.1.tgz#230b8b59cdde8521d50f0eede63ddacb61cc0cd3"
integrity sha512-iFvTUT9W+JC/MSin2aGAk8NqsVlTzcExNC9DZariON1IWirju9NoNeEk47an4Q8iHazkoVI/y1LnDi88+CPcig==
"@rc-component/input@~1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@rc-component/input/-/input-1.3.0.tgz#a8c113000bbc39089cf75337bec68120115b9e05"
integrity sha512-IUUNOdAuWuEvDEFFgfmwQl818tiDbvXwLgon4HL1q2hJeYkqrRrYwYhJN0zfPHGTDxs3gvyVC/C02D4hWFoIcA==
dependencies:
"@rc-component/resize-observer" "^1.1.1"
"@rc-component/util" "^1.11.1"
"@rc-component/util" "^1.4.0"
clsx "^2.1.1"
"@rc-component/mentions@~1.9.0":
@@ -3290,15 +3290,15 @@
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
"@rc-component/menu@~1.3.0", "@rc-component/menu@~1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@rc-component/menu/-/menu-1.3.1.tgz#16cae71a01080914e8bac08359fccdda7bfce540"
integrity sha512-pSZl9nBPgKgxN0aaW7NilIBEwWsc+43S+ulGdWAg9afak96dNOGWsGx0DLLBB1VQsAJvo6bQMTDzXoPlEHsBEw==
"@rc-component/menu@~1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@rc-component/menu/-/menu-1.3.0.tgz#fc70d81ca76ae6013b0d7955f20a2393adef04b3"
integrity sha512-u3NfiwpiEgT177qa5Yxm5QsI8i/93EBGpWj8HYZQDnh2pCZ2xtQCe/+w3pSR2NlwKOZDTCKzEhEyD09mGphssA==
dependencies:
"@rc-component/motion" "^1.1.4"
"@rc-component/overflow" "^1.0.0"
"@rc-component/trigger" "^3.0.0"
"@rc-component/util" "^1.11.1"
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
"@rc-component/mini-decimal@^1.0.1":
@@ -3308,12 +3308,12 @@
dependencies:
"@babel/runtime" "^7.18.0"
"@rc-component/motion@^1.0.0", "@rc-component/motion@^1.1.3", "@rc-component/motion@^1.1.4", "@rc-component/motion@^1.3.3":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@rc-component/motion/-/motion-1.3.3.tgz#6a7bbe0a9f070bd11642168f741d40e78bb28565"
integrity sha512-Xh3IszxvlSv3/PLYFyC2UZi9LNB83yOnkB/LNmRzaypZLvkhqUIPS7MQpGZcCMWrNsXV2p6YTSWbSGvFpEle9A==
"@rc-component/motion@^1.0.0", "@rc-component/motion@^1.1.3", "@rc-component/motion@^1.1.4", "@rc-component/motion@^1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@rc-component/motion/-/motion-1.3.2.tgz#bd96e0fd16ee9d98c1d9be14198f003e367d8feb"
integrity sha512-itfd+GztzJYAb04Z4RkEub1TbJAfZc2Iuy8p44U44xD1F5+fNYFKI3897ijlbIyfvXkTmMm+KGcjkQQGMHywEQ==
dependencies:
"@rc-component/util" "^1.11.0"
"@rc-component/util" "^1.2.0"
clsx "^2.1.1"
"@rc-component/mutate-observer@^2.0.1":
@@ -3342,12 +3342,12 @@
"@rc-component/util" "^1.4.0"
clsx "^2.1.1"
"@rc-component/pagination@~1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@rc-component/pagination/-/pagination-1.3.0.tgz#ee66301e37a03974826fb3028a91a1aeacdcd0ac"
integrity sha512-12ahTY+HPITg1L2bjWKXUqBJe/oOnpA2QsChdCjthqLVf/e19StiCsv8OLKpWoHbc+8PFEkNjRqRqrLoRBHjFw==
"@rc-component/pagination@~1.2.0":
version "1.2.0"
resolved "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.2.0.tgz"
integrity sha512-YcpUFE8dMLfSo6OARJlK6DbHHvrxz7pMGPGmC/caZSJJz6HRKHC1RPP001PRHCvG9Z/veD039uOQmazVuLJzlw==
dependencies:
"@rc-component/util" "^1.11.1"
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
"@rc-component/picker@~1.10.0":
@@ -3377,10 +3377,10 @@
"@rc-component/util" "^1.2.1"
clsx "^2.1.1"
"@rc-component/qrcode@~2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@rc-component/qrcode/-/qrcode-2.0.0.tgz#ef4134c213002e7a43edbe609b24248914de4974"
integrity sha512-aAv3QhPP1xyafuTZOxub6a54pCeBnN3IwQkpETrBtthq4BL5IgxnCbuoBWPDpdLw1y1j6BgBUCAKV92+yX06Dw==
"@rc-component/qrcode@~1.1.1":
version "1.1.1"
resolved "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz"
integrity sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==
dependencies:
"@babel/runtime" "^7.24.7"
@@ -3409,15 +3409,15 @@
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
"@rc-component/select@~1.7.0", "@rc-component/select@~1.7.1":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@rc-component/select/-/select-1.7.1.tgz#cdda0ac185f00ebed1c85e7809ae1f7855a9f7ab"
integrity sha512-GZ1cMJk2xQh0VHyOQjjG8drYL4iu24NcbkXioUcReQOCUr+ub/3fmRonZe6cRPEZhWMbJdeHsqnEltogDaZ5Tg==
"@rc-component/select@~1.6.0", "@rc-component/select@~1.6.15":
version "1.6.15"
resolved "https://registry.yarnpkg.com/@rc-component/select/-/select-1.6.15.tgz#de2a3c8b020834cabd600b52de573a328c061eef"
integrity sha512-SyVCWnqxCQZZcQvQJ/CxSjx2bGma6ds/HtnpkIfZVnt6RoEgbqUmHgD6vrzNarNXwbLXerwVzWwq8F3d1sst7g==
dependencies:
"@rc-component/overflow" "^1.0.0"
"@rc-component/trigger" "^3.0.0"
"@rc-component/util" "^1.11.1"
"@rc-component/virtual-list" "^1.2.0"
"@rc-component/util" "^1.3.0"
"@rc-component/virtual-list" "^1.0.1"
clsx "^2.1.1"
"@rc-component/slider@~1.0.1":
@@ -3444,27 +3444,27 @@
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
"@rc-component/table@~1.10.2":
version "1.10.2"
resolved "https://registry.yarnpkg.com/@rc-component/table/-/table-1.10.2.tgz#7b052fd5eb2ccf6996a93eebeb7af7036a262c8b"
integrity sha512-b3PjqB9Gp25p5t/zq+9QrbXbodkptT8/zvLmwgd2FNPUUtaYyDnQqfxeD5a7ao8E8lpinLHsi2u2vdfPhyNvAw==
"@rc-component/table@~1.10.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@rc-component/table/-/table-1.10.0.tgz#7a98d68176f23f50a762df464f4c9142e7db3942"
integrity sha512-SjtpcCf+rL7dDc62GKT3rXTdERjVuJvRiqjpU7g0Jc/ewCifXynHc7Nm3Em1XsD+WhGrgQtxNDScI/0+Lpfr0w==
dependencies:
"@rc-component/context" "^2.0.1"
"@rc-component/resize-observer" "^1.0.0"
"@rc-component/util" "^1.11.1"
"@rc-component/util" "^1.1.0"
"@rc-component/virtual-list" "^1.0.1"
clsx "^2.1.1"
"@rc-component/tabs@~1.9.1":
version "1.9.1"
resolved "https://registry.yarnpkg.com/@rc-component/tabs/-/tabs-1.9.1.tgz#52b9cb0392c718fba43e7d558f46f6c910d19acb"
integrity sha512-6mY08Fce6aNOHuGsxbzT+f2ekgL9mg1cGGHkittMlVGymjGg+kGupu5v90sRxcUd/paRU9jclLLXtF/PkK1FUA==
"@rc-component/tabs@~1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@rc-component/tabs/-/tabs-1.9.0.tgz#8f3e3755450e5a90d240d1ed3dc140d520b1fbef"
integrity sha512-tn1slmbbaTyt8mgwyWJcT8jo/qNiYUs6u1H7OgGQt9faYO06BJIkU5cTmMqORzIrNmSEeeUY6pD5i+JlqSHYhg==
dependencies:
"@rc-component/dropdown" "~1.0.0"
"@rc-component/menu" "~1.3.0"
"@rc-component/motion" "^1.1.3"
"@rc-component/resize-observer" "^1.0.0"
"@rc-component/util" "^1.11.1"
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
"@rc-component/tooltip@~1.4.0":
@@ -3486,30 +3486,30 @@
"@rc-component/util" "^1.7.0"
clsx "^2.1.1"
"@rc-component/tree-select@~1.10.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@rc-component/tree-select/-/tree-select-1.10.0.tgz#72e337fd58591f677404189cb3e9d3f718cd3332"
integrity sha512-E1U4pn2LAbXEhLJdzIzid7WYbIuFbkTIctuFoeC6weppf8UbPR3+YYB6/ay0c0ksand4gXMRQpa1Z60Auo7VJA==
"@rc-component/tree-select@~1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@rc-component/tree-select/-/tree-select-1.9.0.tgz#13ea516478b6cb558e04181abb0a01ae6fbdd31f"
integrity sha512-GXcFe15a+trUl1/J3OHWQhsVWFpwFpGFK2cqYWZ1sK22Zs3KZTvMwDpzr75PIo1s6QVioVxpE/pRwRopkeDQ6w==
dependencies:
"@rc-component/select" "~1.7.0"
"@rc-component/select" "~1.6.0"
"@rc-component/tree" "~1.3.0"
"@rc-component/util" "^1.4.0"
clsx "^2.1.1"
"@rc-component/tree@~1.3.0", "@rc-component/tree@~1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@rc-component/tree/-/tree-1.3.2.tgz#4b0c13564314eff61ca948c18ef923b87c9d7e44"
integrity sha512-bJFj46wEkpBPnWyTm18XmgAgNQ/4YvprxMOPPY2a6rmhGJYxLuNKEFiL5Qej4Qctu9wHJm8WW+v2SYskafE0kA==
"@rc-component/tree@~1.3.0", "@rc-component/tree@~1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@rc-component/tree/-/tree-1.3.1.tgz#6983ca6bd9d5f6d04dd7258d00cb0fe71cdfe661"
integrity sha512-zlL0PW0bTFlveTtLcA01VD/yMWKK73EywItFMgIZUY5sb6tMOAw7zV6qGzqldufqrV93ZWQB4H3NBNoTMCueJA==
dependencies:
"@rc-component/motion" "^1.0.0"
"@rc-component/util" "^1.11.1"
"@rc-component/virtual-list" "^1.2.0"
"@rc-component/util" "^1.8.1"
"@rc-component/virtual-list" "^1.0.1"
clsx "^2.1.1"
"@rc-component/trigger@^3.0.0", "@rc-component/trigger@^3.6.15", "@rc-component/trigger@^3.7.1", "@rc-component/trigger@^3.9.1":
version "3.9.1"
resolved "https://registry.yarnpkg.com/@rc-component/trigger/-/trigger-3.9.1.tgz#3f730315c558bc392921a563ac9109a1d094ef23"
integrity sha512-LNsYvz60mrLJ/kRvKcHE7boUvcQfVMCfRqZ71x3Fo9AOiZ1KKIEqkzMA8DNvz2V3Bcvir/vwQNn7JF1NPODQ7Q==
"@rc-component/trigger@^3.0.0", "@rc-component/trigger@^3.6.15", "@rc-component/trigger@^3.7.1", "@rc-component/trigger@^3.9.0":
version "3.9.0"
resolved "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.9.0.tgz"
integrity sha512-X8btpwfrT27AgrZVOz4swclhEHTZcqaHeQMXXBgveagOiakTa36uObXbdwerXffgV8G9dH1fAAE0DHtVQs8EHg==
dependencies:
"@rc-component/motion" "^1.1.4"
"@rc-component/portal" "^2.2.0"
@@ -3517,18 +3517,18 @@
"@rc-component/util" "^1.2.1"
clsx "^2.1.1"
"@rc-component/upload@~1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@rc-component/upload/-/upload-1.1.1.tgz#90d16edcdaeb104ffa9111fd0b428061de7c21ac"
integrity sha512-GvYWSKeaJTOxxC5p6+nOSadzfvXA1h8C/iHFPFZX+szH3JUXrvs+DLiW8YUTBgvMh8m63mJeHrlYlJzAlg+pDA==
"@rc-component/upload@~1.1.0":
version "1.1.0"
resolved "https://registry.npmjs.org/@rc-component/upload/-/upload-1.1.0.tgz"
integrity sha512-LIBV90mAnUE6VK5N4QvForoxZc4XqEYZimcp7fk+lkE4XwHHyJWxpIXQQwMU8hJM+YwBbsoZkGksL1sISWHQxw==
dependencies:
"@rc-component/util" "^1.11.1"
"@rc-component/util" "^1.3.0"
clsx "^2.1.1"
"@rc-component/util@^1.10.1", "@rc-component/util@^1.11.0", "@rc-component/util@^1.11.1", "@rc-component/util@^1.2.0", "@rc-component/util@^1.2.1", "@rc-component/util@^1.3.0", "@rc-component/util@^1.4.0", "@rc-component/util@^1.7.0", "@rc-component/util@^1.9.0":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@rc-component/util/-/util-1.11.1.tgz#07d698908339c55648e4f974afa739345e65b483"
integrity sha512-awVlI3ub2vqfqkYxOBc/uQ0efm3jw0wcrhtO/YWLyZfxiKXczKwNbVuhlnyxytDt7H9pbbVQiqr+O6MLATtRYg==
"@rc-component/util@^1.1.0", "@rc-component/util@^1.10.1", "@rc-component/util@^1.11.0", "@rc-component/util@^1.2.0", "@rc-component/util@^1.2.1", "@rc-component/util@^1.3.0", "@rc-component/util@^1.4.0", "@rc-component/util@^1.6.2", "@rc-component/util@^1.7.0", "@rc-component/util@^1.8.1", "@rc-component/util@^1.9.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@rc-component/util/-/util-1.11.0.tgz#965c8b44a3f57fc96dc14e5072afbe32e422fd4d"
integrity sha512-jHG3/BYgUWiP5c7RZHiaUNToyw1L3nlPSKG2RPu+YoiD9b3ajiJwBWhsjO+ZELmCsKFAjNR5DelbKdlF0e2BDA==
dependencies:
is-mobile "^5.0.0"
react-is "^18.2.0"
@@ -3543,16 +3543,6 @@
"@rc-component/util" "^1.4.0"
clsx "^2.1.1"
"@rc-component/virtual-list@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@rc-component/virtual-list/-/virtual-list-1.2.0.tgz#3c9ee8cd7d10da334a2a06ad86ca234e2792a381"
integrity sha512-iavRm1Jo4GDbASQwdGa7jFyk93RvSOo9xHyBT4QL1pgFJj/Fdf1G+3RErH7/7BmAMvx2AkF62mjGYxDbXsK9TQ==
dependencies:
"@babel/runtime" "^7.20.0"
"@rc-component/resize-observer" "^1.0.1"
"@rc-component/util" "^1.4.0"
clsx "^2.1.1"
"@redocly/ajv@^8.18.0":
version "8.18.3"
resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.18.3.tgz#a925753d9a33375219f1b2ba91aef320f9929577"
@@ -4932,110 +4922,110 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@8.61.1", "@typescript-eslint/eslint-plugin@^8.59.3":
version "8.61.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz#6e4b7fee21f1983308e9e9b634ecbaf702c86006"
integrity sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==
"@typescript-eslint/eslint-plugin@8.61.0", "@typescript-eslint/eslint-plugin@^8.59.3":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz#db20271974b94a3a54d3b9544e5f5b3481448400"
integrity sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==
dependencies:
"@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.61.1"
"@typescript-eslint/type-utils" "8.61.1"
"@typescript-eslint/utils" "8.61.1"
"@typescript-eslint/visitor-keys" "8.61.1"
"@typescript-eslint/scope-manager" "8.61.0"
"@typescript-eslint/type-utils" "8.61.0"
"@typescript-eslint/utils" "8.61.0"
"@typescript-eslint/visitor-keys" "8.61.0"
ignore "^7.0.5"
natural-compare "^1.4.0"
ts-api-utils "^2.5.0"
"@typescript-eslint/parser@8.61.1", "@typescript-eslint/parser@^8.61.0":
version "8.61.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.61.1.tgz#881fba60b50636249cdeea2e547bf75715254c72"
integrity sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==
"@typescript-eslint/parser@8.61.0", "@typescript-eslint/parser@^8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.61.0.tgz#1afe73c9ccce16b7a26d6b95f9400b0ccc34af87"
integrity sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==
dependencies:
"@typescript-eslint/scope-manager" "8.61.1"
"@typescript-eslint/types" "8.61.1"
"@typescript-eslint/typescript-estree" "8.61.1"
"@typescript-eslint/visitor-keys" "8.61.1"
"@typescript-eslint/scope-manager" "8.61.0"
"@typescript-eslint/types" "8.61.0"
"@typescript-eslint/typescript-estree" "8.61.0"
"@typescript-eslint/visitor-keys" "8.61.0"
debug "^4.4.3"
"@typescript-eslint/project-service@8.61.1":
version "8.61.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.61.1.tgz#fcd9739964a40867eed55f1ac318d3909f24b4af"
integrity sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==
"@typescript-eslint/project-service@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.61.0.tgz#417a2feac32e8ebd336d63f068c3b42b736ea1ac"
integrity sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.61.1"
"@typescript-eslint/types" "^8.61.1"
"@typescript-eslint/tsconfig-utils" "^8.61.0"
"@typescript-eslint/types" "^8.61.0"
debug "^4.4.3"
"@typescript-eslint/scope-manager@8.61.1":
version "8.61.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz#2479921a40fdb0afa18f5838fae6167264b417b2"
integrity sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==
"@typescript-eslint/scope-manager@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.61.0.tgz#93c2520d05653fe65eb9ee98efc74fd0134a7852"
integrity sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==
dependencies:
"@typescript-eslint/types" "8.61.1"
"@typescript-eslint/visitor-keys" "8.61.1"
"@typescript-eslint/types" "8.61.0"
"@typescript-eslint/visitor-keys" "8.61.0"
"@typescript-eslint/tsconfig-utils@8.61.1":
"@typescript-eslint/tsconfig-utils@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.0.tgz#05d6e3ff20001674ebcd22d03dac29ee448043ba"
integrity sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==
"@typescript-eslint/tsconfig-utils@^8.61.0":
version "8.61.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz#ca88080e0cf191d49516d7f300b67aa090d2254f"
integrity sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==
"@typescript-eslint/tsconfig-utils@^8.61.1":
version "8.62.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz#9440a673581c6d9de308c4d5803dd52ed5d71729"
integrity sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==
"@typescript-eslint/type-utils@8.61.1":
version "8.61.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz#8fa18f453ee140893b47d339d1a6b64cac9b08a1"
integrity sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==
"@typescript-eslint/type-utils@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.61.0.tgz#50219b57e6b89cecfb1a15f093b15ec9ee019974"
integrity sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==
dependencies:
"@typescript-eslint/types" "8.61.1"
"@typescript-eslint/typescript-estree" "8.61.1"
"@typescript-eslint/utils" "8.61.1"
"@typescript-eslint/types" "8.61.0"
"@typescript-eslint/typescript-estree" "8.61.0"
"@typescript-eslint/utils" "8.61.0"
debug "^4.4.3"
ts-api-utils "^2.5.0"
"@typescript-eslint/types@8.61.1":
"@typescript-eslint/types@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.61.0.tgz#0ddb46e012a4288292950bdd253db42f278ce64d"
integrity sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==
"@typescript-eslint/types@^8.61.0":
version "8.61.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.61.1.tgz#0c51f518e4e6848371a1c988e859d59eb7522d5a"
integrity sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==
"@typescript-eslint/types@^8.61.1":
version "8.62.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.62.0.tgz#601427c10203d9f0f34f0b3e474df735eb12b593"
integrity sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==
"@typescript-eslint/typescript-estree@8.61.1":
version "8.61.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz#febbe70365ac0bf7611262b61b338fc8797965c7"
integrity sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==
"@typescript-eslint/typescript-estree@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.0.tgz#98ca47260bbf627fc28f018b3a0abf00e3090690"
integrity sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==
dependencies:
"@typescript-eslint/project-service" "8.61.1"
"@typescript-eslint/tsconfig-utils" "8.61.1"
"@typescript-eslint/types" "8.61.1"
"@typescript-eslint/visitor-keys" "8.61.1"
"@typescript-eslint/project-service" "8.61.0"
"@typescript-eslint/tsconfig-utils" "8.61.0"
"@typescript-eslint/types" "8.61.0"
"@typescript-eslint/visitor-keys" "8.61.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.61.1":
version "8.61.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.61.1.tgz#ffd1054de7dd33b7873cd6c6713ec6b0366316d3"
integrity sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==
"@typescript-eslint/utils@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.61.0.tgz#ed3546a052787e84ea6c5064d0919fc5eea8522f"
integrity sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==
dependencies:
"@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.61.1"
"@typescript-eslint/types" "8.61.1"
"@typescript-eslint/typescript-estree" "8.61.1"
"@typescript-eslint/scope-manager" "8.61.0"
"@typescript-eslint/types" "8.61.0"
"@typescript-eslint/typescript-estree" "8.61.0"
"@typescript-eslint/visitor-keys@8.61.1":
version "8.61.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz#546cf102b4efdb72a9a08e63a1b0d7d745eb66eb"
integrity sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==
"@typescript-eslint/visitor-keys@8.61.0":
version "8.61.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.0.tgz#39b4e1ab8936d23bea973d39fd092f9aa21f275e"
integrity sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==
dependencies:
"@typescript-eslint/types" "8.61.1"
"@typescript-eslint/types" "8.61.0"
eslint-visitor-keys "^5.0.0"
"@ungap/structured-clone@^1.0.0":
@@ -5392,54 +5382,54 @@ ansis@^3.2.0:
resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.17.0.tgz#fa8d9c2a93fe7d1177e0c17f9eeb562a58a832d7"
integrity sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==
antd@^6.4.4:
version "6.4.4"
resolved "https://registry.yarnpkg.com/antd/-/antd-6.4.4.tgz#a422610959b37ac4d4b766dbaac67ea2d8fd0785"
integrity sha512-lgPz4KhfhiYddV/qPYo0ieqWimCVgV2OQF72mbeGNixE753JWNnmEc7UNGy08wBS/zZ7hxrmX0pc5aX7EUaIIg==
antd@^6.4.3:
version "6.4.3"
resolved "https://registry.yarnpkg.com/antd/-/antd-6.4.3.tgz#80a7aab9c13c35daa0e0e7eea80585ba57cb7203"
integrity sha512-6H2avkxCGfxcF67r3J2mwm9Ck50el1pks/73vfM1wDsPL/tPtj5vHuauMgJFnrqmq7CH3g8aoZ0VBQbt+jpAsw==
dependencies:
"@ant-design/colors" "^8.0.1"
"@ant-design/cssinjs" "^2.1.2"
"@ant-design/cssinjs-utils" "^2.1.2"
"@ant-design/fast-color" "^3.0.1"
"@ant-design/icons" "^6.2.5"
"@ant-design/icons" "^6.2.3"
"@ant-design/react-slick" "~2.0.0"
"@babel/runtime" "^7.29.2"
"@rc-component/cascader" "~1.16.1"
"@rc-component/cascader" "~1.15.0"
"@rc-component/checkbox" "~2.0.0"
"@rc-component/collapse" "~1.2.0"
"@rc-component/color-picker" "~3.1.1"
"@rc-component/dialog" "~1.9.0"
"@rc-component/drawer" "~1.4.2"
"@rc-component/dropdown" "~1.0.2"
"@rc-component/form" "~1.8.3"
"@rc-component/form" "~1.8.1"
"@rc-component/image" "~1.9.0"
"@rc-component/input" "~1.3.1"
"@rc-component/input" "~1.3.0"
"@rc-component/input-number" "~1.6.2"
"@rc-component/mentions" "~1.9.0"
"@rc-component/menu" "~1.3.1"
"@rc-component/motion" "^1.3.3"
"@rc-component/menu" "~1.3.0"
"@rc-component/motion" "^1.3.2"
"@rc-component/mutate-observer" "^2.0.1"
"@rc-component/notification" "~2.0.7"
"@rc-component/pagination" "~1.3.0"
"@rc-component/pagination" "~1.2.0"
"@rc-component/picker" "~1.10.0"
"@rc-component/progress" "~1.0.2"
"@rc-component/qrcode" "~2.0.0"
"@rc-component/qrcode" "~1.1.1"
"@rc-component/rate" "~1.0.1"
"@rc-component/resize-observer" "^1.1.2"
"@rc-component/segmented" "~1.3.0"
"@rc-component/select" "~1.7.1"
"@rc-component/select" "~1.6.15"
"@rc-component/slider" "~1.0.1"
"@rc-component/steps" "~1.2.2"
"@rc-component/switch" "~1.0.3"
"@rc-component/table" "~1.10.2"
"@rc-component/tabs" "~1.9.1"
"@rc-component/table" "~1.10.0"
"@rc-component/tabs" "~1.9.0"
"@rc-component/tooltip" "~1.4.0"
"@rc-component/tour" "~2.4.0"
"@rc-component/tree" "~1.3.2"
"@rc-component/tree-select" "~1.10.0"
"@rc-component/trigger" "^3.9.1"
"@rc-component/upload" "~1.1.1"
"@rc-component/util" "^1.11.1"
"@rc-component/tree" "~1.3.1"
"@rc-component/tree-select" "~1.9.0"
"@rc-component/trigger" "^3.9.0"
"@rc-component/upload" "~1.1.0"
"@rc-component/util" "^1.11.0"
clsx "^2.1.1"
dayjs "^1.11.11"
scroll-into-view-if-needed "^3.1.0"
@@ -5698,10 +5688,10 @@ base64-js@^1.3.1, base64-js@^1.5.1:
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
baseline-browser-mapping@^2.10.37, baseline-browser-mapping@^2.9.0, baseline-browser-mapping@^2.9.19:
version "2.10.37"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz#3e636475b6b293244e2b23e2c71a2ab9d9e6ba7d"
integrity sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==
baseline-browser-mapping@^2.10.35, baseline-browser-mapping@^2.9.0, baseline-browser-mapping@^2.9.19:
version "2.10.35"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.35.tgz#f0f2232e0de2d2f82cc491bcf830b05ed05937c6"
integrity sha512-honAfLBde0HAFLdNyBEfuuENkF6zR+ozxqxa/2zJKHBe1qzLqyTSeRKpdPEHAP03rlDGyQOPnCSxnVpVqQo9Mg==
batch@0.6.1:
version "0.6.1"
@@ -5944,10 +5934,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001799:
version "1.0.30001799"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz#5c909138c27f1a61219d3e092071c1cc7d32dc55"
integrity sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001797:
version "1.0.30001797"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz#1332709e1439f01ff92085dd17001e0a45897ec0"
integrity sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==
ccount@^2.0.0:
version "2.0.1"
@@ -7262,10 +7252,17 @@ domhandler@^5.0.2, domhandler@^5.0.3:
dependencies:
domelementtype "^2.3.0"
dompurify@^3.3.1, dompurify@^3.4.0:
version "3.4.11"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.11.tgz#29c8ba496475f279ef4015784068452fb14a0680"
integrity sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==
dompurify@^3.3.1:
version "3.4.2"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.2.tgz#f0ff81be682c485505097ba8195a058d8f575218"
integrity sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==
optionalDependencies:
"@types/trusted-types" "^2.0.7"
dompurify@^3.4.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.1.tgz#521d04483ac12631b2aedf434a5f5390933b8789"
integrity sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==
optionalDependencies:
"@types/trusted-types" "^2.0.7"
@@ -8768,9 +8765,9 @@ http-parser-js@>=0.5.1:
integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==
http-proxy-middleware@^2.0.9:
version "2.0.10"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz#b2df7b705203d7a8c269ac8450cf96b00c532f94"
integrity sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==
version "2.0.9"
resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz"
integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==
dependencies:
"@types/http-proxy" "^1.17.8"
http-proxy "^1.18.1"
@@ -12273,10 +12270,10 @@ prettier-linter-helpers@^1.0.1:
dependencies:
fast-diff "^1.1.2"
prettier@^3.8.4:
version "3.8.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.4.tgz#f334f013ac04a96676f24dabc23c1c4ae1bae411"
integrity sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==
prettier@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.3.tgz#560f2de55bf01b4c0503bc629d5df99b9a1d09b0"
integrity sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==
pretty-error@^4.0.0:
version "4.0.0"
@@ -14502,15 +14499,15 @@ types-ramda@^0.30.1:
dependencies:
ts-toolbelt "^9.6.0"
typescript-eslint@^8.61.1:
version "8.61.1"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.61.1.tgz#7c224a9a643b7f42d295c67a75c1e30fee8c3eaa"
integrity sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==
typescript-eslint@^8.61.0:
version "8.61.0"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.61.0.tgz#6927fb94f5f29623e370d33fd9fa61f15d6d996b"
integrity sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==
dependencies:
"@typescript-eslint/eslint-plugin" "8.61.1"
"@typescript-eslint/parser" "8.61.1"
"@typescript-eslint/typescript-estree" "8.61.1"
"@typescript-eslint/utils" "8.61.1"
"@typescript-eslint/eslint-plugin" "8.61.0"
"@typescript-eslint/parser" "8.61.0"
"@typescript-eslint/typescript-estree" "8.61.0"
"@typescript-eslint/utils" "8.61.0"
typescript@~6.0.3:
version "6.0.3"
@@ -15009,9 +15006,9 @@ webpack-dev-middleware@^7.4.2:
schema-utils "^4.0.0"
webpack-dev-server@^5.2.2:
version "5.2.5"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.5.tgz#648fceaac6a5736b0935e5c1e55d6aa1d0626119"
integrity sha512-4wZtCquSuv9CKX8oybo+mqxtxZqWz47uM1Ch94lxowBztOhWCbhqvRbfC/mODOwxgV2brY+JGZpHq58/SuVFYg==
version "5.2.4"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.4.tgz#6e6306ce59848ed322c235e48b326632b1eed6d6"
integrity sha512-GqDPGZN9bRqKBTkp4aWkobDDHMsrXKoGSdOH56smIri8qR0JG8gfL8/v/f/OZR3/OKXjG8uwJbFVhKm/FNU/UA==
dependencies:
"@types/bonjour" "^3.5.13"
"@types/connect-history-api-fallback" "^1.5.4"

View File

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

View File

@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
# superset
![Version: 0.17.2](https://img.shields.io/badge/Version-0.17.2-informational?style=flat-square)
![Version: 0.16.1](https://img.shields.io/badge/Version-0.16.1-informational?style=flat-square)
Apache Superset is a modern, enterprise-ready business intelligence web application
@@ -111,6 +111,9 @@ On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverri
| init.resources | object | `{}` | |
| init.tolerations | list | `[]` | |
| init.topologySpreadConstraints | list | `[]` | TopologySpreadConstrains to be added to init job |
| initImage.pullPolicy | string | `"IfNotPresent"` | |
| initImage.repository | string | `"apache/superset"` | |
| initImage.tag | string | `"dockerize"` | |
| nameOverride | string | `nil` | Provide a name to override the name of the chart |
| nodeSelector | object | `{}` | |
| postgresql | object | see `values.yaml` | Configuration values for the postgresql dependency. ref: https://github.com/bitnami/charts/tree/main/bitnami/postgresql |
@@ -216,7 +219,6 @@ On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverri
| supersetNode.extraContainers | list | `[]` | Launch additional containers into supersetNode pod |
| 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 |
| supersetNode.livenessProbe.failureThreshold | int | `3` | |
| supersetNode.livenessProbe.httpGet.path | string | `"/health"` | |
| supersetNode.livenessProbe.httpGet.port | string | `"http"` | |
@@ -249,7 +251,6 @@ On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverri
| supersetNode.startupProbe.successThreshold | int | `1` | |
| supersetNode.startupProbe.timeoutSeconds | int | `1` | |
| supersetNode.strategy | object | `{}` | |
| supersetNode.terminationGracePeriodSeconds | string | `nil` | Pod termination grace period (seconds). Set greater than GUNICORN_TIMEOUT so in-flight requests can drain before SIGKILL |
| supersetNode.topologySpreadConstraints | list | `[]` | TopologySpreadConstrains to be added to supersetNode deployments |
| supersetWebsockets.affinity | object | `{}` | Affinity to be added to supersetWebsockets deployment |
| supersetWebsockets.command | list | `[]` | |
@@ -313,7 +314,6 @@ On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverri
| supersetWorker.extraContainers | list | `[]` | Launch additional containers into supersetWorker pod |
| supersetWorker.forceReload | bool | `false` | If true, forces deployment to reload on each upgrade |
| 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 |
| supersetWorker.livenessProbe.failureThreshold | int | `3` | |
| supersetWorker.livenessProbe.initialDelaySeconds | int | `120` | |
@@ -334,7 +334,6 @@ On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverri
| supersetWorker.resources | object | `{}` | Resource settings for the supersetWorker pods - these settings overwrite might existing values from the global resources object defined above. |
| supersetWorker.startupProbe | object | `{}` | No startup/readiness probes by default since we don't really care about its startup time (it doesn't serve traffic) |
| supersetWorker.strategy | object | `{}` | |
| supersetWorker.terminationGracePeriodSeconds | string | `nil` | Pod termination grace period (seconds) for the worker pod so in-flight tasks can drain before SIGKILL |
| supersetWorker.topologySpreadConstraints | list | `[]` | TopologySpreadConstrains to be added to supersetWorker deployments |
| tolerations | list | `[]` | |
| topologySpreadConstraints | list | `[]` | TopologySpreadConstrains to be added to all deployments |

View File

@@ -126,7 +126,7 @@ spec:
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
{{- if .Values.supersetCeleryBeat.extraContainers }}
{{- tpl (toYaml .Values.supersetCeleryBeat.extraContainers) . | nindent 8 }}
{{- toYaml .Values.supersetCeleryBeat.extraContainers | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}

View File

@@ -121,7 +121,7 @@ spec:
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
{{- if .Values.supersetCeleryFlower.extraContainers }}
{{- tpl (toYaml .Values.supersetCeleryFlower.extraContainers) . | nindent 8 }}
{{- toYaml .Values.supersetCeleryFlower.extraContainers | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}

View File

@@ -134,9 +134,6 @@ spec:
{{- if .Values.supersetWorker.livenessProbe }}
livenessProbe: {{- .Values.supersetWorker.livenessProbe | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.supersetWorker.lifecycle }}
lifecycle: {{- .Values.supersetWorker.lifecycle | toYaml | nindent 12 }}
{{- end }}
resources:
{{- if .Values.supersetWorker.resources }}
{{- toYaml .Values.supersetWorker.resources | nindent 12 }}
@@ -144,7 +141,7 @@ spec:
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
{{- if .Values.supersetWorker.extraContainers }}
{{- tpl (toYaml .Values.supersetWorker.extraContainers) . | nindent 8 }}
{{- toYaml .Values.supersetWorker.extraContainers | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}
@@ -173,9 +170,6 @@ spec:
{{- with .Values.tolerations }}
tolerations: {{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.supersetWorker.terminationGracePeriodSeconds }}
terminationGracePeriodSeconds: {{ .Values.supersetWorker.terminationGracePeriodSeconds }}
{{- end }}
{{- if .Values.imagePullSecrets }}
imagePullSecrets: {{- toYaml .Values.imagePullSecrets | nindent 8 }}
{{- end }}

View File

@@ -120,7 +120,7 @@ spec:
livenessProbe: {{- .Values.supersetWebsockets.livenessProbe | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.supersetWebsockets.extraContainers }}
{{- tpl (toYaml .Values.supersetWebsockets.extraContainers) . | nindent 8 }}
{{- toYaml .Values.supersetWebsockets.extraContainers | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}

View File

@@ -144,9 +144,6 @@ spec:
{{- if .Values.supersetNode.livenessProbe }}
livenessProbe: {{- .Values.supersetNode.livenessProbe | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.supersetNode.lifecycle }}
lifecycle: {{- .Values.supersetNode.lifecycle | toYaml | nindent 12 }}
{{- end }}
resources:
{{- if .Values.supersetNode.resources }}
{{- toYaml .Values.supersetNode.resources | nindent 12 }}
@@ -154,7 +151,7 @@ spec:
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
{{- if .Values.supersetNode.extraContainers }}
{{- tpl (toYaml .Values.supersetNode.extraContainers) . | nindent 8 }}
{{- toYaml .Values.supersetNode.extraContainers | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}
@@ -183,9 +180,6 @@ spec:
{{- with .Values.tolerations }}
tolerations: {{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.supersetNode.terminationGracePeriodSeconds }}
terminationGracePeriodSeconds: {{ .Values.supersetNode.terminationGracePeriodSeconds }}
{{- end }}
{{- if .Values.imagePullSecrets }}
imagePullSecrets: {{- toYaml .Values.imagePullSecrets | nindent 8 }}
{{- end }}

View File

@@ -104,7 +104,7 @@ spec:
command: {{ tpl (toJson .Values.init.command) . }}
resources: {{- toYaml .Values.init.resources | nindent 10 }}
{{- if .Values.init.extraContainers }}
{{- tpl (toYaml .Values.init.extraContainers) . | nindent 6 }}
{{- toYaml .Values.init.extraContainers | nindent 6 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}

View File

@@ -194,6 +194,11 @@ image:
imagePullSecrets: []
initImage:
repository: apache/superset
tag: dockerize
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8088
@@ -269,7 +274,7 @@ supersetNode:
command:
- "/bin/sh"
- "-c"
- ". {{ .Values.configMountPath }}/superset_bootstrap.sh; exec /usr/bin/run-server.sh"
- ". {{ .Values.configMountPath }}/superset_bootstrap.sh; /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"
@@ -298,29 +303,15 @@ supersetNode:
# @default -- a container waiting for postgres
initContainers:
- name: wait-for-postgres
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
image: "{{ .Values.initImage.repository }}:{{ .Values.initImage.tag }}"
imagePullPolicy: "{{ .Values.initImage.pullPolicy }}"
envFrom:
- secretRef:
name: "{{ tpl .Values.envFromSecret . }}"
command:
- /bin/bash
- /bin/sh
- -c
- |
# opening a /dev/tcp fd performs a TCP connect without sending any
# payload (avoids postgres "incomplete startup packet" log noise);
# no external `dockerize`, `nc`, or busybox needed. SECONDS-based
# deadline mirrors the prior `dockerize -timeout 120s` behaviour.
SECONDS=0
until (exec 3<>/dev/tcp/"$DB_HOST"/"$DB_PORT") 2>/dev/null; do
if [ "$SECONDS" -ge 120 ]; then
echo "timeout waiting for postgres at $DB_HOST:$DB_PORT after 120s" >&2
exit 1
fi
echo "waiting for postgres at $DB_HOST:$DB_PORT (elapsed ${SECONDS}s)"
sleep 2
done
echo "postgres at $DB_HOST:$DB_PORT is up"
- dockerize -wait "tcp://$DB_HOST:$DB_PORT" -timeout 120s
resources:
limits:
memory: "256Mi"
@@ -369,12 +360,6 @@ supersetNode:
failureThreshold: 3
periodSeconds: 15
successThreshold: 1
# -- Container lifecycle hooks, e.g. a preStop sleep so the Service/Ingress
# stops routing to the pod before gunicorn receives SIGTERM
lifecycle: {}
# -- Pod termination grace period (seconds). Set greater than GUNICORN_TIMEOUT so
# in-flight requests can drain before SIGKILL
terminationGracePeriodSeconds: ~
# -- Resource settings for the supersetNode pods - these settings overwrite might existing values from the global resources object defined above.
resources: {}
# limits:
@@ -415,38 +400,22 @@ supersetWorker:
command:
- "/bin/sh"
- "-c"
- ". {{ .Values.configMountPath }}/superset_bootstrap.sh; exec celery --app=superset.tasks.celery_app:app worker"
- ". {{ .Values.configMountPath }}/superset_bootstrap.sh; celery --app=superset.tasks.celery_app:app worker"
# -- If true, forces deployment to reload on each upgrade
forceReload: false
# -- Init container
# @default -- a container waiting for postgres and redis
initContainers:
- name: wait-for-postgres-redis
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
image: "{{ .Values.initImage.repository }}:{{ .Values.initImage.tag }}"
imagePullPolicy: "{{ .Values.initImage.pullPolicy }}"
envFrom:
- secretRef:
name: "{{ tpl .Values.envFromSecret . }}"
command:
- /bin/bash
- /bin/sh
- -c
- |
# See supersetNode.initContainers for the rationale.
SECONDS=0
wait_for() {
local host=$1 port=$2 name=$3
until (exec 3<>/dev/tcp/"$host"/"$port") 2>/dev/null; do
if [ "$SECONDS" -ge 120 ]; then
echo "timeout waiting for $name at $host:$port after 120s" >&2
exit 1
fi
echo "waiting for $name at $host:$port (elapsed ${SECONDS}s)"
sleep 2
done
echo "$name at $host:$port is up"
}
wait_for "$DB_HOST" "$DB_PORT" postgres
wait_for "$REDIS_HOST" "$REDIS_PORT" redis
- dockerize -wait "tcp://$DB_HOST:$DB_PORT" -wait "tcp://$REDIS_HOST:$REDIS_PORT" -timeout 120s
resources:
limits:
memory: "256Mi"
@@ -495,10 +464,6 @@ supersetWorker:
failureThreshold: 3
periodSeconds: 60
successThreshold: 1
# -- Container lifecycle hooks for the worker pod
lifecycle: {}
# -- Pod termination grace period (seconds) for the worker pod so in-flight tasks can drain before SIGKILL
terminationGracePeriodSeconds: ~
# -- No startup/readiness probes by default since we don't really care about its startup time (it doesn't serve traffic)
startupProbe: {}
# -- No startup/readiness probes by default since we don't really care about its startup time (it doesn't serve traffic)
@@ -523,38 +488,22 @@ supersetCeleryBeat:
command:
- "/bin/sh"
- "-c"
- ". {{ .Values.configMountPath }}/superset_bootstrap.sh; exec celery --app=superset.tasks.celery_app:app beat --pidfile /tmp/celerybeat.pid --schedule /tmp/celerybeat-schedule"
- ". {{ .Values.configMountPath }}/superset_bootstrap.sh; celery --app=superset.tasks.celery_app:app beat --pidfile /tmp/celerybeat.pid --schedule /tmp/celerybeat-schedule"
# -- If true, forces deployment to reload on each upgrade
forceReload: false
# -- List of init containers
# @default -- a container waiting for postgres
initContainers:
- name: wait-for-postgres-redis
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
image: "{{ .Values.initImage.repository }}:{{ .Values.initImage.tag }}"
imagePullPolicy: "{{ .Values.initImage.pullPolicy }}"
envFrom:
- secretRef:
name: "{{ tpl .Values.envFromSecret . }}"
command:
- /bin/bash
- /bin/sh
- -c
- |
# See supersetNode.initContainers for the rationale.
SECONDS=0
wait_for() {
local host=$1 port=$2 name=$3
until (exec 3<>/dev/tcp/"$host"/"$port") 2>/dev/null; do
if [ "$SECONDS" -ge 120 ]; then
echo "timeout waiting for $name at $host:$port after 120s" >&2
exit 1
fi
echo "waiting for $name at $host:$port (elapsed ${SECONDS}s)"
sleep 2
done
echo "$name at $host:$port is up"
}
wait_for "$DB_HOST" "$DB_PORT" postgres
wait_for "$REDIS_HOST" "$REDIS_PORT" redis
- dockerize -wait "tcp://$DB_HOST:$DB_PORT" -wait "tcp://$REDIS_HOST:$REDIS_PORT" -timeout 120s
resources:
limits:
memory: "256Mi"
@@ -645,31 +594,15 @@ supersetCeleryFlower:
# @default -- a container waiting for postgres and redis
initContainers:
- name: wait-for-postgres-redis
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
image: "{{ .Values.initImage.repository }}:{{ .Values.initImage.tag }}"
imagePullPolicy: "{{ .Values.initImage.pullPolicy }}"
envFrom:
- secretRef:
name: "{{ tpl .Values.envFromSecret . }}"
command:
- /bin/bash
- /bin/sh
- -c
- |
# See supersetNode.initContainers for the rationale.
SECONDS=0
wait_for() {
local host=$1 port=$2 name=$3
until (exec 3<>/dev/tcp/"$host"/"$port") 2>/dev/null; do
if [ "$SECONDS" -ge 120 ]; then
echo "timeout waiting for $name at $host:$port after 120s" >&2
exit 1
fi
echo "waiting for $name at $host:$port (elapsed ${SECONDS}s)"
sleep 2
done
echo "$name at $host:$port is up"
}
wait_for "$DB_HOST" "$DB_PORT" postgres
wait_for "$REDIS_HOST" "$REDIS_PORT" redis
- dockerize -wait "tcp://$DB_HOST:$DB_PORT" -wait "tcp://$REDIS_HOST:$REDIS_PORT" -timeout 120s
resources:
limits:
memory: "256Mi"
@@ -831,26 +764,15 @@ init:
# @default -- a container waiting for postgres
initContainers:
- name: wait-for-postgres
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
image: "{{ .Values.initImage.repository }}:{{ .Values.initImage.tag }}"
imagePullPolicy: "{{ .Values.initImage.pullPolicy }}"
envFrom:
- secretRef:
name: "{{ tpl .Values.envFromSecret . }}"
command:
- /bin/bash
- /bin/sh
- -c
- |
# See supersetNode.initContainers for the rationale.
SECONDS=0
until (exec 3<>/dev/tcp/"$DB_HOST"/"$DB_PORT") 2>/dev/null; do
if [ "$SECONDS" -ge 120 ]; then
echo "timeout waiting for postgres at $DB_HOST:$DB_PORT after 120s" >&2
exit 1
fi
echo "waiting for postgres at $DB_HOST:$DB_PORT (elapsed ${SECONDS}s)"
sleep 2
done
echo "postgres at $DB_HOST:$DB_PORT is up"
- dockerize -wait "tcp://$DB_HOST:$DB_PORT" -timeout 120s
resources:
limits:
memory: "256Mi"

View File

@@ -38,20 +38,14 @@ dependencies = [
# no bounds for apache-superset-core until we have a stable version
"apache-superset-core",
"backoff>=1.8.0",
# cachetools is used directly by ``superset.db_engine_specs.aws_iam`` (TTLCache).
# It used to be installed transitively via ``google-auth`` (<2.53), but
# ``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",
"celery>=5.3.6, <6.0.0",
"click>=8.4.0",
"click-option-group",
"colorama",
"flask-cors>=6.0.5, <7.0",
"flask-cors>=6.0.0, <7.0",
"croniter>=6.2.2",
"cron-descriptor",
"cryptography>=48.0.0, <49.0.0",
"cryptography>=42.0.4, <47.0.0",
"deprecation>=2.1.0, <2.2.0",
"flask>=2.2.5, <4.0.0",
"flask-appbuilder>=5.2.1, <6.0.0",
@@ -63,7 +57,7 @@ dependencies = [
"flask-session>=0.4.0, <1.0",
"flask-wtf>=1.3.0, <2.0",
"geopy",
"greenlet<=3.5.1, >=3.5.1",
"greenlet>=3.0.3, <=3.5.0",
"gunicorn>=25.3.0, <26; sys_platform != 'win32'",
"hashids>=1.3.1, <2",
# holidays>=0.45 required for security fix
@@ -73,12 +67,10 @@ dependencies = [
"jsonpath-ng>=1.8.0, <2",
"Mako>=1.2.2",
"markdown>=3.10.2",
# marshmallow 4 compatibility: see superset/marshmallow_compatibility.py for a
# Flask-AppBuilder workaround. Tracking issue:
# https://github.com/apache/superset/issues/33162
"marshmallow>=3.0, <5",
# marshmallow>=4 has issues: https://github.com/apache/superset/issues/33162
"marshmallow>=3.0, <4",
"marshmallow-union>=0.1",
"msgpack>=1.2.0, <1.3",
"msgpack>=1.0.0, <1.2",
"nh3>=0.3.5, <0.4",
"numpy>1.23.5, <2.3",
"packaging",
@@ -98,7 +90,7 @@ dependencies = [
"python-dotenv", # optional dependencies for Flask but required for Superset, see https://flask.palletsprojects.com/en/stable/installation/#optional-dependencies
"pygeohash",
"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",
"pyyaml>=6.0.0, <7.0.0",
"PyJWT>=2.4.0, <3.0",
"redis>=5.0.0, <6.0",
"rison>=2.0.0, <3.0",
@@ -149,7 +141,7 @@ drill = ["sqlalchemy-drill>=1.1.10, <2"]
druid = ["pydruid>=0.6.5,<0.7"]
duckdb = ["duckdb>=1.5.2,<2", "duckdb-engine>=0.17.0"]
dynamodb = ["pydynamodb>=0.4.2"]
solr = ["sqlalchemy-solr >= 0.2.4.3"]
solr = ["sqlalchemy-solr >= 0.2.0"]
elasticsearch = ["elasticsearch-dbapi>=0.2.13, <0.3.0"]
exasol = ["sqlalchemy-exasol>=2.4.0, <8.0"]
excel = ["xlrd>=2.0.2, <2.1"]
@@ -191,7 +183,7 @@ pinot = ["pinotdb>=5.0.0, <10.0.0"]
playwright = ["playwright>=1.60.0, <2"]
postgres = ["psycopg2-binary==2.9.12"]
presto = ["pyhive[presto]>=0.6.5"]
trino = ["trino>=0.337.0"]
trino = ["trino>=0.328.0"]
prophet = ["prophet>=1.1.6, <2"]
redshift = ["sqlalchemy-redshift>=0.8.1, <0.9"]
risingwave = ["sqlalchemy-risingwave"]
@@ -216,7 +208,7 @@ netezza = ["nzalchemy>=11.0.2"]
starrocks = ["starrocks>=1.3.3, <2"]
doris = ["pydoris>=1.0.0, <2.0.0"]
oceanbase = ["oceanbase_py>=0.0.1.2"]
ydb = ["ydb-sqlalchemy>=0.1.22", "ydb-sqlglot-plugin>=0.2.5"]
ydb = ["ydb-sqlalchemy>=0.1.2", "ydb-sqlglot-plugin>=0.2.5"]
development = [
# no bounds for apache-superset-extensions-cli until a stable version
"apache-superset-extensions-cli",
@@ -224,7 +216,7 @@ development = [
"docker",
"flask-testing",
"freezegun",
"grpcio>=1.81.1",
"grpcio>=1.55.3",
"openapi-spec-validator",
"parameterized",
"pip",
@@ -375,6 +367,7 @@ select = [
ignore = [
"S101",
"PT004", # Fixtures that don't return values - underscore prefix conflicts with pytest usage
"PT006",
"T201",
"N999",

View File

@@ -18,30 +18,5 @@
testpaths =
tests
python_files = *_test.py test_*.py *_tests.py *viz/utils.py
# `-p no:warnings` temporarily disabled in favor of more finely tuned `filterwarnings`.
#addopts = -p no:warnings
addopts = -p no:warnings
asyncio_mode = auto
# `ignore` is effectively equivalent to `-p no:warnings`.
# Always print RemovedIn20Warning when SQLALCHEMY_WARN_20=1.
# Additionally, raise errors for refactored RemovedIn20Warning cases to prevent regression.
filterwarnings =
ignore
always::sqlalchemy.exc.RemovedIn20Warning
error:Passing a string to Connection.execute\(\) is deprecated:sqlalchemy.exc.RemovedIn20Warning
# error:"Query" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
# error:"SavedQuery" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
# error:"SqlaTable" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
# 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 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 legacy calling style of select\(\) is deprecated:sqlalchemy.exc.RemovedIn20Warning
# error:The "whens" argument to case:sqlalchemy.exc.RemovedIn20Warning
# error:"User" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning

View File

@@ -26,7 +26,7 @@ filelock>=3.20.3,<4.0.0
brotli>=1.2.0,<2.0.0
numexpr>=2.9.0
# Security: CVE-2026-34073 (MEDIUM) - Improper Certificate Validation
cryptography>=48.0.0,<49.0.0
cryptography>=46.0.7,<47.0.0
# Security: Snyk - XSS vulnerability in Mako templates
mako>=1.3.11,<2.0.0
# Security: CVE-2024-52338 (CRITICAL) - Deserialization of untrusted data in IPC/Parquet readers
@@ -44,10 +44,11 @@ async_timeout>=4.0.0,<5.0.0
# a bit of attention to bump.
apispec>=6.0.0,<6.7.0
# 1.4.1 introduced a memory regression that exhausts memory in the test suite
# (https://github.com/marshmallow-code/marshmallow-sqlalchemy/issues/665). 1.4.2
# claimed a fix but did not address the root cause; only 1.5.0 actually fixes it.
marshmallow-sqlalchemy>=1.5.0
# 1.4.1 appears to use much more memory, where the python test suite runs out of memory
# causing CI to fail. 1.4.0 is the last version that works.
# https://marshmallow-sqlalchemy.readthedocs.io/en/latest/changelog.html#id3
# Opened this issue https://github.com/marshmallow-code/marshmallow-sqlalchemy/issues/665
marshmallow-sqlalchemy>=1.3.0,<1.4.1
# needed for python 3.12 support
openapi-schema-validator>=0.6.3
@@ -57,9 +58,3 @@ openapi-schema-validator>=0.6.3
# Known affected packages: Preset's 'clients' package
# See docs/docs/contributing/pkg-resources-migration.md for details
setuptools<81
# google-auth 2.53+ dropped its transitive dependency on cachetools, which is
# imported directly by superset.db_engine_specs.aws_iam. We declare cachetools
# explicitly in pyproject.toml and pin google-auth to the post-drop range so
# the install path is internally consistent (#40962).
google-auth>=2.53.0,<3.0.0

View File

@@ -45,7 +45,7 @@ cachelib==0.13.0
# flask-caching
# flask-session
cachetools==6.2.1
# via apache-superset (pyproject.toml)
# via google-auth
cattrs==25.1.1
# via requests-cache
celery==5.5.2
@@ -86,11 +86,10 @@ cron-descriptor==1.4.5
# via apache-superset (pyproject.toml)
croniter==6.2.2
# via apache-superset (pyproject.toml)
cryptography==48.0.1
cryptography==46.0.7
# via
# -r requirements/base.in
# apache-superset (pyproject.toml)
# google-auth
# paramiko
# pyopenssl
defusedxml==0.7.1
@@ -132,7 +131,7 @@ flask-caching==2.3.1
# via apache-superset (pyproject.toml)
flask-compress==1.17
# via apache-superset (pyproject.toml)
flask-cors==6.0.5
flask-cors==6.0.2
# via apache-superset (pyproject.toml)
flask-jwt-extended==4.7.1
# via flask-appbuilder
@@ -160,11 +159,9 @@ geographiclib==2.0
# via geopy
geopy==2.4.1
# via apache-superset (pyproject.toml)
google-auth==2.53.0
# via
# -r requirements/base.in
# shillelagh
greenlet==3.5.1
google-auth==2.43.0
# via shillelagh
greenlet==3.5.0
# via
# apache-superset (pyproject.toml)
# shillelagh
@@ -226,13 +223,13 @@ markupsafe==3.0.2
# mako
# werkzeug
# wtforms
marshmallow==4.3.0
marshmallow==3.26.2
# via
# apache-superset (pyproject.toml)
# flask-appbuilder
# marshmallow-sqlalchemy
# marshmallow-union
marshmallow-sqlalchemy==1.5.0
marshmallow-sqlalchemy==1.4.0
# via
# -r requirements/base.in
# flask-appbuilder
@@ -240,7 +237,7 @@ marshmallow-union==0.1.15
# via apache-superset (pyproject.toml)
mdurl==0.1.2
# via markdown-it-py
msgpack==1.2.1
msgpack==1.0.8
# via apache-superset (pyproject.toml)
msgspec==0.19.0
# via flask-session
@@ -273,6 +270,7 @@ packaging==25.0
# deprecation
# gunicorn
# limits
# marshmallow
# shillelagh
pandas==2.1.4
# via apache-superset (pyproject.toml)
@@ -300,7 +298,9 @@ pyarrow==24.0.0
# apache-superset (pyproject.toml)
# apache-superset-core
pyasn1==0.6.3
# via pyasn1-modules
# via
# pyasn1-modules
# rsa
pyasn1-modules==0.4.2
# via google-auth
pycparser==2.22
@@ -323,7 +323,7 @@ pyjwt==2.12.0
# redis
pynacl==1.6.2
# via paramiko
pyopenssl==26.2.0
pyopenssl==26.0.0
# via
# -r requirements/base.in
# shillelagh
@@ -344,11 +344,12 @@ python-dotenv==1.2.2
# via apache-superset (pyproject.toml)
pytz==2025.2
# via
# croniter
# flask-babel
# pandas
pyxlsb==1.0.10
# via pandas
pyyaml==6.0.3
pyyaml==6.0.2
# via
# apache-superset (pyproject.toml)
# apispec
@@ -375,6 +376,8 @@ rpds-py==0.25.0
# via
# jsonschema
# referencing
rsa==4.9.1
# via google-auth
selenium==4.44.0
# via apache-superset (pyproject.toml)
setuptools==80.9.0

View File

@@ -100,7 +100,7 @@ cachelib==0.13.0
cachetools==6.2.1
# via
# -c requirements/base-constraint.txt
# apache-superset
# google-auth
# py-key-value-aio
caio==0.9.25
# via aiofile
@@ -178,12 +178,11 @@ croniter==6.2.2
# via
# -c requirements/base-constraint.txt
# apache-superset
cryptography==48.0.1
cryptography==46.0.7
# via
# -c requirements/base-constraint.txt
# apache-superset
# authlib
# google-auth
# paramiko
# pyjwt
# pyopenssl
@@ -277,7 +276,7 @@ flask-compress==1.17
# via
# -c requirements/base-constraint.txt
# apache-superset
flask-cors==6.0.5
flask-cors==6.0.2
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -341,7 +340,7 @@ google-api-core==2.23.0
# google-cloud-core
# pandas-gbq
# sqlalchemy-bigquery
google-auth==2.53.0
google-auth==2.43.0
# via
# -c requirements/base-constraint.txt
# google-api-core
@@ -374,7 +373,7 @@ googleapis-common-protos==1.66.0
# via
# google-api-core
# grpcio-status
greenlet==3.5.1
greenlet==3.5.0
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -383,7 +382,7 @@ greenlet==3.5.1
# sqlalchemy
griffelib==2.0.2
# via fastmcp
grpcio==1.81.1
grpcio==1.71.0
# via
# apache-superset
# google-api-core
@@ -508,8 +507,6 @@ limits==5.1.0
# via
# -c requirements/base-constraint.txt
# flask-limiter
lz4==4.4.5
# via trino
mako==1.3.12
# via
# -c requirements/base-constraint.txt
@@ -530,14 +527,14 @@ markupsafe==3.0.2
# mako
# werkzeug
# wtforms
marshmallow==4.3.0
marshmallow==3.26.2
# via
# -c requirements/base-constraint.txt
# apache-superset
# flask-appbuilder
# marshmallow-sqlalchemy
# marshmallow-union
marshmallow-sqlalchemy==1.5.0
marshmallow-sqlalchemy==1.4.0
# via
# -c requirements/base-constraint.txt
# flask-appbuilder
@@ -559,7 +556,7 @@ more-itertools==10.8.0
# via
# jaraco-classes
# jaraco-functools
msgpack==1.2.1
msgpack==1.0.8
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -611,8 +608,6 @@ ordered-set==4.1.0
# via
# -c requirements/base-constraint.txt
# flask-limiter
orjson==3.11.9
# via trino
outcome==1.3.0.post0
# via
# -c requirements/base-constraint.txt
@@ -631,6 +626,7 @@ packaging==25.0
# google-cloud-bigquery
# gunicorn
# limits
# marshmallow
# matplotlib
# pytest
# shillelagh
@@ -727,6 +723,7 @@ pyasn1==0.6.3
# -c requirements/base-constraint.txt
# pyasn1-modules
# python-ldap
# rsa
pyasn1-modules==0.4.2
# via
# -c requirements/base-constraint.txt
@@ -783,7 +780,7 @@ pynacl==1.6.2
# via
# -c requirements/base-constraint.txt
# paramiko
pyopenssl==26.2.0
pyopenssl==26.0.0
# via
# -c requirements/base-constraint.txt
# shillelagh
@@ -844,6 +841,7 @@ python-multipart==0.0.29
pytz==2025.2
# via
# -c requirements/base-constraint.txt
# croniter
# flask-babel
# pandas
# trino
@@ -851,7 +849,7 @@ pyxlsb==1.0.10
# via
# -c requirements/base-constraint.txt
# pandas
pyyaml==6.0.3
pyyaml==6.0.2
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -913,6 +911,10 @@ rpds-py==0.25.0
# -c requirements/base-constraint.txt
# jsonschema
# referencing
rsa==4.9.1
# via
# -c requirements/base-constraint.txt
# google-auth
ruff==0.9.7
# via apache-superset
s3transfer==0.16.0
@@ -1015,7 +1017,7 @@ tqdm==4.67.1
# via
# cmdstanpy
# prophet
trino==0.337.0
trino==0.330.0
# via apache-superset
trio==0.33.0
# via
@@ -1035,7 +1037,6 @@ typing-extensions==4.15.0
# apache-superset-core
# cattrs
# exceptiongroup
# grpcio
# limits
# mcp
# opentelemetry-api
@@ -1150,4 +1151,3 @@ zstandard==0.23.0
# via
# -c requirements/base-constraint.txt
# flask-compress
# trino

View File

@@ -30,7 +30,7 @@ from flask import current_app
from flask_appbuilder import Model
from flask_migrate import downgrade, upgrade
from progress.bar import ChargingBar
from sqlalchemy import create_engine, inspect, text
from sqlalchemy import create_engine, inspect
from sqlalchemy.ext.automap import automap_base
from superset import db
@@ -154,7 +154,7 @@ 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")
"SELECT version_num FROM alembic_version"
).scalar()
print(f"Current version of the DB is {current_revision}")

View File

@@ -106,7 +106,6 @@ LANGUAGE_NAMES: dict[str, str] = {
"ru": "Russian",
"sk": "Slovak",
"sl": "Slovenian",
"sr": "Serbian",
"tr": "Turkish",
"uk": "Ukrainian",
"zh": "Chinese (Simplified)",

View File

@@ -1,6 +1,6 @@
{
"name": "@superset-ui/embedded-sdk",
"version": "0.4.0",
"version": "0.3.0",
"description": "SDK for embedding resources from Superset into your own application",
"access": "public",
"keywords": [

View File

@@ -47,11 +47,7 @@ function logError(...args) {
execSync('npm publish --access public', { stdio: 'pipe' });
log(`published ${version} to npm`);
} catch (err) {
// npm writes failure details to stderr (auth/permission/registry
// errors in particular), so surface both streams to avoid masking
// the real cause in CI logs.
if (err.stdout) console.error(String(err.stdout));
if (err.stderr) console.error(String(err.stderr));
console.error(String(err.stdout));
logError('Encountered an error, details should be above');
process.exitCode = 1;
}

View File

@@ -27,6 +27,11 @@ module.exports = {
'\\.svg$': '<rootDir>/spec/__mocks__/svgrMock.tsx',
'^src/(.*)$': '<rootDir>/src/$1',
'^spec/(.*)$': '<rootDir>/spec/$1',
// mapping glyph-core to local package source
'^@superset-ui/glyph-core$':
'<rootDir>/packages/superset-ui-glyph-core/src',
'^@superset-ui/glyph-core/(.*)$':
'<rootDir>/packages/superset-ui-glyph-core/src/$1',
// mapping plugins of superset-ui to source code
'^@superset-ui/([^/]+)/(.*)$':
'<rootDir>/node_modules/@superset-ui/$1/src/$2',
@@ -69,7 +74,7 @@ module.exports = {
],
coverageReporters: ['lcov', 'json-summary', 'html', 'text'],
transformIgnorePatterns: [
'node_modules/(?!@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/(?!@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/*.)',
],
preset: 'ts-jest',
transform: {

File diff suppressed because it is too large Load Diff

View File

@@ -119,7 +119,7 @@
"@great-expectations/jsonforms-antd-renderers": "^2.2.10",
"@jsonforms/core": "^3.7.0",
"@jsonforms/react": "^3.7.0",
"@jsonforms/vanilla-renderers": "^3.8.0",
"@jsonforms/vanilla-renderers": "^3.7.0",
"@luma.gl/constants": "~9.2.5",
"@luma.gl/core": "~9.2.5",
"@luma.gl/engine": "~9.2.5",
@@ -134,6 +134,7 @@
"@scarf/scarf": "^1.4.0",
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
"@superset-ui/core": "file:./packages/superset-ui-core",
"@superset-ui/glyph-core": "file:./packages/superset-ui-glyph-core",
"@superset-ui/legacy-plugin-chart-calendar": "file:./plugins/legacy-plugin-chart-calendar",
"@superset-ui/legacy-plugin-chart-chord": "file:./plugins/legacy-plugin-chart-chord",
"@superset-ui/legacy-plugin-chart-country-map": "file:./plugins/legacy-plugin-chart-country-map",
@@ -158,21 +159,22 @@
"@types/d3-selection": "^3.0.11",
"@types/d3-time-format": "^4.0.3",
"@types/react-google-recaptcha": "^2.1.9",
"@visx/axis": "^4.0.0",
"@visx/grid": "^4.0.0",
"@visx/responsive": "^4.0.0",
"@visx/scale": "^4.0.0",
"@visx/tooltip": "^4.0.0",
"@visx/xychart": "^4.0.0",
"@visx/axis": "^3.8.0",
"@visx/grid": "^3.5.0",
"@visx/responsive": "^3.0.0",
"@visx/scale": "^3.5.0",
"@visx/tooltip": "^3.0.0",
"@visx/xychart": "^3.5.1",
"ag-grid-community": "35.3.1",
"ag-grid-react": "35.3.1",
"antd": "^5.26.0",
"chrono-node": "^2.9.1",
"classnames": "^2.2.5",
"content-disposition": "^2.0.1",
"d3-color": "^3.1.0",
"d3-scale": "^4.0.2",
"dayjs": "^1.11.21",
"dom-to-image-more": "^3.10.0",
"dom-to-image-more": "^3.7.2",
"dom-to-pdf": "^0.3.2",
"echarts": "^5.6.0",
"fast-glob": "^3.3.2",
@@ -190,12 +192,12 @@
"jquery": "^4.0.0",
"js-levenshtein": "^1.1.6",
"json-bigint": "^1.0.0",
"json-stringify-pretty-compact": "^4.0.0",
"json-stringify-pretty-compact": "^2.0.0",
"lodash": "^4.18.1",
"mapbox-gl": "^3.24.1",
"markdown-to-jsx": "^9.8.2",
"mapbox-gl": "^3.24.0",
"markdown-to-jsx": "^9.8.1",
"match-sorter": "^8.3.0",
"memoize-one": "^6.0.0",
"memoize-one": "^5.2.1",
"mousetrap": "^1.6.5",
"mustache": "^4.2.0",
"nanoid": "^5.1.11",
@@ -203,7 +205,7 @@
"query-string": "9.4.0",
"re-resizable": "^6.11.2",
"react": "^18.3.0",
"react-arborist": "^3.10.5",
"react-arborist": "^3.10.1",
"react-checkbox-tree": "^1.8.0",
"react-diff-viewer-continued": "^4.2.2",
"react-dnd": "^11.1.3",
@@ -230,9 +232,9 @@
"redux-undo": "^1.0.0-beta9-9-7",
"rison": "^0.1.1",
"scroll-into-view-if-needed": "^3.1.0",
"simple-zstd": "^2.1.0",
"simple-zstd": "^1.4.2",
"stream-browserify": "^3.0.0",
"tinycolor2": "^1.6.0",
"tinycolor2": "^1.4.2",
"urijs": "^1.19.8",
"use-event-callback": "^0.1.0",
"use-immer": "^0.11.0",
@@ -260,17 +262,17 @@
"@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.14",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@playwright/test": "^1.61.0",
"@playwright/test": "^1.60.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
"@storybook/addon-docs": "10.4.5",
"@storybook/addon-links": "10.4.4",
"@storybook/react-webpack5": "10.4.4",
"@storybook/addon-docs": "10.4.2",
"@storybook/addon-links": "10.4.2",
"@storybook/react-webpack5": "10.4.2",
"@storybook/test-runner": "0.24.4",
"@svgr/webpack": "^8.1.0",
"@swc/core": "^1.15.41",
"@swc/plugin-emotion": "^14.13.0",
"@swc/plugin-emotion": "^14.12.0",
"@swc/plugin-transform-imports": "^12.5.0",
"@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^6.9.1",
@@ -283,7 +285,7 @@
"@types/js-levenshtein": "^1.1.3",
"@types/json-bigint": "^1.0.4",
"@types/mousetrap": "^1.6.15",
"@types/node": "^25.9.3",
"@types/node": "^25.9.2",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@types/react-loadable": "^5.5.11",
@@ -296,21 +298,21 @@
"@types/rison": "0.1.0",
"@types/tinycolor2": "^1.4.3",
"@types/unzipper": "^0.10.11",
"@typescript-eslint/eslint-plugin": "^8.61.1",
"@typescript-eslint/eslint-plugin": "^8.61.0",
"@typescript-eslint/parser": "^8.61.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",
"babel-plugin-lodash": "^3.3.4",
"baseline-browser-mapping": "^2.10.37",
"baseline-browser-mapping": "^2.10.34",
"cheerio": "1.2.0",
"concurrently": "^10.0.3",
"copy-webpack-plugin": "^14.0.0",
"cross-env": "^10.1.0",
"css-loader": "^7.1.4",
"css-minimizer-webpack-plugin": "^8.0.0",
"eslint": "^10.5.0",
"eslint": "^10.4.1",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-typescript": "^4.4.5",
@@ -323,8 +325,8 @@
"eslint-plugin-no-only-tests": "^3.4.0",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react-prefer-function-component": "^5.0.0",
"eslint-plugin-react-you-might-not-need-an-effect": "^1.0.1",
"eslint-plugin-storybook": "10.4.5",
"eslint-plugin-react-you-might-not-need-an-effect": "^1.0.0",
"eslint-plugin-storybook": "10.4.2",
"eslint-plugin-testing-library": "^7.16.2",
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
"fetch-mock": "^12.6.0",
@@ -343,19 +345,19 @@
"lightningcss": "^1.32.0",
"mini-css-extract-plugin": "^2.10.2",
"open-cli": "^9.0.0",
"oxlint": "^1.70.0",
"oxlint": "^1.69.0",
"po2json": "^0.4.5",
"prettier": "3.8.4",
"prettier-plugin-packagejson": "^3.0.2",
"process": "^0.11.10",
"react-dnd-test-backend": "^16.0.1",
"react-dnd-test-backend": "^11.1.3",
"react-refresh": "^0.18.0",
"react-resizable": "^4.0.1",
"redux-mock-store": "^1.5.4",
"source-map": "^0.7.6",
"source-map-support": "^0.5.21",
"speed-measure-webpack-plugin": "^1.6.0",
"storybook": "10.4.6",
"storybook": "10.4.3",
"style-loader": "^4.0.0",
"swc-loader": "^0.2.7",
"terser-webpack-plugin": "^5.6.1",
@@ -369,7 +371,7 @@
"webpack": "^5.107.2",
"webpack-bundle-analyzer": "^5.3.0",
"webpack-cli": "^7.0.3",
"webpack-dev-server": "^5.2.5",
"webpack-dev-server": "^5.2.4",
"webpack-manifest-plugin": "^6.0.1",
"webpack-sources": "^3.5.0",
"webpack-visualizer-plugin2": "^2.0.0"

View File

@@ -37,7 +37,7 @@
"cross-env": "^10.1.0",
"fs-extra": "^11.3.5",
"jest": "^30.4.2",
"yeoman-test": "^11.6.0"
"yeoman-test": "^11.5.3"
},
"engines": {
"npm": ">= 4.0.0",

View File

@@ -39,7 +39,7 @@
"@testing-library/user-event": "*",
"ace-builds": "^1.4.14",
"brace": "^0.11.1",
"memoize-one": "^6.0.0",
"memoize-one": "^5.1.1",
"react": "^18.3.0",
"react-ace": "^10.1.0",
"react-dom": "^18.3.0"

View File

@@ -415,6 +415,8 @@ export interface ControlPanelSectionConfig {
props: ControlPanelsContainerProps,
controlData: AnyDict,
) => boolean;
/** @internal Marks the auto-generated glyph "Chart Options" section */
_glyphChartOptions?: boolean;
}
export interface StandardizedControls {
@@ -447,6 +449,8 @@ export interface ControlPanelConfig {
sectionOverrides?: SectionOverrides;
onInit?: (state: ControlStateMapping) => void;
formDataOverrides?: (formData: QueryFormData) => QueryFormData;
/** @internal Raw glyph argument definitions from defineChart() used for native control panel rendering */
_glyphArgs?: unknown;
}
export type ControlOverrides = {
@@ -677,9 +681,7 @@ export interface ServerPaginationData {
export type TableColumnConfig = {
d3NumberFormat?: string;
// Allow null to match JSON round-trips, where an unset value deserializes
// from the metadata DB as `null` rather than `undefined`.
d3SmallNumberFormat?: string | null;
d3SmallNumberFormat?: string;
d3TimeFormat?: string;
columnWidth?: number;
horizontalAlign?: 'left' | 'right' | 'center';

View File

@@ -29,7 +29,7 @@
"@babel/runtime": "^7.29.7",
"@braintree/sanitize-url": "^7.1.2",
"@types/json-bigint": "^1.0.4",
"@visx/responsive": "^4.0.0",
"@visx/responsive": "^3.12.0",
"ace-builds": "^1.44.0",
"ag-grid-community": "35.3.1",
"ag-grid-react": "35.3.1",
@@ -43,7 +43,7 @@
"d3-time": "^3.1.0",
"d3-time-format": "^4.1.0",
"dayjs": "^1.11.21",
"dompurify": "^3.4.11",
"dompurify": "^3.4.9",
"fetch-retry": "^6.0.0",
"handlebars": "^4.7.9",
"jed": "^1.1.1",
@@ -77,7 +77,7 @@
"@types/d3-time-format": "^4.0.3",
"@types/jquery": "^4.0.1",
"@types/lodash": "^4.17.24",
"@types/node": "^25.9.3",
"@types/node": "^25.9.2",
"@types/prop-types": "^15.7.15",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/react-table": "^7.7.20",

View File

@@ -33,6 +33,14 @@ export enum Behavior {
*/
DrillToDetail = 'DRILL_TO_DETAIL',
DrillBy = 'DRILL_BY',
/**
* Include `ALLOWS_EMPTY_RESULTS` behavior if the chart handles empty/no data
* gracefully (e.g., showing a drop zone for drag-and-drop configuration).
* Charts with this behavior will receive empty data instead of seeing
* the "No results" message.
*/
AllowsEmptyResults = 'ALLOWS_EMPTY_RESULTS',
}
export interface ContextMenuFilters {

View File

@@ -86,7 +86,6 @@ import {
FundProjectionScreenOutlined,
FunctionOutlined,
HighlightOutlined,
HomeOutlined,
InfoCircleOutlined,
InfoCircleFilled,
InsertRowAboveOutlined,
@@ -244,7 +243,6 @@ const AntdIcons = {
GoogleOutlined,
GroupOutlined,
HighlightOutlined,
HomeOutlined,
InfoCircleOutlined,
InfoCircleFilled,
InsertRowAboveOutlined,

View File

@@ -74,10 +74,7 @@ export function transformLinkUri(uri: string): string {
// "java\tscript:" or "java\x01script:") are ignored by browsers, so strip
// them before comparing against the blocklist.
// eslint-disable-next-line no-control-regex
const scheme = url
.slice(0, colon)
.replace(/[\u0000-\u0020]/g, '')
.toLowerCase();
const scheme = url.slice(0, colon).replace(/[\u0000-\u0020]/g, '').toLowerCase();
return DANGEROUS_LINK_PROTOCOLS.includes(scheme) ? '' : url;
}

View File

@@ -519,8 +519,7 @@ const Select = forwardRef(
handleSelectAll();
}}
>
{t('Select all')}{' '}
{`(${formatNumber('SMART_NUMBER', bulkSelectCounts.selectable)})`}
{t('Select all')} {`(${formatNumber('SMART_NUMBER', bulkSelectCounts.selectable)})`}
</Button>
<Button
type="link"
@@ -537,8 +536,7 @@ const Select = forwardRef(
handleDeselectAll();
}}
>
{t('Clear')}{' '}
{`(${formatNumber('SMART_NUMBER', bulkSelectCounts.deselectable)})`}
{t('Clear')} {`(${formatNumber('SMART_NUMBER', bulkSelectCounts.deselectable)})`}
</Button>
</StyledBulkActionsContainer>
),

View File

@@ -52,13 +52,6 @@ const SupersetClient: SupersetClientInterface = {
request: request => getInstance().request(request),
getCSRFToken: () => getInstance().getCSRFToken(),
getUrl: (...args) => getInstance().getUrl(...args),
get guestTokenHeaderName() {
try {
return getInstance().guestTokenHeaderName;
} catch {
return 'X-GuestToken';
}
},
};
export default SupersetClient;

View File

@@ -163,7 +163,6 @@ export interface SupersetClientInterface extends Pick<
configure: (config?: ClientConfig) => SupersetClientInterface;
reset: () => void;
getCSRFToken: () => CsrfPromise;
guestTokenHeaderName?: string;
}
export type SupersetClientResponse = Response | JsonResponse | TextResponse;

View File

@@ -18,11 +18,7 @@
*/
import { ExtensibleFunction } from '../models';
// Import from the concrete modules rather than the `number-format` barrel to
// avoid a circular dependency (the barrel pulls in getSmallNumberFormatter,
// which imports CurrencyFormatter).
import { getNumberFormatter } from '../number-format/NumberFormatterRegistrySingleton';
import NumberFormats from '../number-format/NumberFormats';
import { getNumberFormatter, NumberFormats } from '../number-format';
import { Currency } from '../query';
import { RowData, RowDataValue } from './types';
import { AUTO_CURRENCY_SYMBOL, ISO_4217_REGEX } from './CurrencyFormats';

View File

@@ -1,54 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { CurrencyFormatter } from '../currency-format';
import { Currency } from '../query';
import NumberFormatter from './NumberFormatter';
import { getNumberFormatter } from './NumberFormatterRegistrySingleton';
/**
* Returns the appropriate formatter for small numbers (|value| < 1).
*
* When `d3SmallNumberFormat` is nullish or blank the caller's default
* formatter is returned unchanged, which preserves percentage formats
* that would otherwise be lost.
*
* Handles the cases where `d3SmallNumberFormat` is `null` (from JSON
* serialization of `undefined`) or `""` (from a cleared Select control).
*
* The generic parameter `F` allows callers to pass any formatter type
* (e.g. TimeFormatter, CustomFormatter) without a circular dependency
* on @superset-ui/chart-controls.
*/
export default function getSmallNumberFormatter<F>(
defaultFormatter: F,
d3SmallNumberFormat: string | null | undefined,
currencyFormat?: Currency,
): F | NumberFormatter | CurrencyFormatter {
if (d3SmallNumberFormat == null || d3SmallNumberFormat.trim() === '') {
return defaultFormatter;
}
if (currencyFormat) {
return new CurrencyFormatter({
d3Format: d3SmallNumberFormat,
currency: currencyFormat,
});
}
return getNumberFormatter(d3SmallNumberFormat);
}

View File

@@ -34,4 +34,3 @@ export { default as createDurationFormatter } from './factories/createDurationFo
export { default as createMemoryFormatter } from './factories/createMemoryFormatter';
export { default as createSiAtMostNDigitFormatter } from './factories/createSiAtMostNDigitFormatter';
export { default as createSmartNumberFormatter } from './factories/createSmartNumberFormatter';
export { default as getSmallNumberFormatter } from './getSmallNumberFormatter';

View File

@@ -172,15 +172,4 @@ describe('SupersetClient', () => {
const token = await SupersetClient.getCSRFToken();
expect(token).toBe('my_token');
});
test('guestTokenHeaderName returns the configured header name when instance exists', () => {
SupersetClient.configure({ guestTokenHeaderName: 'X-Custom-Guest' });
expect(SupersetClient.guestTokenHeaderName).toBe('X-Custom-Guest');
});
test('guestTokenHeaderName returns default X-GuestToken when instance is not configured', () => {
// Ensure instance is reset (afterEach calls SupersetClient.reset())
// Access the property without calling configure() first
expect(SupersetClient.guestTokenHeaderName).toBe('X-GuestToken');
});
});

View File

@@ -1,82 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
CurrencyFormatter,
getNumberFormatter,
getSmallNumberFormatter,
NumberFormatter,
} from '@superset-ui/core';
const defaultFormatter = getNumberFormatter('.8%');
describe('getSmallNumberFormatter', () => {
test('returns defaultFormatter when d3SmallNumberFormat is undefined', () => {
expect(getSmallNumberFormatter(defaultFormatter, undefined)).toBe(
defaultFormatter,
);
});
test('returns defaultFormatter when d3SmallNumberFormat is null (JSON round-trip)', () => {
expect(getSmallNumberFormatter(defaultFormatter, null)).toBe(
defaultFormatter,
);
});
test('returns defaultFormatter when d3SmallNumberFormat is empty string (cleared Select)', () => {
expect(getSmallNumberFormatter(defaultFormatter, '')).toBe(
defaultFormatter,
);
});
test('returns defaultFormatter when d3SmallNumberFormat is whitespace', () => {
expect(getSmallNumberFormatter(defaultFormatter, ' ')).toBe(
defaultFormatter,
);
});
test('returns a NumberFormatter when d3SmallNumberFormat is a valid format', () => {
const result = getSmallNumberFormatter(defaultFormatter, ',.4f');
expect(result).toBeInstanceOf(NumberFormatter);
expect(result).not.toBe(defaultFormatter);
expect(result!(0.12345)).toBe('0.1235');
});
test('returns a CurrencyFormatter when currencyFormat is provided', () => {
const result = getSmallNumberFormatter(defaultFormatter, ',.4f', {
symbol: 'USD',
symbolPosition: 'prefix',
});
expect(result).toBeInstanceOf(CurrencyFormatter);
expect(result!(0.12345)).toContain('0.1235');
expect(result!(0.12345)).toContain('$');
});
test('preserves percentage formatter output for small numbers when d3SmallNumberFormat is null', () => {
const pctFormatter = getNumberFormatter('.8%');
const result = getSmallNumberFormatter(pctFormatter, null);
expect(result!(-0.00001229)).toBe('-0.00122900%');
});
test('returns undefined when defaultFormatter is undefined and d3SmallNumberFormat is nullish', () => {
expect(getSmallNumberFormatter(undefined, null)).toBeUndefined();
expect(getSmallNumberFormatter(undefined, undefined)).toBeUndefined();
expect(getSmallNumberFormatter(undefined, '')).toBeUndefined();
});
});

View File

@@ -0,0 +1,335 @@
# Glyph Pattern Migration Guide
This guide documents how to migrate traditional Superset chart plugins to the single-file Glyph pattern.
## Overview
The Glyph pattern simplifies chart plugin development by:
- **Arguments define BOTH controls AND render props** - No separate files needed
- **No `controlPanel.ts`** - Generated from argument definitions
- **No `transformProps.ts`** - Arguments are passed directly to render
- **No `buildQuery.ts`** - Inferred from Metric/Dimension/Temporal arguments
- **Single file** - Everything in one place (~200 lines vs 500+ across multiple files)
## Migration Steps
### 1. Analyze the Existing Chart
Identify from the original chart:
- **Metrics/Dimensions**: What data does it query?
- **Controls**: What options does the user configure?
- **Styling**: What visual customizations exist?
- **Rendering**: How is the data displayed?
### 2. Create the Glyph Chart File
Create a new file: `src/BigNumber/BigNumberGlyph/index.tsx`
```typescript
import { t } from '@apache-superset/core';
import { styled } from '@apache-superset/core/ui';
import { Behavior, getNumberFormatter, CurrencyFormatter } from '@superset-ui/core';
import {
defineChart,
Metric,
Select,
Text,
Checkbox,
NumberFormat,
Currency,
TimeFormat,
ConditionalFormatting,
} from '@superset-ui/glyph-core';
```
### 3. Define Arguments (Controls + Props)
**CRITICAL: Use camelCase for argument names!**
Superset converts control names to camelCase in `formData`. If you use snake_case (`show_metric_name`), it won't match the camelCase key in formData (`showMetricName`).
```typescript
arguments: {
// Data arguments
metric: Metric.with({ label: t('Metric') }),
// Visual arguments - USE CAMELCASE!
headerFontSize: Select.with({
label: t('Font Size'),
options: [
{ label: t('Small'), value: 0.2 },
{ label: t('Large'), value: 0.4 },
],
default: 0.4,
}),
showMetricName: Checkbox.with({
label: t('Show Metric Name'),
default: false,
}),
// Declarative visibility (preferred)
metricNameFontSize: {
arg: Select.with({ ... }),
visibleWhen: { showMetricName: true },
},
// Declarative disabled state
subtitleFontSize: {
arg: Select.with({ ... }),
disabledWhen: { subtitle: '' },
},
}
```
### 4. Available Argument Types
| Type | Control Generated | Value Type | Properties |
|------|------------------|------------|------------|
| `Metric` | MetricControl | `{ value, name, formattedValue }` | `label` |
| `Dimension` | GroupByControl | `string[]` | `label` |
| `Temporal` | TemporalControl | `string` | `label` |
| `Select` | SelectControl | `string \| number` | `label`, `description`, `options`, `default` |
| `Text` | TextControl | `string` | `label`, `description`, `default`, `placeholder` |
| `Checkbox` | CheckboxControl | `boolean` | `label`, `description`, `default` |
| `Int` | SliderControl | `number` | `label`, `description`, `default`, `min`, `max`, `step` |
| `Color` | ColorPickerControl | `string` (hex) | `label`, `description`, `default` |
| `NumberFormat` | SelectControl (freeform) | `string` | `label`, `description`, `default` |
| `Currency` | CurrencyControl | `{ symbol?, symbolPosition? }` | `label`, `description`, `default` |
| `TimeFormat` | SelectControl (freeform) | `string` | `label`, `description`, `default` |
| `ConditionalFormatting` | ConditionalFormattingControl | `Rule[]` | `label`, `description` |
### 5. Declarative Visibility & Disabled States
Instead of Redux `mapStateToProps`, use declarative conditions:
```typescript
// Simple equality check - visible when showMetricName is true
metricNameFontSize: {
arg: Select.with({ ... }),
visibleWhen: { showMetricName: true },
},
// Function check - visible when subtitle is not empty
subtitleFontSize: {
arg: Select.with({ ... }),
visibleWhen: { subtitle: (val) => !!val },
},
// Multiple conditions (AND) - visible when both conditions are met
advancedOption: {
arg: Checkbox.with({ ... }),
visibleWhen: {
showMetricName: true,
subtitle: (val) => !!val,
},
},
// Disabled state (control visible but not editable)
formatOption: {
arg: Select.with({ ... }),
disabledWhen: { forceTimestampFormatting: true },
},
```
### 6. Number, Currency, and Time Formatting
Use the built-in format argument types:
```typescript
arguments: {
numberFormat: NumberFormat.with({
label: t('Number Format'),
description: t('D3 format string'),
default: 'SMART_NUMBER',
}),
currencyFormat: Currency.with({
label: t('Currency Format'),
}),
timeFormat: TimeFormat.with({
label: t('Date Format'),
default: 'smart_date',
}),
}
```
Then use them directly in the render function:
```typescript
render: ({ numberFormat, currencyFormat, timeFormat, metric }) => {
const formatter = currencyFormat?.symbol
? new CurrencyFormatter({
currency: { symbol: currencyFormat.symbol, symbolPosition: currencyFormat.symbolPosition ?? 'prefix' },
d3Format: numberFormat,
})
: getNumberFormatter(numberFormat);
return <div>{formatter(metric.value)}</div>;
}
```
### 7. Conditional Formatting (Colors)
Use `ConditionalFormatting` for color-based rules:
```typescript
import { getColorFormatters } from '@superset-ui/chart-controls';
arguments: {
conditionalFormatting: ConditionalFormatting.with({
label: t('Conditional Formatting'),
description: t('Apply conditional color formatting to metric'),
}),
},
render: ({ conditionalFormatting, metric, data, theme }) => {
let numberColor: string | undefined;
if (conditionalFormatting?.length > 0 && metric.value != null) {
const colorFormatters = getColorFormatters(conditionalFormatting, data, theme, false);
if (colorFormatters) {
for (const formatter of colorFormatters) {
const color = formatter.getColorFromValue(metric.value as number);
if (color) {
numberColor = color;
break;
}
}
}
}
return <BigNumberText color={numberColor}>{metric.formattedValue}</BigNumberText>;
}
```
### 8. Styled Components
Use Superset's theme properties with template literal syntax:
```typescript
const Container = styled.div<{ height: number }>`
${({ theme, height }) => `
height: ${height}px;
padding: ${theme.sizeUnit * 4}px;
font-family: ${theme.fontFamily};
color: ${theme.colorText};
`}
`;
```
**Common theme properties:**
| Property | Description |
|----------|-------------|
| `theme.sizeUnit` | Base spacing unit (typically 4px) |
| `theme.fontFamily` | Default font family |
| `theme.fontWeightNormal` | Normal font weight |
| `theme.fontWeightLight` | Light font weight |
| `theme.fontSizeSM` | Small font size |
| `theme.colorText` | Primary text color |
| `theme.colorTextTertiary` | Muted/secondary text color |
| `theme.borderRadius` | Standard border radius |
### 9. Render Function
The render function receives all arguments directly - no formData lookup needed:
```typescript
render: ({
metric,
headerFontSize,
showMetricName,
numberFormat,
currencyFormat,
conditionalFormatting,
height,
data,
theme,
}) => {
// All arguments are directly available!
const formatter = currencyFormat?.symbol
? new CurrencyFormatter({ currency: currencyFormat, d3Format: numberFormat })
: getNumberFormatter(numberFormat);
const formattedValue = metric.value != null
? formatter(metric.value as number)
: t('No data');
return (
<Container height={height}>
{showMetricName && <MetricName>{metric.name}</MetricName>}
<BigNumberText>{formattedValue}</BigNumberText>
</Container>
);
},
```
### 10. Register the Plugin
In `BigNumber/index.ts`:
```typescript
export { default as BigNumberGlyphChartPlugin } from './BigNumberGlyph';
```
In `plugin-chart-echarts/src/index.ts`:
```typescript
export { BigNumberGlyphChartPlugin } from './BigNumber';
```
In `MainPreset.js`:
```typescript
import { BigNumberGlyphChartPlugin } from '@superset-ui/plugin-chart-echarts';
new BigNumberGlyphChartPlugin().configure({ key: 'big_number_glyph' }),
```
## Common Pitfalls
### 1. Snake Case vs Camel Case
- **WRONG**: `show_metric_name` - won't match formData
- **RIGHT**: `showMetricName` - matches Superset's camelCase conversion
### 2. Theme Undefined
- **WRONG**: `theme.gridUnit` - crashes if theme is undefined
- **RIGHT**: `theme?.gridUnit ?? 4` - safe with fallback
### 3. Metric Value Extraction
The Glyph core automatically extracts metric values from query results. The `metric` argument provides:
- `metric.value` - The raw numeric value
- `metric.name` - The metric label/name
- `metric.formattedValue` - Basic string representation
### 4. Visibility vs Legacy Functions
- **Prefer**: `visibleWhen: { showMetricName: true }` - declarative, clean
- **Legacy**: `visibility: ({ controls }) => controls?.showMetricName?.value === true` - still works
## File Structure Comparison
### Traditional (5+ files, ~500 lines)
```
BigNumberTotal/
├── index.ts # Plugin registration
├── controlPanel.ts # Control definitions (~100 lines)
├── transformProps.ts # Data transformation (~150 lines)
├── buildQuery.ts # Query building (~50 lines)
├── BigNumberViz.tsx # React component (~150 lines)
└── types.ts # TypeScript types (~50 lines)
```
### Glyph Pattern (1 file, ~250 lines)
```
BigNumberGlyph/
└── index.tsx # Everything in one file!
```
## Complete Example
See `BigNumber/BigNumberGlyph/index.tsx` for a complete working example with:
- Metric display
- Number/currency/time formatting
- Conditional color formatting
- Declarative visibility
- Subtitle support
- Font size controls

View File

@@ -0,0 +1,267 @@
<!--
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.
-->
# [SIP] Proposal for single-file chart plugins via `defineChart()` (the "Glyph" pattern)
> Draft — to be filed per the [SIP process](https://github.com/apache/superset/issues/5602).
> The SIP will be numbered by a committer upon acceptance.
### Motivation
Building a Superset visualization plugin today requires authoring and keeping in
sync five or more files per chart — `index.ts`, `controlPanel.ts`,
`transformProps.ts`, `buildQuery.ts`, `types.ts`, plus the component — wired
together through several layers of incidental complexity:
- **String-based control references.** Controls are referenced by name strings
(`'metric'`, `'groupby'`, `'y_axis_format'`) that are resolved at runtime
through registries and `expandControlConfig`. Typos fail silently; renames
require grep-driven archaeology.
- **Array-of-arrays layouts.** Control panel layout is expressed as
`controlSetRows: [[control], [control, control], [<JSX/>]]` — positional
nesting that conflates layout with configuration and is hostile to both
reading and tooling.
- **Triplicated knowledge.** The same fact (e.g. "this chart has a `showLegend`
boolean defaulting to true") is restated in the control panel (control
config), in transformProps (formData extraction + defaulting), and in the
component's prop types. The three routinely drift.
- **No type safety across the seam.** `transformProps` output and component
props are connected only by convention; mismatches surface as silently
`undefined` props at runtime, not compile errors.
The result is a high floor for contributing a chart, and a maintenance burden
proportional to (charts × files × duplicated facts).
### Proposed Change
A new workspace package, **`@superset-ui/glyph-core`**, provides a
**`defineChart()`** function that defines a complete chart plugin in a single
file. The core idea: **argument definitions are the single source of truth**
for the control panel, the query, the transform, and the render props.
```tsx
export default defineChart({
metadata: { name: t('My Chart'), thumbnail, behaviors: [Behavior.InteractiveChart] },
arguments: {
metric: Metric.with({ label: t('Metric') }),
groupby: Dimension.with({ label: t('Dimension') }),
showLegend: ShowLegend, // reusable preset
legendType: { arg: LegendType, visibleWhen: { showLegend: true } },
},
transform: (chartProps, argValues) => ({ echartOptions: build(chartProps) }),
render: props => <Echart {...props} />,
});
```
Key elements:
- **Argument classes** (`Metric`, `Dimension`, `Temporal`, `Select`, `Checkbox`,
`Int`, `Slider`, `Color`, `ColorPicker`, `RadioButton`, `Bounds`, `Text`,
`NumberFormat`, `Currency`, `TimeFormat`, `ConditionalFormatting`) declare
what a chart needs; glyph-core generates the matching control configs,
formData extraction with defaults, and typed render props.
- **Declarative conditions** — `visibleWhen` / `disabledWhen` replace
imperative `visibility:` functions reaching into Redux controls state, with a
single shared evaluator (`evaluateGlyphCondition`) usable from both the
legacy pipeline and native rendering.
- **Generated `buildQuery`** for the common case (built on the core
`getXAxisColumn`/`normalizeTimeColumn` helpers so time grain, adhoc x-axis
columns, and the `DTTM_ALIAS` fallback behave identically to hand-written
queries); charts with post-processing pass their own `buildQuery`.
- **Generated `transformProps`** maps formData → typed render props (metric
label resolution via core `getMetricLabel`); charts with heavy transforms
supply a `transform` whose return value is merged into render props.
- **Escape hatches for incremental migration** — `additionalControls`,
`prependSections` / `middleSections` / `additionalSections`,
`controlOverrides`, `formDataOverrides`, `onInit`, and
`chartOptionsTabOverride` let complex charts keep hand-crafted sections while
adopting the pattern for everything else.
- **Reusable presets** (`presets.ts`) — `ShowLegend`, `LegendType`,
`LegendOrientation`, `HeaderFontSize`, `Subtitle`, etc. — shared,
pre-translated argument configurations that keep label strings and defaults
consistent across chart families.
- **Native Customize-tab rendering** — `GlyphOptionsPanel` renders glyph
arguments directly from formData (value + visibility), bypassing the string
expansion pipeline. The generated "Chart Options" section is identified by a
structural `_glyphChartOptions` marker (never by label matching). Controls
whose config requires `mapStateToProps` (e.g. ConditionalFormatting) and JSX
rows (sub-section headers) route through the existing legacy renderer for
full compatibility.
### Current Status (what has been done)
All work lives on `feat/glyph-single-file`. **Every chart family in the main
preset has been consolidated** to `defineChart()`:
- **plugin-chart-echarts**: Pie, Funnel, Gauge, Sankey, Waterfall, Histogram,
Tree, Bubble, BoxPlot, Sunburst, Radar, Treemap, Graph, Heatmap, Gantt,
BigNumber, BigNumberWithTrendline, BigNumberTotal, BigNumberPeriodOverPeriod,
Timeseries (Generic, Area, Bar, Line, Scatter, SmoothLine, Step),
MixedTimeseries
- **preset-chart-deckgl**: all 11 layers (Arc, Contour, Geojson, Grid, Heatmap,
Hex, Path, Polygon, Scatter, Screengrid, Multi)
- **legacy plugins**: calendar, horizon, chord, country-map, world-map,
paired-t-test, parallel-coordinates, partition, rose
- **legacy-preset-chart-nvd3**: Bubble, Bullet, Compare, TimePivot
- **others**: table, pivot-table, ag-grid-table (light-touch), word-cloud,
point-cluster-map, handlebars, cartodiagram
Net effect: **13,000 lines** (+60k/73k across 482 files).
**Test coverage added**: a 168-test suite spanning glyph-core unit tests
(arguments, defineChart, presets, crossFilter — including regression tests for
every audit finding below), `GlyphOptionsPanel` component tests, plugin-level
smoke tests for migrated charts, and a Playwright E2E test for the Customize
tab.
**A full-branch audit (2026-06-11)** ran a seven-angle review over the diff;
17 findings were confirmed and 14 fixed in-tree, notably:
- `behaviors` no longer defaults to `[Behavior.InteractiveChart]` — charts must
opt in, matching `ChartMetadata`'s own default (21 charts had acquired dead
cross-filter UI).
- The real `BigNumberTotalChartPlugin` is registered under
`big_number_total` again (a demo plugin had displaced it, breaking saved
charts' formats and subheaders).
- `@superset-ui/glyph-core` added to root `package.json` so webpack's
workspace-alias loop resolves live `src/` (fresh builds previously failed).
- Explore's initial-query effect is one-shot again — filling in the last
invalid control no longer auto-fires a query for any chart type.
- `ChartDefinition.metadata` can now express `queryObjectCount`,
`dynamicQueryObjectCount`, `parseMethod`, `suppressContextMenu`, and
`enableNoResults` (Mixed Chart's second Results tab returned).
- `disabledWhen` reads controls from the correct `mapStateToProps` argument
(the API was previously non-functional).
- Generated queries carry `timeGrain` on the x-axis column; generated metric
extraction uses `getMetricLabel` and no longer guesses among multiple numeric
columns.
- `NumberFormat`/`TimeFormat` options derive from chart-controls'
`D3_FORMAT_OPTIONS` (translated, with previews) instead of stale copies.
- Dead parallel implementation (`generators.ts`, 419 lines + tests + orphaned
types) deleted; condition evaluation deduplicated to a single function.
- `scripts/check-custom-rules.js` suppression matching is line-based (real
ESLint semantics) — one disable comment can no longer silence an entire
subtree; the leaks it had hidden were fixed with properly scoped disables.
Branch health: full-monorepo `tsc --noEmit` clean, all 24 packages build, all
tests and the complete pre-commit suite (mypy, ruff, pylint, oxlint, prettier,
frontend type-check, custom rules) pass.
### New or Changed Public Interfaces
- **New package** `@superset-ui/glyph-core` (workspace package; public API:
`defineChart`, argument classes, presets, `evaluateGlyphCondition`,
`getGlyphControlConfig`, `resolveArgClass`, cross-filter utilities).
- **`@superset-ui/chart-controls`**: `ControlPanelConfig._glyphArgs` and
`ControlPanelSectionConfig._glyphChartOptions``@internal` structural
markers consumed by explore.
- **`@superset-ui/core`**: `Behavior.AllowsEmptyResults` added to the
`Behavior` enum (no producer yet — see Remaining Work).
- **Explore**: new `GlyphOptionsPanel` component; `ControlPanelsContainer`,
`ExploreChartPanel`, `ExploreViewContainer`, `ChartRenderer`, and
`getSectionsToRender` learned the glyph path alongside the legacy one.
- **REST API**: `ChartDataExtrasSchema` accepts an optional
`allow_empty_query` boolean (`load_default=False`); `get_sqla_query` honors
it for validation while refusing to compile a zero-column SELECT.
- No new CLI surface, no deployment changes.
### New dependencies
None. `@superset-ui/glyph-core` is a new in-repo workspace package depending
only on existing workspace packages (`@superset-ui/core`,
`@superset-ui/chart-controls`). No new npm or PyPI dependencies.
### Migration Plan and Compatibility
- **No database migrations.** Saved charts keep their `viz_type` keys and
form data; `defineChart` plugins register under the same keys.
- **Form-data compatibility** is preserved through generated controls using the
same shared control names (`metric`, `groupby`, `adhoc_filters`, `x_axis`,
`time_grain_sqla`) and per-chart transforms reading legacy keys. One known
gap remains (camelCase arg keys vs. saved snake_case keys — see Remaining
Work item 2).
- **Incremental by design**: the legacy `controlPanel` pipeline still works
untouched; glyph charts coexist with non-migrated charts. Removal of legacy
controlPanel support is an explicit later phase, gated on the roadmap below.
### Remaining Work / Roadmap
Ordered by priority; items 13 should land before this branch merges or
immediately after.
1. **Restore code-splitting (perf regression).**
`loadChart: () => Promise.resolve(Component)` makes every migrated
renderer — including maplibre-gl (~800 KB) and deck.gl — eager in the
initial bundle, where master lazy-loaded them via
`loadChart: () => import('./Chart')`. Add lazy-render support to
`defineChart` (e.g. `render: () => import('./Render')` or a
`loadRender` field) and move heavy renderers back behind dynamic imports.
2. **Per-arg `formDataKey` aliases (saved-chart compatibility).**
Some migrated charts renamed keys (e.g. BigNumberTotal's `yAxisFormat` /
`subtitle` vs. saved `y_axis_format` / `subheader`). Rendering works via
camelCase conversion and transform fallbacks, but explore controls show
defaults for saved charts and write parallel keys. A
`formDataKey: 'y_axis_format'` alias on argument definitions fixes this
generally — and is the foundation for settings transfer between viz types
(arguments sharing a semantic key carry over on viz-type switch).
3. **Typed escape hatch for legacy transforms.**
~14 plugins (table, pivot-table, ag-grid, all deckgl layers,
point-cluster-map) use `transform: p => transformProps(p as any) as any`
plus `render: props => <Comp {...(props as any)} />`. This double-cast class
is what previously hid a silent render-props bug. Provide a
`defineChart<Props>` overload (typed transform fully replaces render props)
or a `wrapLegacyTransform(transformProps, Component)` helper so the next
shape mismatch is a compile error.
4. **Collapse the dual control-panel pipeline.**
`defineChart` currently emits a full legacy `ControlPanelConfig` *and*
native `_glyphArgs`; visibility/disabled/validation exist in both paths
with different data sources (Redux controls vs. formData). Make the native
definition canonical and derive the legacy config as a thin adapter — this
is the enabler for the planned consistent section/area rendering schema.
5. **Extract shared timeseries transform logic.**
The six Timeseries-family charts each inline ~1,200 lines of largely
duplicated transform code. Hoist a shared `timeseriesTransform` into
glyph-core (or a glyph-echarts helper) before the copies drift.
6. **Resolve the `allow_empty_query` plumbing.**
Backend accepts the flag and `Behavior.AllowsEmptyResults` exists, but
nothing produces either. Either wire the drag-and-drop empty-chart flow
end-to-end (derive the extras flag from chart metadata in one place) or
drop the speculative plumbing until the feature lands.
7. **Finish the migration and remove legacy controlPanel support** once 14
are in place, per the original plan: every plugin on `defineChart`, the
string-expansion pipeline (`expandControlConfig`, array-of-arrays sections)
deleted, and chart wrappers reduced to enable animation/realtime rendering.
### Rejected Alternatives
- **`createGlyphPlugin()` builder API** (a Map-based generator predating
`defineChart`): shipped briefly as `generators.ts`, never adopted by any
chart, drifted from the real implementation, and was deleted during the
audit in favor of the single `defineChart()` entry point.
- **JSON/YAML chart manifests**: a pure-data format can describe controls but
not transforms or renderers, forcing a second mechanism anyway; a typed
TypeScript API keeps definition, transform, and render in one
compiler-checked file.
- **Fixing the legacy controlPanel format in place** (e.g. typed control
references over the existing array-of-arrays): preserves the triplication of
knowledge across controlPanel/transformProps/component and the runtime
string-resolution layer that motivates this SIP.
- **Big-bang removal of the legacy pipeline**: rejected in favor of
coexistence + escape hatches; third-party plugins and complex charts need a
migration window.

View File

@@ -0,0 +1,38 @@
{
"name": "@superset-ui/glyph-core",
"version": "0.20.3",
"description": "Glyph Core - A declarative visualization plugin framework for Apache Superset",
"sideEffects": false,
"main": "lib/index.js",
"module": "esm/index.js",
"files": [
"esm",
"lib"
],
"repository": {
"type": "git",
"url": "https://github.com/apache/superset.git",
"directory": "superset-frontend/packages/superset-ui-glyph-core"
},
"keywords": [
"superset",
"glyph",
"visualization",
"chart"
],
"author": "Superset",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/apache/superset/issues"
},
"homepage": "https://github.com/apache/superset#readme",
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.2.0"
}
}

View File

@@ -0,0 +1,621 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
D3_FORMAT_OPTIONS,
D3_TIME_FORMAT_OPTIONS,
} from '@superset-ui/chart-controls';
import {
ColumnType,
SelectOptions,
SelectOption,
TextOptions,
CheckboxOptions,
IntOptions,
ColorOptions,
MetricOptions,
DimensionOptions,
NumberFormatOptions,
CurrencyOptions,
CurrencyValue,
TimeFormatOptions,
ConditionalFormattingOptions,
ConditionalFormattingRule,
SliderOptions,
BoundsOptions,
BoundsValue,
ColorPickerOptions,
RadioButtonOptions,
RadioOption,
RgbaColor,
} from './types';
/**
* Base Argument class - all argument types extend from this.
*
* Arguments define:
* 1. What the chart needs (semantically)
* 2. How to render controls in the control panel
* 3. Default values and validation
*/
export class Argument {
static label: string | null = null;
static description: string | null = null;
static columnType: ColumnType = ColumnType.Argument;
static controlType: string = 'TextControl';
value: unknown;
constructor(value: unknown) {
this.value = value;
}
}
/**
* Metric - represents a numeric aggregation (SUM, COUNT, AVG, etc.)
*
* Maps to Superset's MetricsControl in the query section.
*/
export class Metric extends Argument {
static override label: string | null = 'Metric';
static override description: string | null =
'A numeric aggregation (SUM, COUNT, AVG, etc.)';
static override columnType = ColumnType.Metric;
static override controlType = 'MetricsControl';
static multi = false;
static with(options: MetricOptions): typeof Metric {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override multi = options.multi ?? Base.multi;
};
}
}
/**
* Dimension - represents a categorical column for grouping data
*
* Maps to Superset's GroupByControl in the query section.
*/
export class Dimension extends Argument {
static override label: string | null = 'Dimension';
static override description: string | null =
'A categorical column for grouping data';
static override columnType = ColumnType.Dimension;
static override controlType = 'GroupByControl';
static multi = true;
static with(options: DimensionOptions): typeof Dimension {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override multi = options.multi ?? Base.multi;
};
}
}
/**
* Temporal - represents a time column
*
* Maps to Superset's temporal controls (x_axis, time_grain_sqla).
*/
export class Temporal extends Argument {
static override label: string | null = 'Time Column';
static override description: string | null =
'A temporal column for time series data';
static override columnType = ColumnType.Temporal;
static override controlType = 'TemporalControl';
static with(options: {
label?: string;
description?: string;
}): typeof Temporal {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
};
}
}
/**
* Select - dropdown selection from predefined options
*
* Maps to Superset's SelectControl.
*/
export class Select extends Argument {
static override label: string | null = 'Select';
static override description: string | null = 'Choose from options';
static override controlType = 'SelectControl';
static default: string | number = '';
static options: SelectOption[] = [];
static clearable = false;
static with(options: SelectOptions): typeof Select {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override default = options.default ?? Base.default;
static override options = options.options ?? Base.options;
};
}
}
/**
* Text - free-form text input
*
* Maps to Superset's TextControl.
*/
export class Text extends Argument {
static override label: string | null = 'Text';
static override description: string | null = 'Text input';
static override controlType = 'TextControl';
static default: string = '';
static placeholder: string = '';
static with(options: TextOptions): typeof Text {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override default = options.default ?? Base.default;
static override placeholder = options.placeholder ?? Base.placeholder;
};
}
}
/**
* Checkbox - boolean toggle
*
* Maps to Superset's CheckboxControl.
*/
export class Checkbox extends Argument {
static override label: string | null = 'Checkbox';
static override description: string | null = 'Toggle option';
static override controlType = 'CheckboxControl';
static default: boolean = false;
static with(options: CheckboxOptions): typeof Checkbox {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override default = options.default ?? Base.default;
};
}
}
/**
* Int - numeric input with slider
*
* Maps to Superset's SliderControl.
*/
export class Int extends Argument {
static override label: string | null = 'Integer';
static override description: string | null = 'A numeric value';
static override controlType = 'SliderControl';
static default: number = 0;
static min: number = 0;
static max: number = 100;
static step: number = 1;
static with(options: IntOptions): typeof Int {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override default = options.default ?? Base.default;
static override min = options.min ?? Base.min;
static override max = options.max ?? Base.max;
static override step = options.step ?? Base.step;
};
}
}
/**
* Color - color picker
*
* Maps to Superset's ColorPickerControl.
*/
export class Color extends Argument {
static override label: string | null = 'Color';
static override description: string | null = 'A color value';
static override controlType = 'ColorPickerControl';
// eslint-disable-next-line theme-colors/no-literal-colors
static default: string = '#000000';
static with(options: ColorOptions): typeof Color {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override default = options.default ?? Base.default;
};
}
}
/**
* NumberFormat - D3 number format string selection
*
* Maps to Superset's SelectControl with D3 format options.
* Allows freeform input for custom formats.
*/
export class NumberFormat extends Argument {
static override label: string | null = 'Number Format';
static override description: string | null =
'D3 format string for number display (e.g., ".2f", ".1%", ",.0f")';
static override controlType = 'NumberFormatControl';
static default: string = 'SMART_NUMBER';
// Standard D3 format options — derived from the canonical list in
// @superset-ui/chart-controls so glyph charts offer the same formats
// (with previews and translations) as legacy charts, and new formats
// propagate automatically.
static readonly FORMAT_OPTIONS: SelectOption[] = D3_FORMAT_OPTIONS.map(
([value, label]) => ({ value, label }),
);
static with(options: NumberFormatOptions): typeof NumberFormat {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override default = options.default ?? Base.default;
};
}
}
/**
* Currency - currency format with symbol and position
*
* Maps to Superset's CurrencyControl.
* Value is { symbol: 'USD', symbolPosition: 'prefix' | 'suffix' }
*/
export class Currency extends Argument {
static override label: string | null = 'Currency Format';
static override description: string | null =
'Currency symbol and position for formatting';
static override controlType = 'CurrencyControl';
static default: CurrencyValue = {};
static with(options: CurrencyOptions): typeof Currency {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override default = options.default ?? Base.default;
};
}
}
/**
* TimeFormat - D3 time format string selection
*
* Maps to Superset's SelectControl with D3 time format options.
* Allows freeform input for custom formats.
*/
export class TimeFormat extends Argument {
static override label: string | null = 'Time Format';
static override description: string | null =
'D3 time format string (e.g., "%Y-%m-%d", "%H:%M:%S")';
static override controlType = 'TimeFormatControl';
static default: string = 'smart_date';
// Standard D3 time format options — derived from the canonical list in
// @superset-ui/chart-controls (see NumberFormat.FORMAT_OPTIONS).
static readonly FORMAT_OPTIONS: SelectOption[] = D3_TIME_FORMAT_OPTIONS.map(
([value, label]) => ({ value, label }),
);
static with(options: TimeFormatOptions): typeof TimeFormat {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override default = options.default ?? Base.default;
};
}
}
/**
* ConditionalFormatting - apply color rules based on metric values
*
* This is a special argument type that encapsulates the complex
* mapStateToProps logic needed for conditional formatting controls.
* The control automatically receives numeric column options from the chart response.
*/
export class ConditionalFormatting extends Argument {
static override label: string | null = 'Conditional Formatting';
static override description: string | null =
'Apply conditional color formatting to metric values';
static override controlType = 'ConditionalFormattingControl';
static default: ConditionalFormattingRule[] = [];
static with(
options: ConditionalFormattingOptions,
): typeof ConditionalFormatting {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
};
}
}
/**
* Slider - continuous floating point values with min/max/step
*
* Similar to Int but for float values.
* Maps to Superset's SliderControl.
*/
export class Slider extends Argument {
static override label: string | null = 'Slider';
static override description: string | null = 'A continuous numeric value';
static override controlType = 'SliderControl';
static default: number = 0;
static min: number = 0;
static max: number = 1;
static step: number = 0.1;
static with(options: SliderOptions): typeof Slider {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override default = options.default ?? Base.default;
static override min = options.min ?? Base.min;
static override max = options.max ?? Base.max;
static override step = options.step ?? Base.step;
};
}
}
/**
* Bounds - min/max value pairs
*
* Used for axis bounds, value ranges, etc.
* Maps to Superset's BoundsControl.
*/
export class Bounds extends Argument {
static override label: string | null = 'Bounds';
static override description: string | null = 'Min and max value bounds';
static override controlType = 'BoundsControl';
static default: BoundsValue = [null, null];
static with(options: BoundsOptions): typeof Bounds {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override default = options.default ?? Base.default;
};
}
}
/**
* ColorPicker - RGBA color selection
*
* Different from Color (which uses hex strings).
* Maps to Superset's ColorPickerControl with RGBA format.
*/
export class ColorPicker extends Argument {
static override label: string | null = 'Color';
static override description: string | null = 'Select a color';
static override controlType = 'ColorPickerControl';
static default: RgbaColor = { r: 0, g: 0, b: 0, a: 1 };
static with(options: ColorPickerOptions): typeof ColorPicker {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override default = options.default ?? Base.default;
};
}
}
/**
* RadioButton - mutually exclusive options
*
* Use for small sets of exclusive choices (2-4 options).
* Maps to Superset's RadioButtonControl.
*/
export class RadioButton extends Argument {
static override label: string | null = 'Option';
static override description: string | null = 'Select one option';
static override controlType = 'RadioButtonControl';
static default: string | boolean = '';
static options: RadioOption[] = [];
static with(options: RadioButtonOptions): typeof RadioButton {
const Base = this;
return class extends Base {
static override label = options.label ?? Base.label;
static override description = options.description ?? Base.description;
static override default = options.default ?? Base.default;
static override options = options.options;
};
}
}
/**
* Type guard to check if an argument class is a ConditionalFormatting type
*/
export function isConditionalFormattingArg(
argClass: typeof Argument,
): argClass is typeof ConditionalFormatting {
return argClass.controlType === 'ConditionalFormattingControl';
}
/**
* Type guard to check if an argument class is a TimeFormat type
*/
export function isTimeFormatArg(
argClass: typeof Argument,
): argClass is typeof TimeFormat {
return argClass.controlType === 'TimeFormatControl';
}
/**
* Type guard to check if an argument class is a NumberFormat type
*/
export function isNumberFormatArg(
argClass: typeof Argument,
): argClass is typeof NumberFormat {
return argClass.controlType === 'NumberFormatControl';
}
/**
* Type guard to check if an argument class is a Currency type
*/
export function isCurrencyArg(
argClass: typeof Argument,
): argClass is typeof Currency {
return argClass.controlType === 'CurrencyControl';
}
/**
* Type guard to check if an argument class is a Select type
*/
export function isSelectArg(
argClass: typeof Argument,
): argClass is typeof Select {
return (
'options' in argClass && Array.isArray((argClass as typeof Select).options)
);
}
/**
* Type guard to check if an argument class is a Checkbox type
*/
export function isCheckboxArg(
argClass: typeof Argument,
): argClass is typeof Checkbox {
return (
'default' in argClass &&
typeof (argClass as typeof Checkbox).default === 'boolean'
);
}
/**
* Type guard to check if an argument class is a Text type
*/
export function isTextArg(argClass: typeof Argument): argClass is typeof Text {
return (
argClass.controlType === 'TextControl' ||
(argClass.prototype instanceof Text &&
!isSelectArg(argClass) &&
!isCheckboxArg(argClass))
);
}
/**
* Type guard to check if an argument class is an Int type
*/
export function isIntArg(argClass: typeof Argument): argClass is typeof Int {
return 'min' in argClass && 'max' in argClass;
}
/**
* Type guard to check if an argument class is a Color type
*/
export function isColorArg(
argClass: typeof Argument,
): argClass is typeof Color {
return (
argClass.controlType === 'ColorPickerControl' ||
argClass.prototype instanceof Color
);
}
/**
* Type guard to check if an argument class is a Metric type
*/
export function isMetricArg(
argClass: typeof Argument,
): argClass is typeof Metric {
return argClass.columnType === ColumnType.Metric;
}
/**
* Type guard to check if an argument class is a Dimension type
*/
export function isDimensionArg(
argClass: typeof Argument,
): argClass is typeof Dimension {
return argClass.columnType === ColumnType.Dimension;
}
/**
* Type guard to check if an argument class is a Temporal type
*/
export function isTemporalArg(
argClass: typeof Argument,
): argClass is typeof Temporal {
return argClass.columnType === ColumnType.Temporal;
}
/**
* Type guard to check if an argument class is a Slider type
*/
export function isSliderArg(
argClass: typeof Argument,
): argClass is typeof Slider {
return (
argClass.controlType === 'SliderControl' &&
'step' in argClass &&
typeof (argClass as typeof Slider).step === 'number'
);
}
/**
* Type guard to check if an argument class is a Bounds type
*/
export function isBoundsArg(
argClass: typeof Argument,
): argClass is typeof Bounds {
return argClass.controlType === 'BoundsControl';
}
/**
* Type guard to check if an argument class is a ColorPicker type
*/
export function isColorPickerArg(
argClass: typeof Argument,
): argClass is typeof ColorPicker {
return (
argClass.controlType === 'ColorPickerControl' &&
'default' in argClass &&
typeof (argClass as typeof ColorPicker).default === 'object' &&
'r' in ((argClass as typeof ColorPicker).default as object)
);
}
/**
* Type guard to check if an argument class is a RadioButton type
*/
export function isRadioButtonArg(
argClass: typeof Argument,
): argClass is typeof RadioButton {
return argClass.controlType === 'RadioButtonControl';
}

View File

@@ -0,0 +1,245 @@
/**
* 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.
*/
/**
* Cross-Filter Utilities for Glyph Charts
*
* This module provides helpers for implementing cross-filtering in Glyph charts.
* Cross-filtering allows charts to filter other charts on the dashboard when
* users click on data points.
*
* ## Quick Start
*
* 1. Add behaviors to metadata:
* ```typescript
* metadata: {
* behaviors: [Behavior.InteractiveChart, Behavior.DrillToDetail, Behavior.DrillBy],
* }
* ```
*
* 2. Extract cross-filter props in transform:
* ```typescript
* transform: (chartProps) => {
* const crossFilterProps = extractCrossFilterProps(chartProps, groupby, labelMap);
* return { transformedProps: { ...otherProps, ...crossFilterProps } };
* }
* ```
*
* 3. Use event handlers in render:
* ```typescript
* render: ({ transformedProps }) => {
* const eventHandlers = allEventHandlers(transformedProps);
* return <Echart eventHandlers={eventHandlers} ... />;
* }
* ```
*/
import type {
ChartProps,
FilterState,
QueryFormColumn,
SetDataMaskHook,
ContextMenuFilters,
} from '@superset-ui/core';
/**
* Props needed for cross-filtering in the render component.
* These are typically returned from the transform function and passed to Echart.
*/
export interface CrossFilterRenderProps {
/** Groupby columns used for filtering */
groupby: QueryFormColumn[];
/** Maps series names to their groupby column values */
labelMap: Record<string, string[]>;
/** Callback to emit cross-filter data mask */
setDataMask: SetDataMaskHook;
/** Maps series indices to selected value names */
selectedValues: Record<number, string>;
/** Whether cross-filters are enabled for this chart */
emitCrossFilters?: boolean;
/** Context menu handler for drill actions */
onContextMenu?: (
clientX: number,
clientY: number,
filters?: ContextMenuFilters,
) => void;
/** Column type mapping for formatting */
coltypeMapping?: Record<string, number>;
}
/**
* Create a selectedValues map from filterState.
*
* The selectedValues map is used by the Echart component to track which
* data points are currently selected (for highlighting).
*
* @param filterState - Current filter state from chartProps
* @param seriesNames - Array of series/data point names
* @returns Map of index -> name for selected values
*
* @example
* ```typescript
* const selectedValues = createSelectedValuesMap(
* filterState,
* transformedData.map(d => d.name),
* );
* ```
*/
export function createSelectedValuesMap(
filterState: FilterState | undefined,
seriesNames: string[],
): Record<number, string> {
return (filterState?.selectedValues || []).reduce(
(acc: Record<number, string>, selectedValue: string) => {
const index = seriesNames.findIndex(name => name === selectedValue);
if (index >= 0) {
return { ...acc, [index]: selectedValue };
}
return acc;
},
{},
);
}
/**
* Extract cross-filter related props from ChartProps.
*
* This is a convenience function that extracts all the props needed for
* cross-filtering from the standard ChartProps object.
*
* @param chartProps - The chart props from Superset
* @param groupby - The groupby columns (dimensions) from form data
* @param labelMap - A map from series names to their groupby values
* @param seriesNames - Array of series/data point names for selectedValues mapping
* @param coltypeMapping - Optional column type mapping
*
* @example
* ```typescript
* // In transform function:
* const labelMap = data.reduce((acc, datum) => ({
* ...acc,
* [extractGroupbyLabel({ datum, groupby })]: groupby.map(col => datum[col]),
* }), {});
*
* const crossFilterProps = extractCrossFilterProps(
* chartProps,
* groupby,
* labelMap,
* transformedData.map(d => d.name),
* coltypeMapping,
* );
*
* return {
* transformedProps: {
* echartOptions,
* formData,
* width,
* height,
* refs,
* ...crossFilterProps,
* },
* };
* ```
*/
export function extractCrossFilterProps(
chartProps: ChartProps,
groupby: QueryFormColumn[],
labelMap: Record<string, string[]>,
seriesNames: string[],
coltypeMapping?: Record<string, number>,
): CrossFilterRenderProps {
const { hooks, filterState, emitCrossFilters, formData } = chartProps;
const { setDataMask = () => {}, onContextMenu } = hooks ?? {};
const selectedValues = createSelectedValuesMap(filterState, seriesNames);
return {
groupby,
labelMap,
setDataMask,
selectedValues,
emitCrossFilters,
onContextMenu,
coltypeMapping,
// Also include formData for context menu formatting
formData,
} as CrossFilterRenderProps & { formData: unknown };
}
/**
* Check if a data point is currently filtered (should be dimmed).
*
* Use this in the transform function to apply opacity/styling to
* data points that are not part of the current filter selection.
*
* @param filterState - Current filter state from chartProps
* @param name - The name/label of the data point to check
* @returns true if the data point should be dimmed, false otherwise
*
* @example
* ```typescript
* const isFiltered = isDataPointFiltered(filterState, datum.name);
* const opacity = isFiltered ? OpacityEnum.SemiTransparent : OpacityEnum.NonTransparent;
* ```
*/
export function isDataPointFiltered(
filterState: FilterState | undefined,
name: string,
): boolean {
return Boolean(
filterState?.selectedValues &&
filterState.selectedValues.length > 0 &&
!filterState.selectedValues.includes(name),
);
}
/**
* Create a labelMap from data records.
*
* The labelMap maps series names (like "USA" or "2024-01") to their
* corresponding groupby column values. This is needed for the cross-filter
* event handlers to construct proper filter clauses.
*
* @param data - Array of data records
* @param groupbyLabels - Array of groupby column labels
* @param extractLabel - Function to extract the series label from a datum
* @returns Map of label -> groupby values
*
* @example
* ```typescript
* const labelMap = createLabelMap(
* data,
* groupbyLabels,
* datum => extractGroupbyLabel({ datum, groupby: groupbyLabels, coltypeMapping }),
* );
* ```
*/
export function createLabelMap<T extends Record<string, unknown>>(
data: T[],
groupbyLabels: string[],
extractLabel: (datum: T) => string,
): Record<string, string[]> {
return data.reduce((acc: Record<string, string[]>, datum: T) => {
const label = extractLabel(datum);
return {
...acc,
[label]: groupbyLabels.map(col => datum[col] as string),
};
}, {});
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
/**
* 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.
*/
/**
* Glyph Core - A declarative visualization plugin framework
*
* This module enables single-file visualization plugins where:
* 1. Arguments define both the chart's inputs AND the control panel
* 2. transformProps is auto-generated from argument definitions
* 3. The chart component is a simple function receiving typed arguments
*
* Features:
* - Single-file chart definitions with defineChart()
* - Declarative argument types (Metric, Dimension, Select, Checkbox, etc.)
* - Conditional visibility with visibleWhen/disabledWhen
* - Cross-filtering support with extractCrossFilterProps() and allEventHandlers()
* - Reusable presets (ShowLegend, HeaderFontSize, etc.)
*
* Example usage:
* ```typescript
* export default defineChart({
* metadata: {
* name: 'My Chart',
* thumbnail,
* behaviors: [Behavior.InteractiveChart, Behavior.DrillToDetail, Behavior.DrillBy],
* },
* arguments: {
* metric: Metric.with({ label: 'Metric' }),
* groupby: Dimension.with({ label: 'Breakdowns' }),
* fontSize: Select.with({
* label: 'Font Size',
* options: [{ label: 'Small', value: 0.2 }, { label: 'Large', value: 0.4 }],
* default: 0.3,
* }),
* },
* transform: (chartProps, argValues) => {
* // Extract cross-filter props for interactive filtering
* const crossFilterProps = extractCrossFilterProps(chartProps, groupby, labelMap, seriesNames);
* return { transformedProps: { echartOptions, ...crossFilterProps } };
* },
* render: ({ transformedProps }) => {
* const eventHandlers = allEventHandlers(transformedProps);
* return <Echart eventHandlers={eventHandlers} ... />;
* },
* });
* ```
*/
// Re-export everything
export * from './types';
export * from './arguments';
export * from './defineChart';
export * from './presets';
export * from './crossFilter';

View File

@@ -0,0 +1,408 @@
/**
* 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.
*/
/**
* Glyph Presets - Reusable argument configurations
*
* This module contains pre-configured arguments that are commonly
* used across multiple visualization types. Charts can import these
* directly or use .with() to customize them further.
*
* Example usage:
* ```typescript
* import { HeaderFontSize, Subtitle } from '../../glyph-core/presets';
*
* arguments: {
* headerFontSize: HeaderFontSize,
* subtitle: Subtitle,
* // Override defaults when needed:
* customSize: HeaderFontSize.with({ default: 0.5 }),
* }
* ```
*/
import { t } from '@apache-superset/core/translation';
import { Select, Text, Checkbox } from './arguments';
import { SelectOption } from './types';
// ============================================================================
// Font Size Options
// ============================================================================
/**
* Large font size options - for primary/header text elements
* Values are multipliers of container height (0.2 = 20% of height)
*/
export const FONT_SIZE_OPTIONS_LARGE: SelectOption[] = [
{ label: t('Tiny'), value: 0.2 },
{ label: t('Small'), value: 0.3 },
{ label: t('Normal'), value: 0.4 },
{ label: t('Large'), value: 0.5 },
{ label: t('Huge'), value: 0.6 },
];
/**
* Small font size options - for secondary text elements (subtitles, labels)
* Values are multipliers of container height
*/
export const FONT_SIZE_OPTIONS_SMALL: SelectOption[] = [
{ label: t('Tiny'), value: 0.125 },
{ label: t('Small'), value: 0.15 },
{ label: t('Normal'), value: 0.2 },
{ label: t('Large'), value: 0.3 },
{ label: t('Huge'), value: 0.4 },
];
// ============================================================================
// Pre-configured Arguments
// ============================================================================
/**
* Header/primary font size selector
* Used for main display elements like big numbers, titles
*/
export const HeaderFontSize = Select.with({
label: t('Font Size'),
description: t('Font size for the primary display element'),
options: FONT_SIZE_OPTIONS_LARGE,
default: 0.4,
});
/**
* Subheader/secondary font size selector
* Used for subtitles, labels, secondary text
*/
export const SubheaderFontSize = Select.with({
label: t('Subheader Font Size'),
description: t('Font size for secondary text elements'),
options: FONT_SIZE_OPTIONS_SMALL,
default: 0.15,
});
/**
* Subtitle text input
* Generic subtitle/description field used by many chart types
*/
export const Subtitle = Text.with({
label: t('Subtitle'),
description: t('Description text displayed below the main content'),
default: '',
});
/**
* Show legend toggle
* Common toggle for charts with legends
*/
export const ShowLegend = Checkbox.with({
// Strings match plugin-chart-echarts' legend controls so existing i18n
// catalogs apply and legend UX stays consistent across chart families.
label: t('Show legend'),
description: t('Whether to display a legend for the chart'),
default: true,
});
/**
* Force timestamp formatting toggle
* Used when a value might be a timestamp but isn't auto-detected
*/
export const ForceTimestampFormatting = Checkbox.with({
label: t('Force Date Format'),
description: t(
'Use date formatting even when the value is not detected as a timestamp',
),
default: false,
});
// ============================================================================
// Legend Options
// ============================================================================
export const LEGEND_TYPE_OPTIONS: SelectOption[] = [
{ label: t('Scroll'), value: 'scroll' },
{ label: t('List'), value: 'plain' },
];
export const LEGEND_ORIENTATION_OPTIONS: SelectOption[] = [
{ label: t('Top'), value: 'top' },
{ label: t('Bottom'), value: 'bottom' },
{ label: t('Left'), value: 'left' },
{ label: t('Right'), value: 'right' },
];
export const LEGEND_SORT_OPTIONS: SelectOption[] = [
{ label: t('No sort'), value: '' },
{ label: t('Ascending'), value: 'asc' },
{ label: t('Descending'), value: 'desc' },
];
/**
* Legend type selector
* Choose between scrollable or plain list legend
*/
export const LegendType = Select.with({
label: t('Type'),
description: t('Legend type'),
options: LEGEND_TYPE_OPTIONS,
default: 'scroll',
});
/**
* Legend orientation selector
* Position the legend relative to the chart
*/
export const LegendOrientation = Select.with({
label: t('Orientation'),
description: t('Legend Orientation'),
options: LEGEND_ORIENTATION_OPTIONS,
default: 'top',
});
/**
* Legend sort selector
* Sort legend items alphabetically
*/
export const LegendSort = Select.with({
label: t('Legend Sort'),
description: t('Sort order for legend items'),
options: LEGEND_SORT_OPTIONS,
default: '',
});
// ============================================================================
// Label Presets
// ============================================================================
/**
* Show labels toggle
* Common toggle for chart labels
*/
export const ShowLabels = Checkbox.with({
label: t('Show Labels'),
description: t('Whether to display labels on the chart'),
default: true,
});
/**
* Show value toggle
* Common toggle for showing values on chart elements
*/
export const ShowValue = Checkbox.with({
label: t('Show Value'),
description: t('Whether to display values on the chart'),
default: false,
});
// ============================================================================
// Metric Name Presets
// ============================================================================
/**
* Show metric name toggle
* Used in BigNumber charts to optionally show the metric name
*/
export const ShowMetricName = Checkbox.with({
label: t('Show Metric Name'),
description: t('Whether to display the metric name as a title'),
default: false,
});
/**
* Metric name font size selector
* Typically used with visibility tied to ShowMetricName
*/
export const MetricNameFontSize = Select.with({
label: t('Metric Name Font Size'),
description: t('Font size for the metric name'),
options: FONT_SIZE_OPTIONS_SMALL,
default: 0.15,
});
// ============================================================================
// Label Type Options (shared by Pie, Funnel, etc.)
// ============================================================================
/**
* Standard label content type options
* Used by Pie, Funnel, and other category-based charts
*/
export const LABEL_TYPE_OPTIONS: SelectOption[] = [
{ label: t('Category Name'), value: 'key' },
{ label: t('Value'), value: 'value' },
{ label: t('Percentage'), value: 'percent' },
{ label: t('Category and Value'), value: 'key_value' },
{ label: t('Category and Percentage'), value: 'key_percent' },
{ label: t('Category, Value and Percentage'), value: 'key_value_percent' },
{ label: t('Value and Percentage'), value: 'value_percent' },
];
/**
* Label type selector for category-based charts
*/
export const LabelType = Select.with({
label: t('Label Type'),
description: t('What should be shown on the label?'),
options: LABEL_TYPE_OPTIONS,
default: 'key',
});
// ============================================================================
// Sort Options
// ============================================================================
export const SORT_OPTIONS: SelectOption[] = [
{ label: t('Descending'), value: 'descending' },
{ label: t('Ascending'), value: 'ascending' },
{ label: t('None'), value: 'none' },
];
/**
* Sort by metric toggle
* Common for charts that need to sort data by metric value
*/
export const SortByMetric = Checkbox.with({
label: t('Sort by Metric'),
description: t('Sort results by the selected metric'),
default: true,
});
// ============================================================================
// Label Position Options
// ============================================================================
export const LABEL_POSITION_OPTIONS: SelectOption[] = [
{ label: t('Top'), value: 'top' },
{ label: t('Left'), value: 'left' },
{ label: t('Right'), value: 'right' },
{ label: t('Bottom'), value: 'bottom' },
{ label: t('Inside'), value: 'inside' },
{ label: t('Inside Left'), value: 'insideLeft' },
{ label: t('Inside Right'), value: 'insideRight' },
{ label: t('Inside Top'), value: 'insideTop' },
{ label: t('Inside Bottom'), value: 'insideBottom' },
];
/**
* Label position selector
* Position labels relative to chart elements
*/
export const LabelPosition = Select.with({
label: t('Label Position'),
description: t('Position of labels on the chart'),
options: LABEL_POSITION_OPTIONS,
default: 'top',
});
// ============================================================================
// Simple Label Type (key/value variants only)
// ============================================================================
/**
* Simple label type options - for charts with fewer label display options
* Used by Radar, Sunburst, etc.
*/
export const SIMPLE_LABEL_TYPE_OPTIONS: SelectOption[] = [
{ label: t('Category Name'), value: 'key' },
{ label: t('Value'), value: 'value' },
{ label: t('Category and Value'), value: 'key_value' },
];
/**
* Simple label type selector
* For charts that only need key/value/key_value options
*/
export const SimpleLabelType = Select.with({
label: t('Label Type'),
description: t('What should be shown on the label?'),
options: SIMPLE_LABEL_TYPE_OPTIONS,
default: 'key',
});
/**
* Value-only label type options - for charts like Radar
*/
export const VALUE_LABEL_TYPE_OPTIONS: SelectOption[] = [
{ label: t('Value'), value: 'value' },
{ label: t('Category and Value'), value: 'key_value' },
];
/**
* Value label type selector
* For charts that show value or category+value
*/
export const ValueLabelType = Select.with({
label: t('Label Type'),
description: t('What should be shown on the label?'),
options: VALUE_LABEL_TYPE_OPTIONS,
default: 'value',
});
// ============================================================================
// Totals and Aggregates
// ============================================================================
/**
* Show total toggle
* For charts that can display aggregate totals
*/
export const ShowTotal = Checkbox.with({
label: t('Show Total'),
description: t('Whether to display the aggregate total'),
default: false,
});
// ============================================================================
// Threshold Controls
// ============================================================================
/**
* Label percentage threshold
* Minimum percentage for showing labels (avoids clutter on small slices)
*/
export const LabelThreshold = Text.with({
label: t('Percentage Threshold'),
description: t('Minimum threshold in percentage points for showing labels'),
default: '5',
});
// ============================================================================
// Shape Options
// ============================================================================
/**
* Circle shape toggle (used by Radar)
*/
export const CircleShape = Checkbox.with({
label: t('Circle Shape'),
description: t('Use circular shape instead of polygon'),
default: false,
});
// ============================================================================
// Data Zoom
// ============================================================================
/**
* Enable data zoom toggle
* For charts with zoomable data areas
*/
export const DataZoom = Checkbox.with({
label: t('Data Zoom'),
description: t('Enable data zooming controls'),
default: false,
});

View File

@@ -0,0 +1,257 @@
/**
* 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.
*/
/**
* Option for Select controls
*/
export interface SelectOption {
label: string;
value: string | number;
}
/**
* Configuration options for Select argument type
*/
export interface SelectOptions {
label?: string;
description?: string;
default?: string | number;
options?: SelectOption[];
clearable?: boolean;
renderTrigger?: boolean;
}
/**
* Configuration options for Text argument type
*/
export interface TextOptions {
label?: string;
description?: string;
default?: string;
placeholder?: string;
}
/**
* Configuration options for Checkbox argument type
*/
export interface CheckboxOptions {
label?: string;
description?: string;
default?: boolean;
}
/**
* Configuration options for Int argument type (slider)
*/
export interface IntOptions {
label?: string;
description?: string;
default?: number;
min?: number;
max?: number;
step?: number;
}
/**
* Configuration options for Color argument type
*/
export interface ColorOptions {
label?: string;
description?: string;
default?: string;
}
/**
* Configuration options for Metric argument type
*/
export interface MetricOptions {
label?: string;
description?: string;
multi?: boolean;
}
/**
* Configuration options for Dimension argument type
*/
export interface DimensionOptions {
label?: string;
description?: string;
multi?: boolean;
}
/**
* Configuration options for NumberFormat argument type
*/
export interface NumberFormatOptions {
label?: string;
description?: string;
default?: string;
}
/**
* Currency value structure
*/
export interface CurrencyValue {
symbol?: string;
symbolPosition?: 'prefix' | 'suffix';
}
/**
* Configuration options for Currency argument type
*/
export interface CurrencyOptions {
label?: string;
description?: string;
default?: CurrencyValue;
}
/**
* Configuration options for TimeFormat argument type
*/
export interface TimeFormatOptions {
label?: string;
description?: string;
default?: string;
}
/**
* Configuration options for ConditionalFormatting argument type
*/
export interface ConditionalFormattingOptions {
label?: string;
description?: string;
}
/**
* Configuration options for Slider argument type (continuous float values)
*/
export interface SliderOptions {
label?: string;
description?: string;
default?: number;
min?: number;
max?: number;
step?: number;
}
/**
* Configuration options for Bounds argument type (min/max pairs)
*/
export interface BoundsOptions {
label?: string;
description?: string;
default?: [number | null, number | null];
}
/**
* Bounds value type - tuple of [min, max] where either can be null
*/
export type BoundsValue = [number | null, number | null];
/**
* Configuration options for ColorPicker argument type (RGBA colors)
*/
export interface ColorPickerOptions {
label?: string;
description?: string;
default?: RgbaColor;
}
/**
* Configuration options for RadioButton argument type
*/
export interface RadioButtonOptions {
label?: string;
description?: string;
default?: string | boolean;
options: RadioOption[];
}
/**
* Option for RadioButton controls
*/
export interface RadioOption {
label: string;
value: string | boolean;
}
/**
* Conditional formatting rule value
*/
export interface ConditionalFormattingRule {
column?: string;
operator?: '<' | '<=' | '>' | '>=' | '==' | '!=' | 'between';
targetValue?: number;
targetValueLeft?: number;
targetValueRight?: number;
colorScheme?: string;
}
/**
* Column type enum for data arguments
*/
export enum ColumnType {
Metric = 'metric',
Dimension = 'dimension',
Temporal = 'temporal',
Argument = 'argument',
}
/**
* RGBA color format used by Superset's ColorPickerControl
*/
export interface RgbaColor {
r: number;
g: number;
b: number;
a: number;
}
/**
* Visibility function for conditional control display (legacy)
*/
export type VisibilityFn = (state: {
controls: Record<string, { value: unknown }>;
}) => boolean;
/**
* Declarative condition for argument visibility/disabled state.
*
* Keys are argument names, values define the condition:
* - Literal value: equality check (e.g., { showMetricName: true })
* - Function: custom check (e.g., { subtitle: (val) => !!val })
*
* Multiple keys are AND'd together.
*
* @example
* // Visible when showMetricName is true
* visibleWhen: { showMetricName: true }
*
* @example
* // Visible when subtitle is not empty
* visibleWhen: { subtitle: (val) => !!val }
*
* @example
* // Visible when showMetricName is true AND subtitle is not empty
* visibleWhen: { showMetricName: true, subtitle: (val) => !!val }
*/
export type ArgumentCondition = Record<
string,
unknown | ((value: unknown) => boolean)
>;

View File

@@ -0,0 +1,475 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
Argument,
Bounds,
Checkbox,
Color,
ColorPicker,
ConditionalFormatting,
Currency,
Dimension,
Int,
isBoundsArg,
isCheckboxArg,
isColorArg,
isColorPickerArg,
isConditionalFormattingArg,
isCurrencyArg,
isDimensionArg,
isIntArg,
isMetricArg,
isNumberFormatArg,
isRadioButtonArg,
isSelectArg,
isSliderArg,
isTemporalArg,
isTextArg,
isTimeFormatArg,
Metric,
NumberFormat,
RadioButton,
Select,
Slider,
Temporal,
Text,
TimeFormat,
} from '@superset-ui/glyph-core';
import { ColumnType } from '@superset-ui/glyph-core/types';
describe('Argument base class', () => {
test('stores its constructor value', () => {
const a = new Argument(42);
expect(a.value).toBe(42);
});
test('has expected static defaults', () => {
expect(Argument.label).toBeNull();
expect(Argument.description).toBeNull();
expect(Argument.columnType).toBe(ColumnType.Argument);
expect(Argument.controlType).toBe('TextControl');
});
});
describe('Metric', () => {
test('has expected static metadata', () => {
expect(Metric.label).toBe('Metric');
expect(Metric.columnType).toBe(ColumnType.Metric);
expect(Metric.controlType).toBe('MetricsControl');
expect(Metric.multi).toBe(false);
});
test('.with() overrides label, description, multi', () => {
const M = Metric.with({
label: 'Sales',
description: 'Total sales',
multi: true,
});
expect(M.label).toBe('Sales');
expect(M.description).toBe('Total sales');
expect(M.multi).toBe(true);
// unaltered ancestor metadata still present
expect(M.columnType).toBe(ColumnType.Metric);
expect(M.controlType).toBe('MetricsControl');
});
test('.with() falls back to parent defaults when option omitted', () => {
const M = Metric.with({ label: 'X' });
expect(M.label).toBe('X');
expect(M.multi).toBe(Metric.multi);
expect(M.description).toBe(Metric.description);
});
test('isMetricArg type guard', () => {
expect(isMetricArg(Metric)).toBe(true);
expect(isMetricArg(Metric.with({ label: 'X' }))).toBe(true);
expect(isMetricArg(Dimension)).toBe(false);
expect(isMetricArg(Select)).toBe(false);
});
});
describe('Dimension', () => {
test('has expected static metadata', () => {
expect(Dimension.label).toBe('Dimension');
expect(Dimension.columnType).toBe(ColumnType.Dimension);
expect(Dimension.controlType).toBe('GroupByControl');
expect(Dimension.multi).toBe(true);
});
test('.with() overrides label, description, multi', () => {
const D = Dimension.with({
label: 'Region',
multi: false,
});
expect(D.label).toBe('Region');
expect(D.multi).toBe(false);
});
test('isDimensionArg type guard', () => {
expect(isDimensionArg(Dimension)).toBe(true);
expect(isDimensionArg(Dimension.with({ label: 'X' }))).toBe(true);
expect(isDimensionArg(Metric)).toBe(false);
});
});
describe('Temporal', () => {
test('has expected static metadata', () => {
expect(Temporal.label).toBe('Time Column');
expect(Temporal.columnType).toBe(ColumnType.Temporal);
expect(Temporal.controlType).toBe('TemporalControl');
});
test('.with() overrides label and description', () => {
const T = Temporal.with({ label: 'Order Date' });
expect(T.label).toBe('Order Date');
});
test('isTemporalArg type guard', () => {
expect(isTemporalArg(Temporal)).toBe(true);
expect(isTemporalArg(Metric)).toBe(false);
});
});
describe('Select', () => {
const OPTIONS = [
{ label: 'A', value: 'a' },
{ label: 'B', value: 'b' },
];
test('has expected static defaults', () => {
expect(Select.label).toBe('Select');
expect(Select.controlType).toBe('SelectControl');
expect(Select.options).toEqual([]);
expect(Select.default).toBe('');
});
test('.with() applies label, default, options', () => {
const S = Select.with({
label: 'Choice',
default: 'a',
options: OPTIONS,
});
expect(S.label).toBe('Choice');
expect(S.default).toBe('a');
expect(S.options).toEqual(OPTIONS);
});
test('isSelectArg type guard', () => {
expect(isSelectArg(Select.with({ options: OPTIONS }))).toBe(true);
expect(isSelectArg(Checkbox)).toBe(false);
expect(isSelectArg(Metric)).toBe(false);
});
});
describe('Text', () => {
test('has expected static defaults', () => {
expect(Text.controlType).toBe('TextControl');
expect(Text.default).toBe('');
expect(Text.placeholder).toBe('');
});
test('.with() applies label, default, placeholder', () => {
const T = Text.with({
label: 'Title',
default: 'Untitled',
placeholder: 'Enter title',
});
expect(T.label).toBe('Title');
expect(T.default).toBe('Untitled');
expect(T.placeholder).toBe('Enter title');
});
test('isTextArg type guard accepts Text but not Select/Checkbox', () => {
expect(isTextArg(Text)).toBe(true);
expect(isTextArg(Text.with({ label: 'X' }))).toBe(true);
expect(isTextArg(Checkbox)).toBe(false);
});
});
describe('Checkbox', () => {
test('has expected static defaults', () => {
expect(Checkbox.controlType).toBe('CheckboxControl');
expect(Checkbox.default).toBe(false);
});
test('.with() applies label, description, default', () => {
const C = Checkbox.with({
label: 'Show legend',
default: true,
});
expect(C.label).toBe('Show legend');
expect(C.default).toBe(true);
});
test('isCheckboxArg type guard', () => {
expect(isCheckboxArg(Checkbox)).toBe(true);
expect(isCheckboxArg(Checkbox.with({ default: true }))).toBe(true);
expect(isCheckboxArg(Text)).toBe(false);
});
});
describe('Int', () => {
test('has expected static defaults', () => {
expect(Int.controlType).toBe('SliderControl');
expect(Int.default).toBe(0);
expect(Int.min).toBe(0);
expect(Int.max).toBe(100);
expect(Int.step).toBe(1);
});
test('.with() applies label, default, min, max, step', () => {
const I = Int.with({
label: 'Limit',
default: 50,
min: 10,
max: 1000,
step: 5,
});
expect(I.label).toBe('Limit');
expect(I.default).toBe(50);
expect(I.min).toBe(10);
expect(I.max).toBe(1000);
expect(I.step).toBe(5);
});
test('isIntArg type guard', () => {
expect(isIntArg(Int)).toBe(true);
expect(isIntArg(Slider)).toBe(true); // Slider also has min/max
expect(isIntArg(Checkbox)).toBe(false);
});
});
describe('Color', () => {
test('has expected static defaults', () => {
expect(Color.controlType).toBe('ColorPickerControl');
expect(Color.default).toBe('#000000');
});
test('.with() applies label, default', () => {
const C = Color.with({ label: 'Fill', default: '#ff0000' });
expect(C.label).toBe('Fill');
expect(C.default).toBe('#ff0000');
});
test('isColorArg type guard', () => {
expect(isColorArg(Color)).toBe(true);
expect(isColorArg(Color.with({ default: '#ff0000' }))).toBe(true);
expect(isColorArg(Metric)).toBe(false);
});
});
describe('NumberFormat', () => {
test('has expected static defaults', () => {
expect(NumberFormat.controlType).toBe('NumberFormatControl');
expect(NumberFormat.default).toBe('SMART_NUMBER');
expect(NumberFormat.FORMAT_OPTIONS.length).toBeGreaterThan(10);
expect(
NumberFormat.FORMAT_OPTIONS.some(o => o.value === 'SMART_NUMBER'),
).toBe(true);
});
test('.with() applies label, default', () => {
const N = NumberFormat.with({ label: 'Amount', default: '.2f' });
expect(N.label).toBe('Amount');
expect(N.default).toBe('.2f');
});
test('isNumberFormatArg type guard', () => {
expect(isNumberFormatArg(NumberFormat)).toBe(true);
expect(isNumberFormatArg(TimeFormat)).toBe(false);
});
});
describe('Currency', () => {
test('has expected static defaults', () => {
expect(Currency.controlType).toBe('CurrencyControl');
expect(Currency.default).toEqual({});
});
test('.with() applies label, default', () => {
const C = Currency.with({
label: 'Money',
default: { symbol: 'USD', symbolPosition: 'prefix' },
});
expect(C.label).toBe('Money');
expect(C.default).toEqual({ symbol: 'USD', symbolPosition: 'prefix' });
});
test('isCurrencyArg type guard', () => {
expect(isCurrencyArg(Currency)).toBe(true);
expect(isCurrencyArg(NumberFormat)).toBe(false);
});
});
describe('TimeFormat', () => {
test('has expected static defaults', () => {
expect(TimeFormat.controlType).toBe('TimeFormatControl');
expect(TimeFormat.default).toBe('smart_date');
expect(
TimeFormat.FORMAT_OPTIONS.some(o => o.value === 'smart_date'),
).toBe(true);
});
test('.with() applies label, default', () => {
const T = TimeFormat.with({ label: 'When', default: '%Y-%m-%d' });
expect(T.label).toBe('When');
expect(T.default).toBe('%Y-%m-%d');
});
test('isTimeFormatArg type guard', () => {
expect(isTimeFormatArg(TimeFormat)).toBe(true);
expect(isTimeFormatArg(NumberFormat)).toBe(false);
});
});
describe('ConditionalFormatting', () => {
test('has expected static defaults', () => {
expect(ConditionalFormatting.controlType).toBe(
'ConditionalFormattingControl',
);
expect(ConditionalFormatting.default).toEqual([]);
});
test('.with() applies label and description (not default)', () => {
const CF = ConditionalFormatting.with({ label: 'Format' });
expect(CF.label).toBe('Format');
});
test('isConditionalFormattingArg type guard', () => {
expect(isConditionalFormattingArg(ConditionalFormatting)).toBe(true);
expect(isConditionalFormattingArg(Select)).toBe(false);
});
});
describe('Slider', () => {
test('has expected float-friendly defaults', () => {
expect(Slider.controlType).toBe('SliderControl');
expect(Slider.default).toBe(0);
expect(Slider.min).toBe(0);
expect(Slider.max).toBe(1);
expect(Slider.step).toBe(0.1);
});
test('.with() applies all numeric fields', () => {
const S = Slider.with({
label: 'Opacity',
default: 0.8,
min: 0,
max: 1,
step: 0.05,
});
expect(S.label).toBe('Opacity');
expect(S.default).toBe(0.8);
expect(S.step).toBe(0.05);
});
test('isSliderArg type guard requires float step', () => {
expect(isSliderArg(Slider)).toBe(true);
// Int is also SliderControl + has step but step is integer-valued — still
// numeric so the guard recognizes it (current behavior); document it.
expect(isSliderArg(Int)).toBe(true);
expect(isSliderArg(Checkbox)).toBe(false);
});
});
describe('Bounds', () => {
test('has expected static defaults', () => {
expect(Bounds.controlType).toBe('BoundsControl');
expect(Bounds.default).toEqual([null, null]);
});
test('.with() applies default', () => {
const B = Bounds.with({ label: 'Range', default: [0, 100] });
expect(B.label).toBe('Range');
expect(B.default).toEqual([0, 100]);
});
test('isBoundsArg type guard', () => {
expect(isBoundsArg(Bounds)).toBe(true);
expect(isBoundsArg(Int)).toBe(false);
});
});
describe('ColorPicker', () => {
test('has expected static defaults', () => {
expect(ColorPicker.controlType).toBe('ColorPickerControl');
expect(ColorPicker.default).toEqual({ r: 0, g: 0, b: 0, a: 1 });
});
test('.with() applies default', () => {
const CP = ColorPicker.with({
label: 'Pick',
default: { r: 255, g: 0, b: 0, a: 0.5 },
});
expect(CP.label).toBe('Pick');
expect(CP.default).toEqual({ r: 255, g: 0, b: 0, a: 0.5 });
});
test('isColorPickerArg distinguishes from Color (string)', () => {
expect(isColorPickerArg(ColorPicker)).toBe(true);
expect(isColorPickerArg(Color)).toBe(false);
});
});
describe('RadioButton', () => {
const RADIO_OPTIONS = [
{ label: 'Yes', value: true },
{ label: 'No', value: false },
];
test('has expected static defaults', () => {
expect(RadioButton.controlType).toBe('RadioButtonControl');
expect(RadioButton.default).toBe('');
expect(RadioButton.options).toEqual([]);
});
test('.with() applies all fields', () => {
const RB = RadioButton.with({
label: 'Toggle',
default: true,
options: RADIO_OPTIONS,
});
expect(RB.label).toBe('Toggle');
expect(RB.default).toBe(true);
expect(RB.options).toEqual(RADIO_OPTIONS);
});
test('isRadioButtonArg type guard', () => {
expect(isRadioButtonArg(RadioButton)).toBe(true);
expect(isRadioButtonArg(Select)).toBe(false);
});
});
describe('Argument inheritance via .with()', () => {
test('chained .with() calls compose overrides', () => {
const Base = Select.with({
label: 'Pick one',
options: [{ label: 'A', value: 'a' }],
});
const Tighter = Base.with({ label: 'Pick exactly one' });
expect(Tighter.label).toBe('Pick exactly one');
expect(Tighter.options).toEqual([{ label: 'A', value: 'a' }]);
});
test('original class is unmodified after .with()', () => {
const before = Metric.multi;
Metric.with({ multi: !before });
expect(Metric.multi).toBe(before);
});
});

View File

@@ -0,0 +1,212 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { ChartProps, FilterState } from '@superset-ui/core';
import {
createLabelMap,
createSelectedValuesMap,
extractCrossFilterProps,
isDataPointFiltered,
} from '@superset-ui/glyph-core';
describe('createSelectedValuesMap', () => {
test('returns empty object when filterState is undefined', () => {
expect(createSelectedValuesMap(undefined, ['a', 'b'])).toEqual({});
});
test('returns empty object when selectedValues is undefined', () => {
expect(
createSelectedValuesMap({} as FilterState, ['a', 'b']),
).toEqual({});
});
test('returns empty object when selectedValues is empty', () => {
expect(
createSelectedValuesMap(
{ selectedValues: [] } as unknown as FilterState,
['a', 'b'],
),
).toEqual({});
});
test('maps selected value to its index in seriesNames', () => {
const result = createSelectedValuesMap(
{ selectedValues: ['b'] } as unknown as FilterState,
['a', 'b', 'c'],
);
expect(result).toEqual({ 1: 'b' });
});
test('maps multiple selected values to their indices', () => {
const result = createSelectedValuesMap(
{ selectedValues: ['a', 'c'] } as unknown as FilterState,
['a', 'b', 'c'],
);
expect(result).toEqual({ 0: 'a', 2: 'c' });
});
test('ignores selected values not in seriesNames', () => {
const result = createSelectedValuesMap(
{ selectedValues: ['x', 'a'] } as unknown as FilterState,
['a', 'b', 'c'],
);
expect(result).toEqual({ 0: 'a' });
});
});
describe('isDataPointFiltered', () => {
test('returns false when no filterState', () => {
expect(isDataPointFiltered(undefined, 'a')).toBe(false);
});
test('returns false when selectedValues is empty', () => {
expect(
isDataPointFiltered(
{ selectedValues: [] } as unknown as FilterState,
'a',
),
).toBe(false);
});
test('returns false when name is in selectedValues', () => {
expect(
isDataPointFiltered(
{ selectedValues: ['a', 'b'] } as unknown as FilterState,
'a',
),
).toBe(false);
});
test('returns true when name is NOT in non-empty selectedValues', () => {
expect(
isDataPointFiltered(
{ selectedValues: ['a', 'b'] } as unknown as FilterState,
'c',
),
).toBe(true);
});
});
describe('createLabelMap', () => {
test('returns empty object for empty data', () => {
expect(createLabelMap([], ['col1'], () => 'label')).toEqual({});
});
test('maps each record to its label and groupby column values', () => {
const data = [
{ country: 'USA', region: 'North' },
{ country: 'Brazil', region: 'South' },
];
const result = createLabelMap(
data,
['country', 'region'],
d => d.country as string,
);
expect(result).toEqual({
USA: ['USA', 'North'],
Brazil: ['Brazil', 'South'],
});
});
test('last record wins when extractLabel collides', () => {
const data = [
{ name: 'X', value: 1 },
{ name: 'X', value: 2 },
];
const result = createLabelMap(data, ['value'], d => d.name as string);
// collision: later entry overwrites
expect(result).toEqual({ X: [2] });
});
test('groupbyLabels controls the columns extracted, not the label', () => {
const data = [{ a: 1, b: 2, c: 3 }];
const result = createLabelMap(data, ['c'], () => 'only-key');
expect(result).toEqual({ 'only-key': [3] });
});
});
describe('extractCrossFilterProps', () => {
const baseChartProps = {
hooks: { setDataMask: jest.fn(), onContextMenu: jest.fn() },
filterState: {
selectedValues: ['USA'],
} as unknown as FilterState,
emitCrossFilters: true,
formData: { viz_type: 'test' },
} as unknown as ChartProps;
test('returns all expected fields', () => {
const result = extractCrossFilterProps(
baseChartProps,
['country'],
{ USA: ['USA'] },
['USA', 'Brazil'],
);
expect(result.groupby).toEqual(['country']);
expect(result.labelMap).toEqual({ USA: ['USA'] });
expect(result.selectedValues).toEqual({ 0: 'USA' });
expect(result.emitCrossFilters).toBe(true);
expect(result.setDataMask).toBe(baseChartProps.hooks!.setDataMask);
expect(result.onContextMenu).toBe(baseChartProps.hooks!.onContextMenu);
});
test('coltypeMapping pass-through when provided', () => {
const result = extractCrossFilterProps(
baseChartProps,
['country'],
{},
[],
{ country: 1 },
);
expect(result.coltypeMapping).toEqual({ country: 1 });
});
test('defaults setDataMask to a no-op when hooks omits it', () => {
const chartProps = {
...baseChartProps,
hooks: {},
} as unknown as ChartProps;
const result = extractCrossFilterProps(chartProps, [], {}, []);
expect(typeof result.setDataMask).toBe('function');
// No throw when invoked
expect(() =>
result.setDataMask({ filterState: {} } as unknown as Parameters<
typeof result.setDataMask
>[0]),
).not.toThrow();
});
test('formData is included in the returned shape (for context menu formatting)', () => {
const result = extractCrossFilterProps(
baseChartProps,
['country'],
{},
[],
) as ReturnType<typeof extractCrossFilterProps> & { formData: unknown };
expect(result.formData).toEqual({ viz_type: 'test' });
});
test('selectedValues is empty when filterState has none', () => {
const chartProps = {
...baseChartProps,
filterState: {} as FilterState,
} as unknown as ChartProps;
const result = extractCrossFilterProps(chartProps, [], {}, ['x']);
expect(result.selectedValues).toEqual({});
});
});

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