mirror of
https://github.com/apache/superset.git
synced 2026-07-29 10:02:32 +00:00
Compare commits
4 Commits
fix/docs-d
...
feat/csp-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
febe71ff8c | ||
|
|
9e708789ea | ||
|
|
64671e7c09 | ||
|
|
2094e4d0e4 |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1,6 +1,6 @@
|
||||
# Notify all committers of DB migration changes, per SIP-59
|
||||
|
||||
/superset/migrations/ @mistercrunch @michael-s-molina @betodealmeida @eschutho @sadpandajoe @rusackas
|
||||
/superset/migrations/ @mistercrunch @michael-s-molina @betodealmeida @eschutho @sadpandajoe
|
||||
|
||||
# Notify some committers of changes in the components
|
||||
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -50,8 +50,9 @@ body:
|
||||
attributes:
|
||||
label: Python version
|
||||
options:
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- Not applicable
|
||||
- I don't know
|
||||
validations:
|
||||
|
||||
1
.github/actions/file-changes-action
vendored
Submodule
1
.github/actions/file-changes-action
vendored
Submodule
Submodule .github/actions/file-changes-action added at a6ca26c142
2
.github/actions/setup-backend/action.yml
vendored
2
.github/actions/setup-backend/action.yml
vendored
@@ -32,6 +32,8 @@ runs:
|
||||
elif [ "$INPUT_PYTHON_VERSION" = "next" ]; then
|
||||
# currently disabled in GHA matrixes because of library compatibility issues
|
||||
RESOLVED_VERSION="3.12"
|
||||
elif [ "$INPUT_PYTHON_VERSION" = "previous" ]; then
|
||||
RESOLVED_VERSION="3.10"
|
||||
elif printf '%s' "$INPUT_PYTHON_VERSION" | grep -Eq '^[0-9]+\.[0-9]+(\.[0-9]+)?$'; then
|
||||
RESOLVED_VERSION="$INPUT_PYTHON_VERSION"
|
||||
else
|
||||
|
||||
342
.github/dependabot.yml
vendored
342
.github/dependabot.yml
vendored
@@ -10,15 +10,8 @@ updates:
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
ignore:
|
||||
# TODO: remove below entries once the application supports React >= 19.0.0
|
||||
- dependency-name: "react"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "react-dom"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "@types/react"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "@types/react-dom"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "@rjsf/*"
|
||||
# TODO: remove below entries until React >= 19.0.0
|
||||
- dependency-name: "react-icons"
|
||||
# JSDOM v30 doesn't play well with Jest v30
|
||||
# Source: https://jestjs.io/blog#known-issues
|
||||
@@ -48,37 +41,12 @@ updates:
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
groups:
|
||||
rjsf:
|
||||
patterns:
|
||||
- "@rjsf/*"
|
||||
typescript-eslint:
|
||||
patterns:
|
||||
- "@typescript-eslint/*"
|
||||
- "typescript-eslint"
|
||||
babel:
|
||||
patterns:
|
||||
- "@babel/*"
|
||||
deckgl:
|
||||
patterns:
|
||||
- "@deck.gl/*"
|
||||
lumagl:
|
||||
patterns:
|
||||
- "@luma.gl/*"
|
||||
storybook:
|
||||
patterns:
|
||||
- "@storybook/*"
|
||||
- "storybook"
|
||||
- "eslint-plugin-storybook"
|
||||
ag-grid:
|
||||
patterns:
|
||||
- "ag-grid-react"
|
||||
- "ag-grid-community"
|
||||
open-pull-requests-limit: 30
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
open-pull-requests-limit: 10
|
||||
@@ -108,22 +76,6 @@ updates:
|
||||
directory: "/docs/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
storybook:
|
||||
patterns:
|
||||
- "@storybook/*"
|
||||
- "storybook"
|
||||
docusaurus:
|
||||
patterns:
|
||||
- "@docusaurus/*"
|
||||
docusaurus-openapi:
|
||||
patterns:
|
||||
- "docusaurus-plugin-openapi-docs"
|
||||
- "docusaurus-theme-openapi-docs"
|
||||
typescript-eslint:
|
||||
patterns:
|
||||
- "@typescript-eslint/*"
|
||||
- "typescript-eslint"
|
||||
open-pull-requests-limit: 10
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
@@ -151,3 +103,291 @@ updates:
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
# Now for all of our plugins and packages!
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-calendar/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-partition/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-world-map/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-pivot-table/"
|
||||
ignore:
|
||||
# TODO: remove below entries until React >= 19.0.0
|
||||
- dependency-name: "react-icons"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-chord/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-horizon/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-rose/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-preset-chart-deckgl/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-table/"
|
||||
ignore:
|
||||
# TODO: remove below entries until React >= 19.0.0
|
||||
- dependency-name: "react-icons"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-country-map/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-map-box/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-preset-chart-nvd3/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-word-cloud/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-paired-t-test/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-echarts/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-ag-grid-table/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-cartodiagram/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-parallel-coordinates/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-handlebars/"
|
||||
ignore:
|
||||
# `just-handlerbars-helpers` library in plugin-chart-handlebars requires `currencyformatter`` to be < 2
|
||||
- dependency-name: "currencyformatter.js"
|
||||
update-types: ["version-update:semver-major"]
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/packages/generator-superset/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/packages/superset-ui-chart-controls/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/packages/superset-ui-core/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/packages/superset-ui-switchboard/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
open-pull-requests-limit: 5
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
8
.github/workflows/bump-python-package.yml
vendored
8
.github/workflows/bump-python-package.yml
vendored
@@ -23,7 +23,7 @@ on:
|
||||
|
||||
jobs:
|
||||
bump-python-package:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
actions: write
|
||||
contents: write
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
checks: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: master
|
||||
@@ -40,9 +40,9 @@ jobs:
|
||||
uses: ./.github/actions/setup-supersetbot/
|
||||
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install uv
|
||||
run: pip install uv
|
||||
|
||||
6
.github/workflows/check-python-deps.yml
vendored
6
.github/workflows/check-python-deps.yml
vendored
@@ -19,10 +19,10 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
check-python-deps:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Login to Docker Hub
|
||||
if: steps.check.outputs.python
|
||||
continue-on-error: true
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
@@ -19,13 +19,13 @@ concurrency:
|
||||
jobs:
|
||||
check_db_migration_conflict:
|
||||
name: Check DB migration conflict
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check and notify
|
||||
|
||||
12
.github/workflows/codeql-analysis.yml
vendored
12
.github/workflows/codeql-analysis.yml
vendored
@@ -16,7 +16,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
frontend: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
# analysis runners don't spin up. push/schedule runs always proceed:
|
||||
# the change-detector returns "all changed" for non-PR events.
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.frontend == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -58,13 +58,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -75,6 +75,6 @@ jobs:
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
8
.github/workflows/dependency-review.yml
vendored
8
.github/workflows/dependency-review.yml
vendored
@@ -24,10 +24,10 @@ permissions:
|
||||
jobs:
|
||||
dependency-review:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Dependency Review"
|
||||
@@ -48,10 +48,10 @@ jobs:
|
||||
python-dependency-liccheck:
|
||||
# NOTE: Configuration for liccheck lives in our pyproject.yml.
|
||||
# You cannot use a liccheck.ini file in this workflow.
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
25
.github/workflows/docker.yml
vendored
25
.github/workflows/docker.yml
vendored
@@ -19,7 +19,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
docker: ${{ steps.check.outputs.docker }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
@@ -40,14 +40,14 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
setup_matrix:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
matrix_config: ${{ steps.set_matrix.outputs.matrix_config }}
|
||||
steps:
|
||||
- id: set_matrix
|
||||
run: |
|
||||
MATRIX_CONFIG=$(if [ "${{ github.event_name }}" == "pull_request" ]; then echo '["dev", "lean"]'; else echo '["dev", "lean", "websocket", "dockerize", "py311", "py312"]'; fi)
|
||||
MATRIX_CONFIG=$(if [ "${{ github.event_name }}" == "pull_request" ]; then echo '["dev", "lean"]'; else echo '["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]'; fi)
|
||||
echo "matrix_config=${MATRIX_CONFIG}" >> $GITHUB_OUTPUT
|
||||
echo $GITHUB_OUTPUT
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
needs.changes.outputs.python == 'true' ||
|
||||
needs.changes.outputs.frontend == 'true' ||
|
||||
needs.changes.outputs.docker == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -123,20 +123,13 @@ jobs:
|
||||
# pull timeouts, 504/401 on push, ECONNRESET) that otherwise fail
|
||||
# the whole job. buildx reuses the buildkit layer cache from the
|
||||
# failed attempt, so a retry mostly re-does just the failed push.
|
||||
#
|
||||
# supersetbot's "dev"/"lean" presets pin their own --build-arg
|
||||
# PY_VER, which lands ahead of --extra-flags on the assembled
|
||||
# buildx command line; docker/buildx keeps the last value for a
|
||||
# repeated --build-arg key, so appending PY_VER here overrides
|
||||
# supersetbot's pin and keeps the build on the Dockerfile's own
|
||||
# supported Python version.
|
||||
for attempt in 1 2 3; do
|
||||
if supersetbot docker \
|
||||
$PUSH_OR_LOAD \
|
||||
--preset "$BUILD_PRESET" \
|
||||
--context "$EVENT" \
|
||||
--context-ref "$RELEASE" $FORCE_LATEST \
|
||||
--extra-flags "--build-arg PY_VER=3.11.14-slim-trixie --build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG" \
|
||||
--extra-flags "--build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG" \
|
||||
$PLATFORM_ARG; then
|
||||
break
|
||||
fi
|
||||
@@ -180,11 +173,11 @@ jobs:
|
||||
# goal is to check that building the latest image works, not required for all PR pushes
|
||||
needs: changes
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.changes.outputs.docker == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Free up disk space
|
||||
|
||||
6
.github/workflows/embedded-sdk-release.yml
vendored
6
.github/workflows/embedded-sdk-release.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
# gate on. Restrict to the canonical repo: forks cannot mint a valid OIDC
|
||||
# token for this package and must not publish.
|
||||
if: github.repository == 'apache/superset'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # required for npm trusted publishing (OIDC)
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
run:
|
||||
working-directory: superset-embedded-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
# Note: registry-url is intentionally omitted. When set, actions/setup-node
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
# token, which makes npm attempt token auth and skip the OIDC
|
||||
# trusted-publishing exchange. With no .npmrc auth line, npm authenticates
|
||||
# via OIDC against the default registry (registry.npmjs.org).
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-embedded-sdk/.nvmrc"
|
||||
- run: npm ci
|
||||
|
||||
6
.github/workflows/embedded-sdk-test.yml
vendored
6
.github/workflows/embedded-sdk-test.yml
vendored
@@ -16,15 +16,15 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
embedded-sdk-test:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
defaults:
|
||||
run:
|
||||
working-directory: superset-embedded-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-embedded-sdk/.nvmrc"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
8
.github/workflows/generate-FOSSA-report.yml
vendored
8
.github/workflows/generate-FOSSA-report.yml
vendored
@@ -11,7 +11,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
config:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
has-secrets: ${{ steps.check.outputs.has-secrets }}
|
||||
steps:
|
||||
@@ -29,15 +29,15 @@ jobs:
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets
|
||||
name: Generate Report
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "11"
|
||||
|
||||
@@ -19,7 +19,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
validate-all-ghas:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
# Required for the zizmor action to upload its SARIF results to
|
||||
@@ -27,12 +27,12 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
@@ -46,4 +46,4 @@ jobs:
|
||||
run: bash .github/workflows/github-action-validator.sh
|
||||
|
||||
- name: Check for security issues on GHA workflows
|
||||
uses: zizmorcore/zizmor-action@6599ee8b7a49aef6a770f63d261d214911a7ce02 # v0.6.0
|
||||
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
|
||||
|
||||
115
.github/workflows/hold-label-ci-gate.yml
vendored
115
.github/workflows/hold-label-ci-gate.yml
vendored
@@ -1,115 +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.
|
||||
name: Hold Label CI Gate
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, unlabeled]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
cancel-on-hold:
|
||||
name: Cancel CI runs when hold label applied
|
||||
if: github.event.action == 'labeled' && startsWith(github.event.label.name, 'hold')
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: write
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: Cancel in-progress workflow runs
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const sha = context.payload.pull_request.head.sha;
|
||||
|
||||
const { data: { workflow_runs: runs } } =
|
||||
await github.rest.actions.listWorkflowRunsForRepo({
|
||||
owner,
|
||||
repo,
|
||||
head_sha: sha,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const active = runs.filter(r =>
|
||||
['in_progress', 'queued', 'waiting', 'requested', 'pending'].includes(r.status)
|
||||
);
|
||||
|
||||
core.info(`Found ${active.length} active run(s) to cancel for SHA ${sha}`);
|
||||
|
||||
for (const run of active) {
|
||||
try {
|
||||
await github.rest.actions.cancelWorkflowRun({
|
||||
owner,
|
||||
repo,
|
||||
run_id: run.id,
|
||||
});
|
||||
core.info(`Cancelled run ${run.id} (${run.name})`);
|
||||
} catch (err) {
|
||||
core.warning(`Could not cancel run ${run.id}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
rerun-on-unhold:
|
||||
name: Re-run CI when hold label removed
|
||||
if: github.event.action == 'unlabeled' && startsWith(github.event.label.name, 'hold')
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
actions: write
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: Re-trigger cancelled workflow runs
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const sha = context.payload.pull_request.head.sha;
|
||||
|
||||
// Check that no other hold* labels remain on the PR
|
||||
const labels = context.payload.pull_request.labels.map(l => l.name);
|
||||
const stillHeld = labels.some(l => l.startsWith('hold'));
|
||||
if (stillHeld) {
|
||||
core.info('PR still has a hold label — skipping re-run.');
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: { workflow_runs: runs } } =
|
||||
await github.rest.actions.listWorkflowRunsForRepo({
|
||||
owner,
|
||||
repo,
|
||||
head_sha: sha,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const cancelled = runs.filter(r => r.conclusion === 'cancelled');
|
||||
core.info(`Found ${cancelled.length} cancelled run(s) to re-trigger for SHA ${sha}`);
|
||||
|
||||
for (const run of cancelled) {
|
||||
try {
|
||||
await github.rest.actions.reRunWorkflow({
|
||||
owner,
|
||||
repo,
|
||||
run_id: run.id,
|
||||
});
|
||||
core.info(`Re-triggered run ${run.id} (${run.name})`);
|
||||
} catch (err) {
|
||||
core.warning(`Could not re-run ${run.id}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
4
.github/workflows/issue_creation.yml
vendored
4
.github/workflows/issue_creation.yml
vendored
@@ -9,14 +9,14 @@ on:
|
||||
|
||||
jobs:
|
||||
superbot-orglabel:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
4
.github/workflows/labeler.yml
vendored
4
.github/workflows/labeler.yml
vendored
@@ -12,9 +12,9 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
|
||||
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
|
||||
with:
|
||||
sync-labels: true
|
||||
|
||||
|
||||
4
.github/workflows/latest-release-tag.yml
vendored
4
.github/workflows/latest-release-tag.yml
vendored
@@ -6,13 +6,13 @@ on:
|
||||
jobs:
|
||||
latest-release:
|
||||
name: Add/update tag to new release
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
6
.github/workflows/license-check.yml
vendored
6
.github/workflows/license-check.yml
vendored
@@ -15,15 +15,15 @@ concurrency:
|
||||
jobs:
|
||||
license_check:
|
||||
name: License Check
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "11"
|
||||
|
||||
2
.github/workflows/no-hold-label.yml
vendored
2
.github/workflows/no-hold-label.yml
vendored
@@ -17,7 +17,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
check-hold-label:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check for 'hold' label
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
|
||||
14
.github/workflows/pr-lint.yml
vendored
14
.github/workflows/pr-lint.yml
vendored
@@ -8,26 +8,20 @@ on:
|
||||
# Possible values: https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-event-pull_request
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
# Serialize runs per PR without cancelling: the `edited` trigger means a PR
|
||||
# opened and then edited has two queued runs for the same head SHA. On
|
||||
# first-time-contributor PRs those runs start together when a maintainer
|
||||
# approves workflows, and cancel-in-progress lets the older run cancel the
|
||||
# newer one — leaving a permanently-cancelled required check on the head SHA
|
||||
# that blocks merging until manually re-run. This job takes seconds, so let
|
||||
# queued runs complete instead.
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: false
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint-check:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
107
.github/workflows/pre-commit.yml
vendored
107
.github/workflows/pre-commit.yml
vendored
@@ -7,11 +7,6 @@ on:
|
||||
- "[0-9].[0-9]*"
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened, ready_for_review]
|
||||
# Nightly full-tree sweep. Per-PR runs only lint changed files, so a change
|
||||
# that invalidates an untouched file (e.g. a type change that breaks an
|
||||
# importing test) can pass every PR yet leave master red. This catches that.
|
||||
schedule:
|
||||
- cron: "0 6 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -23,23 +18,20 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
# Run the full version spread on push (master/release) and nightly,
|
||||
# but only the current version on PRs — lint/format/type results
|
||||
# rarely differ across patch versions, so 3x per PR is wasteful.
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "previous", "next"]') }}
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
# Full history so we can diff a PR/push against its base commit to
|
||||
# determine changed files (see "Determine changed files" below).
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
@@ -47,13 +39,13 @@ jobs:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
||||
|
||||
- name: Install helm-docs
|
||||
run: go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.14.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
@@ -77,98 +69,19 @@ jobs:
|
||||
restore-keys: |
|
||||
pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-
|
||||
|
||||
- name: Determine changed files
|
||||
- name: Get changed files
|
||||
id: changed_files
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
BEFORE_SHA: ${{ github.event.before }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Scheduled runs check the whole tree (see the pre-commit step).
|
||||
if [ "${EVENT_NAME}" = "schedule" ]; then
|
||||
echo "mode=all" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Resolve the commit to diff against.
|
||||
base=""
|
||||
if [ "${EVENT_NAME}" = "pull_request" ]; then
|
||||
# HEAD is the PR merge commit, so its first parent is the current
|
||||
# tip of the base branch. github.event.pull_request.base.sha is
|
||||
# NOT that: GitHub freezes it at PR creation, so on a long-lived
|
||||
# PR it points at the original branch point and the diff picks up
|
||||
# all of the base branch's churn since then — thousands of paths,
|
||||
# enough for the CHANGED_FILES env var below to exceed the
|
||||
# kernel's per-variable size limit and kill the step with
|
||||
# "Argument list too long" before bash even starts.
|
||||
base="$(git rev-parse HEAD^1 2>/dev/null || true)"
|
||||
elif [ -n "${BEFORE_SHA:-}" ] && \
|
||||
[ "${BEFORE_SHA}" != "0000000000000000000000000000000000000000" ]; then
|
||||
base="${BEFORE_SHA}"
|
||||
fi
|
||||
|
||||
# Fail closed: if the diff base can't be resolved, check every file
|
||||
# instead of silently checking nothing. Previously an empty file list
|
||||
# made `pre-commit run --files` a no-op that still reported success,
|
||||
# which let unlinted code reach master.
|
||||
if [ -z "${base}" ] || ! git cat-file -e "${base}^{commit}" 2>/dev/null; then
|
||||
echo "::notice::Could not resolve a diff base; falling back to --all-files."
|
||||
echo "mode=all" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Files present in HEAD that changed since the base (drop deletions).
|
||||
files="$(git diff --name-only --diff-filter=ACMRT "${base}...HEAD")"
|
||||
|
||||
# Env vars have a hard per-variable size limit (E2BIG at step
|
||||
# start). A PR that legitimately touches thousands of files is
|
||||
# better served by --all-files anyway.
|
||||
if [ "$(printf '%s' "${files}" | wc -c)" -gt 100000 ]; then
|
||||
echo "::notice::Changed-file list too large to pass via env; falling back to --all-files."
|
||||
echo "mode=all" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -z "${files}" ]; then
|
||||
echo "mode=none" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "mode=files" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "files<<__CHANGED_FILES_EOF__"
|
||||
echo "${files}"
|
||||
echo "__CHANGED_FILES_EOF__"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
uses: ./.github/actions/file-changes-action
|
||||
with:
|
||||
output: " "
|
||||
|
||||
- name: pre-commit
|
||||
env:
|
||||
MODE: ${{ steps.changed_files.outputs.mode }}
|
||||
CHANGED_FILES: ${{ steps.changed_files.outputs.files }}
|
||||
run: |
|
||||
set +e # Don't exit immediately on failure
|
||||
export SKIP=type-checking-frontend
|
||||
|
||||
case "${MODE}" in
|
||||
all)
|
||||
echo "ℹ️ Running pre-commit on all files."
|
||||
pre-commit run --all-files
|
||||
;;
|
||||
files)
|
||||
echo "ℹ️ Running pre-commit on changed files:"
|
||||
echo "${CHANGED_FILES}"
|
||||
# shellcheck disable=SC2086
|
||||
pre-commit run --files ${CHANGED_FILES}
|
||||
;;
|
||||
none)
|
||||
echo "ℹ️ No source files changed; nothing for pre-commit to check."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "⚠️ Unrecognized changed-files mode '${MODE}'; checking all files."
|
||||
pre-commit run --all-files
|
||||
;;
|
||||
esac
|
||||
pre-commit run --files $CHANGED_FILES
|
||||
PRE_COMMIT_EXIT_CODE=$?
|
||||
git diff --quiet --exit-code
|
||||
GIT_DIFF_EXIT_CODE=$?
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -11,7 +11,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
config:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
has-secrets: ${{ steps.check.outputs.has-secrets }}
|
||||
steps:
|
||||
@@ -29,11 +29,11 @@ jobs:
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets
|
||||
name: Bump version and publish package(s)
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
# pulls all commits (needed for lerna / semantic release to correctly version)
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Install Node.js
|
||||
if: env.HAS_TAGS
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
config:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
has-secrets: ${{ steps.check.outputs.has-secrets }}
|
||||
latest-release: ${{ steps.latest.outputs.tag }}
|
||||
@@ -93,16 +93,16 @@ jobs:
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets == '1'
|
||||
name: docker-rebuild
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
# Mirror the same matrix the release publisher uses so every variant
|
||||
# operators consume from Docker Hub gets the refreshed base.
|
||||
matrix:
|
||||
build_preset: ["dev", "lean", "websocket", "dockerize", "py311", "py312"]
|
||||
build_preset: ["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: "Checkout release tag: ${{ needs.config.outputs.latest-release }}"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ needs.config.outputs.latest-release }}
|
||||
fetch-depth: 0
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
build: "true"
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
notify-on-failure:
|
||||
needs: [config, docker-rebuild]
|
||||
if: failure() && needs.config.outputs.has-secrets == '1'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
2
.github/workflows/showtime-trigger.yml
vendored
2
.github/workflows/showtime-trigger.yml
vendored
@@ -153,7 +153,7 @@ jobs:
|
||||
|
||||
- name: Checkout PR code (only if build needed)
|
||||
if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.build_needed == 'true'
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ steps.check.outputs.target_sha }}
|
||||
persist-credentials: false
|
||||
|
||||
4
.github/workflows/superset-app-cli.yml
vendored
4
.github/workflows/superset-app-cli.yml
vendored
@@ -19,7 +19,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
test-load-examples:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
73
.github/workflows/superset-docs-deploy.yml
vendored
73
.github/workflows/superset-docs-deploy.yml
vendored
@@ -18,12 +18,22 @@ on:
|
||||
|
||||
workflow_dispatch: {}
|
||||
|
||||
# Serialize deploys: the action pushes to apache/superset-site without
|
||||
# rebasing, so concurrent runs race on the final push and the loser fails
|
||||
# with `! [rejected] asf-site -> asf-site (fetch first)`. Cancel any
|
||||
# in-progress run as soon as a newer one starts — the destination repo
|
||||
# isn't touched until the final push step, so canceling mid-build is safe,
|
||||
# and the freshest content always wins.
|
||||
concurrency:
|
||||
group: docs-deploy-asf-site
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
config:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
has-secrets: ${{ steps.check.outputs.has-secrets }}
|
||||
steps:
|
||||
@@ -37,82 +47,31 @@ jobs:
|
||||
|
||||
env:
|
||||
SUPERSET_SITE_BUILD: ${{ (secrets.SUPERSET_SITE_BUILD != '' && secrets.SUPERSET_SITE_BUILD != '') || '' }}
|
||||
|
||||
# Master gets frequent, sometimes bursty pushes, and each one can trigger a
|
||||
# deploy attempt. Rather than let every superseded attempt get force-killed
|
||||
# by the build-deploy concurrency group below (which shows up as a
|
||||
# `cancelled` — i.e. red/failing-looking — check on that commit), have each
|
||||
# run check up front whether it's still building master's current tip and,
|
||||
# if not, skip cleanly. Deliberately outside the docs-deploy-asf-site
|
||||
# concurrency group so it runs immediately for every trigger without
|
||||
# blocking or being blocked by anything.
|
||||
check-freshness:
|
||||
runs-on: ubuntu-26.04
|
||||
outputs:
|
||||
is-current: ${{ steps.check.outputs.is-current }}
|
||||
steps:
|
||||
- name: "Check whether this is still master's current commit"
|
||||
id: check
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
run: |
|
||||
# workflow_dispatch is a deliberate, one-off action (e.g. to deploy
|
||||
# a specific ref) rather than something racing other triggers, so
|
||||
# it always bypasses the freshness gate below.
|
||||
if [ "${EVENT_NAME}" = "workflow_dispatch" ]; then
|
||||
echo "is-current=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
latest_sha="$(gh api "repos/${{ github.repository }}/commits/master" --jq .sha)"
|
||||
if [ "${latest_sha}" = "${BUILD_SHA}" ]; then
|
||||
echo "is-current=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "is-current=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::notice::master has moved on to ${latest_sha} since ${BUILD_SHA} was triggered — a newer run will deploy it, so skipping this one."
|
||||
fi
|
||||
|
||||
build-deploy:
|
||||
needs: [config, check-freshness]
|
||||
# Only the run for master's current tip proceeds; anything superseded
|
||||
# already skipped at check-freshness above instead of landing here.
|
||||
needs: config
|
||||
# For workflow_run triggers, only deploy when the triggering run originated
|
||||
# from this repository (not a fork), ensuring the checked-out code and any
|
||||
# local actions executed with deploy credentials are trusted.
|
||||
if: >-
|
||||
needs.config.outputs.has-secrets &&
|
||||
needs.check-freshness.outputs.is-current == 'true' &&
|
||||
(github.event_name != 'workflow_run' ||
|
||||
github.event.workflow_run.head_repository.full_name == github.repository)
|
||||
name: Build & Deploy
|
||||
runs-on: ubuntu-26.04
|
||||
# Serialize deploys: the action pushes to apache/superset-site without
|
||||
# rebasing, so concurrent runs race on the final push and the loser fails
|
||||
# with `! [rejected] asf-site -> asf-site (fetch first)`. Cancel any
|
||||
# in-progress run as soon as a newer one starts — the destination repo
|
||||
# isn't touched until the final push step, so canceling mid-build is safe,
|
||||
# and the freshest content always wins. The check-freshness gate above
|
||||
# means it should be rare for more than one run to reach this point, but
|
||||
# this stays as a backstop against the actual push race.
|
||||
concurrency:
|
||||
group: docs-deploy-asf-site
|
||||
cancel-in-progress: true
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.event.workflow_run.head_sha || github.sha }}"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./docs/.nvmrc"
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
|
||||
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: "zulu"
|
||||
java-version: "21"
|
||||
|
||||
14
.github/workflows/superset-docs-verify.yml
vendored
14
.github/workflows/superset-docs-verify.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
name: Link Checking
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
# Do not bump this linkinator-action version without opening
|
||||
@@ -67,18 +67,18 @@ jobs:
|
||||
# Build docs when PR changes docs/** (uses committed databases.json)
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Build (PR trigger)
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./docs/.nvmrc"
|
||||
- name: yarn install
|
||||
@@ -106,19 +106,19 @@ jobs:
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.head_repository.full_name == github.repository
|
||||
name: Build (after integration tests)
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- name: "Checkout PR head: ${{ github.event.workflow_run.head_sha }}"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha }}
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./docs/.nvmrc"
|
||||
- name: yarn install
|
||||
|
||||
29
.github/workflows/superset-e2e.yml
vendored
29
.github/workflows/superset-e2e.yml
vendored
@@ -28,7 +28,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
frontend: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
@@ -50,7 +50,8 @@ jobs:
|
||||
cypress-matrix:
|
||||
needs: changes
|
||||
if: (needs.changes.outputs.python == 'true' || needs.changes.outputs.frontend == 'true') && github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-26.04
|
||||
# Somehow one test flakes on 24.04 for unknown reasons, this is the only GHA left on 22.04
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -96,21 +97,21 @@ jobs:
|
||||
# Conditional checkout based on context
|
||||
- name: Checkout for push or pull_request event
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout using ref (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
submodules: recursive
|
||||
- name: Checkout using PR ID (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
@@ -127,7 +128,7 @@ jobs:
|
||||
with:
|
||||
run: testdata
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
@@ -172,7 +173,7 @@ jobs:
|
||||
playwright-tests:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.frontend == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -206,21 +207,21 @@ jobs:
|
||||
# Conditional checkout based on context (same as Cypress workflow)
|
||||
- name: Checkout for push or pull_request event
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout using ref (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
submodules: recursive
|
||||
- name: Checkout using PR ID (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
@@ -237,7 +238,7 @@ jobs:
|
||||
with:
|
||||
run: playwright_testdata
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
@@ -300,7 +301,7 @@ jobs:
|
||||
cypress-matrix-required:
|
||||
needs: [changes, cypress-matrix]
|
||||
if: always()
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
permissions: {}
|
||||
steps:
|
||||
@@ -322,7 +323,7 @@ jobs:
|
||||
playwright-tests-required:
|
||||
needs: [changes, playwright-tests]
|
||||
if: always()
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
permissions: {}
|
||||
steps:
|
||||
|
||||
@@ -19,19 +19,19 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
test-superset-extensions-cli-package:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
# Full version spread on push (master/release) + nightly; current only
|
||||
# on PRs to cut runner cost (cross-version breaks are caught at merge).
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["previous", "current", "next"]') }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: superset-extensions-cli
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
32
.github/workflows/superset-frontend.yml
vendored
32
.github/workflows/superset-frontend.yml
vendored
@@ -21,13 +21,13 @@ permissions:
|
||||
|
||||
jobs:
|
||||
frontend-build:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
should-run: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
git show -s --format=raw HEAD
|
||||
docker buildx build \
|
||||
-t $TAG \
|
||||
--cache-from=type=registry,ref=apache/superset-cache:3.11-slim-trixie \
|
||||
--cache-from=type=registry,ref=apache/superset-cache:3.10-slim-trixie \
|
||||
--target superset-node-ci \
|
||||
.
|
||||
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
@@ -104,13 +104,13 @@ jobs:
|
||||
report-coverage:
|
||||
needs: [sharded-jest-tests]
|
||||
if: needs.frontend-build.outputs.should-run == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -122,19 +122,31 @@ jobs:
|
||||
pattern: coverage-artifacts-*
|
||||
path: coverage/
|
||||
|
||||
- name: Reorganize test result reports
|
||||
run: |
|
||||
find coverage/
|
||||
for i in {1..8}; do
|
||||
mv coverage/coverage-artifacts-${i}/coverage-final.json coverage/coverage-shard-${i}.json
|
||||
done
|
||||
shell: bash
|
||||
|
||||
- name: Merge Code Coverage
|
||||
run: npx nyc merge coverage/ merged-output/coverage-summary.json
|
||||
|
||||
- name: Upload Code Coverage
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
flags: javascript
|
||||
use_oidc: true
|
||||
verbose: true
|
||||
directory: coverage
|
||||
disable_search: true
|
||||
files: merged-output/coverage-summary.json
|
||||
slug: apache/superset
|
||||
|
||||
lint-frontend:
|
||||
needs: frontend-build
|
||||
if: needs.frontend-build.outputs.should-run == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
@@ -159,7 +171,7 @@ jobs:
|
||||
validate-frontend:
|
||||
needs: frontend-build
|
||||
if: needs.frontend-build.outputs.should-run == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
@@ -179,7 +191,7 @@ jobs:
|
||||
test-storybook:
|
||||
needs: frontend-build
|
||||
if: needs.frontend-build.outputs.should-run == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
|
||||
@@ -2,27 +2,24 @@ name: "Helm: lint and test charts"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
paths:
|
||||
- "helm/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Serialize runs per PR without cancelling: when a first-time contributor's
|
||||
# queued runs are approved together, cancel-in-progress lets an older run
|
||||
# cancel a newer one, leaving a permanently-cancelled required check on the
|
||||
# head SHA. Queued runs are cheap here, so let them all complete.
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: false
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint-test:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -31,7 +28,7 @@ jobs:
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
|
||||
with:
|
||||
version: v3.21.3
|
||||
version: v3.16.4
|
||||
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
@@ -59,9 +56,3 @@ jobs:
|
||||
CT_LINT_CONF: lintconf.yaml
|
||||
CT_SINCE: HEAD
|
||||
CT_CHART_REPOS: bitnami=https://charts.bitnami.com/bitnami
|
||||
|
||||
- name: Set up helm-unittest
|
||||
run: helm plugin install https://github.com/helm-unittest/helm-unittest --version v1.1.1
|
||||
|
||||
- name: Run helm-unittest
|
||||
run: helm unittest helm/superset
|
||||
4
.github/workflows/superset-helm-release.yml
vendored
4
.github/workflows/superset-helm-release.yml
vendored
@@ -20,7 +20,7 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref_name }}
|
||||
persist-credentials: true
|
||||
|
||||
14
.github/workflows/superset-playwright.yml
vendored
14
.github/workflows/superset-playwright.yml
vendored
@@ -24,7 +24,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
frontend: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
playwright-tests-experimental:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.frontend == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
continue-on-error: true
|
||||
permissions:
|
||||
@@ -83,21 +83,21 @@ jobs:
|
||||
# Conditional checkout based on context (same as Cypress workflow)
|
||||
- name: Checkout for push or pull_request event
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout using ref (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
submodules: recursive
|
||||
- name: Checkout using PR ID (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
with:
|
||||
run: playwright_testdata
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
|
||||
@@ -20,7 +20,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
python: ${{ steps.check.outputs.python }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
test-mysql:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
test-postgres:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
matrix:
|
||||
# Full version spread on push (master/release) + nightly; current only
|
||||
# on PRs to cut runner cost (cross-version breaks are caught at merge).
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "previous", "next"]') }}
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -188,7 +188,7 @@ jobs:
|
||||
test-sqlite:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -207,7 +207,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -243,7 +243,7 @@ jobs:
|
||||
test-postgres-required:
|
||||
needs: [changes, test-postgres]
|
||||
if: always()
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check test-postgres result
|
||||
|
||||
@@ -16,7 +16,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
python: ${{ steps.check.outputs.python }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
test-postgres-presto:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
test-postgres-hive:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
18
.github/workflows/superset-python-unittest.yml
vendored
18
.github/workflows/superset-python-unittest.yml
vendored
@@ -21,7 +21,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
python: ${{ steps.check.outputs.python }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
unit-tests:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -50,18 +50,12 @@ jobs:
|
||||
matrix:
|
||||
# Full version spread on push (master/release) + nightly; current only
|
||||
# on PRs to cut runner cost (cross-version breaks are caught at merge).
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["previous", "current", "next"]') }}
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
# Promotes the SQLAlchemy 2.0 deprecation warnings already locked in as
|
||||
# errors via pytest.ini's `filterwarnings` to actually run in CI, so a
|
||||
# regression on those fails the build instead of relying on a
|
||||
# contributor remembering to set this locally. See the migration
|
||||
# battleplan: https://github.com/apache/superset/discussions/40273
|
||||
SQLALCHEMY_WARN_20: "1"
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -99,7 +93,7 @@ jobs:
|
||||
unit-tests-required:
|
||||
needs: [changes, unit-tests]
|
||||
if: always()
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check unit-tests result
|
||||
|
||||
@@ -20,7 +20,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
post-comment:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
# Only act when the Translations workflow failed (which means a regression
|
||||
# was detected — the workflow exits 1 on regression).
|
||||
if: github.event.workflow_run.conclusion == 'failure'
|
||||
|
||||
10
.github/workflows/superset-translations.yml
vendored
10
.github/workflows/superset-translations.yml
vendored
@@ -19,13 +19,13 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
frontend-check-translations:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.check.outputs.frontend
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
@@ -55,13 +55,13 @@ jobs:
|
||||
npm run build-translation
|
||||
|
||||
babel-extract:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
6
.github/workflows/superset-websocket.yml
vendored
6
.github/workflows/superset-websocket.yml
vendored
@@ -21,15 +21,15 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
app-checks:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: './superset-websocket/.nvmrc'
|
||||
- name: Install dependencies
|
||||
|
||||
4
.github/workflows/supersetbot.yml
vendored
4
.github/workflows/supersetbot.yml
vendored
@@ -15,7 +15,7 @@ on:
|
||||
|
||||
jobs:
|
||||
supersetbot:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
if: >
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@supersetbot'))
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
});
|
||||
|
||||
- name: "Checkout ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
# zizmor: ignore[artipacked] - required persisted credentials to push synced requirement changes back to remote
|
||||
- name: Checkout source code
|
||||
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
persist-credentials: true
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
|
||||
continue-on-error: true
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
16
.github/workflows/tag-release.yml
vendored
16
.github/workflows/tag-release.yml
vendored
@@ -32,7 +32,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
config:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
has-secrets: ${{ steps.check.outputs.has-secrets }}
|
||||
steps:
|
||||
@@ -50,17 +50,17 @@ jobs:
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets
|
||||
name: docker-release
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
build_preset:
|
||||
["dev", "lean", "websocket", "dockerize", "py311", "py312"]
|
||||
["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
|
||||
- name: Use Node.js 20
|
||||
# zizmor: ignore[cache-poisoning] - node only runs the supersetbot CLI; no dependency cache is enabled
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version: 20
|
||||
package-manager-cache: false
|
||||
@@ -120,20 +120,20 @@ jobs:
|
||||
update-prs-with-release-info:
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Use Node.js 20
|
||||
# zizmor: ignore[cache-poisoning] - node only runs the supersetbot CLI; no dependency cache is enabled
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version: 20
|
||||
package-manager-cache: false
|
||||
|
||||
8
.github/workflows/tech-debt.yml
vendored
8
.github/workflows/tech-debt.yml
vendored
@@ -11,7 +11,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
config:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
has-secrets: ${{ steps.check.outputs.has-secrets }}
|
||||
steps:
|
||||
@@ -28,16 +28,16 @@ jobs:
|
||||
process-and-upload:
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
name: Generate Reports
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
|
||||
|
||||
2
.github/workflows/welcome-new-users.yml
vendored
2
.github/workflows/welcome-new-users.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
welcome:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-24.04
|
||||
if: github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -57,7 +57,6 @@ local_config.py
|
||||
/superset_text.yml
|
||||
superset.egg-info/
|
||||
superset/bin/supersetc
|
||||
superset/extensions/.reload_trigger
|
||||
tmp
|
||||
rat-results.txt
|
||||
superset/app/
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -21,6 +21,9 @@
|
||||
[submodule ".github/actions/pr-lint-action"]
|
||||
path = .github/actions/pr-lint-action
|
||||
url = https://github.com/morrisoncole/pr-lint-action
|
||||
[submodule ".github/actions/file-changes-action"]
|
||||
path = .github/actions/file-changes-action
|
||||
url = https://github.com/trilom/file-changes-action
|
||||
[submodule ".github/actions/cached-dependencies"]
|
||||
path = .github/actions/cached-dependencies
|
||||
url = https://github.com/apache-superset/cached-dependencies
|
||||
|
||||
@@ -31,10 +31,9 @@ repos:
|
||||
types-simplejson,
|
||||
types-python-dateutil,
|
||||
types-requests,
|
||||
# types-redis is intentionally absent: redis-py ships its own
|
||||
# bundled types (py.typed, since 4.6+/5.x), while the stub
|
||||
# package is unmaintained (frozen at the 4.6.0.x API surface).
|
||||
# Installing the stub would shadow the accurate inline types.
|
||||
# types-redis 4.6.0.5 is failing mypy
|
||||
# because of https://github.com/python/typeshed/pull/10531
|
||||
types-redis==4.6.0.4,
|
||||
types-pytz,
|
||||
types-croniter,
|
||||
types-PyYAML,
|
||||
@@ -64,7 +63,7 @@ repos:
|
||||
hooks:
|
||||
- id: prettier-frontend
|
||||
name: prettier (frontend)
|
||||
entry: bash -c 'cd superset-frontend && files=(); for f in "$@"; do files+=("${f#superset-frontend/}"); done; npx prettier --write -- "${files[@]}"' --
|
||||
entry: bash -c 'cd superset-frontend && for file in "$@"; do npx prettier --write "${file#superset-frontend/}"; done'
|
||||
language: system
|
||||
pass_filenames: true
|
||||
files: ^superset-frontend/.*\.(js|jsx|ts|tsx|css|scss|sass|json)$
|
||||
@@ -82,12 +81,6 @@ repos:
|
||||
language: system
|
||||
pass_filenames: true
|
||||
files: ^superset-frontend/.*\.(js|jsx|ts|tsx)$
|
||||
- id: stylelint-frontend
|
||||
name: stylelint (frontend css-in-js)
|
||||
entry: ./scripts/stylelint.sh
|
||||
language: system
|
||||
pass_filenames: true
|
||||
files: ^superset-frontend/.*\.(js|jsx|ts|tsx)$
|
||||
- id: eslint-docs
|
||||
name: eslint (docs)
|
||||
entry: bash -c 'cd docs && FILES=$(printf "%s\n" "$@" | sed "s|^docs/||" | tr "\n" " ") && yarn eslint --fix --quiet $FILES'
|
||||
@@ -144,7 +137,7 @@ repos:
|
||||
git fetch --no-recurse-submodules origin "$TARGET_BRANCH" 2>/dev/null || true
|
||||
fi
|
||||
BASE=$(git merge-base origin/"$TARGET_BRANCH" HEAD 2>/dev/null) || BASE="HEAD"
|
||||
files=$(git diff --name-only --diff-filter=ACM "$BASE"..HEAD 2>/dev/null | grep '^superset/.*\.py$' | grep -v '^superset/migrations/' || true)
|
||||
files=$(git diff --name-only --diff-filter=ACM "$BASE"..HEAD 2>/dev/null | grep '^superset/.*\.py$' || true)
|
||||
if [ -n "$files" ]; then
|
||||
pylint --rcfile=.pylintrc --load-plugins=superset.extensions.pylint --reports=no $files
|
||||
else
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
.codecov.yml
|
||||
.eslintrc
|
||||
.eslintignore
|
||||
.stylelintignore
|
||||
.flake8
|
||||
.nvmrc
|
||||
.prettierrc
|
||||
|
||||
6
Makefile
6
Makefile
@@ -15,8 +15,8 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
# Python version installed; we need 3.11-3.12
|
||||
PYTHON=`command -v python3.11 || command -v python3.12`
|
||||
# Python version installed; we need 3.10-3.11
|
||||
PYTHON=`command -v python3.11 || command -v python3.10`
|
||||
|
||||
.PHONY: install superset venv pre-commit up down logs ps nuke ports open
|
||||
|
||||
@@ -76,7 +76,7 @@ update-js:
|
||||
|
||||
venv:
|
||||
# Create a virtual environment and activate it (recommended)
|
||||
if ! [ -x "${PYTHON}" ]; then echo "You need Python 3.11 or 3.12 installed"; exit 1; fi
|
||||
if ! [ -x "${PYTHON}" ]; then echo "You need Python 3.10 or 3.11 installed"; exit 1; fi
|
||||
test -d venv || ${PYTHON} -m venv venv # setup a python3 virtualenv
|
||||
. venv/bin/activate
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
FROM python:3.11-slim-trixie
|
||||
FROM python:3.10-slim-trixie
|
||||
|
||||
RUN useradd --user-group --create-home --no-log-init --shell /bin/bash superset
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
FROM python:3.11-slim-trixie
|
||||
FROM python:3.10-slim-trixie
|
||||
|
||||
RUN useradd --user-group --create-home --no-log-init --shell /bin/bash superset
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
FROM python:3.11-slim-trixie
|
||||
FROM python:3.10-slim-trixie
|
||||
ARG VERSION
|
||||
|
||||
RUN git clone --depth 1 --branch ${VERSION} https://github.com/apache/superset.git /superset
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
FROM python:3.11-slim-trixie
|
||||
FROM python:3.10-slim-trixie
|
||||
|
||||
RUN apt-get update -y
|
||||
RUN apt-get install -y \
|
||||
|
||||
@@ -168,11 +168,6 @@ categories:
|
||||
url: https://www.now.vn/
|
||||
contributors: ["@davidkohcw"]
|
||||
|
||||
- name: Pattern
|
||||
url: https://www.pattern.com
|
||||
contributors: ["@alephys26", "@hladush", "@sanketjadhavSF"]
|
||||
logo: pattern_logo.png
|
||||
|
||||
- name: Qunar
|
||||
url: https://www.qunar.com/
|
||||
contributors: ["@flametest"]
|
||||
@@ -624,34 +619,14 @@ categories:
|
||||
- name: Symmetrics
|
||||
url: https://www.symmetrics.fyi
|
||||
|
||||
Government / Public authority:
|
||||
- name: Centre Régional Auvergnat de l'Information Géographique (France)
|
||||
url: https://www.craig.fr/
|
||||
logo: craig.png
|
||||
|
||||
Government:
|
||||
- name: City of Ann Arbor, MI
|
||||
url: https://www.a2gov.org/
|
||||
contributors: ["@sfirke"]
|
||||
|
||||
- name: Métropole Européenne de Lille (France)
|
||||
url: https://data.lillemetropole.fr/accueil/
|
||||
logo: mel.jpg
|
||||
|
||||
- name: NRLM - Sarathi, India
|
||||
url: https://pib.gov.in/PressReleasePage.aspx?PRID=1999586
|
||||
|
||||
- name: Région Bretagne (France)
|
||||
url: https://geobretagne.fr/
|
||||
logo: geobretagne.svg
|
||||
|
||||
- name: Région Hauts-de-France (France)
|
||||
url: https://www.geo2france.fr/
|
||||
logo: geo2france.png
|
||||
|
||||
- name: Rennes Métropole (France)
|
||||
url: https://metropole.rennes.fr/
|
||||
logo: rennes-ville-metropole_noir.svg
|
||||
|
||||
- name: RIS3 Strategy of CZ, MIT CR
|
||||
url: https://www.ris3.cz/
|
||||
contributors: ["@RIS3CZ"]
|
||||
@@ -707,10 +682,6 @@ categories:
|
||||
url: https://automattic.com/
|
||||
contributors: ["@Khrol", "@Usiel"]
|
||||
|
||||
- name: CamptoCamp
|
||||
url: https://camptocamp.com/
|
||||
logo: camptocamp.svg
|
||||
|
||||
- name: Dropbox
|
||||
url: https://www.dropbox.com/
|
||||
contributors: ["@bkyryliuk"]
|
||||
@@ -719,10 +690,6 @@ categories:
|
||||
url: https://flowbird.com
|
||||
contributors: ["@EmmanuelCbd"]
|
||||
|
||||
- name: geOrchestra
|
||||
url: https://www.georchestra.org
|
||||
logo: georchestra.svg
|
||||
|
||||
- name: GEOTAB
|
||||
url: https://www.geotab.com
|
||||
contributors: ["@JZ6"]
|
||||
@@ -739,10 +706,6 @@ categories:
|
||||
url: https://www.increff.com/
|
||||
contributors: ["@ishansinghania"]
|
||||
|
||||
- name: jDev
|
||||
url: https://jdev.fr/
|
||||
logo: jDev.svg
|
||||
|
||||
- name: Let's Roam
|
||||
url: https://www.letsroam.com/
|
||||
|
||||
@@ -753,11 +716,6 @@ categories:
|
||||
url: https://1beat.com/
|
||||
contributors: ["@GuyAttia"]
|
||||
|
||||
- name: pi-Geosolutions
|
||||
url: https://www.pi-geosolutions.fr/
|
||||
logo: pi-Geosolutions.png
|
||||
contributors: ["@jeanpommier"]
|
||||
|
||||
- name: X
|
||||
url: https://x.com/
|
||||
|
||||
|
||||
262
SIP-DASHBOARD-COMPONENT-CONTRIBUTION-POINT.md
Normal file
262
SIP-DASHBOARD-COMPONENT-CONTRIBUTION-POINT.md
Normal file
@@ -0,0 +1,262 @@
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
# [SIP] Proposal for a dashboard component Extensions contribution point
|
||||
|
||||
> **Companion SIP:** Pairs with [`SIP.md`](SIP.md) (first-class iframe component +
|
||||
> runtime CSP allowlist). That SIP is the **reference implementation** that proves
|
||||
> this contribution point: the iframe's UI becomes an extension-contributed
|
||||
> dashboard component, while its security-sensitive CSP backend stays in core.
|
||||
>
|
||||
> **Status:** Draft — POC tracked in `feat/csp-runtime-allowlist-iframe`.
|
||||
|
||||
## Motivation
|
||||
|
||||
Adding a new dashboard layout component to Superset today is a **core-only,
|
||||
high-friction** operation. The iframe component in the companion SIP had to touch
|
||||
~12 files: a type constant, the `componentLookup` map, the builder palette, and
|
||||
**seven hardcoded behavior maps** keyed by component-type string
|
||||
(`isValidChild`, `componentIsResizable`, `newComponentFactory`,
|
||||
`shouldWrapChildInRow`, `getDetailedComponentWidth`, `isDashboardEmpty`, plus the
|
||||
prop bundle injected by `DashboardComponent.tsx`). Component types are a **closed
|
||||
enum** baked into core.
|
||||
|
||||
There is a legacy escape hatch — the `DashboardComponentsRegistry` /
|
||||
`DYNAMIC_TYPE` path (`src/visualizations/dashboardComponents/`) — but it is an
|
||||
**antique that should be deprecated**:
|
||||
|
||||
- It is disconnected from the modern VS Code-style Extensions framework
|
||||
(`@apache-superset/core`, `ENABLE_EXTENSIONS`), which already has contribution
|
||||
points for `commands`, `menus`, `views`, `editors`, and `chat`.
|
||||
- Components registered through it are **second-class**: `DynamicComponent`
|
||||
renders them in a generic wrapper that only passes `dashboardData`. They do not
|
||||
receive the first-class layout lifecycle (edit mode, meta editing, resize, DnD)
|
||||
and cannot declare their own layout behavior.
|
||||
|
||||
We want a **single, modern way** to contribute a first-class dashboard layout
|
||||
component — via the Extensions framework — and to deprecate the legacy registry.
|
||||
The iframe component is the ideal pilot because it is self-contained.
|
||||
|
||||
## Proposed Change
|
||||
|
||||
### 1. A `dashboardComponents` contribution point
|
||||
|
||||
Add `dashboardComponents` to the Extensions `Contributions` interface
|
||||
(`packages/superset-core/src/contributions/index.ts`), alongside `views`,
|
||||
`commands`, etc., with a public registration API mirroring the existing ones
|
||||
(`registerDashboardComponent` returning a `Disposable`), exposed on
|
||||
`window.superset.dashboardComponents` and wired into `ExtensionsLoader`.
|
||||
|
||||
### 2. The Dashboard Component Contract (the heart of this SIP)
|
||||
|
||||
The contract has two halves. Getting this right is the real work — it becomes a
|
||||
**public API Superset must support indefinitely**.
|
||||
|
||||
**(a) Declarative behavior metadata** — replaces the seven hardcoded util maps:
|
||||
|
||||
```ts
|
||||
interface DashboardComponentContribution {
|
||||
id: string; // unique type key, namespaced, e.g. "my-org.iframe"
|
||||
name: string; // palette label
|
||||
description?: string;
|
||||
icon: string; // contributed icon id or known icon name
|
||||
resizable?: boolean; // -> componentIsResizable
|
||||
defaultMeta?: { // -> newComponentFactory
|
||||
width?: number;
|
||||
height?: number;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
nesting?: { // -> isValidChild / shouldWrapChildInRow
|
||||
validParents?: string[]; // e.g. [GRID, ROW, COLUMN, TAB]
|
||||
wrapInRow?: boolean;
|
||||
minWidth?: number; // -> getDetailedComponentWidth
|
||||
};
|
||||
isUserContent?: boolean; // -> isDashboardEmpty
|
||||
loadComponent: () => Promise<{ default: ComponentType<DashboardComponentProps> }>;
|
||||
}
|
||||
```
|
||||
|
||||
**(b) Runtime props contract** — a small, stable surface. Crucially, **the host
|
||||
owns the chrome** (the `Draggable` + `ResizableContainer` + `HoverMenu`/delete
|
||||
wrapper that every current `componentLookup` component re-implements today). The
|
||||
extension component renders only its *content* and, optionally, an *editor*:
|
||||
|
||||
```ts
|
||||
interface DashboardComponentProps {
|
||||
id: string;
|
||||
meta: Record<string, unknown>;
|
||||
editMode: boolean;
|
||||
updateMeta: (patch: Record<string, unknown>) => void; // wraps updateComponents
|
||||
// resize/drag/delete handled by the host wrapper, NOT the component
|
||||
}
|
||||
```
|
||||
|
||||
This is a strict improvement over the status quo: the iframe component in the
|
||||
companion PR hand-rolls the Draggable/Resizable/HoverMenu wrapper; under this
|
||||
contract that boilerplate moves into the host once, and contributed components
|
||||
shrink to "render content + edit meta."
|
||||
|
||||
### 3. Registry-driven core
|
||||
|
||||
Refactor `componentLookup` and the seven behavior maps to consult a registry,
|
||||
with the **built-in leaf components seeded into it** at startup. Structural
|
||||
container components (Chart, Tabs, Row, Column, Header) *are* the layout engine
|
||||
and stay bespoke; the contribution point targets **leaf/content components**
|
||||
(today: Markdown, Divider, Iframe; tomorrow: anything). `DashboardComponent.tsx`
|
||||
resolves contributed types through the registry and renders them inside the
|
||||
shared host chrome.
|
||||
|
||||
### 4. Deprecate `DashboardComponentsRegistry` / `DYNAMIC_TYPE`
|
||||
|
||||
Mark the legacy registry and `DYNAMIC_TYPE` deprecated. Provide a shim so existing
|
||||
dynamic components keep working, with a migration note pointing at the new
|
||||
contribution point. Removal happens in a later major per Superset's deprecation
|
||||
policy.
|
||||
|
||||
### 5. Graceful fallback for unknown types
|
||||
|
||||
A saved dashboard layout stores component **type strings** in its position JSON.
|
||||
If a dashboard references a type whose extension is disabled/uninstalled, the host
|
||||
must render a non-destructive placeholder ("This component requires the *X*
|
||||
extension") and **preserve the meta on save** so re-enabling the extension
|
||||
restores it. The layout engine already tolerates unknown types defensively
|
||||
(`componentLookup[type]` → null; `isValidChild` → false); this SIP makes that an
|
||||
intentional, user-visible contract rather than silent breakage.
|
||||
|
||||
### 6. Backend: APIs yes, security policy no
|
||||
|
||||
The Extensions framework **already** lets a component contribute a backend REST
|
||||
API: the `@api` decorator (`superset-core/.../rest_api/decorators.py`) detects
|
||||
extension context and registers the route via `appbuilder.add_api()` at entrypoint
|
||||
import, serving it under `/extensions/{publisher}/{name}/...` and auto-creating
|
||||
the endpoint's FAB permission. **No new work is required for an extension to ship
|
||||
an API.**
|
||||
|
||||
What an extension **cannot** do today, and what this SIP explicitly leaves to
|
||||
core:
|
||||
|
||||
- **Role policy for a permission.** Endpoint permissions are auto-created, but
|
||||
whether a permission is *Admin-only* (e.g. via
|
||||
`SupersetSecurityManager.ADMIN_ONLY_VIEW_MENUS`) is decided in core at
|
||||
`sync_role_definitions` time. The manifest's `permissions: list[str]` field is
|
||||
currently **dormant** (never read), and the `ContributionProcessorRegistry` that
|
||||
would process it is scaffolding that is not wired into the load pipeline.
|
||||
- **Security-sensitive request hooks** (e.g. rewriting CSP/Talisman headers).
|
||||
|
||||
This is exactly why the companion CSP feature keeps its backend in core: the
|
||||
component *UI* is extension-shaped, but punching holes in the CSP and gating it
|
||||
admin-only are core security responsibilities.
|
||||
|
||||
A **future, optional** extension of this SIP could finish wiring
|
||||
`ContributionProcessorRegistry` + a manifest permission-policy schema so
|
||||
extensions can declare role policy — but that is itself a security-review-worthy
|
||||
change and is out of scope here.
|
||||
|
||||
## New or Changed Public Interfaces
|
||||
|
||||
- **New contribution point** `dashboardComponents` on the `Contributions`
|
||||
interface; new `registerDashboardComponent(...) -> Disposable` API; new
|
||||
`window.superset.dashboardComponents` namespace.
|
||||
- **New public types** `DashboardComponentContribution` and
|
||||
`DashboardComponentProps` (the contract) — these become long-term public API.
|
||||
- **Changed (internal → registry-driven)** `componentLookup` and the seven
|
||||
behavior util maps; `DashboardComponent.tsx` resolution path; the host gains a
|
||||
shared component-chrome wrapper.
|
||||
- **Deprecated** `DashboardComponentsRegistry`, `DYNAMIC_TYPE`,
|
||||
`NewDynamicComponent`, `setupDashboardComponents`.
|
||||
|
||||
## New dependencies
|
||||
|
||||
None. Reuses the existing Extensions framework (module federation, manifest
|
||||
schema, `@api` decorator) and the existing functional-registry utilities.
|
||||
|
||||
## Migration Plan and Compatibility
|
||||
|
||||
- **No DB migration.** This is a frontend/framework change plus the (already
|
||||
supported) extension API path.
|
||||
- **Layout JSON is unchanged** — component types remain type strings. The new
|
||||
fallback behavior makes *unknown* types degrade gracefully instead of rendering
|
||||
nothing.
|
||||
- **Backwards compatible:** built-in components are seeded into the registry, so
|
||||
existing dashboards render identically. Legacy `DYNAMIC_TYPE` components keep
|
||||
working via a deprecation shim.
|
||||
- **Rollout:** the contribution point is only active under `ENABLE_EXTENSIONS`;
|
||||
with it off, behavior is identical to today.
|
||||
|
||||
## Rejected Alternatives
|
||||
|
||||
- **Keep / extend `DashboardComponentsRegistry`.** It is disconnected from the
|
||||
modern Extensions framework and produces second-class components. Deprecating it
|
||||
in favor of one contribution model is the goal, not a side effect.
|
||||
- **Require all built-in components to become extensions.** Chart/Tabs/Row/Column
|
||||
are the layout engine; extracting them is high-risk and low-value. The
|
||||
contribution point *adds* leaf components; it does not mandate extraction.
|
||||
- **Let the extension component own its own DnD/resize chrome** (as
|
||||
`componentLookup` components do today). Rejected: it bloats the contract,
|
||||
duplicates host logic, and makes the public API fragile. The host owns chrome.
|
||||
- **One combined SIP with the CSP feature.** Rejected: the framework change and
|
||||
the security-sensitive feature are distinct discussions with different
|
||||
reviewers and risk profiles, even though they share a POC branch.
|
||||
- **Move the CSP permission/role policy into the extension.** Not supported today
|
||||
(dormant manifest `permissions`, unwired contribution processor) and
|
||||
undesirable: admin-only gating and CSP-header rewriting are core security
|
||||
responsibilities.
|
||||
|
||||
## Implementation Status (POC)
|
||||
|
||||
Implemented on the POC branch (`@apache-superset/core` mirrors the `chat`
|
||||
contribution-point pattern from #41000/#41205):
|
||||
|
||||
- [x] `DashboardComponentDefinition` + `DashboardComponentProps` contract types
|
||||
(`packages/superset-core/src/dashboardComponents`), added to the
|
||||
`Contributions` interface and the package's subpath exports
|
||||
- [x] `dashboardComponents` contribution point: host `DashboardComponentsProvider`
|
||||
registry + public `registerDashboardComponent`/`getDashboardComponents` API
|
||||
(`src/core/dashboardComponents`), exposed on `window.superset` via
|
||||
`ExtensionsStartup` + `Namespaces`
|
||||
- [x] Shared host component-chrome wrapper `DashboardExtensionComponent`
|
||||
(owns Draggable/Resizable/HoverMenu/Delete; reads `resizable` from the
|
||||
definition) behind the new `EXTENSION_TYPE`
|
||||
- [x] `componentLookup` + builder palette resolve the registry; the seven
|
||||
behavior maps carry `EXTENSION_TYPE` leaf behavior
|
||||
- [x] Unknown-type graceful fallback (placeholder + meta preserved on save)
|
||||
- [x] Deprecation notices on `DashboardComponentsRegistry` / `DYNAMIC_TYPE`
|
||||
(legacy path still functions)
|
||||
- [x] Reference component: the built-in iframe is now delivered **through** the
|
||||
contribution point (`src/dashboard/extensions/iframe`), registered at
|
||||
startup exactly as a third-party extension would; its CSP backend remains
|
||||
in core per the companion SIP
|
||||
- [x] Tests: registry lifecycle (register/get/replace/dispose), host-wrapper
|
||||
resolution + fallback + `updateMeta`, iframe content + CSP UX
|
||||
|
||||
- [x] Per-component behavior policy honored by the layout engine: `resizable`,
|
||||
`minWidth`, `isUserContent`, `validParents`, and `wrapInRow` are seeded onto
|
||||
instance `meta` at creation and read by `componentIsResizable`,
|
||||
`getDetailedComponentWidth`, `isDashboardEmpty`, `isValidChild`, and
|
||||
`shouldWrapChildInRow` (the pure layout utils stay registry-free; behavior
|
||||
round-trips in the saved layout)
|
||||
- [x] Developer docs: `extension-points/dashboard-components.md` + a
|
||||
`contribution-types.md` section + sidebar entry, with an example extension
|
||||
|
||||
Remaining (follow-up, not POC-blocking):
|
||||
|
||||
- [ ] Manifest `contributions.dashboardComponents` declarative validation in the
|
||||
Python/TS manifest schema (runtime side-effect registration works today,
|
||||
matching how `chat` does it)
|
||||
- [ ] Remove the legacy `DashboardComponentsRegistry`/`DYNAMIC_TYPE` (major)
|
||||
232
SIP.md
Normal file
232
SIP.md
Normal file
@@ -0,0 +1,232 @@
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
# [SIP] Proposal for a first-class iframe dashboard component with a runtime CSP allowlist
|
||||
|
||||
> **Companion SIP:** This proposal pairs with
|
||||
> [`SIP-DASHBOARD-COMPONENT-CONTRIBUTION-POINT.md`](SIP-DASHBOARD-COMPONENT-CONTRIBUTION-POINT.md),
|
||||
> which proposes the Extensions contribution point that would let this iframe
|
||||
> component (and others) be shipped as an extension. The two are deliberately
|
||||
> separate discussions: **this** SIP covers the security-sensitive feature
|
||||
> (runtime CSP override + permissions); the companion covers the framework
|
||||
> change. They share one POC branch so the end-to-end story is demonstrable.
|
||||
|
||||
> **Status:** Draft — tracking the implementation in `feat/csp-runtime-allowlist-iframe`.
|
||||
> This document follows the SIP issue template and is kept in sync with the branch
|
||||
> as the implementation evolves. See SIP-0
|
||||
> (<https://github.com/apache/superset/issues/5602>) for the SIP process.
|
||||
|
||||
## Motivation
|
||||
|
||||
Superset ships a Talisman/Content-Security-Policy (CSP) configuration that, by
|
||||
design, prevents users from embedding arbitrary external content in a dashboard.
|
||||
The default policy declares `default-src 'self'` and **no** `frame-src`
|
||||
directive, so an `<iframe>` pointing at any third-party origin is blocked by the
|
||||
browser.
|
||||
|
||||
This is correct and secure default behavior, but it creates real friction:
|
||||
|
||||
- There is **no first-class "iframe" dashboard component**. Users historically
|
||||
smuggled iframes through Markdown, which is both a footgun and blocked by CSP.
|
||||
- When an embed *is* legitimately needed (an internal tool, a status page, a
|
||||
partner widget), the only way to allow it is to **edit `TALISMAN_CONFIG` and
|
||||
restart every Superset process**. That is a deploy-time, ops-team operation —
|
||||
far too heavyweight for "let me embed this one dashboard from our other
|
||||
internal app."
|
||||
- There is no in-product signal telling a user *why* their embed is blank, and
|
||||
no path to fix it.
|
||||
|
||||
We want to (a) make embedding a real, supported component, and (b) give trusted
|
||||
Admins a controlled, audited way to widen the CSP at runtime — without
|
||||
abandoning the secure-by-default posture that operators rely on.
|
||||
|
||||
## Proposed Change
|
||||
|
||||
The change has five parts.
|
||||
|
||||
### 1. A first-class `IFRAME` dashboard layout component
|
||||
|
||||
A new grid component (`IFRAME_TYPE`) modeled on the existing Markdown/Divider
|
||||
components. In edit mode the user pastes a URL; in view mode the component
|
||||
renders a sandboxed `<iframe>`. The component is registered through the same
|
||||
surface as every other layout element (type constant, `componentLookup`, drag
|
||||
palette, nesting/resize/width/wrap util maps).
|
||||
|
||||
The iframe is rendered with a restrictive `sandbox` attribute
|
||||
(`allow-scripts allow-same-origin allow-popups allow-forms`).
|
||||
|
||||
### 2. Domain flagging
|
||||
|
||||
When the runtime-allowlist feature is enabled, the component compares the
|
||||
embedded URL's **origin** against the current allowlist (fetched from the new
|
||||
API). If the origin is not yet allowed, it shows an inline warning explaining
|
||||
that the domain is blocked by the CSP.
|
||||
|
||||
### 3. "Enable domain in CSP" button
|
||||
|
||||
If the current user holds the new permission (Admins by default), the warning
|
||||
includes an **Enable domain in CSP** button. Clicking it `POST`s the origin to
|
||||
the allowlist API and re-checks. Users without the permission instead see "ask
|
||||
an administrator."
|
||||
|
||||
### 4. Permission gating
|
||||
|
||||
Mutating the allowlist requires `can write on CSPAllowlist`. The `CSPAllowlist`
|
||||
view-menu is registered in `SupersetSecurityManager.ADMIN_ONLY_VIEW_MENUS`, so
|
||||
the capability is reserved for Admins (or a custom role explicitly granted it),
|
||||
consistent with how other trusted, security-sensitive operations are scoped.
|
||||
|
||||
### 5. Runtime CSP override ("punched holes")
|
||||
|
||||
A new `csp_allowlist` metadata table stores allowlist entries. An `after_request`
|
||||
hook — registered **before** flask-talisman so that, because Flask runs
|
||||
`after_request` callbacks in reverse registration order, it runs **after**
|
||||
Talisman has set the header — merges the operator-curated entries into the
|
||||
response CSP header. Entries are cached in-process with a short TTL to avoid a DB
|
||||
hit per response; a write through the API invalidates the cache in the handling
|
||||
worker, and other workers converge when their cached copy expires.
|
||||
|
||||
The entire runtime-override path is inert unless the `CSP_RUNTIME_ALLOWLIST`
|
||||
feature flag is enabled, so the static, deploy-time policy remains the default
|
||||
and operators opt in explicitly.
|
||||
|
||||
```
|
||||
Browser ──> Flask request
|
||||
│
|
||||
Talisman after_request (sets "Content-Security-Policy: default-src 'self'; …")
|
||||
│
|
||||
merge_runtime_csp_allowlist (if flag on: appends allowlist origins to frame-src, …)
|
||||
│
|
||||
Response ──> Browser ("…; frame-src 'self' https://embed.example")
|
||||
```
|
||||
|
||||
#### Design decisions (resolved)
|
||||
|
||||
- **Scope: global.** Allowlist entries apply server-wide. CSP is a single
|
||||
per-response header; a global allowlist keeps the merge context-free and
|
||||
avoids per-dashboard request plumbing. (Per-dashboard scoping is a possible
|
||||
future extension.)
|
||||
- **Operator control: feature-flagged kill-switch.** The runtime override only
|
||||
functions when `CSP_RUNTIME_ALLOWLIST` is on (default **off**). Operators who
|
||||
want a purely static policy simply leave it off and the table is never
|
||||
consulted.
|
||||
|
||||
## New or Changed Public Interfaces
|
||||
|
||||
### REST API
|
||||
|
||||
- `GET /api/v1/csp_allowlist/` — list entries
|
||||
- `GET /api/v1/csp_allowlist/<id>` — get one
|
||||
- `POST /api/v1/csp_allowlist/` — create (validates origin + directive)
|
||||
- `PUT /api/v1/csp_allowlist/<id>` — update
|
||||
- `DELETE /api/v1/csp_allowlist/<id>` — delete
|
||||
- `DELETE /api/v1/csp_allowlist/?q=!(...)` — bulk delete
|
||||
|
||||
All write methods require `can write on CSPAllowlist` (Admin-only by default).
|
||||
Origins are validated server-side: bare `scheme://host[:port]` only — no
|
||||
wildcards, paths, query strings, fragments, or credentials. Only a fixed set of
|
||||
directives may be widened (`frame-src`, `child-src`, `img-src`, `connect-src`,
|
||||
`media-src`, `font-src`); notably **not** `script-src`.
|
||||
|
||||
### Model
|
||||
|
||||
- `CSPAllowlistEntry` (`superset/models/csp.py`, table `csp_allowlist`):
|
||||
`id`, `uuid`, `domain`, `directive` (default `frame-src`), `description`,
|
||||
audit columns. Unique on `(domain, directive)`.
|
||||
|
||||
### Feature flag
|
||||
|
||||
- `CSP_RUNTIME_ALLOWLIST` (default `False`) — gates the entire runtime-override
|
||||
path, backend and frontend.
|
||||
|
||||
### Config
|
||||
|
||||
- `CSP_RUNTIME_ALLOWLIST_CACHE_TTL` (default `30` seconds) — in-process cache TTL
|
||||
for the allowlist; also settable via env var.
|
||||
|
||||
### Frontend
|
||||
|
||||
- New `IFRAME` dashboard layout component and its registration across the
|
||||
dashboard util maps.
|
||||
- New `FeatureFlag.CspRuntimeAllowlist` enum member.
|
||||
|
||||
### Security model
|
||||
|
||||
- New `CSPAllowlist` view-menu added to `ADMIN_ONLY_VIEW_MENUS`.
|
||||
|
||||
## New dependencies
|
||||
|
||||
None. The implementation uses existing libraries (flask-talisman,
|
||||
Flask-AppBuilder, marshmallow, SQLAlchemy on the backend; existing
|
||||
`@superset-ui/core` components on the frontend).
|
||||
|
||||
## Migration Plan and Compatibility
|
||||
|
||||
- One Alembic migration adds the `csp_allowlist` table
|
||||
(`4a50792bd265`, down-revision `3a8e6f2c1b95`). The table is empty on creation.
|
||||
- Fully backward compatible: with the feature flag off (the default), behavior is
|
||||
identical to today — the static CSP is authoritative and the new table is never
|
||||
read. No existing dashboards, URLs, or policies change.
|
||||
- Rollback: dropping the table and disabling the flag fully reverts the feature.
|
||||
|
||||
### Security review notes
|
||||
|
||||
This feature deliberately relocates a *capability* (widening the CSP) from a
|
||||
purely deploy-time operator control into a runtime, permission-gated, audited
|
||||
operation. The mitigations that keep it within Superset's trust model:
|
||||
|
||||
- **Off by default** behind a feature flag the operator owns.
|
||||
- **Admin-only** write permission (a fully trusted principal per `SECURITY.md`).
|
||||
- **Strict origin validation** server-side — no wildcards, no `script-src`.
|
||||
- **Audit trail** via the audit mixin (`created_by` / `changed_by`).
|
||||
- The iframe is **sandboxed** and the merge can only *widen* a directive to a
|
||||
specific origin, never relax nonce/`strict-dynamic` protections on
|
||||
`script-src`.
|
||||
|
||||
## Rejected Alternatives
|
||||
|
||||
- **Dynamically reconfiguring flask-talisman at runtime.** Talisman is configured
|
||||
once at app init. Rather than mutate its internals, we add our own
|
||||
`after_request` hook that post-processes the header it already sets. This is
|
||||
simpler, avoids depending on Talisman internals, and rides the same per-request
|
||||
header machinery Talisman already uses for its nonce.
|
||||
- **Per-dashboard allowlist scoping.** More precise, but CSP is a per-response
|
||||
header; per-dashboard scoping adds request-context complexity for marginal
|
||||
benefit in the common case. Left as a possible future extension.
|
||||
- **"Always on" runtime override (no kill-switch).** Simpler, but moves a
|
||||
security control fully into the app with no operator opt-out. Rejected in favor
|
||||
of the feature-flag kill-switch.
|
||||
- **Shared/Redis-backed allowlist cache with cross-worker invalidation.**
|
||||
Correct but heavier. A short-TTL in-process cache is good enough: writes take
|
||||
effect immediately in the handling worker and within the TTL elsewhere, with no
|
||||
new infrastructure dependency.
|
||||
|
||||
## Implementation Status
|
||||
|
||||
- [x] Feature flag `CSP_RUNTIME_ALLOWLIST` + `CSP_RUNTIME_ALLOWLIST_CACHE_TTL`
|
||||
- [x] `CSPAllowlistEntry` model + Alembic migration
|
||||
- [x] DAO, marshmallow schemas (with origin/directive validation), REST API
|
||||
- [x] Admin-only permission (`CSPAllowlist` view-menu)
|
||||
- [x] `after_request` CSP merge hook + in-process TTL cache + invalidation
|
||||
- [x] `IFRAME` dashboard component + registration across util maps
|
||||
- [x] Domain flagging + permission-gated "Enable domain in CSP" button
|
||||
- [x] Tests: backend unit (validation + merge + hook), backend integration (API),
|
||||
frontend unit (util + component)
|
||||
- [ ] Docs (`docs/`) + `UPDATING.md` entry
|
||||
- [ ] Community/security review feedback
|
||||
255
UPDATING.md
255
UPDATING.md
@@ -24,178 +24,6 @@ assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
|
||||
### Dashboard "Export Data to Excel" requires a Celery worker and S3 bucket
|
||||
|
||||
A new dashboard action exports every chart's data to a single multi-sheet
|
||||
`.xlsx` asynchronously. It is disabled by default and turns on only when
|
||||
`EXCEL_EXPORT_S3_BUCKET` is set (the endpoint returns `501` otherwise). It also
|
||||
requires a running Celery worker and a configured SMTP transport, since the task
|
||||
emails the requesting user a pre-signed download link. New config keys:
|
||||
`EXCEL_EXPORT_S3_BUCKET`, `EXCEL_EXPORT_S3_KEY_PREFIX`,
|
||||
`EXCEL_EXPORT_LINK_TTL_SECONDS`, `EXCEL_EXPORT_S3_CLIENT_KWARGS`, and
|
||||
`EXCEL_EXPORT_TABLE_VIZ_TYPES`.
|
||||
|
||||
The feature depends on `boto3`, which is **not** installed by default; install it
|
||||
with `pip install apache-superset[excel-export]`.
|
||||
|
||||
A second mode, **Export Images to Excel**, embeds non-table charts as rendered
|
||||
images (which viz types stay tabular is controlled by
|
||||
`EXCEL_EXPORT_TABLE_VIZ_TYPES`). It renders through the headless webdriver, so the
|
||||
menu option only appears when the webdriver screenshot feature flags
|
||||
(`ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS`,
|
||||
`ENABLE_DASHBOARD_DOWNLOAD_WEBDRIVER_SCREENSHOT`) are enabled.
|
||||
|
||||
Deployments that override `CELERY_CONFIG` must add
|
||||
`"superset.tasks.export_dashboard_excel"` to their `imports` tuple, or the task
|
||||
will not register and exports will silently never run.
|
||||
|
||||
### SQL_QUERY_MUTATOR now honors MUTATE_AFTER_SPLIT in SQL Lab
|
||||
|
||||
SQL Lab now applies `SQL_QUERY_MUTATOR` according to `MUTATE_AFTER_SPLIT`, matching the documented semantics and the chart/query path. This only affects deployments that define `SQL_QUERY_MUTATOR` in `superset_config.py`:
|
||||
|
||||
- With `MUTATE_AFTER_SPLIT = True`, the mutator previously never ran in SQL Lab; it now runs on each individual statement (including on engines like BigQuery and Kusto that execute multiple statements as one block, where each statement is mutated before the statements are joined).
|
||||
- With `MUTATE_AFTER_SPLIT = False` (the default), multi-statement SQL Lab queries previously applied the mutator to each statement separately; the mutator now runs once on the whole un-split query, as documented. Single-statement queries are unaffected.
|
||||
- With `MUTATE_AFTER_SPLIT = False` on engines that execute statements individually, the mutator's output is re-parsed to split it into statements. A mutator that emits SQL Superset's parser cannot parse will now fail with a clear parse error before execution, and one that strips a query down to nothing raises an invalid-SQL error instead of executing an empty query.
|
||||
|
||||
### Python 3.10 support removed
|
||||
|
||||
Python 3.10 is no longer supported. Superset now requires **Python 3.11 or higher**.
|
||||
Update your environment (virtualenv, Docker base image, CI configuration, etc.) to
|
||||
Python 3.11+ before upgrading. The `apache/superset-cache:3.10-slim-trixie` and
|
||||
`py310` Docker image variants are no longer published.
|
||||
|
||||
### `from_dttm` and `to_dttm` Jinja template variables removed
|
||||
|
||||
The `{{ from_dttm }}` and `{{ to_dttm }}` Jinja template variables, deprecated since
|
||||
v5.0, have been removed from the Jinja context and are no longer available in virtual
|
||||
dataset SQL or custom SQL expressions.
|
||||
|
||||
Replace usages with the `get_time_filter()` function. For example:
|
||||
|
||||
```sql
|
||||
-- Before
|
||||
SELECT * FROM tbl
|
||||
WHERE dttm_col > '{{ from_dttm }}' AND dttm_col < '{{ to_dttm }}'
|
||||
|
||||
-- After
|
||||
{% set tf = get_time_filter("dttm_col") %}
|
||||
SELECT * FROM tbl
|
||||
WHERE dttm_col > {{ tf.from_expr }} AND dttm_col <= {{ tf.to_expr }}
|
||||
```
|
||||
|
||||
Note that `from_expr` and `to_expr` are already fully-formatted SQL expressions (e.g.
|
||||
`TO_TIMESTAMP('2024-01-01', ...)`) — do not wrap them in single quotes.
|
||||
|
||||
### Removed deck.gl JavaScript tooltip/data-mutator controls and ENABLE_JAVASCRIPT_CONTROLS
|
||||
|
||||
The `ENABLE_JAVASCRIPT_CONTROLS` feature flag and the deck.gl chart controls it gated
|
||||
(`js_tooltip`, `js_onclick_href`, `js_data_mutator`, and the GeoJSON layer's label/icon
|
||||
JavaScript-mode generators) have been removed. These controls let users write arbitrary
|
||||
JavaScript, sandboxed via Node's `vm` module, to customize deck.gl tooltips, click
|
||||
behavior, and data transforms; the flag defaulted off and the feature saw negligible use.
|
||||
|
||||
The deck.gl "Extra data for JS" control (`js_columns`) has also been removed. It only
|
||||
ever existed to feed extra columns into the JavaScript controls above; deck.gl's
|
||||
built-in field-based tooltips and cross-filtering already pull in any columns they need
|
||||
via `tooltip_contents`/`cross_filter_column`, so this control had no remaining purpose.
|
||||
Any chart layer whose "Advanced" control panel section only contained this control no
|
||||
longer has an "Advanced" section.
|
||||
|
||||
Any saved charts with these fields set will simply ignore them going forward and fall back
|
||||
to deck.gl's built-in field-based tooltips (`tooltip_contents`/`tooltip_template`) and
|
||||
native click/cross-filter behavior. No migration is required; the fields are dropped
|
||||
silently on next save.
|
||||
|
||||
### Owners, dashboard roles, and RLS roles replaced by Subjects
|
||||
|
||||
Superset now uses subject-based access assignments for dashboards, charts, datasets,
|
||||
alerts/reports, and Row Level Security. A Subject can represent a user, role, or group.
|
||||
|
||||
This is a breaking API and metadata change:
|
||||
|
||||
- `owners` is replaced by `editors` for dashboards, charts, datasets, and alerts/reports.
|
||||
- Dashboard `roles` and the `DASHBOARD_RBAC` feature flag are replaced by dashboard/chart
|
||||
`viewers`, enabled with `ENABLE_VIEWERS`.
|
||||
- RLS `roles` is replaced by `subjects`.
|
||||
- The legacy `dashboard_user`, `slice_user`, `sqlatable_user`, `report_schedule_user`,
|
||||
`dashboard_roles`, and `rls_filter_roles` tables are migrated into subject junction tables
|
||||
and dropped on upgrade.
|
||||
|
||||
For deployments that previously used `DASHBOARD_RBAC` and later disabled it: remove stale rows
|
||||
from the legacy `dashboard_roles` table before upgrading, otherwise those role assignments will
|
||||
become active dashboard Viewers after migration.
|
||||
|
||||
API clients and automation should send and read `editors`, `viewers`, and `subjects` instead
|
||||
of the legacy fields.
|
||||
|
||||
Subject pickers support users, groups, and roles, but only users and groups are selectable by
|
||||
default. Roles remain supported as Subject types for backwards compatibility with RLS role
|
||||
assignments and the previous `DASHBOARD_RBAC` model, but they are not recommended for new
|
||||
resource-specific assignments. Prefer groups for membership-based access and keep roles focused
|
||||
on capability grants. Existing Role subject assignments remain effective after migration even when
|
||||
Roles are hidden from the default dropdown values; configure the relevant `SUBJECTS_RELATED_TYPES_*`
|
||||
setting to make Roles selectable when editing subject lists. See the [Security documentation](docs/admin_docs/security/security.mdx#subjects)
|
||||
for the full Subject model and picker configuration guidance.
|
||||
|
||||
To make roles selectable everywhere:
|
||||
|
||||
```python
|
||||
from superset.subjects.types import SubjectType
|
||||
|
||||
SUBJECTS_RELATED_TYPES = [
|
||||
SubjectType.USER,
|
||||
SubjectType.GROUP,
|
||||
SubjectType.ROLE,
|
||||
]
|
||||
```
|
||||
|
||||
To make roles selectable for RLS while other pickers keep the user and group default, use the
|
||||
RLS-specific override:
|
||||
|
||||
```python
|
||||
from superset.subjects.types import SubjectType
|
||||
|
||||
SUBJECTS_RELATED_TYPES_RLS = [
|
||||
SubjectType.USER,
|
||||
SubjectType.GROUP,
|
||||
SubjectType.ROLE,
|
||||
]
|
||||
```
|
||||
|
||||
Entity-specific `SUBJECTS_RELATED_TYPES_*` settings replace `SUBJECTS_RELATED_TYPES` for that
|
||||
picker.
|
||||
|
||||
Deployments using `EXTRA_OWNERS_RESOLVER` must migrate to `EXTRA_EDITORS_RESOLVER`. The new
|
||||
resolver should return editor Subjects, subject IDs, or dicts with an `id` key instead of FAB
|
||||
User objects. API responses expose these dynamic assignments as `extra_editors` instead of
|
||||
`extra_owners`.
|
||||
|
||||
`DASHBOARD_RBAC` has been removed. To preserve the previous Dashboard RBAC behavior, enable both
|
||||
subject viewers and viewer datasource bypass:
|
||||
|
||||
```python
|
||||
FEATURE_FLAGS = {
|
||||
"ENABLE_VIEWERS": True,
|
||||
}
|
||||
VIEWER_PROMISCUOUS_MODE = True
|
||||
```
|
||||
|
||||
Enabling only `ENABLE_VIEWERS` allows assigning dashboard/chart viewer subjects, but viewers still
|
||||
need normal datasource permissions unless `VIEWER_PROMISCUOUS_MODE` is also enabled.
|
||||
|
||||
For backwards compatibility, enabling `ENABLE_VIEWERS` does not change access for dashboards or
|
||||
charts that have no assigned viewers. Those resources continue to use the implicit dataset-access
|
||||
model: users who can access the underlying dataset can still see published dashboards that use that
|
||||
dataset and charts backed by that dataset.
|
||||
Assigning one or more viewers opts that resource into explicit viewer access for non-editors. To
|
||||
return a resource to the implicit dataset-access model, remove all viewers from it. Explicit Viewers
|
||||
are the intended model going forward; deprecating and removing implicit viewership can be considered
|
||||
in a later major version.
|
||||
|
||||
- [41044](https://github.com/apache/superset/issues/41044): Removes the deprecated `AVOID_COLORS_COLLISION` feature flag (it defaulted to `True`). Color-collision avoidance is now permanently enabled; any config override setting it to `False` is ignored.
|
||||
|
||||
- [41813](https://github.com/apache/superset/pull/41813): `redis` (the Python client, `redis-py`) is bumped from 5.3.1 to 8.0.1. redis-py 8 changes several connection defaults; Superset's own Redis-backed features (`GLOBAL_ASYNC_QUERIES_CACHE_BACKEND`, `DISTRIBUTED_COORDINATION_CONFIG`, and the MCP Redis store) explicitly pin the pre-upgrade behavior so this bump is a no-op for them: the wire protocol stays RESP2 (not the new RESP3 default, which requires Redis/Sentinel 6+ to speak `HELLO`) and there is still no socket timeout by default (redis-py 8 defaults to 5s, which could otherwise newly time out large cached payloads or slow networks). The no-timeout default can now be overridden via two new config keys, `CACHE_REDIS_SOCKET_TIMEOUT` / `CACHE_REDIS_SOCKET_CONNECT_TIMEOUT`, on any `CacheConfig` dict using `CACHE_TYPE: RedisCache` or `RedisSentinelCache`. Separately, redis-py 6+ changed the default for `ssl_check_hostname` from `False` to `True` for SSL connections using `ssl_cert_reqs="required"` (the default) — this is a security improvement, so it has **not** been reverted; deployments with `CACHE_REDIS_SSL=True` whose certificates lack a hostname matching the connection address should set `CACHE_REDIS_SSL_CERT_REQS="none"` (disables cert verification entirely, matching hostname-check bypass) or replace the certificate. General-purpose cache/results backends configured via `CACHE_CONFIG` / `DATA_CACHE_CONFIG` / `RESULTS_BACKEND` with `CACHE_TYPE: RedisCache` go through `flask-caching`'s own Redis backend (outside Superset's code) and are subject to the same new defaults; pass `socket_timeout` / `protocol` via `CACHE_OPTIONS` there if needed. Celery broker and result-backend connections (built by `kombu`, also outside Superset's code) keep their no-socket-timeout behavior (`kombu` passes `socket_timeout=None` explicitly) but do **not** pin the wire protocol, so they follow redis-py's RESP3 default — which requires a Redis server new enough to speak `HELLO` (Redis 6+). Deployments using a pre-6.0 Redis server (EOL) as a Celery broker should upgrade the server before taking this bump.
|
||||
|
||||
- [39925](https://github.com/apache/superset/pull/39925): URL prefixing for `SUPERSET_APP_ROOT` subdirectory deployments is now handled automatically by helpers in `src/utils/navigationUtils` (`openInNewTab`, `redirect`, `getShareableUrl`, `<AppLink>`). Direct imports of `ensureAppRoot` / `makeUrl` from `src/utils/pathUtils` are forbidden outside `navigationUtils.ts` (enforced by a static-invariant test); contributors writing new code should use the focused helpers instead. No runtime behaviour change for existing callers — all 19 prior call sites have been migrated and four pre-existing double-prefix and missing-prefix bugs are fixed as part of the migration.
|
||||
|
||||
- [39925](https://github.com/apache/superset/pull/39925): `SupersetClient.getUrl()` now strips a single leading application-root segment from the supplied `endpoint` before building the request URL, so a caller that accidentally pre-prefixes its endpoint (for example by wrapping it with `ensureAppRoot` before passing it to the client) no longer produces a doubled `/superset/superset/...` URL under subdirectory deployment. The strip is **single-pass** — a genuine `/superset/superset/<slug>` route is preserved, not collapsed — and **silent** (no console warning); the static-invariant test remains the primary signal for pre-prefixing at the call site, and this runtime strip is a safety net beneath it. Code that intentionally targeted a literal `/<app_root>/<app_root>/...` endpoint through `getUrl` (a configuration that has no legitimate use under the prefixing model) would have its first redundant segment removed.
|
||||
@@ -237,16 +65,6 @@ virtual-dataset SQL) raises a parse error in SQL Lab and dashboard-generated
|
||||
queries. Deployments that legitimately run queries above this size should raise
|
||||
the value, and `SQL_MAX_PARSE_LENGTH = None` disables the check entirely.
|
||||
|
||||
### Ant Design upgraded from v5 to v6
|
||||
|
||||
The frontend now builds against Ant Design 6, and `@superset-ui/core` / `@apache-superset/core` peer-depend on `antd ^6`. Custom plugins, extensions, and themes that interact with Ant Design need review:
|
||||
|
||||
- **Internal DOM classes were renamed**, so any custom CSS targeting `.ant-*` internals silently stops matching. Notable renames: `.ant-tabs-content-holder` → `.ant-tabs-body-holder`, `.ant-tabs-content` → `.ant-tabs-body`, `.ant-tabs-tabpane` → `.ant-tabs-content`; `.ant-select-selector` → `.ant-select-content`, `.ant-select-selection-placeholder` → `.ant-select-placeholder`, `.ant-select-arrow` → `.ant-select-suffix`; `.ant-tooltip-inner` → `.ant-tooltip-container`; `.ant-popover-inner` → `.ant-popover-container`; `.ant-steps-item-tail` → `.ant-steps-item-rail`.
|
||||
- **Some component props changed or were removed** — e.g. `Select` no longer accepts `dropdownAlign`, `visible`/`onVisibleChange` are `open`/`onOpenChange`, `Dropdown` `overlay` is `menu`, `Steps.Step` children are the `items` prop, and `styles.body` on Tooltip/Popover is `styles.container`.
|
||||
- **CSS variables are on by default** in antd 6, and `ThemeConfig.cssVar` no longer accepts a boolean; Superset theme configs using `cssVar: true`/`false` are coerced (`true` → `{}`, `false` → omitted).
|
||||
|
||||
Theme tokens are unaffected — antd 6 removed none of the tokens Superset exposes, so existing theme configurations continue to work. See the [Ant Design v6 migration guide](https://ant.design/docs/react/migration-v6) for the complete upstream list.
|
||||
|
||||
### Guest-token RLS rules reject unknown fields
|
||||
|
||||
The `rls` rules passed to `POST /api/v1/security/guest_token/` are now validated strictly: a rule may only contain `dataset` and `clause`. Previously unknown fields were silently dropped, so a mistyped or legacy scope key (most commonly `datasource` instead of `dataset`) produced a rule with no `dataset`, which is treated as a *global* rule applied to every dataset the embedded resource can reach. Such a request now returns HTTP 400 identifying the offending field instead of issuing a token with an unintended global rule. Integrators that were sending extra fields in RLS rules must remove them; valid dataset-scoped (`{"dataset": 41, "clause": "..."}`) and global (`{"clause": "..."}`) rules are unaffected.
|
||||
@@ -276,10 +94,6 @@ helm upgrade <release-name> superset/superset
|
||||
|
||||
Alternatively, perform a fresh install. This is a one-time migration; subsequent upgrades are unaffected.
|
||||
|
||||
### Time-series tooltips follow the selected time grain
|
||||
|
||||
Tooltips on the Time-series and Mixed Time-series ECharts plugins now respect the chart's time grain (and any dashboard-level time-grain override delivered via `extra_form_data`) when the tooltip time format is left on Adaptive formatting (the default). Tooltips read grain-appropriate labels such as `Jan 2021` (month), `2021 Q1` (quarter), `2021` (year), and weekly date ranges, becoming grain-aware like the x-axis, though the two are formatted independently and their labels may not always match exactly. Only a custom, explicitly-set tooltip time format (a d3 format string) is unaffected — that always wins over the grain.
|
||||
|
||||
### Pivot table First/Last aggregations follow data order
|
||||
|
||||
The pivot table chart's `First` and `Last` aggregations now return the first and last value in data (query result) order, instead of effectively returning the minimum and maximum. Existing pivot tables that use these aggregations for totals/subtotals may show different values after upgrading. For deterministic results, ensure the underlying query has a stable sort order.
|
||||
@@ -288,22 +102,6 @@ The pivot table chart's `First` and `Last` aggregations now return the first and
|
||||
|
||||
The `error` and `response` parameters of the `retryDelay` and `retryOn` callbacks in `FetchRetryOptions` (exported from `@superset-ui/core`) are now typed `Error | null` and `Response | null` to match the actual call-site signature provided by `fetch-retry`. Because these parameter types are contravariant, consumers who typed their callbacks with the non-nullable `(attempt: number, error: Error, response: Response) => number` will get a TypeScript compile error. Widen your callback signatures to accept `Error | null` / `Response | null`.
|
||||
|
||||
### Pivot Table totals are now computed by the database (per-metric "Aggregation function" control removed)
|
||||
|
||||
Pivot Table subtotals and grand totals are now computed by the database at each
|
||||
rollup level instead of re-aggregating the already-aggregated cell values on the
|
||||
client. This fixes long-standing incorrect totals for non-additive metrics
|
||||
(ratios such as `SUM(a)/SUM(b)`, `COUNT_DISTINCT`, `AVG`, percentiles, etc.),
|
||||
which previously summed the displayed cell values.
|
||||
|
||||
As a result the per-table **"Aggregation function"** control (which let you pick
|
||||
how totals were aggregated client-side, e.g. Sum/Average/Count) has been
|
||||
removed: totals now always reflect the metric's own definition evaluated at the
|
||||
total's granularity. For additive metrics (`SUM`/`COUNT`/`MIN`/`MAX`) the result
|
||||
is unchanged. Saved charts that set `aggregateFunction` will ignore it; no
|
||||
migration is required. If you previously relied on a plain sum-of-cells total
|
||||
for a non-additive metric, that specific behavior is no longer available.
|
||||
|
||||
### `thumbnail_url` removed from dashboard list API response
|
||||
|
||||
The `thumbnail_url` field has been removed from `GET /api/v1/dashboard/` list responses. External consumers relying on this field must now construct the thumbnail URL client-side using `id` and `changed_on_utc`:
|
||||
@@ -314,12 +112,6 @@ The `thumbnail_url` field has been removed from `GET /api/v1/dashboard/` list re
|
||||
|
||||
The thumbnail endpoint redirects to the current digest URL regardless of whether the supplied digest is exact. If the image is not yet cached, that digest URL may return `202` and trigger async generation. Using `changed_on_utc` as the digest is sufficient for cache-busting purposes.
|
||||
|
||||
### Dashboard import can overwrite related charts, datasets, and databases
|
||||
|
||||
Re-importing an existing dashboard previously overwrote only the dashboard itself; its related charts, datasets, and databases were never updated (the importer hardcoded `overwrite=False` for them). They can now be overwritten as part of the import.
|
||||
|
||||
A new `overwrite_all` form field controls this, and defaults to `false` everywhere, so existing behavior is preserved: passing `overwrite=true` alone still overwrites only the dashboard, exactly as before. To also overwrite the related charts, datasets, and databases on the `/api/v1/dashboard/import/` endpoint, pass `overwrite_all=true` explicitly. The import modal in the UI exposes this as an "also overwrite all assets" checkbox, and the CLI `superset import-dashboards` and the `ImportDashboardsCommand` likewise default `overwrite_all` to `false`.
|
||||
|
||||
### Tagging fix for `create_all`-bootstrapped schemas
|
||||
|
||||
Only affects deployments whose metadata schema was created with SQLAlchemy's `create_all` (rather than `superset db upgrade`) on a foreign-key-enforcing backend — PostgreSQL, or MySQL with `FOREIGN_KEY_CHECKS=1`. Such schemas carry three invalid foreign keys on `tagged_object.object_id` that break tagging (`TAGGING_SYSTEM = True`) with a `ForeignKeyViolation`. Schemas built via `superset db upgrade` are unaffected.
|
||||
@@ -333,30 +125,6 @@ ALTER TABLE tagged_object DROP CONSTRAINT <constraint_name>;
|
||||
-- MySQL: find names via `SHOW CREATE TABLE tagged_object;`
|
||||
ALTER TABLE tagged_object DROP FOREIGN KEY <constraint_name>;
|
||||
```
|
||||
### Entity version-history infrastructure (gated off by default)
|
||||
|
||||
Introduces the schema and SQLAlchemy-Continuum wiring that captures version history for charts, dashboards, and datasets, plus read-only `GET /api/v1/{chart,dashboard,dataset}/<uuid>/versions/` endpoints. This ships **inert**: a new config flag `ENABLE_VERSIONING_CAPTURE` defaults to `False`, so no save writes any version rows and the endpoints return empty. It is an operational kill-switch (a release toggle that becomes a permanent ops switch), not a feature flag — set it to `True` to enable capture once validated. The migration is additive; existing entity `PUT` responses gain `old_version_uuid` / `new_version_uuid` body fields and an `ETag` header (both null/absent when capture is off).
|
||||
|
||||
A few save- and import-path internals change **unconditionally** (independent of the flag), because the versioned mappers must behave correctly whether or not capture is enabled:
|
||||
|
||||
- `DatasetDAO` column/metric updates move from bulk operations to per-row ORM operations, and a metadata refresh now preserves column primary keys via a natural-key (`column_name`) upsert instead of delete-and-reinsert — so charts that reference dataset columns by id keep working across a refresh (previously such references could be invalidated).
|
||||
- `ImportExportMixin.reset_ownership` stamps the current user onto `created_by`/`changed_by` when a request context is present (previously left null for the column default to fill).
|
||||
- `UpdateDashboardCommand` runs its body under `no_autoflush`.
|
||||
|
||||
These are behavior changes that take effect on upgrade regardless of `ENABLE_VERSIONING_CAPTURE`; no operator action is required.
|
||||
|
||||
### Cross-entity version activity stream
|
||||
|
||||
A read-only companion to the version-history endpoints: each entity type gains a `GET /api/v1/{chart,dashboard,dataset}/<uuid>/activity/` endpoint returning a chronological, access-filtered stream of edits — the entity's own edits plus, for charts and dashboards, transitive edits to related entities during their association windows. Datasets have no related layer in V2, so `include=related` returns an empty stream for a dataset and `include=all` reduces to the dataset's own edits.
|
||||
|
||||
| Param | Type | Default | Purpose |
|
||||
|---|---|---|---|
|
||||
| `since` / `until` | ISO 8601 | — | Bound `issued_at` |
|
||||
| `include` | `self` \| `related` \| `all` | `all` | Own edits, related edits, or both |
|
||||
| `q` | string | — | Case-insensitive search over the full history, applied before pagination (so `count` reflects matches) |
|
||||
| `page` / `page_size` | integer | `0` / `25` | Pagination (`page_size` clamped to 200) |
|
||||
|
||||
Authorization reuses the resource's `can_read` permission and per-object `raise_for_access`; related-entity rows are visibility-filtered to what the caller may see. The stream is empty unless version capture is on (`ENABLE_VERSIONING_CAPTURE`).
|
||||
|
||||
### Webhook alerts/reports block private/internal hosts by default
|
||||
|
||||
@@ -552,7 +320,7 @@ With the flag enabled: `DELETE /api/v1/dataset/<id>` no longer hard-deletes the
|
||||
|
||||
**Side-effect change for operators.** Because the row is no longer physically deleted, FAB `ab_view_menu` / permission-view rows tied to the dataset are also preserved. Downstream automation that relied on `DELETE /api/v1/dataset/<id>` cleaning up those rows must now react to the new `POST /api/v1/dataset/<uuid>/restore` lifecycle, or call the eventual hard-delete endpoint.
|
||||
|
||||
**New endpoint** — `POST /api/v1/dataset/<uuid>/restore` clears `deleted_at` and returns the dataset to active state. Requires `can_write on Dataset` and editorship of the row (or admin). Soft-deleted datasets can also be surfaced in the list endpoint via the new `dataset_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to datasets they can edit — the same audience that can restore them.
|
||||
**New endpoint** — `POST /api/v1/dataset/<uuid>/restore` clears `deleted_at` and returns the dataset to active state. Requires `can_write on Dataset` and ownership of the row (or admin). Soft-deleted datasets can also be surfaced in the list endpoint via the new `dataset_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to datasets they own — the same audience that can restore them.
|
||||
|
||||
**Permissions migration:** existing role grants of `can_write on Dataset` cover the new restore endpoint automatically; no role migration is required.
|
||||
|
||||
@@ -562,7 +330,7 @@ With the flag enabled: `DELETE /api/v1/dataset/<id>` no longer hard-deletes the
|
||||
|
||||
**SQL Lab / dataset-creation flows:** creating a dataset over a table whose dataset sits in the trash is refused. The SQL Lab "save as dataset" flow (`get_or_create_dataset`) and file uploads return a **422 naming the hidden twin and the restore endpoint**; the plain create, update, and duplicate paths currently fail with the generic "already exists" 422. In all cases the remediation is the same: restore the hidden dataset (or use a different table name). Perm-string maintenance also covers hidden rows: renaming a database rewrites `perm`/`schema_perm`/`catalog_perm` on soft-deleted datasets and their charts, so a later restore does not resurrect stale permission strings.
|
||||
|
||||
**Importer behavior:** importing a dataset YAML whose UUID matches an existing **soft-deleted** dataset is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dataset imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dataset's exact UUID is an explicit request to bring it back. The restore preserves the original PK, the chart back-reference, `table_columns`, and `sql_metrics`. Non-editors get `ImportFailedError`. Callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
|
||||
**Importer behavior:** importing a dataset YAML whose UUID matches an existing **soft-deleted** dataset is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dataset imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dataset's exact UUID is an explicit request to bring it back. The restore preserves the original PK, the chart back-reference, `table_columns`, and `sql_metrics`. Non-owners get `ImportFailedError`. Callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
|
||||
|
||||
**Uniqueness-validation changes that apply regardless of the feature flag:** two dataset uniqueness checks were tightened alongside this work and are active even with `SOFT_DELETE` off. (1) Create/update uniqueness treats a dataset whose `catalog` is `NULL` as belonging to the database's default catalog, so a legacy twin pair (`catalog=NULL` vs. `catalog=<default>`, same database/schema/name) that older versions allowed now fails validation with "already exists" when either row is edited — resolve by renaming or removing one of the twins. (2) Duplicating a dataset now checks name collisions scoped to the target (database, catalog, schema) instead of globally by name alone: duplicates into other databases that were previously blocked are now allowed.
|
||||
|
||||
@@ -576,7 +344,7 @@ With the flag enabled: `DELETE /api/v1/chart/<id>` no longer hard-deletes the ch
|
||||
|
||||
**Operational notes:** a report schedule whose target chart is soft-deleted now fails its runs with an explicit error ("The chart this report targets was deleted...") until the chart is restored or the report re-pointed — chart deletion is blocked while a report references the chart, but a validate/commit race or a flag toggle can still produce this state. Dashboards **preserve** their membership rows for soft-deleted charts: saving a dashboard does not sever a trashed member, and restoring the chart re-attaches it to its dashboards.
|
||||
|
||||
**New endpoint** — `POST /api/v1/chart/<uuid>/restore` clears `deleted_at` and returns the chart to active state. Requires `can_write on Chart` and editorship of the row (or admin). Soft-deleted charts can also be surfaced in the list endpoint via the new `chart_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to charts they can edit — the same audience that can restore them.
|
||||
**New endpoint** — `POST /api/v1/chart/<uuid>/restore` clears `deleted_at` and returns the chart to active state. Requires `can_write on Chart` and ownership of the row (or admin). Soft-deleted charts can also be surfaced in the list endpoint via the new `chart_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to charts they own — the same audience that can restore them.
|
||||
|
||||
**Permissions migration:** existing role grants of `can_write on Chart` cover the new restore endpoint automatically; no role migration is required.
|
||||
|
||||
@@ -584,7 +352,7 @@ With the flag enabled: `DELETE /api/v1/chart/<id>` no longer hard-deletes the ch
|
||||
|
||||
**Rollback note:** if the application code is rolled back after charts have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted charts silently become live, active charts with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) *before* downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
|
||||
|
||||
**Importer behavior:** importing a chart YAML whose UUID matches an existing **soft-deleted** chart is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active chart imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted chart's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all out-of-archive references (`dashboard_slices` junctions, `report.chart_id`, tag rows). The operation is permission-gated: non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
|
||||
**Importer behavior:** importing a chart YAML whose UUID matches an existing **soft-deleted** chart is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active chart imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted chart's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all out-of-archive references (`dashboard_slices` junctions, `report.chart_id`, tag rows). The operation is permission-gated: non-owners get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
|
||||
|
||||
- [39914](https://github.com/apache/superset/pull/39914) `ALERT_REPORT_SLACK_V2` now defaults to `True` and the legacy Slack v1 integration (`Slack` recipient type, `files.upload` API) is deprecated for removal in the next major. Slack blocked new apps from `files.upload` in May 2024 and fully retired the method for all apps on November 12, 2025; because the v1 path sends files through `files.upload`, v1 file-bearing sends now fail at the API level — only text-only `chat_postMessage` still works via the legacy path. Grant your Slack bot the `channels:read` and `groups:read` scopes so existing `Slack` recipients can be auto-upgraded to `SlackV2` on next send. Operators who explicitly override the flag to `False`, or whose Slack bot is missing those scopes, will see deprecation warnings while text-only sends continue through the legacy path.
|
||||
|
||||
@@ -596,7 +364,7 @@ With the flag enabled: `DELETE /api/v1/chart/<id>` no longer hard-deletes the ch
|
||||
|
||||
With the flag enabled: `DELETE /api/v1/dashboard/<id>` no longer hard-deletes the dashboard (the bulk-delete endpoint behaves the same way). The row is marked with a `deleted_at` timestamp and hidden from the dashboard API's list, detail, and lookup endpoints, which return 404 for soft-deleted dashboards. The embedded-dashboard iframe URL (`/embedded/<uuid>`) keeps rendering because it reads only `embedded.allowed_domains` and `embedded.dashboard_id` (the FK column) without dereferencing the parent dashboard; the frontend's subsequent dashboard-API fetch is what sees the 404 and surfaces "dashboard not found" to the user.
|
||||
|
||||
**New endpoint** — `POST /api/v1/dashboard/<uuid>/restore` clears `deleted_at` and returns the dashboard to active state. Requires `can_write on Dashboard` and editorship of the row (or admin). Soft-deleted dashboards can also be surfaced in the list endpoint via the new `dashboard_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to dashboards they can edit — the same audience that can restore them.
|
||||
**New endpoint** — `POST /api/v1/dashboard/<uuid>/restore` clears `deleted_at` and returns the dashboard to active state. Requires `can_write on Dashboard` and ownership of the row (or admin). Soft-deleted dashboards can also be surfaced in the list endpoint via the new `dashboard_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to dashboards they own — the same audience that can restore them.
|
||||
|
||||
**Permissions migration:** existing role grants of `can_write on Dashboard` cover the new restore endpoint automatically; no role migration is required.
|
||||
|
||||
@@ -608,13 +376,7 @@ The partial-index replacement is dialect-dependent: PostgreSQL uses a native `WH
|
||||
|
||||
**Slug semantics:** on PostgreSQL and MySQL 8.0.13+, the slug of a soft-deleted dashboard is **free for reuse**. A new active dashboard can claim it immediately. Restoring a soft-deleted dashboard whose slug has since been claimed returns **422 with a clean error** (`DashboardSlugConflictError`) — rename one of the dashboards and retry; the restore is not silently rejected by a database-level constraint violation.
|
||||
|
||||
**Importer behavior:** importing a dashboard YAML whose UUID matches an existing **soft-deleted** dashboard is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dashboard imported without `overwrite=true` is returned unchanged (the import never mutates it), but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dashboard's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all pre-deletion relationship rows (`dashboard_slices` junctions, editor/viewer subjects, tags). Callers whose imports must never mutate existing state should treat bundles that may contain previously deleted UUIDs accordingly. The operation is permission-gated: it requires `can_write` and editorship of the deleted row (or admin) — non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
|
||||
|
||||
### Engine spec capability flag: `supports_offset`
|
||||
|
||||
A new `BaseEngineSpec.supports_offset` attribute (default `True`) indicates whether a database engine supports the SQL `OFFSET` clause. Engines that do not support `OFFSET` — such as Elasticsearch SQL and OpenDistro — opt out by setting it to `False`, and Superset uses each engine's cursor API to paginate drill-to-detail samples instead of emitting `OFFSET`. Downstream forks maintaining custom engine specs may set the flag to `False` (and implement `fetch_data_with_cursor`) to avoid crashes when paginated drill-to-detail queries are run against engines without `OFFSET` support.
|
||||
|
||||
**Note on deep-pagination cost:** Cursor-based engines (including Elasticsearch and OpenDistro) are forward-only, so reaching page `N` of a drill-to-detail view issues `N` round trips to the cluster. Deep pagination is therefore linear in page number; users paginating into the hundreds or thousands will notice added latency compared to `OFFSET`-capable engines.
|
||||
**Importer behavior:** importing a dashboard YAML whose UUID matches an existing **soft-deleted** dashboard is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dashboard imported without `overwrite=true` is returned unchanged (the import never mutates it), but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dashboard's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all pre-deletion relationship rows (`dashboard_slices` junctions, role grants, owners, tags) — including role grants that were implicitly revoked by the deletion. Callers whose imports must never mutate existing state should treat bundles that may contain previously deleted UUIDs accordingly. The operation is permission-gated: it requires `can_write` and ownership of the deleted row (or admin) — non-owners get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
|
||||
|
||||
### Granular Export Controls
|
||||
|
||||
@@ -652,10 +414,6 @@ Added a new combined datasource list endpoint at `GET /api/v1/datasource/` to se
|
||||
|
||||
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.
|
||||
|
||||
### Kenya Country Map: Updated Administrative Divisions
|
||||
|
||||
The Kenya country map has been updated to reflect the 47 counties established under Kenya's 2010 constitution, replacing the outdated 8-province boundaries from the Natural Earth dataset. County keys now use ISO 3166-2:KE codes (`KE-01` through `KE-47`), replacing the former province codes (`KE-110`, `KE-200`, ..., `KE-800`). Dashboards that join on the old province codes will need to re-key their datasets to use the new county codes.
|
||||
|
||||
### MCP Tool Observability
|
||||
|
||||
MCP (Model Context Protocol) tools now include enhanced observability instrumentation for monitoring and debugging:
|
||||
@@ -864,7 +622,6 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
|
||||
|
||||
---
|
||||
|
||||
- [38358](https://github.com/apache/superset/pull/38358): Switched CrateDB PyPI package from `crate[sqlalchemy]` to `sqlalchemy-cratedb`.
|
||||
- [35621](https://github.com/apache/superset/pull/35621): The default hash algorithm has changed from MD5 to SHA-256 for improved security and FedRAMP compliance. This affects cache keys for thumbnails, dashboard digests, chart digests, and filter option names. Existing cached data will be invalidated upon upgrade. To opt out of this change and maintain backward compatibility, set `HASH_ALGORITHM = "md5"` in your `superset_config.py`.
|
||||
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ x-common-build: &common-build
|
||||
context: .
|
||||
target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean`
|
||||
cache_from:
|
||||
- apache/superset-cache:3.11-slim-trixie
|
||||
- apache/superset-cache:3.10-slim-trixie
|
||||
args:
|
||||
DEV_MODE: "true"
|
||||
INCLUDE_CHROMIUM: ${INCLUDE_CHROMIUM:-false}
|
||||
|
||||
@@ -33,7 +33,7 @@ x-common-build: &common-build
|
||||
context: .
|
||||
target: dev
|
||||
cache_from:
|
||||
- apache/superset-cache:3.11-slim-trixie
|
||||
- apache/superset-cache:3.10-slim-trixie
|
||||
|
||||
services:
|
||||
redis:
|
||||
|
||||
@@ -34,12 +34,11 @@ x-superset-volumes: &superset-volumes
|
||||
- superset_home:/app/superset_home
|
||||
- ./tests:/app/tests
|
||||
- superset_data:/app/data
|
||||
- ./local_extensions:/app/local_extensions
|
||||
x-common-build: &common-build
|
||||
context: .
|
||||
target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean`
|
||||
cache_from:
|
||||
- apache/superset-cache:3.11-slim-trixie
|
||||
- apache/superset-cache:3.10-slim-trixie
|
||||
args:
|
||||
DEV_MODE: "true"
|
||||
INCLUDE_CHROMIUM: ${INCLUDE_CHROMIUM:-false}
|
||||
|
||||
@@ -98,9 +98,7 @@ case "${1}" in
|
||||
echo " 🔒 Werkzeug debugger disabled (set SUPERSET_DEBUG_ENABLED=true to enable)"
|
||||
fi
|
||||
|
||||
flask run -p $PORT --reload $DEBUGGER_FLAG --host=0.0.0.0 \
|
||||
--extra-files "/app/superset/extensions/.reload_trigger" \
|
||||
--exclude-patterns "*/node_modules/*:*/.venv/*:*/build/*:*/__pycache__/*:*/superset-frontend/*:*/superset/__init__.py"
|
||||
flask run -p $PORT --reload $DEBUGGER_FLAG --host=0.0.0.0 --exclude-patterns "*/node_modules/*:*/.venv/*:*/build/*:*/__pycache__/*:*/superset-frontend/*"
|
||||
;;
|
||||
app-gunicorn)
|
||||
echo "Starting web app..."
|
||||
|
||||
@@ -87,7 +87,6 @@ class CeleryConfig:
|
||||
"superset.tasks.scheduler",
|
||||
"superset.tasks.thumbnails",
|
||||
"superset.tasks.cache",
|
||||
"superset.tasks.export_dashboard_excel",
|
||||
)
|
||||
result_backend = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_RESULTS_DB}"
|
||||
worker_prefetch_multiplier = 1
|
||||
|
||||
@@ -135,7 +135,7 @@ Superset sends an HTTP POST with `Content-Type: application/json`:
|
||||
}
|
||||
```
|
||||
|
||||
When a report includes file attachments (CSV, Excel, PDF, or PNG screenshots), the request is sent as `multipart/form-data` instead. In that case, each top-level payload field (`name`, `text`, `description`, `url`) becomes its own form field, and nested structures like `header` are serialized as a JSON-encoded string in their own field. Every attachment is added as a repeated form field named `files`:
|
||||
When a report includes file attachments (CSV, PDF, or PNG screenshots), the request is sent as `multipart/form-data` instead. In that case, each top-level payload field (`name`, `text`, `description`, `url`) becomes its own form field, and nested structures like `header` are serialized as a JSON-encoded string in their own field. Every attachment is added as a repeated form field named `files`:
|
||||
|
||||
```
|
||||
POST /webhook HTTP/1.1
|
||||
@@ -287,10 +287,8 @@ to specify on behalf of which username to render the dashboards. In general, das
|
||||
are not accessible to unauthorized requests, that is why the worker needs to take over credentials
|
||||
of an existing user to take a snapshot.
|
||||
|
||||
By default, Alerts and Reports are executed as a physical user represented by the alert/report editors
|
||||
(giving priority to the last modifier, then the creator, then the first direct user editor, then a
|
||||
deterministic user from editor roles or groups). To use a fixed user account, just change the config
|
||||
as follows (`admin` in this example):
|
||||
By default, Alerts and Reports are executed as the owner of the alert/report object. To use a fixed user account,
|
||||
just change the config as follows (`admin` in this example):
|
||||
|
||||
```python
|
||||
from superset.tasks.types import FixedExecutor
|
||||
@@ -361,7 +359,7 @@ This is the best source of information about the problem. In a docker compose d
|
||||
|
||||
### Check web browser and webdriver installation
|
||||
|
||||
To take a screenshot, the worker visits the dashboard or chart using a headless browser, then takes a screenshot. If you are able to send a chart as CSV, XLSX, or text but can't send as PNG, your problem may lie with the browser.
|
||||
To take a screenshot, the worker visits the dashboard or chart using a headless browser, then takes a screenshot. If you are able to send a chart as CSV or text but can't send as PNG, your problem may lie with the browser.
|
||||
|
||||
If you are handling the installation of the headless browser on your own, do your own verification to ensure that the headless browser opens successfully in the worker environment.
|
||||
|
||||
|
||||
@@ -139,10 +139,6 @@ from superset.tasks.types import FixedExecutor
|
||||
THUMBNAIL_EXECUTORS = [FixedExecutor("admin")]
|
||||
```
|
||||
|
||||
When using `ExecutorType.EDITOR`, thumbnails are rendered as a physical user represented by
|
||||
the dashboard or chart editors. Superset prioritizes the last modifier, then the creator,
|
||||
then the first direct user editor, then a deterministic user from editor roles or groups.
|
||||
|
||||
For this feature you will need a cache system and celery workers. All thumbnails are stored on cache
|
||||
and are processed asynchronously by the workers.
|
||||
|
||||
@@ -183,10 +179,10 @@ 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 editor, 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 uses editor-based execution
|
||||
(`ExecutorType.EDITOR`) where executor-based cache warmup is used.
|
||||
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):
|
||||
|
||||
|
||||
@@ -149,10 +149,10 @@ database engine on a separate host or container.
|
||||
|
||||
Superset supports the following database engines/versions:
|
||||
|
||||
| Database Engine | Supported Versions |
|
||||
| ----------------------------------------- | ---------------------------------------------- |
|
||||
| [PostgreSQL](https://www.postgresql.org/) | 10.X, 11.X, 12.X, 13.X, 14.X, 15.X, 16.X, 17.X |
|
||||
| [MySQL](https://www.mysql.com/) | 5.7, 8.X |
|
||||
| Database Engine | Supported Versions |
|
||||
| ----------------------------------------- | ---------------------------------------- |
|
||||
| [PostgreSQL](https://www.postgresql.org/) | 10.X, 11.X, 12.X, 13.X, 14.X, 15.X, 16.X |
|
||||
| [MySQL](https://www.mysql.com/) | 5.7, 8.X |
|
||||
|
||||
Use the following database drivers and connection strings:
|
||||
|
||||
|
||||
@@ -25,33 +25,6 @@ DECKGL_BASE_MAP = [
|
||||
]
|
||||
```
|
||||
|
||||
[Yandex Tiles API](https://yandex.ru/maps-api/docs/tiles-api/request.html) can be used as a custom tile provider. The URL must include the `apikey` from the [Yandex Developer Dashboard](https://developer.tech.yandex.ru/services), tile coordinates (`x`, `y`, and `z`), and the required map layer parameters.
|
||||
|
||||
```python
|
||||
DECKGL_BASE_MAP = [
|
||||
[
|
||||
"tile://https://tiles.api-maps.yandex.ru/v1/tiles/"
|
||||
"?apikey=YOUR_YANDEX_API_KEY"
|
||||
"&lang=ru_RU"
|
||||
"&x={x}&y={y}&z={z}"
|
||||
"&l=map"
|
||||
"&projection=web_mercator"
|
||||
"&maptype=map",
|
||||
"Yandex Maps - Web Mercator",
|
||||
],
|
||||
[
|
||||
"tile://https://tiles.api-maps.yandex.ru/v1/tiles/"
|
||||
"?apikey=YOUR_YANDEX_API_KEY"
|
||||
"&lang=ru_RU"
|
||||
"&x={x}&y={y}&z={z}"
|
||||
"&l=map"
|
||||
"&projection=wgs84_mercator"
|
||||
"&maptype=map",
|
||||
"Yandex Maps - WGS84 Mercator",
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
Default values are:
|
||||
```python
|
||||
DECKGL_BASE_MAP = [
|
||||
@@ -83,7 +56,6 @@ CORS_OPTIONS: dict[Any, Any] = {
|
||||
"https://tile.openstreetmap.org",
|
||||
"https://tile.osm.ch",
|
||||
"https://your_personal_url/{z}/{x}/{y}.png",
|
||||
"https://tiles.api-maps.yandex.ru",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -100,7 +72,6 @@ TALISMAN_CONFIG = {
|
||||
"https://tile.openstreetmap.org",
|
||||
"https://tile.osm.ch",
|
||||
"https://your_personal_url/{z}/{x}/{y}.png",
|
||||
"https://tiles.api-maps.yandex.ru",
|
||||
],
|
||||
...
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ See [Connecting AI Clients](#connecting-ai-clients) for Claude Code, Claude Web,
|
||||
## Prerequisites
|
||||
|
||||
- Apache Superset 5.0+ running and accessible
|
||||
- Python 3.11+
|
||||
- Python 3.10+
|
||||
- The `fastmcp` package (`pip install fastmcp`)
|
||||
|
||||
---
|
||||
@@ -529,7 +529,6 @@ MCP_RESPONSE_SIZE_CONFIG = {
|
||||
"enabled": True,
|
||||
"token_limit": 25000,
|
||||
"warn_threshold_pct": 80,
|
||||
"max_list_items": 100,
|
||||
"excluded_tools": [
|
||||
"health_check",
|
||||
"get_chart_preview",
|
||||
@@ -544,7 +543,6 @@ MCP_RESPONSE_SIZE_CONFIG = {
|
||||
| `enabled` | `True` | Enable response size checking |
|
||||
| `token_limit` | `25000` | Maximum estimated token count per response |
|
||||
| `warn_threshold_pct` | `80` | Warn when response exceeds this percentage of the limit |
|
||||
| `max_list_items` | `100` | Cap on list-field length (e.g. `charts`, `native_filters`) applied to the `get_*_info` tools before falling back to more aggressive truncation. Raised from a hardcoded 30 in earlier versions; the higher default only keeps more data before the same token-budget fallback kicks in, so it's not a breaking change, but tenants that tuned workflows around the old 30-item cap should lower this value explicitly. |
|
||||
| `excluded_tools` | See above | Tools exempt from size checking (e.g., tools that return URLs, not data) |
|
||||
|
||||
### Caching
|
||||
|
||||
@@ -60,9 +60,9 @@ There are two approaches to making dashboards publicly accessible:
|
||||
|
||||
**Option 2: Dashboard-level access (selective control)**
|
||||
1. Set `PUBLIC_ROLE_LIKE = "Public"` in `superset_config.py`
|
||||
2. Add the `'ENABLE_VIEWERS': True` [Feature Flag](/admin-docs/configuration/feature-flags)
|
||||
3. Edit each dashboard's properties and add the "Public" role subject as a viewer
|
||||
4. Only published dashboards with the Public role subject explicitly assigned are visible to anonymous users
|
||||
2. Add the `'DASHBOARD_RBAC': True` [Feature Flag](/admin-docs/configuration/feature-flags)
|
||||
3. Edit each dashboard's properties and add the "Public" role
|
||||
4. Only dashboards with the Public role explicitly assigned are visible to anonymous users
|
||||
|
||||
See the [Public role documentation](/admin-docs/security/#public) for more details.
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ made available in the Jinja context:
|
||||
|
||||
- `columns`: columns which to group by in the query
|
||||
- `filter`: filters applied in the query
|
||||
- `from_dttm`: start `datetime` value from the selected time range (`None` if undefined). **Note:** Only available in virtual datasets when a time range filter is applied in Explore/Chart views—not available in standalone SQL Lab queries. (deprecated beginning in version 5.0, use `get_time_filter` instead)
|
||||
- `to_dttm`: end `datetime` value from the selected time range (`None` if undefined). **Note:** Only available in virtual datasets when a time range filter is applied in Explore/Chart views—not available in standalone SQL Lab queries. (deprecated beginning in version 5.0, use `get_time_filter` instead)
|
||||
- `groupby`: columns which to group by in the query (deprecated)
|
||||
- `metrics`: aggregate expressions in the query
|
||||
- `row_limit`: row limit of the query
|
||||
@@ -47,26 +49,85 @@ made available in the Jinja context:
|
||||
- `time_column`: temporal column of the query (`None` if undefined)
|
||||
- `time_grain`: selected time grain (`None` if undefined)
|
||||
|
||||
For example, to add a time range to a virtual dataset, use the `get_time_filter` macro:
|
||||
For example, to add a time range to a virtual dataset, you can write the following:
|
||||
|
||||
```sql
|
||||
{% set tf = get_time_filter("dttm_col") %}
|
||||
SELECT *
|
||||
FROM tbl
|
||||
WHERE dttm_col > {{ tf.from_expr }} AND dttm_col <= {{ tf.to_expr }}
|
||||
WHERE dttm_col > '{{ from_dttm }}' and dttm_col < '{{ to_dttm }}'
|
||||
```
|
||||
|
||||
You can also use [Jinja's logic](https://jinja.palletsprojects.com/en/2.11.x/templates/#tests)
|
||||
to make your query robust to clearing the timerange filter:
|
||||
|
||||
```sql
|
||||
SELECT *
|
||||
FROM tbl
|
||||
WHERE (
|
||||
{% if from_dttm is not none %}
|
||||
dttm_col > '{{ from_dttm }}' AND
|
||||
{% endif %}
|
||||
{% if to_dttm is not none %}
|
||||
dttm_col < '{{ to_dttm }}' AND
|
||||
{% endif %}
|
||||
1 = 1
|
||||
)
|
||||
```
|
||||
|
||||
The `1 = 1` at the end ensures a value is present for the `WHERE` clause even when
|
||||
the time filter is not set. For many database engines, this could be replaced with `true`.
|
||||
|
||||
Note that the Jinja parameters are called within _double_ brackets in the query and with
|
||||
_single_ brackets in the logic blocks.
|
||||
|
||||
### Understanding Context Availability
|
||||
|
||||
Some Jinja variables like `filter` are **only available when a chart or dashboard provides them**. They are populated from:
|
||||
Some Jinja variables like `from_dttm`, `to_dttm`, and `filter` are **only available when a chart or dashboard provides them**. They are populated from:
|
||||
|
||||
- Time range filters applied in Explore/Chart views
|
||||
- Dashboard native filters
|
||||
- Filter components
|
||||
|
||||
**These variables are NOT available in standalone SQL Lab queries** because there's no filter context. If you try to use `{{ from_dttm }}` directly in SQL Lab, you'll get an "undefined parameter" error.
|
||||
|
||||
#### Testing Time-Filtered Queries in SQL Lab
|
||||
|
||||
To test queries that use time variables in SQL Lab, you have several options:
|
||||
|
||||
**Option 1: Use Jinja defaults (recommended)**
|
||||
|
||||
```sql
|
||||
SELECT *
|
||||
FROM tbl
|
||||
WHERE dttm_col > '{{ from_dttm | default("2024-01-01", true) }}'
|
||||
AND dttm_col < '{{ to_dttm | default("2024-12-31", true) }}'
|
||||
```
|
||||
|
||||
**Option 2: Use SQL Lab Parameters**
|
||||
|
||||
Set parameters in the SQL Lab UI (Parameters menu):
|
||||
```json
|
||||
{
|
||||
"from_dttm": "2024-01-01",
|
||||
"to_dttm": "2024-12-31"
|
||||
}
|
||||
```
|
||||
|
||||
**Option 3: Use `{% set %}` for testing**
|
||||
|
||||
```sql
|
||||
{% set from_dttm = "2024-01-01" %}
|
||||
{% set to_dttm = "2024-12-31" %}
|
||||
SELECT *
|
||||
FROM tbl
|
||||
WHERE dttm_col > '{{ from_dttm }}' AND dttm_col < '{{ to_dttm }}'
|
||||
```
|
||||
|
||||
:::tip
|
||||
When you save a SQL Lab query as a virtual dataset and use it in a chart with time filters,
|
||||
the actual filter values will override any defaults or test values you set.
|
||||
:::
|
||||
|
||||
To add custom functionality to the Jinja context, you need to overload the default Jinja
|
||||
context in your environment by defining the `JINJA_CONTEXT_ADDONS` in your superset configuration
|
||||
(`superset_config.py`). Objects referenced in this dictionary are made available for users to use
|
||||
@@ -418,13 +479,10 @@ Here's a concrete example:
|
||||
|
||||
### Time Filter
|
||||
|
||||
The `{{ get_time_filter() }}` macro is the standard way to reference the selected time range
|
||||
in a virtual dataset. It returns a `TimeFilter` object whose `from_expr` and `to_expr`
|
||||
properties are fully-formatted SQL expressions for the start and end of the range — use them
|
||||
directly in your `WHERE` clause without quoting.
|
||||
|
||||
Placing the filter inside the virtual dataset (rather than relying on it being applied to
|
||||
the outer query) also gives many database engines the opportunity to optimize the query better.
|
||||
The `{{ get_time_filter() }}` macro returns the time filter applied to a specific column. This is useful if you want
|
||||
to handle time filters inside the virtual dataset, as by default the time filter is placed on the outer query. This can
|
||||
considerably improve performance, as many databases and query engines are able to optimize the query better
|
||||
if the temporal filter is placed on the inner query, as opposed to the outer query.
|
||||
|
||||
The macro takes the following parameters:
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ https://superset.apache.org/admin-docs/configuration/configuring-superset/#rotat
|
||||
|
||||
### **Appendix C: Secrets Register and Rotation Schedule**
|
||||
|
||||
`SUPERSET_SECRET_KEY` is not the only security-critical secret in a Superset deployment. Maintain an inventory of all such secrets, store each in a secrets manager (not in `superset_config.py` or version control), assign a responsible maintainer, and rotate them on a defined cadence as well as after any suspected compromise.
|
||||
`SUPERSET_SECRET_KEY` is not the only security-critical secret in a Superset deployment. Maintain an inventory of all such secrets, store each in a secrets manager (not in `superset_config.py` or version control), assign an owner, and rotate them on a defined cadence as well as after any suspected compromise.
|
||||
|
||||
| Secret | Purpose | Risk if leaked | Suggested rotation |
|
||||
|---|---|---|---|
|
||||
|
||||
@@ -91,24 +91,24 @@ PUBLIC_ROLE_LIKE = "Public"
|
||||
# PUBLIC_ROLE_LIKE = "Gamma"
|
||||
```
|
||||
|
||||
If you prefer to manually configure the Public role's permissions, or use subject-based
|
||||
dashboard viewer assignments, you do not need to set `PUBLIC_ROLE_LIKE`.
|
||||
If you prefer to manually configure the Public role's permissions (or use `DASHBOARD_RBAC` to
|
||||
grant access at the dashboard level), you do not need to set `PUBLIC_ROLE_LIKE`.
|
||||
|
||||
**Important notes:**
|
||||
|
||||
- **Data access is still required by default:** The Public role only grants UI/API permissions.
|
||||
You must also grant access to specific datasets necessary to view a dashboard. As with other
|
||||
roles, this can be done in two ways:
|
||||
- **Data access is still required:** The Public role only grants UI/API permissions. You must
|
||||
also grant access to specific datasets necessary to view a dashboard. As with other roles,
|
||||
this can be done in two ways:
|
||||
|
||||
- **Dataset-based access:** Dashboards only appear in the list and are accessible if the user
|
||||
has permission to at least one of their datasets. Grant dataset access by editing the Public
|
||||
role in the Superset UI (Menu -> Security -> List Roles -> Public) and adding the relevant
|
||||
data sources. All published dashboards using those datasets become visible.
|
||||
- **Without `DASHBOARD_RBAC`:** Dashboards only appear in the list and are accessible if
|
||||
the user has permission to at least one of their datasets. Grant dataset access by editing
|
||||
the Public role in the Superset UI (Menu → Security → List Roles → Public) and adding the
|
||||
relevant data sources. All published dashboards using those datasets become visible.
|
||||
|
||||
- **Subject-based dashboard access:** When `ENABLE_VIEWERS` is enabled, anonymous users can
|
||||
see published dashboards where the "Public" role subject has been explicitly added as a
|
||||
viewer. If `VIEWER_PROMISCUOUS_MODE` is enabled, viewer access can bypass dataset
|
||||
permission checks for dashboard rendering.
|
||||
- **With `DASHBOARD_RBAC` enabled:** Anonymous users will only see dashboards where the
|
||||
"Public" role has been explicitly added in the dashboard's properties. Dataset permissions
|
||||
are not required—DASHBOARD_RBAC handles the cascading permissions check. This provides
|
||||
fine-grained control over which dashboards are publicly visible.
|
||||
|
||||
- **Role synchronization:** Built-in role permissions (Admin, Alpha, Gamma, sql_lab, and Public
|
||||
when `PUBLIC_ROLE_LIKE = "Public"`) are synchronized when you run `superset init`. Any manual
|
||||
@@ -131,76 +131,10 @@ tables in the **Permissions** dropdown. To select the data sources you want to a
|
||||
You can then confirm with users assigned to the **Gamma** role that they see the
|
||||
objects (dashboards and slices) associated with the tables you just extended them.
|
||||
|
||||
### Subjects
|
||||
|
||||
A **subject** is a unified identity that can be granted access to Superset resources such as
|
||||
dashboards, charts, and datasets. Subjects come in three types:
|
||||
|
||||
- **User** — an individual Superset user
|
||||
- **Group** — a FAB group
|
||||
- **Role** — a FAB role (e.g. Alpha, Gamma, or any custom role)
|
||||
|
||||
Subjects are used throughout Superset to assign **editors** (who can modify a resource) and
|
||||
**viewers** (who can view it). For new resource-specific assignments, Superset defaults to
|
||||
**Users** and **Groups** in subject pickers. Groups are the recommended way to grant access to a
|
||||
set of people because they represent organizational membership, while roles remain focused on
|
||||
capability grants such as "can read dashboards" or "can write charts".
|
||||
|
||||
Role subjects are still supported for backwards compatibility with features that previously used
|
||||
roles directly, including Row Level Security role assignments and `DASHBOARD_RBAC`. Existing role
|
||||
subject assignments continue to be enforced even when roles are not exposed in the default picker
|
||||
configuration. However, when an administrator edits a subject list, roles are not available as new
|
||||
dropdown values unless that picker has been configured to expose role subjects. Roles are not
|
||||
recommended for new subject assignments, because using roles for both permissions and resource
|
||||
membership couples two separate concerns.
|
||||
|
||||
Administrators can control which subject types are available in pickers with
|
||||
`SUBJECTS_RELATED_TYPES`. By default, this setting exposes only users and groups:
|
||||
|
||||
```python
|
||||
from superset.subjects.types import SubjectType
|
||||
|
||||
SUBJECTS_RELATED_TYPES = [
|
||||
SubjectType.USER,
|
||||
SubjectType.GROUP,
|
||||
]
|
||||
```
|
||||
|
||||
Set `SUBJECTS_RELATED_TYPES = None` to expose all subject types, including roles. To expose roles
|
||||
only for a specific entity, set that entity's override:
|
||||
|
||||
```python
|
||||
from superset.subjects.types import SubjectType
|
||||
|
||||
SUBJECTS_RELATED_TYPES = [
|
||||
SubjectType.USER,
|
||||
SubjectType.GROUP,
|
||||
]
|
||||
|
||||
# Leave dashboard, chart, and alert/report overrides unset so they inherit
|
||||
# the users + groups default. Expose roles only in the RLS subject picker for
|
||||
# compatibility with existing role-based RLS workflows.
|
||||
SUBJECTS_RELATED_TYPES_RLS = [
|
||||
SubjectType.USER,
|
||||
SubjectType.GROUP,
|
||||
SubjectType.ROLE,
|
||||
]
|
||||
```
|
||||
|
||||
Available per-entity overrides are:
|
||||
|
||||
- `SUBJECTS_RELATED_TYPES_DASHBOARDS` for dashboard editor/viewer pickers
|
||||
- `SUBJECTS_RELATED_TYPES_CHARTS` for chart editor/viewer pickers
|
||||
- `SUBJECTS_RELATED_TYPES_RLS` for Row Level Security subject pickers
|
||||
- `SUBJECTS_RELATED_TYPES_ALERT_REPORTS` for alert/report editor pickers
|
||||
|
||||
When an override is set, it replaces `SUBJECTS_RELATED_TYPES` for that picker. When it is `None`,
|
||||
the picker inherits the global default.
|
||||
|
||||
### Dashboard Access Control
|
||||
|
||||
Access to dashboards is managed via editors (subjects that have edit permissions to the dashboard).
|
||||
Non-editor user access can be managed in two ways. Note that dashboards must be published to be
|
||||
Access to dashboards is managed via owners (users that have edit permissions to the dashboard).
|
||||
Non-owner user access can be managed in two ways. Note that dashboards must be published to be
|
||||
visible to other users.
|
||||
|
||||
#### Dataset-Based Access (Default)
|
||||
@@ -212,40 +146,30 @@ to a role (Menu → Security → List Roles).
|
||||
This is the simplest approach but provides all-or-nothing access based on dataset permissions—
|
||||
if a user has access to a dataset, they can see all published dashboards using that dataset.
|
||||
|
||||
#### Dashboard and Chart-Level Access
|
||||
#### Dashboard-Level Access (DASHBOARD_RBAC)
|
||||
|
||||
For fine-grained control over which dashboards and charts specific users or groups can access,
|
||||
enable subject-based viewers:
|
||||
For fine-grained control over which dashboards specific roles can access, enable the
|
||||
`DASHBOARD_RBAC` feature flag:
|
||||
|
||||
```python
|
||||
FEATURE_FLAGS = {
|
||||
"ENABLE_VIEWERS": True,
|
||||
"DASHBOARD_RBAC": True,
|
||||
}
|
||||
```
|
||||
|
||||
With this enabled, you can assign viewer subjects to each dashboard or chart in its properties.
|
||||
Users will see resources where they are assigned directly, or indirectly through a group subject.
|
||||
Role subjects can also be enabled for compatibility, but groups are recommended for new viewer
|
||||
assignments.
|
||||
|
||||
`ENABLE_VIEWERS` can be adopted incrementally. Dashboards and charts with no assigned viewers keep
|
||||
the existing implicit dataset-access behavior: users who can access the underlying dataset can still
|
||||
see published dashboards that use that dataset and charts backed by that dataset. Assigning one or
|
||||
more viewers changes that specific resource to explicit viewer access for non-editors. Removing all
|
||||
viewers returns the resource to the dataset-based fallback. Explicit Viewers are the preferred model
|
||||
going forward; the implicit fallback may be deprecated and removed in a later major version.
|
||||
With this enabled, you can assign specific roles to each dashboard in its properties. Users
|
||||
will only see dashboards where their role is explicitly added.
|
||||
|
||||
**Important considerations:**
|
||||
- Viewer access uses normal dataset checks unless `VIEWER_PROMISCUOUS_MODE` is enabled
|
||||
- With `VIEWER_PROMISCUOUS_MODE`, dashboard viewer access can bypass dataset-level checks for
|
||||
charts and datasets in that dashboard
|
||||
- Dashboards and charts without assigned viewers fall back to dataset-based access
|
||||
- Dashboard access **bypasses** dataset-level checks—granting a role access to a dashboard
|
||||
implicitly grants read access to all charts and datasets in that dashboard
|
||||
- Dashboards without any assigned roles fall back to dataset-based access
|
||||
- The dashboard must still be published to be visible
|
||||
|
||||
This feature is particularly useful for:
|
||||
- Making specific dashboards public while keeping others private
|
||||
- Granting access to dashboards without exposing the underlying datasets for other uses
|
||||
- Creating dashboard-specific access patterns that don't align with dataset permissions
|
||||
- Creating dashboard-specific access patterns that don't align with dataset ownership
|
||||
|
||||
### SQL Execution Security Considerations
|
||||
|
||||
@@ -377,16 +301,16 @@ based on the roles and permissions that were attributed.
|
||||
### Row Level Security
|
||||
|
||||
Using Row Level Security filters (under the **Security** menu) you can create filters
|
||||
that are assigned to a particular dataset, as well as a set of subjects.
|
||||
that are assigned to a particular dataset, as well as a set of roles.
|
||||
If you want members of the Finance team to only have access to
|
||||
rows where `department = "finance"`, you could:
|
||||
|
||||
- Create a Row Level Security filter with that clause (`department = "finance"`)
|
||||
- Then assign the clause to the **Finance** role subject and the dataset it applies to
|
||||
- Then assign the clause to the **Finance** role and the dataset it applies to
|
||||
|
||||
The **clause** field, which can contain arbitrary text, is then added to the generated
|
||||
SQL statement's WHERE clause. So you could even do something like create a filter
|
||||
for the last 30 days and apply it to a specific subject, with a clause
|
||||
for the last 30 days and apply it to a specific role, with a clause
|
||||
like `date_field > DATE_SUB(NOW(), INTERVAL 30 DAY)`. It can also support
|
||||
multiple conditions: `client_id = 6` AND `advertiser="foo"`, etc.
|
||||
|
||||
@@ -397,14 +321,12 @@ RLS clauses also support **Jinja templating** when `ENABLE_TEMPLATE_PROCESSING`
|
||||
|
||||
There are two types of RLS filters:
|
||||
|
||||
- **Regular** — The filter clause is applied when the querying user matches one of the
|
||||
subjects assigned to the filter. Use this to restrict what specific users, roles, or groups
|
||||
can see.
|
||||
- **Base** — The filter clause is applied to **all** users _except_ those matching the assigned
|
||||
subjects. Use this to define a default restriction that privileged subjects (e.g. the Admin
|
||||
role subject) are exempt from. For example, a Base filter with clause `1 = 0` and the Admin
|
||||
role subject would hide all rows from everyone except Admin - useful as a deny-by-default
|
||||
baseline.
|
||||
- **Regular** — The filter clause is applied when the querying user belongs to one of the
|
||||
roles assigned to the filter. Use this to restrict what specific roles can see.
|
||||
- **Base** — The filter clause is applied to **all** users _except_ those in the assigned
|
||||
roles. Use this to define a default restriction that privileged roles (e.g. Admin) are
|
||||
exempt from. For example, a Base filter with clause `1 = 0` and the Admin role would
|
||||
hide all rows from everyone except Admin — useful as a deny-by-default baseline.
|
||||
|
||||
#### Group Keys and Filter Combination
|
||||
|
||||
@@ -494,20 +416,21 @@ GET /api/v1/rowlevelsecurity/
|
||||
GET /api/v1/rowlevelsecurity/?q=(filters:!((col:tables,opr:rel_m_m,value:<dataset_id>)))
|
||||
```
|
||||
|
||||
**Filter RLS rules by subject:**
|
||||
**Filter RLS rules by role:**
|
||||
|
||||
```
|
||||
GET /api/v1/rowlevelsecurity/?q=(filters:!((col:subjects,opr:rel_m_m,value:<subject_id>)))
|
||||
GET /api/v1/rowlevelsecurity/?q=(filters:!((col:roles,opr:rel_m_m,value:<role_id>)))
|
||||
```
|
||||
|
||||
**View details of a specific rule** (including clause, assigned datasets, and subjects):
|
||||
**View details of a specific rule** (including clause, assigned datasets, and roles):
|
||||
|
||||
```
|
||||
GET /api/v1/rowlevelsecurity/<id>
|
||||
```
|
||||
|
||||
The response includes the filter's `name`, `filter_type` (Regular or Base), `clause`,
|
||||
`group_key`, assigned `tables` (with id, schema, and table\_name), and assigned `subjects`.
|
||||
`group_key`, assigned `tables` (with id, schema, and table\_name), and assigned `roles`
|
||||
(with id and name).
|
||||
|
||||
:::tip Auditing RLS for virtual datasets
|
||||
To find all RLS rules that could affect a particular virtual dataset, query the list
|
||||
|
||||
@@ -26,7 +26,7 @@ import { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# MetadataBar
|
||||
|
||||
MetadataBar displays a row of metadata items (SQL info, editors, last modified, tags, dashboards, etc.) that collapse responsively based on available width.
|
||||
MetadataBar displays a row of metadata items (SQL info, owners, last modified, tags, dashboards, etc.) that collapse responsively based on available width.
|
||||
|
||||
## Live Example
|
||||
|
||||
@@ -43,9 +43,9 @@ MetadataBar displays a row of metadata items (SQL info, editors, last modified,
|
||||
title: "Click to view query"
|
||||
},
|
||||
{
|
||||
type: "editor",
|
||||
type: "owner",
|
||||
createdBy: "Jane Smith",
|
||||
editors: [
|
||||
owners: [
|
||||
"John Doe",
|
||||
"Mary Wilson"
|
||||
],
|
||||
@@ -104,9 +104,9 @@ function Demo() {
|
||||
const items = [
|
||||
{ type: 'sql', title: 'Click to view query' },
|
||||
{
|
||||
type: 'editor',
|
||||
type: 'owner',
|
||||
createdBy: 'Jane Smith',
|
||||
editors: ['John Doe', 'Mary Wilson'],
|
||||
owners: ['John Doe', 'Mary Wilson'],
|
||||
createdOn: 'a week ago',
|
||||
},
|
||||
{
|
||||
@@ -125,7 +125,7 @@ function Demo() {
|
||||
```tsx live
|
||||
function MinimalMetadata() {
|
||||
const items = [
|
||||
{ type: 'editor', createdBy: 'Admin', editors: ['Admin'], createdOn: 'yesterday' },
|
||||
{ type: 'owner', createdBy: 'Admin', owners: ['Admin'], createdOn: 'yesterday' },
|
||||
{ type: 'lastModified', value: '2 hours ago', modifiedBy: 'Admin' },
|
||||
];
|
||||
return <MetadataBar items={items} />;
|
||||
@@ -138,7 +138,7 @@ function MinimalMetadata() {
|
||||
function FullMetadata() {
|
||||
const items = [
|
||||
{ type: 'sql', title: 'SELECT * FROM ...' },
|
||||
{ type: 'editor', createdBy: 'Jane Smith', editors: ['Jane Smith', 'John Doe', 'Bob Wilson'], createdOn: '2 weeks ago' },
|
||||
{ type: 'owner', createdBy: 'Jane Smith', owners: ['Jane Smith', 'John Doe', 'Bob Wilson'], createdOn: '2 weeks ago' },
|
||||
{ type: 'lastModified', value: '3 days ago', modifiedBy: 'John Doe' },
|
||||
{ type: 'tags', values: ['production', 'finance', 'quarterly'] },
|
||||
{ type: 'dashboards', title: 'Used in 12 dashboards' },
|
||||
@@ -158,7 +158,7 @@ function FullMetadata() {
|
||||
| `createdBy` | `string` | `"Jane Smith"` | - |
|
||||
| `modifiedBy` | `string` | `"Jane Smith"` | - |
|
||||
| `description` | `string` | `"To preview the list of dashboards go to More settings."` | - |
|
||||
| `items` | `any` | `[{"type":"sql","title":"Click to view query"},{"type":"editor","createdBy":"Jane Smith","editors":["John Doe","Mary Wilson"],"createdOn":"a week ago"},{"type":"lastModified","value":"a week ago","modifiedBy":"Jane Smith"},{"type":"tags","values":["management","research","poc"]},{"type":"dashboards","title":"Added to 3 dashboards","description":"To preview the list of dashboards go to More settings."}]` | Items displayed in the metadata bar. The `editor` type shows editors/creators. |
|
||||
| `items` | `any` | `[{"type":"sql","title":"Click to view query"},{"type":"owner","createdBy":"Jane Smith","owners":["John Doe","Mary Wilson"],"createdOn":"a week ago"},{"type":"lastModified","value":"a week ago","modifiedBy":"Jane Smith"},{"type":"tags","values":["management","research","poc"]},{"type":"dashboards","title":"Added to 3 dashboards","description":"To preview the list of dashboards go to More settings."}]` | - |
|
||||
|
||||
## Import
|
||||
|
||||
|
||||
@@ -379,7 +379,7 @@ functioning across environments.
|
||||
Make sure your machine meets the [OS dependencies](https://superset.apache.org/docs/installation/pypi#os-dependencies) before following these steps.
|
||||
You also need to install MySQL.
|
||||
|
||||
Ensure that you are using Python version 3.11 or 3.12, then proceed with:
|
||||
Ensure that you are using Python version 3.9, 3.10 or 3.11, then proceed with:
|
||||
|
||||
```bash
|
||||
# Create a virtual environment and activate it (recommended)
|
||||
|
||||
@@ -82,7 +82,7 @@ If applicable, add screenshots or recordings.
|
||||
|
||||
### Environment
|
||||
- Superset version: [e.g., 3.0.0]
|
||||
- Python version: [e.g., 3.11.7]
|
||||
- Python version: [e.g., 3.9.7]
|
||||
- Node version: [e.g., 18.17.0]
|
||||
- Database: [e.g., PostgreSQL 14]
|
||||
- Browser: [e.g., Chrome 120]
|
||||
@@ -125,7 +125,7 @@ No error messages in browser console or server logs.
|
||||
|
||||
### Environment
|
||||
- Superset version: 3.0.0
|
||||
- Python version: 3.11.16
|
||||
- Python version: 3.9.16
|
||||
- Database: PostgreSQL 14.9
|
||||
- Browser: Chrome 120.0.6099.71
|
||||
- OS: macOS 14.2
|
||||
|
||||
@@ -163,6 +163,10 @@ 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',
|
||||
@@ -174,7 +178,6 @@ plugins: [
|
||||
react: { singleton: true, import: false },
|
||||
'react-dom': { singleton: true, import: false },
|
||||
antd: { singleton: true, import: false },
|
||||
'@apache-superset/core': { singleton: true, import: false },
|
||||
},
|
||||
}),
|
||||
]
|
||||
@@ -184,7 +187,9 @@ This configuration does several important things:
|
||||
|
||||
**`exposes`** - Declares which modules are available to the host application. Superset always loads extensions by requesting the `./index` module from the remote container — this is a fixed convention, not a configurable value. Extensions must expose exactly `'./index': './src/index.tsx'` and place all API registrations (views, commands, menus, editors, event listeners) in that file. The module is executed as a side effect when the extension loads, so any call to `views.registerView`, `commands.registerCommand`, etc. made at the top level of `index.tsx` will run automatically.
|
||||
|
||||
**`shared`** - Prevents duplication of common libraries like React and Ant Design, and, for `@apache-superset/core`, is the mechanism that gives each extension an isolated context (see below). The `singleton: true` setting ensures only one *logical* instance of each library exists — for `react`/`react-dom`/`antd` that means the host's actual instance is reused; for `@apache-superset/core` it means the extension's container defers to whatever module the host's loader supplies for it at init time, which is not the same object for every extension (see [Runtime Resolution](#runtime-resolution)).
|
||||
**`externals` and `externalsType`** - Tell Webpack that when the extension imports `@apache-superset/core`, it should use `window.superset` at runtime instead of bundling its own copy. This ensures extensions use the host's implementation of shared packages.
|
||||
|
||||
**`shared`** - Prevents duplication of common libraries like React and Ant Design. The `singleton: true` setting ensures only one instance of each library exists, avoiding version conflicts and reducing bundle size.
|
||||
|
||||
### Runtime Resolution
|
||||
|
||||
@@ -195,30 +200,25 @@ The following diagram illustrates the module loading process:
|
||||
Here's what happens at runtime:
|
||||
|
||||
1. **Extension Registration**: When an extension is registered, Superset stores its remote entry URL
|
||||
2. **Dynamic Loading**: When the extension is activated, the host fetches the remote entry file, then initializes Module Federation sharing (`__webpack_init_sharing__`) and looks up the extension's container on `window`
|
||||
3. **Per-Extension Scope Injection**: Before calling `container.init()`, the loader builds a **per-container copy** of the webpack share scope in which the `@apache-superset/core` entry is replaced with a synthetic module — a copy of the host's own `window.superset` implementations with `extensions.getContext` bound to that one extension's isolated context object. Because Module Federation caches the resolved module per container, every import of `@apache-superset/core` inside that extension resolves to this pre-bound copy for the lifetime of the container, and because each container gets its own independent scope object, extensions loading in parallel cannot see each other's context.
|
||||
4. **Execution**: The extension code runs with access to the host's APIs (via its per-extension `@apache-superset/core` instance) and shared dependencies (`react`, `react-dom`, `antd`)
|
||||
2. **Dynamic Loading**: When the extension is activated, the host fetches the remote entry file
|
||||
3. **Module Resolution**: The extension imports `@apache-superset/core`, which resolves to `window.superset`
|
||||
4. **Execution**: The extension code runs with access to the host's APIs and shared dependencies
|
||||
|
||||
### Host API Setup
|
||||
|
||||
On the Superset side, the real implementations backing `@apache-superset/core` (`commands`, `views`, `menus`, `extensions`, etc.) are assigned onto `window.superset` during application bootstrap, once the current user is known:
|
||||
On the Superset side, the APIs are mapped to `window.superset` during application bootstrap:
|
||||
|
||||
```typescript
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
import * as supersetCore from '@apache-superset/core';
|
||||
import { commands, views, menus, extensions /* ... */ } from 'src/core';
|
||||
|
||||
window.superset = {
|
||||
...supersetCore,
|
||||
commands,
|
||||
views,
|
||||
menus,
|
||||
extensions,
|
||||
// ...
|
||||
};
|
||||
export default function setupExtensionsAPI() {
|
||||
window.superset = {
|
||||
...supersetCore,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
This runs before any extensions are loaded. `window.superset` is not itself what an extension's `@apache-superset/core` import resolves to — it is the source object the loader reads from when building each extension's per-container scoped instance (see [Runtime Resolution](#runtime-resolution)), so every extension ends up sharing the same underlying `commands`/`views`/`menus` singletons but with its own `extensions.getContext()`.
|
||||
This function runs before any extensions are loaded, ensuring the APIs are available when extensions import from `@apache-superset/core`.
|
||||
|
||||
### Benefits
|
||||
|
||||
|
||||
@@ -129,6 +129,27 @@ chat.registerChat(
|
||||
|
||||
See [Chat](./extension-points/chat.md) for implementation details.
|
||||
|
||||
### Dashboard Components
|
||||
|
||||
Extensions can add first-class layout components to the dashboard builder — elements that live in the grid alongside charts, Markdown, and tabs. The host owns the drag/resize/delete chrome, so the extension only provides the component that renders the element's content. The built-in iframe component is implemented through this contribution point.
|
||||
|
||||
```tsx
|
||||
import { dashboardComponents } from '@apache-superset/core';
|
||||
import WeatherWidget from './WeatherWidget';
|
||||
|
||||
dashboardComponents.registerDashboardComponent(
|
||||
{
|
||||
id: 'my-org.weather',
|
||||
name: 'Weather widget',
|
||||
icon: 'CloudOutlined',
|
||||
defaultMeta: { width: 4, height: 50 },
|
||||
},
|
||||
WeatherWidget,
|
||||
);
|
||||
```
|
||||
|
||||
See [Dashboard Components](./extension-points/dashboard-components.md) for implementation details.
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
---
|
||||
title: Dashboard Components
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Dashboard Component Contributions
|
||||
|
||||
Extensions can add first-class **layout components** to the dashboard builder —
|
||||
elements that sit in the grid alongside charts, Markdown, and tabs. The built-in
|
||||
iframe component is itself implemented through this contribution point.
|
||||
|
||||
The host owns the surrounding **chrome** (the drag handle, the resize container,
|
||||
and the delete affordance), so your component only renders its content and, in
|
||||
edit mode, its own editor affordances. This keeps the contract small and stable.
|
||||
|
||||
> This supersedes the legacy `DashboardComponentsRegistry` / `DYNAMIC_TYPE`
|
||||
> mechanism, which is deprecated.
|
||||
|
||||
## Overview
|
||||
|
||||
A dashboard component contribution is:
|
||||
|
||||
| Part | Role |
|
||||
|------|------|
|
||||
| **Definition** | A descriptor declaring the component's id, palette label, icon, and layout behavior (resizable, default size, nesting). |
|
||||
| **Component** | A React component that renders the element's content and receives the [`DashboardComponentProps`](#component-contract) contract. |
|
||||
|
||||
## The Component Contract
|
||||
|
||||
Your component receives a small, stable set of props. It never deals with drag,
|
||||
resize, or delete — the host renders it inside that chrome.
|
||||
|
||||
```ts
|
||||
interface DashboardComponentProps {
|
||||
/** The layout item id of this instance. */
|
||||
id: string;
|
||||
/** This instance's persisted meta (round-trips in the saved layout). */
|
||||
meta: Record<string, unknown>;
|
||||
/** Whether the dashboard is in edit mode. */
|
||||
editMode: boolean;
|
||||
/** Shallow-merge a patch into this instance's persisted meta. */
|
||||
updateMeta: (patch: Record<string, unknown>) => void;
|
||||
}
|
||||
```
|
||||
|
||||
Persist any per-instance state in `meta` via `updateMeta`. It is saved with the
|
||||
dashboard and rehydrated on load.
|
||||
|
||||
## Registering a Dashboard Component
|
||||
|
||||
Call `dashboardComponents.registerDashboardComponent` from your extension's entry
|
||||
point with a definition and your component:
|
||||
|
||||
```tsx
|
||||
import { dashboardComponents } from '@apache-superset/core';
|
||||
import WeatherWidget from './WeatherWidget';
|
||||
|
||||
dashboardComponents.registerDashboardComponent(
|
||||
{
|
||||
id: 'my-org.weather',
|
||||
name: 'Weather widget',
|
||||
description: 'Shows the current weather for a city',
|
||||
icon: 'CloudOutlined',
|
||||
resizable: true,
|
||||
defaultMeta: { width: 4, height: 50, city: 'Lisbon' },
|
||||
},
|
||||
WeatherWidget,
|
||||
);
|
||||
```
|
||||
|
||||
```tsx
|
||||
// WeatherWidget.tsx
|
||||
import type { dashboardComponents } from '@apache-superset/core';
|
||||
|
||||
type Props = dashboardComponents.DashboardComponentProps;
|
||||
|
||||
export default function WeatherWidget({ meta, editMode, updateMeta }: Props) {
|
||||
const city = (meta.city as string) ?? '';
|
||||
return editMode ? (
|
||||
<input
|
||||
value={city}
|
||||
onChange={e => updateMeta({ city: e.target.value })}
|
||||
placeholder="City"
|
||||
/>
|
||||
) : (
|
||||
<Forecast city={city} />
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The component appears in the dashboard builder's **Layout elements** palette and
|
||||
can be dragged onto the grid like any built-in element.
|
||||
|
||||
## Definition Reference
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `id` | `string` | Namespaced unique id, e.g. `my-org.weather`. Selects the component for each instance. |
|
||||
| `name` | `string` | Label shown in the builder palette. |
|
||||
| `description` | `string` | Optional longer description. |
|
||||
| `icon` | `string` | A known Superset icon name (e.g. `CloudOutlined`). Falls back to a generic icon. |
|
||||
| `resizable` | `boolean` | Whether instances can be resized. Defaults to `true`. |
|
||||
| `defaultMeta` | `object` | `meta` seeded onto a new instance (e.g. `width`, `height`, and your own keys). |
|
||||
| `isUserContent` | `boolean` | Whether an instance counts as content for "is this dashboard empty?" detection. Defaults to `true`. |
|
||||
| `minWidth` | `number` | Minimum width in grid columns. Defaults to `1`. |
|
||||
| `validParents` | `string[]` | Restrict which container types may hold the component (e.g. `['GRID', 'TAB']`). Defaults to standard content-leaf placement (grid, row, column, tab). |
|
||||
| `wrapInRow` | `boolean` | Whether a drop into the grid or a tab auto-wraps the component in a row. Defaults to `true`. |
|
||||
|
||||
The layout-relevant behavior fields are seeded onto each instance's `meta` at
|
||||
creation, so the dashboard honors them — and they round-trip in the saved layout
|
||||
even if the extension later becomes unavailable.
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If a saved dashboard references a component whose extension is disabled or not
|
||||
yet loaded, the host renders a non-destructive placeholder in its place and
|
||||
preserves the instance's `meta` on save. Re-enabling the extension restores the
|
||||
component.
|
||||
|
||||
## Dashboard Components API Reference
|
||||
|
||||
All methods are available on the `dashboardComponents` namespace from
|
||||
`@apache-superset/core`:
|
||||
|
||||
| Method / Event | Description |
|
||||
|----------------|-------------|
|
||||
| `registerDashboardComponent(definition, component)` | Register a component. Returns a `Disposable` to unregister. Registering the same id again replaces the previous registration. |
|
||||
| `getDashboardComponent(id)` | Returns the registered component for `id`, or `undefined`. |
|
||||
| `getDashboardComponents()` | Returns all registered components. |
|
||||
| `onDidRegisterDashboardComponent(listener)` | Subscribe to registration events. Returns a `Disposable`. |
|
||||
| `onDidUnregisterDashboardComponent(listener)` | Subscribe to unregistration events. Returns a `Disposable`. |
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Contribution Types](../contribution-types.md)** — Explore other contribution types
|
||||
- **[Development](../development.md)** — Set up your development environment
|
||||
@@ -47,14 +47,13 @@ Extension developers have access to pre-built UI components via `@apache-superse
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Quick Start](./quick-start)** - Build your first extension with a complete walkthrough
|
||||
- **[Architecture](./architecture)** - Design principles and system overview
|
||||
- **[Dependencies](./dependencies)** - Managing dependencies and understanding API stability
|
||||
- **[Contribution Types](./contribution-types)** - Available extension points
|
||||
- **[Development](./development)** - Project structure, APIs, and development workflow
|
||||
- **[Deployment](./deployment)** - Packaging and deploying extensions
|
||||
- **[MCP Integration](./mcp)** - Adding AI agent capabilities using extensions
|
||||
- **[Security](./security)** - Security considerations and best practices
|
||||
- **[Storage](./storage)** - Managed storage API for persisting extension data
|
||||
- **[Tasks](./tasks)** - Framework for creating and managing long running tasks
|
||||
- **[Community Extensions](./registry)** - Browse extensions shared by the community
|
||||
- **[Quick Start](./quick-start.md)** - Build your first extension with a complete walkthrough
|
||||
- **[Architecture](./architecture.md)** - Design principles and system overview
|
||||
- **[Dependencies](./dependencies.md)** - Managing dependencies and understanding API stability
|
||||
- **[Contribution Types](./contribution-types.md)** - Available extension points
|
||||
- **[Development](./development.md)** - Project structure, APIs, and development workflow
|
||||
- **[Deployment](./deployment.md)** - Packaging and deploying extensions
|
||||
- **[MCP Integration](./mcp.md)** - Adding AI agent capabilities using extensions
|
||||
- **[Security](./security.md)** - Security considerations and best practices
|
||||
- **[Tasks](./tasks.md)** - Framework for creating and managing long running tasks
|
||||
- **[Community Extensions](./registry.md)** - Browse extensions shared by the community
|
||||
|
||||
@@ -223,7 +223,7 @@ The `@apache-superset/core` package must be listed in both `peerDependencies` (t
|
||||
|
||||
**`frontend/webpack.config.js`**
|
||||
|
||||
The webpack configuration requires specific settings for Module Federation. `@apache-superset/core` is declared as a `shared` singleton alongside `react`/`react-dom`/`antd` — Superset injects a per-extension instance of the package into the container's share scope at load time, so the extension's own webpack config only needs to mark it `shared` with `singleton: true`, not resolve it itself. `import: false` on each shared entry means the extension doesn't bundle its own copy and instead uses the instance the host (or, for `@apache-superset/core`, the loader) provides. `remoteEntry.[contenthash].js` gives the built remote entry file a content hash for cache busting.
|
||||
The webpack configuration requires specific settings for Module Federation. Key settings include `externalsType: "window"` and `externals` to map `@apache-superset/core` to `window.superset` at runtime, `import: false` for shared modules to use the host's React instead of bundling a separate copy, and `remoteEntry.[contenthash].js` for cache busting.
|
||||
|
||||
**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.md#module-federation) for a full explanation.
|
||||
|
||||
@@ -255,6 +255,11 @@ module.exports = (env, argv) => {
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
||||
},
|
||||
// Map @apache-superset/core imports to window.superset at runtime
|
||||
externalsType: 'window',
|
||||
externals: {
|
||||
'@apache-superset/core': 'superset',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
@@ -287,10 +292,6 @@ module.exports = (env, argv) => {
|
||||
requiredVersion: packageConfig.peerDependencies['antd'],
|
||||
import: false,
|
||||
},
|
||||
'@apache-superset/core': {
|
||||
singleton: true,
|
||||
import: false, // Resolved to a per-extension instance by the loader
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
@@ -497,7 +498,7 @@ 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`](https://github.com/apache/superset/blob/master/superset-core/src/superset_core/rest_api/decorators.py) 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; the extension's `@apache-superset/core` import resolves to a per-extension instance injected into its container's share scope by the loader
|
||||
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
|
||||
6. **Rendering**: When the user opens your panel, React renders `<HelloWorldPanel />`
|
||||
7. **API call**: The component fetches data from `/extensions/my-org/hello-world/message`
|
||||
|
||||
@@ -1,465 +0,0 @@
|
||||
---
|
||||
title: Storage
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Storage
|
||||
|
||||
Superset Extensions have access to a managed storage API for persisting data. The storage system provides multiple tiers with different persistence characteristics, allowing extensions to choose the right storage for their needs.
|
||||
|
||||
Each extension receives its own isolated storage namespace. When Superset loads your extension, it binds storage to your extension's unique identifier, ensuring data privacy—two extensions using the same key will never collide, and extensions cannot access each other's data.
|
||||
|
||||
Storage is always accessed through the extension context via `getContext()`. This binding to the context is what ties every operation to the current extension's namespace.
|
||||
|
||||
## Storage Tiers
|
||||
|
||||
| Tier | Storage Type | Context Property | Use Case |
|
||||
| ---- | ----------------- | ------------------------------------------ | -------------------------------------- |
|
||||
| 1 | Browser storage | `ctx.storage.local`, `ctx.storage.session` | UI state, wizard progress, draft forms |
|
||||
| 2 | Server-side cache | `ctx.storage.ephemeral` | Job progress, temporary results |
|
||||
| 3 | Database | `ctx.storage.persistent` | Saved artifacts, durable config |
|
||||
|
||||
## Tier 1: Local State
|
||||
|
||||
Browser-based storage that persists on the user's device. Use this for UI state and settings that don't need to sync across devices.
|
||||
|
||||
### Why Use the API Instead of localStorage Directly?
|
||||
|
||||
You might wonder why extensions should use `ctx.storage.local` instead of directly accessing `window.localStorage`. The managed API provides several benefits:
|
||||
|
||||
- **Automatic namespacing**: Each extension's data is isolated. Two extensions using the same key name won't collide.
|
||||
- **User isolation**: By default, data is scoped to the current user, preventing data leakage between users on shared devices.
|
||||
- **Clean uninstall**: When an extension is uninstalled, all its data can be cleanly removed using prefix-based deletion.
|
||||
- **Future sandboxing**: The async API is designed for a future sandboxed execution model where extensions run in isolated contexts without direct DOM access.
|
||||
- **Consistent patterns**: The same API shape works across all storage tiers, making it easy to switch between them.
|
||||
|
||||
### localState
|
||||
|
||||
Data persists across browser sessions until explicitly deleted or the user clears browser storage.
|
||||
|
||||
```typescript
|
||||
import { extensions } from '@apache-superset/core';
|
||||
|
||||
const ctx = extensions.getContext();
|
||||
|
||||
// Save sidebar state
|
||||
await ctx.storage.local.set('sidebar_collapsed', true);
|
||||
|
||||
// Retrieve it later
|
||||
const isCollapsed = await ctx.storage.local.get('sidebar_collapsed');
|
||||
|
||||
// Remove it
|
||||
await ctx.storage.local.remove('sidebar_collapsed');
|
||||
```
|
||||
|
||||
### sessionState
|
||||
|
||||
Data is cleared when the browser tab is closed. Use for transient state within a single session.
|
||||
|
||||
```typescript
|
||||
import { extensions } from '@apache-superset/core';
|
||||
|
||||
const ctx = extensions.getContext();
|
||||
|
||||
// Save wizard progress (lost when tab closes)
|
||||
await ctx.storage.session.set('wizard_step', 3);
|
||||
await ctx.storage.session.set('unsaved_form', { name: 'Draft' });
|
||||
|
||||
// Retrieve on page reload within same tab
|
||||
const step = await ctx.storage.session.get('wizard_step');
|
||||
```
|
||||
|
||||
Tier 1 has no `shared` accessor. Browser storage is per-device, so a "shared" value would only be visible to other users of that same browser, not to other users of the extension generally — see `.shared` on [Tier 2](#tier-2-ephemeral-state) or [Tier 3](#tier-3-persistent-state) for storage that's actually shared across users.
|
||||
|
||||
### When to Use Tier 1
|
||||
|
||||
- UI state (sidebar collapsed, panel sizes)
|
||||
- Recently used items
|
||||
- Draft form values
|
||||
- Any data acceptable to lose if user clears browser
|
||||
|
||||
### Limitations
|
||||
|
||||
- Per-browser, per-device (not shared across devices)
|
||||
- Subject to browser storage quotas (~5-10 MB)
|
||||
- Not accessible from backend code
|
||||
|
||||
## Tier 2: Ephemeral State
|
||||
|
||||
Server-side cache storage with automatic TTL expiration. Use for temporary data that needs to be shared between frontend and backend, or persist across page reloads.
|
||||
|
||||
TTL is required for every `set` call. The maximum allowed TTL is controlled by `MAX_TTL` in the server configuration.
|
||||
|
||||
### Frontend Usage
|
||||
|
||||
```typescript
|
||||
import { extensions } from '@apache-superset/core';
|
||||
|
||||
const ctx = extensions.getContext();
|
||||
|
||||
// Store with a 5-minute TTL
|
||||
await ctx.storage.ephemeral.set('job_progress', { pct: 42, status: 'running' }, { ttl: 300 });
|
||||
|
||||
// Retrieve
|
||||
const progress = await ctx.storage.ephemeral.get('job_progress');
|
||||
|
||||
// Remove
|
||||
await ctx.storage.ephemeral.remove('job_progress');
|
||||
```
|
||||
|
||||
### Backend Usage
|
||||
|
||||
```python
|
||||
from superset_core.extensions.context import get_context
|
||||
from superset_core.extensions.storage.ephemeral import EphemeralSetOptions
|
||||
|
||||
ctx = get_context()
|
||||
|
||||
# Store job progress with a 5-minute TTL
|
||||
ctx.storage.ephemeral.set(
|
||||
'job_progress', {'pct': 42, 'status': 'running'}, EphemeralSetOptions(ttl=300)
|
||||
)
|
||||
|
||||
# Retrieve
|
||||
progress = ctx.storage.ephemeral.get('job_progress')
|
||||
|
||||
# Remove
|
||||
ctx.storage.ephemeral.remove('job_progress')
|
||||
```
|
||||
|
||||
### Shared State
|
||||
|
||||
For data that needs to be visible to all users:
|
||||
|
||||
```typescript
|
||||
import { extensions } from '@apache-superset/core';
|
||||
|
||||
const ctx = extensions.getContext();
|
||||
|
||||
await ctx.storage.ephemeral.shared.set('shared_result', { data: [1, 2, 3] }, { ttl: 3600 });
|
||||
const result = await ctx.storage.ephemeral.shared.get('shared_result');
|
||||
```
|
||||
|
||||
```python
|
||||
from superset_core.extensions.context import get_context
|
||||
from superset_core.extensions.storage.ephemeral import EphemeralSetOptions
|
||||
|
||||
ctx = get_context()
|
||||
|
||||
ctx.storage.ephemeral.shared.set(
|
||||
'shared_result', {'data': [1, 2, 3]}, EphemeralSetOptions(ttl=3600)
|
||||
)
|
||||
result = ctx.storage.ephemeral.shared.get('shared_result')
|
||||
```
|
||||
|
||||
### When to Use Tier 2
|
||||
|
||||
- Background job progress indicators
|
||||
- Cross-request intermediate state
|
||||
- Query result previews
|
||||
- Temporary computation results
|
||||
- Any data that can be recomputed if lost
|
||||
|
||||
### Limitations
|
||||
|
||||
- Not guaranteed to survive server restarts
|
||||
- Subject to cache eviction under memory pressure
|
||||
- TTL-based expiration (data disappears after timeout)
|
||||
- TTL is required on every `set` call
|
||||
|
||||
## Tier 3: Persistent State
|
||||
|
||||
Database-backed storage that survives server restarts, cache evictions, and browser clears. Use for any data that must not be lost.
|
||||
|
||||
### Frontend Usage
|
||||
|
||||
```typescript
|
||||
import { extensions } from '@apache-superset/core';
|
||||
|
||||
const ctx = extensions.getContext();
|
||||
|
||||
// Store a saved SQL snippet
|
||||
await ctx.storage.persistent.set('snippet:top_customers', { sql: 'SELECT ...' });
|
||||
|
||||
// Retrieve
|
||||
const snippet = await ctx.storage.persistent.get('snippet:top_customers');
|
||||
|
||||
// Remove
|
||||
await ctx.storage.persistent.remove('snippet:top_customers');
|
||||
```
|
||||
|
||||
### Backend Usage
|
||||
|
||||
```python
|
||||
from superset_core.extensions.context import get_context
|
||||
|
||||
ctx = get_context()
|
||||
|
||||
# Store a saved SQL snippet
|
||||
ctx.storage.persistent.set('snippet:top_customers', {'sql': 'SELECT ...'})
|
||||
|
||||
# Retrieve
|
||||
snippet = ctx.storage.persistent.get('snippet:top_customers')
|
||||
|
||||
# Remove
|
||||
ctx.storage.persistent.remove('snippet:top_customers')
|
||||
```
|
||||
|
||||
### Shared State
|
||||
|
||||
For data that should be visible to all users of the extension:
|
||||
|
||||
```typescript
|
||||
import { extensions } from '@apache-superset/core';
|
||||
|
||||
const ctx = extensions.getContext();
|
||||
|
||||
await ctx.storage.persistent.shared.set('global_config', { version: 2 });
|
||||
const config = await ctx.storage.persistent.shared.get('global_config');
|
||||
```
|
||||
|
||||
```python
|
||||
from superset_core.extensions.context import get_context
|
||||
|
||||
ctx = get_context()
|
||||
|
||||
ctx.storage.persistent.shared.set('global_config', {'version': 2})
|
||||
config = ctx.storage.persistent.shared.get('global_config')
|
||||
```
|
||||
|
||||
### Listing Entries
|
||||
|
||||
Both the frontend and backend accessors support listing entries in the caller's scope, without needing to know every key in advance. `page` and `pageSize`/`page_size` are required on every call — `list()` always returns one page of results, never the whole result set, so there's no default that could make that fact easy to miss. Check the returned `count` (total entries matching the scope/filters, across all pages) against `pageSize`/`page_size` to know whether more pages exist.
|
||||
|
||||
```typescript
|
||||
import { extensions } from '@apache-superset/core';
|
||||
|
||||
const ctx = extensions.getContext();
|
||||
|
||||
// Filter by resource
|
||||
const result = await ctx.storage.persistent.list({
|
||||
resourceType: 'dashboard',
|
||||
resourceUuid: '1234-uuid',
|
||||
page: 0,
|
||||
pageSize: 25,
|
||||
});
|
||||
result.entries.forEach(entry => {
|
||||
console.log(entry.key, entry.value, entry.codec);
|
||||
});
|
||||
console.log(result.count); // total matching entries across all pages
|
||||
|
||||
// Shared (global) scope
|
||||
const shared = await ctx.storage.persistent.shared.list({ page: 0, pageSize: 10 });
|
||||
```
|
||||
|
||||
```python
|
||||
from superset_core.extensions.context import get_context
|
||||
from superset_core.extensions.storage.persistent import PersistentListOptions
|
||||
|
||||
ctx = get_context()
|
||||
|
||||
# Filter by resource
|
||||
result = ctx.storage.persistent.list(
|
||||
PersistentListOptions(
|
||||
resource_type="dashboard",
|
||||
resource_uuid="1234-uuid",
|
||||
page=0,
|
||||
page_size=25,
|
||||
)
|
||||
)
|
||||
for entry in result.entries:
|
||||
print(entry.key, entry.value, entry.codec)
|
||||
print(result.count) # total matching entries across all pages
|
||||
|
||||
# Shared (global) scope
|
||||
shared = ctx.storage.persistent.shared.list(PersistentListOptions(page=0, page_size=10))
|
||||
```
|
||||
|
||||
`resource_type`/`resource_uuid` filter to entries linked to one resource — since a single resource can have several keyed entries (e.g. `"layout"`, `"notes"`), this is how an extension enumerates all of them without tracking key names itself.
|
||||
|
||||
There is no fixed limit on `page_size`, but the combined size of a requested page's values is checked against `MAX_LIST_PAYLOAD_SIZE` (see [Quotas](#quotas)) before any row's value is read from the database — a page that would exceed it is rejected rather than silently truncated, so reduce `page_size` and retry if that happens.
|
||||
|
||||
Backend `list()` decodes every entry's value unconditionally. The REST API backing the frontend's `list()` call is more restrictive: an entry written with a codec that isn't safe to decode over the wire (e.g. `pickle`) comes back with `value: null` (its `codec` is still reported) rather than being decoded.
|
||||
|
||||
### Enumerating and Managing Storage from the Backend
|
||||
|
||||
`ctx.storage.persistent.list()` covers the common case of listing an extension's own entries. Backend extension code that needs lower-level bulk operations — for example, a cleanup routine that removes storage linked to resources that no longer exist, using filters `list()` doesn't expose — can use `superset_core.extensions.storage.dao.ExtensionStorageDAO` instead:
|
||||
|
||||
```python
|
||||
from superset_core.extensions.storage.dao import ExtensionStorageDAO
|
||||
|
||||
# All entries this extension has linked to a given resource type
|
||||
entries = ExtensionStorageDAO.filter_by(resource_type="my-resource-type")
|
||||
|
||||
# Bulk-delete rows for resources that no longer exist
|
||||
orphaned = [e for e in entries if e.resource_uuid not in my_extension_live_resource_ids()]
|
||||
ExtensionStorageDAO.delete(orphaned)
|
||||
```
|
||||
|
||||
`ExtensionStorageDAO` is automatically scoped to the calling extension's own rows — `extension_id` is never a parameter you pass, so there is no way to reach another extension's storage through this API. It supports the standard DAO operations `find_all`, `find_one_or_none`, `filter_by`, `query`, `update`, and `delete`. `create()` is not supported — it's a raw insert with no upsert dedup, quota check, or locking against the key `.set()` writes to, so use `ctx.storage.persistent.set()` to write a value instead.
|
||||
|
||||
### When to Use Tier 3
|
||||
|
||||
- Extension configuration that must survive restarts
|
||||
- User-specific saved artifacts that need to roam across devices and browsers (e.g. saved SQL snippets, annotations)
|
||||
- Any data where loss is unacceptable
|
||||
|
||||
### Limitations
|
||||
|
||||
- Higher latency than Tiers 1–2 (database round-trip per operation)
|
||||
- Subject to the per-value size limit configured via `EXTENSIONS_PERSISTENT_STORAGE.MAX_VALUE_SIZE` (see [Quotas](#quotas))
|
||||
- Subject to the per-extension quota configured via `EXTENSIONS_PERSISTENT_STORAGE.QUOTA_PER_EXTENSION` (see [Quotas](#quotas))
|
||||
- Requires a database migration when first deployed
|
||||
|
||||
## Working with Binary Data
|
||||
|
||||
Tiers 2 and 3 accept a `codec` option describing how `value` is encoded — `"json"` (the default) for JSON-native values, or `"binary"` for raw bytes. `list()` and `get()` report which codec an entry was written with via `entry.codec`.
|
||||
|
||||
### Backend
|
||||
|
||||
Backend code talks to the storage DAO directly and works with real Python `bytes` — no extra flag is needed:
|
||||
|
||||
```python
|
||||
from superset_core.extensions.context import get_context
|
||||
from superset_core.extensions.storage.persistent import PersistentSetOptions
|
||||
|
||||
ctx = get_context()
|
||||
|
||||
with open('logo.png', 'rb') as f:
|
||||
png_bytes = f.read()
|
||||
|
||||
ctx.storage.persistent.set('logo', png_bytes, PersistentSetOptions(codec='binary'))
|
||||
|
||||
stored = ctx.storage.persistent.get('logo') # raw bytes back, unchanged
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
The frontend SDK talks to the backend over a JSON REST API, which has no byte type — a binary value must be base64-encoded to travel in a JSON request body. There is no way for the SDK to infer this from a JS value's type, so it is never detected automatically: set `isBinary: true` explicitly whenever `value` is binary.
|
||||
|
||||
```typescript
|
||||
import { extensions } from '@apache-superset/core';
|
||||
|
||||
const ctx = extensions.getContext();
|
||||
|
||||
const pngBytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47 /* ... */]);
|
||||
await ctx.storage.persistent.set('logo', pngBytes, { isBinary: true });
|
||||
```
|
||||
|
||||
`get()` returns a binary entry's value exactly as stored on the wire — a base64 string, not a `Uint8Array` — so decode it yourself:
|
||||
|
||||
```typescript
|
||||
const base64 = await ctx.storage.persistent.get<string>('logo');
|
||||
const bytes = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
|
||||
```
|
||||
|
||||
`list()` entries report `isBinary` alongside `codec`, so you know which entries need decoding before you read `value`:
|
||||
|
||||
```typescript
|
||||
const result = await ctx.storage.persistent.list({ page: 0, pageSize: 10 });
|
||||
result.entries.forEach(entry => {
|
||||
const value = entry.isBinary
|
||||
? Uint8Array.from(atob(entry.value as string), c => c.charCodeAt(0))
|
||||
: entry.value;
|
||||
});
|
||||
```
|
||||
|
||||
## Key Patterns
|
||||
|
||||
All storage keys are automatically namespaced:
|
||||
|
||||
| Scope | Key Pattern |
|
||||
| ---------------------- | -------------------------------------------------- |
|
||||
| User-scoped | `superset-ext:{extension_id}:user:{user_id}:{key}` |
|
||||
| Shared (Tiers 2 and 3) | `superset-ext:{extension_id}:shared:{key}` |
|
||||
|
||||
This ensures:
|
||||
|
||||
- Extensions cannot accidentally access each other's data
|
||||
- Users cannot see other users' data (by default)
|
||||
- Clean prefix-based deletion on uninstall
|
||||
|
||||
## Configuration
|
||||
|
||||
### Tier 2: Ephemeral Storage
|
||||
|
||||
Administrators can configure the server-side cache backend in `superset_config.py`:
|
||||
|
||||
```python
|
||||
EXTENSIONS_EPHEMERAL_STORAGE = {
|
||||
# Use Redis for better performance in production
|
||||
"CACHE_TYPE": "RedisCache",
|
||||
"CACHE_REDIS_URL": "redis://localhost:6379/2",
|
||||
"MAX_TTL": 604800, # 7 days maximum TTL
|
||||
"MAX_VALUE_SIZE": 100 * 1024, # 100 KB maximum per stored value
|
||||
}
|
||||
```
|
||||
|
||||
For development, the default `SupersetMetastoreCache` stores data in the metadata database.
|
||||
|
||||
### Tier 3: Persistent Storage
|
||||
|
||||
Tier 3 values are stored in the `extension_storage` database table. Values are stored unencrypted by default; encryption is opt-in per write, available via a `PersistentSetOptions`-shaped `encrypt` option:
|
||||
|
||||
```typescript
|
||||
import { extensions } from '@apache-superset/core';
|
||||
|
||||
const ctx = extensions.getContext();
|
||||
|
||||
await ctx.storage.persistent.set('api_token', 'sk-...', { encrypt: true });
|
||||
const token = await ctx.storage.persistent.get('api_token');
|
||||
```
|
||||
|
||||
```python
|
||||
from superset_core.extensions.context import get_context
|
||||
from superset_core.extensions.storage.persistent import PersistentSetOptions
|
||||
|
||||
ctx = get_context()
|
||||
|
||||
ctx.storage.persistent.set('api_token', 'sk-...', PersistentSetOptions(encrypt=True))
|
||||
token = ctx.storage.persistent.get('api_token')
|
||||
```
|
||||
|
||||
Encryption reuses Superset's existing `EncryptedType` (from `sqlalchemy-utils`) rather than a separate mechanism — the same infrastructure used for database connection credentials. The key is not configured directly: user-scoped values are encrypted with a key derived per-user via HMAC-SHA256 from the deployment's `SECRET_KEY`, so ciphertext for one user cannot be decrypted as another's; shared/global values (written via `.shared`) use `SECRET_KEY` directly. The encryption engine (AES-CBC by default) can be changed for the whole deployment via the existing `SQLALCHEMY_ENCRYPTED_FIELD_ENGINE` config, the same setting that controls encryption for database credentials elsewhere in Superset — there is no separate key list or rotation mechanism specific to extension storage. When `SECRET_KEY` is rotated, extension storage rows are re-encrypted by the existing `SecretsMigrator` tooling alongside database credentials, with no extension-specific steps required.
|
||||
|
||||
### Quotas
|
||||
|
||||
Persistent storage is subject to per-value, per-list-page, and per-extension size limits, configured in `superset_config.py`:
|
||||
|
||||
```python
|
||||
EXTENSIONS_PERSISTENT_STORAGE = {
|
||||
# Maximum total bytes an extension may store across all of its rows
|
||||
# (global, every user's, and shared). Defaults to 100 MB.
|
||||
"QUOTA_PER_EXTENSION": 100 * 1024 * 1024,
|
||||
# Maximum size (in bytes) of a single stored value. Defaults to 1 MB.
|
||||
"MAX_VALUE_SIZE": 1024 * 1024,
|
||||
# Maximum combined value size (in bytes) that a single list() page may
|
||||
# return. Defaults to 10 MB.
|
||||
"MAX_LIST_PAYLOAD_SIZE": 10 * 1024 * 1024,
|
||||
}
|
||||
```
|
||||
|
||||
A write that would push the extension's total stored size over `QUOTA_PER_EXTENSION` is rejected — the frontend SDK's `set()` call rejects with an HTTP 413 response, and backend code calling `ctx.storage.persistent.set()` sees the corresponding exception raised. Overwriting an existing key nets out that key's own current size against usage first, so replacing a value with one of the same or smaller size never spuriously fails even when the extension is already near its quota.
|
||||
|
||||
A write whose encoded value exceeds `MAX_VALUE_SIZE` is rejected outright, independently of the total quota.
|
||||
|
||||
A `list()` call whose requested page would return more than `MAX_LIST_PAYLOAD_SIZE` combined bytes of value data is rejected — reduce `page`/`pageSize` and retry. This check runs against the page's stored sizes before any value is read from the database, so an oversized request fails fast. Because `list()`'s response is consumed as JSON in the browser, raising `MAX_LIST_PAYLOAD_SIZE` substantially above the default risks client-side memory and parse-time issues — treat the default as already generous rather than a floor to build up from.
|
||||
@@ -128,7 +128,7 @@ from superset.dashboards.commands.create import CreateDashboardCommand
|
||||
def test_create_dashboard_success():
|
||||
properties = {
|
||||
"dashboard_title": "Test Dashboard",
|
||||
"editors": [1]
|
||||
"owners": [1]
|
||||
}
|
||||
|
||||
command = CreateDashboardCommand(properties)
|
||||
@@ -275,11 +275,9 @@ def handle_validation_error(error):
|
||||
from typing import List, Optional, Dict, Any
|
||||
from superset.models.dashboard import Dashboard
|
||||
|
||||
def get_dashboards_by_editor(editor_id: int) -> List[Dashboard]:
|
||||
"""Get all dashboards editable by a specific subject"""
|
||||
return db.session.query(Dashboard).filter(
|
||||
Dashboard.editors.any(id=editor_id)
|
||||
).all()
|
||||
def get_dashboards_by_owner(owner_id: int) -> List[Dashboard]:
|
||||
"""Get all dashboards owned by a specific user"""
|
||||
return db.session.query(Dashboard).filter_by(owner_id=owner_id).all()
|
||||
|
||||
def create_dashboard(properties: Dict[str, Any]) -> Optional[Dashboard]:
|
||||
"""Create a new dashboard with the given properties"""
|
||||
|
||||
@@ -153,10 +153,10 @@ class DashboardDAO:
|
||||
return db.session.query(Dashboard).filter_by(slug=slug).first()
|
||||
|
||||
@classmethod
|
||||
def find_by_editor(cls, editor_id: int) -> List[Dashboard]:
|
||||
"""Find all dashboards editable by a subject"""
|
||||
return db.session.query(Dashboard).filter(
|
||||
Dashboard.editors.any(id=editor_id)
|
||||
def find_by_owner(cls, owner_id: int) -> List[Dashboard]:
|
||||
"""Find all dashboards owned by a user"""
|
||||
return db.session.query(Dashboard).filter_by(
|
||||
created_by_fk=owner_id
|
||||
).all()
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -52,7 +52,7 @@ Everything you need to contribute to the Apache Superset project. This section i
|
||||
## Development Resources
|
||||
|
||||
### Prerequisites
|
||||
- **Python**: 3.11 or 3.12
|
||||
- **Python**: 3.9, 3.10, or 3.11
|
||||
- **Node.js**: 18.x or 20.x
|
||||
- **npm**: 9.x or 10.x
|
||||
- **Git**: Basic understanding
|
||||
|
||||
@@ -49,6 +49,7 @@ module.exports = {
|
||||
'extensions/extension-points/sqllab',
|
||||
'extensions/extension-points/editors',
|
||||
'extensions/extension-points/chat',
|
||||
'extensions/extension-points/dashboard-components',
|
||||
],
|
||||
},
|
||||
'extensions/development',
|
||||
|
||||
@@ -120,7 +120,7 @@ SMTP_MAIL_FROM = "superset@localhost"
|
||||
# Must match where your frontend is running
|
||||
WEBDRIVER_BASEURL = "http://localhost:9000/"
|
||||
|
||||
ALERT_REPORTS_EXECUTORS = [ExecutorType.EDITOR]
|
||||
ALERT_REPORTS_EXECUTE_AS = [ExecutorType.OWNER]
|
||||
|
||||
FEATURE_FLAGS = {
|
||||
"ALERT_REPORTS": True,
|
||||
|
||||
@@ -181,23 +181,6 @@ value in milliseconds in the JSON Metadata field:
|
||||
Here, the entire dashboard will refresh at once if periodic refresh is on. The stagger time of 2.5
|
||||
seconds is ignored.
|
||||
|
||||
The manual **Refresh dashboard** button can also stagger its chart requests, controlled by the
|
||||
`SUPERSET_DASHBOARD_MANUAL_REFRESH_STAGGER_MS` server config in `superset_config.py`. This defaults
|
||||
to `0`, which preserves the original behavior where every chart request fires at the same time when
|
||||
the button is clicked. To opt in to staggering, set a positive number of milliseconds; the window
|
||||
then becomes the larger of this value and the per-dashboard `stagger_time` metadata, which itself
|
||||
defaults to `5000` milliseconds when it is not set explicitly. Setting `stagger_refresh` to `false`
|
||||
in a dashboard's JSON Metadata also disables staggering on the manual-refresh path, so its charts
|
||||
fire all at once even when the server config is positive. If the backend does not provide a value at
|
||||
all, for example an older backend that predates this config or a value explicitly set to `None`, the
|
||||
frontend falls back to a built-in default of `5000` milliseconds.
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
# 0 (the default) fires every chart at once; set a positive value to stagger.
|
||||
SUPERSET_DASHBOARD_MANUAL_REFRESH_STAGGER_MS = 0
|
||||
```
|
||||
|
||||
**Why does ‘flask fab’ or superset freeze/hang/not responding when started (my home directory is
|
||||
NFS mounted)?**
|
||||
|
||||
|
||||
@@ -211,8 +211,8 @@ slices and dashboards of your own.
|
||||
|
||||
### Manage access to Dashboards
|
||||
|
||||
Access to dashboards is managed via editors and permissions. Non-editor access can be controlled
|
||||
through dataset permissions, or through dashboard viewers when `ENABLE_VIEWERS` is enabled.
|
||||
Access to dashboards is managed via owners and permissions. Non-owner access can be controlled
|
||||
through dataset permissions or dashboard-level roles (using the `DASHBOARD_RBAC` feature flag).
|
||||
|
||||
For detailed information on configuring dashboard access, see the
|
||||
[Dashboard Access Control](/admin-docs/security/#dashboard-access-control) section in the
|
||||
@@ -228,9 +228,7 @@ title of your dashboard.
|
||||
<img src={useBaseUrl("/img/tutorial/publish_button_dashboard.png" )} />
|
||||
|
||||
:::warning
|
||||
Draft dashboards are only visible to dashboard editors and admins. Published dashboards are visible
|
||||
to users with access to the underlying datasets, or, when `ENABLE_VIEWERS` is enabled, to users
|
||||
who have viewer access directly or through an assigned role or group subject.
|
||||
Draft dashboards are only visible to the dashboard owners and admins. Published dashboards are visible to all users with access to the underlying datasets or if RBAC is enabled, to the roles that have been granted access to the dashboard.
|
||||
:::
|
||||
|
||||
### Mark a Dashboard as Favorite
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
---
|
||||
title: Exporting Dashboard Data to Excel
|
||||
hide_title: true
|
||||
sidebar_position: 7
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Exporting Dashboard Data to Excel
|
||||
|
||||
Superset can export every chart on a dashboard to a single Excel workbook, with
|
||||
each chart's underlying data rendered as its own worksheet. The export reflects
|
||||
the dashboard's currently applied filters and runs asynchronously: when it
|
||||
finishes, the requesting user receives an email with a time-limited download
|
||||
link.
|
||||
|
||||
## Using the export
|
||||
|
||||
From a dashboard, open the **... (actions) → Download** submenu and choose
|
||||
**Export Data to Excel**. The action appears for users who have the dashboard
|
||||
`can_export` permission. You'll see a confirmation that the export is being
|
||||
prepared; the workbook arrives by email when it's ready.
|
||||
|
||||
A second option, **Export Images to Excel**, embeds each non-table chart as a
|
||||
rendered image (tables stay tabular) instead of exporting raw data. Because it
|
||||
renders charts through the headless webdriver, this option only appears when the
|
||||
webdriver screenshot feature flags are enabled (see the prerequisites below);
|
||||
which viz types stay tabular is controlled by `EXCEL_EXPORT_TABLE_VIZ_TYPES`.
|
||||
|
||||
Notes on the generated workbook:
|
||||
|
||||
- One worksheet per chart, named `{chart_id} - {chart title}` (truncated to
|
||||
Excel's 31-character limit; the chart id keeps names unique).
|
||||
- Charts nested in tabs are included.
|
||||
- Data reflects the dashboard's active filter state at the time of export.
|
||||
- A chart with no saved query context is skipped and listed in the email; open
|
||||
the chart in Explore and re-save it to include it next time.
|
||||
- Row counts per sheet are capped the same way as the chart-level CSV/Excel
|
||||
export (`ROW_LIMIT`, bounded by `SQL_MAX_ROW`), and never exceed Excel's
|
||||
per-sheet maximum.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This feature is **disabled by default**. It requires:
|
||||
|
||||
1. **The `boto3` dependency.** It is not installed by default; install it with
|
||||
`pip install apache-superset[excel-export]`. Without it, exports fail and the
|
||||
user receives a failure email.
|
||||
2. **An S3 bucket.** Set `EXCEL_EXPORT_S3_BUCKET`. Until it is set, the export
|
||||
endpoint returns `501` and the menu action surfaces a "not configured"
|
||||
message.
|
||||
3. **A running Celery worker.** The export runs as a Celery task. If no worker
|
||||
is running, the request is accepted but nothing is produced.
|
||||
4. **A configured SMTP transport.** The download link is delivered by email
|
||||
using the same settings as alerts & reports (`SMTP_*`,
|
||||
`EMAIL_REPORTS_SUBJECT_PREFIX`).
|
||||
|
||||
**Export Images to Excel** additionally requires a working headless webdriver —
|
||||
the same infrastructure scheduled reports and thumbnails use (`WEBDRIVER_*`,
|
||||
plus the `ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS` and
|
||||
`ENABLE_DASHBOARD_DOWNLOAD_WEBDRIVER_SCREENSHOT` feature flags). The menu option
|
||||
is hidden when those flags are off; if the webdriver is unreachable, image
|
||||
charts come back empty even though the data export path still works.
|
||||
|
||||
Deployments that override `CELERY_CONFIG` must add
|
||||
`"superset.tasks.export_dashboard_excel"` to the `imports` tuple, or the task
|
||||
will not register.
|
||||
|
||||
## Configuration keys
|
||||
|
||||
| Key | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `EXCEL_EXPORT_S3_BUCKET` | `None` | Destination bucket. Required; `501` if unset. |
|
||||
| `EXCEL_EXPORT_S3_KEY_PREFIX` | `"dashboard-exports/"` | Key prefix: `{prefix}{dashboard_id}/{job_id}.xlsx`. |
|
||||
| `EXCEL_EXPORT_LINK_TTL_SECONDS` | `86400` | Lifetime of the pre-signed download URL (24h). |
|
||||
| `EXCEL_EXPORT_S3_CLIENT_KWARGS` | `{}` | Extra kwargs for `boto3.client("s3", ...)` — e.g. `region_name`, or `endpoint_url` for MinIO/LocalStack. |
|
||||
| `EXCEL_EXPORT_TABLE_VIZ_TYPES` | `None` | Viz types kept tabular in **Export Images to Excel** mode; every other type is embedded as an image. `None` uses the built-in default (`table`, `pivot_table`, `pivot_table_v2`). |
|
||||
|
||||
Credentials and region resolve through the standard boto3 chain (environment
|
||||
variables, shared config, or instance role) unless overridden via
|
||||
`EXCEL_EXPORT_S3_CLIENT_KWARGS`. The worker needs `s3:PutObject` on the bucket.
|
||||
|
||||
## Security considerations
|
||||
|
||||
- The emailed link is a **pre-signed S3 URL**: anyone who holds it can download
|
||||
the workbook until it expires. Keep the bucket **private**, enable
|
||||
encryption, and consider a lifecycle rule to delete objects after a few days.
|
||||
Lower `EXCEL_EXPORT_LINK_TTL_SECONDS` if 24 hours is too long for your data.
|
||||
- The export runs with the requesting user's permissions; each chart's query is
|
||||
access-checked, so users only ever receive data they are entitled to.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Embedded dashboards / guest tokens are not supported** in this version,
|
||||
because guest users have no email address to deliver the link to. Logged-in
|
||||
users viewing an embedded dashboard can still use the export.
|
||||
- The default **Export Data to Excel** mode exports data only (no visual
|
||||
styling). Use **Export Images to Excel** to embed rendered chart images, which
|
||||
requires the webdriver infrastructure described in the prerequisites.
|
||||
- Scheduled/automated exports are not part of this feature.
|
||||
@@ -146,9 +146,7 @@ The `where_in` filter converts the list to SQL format: `('value1', 'value2', 'va
|
||||
|
||||
### Time Filters
|
||||
|
||||
`get_time_filter()` is the standard way to reference the selected time range in a virtual dataset.
|
||||
It returns a `TimeFilter` object with `from_expr` and `to_expr` — fully-formatted SQL expressions
|
||||
that should be used directly in `WHERE` clauses without quoting.
|
||||
For charts with time range filters:
|
||||
|
||||
| Macro | Description |
|
||||
|-------|-------------|
|
||||
@@ -200,9 +198,17 @@ GROUP BY category
|
||||
|
||||
## Testing Templates in SQL Lab
|
||||
|
||||
Some variables like `filter_values()` only work when filters are applied from dashboards or charts. To test in SQL Lab:
|
||||
Some variables like `from_dttm` and `filter_values()` only work when filters are applied from dashboards or charts. To test in SQL Lab:
|
||||
|
||||
**Option 1: Set test parameters**
|
||||
**Option 1: Use defaults**
|
||||
|
||||
```sql
|
||||
SELECT *
|
||||
FROM orders
|
||||
WHERE date >= '{{ from_dttm | default("2024-01-01", true) }}'
|
||||
```
|
||||
|
||||
**Option 2: Set test parameters**
|
||||
|
||||
Add to the Parameters menu:
|
||||
|
||||
@@ -214,7 +220,7 @@ Add to the Parameters menu:
|
||||
}
|
||||
```
|
||||
|
||||
**Option 2: Use `{% set %}`**
|
||||
**Option 3: Use `{% set %}`**
|
||||
|
||||
```sql
|
||||
{% set start_date = "2024-01-01" %}
|
||||
|
||||
@@ -44,29 +44,29 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.2.5",
|
||||
"@docusaurus/core": "^3.10.2",
|
||||
"@docusaurus/faster": "^3.10.2",
|
||||
"@docusaurus/plugin-client-redirects": "^3.10.2",
|
||||
"@docusaurus/preset-classic": "3.10.2",
|
||||
"@docusaurus/theme-live-codeblock": "^3.10.2",
|
||||
"@docusaurus/theme-mermaid": "^3.10.2",
|
||||
"@docusaurus/core": "^3.10.1",
|
||||
"@docusaurus/faster": "^3.10.1",
|
||||
"@docusaurus/plugin-client-redirects": "^3.10.1",
|
||||
"@docusaurus/preset-classic": "3.10.1",
|
||||
"@docusaurus/theme-live-codeblock": "^3.10.1",
|
||||
"@docusaurus/theme-mermaid": "^3.10.1",
|
||||
"@emotion/core": "^11.0.0",
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@fontsource/fira-code": "^5.3.0",
|
||||
"@fontsource/ibm-plex-mono": "^5.3.0",
|
||||
"@fontsource/inter": "^5.3.0",
|
||||
"@fontsource/fira-code": "^5.2.7",
|
||||
"@fontsource/ibm-plex-mono": "^5.2.7",
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@saucelabs/theme-github-codeblock": "^0.3.0",
|
||||
"@storybook/addon-docs": "^10.5.0",
|
||||
"@storybook/addon-docs": "^10.4.5",
|
||||
"@superset-ui/core": "^0.20.4",
|
||||
"@swc/core": "^1.15.46",
|
||||
"antd": "^6.5.1",
|
||||
"baseline-browser-mapping": "^2.10.43",
|
||||
"caniuse-lite": "^1.0.30001806",
|
||||
"docusaurus-plugin-openapi-docs": "^5.1.2",
|
||||
"docusaurus-theme-openapi-docs": "^5.1.2",
|
||||
"js-yaml": "^5.2.2",
|
||||
"@swc/core": "^1.15.43",
|
||||
"antd": "^6.5.0",
|
||||
"baseline-browser-mapping": "^2.10.40",
|
||||
"caniuse-lite": "^1.0.30001799",
|
||||
"docusaurus-plugin-openapi-docs": "^5.1.0",
|
||||
"docusaurus-theme-openapi-docs": "^5.1.0",
|
||||
"js-yaml": "^5.2.0",
|
||||
"json-bigint": "^1.0.0",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^18.3.1",
|
||||
@@ -77,28 +77,28 @@
|
||||
"react-table": "^7.8.0",
|
||||
"remark-import-partial": "^0.0.2",
|
||||
"reselect": "^5.2.0",
|
||||
"storybook": "^10.5.0",
|
||||
"swagger-ui-react": "^5.32.9",
|
||||
"storybook": "^10.4.5",
|
||||
"swagger-ui-react": "^5.32.8",
|
||||
"swc-loader": "^0.2.7",
|
||||
"tinycolor2": "^1.4.2",
|
||||
"unist-util-visit": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.10.2",
|
||||
"@docusaurus/tsconfig": "^3.10.2",
|
||||
"@docusaurus/module-type-aliases": "^3.10.1",
|
||||
"@docusaurus/tsconfig": "^3.10.1",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/react": "^19.1.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.64.0",
|
||||
"@typescript-eslint/parser": "^8.64.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.3",
|
||||
"@typescript-eslint/parser": "^8.61.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.6",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^17.7.0",
|
||||
"prettier": "^3.9.5",
|
||||
"prettier": "^3.9.1",
|
||||
"typescript": "~6.0.3",
|
||||
"typescript-eslint": "^8.64.0",
|
||||
"typescript-eslint": "^8.62.0",
|
||||
"webpack": "^5.108.2"
|
||||
},
|
||||
"browserslist": {
|
||||
@@ -124,6 +124,8 @@
|
||||
"serialize-javascript": "7.0.5",
|
||||
"d3-color": "3.1.0",
|
||||
"ws": "^8.21.0",
|
||||
"@docusaurus/core/@docusaurus/utils/gray-matter/js-yaml": "^3.15.0",
|
||||
"@docusaurus/core/**/js-yaml": "^4.3.0",
|
||||
"docusaurus-plugin-openapi-docs/**/js-yaml": "^4.3.0"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
|
||||
|
||||
@@ -82,14 +82,12 @@ const sidebars = {
|
||||
items: [
|
||||
'extensions/extension-points/sqllab',
|
||||
'extensions/extension-points/editors',
|
||||
'extensions/extension-points/chat',
|
||||
],
|
||||
},
|
||||
'extensions/development',
|
||||
'extensions/deployment',
|
||||
'extensions/mcp',
|
||||
'extensions/security',
|
||||
'extensions/storage',
|
||||
'extensions/tasks',
|
||||
'extensions/registry',
|
||||
],
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"generated": "2026-04-17T17:08:35.132Z",
|
||||
"statistics": {
|
||||
"totalDatabases": 81,
|
||||
"withDocumentation": 81,
|
||||
"withConnectionString": 81,
|
||||
"withDrivers": 29,
|
||||
"totalDatabases": 80,
|
||||
"withDocumentation": 80,
|
||||
"withConnectionString": 80,
|
||||
"withDrivers": 28,
|
||||
"withAuthMethods": 5,
|
||||
"supportsJoins": 75,
|
||||
"supportsSubqueries": 78,
|
||||
"supportsJoins": 74,
|
||||
"supportsSubqueries": 77,
|
||||
"supportsDynamicSchema": 29,
|
||||
"supportsCatalog": 20,
|
||||
"averageScore": 53,
|
||||
"averageScore": 54,
|
||||
"maxScore": 201,
|
||||
"byCategory": {
|
||||
"Cloud - AWS": [
|
||||
@@ -20,7 +20,6 @@
|
||||
],
|
||||
"Query Engines": [
|
||||
"Amazon Athena",
|
||||
"Apache DataFusion",
|
||||
"Apache Drill",
|
||||
"Apache Hive",
|
||||
"Apache Impala",
|
||||
@@ -95,7 +94,6 @@
|
||||
"Vertica"
|
||||
],
|
||||
"Apache Projects": [
|
||||
"Apache DataFusion",
|
||||
"Apache Doris",
|
||||
"Apache Drill",
|
||||
"Apache Druid",
|
||||
@@ -110,7 +108,6 @@
|
||||
"Databricks Interactive Cluster"
|
||||
],
|
||||
"Open Source": [
|
||||
"Apache DataFusion",
|
||||
"Apache Doris",
|
||||
"Apache Drill",
|
||||
"Apache Druid",
|
||||
@@ -3675,7 +3672,7 @@
|
||||
"drivers": [
|
||||
{
|
||||
"name": "crate",
|
||||
"pypi_package": "sqlalchemy-cratedb",
|
||||
"pypi_package": "crate[sqlalchemy]",
|
||||
"connection_string": "crate://{host}:{port}",
|
||||
"is_recommended": true
|
||||
}
|
||||
@@ -9701,97 +9698,6 @@
|
||||
"supports_dynamic_schema": true,
|
||||
"supports_catalog": true,
|
||||
"supports_dynamic_catalog": true
|
||||
},
|
||||
"Apache DataFusion": {
|
||||
"time_grains": {
|
||||
"SECOND": true,
|
||||
"FIVE_SECONDS": true,
|
||||
"THIRTY_SECONDS": true,
|
||||
"MINUTE": true,
|
||||
"FIVE_MINUTES": true,
|
||||
"TEN_MINUTES": true,
|
||||
"FIFTEEN_MINUTES": true,
|
||||
"THIRTY_MINUTES": true,
|
||||
"HALF_HOUR": false,
|
||||
"HOUR": true,
|
||||
"SIX_HOURS": false,
|
||||
"DAY": true,
|
||||
"WEEK": true,
|
||||
"WEEK_STARTING_SUNDAY": false,
|
||||
"WEEK_STARTING_MONDAY": false,
|
||||
"WEEK_ENDING_SATURDAY": false,
|
||||
"WEEK_ENDING_SUNDAY": false,
|
||||
"MONTH": true,
|
||||
"QUARTER": true,
|
||||
"QUARTER_YEAR": false,
|
||||
"YEAR": true
|
||||
},
|
||||
"module": "superset.db_engine_specs.datafusion",
|
||||
"limit_method": 1,
|
||||
"limit_clause": true,
|
||||
"joins": true,
|
||||
"subqueries": true,
|
||||
"alias_in_select": true,
|
||||
"alias_in_orderby": true,
|
||||
"time_groupby_inline": false,
|
||||
"alias_to_source_column": false,
|
||||
"order_by_not_in_select": true,
|
||||
"expressions_in_orderby": false,
|
||||
"cte_in_subquery": true,
|
||||
"max_column_name": null,
|
||||
"sql_comments": true,
|
||||
"escaped_colons": true,
|
||||
"masked_encrypted_extra": false,
|
||||
"column_type_mapping": false,
|
||||
"function_names": false,
|
||||
"user_impersonation": false,
|
||||
"file_upload": false,
|
||||
"get_extra_table_metadata": false,
|
||||
"dbapi_exception_mapping": false,
|
||||
"custom_errors": false,
|
||||
"dynamic_schema": false,
|
||||
"catalog": false,
|
||||
"dynamic_catalog": false,
|
||||
"ssh_tunneling": true,
|
||||
"query_cancelation": false,
|
||||
"get_metrics": false,
|
||||
"where_latest_partition": false,
|
||||
"expand_data": false,
|
||||
"query_cost_estimation": false,
|
||||
"sql_validation": false,
|
||||
"score": 24,
|
||||
"max_score": 201,
|
||||
"documentation": {
|
||||
"description": "DataFusion is a highly performant query engine",
|
||||
"logo": "datafusion.png",
|
||||
"homepage_url": "https://datafusion.apache.org/",
|
||||
"categories": [
|
||||
"Query Engines",
|
||||
"Open Source",
|
||||
"Apache Projects"
|
||||
],
|
||||
"pypi_packages": [
|
||||
"flightsql-dbapi"
|
||||
],
|
||||
"connection_string": "datafusion://host:port",
|
||||
"drivers": [
|
||||
{
|
||||
"name": "Arrow Flight SQL (Recommended)",
|
||||
"pypi_package": "flightsql-dbapi",
|
||||
"connection_string": "datafusion://host:port",
|
||||
"is_recommended": true
|
||||
}
|
||||
],
|
||||
"category": "Apache Projects"
|
||||
},
|
||||
"engine": "datafusion",
|
||||
"engine_name": "Apache DataFusion",
|
||||
"engine_aliases": [],
|
||||
"default_driver": "flightsql",
|
||||
"supports_file_upload": false,
|
||||
"supports_dynamic_schema": false,
|
||||
"supports_catalog": false,
|
||||
"supports_dynamic_catalog": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
docs/static/.htaccess
vendored
3
docs/static/.htaccess
vendored
@@ -28,9 +28,8 @@ RewriteRule ^(.*)$ https://superset.apache.org/$1 [R=301,L]
|
||||
# - *.googleapis.com, *.google.com, *.gstatic.com: Google Calendar embed, kapa.ai reCAPTCHA - all of these loaded with user consent, following policy laid out in https://privacy.apache.org/faq/committers.html
|
||||
# - github.com, *.github.com, *.githubusercontent.com: GitHub user-attachment images in docs (apex github.com serves user-attachments/* assets). Discussed/resolved in this thread: https://issues.apache.org/jira/browse/INFRA-25701?filter=-2 (DPA in place with GitHub)
|
||||
# - *.algolia.net, *.algolianet.com: Algolia DocSearch. Approved here: https://privacy.apache.org/faq/committers.html
|
||||
# - *.run.app: Google-owned domain (Google Cloud Run) used by the kapa.ai widget backend - only reached after explicit user acceptance when opening the widget, allowable per the policy laid out in https://privacy.apache.org/faq/committers.html
|
||||
# See: https://infra.apache.org/tools/csp.html
|
||||
SetEnv CSP_PROJECT_DOMAINS "widget.kapa.ai https://*.googleapis.com/ https://*.google.com/ https://*.gstatic.com/ https://github.com/ https://*.github.com/ https://*.githubusercontent.com/ https://*.algolia.net/ https://*.algolianet.com/ https://*.run.app/"
|
||||
SetEnv CSP_PROJECT_DOMAINS "widget.kapa.ai https://*.googleapis.com/ https://*.google.com/ https://*.gstatic.com/ https://github.com/ https://*.github.com/ https://*.githubusercontent.com/ https://*.algolia.net/ https://*.algolianet.com/"
|
||||
|
||||
# REDIRECTS
|
||||
|
||||
|
||||
33
docs/static/feature-flags.json
vendored
33
docs/static/feature-flags.json
vendored
@@ -21,6 +21,12 @@
|
||||
"lifecycle": "development",
|
||||
"description": "Enables experimental chart plugins"
|
||||
},
|
||||
{
|
||||
"name": "CSP_RUNTIME_ALLOWLIST",
|
||||
"default": false,
|
||||
"lifecycle": "development",
|
||||
"description": "Allow users with the \"can write on CSPAllowlist\" permission (Admins by default) to punch holes in the Content Security Policy at runtime, e.g. to allow a new domain to be embedded in a dashboard iframe component. When disabled, the CSP is purely static/deploy-time and the allowlist is ignored."
|
||||
},
|
||||
{
|
||||
"name": "CSV_UPLOAD_PYARROW_ENGINE",
|
||||
"default": false,
|
||||
@@ -182,13 +188,6 @@
|
||||
"description": "Allows users to add a superset:// DB that can query across databases. Experimental with potential security/performance risks. See SUPERSET_META_DB_LIMIT.",
|
||||
"docs": "https://superset.apache.org/user-docs/databases/supported/superset-meta-database"
|
||||
},
|
||||
{
|
||||
"name": "ENABLE_VIEWERS",
|
||||
"default": false,
|
||||
"lifecycle": "testing",
|
||||
"description": "Subject-based viewer access control for dashboards and charts. When enabled, resources can have explicit viewer Subject assignments.",
|
||||
"category": "security"
|
||||
},
|
||||
{
|
||||
"name": "ESTIMATE_QUERY_COST",
|
||||
"default": false,
|
||||
@@ -263,6 +262,14 @@
|
||||
"description": "Enables CSS Templates in Settings menu and dashboard forms",
|
||||
"category": "runtime_config"
|
||||
},
|
||||
{
|
||||
"name": "DASHBOARD_RBAC",
|
||||
"default": false,
|
||||
"lifecycle": "stable",
|
||||
"description": "Role-based access control for dashboards",
|
||||
"docs": "https://superset.apache.org/docs/using-superset/creating-your-first-dashboard",
|
||||
"category": "runtime_config"
|
||||
},
|
||||
{
|
||||
"name": "DASHBOARD_VIRTUALIZATION",
|
||||
"default": true,
|
||||
@@ -400,11 +407,23 @@
|
||||
}
|
||||
],
|
||||
"deprecated": [
|
||||
{
|
||||
"name": "AVOID_COLORS_COLLISION",
|
||||
"default": true,
|
||||
"lifecycle": "deprecated",
|
||||
"description": "Avoid color collisions in charts by using distinct colors"
|
||||
},
|
||||
{
|
||||
"name": "DRILL_TO_DETAIL",
|
||||
"default": true,
|
||||
"lifecycle": "deprecated",
|
||||
"description": "Enable drill-to-detail functionality in charts"
|
||||
},
|
||||
{
|
||||
"name": "ENABLE_JAVASCRIPT_CONTROLS",
|
||||
"default": false,
|
||||
"lifecycle": "deprecated",
|
||||
"description": "Allow JavaScript in chart controls. WARNING: XSS security vulnerability!"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
BIN
docs/static/img/databases/datafusion.png
vendored
BIN
docs/static/img/databases/datafusion.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
4
docs/static/img/logos/camptocamp.svg
vendored
4
docs/static/img/logos/camptocamp.svg
vendored
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 14 KiB |
BIN
docs/static/img/logos/craig.png
vendored
BIN
docs/static/img/logos/craig.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
BIN
docs/static/img/logos/geo2france.png
vendored
BIN
docs/static/img/logos/geo2france.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 4.6 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user