Compare commits

..
Author SHA1 Message Date
Elizabeth ThompsonandClaude Sonnet 4.6 9058b49971 fix(reports): commit permalink before Playwright navigates to tab URL
CreateDashboardPermalinkCommand only flushes the INSERT when invoked
inside an outer @transaction() (the nesting guard skips the commit).
The row is therefore not visible to Playwright's separate DB connection,
causing dashboard_permalink to return a 404, the <body class="standalone">
element to never appear, and the screenshot to time out after 600s.

Commit immediately after CreateDashboardPermalinkCommand.run() returns,
matching the pattern already used by create_log() in the same class.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:50:30 +00:00
4993 changed files with 145357 additions and 489374 deletions
+2 -2
View File
@@ -105,9 +105,9 @@ Charts built from enriched datasets containing:
pre-commit install
# Quick validation (faster than --all-files)
pre-commit run # Staged files only
pre-commit run # Staged files only
pre-commit run mypy # Python type checking
pre-commit run format # Code formatting
pre-commit run prettier # Code formatting
pre-commit run eslint # Frontend linting
```
+1
View File
@@ -32,6 +32,7 @@
"ms-python.vscode-pylance",
"charliermarsh.ruff",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
}
+1
View File
@@ -59,6 +59,7 @@
"ms-python.vscode-pylance",
"charliermarsh.ruff",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
}
+9 -9
View File
@@ -1,6 +1,8 @@
# Notify all committers of DB migration changes, per SIP-59
/superset/migrations/ @mistercrunch @michael-s-molina @betodealmeida @eschutho @sadpandajoe @rusackas
# https://github.com/apache/superset/issues/13351
/superset/migrations/ @mistercrunch @michael-s-molina @betodealmeida @eschutho @sadpandajoe
# Notify some committers of changes in the components
@@ -10,35 +12,33 @@
# Notify Helm Chart maintainers about changes in it
/helm/superset/ @dpgaspar @villebro @nytai @michael-s-molina @mistercrunch @rusackas @Antonio-RiveroMartnez @hainenber
/helm/superset/ @craig-rueda @dpgaspar @villebro @nytai @michael-s-molina @mistercrunch @rusackas @Antonio-RiveroMartnez
# Notify E2E test maintainers of changes
/superset-frontend/playwright/ @sadpandajoe @geido @eschutho @rusackas @mistercrunch
/superset-frontend/cypress-base/ @sadpandajoe @geido @eschutho @rusackas @mistercrunch
/superset-frontend/cypress-base/ @sadpandajoe @geido @eschutho @rusackas @betodealmeida @mistercrunch
# Notify PMC members of changes to GitHub Actions
/.github/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @kgabryje @sha174n @dpgaspar @sadpandajoe @hainenber
/.github/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar @sadpandajoe @hainenber
# Notify PMC members of changes to CI-executed scripts (supply-chain risk:
# scripts/ files run directly in CI workflows and can execute arbitrary code)
/scripts/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @kgabryje @dpgaspar @sha174n @sadpandajoe @hainenber
/scripts/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar @sadpandajoe @hainenber
# Notify PMC members of changes to required GitHub Actions
/.asf.yaml @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @kgabryje @dpgaspar @sha174n @Antonio-RiveroMartnez
/.asf.yaml @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar @Antonio-RiveroMartnez
# Maps are a finicky contribution process we care about
**/*.geojson @villebro @rusackas
**/*.ipynb @villebro @rusackas
/superset-frontend/plugins/legacy-plugin-chart-country-map/ @villebro @rusackas
# Notify translation maintainers of changes to translations
/superset/translations/ @sfirke @rusackas @villebro @sadpandajoe @hainenber
/superset/translations/ @sfirke @rusackas
# Notify PMC members of changes to extension-related files
+2 -1
View File
@@ -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:
+3 -1
View File
@@ -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
@@ -40,7 +42,7 @@ runs:
fi
echo "python-version=$RESOLVED_VERSION" >> "$GITHUB_OUTPUT"
- name: Set up Python ${{ steps.set-python-version.outputs.python-version }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ steps.set-python-version.outputs.python-version }}
cache: ${{ inputs.cache }}
@@ -17,7 +17,6 @@ runs:
- name: Install supersetbot from npm
if: ${{ inputs.from-npm == 'true' }}
shell: bash
# zizmor: ignore[adhoc-packages] - supersetbot is a first-party Apache CLI (apache-superset/supersetbot) installed globally as a tool; a global CLI install has no application manifest/lockfile context
run: npm install -g supersetbot
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
@@ -32,7 +31,6 @@ runs:
if: ${{ inputs.from-npm == 'false' }}
shell: bash
working-directory: supersetbot
# zizmor: ignore[adhoc-packages] - installs the locally packed supersetbot tarball built from the trusted apache-superset/supersetbot checkout; no lockfile applies to a global CLI install
run: |
# simple trick to install globally with dependencies
npm pack
+306 -58
View File
@@ -3,6 +3,10 @@ enable-beta-ecosystems: true
updates:
- package-ecosystem: "github-actions"
directory: "/"
ignore:
# Ignore temporarily as release schedule is too mentally taxing for dep-handling maintainers
# Additionally, very few PRs are reviewed by this action.
- dependency-name: anthropics/claude-code-action
schedule:
interval: "daily"
cooldown:
@@ -10,15 +14,12 @@ 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: "react-error-boundary"
- dependency-name: "@rjsf/*"
# remark-gfm v4+ requires react-markdown v9+, which needs React 18
- dependency-name: "remark-gfm"
- dependency-name: "react-markdown"
# 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
@@ -35,50 +36,18 @@ updates:
# and confirm the issue https://github.com/apache/superset/issues/39600 is fixed
- dependency-name: "react-checkbox-tree"
update-types: ["version-update:semver-major"]
# Babel 8 (7.x -> 8.x) is blocked on the surrounding ecosystem: @emotion/babel-plugin
# (NodePath#hoist), babel-plugin-jsx-remove-data-test-id (t.jSXOpeningElement), and
# ts-jest all rely on Babel APIs removed in v8 and have not shipped Babel 8 support.
# Ignore the coordinated major bump until the ecosystem catches up; it must be done
# as a single manual upgrade anyway. TODO: remove when Babel 8 support is viable.
- dependency-name: "@babel/*"
update-types: ["version-update:semver-major"]
directory: "/superset-frontend/"
schedule:
interval: "daily"
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
@@ -106,24 +75,10 @@ updates:
- package-ecosystem: "npm"
directory: "/docs/"
ignore:
- dependency-name: "react-error-boundary"
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 +106,296 @@ 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/"
ignore:
# not until React >= 18.0.0
- dependency-name: "react-markdown"
- dependency-name: "remark-gfm"
- dependency-name: "react-error-boundary"
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
+3 -3
View File
@@ -114,7 +114,7 @@ testdata() {
say "::group::Load test data"
# must specify PYTHONPATH to make `tests.superset_test_config` importable
export PYTHONPATH="$GITHUB_WORKSPACE"
uv pip install --system -e .
pip install -e .
superset db upgrade
superset load_test_users
superset load_examples --load-test-data
@@ -127,7 +127,7 @@ playwright_testdata() {
say "::group::Load all examples for Playwright tests"
# must specify PYTHONPATH to make `tests.superset_test_config` importable
export PYTHONPATH="$GITHUB_WORKSPACE"
uv pip install --system -e .
pip install -e .
superset db upgrade
superset load_test_users
superset load_examples
@@ -256,7 +256,7 @@ cypress-run-all() {
# UNCOMMENT the next few commands to monitor memory usage
# monitor_memory & # Start memory monitoring in the background
# memoryMonitorPid=$!
python ../../scripts/cypress_run.py --retries 5 $USE_DASHBOARD_FLAG
python ../../scripts/cypress_run.py --parallelism $PARALLELISM --parallelism-id $PARALLEL_ID --group $PARALLEL_ID --retries 5 $USE_DASHBOARD_FLAG
# kill $memoryMonitorPid
}
+4 -4
View File
@@ -23,7 +23,7 @@ on:
jobs:
bump-python-package:
runs-on: ubuntu-slim
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.11"
python-version: "3.10"
- name: Install uv
run: pip install uv
+3 -8
View File
@@ -11,7 +11,6 @@ on:
permissions:
contents: read
pull-requests: read
actions: read
# cancel previous workflow jobs for PRs
concurrency:
@@ -20,14 +19,10 @@ concurrency:
jobs:
check-python-deps:
runs-on: ubuntu-26.04
runs-on: ubuntu-22.04
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -51,7 +46,7 @@ jobs:
- name: Login to Docker Hub
if: steps.check.outputs.python
continue-on-error: true
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2
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-slim
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check and notify
+88
View File
@@ -0,0 +1,88 @@
name: Claude PR Assistant
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
permissions:
contents: read
jobs:
check-permissions:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude'))
runs-on: ubuntu-latest
outputs:
allowed: ${{ steps.check.outputs.allowed }}
steps:
- name: Check if user is allowed
id: check
env:
COMMENTER: ${{ github.event.comment.user.login }}
run: |
# List of allowed users
ALLOWED_USERS="mistercrunch,rusackas"
echo "Checking permissions for user: $COMMENTER"
# Check if user is in allowed list
if [[ ",$ALLOWED_USERS," == *",$COMMENTER,"* ]]; then
echo "allowed=true" >> $GITHUB_OUTPUT
echo "✅ User $COMMENTER is allowed to use Claude"
else
echo "allowed=false" >> $GITHUB_OUTPUT
echo "❌ User $COMMENTER is not allowed to use Claude"
fi
deny-access:
needs: check-permissions
if: needs.check-permissions.outputs.allowed == 'false'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: Comment access denied
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
COMMENTER_LOGIN: ${{ github.event.comment.user.login || github.event.review.user.login || github.event.issue.user.login }}
with:
script: |
const commenter = process.env.COMMENTER_LOGIN;
const message = `👋 Hi @${commenter}!
Thanks for trying to use Claude Code, but currently only certain team members have access to this feature.
If you believe you should have access, please contact a project maintainer.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: message
});
claude-code-action:
needs: check-permissions
if: needs.check-permissions.outputs.allowed == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
fetch-depth: 1
- name: Run Claude PR Action
uses: anthropics/claude-code-action@5fb899572b81d2bb648d4d187173a2f423a9677c # beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
timeout_minutes: "60"
+6 -7
View File
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check for file changes
@@ -42,12 +42,11 @@ 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
contents: read
pull-requests: read
security-events: write
strategy:
@@ -58,13 +57,13 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -75,6 +74,6 @@ jobs:
# queries: security-extended,security-and-quality
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
with:
category: "/language:${{matrix.language}}"
@@ -1,63 +0,0 @@
name: Auto-approve Dependabot patch bumps
# Posts an approving review on Dependabot PRs that only bump a patch
# version, using the same trigger/guard convention already proven to work
# for Dependabot PRs in sync-requirements-for-python-dep-upgrade-pr.yml
# (plain `pull_request` gets a working, write-capable GITHUB_TOKEN here
# because Dependabot pushes branches directly to this repo, not a fork).
#
# This does NOT auto-merge anything - repo-wide auto-merge is disabled
# (Settings > General > Pull Requests > "Allow auto-merge" is off), and
# flipping that is a separate, repo-wide decision this workflow doesn't
# make on its own. Branch protection also still requires 1 approving
# review; this just means that review can already exist by the time a
# human looks at the PR, for the (large majority of) ecosystems whose
# files aren't matched by any CODEOWNERS pattern. One ecosystem - the npm
# bump under .github/actions - matches the /.github/ CODEOWNERS entry, so
# those PRs will still need a human owner's approval regardless of this
# workflow; it posts a review there too, but that alone won't satisfy the
# code-owner requirement.
on:
pull_request:
types: [opened, synchronize]
# Cancel a superseded run if Dependabot pushes to the same PR again before
# the previous run finished (matches the pattern used elsewhere in
# superset-docs-verify.yml).
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions: {}
jobs:
approve-patch-bump:
name: Approve patch-level bump
# Mirrors the guard in sync-requirements-for-python-dep-upgrade-pr.yml:
# limited to (1) PRs authored by Dependabot and (2) the upstream repo,
# since forked PRs don't get a write-capable token here anyway.
if: >
github.repository == 'apache/superset' &&
github.event.pull_request.user.login == 'dependabot[bot]' &&
github.event.pull_request.head.repo.fork == false
runs-on: ubuntu-latest
permissions:
pull-requests: write # to post the approving review via `gh pr review`
steps:
- name: Fetch Dependabot metadata
id: metadata
# This exact SHA is on ASF Infra's action allowlist
# (apache/infrastructure-actions approved_patterns.yml) as of this
# writing. Do not bump without opening an Infra ticket to allow
# the new SHA first!
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
- name: Approve patch-level bump
if: steps.metadata.outputs.update-type == 'version-update:semver-patch'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
DEPENDENCY_NAMES: ${{ steps.metadata.outputs.dependency-names }}
run: |
gh pr review --approve "$PR_URL" \
--body "Auto-approved: patch-level bump only ($DEPENDENCY_NAMES)."
+6 -10
View File
@@ -20,19 +20,19 @@ concurrency:
permissions:
contents: read
actions: read
jobs:
dependency-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-slim
runs-on: ubuntu-24.04
steps:
- name: "Checkout Repository"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: "Dependency Review"
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
continue-on-error: true
with:
fail-on-severity: critical
# compatible/incompatible licenses addressed here: https://www.apache.org/legal/resolved.html
@@ -43,19 +43,15 @@ jobs:
# the latest version. It's MIT: https://github.com/nbubna/store/blob/master/LICENSE-MIT
# pkg:npm/node-forge@1.3.1
# selecting BSD-3-Clause licensing terms for node-forge to ensure compatibility with Apache
allow-dependencies-licenses: pkg:npm/rgbcolor, pkg:npm/jszip@3.10.1
allow-dependencies-licenses: pkg:npm/store2@2.14.2, pkg:npm/node-forge@1.3.1, pkg:npm/rgbcolor, pkg:npm/jszip@3.10.1
python-dependency-liccheck:
# NOTE: Configuration for liccheck lives in our pyproject.yml.
# You cannot use a liccheck.ini file in this workflow.
runs-on: ubuntu-slim
runs-on: ubuntu-22.04
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
- name: "Checkout Repository"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
+15 -80
View File
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check for file changes
@@ -40,14 +40,14 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
setup_matrix:
runs-on: ubuntu-slim
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,28 +71,10 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Free up disk space
shell: bash
run: |
# Reclaim large preinstalled toolchains we don't use. The image
# build, and especially the docker-compose sanity check (which
# rebuilds from scratch whenever the registry cache image
# apache/superset-cache is unavailable), can otherwise exhaust the
# runner's root disk and fail with "no space left on device".
echo "Disk before cleanup:"; df -h /
sudo rm -rf \
/usr/share/dotnet \
/usr/local/lib/android \
/opt/ghc \
/usr/local/.ghcup \
/opt/hostedtoolcache/CodeQL \
/usr/local/share/boost || true
echo "Disk after cleanup:"; df -h /
- name: Setup Docker Environment
uses: ./.github/actions/setup-docker
with:
@@ -119,34 +101,13 @@ jobs:
PUSH_OR_LOAD="--load"
fi
# Retry to absorb transient Docker Hub registry errors (base-image
# pull timeouts, 504/401 on push, ECONNRESET) that otherwise fail
# the whole job. buildx reuses the buildkit layer cache from the
# failed attempt, so a retry mostly re-does just the failed push.
#
# 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" \
$PLATFORM_ARG; then
break
fi
if [ "$attempt" -eq 3 ]; then
echo "::error::supersetbot docker build failed after 3 attempts"
exit 1
fi
echo "::warning::Build attempt ${attempt} failed; retrying in 30s..."
sleep 30
done
supersetbot docker \
$PUSH_OR_LOAD \
--preset "$BUILD_PRESET" \
--context "$EVENT" \
--context-ref "$RELEASE" $FORCE_LATEST \
--extra-flags "--build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG" \
$PLATFORM_ARG
# in the context of push (using multi-platform build), we need to pull the image locally
- name: Docker pull
@@ -180,28 +141,13 @@ 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Free up disk space
shell: bash
run: |
# The sanity check rebuilds the image from scratch whenever the
# registry cache image apache/superset-cache is unavailable, which
# can exhaust the runner's root disk ("no space left on device").
echo "Disk before cleanup:"; df -h /
sudo rm -rf \
/usr/share/dotnet \
/usr/local/lib/android \
/opt/ghc \
/usr/local/.ghcup \
/opt/hostedtoolcache/CodeQL \
/usr/local/share/boost || true
echo "Disk after cleanup:"; df -h /
- name: Setup Docker Environment
uses: ./.github/actions/setup-docker
with:
@@ -213,14 +159,3 @@ jobs:
shell: bash
run: |
docker compose -f docker-compose-image-tag.yml up superset-init --exit-code-from superset-init
actions-timeline:
needs: [docker-build, docker-compose-image-tag]
if: always()
runs-on: ubuntu-26.04
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
+23 -15
View File
@@ -10,29 +10,37 @@ permissions:
contents: read
jobs:
config:
runs-on: ubuntu-24.04
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
steps:
- name: "Check for secrets"
id: check
shell: bash
run: |
if [ -n "${NPM_TOKEN}" ]; then
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
fi
env:
NPM_TOKEN: ${{ (secrets.NPM_TOKEN != '') || '' }}
build:
# Publishing uses npm trusted publishing (OIDC), so there is no NPM_TOKEN to
# gate on. Restrict to the canonical repo: forks cannot mint a valid OIDC
# token for this package and must not publish.
if: github.repository == 'apache/superset'
runs-on: ubuntu-slim
permissions:
contents: read
id-token: write # required for npm trusted publishing (OIDC)
needs: config
if: needs.config.outputs.has-secrets
runs-on: ubuntu-24.04
defaults:
run:
working-directory: superset-embedded-sdk
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
# Note: registry-url is intentionally omitted. When set, actions/setup-node
# writes an .npmrc with `_authToken=${NODE_AUTH_TOKEN}` and a placeholder
# token, which makes npm attempt token auth and skip the OIDC
# trusted-publishing exchange. With no .npmrc auth line, npm authenticates
# via OIDC against the default registry (registry.npmjs.org).
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: "./superset-embedded-sdk/.nvmrc"
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm run ci:release
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+3 -3
View File
@@ -16,15 +16,15 @@ concurrency:
jobs:
embedded-sdk-test:
runs-on: ubuntu-slim
runs-on: ubuntu-24.04
defaults:
run:
working-directory: superset-embedded-sdk
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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"
+4 -4
View File
@@ -11,7 +11,7 @@ permissions:
jobs:
config:
runs-on: ubuntu-slim
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-slim
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
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,23 +27,20 @@ jobs:
security-events: write
steps:
- name: Checkout Repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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"
- name: Install Dependencies
# Versions are pinned to avoid ad-hoc, unpinned package installs.
# Bump deliberately when upgrading.
# zizmor: ignore[adhoc-packages] - @action-validator is a global CLI tool installed to validate the repo's workflows; a global CLI install has no application manifest/lockfile context, and the versions are pinned above
run: npm install -g @action-validator/core@0.6.0 @action-validator/cli@0.6.0
run: npm install -g @action-validator/core @action-validator/cli --save-dev
- name: Run Script
run: bash .github/workflows/github-action-validator.sh
- name: Check for security issues on GHA workflows
uses: zizmorcore/zizmor-action@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
-115
View File
@@ -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}`);
}
}
@@ -2,21 +2,21 @@ name: supersetbot orglabel based on author
on:
issues:
types: [opened, edited]
types: [created, edited]
pull_request:
types: [opened, edited]
types: [created, edited]
jobs:
superbot-orglabel:
runs-on: ubuntu-slim
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
@@ -30,4 +30,4 @@ jobs:
# Label the issue with the appropriate org using supersetbot
# - this requires for the author to be publicly associated with their org
# - and for the org to be listed in `supersetbot/src/metadata.js`
supersetbot orglabel --issue ${{ github.event.number || github.event.issue.number }} --repo ${{ github.repository }} || true
supersetbot orglabel --issue ${{ github.event.number }} --repo ${{ github.repository }} || true
@@ -1,54 +0,0 @@
name: Label Merge Conflicts
# Sweeps every open PR and labels the ones GitHub reports as CONFLICTING with
# `requires:rebase` (removing it once a rebase makes the PR mergeable again),
# so the label can be used to filter the PR backlog for the ones that need a
# rebase before they can be reviewed/merged.
#
# The action itself always re-checks *every* open PR via GraphQL on each run
# regardless of what triggered it (see eps1lon/actions-label-merge-conflict's
# sources/main.ts) - there's no way to scope it to "just this PR". The
# project's own README suggests triggering on `push` (to the default branch)
# plus `pull_request_target: [synchronize]`, but on a repo with Superset's PR
# volume that combination would re-sweep the entire open-PR list on every
# merge to master *and* every push to *any* open PR - many times an hour.
# A schedule bounds that to a fixed, predictable cadence instead; adjust it
# if 2 hours turns out to be too slow or too chatty in practice.
on:
schedule:
- cron: "0 */2 * * *"
workflow_dispatch:
# Avoid two full backlog sweeps racing (a manual workflow_dispatch landing
# mid-schedule-tick, say); queue rather than cancel so an in-progress
# paginated sweep always runs to completion.
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
permissions: {}
jobs:
label-merge-conflicts:
# Scheduled/dispatch workflows still run on forks that carry this file;
# skip anywhere but the canonical repo.
if: github.repository == 'apache/superset'
name: Label Merge Conflicts
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write # to add/remove requires:rebase and need:merge
steps:
# ASF Infra allowlists this whole action via a wildcard
# (eps1lon/actions-label-merge-conflict@*), so any pinned SHA/version
# is already fine here - no Infra ticket needed for future bumps.
- uses: eps1lon/actions-label-merge-conflict@0273be72a0bbd58fcd71d0d6c02c209b50d1e5e1 # v3.1.0
with:
dirtyLabel: "requires:rebase"
# A conflicting PR isn't actually ready to merge; strip that signal
# if it was previously set so reviewers don't act on a stale one.
removeOnDirtyLabel: "need:merge"
repoToken: ${{ secrets.GITHUB_TOKEN }}
# Intentionally no commentOnDirty/commentOnClean: the label alone is
# the signal (matches the label's existing description, and avoids
# a one-time comment storm across the whole backlog on first run).
+2 -2
View File
@@ -12,9 +12,9 @@ jobs:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-slim
runs-on: ubuntu-24.04
steps:
- uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
with:
sync-labels: true
+2 -2
View File
@@ -6,13 +6,13 @@ on:
jobs:
latest-release:
name: Add/update tag to new release
runs-on: ubuntu-slim
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
+3 -3
View File
@@ -15,15 +15,15 @@ concurrency:
jobs:
license_check:
name: License Check
runs-on: ubuntu-slim
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
- name: Setup Java
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
distribution: "temurin"
java-version: "11"
-113
View File
@@ -1,113 +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.
#
# Mirror the Docker Hub service-container images that CI relies on into the
# repository's GitHub Container Registry (GHCR) namespace.
#
# WHY: CI jobs declare Postgres/MySQL/Redis/Presto as `services:` containers
# pulled anonymously from Docker Hub. Anonymous pulls share the runner's IP
# rate limit, which causes intermittent timeouts / 429s / 502s on `master`
# and same-repo PRs. The obvious fix — adding `credentials:` to the service
# blocks — breaks fork PRs hard: forks can't read secrets, so the templated
# username/password resolve to '' and GitHub rejects the workflow at parse
# time ("Unexpected value ''"), failing every fork job at "Set up job".
#
# Mirroring to GHCR sidesteps both problems: public GHCR images are pulled
# without Docker Hub's anonymous rate limit AND without any credentials, so
# the consuming workflows need no `credentials:` block and forks work
# unchanged.
#
# ONE-TIME BOOTSTRAP (maintainer, after this lands on the default branch):
# 1. Run this workflow once (Actions tab → "Mirror service images to GHCR"
# → Run workflow), or wait for the weekly schedule.
# 2. In the org's Packages settings, set each mirrored package's visibility
# to **public** (apache/superset → ci/postgres, ci/mysql, ci/redis,
# ci/presto). Public visibility is what lets fork CI pull without auth.
# 3. Only then merge the follow-up that repoints the `services.*.image`
# refs at these GHCR copies and drops the `credentials:` blocks.
#
# NOTE: this mirrors only the images declared as `services:` containers (the
# ones that broke forks). The `bde2020` hive-metastore image pulled via
# `docker compose` in the Presto/Hive job is a separate path and is left for
# a follow-up.
name: Mirror service images to GHCR
on:
schedule:
# Weekly, Monday 06:00 UTC — keeps the mirror fresh as upstream tags move.
- cron: "0 6 * * 1"
workflow_dispatch: {}
concurrency:
group: mirror-service-images
cancel-in-progress: false
permissions:
contents: read
packages: write
jobs:
mirror:
# Never run on forks: they lack both the secrets and write access to the
# apache GHCR namespace, so a scheduled run there would only ever fail.
if: github.repository == 'apache/superset'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Keep this list in sync with the `services.*.image` refs in
# superset-e2e.yml, superset-python-integrationtest.yml, and
# superset-python-presto-hive.yml.
image:
- postgres:17-alpine
- redis:7-alpine
- mysql:8.0
- starburstdata/presto:350-e.6
steps:
- name: Log in to Docker Hub (authenticated source pulls)
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to GHCR (push target)
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Copy image to GHCR
env:
# Pass the matrix value through the environment rather than
# interpolating it into the shell, to avoid template injection.
SRC_IMAGE: ${{ matrix.image }}
run: |
set -euo pipefail
# Destination keeps the image's short name (drop any namespace),
# under a `ci/` prefix in this repo's GHCR namespace.
name="${SRC_IMAGE##*/}"
dst="ghcr.io/${GITHUB_REPOSITORY}/ci/${name}"
echo "Mirroring docker.io/${SRC_IMAGE} -> ${dst}"
# imagetools copies the full (multi-arch) manifest registry-to-
# registry without a local pull/retag/push round trip.
docker buildx imagetools create --tag "${dst}" "docker.io/${SRC_IMAGE}"
echo "- \`docker.io/${SRC_IMAGE}\` → \`${dst}\`" >> "${GITHUB_STEP_SUMMARY}"
+1 -1
View File
@@ -17,7 +17,7 @@ concurrency:
jobs:
check-hold-label:
runs-on: ubuntu-slim
runs-on: ubuntu-24.04
steps:
- name: Check for 'hold' label
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+4 -10
View File
@@ -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-slim
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
+19 -115
View File
@@ -7,15 +7,9 @@ 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
actions: read
# cancel previous workflow jobs for PRs
concurrency:
@@ -24,37 +18,35 @@ 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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/
with:
python-version: ${{ matrix.python-version }}
- name: Setup Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
- name: Install helm-docs
run: go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.14.2
- name: Enable brew and helm-docs
# Add brew to the path - see https://github.com/actions/runner-images/issues/6283
run: |
echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
echo "HOMEBREW_PREFIX=$HOMEBREW_PREFIX" >>"${GITHUB_ENV}"
echo "HOMEBREW_CELLAR=$HOMEBREW_CELLAR" >>"${GITHUB_ENV}"
echo "HOMEBREW_REPOSITORY=$HOMEBREW_REPOSITORY" >>"${GITHUB_ENV}"
brew install norwoodj/tap/helm-docs
- 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"
@@ -71,105 +63,26 @@ jobs:
yarn install --immutable
- name: Cache pre-commit environments
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ~/.cache/pre-commit
key: pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
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=$?
@@ -189,12 +102,3 @@ jobs:
echo "📖 More details here: https://superset.apache.org/docs/contributing/development#git-hooks"
exit 1
fi
actions-timeline:
needs: pre-commit
if: always()
runs-on: ubuntu-26.04
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
+6 -6
View File
@@ -11,7 +11,7 @@ permissions:
jobs:
config:
runs-on: ubuntu-slim
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
# pulls all commits (needed for lerna / semantic release to correctly version)
@@ -50,13 +50,13 @@ 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"
- name: Cache npm
if: env.HAS_TAGS
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
@@ -70,7 +70,7 @@ jobs:
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- name: Cache npm
if: env.HAS_TAGS
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
@@ -1,177 +0,0 @@
name: Scheduled Docker image refresh
# Re-runs the Docker image build against the latest published release on a
# weekly cadence. The code being built doesn't change — but the base image
# layers (python:*-slim-trixie and its OS packages) DO get upstream
# security patches between Superset releases, and those patches don't
# reach our published images unless we rebuild.
#
# Without this workflow, `apache/superset:<latest>` lags behind upstream
# Debian/Python base patches by whatever interval falls between Superset
# releases (typically 36 weeks). With it, the lag drops to at most one
# week regardless of release cadence.
#
# This is a security-hygiene cron, not a release. It overwrites the
# existing tags for the most recent release (e.g. `apache/superset:5.0.0`
# and `apache/superset:latest`) with bit-for-bit-equivalent contents
# layered on a refreshed base. Image digests change; everything users
# actually pin against (image content, code, deps) does not.
on:
schedule:
# Mondays at 06:00 UTC — gives the weekend for upstream patches to
# settle and surfaces failures at the start of the work week so a
# human can react.
- cron: "0 6 * * 1"
# Manual trigger so operators can force a refresh on demand (e.g.
# immediately after a high-severity base-image CVE drops).
workflow_dispatch: {}
permissions:
contents: read
# Serialize with itself and with the release publisher (tag-release.yml) —
# both push to the same Docker Hub tags, so a race could end with stale
# layers winning. Both workflows must declare this group for the lock to work.
concurrency:
group: docker-publish-latest-release
cancel-in-progress: false
jobs:
config:
runs-on: ubuntu-26.04
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
latest-release: ${{ steps.latest.outputs.tag }}
force-latest: ${{ steps.latest.outputs.force-latest }}
steps:
- name: Check for Docker Hub secrets
id: check
shell: bash
run: |
if [ -n "${DOCKERHUB_USER}" ]; then
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
fi
env:
DOCKERHUB_USER: ${{ (secrets.DOCKERHUB_USER != '' && secrets.DOCKERHUB_TOKEN != '') || '' }}
- name: Look up latest published release
id: latest
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPOSITORY: ${{ github.repository }}
run: |
# `releases/latest` returns the latest non-prerelease, non-draft
# release — which is exactly what `apache/superset:latest`
# should reflect.
TAG=$(gh api "repos/${REPOSITORY}/releases/latest" --jq .tag_name)
if [ -z "$TAG" ] || [ "$TAG" = "null" ]; then
echo "::error::Could not determine latest release tag"
exit 1
fi
echo "Latest release: $TAG"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
# Only move `:latest` when the release flagged "latest" is also the
# highest semver release. This guards against a mis-click leaving an
# older maintenance release (e.g. a 5.x patch shipped after 6.0 GA)
# marked latest, which would otherwise roll `:latest` back a major
# version on the next cron run. If it isn't the newest, we still
# refresh that release's own version tag but leave `:latest` alone.
HIGHEST=$(gh api --paginate "repos/${REPOSITORY}/releases" \
--jq '.[] | select(.draft|not) | select(.prerelease|not) | .tag_name' \
| sed 's/^v//' | sort -V | tail -n1)
if [ "${TAG#v}" = "$HIGHEST" ]; then
echo "force-latest=1" >> "$GITHUB_OUTPUT"
else
echo "::warning::Latest-flagged release $TAG is not the highest semver ($HIGHEST); refreshing its version tag but leaving :latest untouched"
fi
docker-rebuild:
needs: config
if: needs.config.outputs.has-secrets == '1'
name: docker-rebuild
runs-on: ubuntu-26.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"]
fail-fast: false
steps:
- name: "Checkout release tag: ${{ needs.config.outputs.latest-release }}"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.config.outputs.latest-release }}
fetch-depth: 0
persist-credentials: false
- name: Setup Docker Environment
uses: ./.github/actions/setup-docker
with:
dockerhub-user: ${{ secrets.DOCKERHUB_USER }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
install-docker-compose: "false"
build: "true"
- name: Use Node.js 20
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 20
- name: Setup supersetbot
uses: ./.github/actions/setup-supersetbot/
- name: Rebuild and push
env:
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_PRESET: ${{ matrix.build_preset }}
LATEST_RELEASE: ${{ needs.config.outputs.latest-release }}
FORCE_LATEST_FLAG: ${{ needs.config.outputs.force-latest == '1' && '--force-latest' || '' }}
run: |
# Reuses the same supersetbot invocation as the release
# publisher (`tag-release.yml`), so the resulting tags are
# identical to what a manual release dispatch would produce —
# just with a freshly-pulled base image layer underneath.
# `--force-latest` is only passed when the config job confirmed the
# fetched release is the newest one (see FORCE_LATEST_FLAG above).
supersetbot docker \
--push \
--preset "$BUILD_PRESET" \
--context release \
--context-ref "$LATEST_RELEASE" \
$FORCE_LATEST_FLAG \
--platform "linux/arm64" \
--platform "linux/amd64"
# The whole point of this cron is catching base-image CVEs, so a silent
# failure is the expensive case — a red X in the Actions tab nobody is
# watching on a Monday. File a tracked issue when any rebuild leg fails so
# a missed security refresh surfaces instead of sitting unnoticed.
notify-on-failure:
needs: [config, docker-rebuild]
if: failure() && needs.config.outputs.has-secrets == '1'
runs-on: ubuntu-26.04
permissions:
contents: read
issues: write
steps:
- name: Open a tracking issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPOSITORY: ${{ github.repository }}
LATEST_RELEASE: ${{ needs.config.outputs.latest-release }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
gh issue create \
--repo "$REPOSITORY" \
--title "Scheduled Docker image refresh failed for ${LATEST_RELEASE}" \
--label "infra:container" \
--label "bug" \
--body "The weekly Docker base-image refresh failed for release \`${LATEST_RELEASE}\`. Published images may be missing upstream base-layer security patches until this is resolved.
Failed run: ${RUN_URL}"
+3 -4
View File
@@ -24,14 +24,13 @@ jobs:
permissions:
contents: read
issues: write # delete orphaned showtime label definitions (label CRUD is the issues API)
pull-requests: write
steps:
- name: Install Superset Showtime
run: pip install superset-showtime
- name: Cleanup expired environments and orphaned labels
- name: Cleanup expired environments
run: |
echo "Cleaning up environments respecting TTL labels, and pruning orphaned 🎪 labels"
python -m showtime cleanup --respect-ttl --force
echo "Cleaning up environments respecting TTL labels"
python -m showtime cleanup --respect-ttl
+2 -7
View File
@@ -2,8 +2,7 @@ name: 🎪 Superset Showtime
# Ultra-simple: just sync on any PR state change
on:
# zizmor: ignore[dangerous-triggers] - required to react to PR label changes; PR code is
# only checked out and built after the maintainer-authorization gate (write/admin actors)
# zizmor: ignore[dangerous-triggers] - required to react to PR label changes; this workflow does not check out or execute PR-provided code
pull_request_target:
types: [labeled, unlabeled, synchronize, closed]
@@ -153,14 +152,10 @@ 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ steps.check.outputs.target_sha }}
persist-credentials: false
# Building fork PR code is Showtime's purpose: deploys are gated on the
# maintainer-authorization step above (write/admin actors only), so this
# checkout is an explicit, authorized opt-in rather than an automatic one.
allow-unsafe-pr-checkout: true
- name: Setup Docker Environment (only if build needed)
if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.build_needed == 'true'
+3 -8
View File
@@ -11,7 +11,6 @@ on:
permissions:
contents: read
pull-requests: read
actions: read
# cancel previous workflow jobs for PRs
concurrency:
@@ -20,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,12 +40,8 @@ jobs:
ports:
- 16379:6379
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -66,7 +61,7 @@ jobs:
- name: superset init
if: steps.check.outputs.python
run: |
uv pip install --system -e .
pip install -e .
superset db upgrade
superset load_test_users
- name: superset load_examples
+5 -10
View File
@@ -30,11 +30,10 @@ concurrency:
permissions:
contents: read
actions: read
jobs:
config:
runs-on: ubuntu-26.04
runs-on: ubuntu-24.04
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
steps:
@@ -58,25 +57,21 @@ jobs:
(github.event_name != 'workflow_run' ||
github.event.workflow_run.head_repository.full_name == github.repository)
name: Build & Deploy
runs-on: ubuntu-26.04
runs-on: ubuntu-24.04
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
- name: "Checkout ${{ github.event.workflow_run.head_sha || github.sha }}"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
distribution: "zulu"
java-version: "21"
+38 -53
View File
@@ -21,79 +21,64 @@ permissions:
contents: read
jobs:
lychee:
# See docs here: https://github.com/lycheeverse/lychee-action
linkinator:
# See docs here: https://github.com/marketplace/actions/linkinator
# Only run on pull_request, not workflow_run
if: github.event_name == 'pull_request'
name: Link Checking
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
# Restores the on-disk request cache (.lycheecache) lychee writes when
# `--cache` is set, so re-runs don't re-check every external link from
# scratch. Key never matches exactly (github.run_id is unique per run),
# so actions/cache always saves a fresh copy afterwards; restore-keys
# falls back to the most recent prior cache on lookup.
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .lycheecache
key: lychee-cache-${{ github.run_id }}
restore-keys: lychee-cache-
# v2.8.0 and v2.9.0 are both on ASF Infra's action allowlist
# (apache/infrastructure-actions approved_patterns.yml) as of this
# writing. Do not bump past v2.9.0 without opening an Infra ticket
# to allow the new SHA first!
- uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
# Do not bump this linkinator-action version without opening
# an ASF Infra ticket to allow the new version first!
- uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
continue-on-error: true # This will make the job advisory (non-blocking, no red X)
with:
fail: false
args: >-
--verbose --no-progress
--cache --max-cache-age 7d
--exclude '^https://github.com/apache/(superset|incubator-superset)/(pull|issues)/\d+'
--exclude '^https://github.com/apache/(superset|incubator-superset)/commit/[a-f0-9]+'
--exclude 'superset-frontend/.*CHANGELOG\.md'
--exclude 'http://localhost:8088/'
--exclude 'http://127.0.0.1:3000/'
--exclude 'http://localhost:9001/'
--exclude 'https://charts.bitnami.com/bitnami'
--exclude 'https://www.li.me/'
--exclude 'https://www.fanatics.com/'
--exclude 'https://tails.com/gb/'
--exclude 'https://www.techaudit.info/'
--exclude 'https://avetilearning.com/'
--exclude 'https://www.udemy.com/'
--exclude 'https://trustmedis.com/'
--exclude 'http://theiconic.com.au/'
--exclude 'https://dev.mysql.com/doc/refman/5.7/en/innodb-limits.html'
--exclude '^https://img\.shields\.io/.*'
--exclude 'https://vkusvill.ru/'
--exclude 'https://www.linkedin.com/in/mark-thomas-b16751158/'
--exclude 'https://theiconic.com.au/'
--exclude 'https://wattbewerb.de/'
--exclude 'https://timbr.ai/'
--exclude 'https://opensource.org/license/apache-2-0'
--exclude 'https://www.plaidcloud.com/'
'./**/*.md' './**/*.mdx'
paths: "**/*.md, **/*.mdx"
linksToSkip: >-
^https://github.com/apache/(superset|incubator-superset)/(pull|issues)/\d+,
^https://github.com/apache/(superset|incubator-superset)/commit/[a-f0-9]+,
superset-frontend/.*CHANGELOG\.md,
http://localhost:8088/,
http://127.0.0.1:3000/,
http://localhost:9001/,
https://charts.bitnami.com/bitnami,
https://www.li.me/,
https://www.fanatics.com/,
https://tails.com/gb/,
https://www.techaudit.info/,
https://avetilearning.com/,
https://www.udemy.com/,
https://trustmedis.com/,
http://theiconic.com.au/,
https://dev.mysql.com/doc/refman/5.7/en/innodb-limits.html,
^https://img\.shields\.io/.*,
https://vkusvill.ru/,
https://www.linkedin.com/in/mark-thomas-b16751158/,
https://theiconic.com.au/,
https://wattbewerb.de/,
https://timbr.ai/,
https://opensource.org/license/apache-2-0,
https://www.plaidcloud.com/
build-on-pr:
# 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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
@@ -121,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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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
+26 -43
View File
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check for file changes
@@ -49,21 +49,28 @@ 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
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.frontend == 'true'
# Somehow one test flakes on 24.04 for unknown reasons, this is the only GHA left on 22.04
runs-on: ubuntu-22.04
timeout-minutes: 30
permissions:
contents: read
pull-requests: read
strategy:
# when one test fails, DO NOT cancel the other
# app_root variant, because this will kill Cypress processes
# parallel_id, because this will kill Cypress processes
# leaving the Dashboard hanging ...
# https://github.com/cypress-io/github-action/issues/48
fail-fast: false
matrix:
parallel_id: [0, 1]
browser: ["chrome"]
app_root: ${{ github.event_name == 'push' && fromJSON('["", "/app/prefix"]') || fromJSON('[""]') }}
# The /app/prefix variant (push events only) is smoke-tested on a single
# shard rather than the full matrix, so exclude it from the other shards.
exclude:
- parallel_id: 1
app_root: "/app/prefix"
env:
SUPERSET_ENV: development
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
@@ -90,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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Checkout using ref (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
ref: ${{ github.event.inputs.ref }}
submodules: recursive
- name: Checkout using PR ID (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
@@ -121,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"
@@ -142,6 +149,8 @@ jobs:
uses: ./.github/actions/cached-dependencies
env:
CYPRESS_BROWSER: ${{ matrix.browser }}
PARALLEL_ID: ${{ matrix.parallel_id }}
PARALLELISM: 2
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
NODE_OPTIONS: "--max-old-space-size=4096"
with:
@@ -159,12 +168,12 @@ jobs:
if: failure()
with:
path: ${{ github.workspace }}/superset-frontend/cypress-base/cypress/screenshots
name: cypress-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}--${{ steps.set-safe-app-root.outputs.safe_app_root }}
name: cypress-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}-${{ matrix.parallel_id }}--${{ steps.set-safe-app-root.outputs.safe_app_root }}
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
@@ -198,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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Checkout using ref (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
ref: ${{ github.event.inputs.ref }}
submodules: recursive
- name: Checkout using PR ID (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
@@ -229,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"
@@ -256,21 +265,6 @@ jobs:
NODE_OPTIONS: "--max-old-space-size=4096"
with:
run: playwright-run "${{ matrix.app_root }}"
- name: Run Playwright (Soft-delete Tests)
uses: ./.github/actions/cached-dependencies
env:
NODE_OPTIONS: "--max-old-space-size=4096"
# Scoped to this step: each playwright-run boots its own gunicorn
# with the step's env, so the Required Tests server above keeps
# master's Flask configuration while this one runs with SOFT_DELETE
# on — the same isolation pattern as the Embedded step in
# superset-playwright.yml. Without a flag-on server the
# recently-archived specs skip themselves everywhere and ship zero
# executed E2E coverage; in the Required run above they are
# collected and skipped, which is expected.
SUPERSET_FEATURE_SOFT_DELETE: "true"
with:
run: playwright-run "${{ matrix.app_root }}" recently-archived/
- name: Set safe app root
if: failure()
id: set-safe-app-root
@@ -307,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:
@@ -329,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:
@@ -347,14 +341,3 @@ jobs:
exit 1
fi
echo "playwright-tests result: $RESULT (changes: $CHANGES)"
actions-timeline:
needs: [cypress-matrix, playwright-tests, cypress-matrix-required, playwright-tests-required]
if: always()
runs-on: ubuntu-26.04
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
+3 -13
View File
@@ -11,7 +11,6 @@ on:
permissions:
contents: read
pull-requests: read
actions: read
# cancel previous workflow jobs for PRs
concurrency:
@@ -20,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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -70,12 +69,3 @@ jobs:
with:
name: superset-extensions-cli-coverage-html
path: htmlcov/
actions-timeline:
needs: test-superset-extensions-cli-package
if: always()
runs-on: ubuntu-26.04
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
+22 -32
View File
@@ -21,19 +21,13 @@ permissions:
jobs:
frontend-build:
runs-on: ubuntu-26.04
runs-on: ubuntu-24.04
timeout-minutes: 30
# The change detector reads the PR's file list, which needs
# `pull-requests: read`. Public repos serve that endpoint without it;
# private forks return 403.
permissions:
contents: read
pull-requests: read
outputs:
should-run: ${{ steps.check.outputs.frontend }}
steps:
- name: Checkout Code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
fetch-depth: 0
@@ -57,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 \
.
@@ -80,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
@@ -110,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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
fetch-depth: 0
@@ -128,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
@@ -165,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
@@ -177,11 +183,6 @@ jobs:
run: |
zstd -d < docker-image.tar.zst | docker load
- name: Validate dependency tree
run: |
docker run --rm $TAG bash -c \
"npm ls --all --package-lock-only --depth=0 --json > /dev/null"
- name: Build Plugins Packages
run: |
docker run --rm $TAG bash -c \
@@ -190,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
@@ -206,14 +207,3 @@ jobs:
run: |
docker run --rm $TAG bash -c \
"npm run build-storybook && npx playwright install-deps && npx playwright install chromium && npm run test-storybook:ci"
actions-timeline:
needs: [report-coverage, lint-frontend, validate-frontend, test-storybook]
if: always()
runs-on: ubuntu-26.04
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
@@ -1,77 +0,0 @@
name: "Helm: lint and test charts"
on:
pull_request:
types: [opened, reopened, synchronize]
paths:
- "helm/**"
permissions:
contents: read
actions: 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.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: false
jobs:
lint-test:
runs-on: ubuntu-slim
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
submodules: recursive
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
with:
version: v3.21.3
- name: Setup Python
uses: ./.github/actions/setup-backend/
with:
install-superset: "false"
# Still vendored (not de-vendored like chart-releaser-action below): the
# allowlisted helm/chart-testing-action@v2.8.0 depends internally on
# astral-sh/setup-uv@v7.0.0, which isn't itself on the ASF Actions
# allowlist (only v8.1.0+ are, at apache/infrastructure-actions'
# actions.yml). Needs an INFRA request before this can de-vendor too.
- name: Set up chart-testing
uses: ./.github/actions/chart-testing-action
- name: Run chart-testing (list-changed)
id: list-changed
run: |
changed=$(ct list-changed --print-config)
if [[ -n "$changed" ]]; then
echo "changed=true" >> $GITHUB_OUTPUT
fi
env:
CT_CHART_DIRS: helm
CT_SINCE: HEAD
- name: Run chart-testing (lint)
run: ct lint --print-config
env:
CT_CHART_DIRS: helm
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
+58
View File
@@ -0,0 +1,58 @@
name: "Helm: lint and test charts"
on:
pull_request:
types: [opened, edited, reopened, synchronize]
paths:
- "helm/**"
permissions:
contents: read
# cancel previous workflow jobs for PRs
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
lint-test:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
with:
version: v3.16.4
- name: Setup Python
uses: ./.github/actions/setup-backend/
with:
install-superset: "false"
- name: Set up chart-testing
uses: ./.github/actions/chart-testing-action
- name: Run chart-testing (list-changed)
id: list-changed
run: |
changed=$(ct list-changed --print-config)
if [[ -n "$changed" ]]; then
echo "changed=true" >> $GITHUB_OUTPUT
fi
env:
CT_CHART_DIRS: helm
CT_SINCE: HEAD
- name: Run chart-testing (lint)
run: ct lint --print-config
env:
CT_CHART_DIRS: helm
CT_LINT_CONF: lintconf.yaml
CT_SINCE: HEAD
CT_CHART_REPOS: bitnami=https://charts.bitnami.com/bitnami
+20 -86
View File
@@ -1,9 +1,6 @@
# This workflow automates the release process for Helm charts.
# Each run force-recreates a single 'helm-publish' branch from the tip of 'gh-pages' and
# opens (or reuses) one pull request against 'gh-pages', allowing the changes to be
# reviewed and merged manually. Because chart-releaser rebuilds index.yaml from all
# published GitHub releases, the branch always contains every chart released since the
# last merge, and the PR can never go stale or conflict with gh-pages.
# The workflow creates a new branch for the release and opens a pull request against the 'gh-pages' branch,
# allowing the changes to be reviewed and merged manually.
name: "Helm: release charts"
@@ -21,15 +18,9 @@ on:
required: false
default: "master"
# Serialize runs: concurrent runs would race on force-pushing the shared
# helm-publish branch while chart-releaser is mid-release.
concurrency:
group: helm-release
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-26.04
runs-on: ubuntu-24.04
permissions:
contents: write
pull-requests: write
@@ -38,7 +29,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.ref || github.ref_name }}
persist-credentials: true
@@ -51,7 +42,7 @@ jobs:
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
with:
version: v3.5.4
@@ -66,9 +57,9 @@ jobs:
echo "DEBUG TAGS"
git show-ref --tags
- name: Set pages branch name
- name: Create unique pages branch name
id: vars
run: echo "branch_name=helm-publish" >> $GITHUB_ENV
run: echo "branch_name=helm-publish-${GITHUB_SHA:0:7}" >> $GITHUB_ENV
- name: Force recreate branch from gh-pages
env:
@@ -95,23 +86,23 @@ jobs:
# Return to the original branch
git checkout local_gha_temp
- name: Fetch/list all tags
run: |
git submodule update
cat .github/actions/chart-releaser-action/action.yml
- name: Run chart-releaser
uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f # v1.7.0
uses: ./.github/actions/chart-releaser-action
with:
version: v1.6.0
charts_dir: helm
mark_as_latest: false
# A helm/** change without a Chart.yaml version bump repackages the
# already-released version; without this, cr aborts on the existing
# release tag (422 already_exists) instead of proceeding to rebuild
# the index, and the whole run fails.
skip_existing: true
pages_branch: ${{ env.branch_name }}
env:
CR_TOKEN: "${{ github.token }}"
CR_RELEASE_NAME_TEMPLATE: "superset-helm-chart-{{ .Version }}"
- name: Open or reuse Pull Request
- name: Open Pull Request
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
@@ -122,72 +113,15 @@ jobs:
throw new Error("Branch name is not defined.");
}
// The branch is force-recreated from gh-pages on every run, so an
// already-open PR for it now reflects this run's charts; opening
// another would both fail (422) and recreate the stale-PR pileup
// this fixed branch exists to avoid.
const { data: existing } = await github.rest.pulls.list({
const pr = await github.rest.pulls.create({
owner,
repo,
state: "open",
head: `${owner}:${branchName}`,
base: "gh-pages",
title: `Helm chart release for ${branchName}`,
head: branchName,
base: "gh-pages", // Adjust if the target branch is different
body: `This PR releases Helm charts to the gh-pages branch.`,
});
let current;
if (existing.length > 0) {
current = existing[0];
core.info(`Reusing existing pull request: ${current.html_url}`);
} else {
const { data: pr } = await github.rest.pulls.create({
owner,
repo,
title: "Helm chart release",
head: branchName,
base: "gh-pages", // Adjust if the target branch is different
body: [
"This PR releases Helm charts to the gh-pages branch.",
"",
"It is force-updated from the tip of `gh-pages` by every release run,",
"so it always contains every chart released since the last merge and",
"never needs to be closed as superseded.",
].join("\n"),
});
current = pr;
core.info(`Pull request created: ${current.html_url}`);
}
// Sweep release PRs left open by older runs (per-SHA helm-publish-*
// branches from the previous scheme). Their content is a subset of
// the evergreen PR, so close them with a pointer to it.
const { data: openPrs } = await github.rest.pulls.list({
owner,
repo,
state: "open",
base: "gh-pages",
per_page: 100,
});
for (const stale of openPrs) {
if (
stale.number !== current.number &&
stale.head.repo?.full_name === process.env.GITHUB_REPOSITORY &&
stale.head.ref.startsWith("helm-publish")
) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: stale.number,
body: `Superseded by #${current.number}, which now carries all unreleased charts. Closing.`,
});
await github.rest.pulls.update({
owner,
repo,
pull_number: stale.number,
state: "closed",
});
core.info(`Closed superseded release PR #${stale.number}`);
}
}
core.info(`Pull request created: ${pr.data.html_url}`);
env:
BRANCH_NAME: ${{ env.branch_name }}
+7 -18
View File
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Checkout using ref (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
ref: ${{ github.event.inputs.ref }}
submodules: recursive
- name: Checkout using PR ID (workflow_dispatch)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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"
@@ -170,14 +170,3 @@ jobs:
${{ github.workspace }}/superset-frontend/playwright-results/
${{ github.workspace }}/superset-frontend/test-results/
name: playwright-experimental-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}--${{ steps.set-safe-app-root.outputs.safe_app_root }}
actions-timeline:
needs: playwright-tests-experimental
if: always()
runs-on: ubuntu-26.04
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
@@ -1,11 +1,6 @@
# Python integration tests
name: Python-Integration
# Least-privilege default for GITHUB_TOKEN. Jobs that need more (e.g. OIDC for
# codecov uploads) opt in via their own job-level `permissions:` block.
permissions:
contents: read
on:
push:
branches:
@@ -20,7 +15,7 @@ concurrency:
jobs:
changes:
runs-on: ubuntu-26.04
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
@@ -29,7 +24,7 @@ jobs:
python: ${{ steps.check.outputs.python }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check for file changes
@@ -41,7 +36,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 +67,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -127,7 +122,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 +130,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 +152,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -188,7 +183,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 +202,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -243,7 +238,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
@@ -255,14 +250,3 @@ jobs:
exit 1
fi
echo "test-postgres result: $RESULT"
actions-timeline:
needs: [test-mysql, test-postgres, test-sqlite, test-postgres-required]
if: always()
runs-on: ubuntu-26.04
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -149,7 +149,7 @@ jobs:
run: celery-worker
- name: Python unit tests (PostgreSQL)
run: |
uv pip install --system -e .[hive]
pip install -e .[hive]
./scripts/python_tests.sh -m 'chart_data_flow or sql_json_flow'
- name: Upload code coverage
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
@@ -158,14 +158,3 @@ jobs:
verbose: true
use_oidc: true
slug: apache/superset
actions-timeline:
needs: [test-postgres-presto, test-postgres-hive]
if: always()
runs-on: ubuntu-26.04
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
+6 -24
View File
@@ -1,11 +1,6 @@
# Python unit tests
name: Python-Unit
# Least-privilege default for GITHUB_TOKEN. Jobs that need more (e.g. OIDC for
# codecov uploads) opt in via their own job-level `permissions:` block.
permissions:
contents: read
on:
push:
branches:
@@ -21,7 +16,7 @@ concurrency:
jobs:
changes:
runs-on: ubuntu-26.04
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
@@ -30,7 +25,7 @@ jobs:
python: ${{ steps.check.outputs.python }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Check for file changes
@@ -42,7 +37,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 +45,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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -99,16 +88,9 @@ jobs:
unit-tests-required:
needs: [changes, unit-tests]
if: always()
runs-on: ubuntu-26.04
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
- name: Check unit-tests result
env:
RESULT: ${{ needs.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'
+5 -16
View File
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
submodules: recursive
@@ -153,14 +153,3 @@ jobs:
- name: Fail if regression detected
if: steps.regression.outcome == 'failure'
run: exit 1
actions-timeline:
needs: [frontend-check-translations, babel-extract]
if: always()
runs-on: ubuntu-26.04
permissions:
actions: read
steps:
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
with:
expand-composite-actions: true
+4 -8
View File
@@ -21,17 +21,13 @@ concurrency:
jobs:
app-checks:
runs-on: ubuntu-slim
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: './superset-websocket/.nvmrc'
- name: Install dependencies
working-directory: ./superset-websocket
run: npm ci
@@ -41,9 +37,9 @@ jobs:
- name: typescript checks
working-directory: ./superset-websocket
run: npm run type
- name: code format check
- name: prettier
working-directory: ./superset-websocket
run: npm run format-check
run: npm run prettier-check
- name: unit tests
working-directory: ./superset-websocket
run: npm run test
+2 -2
View File
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
@@ -1,60 +0,0 @@
name: Sync requirements for Python dependency PRs
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: write
pull-requests: read
jobs:
sync-python-dep-requirements:
# This action is limited for (1) PRs authored by Dependabot and (2) upstream repo due to write back to remote
if: github.repository == 'apache/superset' && github.event.pull_request.user.login == 'dependabot[bot]' && github.event.pull_request.head.repo.fork == false
runs-on: ubuntu-26.04
steps:
- name: Fetch Dependabot metadata
id: dependabot-metadata
shell: bash
env:
BRANCH_NAME: ${{ github.head_ref }}
run: |
# Get current branch name, extract the package ecosystem and return as GHA step output
packageEcosystem=$(echo "$BRANCH_NAME" | cut -d'/' -f2)
echo "package-ecosystem=$packageEcosystem" >> $GITHUB_OUTPUT
# zizmor: ignore[artipacked] - required persisted credentials to push synced requirement changes back to remote
- name: Checkout source code
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: true
# Authenticate the Docker daemon so the python:slim pull in
# uv-pip-compile.sh uses our (much higher) authenticated rate limit
# instead of the shared-runner anonymous one.
- name: Login to Docker Hub
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
continue-on-error: true
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Sync requirements in containerized environment
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
run: ./scripts/uv-pip-compile.sh
- name: Push changes to remote PRs
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add requirements
git diff --cached --quiet && exit 0
git commit --signoff --message "build(deps): sync pinned requirements for Dependabot pip PRs"
git push origin "HEAD:refs/heads/${GITHUB_EVENT_PULL_REQUEST_HEAD_REF}"
env:
GITHUB_EVENT_PULL_REQUEST_HEAD_REF: ${{ github.event.pull_request.head.ref }}
+8 -14
View File
@@ -24,15 +24,9 @@ on:
permissions:
contents: read
# Serialize with the scheduled Docker image refresh — both workflows push
# to the same Docker Hub tags and must not race on apache/superset:latest.
concurrency:
group: docker-publish-latest-release
cancel-in-progress: false
jobs:
config:
runs-on: ubuntu-slim
runs-on: ubuntu-24.04
outputs:
has-secrets: ${{ steps.check.outputs.has-secrets }}
steps:
@@ -50,17 +44,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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
fetch-depth: 0
@@ -75,7 +69,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 +114,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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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
+4 -4
View File
@@ -11,7 +11,7 @@ permissions:
jobs:
config:
runs-on: ubuntu-slim
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-slim
runs-on: ubuntu-24.04
name: Generate Reports
steps:
- name: Checkout Repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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"
+1 -1
View File
@@ -7,7 +7,7 @@ on:
jobs:
welcome:
runs-on: ubuntu-slim
runs-on: ubuntu-24.04
if: github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
permissions:
pull-requests: write
-1
View File
@@ -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/
+6
View File
@@ -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
@@ -30,6 +33,9 @@
[submodule ".github/actions/chart-testing-action"]
path = .github/actions/chart-testing-action
url = https://github.com/helm/chart-testing-action
[submodule ".github/actions/chart-releaser-action"]
path = .github/actions/chart-releaser-action
url = https://github.com/helm/chart-releaser-action
[submodule ".github/actions/github-action-push-to-another-repository"]
path = .github/actions/github-action-push-to-another-repository
url = https://github.com/cpina/github-action-push-to-another-repository
Executable → Regular
+7 -14
View File
@@ -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,
@@ -62,9 +61,9 @@ repos:
args: ["--markdown-linebreak-ext=md"]
- repo: local
hooks:
- id: oxfmt-frontend
name: oxfmt (frontend)
entry: bash -c 'cd superset-frontend && files=(); for f in "$@"; do files+=("${f#superset-frontend/}"); done; npx oxfmt --write --no-error-on-unmatched-pattern -- "${files[@]}"' --
- id: prettier-frontend
name: prettier (frontend)
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
+2 -1
View File
@@ -7,9 +7,9 @@
.codecov.yml
.eslintrc
.eslintignore
.stylelintignore
.flake8
.nvmrc
.prettierrc
.rat-excludes
.swcrc
.*log
@@ -63,6 +63,7 @@ tsconfig.tsbuildinfo
.*yml
.*iml
.esprintrc
.prettierignore
generator-superset/*
temporary_superset_ui/*
+2 -2
View File
@@ -19,7 +19,7 @@ git commit --amend # or new commit
```
Common pre-commit failures:
- **Formatting** - black, oxfmt, eslint will auto-fix
- **Formatting** - black, prettier, eslint will auto-fix
- **Type errors** - mypy failures need manual fixes
- **Linting** - ruff, pylint issues need manual fixes
@@ -263,7 +263,7 @@ git add . # Pre-commit only checks staged files
# Quick validation (faster than --all-files)
pre-commit run # Staged files only
pre-commit run mypy # Python type checking
pre-commit run format # Code formatting
pre-commit run prettier # Code formatting
pre-commit run eslint # Frontend linting
```
-1
View File
@@ -50,4 +50,3 @@ under the License.
- [4.1.4](./CHANGELOG/4.1.4.md)
- [5.0.0](./CHANGELOG/5.0.0.md)
- [6.0.0](./CHANGELOG/6.0.0.md)
- [6.1.0](./CHANGELOG/6.1.0.md)
-1563
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -120,7 +120,7 @@ RUN useradd --user-group -d ${SUPERSET_HOME} -m --no-log-init --shell /bin/bash
# Some bash scripts needed throughout the layers
COPY --chmod=755 docker/*.sh /app/docker/
RUN pip install --no-cache-dir --upgrade uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
# Using uv as it's faster/simpler than pip
RUN uv venv /app/.venv
@@ -141,7 +141,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
COPY superset/translations/ /app/translations_mo/
RUN if [ "${BUILD_TRANSLATIONS}" = "true" ]; then \
pybabel compile --use-fuzzy -d /app/translations_mo || true; \
pybabel compile -d /app/translations_mo | true; \
fi; \
rm -f /app/translations_mo/*/*/*.[po,json]
+9 -16
View File
@@ -15,22 +15,19 @@
# 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
install: superset pre-commit
superset:
# Bootstrap uv (the project's installer) into the active environment
pip install uv
# Install external dependencies
uv pip install -r requirements/development.txt
pip install -r requirements/development.txt
# Install Superset in editable (development) mode
uv pip install -e .
pip install -e .
# Create an admin user in your metadata database
superset fab create-admin \
@@ -55,14 +52,11 @@ superset:
update: update-py update-js
update-py:
# Bootstrap uv (the project's installer) into the active environment
pip install uv
# Install external dependencies
uv pip install -r requirements/development.txt
pip install -r requirements/development.txt
# Install Superset in editable (development) mode
uv pip install -e .
pip install -e .
# Initialize the database
superset db upgrade
@@ -76,7 +70,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
@@ -85,8 +79,7 @@ activate:
pre-commit:
# setup pre commit dependencies
pip install uv
uv pip install -r requirements/development.txt
pip3 install -r requirements/development.txt
pre-commit install
format: py-format js-format
@@ -95,7 +88,7 @@ py-format: pre-commit
pre-commit run black --all-files
js-format:
cd superset-frontend; npm run format
cd superset-frontend; npm run prettier
flask-app:
flask run -p 8088 --reload --debugger
+12 -9
View File
@@ -29,7 +29,7 @@ under the License.
[![Last Commit](https://img.shields.io/github/last-commit/apache/superset)](https://github.com/apache/superset/commits/master)
[![Open Issues](https://img.shields.io/github/issues/apache/superset)](https://github.com/apache/superset/issues)
[![Open PRs](https://img.shields.io/github/issues-pr/apache/superset)](https://github.com/apache/superset/pulls)
[![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://bit.ly/join-superset-slack)
[![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](http://bit.ly/join-superset-slack)
[![Documentation](https://img.shields.io/badge/docs-apache.org-blue.svg)](https://superset.apache.org)
<picture width="500">
@@ -186,7 +186,7 @@ Try out Superset's [quickstart](https://superset.apache.org/docs/quickstart/) gu
## Get Involved
- Ask and answer questions on [StackOverflow](https://stackoverflow.com/questions/tagged/apache-superset) using the **apache-superset** tag
- [Join our community's Slack](https://bit.ly/join-superset-slack)
- [Join our community's Slack](http://bit.ly/join-superset-slack)
and please read our [Slack Community Guidelines](https://github.com/apache/superset/blob/master/CODE_OF_CONDUCT.md#slack-community-guidelines)
- [Join our dev@superset.apache.org Mailing list](https://lists.apache.org/list.html?dev@superset.apache.org). To join, simply send an email to [dev-subscribe@superset.apache.org](mailto:dev-subscribe@superset.apache.org)
- Follow us on social media:
@@ -247,13 +247,16 @@ Understanding the Superset Points of View
- [Superset API](https://superset.apache.org/docs/rest-api)
<!--
The OSS Insight "Repo Activity" widget (https://next.ossinsight.io/) was
intentionally removed. This README is rendered on the ASF-hosted website
(superset.apache.org), so its contents are subject to ASF's third-party
content and CSP rules. OSS Insight has no Data Processing Agreement (DPA)
with the ASF, so we cannot embed its images/widgets here. Do not re-add it.
-->
## Repo Activity
<a href="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats?repo_id=39464018" target="_blank" align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats/thumbnail.png?repo_id=39464018&image_size=auto&color_scheme=dark" width="655" height="auto" />
<img alt="Performance Stats of apache/superset - Last 28 days" src="https://next.ossinsight.io/widgets/official/compose-last-28-days-stats/thumbnail.png?repo_id=39464018&image_size=auto&color_scheme=light" width="655" height="auto" />
</picture>
</a>
<!-- Made with [OSS Insight](https://ossinsight.io/) -->
<!-- telemetry/analytics pixel: -->
<img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=bc1c90cd-bc04-4e11-8c7b-289fb2839492" />
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 \
+1 -1
View File
@@ -423,7 +423,7 @@ git push origin ${SUPERSET_VERSION}
### Publishing a Convenience Release to PyPI
Extract the release to the `/tmp` folder to build the PyPI release. Files in the `/tmp` folder will be automatically deleted by the OS.
Extract the release to the `/tmp` folder to build the PiPY release. Files in the `/tmp` folder will be automatically deleted by the OS.
```bash
mkdir -p /tmp/superset && cd /tmp/superset
+2 -2
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
@@ -17,6 +15,8 @@
# specific language governing permissions and limitations
# under the License.
#!/bin/bash
# Function to determine Python command
get_python_command() {
if command -v python3 &>/dev/null; then
+26 -88
View File
@@ -83,9 +83,6 @@ categories:
- name: Clark.de
url: https://clark.de/
- name: Cover Genius
url: https://covergenius.com/
- name: EnquiryLabs
url: https://www.enquirylabs.co.uk
@@ -95,10 +92,6 @@ categories:
- name: KarrotPay
url: https://www.daangnpay.com/
- name: NICE Actimize
url: https://www.niceactimize.com/
contributors: ["@stevensuting"]
- name: Remita
url: https://remita.net
contributors: ["@mujibishola"]
@@ -119,6 +112,9 @@ categories:
url: https://xendit.co/
contributors: ["@LieAlbertTriAdrian"]
- name: Cover Genius
url: https://covergenius.com/
Gaming:
- name: Popoko VM Games Studio
url: https://popoko.live
@@ -168,11 +164,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"]
@@ -305,6 +296,7 @@ categories:
logo: hifadih.png
contributors: ["@saintLaurent00"]
# Logo approved by @anmol-hpe on behalf of HPE
- name: HPE
url: https://www.hpe.com/in/en/home.html
logo: hpe.png
@@ -404,10 +396,6 @@ categories:
url: https://www.techaudit.info
contributors: ["@ETselikov"]
- name: Tech Solution
url: https://www.tech-solution.com.ar/
contributors: ["@danteGiuliano", "@LeandroVallejos", "@McJaben", "@xJeree", "@zeo-return-null"]
- name: Tenable
url: https://www.tenable.com
contributors: ["@dflionis"]
@@ -437,10 +425,6 @@ categories:
logo: userguiding.svg
contributors: ["@tzercin"]
- name: Value Ad
url: https://bestpair.info/
contributors: ["@stevensuting"]
- name: Virtuoso QA
url: https://www.virtuosoqa.com
@@ -525,6 +509,10 @@ categories:
url: https://www.sunbird.org/
contributors: ["@eksteporg"]
- name: The GRAPH Network
url: https://thegraphnetwork.org/
contributors: ["@fccoelho"]
- name: Udemy
url: https://www.udemy.com/
contributors: ["@sungjuly"]
@@ -533,24 +521,7 @@ categories:
url: https://www.vipkid.com.cn/
contributors: ["@illpanda"]
Social Organization:
- name: Living Goods
url: https://www.livinggoods.org
contributors: ["@chelule"]
- name: One Acre Fund
url: https://oneacrefund.org/
contributors: ["@stevensuting"]
- name: Quest Alliance
url: https://www.questalliance.net/
contributors: ["@stevensuting"]
- name: The GRAPH Network
url: https://thegraphnetwork.org/
contributors: ["@fccoelho"]
- name: Wikimedia Foundation
- name: WikiMedia Foundation
url: https://wikimediafoundation.org
contributors: ["@vg"]
@@ -563,10 +534,6 @@ categories:
url: https://www.douroeci.com/
contributors: ["@nunohelibeires"]
- name: Rogow
url: https://rogow.com.br/
contributors: ["@nilmonto"]
- name: Safaricom
url: https://www.safaricom.co.ke/
contributors: ["@mmutiso"]
@@ -579,10 +546,11 @@ categories:
url: https://wattbewerb.de/
contributors: ["@wattbewerb"]
Healthcare:
- name: 2070Health
url: https://2070health.com/
- name: Rogow
url: https://rogow.com.br/
contributors: ["@nilmonto"]
Healthcare:
- name: Amino
url: https://amino.com
contributors: ["@shkr"]
@@ -595,6 +563,10 @@ categories:
url: https://www.getcare.io/
contributors: ["@alandao2021"]
- name: Living Goods
url: https://www.livinggoods.org
contributors: ["@chelule"]
- name: Maieutical Labs
url: https://maieuticallabs.it
contributors: ["@xrmx"]
@@ -613,10 +585,10 @@ categories:
- name: WeSure
url: https://www.wesure.cn/
HR / Staffing:
- name: bluquist
url: https://bluquist.com/
- name: 2070Health
url: https://2070health.com/
HR / Staffing:
- name: Swile
url: https://www.swile.co/
contributors: ["@PaoloTerzi"]
@@ -624,38 +596,21 @@ 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
- name: bluquist
url: https://bluquist.com/
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"]
- name: NRLM - Sarathi, India
url: https://pib.gov.in/PressReleasePage.aspx?PRID=1999586
Mobile Software:
- name: VLMedia
url: https://www.vlmedia.com.tr
@@ -707,10 +662,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 +670,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 +686,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 +696,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/
-542
View File
@@ -24,413 +24,6 @@ assists people when migrating to a new version.
## Next
### Principal listing APIs now honour related-field filters
Two authorization-related listing behaviors changed for API clients. Neither
affects the Superset UI — `include_ids` and `security/subject` have no frontend
callers — but external clients will notice:
- `GET /api/v1/<resource>/related/<column>?include_ids=...` now applies the
endpoint's `base_related_field_filters`. Previously the forced IDs were fetched
with no filtering, so a caller could resolve principals the related-field
filters deliberately hide; those IDs are now omitted from the response.
- `GET /api/v1/security/subject/` now honours `EXCLUDE_USERS_FROM_LISTS` and
`EXTRA_RELATED_QUERY_FILTERS`, matching every other principal-listing endpoint.
List counts may drop, and fetching an excluded principal via
`GET /api/v1/security/subject/<id>` now returns `404`.
### v1 chart import no longer re-adds the importer as editor on overwrite
Re-importing over an existing chart (an overwrite or a soft-delete restore) no
longer appends the importing user to the chart's `editors`, matching the
dashboard importer's behavior. This changes anything only for a user who was not
already an editor of that chart — typically an admin overwriting a chart they do
not own, who was previously added as an editor as a side effect of the import.
Newly-created charts are unaffected.
### ClickHouse: system sampling queries retry with a bounded read
System-generated sampling queries — filter-value dropdowns, the Samples
tab/dataset preview, and datetime format detection — that ClickHouse rejects
with a `max_rows_to_read` error (`TOO_MANY_ROWS`, code 158) are now retried
once with `SETTINGS read_overflow_mode='break'` appended, so they return a
partial result bounded by the operator's row cap instead of failing. The retry
applies only to statements Superset generates for physical-table datasets;
virtual datasets and user-authored SQL remain fully governed by configured
read limits, and queries that already succeed are never altered. Operators who
rely on `max_rows_to_read` as a hard failure gate for these system queries can
restore the previous behavior per database with
`"disable_sampling_read_limit_override": true` in the database's Extra JSON.
Note that a retried query returns partial data with no truncation indicator
(e.g. a filter dropdown may list only a subset of values on tables above the
row cap).
### 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.
- **Breaking — `Superset` view class route prefix removed.** The `Superset` view in `superset/views/core.py` now declares `route_base = ""`, overriding Flask-AppBuilder's auto-derived `/superset` prefix. Routes that previously lived at `/superset/welcome/`, `/superset/dashboard/<id>/`, `/superset/dashboard/p/<key>/`, `/superset/explore/`, etc. now respond at `/welcome/`, `/dashboard/<id>/`, `/dashboard/p/<key>/`, `/explore/`, etc. Under subdirectory deployment (`SUPERSET_APP_ROOT=/superset`) the URLs are unchanged from end-user perspective — `AppRootMiddleware` re-applies the prefix via `SCRIPT_NAME`. Under root deployments, any external integration or bookmark that hard-codes `/superset/<endpoint>/` paths must be updated to drop the prefix. This fixes the doubled `/superset/superset/...` URLs that `url_for` emitted for these endpoints under subdirectory deployment and the related 404s on the routes themselves.
- **Breaking — Three sibling view classes route prefix removed.** Following the same rationale as the `Superset` class above, `ExplorePermalinkView` (`superset/views/explore.py`), `TagModelView`, and `TaggedObjectsModelView` (`superset/views/tags.py`, `superset/views/all_entities.py`) now mount at the application root rather than a hard-coded `/superset/...`. The user-visible URLs `/superset/explore/p/<key>/`, `/superset/tags/`, and `/superset/all_entities/` are unchanged under subdirectory deployment; under root deployments these views now serve `/explore/p/<key>/`, `/tags/`, and `/all_entities/`, so any external integration or bookmark must drop the `/superset/` prefix. `Dashboard.url` and `Dashboard.get_url` likewise return `/dashboard/<id>/` instead of the prior `/superset/dashboard/<id>/` literal so downstream consumers (DashboardList row hrefs, MCP service `dashboard_url`) emit a single, deployment-correct prefix.
- **Legacy `/superset/*` path support.** A new outermost WSGI middleware `LegacyPrefixRedirectMiddleware` (`superset/middleware/legacy_prefix_redirect.py`) 308-redirects every enumerated legacy `/superset/<canonical>` path to its post-`route_base=""` canonical location (e.g. `/superset/welcome/``/welcome/` under root; → `/superset/welcome/` under `SUPERSET_APP_ROOT=/superset`, because the canonical resolves through `AppRootMiddleware`). Bookmarks, email links, and external integrations survive the route-base collapse for one release cycle. POST against a GET-only canonical returns 410 Gone instead of 308 (308 would 405 on retry). The shim is removed at EOL `5.0.0`, matching the `@deprecated(eol_version="5.0.0")` gate on `Superset.explore` and `Superset.explore_json`.
- **PWA web app manifest served dynamically.** The PWA manifest is now served at `/pwa-manifest.json` (under `APPLICATION_ROOT`) by a new `PwaManifestView` (`superset/views/pwa_manifest.py`) instead of the static file at `/static/assets/pwa-manifest.json`. The legacy static source at `superset-frontend/src/pwa-manifest.json` has been removed (along with its `webpack.config.js` `CopyPlugin` rule). The new endpoint resolves `APPLICATION_ROOT` and `STATIC_ASSETS_PREFIX` at request time so PWA install works under subdirectory deployments and split static-prefix / app-root deployments (where `STATIC_ASSETS_PREFIX` points to a CDN host while the Superset backend stays under `APPLICATION_ROOT`). The `<link rel="manifest">` href in `superset/templates/superset/spa.html` was updated correspondingly (using a new `application_root_rstrip` template global). Operators with a forked `spa.html` should switch any manifest `<link>` to `{{ application_root_rstrip }}/pwa-manifest.json`.
- **Hard re-bookmark break — `/superset/sql/<database_id>/`.** SQL Lab moved to its own blueprint at `/sqllab/`. The legacy `/superset/sql/<id>/` shape changed to a query-string form (`/sqllab/?dbid=<id>`); no 1:1 path mapping exists, so `LegacyPrefixRedirectMiddleware` does **not** redirect this route — it passes through and surfaces a 404. Users with bookmarks to `/superset/sql/<id>/` must update them to `/sqllab/?dbid=<id>`.
- **`SqlaTable.sql_url` query-string format.** `SqlaTable.sql_url` now URL-encodes `table_name` and joins it as a query parameter rather than concatenating a second `?`. Previously, with `Database.sql_url` returning `/sqllab/?dbid=<id>`, the concatenation produced `/sqllab/?dbid=<id>?table_name=<raw>` — a malformed second `?` that broke the query parser. External code that parsed the legacy `<base>?table_name=<raw>` shape now sees properly percent-encoded values (e.g. `/``%2F`, ` ``+` or `%20`); decode with `urllib.parse.parse_qsl`.
- **New config flag `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE` (default `False`).** Share/permalink URLs now substitute `window.location.origin` for the backend-supplied origin so a proxied or subdirectory-deployed Superset never hands the user an unreachable internal hostname. Operators whose reverse proxy correctly forwards `X-Forwarded-Host` *and* who want permalinks to carry the backend's literal origin can opt out by setting `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE = True` in `superset_config.py`. Default `False` (rewrite is on); flipping the default would regress the dominant proxied/subdir deployment to an unreachable host.
- [41651](https://github.com/apache/superset/pull/41651): **New do-not-translate standard for translation catalogs.** Strings that must stay identical to the source — icon names (e.g. `bolt`), enum/option values (`step-after`), SQL keywords, API field names (`error_message`), code constants, and example placeholders — are now marked with a `#. do-not-translate` extracted comment. The list lives in the `superset/translations/do-not-translate.txt` registry; `scripts/translations/apply_do_not_translate.py` stamps the marker onto `messages.pot` during `babel_update.sh`, and `pybabel update` propagates it to every `.po`, so the status is consistent across all languages. The AI backfill (`backfill_po.py`) and translators leave these entries untranslated (source fallback). The legacy per-catalog convention (a `# Не переводить` translator comment in the `ru` catalog) is still honored for back-compat but is superseded by this standard; contributors adding new machine-read strings should add the msgid to the registry rather than annotating individual catalogs.
### SQL Lab denies large-object and information_schema access by default
`DISALLOWED_SQL_FUNCTIONS` and `DISALLOWED_SQL_TABLES` now ship with additional default entries, so SQL Lab and chart-data queries that reference them are rejected where they were previously allowed:
- PostgreSQL large-object routines (`lo_from_bytea`, `lo_export`, `lo_import`, `lo_put`, `lo_create`, `lo_creat`, `lowrite`, `lo_get`, `loread`, `lo_unlink`), which read and write bytes on the database server's filesystem.
- The SQL-standard `information_schema` views (`tables`, `columns`, `routines`, `views`, the privilege/grant views, etc.), which expose table, column, privilege, and view-definition metadata across the whole database.
Deployments that legitimately query these (for example tooling that introspects `information_schema`) can restore the previous behavior by overriding `DISALLOWED_SQL_FUNCTIONS` / `DISALLOWED_SQL_TABLES` in `superset_config.py` to drop the entries they need.
Because the denylist now resolves the effective schema through the query-aware path, PostgreSQL queries that change the `search_path` (e.g. `SET search_path = ...`) are rejected on the SQL Lab execution and cost-estimate paths whenever any `DISALLOWED_SQL_TABLES` entry is configured (the default for PostgreSQL), matching the behavior previously applied only when `RLS_IN_SQLLAB` was enabled.
### SQL parser input length cap (SQL_MAX_PARSE_LENGTH)
The SQL parser now rejects scripts whose UTF-8 byte length exceeds the new
`SQL_MAX_PARSE_LENGTH` config option (default `1_000_000` bytes) before they are
handed to sqlglot, which bounds parser memory and CPU usage. A single query
larger than the cap (for example a very large `IN (...)` list or a big
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.
### MCP service requires `MCP_JWT_AUDIENCE` when JWT auth is enabled
When the MCP service has JWT auth enabled (`MCP_AUTH_ENABLED = True`), an audience must be configured via `MCP_JWT_AUDIENCE` so issued tokens are bound to this service. The service now fails to start with a clear configuration error when the audience is unset, instead of starting with audience validation skipped. Deployments that enable MCP JWT auth must set `MCP_JWT_AUDIENCE` to the audience value their identity provider issues for the MCP service. API-key-only MCP deployments (JWT auth disabled) are unaffected.
### Swagger UI is opt-in (off by default)
`FAB_API_SWAGGER_UI` now defaults to `False` and is driven by the `SUPERSET_ENABLE_SWAGGER_UI` environment variable. The interactive Swagger UI / OpenAPI documentation endpoints (e.g. `/swagger/v1`) are therefore no longer exposed by default. To enable them, set `SUPERSET_ENABLE_SWAGGER_UI=true` (the bundled Docker development environment sets this) or override `FAB_API_SWAGGER_UI = True` in `superset_config.py`.
### Build details (git SHA / build number) are admin-only by default
The git SHA and build number surfaced in the "About" section, the bootstrap payload, and the public `/version` endpoint are now only included for admin users by default; the release version string is still shown to everyone. To expose the build details to all users (the previous behavior), set the `SUPERSET_EXPOSE_BUILD_DETAILS` environment variable (or `EXPOSE_BUILD_DETAILS_TO_USERS = True` in `superset_config.py`).
### Helm chart adopts Kubernetes recommended labels (breaking upgrade)
The Helm chart now labels and selects workloads using the [Kubernetes recommended labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/) (`app.kubernetes.io/*`) instead of the legacy `app`/`release` labels. Because a Deployment's `spec.selector.matchLabels` is immutable, `helm upgrade` against an existing release will fail with a `field is immutable` error.
To upgrade, delete the affected workloads (which selector labels changed) before upgrading, then run the upgrade so they are recreated with the new labels:
```bash
kubectl delete deployment,statefulset -l release=<release-name> -n <namespace>
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.
### `FetchRetryOptions` callback parameters widened to allow `null`
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`:
```
/api/v1/dashboard/{id}/thumbnail/{changed_on_utc}/
```
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.
This release stops the ORM from emitting these constraints, but it cannot drop ones already present in your schema. If affected, drop them manually (names vary by backend, so look them up first):
```sql
-- PostgreSQL: names are typically tagged_object_object_id_fkey, _fkey1, _fkey2
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`).
### Version-history retention (pruning)
Entity version history (the `version_transaction` / `*_version` shadow tables that back version capture) is aged out by a nightly Celery beat task, `version_history.prune_old_versions` (`superset.tasks.version_history_retention`).
| Key | Default | Purpose |
|---|---|---|
| `SUPERSET_VERSION_HISTORY_RETENTION_DAYS` | `30` | Version rows whose owning `version_transaction.issued_at` is older than this many days are pruned. Each entity's live row (`end_transaction_id IS NULL`) is always preserved, as are the live rows of its children and associations; closed historical rows (including the baseline) age out. Set to `0` or a negative value to disable pruning. |
The task ships in the default `CeleryConfig.beat_schedule`; a deployment that overrides `CELERY_CONFIG` without inheriting the default will log a startup warning that the prune task is absent (so it never silently stops running). Retention only prunes whatever history exists — capture itself is gated separately by `ENABLE_VERSIONING_CAPTURE` (ships off).
### Deletion retention (soft-deleted entities are eventually purged)
Soft-deleted dashboards, charts, and datasets are now permanently removed after a retention window (default 30 days; `SOFT_DELETE_RETENTION_DAYS`, `0` disables; settable per workspace at runtime via the `deletion-retention set-window` CLI, which takes precedence). The `deletion_retention.purge_soft_deleted` Celery beat task runs daily and removes each aged-out entity together with its M:N join rows, owned children, datasource permission, and version-history shadow rows. After purge an entity is **unrecoverable** — its detail and `/restore` endpoints return 404 and its version history is gone.
The introducing release **defaults to dry-run** (`SOFT_DELETE_PURGE_DRY_RUN=True`): the task logs `would_purge` counts but deletes nothing, so operators can validate against production before activating real purging by setting it to `False`. Note `would_purge` is an **upper bound** — it counts every entity past the retention window without evaluating deletion blockers, so a real run may purge fewer (entities referenced by report schedules or set as a user's welcome dashboard are blocked and reported separately). The task only acts while the temporary `SOFT_DELETE` rollout flag is on.
Deployments that replace the default `CELERY_CONFIG` must add `superset.tasks.deletion_retention` to the Celery `imports` and schedule the `deletion_retention.purge_soft_deleted` task themselves. The shipped Docker development config includes both entries.
Operators can immediately erase a specific entity for compliance (GDPR) via `superset deletion-retention force-purge --uuid <uuid>`; this applies legacy hard-delete semantics — a live chart referencing a force-purged dataset is left without a datasource until re-pointed (the chart is not modified), and it purges the named entity even when it was never soft-deleted. Every purge writes an immutable, content-free audit record to the new `purge_audit_log` table that survives the entity it names: the **scheduled** purge fails closed (an entity whose audit row cannot be written is skipped and retried next run), while **force-purge** proceeds even if the audit write fails — the operator is present and deletion outranks audit for a compliance erasure.
### Recently Archived view and permanent delete (purge) endpoints
Behind the same `SOFT_DELETE` flag, a **Recently Archived** page (Settings menu, `/archived/`) lists soft-deleted charts, dashboards, and datasets with their archive time and archiving user, and offers **Recover** and **Delete permanently** row actions. The page admits any viewer holding `can_read` on **any** of the three types and offers each viewer only the types they can read; a viewer with none of the three sees an explanatory empty state, and unauthenticated requests are redirected to login.
**New endpoints** — `POST /api/v1/{chart,dashboard,dataset}/<uuid>/purge` permanently delete a single **soft-deleted** row, running the same cascade as the retention task. Irreversible. Requires `can_write` on the entity plus editorship of the row (or admin), mirroring `/restore`. The endpoints answer 404 while `SOFT_DELETE` is off (restore deliberately stays live so rows archived before a flag-off remain recoverable), 404 for rows that are not soft-deleted, and 422 with a reason when the purge is blocked (an alert/report references the entity, a user has the dashboard as their welcome page, or a restrictive foreign key intervenes). A purge that cannot write its audit record is refused with 422 rather than executed unrecorded — unlike the operator CLI, an end user's purge never outranks the audit.
With the flag on, delete confirmations across the chart/dashboard/dataset list pages change shape: a recoverable archive is confirmed with a primary **Archive** button and no type-DELETE friction, and the copy states the retention window when one is configured. A bulk dataset selection that includes semantic views keeps the full danger treatment, because semantic views have no soft-delete — they are deleted permanently and the confirmation says so.
This also resolves the limitation noted under *Soft delete and restore for datasets*: a database blocked by soft-deleted datasets can now be freed by purging those datasets (per-entity endpoint, retention task, or `force-purge` CLI) instead of hard-deleting `tables` rows out-of-band.
The `purge_audit_log` table is **never pruned by design** — the audit must survive the entities it names; operators who need to age it out should prune manually.
### Webhook alerts/reports block private/internal hosts by default
Webhook alert/report dispatch (`WebhookNotification.send`) now validates the target URL's host against the same private/internal-IP block applied to dataset import URLs. If the resolved host is in a loopback, link-local, private (RFC-1918), shared-CGNAT, or multicast range, the webhook is rejected with `NotificationParamException`.
@@ -440,7 +33,6 @@ Deployments that intentionally point webhooks at internal targets (chatops bridg
### Impala cancel_query blocks private/internal hosts by default
The Impala engine spec's `cancel_query` issues an HTTP request from the Superset backend to the host configured on the Impala database connection. That host is now validated before the request: if it resolves to a private/internal IP range, the cancel call is refused and a warning is logged. Operators whose Impala cluster runs on an internal network can opt out by setting `IMPALA_CANCEL_QUERY_ALLOW_INTERNAL_HOSTS = True` in `superset_config.py`. This mirrors the dataset-import and webhook opt-out flags.
### Map chart renderer and OpenStreetMap migration behavior
The MapLibre migration for deck.gl charts preserves saved non-Mapbox styles on
@@ -478,11 +70,6 @@ Operators can tune or disable the policy via config:
### Data uploads bounded by UPLOAD_MAX_FILE_SIZE_BYTES
Single data-file uploads (CSV, Excel, columnar) are now bounded by the `UPLOAD_MAX_FILE_SIZE_BYTES` config option, which defaults to `100 * 1024 * 1024` (100 MB). Files larger than this are rejected with a `413` before their contents are buffered into memory. Set `UPLOAD_MAX_FILE_SIZE_BYTES = None` to disable the check and restore unbounded uploads.
### Currency symbol position follows the locale when unset
When a chart's currency control leaves the **Prefix or suffix** field empty, the currency symbol position is now derived from the deployment locale's own convention via `Intl.NumberFormat` instead of always defaulting to a suffix. For example, under the default `en-US` locale `USD`, `GBP`, and `EUR` render as a prefix (`$ 1,000`), while eurozone locales such as `fr-FR` render `EUR` as a suffix (`1 000 €`). An explicit Prefix/Suffix selection is always honored and is unaffected.
Charts that relied on the previous always-suffix default for an unset position will render the symbol on the locale-appropriate side instead; set the position explicitly on the metric's currency control to pin it.
### Duration formatter precision
@@ -548,18 +135,6 @@ Runbook to adopt:
2. Set that value on the tunnel's `server_host_key` (via the database/SSH tunnel API or UI payload).
3. Optionally set `SSH_TUNNEL_STRICT_HOST_KEY_CHECKING = True` in `superset_config.py` to require host-key verification on all tunnels.
### SMTP server certificate validation enabled by default
`SMTP_SSL_SERVER_AUTH` now defaults to `True` (previously `False`). With this default, STARTTLS/SSL connections to the configured SMTP server validate the server's TLS certificate against the system trusted CA store. This makes outbound email (alerts and reports) verify the mail server's identity out of the box.
If your SMTP server presents a self-signed certificate, or a certificate that is not trusted by the system CA store, email delivery may now fail with a certificate verification error. To restore the previous behavior of skipping certificate validation, set the following in `superset_config.py`:
```python
SMTP_SSL_SERVER_AUTH = False
```
The recommended fix is to add the SMTP server's certificate (or its issuing CA) to the system trust store rather than disabling validation.
### Dataset import validates catalog against the target connection
Importing a dataset now validates the `catalog` field against the target database connection. When the connection has multi-catalog disabled (`allow_multi_catalog` off) and the dataset's catalog is not the connection's default catalog, the import fails instead of silently persisting the non-default catalog. This matches the validation already enforced on the dataset update path and prevents imported datasets from querying an unintended database.
@@ -609,86 +184,6 @@ Schedule the cutover in a quiet window. Runtime reads use only the single config
The migration is transactional (all-or-nothing) and idempotent — it can be safely re-run or resumed. Note that AES-GCM, unlike AES-CBC, does not support querying directly over encrypted columns; audit any code that filters on an encrypted column before switching. See the SIP at `docs/sip/authenticated-encryption-at-rest.md` for details.
### Soft delete and restore for datasets
**The soft-delete behavior in this section applies only when the `SOFT_DELETE` feature flag is enabled. The flag defaults to `False`** (`@lifecycle: development`), so on a default deployment `DELETE /api/v1/dataset/<id>` continues to **hard-delete permanently** — nothing is recoverable. Enable `SOFT_DELETE` to get the behavior described below.
**Flag-toggle caveat:** the soft-delete visibility filter is evaluated per query while the flag is on. If datasets are soft-deleted during a flag-on window and the flag is later turned **off**, those rows reappear as live datasets in all lists, lookups, and relationship loads (including charts that reference them). The `POST /<uuid>/restore` endpoint and the `dataset_deleted_state` list filter remain functional regardless of the flag, deliberately, so rows soft-deleted during a flag-on window stay discoverable and restorable after a rollback of the flag.
**Flag-independent parts of this work** (active even with `SOFT_DELETE` off): the restore endpoint and deleted-state filter (above); the database-deletion guard counting soft-deleted datasets; the `get_or_create_dataset` soft-deleted-twin pre-check; the combined datasource listing (`GET /api/v1/datasource/...`) always excluding soft-deleted datasets; and the two uniqueness-validation changes documented at the end of this section. Everything else — the soft DELETE itself and the visibility filtering — is flag-gated.
With the flag enabled: `DELETE /api/v1/dataset/<id>` no longer hard-deletes the dataset (the bulk-delete endpoint behaves the same way). The row is marked with a `deleted_at` timestamp and hidden from all list, detail, and lookup endpoints. Datasets in this state are excluded from default queries and from relationship loads (e.g. `database.tables`).
**No cascade in v1.** Soft-delete does not propagate to dependent charts or dashboards: they remain visible. Loading a chart whose dataset is soft-deleted surfaces a "datasource not found" error at chart-load time. Restore the dataset to recover.
**Database deletion is blocked by soft-deleted datasets.** Superset already refuses to delete a database that still has datasets (`DatabaseDeleteDatasetsExistFailedError`); that check now explicitly counts soft-deleted datasets too (it bypasses the visibility filter), since the soft-deleted `tables` rows still reference the database via `database_id` and must not be orphaned. Consequence: because dataset `DELETE` is soft and v1 ships no hard-delete/purge, **a database that has ever had datasets cannot be deleted through the API once those datasets are soft-deleted** — the rows remain and keep blocking the delete. Until a purge capability lands, operators who must remove such a database have to hard-delete the underlying `tables` rows out-of-band first. This is a deliberate trade-off (no orphaned rows / restorable datasets) and is expected to be resolved by the planned purge work.
**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.
**Permissions migration:** existing role grants of `can_write on Dataset` cover the new restore endpoint automatically; no role migration is required.
**Schema migration:** the migration adds a nullable `deleted_at` column and an index on it (`ix_tables_deleted_at`) to the `tables` table. The column add is instant; the index build runs inline (no `CONCURRENTLY`) and may briefly block writes on the `tables` table (INSERT/UPDATE/DELETE are queued while the index builds; reads are unaffected) on large Postgres deployments. MySQL InnoDB builds the index online (no blocking). Production deployments with many thousands of datasets should run this migration during a maintenance window.
**Rollback note:** if the application code is rolled back after datasets 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 datasets silently become live, active datasets 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.
**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.
**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.
### Soft delete and restore for charts
**Everything in this section applies only when the `SOFT_DELETE` feature flag is enabled. The flag defaults to `False`** (`@lifecycle: development`), so on a default deployment `DELETE /api/v1/chart/<id>` continues to **hard-delete permanently** — nothing is recoverable. Enable `SOFT_DELETE` to get the behavior described below.
**Flag-toggle caveat:** the soft-delete visibility filter is evaluated per query while the flag is on. If charts are soft-deleted during a flag-on window and the flag is later turned **off**, those rows reappear as live charts in all lists, lookups, and relationship loads (including dashboards that contained them). The `POST /<uuid>/restore` endpoint and the `chart_deleted_state` list filter remain functional regardless of the flag, deliberately, so rows soft-deleted during a flag-on window stay discoverable and restorable after a rollback of the flag.
With the flag enabled: `DELETE /api/v1/chart/<id>` no longer hard-deletes the chart (the bulk-delete endpoint behaves the same way). The row is marked with a `deleted_at` timestamp and hidden from all list, detail, and lookup endpoints. Charts in this state are excluded from default queries and from relationship loads (e.g. `dashboard.slices`).
**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.
**Permissions migration:** existing role grants of `can_write on Chart` cover the new restore endpoint automatically; no role migration is required.
**Schema migration:** the migration adds a nullable `deleted_at` column and an index on it (`ix_slices_deleted_at`) to the `slices` table. The column add is instant; the index build runs inline (no `CONCURRENTLY`) and may briefly block writes on the `slices` table (INSERT/UPDATE/DELETE are queued while the index builds; reads are unaffected) on large Postgres deployments. MySQL InnoDB builds the index online (no blocking).
**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.
- [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.
### Soft delete and restore for dashboards
**Everything in this section applies only when the `SOFT_DELETE` feature flag is enabled. The flag defaults to `False`** (`@lifecycle: development`), so on a default deployment `DELETE /api/v1/dashboard/<id>` continues to **hard-delete permanently** — nothing is recoverable. Enable `SOFT_DELETE` to get the behavior described below.
**Flag-toggle caveat:** the soft-delete visibility filter is evaluated per query while the flag is on. If dashboards are soft-deleted during a flag-on window and the flag is later turned **off**, those rows reappear as live dashboards in all lists and lookups (including slug lookups — if a soft-deleted dashboard's slug was reused while the flag was on, both rows become visible with the same slug). The `POST /<uuid>/restore` endpoint and the `dashboard_deleted_state` list filter remain functional regardless of the flag, deliberately, so rows soft-deleted during a flag-on window stay discoverable and restorable after a rollback of the flag.
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.
**Permissions migration:** existing role grants of `can_write on Dashboard` cover the new restore endpoint automatically; no role migration is required.
**Schema migration:** the migration adds a nullable `deleted_at` column and an index on it (`ix_dashboards_deleted_at`) to the `dashboards` table, and **replaces the full unique constraint on `slug`** with a partial unique index (`ix_dashboards_active_slug`) enforcing slug uniqueness only among active (non-soft-deleted) rows. The column add is instant. On Postgres the constraint swap briefly blocks reads and writes during `ALTER TABLE ... DROP CONSTRAINT` (acquires `ACCESS EXCLUSIVE`), then blocks writes only during `CREATE UNIQUE INDEX` (acquires `ShareLock`); reads pass through during the index build. Both windows are sub-second on a typical `dashboards` table. MySQL InnoDB builds the functional index online (no blocking).
**Rollback note:** the downgrade restores the original full unique constraint on `slug`. If the partial-index window allowed slug reuse (a soft-deleted row and an active row holding the same slug), `ALTER TABLE ... ADD CONSTRAINT idx_unique_slug UNIQUE (slug)` will abort with a unique-constraint violation. Before downgrading, hard-delete the soft-deleted duplicates (or rename one side) so each slug appears at most once across all rows. Rolling back the application code while leaving the new migration in place is also possible but exposes soft-deleted rows to the older code path; pair the rollback with a data decision (restore, hard-delete, or migrate-down).
The partial-index replacement is dialect-dependent: PostgreSQL uses a native `WHERE deleted_at IS NULL` partial index; MySQL 8.0.13+ uses a functional index over `(CASE WHEN deleted_at IS NULL THEN slug END)` (8.0.13 is the first release with functional key parts). **MySQL <8.0.13, MariaDB, and SQLite keep the original full unique constraint** (functional indexes / column-level UNIQUE recreation aren't supported cleanly — MariaDB is excluded even at 10.x because its `CASE`-expression index semantics differ), so on those backends a soft-deleted dashboard continues to reserve its slug for the lifetime of the row.
**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.
### Granular Export Controls
A new feature flag `GRANULAR_EXPORT_CONTROLS` introduces three fine-grained permissions that replace the legacy `can_csv` permission:
@@ -718,23 +213,10 @@ Added a new combined datasource list endpoint at `GET /api/v1/datasource/` to se
- The endpoint is available to users with at least one of `can_read` on `Dataset` or `SemanticView`.
- Semantic views are included only when the `SEMANTIC_LAYERS` feature flag is enabled.
- The endpoint enforces strict `order_column` validation and returns `400` for invalid sort columns.
### Custom time range "Now"/"Today" anchors resolve in local time
Custom time ranges that use the "Now" or "Today" anchor (for the Start, End, or the relative anchor itself) previously resolved that anchor in UTC before formatting it into a naive datetime string, which was then re-parsed elsewhere as local time. For users outside UTC, this made the resolved anchor drift by their browser's UTC offset. "Now"/"Today" now resolve directly in local time, matching the later local re-parse.
Charts and dashboards using these anchors will compute a different (correct) timestamp after upgrading; if a chart's filters or drill-downs were tuned to compensate for the old offset, review them after upgrading.
## 6.1.0
### ClickHouse minimum driver version bump
The minimum required version of `clickhouse-connect` has been raised to `>=0.13.0`. If you are using the ClickHouse connector, please upgrade your `clickhouse-connect` package. The `_mutate_label` workaround that appended hash suffixes to column aliases has also been removed, as it is no longer needed with modern versions of the driver.
### 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:
@@ -943,7 +425,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.
@@ -989,29 +470,6 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
}
```
### Composite primary keys on many-to-many association tables
Eight M:N association tables move from a synthetic `id INTEGER PRIMARY KEY` to a composite `PRIMARY KEY (fk1, fk2)` on their two foreign-key columns. The surrogate `id` is dropped, and the redundant `UNIQUE (fk1, fk2)` on the two tables that carried one is removed (now subsumed by the PK).
| Table | Composite PK |
|---|---|
| `dashboard_roles` | `(dashboard_id, role_id)` |
| `dashboard_slices` | `(dashboard_id, slice_id)` |
| `dashboard_user` | `(user_id, dashboard_id)` |
| `report_schedule_user` | `(user_id, report_schedule_id)` |
| `rls_filter_roles` | `(role_id, rls_filter_id)` |
| `rls_filter_tables` | `(table_id, rls_filter_id)` |
| `slice_user` | `(user_id, slice_id)` |
| `sqlatable_user` | `(user_id, table_id)` |
**Before upgrading:**
- The migration **deletes** two classes of pre-existing rows the composite PK cannot accommodate: duplicate `(fk1, fk2)` pairs (it keeps the lowest `id` and removes the rest) and rows with `NULL` in either FK column. Both are meaningless for `secondary=` association tables, but export the affected rows first if you need an audit record.
- External tooling (BI tools, backup scripts) that references the surrogate `id` on these tables will break; no application code references it.
- Downgrade restores the `id` column (and the original `UNIQUE` on the two tables that had it) but leaves the FK columns `NOT NULL` (intentional — a `NULL` FK in a junction row is meaningless).
For large `dashboard_slices` / `report_schedule_user` tables, see the operator runbook in [#39859](https://github.com/apache/superset/pull/39859) — pre-flight inventory queries, per-dialect lock-window sizing, and the duplicate / NULL-FK roll-up — to plan the maintenance window.
## 6.0.0
- [33055](https://github.com/apache/superset/pull/33055): Upgrades Flask-AppBuilder to 5.0.0. The AUTH_OID authentication type has been deprecated and is no longer available as an option in Flask-AppBuilder. OpenID (OID) is considered a deprecated authentication protocol - if you are using AUTH_OID, you will need to migrate to an alternative authentication method such as OAuth, LDAP, or database authentication before upgrading.
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
+1 -7
View File
@@ -28,10 +28,8 @@ x-superset-image: &superset-image apachesuperset.docker.scarf.sh/apache/superset
x-superset-volumes:
&superset-volumes # /app/pythonpath_docker will be appended to the PYTHONPATH in the final container
- ./docker:/app/docker
- ./superset-core:/app/superset-core
- superset_home:/app/superset_home
x-superset-environment: &superset-environment
# Tagged images must use their bundled packages rather than host bind mounts.
DEV_MODE: "false"
services:
redis:
@@ -60,7 +58,6 @@ services:
required: true
- path: docker/.env-local # optional override
required: false
environment: *superset-environment
image: *superset-image
container_name: superset_app
command: ["/app/docker/docker-bootstrap.sh", "app-gunicorn"]
@@ -82,7 +79,6 @@ services:
required: true
- path: docker/.env-local # optional override
required: false
environment: *superset-environment
depends_on:
db:
condition: service_started
@@ -102,7 +98,6 @@ services:
required: true
- path: docker/.env-local # optional override
required: false
environment: *superset-environment
restart: unless-stopped
depends_on:
superset-init:
@@ -125,7 +120,6 @@ services:
required: true
- path: docker/.env-local # optional override
required: false
environment: *superset-environment
restart: unless-stopped
depends_on:
superset-init:
+1 -1
View File
@@ -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}
+1 -1
View File
@@ -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:
+8 -12
View File
@@ -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}
@@ -73,23 +72,20 @@ services:
- -c
- |
url="http://host.docker.internal:9000/static/assets/manifest.json"
max_attempts=300 # ~10 minutes at 2s intervals; first build can be slow
echo "Waiting for webpack dev server at $$url..."
max_attempts=150 # ~5 minutes at 2s intervals
echo "Waiting for webpack dev server at $url..."
attempt=0
until curl -sf --max-time 5 -H "Host: localhost" -o /dev/null "$$url"; do
attempt=$$((attempt + 1))
if [ "$$attempt" -ge "$$max_attempts" ]; then
echo "ERROR: webpack dev server did not serve $$url after $$max_attempts attempts." >&2
until curl -sf --max-time 5 -o /dev/null "$url"; do
attempt=$((attempt + 1))
if [ "$attempt" -ge "$max_attempts" ]; then
echo "ERROR: webpack dev server did not serve $url after $max_attempts attempts (~5 minutes)." >&2
echo "Is the dev server running? With BUILD_SUPERSET_FRONTEND_IN_DOCKER=false you must start it on the host (e.g. 'npm run dev' in superset-frontend)." >&2
exit 1
fi
if [ $$((attempt % 15)) -eq 0 ]; then
echo "Still waiting for webpack dev server... ($$attempt/$$max_attempts)"
fi
sleep 2
done
echo "Webpack dev server is ready; starting nginx."
exec /docker-entrypoint.sh nginx -g 'daemon off;'
exec nginx -g 'daemon off;'
redis:
image: redis:7
-2
View File
@@ -70,8 +70,6 @@ SUPERSET_LOG_LEVEL=info
SUPERSET_APP_ROOT="/"
SUPERSET_ENV=development
# Swagger UI is opt-in (off by default); enable it for local development.
SUPERSET_ENABLE_SWAGGER_UI=true
SUPERSET_LOAD_EXAMPLES=yes
CYPRESS_CONFIG=false
SUPERSET_PORT=8088
+1 -1
View File
@@ -38,7 +38,7 @@ RESET='\033[0m'
echo -e "${GREEN}Updating package lists...${RESET}"
apt-get update -qq
echo -e "${GREEN}Installing packages: $*${RESET}"
echo -e "${GREEN}Installing packages: $@${RESET}"
apt-get install -yqq --no-install-recommends "$@"
echo -e "${GREEN}Autoremoving unnecessary packages...${RESET}"
+10 -14
View File
@@ -71,36 +71,32 @@ case "${1}" in
worker)
echo "Starting Celery worker..."
# setting up only 2 workers by default to contain memory usage in dev environments
celery --app=superset.tasks.celery_app:app worker -O fair -l INFO --concurrency=${CELERYD_CONCURRENCY:-2} ${WORKER_LOG_FILE:+--logfile=$WORKER_LOG_FILE}
celery --app=superset.tasks.celery_app:app worker -O fair -l INFO --concurrency=${CELERYD_CONCURRENCY:-2}
;;
beat)
echo "Starting Celery beat..."
rm -f /tmp/celerybeat.pid
celery --app=superset.tasks.celery_app:app beat --pidfile /tmp/celerybeat.pid -l INFO -s "${SUPERSET_HOME}"/celerybeat-schedule ${BEAT_LOG_FILE:+--logfile=$BEAT_LOG_FILE}
celery --app=superset.tasks.celery_app:app beat --pidfile /tmp/celerybeat.pid -l INFO -s "${SUPERSET_HOME}"/celerybeat-schedule
;;
app)
echo "Starting web app (using development server)..."
# Default to Flask debug mode in this dev compose entrypoint so the Talisman
# dev CSP (which permits 'unsafe-eval' required by React Refresh / HMR) is
# served. Operators can still set FLASK_DEBUG=false in docker/.env-local
# to exercise the production-like CSP and error handling.
: "${FLASK_DEBUG:=1}"
export FLASK_DEBUG
# Werkzeug's interactive debugger (/console) is a separate, security-sensitive
# feature and must be opted into explicitly via SUPERSET_DEBUG_ENABLED=true.
# Environment-based debugger control for security
# Only enable Werkzeug interactive debugger when explicitly requested
# Modern Werkzeug (3.0+) includes PIN protection, but defense-in-depth approach
# Override FLASK_DEBUG so the effective state matches SUPERSET_DEBUG_ENABLED even
# when FLASK_DEBUG=true is inherited from docker/.env or .flaskenv
if [[ "${SUPERSET_DEBUG_ENABLED:-}" == "true" ]]; then
export FLASK_DEBUG=1
DEBUGGER_FLAG="--debugger"
echo " ⚠️ Werkzeug debugger enabled (requires PIN for /console access)"
else
export FLASK_DEBUG=0
DEBUGGER_FLAG="--no-debugger"
echo " 🔒 Werkzeug debugger disabled (set SUPERSET_DEBUG_ENABLED=true to enable)"
fi
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..."
+1 -1
View File
@@ -19,7 +19,7 @@
#
HYPHEN_SYMBOL='-'
exec gunicorn \
gunicorn \
--bind "${SUPERSET_BIND_ADDRESS:-0.0.0.0}:${SUPERSET_PORT:-8088}" \
--access-logfile "${ACCESS_LOG_FILE:-$HYPHEN_SYMBOL}" \
--error-logfile "${ERROR_LOG_FILE:-$HYPHEN_SYMBOL}" \
-9
View File
@@ -84,11 +84,9 @@ class CeleryConfig:
broker_url = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_CELERY_DB}"
imports = (
"superset.sql_lab",
"superset.tasks.deletion_retention",
"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
@@ -102,13 +100,6 @@ class CeleryConfig:
"task": "reports.prune_log",
"schedule": crontab(minute=10, hour=0),
},
# Gated on the SOFT_DELETE feature flag, which is off by default: the
# task is scheduled either way, but purges nothing while the flag is
# unset. Enable it in FEATURE_FLAGS below to exercise retention locally.
"deletion_retention.purge_soft_deleted": {
"task": "deletion_retention.purge_soft_deleted",
"schedule": crontab(minute=0, hour=0),
},
}
+2 -2
View File
@@ -163,10 +163,10 @@ do
# Iterate through the components of the version strings
for (( j=0; j<${#THIS_TAG_NAME_ARRAY[@]}; j++ )); do
echo "Comparing ${THIS_TAG_NAME_ARRAY[$j]} to ${LATEST_RELEASE_TAG_ARRAY[$j]}"
if [[ $((THIS_TAG_NAME_ARRAY[$j])) -gt $((LATEST_RELEASE_TAG_ARRAY[$j])) ]]; then
if [[ $((THIS_TAG_NAME_ARRAY[$j])) > $((LATEST_RELEASE_TAG_ARRAY[$j])) ]]; then
compare_result="greater"
break
elif [[ $((THIS_TAG_NAME_ARRAY[$j])) -lt $((LATEST_RELEASE_TAG_ARRAY[$j])) ]]; then
elif [[ $((THIS_TAG_NAME_ARRAY[$j])) < $((LATEST_RELEASE_TAG_ARRAY[$j])) ]]; then
compare_result="lesser"
break
fi
+7 -16
View File
@@ -9,13 +9,11 @@ When working on the Storybook-to-MDX documentation system:
## Why This Matters
The generator (`scripts/generate-superset-components.mjs`) should be lightweight - it extracts data from stories and passes it through. When you add special cases to the generator:
- It becomes harder to maintain
- Stories diverge from their docs representation
- Future stories need to know about generator quirks
When you fix stories to match the expected patterns:
- Stories work identically in Storybook and Docs
- The generator stays simple and predictable
- Patterns are consistent and learnable
@@ -23,7 +21,6 @@ When you fix stories to match the expected patterns:
## Story Patterns for Docs Generation
### Required Structure
```tsx
// Use inline export default (NOT const meta = ...; export default meta)
export default {
@@ -48,7 +45,6 @@ export const InteractiveMyComponent: Story = {
```
### For Components with Variants (size × style grids)
```tsx
const sizes = ['small', 'medium', 'large'];
const variants = ['primary', 'secondary', 'danger'];
@@ -67,20 +63,16 @@ InteractiveButton.parameters = {
```
### For Components Requiring Children
```tsx
InteractiveIconTooltip.parameters = {
docs: {
// Component descriptors with dot notation for nested components
sampleChildren: [
{ component: 'Icons.InfoCircleOutlined', props: { iconSize: 'l' } },
],
sampleChildren: [{ component: 'Icons.InfoCircleOutlined', props: { iconSize: 'l' } }],
},
};
```
### For Custom Live Code Examples
```tsx
InteractiveMyComponent.parameters = {
docs: {
@@ -92,7 +84,6 @@ InteractiveMyComponent.parameters = {
```
### For Complex Props (objects, arrays)
```tsx
InteractiveMenu.parameters = {
docs: {
@@ -108,13 +99,13 @@ InteractiveMenu.parameters = {
## Common Issues and How to Fix Them (in the Story)
| Issue | Wrong Approach | Right Approach |
| --------------------------------------- | ----------------------------- | ------------------------------------------------- |
| Component not generated | Add pattern to generator | Change story to use inline `export default` |
| Issue | Wrong Approach | Right Approach |
|-------|---------------|----------------|
| Component not generated | Add pattern to generator | Change story to use inline `export default` |
| Control shows as text instead of select | Add special case in generator | Add `argTypes` with `control: { type: 'select' }` |
| Missing children/content | Modify StorybookWrapper | Add `parameters.docs.sampleChildren` |
| Gallery not showing | Add to generator output | Add `parameters.docs.gallery` config |
| Wrong live example | Hardcode in generator | Add `parameters.docs.liveExample` |
| Missing children/content | Modify StorybookWrapper | Add `parameters.docs.sampleChildren` |
| Gallery not showing | Add to generator output | Add `parameters.docs.gallery` config |
| Wrong live example | Hardcode in generator | Add `parameters.docs.liveExample` |
## Files
-1
View File
@@ -1 +0,0 @@
../superset-frontend/.nvmrc
+1
View File
@@ -0,0 +1 @@
v24.16.0
-9
View File
@@ -1,9 +0,0 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "avoid",
"printWidth": 80,
"sortPackageJson": false,
"ignorePatterns": ["**/*.mdx"]
}
+5
View File
@@ -0,0 +1,5 @@
{
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "avoid"
}
+53 -77
View File
@@ -69,7 +69,6 @@ yarn eslint # Lint TypeScript/JavaScript files
## 📁 Documentation Structure
### Main Documentation (`/docs`)
The primary documentation lives in `/docs` with this structure:
```
@@ -105,57 +104,54 @@ docs/
```
### Admin Docs (`/admin_docs`)
Admin-focused content: installation, configuration, security.
### Developer Docs (`/developer_docs`)
Developer-focused content: API documentation, architecture guides, CLI tools, code examples.
### Component Playground (`/components`)
Interactive component examples for UI development.
## 📝 Documentation Standards
### File Types
- **`.md` files**: Basic Markdown documents
- **`.mdx` files**: Markdown with JSX - can include React components
- **`.tsx` files in `/src`**: Custom React components and pages
### Frontmatter Structure
Every documentation page should have frontmatter:
```yaml
---
title: Page Title
description: Brief description for SEO
sidebar_position: 1 # Optional: controls order in sidebar
sidebar_position: 1 # Optional: controls order in sidebar
---
```
### MDX Component Usage
MDX files can import and use React components:
````mdx
```mdx
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs>
<TabItem value="npm" label="npm" default>
```bash npm install superset ```
```bash
npm install superset
```
</TabItem>
<TabItem value="yarn" label="yarn">
```bash yarn add superset ```
```bash
yarn add superset
```
</TabItem>
</Tabs>
````
```
### Code Blocks
Use triple backticks with language identifiers:
````markdown
@@ -175,7 +171,6 @@ pip install apache-superset
````
### Admonitions
Docusaurus supports various admonition types:
```markdown
@@ -203,21 +198,20 @@ This is an info box
## 🔄 Version Management
### Version Configuration
Versions are managed through `versions-config.json`:
```json
{
"docs": {
"disabled": false,
"lastVersion": "6.0.0", // Default version shown
"includeCurrentVersion": true, // Show "Next" version
"lastVersion": "6.0.0", // Default version shown
"includeCurrentVersion": true, // Show "Next" version
"onlyIncludeVersions": ["current", "6.0.0"],
"versions": {
"current": {
"label": "Next",
"path": "",
"banner": "unreleased" // Shows warning banner
"banner": "unreleased" // Shows warning banner
},
"6.0.0": {
"label": "6.0.0",
@@ -230,7 +224,6 @@ Versions are managed through `versions-config.json`:
```
### Creating New Versions
**IMPORTANT**: Always use the custom scripts, NOT native Docusaurus commands:
```bash
@@ -242,10 +235,8 @@ yarn docusaurus docs:version 6.1.0
```
### Version Files Created
When versioning, these files are created (per section, with the
section's plugin id as prefix):
- `<section>_versioned_docs/version-X.X.X/` - Snapshot of current docs
- `<section>_versioned_sidebars/version-X.X.X-sidebars.json` - Sidebar config
- `<section>_versions.json` - List of all versions
@@ -255,7 +246,6 @@ Section plugin ids: `user_docs`, `admin_docs`, `developer_docs`, `components`.
## 🎨 Styling and Theming
### Custom CSS
Add custom styles in `/src/css/custom.css`:
```css
@@ -266,14 +256,13 @@ Add custom styles in `/src/css/custom.css`:
```
### Custom Components
Create React components in `/src/components/`:
```tsx
// src/components/FeatureCard.tsx
import React from 'react';
export default function FeatureCard({ title, description }) {
export default function FeatureCard({title, description}) {
return (
<div className="card">
<h3>{title}</h3>
@@ -288,7 +277,10 @@ Use in MDX:
```mdx
import FeatureCard from '@site/src/components/FeatureCard';
<FeatureCard title="Fast" description="Lightning fast queries" />
<FeatureCard
title="Fast"
description="Lightning fast queries"
/>
```
## 📦 Key Dependencies
@@ -303,7 +295,6 @@ import FeatureCard from '@site/src/components/FeatureCard';
## 🔗 Linking Strategies
### Internal Links
Use relative paths for internal documentation:
```markdown
@@ -312,7 +303,6 @@ Use relative paths for internal documentation:
```
### External Links
Always use full URLs:
```markdown
@@ -320,7 +310,6 @@ Always use full URLs:
```
### Linking to Code
Reference code in the main repository:
```markdown
@@ -330,24 +319,21 @@ See the [main configuration file](https://github.com/apache/superset/blob/master
## 🛠️ Common Documentation Tasks
### Adding a New Guide
1. Create the `.mdx` file in the appropriate directory
2. Add frontmatter with title and description
3. Update sidebar if needed (for manual sidebar configs)
### Adding API Documentation
The API docs use Swagger UI embedded in `/docs/api.mdx`:
```mdx
import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';
import SwaggerUI from "swagger-ui-react";
import "swagger-ui-react/swagger-ui.css";
<SwaggerUI url="/api/v1/openapi.json" />
```
### Adding Interactive Examples
Use MDX to create interactive documentation:
```mdx
@@ -374,7 +360,6 @@ When creating or updating documentation:
## 🔍 Searching and Navigation
### Sidebar Configuration
Sidebars are configured in `/sidebars.js`:
```javascript
@@ -400,7 +385,6 @@ module.exports = {
```
### Search
Docusaurus includes Algolia DocSearch integration configured in `docusaurus.config.ts`.
## 🚫 Common Pitfalls to Avoid
@@ -414,7 +398,6 @@ Docusaurus includes Algolia DocSearch integration configured in `docusaurus.conf
## 🔧 Troubleshooting
### Dev Server Issues
```bash
yarn stop # Kill any running servers
yarn clear # Clear cache
@@ -422,7 +405,6 @@ yarn start # Restart
```
### Build Failures
```bash
# Check for broken links
yarn build
@@ -435,9 +417,7 @@ yarn eslint
```
### Version Issues
If versions don't appear in dropdown:
1. Check `versions-config.json` includes the version
2. Verify version files exist in `<section>_versioned_docs/`
3. Restart dev server
@@ -452,10 +432,9 @@ If versions don't appear in dropdown:
## 📖 Real Examples and Patterns
### Example: Configuration Documentation Pattern
From `docs/configuration/configuring-superset.mdx`:
````mdx
```mdx
---
title: Configuring Superset
hide_title: true
@@ -473,9 +452,7 @@ Superset exposes hundreds of configurable parameters through its
```bash
export SUPERSET_CONFIG_PATH=/app/superset_config.py
```
````
````
```
**Key patterns:**
- Links to source code for reference
@@ -499,10 +476,9 @@ documentation at [docs.preset.io](https://docs.preset.io/).
### Connecting to a new database
<img src={useBaseUrl("/img/tutorial/tutorial_01_add_database_connection.png")} width="600" />
````
```
**Key patterns:**
- Import Docusaurus hooks for dynamic URLs
- Use of admonitions (:::tip) for helpful information
- Screenshots with useBaseUrl for proper path resolution
@@ -510,12 +486,11 @@ documentation at [docs.preset.io](https://docs.preset.io/).
- Step-by-step visual guides
### Example: API Documentation Pattern
From `docs/api.mdx`:
```mdx
import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';
import SwaggerUI from "swagger-ui-react";
import "swagger-ui-react/swagger-ui.css";
## API Documentation
@@ -523,7 +498,6 @@ import 'swagger-ui-react/swagger-ui.css';
```
**Key patterns:**
- Embedding interactive Swagger UI
- Importing necessary CSS
- Direct API spec integration
@@ -534,39 +508,49 @@ import 'swagger-ui-react/swagger-ui.css';
// For images in static folder
import useBaseUrl from "@docusaurus/useBaseUrl";
<img src={useBaseUrl('/img/feature-screenshot.png')} width="600" />
<img src={useBaseUrl("/img/feature-screenshot.png")} width="600" />
// With caption
<figure>
<img src={useBaseUrl('/img/dashboard.png')} alt="Dashboard view" />
<img src={useBaseUrl("/img/dashboard.png")} alt="Dashboard view" />
<figcaption>Superset Dashboard Interface</figcaption>
</figure>
```
### Multi-Tab Code Examples
````mdx
```mdx
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs
defaultValue="docker"
values={[
{ label: 'Docker', value: 'docker' },
{ label: 'Kubernetes', value: 'k8s' },
{ label: 'PyPI', value: 'pypi' },
]}
>
<TabItem value="docker">```bash docker-compose up ```</TabItem>
<TabItem value="k8s">```bash kubectl apply -f superset.yaml ```</TabItem>
<TabItem value="pypi">```bash pip install apache-superset ```</TabItem>
{label: 'Docker', value: 'docker'},
{label: 'Kubernetes', value: 'k8s'},
{label: 'PyPI', value: 'pypi'},
]}>
<TabItem value="docker">
```bash
docker-compose up
```
</TabItem>
<TabItem value="k8s">
```bash
kubectl apply -f superset.yaml
```
</TabItem>
<TabItem value="pypi">
```bash
pip install apache-superset
```
</TabItem>
</Tabs>
````
```
### Configuration File Examples
````mdx
```mdx
```python title="superset_config.py"
# Database connection example
SQLALCHEMY_DATABASE_URI = 'postgresql://user:password@localhost/superset'
@@ -581,9 +565,7 @@ FEATURE_FLAGS = {
'DASHBOARD_NATIVE_FILTERS': True,
}
```
````
````
```
### Cross-Referencing Pattern
@@ -596,11 +578,11 @@ For detailed configuration options, see:
External resources:
- [SQLAlchemy Documentation](https://docs.sqlalchemy.org/)
- [Flask Configuration](https://flask.palletsprojects.com/config/)
````
```
### Writing Installation Guides
````mdx
```mdx
## Prerequisites
:::warning
@@ -614,10 +596,8 @@ Ensure you have Python 3.9+ and Node.js 16+ installed before proceeding.
git clone https://github.com/apache/superset.git
cd superset
```
````
2. **Install Python dependencies**
```bash
pip install -e .
```
@@ -631,8 +611,7 @@ Ensure you have Python 3.9+ and Node.js 16+ installed before proceeding.
:::tip Success Check
Navigate to http://localhost:8088 and login with admin/admin
:::
````
```
### Documenting API Endpoints
@@ -651,10 +630,9 @@ Returns a list of charts.
```bash
curl -X GET "http://localhost:8088/api/v1/chart/" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
````
```
**Example Response:**
```json
{
"count": 42,
@@ -667,10 +645,8 @@ curl -X GET "http://localhost:8088/api/v1/chart/" \
]
}
```
```
---
**Note**: This documentation site serves as the primary resource for Superset users, administrators, and contributors. Always prioritize clarity, accuracy, and completeness when creating or updating documentation.
```
-13
View File
@@ -66,7 +66,6 @@ yarn version:add:components 1.2.0
```
What the script does:
1. Refreshes auto-generated content via `generate:smart` (database pages, API reference, component pages).
2. Calls `yarn docusaurus docs:version` (or the per-section equivalent) to snapshot the section.
3. Freezes any data-file imports (`@site/static/*.json`, `../../data/*.json`) into a snapshot-local `_versioned_data/` dir so the historical version doesn't silently mutate when the source files change.
@@ -74,7 +73,6 @@ What the script does:
5. Updates `versions-config.json` and `<section>_versions.json`.
**Do NOT use** the native Docusaurus commands directly (`yarn docusaurus docs:version`), as they will:
- ❌ Create version files but NOT update `versions-config.json`
- ❌ Skip auto-gen refresh, freezing whatever was on disk
- ❌ Skip data-import freezing, leaving the snapshot pointed at live data
@@ -84,11 +82,9 @@ What the script does:
### Managing Versions
#### With Automated Scripts
The automated scripts handle all configuration updates automatically. No manual editing required!
#### Manual Configuration
If creating versions manually, you'll need to:
1. **Update `versions-config.json`** (or `docusaurus.config.ts` if not using dynamic config):
@@ -113,7 +109,6 @@ If creating versions manually, you'll need to:
### Removing a Version
#### Using Automated Scripts (Recommended)
```bash
# Main Documentation
yarn version:remove:user_docs 1.0.0
@@ -129,7 +124,6 @@ yarn version:remove:components 1.0.0
```
#### Manual Removal
To manually remove a version:
1. **Delete the version folder** from the appropriate location:
@@ -159,7 +153,6 @@ To manually remove a version:
### Version Configuration Examples
#### Main Documentation (default plugin)
```typescript
docs: {
includeCurrentVersion: true,
@@ -181,7 +174,6 @@ docs: {
```
#### Developer Docs & Components (custom plugins)
```typescript
{
id: 'developer_docs',
@@ -218,28 +210,23 @@ docs: {
#### Version Not Showing After Creation
If you accidentally used `yarn docusaurus docs:version` instead of `yarn version:add`:
1. **Problem**: The version files were created but `versions-config.json` wasn't updated
2. **Solution**: Either:
- Revert the changes: `git restore user_docs_versions.json && rm -rf user_docs_versioned_docs/ user_docs_versioned_sidebars/`
- Then use the correct command: `yarn version:add:user_docs <version>`
For other issues:
- **Restart the server**: Changes to version configuration require a server restart
- **Check config file**: Ensure `versions-config.json` includes the new version
- **Verify files exist**: Check that versioned docs folder was created
#### Broken Links in Versioned Documentation
When creating a new version, links in the documentation are preserved as-is. Common issues:
- **Cross-section links**: Links between sections (e.g., from developer_docs to docs) need to be version-aware
- **Absolute vs relative paths**: Use relative paths within the same section
- **Version-specific URLs**: Update hardcoded URLs to use version variables
To fix broken links:
1. Use `type: 'doc'` with `docId` for version-aware navigation in navbar
2. Use relative paths within the same documentation section
3. Test all versions after creation to identify broken links
@@ -9,8 +9,8 @@ version: 2
Users can configure automated alerts and reports to send dashboards or charts to an email recipient or Slack channel.
- _Alerts_ are sent when a SQL condition is reached
- _Reports_ are sent on a schedule
- *Alerts* are sent when a SQL condition is reached
- *Reports* are sent on a schedule
Alerts and reports are disabled by default. To turn them on, you'll need to change configuration settings and install a suitable headless browser in your environment.
@@ -26,17 +26,16 @@ Alerts and reports are disabled by default. To turn them on, you'll need to chan
- emails: `SMTP_*` settings
- Slack messages: `SLACK_API_TOKEN`
- Users can customize the email subject by including date code placeholders, which will automatically be replaced with the corresponding UTC date when the email is sent. To enable this functionality, activate the `"DATE_FORMAT_IN_EMAIL_SUBJECT"` [feature flag](/admin-docs/configuration/configuring-superset#feature-flags). This enables date formatting in email subjects, preventing all reporting emails from being grouped into the same thread (optional for the reporting feature).
- Use date codes from [strftime.org](https://strftime.org/) to create the email subject.
- If no date code is provided, the original string will be used as the email subject.
- Use date codes from [strftime.org](https://strftime.org/) to create the email subject.
- If no date code is provided, the original string will be used as the email subject.
##### Disable dry-run mode
Screenshots will be taken but no messages actually sent as long as `ALERT_REPORTS_NOTIFICATION_DRY_RUN = True`, its default value in `docker/pythonpath_dev/superset_config.py`. To disable dry-run mode and start receiving email/Slack notifications, set `ALERT_REPORTS_NOTIFICATION_DRY_RUN` to `False` in [superset config](https://github.com/apache/superset/blob/master/docker/pythonpath_dev/superset_config.py).
Screenshots will be taken but no messages actually sent as long as `ALERT_REPORTS_NOTIFICATION_DRY_RUN = True`, its default value in `docker/pythonpath_dev/superset_config.py`. To disable dry-run mode and start receiving email/Slack notifications, set `ALERT_REPORTS_NOTIFICATION_DRY_RUN` to `False` in [superset config](https://github.com/apache/superset/blob/master/docker/pythonpath_dev/superset_config.py).
#### In your `Dockerfile`
You'll need to extend the Superset image to include a headless browser. Your options include:
- Use Playwright with Chromium: this is the recommended approach as of version 4.1.x or greater. Playwright always uses Chromium — the `WEBDRIVER_TYPE` config setting has no effect when Playwright is active. A working example of a Dockerfile that installs these tools is provided under "Building your own production Docker image" on the [Docker Builds](/admin-docs/installation/docker-builds#building-your-own-production-docker-image) page. Enable the `PLAYWRIGHT_REPORTS_AND_THUMBNAILS` feature flag in your config to activate it.
- Use Firefox (Selenium): you'll need to install geckodriver and Firefox. Set `WEBDRIVER_TYPE` to `"firefox"` in your `superset_config.py`.
- Use Chrome (Selenium): you'll need to install Chrome. Set `WEBDRIVER_TYPE` to `"chrome"` in your `superset_config.py`.
@@ -82,21 +81,6 @@ SLACK_CACHE_TIMEOUT = int(timedelta(days=2).total_seconds())
SLACK_API_RATE_LIMIT_RETRY_COUNT = 5
```
#### Slack Enterprise Grid (org-scoped tokens)
On a Slack Enterprise Grid org, an org-scoped token spans multiple workspaces, so
workspace-scoped methods such as `conversations.list` require a `team_id` to
indicate which workspace to target. Set `SLACK_TEAM_ID` to your workspace (team)
ID so Superset can list channels and deliver reports:
```python
# The workspace (team) ID to target, e.g. "T01234567"
SLACK_TEAM_ID = "T01234567"
```
This defaults to `None` and only needs to be set when using an org-scoped token;
it is accepted but ignored for standard workspace-level tokens.
### Webhook integration
Superset can send alert and report notifications to any HTTP endpoint — useful for chat platforms, incident management tools, or custom automation.
@@ -136,7 +120,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
@@ -176,7 +160,7 @@ When enabled, Superset rejects webhook configurations that use `http://` URLs.
#### Retry Behavior
Superset automatically retries webhook deliveries on `429 Too Many Requests` and `5xx` server errors using exponential backoff. Retries are bounded to roughly 120 seconds of cumulative wall-clock time (worst case ~210 seconds, because the bound is checked against the time elapsed before each attempt, so the final request can begin just under the limit and still run its full request timeout), after which the delivery is abandoned.
Superset automatically retries webhook deliveries on `429 Too Many Requests` and `5xx` server errors using exponential backoff.
### Kubernetes-specific
@@ -205,7 +189,7 @@ You need to replace default values with your custom Redis, Slack and/or SMTP con
Superset uses Celery beat and Celery worker(s) to send alerts and reports.
- The beat is the scheduler that tells the worker when to perform its tasks. This schedule is defined when you create the alert or report.
- The worker will process the tasks that need to be performed when an alert or report is fired.
- The worker will process the tasks that need to be performed when an alert or report is fired.
In the `CeleryConfig`, only the `beat_schedule` is relevant to this feature, the rest of the `CeleryConfig` can be changed for your needs.
@@ -288,10 +272,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
@@ -314,7 +296,7 @@ Please refer to `ExecutorType` in the codebase for other executor types.
It's also possible to specify a minimum interval between each report's execution through the config file:
```python
``` python
# Set a minimum interval threshold between executions (for each Alert/Report)
# Value should be an integer
ALERT_MINIMUM_INTERVAL = int(timedelta(minutes=10).total_seconds())
@@ -323,7 +305,7 @@ REPORT_MINIMUM_INTERVAL = int(timedelta(minutes=5).total_seconds())
Alternatively, you can assign a function to `ALERT_MINIMUM_INTERVAL` and/or `REPORT_MINIMUM_INTERVAL`. This is useful to dynamically retrieve a value as needed:
```python
``` python
def alert_dynamic_minimal_interval(**kwargs) -> int:
"""
Define logic here to retrieve the value dynamically
@@ -336,7 +318,7 @@ ALERT_MINIMUM_INTERVAL = alert_dynamic_minimal_interval
For security, Superset rewrites external links in alert/report email HTML so
they go through a warning page before the user is navigated to the external
site. Internal links (matching your configured base URL) are not affected.
site. Internal links (matching your configured base URL) are not affected.
```python
# Disable external link redirection entirely (default: True)
@@ -348,21 +330,21 @@ to determine which hosts are internal.
## Troubleshooting
There are many reasons that reports might not be working. Try these steps to check for specific issues.
There are many reasons that reports might not be working. Try these steps to check for specific issues.
### Confirm feature flag is enabled and you have sufficient permissions
If you don't see "Alerts & Reports" under the _Manage_ section of the Settings dropdown in the Superset UI, you need to enable the `ALERT_REPORTS` feature flag (see above). Enable another feature flag and check to see that it took effect, to verify that your config file is getting loaded.
If you don't see "Alerts & Reports" under the *Manage* section of the Settings dropdown in the Superset UI, you need to enable the `ALERT_REPORTS` feature flag (see above). Enable another feature flag and check to see that it took effect, to verify that your config file is getting loaded.
Log in as an admin user to ensure you have adequate permissions.
### Check the logs of your Celery worker
This is the best source of information about the problem. In a docker compose deployment, you can do this with a command like `docker logs superset_worker --since 1h`.
This is the best source of information about the problem. In a docker compose deployment, you can do this with a command like `docker logs superset_worker --since 1h`.
### 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.
@@ -370,7 +352,7 @@ If you are handling the installation of the headless browser on your own, do you
One symptom of an invalid connection to an email server is receiving an error of `[Errno 110] Connection timed out` in your logs when the report tries to send.
Confirm via testing that your outbound email configuration is correct. Here is the simplest test, for an un-authenticated email SMTP email service running on port 25. If you are sending over SSL, for instance, study how [Superset's codebase sends emails](https://github.com/apache/superset/blob/master/superset/utils/core.py#L818) and then test with those commands and arguments.
Confirm via testing that your outbound email configuration is correct. Here is the simplest test, for an un-authenticated email SMTP email service running on port 25. If you are sending over SSL, for instance, study how [Superset's codebase sends emails](https://github.com/apache/superset/blob/master/superset/utils/core.py#L818) and then test with those commands and arguments.
Start Python in your worker environment, replace all example values, and run:
@@ -396,16 +378,16 @@ This should send an email.
Possible fixes:
- Some cloud hosts disable outgoing unauthenticated SMTP email to prevent spam. For instance, [Azure blocks port 25 by default on some machines](https://learn.microsoft.com/en-us/azure/virtual-network/troubleshoot-outbound-smtp-connectivity). Enable that port or use another sending method.
- Some cloud hosts disable outgoing unauthenticated SMTP email to prevent spam. For instance, [Azure blocks port 25 by default on some machines](https://learn.microsoft.com/en-us/azure/virtual-network/troubleshoot-outbound-smtp-connectivity). Enable that port or use another sending method.
- Use another set of SMTP credentials that you verify works in this setup.
### Browse to your report from the worker
The worker may be unable to reach the report. It will use the value of `WEBDRIVER_BASEURL` to browse to the report. If that route is invalid, or presents an authentication challenge that the worker can't pass, the report screenshot will fail.
The worker may be unable to reach the report. It will use the value of `WEBDRIVER_BASEURL` to browse to the report. If that route is invalid, or presents an authentication challenge that the worker can't pass, the report screenshot will fail.
Check this by attempting to `curl` the URL of a report that you see in the error logs of your worker. For instance, from the worker environment, run `curl http://superset_app:8088/superset/dashboard/1/`. You may get different responses depending on whether the dashboard exists - for example, you may need to change the `1` in that URL. If there's a URL in your logs from a failed report screenshot, that's a good place to start. The goal is to determine a valid value for `WEBDRIVER_BASEURL` and determine if an issue like HTTPS or authentication is redirecting your worker.
In a deployment with authentication measures enabled like HTTPS and Single Sign-On, it may make sense to have the worker navigate directly to the Superset application running in the same location, avoiding the need to sign in. For instance, you could use `WEBDRIVER_BASEURL="http://superset_app:8088"` for a docker compose deployment, and set `"force_https": False,` in your `TALISMAN_CONFIG`.
In a deployment with authentication measures enabled like HTTPS and Single Sign-On, it may make sense to have the worker navigate directly to the Superset application running in the same location, avoiding the need to sign in. For instance, you could use `WEBDRIVER_BASEURL="http://superset_app:8088"` for a docker compose deployment, and set `"force_https": False,` in your `TALISMAN_CONFIG`.
### Duplicate report deliveries
@@ -513,7 +495,6 @@ schedule the queries that have `schedule_info` in their JSON metadata. For sched
Airflow, additional fields can be easily added to the configuration file above.
:::resources
- [Tutorial: Automated Alerts and Reporting via Slack/Email in Superset](https://dev.to/ngtduc693/apache-superset-topic-5-automated-alerts-and-reporting-via-slackemail-in-superset-2gbe)
- [Blog: Integrating Slack alerts and Apache Superset for better data observability](https://medium.com/affinityanswers-tech/integrating-slack-alerts-and-apache-superset-for-better-data-observability-fd2f9a12c350)
:::
:::
@@ -104,6 +104,5 @@ celery --app=superset.tasks.celery_app:app flower
```
:::resources
- [Blog: How to Set Up Global Async Queries (GAQ) in Apache Superset](https://medium.com/@ngigilevis/how-to-set-up-global-async-queries-gaq-in-apache-superset-a-complete-guide-9d2f4a047559)
:::
:::
+28 -10
View File
@@ -1,7 +1,25 @@
{/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/}
---
title: AWS IAM Authentication
sidebar_label: AWS IAM Authentication
version: 1
sidebar_position: 15
---
@@ -13,7 +31,7 @@ Cross-account IAM role assumption via STS `AssumeRole` is supported, allowing a
## Prerequisites
- Enable the `AWS_DATABASE_IAM_AUTH` feature flag in `superset_config.py`. IAM authentication is gated behind this flag; if it is disabled, connections using `aws_iam` fail with _"AWS IAM database authentication is not enabled."_
- Enable the `AWS_DATABASE_IAM_AUTH` feature flag in `superset_config.py`. IAM authentication is gated behind this flag; if it is disabled, connections using `aws_iam` fail with *"AWS IAM database authentication is not enabled."*
```python
FEATURE_FLAGS = {
"AWS_DATABASE_IAM_AUTH": True,
@@ -48,14 +66,14 @@ IAM authentication is configured via the **encrypted_extra** field of the databa
}
```
| Field | Required | Description |
| ------------------ | -------- | ---------------------------------------------------------------------------------------- |
| `enabled` | Yes | Set to `true` to activate IAM auth |
| `role_arn` | No | ARN of the cross-account IAM role to assume via STS. Omit for same-account auth |
| `external_id` | No | External ID for the STS `AssumeRole` call, if required by the target role's trust policy |
| `region` | Yes | AWS region of the database cluster |
| `db_username` | Yes | The database username associated with the IAM identity |
| `session_duration` | No | STS session duration in seconds (default: `3600`) |
| Field | Required | Description |
|-------|----------|-------------|
| `enabled` | Yes | Set to `true` to activate IAM auth |
| `role_arn` | No | ARN of the cross-account IAM role to assume via STS. Omit for same-account auth |
| `external_id` | No | External ID for the STS `AssumeRole` call, if required by the target role's trust policy |
| `region` | Yes | AWS region of the database cluster |
| `db_username` | Yes | The database username associated with the IAM identity |
| `session_duration` | No | STS session duration in seconds (default: `3600`) |
### Redshift (Serverless)
+5 -25
View File
@@ -76,21 +76,6 @@ value defined in `DATA_CACHE_CONFIG`.
Note, that by setting the cache timeout to `-1`, caching for charting data can be disabled, either
per chart, dataset or database, or by default if set in `DATA_CACHE_CONFIG`.
## Limiting Cached Result Size
Very large chart or SQL query results can flood the cache backend (Redis/Memcached), evicting many
smaller useful entries or exhausting memory. To cap the size of any single value written to the data
cache, set `DATA_CACHE_MAX_VALUE_SIZE` (in bytes) in `superset_config.py`:
```python
DATA_CACHE_MAX_VALUE_SIZE = 10 * 1024 * 1024 # 10 MB
```
When a result's serialized size exceeds this threshold it is not written to the data cache — the
chart still renders, but the next load re-queries the datasource instead of getting a cache hit. The
`skip_cache_value_too_large` statsd metric is incremented each time this happens. Set to `None` (the
default) to disable the check.
## SQL Lab Query Results
Caching for SQL Lab query results is used when async queries are enabled and is configured using
@@ -154,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.
@@ -198,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):
@@ -323,7 +304,6 @@ While database-backed operations work reliably, the Redis backend is recommended
deployments where low latency and reduced database load are important.
:::resources
- [Blog: The Data Engineer's Guide to Lightning-Fast Superset Dashboards](https://preset.io/blog/the-data-engineers-guide-to-lightning-fast-apache-superset-dashboards/)
- [Blog: Accelerating Dashboards with Materialized Views](https://preset.io/blog/accelerating-apache-superset-dashboards-with-materialized-views/)
:::
:::
@@ -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:
@@ -225,7 +225,7 @@ RequestHeader set X-Forwarded-Proto "https"
## Configuring the application root
_Please be advised that this feature is in BETA._
*Please be advised that this feature is in BETA.*
Superset supports running the application under a non-root path. The root path
prefix can be specified in one of three ways:
@@ -312,13 +312,10 @@ AUTH_USER_REGISTRATION_ROLE = "Public"
```
In case you want to assign the `Admin` role on new user registration, it can be assigned as follows:
```python
AUTH_USER_REGISTRATION_ROLE = "Admin"
```
If you encounter the [issue](https://github.com/apache/superset/issues/13243) of not being able to list users from the Superset main page settings, although a newly registered user has an `Admin` role, please re-run `superset init` to sync the required permissions. Below is the command to re-run `superset init` using docker compose.
```
docker-compose exec superset superset init
```
@@ -458,51 +455,6 @@ def FLASK_APP_MUTATOR(app: Flask) -> None:
app.before_request_funcs.setdefault(None, []).append(make_session_permanent)
```
## Customizing the landing page (index view)
The page served at `/` is rendered by an index view. By default Superset registers
`SupersetIndexView`, which redirects to `/superset/welcome/` and also adds the
`/lang/<locale>` locale handler. You can replace it with your own view, for example
to send users straight to a specific dashboard or to a chart list.
Set `FAB_INDEX_VIEW` to the **importable dotted path** of your view class. Flask-AppBuilder
resolves this during app initialization and uses it in place of the default:
```python
# my_overrides.py — must be importable on the PYTHONPATH
from flask import redirect
from superset.initialization import SupersetIndexView
from superset.superset_typing import FlaskResponse
from flask_appbuilder import expose
class MyIndexView(SupersetIndexView):
@expose("/")
def index(self) -> FlaskResponse:
return redirect("/chart/list/")
```
```python
# superset_config.py
FAB_INDEX_VIEW = "my_overrides.MyIndexView"
```
A few things that commonly trip people up:
- **Subclass `SupersetIndexView`, not Flask-AppBuilder's bare `IndexView`.** Subclassing
keeps Superset's `/lang/<locale>` locale handling; replacing it with a bare `IndexView`
silently drops that behavior.
- **The class must be importable as a real module.** `FAB_INDEX_VIEW` is resolved by
importing the dotted path, which is independent of how `superset_config.py` itself is
loaded. Superset only copies **uppercase** names out of `superset_config.py` into its
runtime config, so a `FAB_INDEX_VIEW = "superset_config.MyIndexView"` reference only works
if `superset_config` is itself importable by that name on the `PYTHONPATH`. If you load
config via `SUPERSET_CONFIG_PATH` (an arbitrary file path), put the view in a separate
importable module instead and reference that module.
- **Don't set `appbuilder.indexview` from `FLASK_APP_MUTATOR`.** The mutator runs after
routes are already registered, so the assignment has no effect on the `/` route. Use
`FAB_INDEX_VIEW` instead.
## Feature Flags
To support a diverse set of users, Superset has some features that are not enabled by default. For
@@ -552,25 +504,6 @@ CELERY_BEAT_SCHEDULE = {
Adjust `retention_period_days` to control how long query rows are kept. Companion opt-in tasks (`prune_logs`, `prune_tasks`) exist for pruning the logs and tasks tables; see the commented-out examples in `superset/config.py`. Without enabling these tasks, the metadata database will grow unbounded over time.
## Dashboard Layout Size Limit
Each dashboard stores its layout (the position, size, and nesting of every chart, row, and tab) as a JSON blob in the metadata database. Superset caps the length of this serialized blob with `SUPERSET_DASHBOARD_POSITION_DATA_LIMIT`, which defaults to `65535`:
```python
SUPERSET_DASHBOARD_POSITION_DATA_LIMIT = 65535
```
This is a Python-level cap (65535 is 2¹⁶ 1), independent of the database column capacity — the `position_json` column is a `MEDIUMTEXT`, which holds far more. When the serialized layout reaches this limit, the editor blocks the save and reports the current length, the limit, and this setting's name. A warning is shown once the layout passes 90% of the limit.
Large dashboards — for example, many charts spread across nested tabs — can exceed the default. Because the underlying column comfortably stores larger values, you can safely raise the limit:
```python
SUPERSET_DASHBOARD_POSITION_DATA_LIMIT = 131072 # double the default
```
Alternatively, split a very large dashboard into several smaller ones. Note that this check is enforced when saving layout edits in the UI; a dashboard imported from a ZIP with an oversized layout will load and render, but cannot be edited and re-saved until the limit is raised.
:::resources
- [Blog: Feature Flags in Apache Superset](https://preset.io/blog/feature-flags-in-apache-superset-and-preset/)
:::
:::
@@ -22,10 +22,10 @@ The current list of countries can be found in the src
The Country Maps visualization already ships with the maps for the following countries:
<ul style={{ columns: 3 }}>
{countriesData.countries.map((country, index) => (
<li key={index}>{country}</li>
))}
<ul style={{columns: 3}}>
{countriesData.countries.map((country, index) => (
<li key={index}>{country}</li>
))}
</ul>
## Adding a New Country
+10 -17
View File
@@ -7,7 +7,7 @@ version: 1
import featureFlags from '@site/static/feature-flags.json';
export const FlagTable = ({ flags }) => (
export const FlagTable = ({flags}) => (
<table>
<thead>
<tr>
@@ -17,21 +17,14 @@ export const FlagTable = ({ flags }) => (
</tr>
</thead>
<tbody>
{flags.map(flag => (
{flags.map((flag) => (
<tr key={flag.name}>
<td>
<code>{flag.name}</code>
</td>
<td>
<code>{flag.default ? 'True' : 'False'}</code>
</td>
<td><code>{flag.name}</code></td>
<td><code>{flag.default ? 'True' : 'False'}</code></td>
<td>
{flag.description}
{flag.docs && (
<>
{' '}
(<a href={flag.docs}>docs</a>)
</>
<> (<a href={flag.docs}>docs</a>)</>
)}
</td>
</tr>
@@ -57,12 +50,12 @@ FEATURE_FLAGS = {
Feature flags progress through lifecycle stages:
| Stage | Description |
| --------------- | ------------------------------------------------------------------------------ |
| Stage | Description |
|-------|-------------|
| **Development** | Experimental features under active development. May be incomplete or unstable. |
| **Testing** | Feature complete but undergoing testing. Usable but may contain bugs. |
| **Stable** | Production-ready features. Safe for all deployments. |
| **Deprecated** | Features scheduled for removal. Migrate away from these. |
| **Testing** | Feature complete but undergoing testing. Usable but may contain bugs. |
| **Stable** | Production-ready features. Safe for all deployments. |
| **Deprecated** | Features scheduled for removal. Migrate away from these. |
---

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