Compare commits

..

2 Commits

Author SHA1 Message Date
Kamil Gabryjelski
eb0359c401 fix(dev): use localhost in .agor.yml app_url template
Replace hardcoded private IP with localhost so the config works
for all contributors. Can be overridden per-worktree in the Agor UI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 00:36:04 +00:00
Kamil Gabryjelski
f6879240b3 fix(dev): allow external access to webpack dev server via env var
Add WEBPACK_ALLOW_ALL_HOSTS env var to opt into webpack-dev-server
allowedHosts: 'all', enabling access from external IPs (e.g. remote
dev environments). Default behavior is unchanged for local development.

Also adds .agor.yml environment template for docker-compose-light.yml
and passes the new env var in the light compose config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 00:31:44 +00:00
1649 changed files with 9439 additions and 54567 deletions

12
.agor.yml Normal file
View File

@@ -0,0 +1,12 @@
environment:
up_command: >-
NODE_PORT={{add 9100 worktree.unique_id}}
docker compose -p {{worktree.name}} -f docker-compose-light.yml up -d
down_command: >-
docker compose -p {{worktree.name}} -f docker-compose-light.yml down
nuke_command: >-
docker compose -p {{worktree.name}} -f docker-compose-light.yml down -v
health_check_url: 'http://localhost:{{add 9100 worktree.unique_id}}/health'
app_url: 'http://localhost:{{add 9100 worktree.unique_id}}'
logs_command: >-
docker compose -p {{worktree.name}} -f docker-compose-light.yml logs --tail=100 superset-node-light

View File

@@ -26,16 +26,16 @@ runs:
- name: Set up QEMU
if: ${{ inputs.build == 'true' }}
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
if: ${{ inputs.build == 'true' }}
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
uses: docker/setup-buildx-action@v3
- name: Try to login to DockerHub
if: ${{ inputs.login-to-dockerhub == 'true' }}
continue-on-error: true
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
uses: docker/login-action@v3
with:
username: ${{ inputs.dockerhub-user }}
password: ${{ inputs.dockerhub-token }}

View File

@@ -32,7 +32,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: true
ref: master
@@ -41,7 +41,7 @@ jobs:
uses: ./.github/actions/setup-supersetbot/
- name: Set up Python ${{ inputs.python-version }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
uses: actions/setup-python@v6
with:
python-version: "3.10"

View File

@@ -31,7 +31,7 @@ jobs:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
if: steps.check_queued.outputs.count >= 20
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: Cancel duplicate workflow runs
if: steps.check_queued.outputs.count >= 20

View File

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

View File

@@ -25,9 +25,9 @@ jobs:
pull-requests: write
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: Check and notify
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{ github.token }}
script: |

View File

@@ -44,7 +44,7 @@ jobs:
pull-requests: write
steps:
- name: Comment access denied
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
script: |
const message = `👋 Hi @${{ github.event.comment.user.login || github.event.review.user.login || github.event.issue.user.login }}!
@@ -71,12 +71,12 @@ jobs:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Run Claude PR Action
uses: anthropics/claude-code-action@28f83620103c48a57093dcc2837eec89e036bb9f # beta
uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
timeout_minutes: "60"

View File

@@ -31,7 +31,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: Check for file changes
id: check

View File

@@ -27,9 +27,9 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout Repository"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: "Dependency Review"
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
uses: actions/dependency-review-action@v4
continue-on-error: true
with:
fail-on-severity: critical
@@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: "Checkout Repository"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: Setup Python
uses: ./.github/actions/setup-backend/

View File

@@ -14,6 +14,7 @@ concurrency:
cancel-in-progress: true
jobs:
setup_matrix:
runs-on: ubuntu-24.04
outputs:
@@ -39,8 +40,9 @@ jobs:
IMAGE_TAG: apache/superset:GHA-${{ matrix.build_preset }}-${{ github.run_id }}
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
@@ -89,7 +91,7 @@ jobs:
# in the context of push (using multi-platform build), we need to pull the image locally
- name: Docker pull
if: github.event_name == 'push' && (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker)
run: docker pull $IMAGE_TAG
run: docker pull $IMAGE_TAG
- name: Print docker stats
if: steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker
@@ -99,6 +101,23 @@ jobs:
docker images $IMAGE_TAG
docker history $IMAGE_TAG
# Scan for vulnerabilities in built container image after pushes to mainline branch.
- name: Run Trivy container image vulnerabity scan
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker) && matrix.build_preset == 'lean'
uses: aquasecurity/trivy-action@97e0b3872f55f89b95b2f65b3dbab56962816478 # v0.34.2
with:
image-ref: ${{ env.IMAGE_TAG }}
format: 'sarif'
output: 'trivy-results.sarif'
vuln-type: 'os'
severity: 'CRITICAL,HIGH'
ignore-unfixed: true
- name: Upload Trivy scan results to GitHub Security tab
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker) && matrix.build_preset == 'lean'
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: 'trivy-results.sarif'
- name: docker-compose sanity check
if: (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker) && matrix.build_preset == 'dev'
shell: bash
@@ -115,7 +134,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Check for file changes

View File

@@ -28,8 +28,8 @@ jobs:
run:
working-directory: superset-embedded-sdk
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: './superset-embedded-sdk/.nvmrc'
registry-url: 'https://registry.npmjs.org'

View File

@@ -18,8 +18,8 @@ jobs:
run:
working-directory: superset-embedded-sdk
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: './superset-embedded-sdk/.nvmrc'
registry-url: 'https://registry.npmjs.org'

View File

@@ -69,7 +69,7 @@ jobs:
- name: Comment (success)
if: steps.describe-services.outputs.active == 'true'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{github.token}}
script: |

View File

@@ -63,7 +63,7 @@ jobs:
- name: Get event SHA
id: get-sha
if: steps.eval-label.outputs.result == 'up'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
@@ -94,7 +94,7 @@ jobs:
core.setOutput("sha", prSha);
- name: Looking for feature flags in PR description
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
id: eval-feature-flags
if: steps.eval-label.outputs.result == 'up'
with:
@@ -116,7 +116,7 @@ jobs:
return results;
- name: Reply with confirmation comment
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
if: steps.eval-label.outputs.result == 'up'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -160,7 +160,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ needs.ephemeral-env-label.outputs.sha }} : ${{steps.get-sha.outputs.sha}} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
ref: ${{ needs.ephemeral-env-label.outputs.sha }}
persist-credentials: false
@@ -189,7 +189,7 @@ jobs:
--extra-flags "--build-arg INCLUDE_CHROMIUM=false"
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
@@ -197,7 +197,7 @@ jobs:
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@c962da2960ed15f492addc26fffa274485265950 # v2
uses: aws-actions/amazon-ecr-login@v2
- name: Load, tag and push image to ECR
id: push-image
@@ -220,12 +220,12 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
@@ -233,7 +233,7 @@ jobs:
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@c962da2960ed15f492addc26fffa274485265950 # v2
uses: aws-actions/amazon-ecr-login@v2
- name: Check target image exists in ECR
id: check-image
@@ -248,7 +248,7 @@ jobs:
- name: Fail on missing container image
if: steps.check-image.outcome == 'failure'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{ github.token }}
script: |
@@ -263,7 +263,7 @@ jobs:
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@77954e213ba1f9f9cb016b86a1d4f6fcdea0d57e # v1
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: .github/workflows/ecs-task-definition.json
container-name: superset-ci
@@ -296,7 +296,7 @@ jobs:
--tags key=pr,value=$PR_NUMBER key=github_user,value=${{ github.actor }}
- name: Deploy Amazon ECS task definition
id: deploy-task
uses: aws-actions/amazon-ecs-deploy-task-definition@cbf54ec46642b86ff78c2f5793da6746954cf8ff # v2
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service
@@ -318,7 +318,7 @@ jobs:
echo "ip=$(aws ec2 describe-network-interfaces --network-interface-ids ${{ steps.get-eni.outputs.eni }} | jq -r '.NetworkInterfaces | first | .Association.PublicIp')" >> $GITHUB_OUTPUT
- name: Comment (success)
if: ${{ success() }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{github.token}}
script: |
@@ -331,7 +331,7 @@ jobs:
});
- name: Comment (failure)
if: ${{ failure() }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{github.token}}
script: |

View File

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

View File

@@ -14,10 +14,10 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version: '20'

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false

View File

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

View File

@@ -15,12 +15,12 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '11'

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Check for 'hold' label
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |

View File

@@ -16,7 +16,7 @@ jobs:
pull-requests: write
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive

View File

@@ -24,7 +24,7 @@ jobs:
python-version: ["current", "previous", "next"]
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -42,7 +42,7 @@ jobs:
echo "HOMEBREW_REPOSITORY=$HOMEBREW_REPOSITORY" >>"${GITHUB_ENV}"
brew install norwoodj/tap/helm-docs
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version: '20'
@@ -57,7 +57,7 @@ jobs:
yarn install --immutable
- name: Cache pre-commit environments
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache@v5
with:
path: ~/.cache/pre-commit
key: pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}

View File

@@ -26,7 +26,7 @@ jobs:
name: Bump version and publish package(s)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v6
with:
# pulls all commits (needed for lerna / semantic release to correctly version)
fetch-depth: 0
@@ -42,13 +42,13 @@ jobs:
- name: Install Node.js
if: env.HAS_TAGS
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: './superset-frontend/.nvmrc'
- name: Cache npm
if: env.HAS_TAGS
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache@v5
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
@@ -62,7 +62,7 @@ jobs:
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- name: Cache npm
if: env.HAS_TAGS
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
uses: actions/cache@v5
id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}

View File

@@ -37,7 +37,7 @@ jobs:
steps:
- name: Security Check - Authorize Maintainers Only
id: auth
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -147,7 +147,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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
ref: ${{ steps.check.outputs.target_sha }}
persist-credentials: false

View File

@@ -37,7 +37,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive

View File

@@ -38,21 +38,21 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.event.workflow_run.head_sha || github.sha }}"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: "./docs/.nvmrc"
node-version-file: './docs/.nvmrc'
- name: Setup Python
uses: ./.github/actions/setup-backend/
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
- uses: actions/setup-java@v5
with:
distribution: "zulu"
java-version: "21"
distribution: 'zulu'
java-version: '21'
- name: Install Graphviz
run: sudo apt-get install -y graphviz
- name: Compute Entity Relationship diagram (ERD)
@@ -68,7 +68,7 @@ jobs:
yarn install --check-cache
- name: Download database diagnostics (if triggered by integration tests)
if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success'
uses: dawidd6/action-download-artifact@8a338493df3d275e4a7a63bcff3b8fe97e51a927 # v19
uses: dawidd6/action-download-artifact@v16
continue-on-error: true
with:
workflow: superset-python-integrationtest.yml
@@ -77,7 +77,7 @@ jobs:
path: docs/src/data/
- name: Try to download latest diagnostics (for push/dispatch triggers)
if: github.event_name != 'workflow_run'
uses: dawidd6/action-download-artifact@8a338493df3d275e4a7a63bcff3b8fe97e51a927 # v19
uses: dawidd6/action-download-artifact@v16
continue-on-error: true
with:
workflow: superset-python-integrationtest.yml

View File

@@ -24,10 +24,10 @@ jobs:
name: Link Checking
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@v6
# Do not bump this linkinator-action version without opening
# an ASF Infra ticket to allow the new version first!
- uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
- uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
continue-on-error: true # This will make the job advisory (non-blocking, no red X)
with:
paths: "**/*.md, **/*.mdx"
@@ -67,14 +67,14 @@ jobs:
working-directory: docs
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: "./docs/.nvmrc"
node-version-file: './docs/.nvmrc'
- name: yarn install
run: |
yarn install --check-cache
@@ -98,26 +98,25 @@ jobs:
working-directory: docs
steps:
- name: "Checkout PR head: ${{ github.event.workflow_run.head_sha }}"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
ref: ${{ github.event.workflow_run.head_sha }}
persist-credentials: false
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: "./docs/.nvmrc"
node-version-file: './docs/.nvmrc'
- name: yarn install
run: |
yarn install --check-cache
- name: Download database diagnostics from integration tests
uses: dawidd6/action-download-artifact@8a338493df3d275e4a7a63bcff3b8fe97e51a927 # v19
uses: dawidd6/action-download-artifact@v16
with:
workflow: superset-python-integrationtest.yml
run_id: ${{ github.event.workflow_run.id }}
name: database-diagnostics
path: docs/src/data/
if_no_artifact_found: "warning"
- name: Use fresh diagnostics
run: |
if [ -f "src/data/databases-diagnostics.json" ]; then

View File

@@ -69,21 +69,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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
@@ -109,7 +109,7 @@ jobs:
run: testdata
- name: Setup Node.js
if: steps.check.outputs.python || steps.check.outputs.frontend
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: './superset-frontend/.nvmrc'
- name: Install npm dependencies
@@ -146,7 +146,7 @@ jobs:
SAFE_APP_ROOT=${APP_ROOT//\//_}
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
- name: Upload Artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
if: failure()
with:
path: ${{ github.workspace }}/superset-frontend/cypress-base/cypress/screenshots
@@ -186,21 +186,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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
@@ -226,7 +226,7 @@ jobs:
run: playwright_testdata
- name: Setup Node.js
if: steps.check.outputs.python || steps.check.outputs.frontend
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: './superset-frontend/.nvmrc'
- name: Install npm dependencies
@@ -259,7 +259,7 @@ jobs:
SAFE_APP_ROOT=${APP_ROOT//\//_}
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
- name: Upload Playwright Artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
if: failure()
with:
path: |

View File

@@ -24,7 +24,7 @@ jobs:
working-directory: superset-extensions-cli
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -49,7 +49,7 @@ jobs:
- name: Upload coverage reports to Codecov
if: steps.check.outputs.superset-extensions-cli
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
file: ./coverage.xml
flags: superset-extensions-cli
@@ -58,7 +58,7 @@ jobs:
- name: Upload HTML coverage report
if: steps.check.outputs.superset-extensions-cli
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
with:
name: superset-extensions-cli-coverage-html
path: htmlcov/

View File

@@ -23,7 +23,7 @@ jobs:
should-run: ${{ steps.check.outputs.frontend }}
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 0
@@ -58,7 +58,7 @@ jobs:
- name: Upload Docker Image Artifact
if: steps.check.outputs.frontend
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
with:
name: docker-image
path: docker-image.tar.gz
@@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
uses: actions/download-artifact@v8
with:
name: docker-image
@@ -90,7 +90,7 @@ jobs:
"npm run test -- --coverage --shard=${{ matrix.shard }}/8 --coverageReporters=json"
- name: Upload Coverage Artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
with:
name: coverage-artifacts-${{ matrix.shard }}
path: superset-frontend/coverage
@@ -103,14 +103,14 @@ jobs:
id-token: write
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 0
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Download Coverage Artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
uses: actions/download-artifact@v8
with:
pattern: coverage-artifacts-*
path: coverage/
@@ -127,7 +127,7 @@ jobs:
run: npx nyc merge coverage/ merged-output/coverage-summary.json
- name: Upload Code Coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: javascript
use_oidc: true
@@ -142,7 +142,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
uses: actions/download-artifact@v8
with:
name: docker-image
@@ -166,7 +166,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
uses: actions/download-artifact@v8
with:
name: docker-image
@@ -184,7 +184,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
uses: actions/download-artifact@v8
with:
name: docker-image

View File

@@ -16,14 +16,14 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
uses: azure/setup-helm@v4
with:
version: v3.16.4

View File

@@ -29,7 +29,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref_name }}
persist-credentials: true
@@ -42,7 +42,7 @@ jobs:
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
uses: azure/setup-helm@v4
with:
version: v3.5.4
@@ -101,7 +101,7 @@ jobs:
CR_RELEASE_NAME_TEMPLATE: "superset-helm-chart-{{ .Version }}"
- name: Open Pull Request
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
script: |
const branchName = '${{ env.branch_name }}';

View File

@@ -60,21 +60,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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
@@ -100,7 +100,7 @@ jobs:
run: playwright_testdata
- name: Setup Node.js
if: steps.check.outputs.python || steps.check.outputs.frontend
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: './superset-frontend/.nvmrc'
- name: Install npm dependencies
@@ -133,7 +133,7 @@ jobs:
SAFE_APP_ROOT=${APP_ROOT//\//_}
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
- name: Upload Playwright Artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
if: failure()
with:
path: |

View File

@@ -41,7 +41,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -68,7 +68,7 @@ jobs:
run: |
./scripts/python_tests.sh
- name: Upload code coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: python,mysql
token: ${{ secrets.CODECOV_TOKEN }}
@@ -98,7 +98,7 @@ jobs:
"
- name: Upload database diagnostics artifact
if: steps.check.outputs.python
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
uses: actions/upload-artifact@v7
with:
name: database-diagnostics
path: databases-diagnostics.json
@@ -129,7 +129,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -159,7 +159,7 @@ jobs:
run: |
./scripts/python_tests.sh
- name: Upload code coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: python,postgres
token: ${{ secrets.CODECOV_TOKEN }}
@@ -182,7 +182,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -211,7 +211,7 @@ jobs:
run: |
./scripts/python_tests.sh
- name: Upload code coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: python,sqlite
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -48,7 +48,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -77,7 +77,7 @@ jobs:
run: |
./scripts/python_tests.sh -m 'chart_data_flow or sql_json_flow'
- name: Upload code coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: python,presto
token: ${{ secrets.CODECOV_TOKEN }}
@@ -108,7 +108,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -145,7 +145,7 @@ jobs:
pip install -e .[hive]
./scripts/python_tests.sh -m 'chart_data_flow or sql_json_flow'
- name: Upload code coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: python,hive
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -24,7 +24,7 @@ jobs:
PYTHONPATH: ${{ github.workspace }}
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -53,7 +53,7 @@ jobs:
run: |
pytest --durations-min=0.5 --cov=superset/sql/ ./tests/unit_tests/sql/ --cache-clear --cov-fail-under=100
- name: Upload code coverage
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
uses: codecov/codecov-action@v5
with:
flags: python,unit
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -31,7 +31,7 @@ jobs:
- name: Setup Node.js
if: steps.check.outputs.frontend
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: './superset-frontend/.nvmrc'
- name: Install dependencies
@@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
@@ -62,10 +62,6 @@ jobs:
- name: Setup Python
if: steps.check.outputs.python
uses: ./.github/actions/setup-backend/
- name: Install msgcat
run: sudo apt update && sudo apt install gettext
- name: Test babel extraction
if: steps.check.outputs.python
run: ./scripts/translations/babel_update.sh

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Install dependencies

View File

@@ -26,7 +26,7 @@ jobs:
steps:
- name: Quickly add thumbs up!
if: github.event_name == 'issue_comment' && contains(github.event.comment.body, '@supersetbot')
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@v8
with:
script: |
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/')
@@ -38,7 +38,7 @@ jobs:
});
- name: "Checkout ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
persist-credentials: false

View File

@@ -47,7 +47,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -60,7 +60,7 @@ jobs:
build: "true"
- name: Use Node.js 20
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version: 20
@@ -107,12 +107,12 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Use Node.js 20
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version: 20

View File

@@ -30,10 +30,10 @@ jobs:
name: Generate Reports
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@v6
with:
node-version-file: './superset-frontend/.nvmrc'

1
.gitignore vendored
View File

@@ -133,7 +133,6 @@ CLAUDE.local.md
PROJECT.md
.aider*
.claude_rc*
.claude/settings.local.json
.env.local
oxc-custom-build/
*.code-workspace

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -625,9 +625,6 @@ categories:
- name: Stockarea
url: https://stockarea.io
- name: VTG
url: https://www.vtg.de
Sports:
- name: Club 25 de Agosto (Femenino / Women's Team)
url: https://www.instagram.com/25deagosto.basketfemenino/

View File

@@ -24,12 +24,46 @@ assists people when migrating to a new version.
## Next
## 6.1.0
### ClickHouse minimum driver version bump
The minimum required version of `clickhouse-connect` has been raised to `>=0.13.0`. If you are using the ClickHouse connector, please upgrade your `clickhouse-connect` package. The `_mutate_label` workaround that appended hash suffixes to column aliases has also been removed, as it is no longer needed with modern versions of the driver.
### MCP Tool Observability
MCP (Model Context Protocol) tools now include enhanced observability instrumentation for monitoring and debugging:
**Two-layer instrumentation:**
1. **Middleware layer** (`LoggingMiddleware`): Automatically logs all MCP tool calls with `duration_ms` and `success` status in the audit log (Action Log UI, logs table)
2. **Sub-operation tracking**: All 19 MCP tools include granular `event_logger.log_context()` blocks for tracking individual operations like validation, database writes, and query execution
**Action naming convention:**
- Tool-level logs: `mcp_tool_call` (via middleware)
- Sub-operation logs: `mcp.{tool_name}.{operation}` (e.g., `mcp.generate_chart.validation`, `mcp.execute_sql.query_execution`)
**Querying MCP logs:**
```sql
-- Top slowest MCP operations
SELECT action, COUNT(*) as calls, AVG(duration_ms) as avg_ms
FROM logs
WHERE action LIKE 'mcp.%'
GROUP BY action
ORDER BY avg_ms DESC
LIMIT 20;
-- MCP tool success rate
SELECT
json_extract(curated_payload, '$.tool') as tool,
COUNT(*) as total_calls,
SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) as successful,
ROUND(100.0 * SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) / COUNT(*), 2) as success_rate
FROM logs
WHERE action = 'mcp_tool_call'
GROUP BY tool
ORDER BY total_calls DESC;
```
**Security note:** Sensitive parameters (passwords, API keys, tokens) are automatically redacted in logs as `[REDACTED]`.
### Distributed Coordination Backend
A new `DISTRIBUTED_COORDINATION_CONFIG` configuration provides a unified Redis-based backend for real-time coordination features in Superset. This backend enables:
@@ -41,7 +75,6 @@ A new `DISTRIBUTED_COORDINATION_CONFIG` configuration provides a unified Redis-b
The distributed coordination is used by the Global Task Framework (GTF) for abort notifications and task completion signaling, and will eventually replace `GLOBAL_ASYNC_QUERIES_CACHE_BACKEND` as the standard signaling backend. Configuring this is recommended for Redis enabled production deployments.
Example configuration in `superset_config.py`:
```python
DISTRIBUTED_COORDINATION_CONFIG = {
"CACHE_TYPE": "RedisCache",
@@ -56,11 +89,9 @@ See `superset/config.py` for complete configuration options.
### WebSocket config for GAQ with Docker
[35896](https://github.com/apache/superset/pull/35896) and [37624](https://github.com/apache/superset/pull/37624) updated documentation on how to run and configure Superset with Docker. Specifically for the WebSocket configuration, a new `docker/superset-websocket/config.example.json` was added to the repo, so that users could copy it to create a `docker/superset-websocket/config.json` file. The existing `docker/superset-websocket/config.json` was removed and git-ignored, so if you're using GAQ / WebSocket make sure to:
- Stash/backup your existing `config.json` file, to re-apply it after (will get git-ignored going forward)
- Update the `volumes` configuration for the `superset-websocket` service in your `docker-compose.override.yml` file, to include the `docker/superset-websocket/config.json` file. For example:
```yaml
``` yaml
services:
superset-websocket:
volumes:
@@ -73,9 +104,7 @@ services:
### Example Data Loading Improvements
#### New Directory Structure
Examples are now organized by name with data and configs co-located:
```
superset/examples/
├── _shared/ # Shared database & metadata configs
@@ -87,12 +116,31 @@ superset/examples/
└── ...
```
#### Simplified Parquet-based Loading
- Auto-discovery: create `superset/examples/my_dataset/data.parquet` to add a new example
- Parquet is an Apache project format: compressed (~27% smaller), self-describing schema
- YAML configs define datasets, charts, and dashboards declaratively
- Removed Python-based data generation from individual example files
#### Test Data Reorganization
- Moved `big_data.py` to `superset/cli/test_loaders.py` - better reflects its purpose as a test utility
- Fixed inverted logic for `--load-test-data` flag (now correctly includes .test.yaml files when flag is set)
- Clarified CLI flags:
- `--force` / `-f`: Force reload even if tables exist
- `--only-metadata` / `-m`: Create table metadata without loading data
- `--load-test-data` / `-t`: Include test dashboards and .test.yaml configs
- `--load-big-data` / `-b`: Generate synthetic stress-test data
#### Bug Fixes
- Fixed numpy array serialization for PostgreSQL (converts complex types to JSON strings)
- Fixed KeyError for `allow_csv_upload` field in database configs (now optional with default)
- Fixed test data loading logic that was incorrectly filtering files
### MCP Service
The MCP (Model Context Protocol) service enables AI assistants and automation tools to interact programmatically with Superset.
#### New Features
- MCP service infrastructure with FastMCP framework
- Tools for dashboards, charts, datasets, SQL Lab, and instance metadata
- Optional dependency: install with `pip install apache-superset[fastmcp]`
@@ -102,7 +150,6 @@ The MCP (Model Context Protocol) service enables AI assistants and automation to
#### New Configuration Options
**Development** (single-user, local testing):
```python
# superset_config.py
MCP_DEV_USERNAME = "admin" # User for MCP authentication
@@ -111,7 +158,6 @@ MCP_SERVICE_PORT = 5008
```
**Production** (JWT-based, multi-user):
```python
# superset_config.py
MCP_AUTH_ENABLED = True
@@ -157,14 +203,12 @@ superset mcp run --port 5008 --use-factory-config
The MCP service runs as a **separate process** from the Superset web server.
**Important**:
- Requires same Python environment and configuration as Superset
- Shares database connections with main Superset app
- Can be scaled independently from web server
- Requires `fastmcp` package (optional dependency)
**Installation**:
```bash
# Install with MCP support
pip install apache-superset[fastmcp]
@@ -178,7 +222,6 @@ Use systemd, supervisord, or Kubernetes to manage the MCP service process.
See `superset/mcp_service/PRODUCTION.md` for deployment guides.
**Security**:
- Development: Uses `MCP_DEV_USERNAME` for single-user access
- Production: **MUST** configure JWT authentication
- See `superset/mcp_service/SECURITY.md` for details
@@ -191,50 +234,14 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
- Developer Guide: `superset/mcp_service/CLAUDE.md`
- Quick Start: `superset/mcp_service/README.md`
### MCP Tool Observability
---
MCP (Model Context Protocol) tools now include enhanced observability instrumentation for monitoring and debugging:
**Two-layer instrumentation:**
1. **Middleware layer** (`LoggingMiddleware`): Automatically logs all MCP tool calls with `duration_ms` and `success` status in the audit log (Action Log UI, logs table)
2. **Sub-operation tracking**: All 19 MCP tools include granular `event_logger.log_context()` blocks for tracking individual operations like validation, database writes, and query execution
**Action naming convention:**
- Tool-level logs: `mcp_tool_call` (via middleware)
- Sub-operation logs: `mcp.{tool_name}.{operation}` (e.g., `mcp.generate_chart.validation`, `mcp.execute_sql.query_execution`)
**Querying MCP logs:**
```sql
-- Top slowest MCP operations
SELECT action, COUNT(*) as calls, AVG(duration_ms) as avg_ms
FROM logs
WHERE action LIKE 'mcp.%'
GROUP BY action
ORDER BY avg_ms DESC
LIMIT 20;
-- MCP tool success rate
SELECT
json_extract(curated_payload, '$.tool') as tool,
COUNT(*) as total_calls,
SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) as successful,
ROUND(100.0 * SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) / COUNT(*), 2) as success_rate
FROM logs
WHERE action = 'mcp_tool_call'
GROUP BY tool
ORDER BY total_calls DESC;
```
**Security note:** Sensitive parameters (passwords, API keys, tokens) are automatically redacted in logs as `[REDACTED]`.
### APP_NAME configuration
- [35621](https://github.com/apache/superset/pull/35621): The default hash algorithm has changed from MD5 to SHA-256 for improved security and FedRAMP compliance. This affects cache keys for thumbnails, dashboard digests, chart digests, and filter option names. Existing cached data will be invalidated upon upgrade. To opt out of this change and maintain backward compatibility, set `HASH_ALGORITHM = "md5"` in your `superset_config.py`.
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
### Breaking Changes
- [37370](https://github.com/apache/superset/pull/37370): The `APP_NAME` configuration variable no longer controls the browser window/tab title or other frontend branding. Application names should now be configured using the theme system with the `brandAppName` token. The `APP_NAME` config is still used for backend contexts (MCP service, logs, etc.) and serves as a fallback if `brandAppName` is not set.
- **Migration:**
```python
# Before (Superset 5.x)
APP_NAME = "My Custom App"
@@ -253,22 +260,16 @@ ORDER BY total_calls DESC;
APP_NAME = "My Custom App"
# But you should migrate to THEME_DEFAULT.token.brandAppName
```
- **Note:** For dark mode, set the same tokens in `THEME_DARK` configuration.
### CUSTOM_FONT_URLS configuration
- [36317](https://github.com/apache/superset/pull/36317): The `CUSTOM_FONT_URLS` configuration option has been removed. Use the new per-theme `fontUrls` token in `THEME_DEFAULT` or database-managed themes instead.
- **Before:**
```python
CUSTOM_FONT_URLS = [
"https://fonts.example.com/myfont.css",
]
```
- **After:**
```python
THEME_DEFAULT = {
"token": {
@@ -280,13 +281,7 @@ ORDER BY total_calls DESC;
}
```
### Other
- [35621](https://github.com/apache/superset/pull/35621): The default hash algorithm has changed from MD5 to SHA-256 for improved security and FedRAMP compliance. This affects cache keys for thumbnails, dashboard digests, chart digests, and filter option names. Existing cached data will be invalidated upon upgrade. To opt out of this change and maintain backward compatibility, set `HASH_ALGORITHM = "md5"` in your `superset_config.py`.
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
## 6.0.0
- [33055](https://github.com/apache/superset/pull/33055): Upgrades Flask-AppBuilder to 5.0.0. The AUTH_OID authentication type has been deprecated and is no longer available as an option in Flask-AppBuilder. OpenID (OID) is considered a deprecated authentication protocol - if you are using AUTH_OID, you will need to migrate to an alternative authentication method such as OAuth, LDAP, or database authentication before upgrading.
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
- [34782](https://github.com/apache/superset/pull/34782): Dataset exports now include the dataset ID in their file name (similar to charts and dashboards). If managing assets as code, make sure to rename existing dataset YAMLs to include the ID (and avoid duplicated files).
@@ -295,8 +290,8 @@ ORDER BY total_calls DESC;
- Change any hex color values to one of: `"success"`, `"processing"`, `"error"`, `"warning"`, `"default"`
- Custom colors are no longer supported to maintain consistency with Ant Design components
- [34561](https://github.com/apache/superset/pull/34561) Added tiled screenshot functionality for Playwright-based reports to handle large dashboards more efficiently. When enabled (default: `SCREENSHOT_TILED_ENABLED = True`), dashboards with 20+ charts or height exceeding 5000px will be captured using multiple viewport-sized tiles and combined into a single image. This improves report generation performance and reliability for large dashboards.
Note: Pillow is now a required dependency (previously optional) to support image processing for tiled screenshots.
`thumbnails` optional dependency is now deprecated and will be removed in the next major release (7.0).
Note: Pillow is now a required dependency (previously optional) to support image processing for tiled screenshots.
`thumbnails` optional dependency is now deprecated and will be removed in the next major release (7.0).
- [33084](https://github.com/apache/superset/pull/33084) The DISALLOWED_SQL_FUNCTIONS configuration now includes additional potentially sensitive database functions across PostgreSQL, MySQL, SQLite, MS SQL Server, and ClickHouse. Existing queries using these functions may now be blocked. Review your SQL Lab queries and dashboards if you encounter "disallowed function" errors after upgrading
- [34235](https://github.com/apache/superset/pull/34235) CSV exports now use `utf-8-sig` encoding by default to include a UTF-8 BOM, improving compatibility with Excel.
- [34258](https://github.com/apache/superset/pull/34258) changing the default in Dockerfile to INCLUDE_CHROMIUM="false" (from "true") in the past. This ensures the `lean` layer is lean by default, and people can opt-in to the `chromium` layer by setting the build arg `INCLUDE_CHROMIUM=true`. This is a breaking change for anyone using the `lean` layer, as it will no longer include Chromium by default.
@@ -686,6 +681,7 @@ ORDER BY total_calls DESC;
- [11509](https://github.com/apache/superset/pull/12491): Dataset metadata updates check user ownership, only owners or an Admin are allowed.
- Security simplification (SIP-19), the following permission domains were simplified:
- [12072](https://github.com/apache/superset/pull/12072): `Query` with `can_read`, `can_write`
- [12036](https://github.com/apache/superset/pull/12036): `Database` with `can_read`, `can_write`.
- [12012](https://github.com/apache/superset/pull/12036): `Dashboard` with `can_read`, `can_write`.

View File

@@ -162,6 +162,7 @@ services:
# Webpack dev server must bind to 0.0.0.0 to be accessible from outside the container
WEBPACK_DEVSERVER_HOST: "${WEBPACK_DEVSERVER_HOST:-0.0.0.0}"
WEBPACK_DEVSERVER_PORT: "${WEBPACK_DEVSERVER_PORT:-9000}"
WEBPACK_ALLOW_ALL_HOSTS: "${WEBPACK_ALLOW_ALL_HOSTS:-true}"
ports:
- "${NODE_PORT:-9001}:9000" # Parameterized port, accessible on all interfaces
command: ["/app/docker/docker-frontend.sh"]

View File

@@ -1 +1 @@
v22.22.0
v20.20.0

View File

@@ -233,20 +233,6 @@ def alert_dynamic_minimal_interval(**kwargs) -> int:
ALERT_MINIMUM_INTERVAL = alert_dynamic_minimal_interval
```
## External Link Redirection
For security, Superset rewrites external links in alert/report email HTML so
they go through a warning page before the user is navigated to the external
site. Internal links (matching your configured base URL) are not affected.
```python
# Disable external link redirection entirely (default: True)
ALERT_REPORTS_ENABLE_LINK_REDIRECT = False
```
The feature uses `WEBDRIVER_BASEURL_USER_FRIENDLY` (or `WEBDRIVER_BASEURL`)
to determine which hosts are internal.
## Troubleshooting
There are many reasons that reports might not be working. Try these steps to check for specific issues.

View File

@@ -1,162 +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.
*/}
---
title: AWS IAM Authentication
sidebar_label: AWS IAM Authentication
sidebar_position: 15
---
# AWS IAM Authentication for AWS Databases
Superset supports IAM-based authentication for **Amazon Aurora** (PostgreSQL and MySQL) and **Amazon Redshift**. IAM auth eliminates the need for database passwords — Superset generates a short-lived auth token using temporary AWS credentials instead.
Cross-account IAM role assumption via STS `AssumeRole` is supported, allowing a Superset deployment in one AWS account to connect to databases in a different account.
## Prerequisites
- Enable the `AWS_DATABASE_IAM_AUTH` feature flag in `superset_config.py`. IAM authentication is gated behind this flag; if it is disabled, connections using `aws_iam` fail with *"AWS IAM database authentication is not enabled."*
```python
FEATURE_FLAGS = {
"AWS_DATABASE_IAM_AUTH": True,
}
```
- `boto3` must be installed in your Superset environment:
```bash
pip install boto3
```
- The Superset server's IAM role (or static credentials) must have permission to call `sts:AssumeRole` (for cross-account) or the same-account permissions for the target service:
- **Aurora (RDS)**: `rds-db:connect`
- **Redshift provisioned**: `redshift:GetClusterCredentials`
- **Redshift Serverless**: `redshift-serverless:GetCredentials` and `redshift-serverless:GetWorkgroup`
- SSL must be enabled on the Aurora / Redshift endpoint (required for IAM token auth).
## Configuration
IAM authentication is configured via the **encrypted_extra** field of the database connection. Access this field in the **Advanced** → **Security** section of the database connection form, under **Secure Extra**.
### Aurora PostgreSQL or Aurora MySQL
```json
{
"aws_iam": {
"enabled": true,
"role_arn": "arn:aws:iam::222222222222:role/SupersetDatabaseAccess",
"external_id": "superset-prod-12345",
"region": "us-east-1",
"db_username": "superset_iam_user",
"session_duration": 3600
}
}
```
| Field | Required | Description |
|-------|----------|-------------|
| `enabled` | Yes | Set to `true` to activate IAM auth |
| `role_arn` | No | ARN of the cross-account IAM role to assume via STS. Omit for same-account auth |
| `external_id` | No | External ID for the STS `AssumeRole` call, if required by the target role's trust policy |
| `region` | Yes | AWS region of the database cluster |
| `db_username` | Yes | The database username associated with the IAM identity |
| `session_duration` | No | STS session duration in seconds (default: `3600`) |
### Redshift (Serverless)
```json
{
"aws_iam": {
"enabled": true,
"role_arn": "arn:aws:iam::222222222222:role/SupersetRedshiftAccess",
"region": "us-east-1",
"workgroup_name": "my-workgroup",
"db_name": "dev"
}
}
```
### Redshift (Provisioned Cluster)
```json
{
"aws_iam": {
"enabled": true,
"role_arn": "arn:aws:iam::222222222222:role/SupersetRedshiftAccess",
"region": "us-east-1",
"cluster_identifier": "my-cluster",
"db_username": "superset_iam_user",
"db_name": "dev"
}
}
```
## Cross-Account IAM Setup
To connect to a database in Account B from a Superset deployment in Account A:
**1. In Account B — create a database-access role:**
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["rds-db:connect"],
"Resource": "arn:aws:rds-db:us-east-1:222222222222:dbuser/db-XXXXXXXXXXXX/superset_iam_user"
}
]
}
```
**Trust policy** (allows Account A's Superset role to assume it):
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/SupersetInstanceRole"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "superset-prod-12345"
}
}
}
]
}
```
**2. In Account A — grant Superset's role permission to assume the Account B role:**
```json
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::222222222222:role/SupersetDatabaseAccess"
}
```
**3. Configure the database connection in Superset** using the `role_arn` and `external_id` from the trust policy (as shown in the configuration example above).
## Credential Caching
STS credentials are cached in memory keyed by `(role_arn, region, external_id)` with a 10-minute TTL. This reduces the number of STS API calls when multiple queries are executed with the same connection. Tokens are refreshed automatically before expiry.

View File

@@ -138,33 +138,14 @@ THUMBNAIL_CACHE_CONFIG = init_thumbnail_cache
```
Using the above example cache keys for dashboards will be `superset_thumb__dashboard__{ID}`. You can
override the base URL for Selenium using:
override the base URL for selenium using:
```
WEBDRIVER_BASEURL = "https://superset.company.com"
```
To control which user account is used for rendering thumbnails and warming up caches, configure
`THUMBNAIL_EXECUTORS` and `CACHE_WARMUP_EXECUTORS`. Each accepts a list of executor types (which
resolve to an owner, creator, modifier, or the currently-logged-in user) and/or a `FixedExecutor`
pinned to a specific username. By default, thumbnails render as the current user
(`ExecutorType.CURRENT_USER`) and cache warmup runs as the chart/dashboard owner
(`ExecutorType.OWNER`).
To force both to run as a dedicated service account (`admin` in this example):
```python
from superset.tasks.types import ExecutorType, FixedExecutor
THUMBNAIL_EXECUTORS = [FixedExecutor("admin")]
CACHE_WARMUP_EXECUTORS = [FixedExecutor("admin")]
```
Use a dedicated read-only service account here rather than a personal admin account, so that
thumbnail rendering and cache warmup tasks don't fail if a specific user's credentials change.
Additional Selenium WebDriver configuration can be set using `WEBDRIVER_CONFIGURATION`. You can
implement a custom function to authenticate Selenium. The default function uses the `flask-login`
Additional selenium web drive configuration can be set using `WEBDRIVER_CONFIGURATION`. You can
implement a custom function to authenticate selenium. The default function uses the `flask-login`
session cookie. Here's an example of a custom function signature:
```python
@@ -178,20 +159,6 @@ Then on configuration:
WEBDRIVER_AUTH_FUNC = auth_driver
```
## ETag Support for Thumbnails
Thumbnail and screenshot endpoints return `ETag` response headers based on the cached content digest. Clients can use conditional requests to avoid downloading unchanged images:
```
GET /api/v1/chart/42/thumbnail/
If-None-Match: "abc123..."
→ 304 Not Modified (if unchanged)
→ 200 OK (with new image if changed)
```
This is particularly useful for embedded dashboards and external integrations that periodically poll for updated screenshots — unchanged thumbnails return immediately with no payload.
## Distributed Coordination Backend
Superset supports an optional distributed coordination (`DISTRIBUTED_COORDINATION_CONFIG`) for

View File

@@ -364,26 +364,6 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
]
```
### PKCE Support
For public OAuth2 clients that cannot securely store a client secret, enable Proof Key for Code Exchange (PKCE) by adding `code_challenge_method` to the `remote_app` configuration:
```python
OAUTH_PROVIDERS = [
{
'name': 'myProvider',
'remote_app': {
'client_id': 'myClientId',
'client_secret': 'mySecret', # may be empty for pure public clients
'code_challenge_method': 'S256', # enables PKCE
'server_metadata_url': 'https://myAuthorizationServer/.well-known/openid-configuration'
}
}
]
```
PKCE (`S256`) is recommended for all OAuth2 flows, even when a client secret is present, as it protects against authorization code interception attacks.
## LDAP Authentication
FAB supports authenticating user credentials against an LDAP server.

View File

@@ -10,10 +10,6 @@ version: 1
The superset cli allows you to import and export datasources from and to YAML. Datasources include
databases. The data is expected to be organized in the following hierarchy:
:::info
Superset's ZIP-based import/export also covers **dashboards**, **charts**, and **saved queries**, exercised through the UI and REST API. The [Dashboard Import Overwrite Behavior](#dashboard-import-overwrite-behavior) and [UUIDs in API Responses](#uuids-in-api-responses) sections below document the behavior shared across all asset types.
:::
```text
├──databases
| ├──database_1
@@ -30,10 +26,6 @@ Superset's ZIP-based import/export also covers **dashboards**, **charts**, and *
| └── ... (more databases)
```
:::note
When you export a database connection, the `masked_encrypted_extra` field (used for sensitive connection parameters such as service account JSON, OAuth tokens, and other encrypted credentials) is included in the export. When importing on another instance, these values are decrypted and re-encrypted using the destination instance's `SECRET_KEY`. Ensure the receiving instance has a valid `SECRET_KEY` configured before importing.
:::
## Exporting Datasources to YAML
You can print your current datasources to stdout by running:
@@ -83,29 +75,6 @@ The optional username flag **-u** sets the user used for the datasource import.
superset import_datasources -p <path / filename> -u 'admin'
```
## Dashboard Import Overwrite Behavior
When importing a dashboard ZIP with the **overwrite** option enabled, any existing charts that are part of the dashboard are **replaced** rather than duplicated. This applies to:
- Charts whose UUID matches a chart already present in the target instance
- The full chart configuration (query, visualization type, columns, metrics) is replaced by the imported version
If you import without the overwrite flag, existing charts with conflicting UUIDs are left unchanged and the import skips those objects. Use overwrite when you want to push a fully updated dashboard (including chart definitions) from a development or staging environment to production.
## UUIDs in API Responses
The REST API POST endpoints for **datasets**, **charts**, and **dashboards** include the auto-generated `uuid` field in the response body:
```json
{
"id": 42,
"uuid": "b8a8d5c3-1234-4abc-8def-0123456789ab",
...
}
```
UUIDs remain stable across import/export cycles and can be used for cross-environment workflows — for example, recording a UUID when creating a chart in development and using it to identify the matching chart after importing into production.
## Legacy Importing Datasources
### From older versions of Superset to current version

View File

@@ -1,845 +0,0 @@
---
title: MCP Server Deployment & Authentication
hide_title: true
sidebar_position: 14
version: 1
---
<!--
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.
-->
# MCP Server Deployment & Authentication
Superset includes a built-in [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that lets AI assistants -- Claude, ChatGPT, and other MCP-compatible clients -- interact with your Superset instance. Through MCP, clients can list dashboards, query datasets, execute SQL, create charts, and more.
This guide covers how to run, secure, and deploy the MCP server.
:::tip Looking for user docs?
See **[Using AI with Superset](/user-docs/using-superset/using-ai-with-superset)** for a guide on what AI can do with Superset and how to connect your AI client.
:::
```mermaid
flowchart LR
A["AI Client<br/>(Claude, ChatGPT, etc.)"] -- "MCP protocol<br/>(HTTP + JSON-RPC)" --> B["MCP Server<br/>(:5008/mcp)"]
B -- "Superset context<br/>(app, db, RBAC)" --> C["Superset<br/>(:8088)"]
C --> D[("Database<br/>(Postgres)")]
```
---
## Quick Start
Get the MCP server running locally and connect an AI client in three steps.
### 1. Start the MCP server
The MCP server runs as a separate process alongside Superset:
```bash
superset mcp run --host 127.0.0.1 --port 5008
```
| Flag | Default | Description |
|------|---------|-------------|
| `--host` | `127.0.0.1` | Host to bind to |
| `--port` | `5008` | Port to bind to |
| `--debug` | off | Enable debug logging |
The endpoint is available at `http://<host>:<port>/mcp`.
### 2. Set a development user
For local development, tell the MCP server which Superset user to impersonate (the user must already exist in your database):
```python
# superset_config.py
MCP_DEV_USERNAME = "admin"
```
### 3. Connect an AI client
Point your MCP client at the server. For **Claude Desktop**, edit the config file:
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"superset": {
"url": "http://localhost:5008/mcp"
}
}
}
```
Restart Claude Desktop. The hammer icon in the chat bar confirms the connection.
See [Connecting AI Clients](#connecting-ai-clients) for Claude Code, Claude Web, ChatGPT, and raw HTTP examples.
---
## Prerequisites
- Apache Superset 5.0+ running and accessible
- Python 3.10+
- The `fastmcp` package (`pip install fastmcp`)
---
## Authentication
The MCP server supports multiple authentication methods depending on your deployment scenario.
```mermaid
flowchart TD
R["Incoming MCP Request"] --> F{"MCP_AUTH_FACTORY<br/>set?"}
F -- Yes --> CF["Custom Auth Provider"]
F -- No --> AE{"MCP_AUTH_ENABLED?"}
AE -- "True" --> JWT["JWT Validation"]
AE -- "False" --> DU["Dev Mode<br/>(MCP_DEV_USERNAME)"]
JWT --> ALG{"MCP_JWT_ALGORITHM"}
ALG -- "RS256 + JWKS" --> JWKS["Fetch keys from<br/>MCP_JWKS_URI"]
ALG -- "RS256 + static" --> PK["Use<br/>MCP_JWT_PUBLIC_KEY"]
ALG -- "HS256" --> SEC["Use<br/>MCP_JWT_SECRET"]
JWKS --> V["Validate token<br/>(exp, iss, aud, scopes)"]
PK --> V
SEC --> V
V --> UR["Resolve Superset user<br/>from token claims"]
UR --> OK["Authenticated request"]
CF --> OK
DU --> OK
```
### Development Mode (No Auth)
Disable authentication and use a fixed user:
```python
# superset_config.py
MCP_AUTH_ENABLED = False
MCP_DEV_USERNAME = "admin"
```
All operations run as the configured user.
:::warning
Never use development mode in production. Always enable authentication for any internet-facing deployment.
:::
### JWT Authentication
For production, enable JWT-based authentication. The MCP server validates a Bearer token on every request.
#### Option A: RS256 with JWKS endpoint
The most common setup for OAuth 2.0 / OIDC providers that publish a JWKS (JSON Web Key Set) endpoint:
```python
# superset_config.py
MCP_AUTH_ENABLED = True
MCP_JWT_ALGORITHM = "RS256"
MCP_JWKS_URI = "https://your-identity-provider.com/.well-known/jwks.json"
MCP_JWT_ISSUER = "https://your-identity-provider.com/"
MCP_JWT_AUDIENCE = "your-superset-instance"
```
#### Option B: RS256 with static public key
Use this when you have a fixed RSA key pair (e.g., self-signed tokens):
```python
# superset_config.py
MCP_AUTH_ENABLED = True
MCP_JWT_ALGORITHM = "RS256"
MCP_JWT_PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----"""
MCP_JWT_ISSUER = "your-issuer"
MCP_JWT_AUDIENCE = "your-audience"
```
#### Option C: HS256 with shared secret
Use this when both the token issuer and the MCP server share a symmetric secret:
```python
# superset_config.py
MCP_AUTH_ENABLED = True
MCP_JWT_ALGORITHM = "HS256"
MCP_JWT_SECRET = "your-shared-secret-key"
MCP_JWT_ISSUER = "your-issuer"
MCP_JWT_AUDIENCE = "your-audience"
```
:::warning
Store `MCP_JWT_SECRET` securely. Never commit it to version control. Use environment variables:
```python
import os
MCP_JWT_SECRET = os.environ.get("MCP_JWT_SECRET")
```
:::
#### JWT claims
The MCP server validates these standard claims:
| Claim | Config Key | Description |
|-------|-----------|-------------|
| `exp` | -- | Expiration time (always validated) |
| `iss` | `MCP_JWT_ISSUER` | Token issuer (optional but recommended) |
| `aud` | `MCP_JWT_AUDIENCE` | Token audience (optional but recommended) |
| `sub` | -- | Subject -- primary claim used to resolve the Superset user |
#### User resolution
After validating the token, the MCP server resolves a Superset username from the claims. It checks these in order, using the first non-empty value:
1. `subject` -- the standard `sub` claim (via the access token object)
2. `client_id` -- for machine-to-machine tokens
3. `payload["sub"]` -- fallback to raw payload
4. `payload["email"]` -- email-based lookup
5. `payload["username"]` -- explicit username claim
The resolved value must match a `username` in the Superset `ab_user` table.
#### Scoped access
Require specific scopes in the JWT to limit what MCP operations a token can perform:
```python
# superset_config.py
MCP_REQUIRED_SCOPES = ["mcp:read", "mcp:write"]
```
Only tokens that include **all** required scopes are accepted.
### Custom Auth Provider
For advanced scenarios (e.g., a proprietary auth system), provide a factory function. This takes precedence over all built-in JWT configuration:
```python
# superset_config.py
def my_custom_auth_factory(app):
"""Return a FastMCP auth provider instance."""
from fastmcp.server.auth.providers.jwt import JWTVerifier
return JWTVerifier(
jwks_uri="https://my-auth.example.com/.well-known/jwks.json",
issuer="https://my-auth.example.com/",
audience="superset-mcp",
)
MCP_AUTH_FACTORY = my_custom_auth_factory
```
---
## Connecting AI Clients
### Claude Desktop
**Local development (no auth):**
```json
{
"mcpServers": {
"superset": {
"url": "http://localhost:5008/mcp"
}
}
}
```
**With JWT authentication:**
```json
{
"mcpServers": {
"superset": {
"command": "npx",
"args": [
"-y",
"mcp-remote@latest",
"http://your-superset-host:5008/mcp",
"--header",
"Authorization: Bearer YOUR_TOKEN"
]
}
}
}
```
### Claude Code (CLI)
Add to your project's `.mcp.json`:
```json
{
"mcpServers": {
"superset": {
"type": "url",
"url": "http://localhost:5008/mcp"
}
}
}
```
With authentication:
```json
{
"mcpServers": {
"superset": {
"type": "url",
"url": "http://localhost:5008/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}
```
### Claude Web (claude.ai)
1. Open [claude.ai](https://claude.ai)
2. Click the **+** button (or your profile icon)
3. Select **Connectors**
4. Click **Manage Connectors** > **Add custom connector**
5. Enter a name and your MCP URL (e.g., `https://your-superset-host/mcp`)
6. Click **Add**
:::info
Custom connectors on Claude Web require a Pro, Max, Team, or Enterprise plan.
:::
### ChatGPT
1. Click your profile icon > **Settings** > **Apps and Connectors**
2. Enable **Developer Mode** in Advanced Settings
3. In the chat composer, press **+** > **Add sources** > **App** > **Connect more** > **Create app**
4. Enter a name and your MCP server URL
5. Click **I understand and continue**
:::info
ChatGPT MCP connectors require a Pro, Team, Enterprise, or Edu plan.
:::
### Direct HTTP requests
Call the MCP server directly with any HTTP client:
```bash
curl -X POST http://localhost:5008/mcp \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
-d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
```
---
## Deployment
### Single Process
The simplest setup: run the MCP server alongside Superset on the same host.
```mermaid
flowchart TD
subgraph host["Host / VM"]
direction TB
S["Superset<br/>:8088"] --> DB[("Postgres")]
M["MCP Server<br/>:5008"] --> DB
end
C["AI Client"] -- "HTTPS" --> P["Reverse Proxy<br/>(Nginx / Caddy)"]
U["Browser"] -- "HTTPS" --> P
P -- ":8088" --> S
P -- ":5008/mcp" --> M
```
**superset_config.py:**
```python
MCP_SERVICE_HOST = "0.0.0.0"
MCP_SERVICE_PORT = 5008
MCP_DEV_USERNAME = "admin" # or enable JWT auth
# If behind a reverse proxy, set the public-facing URL so
# MCP-generated links (chart previews, SQL Lab URLs) resolve correctly:
MCP_SERVICE_URL = "https://superset.example.com"
```
**Start both processes:**
```bash
# Terminal 1 -- Superset web server
superset run -h 0.0.0.0 -p 8088
# Terminal 2 -- MCP server
superset mcp run --host 0.0.0.0 --port 5008
```
**Nginx reverse proxy with TLS:**
```nginx
server {
listen 443 ssl;
server_name superset.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# Superset web UI
location / {
proxy_pass http://127.0.0.1:8088;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# MCP endpoint
location /mcp {
proxy_pass http://127.0.0.1:5008/mcp;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Authorization $http_authorization;
}
}
```
### Docker Compose
Run Superset and the MCP server as separate containers sharing the same config:
```yaml
# docker-compose.yml
services:
superset:
image: apache/superset:latest
ports:
- "8088:8088"
volumes:
- ./superset_config.py:/app/superset_config.py
environment:
- SUPERSET_CONFIG_PATH=/app/superset_config.py
mcp:
image: apache/superset:latest
command: ["superset", "mcp", "run", "--host", "0.0.0.0", "--port", "5008"]
ports:
- "5008:5008"
volumes:
- ./superset_config.py:/app/superset_config.py
environment:
- SUPERSET_CONFIG_PATH=/app/superset_config.py
depends_on:
- superset
```
Both containers share the same `superset_config.py`, so authentication settings, database connections, and feature flags stay in sync.
### Multi-Pod (Kubernetes)
For high-availability deployments, configure Redis so that replicas share session state:
```mermaid
flowchart TD
LB["Load Balancer"] --> M1["MCP Pod 1"]
LB --> M2["MCP Pod 2"]
LB --> M3["MCP Pod 3"]
M1 --> R[("Redis<br/>(session store)")]
M2 --> R
M3 --> R
M1 --> DB[("Postgres")]
M2 --> DB
M3 --> DB
```
**superset_config.py:**
```python
MCP_STORE_CONFIG = {
"enabled": True,
"CACHE_REDIS_URL": "redis://redis-host:6379/0",
"event_store_max_events": 100,
"event_store_ttl": 3600,
}
```
When `CACHE_REDIS_URL` is set, the MCP server uses a Redis-backed EventStore for session management, allowing replicas to share state. Without Redis, each pod manages its own in-memory sessions and stateful MCP interactions may fail when requests hit different replicas.
---
## Configuration Reference
All MCP settings go in `superset_config.py`. Defaults are defined in `superset/mcp_service/mcp_config.py`.
### Core
| Setting | Default | Description |
|---------|---------|-------------|
| `MCP_SERVICE_HOST` | `"localhost"` | Host the MCP server binds to |
| `MCP_SERVICE_PORT` | `5008` | Port the MCP server binds to |
| `MCP_SERVICE_URL` | `None` | Public base URL for MCP-generated links (set this when behind a reverse proxy) |
| `MCP_DEBUG` | `False` | Enable debug logging |
| `MCP_DEV_USERNAME` | -- | Superset username for development mode (no auth) |
| `MCP_RBAC_ENABLED` | `True` | Enforce Superset's role-based access control on MCP tool calls. When `True`, each tool checks that the authenticated user has the required FAB permission before executing. Disable only for testing or trusted-network deployments. |
### Authentication
| Setting | Default | Description |
|---------|---------|-------------|
| `MCP_AUTH_ENABLED` | `False` | Enable JWT authentication |
| `MCP_JWT_ALGORITHM` | `"RS256"` | JWT signing algorithm (`RS256` or `HS256`) |
| `MCP_JWKS_URI` | `None` | JWKS endpoint URL (RS256) |
| `MCP_JWT_PUBLIC_KEY` | `None` | Static RSA public key string (RS256) |
| `MCP_JWT_SECRET` | `None` | Shared secret string (HS256) |
| `MCP_JWT_ISSUER` | `None` | Expected `iss` claim |
| `MCP_JWT_AUDIENCE` | `None` | Expected `aud` claim |
| `MCP_REQUIRED_SCOPES` | `[]` | Required JWT scopes |
| `MCP_JWT_DEBUG_ERRORS` | `False` | Log detailed JWT errors server-side (never exposed in HTTP responses per RFC 6750) |
| `MCP_AUTH_FACTORY` | `None` | Custom auth provider factory `(flask_app) -> auth_provider`. Takes precedence over built-in JWT |
| `MCP_USER_RESOLVER` | `None` | Custom function `(app, access_token) -> username` to extract a Superset username from a validated JWT token. When `None`, the default resolver checks `preferred_username`, `username`, `email`, and `sub` claims in that order. |
### Response Size Guard
Limits response sizes to prevent exceeding LLM context windows:
```python
MCP_RESPONSE_SIZE_CONFIG = {
"enabled": True,
"token_limit": 25000,
"warn_threshold_pct": 80,
"excluded_tools": [
"health_check",
"get_chart_preview",
"generate_explore_link",
"open_sql_lab_with_context",
],
}
```
| Key | Default | Description |
|-----|---------|-------------|
| `enabled` | `True` | Enable response size checking |
| `token_limit` | `25000` | Maximum estimated token count per response |
| `warn_threshold_pct` | `80` | Warn when response exceeds this percentage of the limit |
| `excluded_tools` | See above | Tools exempt from size checking (e.g., tools that return URLs, not data) |
### Caching
Optional response caching for read-heavy workloads. Requires Redis when used with multiple replicas.
```python
MCP_CACHE_CONFIG = {
"enabled": False,
"CACHE_KEY_PREFIX": None,
"list_tools_ttl": 300, # 5 min
"list_resources_ttl": 300,
"list_prompts_ttl": 300,
"read_resource_ttl": 3600, # 1 hour
"get_prompt_ttl": 3600,
"call_tool_ttl": 3600,
"max_item_size": 1048576, # 1 MB
"excluded_tools": [
"execute_sql",
"generate_dashboard",
"generate_chart",
"update_chart",
],
}
```
| Key | Default | Description |
|-----|---------|-------------|
| `enabled` | `False` | Enable response caching |
| `CACHE_KEY_PREFIX` | `None` | Optional prefix for cache keys (useful for shared Redis) |
| `list_tools_ttl` | `300` | Cache TTL in seconds for `tools/list` |
| `list_resources_ttl` | `300` | Cache TTL for `resources/list` |
| `list_prompts_ttl` | `300` | Cache TTL for `prompts/list` |
| `read_resource_ttl` | `3600` | Cache TTL for `resources/read` |
| `get_prompt_ttl` | `3600` | Cache TTL for `prompts/get` |
| `call_tool_ttl` | `3600` | Cache TTL for `tools/call` |
| `max_item_size` | `1048576` | Maximum cached item size in bytes (1 MB) |
| `excluded_tools` | See above | Tools that are never cached (mutating or non-deterministic) |
### Redis Store (Multi-Pod)
Enables Redis-backed session and event storage for multi-replica deployments:
```python
MCP_STORE_CONFIG = {
"enabled": False,
"CACHE_REDIS_URL": None,
"event_store_max_events": 100,
"event_store_ttl": 3600,
}
```
| Key | Default | Description |
|-----|---------|-------------|
| `enabled` | `False` | Enable Redis-backed store |
| `CACHE_REDIS_URL` | `None` | Redis connection URL (e.g., `redis://redis-host:6379/0`) |
| `event_store_max_events` | `100` | Maximum events retained per session |
| `event_store_ttl` | `3600` | Event TTL in seconds |
### Tool Search
By default the MCP server exposes a lightweight tool-search interface instead of advertising every tool at once. This reduces the initial context sent to the LLM by ~70%, which lowers cost and latency. The AI client discovers tools on demand by calling `search_tools` and then invokes them via `call_tool`.
```python
MCP_TOOL_SEARCH_CONFIG = {
"enabled": True,
"strategy": "bm25", # "bm25" (natural language) or "regex"
"max_results": 5,
"always_visible": [ # Tools always listed (pinned)
"health_check",
"get_instance_info",
],
"search_tool_name": "search_tools",
"call_tool_name": "call_tool",
"include_schemas": False, # False=summary mode (name + parameters_hint)
"compact_schemas": True, # Strip $defs (only applies when include_schemas=True)
"max_description_length": 300,
}
```
| Key | Default | Description |
|-----|---------|-------------|
| `enabled` | `True` | Enable tool search. When `False`, all tools are listed upfront |
| `strategy` | `"bm25"` | Search ranking algorithm. `"bm25"` supports natural language; `"regex"` supports pattern matching |
| `max_results` | `5` | Maximum tools returned per search query |
| `always_visible` | See above | Tools that always appear in `list_tools`, regardless of search |
| `include_schemas` | `False` | When `False` (default, "summary mode"), search results omit `inputSchema` entirely and include a lightweight `parameters_hint` listing top-level parameter names. Set to `True` to include the full `inputSchema` in search results. Full schemas are always used when a tool is actually invoked via `call_tool`. |
| `compact_schemas` | `True` | Strip `$defs` / `$ref` and replace with `{"type": "object"}` in search results to reduce token cost. Only takes effect when `include_schemas=True` — ignored in summary mode. |
| `max_description_length` | `300` | Truncate tool descriptions in search results (0 = no truncation). Applies in both summary and full-schema modes. |
:::tip
Set `enabled: False` to revert to the traditional "show all tools at once" behavior, which some clients or workflows may prefer.
:::
Tool search reduces the initial token cost from ~1520K tokens (full catalog) down to ~45K tokens (pinned tools + search interface) — roughly 85% savings at the start of each conversation.
### Session & CSRF
These values are flat-merged into the Flask app config used by the MCP server process:
```python
MCP_SESSION_CONFIG = {
"SESSION_COOKIE_HTTPONLY": True,
"SESSION_COOKIE_SECURE": False,
"SESSION_COOKIE_SAMESITE": "Lax",
"SESSION_COOKIE_NAME": "superset_session",
"PERMANENT_SESSION_LIFETIME": 86400,
}
MCP_CSRF_CONFIG = {
"WTF_CSRF_ENABLED": True,
"WTF_CSRF_TIME_LIMIT": None,
}
```
---
## Access Control
### RBAC Enforcement
The MCP server respects Superset's full role-based access control (RBAC). Every authenticated user can only access the data and operations their Superset roles permit — the same rules that apply in the Superset UI apply through MCP.
Each tool declares one or more required FAB permissions. The table below maps tool groups to their permission requirements:
| Tool group | Required FAB permission |
|------------|------------------------|
| `list_charts`, `get_chart_info`, `get_chart_data`, `get_chart_preview`, `generate_chart`, `update_chart` | `can_read` on `Chart` (read), `can_write` on `Chart` (mutate) |
| `list_dashboards`, `get_dashboard_info`, `generate_dashboard`, `add_chart_to_existing_dashboard` | `can_read` on `Dashboard` (read), `can_write` on `Dashboard` (mutate) |
| `list_datasets`, `get_dataset_info`, `create_virtual_dataset` | `can_read` on `Dataset` (read), `can_write` on `Dataset` (mutate) |
| `list_databases`, `get_database_info` | `can_read` on `Database` |
| `execute_sql` | `can_execute_sql_query` on `SQLLab` |
| `open_sql_lab_with_context` | `can_read` on `SQLLab` |
| `save_sql_query` | `can_write` on `SavedQuery` |
| `health_check` | None (public) |
To disable RBAC checking globally (for trusted-network deployments or testing), set:
```python
# superset_config.py
MCP_RBAC_ENABLED = False
```
:::warning
Disabling RBAC removes all permission checks from MCP tool calls. Only do this on isolated, internal deployments where all MCP users are trusted admins.
:::
### Audit Log
All MCP tool calls are recorded in Superset's action log. You can view them at **Settings → Action Log** (admin only). Each log entry records:
- The tool name (e.g., `mcp.generate_chart.db_write`)
- The authenticated user
- A timestamp
This makes MCP activity fully auditable alongside regular Superset activity. The action log uses the same event logger as the rest of Superset, so existing log ingestion pipelines (e.g., sending logs to Elasticsearch or a SIEM) capture MCP events automatically.
### Middleware Pipeline
Every MCP request passes through a middleware stack before reaching the tool function. The default stack (assembled in `build_middleware_list()` in `server.py`) is:
| Middleware | Purpose | Default |
|------------|---------|---------|
| `StructuredContentStripperMiddleware` | Strips `structuredContent` from responses for Claude.ai bridge compatibility | Enabled |
| `LoggingMiddleware` | Logs each tool call with user, parameters, and duration | Enabled |
| `GlobalErrorHandlerMiddleware` | Catches unhandled exceptions and sanitizes sensitive data before it reaches the client | Enabled |
| `ResponseSizeGuardMiddleware` | Estimates token count, warns at 80% of limit, blocks at limit | Enabled (configurable via `MCP_RESPONSE_SIZE_CONFIG`) |
| `ResponseCachingMiddleware` | Caches read-heavy tool responses (in-memory or Redis) | Disabled (enable via `MCP_CACHE_CONFIG`) |
Additional middleware classes (`RateLimitMiddleware`, `FieldPermissionsMiddleware`, `PrivateToolMiddleware`) are implemented in `superset/mcp_service/middleware.py` but are not added to the default pipeline. They are available for operators who want to layer them in via a custom startup path.
### Error Sanitization
The `GlobalErrorHandlerMiddleware` automatically redacts sensitive information from all error messages before they reach the LLM client. The following are replaced with generic messages:
- **Database connection strings** — replaced with a generic connection error message
- **API keys and tokens** — redacted from error traces
- **File system paths** — stripped to prevent information disclosure
- **IP addresses** — removed from error context
This ensures that a misconfigured database connection or an unexpected exception never leaks credentials or internal topology to the LLM or its users. All regex patterns used for redaction are bounded to prevent ReDoS attacks.
---
## Performance
### Connection Pooling
Each MCP server process maintains its own SQLAlchemy connection pool to the database. For multi-worker deployments, total open connections = **workers × pool size**.
```python
# superset_config.py
SQLALCHEMY_POOL_SIZE = 5
SQLALCHEMY_MAX_OVERFLOW = 10
SQLALCHEMY_POOL_TIMEOUT = 30
SQLALCHEMY_POOL_RECYCLE = 3600 # Recycle connections after 1 hour
```
For a 3-pod Kubernetes deployment with the defaults above, expect up to 3 × (5 + 10) = 45 connections. Size your database's `max_connections` accordingly.
### Response Caching
Enable response caching for read-heavy workloads (dashboards/datasets that don't change frequently). With the in-memory backend (default when `MCP_STORE_CONFIG` is disabled), caching is per-process. Use Redis-backed caching for consistent cache hits across multiple pods:
```python
MCP_CACHE_CONFIG = {"enabled": True, "call_tool_ttl": 3600}
MCP_STORE_CONFIG = {"enabled": True, "CACHE_REDIS_URL": "redis://redis:6379/0"}
```
Mutating tools (`generate_chart`, `update_chart`, `execute_sql`, `generate_dashboard`) are always excluded from caching regardless of this setting.
---
## Troubleshooting
### Server won't start
- Verify `fastmcp` is installed: `pip install fastmcp`
- Check that `MCP_DEV_USERNAME` is set if auth is disabled -- the server requires a user identity
- Confirm the port is not already in use: `lsof -i :5008`
### 401 Unauthorized
- Verify your JWT token has not expired (`exp` claim)
- Check that `MCP_JWT_ISSUER` and `MCP_JWT_AUDIENCE` match the token's `iss` and `aud` claims exactly
- For RS256 with JWKS: confirm the JWKS URI is reachable from the MCP server
- For RS256 with static key: confirm the public key string includes the `BEGIN`/`END` markers
- For HS256: confirm the secret matches between the token issuer and `MCP_JWT_SECRET`
- Enable `MCP_JWT_DEBUG_ERRORS = True` for detailed server-side logging (errors are never leaked to the client)
### Tool not found
- Ensure the MCP server and Superset share the same `superset_config.py`
- Check server logs at startup -- tool registration errors are logged with the tool name and reason
### Client can't connect
- Verify the MCP server URL is reachable from the client machine
- For Claude Desktop: fully quit the app (not just close the window) and restart after config changes
- For remote access: ensure your firewall and reverse proxy allow traffic to the MCP port
- Confirm the URL path ends with `/mcp` (e.g., `http://localhost:5008/mcp`)
### Permission errors on tool calls
- The MCP server enforces Superset's RBAC permissions -- the authenticated user must have the required roles
- In development mode, ensure `MCP_DEV_USERNAME` maps to a user with appropriate roles (e.g., Admin)
- Check `superset/security/manager.py` for the specific permission tuples required by each tool domain (e.g., `("can_execute_sql_query", "SQLLab")`)
### Response too large
- If a tool call returns an error about exceeding token limits, the response size guard is blocking an oversized result
- Reduce `page_size` or `limit` parameters, use `select_columns` to exclude large fields, or add filters to narrow results
- To adjust the threshold, change `token_limit` in `MCP_RESPONSE_SIZE_CONFIG`
- To disable the guard entirely, set `MCP_RESPONSE_SIZE_CONFIG = {"enabled": False}`
---
## Audit Events
All MCP tool calls are logged to Superset's event logger, the same system used by the web UI (viewable at **Settings → Action Log**). Each event captures:
- **Action**: `mcp.<tool_name>.<phase>` (e.g., `mcp.list_databases.query`)
- **User**: the resolved Superset username from the JWT or dev config
- **Timestamp**: when the operation ran
This means MCP activity is auditable alongside normal user activity. No additional configuration is required — logging is on by default whenever the event logger is enabled in your Superset deployment.
## Tool Pagination
MCP list tools (`list_datasets`, `list_charts`, `list_dashboards`, `list_databases`) use **offset pagination** via `page` (1-based) and `page_size` parameters. Responses include `page`, `page_size`, `total_count`, `total_pages`, `has_previous`, and `has_next`. To iterate through all results:
```python
# Example: fetch all charts across pages
all_charts = []
page = 1
while True:
result = mcp.list_charts(page=page, page_size=50)
all_charts.extend(result["charts"])
if not result.get("has_next"):
break
page += 1
```
## Security Best Practices
- **Use TLS** for all production MCP endpoints -- place the server behind a reverse proxy with HTTPS
- **Enable JWT authentication** for any internet-facing deployment
- **RBAC enforcement** -- The MCP server respects Superset's role-based access control. Users can only access data their roles permit
- **Secrets management** -- Store `MCP_JWT_SECRET`, database credentials, and API keys in environment variables or a secrets manager, never in config files committed to version control
- **Scoped tokens** -- Use `MCP_REQUIRED_SCOPES` to limit what operations a token can perform
- **Network isolation** -- In Kubernetes, restrict MCP pod network policies to only allow traffic from your AI client endpoints
- Review the **[Security documentation](/developer-docs/extensions/security)** for additional extension security guidance
---
## Next Steps
- **[Using AI with Superset](/user-docs/using-superset/using-ai-with-superset)** -- What AI can do with Superset and how to get started
- **[MCP Integration](/developer-docs/extensions/mcp)** -- Build custom MCP tools and prompts via Superset extensions
- **[Security](/developer-docs/extensions/security)** -- Security best practices for extensions
- **[Deployment](/developer-docs/extensions/deployment)** -- Package and deploy Superset extensions

View File

@@ -84,35 +84,6 @@ THEME_DARK = {
# - OS preference detection is automatically enabled
```
### App Branding
The application name shown in the browser title bar and navigation can be
set via the `brandAppName` theme token:
```python
THEME_DEFAULT = {
"token": {
"brandAppName": "Acme Analytics",
# ... other tokens
}
}
```
Or in the theme CRUD UI JSON editor:
```json
{
"token": {
"brandAppName": "Acme Analytics"
}
}
```
The existing `APP_NAME` Python config key continues to work for backward compatibility.
`brandAppName` takes precedence when both are set, and allows different themes to carry different brand names.
Email and alert/report notification subjects are driven by backend settings such as
`EMAIL_REPORTS_SUBJECT_PREFIX` and `APP_NAME`, not by this theme token.
### Migration from Configuration to UI
When `ENABLE_UI_THEME_ADMINISTRATION = True`:
@@ -341,25 +312,11 @@ Available chart types for `echartsOptionsOverridesByChartType`:
- `echarts_heatmap` - Heatmaps
- `echarts_mixed_timeseries` - Mixed time series
### Array Property Overrides
Array properties (such as color palettes) are fully supported in overrides. Arrays are **replaced entirely** rather than merged, so specify the complete array:
```python
THEME_DEFAULT = {
"token": { ... },
"echartsOptionsOverrides": {
# Replace the default color palette for all ECharts visualizations
"color": ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b"]
}
}
```
### Best Practices
1. **Start with global overrides** for consistent styling across all charts
2. **Use chart-specific overrides** for unique requirements per visualization type
3. **Test thoroughly** as overrides use deep merge for objects, but arrays are completely replaced — always specify the full array value
3. **Test thoroughly** as overrides use deep merge - nested objects are combined, but arrays are completely replaced
4. **Document your overrides** to help team members understand custom styling
5. **Consider performance** - complex overrides may impact chart rendering speed

View File

@@ -44,15 +44,6 @@ only see the objects that they have access to.
The **sql_lab** role grants access to SQL Lab. Note that while **Admin** users have access
to all databases by default, both **Alpha** and **Gamma** users need to be given access on a per database basis.
Beyond the base `sql_lab` role, two additional SQL Lab permissions must be explicitly granted for users who need these capabilities:
| Permission | Feature |
|------------|---------|
| `can_estimate_query_cost` on `SQLLab` | Estimate query cost before running |
| `can_format_sql` on `SQLLab` | Format SQL using the database's dialect |
Grant these in **Security → List Roles** by adding the permissions to the relevant role.
### Public
The **Public** role is the most restrictive built-in role, designed specifically for anonymous/unauthenticated
@@ -183,8 +174,6 @@ However, it is crucial to understand the following:
By combining Superset's configurable safeguards with strong database-level security practices, you can achieve a more robust and layered security posture.
**Dataset Sample Access**: The `get_samples()` endpoint now enforces datasource-level access control. Users can only fetch sample rows from datasets they have been explicitly granted access to — the same permission check applied when running chart queries. This closes a prior gap where unauthenticated or under-privileged access could retrieve sample data.
### REST API for user & role management
Flask-AppBuilder supports a REST API for user CRUD,

View File

@@ -47,10 +47,10 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get the CSRF token](/developer-docs/api/get-the-csrf-token) | `/api/v1/security/csrf_token/` |
| `POST` | [Get a guest token](/developer-docs/api/get-a-guest-token) | `/api/v1/security/guest_token/` |
| `POST` | [Create security login](/developer-docs/api/create-security-login) | `/api/v1/security/login` |
| `POST` | [Create security refresh](/developer-docs/api/create-security-refresh) | `/api/v1/security/refresh` |
| `GET` | [Get the CSRF token](./api/get-the-csrf-token) | `/api/v1/security/csrf_token/` |
| `POST` | [Get a guest token](./api/get-a-guest-token) | `/api/v1/security/guest_token/` |
| `POST` | [Create security login](./api/create-security-login) | `/api/v1/security/login` |
| `POST` | [Create security refresh](./api/create-security-refresh) | `/api/v1/security/refresh` |
---
@@ -63,32 +63,32 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete dashboards](/developer-docs/api/bulk-delete-dashboards) | `/api/v1/dashboard/` |
| `GET` | [Get a list of dashboards](/developer-docs/api/get-a-list-of-dashboards) | `/api/v1/dashboard/` |
| `POST` | [Create a new dashboard](/developer-docs/api/create-a-new-dashboard) | `/api/v1/dashboard/` |
| `GET` | [Get metadata information about this API resource (dashboard--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-dashboard-info) | `/api/v1/dashboard/_info` |
| `GET` | [Get a dashboard detail information](/developer-docs/api/get-a-dashboard-detail-information) | `/api/v1/dashboard/{id_or_slug}` |
| `GET` | [Get a dashboard's chart definitions.](/developer-docs/api/get-a-dashboard-s-chart-definitions) | `/api/v1/dashboard/{id_or_slug}/charts` |
| `POST` | [Create a copy of an existing dashboard](/developer-docs/api/create-a-copy-of-an-existing-dashboard) | `/api/v1/dashboard/{id_or_slug}/copy/` |
| `GET` | [Get dashboard's datasets](/developer-docs/api/get-dashboard-s-datasets) | `/api/v1/dashboard/{id_or_slug}/datasets` |
| `DELETE` | [Delete a dashboard's embedded configuration](/developer-docs/api/delete-a-dashboard-s-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `GET` | [Get the dashboard's embedded configuration](/developer-docs/api/get-the-dashboard-s-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `POST` | [Set a dashboard's embedded configuration](/developer-docs/api/set-a-dashboard-s-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `PUT` | [Update dashboard by id_or_slug embedded](/developer-docs/api/update-dashboard-by-id-or-slug-embedded) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `GET` | [Get dashboard's tabs](/developer-docs/api/get-dashboard-s-tabs) | `/api/v1/dashboard/{id_or_slug}/tabs` |
| `DELETE` | [Delete a dashboard](/developer-docs/api/delete-a-dashboard) | `/api/v1/dashboard/{pk}` |
| `PUT` | [Update a dashboard](/developer-docs/api/update-a-dashboard) | `/api/v1/dashboard/{pk}` |
| `POST` | [Compute and cache a screenshot (dashboard-pk-cache-dashboard-screenshot)](/developer-docs/api/compute-and-cache-a-screenshot-dashboard-pk-cache-dashboard-screenshot) | `/api/v1/dashboard/{pk}/cache_dashboard_screenshot/` |
| `PUT` | [Update colors configuration for a dashboard.](/developer-docs/api/update-colors-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/colors` |
| `DELETE` | [Remove the dashboard from the user favorite list](/developer-docs/api/remove-the-dashboard-from-the-user-favorite-list) | `/api/v1/dashboard/{pk}/favorites/` |
| `POST` | [Mark the dashboard as favorite for the current user](/developer-docs/api/mark-the-dashboard-as-favorite-for-the-current-user) | `/api/v1/dashboard/{pk}/favorites/` |
| `PUT` | [Update native filters configuration for a dashboard.](/developer-docs/api/update-native-filters-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/filters` |
| `GET` | [Get a computed screenshot from cache (dashboard-pk-screenshot-digest)](/developer-docs/api/get-a-computed-screenshot-from-cache-dashboard-pk-screenshot-digest) | `/api/v1/dashboard/{pk}/screenshot/{digest}/` |
| `GET` | [Get dashboard's thumbnail](/developer-docs/api/get-dashboard-s-thumbnail) | `/api/v1/dashboard/{pk}/thumbnail/{digest}/` |
| `GET` | [Download multiple dashboards as YAML files](/developer-docs/api/download-multiple-dashboards-as-yaml-files) | `/api/v1/dashboard/export/` |
| `GET` | [Check favorited dashboards for current user](/developer-docs/api/check-favorited-dashboards-for-current-user) | `/api/v1/dashboard/favorite_status/` |
| `POST` | [Import dashboard(s) with associated charts/datasets/databases](/developer-docs/api/import-dashboard-s-with-associated-charts-datasets-databases) | `/api/v1/dashboard/import/` |
| `GET` | [Get related fields data (dashboard-related-column-name)](/developer-docs/api/get-related-fields-data-dashboard-related-column-name) | `/api/v1/dashboard/related/{column_name}` |
| `DELETE` | [Bulk delete dashboards](./api/bulk-delete-dashboards) | `/api/v1/dashboard/` |
| `GET` | [Get a list of dashboards](./api/get-a-list-of-dashboards) | `/api/v1/dashboard/` |
| `POST` | [Create a new dashboard](./api/create-a-new-dashboard) | `/api/v1/dashboard/` |
| `GET` | [Get metadata information about this API resource (dashboard--info)](./api/get-metadata-information-about-this-api-resource-dashboard-info) | `/api/v1/dashboard/_info` |
| `GET` | [Get a dashboard detail information](./api/get-a-dashboard-detail-information) | `/api/v1/dashboard/{id_or_slug}` |
| `GET` | [Get a dashboard's chart definitions.](./api/get-a-dashboard-s-chart-definitions) | `/api/v1/dashboard/{id_or_slug}/charts` |
| `POST` | [Create a copy of an existing dashboard](./api/create-a-copy-of-an-existing-dashboard) | `/api/v1/dashboard/{id_or_slug}/copy/` |
| `GET` | [Get dashboard's datasets](./api/get-dashboard-s-datasets) | `/api/v1/dashboard/{id_or_slug}/datasets` |
| `DELETE` | [Delete a dashboard's embedded configuration](./api/delete-a-dashboard-s-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `GET` | [Get the dashboard's embedded configuration](./api/get-the-dashboard-s-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `POST` | [Set a dashboard's embedded configuration](./api/set-a-dashboard-s-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `PUT` | [Update dashboard by id_or_slug embedded](./api/update-dashboard-by-id-or-slug-embedded) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `GET` | [Get dashboard's tabs](./api/get-dashboard-s-tabs) | `/api/v1/dashboard/{id_or_slug}/tabs` |
| `DELETE` | [Delete a dashboard](./api/delete-a-dashboard) | `/api/v1/dashboard/{pk}` |
| `PUT` | [Update a dashboard](./api/update-a-dashboard) | `/api/v1/dashboard/{pk}` |
| `POST` | [Compute and cache a screenshot (dashboard-pk-cache-dashboard-screenshot)](./api/compute-and-cache-a-screenshot-dashboard-pk-cache-dashboard-screenshot) | `/api/v1/dashboard/{pk}/cache_dashboard_screenshot/` |
| `PUT` | [Update colors configuration for a dashboard.](./api/update-colors-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/colors` |
| `DELETE` | [Remove the dashboard from the user favorite list](./api/remove-the-dashboard-from-the-user-favorite-list) | `/api/v1/dashboard/{pk}/favorites/` |
| `POST` | [Mark the dashboard as favorite for the current user](./api/mark-the-dashboard-as-favorite-for-the-current-user) | `/api/v1/dashboard/{pk}/favorites/` |
| `PUT` | [Update native filters configuration for a dashboard.](./api/update-native-filters-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/filters` |
| `GET` | [Get a computed screenshot from cache (dashboard-pk-screenshot-digest)](./api/get-a-computed-screenshot-from-cache-dashboard-pk-screenshot-digest) | `/api/v1/dashboard/{pk}/screenshot/{digest}/` |
| `GET` | [Get dashboard's thumbnail](./api/get-dashboard-s-thumbnail) | `/api/v1/dashboard/{pk}/thumbnail/{digest}/` |
| `GET` | [Download multiple dashboards as YAML files](./api/download-multiple-dashboards-as-yaml-files) | `/api/v1/dashboard/export/` |
| `GET` | [Check favorited dashboards for current user](./api/check-favorited-dashboards-for-current-user) | `/api/v1/dashboard/favorite_status/` |
| `POST` | [Import dashboard(s) with associated charts/datasets/databases](./api/import-dashboard-s-with-associated-charts-datasets-databases) | `/api/v1/dashboard/import/` |
| `GET` | [Get related fields data (dashboard-related-column-name)](./api/get-related-fields-data-dashboard-related-column-name) | `/api/v1/dashboard/related/{column_name}` |
</details>
@@ -97,26 +97,26 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete charts](/developer-docs/api/bulk-delete-charts) | `/api/v1/chart/` |
| `GET` | [Get a list of charts](/developer-docs/api/get-a-list-of-charts) | `/api/v1/chart/` |
| `POST` | [Create a new chart](/developer-docs/api/create-a-new-chart) | `/api/v1/chart/` |
| `GET` | [Get metadata information about this API resource (chart--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-chart-info) | `/api/v1/chart/_info` |
| `DELETE` | [Delete a chart](/developer-docs/api/delete-a-chart) | `/api/v1/chart/{pk}` |
| `GET` | [Get a chart detail information](/developer-docs/api/get-a-chart-detail-information) | `/api/v1/chart/{pk}` |
| `PUT` | [Update a chart](/developer-docs/api/update-a-chart) | `/api/v1/chart/{pk}` |
| `GET` | [Compute and cache a screenshot (chart-pk-cache-screenshot)](/developer-docs/api/compute-and-cache-a-screenshot-chart-pk-cache-screenshot) | `/api/v1/chart/{pk}/cache_screenshot/` |
| `GET` | [Return payload data response for a chart](/developer-docs/api/return-payload-data-response-for-a-chart) | `/api/v1/chart/{pk}/data/` |
| `DELETE` | [Remove the chart from the user favorite list](/developer-docs/api/remove-the-chart-from-the-user-favorite-list) | `/api/v1/chart/{pk}/favorites/` |
| `POST` | [Mark the chart as favorite for the current user](/developer-docs/api/mark-the-chart-as-favorite-for-the-current-user) | `/api/v1/chart/{pk}/favorites/` |
| `GET` | [Get a computed screenshot from cache (chart-pk-screenshot-digest)](/developer-docs/api/get-a-computed-screenshot-from-cache-chart-pk-screenshot-digest) | `/api/v1/chart/{pk}/screenshot/{digest}/` |
| `GET` | [Get chart thumbnail](/developer-docs/api/get-chart-thumbnail) | `/api/v1/chart/{pk}/thumbnail/{digest}/` |
| `POST` | [Return payload data response for the given query (chart-data)](/developer-docs/api/return-payload-data-response-for-the-given-query-chart-data) | `/api/v1/chart/data` |
| `GET` | [Return payload data response for the given query (chart-data-cache-key)](/developer-docs/api/return-payload-data-response-for-the-given-query-chart-data-cache-key) | `/api/v1/chart/data/{cache_key}` |
| `GET` | [Download multiple charts as YAML files](/developer-docs/api/download-multiple-charts-as-yaml-files) | `/api/v1/chart/export/` |
| `GET` | [Check favorited charts for current user](/developer-docs/api/check-favorited-charts-for-current-user) | `/api/v1/chart/favorite_status/` |
| `POST` | [Import chart(s) with associated datasets and databases](/developer-docs/api/import-chart-s-with-associated-datasets-and-databases) | `/api/v1/chart/import/` |
| `GET` | [Get related fields data (chart-related-column-name)](/developer-docs/api/get-related-fields-data-chart-related-column-name) | `/api/v1/chart/related/{column_name}` |
| `PUT` | [Warm up the cache for the chart](/developer-docs/api/warm-up-the-cache-for-the-chart) | `/api/v1/chart/warm_up_cache` |
| `DELETE` | [Bulk delete charts](./api/bulk-delete-charts) | `/api/v1/chart/` |
| `GET` | [Get a list of charts](./api/get-a-list-of-charts) | `/api/v1/chart/` |
| `POST` | [Create a new chart](./api/create-a-new-chart) | `/api/v1/chart/` |
| `GET` | [Get metadata information about this API resource (chart--info)](./api/get-metadata-information-about-this-api-resource-chart-info) | `/api/v1/chart/_info` |
| `DELETE` | [Delete a chart](./api/delete-a-chart) | `/api/v1/chart/{pk}` |
| `GET` | [Get a chart detail information](./api/get-a-chart-detail-information) | `/api/v1/chart/{pk}` |
| `PUT` | [Update a chart](./api/update-a-chart) | `/api/v1/chart/{pk}` |
| `GET` | [Compute and cache a screenshot (chart-pk-cache-screenshot)](./api/compute-and-cache-a-screenshot-chart-pk-cache-screenshot) | `/api/v1/chart/{pk}/cache_screenshot/` |
| `GET` | [Return payload data response for a chart](./api/return-payload-data-response-for-a-chart) | `/api/v1/chart/{pk}/data/` |
| `DELETE` | [Remove the chart from the user favorite list](./api/remove-the-chart-from-the-user-favorite-list) | `/api/v1/chart/{pk}/favorites/` |
| `POST` | [Mark the chart as favorite for the current user](./api/mark-the-chart-as-favorite-for-the-current-user) | `/api/v1/chart/{pk}/favorites/` |
| `GET` | [Get a computed screenshot from cache (chart-pk-screenshot-digest)](./api/get-a-computed-screenshot-from-cache-chart-pk-screenshot-digest) | `/api/v1/chart/{pk}/screenshot/{digest}/` |
| `GET` | [Get chart thumbnail](./api/get-chart-thumbnail) | `/api/v1/chart/{pk}/thumbnail/{digest}/` |
| `POST` | [Return payload data response for the given query (chart-data)](./api/return-payload-data-response-for-the-given-query-chart-data) | `/api/v1/chart/data` |
| `GET` | [Return payload data response for the given query (chart-data-cache-key)](./api/return-payload-data-response-for-the-given-query-chart-data-cache-key) | `/api/v1/chart/data/{cache_key}` |
| `GET` | [Download multiple charts as YAML files](./api/download-multiple-charts-as-yaml-files) | `/api/v1/chart/export/` |
| `GET` | [Check favorited charts for current user](./api/check-favorited-charts-for-current-user) | `/api/v1/chart/favorite_status/` |
| `POST` | [Import chart(s) with associated datasets and databases](./api/import-chart-s-with-associated-datasets-and-databases) | `/api/v1/chart/import/` |
| `GET` | [Get related fields data (chart-related-column-name)](./api/get-related-fields-data-chart-related-column-name) | `/api/v1/chart/related/{column_name}` |
| `PUT` | [Warm up the cache for the chart](./api/warm-up-the-cache-for-the-chart) | `/api/v1/chart/warm_up_cache` |
</details>
@@ -125,24 +125,24 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete datasets](/developer-docs/api/bulk-delete-datasets) | `/api/v1/dataset/` |
| `GET` | [Get a list of datasets](/developer-docs/api/get-a-list-of-datasets) | `/api/v1/dataset/` |
| `POST` | [Create a new dataset](/developer-docs/api/create-a-new-dataset) | `/api/v1/dataset/` |
| `GET` | [Get metadata information about this API resource (dataset--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-dataset-info) | `/api/v1/dataset/_info` |
| `DELETE` | [Delete a dataset](/developer-docs/api/delete-a-dataset) | `/api/v1/dataset/{pk}` |
| `GET` | [Get a dataset](/developer-docs/api/get-a-dataset) | `/api/v1/dataset/{pk}` |
| `PUT` | [Update a dataset](/developer-docs/api/update-a-dataset) | `/api/v1/dataset/{pk}` |
| `DELETE` | [Delete a dataset column](/developer-docs/api/delete-a-dataset-column) | `/api/v1/dataset/{pk}/column/{column_id}` |
| `DELETE` | [Delete a dataset metric](/developer-docs/api/delete-a-dataset-metric) | `/api/v1/dataset/{pk}/metric/{metric_id}` |
| `PUT` | [Refresh and update columns of a dataset](/developer-docs/api/refresh-and-update-columns-of-a-dataset) | `/api/v1/dataset/{pk}/refresh` |
| `GET` | [Get charts and dashboards count associated to a dataset](/developer-docs/api/get-charts-and-dashboards-count-associated-to-a-dataset) | `/api/v1/dataset/{pk}/related_objects` |
| `GET` | [Get distinct values from field data (dataset-distinct-column-name)](/developer-docs/api/get-distinct-values-from-field-data-dataset-distinct-column-name) | `/api/v1/dataset/distinct/{column_name}` |
| `POST` | [Duplicate a dataset](/developer-docs/api/duplicate-a-dataset) | `/api/v1/dataset/duplicate` |
| `GET` | [Download multiple datasets as YAML files](/developer-docs/api/download-multiple-datasets-as-yaml-files) | `/api/v1/dataset/export/` |
| `POST` | [Retrieve a table by name, or create it if it does not exist](/developer-docs/api/retrieve-a-table-by-name-or-create-it-if-it-does-not-exist) | `/api/v1/dataset/get_or_create/` |
| `POST` | [Import dataset(s) with associated databases](/developer-docs/api/import-dataset-s-with-associated-databases) | `/api/v1/dataset/import/` |
| `GET` | [Get related fields data (dataset-related-column-name)](/developer-docs/api/get-related-fields-data-dataset-related-column-name) | `/api/v1/dataset/related/{column_name}` |
| `PUT` | [Warm up the cache for each chart powered by the given table](/developer-docs/api/warm-up-the-cache-for-each-chart-powered-by-the-given-table) | `/api/v1/dataset/warm_up_cache` |
| `DELETE` | [Bulk delete datasets](./api/bulk-delete-datasets) | `/api/v1/dataset/` |
| `GET` | [Get a list of datasets](./api/get-a-list-of-datasets) | `/api/v1/dataset/` |
| `POST` | [Create a new dataset](./api/create-a-new-dataset) | `/api/v1/dataset/` |
| `GET` | [Get metadata information about this API resource (dataset--info)](./api/get-metadata-information-about-this-api-resource-dataset-info) | `/api/v1/dataset/_info` |
| `DELETE` | [Delete a dataset](./api/delete-a-dataset) | `/api/v1/dataset/{pk}` |
| `GET` | [Get a dataset](./api/get-a-dataset) | `/api/v1/dataset/{pk}` |
| `PUT` | [Update a dataset](./api/update-a-dataset) | `/api/v1/dataset/{pk}` |
| `DELETE` | [Delete a dataset column](./api/delete-a-dataset-column) | `/api/v1/dataset/{pk}/column/{column_id}` |
| `DELETE` | [Delete a dataset metric](./api/delete-a-dataset-metric) | `/api/v1/dataset/{pk}/metric/{metric_id}` |
| `PUT` | [Refresh and update columns of a dataset](./api/refresh-and-update-columns-of-a-dataset) | `/api/v1/dataset/{pk}/refresh` |
| `GET` | [Get charts and dashboards count associated to a dataset](./api/get-charts-and-dashboards-count-associated-to-a-dataset) | `/api/v1/dataset/{pk}/related_objects` |
| `GET` | [Get distinct values from field data (dataset-distinct-column-name)](./api/get-distinct-values-from-field-data-dataset-distinct-column-name) | `/api/v1/dataset/distinct/{column_name}` |
| `POST` | [Duplicate a dataset](./api/duplicate-a-dataset) | `/api/v1/dataset/duplicate` |
| `GET` | [Download multiple datasets as YAML files](./api/download-multiple-datasets-as-yaml-files) | `/api/v1/dataset/export/` |
| `POST` | [Retrieve a table by name, or create it if it does not exist](./api/retrieve-a-table-by-name-or-create-it-if-it-does-not-exist) | `/api/v1/dataset/get_or_create/` |
| `POST` | [Import dataset(s) with associated databases](./api/import-dataset-s-with-associated-databases) | `/api/v1/dataset/import/` |
| `GET` | [Get related fields data (dataset-related-column-name)](./api/get-related-fields-data-dataset-related-column-name) | `/api/v1/dataset/related/{column_name}` |
| `PUT` | [Warm up the cache for each chart powered by the given table](./api/warm-up-the-cache-for-each-chart-powered-by-the-given-table) | `/api/v1/dataset/warm_up_cache` |
</details>
@@ -151,37 +151,37 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get a list of databases](/developer-docs/api/get-a-list-of-databases) | `/api/v1/database/` |
| `POST` | [Create a new database](/developer-docs/api/create-a-new-database) | `/api/v1/database/` |
| `GET` | [Get metadata information about this API resource (database--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-database-info) | `/api/v1/database/_info` |
| `DELETE` | [Delete a database](/developer-docs/api/delete-a-database) | `/api/v1/database/{pk}` |
| `GET` | [Get a database](/developer-docs/api/get-a-database) | `/api/v1/database/{pk}` |
| `PUT` | [Change a database](/developer-docs/api/change-a-database) | `/api/v1/database/{pk}` |
| `GET` | [Get all catalogs from a database](/developer-docs/api/get-all-catalogs-from-a-database) | `/api/v1/database/{pk}/catalogs/` |
| `GET` | [Get a database connection info](/developer-docs/api/get-a-database-connection-info) | `/api/v1/database/{pk}/connection` |
| `GET` | [Get function names supported by a database](/developer-docs/api/get-function-names-supported-by-a-database) | `/api/v1/database/{pk}/function_names/` |
| `GET` | [Get charts and dashboards count associated to a database](/developer-docs/api/get-charts-and-dashboards-count-associated-to-a-database) | `/api/v1/database/{pk}/related_objects/` |
| `GET` | [The list of the database schemas where to upload information](/developer-docs/api/the-list-of-the-database-schemas-where-to-upload-information) | `/api/v1/database/{pk}/schemas_access_for_file_upload/` |
| `GET` | [Get all schemas from a database](/developer-docs/api/get-all-schemas-from-a-database) | `/api/v1/database/{pk}/schemas/` |
| `GET` | [Get database select star for table (database-pk-select-star-table-name)](/developer-docs/api/get-database-select-star-for-table-database-pk-select-star-table-name) | `/api/v1/database/{pk}/select_star/{table_name}/` |
| `GET` | [Get database select star for table (database-pk-select-star-table-name-schema-name)](/developer-docs/api/get-database-select-star-for-table-database-pk-select-star-table-name-schema-name) | `/api/v1/database/{pk}/select_star/{table_name}/{schema_name}/` |
| `DELETE` | [Delete a SSH tunnel](/developer-docs/api/delete-a-ssh-tunnel) | `/api/v1/database/{pk}/ssh_tunnel/` |
| `POST` | [Re-sync all permissions for a database connection](/developer-docs/api/re-sync-all-permissions-for-a-database-connection) | `/api/v1/database/{pk}/sync_permissions/` |
| `GET` | [Get table extra metadata (database-pk-table-extra-table-name-schema-name)](/developer-docs/api/get-table-extra-metadata-database-pk-table-extra-table-name-schema-name) | `/api/v1/database/{pk}/table_extra/{table_name}/{schema_name}/` |
| `GET` | [Get table metadata](/developer-docs/api/get-table-metadata) | `/api/v1/database/{pk}/table_metadata/` |
| `GET` | [Get table extra metadata (database-pk-table-metadata-extra)](/developer-docs/api/get-table-extra-metadata-database-pk-table-metadata-extra) | `/api/v1/database/{pk}/table_metadata/extra/` |
| `GET` | [Get database table metadata](/developer-docs/api/get-database-table-metadata) | `/api/v1/database/{pk}/table/{table_name}/{schema_name}/` |
| `GET` | [Get a list of tables for given database](/developer-docs/api/get-a-list-of-tables-for-given-database) | `/api/v1/database/{pk}/tables/` |
| `POST` | [Upload a file to a database table](/developer-docs/api/upload-a-file-to-a-database-table) | `/api/v1/database/{pk}/upload/` |
| `POST` | [Validate arbitrary SQL](/developer-docs/api/validate-arbitrary-sql) | `/api/v1/database/{pk}/validate_sql/` |
| `GET` | [Get names of databases currently available](/developer-docs/api/get-names-of-databases-currently-available) | `/api/v1/database/available/` |
| `GET` | [Download database(s) and associated dataset(s) as a zip file](/developer-docs/api/download-database-s-and-associated-dataset-s-as-a-zip-file) | `/api/v1/database/export/` |
| `POST` | [Import database(s) with associated datasets](/developer-docs/api/import-database-s-with-associated-datasets) | `/api/v1/database/import/` |
| `GET` | [Receive personal access tokens from OAuth2](/developer-docs/api/receive-personal-access-tokens-from-oauth2) | `/api/v1/database/oauth2/` |
| `GET` | [Get related fields data (database-related-column-name)](/developer-docs/api/get-related-fields-data-database-related-column-name) | `/api/v1/database/related/{column_name}` |
| `POST` | [Test a database connection](/developer-docs/api/test-a-database-connection) | `/api/v1/database/test_connection/` |
| `POST` | [Upload a file and returns file metadata](/developer-docs/api/upload-a-file-and-returns-file-metadata) | `/api/v1/database/upload_metadata/` |
| `POST` | [Validate database connection parameters](/developer-docs/api/validate-database-connection-parameters) | `/api/v1/database/validate_parameters/` |
| `GET` | [Get a list of databases](./api/get-a-list-of-databases) | `/api/v1/database/` |
| `POST` | [Create a new database](./api/create-a-new-database) | `/api/v1/database/` |
| `GET` | [Get metadata information about this API resource (database--info)](./api/get-metadata-information-about-this-api-resource-database-info) | `/api/v1/database/_info` |
| `DELETE` | [Delete a database](./api/delete-a-database) | `/api/v1/database/{pk}` |
| `GET` | [Get a database](./api/get-a-database) | `/api/v1/database/{pk}` |
| `PUT` | [Change a database](./api/change-a-database) | `/api/v1/database/{pk}` |
| `GET` | [Get all catalogs from a database](./api/get-all-catalogs-from-a-database) | `/api/v1/database/{pk}/catalogs/` |
| `GET` | [Get a database connection info](./api/get-a-database-connection-info) | `/api/v1/database/{pk}/connection` |
| `GET` | [Get function names supported by a database](./api/get-function-names-supported-by-a-database) | `/api/v1/database/{pk}/function_names/` |
| `GET` | [Get charts and dashboards count associated to a database](./api/get-charts-and-dashboards-count-associated-to-a-database) | `/api/v1/database/{pk}/related_objects/` |
| `GET` | [The list of the database schemas where to upload information](./api/the-list-of-the-database-schemas-where-to-upload-information) | `/api/v1/database/{pk}/schemas_access_for_file_upload/` |
| `GET` | [Get all schemas from a database](./api/get-all-schemas-from-a-database) | `/api/v1/database/{pk}/schemas/` |
| `GET` | [Get database select star for table (database-pk-select-star-table-name)](./api/get-database-select-star-for-table-database-pk-select-star-table-name) | `/api/v1/database/{pk}/select_star/{table_name}/` |
| `GET` | [Get database select star for table (database-pk-select-star-table-name-schema-name)](./api/get-database-select-star-for-table-database-pk-select-star-table-name-schema-name) | `/api/v1/database/{pk}/select_star/{table_name}/{schema_name}/` |
| `DELETE` | [Delete a SSH tunnel](./api/delete-a-ssh-tunnel) | `/api/v1/database/{pk}/ssh_tunnel/` |
| `POST` | [Re-sync all permissions for a database connection](./api/re-sync-all-permissions-for-a-database-connection) | `/api/v1/database/{pk}/sync_permissions/` |
| `GET` | [Get table extra metadata (database-pk-table-extra-table-name-schema-name)](./api/get-table-extra-metadata-database-pk-table-extra-table-name-schema-name) | `/api/v1/database/{pk}/table_extra/{table_name}/{schema_name}/` |
| `GET` | [Get table metadata](./api/get-table-metadata) | `/api/v1/database/{pk}/table_metadata/` |
| `GET` | [Get table extra metadata (database-pk-table-metadata-extra)](./api/get-table-extra-metadata-database-pk-table-metadata-extra) | `/api/v1/database/{pk}/table_metadata/extra/` |
| `GET` | [Get database table metadata](./api/get-database-table-metadata) | `/api/v1/database/{pk}/table/{table_name}/{schema_name}/` |
| `GET` | [Get a list of tables for given database](./api/get-a-list-of-tables-for-given-database) | `/api/v1/database/{pk}/tables/` |
| `POST` | [Upload a file to a database table](./api/upload-a-file-to-a-database-table) | `/api/v1/database/{pk}/upload/` |
| `POST` | [Validate arbitrary SQL](./api/validate-arbitrary-sql) | `/api/v1/database/{pk}/validate_sql/` |
| `GET` | [Get names of databases currently available](./api/get-names-of-databases-currently-available) | `/api/v1/database/available/` |
| `GET` | [Download database(s) and associated dataset(s) as a zip file](./api/download-database-s-and-associated-dataset-s-as-a-zip-file) | `/api/v1/database/export/` |
| `POST` | [Import database(s) with associated datasets](./api/import-database-s-with-associated-datasets) | `/api/v1/database/import/` |
| `GET` | [Receive personal access tokens from OAuth2](./api/receive-personal-access-tokens-from-oauth2) | `/api/v1/database/oauth2/` |
| `GET` | [Get related fields data (database-related-column-name)](./api/get-related-fields-data-database-related-column-name) | `/api/v1/database/related/{column_name}` |
| `POST` | [Test a database connection](./api/test-a-database-connection) | `/api/v1/database/test_connection/` |
| `POST` | [Upload a file and returns file metadata](./api/upload-a-file-and-returns-file-metadata) | `/api/v1/database/upload_metadata/` |
| `POST` | [Validate database connection parameters](./api/validate-database-connection-parameters) | `/api/v1/database/validate_parameters/` |
</details>
@@ -192,7 +192,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Assemble Explore related information in a single endpoint](/developer-docs/api/assemble-explore-related-information-in-a-single-endpoint) | `/api/v1/explore/` |
| `GET` | [Assemble Explore related information in a single endpoint](./api/assemble-explore-related-information-in-a-single-endpoint) | `/api/v1/explore/` |
</details>
@@ -201,12 +201,12 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get the bootstrap data for SqlLab page](/developer-docs/api/get-the-bootstrap-data-for-sqllab-page) | `/api/v1/sqllab/` |
| `POST` | [Estimate the SQL query execution cost](/developer-docs/api/estimate-the-sql-query-execution-cost) | `/api/v1/sqllab/estimate/` |
| `POST` | [Execute a SQL query](/developer-docs/api/execute-a-sql-query) | `/api/v1/sqllab/execute/` |
| `GET` | [Export the SQL query results to a CSV](/developer-docs/api/export-the-sql-query-results-to-a-csv) | `/api/v1/sqllab/export/{client_id}/` |
| `POST` | [Format SQL code](/developer-docs/api/format-sql-code) | `/api/v1/sqllab/format_sql/` |
| `GET` | [Get the result of a SQL query execution](/developer-docs/api/get-the-result-of-a-sql-query-execution) | `/api/v1/sqllab/results/` |
| `GET` | [Get the bootstrap data for SqlLab page](./api/get-the-bootstrap-data-for-sqllab-page) | `/api/v1/sqllab/` |
| `POST` | [Estimate the SQL query execution cost](./api/estimate-the-sql-query-execution-cost) | `/api/v1/sqllab/estimate/` |
| `POST` | [Execute a SQL query](./api/execute-a-sql-query) | `/api/v1/sqllab/execute/` |
| `GET` | [Export the SQL query results to a CSV](./api/export-the-sql-query-results-to-a-csv) | `/api/v1/sqllab/export/{client_id}/` |
| `POST` | [Format SQL code](./api/format-sql-code) | `/api/v1/sqllab/format_sql/` |
| `GET` | [Get the result of a SQL query execution](./api/get-the-result-of-a-sql-query-execution) | `/api/v1/sqllab/results/` |
</details>
@@ -215,23 +215,23 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get a list of queries](/developer-docs/api/get-a-list-of-queries) | `/api/v1/query/` |
| `GET` | [Get query detail information](/developer-docs/api/get-query-detail-information) | `/api/v1/query/{pk}` |
| `GET` | [Get distinct values from field data (query-distinct-column-name)](/developer-docs/api/get-distinct-values-from-field-data-query-distinct-column-name) | `/api/v1/query/distinct/{column_name}` |
| `GET` | [Get related fields data (query-related-column-name)](/developer-docs/api/get-related-fields-data-query-related-column-name) | `/api/v1/query/related/{column_name}` |
| `POST` | [Manually stop a query with client_id](/developer-docs/api/manually-stop-a-query-with-client-id) | `/api/v1/query/stop` |
| `GET` | [Get a list of queries that changed after last_updated_ms](/developer-docs/api/get-a-list-of-queries-that-changed-after-last-updated-ms) | `/api/v1/query/updated_since` |
| `DELETE` | [Bulk delete saved queries](/developer-docs/api/bulk-delete-saved-queries) | `/api/v1/saved_query/` |
| `GET` | [Get a list of saved queries](/developer-docs/api/get-a-list-of-saved-queries) | `/api/v1/saved_query/` |
| `POST` | [Create a saved query](/developer-docs/api/create-a-saved-query) | `/api/v1/saved_query/` |
| `GET` | [Get metadata information about this API resource (saved-query--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-saved-query-info) | `/api/v1/saved_query/_info` |
| `DELETE` | [Delete a saved query](/developer-docs/api/delete-a-saved-query) | `/api/v1/saved_query/{pk}` |
| `GET` | [Get a saved query](/developer-docs/api/get-a-saved-query) | `/api/v1/saved_query/{pk}` |
| `PUT` | [Update a saved query](/developer-docs/api/update-a-saved-query) | `/api/v1/saved_query/{pk}` |
| `GET` | [Get distinct values from field data (saved-query-distinct-column-name)](/developer-docs/api/get-distinct-values-from-field-data-saved-query-distinct-column-name) | `/api/v1/saved_query/distinct/{column_name}` |
| `GET` | [Download multiple saved queries as YAML files](/developer-docs/api/download-multiple-saved-queries-as-yaml-files) | `/api/v1/saved_query/export/` |
| `POST` | [Import saved queries with associated databases](/developer-docs/api/import-saved-queries-with-associated-databases) | `/api/v1/saved_query/import/` |
| `GET` | [Get related fields data (saved-query-related-column-name)](/developer-docs/api/get-related-fields-data-saved-query-related-column-name) | `/api/v1/saved_query/related/{column_name}` |
| `GET` | [Get a list of queries](./api/get-a-list-of-queries) | `/api/v1/query/` |
| `GET` | [Get query detail information](./api/get-query-detail-information) | `/api/v1/query/{pk}` |
| `GET` | [Get distinct values from field data (query-distinct-column-name)](./api/get-distinct-values-from-field-data-query-distinct-column-name) | `/api/v1/query/distinct/{column_name}` |
| `GET` | [Get related fields data (query-related-column-name)](./api/get-related-fields-data-query-related-column-name) | `/api/v1/query/related/{column_name}` |
| `POST` | [Manually stop a query with client_id](./api/manually-stop-a-query-with-client-id) | `/api/v1/query/stop` |
| `GET` | [Get a list of queries that changed after last_updated_ms](./api/get-a-list-of-queries-that-changed-after-last-updated-ms) | `/api/v1/query/updated_since` |
| `DELETE` | [Bulk delete saved queries](./api/bulk-delete-saved-queries) | `/api/v1/saved_query/` |
| `GET` | [Get a list of saved queries](./api/get-a-list-of-saved-queries) | `/api/v1/saved_query/` |
| `POST` | [Create a saved query](./api/create-a-saved-query) | `/api/v1/saved_query/` |
| `GET` | [Get metadata information about this API resource (saved-query--info)](./api/get-metadata-information-about-this-api-resource-saved-query-info) | `/api/v1/saved_query/_info` |
| `DELETE` | [Delete a saved query](./api/delete-a-saved-query) | `/api/v1/saved_query/{pk}` |
| `GET` | [Get a saved query](./api/get-a-saved-query) | `/api/v1/saved_query/{pk}` |
| `PUT` | [Update a saved query](./api/update-a-saved-query) | `/api/v1/saved_query/{pk}` |
| `GET` | [Get distinct values from field data (saved-query-distinct-column-name)](./api/get-distinct-values-from-field-data-saved-query-distinct-column-name) | `/api/v1/saved_query/distinct/{column_name}` |
| `GET` | [Download multiple saved queries as YAML files](./api/download-multiple-saved-queries-as-yaml-files) | `/api/v1/saved_query/export/` |
| `POST` | [Import saved queries with associated databases](./api/import-saved-queries-with-associated-databases) | `/api/v1/saved_query/import/` |
| `GET` | [Get related fields data (saved-query-related-column-name)](./api/get-related-fields-data-saved-query-related-column-name) | `/api/v1/saved_query/related/{column_name}` |
</details>
@@ -240,7 +240,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get possible values for a datasource column](/developer-docs/api/get-possible-values-for-a-datasource-column) | `/api/v1/datasource/{datasource_type}/{datasource_id}/column/{column_name}/values/` |
| `GET` | [Get possible values for a datasource column](./api/get-possible-values-for-a-datasource-column) | `/api/v1/datasource/{datasource_type}/{datasource_id}/column/{column_name}/values/` |
</details>
@@ -249,8 +249,8 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Return an AdvancedDataTypeResponse](/developer-docs/api/return-an-advanceddatatyperesponse) | `/api/v1/advanced_data_type/convert` |
| `GET` | [Return a list of available advanced data types](/developer-docs/api/return-a-list-of-available-advanced-data-types) | `/api/v1/advanced_data_type/types` |
| `GET` | [Return an AdvancedDataTypeResponse](./api/return-an-advanceddatatyperesponse) | `/api/v1/advanced_data_type/convert` |
| `GET` | [Return a list of available advanced data types](./api/return-a-list-of-available-advanced-data-types) | `/api/v1/advanced_data_type/types` |
</details>
@@ -261,21 +261,21 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete tags](/developer-docs/api/bulk-delete-tags) | `/api/v1/tag/` |
| `GET` | [Get a list of tags](/developer-docs/api/get-a-list-of-tags) | `/api/v1/tag/` |
| `POST` | [Create a tag](/developer-docs/api/create-a-tag) | `/api/v1/tag/` |
| `GET` | [Get metadata information about tag API endpoints](/developer-docs/api/get-metadata-information-about-tag-api-endpoints) | `/api/v1/tag/_info` |
| `POST` | [Add tags to an object](/developer-docs/api/add-tags-to-an-object) | `/api/v1/tag/{object_type}/{object_id}/` |
| `DELETE` | [Delete a tagged object](/developer-docs/api/delete-a-tagged-object) | `/api/v1/tag/{object_type}/{object_id}/{tag}/` |
| `DELETE` | [Delete a tag](/developer-docs/api/delete-a-tag) | `/api/v1/tag/{pk}` |
| `GET` | [Get a tag detail information](/developer-docs/api/get-a-tag-detail-information) | `/api/v1/tag/{pk}` |
| `PUT` | [Update a tag](/developer-docs/api/update-a-tag) | `/api/v1/tag/{pk}` |
| `DELETE` | [Delete tag by pk favorites](/developer-docs/api/delete-tag-by-pk-favorites) | `/api/v1/tag/{pk}/favorites/` |
| `POST` | [Create tag by pk favorites](/developer-docs/api/create-tag-by-pk-favorites) | `/api/v1/tag/{pk}/favorites/` |
| `POST` | [Bulk create tags and tagged objects](/developer-docs/api/bulk-create-tags-and-tagged-objects) | `/api/v1/tag/bulk_create` |
| `GET` | [Get tag favorite status](/developer-docs/api/get-tag-favorite-status) | `/api/v1/tag/favorite_status/` |
| `GET` | [Get all objects associated with a tag](/developer-docs/api/get-all-objects-associated-with-a-tag) | `/api/v1/tag/get_objects/` |
| `GET` | [Get related fields data (tag-related-column-name)](/developer-docs/api/get-related-fields-data-tag-related-column-name) | `/api/v1/tag/related/{column_name}` |
| `DELETE` | [Bulk delete tags](./api/bulk-delete-tags) | `/api/v1/tag/` |
| `GET` | [Get a list of tags](./api/get-a-list-of-tags) | `/api/v1/tag/` |
| `POST` | [Create a tag](./api/create-a-tag) | `/api/v1/tag/` |
| `GET` | [Get metadata information about tag API endpoints](./api/get-metadata-information-about-tag-api-endpoints) | `/api/v1/tag/_info` |
| `POST` | [Add tags to an object](./api/add-tags-to-an-object) | `/api/v1/tag/{object_type}/{object_id}/` |
| `DELETE` | [Delete a tagged object](./api/delete-a-tagged-object) | `/api/v1/tag/{object_type}/{object_id}/{tag}/` |
| `DELETE` | [Delete a tag](./api/delete-a-tag) | `/api/v1/tag/{pk}` |
| `GET` | [Get a tag detail information](./api/get-a-tag-detail-information) | `/api/v1/tag/{pk}` |
| `PUT` | [Update a tag](./api/update-a-tag) | `/api/v1/tag/{pk}` |
| `DELETE` | [Delete tag by pk favorites](./api/delete-tag-by-pk-favorites) | `/api/v1/tag/{pk}/favorites/` |
| `POST` | [Create tag by pk favorites](./api/create-tag-by-pk-favorites) | `/api/v1/tag/{pk}/favorites/` |
| `POST` | [Bulk create tags and tagged objects](./api/bulk-create-tags-and-tagged-objects) | `/api/v1/tag/bulk_create` |
| `GET` | [Get tag favorite status](./api/get-tag-favorite-status) | `/api/v1/tag/favorite_status/` |
| `GET` | [Get all objects associated with a tag](./api/get-all-objects-associated-with-a-tag) | `/api/v1/tag/get_objects/` |
| `GET` | [Get related fields data (tag-related-column-name)](./api/get-related-fields-data-tag-related-column-name) | `/api/v1/tag/related/{column_name}` |
</details>
@@ -284,20 +284,20 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Delete multiple annotation layers in a bulk operation](/developer-docs/api/delete-multiple-annotation-layers-in-a-bulk-operation) | `/api/v1/annotation_layer/` |
| `GET` | [Get a list of annotation layers (annotation-layer)](/developer-docs/api/get-a-list-of-annotation-layers-annotation-layer) | `/api/v1/annotation_layer/` |
| `POST` | [Create an annotation layer (annotation-layer)](/developer-docs/api/create-an-annotation-layer-annotation-layer) | `/api/v1/annotation_layer/` |
| `GET` | [Get metadata information about this API resource (annotation-layer--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-annotation-layer-info) | `/api/v1/annotation_layer/_info` |
| `DELETE` | [Delete annotation layer (annotation-layer-pk)](/developer-docs/api/delete-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
| `GET` | [Get an annotation layer (annotation-layer-pk)](/developer-docs/api/get-an-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
| `PUT` | [Update an annotation layer (annotation-layer-pk)](/developer-docs/api/update-an-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
| `DELETE` | [Bulk delete annotation layers](/developer-docs/api/bulk-delete-annotation-layers) | `/api/v1/annotation_layer/{pk}/annotation/` |
| `GET` | [Get a list of annotation layers (annotation-layer-pk-annotation)](/developer-docs/api/get-a-list-of-annotation-layers-annotation-layer-pk-annotation) | `/api/v1/annotation_layer/{pk}/annotation/` |
| `POST` | [Create an annotation layer (annotation-layer-pk-annotation)](/developer-docs/api/create-an-annotation-layer-annotation-layer-pk-annotation) | `/api/v1/annotation_layer/{pk}/annotation/` |
| `DELETE` | [Delete annotation layer (annotation-layer-pk-annotation-annotation-id)](/developer-docs/api/delete-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
| `GET` | [Get an annotation layer (annotation-layer-pk-annotation-annotation-id)](/developer-docs/api/get-an-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
| `PUT` | [Update an annotation layer (annotation-layer-pk-annotation-annotation-id)](/developer-docs/api/update-an-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
| `GET` | [Get related fields data (annotation-layer-related-column-name)](/developer-docs/api/get-related-fields-data-annotation-layer-related-column-name) | `/api/v1/annotation_layer/related/{column_name}` |
| `DELETE` | [Delete multiple annotation layers in a bulk operation](./api/delete-multiple-annotation-layers-in-a-bulk-operation) | `/api/v1/annotation_layer/` |
| `GET` | [Get a list of annotation layers (annotation-layer)](./api/get-a-list-of-annotation-layers-annotation-layer) | `/api/v1/annotation_layer/` |
| `POST` | [Create an annotation layer (annotation-layer)](./api/create-an-annotation-layer-annotation-layer) | `/api/v1/annotation_layer/` |
| `GET` | [Get metadata information about this API resource (annotation-layer--info)](./api/get-metadata-information-about-this-api-resource-annotation-layer-info) | `/api/v1/annotation_layer/_info` |
| `DELETE` | [Delete annotation layer (annotation-layer-pk)](./api/delete-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
| `GET` | [Get an annotation layer (annotation-layer-pk)](./api/get-an-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
| `PUT` | [Update an annotation layer (annotation-layer-pk)](./api/update-an-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
| `DELETE` | [Bulk delete annotation layers](./api/bulk-delete-annotation-layers) | `/api/v1/annotation_layer/{pk}/annotation/` |
| `GET` | [Get a list of annotation layers (annotation-layer-pk-annotation)](./api/get-a-list-of-annotation-layers-annotation-layer-pk-annotation) | `/api/v1/annotation_layer/{pk}/annotation/` |
| `POST` | [Create an annotation layer (annotation-layer-pk-annotation)](./api/create-an-annotation-layer-annotation-layer-pk-annotation) | `/api/v1/annotation_layer/{pk}/annotation/` |
| `DELETE` | [Delete annotation layer (annotation-layer-pk-annotation-annotation-id)](./api/delete-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
| `GET` | [Get an annotation layer (annotation-layer-pk-annotation-annotation-id)](./api/get-an-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
| `PUT` | [Update an annotation layer (annotation-layer-pk-annotation-annotation-id)](./api/update-an-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
| `GET` | [Get related fields data (annotation-layer-related-column-name)](./api/get-related-fields-data-annotation-layer-related-column-name) | `/api/v1/annotation_layer/related/{column_name}` |
</details>
@@ -306,14 +306,14 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete CSS templates](/developer-docs/api/bulk-delete-css-templates) | `/api/v1/css_template/` |
| `GET` | [Get a list of CSS templates](/developer-docs/api/get-a-list-of-css-templates) | `/api/v1/css_template/` |
| `POST` | [Create a CSS template](/developer-docs/api/create-a-css-template) | `/api/v1/css_template/` |
| `GET` | [Get metadata information about this API resource (css-template--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-css-template-info) | `/api/v1/css_template/_info` |
| `DELETE` | [Delete a CSS template](/developer-docs/api/delete-a-css-template) | `/api/v1/css_template/{pk}` |
| `GET` | [Get a CSS template](/developer-docs/api/get-a-css-template) | `/api/v1/css_template/{pk}` |
| `PUT` | [Update a CSS template](/developer-docs/api/update-a-css-template) | `/api/v1/css_template/{pk}` |
| `GET` | [Get related fields data (css-template-related-column-name)](/developer-docs/api/get-related-fields-data-css-template-related-column-name) | `/api/v1/css_template/related/{column_name}` |
| `DELETE` | [Bulk delete CSS templates](./api/bulk-delete-css-templates) | `/api/v1/css_template/` |
| `GET` | [Get a list of CSS templates](./api/get-a-list-of-css-templates) | `/api/v1/css_template/` |
| `POST` | [Create a CSS template](./api/create-a-css-template) | `/api/v1/css_template/` |
| `GET` | [Get metadata information about this API resource (css-template--info)](./api/get-metadata-information-about-this-api-resource-css-template-info) | `/api/v1/css_template/_info` |
| `DELETE` | [Delete a CSS template](./api/delete-a-css-template) | `/api/v1/css_template/{pk}` |
| `GET` | [Get a CSS template](./api/get-a-css-template) | `/api/v1/css_template/{pk}` |
| `PUT` | [Update a CSS template](./api/update-a-css-template) | `/api/v1/css_template/{pk}` |
| `GET` | [Get related fields data (css-template-related-column-name)](./api/get-related-fields-data-css-template-related-column-name) | `/api/v1/css_template/related/{column_name}` |
</details>
@@ -324,8 +324,8 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a new dashboard's permanent link](/developer-docs/api/create-a-new-dashboard-s-permanent-link) | `/api/v1/dashboard/{pk}/permalink` |
| `GET` | [Get dashboard's permanent link state](/developer-docs/api/get-dashboard-s-permanent-link-state) | `/api/v1/dashboard/permalink/{key}` |
| `POST` | [Create a new dashboard's permanent link](./api/create-a-new-dashboard-s-permanent-link) | `/api/v1/dashboard/{pk}/permalink` |
| `GET` | [Get dashboard's permanent link state](./api/get-dashboard-s-permanent-link-state) | `/api/v1/dashboard/permalink/{key}` |
</details>
@@ -334,8 +334,8 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a new permanent link (explore-permalink)](/developer-docs/api/create-a-new-permanent-link-explore-permalink) | `/api/v1/explore/permalink` |
| `GET` | [Get chart's permanent link state](/developer-docs/api/get-chart-s-permanent-link-state) | `/api/v1/explore/permalink/{key}` |
| `POST` | [Create a new permanent link (explore-permalink)](./api/create-a-new-permanent-link-explore-permalink) | `/api/v1/explore/permalink` |
| `GET` | [Get chart's permanent link state](./api/get-chart-s-permanent-link-state) | `/api/v1/explore/permalink/{key}` |
</details>
@@ -344,8 +344,8 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a new permanent link (sqllab-permalink)](/developer-docs/api/create-a-new-permanent-link-sqllab-permalink) | `/api/v1/sqllab/permalink` |
| `GET` | [Get permanent link state for SQLLab editor.](/developer-docs/api/get-permanent-link-state-for-sqllab-editor) | `/api/v1/sqllab/permalink/{key}` |
| `POST` | [Create a new permanent link (sqllab-permalink)](./api/create-a-new-permanent-link-sqllab-permalink) | `/api/v1/sqllab/permalink` |
| `GET` | [Get permanent link state for SQLLab editor.](./api/get-permanent-link-state-for-sqllab-editor) | `/api/v1/sqllab/permalink/{key}` |
</details>
@@ -354,7 +354,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get a report schedule log (embedded-dashboard-uuid)](/developer-docs/api/get-a-report-schedule-log-embedded-dashboard-uuid) | `/api/v1/embedded_dashboard/{uuid}` |
| `GET` | [Get a report schedule log (embedded-dashboard-uuid)](./api/get-a-report-schedule-log-embedded-dashboard-uuid) | `/api/v1/embedded_dashboard/{uuid}` |
</details>
@@ -363,10 +363,10 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a dashboard's filter state](/developer-docs/api/create-a-dashboard-s-filter-state) | `/api/v1/dashboard/{pk}/filter_state` |
| `DELETE` | [Delete a dashboard's filter state value](/developer-docs/api/delete-a-dashboard-s-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
| `GET` | [Get a dashboard's filter state value](/developer-docs/api/get-a-dashboard-s-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
| `PUT` | [Update a dashboard's filter state value](/developer-docs/api/update-a-dashboard-s-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
| `POST` | [Create a dashboard's filter state](./api/create-a-dashboard-s-filter-state) | `/api/v1/dashboard/{pk}/filter_state` |
| `DELETE` | [Delete a dashboard's filter state value](./api/delete-a-dashboard-s-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
| `GET` | [Get a dashboard's filter state value](./api/get-a-dashboard-s-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
| `PUT` | [Update a dashboard's filter state value](./api/update-a-dashboard-s-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
</details>
@@ -375,10 +375,10 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a new form_data](/developer-docs/api/create-a-new-form-data) | `/api/v1/explore/form_data` |
| `DELETE` | [Delete a form_data](/developer-docs/api/delete-a-form-data) | `/api/v1/explore/form_data/{key}` |
| `GET` | [Get a form_data](/developer-docs/api/get-a-form-data) | `/api/v1/explore/form_data/{key}` |
| `PUT` | [Update an existing form_data](/developer-docs/api/update-an-existing-form-data) | `/api/v1/explore/form_data/{key}` |
| `POST` | [Create a new form_data](./api/create-a-new-form-data) | `/api/v1/explore/form_data` |
| `DELETE` | [Delete a form_data](./api/delete-a-form-data) | `/api/v1/explore/form_data/{key}` |
| `GET` | [Get a form_data](./api/get-a-form-data) | `/api/v1/explore/form_data/{key}` |
| `PUT` | [Update an existing form_data](./api/update-an-existing-form-data) | `/api/v1/explore/form_data/{key}` |
</details>
@@ -389,17 +389,17 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete report schedules](/developer-docs/api/bulk-delete-report-schedules) | `/api/v1/report/` |
| `GET` | [Get a list of report schedules](/developer-docs/api/get-a-list-of-report-schedules) | `/api/v1/report/` |
| `POST` | [Create a report schedule](/developer-docs/api/create-a-report-schedule) | `/api/v1/report/` |
| `GET` | [Get metadata information about this API resource (report--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-report-info) | `/api/v1/report/_info` |
| `DELETE` | [Delete a report schedule](/developer-docs/api/delete-a-report-schedule) | `/api/v1/report/{pk}` |
| `GET` | [Get a report schedule](/developer-docs/api/get-a-report-schedule) | `/api/v1/report/{pk}` |
| `PUT` | [Update a report schedule](/developer-docs/api/update-a-report-schedule) | `/api/v1/report/{pk}` |
| `GET` | [Get a list of report schedule logs](/developer-docs/api/get-a-list-of-report-schedule-logs) | `/api/v1/report/{pk}/log/` |
| `GET` | [Get a report schedule log (report-pk-log-log-id)](/developer-docs/api/get-a-report-schedule-log-report-pk-log-log-id) | `/api/v1/report/{pk}/log/{log_id}` |
| `GET` | [Get related fields data (report-related-column-name)](/developer-docs/api/get-related-fields-data-report-related-column-name) | `/api/v1/report/related/{column_name}` |
| `GET` | [Get slack channels](/developer-docs/api/get-slack-channels) | `/api/v1/report/slack_channels/` |
| `DELETE` | [Bulk delete report schedules](./api/bulk-delete-report-schedules) | `/api/v1/report/` |
| `GET` | [Get a list of report schedules](./api/get-a-list-of-report-schedules) | `/api/v1/report/` |
| `POST` | [Create a report schedule](./api/create-a-report-schedule) | `/api/v1/report/` |
| `GET` | [Get metadata information about this API resource (report--info)](./api/get-metadata-information-about-this-api-resource-report-info) | `/api/v1/report/_info` |
| `DELETE` | [Delete a report schedule](./api/delete-a-report-schedule) | `/api/v1/report/{pk}` |
| `GET` | [Get a report schedule](./api/get-a-report-schedule) | `/api/v1/report/{pk}` |
| `PUT` | [Update a report schedule](./api/update-a-report-schedule) | `/api/v1/report/{pk}` |
| `GET` | [Get a list of report schedule logs](./api/get-a-list-of-report-schedule-logs) | `/api/v1/report/{pk}/log/` |
| `GET` | [Get a report schedule log (report-pk-log-log-id)](./api/get-a-report-schedule-log-report-pk-log-log-id) | `/api/v1/report/{pk}/log/{log_id}` |
| `GET` | [Get related fields data (report-related-column-name)](./api/get-related-fields-data-report-related-column-name) | `/api/v1/report/related/{column_name}` |
| `GET` | [Get slack channels](./api/get-slack-channels) | `/api/v1/report/slack_channels/` |
</details>
@@ -410,16 +410,16 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security roles](/developer-docs/api/get-security-roles) | `/api/v1/security/roles/` |
| `POST` | [Create security roles](/developer-docs/api/create-security-roles) | `/api/v1/security/roles/` |
| `GET` | [Get security roles info](/developer-docs/api/get-security-roles-info) | `/api/v1/security/roles/_info` |
| `DELETE` | [Delete security roles by pk](/developer-docs/api/delete-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
| `GET` | [Get security roles by pk](/developer-docs/api/get-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
| `PUT` | [Update security roles by pk](/developer-docs/api/update-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
| `POST` | [Create security roles by role_id permissions](/developer-docs/api/create-security-roles-by-role-id-permissions) | `/api/v1/security/roles/{role_id}/permissions` |
| `GET` | [Get security roles by role_id permissions](/developer-docs/api/get-security-roles-by-role-id-permissions) | `/api/v1/security/roles/{role_id}/permissions/` |
| `PUT` | [Update security roles by role_id users](/developer-docs/api/update-security-roles-by-role-id-users) | `/api/v1/security/roles/{role_id}/users` |
| `GET` | [List roles](/developer-docs/api/list-roles) | `/api/v1/security/roles/search/` |
| `GET` | [Get security roles](./api/get-security-roles) | `/api/v1/security/roles/` |
| `POST` | [Create security roles](./api/create-security-roles) | `/api/v1/security/roles/` |
| `GET` | [Get security roles info](./api/get-security-roles-info) | `/api/v1/security/roles/_info` |
| `DELETE` | [Delete security roles by pk](./api/delete-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
| `GET` | [Get security roles by pk](./api/get-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
| `PUT` | [Update security roles by pk](./api/update-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
| `POST` | [Create security roles by role_id permissions](./api/create-security-roles-by-role-id-permissions) | `/api/v1/security/roles/{role_id}/permissions` |
| `GET` | [Get security roles by role_id permissions](./api/get-security-roles-by-role-id-permissions) | `/api/v1/security/roles/{role_id}/permissions/` |
| `PUT` | [Update security roles by role_id users](./api/update-security-roles-by-role-id-users) | `/api/v1/security/roles/{role_id}/users` |
| `GET` | [List roles](./api/list-roles) | `/api/v1/security/roles/search/` |
</details>
@@ -428,12 +428,12 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security users](/developer-docs/api/get-security-users) | `/api/v1/security/users/` |
| `POST` | [Create security users](/developer-docs/api/create-security-users) | `/api/v1/security/users/` |
| `GET` | [Get security users info](/developer-docs/api/get-security-users-info) | `/api/v1/security/users/_info` |
| `DELETE` | [Delete security users by pk](/developer-docs/api/delete-security-users-by-pk) | `/api/v1/security/users/{pk}` |
| `GET` | [Get security users by pk](/developer-docs/api/get-security-users-by-pk) | `/api/v1/security/users/{pk}` |
| `PUT` | [Update security users by pk](/developer-docs/api/update-security-users-by-pk) | `/api/v1/security/users/{pk}` |
| `GET` | [Get security users](./api/get-security-users) | `/api/v1/security/users/` |
| `POST` | [Create security users](./api/create-security-users) | `/api/v1/security/users/` |
| `GET` | [Get security users info](./api/get-security-users-info) | `/api/v1/security/users/_info` |
| `DELETE` | [Delete security users by pk](./api/delete-security-users-by-pk) | `/api/v1/security/users/{pk}` |
| `GET` | [Get security users by pk](./api/get-security-users-by-pk) | `/api/v1/security/users/{pk}` |
| `PUT` | [Update security users by pk](./api/update-security-users-by-pk) | `/api/v1/security/users/{pk}` |
</details>
@@ -442,9 +442,9 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security permissions](/developer-docs/api/get-security-permissions) | `/api/v1/security/permissions/` |
| `GET` | [Get security permissions info](/developer-docs/api/get-security-permissions-info) | `/api/v1/security/permissions/_info` |
| `GET` | [Get security permissions by pk](/developer-docs/api/get-security-permissions-by-pk) | `/api/v1/security/permissions/{pk}` |
| `GET` | [Get security permissions](./api/get-security-permissions) | `/api/v1/security/permissions/` |
| `GET` | [Get security permissions info](./api/get-security-permissions-info) | `/api/v1/security/permissions/_info` |
| `GET` | [Get security permissions by pk](./api/get-security-permissions-by-pk) | `/api/v1/security/permissions/{pk}` |
</details>
@@ -453,12 +453,12 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security resources](/developer-docs/api/get-security-resources) | `/api/v1/security/resources/` |
| `POST` | [Create security resources](/developer-docs/api/create-security-resources) | `/api/v1/security/resources/` |
| `GET` | [Get security resources info](/developer-docs/api/get-security-resources-info) | `/api/v1/security/resources/_info` |
| `DELETE` | [Delete security resources by pk](/developer-docs/api/delete-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
| `GET` | [Get security resources by pk](/developer-docs/api/get-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
| `PUT` | [Update security resources by pk](/developer-docs/api/update-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
| `GET` | [Get security resources](./api/get-security-resources) | `/api/v1/security/resources/` |
| `POST` | [Create security resources](./api/create-security-resources) | `/api/v1/security/resources/` |
| `GET` | [Get security resources info](./api/get-security-resources-info) | `/api/v1/security/resources/_info` |
| `DELETE` | [Delete security resources by pk](./api/delete-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
| `GET` | [Get security resources by pk](./api/get-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
| `PUT` | [Update security resources by pk](./api/update-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
</details>
@@ -467,12 +467,12 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security permissions resources](/developer-docs/api/get-security-permissions-resources) | `/api/v1/security/permissions-resources/` |
| `POST` | [Create security permissions resources](/developer-docs/api/create-security-permissions-resources) | `/api/v1/security/permissions-resources/` |
| `GET` | [Get security permissions resources info](/developer-docs/api/get-security-permissions-resources-info) | `/api/v1/security/permissions-resources/_info` |
| `DELETE` | [Delete security permissions resources by pk](/developer-docs/api/delete-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
| `GET` | [Get security permissions resources by pk](/developer-docs/api/get-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
| `PUT` | [Update security permissions resources by pk](/developer-docs/api/update-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
| `GET` | [Get security permissions resources](./api/get-security-permissions-resources) | `/api/v1/security/permissions-resources/` |
| `POST` | [Create security permissions resources](./api/create-security-permissions-resources) | `/api/v1/security/permissions-resources/` |
| `GET` | [Get security permissions resources info](./api/get-security-permissions-resources-info) | `/api/v1/security/permissions-resources/_info` |
| `DELETE` | [Delete security permissions resources by pk](./api/delete-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
| `GET` | [Get security permissions resources by pk](./api/get-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
| `PUT` | [Update security permissions resources by pk](./api/update-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
</details>
@@ -481,14 +481,14 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete RLS rules](/developer-docs/api/bulk-delete-rls-rules) | `/api/v1/rowlevelsecurity/` |
| `GET` | [Get a list of RLS](/developer-docs/api/get-a-list-of-rls) | `/api/v1/rowlevelsecurity/` |
| `POST` | [Create a new RLS rule](/developer-docs/api/create-a-new-rls-rule) | `/api/v1/rowlevelsecurity/` |
| `GET` | [Get metadata information about this API resource (rowlevelsecurity--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-rowlevelsecurity-info) | `/api/v1/rowlevelsecurity/_info` |
| `DELETE` | [Delete an RLS](/developer-docs/api/delete-an-rls) | `/api/v1/rowlevelsecurity/{pk}` |
| `GET` | [Get an RLS](/developer-docs/api/get-an-rls) | `/api/v1/rowlevelsecurity/{pk}` |
| `PUT` | [Update an RLS rule](/developer-docs/api/update-an-rls-rule) | `/api/v1/rowlevelsecurity/{pk}` |
| `GET` | [Get related fields data (rowlevelsecurity-related-column-name)](/developer-docs/api/get-related-fields-data-rowlevelsecurity-related-column-name) | `/api/v1/rowlevelsecurity/related/{column_name}` |
| `DELETE` | [Bulk delete RLS rules](./api/bulk-delete-rls-rules) | `/api/v1/rowlevelsecurity/` |
| `GET` | [Get a list of RLS](./api/get-a-list-of-rls) | `/api/v1/rowlevelsecurity/` |
| `POST` | [Create a new RLS rule](./api/create-a-new-rls-rule) | `/api/v1/rowlevelsecurity/` |
| `GET` | [Get metadata information about this API resource (rowlevelsecurity--info)](./api/get-metadata-information-about-this-api-resource-rowlevelsecurity-info) | `/api/v1/rowlevelsecurity/_info` |
| `DELETE` | [Delete an RLS](./api/delete-an-rls) | `/api/v1/rowlevelsecurity/{pk}` |
| `GET` | [Get an RLS](./api/get-an-rls) | `/api/v1/rowlevelsecurity/{pk}` |
| `PUT` | [Update an RLS rule](./api/update-an-rls-rule) | `/api/v1/rowlevelsecurity/{pk}` |
| `GET` | [Get related fields data (rowlevelsecurity-related-column-name)](./api/get-related-fields-data-rowlevelsecurity-related-column-name) | `/api/v1/rowlevelsecurity/related/{column_name}` |
</details>
@@ -499,8 +499,8 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Export all assets](/developer-docs/api/export-all-assets) | `/api/v1/assets/export/` |
| `POST` | [Import multiple assets](/developer-docs/api/import-multiple-assets) | `/api/v1/assets/import/` |
| `GET` | [Export all assets](./api/export-all-assets) | `/api/v1/assets/export/` |
| `POST` | [Import multiple assets](./api/import-multiple-assets) | `/api/v1/assets/import/` |
</details>
@@ -509,7 +509,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Invalidate cache records and remove the database records](/developer-docs/api/invalidate-cache-records-and-remove-the-database-records) | `/api/v1/cachekey/invalidate` |
| `POST` | [Invalidate cache records and remove the database records](./api/invalidate-cache-records-and-remove-the-database-records) | `/api/v1/cachekey/invalidate` |
</details>
@@ -518,10 +518,10 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get a list of logs](/developer-docs/api/get-a-list-of-logs) | `/api/v1/log/` |
| `POST` | [Create log](/developer-docs/api/create-log) | `/api/v1/log/` |
| `GET` | [Get a log detail information](/developer-docs/api/get-a-log-detail-information) | `/api/v1/log/{pk}` |
| `GET` | [Get recent activity data for a user](/developer-docs/api/get-recent-activity-data-for-a-user) | `/api/v1/log/recent_activity/` |
| `GET` | [Get a list of logs](./api/get-a-list-of-logs) | `/api/v1/log/` |
| `POST` | [Create log](./api/create-log) | `/api/v1/log/` |
| `GET` | [Get a log detail information](./api/get-a-log-detail-information) | `/api/v1/log/{pk}` |
| `GET` | [Get recent activity data for a user](./api/get-recent-activity-data-for-a-user) | `/api/v1/log/recent_activity/` |
</details>
@@ -532,8 +532,8 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get the user object](/developer-docs/api/get-the-user-object) | `/api/v1/me/` |
| `GET` | [Get the user roles](/developer-docs/api/get-the-user-roles) | `/api/v1/me/roles/` |
| `GET` | [Get the user object](./api/get-the-user-object) | `/api/v1/me/` |
| `GET` | [Get the user roles](./api/get-the-user-roles) | `/api/v1/me/roles/` |
</details>
@@ -542,7 +542,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get the user avatar](/developer-docs/api/get-the-user-avatar) | `/api/v1/user/{user_id}/avatar.png` |
| `GET` | [Get the user avatar](./api/get-the-user-avatar) | `/api/v1/user/{user_id}/avatar.png` |
</details>
@@ -551,7 +551,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get menu](/developer-docs/api/get-menu) | `/api/v1/menu/` |
| `GET` | [Get menu](./api/get-menu) | `/api/v1/menu/` |
</details>
@@ -560,7 +560,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get all available domains](/developer-docs/api/get-all-available-domains) | `/api/v1/available_domains/` |
| `GET` | [Get all available domains](./api/get-all-available-domains) | `/api/v1/available_domains/` |
</details>
@@ -569,7 +569,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Read off of the Redis events stream](/developer-docs/api/read-off-of-the-redis-events-stream) | `/api/v1/async_event/` |
| `GET` | [Read off of the Redis events stream](./api/read-off-of-the-redis-events-stream) | `/api/v1/async_event/` |
</details>
@@ -578,7 +578,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get api by version openapi](/developer-docs/api/get-api-by-version-openapi) | `/api/{version}/_openapi` |
| `GET` | [Get api by version openapi](./api/get-api-by-version-openapi) | `/api/{version}/_openapi` |
</details>

View File

@@ -485,7 +485,7 @@ Frontend assets (TypeScript, JavaScript, CSS, and images) must be compiled in or
First, be sure you are using the following versions of Node.js and npm:
- `Node.js`: Version 22 (LTS)
- `Node.js`: Version 20
- `npm`: Version 10
We recommend using [nvm](https://github.com/nvm-sh/nvm) to manage your node environment:

View File

@@ -33,15 +33,13 @@ The extension architecture is built on six core principles that guide all techni
### 1. Lean Core
Superset's core should remain minimal, with many features delegated to extensions. Built-in features use the same APIs and extension mechanisms available to external developers. This approach:
- Reduces maintenance burden and complexity
- Encourages modularity
- Allows the community to innovate independently of the main codebase
### 2. Explicit Contribution Points
All extension points are clearly defined and documented. Extension authors know exactly where and how they can interact with the host system. Both backend and frontend contributions are registered directly in code — backend contributions via classes decorated with `@api` (and other decorators) imported from the auto-discovered entrypoint, frontend contributions via calls like `views.registerView` and `commands.registerCommand` executed at module load time in `index.tsx`. This gives the host clear visibility into what each extension provides:
All extension points are clearly defined and documented. Extension authors know exactly where and how they can interact with the host system. Backend contributions are declared in `extension.json`. Frontend contributions are registered directly in code at module load time, giving the host clear visibility into what each extension provides:
- Manage the extension lifecycle
- Provide a consistent user experience
- Validate extension compatibility
@@ -49,7 +47,6 @@ All extension points are clearly defined and documented. Extension authors know
### 3. Versioned and Stable APIs
Public interfaces for extensions follow semantic versioning, allowing for:
- Safe evolution of the platform
- Backward compatibility
- Clear upgrade paths for extension authors
@@ -57,7 +54,6 @@ Public interfaces for extensions follow semantic versioning, allowing for:
### 4. Lazy Loading and Activation
Extensions are loaded and activated only when needed, which:
- Minimizes performance overhead
- Reduces resource consumption
- Improves startup time
@@ -65,7 +61,6 @@ Extensions are loaded and activated only when needed, which:
### 5. Composability and Reuse
The architecture encourages reusing extension points and patterns across different modules, promoting:
- Consistency across extensions
- Reduced duplication
- Shared best practices
@@ -85,7 +80,6 @@ Two core packages provide the foundation for extension development:
**Frontend: `@apache-superset/core`**
This package provides essential building blocks for frontend extensions and the host application:
- Shared UI components
- Utility functions
- APIs and hooks
@@ -96,7 +90,6 @@ By centralizing these resources, both extensions and built-in features use the s
**Backend: `apache-superset-core`**
This package exposes key classes and APIs for backend extensions:
- Database connectors
- API extensions
- Security manager customization
@@ -109,7 +102,6 @@ It includes dependencies on critical libraries like Flask-AppBuilder and SQLAlch
**`apache-superset-extensions-cli`**
The CLI provides comprehensive commands for extension development:
- Project scaffolding
- Code generation
- Building and bundling
@@ -122,7 +114,6 @@ By standardizing these processes, the CLI ensures extensions are built consisten
The Superset host application serves as the runtime environment for extensions:
**Extension Management**
- Exposes `/api/v1/extensions` endpoint for registration and management
- Provides a dedicated UI for managing extensions
- Stores extension metadata in the `extensions` database table
@@ -130,7 +121,6 @@ The Superset host application serves as the runtime environment for extensions:
**Extension Storage**
The extensions table contains:
- Extension name, version, and author
- Metadata and configuration
- Built frontend and/or backend code
@@ -142,7 +132,6 @@ The following diagram illustrates how these components work together:
<img width="955" height="586" alt="Extension System Architecture" src="https://github.com/user-attachments/assets/cc2a41df-55a4-48c8-b056-35f7a1e567c6" />
The diagram shows:
1. **Extension projects** depend on core packages for development
2. **Core packages** provide APIs and type definitions
3. **The host application** implements the APIs and manages extensions
@@ -162,25 +151,23 @@ The architecture leverages Webpack's Module Federation to enable dynamic loading
Extensions configure Webpack to expose their entry points:
```javascript
externalsType: 'window',
externals: {
'@apache-superset/core': 'superset',
},
plugins: [
new ModuleFederationPlugin({
name: 'my_extension',
filename: 'remoteEntry.[contenthash].js',
exposes: {
'./index': './src/index.tsx',
},
shared: {
react: { singleton: true, import: false },
'react-dom': { singleton: true, import: false },
antd: { singleton: true, import: false },
},
}),
]
``` typescript
new ModuleFederationPlugin({
name: 'my_extension',
filename: 'remoteEntry.[contenthash].js',
exposes: {
'./index': './src/index.tsx',
},
externalsType: 'window',
externals: {
'@apache-superset/core': 'superset',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
'antd-v5': { singleton: true }
}
})
```
This configuration does several important things:
@@ -208,12 +195,24 @@ Here's what happens at runtime:
On the Superset side, the APIs are mapped to `window.superset` during application bootstrap:
```typescript
``` typescript
import * as supersetCore from '@apache-superset/core';
import {
authentication,
core,
commands,
extensions,
sqlLab,
} from 'src/extensions';
export default function setupExtensionsAPI() {
window.superset = {
...supersetCore,
authentication,
core,
commands,
extensions,
sqlLab,
};
}
```

View File

@@ -23,7 +23,7 @@ sidebar_label: Alert
-->
import { StoryWithControls } from '../../../src/components/StorybookWrapper';
import { Alert } from '@apache-superset/core/components';
import { Alert } from '@apache-superset/core/ui';
# Alert
@@ -105,10 +105,10 @@ function Demo() {
## Usage in Extensions
This component is available in the `@apache-superset/core/components` package, which is automatically available to Superset extensions.
This component is available in the `@apache-superset/core/ui` package, which is automatically available to Superset extensions.
```tsx
import { Alert } from '@apache-superset/core/components';
import { Alert } from '@apache-superset/core/ui';
function MyExtension() {
return (

View File

@@ -25,7 +25,7 @@ sidebar_position: 1
# Extension Components
These UI components are available to Superset extension developers through the `@apache-superset/core/components` package. They provide a consistent look and feel with the rest of Superset and are designed to be used in extension panels, views, and other UI elements.
These UI components are available to Superset extension developers through the `@apache-superset/core/ui` package. They provide a consistent look and feel with the rest of Superset and are designed to be used in extension panels, views, and other UI elements.
## Available Components
@@ -33,10 +33,10 @@ These UI components are available to Superset extension developers through the `
## Usage
All components are exported from the `@apache-superset/core/components` package:
All components are exported from the `@apache-superset/core/ui` package:
```tsx
import { Alert } from '@apache-superset/core/components';
import { Alert } from '@apache-superset/core/ui';
export function MyExtensionPanel() {
return (
@@ -49,7 +49,7 @@ export function MyExtensionPanel() {
## Adding New Components
Components in `@apache-superset/core/components` are automatically documented here. To add a new extension component:
Components in `@apache-superset/core/ui` are automatically documented here. To add a new extension component:
1. Add the component to `superset-frontend/packages/superset-core/src/ui/components/`
2. Export it from `superset-frontend/packages/superset-core/src/ui/components/index.ts`

View File

@@ -28,7 +28,7 @@ To facilitate the development of extensions, we define a set of well-defined con
## Frontend
Frontend contribution types allow extensions to extend Superset's user interface with new views, commands, and menu items. Frontend contributions are registered directly in code from your extension's `index.tsx` entry point.
Frontend contribution types allow extensions to extend Superset's user interface with new views, commands, and menu items. Frontend contributions are registered directly in code from your extension's `index.tsx` entry point — they do not need to be declared in `extension.json`.
### Views
@@ -68,28 +68,25 @@ commands.registerCommand(
### Menus
Extensions can contribute new menu items or context menus to the host application, providing users with additional actions and options. Each menu item specifies the view and command to execute, the target area, and the location (`primary`, `secondary`, or `context`). Menu contribution areas are uniquely identified (e.g., `sqllab.editor` for the SQL Lab editor).
Extensions can contribute new menu items or context menus to the host application, providing users with additional actions and options. Each menu item specifies the target area, the command to execute, and its placement (primary, secondary, or context). Menu contribution areas are uniquely identified (e.g., `sqllab.editor` for the SQL Lab editor).
```typescript
import { menus } from '@apache-superset/core';
menus.registerMenuItem(
{ view: 'sqllab.editor', command: 'my-extension.copy-query' },
'sqllab.editor',
'primary',
);
menus.addMenuItem('sqllab.editor', {
placement: 'primary',
command: 'my-extension.copy-query',
});
menus.registerMenuItem(
{ view: 'sqllab.editor', command: 'my-extension.prettify' },
'sqllab.editor',
'secondary',
);
menus.addMenuItem('sqllab.editor', {
placement: 'secondary',
command: 'my-extension.prettify',
});
menus.registerMenuItem(
{ view: 'sqllab.editor', command: 'my-extension.clear' },
'sqllab.editor',
'context',
);
menus.addMenuItem('sqllab.editor', {
placement: 'context',
command: 'my-extension.clear',
});
```
### Editors
@@ -114,31 +111,24 @@ See [Editors Extension Point](./extension-points/editors) for implementation det
## Backend
Backend contribution types allow extensions to extend Superset's server-side capabilities. Backend contributions are registered at startup via classes and functions imported from the auto-discovered `entrypoint.py` file.
Backend contribution types allow extensions to extend Superset's server-side capabilities with new API endpoints, MCP tools, and MCP prompts.
### REST API Endpoints
Extensions can register custom REST API endpoints under the `/extensions/` namespace. This dedicated namespace prevents conflicts with built-in endpoints and provides a clear separation between core and extension functionality.
```python
from flask import Response
from flask_appbuilder.api import expose, permission_name, protect, safe
from superset_core.rest_api.api import RestApi
from superset_core.rest_api.decorators import api
from superset_core.api.rest_api import RestApi, api
from flask_appbuilder.api import expose, protect
@api(
id="my_extension_api",
name="My Extension API",
description="Custom API endpoints for my extension",
description="Custom API endpoints for my extension"
)
class MyExtensionAPI(RestApi):
openapi_spec_tag = "My Extension"
class_permission_name = "my_extension_api"
@expose("/hello", methods=("GET",))
@protect()
@safe
@permission_name("read")
def hello(self) -> Response:
return self.response(200, result={"message": "Hello from extension!"})
@@ -146,7 +136,7 @@ class MyExtensionAPI(RestApi):
from .api import MyExtensionAPI
```
**Note**: The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator automatically detects context and generates appropriate paths:
**Note**: The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator automatically detects context and generates appropriate paths:
- **Extension context**: `/extensions/{publisher}/{name}/` with ID prefixed as `extensions.{publisher}.{name}.{id}`
- **Host context**: `/api/v1/` with original ID
@@ -162,65 +152,16 @@ You can also specify a `resource_name` parameter to add an additional path segme
@api(
id="analytics_api",
name="Analytics API",
resource_name="analytics", # Adds /analytics to the path
resource_name="analytics" # Adds /analytics to the path
)
class AnalyticsAPI(RestApi):
@expose("/insights", methods=("GET",))
@protect()
@safe
@permission_name("read")
def insights(self) -> Response:
def insights(self):
# This endpoint will be available at:
# /extensions/my-org/dataset-tools/analytics/insights
return self.response(200, result={"insights": []})
```
### MCP Tools
### MCP Tools and Prompts
Extensions can register Python functions as MCP tools that AI agents can discover and call. Tools provide executable functionality such as data processing, custom analytics, or integration with external services. Each tool specifies a unique name and an optional description that helps AI agents decide when to use it.
```python
from superset_core.mcp.decorators import tool
@tool(
name="my-extension.get_summary",
description="Get a summary of recent query activity",
tags=["analytics", "queries"],
)
def get_summary() -> dict:
"""Returns a summary of recent query activity."""
return {"status": "success", "result": {"queries_today": 42}}
```
See [MCP Integration](./mcp) for implementation details.
### MCP Prompts
Extensions can register MCP prompts that provide interactive guidance and context to AI agents. Prompts help agents understand domain-specific workflows, best practices, or troubleshooting steps for your extension's use cases.
```python
from superset_core.mcp.decorators import prompt
from fastmcp import Context
@prompt(
"my-extension.analysis_guide",
title="Analysis Guide",
description="Step-by-step guidance for data analysis workflows",
)
async def analysis_guide(ctx: Context) -> str:
"""Provides guidance for data analysis workflows."""
return """
# Data Analysis Guide
Follow these steps for effective analysis:
1. **Explore your data** - Review available datasets and schema
2. **Build your query** - Use SQL Lab to craft and test queries
3. **Visualize results** - Choose the right chart type for your data
What would you like to analyze today?
"""
```
See [MCP Integration](./mcp) for implementation details.
Extensions can contribute Model Context Protocol (MCP) tools and prompts that AI agents can discover and use. See [MCP Integration](./mcp) for detailed documentation.

View File

@@ -70,8 +70,8 @@ import { someInternalFunction } from 'src/explore/components/SomeComponent';
```python
# ✅ Public API - stable
from superset_core.common.models import Database
from superset_core.common.daos import DatabaseDAO
from superset_core.api.models import Database
from superset_core.api.daos import DatabaseDAO
# ❌ Internal code - may break without notice
from superset.views.core import SomeInternalClass
@@ -117,7 +117,7 @@ Extension developers should depend on and use core libraries directly:
**Frontend (examples):**
- [React](https://react.dev/) - UI framework
- [Ant Design](https://ant.design/) - UI component library (prefer Superset components from `@apache-superset/core/components` when available to preserve visual consistency)
- [Ant Design](https://ant.design/) - UI component library (prefer Superset components from `@apache-superset/core/ui` when available to preserve visual consistency)
- [Emotion](https://emotion.sh/) - CSS-in-JS styling
- ...

View File

@@ -38,8 +38,6 @@ superset-extensions build: Builds extension assets.
superset-extensions bundle: Packages the extension into a .supx file.
superset-extensions dev: Automatically rebuilds the extension as files change.
superset-extensions validate: Validates the extension structure and metadata.
```
When creating a new extension with `superset-extensions init`, the CLI generates a standardized folder structure:
@@ -54,10 +52,9 @@ dataset-references/
│ └── package.json
├── backend/
│ ├── src/
│ │ └── my_org/
│ │ └── superset_extensions/
│ │ └── dataset_references/
│ ├── api.py
│ │ └── entrypoint.py
├── tests/
│ ├── pyproject.toml
│ └── requirements.txt
├── dist/
@@ -67,20 +64,20 @@ dataset-references/
│ │ ├── remoteEntry.d7a9225d042e4ccb6354.js
│ │ └── 900.038b20cdff6d49cfa8d9.js
│ └── backend
│ └── my_org/
│ └── superset_extensions/
│ └── dataset_references/
│ ├── __init__.py
│ ├── api.py
│ └── entrypoint.py
├── my-org.dataset-references-1.0.0.supx
├── dataset-references-1.0.0.supx
└── README.md
```
**Note**: With publisher `my-org` and name `dataset-references`, the technical names are:
**Note**: The extension ID (`dataset-references`) serves as the basis for all technical names:
- Directory name: `dataset-references` (kebab-case)
- Backend Python namespace: `my_org.dataset_references`
- Backend distribution package: `my_org-dataset_references`
- Frontend package name: `@my-org/dataset-references` (scoped)
- Module Federation name: `myOrg_datasetReferences` (camelCase)
- Backend Python package: `dataset_references` (snake_case)
- Frontend package name: `dataset-references` (kebab-case)
- Module Federation name: `datasetReferences` (camelCase)
The `extension.json` file serves as the declared metadata for the extension, containing the extension's name, version, author, description, and a list of capabilities. This file is essential for the host application to understand how to load and manage the extension.
@@ -111,7 +108,7 @@ The `extension.json` file contains the metadata necessary for the host applicati
Extensions use standardized entry point locations:
- **Backend**: `backend/src/{publisher}/{name}/entrypoint.py`
- **Backend**: `backend/src/superset_extensions/{publisher}/{name}/entrypoint.py`
- **Frontend**: `frontend/src/index.tsx`
### Build Configuration
@@ -127,7 +124,7 @@ license = "Apache-2.0"
[tool.apache_superset_extensions.build]
# Files to include in the extension build/bundle
include = [
"src/my_org/dataset_references/**/*.py",
"src/superset_extensions/my_org/dataset_references/**/*.py",
]
exclude = []
```
@@ -204,10 +201,9 @@ Backend APIs (via `apache-superset-core`) follow a similar pattern, providing ac
Extension endpoints are registered under a dedicated `/extensions` namespace to avoid conflicting with built-in endpoints and also because they don't share the same version constraints. By grouping all extension endpoints under `/extensions`, Superset establishes a clear boundary between core and extension functionality, making it easier to manage, document, and secure both types of APIs.
```python
from superset_core.common.models import Database, get_session
from superset_core.common.daos import DatabaseDAO
from superset_core.rest_api.api import RestApi
from superset_core.rest_api.decorators import api
from superset_core.api.models import Database, get_session
from superset_core.api.daos import DatabaseDAO
from superset_core.api.rest_api import RestApi, api
from flask_appbuilder.api import expose, protect
@api(
@@ -248,7 +244,7 @@ class DatasetReferencesAPI(RestApi):
### Automatic Context Detection
The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator automatically detects whether it's being used in host or extension code:
The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator automatically detects whether it's being used in host or extension code:
- **Extension APIs**: Registered under `/extensions/{publisher}/{name}/` with IDs prefixed as `extensions.{publisher}.{name}.{id}`
- **Host APIs**: Registered under `/api/v1/` with original IDs
@@ -266,7 +262,7 @@ LOCAL_EXTENSIONS = [
]
```
This instructs Superset to load and serve extensions directly from disk, so you can iterate quickly. Running `superset-extensions dev` watches for file changes and rebuilds assets automatically, while the Webpack development server (started separately with `npm run start`) serves updated files as soon as they're modified. This enables immediate feedback for React components, styles, and other frontend code. Changes to backend files are also detected automatically and immediately synced, ensuring that both frontend and backend updates are reflected in your development environment.
This instructs Superset to load and serve extensions directly from disk, so you can iterate quickly. Running `superset-extensions dev` watches for file changes and rebuilds assets automatically, while the Webpack development server (started separately with `npm run dev-server`) serves updated files as soon as they're modified. This enables immediate feedback for React components, styles, and other frontend code. Changes to backend files are also detected automatically and immediately synced, ensuring that both frontend and backend updates are reflected in your development environment.
Example output when running in development mode:

View File

@@ -37,11 +37,31 @@ Superset uses text editors in various places throughout the application:
| `css` | Dashboard Properties, CSS Template Modal |
| `markdown` | Dashboard Markdown component |
| `yaml` | Template Params Editor |
| `javascript` | Custom JavaScript editor contexts |
| `python` | Custom Python editor contexts |
| `text` | Plain text editor contexts |
By registering an editor for a language, your extension replaces the default Ace editor in **all** locations that use that language.
By registering an editor provider for a language, your extension replaces the default Ace editor in **all** locations that use that language.
## Manifest Configuration
Declare editor contributions in your `extension.json` manifest:
```json
{
"name": "monaco-editor",
"version": "1.0.0",
"frontend": {
"contributions": {
"editors": [
{
"id": "monaco-editor.sql",
"name": "Monaco SQL Editor",
"languages": ["sql"],
"description": "Monaco-based SQL editor with IntelliSense"
}
]
}
}
}
```
## Implementing an Editor
@@ -145,22 +165,21 @@ const MonacoSQLEditor = forwardRef<editors.EditorHandle, editors.EditorProps>(
export default MonacoSQLEditor;
```
### index.tsx
Register the editor at module load time from your extension's entry point:
### activate.ts
```typescript
import { editors } from '@apache-superset/core';
import MonacoSQLEditor from './MonacoSQLEditor';
editors.registerEditor(
{
id: 'my-extension.monaco-sql',
name: 'Monaco SQL Editor',
languages: ['sql'],
},
MonacoSQLEditor,
);
export function activate(context) {
// Register the Monaco editor for SQL using the contribution ID from extension.json
const disposable = editors.registerEditorProvider(
'monaco-sql-editor.sql',
MonacoSQLEditor,
);
context.subscriptions.push(disposable);
}
```
## Handling Hotkeys

View File

@@ -86,73 +86,132 @@ Extensions can replace the default SQL editor with custom implementations (Monac
This example adds a "Data Profiler" panel to SQL Lab:
```json
{
"name": "data_profiler",
"version": "1.0.0",
"frontend": {
"contributions": {
"views": {
"sqllab": {
"panels": [
{
"id": "data_profiler.main",
"name": "Data Profiler"
}
]
}
}
}
}
}
```
```typescript
import React from 'react';
import { views } from '@apache-superset/core';
import { core } from '@apache-superset/core';
import DataProfilerPanel from './DataProfilerPanel';
views.registerView(
{ id: 'my-extension.data-profiler', name: 'Data Profiler' },
'sqllab.panels',
() => <DataProfilerPanel />,
);
export function activate(context) {
// Register the panel view with the ID declared in extension.json
const disposable = core.registerView('data_profiler.main', <DataProfilerPanel />);
context.subscriptions.push(disposable);
}
```
### Adding Actions to the Editor
This example adds primary, secondary, and context actions to the editor:
```json
{
"name": "query_tools",
"version": "1.0.0",
"frontend": {
"contributions": {
"commands": [
{
"command": "query_tools.format",
"title": "Format Query",
"icon": "FormatPainterOutlined"
},
{
"command": "query_tools.explain",
"title": "Explain Query"
},
{
"command": "query_tools.copy_as_cte",
"title": "Copy as CTE"
}
],
"menus": {
"sqllab": {
"editor": {
"primary": [
{
"view": "builtin.editor",
"command": "query_tools.format"
}
],
"secondary": [
{
"view": "builtin.editor",
"command": "query_tools.explain"
}
],
"context": [
{
"view": "builtin.editor",
"command": "query_tools.copy_as_cte"
}
]
}
}
}
}
}
}
```
```typescript
import { commands, menus, sqlLab } from '@apache-superset/core';
import { commands, sqlLab } from '@apache-superset/core';
commands.registerCommand(
{ id: 'my-extension.format', title: 'Format Query', icon: 'FormatPainterOutlined' },
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Format the SQL query
}
},
);
export function activate(context) {
// Register the commands declared in extension.json
const formatCommand = commands.registerCommand(
'query_tools.format',
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Format the SQL query
}
},
);
commands.registerCommand(
{ id: 'my-extension.explain', title: 'Explain Query' },
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Show query explanation
}
},
);
const explainCommand = commands.registerCommand(
'query_tools.explain',
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Show query explanation
}
},
);
commands.registerCommand(
{ id: 'my-extension.copy-as-cte', title: 'Copy as CTE' },
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Copy selected text as CTE
}
},
);
const copyAsCteCommand = commands.registerCommand(
'query_tools.copy_as_cte',
async () => {
const tab = sqlLab.getCurrentTab();
if (tab) {
const editor = await tab.getEditor();
// Copy selected text as CTE
}
},
);
menus.registerMenuItem(
{ view: 'builtin.editor', command: 'my-extension.format' },
'sqllab.editor',
'primary',
);
menus.registerMenuItem(
{ view: 'builtin.editor', command: 'my-extension.explain' },
'sqllab.editor',
'secondary',
);
menus.registerMenuItem(
{ view: 'builtin.editor', command: 'my-extension.copy-as-cte' },
'sqllab.editor',
'context',
);
context.subscriptions.push(formatCommand, explainCommand, copyAsCteCommand);
}
```
## Next Steps

View File

@@ -61,7 +61,7 @@ Prompts provide interactive guidance and context to AI agents. They help agents
The simplest way to create an MCP tool is using the `@tool` decorator:
```python
from superset_core.mcp.decorators import tool
from superset_core.api.mcp import tool
@tool
def hello_world() -> dict:
@@ -94,7 +94,7 @@ Here's a more comprehensive example showing best practices:
import random
from datetime import datetime, timezone
from pydantic import BaseModel, Field
from superset_core.mcp.decorators import tool
from superset_core.api.mcp import tool
class RandomNumberRequest(BaseModel):
"""Request schema for random number generation."""
@@ -253,7 +253,7 @@ The AI agent sees your tool's:
Create interactive prompts using the `@prompt` decorator:
```python
from superset_core.mcp.decorators import prompt
from superset_core.api.mcp import prompt
from fastmcp import Context
@prompt("my_extension.workflow_guide")

View File

@@ -43,7 +43,7 @@ Extensions can provide:
## UI Components for Extensions
Extension developers have access to pre-built UI components via `@apache-superset/core/components`. Browse all available components on the [UI Components](/docs/components/) page and filter by **Extension Compatible** to see components available to extensions.
Extension developers have access to pre-built UI components via `@apache-superset/core/ui`. Browse all available components on the [UI Components](/docs/components/) page and filter by **Extension Compatible** to see components available to extensions.
## Next Steps

View File

@@ -64,24 +64,26 @@ Include backend? [Y/n]: Y
```
**Publisher Namespaces**: Extensions use organizational namespaces similar to VS Code extensions, providing collision-safe naming across organizations:
- **NPM package**: `@my-org/hello-world` (scoped package for frontend distribution)
- **Module Federation name**: `myOrg_helloWorld` (collision-safe JavaScript identifier)
- **Backend package**: `my_org-hello_world` (collision-safe Python distribution name)
- **Python namespace**: `my_org.hello_world`
- **Python namespace**: `superset_extensions.my_org.hello_world`
This approach ensures that extensions from different organizations cannot conflict, even if they use the same technical name (e.g., both `acme.dashboard-widgets` and `corp.dashboard-widgets` can coexist).
This creates a complete project structure:
```
hello-world/
my-org.hello-world/
├── extension.json # Extension metadata and configuration
├── backend/ # Backend Python code
│ ├── src/
│ │ └── my_org/
│ │ └── hello_world/
│ │ ── entrypoint.py # Backend registration
│ │ └── superset_extensions/
│ │ └── my_org/
│ │ ── __init__.py
│ │ └── hello_world/
│ │ ├── __init__.py
│ │ └── entrypoint.py # Backend registration
│ └── pyproject.toml
└── frontend/ # Frontend TypeScript/React code
├── src/
@@ -93,7 +95,7 @@ hello-world/
## Step 3: Configure Extension Metadata
The generated `extension.json` contains the extension's metadata.
The generated `extension.json` contains the extension's metadata. It is used to identify the extension and declare its backend entry points. Frontend contributions are registered directly in code (see Step 5).
```json
{
@@ -102,6 +104,10 @@ The generated `extension.json` contains the extension's metadata.
"displayName": "Hello World",
"version": "0.1.0",
"license": "Apache-2.0",
"backend": {
"entryPoints": ["superset_extensions.my_org.hello_world.entrypoint"],
"files": ["backend/src/superset_extensions/my_org/hello_world/**/*.py"]
},
"permissions": ["can_read"]
}
```
@@ -111,19 +117,19 @@ The generated `extension.json` contains the extension's metadata.
- `publisher`: Organizational namespace for the extension
- `name`: Technical identifier (kebab-case)
- `displayName`: Human-readable name shown to users
- `permissions`: List of permissions the extension requires
- `backend.entryPoints`: Python modules to load eagerly when the extension starts
- `backend.files`: Glob patterns for Python source files to include in the bundle
## Step 4: Create Backend API
The CLI generated a basic `backend/src/my_org/hello_world/entrypoint.py`. We'll create an API endpoint.
The CLI generated a basic `backend/src/superset_extensions/my_org/hello_world/entrypoint.py`. We'll create an API endpoint.
**Create `backend/src/my_org/hello_world/api.py`**
**Create `backend/src/superset_extensions/my_org/hello_world/api.py`**
```python
from flask import Response
from flask_appbuilder.api import expose, protect, safe
from superset_core.rest_api.api import RestApi
from superset_core.rest_api.decorators import api
from superset_core.api.rest_api import RestApi, api
@api(
@@ -168,23 +174,25 @@ class HelloWorldAPI(RestApi):
**Key points:**
- Uses [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator with automatic context detection
- Extends `RestApi` from `superset_core.rest_api.api`
- Uses [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator with automatic context detection
- Extends `RestApi` from `superset_core.api.rest_api`
- Uses Flask-AppBuilder decorators (`@expose`, `@protect`, `@safe`)
- Returns responses using `self.response(status_code, result=data)`
- The endpoint will be accessible at `/extensions/my-org/hello-world/message` (automatic extension context)
- OpenAPI docstrings are crucial - Flask-AppBuilder uses them to automatically generate interactive API documentation at `/swagger/v1`, allowing developers to explore endpoints, understand schemas, and test the API directly from the browser
**Update `backend/src/my_org/hello_world/entrypoint.py`**
**Update `backend/src/superset_extensions/my_org/hello_world/entrypoint.py`**
Replace the generated print statement with API import to trigger registration:
```python
# Importing the API class triggers the @api decorator registration
from .api import HelloWorldAPI # noqa: F401
from .api import HelloWorldAPI
print("Hello World extension loaded successfully!")
```
The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator automatically detects extension context and registers your API with proper namespacing.
The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator automatically detects extension context and registers your API with proper namespacing.
## Step 5: Create Frontend Component
@@ -228,53 +236,52 @@ The webpack configuration requires specific settings for Module Federation. Key
**Convention**: Superset always loads extensions by requesting the `./index` module from the Module Federation container. The `exposes` entry must be exactly `'./index': './src/index.tsx'` — do not rename or add additional entries. All API registrations must be reachable from that file. See [Architecture](./architecture#module-federation) for a full explanation.
```javascript
const path = require('path');
const { ModuleFederationPlugin } = require('webpack').container;
const packageConfig = require('./package');
const extensionConfig = require('../extension.json');
const path = require("path");
const { ModuleFederationPlugin } = require("webpack").container;
const packageConfig = require("./package.json");
module.exports = (env, argv) => {
const isProd = argv.mode === 'production';
const isProd = argv.mode === "production";
return {
entry: isProd ? {} : './src/index.tsx',
mode: isProd ? 'production' : 'development',
entry: isProd ? {} : "./src/index.tsx",
mode: isProd ? "production" : "development",
devServer: {
port: 3000,
port: 3001,
headers: {
'Access-Control-Allow-Origin': '*',
"Access-Control-Allow-Origin": "*",
},
},
output: {
filename: isProd ? undefined : "[name].[contenthash].js",
chunkFilename: "[name].[contenthash].js",
clean: true,
filename: isProd ? undefined : '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
publicPath: `/api/v1/extensions/${extensionConfig.publisher}/${extensionConfig.name}/`,
path: path.resolve(__dirname, "dist"),
publicPath: `/api/v1/extensions/my-org/hello-world/`,
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
extensions: [".ts", ".tsx", ".js", ".jsx"],
},
// Map @apache-superset/core imports to window.superset at runtime
externalsType: 'window',
externalsType: "window",
externals: {
'@apache-superset/core': 'superset',
"@apache-superset/core": "superset",
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
use: "ts-loader",
exclude: /node_modules/,
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'myOrg_helloWorld',
filename: 'remoteEntry.[contenthash].js',
name: "myOrg_helloWorld",
filename: "remoteEntry.[contenthash].js",
exposes: {
'./index': './src/index.tsx',
"./index": "./src/index.tsx",
},
shared: {
react: {
@@ -282,14 +289,9 @@ module.exports = (env, argv) => {
requiredVersion: packageConfig.peerDependencies.react,
import: false, // Use host's React, don't bundle
},
'react-dom': {
"react-dom": {
singleton: true,
requiredVersion: packageConfig.peerDependencies['react-dom'],
import: false,
},
antd: {
singleton: true,
requiredVersion: packageConfig.peerDependencies['antd'],
requiredVersion: packageConfig.peerDependencies["react-dom"],
import: false,
},
},
@@ -304,9 +306,8 @@ module.exports = (env, argv) => {
```json
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"moduleResolution": "node10",
"baseUrl": ".",
"moduleResolution": "node",
"jsx": "react",
"strict": true,
"esModuleInterop": true,
@@ -331,16 +332,16 @@ const HelloWorldPanel: React.FC = () => {
const [error, setError] = useState<string>('');
useEffect(() => {
const fetchMessage = async () => {
try {
const csrfToken = await authentication.getCSRFToken();
const response = await fetch('/extensions/my-org/hello-world/message', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken!,
},
});
const fetchMessage = async () => {
try {
const csrfToken = await authentication.getCSRFToken();
const response = await fetch('/extensions/my-org/hello-world/message', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken!,
},
});
if (!response.ok) {
throw new Error(`Server returned ${response.status}`);
@@ -495,8 +496,8 @@ Superset will extract and validate the extension metadata, load the assets, regi
Here's what happens when your extension loads:
1. **Superset starts**: Reads `manifest.json` from the `.supx` bundle and loads the backend entrypoint
2. **Backend registration**: `entrypoint.py` imports your API class, triggering the [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator to register it automatically
1. **Superset starts**: Reads `extension.json` and loads the backend entrypoint
2. **Backend registration**: `entrypoint.py` imports your API class, triggering the [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator to register it automatically
3. **Frontend loads**: When SQL Lab opens, Superset fetches the remote entry file
4. **Module Federation**: Webpack loads your extension module and resolves `@apache-superset/core` to `window.superset`
5. **Registration**: The module executes at load time, calling `views.registerView` to register your panel

View File

@@ -30,15 +30,15 @@ This page serves as a registry of community-created Superset extensions. These e
| Name | Description | Author | Preview |
| ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Extensions API Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api-explorer) | A SQL Lab panel that demonstrates the Extensions API by providing an interactive explorer for testing commands like getTabs, getCurrentTab, and getDatabases. Useful for extension developers to understand and experiment with the available APIs. | Michael S. Molina | <a href="/img/extensions/api-explorer.png" target="_blank"><img src="/img/extensions/api-explorer.png" alt="Extensions API Explorer" width="120" /></a> |
| [Extensions API Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api_explorer) | A SQL Lab panel that demonstrates the Extensions API by providing an interactive explorer for testing commands like getTabs, getCurrentTab, and getDatabases. Useful for extension developers to understand and experiment with the available APIs. | Michael S. Molina | <a href="/img/extensions/api-explorer.png" target="_blank"><img src="/img/extensions/api-explorer.png" alt="Extensions API Explorer" width="120" /></a> |
| [SQL Query Flow Visualizer](https://github.com/msyavuz/superset-sql-visualizer) | A SQL Lab panel that transforms SQL queries into interactive flow diagrams, helping developers and analysts understand query execution paths and data relationships. | Mehmet Salih Yavuz | <a href="/img/extensions/sql-flow-visualizer.png" target="_blank"><img src="/img/extensions/sql-flow-visualizer.png" alt="SQL Flow Visualizer" width="120" /></a> |
| [SQL Lab Export to Google Sheets](https://github.com/michael-s-molina/superset-extensions/tree/main/sqllab-gsheets) | A Superset extension that allows users to export SQL Lab query results directly to Google Sheets. | Michael S. Molina | <a href="/img/extensions/gsheets-export.png" target="_blank"><img src="/img/extensions/gsheets-export.png" alt="SQL Lab Export to Google Sheets" width="120" /></a> |
| [SQL Lab Export to Google Sheets](https://github.com/michael-s-molina/superset-extensions/tree/main/sqllab_gsheets) | A Superset extension that allows users to export SQL Lab query results directly to Google Sheets. | Michael S. Molina | <a href="/img/extensions/gsheets-export.png" target="_blank"><img src="/img/extensions/gsheets-export.png" alt="SQL Lab Export to Google Sheets" width="120" /></a> |
| [SQL Lab Export to Parquet](https://github.com/rusackas/superset-extensions/tree/main/sqllab_parquet) | Export SQL Lab query results directly to Apache Parquet format with Snappy compression. | Evan Rusackas | <a href="/img/extensions/parquet-export.png" target="_blank"><img src="/img/extensions/parquet-export.png" alt="SQL Lab Export to Parquet" width="120" /></a> |
| [SQL Lab Query Comparison](https://github.com/michael-s-molina/superset-extensions/tree/main/query-comparison) | A SQL Lab extension that enables side-by-side comparison of query results across different tabs, with GitHub-style diff visualization showing added/removed rows and columns. | Michael S. Molina | <a href="/img/extensions/query-comparison.png" target="_blank"><img src="/img/extensions/query-comparison.png" alt="Query Comparison" width="120" /></a> |
| [SQL Lab Result Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result-stats) | A SQL Lab extension that automatically computes statistics for query results, providing type-aware analysis including numeric metrics (min, max, mean, median, std dev), string analysis (length, empty counts), and date range information. | Michael S. Molina | <a href="/img/extensions/result-stats.png" target="_blank"><img src="/img/extensions/result-stats.png" alt="Result Stats" width="120" /></a> |
| [Editor Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/editor-snippets) | A SQL Lab extension for managing and inserting reusable code snippets into the editor, with server-side persistence per user. | Michael S. Molina | <a href="/img/extensions/editor-snippets.png" target="_blank"><img src="/img/extensions/editor-snippets.png" alt="Editor Snippets" width="120" /></a> |
| [SQL Lab Query Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query-estimator) | A SQL Lab panel that analyzes query execution plans to estimate resource impact, detect performance issues like Cartesian products and high-cost operations, and visualize the query plan tree. | Michael S. Molina | <a href="/img/extensions/query-estimator.png" target="_blank"><img src="/img/extensions/query-estimator.png" alt="Query Estimator" width="120" /></a> |
| [Editors Bundle](https://github.com/michael-s-molina/superset-extensions/tree/main/editors-bundle) | A Superset extension that demonstrates how to provide custom code editors for different languages. This extension showcases the editor contribution system by registering alternative editors that can replace Superset's default Ace editor. | Michael S. Molina | <a href="/img/extensions/editors-bundle.png" target="_blank"><img src="/img/extensions/editors-bundle.png" alt="Editors Bundle" width="120" /></a> |
| [SQL Lab Query Comparison](https://github.com/michael-s-molina/superset-extensions/tree/main/query_comparison) | A SQL Lab extension that enables side-by-side comparison of query results across different tabs, with GitHub-style diff visualization showing added/removed rows and columns. | Michael S. Molina | <a href="/img/extensions/query-comparison.png" target="_blank"><img src="/img/extensions/query-comparison.png" alt="Query Comparison" width="120" /></a> |
| [SQL Lab Result Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result_stats) | A SQL Lab extension that automatically computes statistics for query results, providing type-aware analysis including numeric metrics (min, max, mean, median, std dev), string analysis (length, empty counts), and date range information. | Michael S. Molina | <a href="/img/extensions/result-stats.png" target="_blank"><img src="/img/extensions/result-stats.png" alt="Result Stats" width="120" /></a> |
| [SQL Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/sql_snippets) | A SQL Lab extension that provides reusable SQL code snippets, enabling quick insertion of commonly used code blocks such as license headers, author information, and frequently used SQL patterns. | Michael S. Molina | <a href="/img/extensions/sql-snippets.png" target="_blank"><img src="/img/extensions/sql-snippets.png" alt="SQL Snippets" width="120" /></a> |
| [SQL Lab Query Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query_estimator) | A SQL Lab panel that analyzes query execution plans to estimate resource impact, detect performance issues like Cartesian products and high-cost operations, and visualize the query plan tree. | Michael S. Molina | <a href="/img/extensions/query-estimator.png" target="_blank"><img src="/img/extensions/query-estimator.png" alt="Query Estimator" width="120" /></a> |
| [Editors Bundle](https://github.com/michael-s-molina/superset-extensions/tree/main/editors_bundle) | A Superset extension that demonstrates how to provide custom code editors for different languages. This extension showcases the editor contribution system by registering alternative editors that can replace Superset's default Ace editor. | Michael S. Molina | <a href="/img/extensions/editors-bundle.png" target="_blank"><img src="/img/extensions/editors-bundle.png" alt="Editors Bundle" width="120" /></a> |
## How to Add Your Extension

View File

@@ -50,7 +50,7 @@ When GTF is considered stable, it will replace legacy Celery tasks for built-in
### Define a Task
```python
from superset_core.tasks.decorators import task, get_context
from superset_core.api.tasks import task, get_context
@task
def process_data(dataset_id: int) -> None:
@@ -245,8 +245,7 @@ Always implement an abort handler for long-running tasks. This allows users to c
Set a timeout to automatically abort tasks that run too long:
```python
from superset_core.tasks.decorators import task, get_context
from superset_core.tasks.types import TaskOptions
from superset_core.api.tasks import task, get_context, TaskOptions
# Set default timeout in decorator
@task(timeout=300) # 5 minutes
@@ -300,7 +299,7 @@ Timeouts require an abort handler to be effective. Without one, the timeout trig
Use `task_key` to prevent duplicate task execution:
```python
from superset_core.tasks.types import TaskOptions
from superset_core.api.tasks import TaskOptions
# Without key - creates new task each time (random UUID)
task1 = my_task.schedule(x=1)
@@ -332,8 +331,7 @@ print(task2.status) # "success" (terminal status)
## Task Scopes
```python
from superset_core.tasks.decorators import task
from superset_core.tasks.types import TaskScope
from superset_core.api.tasks import task, TaskScope
@task # Private by default
def private_task(): ...

View File

@@ -63,12 +63,6 @@ by clicking the **Connect** button in the bottom right corner of the modal windo
Congratulations, you've just added a new data source in Superset!
### Sharing a Database Connection
When adding a new database, you can share the connection with other Superset users. Shared connections appear in other users' database lists, making it easier to collaborate on the same data without requiring each user to configure the same connection separately.
To share a connection, enable the **Share connection with other users** option in the **Advanced** tab of the database connection modal before saving. You can change sharing settings later by editing the database connection.
### Registering a new table
Now that youve configured a data source, you can select specific tables (called **Datasets** in Superset)
@@ -86,22 +80,6 @@ we register the **cleaned_sales_data** table from the **examples** database.
To finish, click the **Add** button in the bottom right corner. You should now see your dataset in the list of datasets.
### Organizing Datasets into Folders
The Datasets list view supports **folders** for organizing datasets into groups. To create and manage folders:
1. In the **Datasets** list, click the **Folders** panel on the left sidebar.
2. Click **+ New Folder** to create a top-level folder, or drag an existing folder to nest it.
3. Drag dataset rows onto a folder to move them in, or right-click a dataset and select **Move to folder**.
Folders are per-user organizational aids — they do not affect dataset access permissions or how other users see the datasets.
### Uploading Files via the OS File Manager (PWA)
When Superset is installed as a **Progressive Web App (PWA)** from your browser, your operating system will offer Superset as an option when opening CSV, Excel (`.xls`/`.xlsx`), and Parquet files. Double-clicking or right-clicking a supported file and selecting "Open with Superset" navigates directly to the upload workflow for that file.
To install Superset as a PWA, look for the install icon in your browser's address bar (Chrome, Edge) when visiting your Superset instance over HTTPS. PWA installation requires HTTPS and a valid manifest — your admin needs to confirm the app manifest is served correctly.
### Customizing column properties
Now that you've registered your dataset, you can configure column properties
@@ -256,112 +234,6 @@ For example, when running the local development build, the following will disabl
Top Nav and remove the Filter Bar:
`http://localhost:8088/superset/dashboard/my-dashboard/?standalone=1&show_filters=0`
### Table Chart Features
The **Table** chart type has several advanced capabilities worth knowing:
#### Conditional Formatting
Conditional formatting rules highlight cells based on their values. Rules can be applied to:
- **Numeric columns** — color cells above/below a threshold, or use a gradient across a range
- **String columns** — highlight cells matching specific text values or patterns
- **Boolean columns** — color cells that are `true` or `false`, or `null`/`not null`
Each rule has a **"Use gradient"** toggle: enabled applies a varying opacity (lighter = further from threshold), disabled applies a solid fill at full opacity regardless of value.
#### HTML Rendering in Table Cells
Table chart cells can render raw HTML, enabling rich formatting such as hyperlinks, colored badges, and icons directly in the data. Enable this per-column in the chart's **Column Configuration** panel by toggling **Render HTML**.
:::caution
Only enable HTML rendering for columns sourced from data you control. Rendering untrusted HTML can expose users to cross-site scripting (XSS) risks.
:::
#### Column Header Tooltips
Column headers display a tooltip with the column's **Description** from the dataset editor when the user hovers over them. Keep dataset column descriptions up to date to improve chart discoverability.
#### Display Controls
In dashboard view mode (without entering Edit mode), charts with configurable display options expose a **Display Controls** panel accessible from the chart's context menu. This surfaces controls such as Time Grain, Time Column, and layer visibility for applicable chart types — making it easy to adjust a chart's view without going to Explore.
### AG Grid Interactive Table
The **AG Grid Interactive Table** chart type is Superset's fully-featured data grid, suitable for large paginated datasets where the standard Table chart is not enough.
#### Server-Side Column Filters
AG Grid supports server-side column filters that query the full dataset — not just the loaded page. Filters are applied before data is sent to the browser, so results are correct even across millions of rows.
**Available filter types:**
| Column type | Filter options |
|---|---|
| Text | Contains, equals, starts with, ends with |
| Number | Equals, not equal, less than, greater than, between |
| Date | Before, after, between, blank |
| Set | Select from a list of distinct values |
**AND / OR logic:** Each column supports combining multiple conditions with AND or OR. Filters from different columns are always combined with AND.
**Interaction with pagination:** Server-side filters run as WHERE clauses in the underlying SQL query, so pagination always operates over the already-filtered result set.
#### Time Shift (Time Comparison)
AG Grid Interactive Table supports **Time Shift** (time comparison), matching the behavior of the standard Table chart. In the **Advanced Analytics** → **Time Comparison** section of the chart configuration, enter a shift expression (e.g., `1 year ago`, `minus 7 days`) to add comparison columns showing values from the offset period. Dashboard-level time range overrides apply to both the base and comparison periods.
### Dynamic Currency Formatting
Chart metric values can display currencies dynamically rather than using a fixed currency code. To enable:
1. Open the dataset editor for your dataset (**Datasets → Edit**).
2. In the **Advanced** tab, set **Currency Code Column** to the name of a column in your dataset that contains ISO 4217 currency codes (e.g., `USD`, `EUR`, `GBP`).
3. In the Explore chart configuration, open the metric's **Number format** section and select **Auto-detect** for currency.
When Auto-detect is active, each row uses the currency code from the designated column, so a single chart can display values in multiple currencies — each formatted correctly for its currency.
### ECharts Option Editor
For ECharts-based chart types (line, bar, area, scatter, pie, and others), Explore includes an advanced **ECharts Option Editor** that accepts raw JSON overrides for the underlying ECharts configuration.
Access it via the **Customize** tab → **ECharts Options** section at the bottom of the panel. The JSON you enter is deep-merged on top of Superset's generated ECharts config, so you can override specific options without rewriting the entire config.
**Example:** override the legend position and add a custom title:
```json
{
"legend": { "orient": "vertical", "right": "5%", "top": "middle" },
"title": { "text": "My Custom Title", "left": "center" }
}
```
:::caution
ECharts option overrides bypass Superset's validation layer. Invalid option keys are silently ignored by ECharts. Overrides that conflict with Superset-generated options (e.g., `series`) may produce unexpected results.
:::
### Table Chart: Exporting Filtered Data
When the **Search Box** is visible in a Table chart, the **Download** action exports only the rows currently visible after the search filter is applied — not the full underlying dataset. This matches the visual output and is intentional. To export the full dataset regardless of search state, use the **Download as CSV** option from the chart's three-dot menu in the dashboard or from the Explore chart toolbar before applying a search filter.
### Sharing a Specific Tab
When a dashboard has tabs, each tab gets its own shareable URL. Navigate to the tab you want to share and copy the URL from your browser's address bar — the tab anchor is encoded in the URL so that anyone opening the link lands directly on that tab.
### Auto-Refresh
Dashboards can be configured to refresh automatically at a fixed interval without user interaction. Open a dashboard, click the **⋮** (more options) menu in the top-right, and select **Set auto-refresh interval**. Choose an interval (e.g., every 10 seconds, 1 minute, or 10 minutes). The setting is per-session and resets when you close the tab.
:::note
Auto-refresh triggers a full data reload for all charts on the dashboard. For dashboards with expensive queries, choose longer intervals to avoid overloading your database.
:::
### Last Queried Timestamp
Charts can display a "Last queried at" timestamp showing when the chart data was last fetched. This is useful on auto-refreshing dashboards to confirm data freshness. Enable it in **Dashboard Properties → Styling → Show last queried time**.
### Saving a Chart to a Specific Tab
When saving or adding a chart to a dashboard from Explore, you can select which tab it should land on using the tab tree-select dropdown in the "Add to dashboard" modal.
:::resources
- [Dashboard Customization](https://docs.preset.io/docs/dashboard-customization) - Advanced dashboard styling and layout options
- [Blog: BI Dashboard Best Practices](https://preset.io/blog/bi-dashboard-best-practices/)

View File

@@ -1,130 +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.
*/}
---
title: Embedding Superset
sidebar_position: 6
---
# Embedding Superset
Superset dashboards can be embedded directly in host applications using the `@superset-ui/embedded-sdk` package.
:::info Prerequisites
- The `EMBEDDED_SUPERSET` feature flag must be enabled.
- The embedding domain and allowed origins must be configured by an admin.
:::
## Quick Start
Install the SDK:
```bash
npm install @superset-ui/embedded-sdk
```
Embed a dashboard:
```javascript
import { embedDashboard } from '@superset-ui/embedded-sdk';
embedDashboard({
id: 'dashboard-uuid-here', // from Dashboard → Embed
supersetDomain: 'https://superset.example.com',
mountPoint: document.getElementById('superset-container'),
fetchGuestToken: () => fetchTokenFromYourBackend(),
dashboardUiConfig: {
hideTitle: true,
filters: { expanded: false },
},
});
```
`fetchGuestToken` must return a **guest token** obtained from your server by calling Superset's `/api/v1/security/guest_token/` endpoint with a service account. Do not call this endpoint from client-side code.
---
## Callbacks
### `resolvePermalinkUrl`
When a user copies a permalink from an embedded dashboard, Superset generates a URL on its own domain. In an embedded context this URL is usually not meaningful to the host application's users — the dashboard is rendered inside the host app, not at the Superset URL.
The `resolvePermalinkUrl` callback lets the host app intercept permalink generation and return a URL on the host domain instead:
```javascript
embedDashboard({
id: 'my-dashboard-uuid',
supersetDomain: 'https://superset.example.com',
mountPoint: document.getElementById('superset-container'),
fetchGuestToken: () => fetchGuestToken(),
/**
* Called when Superset generates a permalink.
* @param {Object} args - { key: string } — the permalink key
* @returns {string | null} - your host URL, or null to use Superset's default
*/
resolvePermalinkUrl: ({ key }) => {
return `https://myapp.example.com/dashboard?permalink=${key}`;
},
});
```
If the callback returns `null` or is not provided, Superset uses its own permalink URL as a fallback.
---
## Feature Flags for Embedded Mode
### `DISABLE_EMBEDDED_SUPERSET_LOGOUT`
Hides the logout button when Superset is embedded in a host application. This is useful when the host application manages the session lifecycle and you do not want users to accidentally log out of the embedded Superset session:
```python
# superset_config.py
FEATURE_FLAGS = {
"EMBEDDED_SUPERSET": True,
"DISABLE_EMBEDDED_SUPERSET_LOGOUT": True,
}
```
When enabled, the **Logout** menu item is removed from the user avatar dropdown in the embedded view. The session can still be invalidated server-side by revoking the guest token.
### `EMBEDDED_SUPERSET`
Must be `True` to enable the embedded SDK and the guest token endpoint. Without this flag, `embedDashboard` will fail to load.
---
## URL Parameters
The following URL parameters can be passed through the `urlParams` option in `dashboardUiConfig` or appended to the embedded iframe URL:
| Parameter | Values | Effect |
|-----------|--------|--------|
| `standalone` | `0`, `1`, `2`, `3` | `0`: normal; `1`: hide nav; `2`: hide nav + title; `3`: hide nav + title + tabs |
| `show_filters` | `0`, `1` | Show or hide the native filter bar |
| `expand_filters` | `0`, `1` | Start with filter bar expanded or collapsed |
---
## Security Notes
- **Guest tokens expire** — their lifetime is controlled by the `GUEST_TOKEN_JWT_EXP_SECONDS` config (default: 5 minutes). Refresh tokens before they expire using a token refresh mechanism in your host app.
- **Row-level security** — pass `rls` rules in the guest token request to restrict which rows are visible to the embedded user.
- **Allowed domains** — restrict which host origins can embed a dashboard by setting **Allowed Domains** per-dashboard in the *Embed* settings modal. Superset checks the request's `Referer` header against this list before serving the embedded view; an empty list allows any origin, so configure this explicitly for production.

View File

@@ -329,27 +329,6 @@ various options in this section, refer to the
Lastly, save your chart as Tutorial Resample and add it to the Tutorial Dashboard. Go to the
tutorial dashboard to see the four charts side by side and compare the different outputs.
### SQL Lab Tips
**Schema and table browser**: The left-side table browser uses a collapsible treeview — click a schema to expand its tables, and click a table to see its columns and sample data inline. This makes navigating large schemas much faster than the previous flat list.
**Find in editor**: Press **Ctrl+F** (or **Cmd+F** on Mac) to open the Monaco find/replace widget inside the SQL editor without leaving the editor.
**Resizable panels**: The dividers between the SQL editor, schema browser, and results pane are draggable. Adjust them to match your workflow and screen size.
**Dialect-aware Format SQL**: The **Format SQL** button applies the SQL dialect of the currently selected database — Trino, Presto, MySQL, PostgreSQL, etc. — rather than a generic formatter. Switch to a different database in the toolbar and re-format to get dialect-specific output. Jinja template syntax (`{{ }}`, `{% %}`) is preserved during formatting and will not cause format errors.
### Time Range Natural Language Expressions
The **Custom** time range picker accepts natural language expressions alongside specific dates:
- **Relative**: `Last 7 days`, `Last month`, `Last quarter`, `Last year`
- **Anchored**: `previous calendar week`, `previous calendar month`
- **"First of" expressions**: `first day of this week`, `first day of this month`, `first day of this quarter`, `first day of this year`, `first week of this year`
- **Offsets**: `30 days ago`, `1 year ago`, `next week`
These expressions are evaluated at query time, so saved charts always display data relative to the current date.
:::resources
- [Chart Walkthroughs](https://docs.preset.io/docs/chart-walkthroughs) - Detailed guides for most chart types
- [Blog: Why Apache ECharts is the Future of Apache Superset](https://preset.io/blog/2021-4-1-why-echarts/)

View File

@@ -33,29 +33,6 @@ SQL templating must be enabled by your administrator via the `ENABLE_TEMPLATE_PR
For advanced configuration options, see the [SQL Templating Configuration Guide](/admin-docs/configuration/sql-templating).
:::
## Using Jinja in Calculated Columns
Jinja template macros are available in calculated column expressions in the dataset editor — not just in SQL Lab queries and virtual datasets. This allows column expressions to reference the current user or dynamic context.
**Example: User-scoped calculated column**
```sql
CASE WHEN sales_rep = '{{ current_username() }}' THEN amount ELSE 0 END
```
**Example: Conditional display based on role**
Because `current_user_roles()` returns a Python list, test role membership with a Jinja
conditional at template time rather than matching against the list's string representation:
```sql
{% if 'Finance' in current_user_roles() %}revenue{% else %}NULL{% endif %} AS finance_revenue
```
:::note
The `ENABLE_TEMPLATE_PROCESSING` feature flag must be enabled by your administrator for Jinja in calculated columns to work.
:::
## Basic Usage
Jinja templates use double curly braces `{{ }}` for expressions and `{% %}` for logic blocks.
@@ -266,7 +243,6 @@ Using `remove_filter=True` applies the filter in the inner query for better perf
- Use `|tojson` to serialize arrays as JSON strings
- Test queries with explicit parameter values before relying on filter context
- For complex templating needs, ask your administrator about custom Jinja macros
- **Format SQL is Jinja-aware**: The "Format SQL" button in SQL Lab correctly preserves `{{ }}` and `{% %}` template syntax and applies your selected database's SQL dialect when formatting.
:::resources
- [Admin Guide: SQL Templating Configuration](/admin-docs/configuration/sql-templating)

View File

@@ -1,312 +0,0 @@
---
title: Using AI with Superset
hide_title: true
sidebar_position: 5
version: 1
---
<!--
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.
-->
# Using AI with Superset
Superset supports AI assistants through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/). Connect Claude, ChatGPT, or other MCP-compatible clients to explore your data, build charts, create dashboards, and run SQL -- all through natural language.
:::info
Requires Superset 5.0+. Your admin must enable and deploy the MCP server before you can connect.
See the **[MCP Server admin guide](/admin-docs/configuration/mcp-server)** for setup instructions.
:::
---
## What Can AI Do with Superset?
### Explore Your Data
Ask your AI assistant to browse what's available in your Superset instance:
- **List datasets** -- see all datasets you have access to, with filtering and search
- **Get dataset details** -- column names, types, available metrics, and filters
- **List charts and dashboards** -- find existing visualizations by name or keyword
- **Get chart and dashboard details** -- understand what a chart shows, its query, and configuration
**Example prompts:**
> "What datasets are available?"
> "Show me the columns in the sales_orders dataset"
> "Find dashboards related to revenue"
### Build Charts
Describe the visualization you want and AI creates it for you:
- **Preview-first workflow** -- by default AI generates an Explore link so you can review the chart before it is saved. Say "save it" to commit permanently
- **Create charts from natural language** -- describe what you want to see and AI picks the right chart type, metrics, and dimensions
- **Preview before saving** -- `generate_chart` defaults to `save_chart=False`, showing the chart in Explore before it's committed. Ask AI to save once you're satisfied.
- **Modify existing charts** -- `update_chart` also supports preview mode so you can review changes before saving (update filters, change chart types, add metrics)
- **Get Explore links** -- open any chart in Superset's Explore view for further refinement
**Example prompts:**
> "Create a bar chart showing monthly revenue by region from the sales dataset"
> "Update chart 42 to use a line chart instead"
> "Give me a link to explore this chart further"
:::tip Preview-first workflow
Charts are **not saved by default**. The workflow is intentionally iterative:
1. **Explore** — AI generates an Explore link so you can see the chart before it exists in Superset
2. **Iterate** — ask the AI to adjust the chart; changes are previewed without touching the database
3. **Save** — when you're happy, say "save it" and the chart is permanently stored
To skip the preview and save immediately, include "and save it" in your prompt.
:::
### Create Dashboards
Build dashboards from a collection of charts:
- **Generate dashboards** -- create a new dashboard with a set of charts, automatically laid out
- **Add charts to existing dashboards** -- place a chart on an existing dashboard with automatic positioning
**Example prompts:**
> "Create a dashboard called 'Q4 Sales Overview' with charts 10, 15, and 22"
> "Add the revenue trend chart to the executive dashboard"
### Browse Databases
Discover what database connections are configured in your Superset instance:
- **List databases** -- see all database connections you have access to
- **Get database details** -- name, backend type (PostgreSQL, Snowflake, etc.), and connection status
**Example prompts:**
> "What databases are connected to Superset?"
> "Show me details about the data warehouse connection"
### Create Virtual Datasets
Build ad-hoc SQL datasets that can be used as the basis for charts:
- **Create virtual datasets** -- write a SQL query and save it as a reusable dataset
- **Use immediately in charts** -- the returned dataset ID can be passed directly to chart creation
**Example prompts:**
> "Create a dataset from: SELECT region, SUM(revenue) as total_revenue FROM orders GROUP BY region"
> "Make a virtual dataset called 'monthly_signups' from the users table filtered to last 12 months"
### Run SQL Queries
Execute SQL directly through your AI assistant:
- **Run queries** -- execute SQL with full Superset RBAC enforcement (you can only query data your roles allow)
- **Open SQL Lab** -- get a link to SQL Lab pre-populated with a query, ready to run and explore
- **Save queries** -- save a SQL query to SQL Lab's Saved Queries for later reuse
**Example prompts:**
> "Run this query: SELECT region, SUM(revenue) FROM sales GROUP BY region"
> "Open SQL Lab with a query to show the top 10 customers by order count"
> "Save this query as 'Weekly Revenue Report'"
### Analyze Chart Data
Pull the raw data behind any chart:
- **Get chart data** -- retrieve the data a chart displays, with support for JSON, CSV, and Excel export formats
- **Inspect results** -- useful for verifying what a visualization shows or feeding data into other tools
**Example prompts:**
> "Get the data behind chart 42"
> "Export chart 15 data as CSV"
### Check Instance Status
- **Health check** -- verify your Superset instance is up and the MCP connection is working
- **Instance info** -- get high-level statistics about your Superset instance (number of datasets, charts, dashboards)
**Example prompts:**
> "Is Superset healthy?"
> "How many dashboards are in this instance?"
---
## Connecting Your AI Client
Once your admin has deployed the MCP server, connect your AI client using the instructions below.
### Claude Desktop
Edit your Claude Desktop config file:
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"superset": {
"url": "http://localhost:5008/mcp"
}
}
}
```
Restart Claude Desktop. The hammer icon in the chat bar confirms the connection.
If your admin has enabled JWT authentication, you may need to include a token:
```json
{
"mcpServers": {
"superset": {
"command": "npx",
"args": [
"-y",
"mcp-remote@latest",
"http://your-superset-host:5008/mcp",
"--header",
"Authorization: Bearer YOUR_TOKEN"
]
}
}
}
```
### Claude Code (CLI)
Add to your project's `.mcp.json`:
```json
{
"mcpServers": {
"superset": {
"type": "url",
"url": "http://localhost:5008/mcp"
}
}
}
```
### ChatGPT
1. Click your profile icon > **Settings** > **Apps and Connectors**
2. Enable **Developer Mode** in Advanced Settings
3. In the chat composer, press **+** > **Add sources** > **App** > **Connect more** > **Create app**
4. Enter a name and your MCP server URL
5. Click **I understand and continue**
:::info
ChatGPT MCP connectors require a Pro, Team, Enterprise, or Edu plan.
:::
Ask your admin for the MCP server URL and any authentication tokens you need.
---
## Tips for Best Results
- **Be specific** -- "Create a bar chart of monthly revenue by region from the sales dataset" works better than "Make me a chart"
- **Start with exploration** -- ask what datasets and charts exist before creating new ones
- **Review AI-generated content** -- always check chart configurations and SQL before saving or sharing
- **Use Explore for refinement** -- ask AI for an Explore link, then fine-tune interactively in the Superset UI
- **Check permissions if you get errors** -- AI respects Superset's RBAC, so you can only access data your roles allow
---
## Available Tools Reference
### Exploration & Discovery
| Tool | Description |
|------|-------------|
| `health_check` | Verify the MCP server is running and connected |
| `get_instance_info` | Get instance statistics (dataset, chart, dashboard counts) |
| `get_schema` | Discover available charts, datasets, and dashboards with schema info |
### Datasets
| Tool | Description |
|------|-------------|
| `list_datasets` | List datasets with filtering and search |
| `get_dataset_info` | Get dataset metadata (columns, metrics, filters) |
| `create_virtual_dataset` | Create a virtual dataset from a SQL query |
### Charts
| Tool | Description |
|------|-------------|
| `list_charts` | List charts with filtering and search |
| `get_chart_info` | Get chart metadata and configuration |
| `get_chart_data` | Retrieve chart data (JSON, CSV, or Excel) |
| `get_chart_preview` | Generate a chart preview (URL, ASCII, table, or Vega-Lite) |
| `get_chart_type_schema` | Get the configuration schema for a chart type |
| `generate_chart` | Create a new chart from a specification (defaults to preview mode — review before saving) |
| `update_chart` | Modify an existing chart's configuration (pass `generate_preview=False` to persist immediately instead of returning a preview URL) |
| `update_chart_preview` | Update a cached chart preview without saving |
| `generate_explore_link` | Generate an Explore URL for interactive visualization |
### Dashboards
| Tool | Description |
|------|-------------|
| `list_dashboards` | List dashboards with filtering and search |
| `get_dashboard_info` | Get dashboard metadata and layout |
| `generate_dashboard` | Create a new dashboard with specified charts |
| `add_chart_to_existing_dashboard` | Add a chart to an existing dashboard |
### SQL
| Tool | Description |
|------|-------------|
| `execute_sql` | Run a SQL query with RBAC enforcement |
| `save_sql_query` | Persist a SQL query to SQL Lab's saved queries |
| `open_sql_lab_with_context` | Open SQL Lab with a pre-populated query |
### Databases
| Tool | Description |
|------|-------------|
| `list_databases` | List configured database connections |
| `get_database_info` | Get details about a specific database connection |
---
## Troubleshooting
### "Connection refused" or "Cannot connect"
- Confirm the MCP server URL with your admin
- For Claude Desktop: fully quit the app (not just close the window) and restart after config changes
- Check that the URL path ends with `/mcp` (e.g., `http://localhost:5008/mcp`)
### "Permission denied" or missing data
- Superset's RBAC controls what you can access through AI, just like in the Superset UI
- Ask your admin to verify your roles and permissions
- Try accessing the same data through the Superset web UI to confirm your access
### "Response too large"
- Ask for smaller result sets: use filters, reduce `page_size`, or request specific columns
- Example: "Show me the top 10 rows from the sales dataset" instead of "Show me all sales data"
### AI doesn't see Superset tools
- Verify the connection in your AI client (e.g., the hammer icon in Claude Desktop)
- Ask the AI "What Superset tools are available?" to confirm the connection
- Restart your AI client if you recently changed the configuration

View File

@@ -107,7 +107,7 @@
"prettier": "^3.8.1",
"typescript": "~5.9.3",
"typescript-eslint": "^8.56.1",
"webpack": "^5.105.4"
"webpack": "^5.105.3"
},
"browserslist": {
"production": [

View File

@@ -202,7 +202,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \\
mdx += `| Method | Endpoint | Description |\n`;
mdx += `|--------|----------|-------------|\n`;
for (const ep of tagEndpoints['Security']) {
mdx += `| \`${ep.method}\` | [${ep.summary}](/developer-docs/api/${ep.slug}) | \`${ep.path}\` |\n`;
mdx += `| \`${ep.method}\` | [${ep.summary}](./api/${ep.slug}) | \`${ep.path}\` |\n`;
}
mdx += '\n';
renderedTags.add('Security');
@@ -229,7 +229,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \\
mdx += `|--------|----------|-------------|\n`;
for (const ep of endpoints) {
mdx += `| \`${ep.method}\` | [${ep.summary}](/developer-docs/api/${ep.slug}) | \`${ep.path}\` |\n`;
mdx += `| \`${ep.method}\` | [${ep.summary}](./api/${ep.slug}) | \`${ep.path}\` |\n`;
}
mdx += `\n</details>\n\n`;
@@ -252,7 +252,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \\
mdx += `|--------|----------|-------------|\n`;
for (const ep of endpoints) {
mdx += `| \`${ep.method}\` | [${ep.summary}](/developer-docs/api/${ep.slug}) | \`${ep.path}\` |\n`;
mdx += `| \`${ep.method}\` | [${ep.summary}](./api/${ep.slug}) | \`${ep.path}\` |\n`;
}
mdx += `\n</details>\n\n`;

View File

@@ -152,8 +152,8 @@ const SOURCES = [
{
name: 'Extension Components',
path: 'packages/superset-core/src',
importPrefix: '@apache-superset/core/components',
docImportPrefix: '@apache-superset/core/components',
importPrefix: '@apache-superset/core/ui',
docImportPrefix: '@apache-superset/core/ui',
category: 'extension',
enabled: true,
extensionCompatible: true,
@@ -1155,7 +1155,7 @@ Help improve it by [editing the story file](https://github.com/apache/superset/e
const CATEGORY_LABELS = {
ui: { title: 'Core Components', sidebarLabel: 'Core Components', description: 'Buttons, inputs, modals, selects, and other fundamental UI elements.' },
'design-system': { title: 'Layout Components', sidebarLabel: 'Layout Components', description: 'Grid, Layout, Table, Flex, Space, and container components for page structure.' },
extension: { title: 'Extension Components', sidebarLabel: 'Extension Components', description: 'Components available to extension developers via @apache-superset/core/components.' },
extension: { title: 'Extension Components', sidebarLabel: 'Extension Components', description: 'Components available to extension developers via @apache-superset/core/ui.' },
};
/**
@@ -1463,7 +1463,7 @@ function generateExtensionTypeDeclarations(extensionComponents) {
*/
/**
* Type declarations for @apache-superset/core/components
* Type declarations for @apache-superset/core/ui
*
* AUTO-GENERATED by scripts/generate-superset-components.mjs
* Do not edit manually - regenerate by running: yarn generate:superset-components

View File

@@ -39,7 +39,7 @@ function getComponentRegistry() {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const SupersetComponents = require('@superset/components');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const CoreUI = require('@apache-superset/core/components');
const CoreUI = require('@apache-superset/core/ui');
// Build component registry with antd as base fallback layer.
// Some Superset components (e.g., Typography) use styled-components that may
@@ -65,7 +65,7 @@ function getProviders() {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { themeObject } = require('@apache-superset/core/theme');
const { themeObject } = require('@apache-superset/core/ui');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { App, ConfigProvider } = require('antd');

View File

@@ -1,120 +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.
*
* Swizzled from docusaurus-theme-openapi-docs to fix SSG crash.
*
* The original component calls useTypedSelector (Redux) at the top level,
* which fails during static site generation because no Redux store is
* available. This version moves the hook into a browser-only child component
* so SSG can render the page without a store context.
*/
import React from "react";
import BrowserOnly from "@docusaurus/BrowserOnly";
import { useSelector } from "react-redux";
interface ServerVariable {
default?: string;
}
interface ServerValue {
url: string;
variables?: Record<string, ServerVariable>;
}
interface StoreState {
server: { value: ServerValue | null };
}
function colorForMethod(method: string) {
switch (method.toLowerCase()) {
case "get":
return "primary";
case "post":
return "success";
case "delete":
return "danger";
case "put":
return "info";
case "patch":
return "warning";
case "head":
return "secondary";
case "event":
return "secondary";
default:
return undefined;
}
}
export interface Props {
method: string;
path: string;
context?: "endpoint" | "callback";
}
// Inner component rendered only in the browser, where the Redux store exists.
function ServerUrl() {
const serverValue = useSelector((state: StoreState) => state.server.value);
if (serverValue && serverValue.variables) {
let serverUrlWithVariables = serverValue.url.replace(/\/$/, "");
Object.keys(serverValue.variables).forEach((variable) => {
serverUrlWithVariables = serverUrlWithVariables.replace(
`{${variable}}`,
serverValue.variables?.[variable].default ?? ""
);
});
return <>{serverUrlWithVariables}</>;
}
if (serverValue && serverValue.url) {
return <>{serverValue.url}</>;
}
return null;
}
function MethodEndpoint({ method, path, context }: Props) {
const renderServerUrl = () => {
if (context === "callback") {
return "";
}
return <BrowserOnly>{() => <ServerUrl />}</BrowserOnly>;
};
return (
<>
<pre className="openapi__method-endpoint">
<span className={"badge badge--" + colorForMethod(method)}>
{method === "event" ? "Webhook" : method.toUpperCase()}
</span>{" "}
{method !== "event" && (
<h2 className="openapi__method-endpoint-path">
{renderServerUrl()}
{`${path.replace(/{([a-z0-9-_]+)}/gi, ":$1")}`}
</h2>
)}
</pre>
<div className="openapi__divider" />
</>
);
}
export default MethodEndpoint;

View File

@@ -35,7 +35,7 @@ function getThemeWrapper() {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { themeObject } = require('@apache-superset/core/theme');
const { themeObject } = require('@apache-superset/core/ui');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { App } = require('antd');

View File

@@ -50,7 +50,7 @@ if (isBrowser) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const SupersetComponents = require('@superset/components');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { Alert } = require('@apache-superset/core/components');
const { Alert } = require('@apache-superset/core/ui');
console.log('[ReactLiveScope] SupersetComponents keys:', Object.keys(SupersetComponents || {}).slice(0, 10));
console.log('[ReactLiveScope] Has Button?', 'Button' in (SupersetComponents || {}));

View File

@@ -18,7 +18,7 @@
*/
/**
* Type declarations for @apache-superset/core/components
* Type declarations for @apache-superset/core/ui
*
* AUTO-GENERATED by scripts/generate-superset-components.mjs
* Do not edit manually - regenerate by running: yarn generate:superset-components

View File

@@ -156,9 +156,9 @@ export default function webpackExtendPlugin(): Plugin<void> {
// to source so the docs build doesn't depend on pre-built lib/ artifacts.
// More specific sub-path aliases must come first; webpack matches the
// longest prefix.
'@apache-superset/core/components': path.resolve(
'@apache-superset/core/ui': path.resolve(
__dirname,
'../../superset-frontend/packages/superset-core/src/components',
'../../superset-frontend/packages/superset-core/src/ui',
),
'@apache-superset/core/api/core': path.resolve(
__dirname,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 358 KiB

After

Width:  |  Height:  |  Size: 358 KiB

View File

@@ -14,10 +14,10 @@
"paths": {
"@superset-ui/core": ["../superset-frontend/packages/superset-ui-core/src"],
"@superset-ui/core/*": ["../superset-frontend/packages/superset-ui-core/src/*"],
// Types for @apache-superset/core/components are auto-generated by scripts/generate-superset-components.mjs
// Types for @apache-superset/core/ui are auto-generated by scripts/generate-superset-components.mjs
// Runtime resolution uses webpack alias pointing to actual source (see src/webpack.extend.ts)
// Using /ui path matches the established pattern used throughout the Superset codebase
"@apache-superset/core/components": ["./src/types/apache-superset-core"],
"@apache-superset/core/ui": ["./src/types/apache-superset-core"],
"*": ["src/*", "node_modules/*"]
}
},

View File

@@ -4511,6 +4511,11 @@
node-addon-api "^8.3.1"
node-gyp-build "^4.8.4"
"@trysound/sax@0.2.0":
version "0.2.0"
resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz"
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
"@tybys/wasm-util@^0.10.1":
version "0.10.1"
resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414"
@@ -5509,7 +5514,12 @@ acorn@^6.1.1:
resolved "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz"
integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==
acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0, acorn@^8.16.0:
acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0:
version "8.15.0"
resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz"
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
acorn@^8.16.0:
version "8.16.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a"
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
@@ -7705,10 +7715,10 @@ encodeurl@~2.0.0:
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
enhanced-resolve@^5.20.0:
version "5.20.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz#323c2a70d2aa7fb4bdfd6d3c24dfc705c581295d"
integrity sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==
enhanced-resolve@^5.19.0:
version "5.19.0"
resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz"
integrity sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.3.0"
@@ -13794,11 +13804,6 @@ sax@^1.2.4:
resolved "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz"
integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
sax@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.5.0.tgz#b5549b671069b7aa392df55ec7574cf411179eb8"
integrity sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==
scheduler@^0.23.2:
version "0.23.2"
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz"
@@ -13935,7 +13940,7 @@ serialize-error@^8.1.0:
dependencies:
type-fest "^0.20.2"
serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
serialize-javascript@^6.0.0, serialize-javascript@^6.0.1, serialize-javascript@^6.0.2:
version "6.0.2"
resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz"
integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==
@@ -14565,17 +14570,17 @@ svg-parser@^2.0.4:
integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==
svgo@^3.0.2, svgo@^3.2.0:
version "3.3.3"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.3.3.tgz#8246aee0b08791fde3b0ed22b5661b471fadf58e"
integrity sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==
version "3.3.2"
resolved "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz"
integrity sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==
dependencies:
"@trysound/sax" "0.2.0"
commander "^7.2.0"
css-select "^5.1.0"
css-tree "^2.3.1"
css-what "^6.1.0"
csso "^5.0.5"
picocolors "^1.0.0"
sax "^1.5.0"
swagger-client@^3.37.0:
version "3.37.0"
@@ -14686,14 +14691,15 @@ tapable@^2.0.0, tapable@^2.2.1, tapable@^2.3.0:
resolved "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz"
integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==
terser-webpack-plugin@^5.3.17, terser-webpack-plugin@^5.3.9:
version "5.3.17"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.17.tgz#75ea98876297fbb190d2fbb395e982582b859a67"
integrity sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==
terser-webpack-plugin@^5.3.16, terser-webpack-plugin@^5.3.9:
version "5.3.16"
resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz"
integrity sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==
dependencies:
"@jridgewell/trace-mapping" "^0.3.25"
jest-worker "^27.4.5"
schema-utils "^4.3.0"
serialize-javascript "^6.0.2"
terser "^5.31.1"
terser@^5.10.0, terser@^5.15.1, terser@^5.31.1:
@@ -15585,6 +15591,11 @@ webpack-merge@^6.0.1:
flat "^5.0.2"
wildcard "^2.0.1"
webpack-sources@^3.3.3:
version "3.3.3"
resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz"
integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==
webpack-sources@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.4.tgz#a338b95eb484ecc75fbb196cbe8a2890618b4891"
@@ -15595,10 +15606,10 @@ webpack-virtual-modules@^0.6.2:
resolved "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz"
integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==
webpack@^5.105.4, webpack@^5.88.1, webpack@^5.95.0:
version "5.105.4"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.105.4.tgz#1b77fcd55a985ac7ca9de80a746caffa38220169"
integrity sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==
webpack@^5.105.3:
version "5.105.3"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.105.3.tgz#307ad95bafffd08bc81049d6519477b16e42e7ba"
integrity sha512-LLBBA4oLmT7sZdHiYE/PeVuifOxYyE2uL/V+9VQP7YSYdJU7bSf7H8bZRRxW8kEPMkmVjnrXmoR3oejIdX0xbg==
dependencies:
"@types/eslint-scope" "^3.7.7"
"@types/estree" "^1.0.8"
@@ -15610,7 +15621,7 @@ webpack@^5.105.4, webpack@^5.88.1, webpack@^5.95.0:
acorn-import-phases "^1.0.3"
browserslist "^4.28.1"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.20.0"
enhanced-resolve "^5.19.0"
es-module-lexer "^2.0.0"
eslint-scope "5.1.1"
events "^3.2.0"
@@ -15622,10 +15633,41 @@ webpack@^5.105.4, webpack@^5.88.1, webpack@^5.95.0:
neo-async "^2.6.2"
schema-utils "^4.3.3"
tapable "^2.3.0"
terser-webpack-plugin "^5.3.17"
terser-webpack-plugin "^5.3.16"
watchpack "^2.5.1"
webpack-sources "^3.3.4"
webpack@^5.88.1, webpack@^5.95.0:
version "5.105.2"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.105.2.tgz#f3b76f9fc36f1152e156e63ffda3bbb82e6739ea"
integrity sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==
dependencies:
"@types/eslint-scope" "^3.7.7"
"@types/estree" "^1.0.8"
"@types/json-schema" "^7.0.15"
"@webassemblyjs/ast" "^1.14.1"
"@webassemblyjs/wasm-edit" "^1.14.1"
"@webassemblyjs/wasm-parser" "^1.14.1"
acorn "^8.15.0"
acorn-import-phases "^1.0.3"
browserslist "^4.28.1"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.19.0"
es-module-lexer "^2.0.0"
eslint-scope "5.1.1"
events "^3.2.0"
glob-to-regexp "^0.4.1"
graceful-fs "^4.2.11"
json-parse-even-better-errors "^2.3.1"
loader-runner "^4.3.1"
mime-types "^2.1.27"
neo-async "^2.6.2"
schema-utils "^4.3.3"
tapable "^2.3.0"
terser-webpack-plugin "^5.3.16"
watchpack "^2.5.1"
webpack-sources "^3.3.3"
webpackbar@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz"

View File

@@ -144,7 +144,7 @@ solr = ["sqlalchemy-solr >= 0.2.0"]
elasticsearch = ["elasticsearch-dbapi>=0.2.12, <0.3.0"]
exasol = ["sqlalchemy-exasol >= 2.4.0, <3.0"]
excel = ["xlrd>=1.2.0, <1.3"]
fastmcp = ["fastmcp>=3.1.0,<4.0"]
fastmcp = ["fastmcp==2.14.3"]
firebird = ["sqlalchemy-firebird>=0.7.0, <0.8"]
firebolt = ["firebolt-sqlalchemy>=1.0.0, <2"]
gevent = ["gevent>=23.9.1"]
@@ -372,7 +372,6 @@ unfixable = []
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.lint.per-file-ignores]
"superset/mcp_service/app.py" = ["S608", "E501"] # LLM instruction text: SQL examples (S608) and long lines in multiline string (E501)
"scripts/*" = ["TID251"]
"setup.py" = ["TID251"]
"superset/config.py" = ["TID251"]

View File

@@ -18,8 +18,8 @@
#
# Security: CVE-2026-21441 - decompression bomb bypass on redirects
urllib3>=2.6.3,<3.0.0
# Security: CVE-2026-27199 - Windows device name handling in safe_join
werkzeug>=3.1.6,<4.0.0
# Security: GHSA-87hc-h4r5-73f7 - Windows path traversal fix
werkzeug>=3.1.5,<4.0.0
# Security: CVE-2025-68146 - TOCTOU symlink vulnerability
filelock>=3.20.3,<4.0.0
# Security: decompression bomb fix (required by aiohttp 3.13.3)

View File

@@ -54,7 +54,7 @@ certifi==2025.6.15
# via
# requests
# selenium
cffi==2.0.0
cffi==1.17.1
# via
# cryptography
# pynacl
@@ -86,7 +86,7 @@ cron-descriptor==1.4.5
# via apache-superset (pyproject.toml)
croniter==6.0.0
# via apache-superset (pyproject.toml)
cryptography==46.0.5
cryptography==44.0.3
# via
# apache-superset (pyproject.toml)
# paramiko
@@ -219,7 +219,7 @@ markupsafe==3.0.2
# mako
# werkzeug
# wtforms
marshmallow==3.26.2
marshmallow==3.26.1
# via
# apache-superset (pyproject.toml)
# flask-appbuilder
@@ -317,9 +317,9 @@ pyjwt==2.10.1
# flask-appbuilder
# flask-jwt-extended
# redis
pynacl==1.6.2
pynacl==1.5.0
# via paramiko
pyopenssl==25.3.0
pyopenssl==25.1.0
# via shillelagh
pyparsing==3.2.3
# via apache-superset (pyproject.toml)
@@ -457,7 +457,7 @@ wcwidth==0.2.13
# via prompt-toolkit
websocket-client==1.8.0
# via selenium
werkzeug==3.1.6
werkzeug==3.1.5
# via
# -r requirements/base.in
# flask

View File

@@ -10,8 +10,6 @@
# via
# -r requirements/development.in
# apache-superset
aiofile==3.9.0
# via py-key-value-aio
alembic==1.15.2
# via
# -c requirements/base-constraint.txt
@@ -28,10 +26,8 @@ anyio==4.11.0
# via
# httpx
# mcp
# py-key-value-aio
# sse-starlette
# starlette
# watchfiles
apispec==6.6.1
# via
# -c requirements/base-constraint.txt
@@ -52,7 +48,7 @@ attrs==25.3.0
# referencing
# requests-cache
# trio
authlib==1.6.7
authlib==1.6.5
# via fastmcp
babel==2.17.0
# via
@@ -69,7 +65,9 @@ bcrypt==4.3.0
# -c requirements/base-constraint.txt
# paramiko
beartype==0.22.5
# via py-key-value-aio
# via
# py-key-value-aio
# py-key-value-shared
billiard==4.2.1
# via
# -c requirements/base-constraint.txt
@@ -102,8 +100,6 @@ cachetools==6.2.1
# -c requirements/base-constraint.txt
# google-auth
# py-key-value-aio
caio==0.9.25
# via aiofile
cattrs==25.1.1
# via
# -c requirements/base-constraint.txt
@@ -119,7 +115,7 @@ certifi==2025.6.15
# httpx
# requests
# selenium
cffi==2.0.0
cffi==1.17.1
# via
# -c requirements/base-constraint.txt
# cryptography
@@ -142,6 +138,7 @@ click==8.2.1
# click-repl
# flask
# flask-appbuilder
# typer
# uvicorn
click-didyoumean==0.3.1
# via
@@ -159,6 +156,8 @@ click-repl==0.3.0
# via
# -c requirements/base-constraint.txt
# celery
cloudpickle==3.1.2
# via pydocket
cmdstanpy==1.1.0
# via prophet
colorama==0.4.6
@@ -178,7 +177,7 @@ croniter==6.0.0
# via
# -c requirements/base-constraint.txt
# apache-superset
cryptography==46.0.5
cryptography==44.0.3
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -207,6 +206,8 @@ deprecation==2.1.0
# apache-superset
dill==0.4.0
# via pylint
diskcache==5.6.3
# via py-key-value-aio
distlib==0.3.8
# via virtualenv
dnspython==2.7.0
@@ -236,7 +237,9 @@ et-xmlfile==2.0.0
# openpyxl
exceptiongroup==1.3.0
# via fastmcp
fastmcp==3.1.0
fakeredis==2.32.1
# via pydocket
fastmcp==2.14.3
# via apache-superset
filelock==3.20.3
# via
@@ -471,8 +474,6 @@ jsonpath-ng==1.7.0
# via
# -c requirements/base-constraint.txt
# apache-superset
jsonref==1.1.0
# via fastmcp
jsonschema==4.23.0
# via
# -c requirements/base-constraint.txt
@@ -503,6 +504,8 @@ limits==5.1.0
# via
# -c requirements/base-constraint.txt
# flask-limiter
lupa==2.6
# via fakeredis
mako==1.3.10
# via
# -c requirements/base-constraint.txt
@@ -523,7 +526,7 @@ markupsafe==3.0.2
# mako
# werkzeug
# wtforms
marshmallow==3.26.2
marshmallow==3.26.1
# via
# -c requirements/base-constraint.txt
# apache-superset
@@ -600,7 +603,7 @@ openpyxl==3.1.5
# -c requirements/base-constraint.txt
# pandas
opentelemetry-api==1.39.1
# via fastmcp
# via pydocket
ordered-set==4.1.0
# via
# -c requirements/base-constraint.txt
@@ -619,7 +622,6 @@ packaging==25.0
# deprecation
# docker
# duckdb-engine
# fastmcp
# google-cloud-bigquery
# gunicorn
# limits
@@ -651,6 +653,8 @@ parsedatetime==2.6
# apache-superset
pathable==0.4.3
# via jsonschema-path
pathvalidate==3.3.1
# via py-key-value-aio
pgsanity==0.2.9
# via
# -c requirements/base-constraint.txt
@@ -687,6 +691,8 @@ prison==0.2.1
# flask-appbuilder
progress==1.6
# via apache-superset
prometheus-client==0.23.1
# via pydocket
prompt-toolkit==3.0.51
# via
# -c requirements/base-constraint.txt
@@ -697,7 +703,7 @@ proto-plus==1.25.0
# via
# google-api-core
# google-cloud-bigquery-storage
protobuf==4.25.8
protobuf==4.25.5
# via
# google-api-core
# google-cloud-bigquery-storage
@@ -708,8 +714,12 @@ psutil==6.1.0
# via apache-superset
psycopg2-binary==2.9.9
# via apache-superset
py-key-value-aio==0.4.4
# via fastmcp
py-key-value-aio==0.3.0
# via
# fastmcp
# pydocket
py-key-value-shared==0.3.0
# via py-key-value-aio
pyarrow==16.1.0
# via
# -c requirements/base-constraint.txt
@@ -748,6 +758,8 @@ pydantic-settings==2.10.1
# via mcp
pydata-google-auth==1.9.0
# via pandas-gbq
pydocket==0.17.1
# via fastmcp
pydruid==0.6.9
# via apache-superset
pyfakefs==5.3.5
@@ -774,11 +786,11 @@ pyjwt==2.10.1
# redis
pylint==3.3.7
# via apache-superset
pynacl==1.6.2
pynacl==1.5.0
# via
# -c requirements/base-constraint.txt
# paramiko
pyopenssl==25.3.0
pyopenssl==25.1.0
# via
# -c requirements/base-constraint.txt
# shillelagh
@@ -832,6 +844,8 @@ python-dotenv==1.1.0
# apache-superset
# fastmcp
# pydantic-settings
python-json-logger==4.0.0
# via pydocket
python-ldap==3.4.4
# via apache-superset
python-multipart==0.0.20
@@ -852,13 +866,15 @@ pyyaml==6.0.2
# -c requirements/base-constraint.txt
# apache-superset
# apispec
# fastmcp
# jsonschema-path
# pre-commit
redis==5.3.1
# via
# -c requirements/base-constraint.txt
# apache-superset
# fakeredis
# py-key-value-aio
# pydocket
referencing==0.36.2
# via
# -c requirements/base-constraint.txt
@@ -894,7 +910,9 @@ rich==13.9.4
# cyclopts
# fastmcp
# flask-limiter
# pydocket
# rich-rst
# typer
rich-rst==1.3.1
# via cyclopts
rpds-py==0.25.0
@@ -926,6 +944,8 @@ setuptools==80.9.0
# pydata-google-auth
# zope-event
# zope-interface
shellingham==1.5.4
# via typer
shillelagh==1.4.3
# via
# -c requirements/base-constraint.txt
@@ -953,6 +973,7 @@ sniffio==1.3.1
sortedcontainers==2.4.0
# via
# -c requirements/base-constraint.txt
# fakeredis
# trio
sqlalchemy==1.4.54
# via
@@ -988,7 +1009,7 @@ sshtunnel==0.4.0
# via
# -c requirements/base-constraint.txt
# apache-superset
starlette==0.49.1
starlette==0.48.0
# via mcp
statsd==4.0.1
# via apache-superset
@@ -1013,6 +1034,8 @@ trio-websocket==0.12.2
# via
# -c requirements/base-constraint.txt
# selenium
typer==0.20.0
# via pydocket
typing-extensions==4.15.0
# via
# -c requirements/base-constraint.txt
@@ -1025,14 +1048,16 @@ typing-extensions==4.15.0
# limits
# mcp
# opentelemetry-api
# py-key-value-aio
# py-key-value-shared
# pydantic
# pydantic-core
# pydocket
# pyopenssl
# referencing
# selenium
# shillelagh
# starlette
# typer
# typing-inspection
typing-inspection==0.4.1
# via
@@ -1047,8 +1072,6 @@ tzdata==2025.2
# pandas
tzlocal==5.2
# via trino
uncalled-for==0.2.0
# via fastmcp
url-normalize==2.2.1
# via
# -c requirements/base-constraint.txt
@@ -1078,8 +1101,6 @@ watchdog==6.0.0
# -c requirements/base-constraint.txt
# apache-superset
# apache-superset-extensions-cli
watchfiles==1.1.1
# via fastmcp
wcwidth==0.2.13
# via
# -c requirements/base-constraint.txt
@@ -1090,7 +1111,7 @@ websocket-client==1.8.0
# selenium
websockets==15.0.1
# via fastmcp
werkzeug==3.1.6
werkzeug==3.1.5
# via
# -c requirements/base-constraint.txt
# flask

View File

@@ -45,17 +45,7 @@ if [ ${#js_ts_files[@]} -gt 0 ]; then
# Skip custom OXC build in pre-commit for speed
export SKIP_CUSTOM_OXC=true
# Use quiet mode in pre-commit to reduce noise (only show errors)
# Capture output so we can treat "No files found" (all files ignored by
# ignorePatterns) as success rather than a false-positive failure.
output=$(npx oxlint --config oxlint.json --fix --quiet "${js_ts_files[@]}" 2>&1) || {
if echo "$output" | grep -q "No files found"; then
echo "No files to lint after applying ignore patterns"
exit 0
fi
echo "$output" >&2
exit 1
}
[ -n "$output" ] && echo "$output"
npx oxlint --config oxlint.json --fix --quiet "${js_ts_files[@]}"
else
echo "No JavaScript/TypeScript files to lint"
fi

View File

@@ -31,70 +31,70 @@ The official core package for building Apache Superset backend extensions and in
pip install apache-superset-core
```
## 🏗️ Package Structure
## 🏗️ Architecture
```
src/superset_core/
├── common/
├── extensions/
├── mcp/
├── queries/
├── rest_api/
├── tasks/
└── __init__.py
```
The package is organized into logical modules, each providing specific functionality:
- **`api`** - REST API base classes, models access, query utilities, and registration
- **`api.models`** - Access to Superset's database models (datasets, databases, etc.)
- **`api.query`** - Database query utilities and SQL dialect handling
- **`api.rest_api`** - Extension API registration and management
- **`api.types.rest_api`** - REST API base classes and type definitions
## 🚀 Quick Start
### Basic Extension API
### Basic Extension Structure
```python
from flask import request, Response
from flask_appbuilder.api import expose, permission_name, protect, safe
from superset_core.rest_api.api import RestApi
from superset_core.rest_api.decorators import api
from superset_core.api import models, query, rest_api
from superset_core.api.rest_api import RestApi
@api(id="dataset_references", name="Dataset References API")
class DatasetReferencesAPI(RestApi):
"""Example extension API demonstrating core functionality."""
resource_name = "dataset_references"
openapi_spec_tag = "Dataset references"
class_permission_name = "dataset_references"
@expose("/metadata", methods=("POST",))
@protect()
@safe
@permission_name("read")
def metadata(self) -> Response:
# ... endpoint implementation
"""Get dataset metadata for tables referenced in SQL."""
sql: str = request.json.get("sql")
database_id: int = request.json.get("databaseId")
# Access Superset's models using core APIs
databases = models.get_databases(id=database_id)
if not databases:
return self.response_404()
database = databases[0]
dialect = query.get_sqlglot_dialect(database)
# Access datasets to get owner information
datasets = models.get_datasets()
owners_map = {
dataset.table_name: [
f"{owner.first_name} {owner.last_name}"
for owner in dataset.owners
]
for dataset in datasets
}
# Process SQL and return dataset metadata
return self.response(200, result=owners_map)
# Register the extension API
rest_api.add_extension_api(DatasetReferencesAPI)
```
### Background Tasks
## 🤝 Contributing
```python
from superset_core.tasks.decorators import task
from superset_core.tasks.types import TaskScope
@task(name="generate_report", scope=TaskScope.SHARED)
def generate_report(chart_id: int) -> None:
# ... task implementation
```
### MCP Tools
```python
from superset_core.mcp.decorators import tool
@tool(name="my_tool", description="Custom business logic", tags=["extension"])
def my_extension_tool(param: str) -> dict:
# ... tool implementation
```
### MCP Prompts
```python
from superset_core.mcp.decorators import prompt
@prompt(name="my_prompt", title="My Prompt", description="Interactive prompt", tags={"extension"})
async def my_prompt_handler(ctx: Context) -> str:
# ... prompt implementation
```
We welcome contributions! Please see the [Developer Portal](https://superset.apache.org/developer_portal/) for details.
## 📄 License
@@ -102,6 +102,12 @@ Licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com
## 🔗 Links
- [Apache Superset](https://superset.apache.org/)
- [Documentation](https://superset.apache.org/docs/)
- [Community](https://superset.apache.org/community/)
- [GitHub Repository](https://github.com/apache/superset)
- [Extensions Documentation](https://superset.apache.org/developer-docs/extensions/overview)
- [Extension Development Guide](https://superset.apache.org/docs/extensions/)
---
**Note**: This package is currently in release candidate status. APIs may change before the 1.0.0 release. Please check the [changelog](CHANGELOG.md) for breaking changes between versions.

View File

@@ -18,20 +18,20 @@
[project]
name = "apache-superset-core"
version = "0.1.0rc3"
version = "0.0.1rc4"
description = "Core Python package for building Apache Superset backend extensions and integrations"
readme = "README.md"
authors = [
{ name = "Apache Software Foundation", email = "dev@superset.apache.org" },
]
license = "Apache-2.0"
license-files = ["LICENSE.txt"]
license = { file="LICENSE.txt" }
requires-python = ">=3.10"
keywords = ["superset", "apache", "analytics", "business-intelligence", "extensions", "visualization"]
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",

View File

@@ -16,13 +16,13 @@
# under the License.
"""
Common Data Access Object API for superset-core.
Data Access Object API for superset-core.
Provides dependency-injected DAO classes that will be replaced by
host implementations during initialization.
Usage:
from superset_core.common.daos import DatasetDAO, DatabaseDAO
from superset_core.api.daos import DatasetDAO, DatabaseDAO
# Use standard BaseDAO methods
datasets = DatasetDAO.find_all()
@@ -36,14 +36,17 @@ from typing import Any, ClassVar, Generic, TypeVar
from flask_appbuilder.models.filters import BaseFilter
from sqlalchemy.orm import Query as SQLAQuery
from superset_core.common.models import (
from superset_core.api.models import (
Chart,
CoreModel,
Dashboard,
Database,
Dataset,
KeyValue,
Query,
SavedQuery,
Tag,
Task,
User,
)
@@ -190,6 +193,34 @@ class UserDAO(BaseDAO[User]):
id_column_name = "id"
class QueryDAO(BaseDAO[Query]):
"""
Abstract Query DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
class SavedQueryDAO(BaseDAO[SavedQuery]):
"""
Abstract SavedQuery DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
class TagDAO(BaseDAO[Tag]):
"""
Abstract Tag DAO interface.
@@ -218,6 +249,48 @@ class KeyValueDAO(BaseDAO[KeyValue]):
id_column_name = "id"
class TaskDAO(BaseDAO[Task]):
"""
Abstract Task DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
uuid_column_name = "uuid"
@classmethod
@abstractmethod
def find_by_task_key(
cls,
task_type: str,
task_key: str,
scope: str = "private",
user_id: int | None = None,
) -> Task | None:
"""
Find active task by type, key, scope, and user.
Uses dedup_key internally for efficient querying with a unique index.
Only returns tasks that are active (pending or in progress).
Uniqueness logic by scope:
- private: scope + task_type + task_key + user_id
- shared/system: scope + task_type + task_key (user-agnostic)
:param task_type: Task type to filter by
:param task_key: Task identifier for deduplication
:param scope: Task scope (private/shared/system)
:param user_id: User ID (required for private tasks)
:returns: Task instance or None if not found or not active
"""
...
__all__ = [
"BaseDAO",
"DatasetDAO",
@@ -225,6 +298,9 @@ __all__ = [
"ChartDAO",
"DashboardDAO",
"UserDAO",
"QueryDAO",
"SavedQueryDAO",
"TagDAO",
"KeyValueDAO",
"TaskDAO",
]

View File

@@ -22,7 +22,7 @@ This module provides a decorator interface to register MCP tools with the
host application.
Usage:
from superset_core.mcp.decorators import tool
from superset_core.api.mcp import tool
@tool(name="my_tool", description="Custom business logic", tags=["extension"])
def my_extension_tool(param: str) -> dict:
@@ -37,13 +37,6 @@ Usage:
from typing import Any, Callable, TypeVar
try:
from mcp.types import ToolAnnotations
except (
ImportError
): # MCP extras may not be installed in superset-core-only environments
ToolAnnotations = dict
# Type variable for decorated functions
F = TypeVar("F", bound=Callable[..., Any])
@@ -55,15 +48,11 @@ def tool(
description: str | None = None,
tags: list[str] | None = None,
protect: bool = True,
class_permission_name: str | None = None,
method_permission_name: str | None = None,
annotations: ToolAnnotations | None = None,
) -> Any: # Use Any to avoid mypy issues with dependency injection
"""
Decorator to register an MCP tool with optional authentication.
This decorator combines FastMCP tool registration with optional authentication
and RBAC permission checking.
This decorator combines FastMCP tool registration with optional authentication.
Can be used as:
@tool
@@ -80,13 +69,6 @@ def tool(
description: Tool description (defaults to function docstring)
tags: List of tags for categorizing the tool (defaults to empty list)
protect: Whether to require Superset authentication (defaults to True)
class_permission_name: FAB view/resource name for RBAC checking
(e.g., "Chart", "Dashboard", "SQLLab"). When set, enables
permission checking via security_manager.can_access().
method_permission_name: FAB action name (e.g., "read", "write").
Defaults to "write" if tags includes "mutate", else "read".
annotations: MCP tool annotations (title, readOnlyHint, destructiveHint, etc.)
These hints help MCP clients understand tool behavior and safety.
Returns:
Decorator function that registers and wraps the tool, or the wrapped function
@@ -108,18 +90,6 @@ def tool(
def public_tool() -> str:
'''Public tool accessible without auth'''
return "Hello world"
@tool(class_permission_name="Chart") # RBAC: requires can_read on Chart
def list_charts() -> list:
'''List charts the user can access'''
return []
@tool( # RBAC: can_write on Chart
tags=["mutate"], class_permission_name="Chart",
)
def create_chart(name: str) -> dict:
'''Create a new chart'''
return {"name": name}
"""
raise NotImplementedError(
"MCP tool decorator not initialized. "
@@ -188,5 +158,4 @@ def prompt(
__all__ = [
"tool",
"prompt",
"ToolAnnotations",
]

View File

@@ -16,13 +16,13 @@
# under the License.
"""
Common model API for superset-core.
Model API for superset-core.
Provides core model classes that will be replaced by host implementations
Provides model classes that will be replaced by host implementations
during initialization for extension developers to use.
Usage:
from superset_core.common.models import Dataset, Database, get_session
from superset_core.api.models import Dataset, Database, get_session
# Use as regular model classes
dataset = Dataset(name="My Dataset")
@@ -40,7 +40,8 @@ from flask_appbuilder import Model
from sqlalchemy.orm import scoped_session
if TYPE_CHECKING:
from superset_core.queries.types import (
from superset_core.api.tasks import TaskProperties
from superset_core.api.types import (
AsyncQueryHandle,
QueryOptions,
QueryResult,
@@ -87,8 +88,8 @@ class Database(CoreModel):
def execute(
self,
sql: str,
options: "QueryOptions | None" = None,
) -> "QueryResult":
options: QueryOptions | None = None,
) -> QueryResult:
"""
Execute SQL synchronously.
@@ -102,8 +103,8 @@ class Database(CoreModel):
:returns: QueryResult with status, data (DataFrame), and metadata
Example:
from superset_core.common.daos import DatabaseDAO
from superset_core.queries.types import QueryOptions, QueryStatus
from superset_core.api.daos import DatabaseDAO
from superset_core.api.types import QueryOptions, QueryStatus
db = DatabaseDAO.find_one_or_none(id=1)
result = db.execute(
@@ -135,8 +136,8 @@ class Database(CoreModel):
def execute_async(
self,
sql: str,
options: "QueryOptions | None" = None,
) -> "AsyncQueryHandle":
options: QueryOptions | None = None,
) -> AsyncQueryHandle:
"""
Execute SQL asynchronously.
@@ -285,6 +286,47 @@ class User(CoreModel):
active: bool
class Query(CoreModel):
"""
Abstract Query model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
client_id: str | None
database_id: int | None
sql: str | None
status: str | None
user_id: int | None
progress: int
error_message: str | None
class SavedQuery(CoreModel):
"""
Abstract SavedQuery model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
uuid: UUID | None
label: str | None
sql: str | None
database_id: int | None
description: str | None
user_id: int | None
class Tag(CoreModel):
"""
Abstract Tag model interface.
@@ -320,6 +362,132 @@ class KeyValue(CoreModel):
changed_by_fk: int | None
class Task(CoreModel):
"""
Abstract Task model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
This model represents async tasks in the Global Task Framework (GTF).
Non-filterable fields (progress, error info, execution config) are stored
in a `properties` JSON blob for schema flexibility.
"""
__abstract__ = True
# Type hints for expected column attributes
id: int
uuid: UUID
task_key: str # For deduplication
task_type: str # e.g., 'sql_execution'
task_name: str | None # Human readable name
scope: str # private/shared/system
status: str
dedup_key: str # Computed deduplication key
# Timestamps (from AuditMixinNullable)
created_on: datetime | None
changed_on: datetime | None
started_at: datetime | None
ended_at: datetime | None
# User context
created_by_fk: int | None
user_id: int | None
# Task output data
payload: str # JSON serialized task output data
def get_payload(self) -> dict[str, Any]:
"""
Get payload as parsed JSON.
Payload contains task-specific output data set by task code.
Host implementations will replace this method during initialization
with concrete implementation providing actual functionality.
:returns: Dictionary containing payload data
"""
raise NotImplementedError("Method will be replaced during initialization")
def set_payload(self, data: dict[str, Any]) -> None:
"""
Update payload with new data (merges with existing).
Host implementations will replace this method during initialization
with concrete implementation providing actual functionality.
:param data: Dictionary of data to merge into payload
"""
raise NotImplementedError("Method will be replaced during initialization")
@property
def properties(self) -> Any:
"""
Get typed properties (runtime state and execution config).
Properties contain:
- is_abortable: bool | None - has abort handler registered
- progress_percent: float | None - progress 0.0-1.0
- progress_current: int | None - current iteration count
- progress_total: int | None - total iterations
- error_message: str | None - human-readable error message
- exception_type: str | None - exception class name
- stack_trace: str | None - full formatted traceback
- timeout: int | None - timeout in seconds
Host implementations will replace this property during initialization.
:returns: TaskProperties dataclass instance
"""
raise NotImplementedError("Property will be replaced during initialization")
def update_properties(self, updates: "TaskProperties") -> None:
"""
Update specific properties fields (merge semantics).
Only updates fields present in the updates dict.
Host implementations will replace this method during initialization.
:param updates: TaskProperties dict with fields to update
Example:
task.update_properties({"is_abortable": True})
"""
raise NotImplementedError("Method will be replaced during initialization")
class TaskSubscriber(CoreModel):
"""
Abstract TaskSubscriber model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
This model tracks task subscriptions for multi-user shared tasks. When a user
schedules a shared task with the same parameters as an existing task,
they are subscribed to that task instead of creating a duplicate.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
task_id: int
user_id: int
subscribed_at: datetime
# Audit fields from AuditMixinNullable
created_on: datetime | None
changed_on: datetime | None
created_by_fk: int | None
changed_by_fk: int | None
def get_session() -> scoped_session:
"""
Retrieve the SQLAlchemy session to directly interface with the
@@ -339,8 +507,12 @@ __all__ = [
"Chart",
"Dashboard",
"User",
"Query",
"SavedQuery",
"Tag",
"KeyValue",
"Task",
"TaskSubscriber",
"CoreModel",
"get_session",
]

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