mirror of
https://github.com/apache/superset.git
synced 2026-07-28 09:32:28 +00:00
Compare commits
1 Commits
dependabot
...
sc-114990-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b73f3dfbf |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1,6 +1,6 @@
|
||||
# Notify all committers of DB migration changes, per SIP-59
|
||||
|
||||
/superset/migrations/ @mistercrunch @michael-s-molina @betodealmeida @eschutho @sadpandajoe @rusackas
|
||||
/superset/migrations/ @mistercrunch @michael-s-molina @betodealmeida @eschutho @sadpandajoe
|
||||
|
||||
# Notify some committers of changes in the components
|
||||
|
||||
|
||||
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -70,10 +70,6 @@ updates:
|
||||
- "@storybook/*"
|
||||
- "storybook"
|
||||
- "eslint-plugin-storybook"
|
||||
ag-grid:
|
||||
patterns:
|
||||
- "ag-grid-react"
|
||||
- "ag-grid-community"
|
||||
open-pull-requests-limit: 30
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
|
||||
4
.github/workflows/bump-python-package.yml
vendored
4
.github/workflows/bump-python-package.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
checks: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
|
||||
2
.github/workflows/check-python-deps.yml
vendored
2
.github/workflows/check-python-deps.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check and notify
|
||||
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
frontend: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
@@ -58,13 +58,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
||||
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -75,6 +75,6 @@ jobs:
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@e0647621c2984b5ed2f768cb892365bf2a616ad1 # v4.37.2
|
||||
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Dependency Review"
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
6
.github/workflows/docker.yml
vendored
6
.github/workflows/docker.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
docker: ${{ steps.check.outputs.docker }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -184,7 +184,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Free up disk space
|
||||
|
||||
2
.github/workflows/embedded-sdk-release.yml
vendored
2
.github/workflows/embedded-sdk-release.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
run:
|
||||
working-directory: superset-embedded-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
# Note: registry-url is intentionally omitted. When set, actions/setup-node
|
||||
|
||||
2
.github/workflows/embedded-sdk-test.yml
vendored
2
.github/workflows/embedded-sdk-test.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
run:
|
||||
working-directory: superset-embedded-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
|
||||
2
.github/workflows/generate-FOSSA-report.yml
vendored
2
.github/workflows/generate-FOSSA-report.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
2
.github/workflows/issue_creation.yml
vendored
2
.github/workflows/issue_creation.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
issues: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
|
||||
- uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
|
||||
with:
|
||||
sync-labels: true
|
||||
|
||||
|
||||
2
.github/workflows/latest-release-tag.yml
vendored
2
.github/workflows/latest-release-tag.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/license-check.yml
vendored
2
.github/workflows/license-check.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/pr-lint.yml
vendored
2
.github/workflows/pr-lint.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
# pulls all commits (needed for lerna / semantic release to correctly version)
|
||||
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: "Checkout release tag: ${{ needs.config.outputs.latest-release }}"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ needs.config.outputs.latest-release }}
|
||||
fetch-depth: 0
|
||||
|
||||
2
.github/workflows/showtime-trigger.yml
vendored
2
.github/workflows/showtime-trigger.yml
vendored
@@ -153,7 +153,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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ steps.check.outputs.target_sha }}
|
||||
persist-credentials: false
|
||||
|
||||
2
.github/workflows/superset-app-cli.yml
vendored
2
.github/workflows/superset-app-cli.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/superset-docs-deploy.yml
vendored
2
.github/workflows/superset-docs-deploy.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.event.workflow_run.head_sha || github.sha }}"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
6
.github/workflows/superset-docs-verify.yml
vendored
6
.github/workflows/superset-docs-verify.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
name: Link Checking
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha }}
|
||||
persist-credentials: false
|
||||
|
||||
14
.github/workflows/superset-e2e.yml
vendored
14
.github/workflows/superset-e2e.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
frontend: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
@@ -96,21 +96,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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
@@ -206,21 +206,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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
working-directory: superset-extensions-cli
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
18
.github/workflows/superset-frontend.yml
vendored
18
.github/workflows/superset-frontend.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
should-run: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -122,13 +122,25 @@ jobs:
|
||||
pattern: coverage-artifacts-*
|
||||
path: coverage/
|
||||
|
||||
- name: Reorganize test result reports
|
||||
run: |
|
||||
find coverage/
|
||||
for i in {1..8}; do
|
||||
mv coverage/coverage-artifacts-${i}/coverage-final.json coverage/coverage-shard-${i}.json
|
||||
done
|
||||
shell: bash
|
||||
|
||||
- name: Merge Code Coverage
|
||||
run: npx nyc merge coverage/ merged-output/coverage-summary.json
|
||||
|
||||
- name: Upload Code Coverage
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
flags: javascript
|
||||
use_oidc: true
|
||||
verbose: true
|
||||
directory: coverage
|
||||
disable_search: true
|
||||
files: merged-output/coverage-summary.json
|
||||
slug: apache/superset
|
||||
|
||||
lint-frontend:
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/superset-helm-release.yml
vendored
2
.github/workflows/superset-helm-release.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref_name }}
|
||||
persist-credentials: true
|
||||
|
||||
8
.github/workflows/superset-playwright.yml
vendored
8
.github/workflows/superset-playwright.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
frontend: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
python: ${{ steps.check.outputs.python }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -207,7 +207,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
python: ${{ steps.check.outputs.python }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
python: ${{ steps.check.outputs.python }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
SQLALCHEMY_WARN_20: "1"
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
4
.github/workflows/superset-translations.yml
vendored
4
.github/workflows/superset-translations.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/superset-websocket.yml
vendored
2
.github/workflows/superset-websocket.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Node.js
|
||||
|
||||
2
.github/workflows/supersetbot.yml
vendored
2
.github/workflows/supersetbot.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
});
|
||||
|
||||
- name: "Checkout ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
# 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
persist-credentials: true
|
||||
|
||||
4
.github/workflows/tag-release.yml
vendored
4
.github/workflows/tag-release.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
2
.github/workflows/tech-debt.yml
vendored
2
.github/workflows/tech-debt.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
name: Generate Reports
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -57,7 +57,6 @@ local_config.py
|
||||
/superset_text.yml
|
||||
superset.egg-info/
|
||||
superset/bin/supersetc
|
||||
superset/extensions/.reload_trigger
|
||||
tmp
|
||||
rat-results.txt
|
||||
superset/app/
|
||||
|
||||
@@ -144,7 +144,7 @@ repos:
|
||||
git fetch --no-recurse-submodules origin "$TARGET_BRANCH" 2>/dev/null || true
|
||||
fi
|
||||
BASE=$(git merge-base origin/"$TARGET_BRANCH" HEAD 2>/dev/null) || BASE="HEAD"
|
||||
files=$(git diff --name-only --diff-filter=ACM "$BASE"..HEAD 2>/dev/null | grep '^superset/.*\.py$' | grep -v '^superset/migrations/' || true)
|
||||
files=$(git diff --name-only --diff-filter=ACM "$BASE"..HEAD 2>/dev/null | grep '^superset/.*\.py$' || true)
|
||||
if [ -n "$files" ]; then
|
||||
pylint --rcfile=.pylintrc --load-plugins=superset.extensions.pylint --reports=no $files
|
||||
else
|
||||
|
||||
@@ -168,11 +168,6 @@ categories:
|
||||
url: https://www.now.vn/
|
||||
contributors: ["@davidkohcw"]
|
||||
|
||||
- name: Pattern
|
||||
url: https://www.pattern.com
|
||||
contributors: ["@alephys26", "@hladush", "@sanketjadhavSF"]
|
||||
logo: pattern_logo.png
|
||||
|
||||
- name: Qunar
|
||||
url: https://www.qunar.com/
|
||||
contributors: ["@flametest"]
|
||||
|
||||
10
UPDATING.md
10
UPDATING.md
@@ -358,16 +358,6 @@ A read-only companion to the version-history endpoints: each entity type gains a
|
||||
|
||||
Authorization reuses the resource's `can_read` permission and per-object `raise_for_access`; related-entity rows are visibility-filtered to what the caller may see. The stream is empty unless version capture is on (`ENABLE_VERSIONING_CAPTURE`).
|
||||
|
||||
### Version-history retention (pruning)
|
||||
|
||||
Entity version history (the `version_transaction` / `*_version` shadow tables that back version capture) is aged out by a nightly Celery beat task, `version_history.prune_old_versions` (`superset.tasks.version_history_retention`).
|
||||
|
||||
| Key | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `SUPERSET_VERSION_HISTORY_RETENTION_DAYS` | `30` | Version rows whose owning `version_transaction.issued_at` is older than this many days are pruned. Each entity's live row (`end_transaction_id IS NULL`) is always preserved, as are the live rows of its children and associations; closed historical rows (including the baseline) age out. Set to `0` or a negative value to disable pruning. |
|
||||
|
||||
The task ships in the default `CeleryConfig.beat_schedule`; a deployment that overrides `CELERY_CONFIG` without inheriting the default will log a startup warning that the prune task is absent (so it never silently stops running). Retention only prunes whatever history exists — capture itself is gated separately by `ENABLE_VERSIONING_CAPTURE` (ships off).
|
||||
|
||||
### Webhook alerts/reports block private/internal hosts by default
|
||||
|
||||
Webhook alert/report dispatch (`WebhookNotification.send`) now validates the target URL's host against the same private/internal-IP block applied to dataset import URLs. If the resolved host is in a loopback, link-local, private (RFC-1918), shared-CGNAT, or multicast range, the webhook is rejected with `NotificationParamException`.
|
||||
|
||||
@@ -34,7 +34,6 @@ x-superset-volumes: &superset-volumes
|
||||
- superset_home:/app/superset_home
|
||||
- ./tests:/app/tests
|
||||
- superset_data:/app/data
|
||||
- ./local_extensions:/app/local_extensions
|
||||
x-common-build: &common-build
|
||||
context: .
|
||||
target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean`
|
||||
|
||||
@@ -98,9 +98,7 @@ case "${1}" in
|
||||
echo " 🔒 Werkzeug debugger disabled (set SUPERSET_DEBUG_ENABLED=true to enable)"
|
||||
fi
|
||||
|
||||
flask run -p $PORT --reload $DEBUGGER_FLAG --host=0.0.0.0 \
|
||||
--extra-files "/app/superset/extensions/.reload_trigger" \
|
||||
--exclude-patterns "*/node_modules/*:*/.venv/*:*/build/*:*/__pycache__/*:*/superset-frontend/*:*/superset/__init__.py"
|
||||
flask run -p $PORT --reload $DEBUGGER_FLAG --host=0.0.0.0 --exclude-patterns "*/node_modules/*:*/.venv/*:*/build/*:*/__pycache__/*:*/superset-frontend/*"
|
||||
;;
|
||||
app-gunicorn)
|
||||
echo "Starting web app..."
|
||||
|
||||
@@ -25,33 +25,6 @@ DECKGL_BASE_MAP = [
|
||||
]
|
||||
```
|
||||
|
||||
[Yandex Tiles API](https://yandex.ru/maps-api/docs/tiles-api/request.html) can be used as a custom tile provider. The URL must include the `apikey` from the [Yandex Developer Dashboard](https://developer.tech.yandex.ru/services), tile coordinates (`x`, `y`, and `z`), and the required map layer parameters.
|
||||
|
||||
```python
|
||||
DECKGL_BASE_MAP = [
|
||||
[
|
||||
"tile://https://tiles.api-maps.yandex.ru/v1/tiles/"
|
||||
"?apikey=YOUR_YANDEX_API_KEY"
|
||||
"&lang=ru_RU"
|
||||
"&x={x}&y={y}&z={z}"
|
||||
"&l=map"
|
||||
"&projection=web_mercator"
|
||||
"&maptype=map",
|
||||
"Yandex Maps - Web Mercator",
|
||||
],
|
||||
[
|
||||
"tile://https://tiles.api-maps.yandex.ru/v1/tiles/"
|
||||
"?apikey=YOUR_YANDEX_API_KEY"
|
||||
"&lang=ru_RU"
|
||||
"&x={x}&y={y}&z={z}"
|
||||
"&l=map"
|
||||
"&projection=wgs84_mercator"
|
||||
"&maptype=map",
|
||||
"Yandex Maps - WGS84 Mercator",
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
Default values are:
|
||||
```python
|
||||
DECKGL_BASE_MAP = [
|
||||
@@ -83,7 +56,6 @@ CORS_OPTIONS: dict[Any, Any] = {
|
||||
"https://tile.openstreetmap.org",
|
||||
"https://tile.osm.ch",
|
||||
"https://your_personal_url/{z}/{x}/{y}.png",
|
||||
"https://tiles.api-maps.yandex.ru",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -100,7 +72,6 @@ TALISMAN_CONFIG = {
|
||||
"https://tile.openstreetmap.org",
|
||||
"https://tile.osm.ch",
|
||||
"https://your_personal_url/{z}/{x}/{y}.png",
|
||||
"https://tiles.api-maps.yandex.ru",
|
||||
],
|
||||
...
|
||||
}
|
||||
|
||||
@@ -53,20 +53,20 @@
|
||||
"@emotion/core": "^11.0.0",
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@fontsource/fira-code": "^5.3.0",
|
||||
"@fontsource/ibm-plex-mono": "^5.3.0",
|
||||
"@fontsource/inter": "^5.3.0",
|
||||
"@fontsource/fira-code": "^5.2.7",
|
||||
"@fontsource/ibm-plex-mono": "^5.2.7",
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@saucelabs/theme-github-codeblock": "^0.3.0",
|
||||
"@storybook/addon-docs": "^10.5.0",
|
||||
"@superset-ui/core": "^0.20.4",
|
||||
"@swc/core": "^1.15.46",
|
||||
"@swc/core": "^1.15.43",
|
||||
"antd": "^6.5.1",
|
||||
"baseline-browser-mapping": "^2.10.43",
|
||||
"caniuse-lite": "^1.0.30001806",
|
||||
"caniuse-lite": "^1.0.30001805",
|
||||
"docusaurus-plugin-openapi-docs": "^5.1.2",
|
||||
"docusaurus-theme-openapi-docs": "^5.1.2",
|
||||
"js-yaml": "^5.2.2",
|
||||
"js-yaml": "^5.2.0",
|
||||
"json-bigint": "^1.0.0",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^18.3.1",
|
||||
@@ -78,7 +78,7 @@
|
||||
"remark-import-partial": "^0.0.2",
|
||||
"reselect": "^5.2.0",
|
||||
"storybook": "^10.5.0",
|
||||
"swagger-ui-react": "^5.32.9",
|
||||
"swagger-ui-react": "^5.32.8",
|
||||
"swc-loader": "^0.2.7",
|
||||
"tinycolor2": "^1.4.2",
|
||||
"unist-util-visit": "^5.1.0"
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"generated": "2026-04-17T17:08:35.132Z",
|
||||
"statistics": {
|
||||
"totalDatabases": 81,
|
||||
"withDocumentation": 81,
|
||||
"withConnectionString": 81,
|
||||
"withDrivers": 29,
|
||||
"totalDatabases": 80,
|
||||
"withDocumentation": 80,
|
||||
"withConnectionString": 80,
|
||||
"withDrivers": 28,
|
||||
"withAuthMethods": 5,
|
||||
"supportsJoins": 75,
|
||||
"supportsSubqueries": 78,
|
||||
"supportsJoins": 74,
|
||||
"supportsSubqueries": 77,
|
||||
"supportsDynamicSchema": 29,
|
||||
"supportsCatalog": 20,
|
||||
"averageScore": 53,
|
||||
"averageScore": 54,
|
||||
"maxScore": 201,
|
||||
"byCategory": {
|
||||
"Cloud - AWS": [
|
||||
@@ -20,7 +20,6 @@
|
||||
],
|
||||
"Query Engines": [
|
||||
"Amazon Athena",
|
||||
"Apache DataFusion",
|
||||
"Apache Drill",
|
||||
"Apache Hive",
|
||||
"Apache Impala",
|
||||
@@ -95,7 +94,6 @@
|
||||
"Vertica"
|
||||
],
|
||||
"Apache Projects": [
|
||||
"Apache DataFusion",
|
||||
"Apache Doris",
|
||||
"Apache Drill",
|
||||
"Apache Druid",
|
||||
@@ -110,7 +108,6 @@
|
||||
"Databricks Interactive Cluster"
|
||||
],
|
||||
"Open Source": [
|
||||
"Apache DataFusion",
|
||||
"Apache Doris",
|
||||
"Apache Drill",
|
||||
"Apache Druid",
|
||||
@@ -9701,97 +9698,6 @@
|
||||
"supports_dynamic_schema": true,
|
||||
"supports_catalog": true,
|
||||
"supports_dynamic_catalog": true
|
||||
},
|
||||
"Apache DataFusion": {
|
||||
"time_grains": {
|
||||
"SECOND": true,
|
||||
"FIVE_SECONDS": true,
|
||||
"THIRTY_SECONDS": true,
|
||||
"MINUTE": true,
|
||||
"FIVE_MINUTES": true,
|
||||
"TEN_MINUTES": true,
|
||||
"FIFTEEN_MINUTES": true,
|
||||
"THIRTY_MINUTES": true,
|
||||
"HALF_HOUR": false,
|
||||
"HOUR": true,
|
||||
"SIX_HOURS": false,
|
||||
"DAY": true,
|
||||
"WEEK": true,
|
||||
"WEEK_STARTING_SUNDAY": false,
|
||||
"WEEK_STARTING_MONDAY": false,
|
||||
"WEEK_ENDING_SATURDAY": false,
|
||||
"WEEK_ENDING_SUNDAY": false,
|
||||
"MONTH": true,
|
||||
"QUARTER": true,
|
||||
"QUARTER_YEAR": false,
|
||||
"YEAR": true
|
||||
},
|
||||
"module": "superset.db_engine_specs.datafusion",
|
||||
"limit_method": 1,
|
||||
"limit_clause": true,
|
||||
"joins": true,
|
||||
"subqueries": true,
|
||||
"alias_in_select": true,
|
||||
"alias_in_orderby": true,
|
||||
"time_groupby_inline": false,
|
||||
"alias_to_source_column": false,
|
||||
"order_by_not_in_select": true,
|
||||
"expressions_in_orderby": false,
|
||||
"cte_in_subquery": true,
|
||||
"max_column_name": null,
|
||||
"sql_comments": true,
|
||||
"escaped_colons": true,
|
||||
"masked_encrypted_extra": false,
|
||||
"column_type_mapping": false,
|
||||
"function_names": false,
|
||||
"user_impersonation": false,
|
||||
"file_upload": false,
|
||||
"get_extra_table_metadata": false,
|
||||
"dbapi_exception_mapping": false,
|
||||
"custom_errors": false,
|
||||
"dynamic_schema": false,
|
||||
"catalog": false,
|
||||
"dynamic_catalog": false,
|
||||
"ssh_tunneling": true,
|
||||
"query_cancelation": false,
|
||||
"get_metrics": false,
|
||||
"where_latest_partition": false,
|
||||
"expand_data": false,
|
||||
"query_cost_estimation": false,
|
||||
"sql_validation": false,
|
||||
"score": 24,
|
||||
"max_score": 201,
|
||||
"documentation": {
|
||||
"description": "DataFusion is a highly performant query engine",
|
||||
"logo": "datafusion.png",
|
||||
"homepage_url": "https://datafusion.apache.org/",
|
||||
"categories": [
|
||||
"Query Engines",
|
||||
"Open Source",
|
||||
"Apache Projects"
|
||||
],
|
||||
"pypi_packages": [
|
||||
"flightsql-dbapi"
|
||||
],
|
||||
"connection_string": "datafusion://host:port",
|
||||
"drivers": [
|
||||
{
|
||||
"name": "Arrow Flight SQL (Recommended)",
|
||||
"pypi_package": "flightsql-dbapi",
|
||||
"connection_string": "datafusion://host:port",
|
||||
"is_recommended": true
|
||||
}
|
||||
],
|
||||
"category": "Apache Projects"
|
||||
},
|
||||
"engine": "datafusion",
|
||||
"engine_name": "Apache DataFusion",
|
||||
"engine_aliases": [],
|
||||
"default_driver": "flightsql",
|
||||
"supports_file_upload": false,
|
||||
"supports_dynamic_schema": false,
|
||||
"supports_catalog": false,
|
||||
"supports_dynamic_catalog": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
docs/static/img/databases/datafusion.png
vendored
BIN
docs/static/img/databases/datafusion.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
BIN
docs/static/img/logos/pattern_logo.png
vendored
BIN
docs/static/img/logos/pattern_logo.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 6.9 KiB |
212
docs/yarn.lock
212
docs/yarn.lock
@@ -2529,20 +2529,20 @@
|
||||
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-5.5.3.tgz#18e3af6b8eae7984072bbeb0c0858474d7c4cefe"
|
||||
integrity sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==
|
||||
|
||||
"@fontsource/fira-code@^5.3.0":
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@fontsource/fira-code/-/fira-code-5.3.0.tgz#487475cedfa1f7ba7650810ddce00b6c7b1b7a25"
|
||||
integrity sha512-EJL968RJRkakubAj/coU8pSUaeTE5UNoRjtzAr6kGiSZ3jWuN8/AKWHwym/PFUaQL1q7IL/H+EXs4358YhrTBQ==
|
||||
"@fontsource/fira-code@^5.2.7":
|
||||
version "5.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@fontsource/fira-code/-/fira-code-5.2.7.tgz#9ecbd909d53e7196a5d895b601747fe34491fc6a"
|
||||
integrity sha512-tnB9NNund9TwIym8/7DMJe573nlPEQb+fKUV5GL8TBYXjIhDvL0D7mgmNVNQUPhXp+R7RylQeiBdkA4EbOHPGQ==
|
||||
|
||||
"@fontsource/ibm-plex-mono@^5.3.0":
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.3.0.tgz#1879699d104602d5331e28e103f33c3b1b766b17"
|
||||
integrity sha512-eTgnZjZEGk1QtD3ZstF+Vclo2HLAni8YMy34/DxllwZvyz1lR/1RF/xTiAquOBO7MvqBx8D2Ig2WCPMVfdZu7Q==
|
||||
"@fontsource/ibm-plex-mono@^5.2.7":
|
||||
version "5.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.2.7.tgz#ef5b6f052115fdf6666208a5f8a0f13fcd7ba1fd"
|
||||
integrity sha512-MKAb8qV+CaiMQn2B0dIi1OV3565NYzp3WN5b4oT6LTkk+F0jR6j0ZN+5BKJiIhffDC3rtBULsYZE65+0018z9w==
|
||||
|
||||
"@fontsource/inter@^5.3.0":
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.3.0.tgz#866278dc2357659f8be1755c8b67f29d3697b440"
|
||||
integrity sha512-RofMylZmjlJEfELXeNHFWBRcSs75rGU/6bV2S2jfnvv/3rPXPGe0LgUJTklcHZ9lM4OZmAVFhcJPnACfb91A3g==
|
||||
"@fontsource/inter@^5.2.8":
|
||||
version "5.2.8"
|
||||
resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.2.8.tgz#10c95d877d972c7de5bd4592309d42fb6a5e1a5b"
|
||||
integrity sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==
|
||||
|
||||
"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
|
||||
version "9.3.0"
|
||||
@@ -4697,86 +4697,86 @@
|
||||
dependencies:
|
||||
apg-lite "^1.0.4"
|
||||
|
||||
"@swc/core-darwin-arm64@1.15.46":
|
||||
version "1.15.46"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.46.tgz#393903c7eda790dbd89abd8fa0afdd9041543e5f"
|
||||
integrity sha512-IsISIT22EfktVJrlvIpnAxG2u/A9aob9l99HMlx80x72WlFmFPk1V3UhkEzx86eJP8hw049KTFv/RISho2cq2Q==
|
||||
"@swc/core-darwin-arm64@1.15.43":
|
||||
version "1.15.43"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.43.tgz#386294f8427dde2df1a70dd0a5826d67af70e996"
|
||||
integrity sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA==
|
||||
|
||||
"@swc/core-darwin-x64@1.15.46":
|
||||
version "1.15.46"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.46.tgz#ddf16787e320636621180df480a3490fd9a868ca"
|
||||
integrity sha512-4Tj4ppVIPCmUMpmGFiGtyEriwLyJ+yi/US4WfBrP/ok8COGddDZXLEzQETnKyK46mjvr1v0jevrS23zjoff7vA==
|
||||
"@swc/core-darwin-x64@1.15.43":
|
||||
version "1.15.43"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.43.tgz#c4823529c424e2ae25b7eb786438474741521fcb"
|
||||
integrity sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ==
|
||||
|
||||
"@swc/core-linux-arm-gnueabihf@1.15.46":
|
||||
version "1.15.46"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.46.tgz#7bee01b7311c43b913771ef9c7012931871de73b"
|
||||
integrity sha512-i8tUGnNjyOgMmfmgFSg4aeJLQoFyfpIHK5FjpQAwpRyQIqEUB2w1e8zIDQzY1WhOxx8NoS1S5iUL813Un4Sf5A==
|
||||
"@swc/core-linux-arm-gnueabihf@1.15.43":
|
||||
version "1.15.43"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.43.tgz#c0a0ed17cffc5d4af192935667f12f05feeb39f9"
|
||||
integrity sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA==
|
||||
|
||||
"@swc/core-linux-arm64-gnu@1.15.46":
|
||||
version "1.15.46"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.46.tgz#964596d757d18f04a02873d85a3660416c09c187"
|
||||
integrity sha512-c0OnhqzdhfOvv6qhNCcByepB+sNYOGZyhtr2Qa6ZCHvAWTYhSRw4j/u92Stue9PbZ/6q74b9nHzi76+kVzqQHQ==
|
||||
"@swc/core-linux-arm64-gnu@1.15.43":
|
||||
version "1.15.43"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.43.tgz#1eb2d9c5eeee5bb9d00599b475ddc31dc2870d22"
|
||||
integrity sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw==
|
||||
|
||||
"@swc/core-linux-arm64-musl@1.15.46":
|
||||
version "1.15.46"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.46.tgz#213d3ece772689a8166ed51064836346c6ce1c2a"
|
||||
integrity sha512-imyRpNEcUzFQFV2LE4jL68ErvmKEuZCbvZru77iQREunJ+bR4i658cupTgtG1mLYM3F1Tzy3Sb9xYb02KghWTg==
|
||||
"@swc/core-linux-arm64-musl@1.15.43":
|
||||
version "1.15.43"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.43.tgz#ea6b5c38088f3921a57922d3931b2d74fd23a9fd"
|
||||
integrity sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ==
|
||||
|
||||
"@swc/core-linux-ppc64-gnu@1.15.46":
|
||||
version "1.15.46"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.46.tgz#4d2ec554103c6bef60cc1e294f374ea5a5edaf78"
|
||||
integrity sha512-ctEfcl/HcUeomK33cbySiHZm98GEDIxTm1EkpBsYCiHxElYBzvTXVeuQT2YwbUXn9XCrjiw4ipyUNk33k26qRg==
|
||||
"@swc/core-linux-ppc64-gnu@1.15.43":
|
||||
version "1.15.43"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.43.tgz#538fac30bbd5f1e678bb7bac9ccc62246a6f6d7a"
|
||||
integrity sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg==
|
||||
|
||||
"@swc/core-linux-s390x-gnu@1.15.46":
|
||||
version "1.15.46"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.46.tgz#097a19792ec22e2f51f6bfac02da1e0b3f5e5bb1"
|
||||
integrity sha512-DxlMdnt84TtRVTv7WL/thWyz9+QU8QZNNoAP9rrk0P68LziuhfePp8MjQ44zIprpTHTsEwyziIuGUUN5iSC1bQ==
|
||||
"@swc/core-linux-s390x-gnu@1.15.43":
|
||||
version "1.15.43"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.43.tgz#ee564b45f3f578b1fc82136c4dab163189316641"
|
||||
integrity sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA==
|
||||
|
||||
"@swc/core-linux-x64-gnu@1.15.46":
|
||||
version "1.15.46"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.46.tgz#39c1ca215f9ca643a4aa3ca6250cc38ba5f5c673"
|
||||
integrity sha512-SKxI7J6t90XPl8hRUqtJi9NfGdunN/E/vZMc7Bc0figeRdOPDBT+Tm8g7cx9xM0T0mewh2l+8dewa3Am27/P+A==
|
||||
"@swc/core-linux-x64-gnu@1.15.43":
|
||||
version "1.15.43"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.43.tgz#e6e3bfea76921c7f5e16d50a126615f2e04ce1c8"
|
||||
integrity sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg==
|
||||
|
||||
"@swc/core-linux-x64-musl@1.15.46":
|
||||
version "1.15.46"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.46.tgz#323a720bc965fffeedacdc3167b46a291553b5e0"
|
||||
integrity sha512-qj9T6B7bosI0VEsrWOVXZN1OXxS8Tp63ywyrLxNdOycnUtLdkgYcoBsN5y8ImnDDsnwrEWZOy1e+J4xSe7mA3Q==
|
||||
"@swc/core-linux-x64-musl@1.15.43":
|
||||
version "1.15.43"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.43.tgz#539f6f2721c0cc32e5db5cf0d453c82045f6662d"
|
||||
integrity sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ==
|
||||
|
||||
"@swc/core-win32-arm64-msvc@1.15.46":
|
||||
version "1.15.46"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.46.tgz#9c2cfd2a59be74671a018097b8914f8cfbcc698d"
|
||||
integrity sha512-8p7l4c3LU+eA5g9Et1JPhNeMC1oQwXTGU+uah8DPIBX7YXzqswvaBtyKVmXefVGi/DJU1x3YJsc3mbAp9aWzSQ==
|
||||
"@swc/core-win32-arm64-msvc@1.15.43":
|
||||
version "1.15.43"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.43.tgz#b7bb6b611d484ac19d0ee21469e7012d646c28b5"
|
||||
integrity sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw==
|
||||
|
||||
"@swc/core-win32-ia32-msvc@1.15.46":
|
||||
version "1.15.46"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.46.tgz#bd7bd009a47b0f9826212e7ed36385d32fe193d8"
|
||||
integrity sha512-tUEnfr3Bn9u6FOjUb3PN9p+09qZC2j+wNDLKHzXXZn22rqGcUqR/ohCRSS+nG9B9+X+U+3FewNEHJkTmdIvMjQ==
|
||||
"@swc/core-win32-ia32-msvc@1.15.43":
|
||||
version "1.15.43"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.43.tgz#e5b25722a7d27bb0c9a9bdee7863f29c8674364e"
|
||||
integrity sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g==
|
||||
|
||||
"@swc/core-win32-x64-msvc@1.15.46":
|
||||
version "1.15.46"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.46.tgz#8371845a5bdb330cf05b009f602bb8c4636c6beb"
|
||||
integrity sha512-Vux7UDzBJYQggSuPfcl2w9iu+IJpgpRCxHzgCaVkELnAXAE4XZMOTX9HNcaNiwfeIDqdu2rkr69RuDm6wY8neA==
|
||||
"@swc/core-win32-x64-msvc@1.15.43":
|
||||
version "1.15.43"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.43.tgz#d28842621201c345383d468d40c09648b6cd6e68"
|
||||
integrity sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg==
|
||||
|
||||
"@swc/core@^1.15.40", "@swc/core@^1.15.46":
|
||||
version "1.15.46"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.46.tgz#8acc0f68ee55010fdc876adf2a8faf0b097c681b"
|
||||
integrity sha512-Ri3em2mBpq3h2zSPliCYl63otDGqek8PPEfv2nWgRQEbZ/VBCNyypVTVQ6cEbTCXBhy+WE2T3fQb08moIyuYaw==
|
||||
"@swc/core@^1.15.40", "@swc/core@^1.15.43":
|
||||
version "1.15.43"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.43.tgz#653e6573968fd5c74163b9885ea0a933012c9f22"
|
||||
integrity sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw==
|
||||
dependencies:
|
||||
"@swc/counter" "^0.1.3"
|
||||
"@swc/types" "^0.1.27"
|
||||
optionalDependencies:
|
||||
"@swc/core-darwin-arm64" "1.15.46"
|
||||
"@swc/core-darwin-x64" "1.15.46"
|
||||
"@swc/core-linux-arm-gnueabihf" "1.15.46"
|
||||
"@swc/core-linux-arm64-gnu" "1.15.46"
|
||||
"@swc/core-linux-arm64-musl" "1.15.46"
|
||||
"@swc/core-linux-ppc64-gnu" "1.15.46"
|
||||
"@swc/core-linux-s390x-gnu" "1.15.46"
|
||||
"@swc/core-linux-x64-gnu" "1.15.46"
|
||||
"@swc/core-linux-x64-musl" "1.15.46"
|
||||
"@swc/core-win32-arm64-msvc" "1.15.46"
|
||||
"@swc/core-win32-ia32-msvc" "1.15.46"
|
||||
"@swc/core-win32-x64-msvc" "1.15.46"
|
||||
"@swc/core-darwin-arm64" "1.15.43"
|
||||
"@swc/core-darwin-x64" "1.15.43"
|
||||
"@swc/core-linux-arm-gnueabihf" "1.15.43"
|
||||
"@swc/core-linux-arm64-gnu" "1.15.43"
|
||||
"@swc/core-linux-arm64-musl" "1.15.43"
|
||||
"@swc/core-linux-ppc64-gnu" "1.15.43"
|
||||
"@swc/core-linux-s390x-gnu" "1.15.43"
|
||||
"@swc/core-linux-x64-gnu" "1.15.43"
|
||||
"@swc/core-linux-x64-musl" "1.15.43"
|
||||
"@swc/core-win32-arm64-msvc" "1.15.43"
|
||||
"@swc/core-win32-ia32-msvc" "1.15.43"
|
||||
"@swc/core-win32-x64-msvc" "1.15.43"
|
||||
|
||||
"@swc/counter@^0.1.3":
|
||||
version "0.1.3"
|
||||
@@ -6394,9 +6394,9 @@ binary-extensions@^2.0.0:
|
||||
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
|
||||
|
||||
body-parser@~1.20.5:
|
||||
version "1.20.6"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.6.tgz#60c789c78e0992d906da0a29d71ae01d15c1ed76"
|
||||
integrity sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==
|
||||
version "1.20.5"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.5.tgz#303c8c34423d1d6fa799bc764e93c1e4dc6ebf64"
|
||||
integrity sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==
|
||||
dependencies:
|
||||
bytes "~3.1.2"
|
||||
content-type "~1.0.5"
|
||||
@@ -6602,10 +6602,10 @@ caniuse-api@^3.0.0:
|
||||
lodash.memoize "^4.1.2"
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001799, caniuse-lite@^1.0.30001806:
|
||||
version "1.0.30001806"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz#1bc8e502b723fa393455dfbedd5ccec0c29bb74e"
|
||||
integrity sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001799, caniuse-lite@^1.0.30001805:
|
||||
version "1.0.30001805"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz#78d5d5968a69b7ff81af87a96d7ddc7ea6670b1e"
|
||||
integrity sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==
|
||||
|
||||
ccount@^2.0.0:
|
||||
version "2.0.1"
|
||||
@@ -7007,7 +7007,7 @@ copy-text-to-clipboard@^3.2.0:
|
||||
resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz#99bc79db3f2d355ec33a08d573aff6804491ddb9"
|
||||
integrity sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==
|
||||
|
||||
copy-to-clipboard@^3.3.3:
|
||||
copy-to-clipboard@^3.3.1:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0"
|
||||
integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==
|
||||
@@ -10155,10 +10155,10 @@ js-yaml@=4.2.0:
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
js-yaml@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-5.2.2.tgz#497bfe63f0b0db11c7bbc5ce8bc568e836c8b08c"
|
||||
integrity sha512-dayzUzKkJ1MkuUtZglSebU43utNXH0OWQByK9rKOOuYIO8M5TV1y+n8ALMdG0rdzBnfNkOmZEqrURepb0ejqBw==
|
||||
js-yaml@^5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-5.2.1.tgz#bb3f2e87295ebfe6ef0a75e17c62834daeff9ce2"
|
||||
integrity sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
@@ -11681,10 +11681,10 @@ mz@^2.7.0:
|
||||
object-assign "^4.0.1"
|
||||
thenify-all "^1.0.0"
|
||||
|
||||
nanoid@^3.3.16:
|
||||
version "3.3.16"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.16.tgz#a04d8ec4b1f10009d2d533947aefe4293737816c"
|
||||
integrity sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==
|
||||
nanoid@^3.3.12:
|
||||
version "3.3.15"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.15.tgz#36c490fad8c6e86c824c940dfdde999b69ed4316"
|
||||
integrity sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
@@ -12974,11 +12974,11 @@ postcss-zindex@^6.0.2:
|
||||
integrity sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==
|
||||
|
||||
postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.33, postcss@^8.5.4:
|
||||
version "8.5.23"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.23.tgz#3493550116f478487298301d2c2e8dc5a56e6594"
|
||||
integrity sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==
|
||||
version "8.5.16"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.16.tgz#1230ce0b5df354c24c0ea45f99ce5f6a88279d28"
|
||||
integrity sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==
|
||||
dependencies:
|
||||
nanoid "^3.3.16"
|
||||
nanoid "^3.3.12"
|
||||
picocolors "^1.1.1"
|
||||
source-map-js "^1.2.1"
|
||||
|
||||
@@ -13243,12 +13243,12 @@ rc@1.2.8:
|
||||
minimist "^1.2.0"
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
react-copy-to-clipboard@5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.1.tgz#76adb8be03616e99692fcf3f762365ed3fb5ff16"
|
||||
integrity sha512-s+HrzLyJBxrpGTYXF15dTgMjAJpEPZT/Yp6NytAtZMRngejxt6Pt5WrfFxLAcsqUDU6sY1Jz6tyHwIicE1U2Xg==
|
||||
react-copy-to-clipboard@5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz#09aae5ec4c62750ccb2e6421a58725eabc41255c"
|
||||
integrity sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==
|
||||
dependencies:
|
||||
copy-to-clipboard "^3.3.3"
|
||||
copy-to-clipboard "^3.3.1"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
react-debounce-input@=3.3.0:
|
||||
@@ -14925,10 +14925,10 @@ swagger-client@^3.37.4:
|
||||
ramda "^0.30.1"
|
||||
ramda-adjunct "^5.1.0"
|
||||
|
||||
swagger-ui-react@^5.32.9:
|
||||
version "5.32.9"
|
||||
resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.32.9.tgz#d0e8521091b270e35f8d87771fba49eede425b38"
|
||||
integrity sha512-zs6PG4G6XEWG2evKHSwx32nWXIlk48a9Hw9YS9hlt55O5W0FM1cpltMrM/AnU+mohdnV7oEx0ap2TaG3fbfLIA==
|
||||
swagger-ui-react@^5.32.8:
|
||||
version "5.32.8"
|
||||
resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.32.8.tgz#0608b45cf552f33fcc9b3fc5e07740c9a854861f"
|
||||
integrity sha512-Cstx4Tq8fT5l2TBxHxts8pG+ks0qKSkuO1pwUwgrQQiZ241Mqs+KUODLVIonsYXL/gqX143rkcipUa4d0Rid7w==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.27.1"
|
||||
"@scarf/scarf" "=1.4.0"
|
||||
@@ -14946,7 +14946,7 @@ swagger-ui-react@^5.32.9:
|
||||
prop-types "^15.8.1"
|
||||
randexp "^0.5.3"
|
||||
randombytes "^2.1.0"
|
||||
react-copy-to-clipboard "5.1.1"
|
||||
react-copy-to-clipboard "5.1.0"
|
||||
react-debounce-input "=3.3.0"
|
||||
react-immutable-proptypes "2.2.0"
|
||||
react-immutable-pure-component "^2.2.0"
|
||||
|
||||
@@ -29,7 +29,7 @@ maintainers:
|
||||
- name: craig-rueda
|
||||
email: craig@craigrueda.com
|
||||
url: https://github.com/craig-rueda
|
||||
version: 0.22.4 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
|
||||
version: 0.22.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
|
||||
|
||||
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
|
||||
|
||||
# superset
|
||||
|
||||

|
||||

|
||||
|
||||
Apache Superset is a modern, enterprise-ready business intelligence web application
|
||||
|
||||
@@ -131,7 +131,7 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
|
||||
| httproute.hostnames | list | `[]` | Hostnames that match against the HTTP Host header (templated) |
|
||||
| httproute.labels | object | `{}` | Additional labels to add to the HTTPRoute |
|
||||
| httproute.parentRefs | list | `[]` | Gateways this HTTPRoute attaches to |
|
||||
| httproute.rules | list | `[{"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]` | Routing rules. Each rule is backed by the Superset service. Set `weight` per rule to leave room for traffic splitting (defaults to 1). When `supersetWebsockets.enabled` is true, an extra rule routing `supersetWebsockets.ingress.path` to the `-ws` service is appended automatically, mirroring the ingress behavior. When both `supersetMcp.enabled` and `supersetMcp.httproute.enabled` are true, an extra rule routing `supersetMcp.httproute.path` to the `-mcp` service is also appended. |
|
||||
| httproute.rules | list | `[{"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]` | Routing rules. Each rule is backed by the Superset service. Set `weight` per rule to leave room for traffic splitting (defaults to 1). When `supersetWebsockets.enabled` is true, an extra rule routing `supersetWebsockets.ingress.path` to the `-ws` service is appended automatically, mirroring the ingress behavior. |
|
||||
| image.pullPolicy | string | `"IfNotPresent"` | |
|
||||
| image.repository | string | `"apachesuperset.docker.scarf.sh/apache/superset"` | |
|
||||
| image.tag | string | `nil` | |
|
||||
@@ -159,10 +159,6 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
|
||||
| init.extraInitContainers | list | `[]` | Extra init containers appended after init job initContainers |
|
||||
| init.initContainers | list | a container waiting for postgres | List of initContainers |
|
||||
| init.initscript | string | unused; kept for backwards-compatibility only | DEPRECATED: this field is no longer used by the chart. The init script is rendered entirely from the internal `superset.initScript` template (which runs `superset db upgrade`, `superset init`, admin creation, and examples). Any customization placed here is silently ignored. See UPGRADING.md. |
|
||||
| init.istio | object | `{"disableSidecarInjection":false,"quitEndpoint":"http://localhost:15020/quitquitquit","terminateSidecarOnExit":false}` | Configuration for compatibility with the Istio service mesh. Without these mitigations, when the init-db Job runs in a namespace with automatic Istio sidecar injection enabled, the injected envoy-proxy container keeps running after the init container has exited, preventing the Job from ever reaching the Completed state. See https://github.com/apache/superset/issues/25798 |
|
||||
| init.istio.disableSidecarInjection | bool | `false` | When true, adds the `sidecar.istio.io/inject: "false"` label to the init job pod template, opting it out of Istio automatic sidecar injection. This is the recommended fix when the cluster's mesh policy allows opting out per pod. |
|
||||
| init.istio.quitEndpoint | string | `"http://localhost:15020/quitquitquit"` | Endpoint that the trap POSTs to in order to terminate the sidecar. The default matches the standard Istio pilot-agent admin port. |
|
||||
| init.istio.terminateSidecarOnExit | bool | `false` | When true, the rendered `superset_init.sh` script (from the internal `superset.initScript` template) registers an `EXIT` trap that POSTs to the Istio pilot-agent's `/quitquitquit` endpoint after the init logic finishes, gracefully terminating an injected envoy-proxy sidecar so the Job can complete. Enable this when sidecar injection cannot be disabled per pod (e.g. when enforced by a cluster-wide Istio policy). Note: requires `curl` to be available in the init container image (it is included in the default `apache/superset` image) and only takes effect when `init.command` sources the rendered `superset_init.sh`. |
|
||||
| init.jobAnnotations."helm.sh/hook" | string | `"post-install,post-upgrade"` | |
|
||||
| init.jobAnnotations."helm.sh/hook-delete-policy" | string | `"before-hook-creation"` | |
|
||||
| init.loadExamples | bool | `false` | |
|
||||
@@ -270,8 +266,6 @@ Alternatively, perform a fresh install. This is a one-time migration; subsequent
|
||||
| supersetMcp.extraContainers | list | `[]` | Launch additional containers into supersetMcp pods |
|
||||
| supersetMcp.extraInitContainers | list | `[]` | Extra init containers appended after supersetMcp initContainers |
|
||||
| supersetMcp.forceReload | bool | `false` | If true, forces deployment to reload on each upgrade |
|
||||
| supersetMcp.httproute.enabled | bool | `false` | If true, the MCP server will be exposed via the HTTPRoute /mcp subpath |
|
||||
| supersetMcp.httproute.path | string | `"/mcp"` | |
|
||||
| supersetMcp.ingress.enabled | bool | `false` | If true, the MCP server will be exposed via the ingress /mcp subpath |
|
||||
| supersetMcp.ingress.path | string | `"/mcp"` | |
|
||||
| supersetMcp.ingress.pathType | string | `"Prefix"` | |
|
||||
|
||||
@@ -685,14 +685,6 @@ TALISMAN_CONFIG = {
|
||||
{{- define "superset.initScript" -}}
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
{{- if dig "istio" "terminateSidecarOnExit" false .Values.init }}
|
||||
# Notify the Istio pilot-agent sidecar to exit when this script completes
|
||||
# (whether successfully or via `set -e`), so that the Job can reach the
|
||||
# Completed state instead of hanging on a still-running envoy-proxy.
|
||||
# See https://github.com/apache/superset/issues/25798
|
||||
ISTIO_QUIT_ENDPOINT={{ dig "istio" "quitEndpoint" "http://localhost:15020/quitquitquit" .Values.init | replace "'" "'\\''" | squote }}
|
||||
trap 'rc=$?; curl -fsS -m 5 -X POST "$ISTIO_QUIT_ENDPOINT" >/dev/null 2>&1 || echo "WARNING: failed to notify Istio sidecar at $ISTIO_QUIT_ENDPOINT to quit; the Job may hang if sidecar injection is active" >&2; exit $rc' EXIT
|
||||
{{- end }}
|
||||
echo "Upgrading DB schema..."
|
||||
superset db upgrade
|
||||
echo "Initializing roles and permissions..."
|
||||
|
||||
@@ -80,16 +80,4 @@ spec:
|
||||
type: PathPrefix
|
||||
value: {{ .Values.supersetWebsockets.ingress.path }}
|
||||
{{- end }}
|
||||
{{- if and .Values.supersetMcp.enabled .Values.supersetMcp.httproute.enabled }}
|
||||
- backendRefs:
|
||||
- group: ''
|
||||
kind: Service
|
||||
name: {{ $fullName }}-mcp
|
||||
port: {{ .Values.supersetMcp.service.port }}
|
||||
weight: 1
|
||||
matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: {{ .Values.supersetMcp.httproute.path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -35,7 +35,6 @@ spec:
|
||||
{{- if .Values.init.podAnnotations }}
|
||||
annotations: {{- toYaml .Values.init.podAnnotations | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- $istioDisableInject := dig "istio" "disableSidecarInjection" false .Values.init }}
|
||||
labels:
|
||||
{{- include "superset.componentSelectorLabels" (dict "component" "init" "root" .) | nindent 8 }}
|
||||
job: {{ template "superset.fullname" . }}-init-db
|
||||
@@ -45,9 +44,6 @@ spec:
|
||||
{{- if .Values.init.podLabels }}
|
||||
{{- toYaml .Values.init.podLabels | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if $istioDisableInject }}
|
||||
sidecar.istio.io/inject: "false"
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.init.additionalPodSpec }}
|
||||
{{- tpl (toYaml .Values.init.additionalPodSpec) . | nindent 6 }}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
suite: HTTPRoute
|
||||
templates:
|
||||
- httproute.yaml
|
||||
tests:
|
||||
- it: routes the general Superset path
|
||||
set:
|
||||
httproute.enabled: true
|
||||
asserts:
|
||||
- lengthEqual:
|
||||
path: spec.rules
|
||||
count: 1
|
||||
- equal:
|
||||
path: spec.rules[0].backendRefs[0].name
|
||||
value: RELEASE-NAME-superset
|
||||
- equal:
|
||||
path: spec.rules[0].backendRefs[0].port
|
||||
value: 8088
|
||||
- equal:
|
||||
path: spec.rules[0].matches[0].path.value
|
||||
value: /
|
||||
|
||||
- it: routes the WebSocket path when WebSockets are enabled
|
||||
set:
|
||||
httproute.enabled: true
|
||||
supersetWebsockets.enabled: true
|
||||
asserts:
|
||||
- lengthEqual:
|
||||
path: spec.rules
|
||||
count: 2
|
||||
- equal:
|
||||
path: spec.rules[1].backendRefs[0].name
|
||||
value: RELEASE-NAME-superset-ws
|
||||
- equal:
|
||||
path: spec.rules[1].backendRefs[0].port
|
||||
value: 8080
|
||||
- equal:
|
||||
path: spec.rules[1].matches[0].path.value
|
||||
value: /ws
|
||||
|
||||
- it: does not route the WebSocket path when WebSockets are disabled
|
||||
set:
|
||||
httproute.enabled: true
|
||||
supersetWebsockets.enabled: false
|
||||
asserts:
|
||||
- lengthEqual:
|
||||
path: spec.rules
|
||||
count: 1
|
||||
|
||||
- it: routes the MCP path when MCP HTTPRoute is enabled
|
||||
set:
|
||||
httproute.enabled: true
|
||||
supersetMcp.enabled: true
|
||||
supersetMcp.httproute.enabled: true
|
||||
asserts:
|
||||
- lengthEqual:
|
||||
path: spec.rules
|
||||
count: 2
|
||||
- equal:
|
||||
path: spec.rules[1].backendRefs[0].name
|
||||
value: RELEASE-NAME-superset-mcp
|
||||
- equal:
|
||||
path: spec.rules[1].backendRefs[0].port
|
||||
value: 5008
|
||||
- equal:
|
||||
path: spec.rules[1].matches[0].path.value
|
||||
value: /mcp
|
||||
|
||||
- it: does not route MCP when MCP HTTPRoute is disabled
|
||||
set:
|
||||
httproute.enabled: true
|
||||
supersetMcp.enabled: true
|
||||
supersetMcp.httproute.enabled: false
|
||||
asserts:
|
||||
- lengthEqual:
|
||||
path: spec.rules
|
||||
count: 1
|
||||
@@ -1,183 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Renders the chart with `helm template` for several `init.istio.*` value
|
||||
# combinations and asserts that the relevant manifests contain (or omit)
|
||||
# the expected fields. Intended to be run from the chart directory or via
|
||||
# `bash helm/superset/tests/test-istio.sh` from the repo root.
|
||||
#
|
||||
# Covers the fix for:
|
||||
# https://github.com/apache/superset/issues/25798
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CHART_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
|
||||
assert_contains() {
|
||||
local label="$1"
|
||||
local needle="$2"
|
||||
local haystack="$3"
|
||||
if grep -qF -- "${needle}" <<<"${haystack}"; then
|
||||
echo " PASS: ${label}"
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
echo " FAIL: ${label}"
|
||||
echo " expected to contain: ${needle}"
|
||||
fail=$((fail + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_contains() {
|
||||
local label="$1"
|
||||
local needle="$2"
|
||||
local haystack="$3"
|
||||
if ! grep -qF -- "${needle}" <<<"${haystack}"; then
|
||||
echo " PASS: ${label}"
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
echo " FAIL: ${label}"
|
||||
echo " expected NOT to contain: ${needle}"
|
||||
fail=$((fail + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
render() {
|
||||
helm template release "${CHART_DIR}" "$@"
|
||||
}
|
||||
|
||||
extract_init_job() {
|
||||
awk '
|
||||
/^# Source: superset\/templates\/init-job\.yaml/ { capture = 1 }
|
||||
capture && /^---$/ { capture = 0 }
|
||||
capture { print }
|
||||
' <<<"$1"
|
||||
}
|
||||
|
||||
extract_config_secret() {
|
||||
awk '
|
||||
/^# Source: superset\/templates\/secret-superset-config\.yaml/ { capture = 1 }
|
||||
capture && /^---$/ { capture = 0 }
|
||||
capture { print }
|
||||
' <<<"$1"
|
||||
}
|
||||
|
||||
echo "==> defaults: no istio mitigations applied"
|
||||
out_default="$(render)"
|
||||
init_job_default="$(extract_init_job "${out_default}")"
|
||||
config_default="$(extract_config_secret "${out_default}")"
|
||||
assert_not_contains "default does not set sidecar.istio.io/inject label" \
|
||||
'sidecar.istio.io/inject' "${init_job_default}"
|
||||
assert_not_contains "default initscript does not register quitquitquit trap" \
|
||||
'quitquitquit' "${config_default}"
|
||||
|
||||
echo "==> init.istio.disableSidecarInjection=true"
|
||||
out_disable="$(render --set init.istio.disableSidecarInjection=true)"
|
||||
init_job_disable="$(extract_init_job "${out_disable}")"
|
||||
config_disable="$(extract_config_secret "${out_disable}")"
|
||||
assert_contains "init job sets sidecar.istio.io/inject: \"false\"" \
|
||||
'sidecar.istio.io/inject: "false"' "${init_job_disable}"
|
||||
assert_not_contains "disableSidecarInjection alone does not add quitquitquit trap" \
|
||||
'quitquitquit' "${config_disable}"
|
||||
|
||||
echo "==> init.istio.terminateSidecarOnExit=true"
|
||||
out_terminate="$(render --set init.istio.terminateSidecarOnExit=true)"
|
||||
init_job_terminate="$(extract_init_job "${out_terminate}")"
|
||||
config_terminate="$(extract_config_secret "${out_terminate}")"
|
||||
assert_not_contains "terminateSidecarOnExit alone does not add inject label" \
|
||||
'sidecar.istio.io/inject' "${init_job_terminate}"
|
||||
assert_contains "initscript registers EXIT trap that calls quitquitquit" \
|
||||
"trap 'rc=\$?; curl -fsS -m 5 -X POST" "${config_terminate}"
|
||||
assert_contains "initscript trap targets the configured quit endpoint" \
|
||||
'http://localhost:15020/quitquitquit' "${config_terminate}"
|
||||
assert_contains "initscript trap binds to the EXIT signal" \
|
||||
"' EXIT" "${config_terminate}"
|
||||
|
||||
echo "==> init.istio.quitEndpoint override"
|
||||
out_endpoint="$(render --set init.istio.terminateSidecarOnExit=true \
|
||||
--set init.istio.quitEndpoint=http://127.0.0.1:15020/quitquitquit)"
|
||||
config_endpoint="$(extract_config_secret "${out_endpoint}")"
|
||||
assert_contains "trap honours custom quitEndpoint" \
|
||||
'http://127.0.0.1:15020/quitquitquit' "${config_endpoint}"
|
||||
|
||||
echo "==> both options combined"
|
||||
out_both="$(render --set init.istio.disableSidecarInjection=true \
|
||||
--set init.istio.terminateSidecarOnExit=true)"
|
||||
init_job_both="$(extract_init_job "${out_both}")"
|
||||
config_both="$(extract_config_secret "${out_both}")"
|
||||
assert_contains "combined: inject label present" \
|
||||
'sidecar.istio.io/inject: "false"' "${init_job_both}"
|
||||
assert_contains "combined: trap present" \
|
||||
'quitquitquit' "${config_both}"
|
||||
|
||||
echo "==> existing init.podLabels are preserved alongside istio label"
|
||||
out_labels="$(render --set init.istio.disableSidecarInjection=true \
|
||||
--set init.podLabels.team=data-platform)"
|
||||
init_job_labels="$(extract_init_job "${out_labels}")"
|
||||
assert_contains "user-supplied podLabel still rendered" \
|
||||
'team: data-platform' "${init_job_labels}"
|
||||
assert_contains "istio inject label rendered alongside" \
|
||||
'sidecar.istio.io/inject: "false"' "${init_job_labels}"
|
||||
|
||||
echo "==> init.istio explicitly overridden to null"
|
||||
out_null_istio="$(render --set init.istio=null)"
|
||||
init_job_null_istio="$(extract_init_job "${out_null_istio}")"
|
||||
config_null_istio="$(extract_config_secret "${out_null_istio}")"
|
||||
assert_not_contains "null init.istio does not set inject label" \
|
||||
'sidecar.istio.io/inject' "${init_job_null_istio}"
|
||||
assert_not_contains "null init.istio does not register quitquitquit trap" \
|
||||
'quitquitquit' "${config_null_istio}"
|
||||
|
||||
echo "==> EXIT trap propagates the script's exit code, not the notification's"
|
||||
# The trap's own curl call is best-effort (failures are logged with
|
||||
# `|| echo ... >&2`, not swallowed with `|| true`) and must not mask a
|
||||
# failed migration. Extract the two rendered lines and actually run them,
|
||||
# with curl pointed at a closed local port so the notification itself fails,
|
||||
# to make sure the wrapped script's real exit code still comes through.
|
||||
quit_endpoint_line="$(grep -F 'ISTIO_QUIT_ENDPOINT=' <<<"${config_terminate}" || true)"
|
||||
trap_line="$(grep -F "trap 'rc=\$?; curl" <<<"${config_terminate}" || true)"
|
||||
if [[ -z "${quit_endpoint_line}" || -z "${trap_line}" ]]; then
|
||||
echo " FAIL: script exit code (42) survives a failing quitquitquit notification"
|
||||
echo " could not locate the rendered ISTIO_QUIT_ENDPOINT/trap lines to exercise"
|
||||
fail=$((fail + 1))
|
||||
else
|
||||
set +e
|
||||
(
|
||||
eval "${quit_endpoint_line}"
|
||||
ISTIO_QUIT_ENDPOINT="http://127.0.0.1:1/quitquitquit"
|
||||
eval "${trap_line}"
|
||||
exit 42
|
||||
)
|
||||
trap_test_rc=$?
|
||||
set -e
|
||||
if [[ "${trap_test_rc}" -eq 42 ]]; then
|
||||
echo " PASS: script exit code (42) survives a failing quitquitquit notification"
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
echo " FAIL: script exit code (42) survives a failing quitquitquit notification"
|
||||
echo " got exit code: ${trap_test_rc}"
|
||||
fail=$((fail + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "passed: ${pass}, failed: ${fail}"
|
||||
if [[ ${fail} -gt 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -98,13 +98,13 @@ envFromSecrets: []
|
||||
# -- Extra environment variables that will be passed into pods
|
||||
extraEnv: {}
|
||||
# Different gunicorn settings, refer to the gunicorn documentation
|
||||
# https://gunicorn.org/reference/settings/
|
||||
# https://docs.gunicorn.org/en/stable/settings.html#
|
||||
# These variables are used as Flags at the gunicorn startup
|
||||
# https://github.com/apache/superset/blob/master/docker/entrypoints/run-server.sh#L22
|
||||
# https://github.com/apache/superset/blob/master/docker/run-server.sh#L22
|
||||
# Extend timeout to allow long running queries.
|
||||
# GUNICORN_TIMEOUT: 300
|
||||
# Increase the gunicorn worker amount, can improve performance drastically
|
||||
# See: https://gunicorn.org/design/#how-many-workers
|
||||
# See: https://docs.gunicorn.org/en/stable/design.html#how-many-workers
|
||||
# SERVER_WORKER_AMOUNT: 4
|
||||
# WORKER_MAX_REQUESTS: 0
|
||||
# WORKER_MAX_REQUESTS_JITTER: 0
|
||||
@@ -277,9 +277,7 @@ httproute:
|
||||
# per rule to leave room for traffic splitting (defaults to 1). When
|
||||
# `supersetWebsockets.enabled` is true, an extra rule routing
|
||||
# `supersetWebsockets.ingress.path` to the `-ws` service is appended
|
||||
# automatically, mirroring the ingress behavior. When both `supersetMcp.enabled` and
|
||||
# `supersetMcp.httproute.enabled` are true, an extra rule routing
|
||||
# `supersetMcp.httproute.path` to the `-mcp` service is also appended.
|
||||
# automatically, mirroring the ingress behavior.
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
@@ -989,6 +987,7 @@ supersetWebsockets:
|
||||
# -- Set priorityClassName for supersetWebsockets pods
|
||||
priorityClassName: ~
|
||||
|
||||
|
||||
supersetMcp:
|
||||
# -- Enables the Superset MCP Server. To expose it via the shared ingress at /mcp,
|
||||
# also set supersetMcp.ingress.enabled=true.
|
||||
@@ -1016,10 +1015,6 @@ supersetMcp:
|
||||
enabled: false
|
||||
path: /mcp
|
||||
pathType: Prefix
|
||||
httproute:
|
||||
# -- If true, the MCP server will be exposed via the HTTPRoute /mcp subpath
|
||||
enabled: false
|
||||
path: /mcp
|
||||
service:
|
||||
type: ClusterIP
|
||||
annotations: {}
|
||||
@@ -1241,31 +1236,6 @@ init:
|
||||
topologySpreadConstraints: []
|
||||
# -- Set priorityClassName for init job pods
|
||||
priorityClassName: ~
|
||||
# -- Configuration for compatibility with the Istio service mesh.
|
||||
# Without these mitigations, when the init-db Job runs in a namespace with
|
||||
# automatic Istio sidecar injection enabled, the injected envoy-proxy
|
||||
# container keeps running after the init container has exited, preventing
|
||||
# the Job from ever reaching the Completed state.
|
||||
# See https://github.com/apache/superset/issues/25798
|
||||
istio:
|
||||
# -- When true, adds the `sidecar.istio.io/inject: "false"` label to the
|
||||
# init job pod template, opting it out of Istio automatic sidecar
|
||||
# injection. This is the recommended fix when the cluster's mesh policy
|
||||
# allows opting out per pod.
|
||||
disableSidecarInjection: false
|
||||
# -- When true, the rendered `superset_init.sh` script (from the internal
|
||||
# `superset.initScript` template) registers an `EXIT` trap that POSTs to
|
||||
# the Istio pilot-agent's `/quitquitquit` endpoint after the init logic
|
||||
# finishes, gracefully terminating an injected envoy-proxy sidecar so
|
||||
# the Job can complete. Enable this when sidecar injection cannot be
|
||||
# disabled per pod (e.g. when enforced by a cluster-wide Istio policy).
|
||||
# Note: requires `curl` to be available in the init container image
|
||||
# (it is included in the default `apache/superset` image) and only
|
||||
# takes effect when `init.command` sources the rendered `superset_init.sh`.
|
||||
terminateSidecarOnExit: false
|
||||
# -- Endpoint that the trap POSTs to in order to terminate the sidecar.
|
||||
# The default matches the standard Istio pilot-agent admin port.
|
||||
quitEndpoint: "http://localhost:15020/quitquitquit"
|
||||
|
||||
# -- Configuration values for the postgresql dependency.
|
||||
# ref: https://github.com/bitnami/charts/tree/main/bitnami/postgresql
|
||||
|
||||
@@ -54,7 +54,7 @@ dependencies = [
|
||||
"deprecation>=2.1.0, <2.2.0",
|
||||
"flask>=2.2.5, <4.0.0",
|
||||
"flask-appbuilder>=5.2.2, <6.0.0",
|
||||
"flask-caching>=2.4.1, <3",
|
||||
"flask-caching>=2.1.0, <3",
|
||||
"flask-compress>=1.13, <2.0",
|
||||
"flask-talisman>=1.0.0, <2.0",
|
||||
"flask-login>=0.6.0, < 1.0",
|
||||
@@ -92,7 +92,7 @@ dependencies = [
|
||||
"Pillow>=11.0.0, <13",
|
||||
"polyline>=2.0.0, <3.0",
|
||||
"pydantic>=2.8.0",
|
||||
"pyparsing>=3.3.2, <4",
|
||||
"pyparsing>=3.0.6, <4",
|
||||
"python-dateutil",
|
||||
"python-dotenv", # optional dependencies for Flask but required for Superset, see https://flask.palletsprojects.com/en/stable/installation/#optional-dependencies
|
||||
"pygeohash",
|
||||
@@ -106,7 +106,7 @@ dependencies = [
|
||||
"sshtunnel>=0.4.0, <0.5",
|
||||
"simplejson>=4.1.1",
|
||||
"slack_sdk>=3.43.0, <4",
|
||||
"sqlalchemy>=1.4.43, <2", # 1.4.43 adds the python-oracledb (oracle+oracledb) dialect
|
||||
"sqlalchemy>=1.4, <2",
|
||||
"sqlalchemy-continuum>=1.6.0, <2.0.0",
|
||||
"sqlalchemy-utils>=0.42.1, <0.43", # expanding lowerbound to work with pydoris
|
||||
"sqlglot>=30.12.0, <31",
|
||||
@@ -127,7 +127,7 @@ aurora-data-api = ["preset-sqlalchemy-aurora-data-api>=0.2.8,<0.3"]
|
||||
bigquery = [
|
||||
"pandas-gbq>=0.35.0",
|
||||
"sqlalchemy-bigquery>=1.17.0",
|
||||
"google-cloud-bigquery>=3.42.2",
|
||||
"google-cloud-bigquery>=3.42.1",
|
||||
]
|
||||
clickhouse = ["clickhouse-connect>=1.4.2, <2.0"]
|
||||
cockroachdb = ["cockroachdb>=0.3.5, <0.4"]
|
||||
@@ -139,11 +139,10 @@ d1 = [
|
||||
]
|
||||
databend = ["databend-sqlalchemy>=0.5.5, <1.0"]
|
||||
databricks = [
|
||||
"databricks-sql-connector>=4.2.6, <4.4.0",
|
||||
"databricks-sql-connector==4.2.6",
|
||||
"databricks-sqlalchemy==1.0.5",
|
||||
]
|
||||
datafusion = ["flightsql-dbapi>=0.2.2, <0.3"]
|
||||
db2 = ["ibm-db-sa<=0.4.4, >=0.4.4"]
|
||||
db2 = ["ibm-db-sa>0.3.8, <=0.4.4"]
|
||||
denodo = ["denodo-sqlalchemy>=2.0.5,<2.1.0"]
|
||||
dremio = ["sqlalchemy-dremio>=1.2.1, <4"]
|
||||
drill = ["sqlalchemy-drill>=1.1.10, <2"]
|
||||
@@ -189,7 +188,7 @@ ocient = [
|
||||
"shapely",
|
||||
"geojson",
|
||||
]
|
||||
oracle = ["oracledb>=4.0.2, <5"]
|
||||
oracle = ["oracledb>=2.0.0, <5"]
|
||||
parseable = ["sqlalchemy-parseable>=0.1.6,<0.2.0"]
|
||||
pinot = ["pinotdb>=5.0.0, <10.0.0"]
|
||||
playwright = ["playwright>=1.61.0, <2"]
|
||||
@@ -201,7 +200,7 @@ redshift = ["sqlalchemy-redshift>=0.8.1, <0.9"]
|
||||
risingwave = ["sqlalchemy-risingwave"]
|
||||
shillelagh = ["shillelagh[all]>=1.4.4, <2"]
|
||||
singlestore = ["sqlalchemy-singlestoredb>=1.2.1, <2"]
|
||||
snowflake = ["snowflake-sqlalchemy>=1.11.0, <2"]
|
||||
snowflake = ["snowflake-sqlalchemy>=1.10.2, <2"]
|
||||
sqlite = ["syntaqlite>=0.7.0,<0.8.0"]
|
||||
spark = [
|
||||
"pyhive[hive_pure_sasl]>=0.7",
|
||||
@@ -210,7 +209,7 @@ spark = [
|
||||
]
|
||||
tdengine = [
|
||||
"taospy>=2.8.9",
|
||||
"taos-ws-py>=0.7.0"
|
||||
"taos-ws-py>=0.6.9"
|
||||
]
|
||||
teradata = ["teradatasql>=20.0.0.62"]
|
||||
thumbnails = [] # deprecated, will be removed in 7.0
|
||||
@@ -227,7 +226,7 @@ development = [
|
||||
"docker",
|
||||
"flask-testing",
|
||||
"freezegun",
|
||||
"grpcio>=1.82.1",
|
||||
"grpcio>=1.81.1",
|
||||
"openapi-spec-validator",
|
||||
"parameterized",
|
||||
"pip",
|
||||
|
||||
@@ -36,9 +36,11 @@ filterwarnings =
|
||||
error:"TableColumn" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
|
||||
error:"TaggedObject" object is being merged into a Session:sqlalchemy.exc.RemovedIn20Warning
|
||||
error:The autoload parameter is deprecated:sqlalchemy.exc.RemovedIn20Warning
|
||||
error:The connection.execute\(\) method:sqlalchemy.exc.RemovedIn20Warning
|
||||
error:The current statement is being autocommitted using implicit autocommit:sqlalchemy.exc.RemovedIn20Warning
|
||||
error:The connection.execute\(\) method:sqlalchemy.exc.RemovedIn20Warning
|
||||
# error:The current statement is being autocommitted using implicit autocommit:sqlalchemy.exc.RemovedIn20Warning
|
||||
error:The ``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
|
||||
|
||||
@@ -28,8 +28,6 @@ babel==2.17.0
|
||||
# via flask-babel
|
||||
backoff==2.2.1
|
||||
# via apache-superset (pyproject.toml)
|
||||
backports-zstd==1.6.0
|
||||
# via flask-compress
|
||||
bcrypt==4.3.0
|
||||
# via paramiko
|
||||
billiard==4.2.1
|
||||
@@ -130,9 +128,9 @@ flask-appbuilder==5.2.2
|
||||
# apache-superset-core
|
||||
flask-babel==3.1.0
|
||||
# via flask-appbuilder
|
||||
flask-caching==2.4.1
|
||||
flask-caching==2.3.1
|
||||
# via apache-superset (pyproject.toml)
|
||||
flask-compress==1.24
|
||||
flask-compress==1.17
|
||||
# via apache-superset (pyproject.toml)
|
||||
flask-cors==6.0.5
|
||||
# via apache-superset (pyproject.toml)
|
||||
@@ -246,7 +244,7 @@ msgpack==1.2.1
|
||||
# via apache-superset (pyproject.toml)
|
||||
msgspec==0.19.0
|
||||
# via flask-session
|
||||
nh3==0.3.6
|
||||
nh3==0.3.5
|
||||
# via apache-superset (pyproject.toml)
|
||||
numexpr==2.10.2
|
||||
# via -r requirements/base.in
|
||||
@@ -287,7 +285,7 @@ parsedatetime==2.6
|
||||
# via apache-superset (pyproject.toml)
|
||||
pgsanity==0.2.9
|
||||
# via apache-superset (pyproject.toml)
|
||||
pillow==12.3.0
|
||||
pillow==12.2.0
|
||||
# via apache-superset (pyproject.toml)
|
||||
platformdirs==4.3.8
|
||||
# via requests-cache
|
||||
@@ -302,17 +300,17 @@ pyarrow==24.0.0
|
||||
# -r requirements/base.in
|
||||
# apache-superset (pyproject.toml)
|
||||
# apache-superset-core
|
||||
pyasn1==0.6.4
|
||||
pyasn1==0.6.3
|
||||
# via pyasn1-modules
|
||||
pyasn1-modules==0.4.2
|
||||
# via google-auth
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
pydantic==2.13.4
|
||||
pydantic==2.11.7
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# apache-superset-core
|
||||
pydantic-core==2.46.4
|
||||
pydantic-core==2.33.2
|
||||
# via pydantic
|
||||
pygeohash==3.2.2
|
||||
# via apache-superset (pyproject.toml)
|
||||
@@ -329,7 +327,7 @@ pyopenssl==26.3.0
|
||||
# via
|
||||
# -r requirements/base.in
|
||||
# shillelagh
|
||||
pyparsing==3.3.2
|
||||
pyparsing==3.2.3
|
||||
# via apache-superset (pyproject.toml)
|
||||
pysocks==1.7.1
|
||||
# via urllib3
|
||||
@@ -410,7 +408,7 @@ sqlalchemy==1.4.54
|
||||
# shillelagh
|
||||
# sqlalchemy-continuum
|
||||
# sqlalchemy-utils
|
||||
sqlalchemy-continuum==1.7.0
|
||||
sqlalchemy-continuum==1.6.0
|
||||
# via apache-superset (pyproject.toml)
|
||||
sqlalchemy-utils==0.42.1
|
||||
# via
|
||||
@@ -445,7 +443,7 @@ typing-extensions==4.16.0
|
||||
# selenium
|
||||
# shillelagh
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.2
|
||||
typing-inspection==0.4.1
|
||||
# via pydantic
|
||||
tzdata==2025.2
|
||||
# via
|
||||
@@ -498,3 +496,5 @@ xlsxwriter==3.2.9
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# pandas
|
||||
zstandard==0.23.0
|
||||
# via flask-compress
|
||||
|
||||
@@ -64,10 +64,6 @@ backoff==2.2.1
|
||||
# apache-superset
|
||||
backports-tarfile==1.2.0
|
||||
# via jaraco-context
|
||||
backports-zstd==1.6.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask-compress
|
||||
bcrypt==4.3.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -276,11 +272,11 @@ flask-babel==3.1.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask-appbuilder
|
||||
flask-caching==2.4.1
|
||||
flask-caching==2.3.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
flask-compress==1.24
|
||||
flask-compress==1.17
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -341,7 +337,7 @@ geopy==2.4.1
|
||||
# apache-superset
|
||||
gevent==26.4.0
|
||||
# via apache-superset
|
||||
google-api-core==2.33.0
|
||||
google-api-core==2.23.0
|
||||
# via
|
||||
# google-cloud-bigquery
|
||||
# google-cloud-core
|
||||
@@ -362,7 +358,7 @@ google-auth-oauthlib==1.2.1
|
||||
# via
|
||||
# pandas-gbq
|
||||
# pydata-google-auth
|
||||
google-cloud-bigquery==3.42.2
|
||||
google-cloud-bigquery==3.42.1
|
||||
# via
|
||||
# apache-superset
|
||||
# pandas-gbq
|
||||
@@ -386,7 +382,7 @@ greenlet==3.5.3
|
||||
# sqlalchemy
|
||||
griffelib==2.0.2
|
||||
# via fastmcp-slim
|
||||
grpcio==1.83.0
|
||||
grpcio==1.81.1
|
||||
# via
|
||||
# apache-superset
|
||||
# google-api-core
|
||||
@@ -574,7 +570,7 @@ msgspec==0.19.0
|
||||
# flask-session
|
||||
mysqlclient==2.2.8
|
||||
# via apache-superset
|
||||
nh3==0.3.6
|
||||
nh3==0.3.5
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -669,7 +665,7 @@ pgsanity==0.2.9
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
pillow==12.3.0
|
||||
pillow==12.2.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -728,7 +724,7 @@ pyarrow==24.0.0
|
||||
# apache-superset-core
|
||||
# db-dtypes
|
||||
# pandas-gbq
|
||||
pyasn1==0.6.4
|
||||
pyasn1==0.6.3
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# pyasn1-modules
|
||||
@@ -742,7 +738,7 @@ pycparser==2.22
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# cffi
|
||||
pydantic==2.13.4
|
||||
pydantic==2.11.7
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -751,7 +747,7 @@ pydantic==2.13.4
|
||||
# mcp
|
||||
# openapi-pydantic
|
||||
# pydantic-settings
|
||||
pydantic-core==2.46.4
|
||||
pydantic-core==2.33.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# pydantic
|
||||
@@ -795,7 +791,7 @@ pyopenssl==26.3.0
|
||||
# -c requirements/base-constraint.txt
|
||||
# google-auth
|
||||
# shillelagh
|
||||
pyparsing==3.3.2
|
||||
pyparsing==3.2.3
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -991,7 +987,7 @@ sqlalchemy==1.4.54
|
||||
# sqlalchemy-utils
|
||||
sqlalchemy-bigquery==1.17.0
|
||||
# via apache-superset
|
||||
sqlalchemy-continuum==1.7.0
|
||||
sqlalchemy-continuum==1.6.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -1070,7 +1066,7 @@ typing-extensions==4.16.0
|
||||
# shillelagh
|
||||
# starlette
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.2
|
||||
typing-inspection==0.4.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# mcp
|
||||
@@ -1172,4 +1168,7 @@ zope-event==5.0
|
||||
zope-interface==5.4.0
|
||||
# via gevent
|
||||
zstandard==0.23.0
|
||||
# via trino
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask-compress
|
||||
# trino
|
||||
|
||||
20
superset-embedded-sdk/package-lock.json
generated
20
superset-embedded-sdk/package-lock.json
generated
@@ -2662,9 +2662,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3937,9 +3937,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.16",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
|
||||
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4114,9 +4114,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.23",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
|
||||
"integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
|
||||
"version": "8.5.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4134,7 +4134,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.16",
|
||||
"nanoid": "^3.3.12",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
|
||||
@@ -97,7 +97,7 @@ export const databasesPage = {
|
||||
infoAlert: '.ant-alert',
|
||||
serviceAccountInput: '[name="credentials_info"]',
|
||||
connectionStep: {
|
||||
modal: '.ant-modal-container',
|
||||
modal: '.ant-modal-content',
|
||||
modalBody: '.ant-modal-body',
|
||||
stepTitle: '.css-7x6kk > h4',
|
||||
helperBottom: '.helper-bottom',
|
||||
@@ -261,7 +261,7 @@ export const datasetsList = {
|
||||
},
|
||||
},
|
||||
deleteDatasetModal: {
|
||||
modal: '.ant-modal-container',
|
||||
modal: '.ant-modal-content',
|
||||
deleteInput: dataTestLocator('delete-modal-input'),
|
||||
deleteButton: dataTestLocator('modal-confirm-button'),
|
||||
text: '.css-kxmt87',
|
||||
@@ -474,7 +474,7 @@ export const exploreView = {
|
||||
},
|
||||
chartAreaItem: '.nv-legend-text',
|
||||
viewQueryModal: {
|
||||
container: '.ant-modal-container',
|
||||
container: '.ant-modal-content',
|
||||
closeButton: 'button.ant-modal-close',
|
||||
},
|
||||
embedCodeModal: {
|
||||
@@ -482,7 +482,7 @@ export const exploreView = {
|
||||
textfield: dataTestLocator('embed-code-textarea'),
|
||||
},
|
||||
saveModal: {
|
||||
modal: '.ant-modal-container',
|
||||
modal: '.ant-modal-content',
|
||||
chartNameInput: dataTestLocator('new-chart-name'),
|
||||
dashboardNameInput: '.ant-select-input',
|
||||
addToDashboardInput: dataTestLocator(
|
||||
@@ -578,7 +578,7 @@ export const exploreView = {
|
||||
},
|
||||
},
|
||||
editDatasetModal: {
|
||||
container: '.ant-modal-container',
|
||||
container: '.ant-modal-content',
|
||||
datasetTabsContainer: dataTestLocator('edit-dataset-tabs'),
|
||||
saveButton: dataTestLocator('datasource-modal-save'),
|
||||
metricsTab: {
|
||||
@@ -617,12 +617,12 @@ export const dashboardView = {
|
||||
closeButton: dataTestLocator('close-button'),
|
||||
},
|
||||
saveModal: {
|
||||
modal: '.ant-modal-container',
|
||||
modal: '.ant-modal-content',
|
||||
dashboardNameInput: '.ant-input',
|
||||
saveButton: dataTestLocator('modal-save-dashboard-button'),
|
||||
},
|
||||
dashboardProperties: {
|
||||
modal: '.ant-modal-container',
|
||||
modal: '.ant-modal-content',
|
||||
dashboardTitleInput: dataTestLocator('dashboard-title-input'),
|
||||
modalButton: '[type="button"]',
|
||||
},
|
||||
|
||||
6645
superset-frontend/cypress-base/package-lock.json
generated
6645
superset-frontend/cypress-base/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -215,7 +215,6 @@
|
||||
"jsx-a11y/no-noninteractive-tabindex": "error",
|
||||
"jsx-a11y/no-redundant-roles": "error",
|
||||
"jsx-a11y/no-static-element-interactions": "error",
|
||||
"jsx-a11y/prefer-tag-over-role": "error",
|
||||
"jsx-a11y/role-has-required-aria-props": "error",
|
||||
"jsx-a11y/role-supports-aria-props": "error",
|
||||
"jsx-a11y/scope": "error",
|
||||
|
||||
1045
superset-frontend/package-lock.json
generated
1045
superset-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -115,9 +115,9 @@
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@fontsource/fira-code": "^5.3.0",
|
||||
"@fontsource/ibm-plex-mono": "^5.3.0",
|
||||
"@fontsource/inter": "^5.3.0",
|
||||
"@fontsource/fira-code": "^5.2.7",
|
||||
"@fontsource/ibm-plex-mono": "^5.2.7",
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@googleapis/sheets": "^13.0.2",
|
||||
"@great-expectations/jsonforms-antd-renderers": "^2.2.10",
|
||||
"@jsonforms/core": "^3.7.0",
|
||||
@@ -130,9 +130,9 @@
|
||||
"@luma.gl/shadertools": "~9.2.5",
|
||||
"@luma.gl/webgl": "~9.2.5",
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"@rjsf/core": "^6.7.0",
|
||||
"@rjsf/core": "^6.6.2",
|
||||
"@rjsf/utils": "^6.6.2",
|
||||
"@rjsf/validator-ajv8": "^6.7.0",
|
||||
"@rjsf/validator-ajv8": "^6.6.2",
|
||||
"@scarf/scarf": "^1.4.0",
|
||||
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
|
||||
"@superset-ui/core": "file:./packages/superset-ui-core",
|
||||
@@ -166,17 +166,17 @@
|
||||
"@visx/scale": "^4.0.0",
|
||||
"@visx/tooltip": "^4.0.0",
|
||||
"@visx/xychart": "^4.0.0",
|
||||
"ag-grid-community": "36.0.1",
|
||||
"ag-grid-react": "36.0.1",
|
||||
"ag-grid-community": "36.0.0",
|
||||
"ag-grid-react": "36.0.0",
|
||||
"antd": "^6.5.1",
|
||||
"chrono-node": "^2.10.1",
|
||||
"chrono-node": "^2.10.0",
|
||||
"classnames": "^2.2.5",
|
||||
"content-disposition": "^2.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"dayjs": "^1.11.21",
|
||||
"dom-to-image-more": "^3.10.2",
|
||||
"dom-to-pdf": "^0.3.2",
|
||||
"echarts": "^6.1.0",
|
||||
"echarts": "^5.6.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"fs-extra": "^11.3.6",
|
||||
"fuse.js": "^7.5.0",
|
||||
@@ -187,7 +187,7 @@
|
||||
"geostyler-style": "11.0.2",
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"google-auth-library": "^10.9.0",
|
||||
"immer": "^11.1.15",
|
||||
"immer": "^11.1.11",
|
||||
"interweave": "^13.1.1",
|
||||
"jquery": "^4.0.0",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
@@ -206,7 +206,7 @@
|
||||
"query-string": "9.4.1",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react": "^18.3.0",
|
||||
"react-arborist": "^3.15.0",
|
||||
"react-arborist": "^3.13.2",
|
||||
"react-checkbox-tree": "^1.8.0",
|
||||
"react-diff-viewer-continued": "^4.4.0",
|
||||
"react-dnd": "^11.1.3",
|
||||
@@ -263,16 +263,16 @@
|
||||
"@babel/types": "^7.29.7",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/jest": "^11.14.2",
|
||||
"@formatjs/intl-durationformat": "^0.10.18",
|
||||
"@formatjs/intl-durationformat": "^0.10.17",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@playwright/test": "^1.61.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
|
||||
"@storybook/addon-docs": "10.5.2",
|
||||
"@storybook/addon-links": "10.5.2",
|
||||
"@storybook/react-webpack5": "10.5.2",
|
||||
"@storybook/addon-docs": "10.5.0",
|
||||
"@storybook/addon-links": "10.5.0",
|
||||
"@storybook/react-webpack5": "10.5.0",
|
||||
"@storybook/test-runner": "0.24.4",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@swc/core": "^1.15.46",
|
||||
"@swc/core": "^1.15.43",
|
||||
"@swc/plugin-emotion": "^14.15.0",
|
||||
"@swc/plugin-transform-imports": "^12.5.0",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
@@ -325,7 +325,7 @@
|
||||
"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.5.2",
|
||||
"eslint-plugin-storybook": "10.5.0",
|
||||
"eslint-plugin-testing-library": "^7.16.2",
|
||||
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
|
||||
"fetch-mock": "^12.6.0",
|
||||
@@ -340,7 +340,7 @@
|
||||
"js-yaml-loader": "^1.2.2",
|
||||
"jsdom": "^29.1.1",
|
||||
"lerna": "^9.0.4",
|
||||
"lightningcss": "^1.33.0",
|
||||
"lightningcss": "^1.32.0",
|
||||
"mini-css-extract-plugin": "^2.10.2",
|
||||
"minimizer-webpack-plugin": "^5.6.1",
|
||||
"open-cli": "^9.0.0",
|
||||
@@ -354,12 +354,12 @@
|
||||
"react-refresh": "^0.18.0",
|
||||
"react-resizable": "^4.0.2",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
"source-map": "^0.8.0",
|
||||
"source-map": "^0.7.6",
|
||||
"source-map-support": "^0.5.21",
|
||||
"speed-measure-webpack-plugin": "^1.6.0",
|
||||
"storybook": "10.5.2",
|
||||
"storybook": "10.5.0",
|
||||
"style-loader": "^4.0.0",
|
||||
"stylelint": "^17.14.1",
|
||||
"stylelint": "^17.14.0",
|
||||
"swc-loader": "^0.2.7",
|
||||
"ts-jest": "^29.4.11",
|
||||
"tscw-config": "^1.1.2",
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@visx/responsive": "^4.0.0",
|
||||
"ace-builds": "^1.44.0",
|
||||
"ag-grid-community": "36.0.1",
|
||||
"ag-grid-react": "36.0.1",
|
||||
"ag-grid-community": "36.0.0",
|
||||
"ag-grid-react": "36.0.0",
|
||||
"brace": "^0.11.1",
|
||||
"classnames": "^2.5.1",
|
||||
"core-js": "^3.49.0",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { render, waitFor, configure, act } from '@testing-library/react';
|
||||
import { render, waitFor, configure } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import StatefulChart from './StatefulChart';
|
||||
import getChartControlPanelRegistry from '../registries/ChartControlPanelRegistrySingleton';
|
||||
@@ -67,19 +67,19 @@ beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Setup default registry mocks
|
||||
jest.mocked(getChartMetadataRegistry).mockReturnValue({
|
||||
(getChartMetadataRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue({
|
||||
useLegacyApi: false,
|
||||
}),
|
||||
} as unknown as ReturnType<typeof getChartMetadataRegistry>);
|
||||
});
|
||||
|
||||
jest.mocked(getChartBuildQueryRegistry).mockReturnValue({
|
||||
(getChartBuildQueryRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockResolvedValue(null),
|
||||
} as unknown as ReturnType<typeof getChartBuildQueryRegistry>);
|
||||
});
|
||||
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
(getChartControlPanelRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue(null),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
});
|
||||
|
||||
// Mock ChartClient constructor
|
||||
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
|
||||
@@ -113,9 +113,9 @@ test('should refetch data when non-renderTrigger control changes', async () => {
|
||||
],
|
||||
};
|
||||
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
(getChartControlPanelRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue(controlPanelConfig),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
});
|
||||
|
||||
const { rerender } = render(
|
||||
<StatefulChart formData={mockFormData} chartType="test_chart" />,
|
||||
@@ -165,9 +165,9 @@ test('should NOT refetch data when only renderTrigger controls change', async ()
|
||||
],
|
||||
};
|
||||
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
(getChartControlPanelRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue(controlPanelConfig),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
});
|
||||
|
||||
const { rerender, getByTestId } = render(
|
||||
<StatefulChart formData={mockFormData} chartType="test_chart" />,
|
||||
@@ -201,9 +201,9 @@ test('should NOT refetch data when only renderTrigger controls change', async ()
|
||||
|
||||
test('should refetch when control panel config is not available', async () => {
|
||||
// No control panel config available
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
(getChartControlPanelRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue(null),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
});
|
||||
|
||||
const { rerender } = render(
|
||||
<StatefulChart formData={mockFormData} chartType="test_chart" />,
|
||||
@@ -245,9 +245,9 @@ test('should refetch when viz_type changes', async () => {
|
||||
],
|
||||
};
|
||||
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
(getChartControlPanelRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue(controlPanelConfig),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
});
|
||||
|
||||
const { rerender } = render(
|
||||
<StatefulChart formData={mockFormData} chartType="test_chart" />,
|
||||
@@ -299,9 +299,9 @@ test('should handle mixed renderTrigger and non-renderTrigger changes', async ()
|
||||
],
|
||||
};
|
||||
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
(getChartControlPanelRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue(controlPanelConfig),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
});
|
||||
|
||||
const { rerender } = render(
|
||||
<StatefulChart formData={mockFormData} chartType="test_chart" />,
|
||||
@@ -352,9 +352,9 @@ test('should handle controls with complex structure', async () => {
|
||||
],
|
||||
};
|
||||
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
(getChartControlPanelRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue(controlPanelConfig),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
});
|
||||
|
||||
const { rerender, getByTestId } = render(
|
||||
<StatefulChart formData={mockFormData} chartType="test_chart" />,
|
||||
@@ -404,11 +404,11 @@ test('should not refetch when formData has not changed', async () => {
|
||||
|
||||
test('should handle errors gracefully when accessing registry', async () => {
|
||||
// Mock registry to throw an error
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
(getChartControlPanelRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockImplementation(() => {
|
||||
throw new Error('Registry error');
|
||||
}),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
});
|
||||
|
||||
const { rerender } = render(
|
||||
<StatefulChart formData={mockFormData} chartType="test_chart" />,
|
||||
@@ -492,9 +492,9 @@ test('should NOT refetch data when string-based renderTrigger control (zoomable)
|
||||
],
|
||||
};
|
||||
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
(getChartControlPanelRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue(controlPanelConfig),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
});
|
||||
|
||||
const formDataWithZoom = {
|
||||
...mockFormData,
|
||||
@@ -542,9 +542,9 @@ test('should NOT refetch data when other string-based renderTrigger controls cha
|
||||
],
|
||||
};
|
||||
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
(getChartControlPanelRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue(controlPanelConfig),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
});
|
||||
|
||||
const { rerender, getByTestId } = render(
|
||||
<StatefulChart formData={mockFormData} chartType="test_chart" />,
|
||||
@@ -585,9 +585,9 @@ test('should refetch when string control is NOT in RENDER_TRIGGER_SHARED_CONTROL
|
||||
],
|
||||
};
|
||||
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
(getChartControlPanelRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue(controlPanelConfig),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
});
|
||||
|
||||
const { rerender } = render(
|
||||
<StatefulChart formData={mockFormData} chartType="test_chart" />,
|
||||
@@ -631,9 +631,9 @@ test('should handle mixed string and object controls correctly', async () => {
|
||||
],
|
||||
};
|
||||
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
(getChartControlPanelRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue(controlPanelConfig),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
});
|
||||
|
||||
const formDataWithControls = {
|
||||
...mockFormData,
|
||||
@@ -687,9 +687,9 @@ test('should refetch when mixing renderTrigger string control with non-renderTri
|
||||
],
|
||||
};
|
||||
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
(getChartControlPanelRegistry as any).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue(controlPanelConfig),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
});
|
||||
|
||||
const formDataWithZoom = {
|
||||
...mockFormData,
|
||||
@@ -720,435 +720,6 @@ test('should refetch when mixing renderTrigger string control with non-renderTri
|
||||
});
|
||||
});
|
||||
|
||||
test('resolves async (202) responses via the injected handleAsyncChartData hook', async () => {
|
||||
const asyncJob = {
|
||||
channel_id: 'c1',
|
||||
job_id: 'j1',
|
||||
status: 'running',
|
||||
result_url: '/api/v1/chart/data/abc',
|
||||
};
|
||||
mockChartClient.client.post.mockResolvedValue({
|
||||
response: { status: 202 } as Response,
|
||||
json: asyncJob,
|
||||
});
|
||||
const handleAsyncChartData = jest
|
||||
.fn()
|
||||
.mockResolvedValue([{ data: 'async result' }]);
|
||||
|
||||
const { getByTestId } = render(
|
||||
<StatefulChart
|
||||
formData={mockFormData}
|
||||
chartType="test_chart"
|
||||
hooks={{ handleAsyncChartData }}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
// Delegates the raw response + job metadata (and useLegacyApi + abort signal)
|
||||
expect(handleAsyncChartData).toHaveBeenCalledWith(
|
||||
{ status: 202 },
|
||||
asyncJob,
|
||||
false,
|
||||
expect.any(AbortSignal),
|
||||
);
|
||||
// Chart renders once the async data resolves
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('super-chart')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('errors on async (202) response when no async handler is provided', async () => {
|
||||
mockChartClient.client.post.mockResolvedValue({
|
||||
response: { status: 202 } as Response,
|
||||
json: { job_id: 'j1', channel_id: 'c1', status: 'running' },
|
||||
});
|
||||
const onError = jest.fn();
|
||||
|
||||
const { findByText } = render(
|
||||
<StatefulChart
|
||||
formData={mockFormData}
|
||||
chartType="test_chart"
|
||||
onError={onError}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Fails loudly instead of rendering the job metadata as empty data
|
||||
expect(await findByText(/async handler/i)).toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(onError).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
test('renders synchronous (200) responses that include a response object', async () => {
|
||||
mockChartClient.client.post.mockResolvedValue({
|
||||
response: { status: 200 } as Response,
|
||||
json: [{ result: [{ data: 'sync result' }] }],
|
||||
});
|
||||
|
||||
const { getByTestId } = render(
|
||||
<StatefulChart formData={mockFormData} chartType="test_chart" />,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('super-chart')).toBeInTheDocument();
|
||||
});
|
||||
// Synchronous path: no async handler needed, single request
|
||||
expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('wraps the legacy async body as { result: [body] } for the async handler', async () => {
|
||||
const legacyBody = { job_id: 'j1', channel_id: 'c1', status: 'running' };
|
||||
mockChartClient.client.post.mockResolvedValue({
|
||||
response: { status: 202 } as Response,
|
||||
json: legacyBody,
|
||||
});
|
||||
// Force the legacy API path for this viz type
|
||||
jest.mocked(getChartMetadataRegistry).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue({ useLegacyApi: true }),
|
||||
} as unknown as ReturnType<typeof getChartMetadataRegistry>);
|
||||
const handleAsyncChartData = jest
|
||||
.fn()
|
||||
.mockResolvedValue([{ data: 'legacy result' }]);
|
||||
|
||||
const { getByTestId } = render(
|
||||
<StatefulChart
|
||||
formData={mockFormData}
|
||||
chartType="test_chart"
|
||||
hooks={{ handleAsyncChartData }}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
// Legacy body must be wrapped to match the V1 response signature
|
||||
expect(handleAsyncChartData).toHaveBeenCalledWith(
|
||||
{ status: 202 },
|
||||
{ result: [legacyBody] },
|
||||
true,
|
||||
expect.any(AbortSignal),
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('super-chart')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('does not apply a superseded async response over a newer one', async () => {
|
||||
mockChartClient.client.post.mockResolvedValue({
|
||||
response: { status: 202 } as Response,
|
||||
json: { job_id: 'j', channel_id: 'c' },
|
||||
});
|
||||
let resolveFirst: (data: unknown) => void = () => {};
|
||||
let resolveSecond: (data: unknown) => void = () => {};
|
||||
const handleAsyncChartData = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise(resolve => {
|
||||
resolveFirst = resolve;
|
||||
}),
|
||||
)
|
||||
.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise(resolve => {
|
||||
resolveSecond = resolve;
|
||||
}),
|
||||
);
|
||||
const onLoad = jest.fn();
|
||||
|
||||
const { rerender } = render(
|
||||
<StatefulChart
|
||||
formData={mockFormData}
|
||||
chartType="test_chart"
|
||||
hooks={{ handleAsyncChartData }}
|
||||
onLoad={onLoad}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// A newer request supersedes the first (viz_type change forces a refetch)
|
||||
const newFormData = { ...mockFormData, viz_type: 'different_chart' };
|
||||
rerender(
|
||||
<StatefulChart
|
||||
formData={newFormData}
|
||||
chartType="different_chart"
|
||||
hooks={{ handleAsyncChartData }}
|
||||
onLoad={onLoad}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
// Resolve the newer request first, then the stale one
|
||||
await act(async () => {
|
||||
resolveSecond([{ data: 'B' }]);
|
||||
});
|
||||
await act(async () => {
|
||||
resolveFirst([{ data: 'A' }]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onLoad).toHaveBeenCalledWith([{ data: 'B' }]);
|
||||
});
|
||||
// The stale (superseded) response must not overwrite the newer one
|
||||
expect(onLoad).not.toHaveBeenCalledWith([{ data: 'A' }]);
|
||||
expect(onLoad).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('preserves the detailed message from an async (array) rejection', async () => {
|
||||
mockChartClient.client.post.mockResolvedValue({
|
||||
response: { status: 202 } as Response,
|
||||
json: { job_id: 'j', channel_id: 'c' },
|
||||
});
|
||||
const handleAsyncChartData = jest
|
||||
.fn()
|
||||
.mockRejectedValue([{ error: 'Async query failed: table not found' }]);
|
||||
const onError = jest.fn();
|
||||
|
||||
const { findByText } = render(
|
||||
<StatefulChart
|
||||
formData={mockFormData}
|
||||
chartType="test_chart"
|
||||
hooks={{ handleAsyncChartData }}
|
||||
onError={onError}
|
||||
/>,
|
||||
);
|
||||
|
||||
// The detailed message survives instead of collapsing to the generic one
|
||||
expect(await findByText(/table not found/i)).toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(onError).toHaveBeenCalledTimes(1);
|
||||
expect(onError.mock.calls[0][0].message).toContain('table not found');
|
||||
});
|
||||
});
|
||||
|
||||
test('refetches with the latest formData rather than the initial props', async () => {
|
||||
mockChartClient.client.post.mockResolvedValue({
|
||||
response: { status: 200 } as Response,
|
||||
json: [{ result: [{ data: 'x' }] }],
|
||||
});
|
||||
|
||||
const { rerender } = render(
|
||||
<StatefulChart
|
||||
formData={{ ...mockFormData, metrics: ['metric_v1'] }}
|
||||
chartType="test_chart"
|
||||
/>,
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// Change a data-affecting control -> triggers a refetch
|
||||
rerender(
|
||||
<StatefulChart
|
||||
formData={{ ...mockFormData, metrics: ['metric_v2'] }}
|
||||
chartType="test_chart"
|
||||
/>,
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(mockChartClient.client.post).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
// The second request must carry the updated formData, not the initial props
|
||||
const secondRequestConfig = mockChartClient.client.post.mock.calls[1][0];
|
||||
expect(JSON.stringify(secondRequestConfig)).toContain('metric_v2');
|
||||
expect(JSON.stringify(secondRequestConfig)).not.toContain('metric_v1');
|
||||
});
|
||||
|
||||
test('does not revert a render-only change when a slow async request resolves', async () => {
|
||||
mockChartClient.client.post.mockResolvedValue({
|
||||
response: { status: 202 } as Response,
|
||||
json: { job_id: 'j', channel_id: 'c' },
|
||||
});
|
||||
// color_scheme is a renderTrigger control -> its change does not refetch
|
||||
jest.mocked(getChartControlPanelRegistry).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue({
|
||||
controlPanelSections: [
|
||||
{
|
||||
controlSetRows: [
|
||||
[{ name: 'color_scheme', config: { renderTrigger: true } }],
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
} as unknown as ReturnType<typeof getChartControlPanelRegistry>);
|
||||
let resolveAsync: (data: unknown) => void = () => {};
|
||||
const handleAsyncChartData = jest.fn(
|
||||
() =>
|
||||
new Promise(resolve => {
|
||||
resolveAsync = resolve;
|
||||
}),
|
||||
);
|
||||
|
||||
const { rerender, getByTestId } = render(
|
||||
<StatefulChart
|
||||
formData={{ ...mockFormData, color_scheme: 'scheme_one' }}
|
||||
chartType="test_chart"
|
||||
hooks={{ handleAsyncChartData }}
|
||||
/>,
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// Render-only change while the async request is still pending (no refetch)
|
||||
rerender(
|
||||
<StatefulChart
|
||||
formData={{ ...mockFormData, color_scheme: 'scheme_two' }}
|
||||
chartType="test_chart"
|
||||
hooks={{ handleAsyncChartData }}
|
||||
/>,
|
||||
);
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
|
||||
|
||||
// The stale request resolves; it must not revert color_scheme back
|
||||
await act(async () => {
|
||||
resolveAsync([{ data: 'd' }]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('super-chart')).toHaveTextContent('scheme_two');
|
||||
});
|
||||
expect(getByTestId('super-chart')).not.toHaveTextContent('scheme_one');
|
||||
});
|
||||
|
||||
test('passes an abort signal to the async handler and aborts it on unmount', async () => {
|
||||
mockChartClient.client.post.mockResolvedValue({
|
||||
response: { status: 202 } as Response,
|
||||
json: { job_id: 'j', channel_id: 'c' },
|
||||
});
|
||||
// Typed with a rest param so mock.calls is indexable (the 4th arg is the signal)
|
||||
const handleAsyncChartData = jest.fn(
|
||||
(..._args: unknown[]) => new Promise<never>(() => {}), // never resolves
|
||||
);
|
||||
|
||||
const { unmount } = render(
|
||||
<StatefulChart
|
||||
formData={mockFormData}
|
||||
chartType="test_chart"
|
||||
hooks={{ handleAsyncChartData }}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
const signal = handleAsyncChartData.mock.calls[0][3] as AbortSignal;
|
||||
expect(signal).toBeInstanceOf(AbortSignal);
|
||||
expect(signal.aborted).toBe(false);
|
||||
|
||||
// Unmounting aborts the signal so a signal-aware handler can stop polling
|
||||
unmount();
|
||||
expect(signal.aborted).toBe(true);
|
||||
});
|
||||
|
||||
test('suppresses stale error state from a superseded request', async () => {
|
||||
mockChartClient.client.post.mockResolvedValue({
|
||||
response: { status: 202 } as Response,
|
||||
json: { job_id: 'j', channel_id: 'c' },
|
||||
});
|
||||
let rejectFirst: (err: unknown) => void = () => {};
|
||||
const handleAsyncChartData = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise((_resolve, reject) => {
|
||||
rejectFirst = reject;
|
||||
}),
|
||||
)
|
||||
.mockImplementationOnce(() => new Promise(() => {})); // newer request stays pending
|
||||
const onError = jest.fn();
|
||||
|
||||
const { rerender } = render(
|
||||
<StatefulChart
|
||||
formData={mockFormData}
|
||||
chartType="test_chart"
|
||||
hooks={{ handleAsyncChartData }}
|
||||
onError={onError}
|
||||
/>,
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// Supersede the first request (aborts its controller)
|
||||
rerender(
|
||||
<StatefulChart
|
||||
formData={{ ...mockFormData, viz_type: 'different_chart' }}
|
||||
chartType="different_chart"
|
||||
hooks={{ handleAsyncChartData }}
|
||||
onError={onError}
|
||||
/>,
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
// The stale request now fails; its error must not surface
|
||||
await act(async () => {
|
||||
rejectFirst(new Error('stale failure'));
|
||||
});
|
||||
expect(onError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('does not publish stale data when switching from chartId to formData mode', async () => {
|
||||
mockChartClient.loadFormData.mockResolvedValue({ ...mockFormData });
|
||||
mockChartClient.client.post.mockResolvedValue({
|
||||
response: { status: 202 } as Response,
|
||||
json: { job_id: 'j', channel_id: 'c' },
|
||||
});
|
||||
let resolveFirst: (data: unknown) => void = () => {};
|
||||
const handleAsyncChartData = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise(resolve => {
|
||||
resolveFirst = resolve;
|
||||
}),
|
||||
)
|
||||
.mockImplementationOnce(() => new Promise(() => {}));
|
||||
const onLoad = jest.fn();
|
||||
|
||||
// Start in chartId mode
|
||||
const { rerender } = render(
|
||||
<StatefulChart
|
||||
chartId={1}
|
||||
chartType="test_chart"
|
||||
hooks={{ handleAsyncChartData }}
|
||||
onLoad={onLoad}
|
||||
/>,
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// Switch to direct-formData mode
|
||||
rerender(
|
||||
<StatefulChart
|
||||
formData={{ ...mockFormData, metrics: ['m'] }}
|
||||
chartType="test_chart"
|
||||
hooks={{ handleAsyncChartData }}
|
||||
onLoad={onLoad}
|
||||
/>,
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
// The stale chartId-mode request resolves; its data must not be published
|
||||
await act(async () => {
|
||||
resolveFirst([{ data: 'stale' }]);
|
||||
});
|
||||
expect(onLoad).not.toHaveBeenCalledWith([{ data: 'stale' }]);
|
||||
});
|
||||
|
||||
test('should display error message when HTTP request fails with Response object', async () => {
|
||||
const errorBody = JSON.stringify({ message: 'Error: division by zero' });
|
||||
const mockResponse = new Response(errorBody, {
|
||||
|
||||
@@ -18,19 +18,17 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { isEqual } from 'lodash';
|
||||
import { ParentSize } from '@visx/responsive';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import {
|
||||
QueryFormData,
|
||||
QueryData,
|
||||
JsonObject,
|
||||
SupersetClientInterface,
|
||||
buildQueryContext,
|
||||
RequestConfig,
|
||||
getClientErrorObject,
|
||||
ensureIsArray,
|
||||
} from '../..';
|
||||
import type { HandlerFunction } from '../types/Base';
|
||||
import { Loading } from '../../components/Loading';
|
||||
import ChartClient from '../clients/ChartClient';
|
||||
import getChartBuildQueryRegistry from '../registries/ChartBuildQueryRegistrySingleton';
|
||||
@@ -191,12 +189,6 @@ export default function StatefulChart(props: StatefulChartProps) {
|
||||
const chartClientRef = useRef<ChartClient>();
|
||||
const abortControllerRef = useRef<AbortController>();
|
||||
|
||||
// fetchData is memoized with an empty dep list, so it would otherwise close
|
||||
// over the first render's props. Keep the latest props in a ref so refetches
|
||||
// (triggered by updated filters/formData/overrides) use current values.
|
||||
const propsRef = useRef(props);
|
||||
propsRef.current = props;
|
||||
|
||||
// Initialize chart client
|
||||
if (!chartClientRef.current) {
|
||||
chartClientRef.current = new ChartClient({ client: props.client });
|
||||
@@ -207,48 +199,20 @@ export default function StatefulChart(props: StatefulChartProps) {
|
||||
chartId,
|
||||
formData: propsFormData,
|
||||
formDataOverrides,
|
||||
onError,
|
||||
onLoad,
|
||||
chartType,
|
||||
force,
|
||||
timeout,
|
||||
hooks,
|
||||
} = propsRef.current;
|
||||
} = props;
|
||||
|
||||
// Cancel any in-flight requests
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
}
|
||||
|
||||
// Create new abort controller (kept in a local so we can detect when this
|
||||
// request has been superseded by a newer one, even across async awaits).
|
||||
const controller = new AbortController();
|
||||
abortControllerRef.current = controller;
|
||||
|
||||
// A request is superseded if it was aborted, or if the props changed in a
|
||||
// data-affecting way since it began - including switching between chartId
|
||||
// and direct-formData modes. Props are captured during render but the abort
|
||||
// happens in a passive effect, so the abort signal alone can let a stale
|
||||
// success or error slip through in the render->effect gap. This mirrors the
|
||||
// effect's own refetch decision; render-only changes are intentionally not
|
||||
// treated as superseding.
|
||||
const isSuperseded = () => {
|
||||
if (controller.signal.aborted) {
|
||||
return true;
|
||||
}
|
||||
const latest = propsRef.current;
|
||||
const vizTypeForCompare = latest.formData?.viz_type || latest.chartType;
|
||||
return (
|
||||
latest.chartId !== chartId ||
|
||||
// Deep compare overrides: callers commonly pass a fresh object with the
|
||||
// same contents each render, which should not count as superseding.
|
||||
!isEqual(latest.formDataOverrides, formDataOverrides) ||
|
||||
latest.force !== force ||
|
||||
Boolean(propsFormData) !== Boolean(latest.formData) ||
|
||||
(!!propsFormData &&
|
||||
!!latest.formData &&
|
||||
latest.formData !== propsFormData &&
|
||||
shouldRefetchData(propsFormData, latest.formData, vizTypeForCompare))
|
||||
);
|
||||
};
|
||||
// Create new abort controller
|
||||
abortControllerRef.current = new AbortController();
|
||||
|
||||
setStatus('loading');
|
||||
setError(undefined);
|
||||
@@ -260,7 +224,7 @@ export default function StatefulChart(props: StatefulChartProps) {
|
||||
// Load formData from chartId
|
||||
finalFormData = await chartClientRef.current!.loadFormData(
|
||||
{ sliceId: chartId },
|
||||
{ signal: controller.signal } as RequestConfig,
|
||||
{ signal: abortControllerRef.current.signal } as RequestConfig,
|
||||
);
|
||||
} else if (propsFormData) {
|
||||
// Use provided formData
|
||||
@@ -303,7 +267,7 @@ export default function StatefulChart(props: StatefulChartProps) {
|
||||
|
||||
const requestConfig: RequestConfig = {
|
||||
endpoint,
|
||||
signal: controller.signal,
|
||||
signal: abortControllerRef.current.signal,
|
||||
...(timeout && { timeout: timeout * 1000 }),
|
||||
};
|
||||
|
||||
@@ -321,136 +285,39 @@ export default function StatefulChart(props: StatefulChartProps) {
|
||||
};
|
||||
}
|
||||
|
||||
const clientResponse =
|
||||
await chartClientRef.current!.client.post(requestConfig);
|
||||
const response = await chartClientRef.current!.client.post(requestConfig);
|
||||
let responseData = Array.isArray(response.json)
|
||||
? response.json
|
||||
: [response.json];
|
||||
|
||||
// A newer request may have started while the POST was in flight; discard
|
||||
// this stale response so it can't overwrite the newer chart data.
|
||||
if (isSuperseded()) {
|
||||
return;
|
||||
// Handle the nested result structure from the new API
|
||||
if (!useLegacyApi && responseData[0]?.result) {
|
||||
responseData = responseData[0].result;
|
||||
}
|
||||
|
||||
const rawResponse = clientResponse.response as Response | undefined;
|
||||
|
||||
let responseData: QueryData[];
|
||||
if (rawResponse?.status === 202) {
|
||||
// With GLOBAL_ASYNC_QUERIES the query is dispatched to a Celery worker
|
||||
// and the 202 body is job metadata (channel_id, job_id, result_url),
|
||||
// not chart data. Delegate to the injected handler, which polls the
|
||||
// async event channel and resolves the cached results. Without a
|
||||
// handler we fail loudly rather than rendering the job metadata as if
|
||||
// it were an (empty) result set.
|
||||
if (!hooks?.handleAsyncChartData) {
|
||||
throw new Error(
|
||||
'Received an async chart data response (HTTP 202) but no async ' +
|
||||
'handler was provided, so results cannot be retrieved. Wire up ' +
|
||||
'the async handler or disable GLOBAL_ASYNC_QUERIES for this chart.',
|
||||
);
|
||||
}
|
||||
// The async handler (handleChartDataResponse) expects the V1 chart data
|
||||
// response signature. The legacy endpoint returns a flat body, so wrap
|
||||
// it as { result: [body] } exactly like legacyChartDataRequest does for
|
||||
// the standard chart path; the V1 body is already correctly shaped.
|
||||
const asyncPayload = useLegacyApi
|
||||
? ({ result: [clientResponse.json] } as JsonObject)
|
||||
: (clientResponse.json as JsonObject);
|
||||
responseData = ensureIsArray(
|
||||
await hooks.handleAsyncChartData(
|
||||
rawResponse,
|
||||
asyncPayload,
|
||||
useLegacyApi,
|
||||
controller.signal,
|
||||
),
|
||||
);
|
||||
|
||||
// Async results can resolve well after a newer request began polling.
|
||||
if (isSuperseded()) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const rows = (
|
||||
Array.isArray(clientResponse.json)
|
||||
? clientResponse.json
|
||||
: [clientResponse.json]
|
||||
) as JsonObject[];
|
||||
|
||||
// Handle the nested result structure from the new API
|
||||
responseData = (
|
||||
!useLegacyApi && rows[0]?.result ? rows[0].result : rows
|
||||
) as QueryData[];
|
||||
}
|
||||
|
||||
// Don't pair this request's data with newer props or fire a stale onLoad
|
||||
// if it has been superseded (see isSuperseded).
|
||||
if (isSuperseded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const latestProps = propsRef.current;
|
||||
setStatus('loaded');
|
||||
setData(responseData);
|
||||
// Render the resolved data with the latest formData so a render-only
|
||||
// change made while the request was in flight isn't reverted.
|
||||
setFormData(
|
||||
latestProps.formData
|
||||
? {
|
||||
...latestProps.formData,
|
||||
...latestProps.formDataOverrides,
|
||||
viz_type: finalFormData.viz_type,
|
||||
}
|
||||
: finalFormData,
|
||||
);
|
||||
setFormData(finalFormData);
|
||||
|
||||
// Read onLoad from the latest props (like setFormData above) so a stale
|
||||
// callback captured at request start isn't invoked.
|
||||
if (latestProps.onLoad) {
|
||||
latestProps.onLoad(responseData);
|
||||
if (onLoad) {
|
||||
onLoad(responseData);
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore aborted requests, whether they threw AbortError or were
|
||||
// superseded by a newer request (including the render->effect gap).
|
||||
if ((err as Error)?.name === 'AbortError' || isSuperseded()) {
|
||||
// Ignore abort errors
|
||||
if ((err as Error).name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
|
||||
// waitForAsyncData rejects with an array of already-parsed client-error
|
||||
// objects; unwrap the first element so its detailed message survives.
|
||||
const rawError = Array.isArray(err) ? err[0] : err;
|
||||
|
||||
let errorMessage: string | undefined;
|
||||
if (
|
||||
rawError &&
|
||||
typeof rawError === 'object' &&
|
||||
!(rawError instanceof Error) &&
|
||||
!(rawError instanceof Response) &&
|
||||
typeof (rawError as { error?: unknown }).error === 'string'
|
||||
) {
|
||||
// Already a parsed client-error object (e.g. from the async handler);
|
||||
// getClientErrorObject would discard its `error` field, so read it here.
|
||||
const parsed = rawError as { error?: string; message?: string };
|
||||
errorMessage = parsed.error || parsed.message;
|
||||
} else {
|
||||
const parsedError = await getClientErrorObject(
|
||||
rawError as Parameters<typeof getClientErrorObject>[0],
|
||||
);
|
||||
errorMessage = parsedError.error || parsedError.message;
|
||||
}
|
||||
|
||||
const errorObj = new Error(errorMessage || 'An error occurred');
|
||||
|
||||
// The request may have been superseded while its error response was being
|
||||
// parsed above (or in the render->effect gap before its abort ran); don't
|
||||
// set stale error state or call onError in that case.
|
||||
if (isSuperseded()) {
|
||||
return;
|
||||
}
|
||||
const parsedError = await getClientErrorObject(
|
||||
err as Parameters<typeof getClientErrorObject>[0],
|
||||
);
|
||||
const errorMessage =
|
||||
parsedError.error || parsedError.message || 'An error occurred';
|
||||
|
||||
const errorObj = new Error(errorMessage);
|
||||
setStatus('error');
|
||||
setError(errorObj);
|
||||
|
||||
// Read onError from the latest props so a stale callback captured at
|
||||
// request start isn't invoked.
|
||||
const { onError } = propsRef.current;
|
||||
if (onError) {
|
||||
onError(errorObj);
|
||||
}
|
||||
@@ -614,7 +481,7 @@ export default function StatefulChart(props: StatefulChartProps) {
|
||||
enableNoResults={enableNoResults}
|
||||
noResults={NoDataComponent && <NoDataComponent />}
|
||||
onRenderSuccess={onRenderSuccess}
|
||||
onRenderFailure={onRenderFailure}
|
||||
onRenderFailure={onRenderFailure as HandlerFunction | undefined}
|
||||
hooks={hooks}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -66,18 +66,6 @@ type Hooks = {
|
||||
setTooltip?: HandlerFunction;
|
||||
/* handle legend scroll changes */
|
||||
onLegendScroll?: HandlerFunction;
|
||||
/**
|
||||
* Resolve an async chart-data response (HTTP 202 from GLOBAL_ASYNC_QUERIES).
|
||||
* Injected by the app so components in this package (e.g. Matrixify's
|
||||
* StatefulChart) can await async results without importing app-level
|
||||
* async-event middleware. Returns the resolved query results.
|
||||
*/
|
||||
handleAsyncChartData?: (
|
||||
response: Response,
|
||||
json: JsonObject,
|
||||
useLegacyApi?: boolean,
|
||||
signal?: AbortSignal,
|
||||
) => Promise<QueryData[]> | QueryData[];
|
||||
} & PlainObject;
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { render, screen, userEvent } from '@superset-ui/core/spec';
|
||||
import { render, screen, userEvent, fireEvent } from '@superset-ui/core/spec';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import { ActionButton } from '.';
|
||||
|
||||
@@ -45,6 +45,18 @@ test('calls onClick when clicked', async () => {
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('calls onClick when activated with the keyboard', () => {
|
||||
const onClick = jest.fn();
|
||||
render(<ActionButton {...defaultProps} onClick={onClick} />);
|
||||
|
||||
const button = screen.getByRole('button');
|
||||
fireEvent.keyDown(button, { key: 'Enter' });
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
|
||||
fireEvent.keyDown(button, { key: ' ' });
|
||||
expect(onClick).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('renders with tooltip when tooltip prop is provided', async () => {
|
||||
const tooltipText = 'This is a tooltip';
|
||||
render(<ActionButton {...defaultProps} tooltip={tooltipText} />);
|
||||
@@ -103,6 +115,6 @@ test('has proper accessibility attributes', () => {
|
||||
render(<ActionButton {...defaultProps} />);
|
||||
|
||||
const button = screen.getByRole('button');
|
||||
expect(button.tagName).toBe('BUTTON');
|
||||
expect(button).toHaveAttribute('type', 'button');
|
||||
expect(button).toHaveAttribute('tabIndex', '0');
|
||||
expect(button).toHaveAttribute('role', 'button');
|
||||
});
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { handleKeyboardActivation } from '../../utils';
|
||||
import type { ReactElement, ReactNode } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { Tooltip, type TooltipPlacement } from '@superset-ui/core/components';
|
||||
import { css, useTheme } from '@apache-superset/core/theme';
|
||||
|
||||
@@ -28,9 +28,6 @@ export interface ActionProps {
|
||||
placement?: TooltipPlacement;
|
||||
icon: ReactNode;
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
dataTest?: string;
|
||||
}
|
||||
|
||||
export const ActionButton = ({
|
||||
@@ -39,45 +36,30 @@ export const ActionButton = ({
|
||||
placement,
|
||||
icon,
|
||||
onClick,
|
||||
className,
|
||||
disabled = false,
|
||||
dataTest,
|
||||
}: ActionProps) => {
|
||||
const theme = useTheme();
|
||||
const actionButton = (
|
||||
<button
|
||||
type="button"
|
||||
aria-disabled={disabled}
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={typeof tooltip === 'string' ? tooltip : label}
|
||||
css={css`
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
line-height: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: ${theme.colorIcon};
|
||||
margin-right: ${theme.sizeUnit}px;
|
||||
&:not(.disabled):hover {
|
||||
&:hover {
|
||||
path {
|
||||
fill: ${theme.colorPrimary};
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
color: ${theme.colorTextDisabled};
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`}
|
||||
className={cx('action-button', className, { disabled })}
|
||||
data-test={dataTest ?? label}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
className="action-button"
|
||||
data-test={label}
|
||||
onClick={onClick}
|
||||
onKeyDown={onClick ? handleKeyboardActivation(onClick) : undefined}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
|
||||
const tooltipId = `${label.replaceAll(' ', '-').toLowerCase()}-tooltip`;
|
||||
|
||||
@@ -21,11 +21,7 @@ import type { ButtonGroupProps } from './types';
|
||||
export function ButtonGroup(props: ButtonGroupProps) {
|
||||
const { className, children } = props;
|
||||
return (
|
||||
// role="group" is the correct ARIA pattern for a generic button toolbar;
|
||||
// the suggested native tags (fieldset, etc.) carry unrelated form
|
||||
// semantics and unwanted default browser styling.
|
||||
<div
|
||||
// eslint-disable-next-line jsx-a11y/prefer-tag-over-role
|
||||
role="group"
|
||||
className={className}
|
||||
css={{
|
||||
|
||||
@@ -134,10 +134,6 @@ const ImageContainer = ({
|
||||
? imageMap[image as keyof typeof imageMap]
|
||||
: image;
|
||||
return (
|
||||
// Groups Empty's SVG illustration + description into one accessible
|
||||
// image; can't be a literal <img> since it's a component tree, not an
|
||||
// image file reference.
|
||||
// eslint-disable-next-line jsx-a11y/prefer-tag-over-role
|
||||
<div role="img" aria-label="empty">
|
||||
<Empty
|
||||
description={false}
|
||||
|
||||
@@ -25,12 +25,8 @@ import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import { Tooltip } from '../Tooltip';
|
||||
import type { FaveStarProps } from './types';
|
||||
|
||||
const StyledLink = styled.button`
|
||||
const StyledLink = styled.a`
|
||||
${({ theme }) => css`
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: none;
|
||||
font: inherit;
|
||||
font-size: ${theme.fontSizeXL}px;
|
||||
display: flex;
|
||||
padding: 0 0 0 ${theme.sizeUnit * 2}px;
|
||||
@@ -59,10 +55,12 @@ export const FaveStar = ({
|
||||
|
||||
const content = (
|
||||
<StyledLink
|
||||
type="button"
|
||||
href="#"
|
||||
onClick={onClick}
|
||||
className="fave-unfave-icon"
|
||||
data-test="fave-unfave-icon"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
{isStarred ? (
|
||||
<Icons.StarFilled
|
||||
|
||||
@@ -102,10 +102,6 @@ export const LabeledErrorBoundInput = ({
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
// input[type=password] doesn't get an implicit "textbox" role
|
||||
// (unlike other text inputs), so this explicit override is
|
||||
// needed for it to be discoverable via role-based queries/AT.
|
||||
// eslint-disable-next-line jsx-a11y/prefer-tag-over-role
|
||||
role="textbox"
|
||||
/>
|
||||
) : renderAsTextArea ? (
|
||||
|
||||
@@ -79,12 +79,8 @@ const IconButton: React.FC<IconButtonProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
// antd's Card renders a fixed <div> (no polymorphic tag support) with
|
||||
// its own rich internal layout (cover/title/tooltip); that doesn't map
|
||||
// onto a native <button>, so role="button" is used instead.
|
||||
<Card
|
||||
hoverable
|
||||
// eslint-disable-next-line jsx-a11y/prefer-tag-over-role
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={buttonText}
|
||||
|
||||
@@ -99,10 +99,6 @@ export function Loading({
|
||||
$spinnerHeight="auto"
|
||||
$opacity={opacity}
|
||||
className={cls('loading', position, className)}
|
||||
// role="status" is the standard WAI-ARIA live-region pattern for a
|
||||
// loading spinner; <output> (the suggested tag) is for form
|
||||
// calculation results, not a fit here.
|
||||
// eslint-disable-next-line jsx-a11y/prefer-tag-over-role
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-label={t('Loading')}
|
||||
|
||||
@@ -89,7 +89,6 @@ const StyledItem = styled.div<{
|
||||
& .metadata-text {
|
||||
color: ${theme.colorTextSecondary};
|
||||
min-width: ${TEXT_MIN_WIDTH}px;
|
||||
max-width: ${TEXT_MAX_WIDTH}px;
|
||||
overflow: hidden;
|
||||
text-overflow: ${collapsed ? 'unset' : 'ellipsis'};
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -84,7 +84,7 @@ export const StyledModal = styled(BaseModal)<StyledModalProps>`
|
||||
`
|
||||
}
|
||||
|
||||
.ant-modal-container {
|
||||
.ant-modal-content {
|
||||
background-color: ${theme.colorBgContainer};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -196,7 +196,7 @@ export const StyledModal = styled(BaseModal)<StyledModalProps>`
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ant-modal-container {
|
||||
.ant-modal-content {
|
||||
height: 100%;
|
||||
|
||||
.ant-modal-body {
|
||||
|
||||
@@ -127,15 +127,10 @@ export const ModalTrigger = forwardRef(
|
||||
</Button>
|
||||
)}
|
||||
{!isButton && (
|
||||
// Generic wrapper used by 19+ callers passing arbitrary
|
||||
// triggerNode content that relies on block-level <div> layout;
|
||||
// swapping to <button> risks a layout regression across callers
|
||||
// with no shared CSS to guide a safe reset.
|
||||
<div
|
||||
data-test="span-modal-trigger"
|
||||
onClick={open}
|
||||
onKeyDown={handleKeyboardActivation(open)}
|
||||
// eslint-disable-next-line jsx-a11y/prefer-tag-over-role
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
|
||||
@@ -103,18 +103,7 @@ const PopoverDropdown = (props: PopoverDropdownProps) => {
|
||||
})),
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
css={{
|
||||
appearance: 'none',
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
padding: 0,
|
||||
font: 'inherit',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<div role="button" css={{ display: 'flex', alignItems: 'center' }}>
|
||||
{selected && renderButton(selected)}
|
||||
<Icons.DownOutlined
|
||||
iconSize="s"
|
||||
@@ -123,7 +112,7 @@ const PopoverDropdown = (props: PopoverDropdownProps) => {
|
||||
marginLeft: theme.sizeUnit * 0.5,
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ import PopoverSection from '.';
|
||||
test('renders with default props', async () => {
|
||||
render(
|
||||
<PopoverSection title="Title">
|
||||
<form aria-label="test-form" />
|
||||
<div role="form" />
|
||||
</PopoverSection>,
|
||||
);
|
||||
expect(await screen.findByRole('form')).toBeInTheDocument();
|
||||
@@ -32,7 +32,7 @@ test('renders with default props', async () => {
|
||||
test('renders tooltip icon', async () => {
|
||||
render(
|
||||
<PopoverSection title="Title" info="Tooltip">
|
||||
<form />
|
||||
<div role="form" />
|
||||
</PopoverSection>,
|
||||
);
|
||||
expect((await screen.findAllByRole('img')).length).toBe(2);
|
||||
@@ -41,7 +41,7 @@ test('renders tooltip icon', async () => {
|
||||
test('renders a tooltip when hovered', async () => {
|
||||
render(
|
||||
<PopoverSection title="Title" info="Tooltip">
|
||||
<form />
|
||||
<div role="form" />
|
||||
</PopoverSection>,
|
||||
);
|
||||
await userEvent.hover(screen.getAllByRole('img')[0]);
|
||||
@@ -52,7 +52,7 @@ test('calls onSelect when clicked', async () => {
|
||||
const onSelect = jest.fn();
|
||||
render(
|
||||
<PopoverSection title="Title" onSelect={onSelect}>
|
||||
<form />
|
||||
<div role="form" />
|
||||
</PopoverSection>,
|
||||
);
|
||||
await userEvent.click(await screen.findByRole('img'));
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { MouseEventHandler, ReactNode } from 'react';
|
||||
import { handleKeyboardActivation } from '../../utils';
|
||||
import { ReactNode, SyntheticEvent } from 'react';
|
||||
import { css, useTheme } from '@apache-superset/core/theme';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import { Tooltip } from '../Tooltip';
|
||||
@@ -24,7 +25,10 @@ import { Tooltip } from '../Tooltip';
|
||||
export interface PopoverSectionProps {
|
||||
title: string;
|
||||
isSelected?: boolean;
|
||||
onSelect?: MouseEventHandler<HTMLButtonElement>;
|
||||
// `SyntheticEvent` (rather than `MouseEventHandler`) so the same callback
|
||||
// can be reused as the keyboard-activation handler via
|
||||
// `handleKeyboardActivation`, which invokes it with a `KeyboardEvent`.
|
||||
onSelect?: (event: SyntheticEvent) => void;
|
||||
info?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
@@ -44,17 +48,12 @@ export default function PopoverSection({
|
||||
opacity: isSelected ? 1 : 0.6,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onSelect}
|
||||
onKeyDown={onSelect ? handleKeyboardActivation(onSelect) : undefined}
|
||||
css={css`
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: ${onSelect ? 'pointer' : 'default'};
|
||||
@@ -69,9 +68,8 @@ export default function PopoverSection({
|
||||
margin-right: ${theme.sizeUnit}px;
|
||||
`}
|
||||
>
|
||||
{/* role is auto-computed by BaseIconComponent as "img" since
|
||||
there's no onClick, so no explicit role needed here. */}
|
||||
<Icons.InfoCircleOutlined
|
||||
role="img"
|
||||
iconSize="s"
|
||||
iconColor={theme.colorIcon}
|
||||
/>
|
||||
@@ -79,9 +77,10 @@ export default function PopoverSection({
|
||||
)}
|
||||
<Icons.CheckOutlined
|
||||
iconSize="s"
|
||||
role="img"
|
||||
iconColor={isSelected ? theme.colorPrimary : theme.colorIcon}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
css={css`
|
||||
margin-left: ${theme.sizeUnit}px;
|
||||
|
||||
@@ -60,26 +60,18 @@ test('should render with success icon', () => {
|
||||
expect(screen.getByLabelText('check-circle')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// The stripes are painted on antd's inner progress track, so assert the
|
||||
// computed style of that element — asserting the outer container would pass
|
||||
// whether or not the gradient ever reached the track.
|
||||
const getTrack = (container: HTMLElement) =>
|
||||
container.querySelector('.ant-progress-track') as HTMLElement;
|
||||
|
||||
test('should render with stripes', () => {
|
||||
const stripedProps = {
|
||||
...mockedProps,
|
||||
striped: true,
|
||||
};
|
||||
const { container } = render(<ProgressBar {...stripedProps} />);
|
||||
expect(getComputedStyle(getTrack(container)).backgroundImage).toContain(
|
||||
'linear-gradient(45deg',
|
||||
);
|
||||
});
|
||||
|
||||
test('should render without stripes by default', () => {
|
||||
const { container } = render(<ProgressBar {...mockedProps} />);
|
||||
expect(getComputedStyle(getTrack(container)).backgroundImage).not.toContain(
|
||||
'linear-gradient',
|
||||
expect(container).toHaveStyle(
|
||||
`background-image: 'linear-gradient(
|
||||
45deg,rgba(255, 255, 255, 0.15) 25%,
|
||||
transparent 25%, transparent 50%,
|
||||
rgba(255, 255, 255, 0.15) 50%,
|
||||
rgba(255, 255, 255, 0.15) 75%,
|
||||
transparent 75%, transparent) !important'`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -29,10 +29,10 @@ const ProgressBar = styled(({ striped, ...props }: ProgressBarProps) => (
|
||||
<AntdProgress data-test="progress-bar" {...props} />
|
||||
))`
|
||||
position: static;
|
||||
.ant-progress-rail {
|
||||
.ant-progress-inner {
|
||||
position: static;
|
||||
}
|
||||
.ant-progress-track {
|
||||
.ant-progress-bg {
|
||||
position: static;
|
||||
${({ striped }) =>
|
||||
striped &&
|
||||
|
||||
@@ -35,8 +35,7 @@ const RefreshLabel = ({
|
||||
<Tooltip title={tooltipContent}>
|
||||
<Icons.SyncOutlined
|
||||
iconSize="l"
|
||||
// role is auto-computed by BaseIconComponent as "button" whenever
|
||||
// onClick is present (and "img" otherwise), so no explicit role here.
|
||||
role="button"
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
css={(theme: SupersetTheme) => ({
|
||||
|
||||
@@ -870,14 +870,14 @@ test('Renders only an overflow tag if dropdown is open in oneLine mode', async (
|
||||
test('does not fire onChange when searching but no selection', async () => {
|
||||
const onChange = jest.fn();
|
||||
render(
|
||||
<main>
|
||||
<div role="main">
|
||||
<AsyncSelect
|
||||
{...defaultProps}
|
||||
onChange={onChange}
|
||||
mode="multiple"
|
||||
allowNewOptions
|
||||
/>
|
||||
</main>,
|
||||
</div>,
|
||||
);
|
||||
await open();
|
||||
await type('Joh');
|
||||
|
||||
@@ -857,7 +857,7 @@ const AsyncSelect = forwardRef(
|
||||
getPopupContainer={
|
||||
getPopupContainer ||
|
||||
((triggerNode: HTMLElement) =>
|
||||
(triggerNode?.closest('.ant-modal-container') as HTMLElement) ||
|
||||
(triggerNode?.closest('.ant-modal-content') as HTMLElement) ||
|
||||
(triggerNode.parentNode as HTMLElement))
|
||||
}
|
||||
headerPosition={headerPosition}
|
||||
|
||||
@@ -1032,35 +1032,6 @@ test('do not count unselected disabled options in "Select all"', async () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('"Select all" does not count null-valued options', async () => {
|
||||
// A falsy-valued option (e.g. <NULL>, value: null) is skipped by
|
||||
// handleSelectAll, so it must not be counted in the "Select all" badge or
|
||||
// the count overstates the selection. Regression test for #40228. Uses a
|
||||
// local options array to stay isolated from tests that mutate OPTIONS.
|
||||
const localOptions = [
|
||||
{ label: 'Alpha', value: 1 },
|
||||
{ label: 'Bravo', value: 2 },
|
||||
];
|
||||
render(
|
||||
<Select
|
||||
{...defaultProps}
|
||||
options={[...localOptions, NULL_OPTION]}
|
||||
mode="multiple"
|
||||
maxTagCount={0}
|
||||
/>,
|
||||
);
|
||||
await open();
|
||||
// Three options are visible, but the <NULL> option is not bulk-selectable,
|
||||
// so the badge must count only the two real options (would be 3 before fix).
|
||||
await userEvent.click(
|
||||
await screen.findByText(selectAllButtonText(localOptions.length)),
|
||||
);
|
||||
// And Select all selects exactly those two — the null option is skipped.
|
||||
const values = await findAllSelectValues();
|
||||
expect(values.length).toBe(1);
|
||||
expect(values[0]).toHaveTextContent(`+ ${localOptions.length} ...`);
|
||||
});
|
||||
|
||||
test('"Deselect all" counts all selected options', async () => {
|
||||
render(<Select {...defaultProps} allowNewOptions mode="multiple" />);
|
||||
await open();
|
||||
@@ -1163,14 +1134,14 @@ test('dropdown takes full width of the select input for single select', async ()
|
||||
test('does not fire onChange when searching but no selection', async () => {
|
||||
const onChange = jest.fn();
|
||||
render(
|
||||
<main>
|
||||
<div role="main">
|
||||
<Select
|
||||
{...defaultProps}
|
||||
onChange={onChange}
|
||||
mode="multiple"
|
||||
allowNewOptions
|
||||
/>
|
||||
</main>,
|
||||
</div>,
|
||||
);
|
||||
await open();
|
||||
await type('Joh');
|
||||
|
||||
@@ -332,12 +332,7 @@ const Select = forwardRef(
|
||||
const isDisabled = option.disabled;
|
||||
const isNew = option.isNewOption;
|
||||
|
||||
// Mirror handleSelectAll, which skips falsy-valued options (e.g. the
|
||||
// <NULL> option whose value is null): they are not bulk-selectable,
|
||||
// so counting them here makes the "Select all" badge overstate what
|
||||
// gets selected.
|
||||
if (
|
||||
option.value &&
|
||||
(!isDisabled || isSelected) &&
|
||||
((isNew && isSelected) || !isNew)
|
||||
) {
|
||||
@@ -917,7 +912,7 @@ const Select = forwardRef(
|
||||
getPopupContainer={
|
||||
getPopupContainer ||
|
||||
((triggerNode: HTMLElement) =>
|
||||
(triggerNode?.closest('.ant-modal-container') as HTMLElement) ||
|
||||
(triggerNode?.closest('.ant-modal-content') as HTMLElement) ||
|
||||
(triggerNode.parentNode as HTMLElement))
|
||||
}
|
||||
headerPosition={headerPosition}
|
||||
|
||||
@@ -61,7 +61,7 @@ const StyledTable = styled(AntTable)(
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ant-spin .ant-spin-dot {
|
||||
.ant-spin-nested-loading .ant-spin .ant-spin-dot {
|
||||
width: ${theme.sizeUnit * 12}px;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ const StyledTable = styled(AntTable as FC<AntTableProps>)<{ height?: number }>(
|
||||
scrollbar-color: ${theme.colorFillSecondary} ${theme.colorFillQuaternary};
|
||||
}
|
||||
|
||||
.ant-spin .ant-spin-dot {
|
||||
.ant-spin-nested-loading .ant-spin .ant-spin-dot {
|
||||
width: ${theme.sizeXXL}px;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,12 @@ const StyledTable = styled(Table)<{
|
||||
showRowCount?: boolean;
|
||||
}>`
|
||||
${({ theme, isPaginationSticky, showRowCount }) => `
|
||||
th.ant-column-cell {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.actions {
|
||||
opacity: 0;
|
||||
font-size: ${theme.fontSizeXL}px;
|
||||
|
||||
@@ -142,9 +142,7 @@ EditableTabs.defaultProps = {
|
||||
};
|
||||
|
||||
EditableTabs.TabPane.defaultProps = {
|
||||
// rc-tabs already wraps closeIcon in its own <button role="tab"
|
||||
// aria-label="remove">; this is just decorative content inside it.
|
||||
closeIcon: <StyledCloseOutlined iconSize="s" />,
|
||||
closeIcon: <StyledCloseOutlined iconSize="s" role="button" tabIndex={0} />,
|
||||
};
|
||||
|
||||
export const StyledLineEditableTabs = styled(EditableTabs)`
|
||||
|
||||
@@ -31,22 +31,10 @@
|
||||
* the exact class names our CSS depends on, so a future antd bump that renames
|
||||
* one fails here with a clear pointer instead of shipping a visual regression.
|
||||
*/
|
||||
import { render, screen, userEvent, waitFor } from '@superset-ui/core/spec';
|
||||
import {
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
Alert,
|
||||
Collapse,
|
||||
Modal,
|
||||
Popover,
|
||||
Progress,
|
||||
Spin,
|
||||
Steps,
|
||||
Tabs,
|
||||
Tag,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
import { Modal as SupersetModal } from './Modal';
|
||||
import { AsyncSelect, Select } from './Select';
|
||||
import { render } from '@superset-ui/core/spec';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Collapse, Modal, Popover, Steps, Tabs, Tag, Tooltip } from 'antd';
|
||||
import { Select } from './Select';
|
||||
|
||||
const antClasses = (root: ParentNode): string[] => {
|
||||
const classes = new Set<string>();
|
||||
@@ -188,17 +176,9 @@ test('Steps classes (QueryStatusBar, ChartCreation, SQL Lab loading detection)',
|
||||
'ant-steps-item-icon',
|
||||
'ant-steps-item-title',
|
||||
'ant-steps-item-rail',
|
||||
'ant-steps-item-content',
|
||||
]),
|
||||
);
|
||||
expect(classes).not.toContain('ant-steps-item-tail');
|
||||
// antd 6 removed the nested `.ant-steps-item-description`; the description text
|
||||
// now renders directly inside `.ant-steps-item-content`. ChartCreation styles
|
||||
// the description by targeting `.ant-steps-item-content` for exactly this reason.
|
||||
expect(classes).not.toContain('ant-steps-item-description');
|
||||
expect(container.querySelector('.ant-steps-item-content')).toHaveTextContent(
|
||||
'd',
|
||||
);
|
||||
});
|
||||
|
||||
test('Select suffix (arrow) class (plugin-chart-table page-size Select targets it)', () => {
|
||||
@@ -236,126 +216,13 @@ test('Collapse panel/body classes (Collapse.tsx, VizTypeGallery, config modals)'
|
||||
expect(classes).not.toContain('ant-collapse-content-box');
|
||||
});
|
||||
|
||||
test('Modal container + body classes (many *.styles.ts modal overrides target them)', () => {
|
||||
test('Modal body class (many *.styles.ts modal overrides target it)', () => {
|
||||
render(
|
||||
<Modal open title="t">
|
||||
body
|
||||
</Modal>,
|
||||
);
|
||||
const classes = antClasses(document.body);
|
||||
// antd 6 renamed the modal content wrapper `.ant-modal-content` ->
|
||||
// `.ant-modal-container`. Several `.styles.ts` overrides target it, and Select /
|
||||
// popup `getPopupContainer`/`.closest()` lookups anchor popups to it so their
|
||||
// menus stay clipped inside the modal. `-body`/`-close` are unchanged but the
|
||||
// report-screenshot error-modal flow in `webdriver.py` walks all three, so pin
|
||||
// the whole chain it depends on.
|
||||
expect(classes).toEqual(
|
||||
expect.arrayContaining([
|
||||
'ant-modal-container',
|
||||
'ant-modal-body',
|
||||
'ant-modal-close',
|
||||
]),
|
||||
);
|
||||
expect(classes).not.toContain('ant-modal-content');
|
||||
});
|
||||
|
||||
test.each([
|
||||
[
|
||||
'Select',
|
||||
<Select
|
||||
ariaLabel="in-modal"
|
||||
options={[{ label: 'Alpha', value: 'a' }]}
|
||||
key="sync"
|
||||
/>,
|
||||
],
|
||||
[
|
||||
'AsyncSelect',
|
||||
<AsyncSelect
|
||||
ariaLabel="in-modal"
|
||||
options={async () => ({
|
||||
data: [{ label: 'Alpha', value: 'a' }],
|
||||
totalCount: 1,
|
||||
})}
|
||||
key="async"
|
||||
/>,
|
||||
],
|
||||
])(
|
||||
'%s popup mounts inside the enclosing modal container',
|
||||
async (_name, select) => {
|
||||
// Select/AsyncSelect anchor their popup with
|
||||
// `triggerNode.closest('.ant-modal-container')` so the menu is clipped by
|
||||
// the modal instead of escaping to <body> and scrolling away from its
|
||||
// trigger. The class contract above only proves the class exists — this
|
||||
// asserts the containment behaviour it exists for.
|
||||
render(
|
||||
<SupersetModal show title="t" onHide={() => {}}>
|
||||
{select}
|
||||
</SupersetModal>,
|
||||
);
|
||||
await userEvent.click(screen.getByRole('combobox'));
|
||||
await screen.findByTitle('Alpha');
|
||||
|
||||
await waitFor(() => {
|
||||
const popup = document.querySelector('.ant-select-dropdown');
|
||||
expect(popup).not.toBeNull();
|
||||
// Assert the *direct* parent, not just an ancestor: when the `.closest()`
|
||||
// lookup misses, the `triggerNode.parentNode` fallback still sits inside
|
||||
// the modal, so an `ancestor` check passes either way and proves nothing.
|
||||
expect(popup?.parentElement).toHaveClass('ant-modal-container');
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test('Progress rail/track classes (ProgressBar + DatabaseModal striped overrides target them)', () => {
|
||||
const { container } = render(<Progress percent={50} />);
|
||||
const classes = antClasses(container);
|
||||
// antd 6 renamed `.ant-progress-inner` -> `.ant-progress-rail` and
|
||||
// `.ant-progress-bg` -> `.ant-progress-track`. ProgressBar's styled wrapper
|
||||
// paints the striped gradient on the track; DatabaseModal sizes the rail.
|
||||
expect(classes).toEqual(
|
||||
expect.arrayContaining(['ant-progress-rail', 'ant-progress-track']),
|
||||
);
|
||||
expect(classes).not.toContain('ant-progress-inner');
|
||||
expect(classes).not.toContain('ant-progress-bg');
|
||||
});
|
||||
|
||||
test('Alert title/actions classes (ImportModal, DatabaseModal, SqlEditor, native-filter overrides target them)', () => {
|
||||
const { container } = render(
|
||||
<Alert
|
||||
type="info"
|
||||
title="msg"
|
||||
description="desc"
|
||||
action={<span>x</span>}
|
||||
/>,
|
||||
);
|
||||
const classes = antClasses(container);
|
||||
// antd 6 renamed `.ant-alert-message` -> `.ant-alert-title` and pluralised the
|
||||
// action slot `.ant-alert-action` -> `.ant-alert-actions`. Several `.styles.ts`
|
||||
// overrides and the SQL Lab / native-filter modal footers target these.
|
||||
expect(classes).toEqual(
|
||||
expect.arrayContaining(['ant-alert-title', 'ant-alert-actions']),
|
||||
);
|
||||
expect(classes).not.toContain('ant-alert-message');
|
||||
expect(classes).not.toContain('ant-alert-action');
|
||||
});
|
||||
|
||||
test('Spin nested structure (Table/VirtualTable spinner sizing overrides target it)', () => {
|
||||
const { container } = render(
|
||||
<Spin spinning>
|
||||
<div>content</div>
|
||||
</Spin>,
|
||||
);
|
||||
const classes = antClasses(container);
|
||||
// antd 6 dropped the `.ant-spin-nested-loading` wrapper: `.ant-spin` is now the
|
||||
// outer element and `.ant-spin-dot` lives beneath it. Table/VirtualTable size the
|
||||
// dot via `.ant-spin .ant-spin-dot`, so that descendant chain must hold.
|
||||
expect(classes).toEqual(
|
||||
expect.arrayContaining(['ant-spin', 'ant-spin-container', 'ant-spin-dot']),
|
||||
);
|
||||
expect(classes).not.toContain('ant-spin-nested-loading');
|
||||
expect(
|
||||
container.querySelector('.ant-spin-dot')?.closest('.ant-spin'),
|
||||
).not.toBeNull();
|
||||
expect(antClasses(document.body)).toContain('ant-modal-body');
|
||||
});
|
||||
|
||||
test('Tag keeps its v5 trailing margin (GlobalStyles parity rule)', () => {
|
||||
|
||||
@@ -66,30 +66,3 @@ test('falls back to localeCompare when strings have no match relationship to sea
|
||||
expect(rankedSearchCompare('abc', 'def', 'xyz')).toBeLessThan(0);
|
||||
expect(rankedSearchCompare('def', 'abc', 'xyz')).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('ranks a case-insensitive substring match above a non-match', () => {
|
||||
// `zzABCzz` contains the search term ignoring case, `aaaa` does not match at
|
||||
// all, so the match must win even though localeCompare would order it last.
|
||||
expect(rankedSearchCompare('zzABCzz', 'aaaa', 'abc')).toBeLessThan(0);
|
||||
expect(rankedSearchCompare('aaaa', 'zzABCzz', 'abc')).toBeGreaterThan(0);
|
||||
expect(['aaaa', 'zzABCzz'].sort(searchSort('abc'))).toEqual([
|
||||
'zzABCzz',
|
||||
'aaaa',
|
||||
]);
|
||||
});
|
||||
|
||||
test('is antisymmetric so Array.prototype.sort stays well defined', () => {
|
||||
const pairs: [string, string, string][] = [
|
||||
['zzABCzz', 'aaaa', 'abc'],
|
||||
['Total Revenue', 'ARR', 'revenue'],
|
||||
['My Country', 'zzz', 'country'],
|
||||
['%f %B', '%F %b', '%F'],
|
||||
['her', 'Cher', 'Her'],
|
||||
];
|
||||
pairs.forEach(([a, b, search]) => {
|
||||
expect(
|
||||
Math.sign(rankedSearchCompare(a, b, search)) +
|
||||
Math.sign(rankedSearchCompare(b, a, search)),
|
||||
).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user