mirror of
https://github.com/apache/superset.git
synced 2026-09-01 04:51:23 +00:00
Compare commits
38
Commits
@@ -77,17 +77,23 @@ github:
|
||||
# combination here.
|
||||
contexts:
|
||||
- lint-check
|
||||
- cypress-matrix-required
|
||||
- cypress-matrix (0, chrome)
|
||||
- cypress-matrix (1, chrome)
|
||||
- cypress-matrix (2, chrome)
|
||||
- cypress-matrix (3, chrome)
|
||||
- cypress-matrix (4, chrome)
|
||||
- cypress-matrix (5, chrome)
|
||||
- dependency-review
|
||||
- frontend-build
|
||||
- playwright-tests-required
|
||||
- playwright-tests (chromium)
|
||||
- pre-commit (current)
|
||||
- pre-commit (previous)
|
||||
- test-mysql
|
||||
- test-postgres-required
|
||||
- test-postgres (current)
|
||||
- test-postgres-hive
|
||||
- test-postgres-presto
|
||||
- test-sqlite
|
||||
- unit-tests-required
|
||||
- unit-tests (current)
|
||||
|
||||
required_pull_request_reviews:
|
||||
dismiss_stale_reviews: false
|
||||
|
||||
+8
-8
@@ -1,5 +1,7 @@
|
||||
# Notify all committers of DB migration changes, per SIP-59
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ body:
|
||||
label: Superset version
|
||||
options:
|
||||
- master / latest-dev
|
||||
- "6.1.0"
|
||||
- "6.0.0"
|
||||
- "5.0.0"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
||||
Submodule
+1
Submodule .github/actions/file-changes-action added at a6ca26c142
@@ -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
|
||||
|
||||
+334
-33
@@ -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,18 @@ 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"]
|
||||
# TODO: remove below entries until React >= 18.0.0
|
||||
- dependency-name: "storybook"
|
||||
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
||||
- dependency-name: "@storybook*"
|
||||
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
||||
- dependency-name: "eslint-plugin-storybook"
|
||||
- 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,41 +42,26 @@ 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"]
|
||||
groups:
|
||||
storybook:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "@storybook*"
|
||||
- "storybook"
|
||||
update-types:
|
||||
- "patch"
|
||||
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/*"
|
||||
open-pull-requests-limit: 30
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
open-pull-requests-limit: 10
|
||||
@@ -97,6 +89,22 @@ updates:
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/docs/"
|
||||
ignore:
|
||||
# TODO: remove below entries until React >= 18.0.0 in superset-frontend
|
||||
- dependency-name: "storybook"
|
||||
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
||||
- dependency-name: "@storybook*"
|
||||
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
||||
- dependency-name: "eslint-plugin-storybook"
|
||||
- dependency-name: "react-error-boundary"
|
||||
groups:
|
||||
storybook:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "@storybook*"
|
||||
- "storybook"
|
||||
update-types:
|
||||
- "patch"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
@@ -126,3 +134,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
|
||||
|
||||
@@ -20,6 +20,10 @@ set -e
|
||||
GITHUB_WORKSPACE=${GITHUB_WORKSPACE:-.}
|
||||
ASSETS_MANIFEST="$GITHUB_WORKSPACE/superset/static/assets/manifest.json"
|
||||
|
||||
# Rounded job start time, used to create a unique Cypress build id for
|
||||
# parallelization so we can manually rerun a job after 20 minutes
|
||||
NONCE=$(echo "$(date "+%Y%m%d%H%M") - ($(date +%M)%20)" | bc)
|
||||
|
||||
# Echo only when not in parallel mode
|
||||
say() {
|
||||
if [[ $(echo "$INPUT_PARALLEL" | tr '[:lower:]' '[:upper:]') != 'TRUE' ]]; then
|
||||
@@ -114,7 +118,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 +131,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
|
||||
|
||||
@@ -30,8 +30,9 @@ jobs:
|
||||
pull-requests: write
|
||||
checks: write
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: master
|
||||
@@ -40,7 +41,7 @@ jobs:
|
||||
uses: ./.github/actions/setup-supersetbot/
|
||||
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -38,19 +38,6 @@ jobs:
|
||||
if: steps.check.outputs.python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
|
||||
# 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. Best-effort: on fork PRs the
|
||||
# secrets are unavailable, so this no-ops and the pull falls back to
|
||||
# anonymous (covered by the retry loop in the script).
|
||||
- name: Login to Docker Hub
|
||||
if: steps.check.outputs.python
|
||||
continue-on-error: true
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Run uv
|
||||
if: steps.check.outputs.python
|
||||
run: ./scripts/uv-pip-compile.sh
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check and notify
|
||||
|
||||
@@ -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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
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"
|
||||
@@ -15,39 +15,12 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
python: ${{ steps.check.outputs.python }}
|
||||
frontend: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
analyze:
|
||||
name: Analyze
|
||||
needs: changes
|
||||
# Skip on PRs that touch neither code group (e.g. docs-only) so the
|
||||
# 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-24.04
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
@@ -58,13 +31,19 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -75,6 +54,7 @@ jobs:
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: "Dependency Review"
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
# the latest version. It's MIT: https://github.com/nbubna/store/blob/master/LICENSE-MIT
|
||||
# pkg:npm/node-forge@1.3.1
|
||||
# selecting BSD-3-Clause licensing terms for node-forge to ensure compatibility with Apache
|
||||
allow-dependencies-licenses: pkg:npm/rgbcolor, pkg:npm/jszip@3.10.1
|
||||
allow-dependencies-licenses: pkg:npm/store2@2.14.2, pkg:npm/node-forge@1.3.1, pkg:npm/rgbcolor, pkg:npm/jszip@3.10.1
|
||||
|
||||
python-dependency-liccheck:
|
||||
# NOTE: Configuration for liccheck lives in our pyproject.yml.
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -18,30 +18,9 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
python: ${{ steps.check.outputs.python }}
|
||||
frontend: ${{ steps.check.outputs.frontend }}
|
||||
docker: ${{ steps.check.outputs.docker }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
setup_matrix:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
matrix_config: ${{ steps.set_matrix.outputs.matrix_config }}
|
||||
steps:
|
||||
@@ -53,13 +32,8 @@ jobs:
|
||||
|
||||
docker-build:
|
||||
name: docker-build
|
||||
needs: [setup_matrix, changes]
|
||||
if: >-
|
||||
needs.changes.outputs.python == 'true' ||
|
||||
needs.changes.outputs.frontend == 'true' ||
|
||||
needs.changes.outputs.docker == 'true'
|
||||
needs: setup_matrix
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
matrix:
|
||||
build_preset: ${{fromJson(needs.setup_matrix.outputs.matrix_config)}}
|
||||
@@ -70,30 +44,20 @@ jobs:
|
||||
IMAGE_TAG: apache/superset:GHA-${{ matrix.build_preset }}-${{ github.run_id }}
|
||||
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
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: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Docker Environment
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker
|
||||
uses: ./.github/actions/setup-docker
|
||||
with:
|
||||
dockerhub-user: ${{ secrets.DOCKERHUB_USER }}
|
||||
@@ -101,9 +65,11 @@ jobs:
|
||||
build: "true"
|
||||
|
||||
- name: Setup supersetbot
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker
|
||||
uses: ./.github/actions/setup-supersetbot/
|
||||
|
||||
- name: Build Docker Image
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -119,31 +85,17 @@ jobs:
|
||||
PUSH_OR_LOAD="--load"
|
||||
fi
|
||||
|
||||
# Retry to absorb transient Docker Hub registry errors (base-image
|
||||
# pull timeouts, 504/401 on push, ECONNRESET) that otherwise fail
|
||||
# the whole job. buildx reuses the buildkit layer cache from the
|
||||
# failed attempt, so a retry mostly re-does just the failed push.
|
||||
for attempt in 1 2 3; do
|
||||
if supersetbot docker \
|
||||
$PUSH_OR_LOAD \
|
||||
--preset "$BUILD_PRESET" \
|
||||
--context "$EVENT" \
|
||||
--context-ref "$RELEASE" $FORCE_LATEST \
|
||||
--extra-flags "--build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG" \
|
||||
$PLATFORM_ARG; then
|
||||
break
|
||||
fi
|
||||
if [ "$attempt" -eq 3 ]; then
|
||||
echo "::error::supersetbot docker build failed after 3 attempts"
|
||||
exit 1
|
||||
fi
|
||||
echo "::warning::Build attempt ${attempt} failed; retrying in 30s..."
|
||||
sleep 30
|
||||
done
|
||||
supersetbot docker \
|
||||
$PUSH_OR_LOAD \
|
||||
--preset "$BUILD_PRESET" \
|
||||
--context "$EVENT" \
|
||||
--context-ref "$RELEASE" $FORCE_LATEST \
|
||||
--extra-flags "--build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG" \
|
||||
$PLATFORM_ARG
|
||||
|
||||
# in the context of push (using multi-platform build), we need to pull the image locally
|
||||
- name: Docker pull
|
||||
if: github.event_name == 'push'
|
||||
if: github.event_name == 'push' && (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker)
|
||||
run: |
|
||||
for i in 1 2 3; do
|
||||
docker pull $IMAGE_TAG && break
|
||||
@@ -151,6 +103,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Print docker stats
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker
|
||||
run: |
|
||||
echo "SHA: ${{ github.sha }}"
|
||||
echo "IMAGE: $IMAGE_TAG"
|
||||
@@ -158,7 +111,7 @@ jobs:
|
||||
docker history $IMAGE_TAG
|
||||
|
||||
- name: docker-compose sanity check
|
||||
if: matrix.build_preset == 'dev'
|
||||
if: (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker) && matrix.build_preset == 'dev'
|
||||
shell: bash
|
||||
env:
|
||||
BUILD_PRESET: ${{ matrix.build_preset }}
|
||||
@@ -171,31 +124,20 @@ jobs:
|
||||
docker-compose-image-tag:
|
||||
# Run this job only on pushes to master (not for PRs)
|
||||
# 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'
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
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: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Setup Docker Environment
|
||||
if: steps.check.outputs.docker
|
||||
uses: ./.github/actions/setup-docker
|
||||
with:
|
||||
dockerhub-user: ${{ secrets.DOCKERHUB_USER }}
|
||||
@@ -203,6 +145,7 @@ jobs:
|
||||
build: "false"
|
||||
install-docker-compose: "true"
|
||||
- name: docker-compose sanity check
|
||||
if: steps.check.outputs.docker
|
||||
shell: bash
|
||||
run: |
|
||||
docker compose -f docker-compose-image-tag.yml up superset-init --exit-code-from superset-init
|
||||
|
||||
@@ -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'
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # required for npm trusted publishing (OIDC)
|
||||
defaults:
|
||||
run:
|
||||
working-directory: superset-embedded-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
# Note: registry-url is intentionally omitted. When set, actions/setup-node
|
||||
# writes an .npmrc with `_authToken=${NODE_AUTH_TOKEN}` and a placeholder
|
||||
# token, which makes npm attempt token auth and skip the OIDC
|
||||
# trusted-publishing exchange. With no .npmrc auth line, npm authenticates
|
||||
# via OIDC against the default registry (registry.npmjs.org).
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-embedded-sdk/.nvmrc"
|
||||
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 }}
|
||||
|
||||
@@ -21,13 +21,13 @@ jobs:
|
||||
run:
|
||||
working-directory: superset-embedded-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-embedded-sdk/.nvmrc"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
node-version-file: './superset-embedded-sdk/.nvmrc'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
- run: npm run build
|
||||
|
||||
@@ -32,12 +32,12 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "11"
|
||||
|
||||
@@ -12,12 +12,8 @@ on:
|
||||
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:
|
||||
|
||||
validate-all-ghas:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
@@ -27,23 +23,20 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: "20"
|
||||
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@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
|
||||
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
|
||||
|
||||
@@ -15,8 +15,9 @@ jobs:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -2,11 +2,6 @@ name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
permissions:
|
||||
@@ -14,7 +9,7 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
|
||||
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
|
||||
with:
|
||||
sync-labels: true
|
||||
|
||||
|
||||
@@ -11,29 +11,29 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
- name: Check for latest tag
|
||||
id: latest-tag
|
||||
env:
|
||||
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
source ./scripts/tag_latest_release.sh "$RELEASE_TAG_NAME" --dry-run
|
||||
- name: Check for latest tag
|
||||
id: latest-tag
|
||||
env:
|
||||
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
source ./scripts/tag_latest_release.sh "$RELEASE_TAG_NAME" --dry-run
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name "$GITHUB_ACTOR"
|
||||
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name "$GITHUB_ACTOR"
|
||||
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||
|
||||
- name: Run latest-tag
|
||||
uses: ./.github/actions/latest-tag
|
||||
if: steps.latest-tag.outputs.SKIP_TAG != 'true'
|
||||
with:
|
||||
description: Superset latest release
|
||||
tag-name: latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
- name: Run latest-tag
|
||||
uses: ./.github/actions/latest-tag
|
||||
if: steps.latest-tag.outputs.SKIP_TAG != 'true'
|
||||
with:
|
||||
description: Superset latest release
|
||||
tag-name: latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
@@ -18,14 +18,14 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "11"
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
- name: Run license check
|
||||
run: ./scripts/check_license.sh
|
||||
|
||||
@@ -8,17 +8,6 @@ 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.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
lint-check:
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -27,7 +16,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -37,5 +26,6 @@ jobs:
|
||||
on-failed-regex-fail-action: true
|
||||
on-failed-regex-request-changes: false
|
||||
on-failed-regex-create-review: false
|
||||
on-failed-regex-comment: "Please format your PR title to match: `%regex%`!"
|
||||
on-failed-regex-comment:
|
||||
"Please format your PR title to match: `%regex%`!"
|
||||
repo-token: "${{ github.token }}"
|
||||
|
||||
@@ -7,11 +7,6 @@ on:
|
||||
- "[0-9].[0-9]*"
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened, ready_for_review]
|
||||
# Nightly full-tree sweep. Per-PR runs only lint changed files, so a change
|
||||
# that invalidates an untouched file (e.g. a type change that breaks an
|
||||
# importing test) can pass every PR yet leave master red. This catches that.
|
||||
schedule:
|
||||
- cron: "0 6 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -24,40 +19,32 @@ concurrency:
|
||||
jobs:
|
||||
pre-commit:
|
||||
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: ["current", "previous", "next"]
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
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@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "superset-frontend/package-lock.json"
|
||||
node-version: '20'
|
||||
|
||||
- name: Install Frontend Dependencies
|
||||
run: |
|
||||
@@ -70,89 +57,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 }}
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
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
|
||||
base="${BASE_SHA}"
|
||||
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")"
|
||||
|
||||
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=$?
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
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 3–6 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-24.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-24.04
|
||||
strategy:
|
||||
# Mirror the same matrix the release publisher uses so every variant
|
||||
# operators consume from Docker Hub gets the refreshed base.
|
||||
matrix:
|
||||
build_preset: ["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: "Checkout release tag: ${{ needs.config.outputs.latest-release }}"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.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-24.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}"
|
||||
@@ -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,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]
|
||||
|
||||
@@ -11,11 +10,11 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: "PR number to sync"
|
||||
description: 'PR number to sync'
|
||||
required: true
|
||||
type: number
|
||||
sha:
|
||||
description: "Specific SHA to deploy (optional, defaults to latest)"
|
||||
description: 'Specific SHA to deploy (optional, defaults to latest)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
@@ -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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
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'
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
- name: superset init
|
||||
if: steps.check.outputs.python
|
||||
run: |
|
||||
uv pip install --system -e .
|
||||
pip install -e .
|
||||
superset db upgrade
|
||||
superset load_test_users
|
||||
- name: superset load_examples
|
||||
|
||||
@@ -60,21 +60,21 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.event.workflow_run.head_sha || github.sha }}"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./docs/.nvmrc"
|
||||
node-version-file: './docs/.nvmrc'
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
distribution: "zulu"
|
||||
java-version: "21"
|
||||
distribution: 'zulu'
|
||||
java-version: '21'
|
||||
- name: Install Graphviz
|
||||
run: sudo apt-get install -y graphviz
|
||||
- name: Compute Entity Relationship diagram (ERD)
|
||||
|
||||
@@ -28,12 +28,12 @@ jobs:
|
||||
name: Link Checking
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
# Do not bump this linkinator-action version without opening
|
||||
# an ASF Infra ticket to allow the new version first!
|
||||
- uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
|
||||
- uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
|
||||
continue-on-error: true # This will make the job advisory (non-blocking, no red X)
|
||||
with:
|
||||
paths: "**/*.md, **/*.mdx"
|
||||
@@ -73,14 +73,14 @@ jobs:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./docs/.nvmrc"
|
||||
node-version-file: './docs/.nvmrc'
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install --check-cache
|
||||
@@ -112,15 +112,15 @@ jobs:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- name: "Checkout PR head: ${{ github.event.workflow_run.head_sha }}"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha }}
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./docs/.nvmrc"
|
||||
node-version-file: './docs/.nvmrc'
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install --check-cache
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
name: database-diagnostics
|
||||
path: docs/src/data/
|
||||
if_no_artifact_found: "warning"
|
||||
if_no_artifact_found: 'warning'
|
||||
- name: Use fresh diagnostics
|
||||
run: |
|
||||
if [ -f "src/data/databases-diagnostics.json" ]; then
|
||||
|
||||
@@ -10,49 +10,26 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
use_dashboard:
|
||||
description: "Use Cypress Dashboard (true/false) [paid service - trigger manually when needed]. You MUST provide a branch and/or PR number below for this to work."
|
||||
description: 'Use Cypress Dashboard (true/false) [paid service - trigger manually when needed]. You MUST provide a branch and/or PR number below for this to work.'
|
||||
required: false
|
||||
default: "false"
|
||||
default: 'false'
|
||||
ref:
|
||||
description: "The branch or tag to checkout"
|
||||
description: 'The branch or tag to checkout'
|
||||
required: false
|
||||
default: ""
|
||||
default: ''
|
||||
pr_id:
|
||||
description: "The pull request ID to checkout"
|
||||
description: 'The pull request ID to checkout'
|
||||
required: false
|
||||
default: ""
|
||||
default: ''
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
python: ${{ steps.check.outputs.python }}
|
||||
frontend: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
cypress-matrix:
|
||||
needs: changes
|
||||
if: (needs.changes.outputs.python == 'true' || needs.changes.outputs.frontend == 'true') && github.event.pull_request.draft == false
|
||||
# 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
|
||||
@@ -63,14 +40,9 @@ jobs:
|
||||
# https://github.com/cypress-io/github-action/issues/48
|
||||
fail-fast: false
|
||||
matrix:
|
||||
parallel_id: [0, 1]
|
||||
parallel_id: [0, 1, 2, 3, 4, 5]
|
||||
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
|
||||
@@ -97,60 +69,71 @@ jobs:
|
||||
# Conditional checkout based on context
|
||||
- name: Checkout for push or pull_request event
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout using ref (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
submodules: recursive
|
||||
- name: Checkout using PR ID (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
submodules: recursive
|
||||
# -------------------------------------------------------
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
- name: Setup postgres
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: setup-postgres
|
||||
- name: Import test data
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: testdata
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "superset-frontend/package-lock.json"
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install npm dependencies
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: npm-install
|
||||
- name: Build javascript packages
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: build-instrumented-assets
|
||||
- name: Install cypress
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: cypress-install
|
||||
- name: Run Cypress
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
env:
|
||||
CYPRESS_BROWSER: ${{ matrix.browser }}
|
||||
PARALLEL_ID: ${{ matrix.parallel_id }}
|
||||
PARALLELISM: 2
|
||||
PARALLELISM: 6
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||
with:
|
||||
@@ -171,10 +154,7 @@ jobs:
|
||||
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-22.04
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
@@ -207,59 +187,71 @@ jobs:
|
||||
# Conditional checkout based on context (same as Cypress workflow)
|
||||
- name: Checkout for push or pull_request event
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout using ref (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
submodules: recursive
|
||||
- name: Checkout using PR ID (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
submodules: recursive
|
||||
# -------------------------------------------------------
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
- name: Setup postgres
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: setup-postgres
|
||||
- name: Import test data
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: playwright_testdata
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "superset-frontend/package-lock.json"
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install npm dependencies
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: npm-install
|
||||
- name: Build javascript packages
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: build-instrumented-assets
|
||||
- name: Build embedded SDK
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: build-embedded-sdk
|
||||
- name: Install Playwright
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: playwright-install
|
||||
- name: Run Playwright (Required Tests)
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||
@@ -281,63 +273,3 @@ jobs:
|
||||
${{ github.workspace }}/superset-frontend/playwright-results/
|
||||
${{ github.workspace }}/superset-frontend/test-results/
|
||||
name: playwright-artifact-${{ github.run_id }}-${{ github.job }}-${{ matrix.browser }}--${{ steps.set-safe-app-root.outputs.safe_app_root }}
|
||||
|
||||
# Stable required-status-check anchors. cypress-matrix and playwright-tests
|
||||
# are matrix jobs gated on change detection (python || frontend). On a PR
|
||||
# that touches neither — e.g. a docs-only PR — they are skipped at the job
|
||||
# level, which happens before matrix expansion, so the per-combination
|
||||
# contexts (`cypress-matrix (0, chrome)`, `playwright-tests (chromium)`) are
|
||||
# never produced and branch protection waits on them forever. These
|
||||
# always-running jobs report a single stable context that passes when the
|
||||
# underlying matrix job succeeded or was skipped, and fails only on a real
|
||||
# failure. Require these in .asf.yaml instead of the matrix-expanded names.
|
||||
#
|
||||
# A matrix job reads as "skipped" in two distinct cases, and only the first
|
||||
# is a legitimate pass: (a) change detection succeeded and gated the job off
|
||||
# (docs-only PR); (b) the `changes` job itself failed or was cancelled, in
|
||||
# which case GHA skips its dependents too. Accepting (b) would let a broken
|
||||
# change-detector report a false green, so each anchor first requires
|
||||
# `changes` to have succeeded before honouring a skip.
|
||||
cypress-matrix-required:
|
||||
needs: [changes, cypress-matrix]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Check cypress-matrix result
|
||||
env:
|
||||
CHANGES: ${{ needs.changes.result }}
|
||||
RESULT: ${{ needs.cypress-matrix.result }}
|
||||
run: |
|
||||
if [ "$CHANGES" != "success" ]; then
|
||||
echo "change detection did not succeed (result: $CHANGES); refusing to pass on a skipped matrix"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$RESULT" != "success" ] && [ "$RESULT" != "skipped" ]; then
|
||||
echo "cypress-matrix did not pass (result: $RESULT)"
|
||||
exit 1
|
||||
fi
|
||||
echo "cypress-matrix result: $RESULT (changes: $CHANGES)"
|
||||
|
||||
playwright-tests-required:
|
||||
needs: [changes, playwright-tests]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Check playwright-tests result
|
||||
env:
|
||||
CHANGES: ${{ needs.changes.result }}
|
||||
RESULT: ${{ needs.playwright-tests.result }}
|
||||
run: |
|
||||
if [ "$CHANGES" != "success" ]; then
|
||||
echo "change detection did not succeed (result: $CHANGES); refusing to pass on a skipped matrix"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$RESULT" != "success" ] && [ "$RESULT" != "skipped" ]; then
|
||||
echo "playwright-tests did not pass (result: $RESULT)"
|
||||
exit 1
|
||||
fi
|
||||
echo "playwright-tests result: $RESULT (changes: $CHANGES)"
|
||||
|
||||
@@ -20,18 +20,15 @@ concurrency:
|
||||
jobs:
|
||||
test-superset-extensions-cli-package:
|
||||
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: ["previous", "current", "next"]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: superset-extensions-cli
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -56,7 +53,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
if: steps.check.outputs.superset-extensions-cli
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
flags: superset-extensions-cli
|
||||
|
||||
@@ -22,12 +22,11 @@ permissions:
|
||||
jobs:
|
||||
frontend-build:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
should-run: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -75,7 +74,6 @@ jobs:
|
||||
shard: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
@@ -105,12 +103,11 @@ jobs:
|
||||
needs: [sharded-jest-tests]
|
||||
if: needs.frontend-build.outputs.should-run == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -134,7 +131,7 @@ jobs:
|
||||
run: npx nyc merge coverage/ merged-output/coverage-summary.json
|
||||
|
||||
- name: Upload Code Coverage
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
flags: javascript
|
||||
use_oidc: true
|
||||
@@ -147,7 +144,6 @@ jobs:
|
||||
needs: frontend-build
|
||||
if: needs.frontend-build.outputs.should-run == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
@@ -172,7 +168,6 @@ jobs:
|
||||
needs: frontend-build
|
||||
if: needs.frontend-build.outputs.should-run == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
@@ -192,7 +187,6 @@ jobs:
|
||||
needs: frontend-build
|
||||
if: needs.frontend-build.outputs.should-run == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Download Docker Image Artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
|
||||
@@ -2,41 +2,38 @@ name: "Helm: lint and test charts"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
paths:
|
||||
- "helm/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Serialize runs per PR without cancelling: when a first-time contributor's
|
||||
# queued runs are approved together, cancel-in-progress lets an older run
|
||||
# cancel a newer one, leaving a permanently-cancelled required check on the
|
||||
# head SHA. Queued runs are cheap here, so let them all complete.
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: false
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint-test:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
|
||||
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"
|
||||
install-superset: 'false'
|
||||
|
||||
- name: Set up chart-testing
|
||||
uses: ./.github/actions/chart-testing-action
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref_name }}
|
||||
persist-credentials: true
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
|
||||
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
|
||||
with:
|
||||
version: v3.5.4
|
||||
|
||||
|
||||
@@ -10,46 +10,23 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: "The branch or tag to checkout"
|
||||
description: 'The branch or tag to checkout'
|
||||
required: false
|
||||
default: ""
|
||||
default: ''
|
||||
pr_id:
|
||||
description: "The pull request ID to checkout"
|
||||
description: 'The pull request ID to checkout'
|
||||
required: false
|
||||
default: ""
|
||||
default: ''
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
python: ${{ steps.check.outputs.python }}
|
||||
frontend: ${{ steps.check.outputs.frontend }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# NOTE: Required Playwright tests are in superset-e2e.yml (E2E / playwright-tests)
|
||||
# This workflow contains only experimental tests that run in shadow mode
|
||||
playwright-tests-experimental:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.frontend == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
continue-on-error: true
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -83,65 +60,78 @@ jobs:
|
||||
# Conditional checkout based on context (same as Cypress workflow)
|
||||
- name: Checkout for push or pull_request event
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout using ref (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != ''
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
submodules: recursive
|
||||
- name: Checkout using PR ID (workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != ''
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge
|
||||
submodules: recursive
|
||||
# -------------------------------------------------------
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
- name: Setup postgres
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: setup-postgres
|
||||
- name: Import test data
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: playwright_testdata
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "superset-frontend/package-lock.json"
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install npm dependencies
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: npm-install
|
||||
- name: Build javascript packages
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: build-instrumented-assets
|
||||
- name: Build embedded SDK
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: build-embedded-sdk
|
||||
- name: Install Playwright
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: playwright-install
|
||||
- name: Run Playwright (Experimental Tests)
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||
with:
|
||||
run: playwright-run "${{ matrix.app_root }}" experimental/
|
||||
- name: Run Playwright (Embedded Tests)
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||
|
||||
@@ -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:
|
||||
@@ -19,30 +14,8 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
python: ${{ steps.check.outputs.python }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
test-mysql:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
id-token: write
|
||||
env:
|
||||
@@ -54,8 +27,6 @@ jobs:
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
# Authenticated pulls use our higher Docker Hub rate limit. Empty on
|
||||
# fork PRs (secrets unavailable) -> runner falls back to anonymous.
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
ports:
|
||||
@@ -72,31 +43,41 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
if: steps.check.outputs.python
|
||||
- name: Setup MySQL
|
||||
if: steps.check.outputs.python
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: setup-mysql
|
||||
- name: Start Celery worker
|
||||
if: steps.check.outputs.python
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: celery-worker
|
||||
- name: Python integration tests (MySQL)
|
||||
if: steps.check.outputs.python
|
||||
run: |
|
||||
./scripts/python_tests.sh
|
||||
- name: Upload code coverage
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
flags: python,mysql
|
||||
verbose: true
|
||||
use_oidc: true
|
||||
slug: apache/superset
|
||||
- name: Generate database diagnostics for docs
|
||||
if: steps.check.outputs.python
|
||||
env:
|
||||
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
|
||||
SUPERSET__SQLALCHEMY_DATABASE_URI: |
|
||||
@@ -119,23 +100,19 @@ jobs:
|
||||
print(f'Generated diagnostics for {len(docs)} databases')
|
||||
"
|
||||
- name: Upload database diagnostics artifact
|
||||
if: steps.check.outputs.python
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: database-diagnostics
|
||||
path: databases-diagnostics.json
|
||||
retention-days: 7
|
||||
test-postgres:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
id-token: write
|
||||
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: ["current", "previous", "next"]
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
|
||||
@@ -157,28 +134,37 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
if: steps.check.outputs.python
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Setup Postgres
|
||||
if: steps.check.outputs.python
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: |
|
||||
setup-postgres
|
||||
- name: Start Celery worker
|
||||
if: steps.check.outputs.python
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: celery-worker
|
||||
- name: Python integration tests (PostgreSQL)
|
||||
if: steps.check.outputs.python
|
||||
run: |
|
||||
./scripts/python_tests.sh
|
||||
- name: Upload code coverage
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
flags: python,postgres
|
||||
verbose: true
|
||||
@@ -186,10 +172,7 @@ jobs:
|
||||
slug: apache/superset
|
||||
|
||||
test-sqlite:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
id-token: write
|
||||
env:
|
||||
@@ -207,51 +190,38 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
if: steps.check.outputs.python
|
||||
- name: Install dependencies
|
||||
if: steps.check.outputs.python
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: |
|
||||
# sqlite needs this working directory
|
||||
mkdir ${{ github.workspace }}/.temp
|
||||
- name: Start Celery worker
|
||||
if: steps.check.outputs.python
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: celery-worker
|
||||
- name: Python integration tests (SQLite)
|
||||
if: steps.check.outputs.python
|
||||
run: |
|
||||
./scripts/python_tests.sh
|
||||
- name: Upload code coverage
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
flags: python,sqlite
|
||||
verbose: true
|
||||
use_oidc: true
|
||||
slug: apache/superset
|
||||
|
||||
# Stable required-status-check anchor for the matrix-based test-postgres job.
|
||||
# It is gated on change detection, so on non-Python PRs it is skipped and
|
||||
# never produces its `test-postgres (current)` context (a job-level skip
|
||||
# happens before matrix expansion). This always-running job reports a single
|
||||
# context branch protection can require: it passes when test-postgres
|
||||
# succeeded or was skipped, and fails only on a real failure.
|
||||
test-postgres-required:
|
||||
needs: [changes, test-postgres]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check test-postgres result
|
||||
env:
|
||||
RESULT: ${{ needs.test-postgres.result }}
|
||||
run: |
|
||||
if [ "$RESULT" != "success" ] && [ "$RESULT" != "skipped" ]; then
|
||||
echo "test-postgres did not pass (result: $RESULT)"
|
||||
exit 1
|
||||
fi
|
||||
echo "test-postgres result: $RESULT"
|
||||
|
||||
@@ -15,30 +15,8 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
python: ${{ steps.check.outputs.python }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
test-postgres-presto:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
id-token: write
|
||||
env:
|
||||
@@ -72,25 +50,36 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
if: steps.check.outputs.python == 'true'
|
||||
- name: Setup Postgres
|
||||
if: steps.check.outputs.python
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: setup-postgres
|
||||
run: |
|
||||
echo "${{ steps.check.outputs.python }}"
|
||||
setup-postgres
|
||||
- name: Start Celery worker
|
||||
if: steps.check.outputs.python
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: celery-worker
|
||||
- name: Python unit tests (PostgreSQL)
|
||||
if: steps.check.outputs.python
|
||||
run: |
|
||||
./scripts/python_tests.sh -m 'chart_data_flow or sql_json_flow'
|
||||
- name: Upload code coverage
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
flags: python,presto
|
||||
verbose: true
|
||||
@@ -98,10 +87,7 @@ jobs:
|
||||
slug: apache/superset
|
||||
|
||||
test-postgres-hive:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
id-token: write
|
||||
env:
|
||||
@@ -127,32 +113,44 @@ jobs:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create csv upload directory
|
||||
if: steps.check.outputs.python
|
||||
run: sudo mkdir -p /tmp/.superset/uploads
|
||||
- name: Give write access to the csv upload directory
|
||||
if: steps.check.outputs.python
|
||||
run: sudo chown -R $USER:$USER /tmp/.superset
|
||||
- name: Start hadoop and hive
|
||||
if: steps.check.outputs.python
|
||||
run: docker compose -f scripts/databases/hive/docker-compose.yml up -d
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
if: steps.check.outputs.python
|
||||
- name: Setup Postgres
|
||||
if: steps.check.outputs.python
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: setup-postgres
|
||||
- name: Start Celery worker
|
||||
if: steps.check.outputs.python
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
with:
|
||||
run: celery-worker
|
||||
- name: Python unit tests (PostgreSQL)
|
||||
if: steps.check.outputs.python
|
||||
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
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
flags: python,hive
|
||||
verbose: true
|
||||
|
||||
@@ -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:
|
||||
@@ -20,62 +15,40 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
unit-tests:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
python: ${{ steps.check.outputs.python }}
|
||||
id-token: write
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["previous", "current", "next"]
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Check for file changes
|
||||
id: check
|
||||
uses: ./.github/actions/change-detector/
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
unit-tests:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
id-token: write
|
||||
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"]') }}
|
||||
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
if: steps.check.outputs.python
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Python unit tests
|
||||
if: steps.check.outputs.python
|
||||
env:
|
||||
SUPERSET_TESTENV: true
|
||||
SUPERSET_SECRET_KEY: not-a-secret
|
||||
run: |
|
||||
pytest --durations-min=0.5 --cov-report= --cov=superset ./tests/common ./tests/unit_tests --cache-clear --maxfail=50
|
||||
- name: Python 100% coverage unit tests
|
||||
if: steps.check.outputs.python
|
||||
env:
|
||||
SUPERSET_TESTENV: true
|
||||
SUPERSET_SECRET_KEY: not-a-secret
|
||||
@@ -83,31 +56,9 @@ jobs:
|
||||
pytest --durations-min=0.5 --cov=superset/sql/ ./tests/unit_tests/sql/ --cache-clear --cov-fail-under=100
|
||||
pytest --durations-min=0.5 --cov=superset/semantic_layers/ ./tests/unit_tests/semantic_layers/ --cache-clear --cov-fail-under=100
|
||||
- name: Upload code coverage
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
flags: python,unit
|
||||
verbose: true
|
||||
use_oidc: true
|
||||
slug: apache/superset
|
||||
|
||||
# Stable required-status-check anchor. `unit-tests` is a matrix job gated on
|
||||
# change detection, so on non-Python PRs it is skipped and never produces its
|
||||
# `unit-tests (current)` context (a job-level skip happens before matrix
|
||||
# expansion). This always-running job reports a single context that branch
|
||||
# protection can require: it passes when unit-tests succeeded or was skipped,
|
||||
# and fails only on a real failure.
|
||||
unit-tests-required:
|
||||
needs: [changes, unit-tests]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check unit-tests result
|
||||
env:
|
||||
RESULT: ${{ needs.unit-tests.result }}
|
||||
run: |
|
||||
if [ "$RESULT" != "success" ] && [ "$RESULT" != "skipped" ]; then
|
||||
echo "unit-tests did not pass (result: $RESULT)"
|
||||
exit 1
|
||||
fi
|
||||
echo "unit-tests result: $RESULT"
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -38,11 +38,9 @@ jobs:
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.check.outputs.frontend
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "superset-frontend/package-lock.json"
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install dependencies
|
||||
if: steps.check.outputs.frontend
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
@@ -61,7 +59,7 @@ jobs:
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
@@ -22,16 +22,11 @@ concurrency:
|
||||
jobs:
|
||||
app-checks:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: './superset-websocket/.nvmrc'
|
||||
- name: Install dependencies
|
||||
working-directory: ./superset-websocket
|
||||
run: npm ci
|
||||
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
comment_body:
|
||||
description: "Comment Body"
|
||||
description: 'Comment Body'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
});
|
||||
|
||||
- name: "Checkout ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
persist-credentials: true
|
||||
|
||||
# Authenticate the Docker daemon so the python:slim pull in
|
||||
# uv-pip-compile.sh uses our (much higher) authenticated rate limit
|
||||
# instead of the shared-runner anonymous one.
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
|
||||
continue-on-error: true
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Sync requirements in containerized environment
|
||||
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
|
||||
run: ./scripts/uv-pip-compile.sh
|
||||
|
||||
- name: Push changes to remote PRs
|
||||
if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'pip' }}
|
||||
run: |
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
||||
git add requirements
|
||||
git diff --cached --quiet && exit 0
|
||||
git commit --signoff --message "build(deps): sync pinned requirements for Dependabot pip PRs"
|
||||
git push origin "HEAD:refs/heads/${GITHUB_EVENT_PULL_REQUEST_HEAD_REF}"
|
||||
env:
|
||||
GITHUB_EVENT_PULL_REQUEST_HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||
@@ -16,20 +16,14 @@ on:
|
||||
force-latest:
|
||||
required: true
|
||||
type: choice
|
||||
default: "false"
|
||||
default: 'false'
|
||||
description: Whether to force a latest tag on the release
|
||||
options:
|
||||
- "true"
|
||||
- "false"
|
||||
- 'true'
|
||||
- 'false'
|
||||
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-24.04
|
||||
@@ -55,12 +49,12 @@ jobs:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
build_preset:
|
||||
["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]
|
||||
build_preset: ["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]
|
||||
fail-fast: false
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version: 20
|
||||
package-manager-cache: false
|
||||
@@ -125,15 +119,16 @@ jobs:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version: 20
|
||||
package-manager-cache: false
|
||||
|
||||
@@ -32,14 +32,14 @@ jobs:
|
||||
name: Generate Reports
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version-file: "./superset-frontend/.nvmrc"
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable → Regular
+4
-11
@@ -31,10 +31,9 @@ repos:
|
||||
types-simplejson,
|
||||
types-python-dateutil,
|
||||
types-requests,
|
||||
# types-redis is intentionally absent: redis-py ships its own
|
||||
# bundled types (py.typed, since 4.6+/5.x), while the stub
|
||||
# package is unmaintained (frozen at the 4.6.0.x API surface).
|
||||
# Installing the stub would shadow the accurate inline types.
|
||||
# types-redis 4.6.0.5 is failing mypy
|
||||
# because of https://github.com/python/typeshed/pull/10531
|
||||
types-redis==4.6.0.4,
|
||||
types-pytz,
|
||||
types-croniter,
|
||||
types-PyYAML,
|
||||
@@ -64,7 +63,7 @@ repos:
|
||||
hooks:
|
||||
- id: prettier-frontend
|
||||
name: prettier (frontend)
|
||||
entry: bash -c 'cd superset-frontend && files=(); for f in "$@"; do files+=("${f#superset-frontend/}"); done; npx prettier --write -- "${files[@]}"' --
|
||||
entry: bash -c 'cd superset-frontend && for file in "$@"; do npx prettier --write "${file#superset-frontend/}"; done'
|
||||
language: system
|
||||
pass_filenames: true
|
||||
files: ^superset-frontend/.*\.(js|jsx|ts|tsx|css|scss|sass|json)$
|
||||
@@ -82,12 +81,6 @@ repos:
|
||||
language: system
|
||||
pass_filenames: true
|
||||
files: ^superset-frontend/.*\.(js|jsx|ts|tsx)$
|
||||
- id: stylelint-frontend
|
||||
name: stylelint (frontend css-in-js)
|
||||
entry: ./scripts/stylelint.sh
|
||||
language: system
|
||||
pass_filenames: true
|
||||
files: ^superset-frontend/.*\.(js|jsx|ts|tsx)$
|
||||
- id: eslint-docs
|
||||
name: eslint (docs)
|
||||
entry: bash -c 'cd docs && FILES=$(printf "%s\n" "$@" | sed "s|^docs/||" | tr "\n" " ") && yarn eslint --fix --quiet $FILES'
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
.codecov.yml
|
||||
.eslintrc
|
||||
.eslintignore
|
||||
.stylelintignore
|
||||
.flake8
|
||||
.nvmrc
|
||||
.prettierrc
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+895
@@ -0,0 +1,895 @@
|
||||
Chatbot extensions
|
||||
Author: Enzo Martellucci
|
||||
Team: Preset
|
||||
Status: Draft | Under Review | Completed
|
||||
Day: May, 2026
|
||||
|
||||
1. Introduction
|
||||
This SIP proposes a new extension point that enables third-party chatbot integrations to be embedded directly into the Superset user interface through the existing extension framework.
|
||||
The goal is to provide a stable, supported mechanism for chatbot providers to integrate with Superset without requiring direct access to internal application state, Redux stores, or implementation-specific frontend modules. Chatbot extensions should interact with Superset through the same extension-oriented principles already established for other extension surfaces, such as SQL Lab.
|
||||
The proposal focuses on three core concerns:
|
||||
• Defining how chatbot extensions are registered and rendered.
|
||||
• Defining how chatbot extensions receive contextual information about the currently active application surface.
|
||||
• Defining how administrators manage chatbot availability and select the active chatbot when multiple chatbot extensions are installed.
|
||||
This SIP intentionally does not prescribe any specific chatbot implementation, user experience, LLM provider, or backend architecture.
|
||||
1.1 Motivation
|
||||
AI-powered assistants are becoming a common way for users to interact with analytical applications. Superset should provide a standardized extension mechanism that allows community-built chatbot integrations to participate in the platform without depending on internal frontend implementation details.
|
||||
Today, chatbot integrations must either be embedded through custom application modifications or rely on unsupported access to internal application state. Both approaches create maintenance challenges and make integrations fragile when frontend architecture evolves.
|
||||
|
||||
This SIP introduces a stable extension contract that:
|
||||
• Enables chatbot integrations to be distributed as standard Superset extensions.
|
||||
• Preserves separation between host and extension responsibilities.
|
||||
• Allows chatbot implementations to access contextual information about the current page and entity being viewed.
|
||||
• Keeps authorization and permission enforcement aligned with existing Superset APIs.
|
||||
• Remains compatible with future frontend architecture changes.
|
||||
1.2 Goals
|
||||
The goals of this SIP are:
|
||||
Introduce a dedicated chatbot extension point within the Superset application shell.
|
||||
Provide chatbot extensions with host-managed, permission-aligned page context.
|
||||
Establish stable extension-facing APIs for dashboard, explore, dataset, and navigation context.
|
||||
Support deployment-wide administration of chatbot availability and selection.
|
||||
Maintain isolation between chatbot implementations and Superset internals.
|
||||
Preserve compatibility with future extension capabilities and AI-related initiatives.
|
||||
1.3 Out of Scope
|
||||
The following capabilities are explicitly out of scope for this SIP.
|
||||
Client Actions and Agentic UI Manipulation
|
||||
This SIP defines how chatbot extensions are mounted and how they receive context from the host application.
|
||||
It does not define how a chatbot performs actions within the user interface, such as:
|
||||
• Modifying chart configuration.
|
||||
• Updating dashboard layouts.
|
||||
• Editing SQL queries.
|
||||
• Triggering frontend workflows.
|
||||
These capabilities are deferred to the proposed Client Actions SIP.
|
||||
Chatbot User Experience
|
||||
The chatbot user interface remains entirely owned by the extension.
|
||||
This SIP does not prescribe:
|
||||
• Visual design.
|
||||
• Conversation experience.
|
||||
• Streaming behavior.
|
||||
• Message persistence.
|
||||
• Prompting strategy.
|
||||
• Accessibility implementation details.
|
||||
• Branding or styling.
|
||||
LLM and Backend Infrastructure
|
||||
The following concerns remain extension-specific:
|
||||
• Model providers.
|
||||
• MCP implementations.
|
||||
• Agent frameworks.
|
||||
• Tool execution systems.
|
||||
• Prompt orchestration.
|
||||
• Backend services.
|
||||
Superset acts only as the host application and context provider.
|
||||
|
||||
2. Requirements
|
||||
|
||||
2.1 Functional Requirements
|
||||
Registration and Rendering
|
||||
The platform must allow extensions to register chatbot providers through the standard extension system.
|
||||
The host must:
|
||||
• Support registration of chatbot extensions.
|
||||
• Render a chatbot UI contributed by an extension.
|
||||
• Maintain a single active chatbot instance at any given time.
|
||||
• Make the chatbot available across supported application surfaces.
|
||||
• Support fully custom chatbot user interfaces.
|
||||
|
||||
Context Sharing
|
||||
The platform must provide chatbot extensions with contextual information about the user's current application state.
|
||||
At minimum, the host must expose:
|
||||
• Current page type (`home`, `dashboard`, `explore`, `sqllab`, `dataset`, `other`).
|
||||
• Dashboard context.
|
||||
• Explore/chart context.
|
||||
• Dataset identity context.
|
||||
• SQL Lab context.
|
||||
• Navigation events.
|
||||
The chatbot must be notified of relevant context changes without polling.
|
||||
Examples include:
|
||||
• Route changes.
|
||||
• Dashboard changes.
|
||||
• Chart changes.
|
||||
• Dataset changes.
|
||||
• Title changes.
|
||||
• Filter changes.
|
||||
Host-Owned Context
|
||||
Context exposed to extensions must be computed by the host application.
|
||||
Extensions must not be required to:
|
||||
• Read Redux state.
|
||||
• Access internal application modules.
|
||||
• Depend on component-level implementation details.
|
||||
• Reconstruct semantic context from frontend internals.
|
||||
Instead, extensions consume stable namespace APIs provided by the host.
|
||||
Conversation State
|
||||
The conversation state remains entirely owned by the chatbot extension.
|
||||
This includes:
|
||||
• Message history.
|
||||
• Tool execution state.
|
||||
• Streaming buffers.
|
||||
• Conversation persistence.
|
||||
• Session management.
|
||||
The host is responsible only for exposing contextual information.
|
||||
2.2 Non-Functional Requirements
|
||||
Security and Authorization
|
||||
Context shared with chatbot extensions must remain aligned with Superset's existing authorization model.
|
||||
The host must not expose:
|
||||
• Entities the current user cannot access.
|
||||
• Metadata outside the user's permission scope.
|
||||
• Datasource-derived information unavailable through existing APIs.
|
||||
Authorization remains enforced by backend APIs. The extension-facing APIs defined by this SIP operate on data that has already been scoped to the current user.
|
||||
Stable Extension Contracts
|
||||
Extension-facing APIs must remain independent of frontend implementation details.
|
||||
Extensions should rely on documented namespace contracts rather than:
|
||||
• Redux slices.
|
||||
• Internal selectors.
|
||||
• Component state.
|
||||
• Routing implementation details.
|
||||
This allows frontend architecture to evolve without breaking extensions.
|
||||
Performance
|
||||
The architecture must minimize impact on existing application performance.
|
||||
In particular:
|
||||
• Context APIs must avoid unnecessary application re-renders.
|
||||
• Context change notifications must not rely on polling.
|
||||
• Chatbot integrations should not introduce additional work for unrelated surfaces.
|
||||
Fault Isolation
|
||||
Failures within chatbot extensions must not affect the stability of the host application.
|
||||
Errors originating from third-party chatbot implementations should be isolated to the chatbot mount boundary.
|
||||
Extensibility
|
||||
The architecture should support future:
|
||||
• Application surfaces.
|
||||
• AI-related capabilities.
|
||||
• Extension APIs.
|
||||
• Context providers.
|
||||
without requiring redesign of the chatbot extension model.
|
||||
Vendor Neutrality
|
||||
The architecture must remain independent of any specific:
|
||||
• LLM provider.
|
||||
• AI platform.
|
||||
• Agent framework.
|
||||
• Backend implementation.
|
||||
|
||||
3. Administration
|
||||
3.1 Overview
|
||||
Administrators can manage chatbot availability and select the active chatbot when multiple chatbot extensions are installed.
|
||||
Administration is exposed through the existing Extensions management interface.
|
||||
For chatbot extensions, administrators can:
|
||||
• Enable or disable individual chatbot extensions.
|
||||
• Select the default chatbot when multiple chatbot providers are available.
|
||||
Only one chatbot may be active at a time.
|
||||
3.2 Default Chatbot Selection
|
||||
Extensions that contribute a chatbot view participate in a deployment-wide chatbot selection process.
|
||||
The host discovers available chatbot candidates from the chatbot contribution location and allows administrators to designate a single active chatbot.
|
||||
|
||||
When multiple chatbot extensions are installed:
|
||||
Administrators select the preferred chatbot.
|
||||
The host resolves the active chatbot using the configured selection.
|
||||
Only the selected chatbot is rendered.
|
||||
Changes are applied dynamically without requiring a page reload.
|
||||
3.3 Scope of Administration
|
||||
The administration model introduced by this SIP is deployment-wide.
|
||||
Administrative settings answer the question:
|
||||
"Which chatbot integrations are available within this Superset deployment?"
|
||||
They do not answer:
|
||||
"Which chatbot integrations does a specific user prefer to use?"
|
||||
This distinction is intentional.
|
||||
Deployment administrators determine which integrations are available across the environment, while user-specific preferences remain a separate concern.
|
||||
3.4 Future User Preferences
|
||||
Per-user chatbot preferences are considered an important future capability but are intentionally out of scope for this SIP.
|
||||
This proposal does not introduce user-scoped extension availability.
|
||||
Instead, future user preferences should be layered on top of deployment availability using the following model:
|
||||
Effective Availability = Deployment Availability AND User Preference
|
||||
The recommended persistence layer for future user preferences is the Extension Storage API, which provides user-scoped extension storage and aligns with the architecture established by SIP-127 (User Preferences).
|
||||
This separation preserves a clear distinction between:
|
||||
• Deployment configuration.
|
||||
• User customization.
|
||||
and avoids introducing multiple ownership models for extension availability.
|
||||
Consequently, this SIP focuses exclusively on deployment-wide administration and active chatbot selection. 4. Proposed Extension Point
|
||||
4.1 Overview
|
||||
This SIP introduces a single extension point that allows chatbot providers to integrate directly into the Superset application shell.
|
||||
Extension Point
|
||||
Contribution Location
|
||||
Registration API
|
||||
Cardinality
|
||||
Chatbot Bubble
|
||||
superset.chatbot
|
||||
views.registerView()
|
||||
Singleton
|
||||
|
||||
The chatbot contribution point is application-wide and persists across supported Superset surfaces, including dashboards, Explore, SQL Lab, and dataset-related pages.
|
||||
Unlike most contribution locations, which allow multiple contributions to be rendered simultaneously, the chatbot location is intentionally exclusive and renders a single active provider.
|
||||
|
||||
4.2 Chatbot Contribution Location
|
||||
Contribution Area
|
||||
The contribution location introduced by this SIP is:
|
||||
superset.chatbot
|
||||
The host provides a fixed mount point within the application shell and renders the active chatbot provider at that location.
|
||||
The mount point persists across route changes, allowing chatbot conversations and UI state to remain available while users navigate between application surfaces.
|
||||
The chatbot extension contributes a single React component representing the entire chatbot experience.
|
||||
Manifest Support
|
||||
The current contribution manifest schema is focused on SQL Lab contribution locations and does not provide an application-shell-level contribution scope.
|
||||
To support chatbot integrations, the manifest schema must be extended with an application-level contribution scope capable of declaring:
|
||||
{
|
||||
"views": {
|
||||
"app": [
|
||||
{
|
||||
"location": "superset.chatbot"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
This is a schema-level change and requires updates to both:
|
||||
• Manifest validation.
|
||||
• Runtime registration infrastructure.
|
||||
The runtime registration API alone is not sufficient because chatbot contributions must also be discoverable through extension manifests.
|
||||
4.3 Singleton Rendering Model
|
||||
The chatbot location is intentionally exclusive.
|
||||
Only one chatbot may be active at a time.
|
||||
This differs from other contribution locations that allow multiple views to be rendered simultaneously.
|
||||
Motivation
|
||||
Chatbot interactions are inherently conversational and user-focused.
|
||||
Rendering multiple chatbot providers simultaneously would:
|
||||
• Create competing user experiences.
|
||||
• Introduce ambiguity regarding which chatbot should respond.
|
||||
• Increase UI complexity.
|
||||
• Reduce discoverability.
|
||||
For these reasons, chatbot rendering is treated as a deployment-level selection rather than a multi-provider composition model.
|
||||
Resolution Rules
|
||||
The host applies the following behavior:
|
||||
Installed Chatbots
|
||||
Behavior
|
||||
None
|
||||
No chatbot is rendered
|
||||
One
|
||||
The chatbot is rendered automatically
|
||||
Multiple
|
||||
The administrator-selected chatbot is rendered
|
||||
|
||||
The singleton policy is implemented entirely by the host.
|
||||
Extensions continue to register normally through the existing view registry.
|
||||
4.4 Provider Isolation
|
||||
A key architectural principle of this SIP is that extensions may discover registrations but may not invoke another extension's rendering logic.
|
||||
Public View Discovery
|
||||
The existing registry exposes:
|
||||
getViews(location);
|
||||
This API returns metadata describing registered views:
|
||||
interface View {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
}
|
||||
The returned descriptors are intentionally passive metadata.
|
||||
They allow extensions and host components to:
|
||||
• Discover available contributions.
|
||||
• Display contribution information.
|
||||
• Populate administration interfaces.
|
||||
They do not allow rendering.
|
||||
Why Providers Are Not Exposed
|
||||
The view provider is executable rendering logic.
|
||||
If providers were exposed through the public registry:
|
||||
• Extensions could render another extension's UI.
|
||||
• Extensions could bypass host lifecycle management.
|
||||
• Extensions could circumvent fault-isolation boundaries.
|
||||
• Rendering ownership would become ambiguous.
|
||||
This would violate the separation between extension discovery and extension execution.
|
||||
For this reason:
|
||||
"Extensions may discover registered views, but only the host may render registered views."
|
||||
Host-Managed Resolution
|
||||
The host uses internal APIs to resolve the active chatbot provider.
|
||||
These APIs are not exposed through the public extension surface.
|
||||
Conceptually:
|
||||
const provider = getViewProvider("superset.chatbot", selectedId);
|
||||
The active chatbot is determined through a host-managed resolution policy:
|
||||
const chatbot = getActiveChatbot(adminSelectedId, enabledMap);
|
||||
This policy considers:
|
||||
• Enabled state.
|
||||
• Administrative selection.
|
||||
• Runtime settings.
|
||||
• Registration state.
|
||||
|
||||
before rendering any provider.
|
||||
As a result, chatbot selection is implemented as a host-side rendering policy rather than a new registration primitive.
|
||||
4.5 Chatbot Lifecycle
|
||||
Host Responsibilities
|
||||
The host is responsible for:
|
||||
• Providing the chatbot mount point.
|
||||
• Resolving the active chatbot provider.
|
||||
• Loading chatbot extensions.
|
||||
• Managing chatbot lifecycle integration.
|
||||
• Handling activation and deactivation.
|
||||
• Maintaining fault isolation boundaries.
|
||||
• Preserving chatbot availability across route changes.
|
||||
• Providing context APIs defined by this SIP.
|
||||
The host also provides fixed positioning and layering behavior to ensure chatbot visibility remains consistent throughout the application.
|
||||
Fault Isolation
|
||||
Chatbot providers execute within a host-managed boundary.
|
||||
Failures originating from a chatbot extension must not affect the rest of the application.
|
||||
Examples include:
|
||||
• Module Federation loading failures.
|
||||
• Runtime exceptions.
|
||||
• Provider initialization errors.
|
||||
If a chatbot fails to load, the host logs the failure, surfaces an appropriate notification, and continues operating normally.
|
||||
The application shell remains functional even when the chatbot provider is unavailable.
|
||||
|
||||
4.6 Extension Responsibilities
|
||||
The registered chatbot component owns the complete chatbot experience.
|
||||
The extension is responsible for:
|
||||
User Interface
|
||||
• Collapsed bubble UI.
|
||||
• Expanded panel UI.
|
||||
• Branding.
|
||||
• Icons and badges.
|
||||
• Layout.
|
||||
• Responsiveness.
|
||||
Interaction Model
|
||||
• Open and close behavior.
|
||||
• Keyboard shortcuts.
|
||||
• Focus management.
|
||||
• Accessibility behavior.
|
||||
• Conversation navigation.
|
||||
Conversation Runtime
|
||||
• Message history.
|
||||
• Streaming state.
|
||||
• Tool execution.
|
||||
• Persistence.
|
||||
• Session management.
|
||||
Backend Integration
|
||||
• LLM communication.
|
||||
• MCP integration.
|
||||
• Agent orchestration.
|
||||
• Tool invocation.
|
||||
The host does not manage any chatbot-specific runtime state.
|
||||
|
||||
4.7 Registration Example
|
||||
Chatbot extensions register a single provider through the existing view registration API.
|
||||
import { views, type ExtensionContext } from '@apache-superset/core';
|
||||
import { ChatbotApp } from './ChatbotApp';
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
const disposable = views.registerView(
|
||||
{
|
||||
id: 'acme.chatbot',
|
||||
name: 'Acme Chatbot',
|
||||
icon: 'Bubble',
|
||||
},
|
||||
'superset.chatbot',
|
||||
() => <ChatbotApp />,
|
||||
);
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
The registration process remains consistent with existing extension contribution patterns.
|
||||
The only difference is that the host applies singleton resolution before selecting the provider to render.
|
||||
|
||||
4.8 Chatbot Descriptor Metadata
|
||||
Chatbot registrations may include an optional icon descriptor.
|
||||
{
|
||||
id: 'acme.chatbot',
|
||||
name: 'Acme Chatbot',
|
||||
icon: 'Bubble',
|
||||
}
|
||||
This metadata is used by:
|
||||
• Extension administration interfaces.
|
||||
• Chatbot selection interfaces.
|
||||
• Extension discovery surfaces.
|
||||
Design Decision
|
||||
The icon descriptor is treated as static registration metadata.
|
||||
Runtime UI state such as:
|
||||
• Notification indicators.
|
||||
• Unread counts.
|
||||
• Loading states.
|
||||
• Thinking indicators.
|
||||
belongs to the chatbot component itself rather than the registration descriptor.
|
||||
This keeps the registry simple while allowing chatbot implementations complete control over their user experience.
|
||||
If future requirements emerge for host-visible dynamic icon updates, that capability can be introduced independently without expanding the registration model defined by this SIP. 5. Context and Namespace Model
|
||||
5.1 Overview
|
||||
Chatbot extensions require access to contextual information about the user's current activity within Superset. This SIP introduces a namespace-based context model that allows extensions to consume stable, host-managed APIs rather than depending on internal frontend implementation details.
|
||||
The host exposes context through a set of surface-specific namespaces. Each namespace owns the context for a particular application surface and provides:
|
||||
• Synchronous state getters.
|
||||
• Event-based change notifications.
|
||||
• Stable extension-facing contracts.
|
||||
• Context aligned with the current user's authorized application view.
|
||||
Extensions consume these namespaces and compose them into higher-level context models tailored to their own use cases.
|
||||
5.2 Design Principles
|
||||
The namespace model is guided by the following principles.
|
||||
Stable Extension Contracts
|
||||
Extensions must depend on documented APIs rather than frontend implementation details.
|
||||
In particular, extensions must not depend on:
|
||||
• Redux slices.
|
||||
• Store shape.
|
||||
• Selectors.
|
||||
• Component-local state.
|
||||
• Routing implementation details.
|
||||
This allows Superset to evolve its frontend architecture without breaking extension integrations.
|
||||
Host-Owned Context Normalization
|
||||
The host is responsible for transforming application state into semantic extension-facing contracts.
|
||||
Extensions consume normalized context rather than deriving it from raw frontend state.
|
||||
Backend-Authorized Context
|
||||
Authorization remains a backend responsibility.
|
||||
Namespaces expose context that has already been scoped by backend APIs according to the current user's permissions.
|
||||
Namespaces do not implement authorization logic themselves and should not be considered security boundaries.
|
||||
Event-Driven Updates
|
||||
Context changes are propagated through events rather than polling.
|
||||
Extensions can subscribe to context updates and react immediately when relevant application state changes.
|
||||
5.3 Available Namespaces
|
||||
The following namespaces are available to chatbot extensions.
|
||||
Namespace
|
||||
Status
|
||||
Purpose
|
||||
sqlLab
|
||||
Existing
|
||||
SQL Lab context and events
|
||||
authentication
|
||||
Existing
|
||||
Current user and session context
|
||||
commands
|
||||
Existing
|
||||
Host actions and commands
|
||||
dashboard
|
||||
New
|
||||
Dashboard context
|
||||
explore
|
||||
New
|
||||
Explore/chart context
|
||||
dataset
|
||||
New
|
||||
Dataset identity context
|
||||
navigation
|
||||
New
|
||||
Routing and page context
|
||||
|
||||
The new namespaces introduced by this SIP follow the same high-level contract pattern established by the existing sqlLab namespace.
|
||||
5.4 Namespace API Shape
|
||||
Each namespace follows a common structure:
|
||||
const current = namespace.getCurrent();
|
||||
const disposable = namespace.onDidChange((next) => {
|
||||
// react to updates
|
||||
});
|
||||
The exact contracts differ by surface, but every namespace provides:
|
||||
• One or more synchronous getters.
|
||||
• Event-based change notifications.
|
||||
• Stable semantic contracts.
|
||||
This pattern allows extensions to remain synchronized with application state without polling.
|
||||
|
||||
5.5 Dashboard Namespace
|
||||
The dashboard namespace provides contextual information about the currently active dashboard.
|
||||
API
|
||||
dashboard.getCurrentDashboard();
|
||||
Contract
|
||||
interface DashboardContext {
|
||||
dashboardId: number;
|
||||
title: string;
|
||||
filters: FilterValue[];
|
||||
charts: ChartSummary[];
|
||||
}
|
||||
|
||||
interface ChartSummary {
|
||||
chartId: number;
|
||||
chartName: string;
|
||||
vizType: string;
|
||||
datasourceId: number | null;
|
||||
datasourceName: string | null;
|
||||
isVisible: boolean;}
|
||||
The context includes:
|
||||
• Dashboard identity.
|
||||
• Active filter state.
|
||||
• Dashboard charts.
|
||||
• Per-chart visibility information.
|
||||
Returning all charts while exposing visibility allows chatbot implementations to answer both:
|
||||
• "Which charts are currently visible?"
|
||||
• "Find the chart named Revenue by Region."
|
||||
without requiring additional lookups.
|
||||
Normalization Requirements
|
||||
The namespace must expose semantic dashboard context rather than raw application state.
|
||||
For example:
|
||||
dashboard.getCurrentDashboard();
|
||||
returns a normalized contract rather than Redux slices or internal entities.
|
||||
This abstraction layer preserves compatibility as frontend implementation details evolve.
|
||||
Page-Type Guarding
|
||||
The getter returns undefined when the current page is not a dashboard.
|
||||
Conceptually:
|
||||
if (navigation.getPageType() !== "dashboard") {
|
||||
return undefined;
|
||||
}
|
||||
This prevents stale dashboard state from leaking across application surfaces.
|
||||
5.6 Explore Namespace
|
||||
The explore namespace provides context for the currently active Explore session.
|
||||
API
|
||||
explore.getCurrentChart();
|
||||
Contract
|
||||
interface ChartContext {
|
||||
chartId: number | null;
|
||||
chartName: string | null;
|
||||
datasourceId: number | null;
|
||||
datasourceName: string | null;
|
||||
vizType: string;
|
||||
}
|
||||
|
||||
The namespace exposes:
|
||||
• Chart identity. `chartId` and `chartName` are null for a new, unsaved chart that has not yet been persisted.
|
||||
• Saved chart metadata (name, datasource, viz type)
|
||||
• Current Explore context: `vizType` reflects the type currently selected in the editor, so the value tracks the live session rather than only the last saved state.
|
||||
The contract is intentionally focused on chart-specific information relevant to chatbot integrations.
|
||||
Reflecting the live editing session — rather than reconstructing chart state from
|
||||
the route alone — is the primary reason this SIP exposes frontend context
|
||||
directly (see §6.2, Option C).
|
||||
|
||||
Page-Type Guarding
|
||||
The getter returns undefined when the current page is not an Explore surface.
|
||||
Conceptually:
|
||||
if (navigation.getPageType() !== "explore") {
|
||||
return undefined;
|
||||
}
|
||||
This ensures the namespace reflects only active Explore context.
|
||||
5.7 Dataset Namespace
|
||||
The dataset namespace exposes the dataset currently being viewed or edited.
|
||||
API
|
||||
dataset.getCurrentDataset();
|
||||
Contract
|
||||
interface DatasetContext {
|
||||
datasetId: number;
|
||||
datasetName: string;
|
||||
schema: string | null;
|
||||
catalog: string | null;
|
||||
databaseName: string | null;
|
||||
isVirtual: boolean;}
|
||||
This contract is intentionally identity-focused.
|
||||
It answers:
|
||||
• Which dataset is currently in focus?
|
||||
• Is the dataset virtual or physical?
|
||||
• Which database and schema does it belong to?
|
||||
It does not expose:
|
||||
• Column definitions.
|
||||
• Lineage information.
|
||||
• Dataset dependencies.
|
||||
Those concerns are expected to be resolved by backend services using the dataset identifier.
|
||||
Producer-Backed Context
|
||||
Unlike dashboard and explore namespaces, dataset pages do not currently expose a shared source of truth suitable for namespace consumption.
|
||||
For this reason, dataset context is published by dataset pages through a host-managed producer mechanism.
|
||||
Dataset pages publish the active dataset as it loads, and:
|
||||
dataset.getCurrentDataset();
|
||||
returns the most recently published value.
|
||||
Until dataset information has been published, the getter returns:
|
||||
undefined;
|
||||
This design keeps the public contract stable without requiring the introduction of a dedicated Redux slice.
|
||||
|
||||
Example Use Cases
|
||||
The dataset namespace enables chatbot workflows such as:
|
||||
• Explain this dataset.
|
||||
• Summarize this dataset's purpose.
|
||||
• Show lineage for this dataset.
|
||||
• Which charts depend on this dataset?
|
||||
The namespace provides the identity required to perform those lookups while avoiding duplication of backend metadata.
|
||||
5.8 Navigation Namespace
|
||||
The navigation namespace provides routing-related context.
|
||||
API
|
||||
navigation.getPageType();
|
||||
Events
|
||||
navigation.onDidChangePage(...)
|
||||
|
||||
Contract
|
||||
type PageType =
|
||||
| "home"
|
||||
| "dashboard"
|
||||
| "explore"
|
||||
| "sqllab"
|
||||
| "dataset"
|
||||
| "other";
|
||||
The namespace answers a single question:
|
||||
"Which application surface is currently active?"
|
||||
It intentionally does not expose entity-specific information.
|
||||
Entity context remains owned by the corresponding surface namespace.
|
||||
Examples:
|
||||
dashboard.getCurrentDashboard();
|
||||
explore.getCurrentChart();
|
||||
dataset.getCurrentDataset();
|
||||
This separation preserves clear ownership boundaries and prevents duplication across namespaces.
|
||||
|
||||
5.9 Context Composition
|
||||
This SIP intentionally does not introduce a host-owned aggregate context object.
|
||||
Instead, extensions compose the context they require from individual namespaces.
|
||||
For example:
|
||||
const pageContext = {
|
||||
pageType: navigation.getPageType(),
|
||||
dashboard: dashboard.getCurrentDashboard(),
|
||||
chart: explore.getCurrentChart(),
|
||||
dataset: dataset.getCurrentDataset(),
|
||||
sqlLab: sqlLab.getCurrentTab(),
|
||||
};
|
||||
The extension assembles a higher-level context tailored to its own requirements.
|
||||
The host remains responsible for:
|
||||
• Context ownership.
|
||||
• Context normalization.
|
||||
• Authorization alignment.
|
||||
The extension remains responsible for:
|
||||
• Context composition.
|
||||
• Prompt construction.
|
||||
• Application-specific interpretation.
|
||||
This separation avoids introducing a centralized context abstraction while allowing new surfaces to be added incrementally over time.
|
||||
|
||||
5.10 Compatibility and Evolution
|
||||
Namespace contracts are part of the public Superset extension API surface.
|
||||
Breaking changes require standard compatibility and deprecation processes.
|
||||
Extensions should depend only on documented namespace contracts and must not rely on implementation details behind those contracts.
|
||||
As new application surfaces become extension-aware, additional namespaces may be introduced without affecting existing integrations.
|
||||
This additive model allows the extension ecosystem to evolve while preserving backward compatibility.
|
||||
|
||||
6. Design Decisions
|
||||
This section consolidates the key architectural decisions made by this SIP and summarizes the alternatives that were evaluated.
|
||||
The goal is to capture the rationale behind the extension model so that future contributors can understand not only what was selected, but why alternative approaches were rejected.
|
||||
6.1 Decision Summary
|
||||
Decision
|
||||
Topic
|
||||
Selected Approach
|
||||
D1
|
||||
Page Context Model
|
||||
Extension-composed context from host-provided namespaces
|
||||
D2
|
||||
Chatbot Resolution
|
||||
Host-managed singleton resolution
|
||||
D3
|
||||
Descriptor Metadata
|
||||
Static icon metadata
|
||||
D4
|
||||
Administration Scope
|
||||
Deployment-wide administration
|
||||
D5
|
||||
Per-Page Visibility
|
||||
Deferred - open question, see §8
|
||||
D6
|
||||
Generalized Floating Slots
|
||||
Deferred - open question, see §8
|
||||
|
||||
6.2 D1 — Page Context Model
|
||||
A central design question is how chatbot extensions obtain contextual information about the currently active application surface.
|
||||
Three approaches were considered.
|
||||
Option A — Host-Owned Aggregate Context
|
||||
The host exposes a single API:
|
||||
context.getPageContext();
|
||||
which returns a fully assembled context object containing dashboard, chart, dataset, navigation, and SQL Lab information.
|
||||
Rejected Because
|
||||
• The host becomes responsible for understanding every application surface.
|
||||
• The aggregate contract grows whenever a new surface is introduced.
|
||||
• Changes in any surface can trigger unnecessary recomputation.
|
||||
• The host becomes coupled to a single canonical context model.
|
||||
• Ownership boundaries become unclear over time.
|
||||
Option B — Surface Namespaces Composed by Extensions (Selected)
|
||||
The host exposes independent namespaces:
|
||||
• dashboard
|
||||
• explore
|
||||
• dataset
|
||||
• navigation
|
||||
• sqlLab
|
||||
Extensions compose these primitives into their own application-specific context.
|
||||
Advantages
|
||||
• Clear ownership boundaries.
|
||||
• Independent evolution of namespaces.
|
||||
• Additive extensibility.
|
||||
• Reduced coupling between surfaces.
|
||||
• Extensions subscribe only to the context they require.
|
||||
Option C — Route-Only Context
|
||||
The host exposes only routing information.
|
||||
Chatbot providers independently reconstruct context through APIs or backend services.
|
||||
Rejected Because
|
||||
This approach cannot reliably represent transient frontend state.
|
||||
Examples include:
|
||||
• Unsaved chart edits.
|
||||
• Temporary dashboard filters.
|
||||
• Active dashboard tabs.
|
||||
• SQL editor state.
|
||||
• Draft configuration changes.
|
||||
As a result, chatbot context would frequently drift from what the user is actually viewing.
|
||||
Decision
|
||||
Option B is selected.
|
||||
The host owns context normalization while extensions own context composition.
|
||||
This preserves separation of concerns, minimizes coupling, and provides a stable foundation for future extension capabilities.
|
||||
6.3 D2 — Singleton Chatbot Resolution
|
||||
When multiple chatbot extensions are installed, the host must determine which chatbot is rendered.
|
||||
This decision shapes both the rendering model and the extension isolation model.
|
||||
Option A — Expose Providers Through getViews()
|
||||
Allow:
|
||||
getViews(location);
|
||||
to return both metadata and rendering providers.
|
||||
Rejected Because
|
||||
Rendering providers are executable logic.
|
||||
Exposing providers would allow one extension to:
|
||||
• Render another extension.
|
||||
• Bypass host lifecycle management.
|
||||
• Circumvent fault isolation.
|
||||
• Assume ownership of another extension's UI.
|
||||
This violates a deliberate separation between extension discovery and extension execution.
|
||||
Option B — Host-Managed Provider Resolution (Selected)
|
||||
The host exposes only metadata publicly while retaining provider resolution internally.
|
||||
Conceptually:
|
||||
const provider = getViewProvider("superset.chatbot", selectedId);
|
||||
Chatbot selection is handled through a host-managed policy:
|
||||
const chatbot = getActiveChatbot(adminSelectedId, enabledMap);
|
||||
Advantages
|
||||
• Preserves extension isolation.
|
||||
• Preserves host ownership of rendering.
|
||||
• Supports administrative selection.
|
||||
• Supports enablement checks.
|
||||
• Supports future policy evolution.
|
||||
Option C — Reuse resolveView()
|
||||
Use the existing rendering helper:
|
||||
resolveView(id);
|
||||
to render chatbot providers.
|
||||
Rejected Because
|
||||
resolveView() assumes the caller already knows which view should be rendered.
|
||||
It does not account for:
|
||||
• Administrative selection.
|
||||
• Enablement state.
|
||||
• Settings synchronization.
|
||||
• Chatbot-specific resolution policy.
|
||||
Decision
|
||||
Option B is selected.
|
||||
The host owns chatbot selection and rendering.
|
||||
The registry remains a discovery mechanism rather than a rendering mechanism.
|
||||
Architectural Principle
|
||||
A core principle established by this SIP is:
|
||||
"Extensions may discover registered views, but only the host may render registered views."
|
||||
This preserves extension isolation and prevents cross-extension rendering dependencies.
|
||||
6.4 D3 — Descriptor Metadata Ownership
|
||||
Chatbot registrations may include metadata used by administrative and discovery interfaces.
|
||||
A key question is whether descriptor metadata should be static or runtime-updatable.
|
||||
|
||||
Option A — Static Descriptor Metadata (Selected)
|
||||
Metadata is defined at registration time and remains unchanged for the lifetime of the registration.
|
||||
Example:
|
||||
{
|
||||
id: 'acme.chatbot',
|
||||
name: 'Acme Chatbot',
|
||||
icon: 'Bubble',
|
||||
}
|
||||
Advantages
|
||||
• Simpler registry implementation.
|
||||
• Clear ownership model.
|
||||
• Consistent administration UI.
|
||||
• No registry update lifecycle.
|
||||
Option B — Runtime-Updatable Metadata
|
||||
Extensions can update descriptor metadata after registration.
|
||||
Examples:
|
||||
• Notification badges.
|
||||
• Thinking indicators.
|
||||
• Dynamic branding.
|
||||
Rejected Because
|
||||
These states belong to the chatbot user interface rather than the registration descriptor.
|
||||
Supporting dynamic metadata would:
|
||||
• Increase registry complexity.
|
||||
• Introduce update synchronization concerns.
|
||||
• Provide limited benefit for current consumers.
|
||||
Decision
|
||||
Option A is selected.
|
||||
Descriptor metadata remains static.
|
||||
Dynamic UI state remains the responsibility of the chatbot component.
|
||||
Future requirements for dynamic metadata can be addressed independently if needed.
|
||||
6.5 D4 — Administration Scope
|
||||
This SIP introduces deployment-wide chatbot administration.
|
||||
A key question is whether availability should be deployment-scoped or user-scoped.
|
||||
Option A — Deployment-Wide Administration (Selected)
|
||||
Administrators manage:
|
||||
• Extension availability.
|
||||
• Default chatbot selection.
|
||||
These settings apply to the entire deployment.
|
||||
Advantages
|
||||
• Clear administrative ownership.
|
||||
• Simple operational model.
|
||||
• Consistent with existing extension administration patterns.
|
||||
• Avoids introducing multiple configuration layers.
|
||||
Option B — User-Scoped Availability
|
||||
Availability and chatbot selection become user-specific settings.
|
||||
Rejected Because
|
||||
Administrative availability and user preference represent different concerns.
|
||||
Administrators answer:
|
||||
"Which integrations are available in this deployment?"
|
||||
Users answer:
|
||||
"Which available integrations do I prefer?"
|
||||
Combining these concerns into a single model creates unclear ownership and duplicated configuration responsibilities.
|
||||
|
||||
Decision
|
||||
Option A is selected.
|
||||
This SIP introduces only deployment-wide administration.
|
||||
Future user preferences should be layered on top using the following model:
|
||||
Effective Availability = Deployment Availability AND User Preference
|
||||
The recommended persistence mechanism for user-specific preferences is the Extension Storage API.
|
||||
This approach aligns with SIP-127 and preserves a clear separation between administrative configuration and user customization.
|
||||
|
||||
7. Risks and Future Considerations
|
||||
The selected architecture introduces several tradeoffs.
|
||||
Namespace Maintenance
|
||||
As additional application surfaces become extension-aware, new namespaces may be required.
|
||||
This increases the maintenance burden of the extension API surface.
|
||||
Contract Evolution
|
||||
Namespace contracts are intended to be stable.
|
||||
Over time, extensions may require additional context that is not initially exposed.
|
||||
Future additions must preserve compatibility and avoid leaking implementation details.
|
||||
Context Growth
|
||||
Dashboard and chart context may become increasingly rich over time.
|
||||
Care must be taken to ensure context APIs remain focused and do not evolve into large aggregate objects.
|
||||
Extension Expectations
|
||||
Chatbot vendors may request direct access to internal application state for convenience.
|
||||
This SIP intentionally rejects that approach in favor of stable semantic contracts.
|
||||
Maintaining that boundary may require additional namespace evolution over time. 8. Open Questions
|
||||
D5 — Per-Page Visibility
|
||||
Should chatbot extensions be able to declare page visibility constraints?
|
||||
Two approaches remain possible.
|
||||
Extension-Controlled Visibility
|
||||
Extensions observe:
|
||||
navigation.onDidChangePage(...)
|
||||
and decide whether to render themselves.
|
||||
Host-Enforced Visibility
|
||||
Extensions declare supported page types through manifest metadata and the host enforces visibility.
|
||||
Recommendation
|
||||
Defer this decision.
|
||||
The current architecture already supports extension-controlled visibility without requiring additional platform capabilities.
|
||||
|
||||
D6 — Generalized Floating Contribution Areas
|
||||
The current proposal introduces a chatbot-specific contribution location:
|
||||
superset.chatbot
|
||||
A future question is whether this should evolve into a more generic floating-widget framework.
|
||||
Examples might include:
|
||||
• Chatbots.
|
||||
• Guided tours.
|
||||
• Notification centers.
|
||||
• Productivity assistants.
|
||||
Recommendation
|
||||
Keep the contribution area chatbot-specific.
|
||||
If broader floating-widget requirements emerge, introduce a dedicated abstraction rather than expanding the scope of this SIP.
|
||||
|
||||
9. Related Documents
|
||||
Contribution types
|
||||
Client actions
|
||||
|
||||
The following proposals are related to this SIP.
|
||||
Extension Storage API
|
||||
Add storage API for extensions (#39171)
|
||||
Introduces namespace-isolated storage for extensions with support for:
|
||||
• Local storage.
|
||||
• Session storage.
|
||||
• Ephemeral server storage.
|
||||
• Persistent database-backed storage.
|
||||
This proposal is complementary to the administration model defined by this SIP and is the recommended foundation for future user-specific extension preferences.
|
||||
|
||||
SIP-127 — User Preferences
|
||||
[SIP-127] User Preferences (#28047)
|
||||
Establishes the per-user preference model used by Superset core.
|
||||
The Extension Storage API serves as the extension-scoped equivalent of this pattern and provides the recommended approach for future user-specific chatbot preferences. 10. Migration Plan
|
||||
Base branch enxdev/chat-prototype
|
||||
Branch for testing test/chatbot-local
|
||||
The following capabilities are required to fully realize this SIP.
|
||||
Core Platform Changes
|
||||
Implemented
|
||||
• superset.chatbot contribution location.
|
||||
• Host-side chatbot resolution.
|
||||
• Administration UI for chatbot selection.
|
||||
• Dashboard namespace.
|
||||
• Explore namespace.
|
||||
• Navigation namespace.
|
||||
• Runtime settings synchronization.
|
||||
Pending
|
||||
• Dataset namespace implementation.
|
||||
• Dashboard chart visibility context.
|
||||
• Permission-scoped dashboard context endpoint.
|
||||
• Manifest support for application-level contribution scopes.
|
||||
• Optional descriptor icon support.
|
||||
|
||||
11. Implementation Phases
|
||||
Phase 1 — Chatbot Mount Point
|
||||
• Chatbot contribution location.
|
||||
• Host-side rendering.
|
||||
• Lifecycle management.
|
||||
• Fault isolation.
|
||||
Status: Complete
|
||||
Phase 2 — Administration
|
||||
• Enable/disable support.
|
||||
• Default chatbot selection.
|
||||
• Runtime synchronization.
|
||||
Status: Complete
|
||||
Phase 3 — Context APIs
|
||||
• Dashboard namespace.
|
||||
• Explore namespace.
|
||||
• Navigation namespace.
|
||||
• Dataset namespace.
|
||||
Status: Partially Complete
|
||||
Remaining work:
|
||||
• Dataset namespace.
|
||||
• Dashboard chart visibility context.
|
||||
• Dashboard context endpoint.
|
||||
Phase 4 — Client Actions
|
||||
Client actions and agentic UI interactions remain outside the scope of this SIP and are expected to be addressed through a separate proposal.
|
||||
+3
-10
@@ -29,7 +29,7 @@ ARG BUILD_TRANSLATIONS="false"
|
||||
######################################################################
|
||||
# superset-node-ci used as a base for building frontend assets and CI
|
||||
######################################################################
|
||||
FROM --platform=${BUILDPLATFORM} node:24-trixie-slim AS superset-node-ci
|
||||
FROM --platform=${BUILDPLATFORM} node:22-trixie-slim AS superset-node-ci
|
||||
ARG BUILD_TRANSLATIONS
|
||||
ENV BUILD_TRANSLATIONS=${BUILD_TRANSLATIONS}
|
||||
ARG DEV_MODE="false" # Skip frontend build in dev mode
|
||||
@@ -55,13 +55,6 @@ WORKDIR /app/superset-frontend
|
||||
RUN mkdir -p /app/superset/static/assets \
|
||||
/app/superset/translations
|
||||
|
||||
# Harden `npm ci` against transient npm-registry network blips (e.g. ECONNRESET),
|
||||
# which otherwise fail the entire multi-platform image build with no retry.
|
||||
ENV npm_config_fetch_retries=5 \
|
||||
npm_config_fetch_retry_mintimeout=20000 \
|
||||
npm_config_fetch_retry_maxtimeout=120000 \
|
||||
npm_config_fetch_timeout=600000
|
||||
|
||||
# Mount package files and install dependencies if not in dev mode
|
||||
# NOTE: we mount packages and plugins as they are referenced in package.json as workspaces
|
||||
# ideally we'd COPY only their package.json. Here npm ci will be cached as long
|
||||
@@ -120,7 +113,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 +134,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]
|
||||
|
||||
|
||||
@@ -23,14 +23,11 @@ PYTHON=`command -v python3.11 || command -v python3.10`
|
||||
install: superset pre-commit
|
||||
|
||||
superset:
|
||||
# Bootstrap uv (the project's installer) into the active environment
|
||||
pip install uv
|
||||
|
||||
# Install external dependencies
|
||||
uv pip install -r requirements/development.txt
|
||||
pip install -r requirements/development.txt
|
||||
|
||||
# Install Superset in editable (development) mode
|
||||
uv pip install -e .
|
||||
pip install -e .
|
||||
|
||||
# Create an admin user in your metadata database
|
||||
superset fab create-admin \
|
||||
@@ -55,14 +52,11 @@ superset:
|
||||
update: update-py update-js
|
||||
|
||||
update-py:
|
||||
# Bootstrap uv (the project's installer) into the active environment
|
||||
pip install uv
|
||||
|
||||
# Install external dependencies
|
||||
uv pip install -r requirements/development.txt
|
||||
pip install -r requirements/development.txt
|
||||
|
||||
# Install Superset in editable (development) mode
|
||||
uv pip install -e .
|
||||
pip install -e .
|
||||
|
||||
# Initialize the database
|
||||
superset db upgrade
|
||||
@@ -85,8 +79,7 @@ activate:
|
||||
|
||||
pre-commit:
|
||||
# setup pre commit dependencies
|
||||
pip install uv
|
||||
uv pip install -r requirements/development.txt
|
||||
pip3 install -r requirements/development.txt
|
||||
pre-commit install
|
||||
|
||||
format: py-format js-format
|
||||
|
||||
@@ -189,11 +189,6 @@ Try out Superset's [quickstart](https://superset.apache.org/docs/quickstart/) gu
|
||||
- [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:
|
||||
[X](https://x.com/apachesuperset) |
|
||||
[LinkedIn](https://www.linkedin.com/company/apache-superset) |
|
||||
[Bluesky](https://bsky.app/profile/apachesuperset.bsky.social) |
|
||||
[Reddit](https://reddit.com/r/apache-superset)
|
||||
- If you want to help troubleshoot GitHub Issues involving the numerous database drivers that Superset supports, please consider adding your name and the databases you have access to on the [Superset Database Familiarity Rolodex](https://docs.google.com/spreadsheets/d/1U1qxiLvOX0kBTUGME1AHHi6Ywel6ECF8xk_Qy-V9R8c/edit#gid=0)
|
||||
- Join Superset's Town Hall and [Operational Model](https://preset.io/blog/the-superset-operational-model-wants-you/) recurring meetings. Meeting info is available on the [Superset Community Calendar](https://superset.apache.org/community)
|
||||
|
||||
@@ -247,13 +242,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" />
|
||||
|
||||
+26
-83
@@ -83,9 +83,6 @@ categories:
|
||||
- name: Clark.de
|
||||
url: https://clark.de/
|
||||
|
||||
- name: Cover Genius
|
||||
url: https://covergenius.com/
|
||||
|
||||
- name: EnquiryLabs
|
||||
url: https://www.enquirylabs.co.uk
|
||||
|
||||
@@ -95,10 +92,6 @@ categories:
|
||||
- name: KarrotPay
|
||||
url: https://www.daangnpay.com/
|
||||
|
||||
- name: NICE Actimize
|
||||
url: https://www.niceactimize.com/
|
||||
contributors: ["@stevensuting"]
|
||||
|
||||
- name: Remita
|
||||
url: https://remita.net
|
||||
contributors: ["@mujibishola"]
|
||||
@@ -119,6 +112,9 @@ categories:
|
||||
url: https://xendit.co/
|
||||
contributors: ["@LieAlbertTriAdrian"]
|
||||
|
||||
- name: Cover Genius
|
||||
url: https://covergenius.com/
|
||||
|
||||
Gaming:
|
||||
- name: Popoko VM Games Studio
|
||||
url: https://popoko.live
|
||||
@@ -300,6 +296,7 @@ categories:
|
||||
logo: hifadih.png
|
||||
contributors: ["@saintLaurent00"]
|
||||
|
||||
# Logo approved by @anmol-hpe on behalf of HPE
|
||||
- name: HPE
|
||||
url: https://www.hpe.com/in/en/home.html
|
||||
logo: hpe.png
|
||||
@@ -399,10 +396,6 @@ categories:
|
||||
url: https://www.techaudit.info
|
||||
contributors: ["@ETselikov"]
|
||||
|
||||
- name: Tech Solution
|
||||
url: https://www.tech-solution.com.ar/
|
||||
contributors: ["@danteGiuliano", "@LeandroVallejos", "@McJaben", "@xJeree", "@zeo-return-null"]
|
||||
|
||||
- name: Tenable
|
||||
url: https://www.tenable.com
|
||||
contributors: ["@dflionis"]
|
||||
@@ -432,10 +425,6 @@ categories:
|
||||
logo: userguiding.svg
|
||||
contributors: ["@tzercin"]
|
||||
|
||||
- name: Value Ad
|
||||
url: https://bestpair.info/
|
||||
contributors: ["@stevensuting"]
|
||||
|
||||
- name: Virtuoso QA
|
||||
url: https://www.virtuosoqa.com
|
||||
|
||||
@@ -520,6 +509,10 @@ categories:
|
||||
url: https://www.sunbird.org/
|
||||
contributors: ["@eksteporg"]
|
||||
|
||||
- name: The GRAPH Network
|
||||
url: https://thegraphnetwork.org/
|
||||
contributors: ["@fccoelho"]
|
||||
|
||||
- name: Udemy
|
||||
url: https://www.udemy.com/
|
||||
contributors: ["@sungjuly"]
|
||||
@@ -528,24 +521,7 @@ categories:
|
||||
url: https://www.vipkid.com.cn/
|
||||
contributors: ["@illpanda"]
|
||||
|
||||
Social Organization:
|
||||
- name: Living Goods
|
||||
url: https://www.livinggoods.org
|
||||
contributors: ["@chelule"]
|
||||
|
||||
- name: One Acre Fund
|
||||
url: https://oneacrefund.org/
|
||||
contributors: ["@stevensuting"]
|
||||
|
||||
- name: Quest Alliance
|
||||
url: https://www.questalliance.net/
|
||||
contributors: ["@stevensuting"]
|
||||
|
||||
- name: The GRAPH Network
|
||||
url: https://thegraphnetwork.org/
|
||||
contributors: ["@fccoelho"]
|
||||
|
||||
- name: Wikimedia Foundation
|
||||
- name: WikiMedia Foundation
|
||||
url: https://wikimediafoundation.org
|
||||
contributors: ["@vg"]
|
||||
|
||||
@@ -558,10 +534,6 @@ categories:
|
||||
url: https://www.douroeci.com/
|
||||
contributors: ["@nunohelibeires"]
|
||||
|
||||
- name: Rogow
|
||||
url: https://rogow.com.br/
|
||||
contributors: ["@nilmonto"]
|
||||
|
||||
- name: Safaricom
|
||||
url: https://www.safaricom.co.ke/
|
||||
contributors: ["@mmutiso"]
|
||||
@@ -574,10 +546,11 @@ categories:
|
||||
url: https://wattbewerb.de/
|
||||
contributors: ["@wattbewerb"]
|
||||
|
||||
Healthcare:
|
||||
- name: 2070Health
|
||||
url: https://2070health.com/
|
||||
- name: Rogow
|
||||
url: https://rogow.com.br/
|
||||
contributors: ["@nilmonto"]
|
||||
|
||||
Healthcare:
|
||||
- name: Amino
|
||||
url: https://amino.com
|
||||
contributors: ["@shkr"]
|
||||
@@ -590,6 +563,10 @@ categories:
|
||||
url: https://www.getcare.io/
|
||||
contributors: ["@alandao2021"]
|
||||
|
||||
- name: Living Goods
|
||||
url: https://www.livinggoods.org
|
||||
contributors: ["@chelule"]
|
||||
|
||||
- name: Maieutical Labs
|
||||
url: https://maieuticallabs.it
|
||||
contributors: ["@xrmx"]
|
||||
@@ -608,10 +585,10 @@ categories:
|
||||
- name: WeSure
|
||||
url: https://www.wesure.cn/
|
||||
|
||||
HR / Staffing:
|
||||
- name: bluquist
|
||||
url: https://bluquist.com/
|
||||
- name: 2070Health
|
||||
url: https://2070health.com/
|
||||
|
||||
HR / Staffing:
|
||||
- name: Swile
|
||||
url: https://www.swile.co/
|
||||
contributors: ["@PaoloTerzi"]
|
||||
@@ -619,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
|
||||
@@ -702,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"]
|
||||
@@ -714,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"]
|
||||
@@ -734,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/
|
||||
|
||||
@@ -748,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/
|
||||
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ If yes, it is in scope. If no, it is out of scope. The lists below apply that te
|
||||
- Any action an Admin role can perform through documented configuration, API, or UI. The Admin role is a trusted operational principal by policy. Per MITRE CNA Operational Rules 4.1, a qualifying vulnerability must violate a security policy; behavior within a documented trust boundary does not.
|
||||
- Deployment or operator decisions: the values of secrets and tokens, whether internal networks are reachable from the server, which database connectors or cache backends are enabled, which feature flags are set, where notifications are delivered, and which third-party plugins are loaded.
|
||||
- Compromise, modification, or malicious control of trusted backend infrastructure. Apache Superset assumes the integrity of its metastore, cache backends (for example Redis or Memcached), message brokers, secret stores, and other operator-managed infrastructure. Findings that require an attacker to read from, write to, or otherwise tamper with these systems, including injecting malicious state, serialized objects, cache entries, task metadata, configuration, or database records, are post-compromise scenarios and do not constitute vulnerabilities in Apache Superset itself. A finding remains in scope only if an unprivileged user can cause such modification through a vulnerability in Apache Superset.
|
||||
- The continued presence of expired key-value or metastore-cache entries that have not yet been deleted from the metadata database. Such entries are excluded from reads once expired, are purged opportunistically on write, and are removed in bulk by the scheduled `prune_key_value` maintenance task; their lingering until purged is an eventual-cleanup property, not a security boundary, and does not constitute a vulnerability.
|
||||
- Code paths whose intended purpose is example data, demos, fixtures, local development, or documentation, rather than the production runtime.
|
||||
- How a downstream application (spreadsheet program, email client, browser handling user-downloaded files) interprets output Apache Superset produced for it.
|
||||
- Findings without a reproducible proof of concept against a supported release. The burden of demonstrating exploitability rests with the reporter; findings closed for lack of a proof of concept may be refiled if one is later produced.
|
||||
- Brute force, rate limiting, denial of service, or resource exhaustion that does not bypass a documented control.
|
||||
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
# Chatbot Extensions — Tickets
|
||||
|
||||
Lightweight, pre-implementation tickets. Each says what to build and where the
|
||||
boundaries are; it does not prescribe the final code. Stack order (bottom → top):
|
||||
contribution point → frontend API mount → eager loading → admin UI → backend
|
||||
settings/permissions → context sharing → import/delete.
|
||||
|
||||
---
|
||||
|
||||
## 1. Define the contribution point
|
||||
|
||||
**Goal:** Introduce the `superset.chatbot` contribution area and the host plumbing
|
||||
needed to mount a single chatbot at the application-shell level, persistent across
|
||||
routes. This is the keystone everything else builds on.
|
||||
|
||||
**Build:**
|
||||
|
||||
- Register `superset.chatbot` as a recognized contribution location in the view
|
||||
registry.
|
||||
- Add an app-shell / app-root contribution scope to the extension manifest schema
|
||||
so the location can be declared in `extension.json` (the current schema is
|
||||
SQL-Lab-only). Teach both manifest validation and runtime registration about it.
|
||||
- Provide an exclusive-location resolver that selects exactly one renderable
|
||||
chatbot for the slot, with a deterministic first-to-register fallback and a seam
|
||||
for an externally supplied "active chatbot id" (so admin/runtime policy can plug
|
||||
in later without touching the resolver).
|
||||
- Host-managed mount layout: fixed bottom-right, 24px margin, z-index above content
|
||||
and toasts, below modals.
|
||||
|
||||
**Out of scope:** fault isolation, admin selection UI, the lifecycle/teardown
|
||||
contract, eager loading, streaming, context namespaces, authoring docs.
|
||||
|
||||
**Depends on:** nothing — this unblocks the rest.
|
||||
|
||||
**Done when:** an extension can register at `superset.chatbot` and render at the
|
||||
app shell across routes; the resolver returns one provider (admin-id seam +
|
||||
first-to-register fallback); unregistering removes the mount cleanly with no
|
||||
duplicate bubbles.
|
||||
|
||||
Base branch: `enxdev/chat-prototype`
|
||||
|
||||
**External Links:** https://github.com/apache/superset/pull/40439
|
||||
|
||||
---
|
||||
|
||||
## 2. Host resolution & mount (frontend API entry point)
|
||||
|
||||
**Goal:** Turn a registered `superset.chatbot` view into a rendered, fault-isolated
|
||||
bubble — the host-internal provider accessor, the selection policy, and the
|
||||
fixed-position mount.
|
||||
|
||||
**Build:**
|
||||
|
||||
- Host-internal accessors on the views registry: `getViewProvider(location, id)`
|
||||
and `getRegisteredViewIds(location)`. Keep the public `getViews` descriptor-only —
|
||||
do not expose providers on the public surface.
|
||||
- A registry change subscription so a mount can re-resolve without polling (fired
|
||||
on register/unregister).
|
||||
- The `getActiveChatbot(adminSelectedId?, enabledMap?)` resolver implementing the
|
||||
selection policy: empty → none; drop disabled ids; admin-selected-and-enabled
|
||||
wins; else first enabled in registration order.
|
||||
- A `ChatbotMount` component at the app shell that renders the active provider
|
||||
inside the host `ErrorBoundary`, re-resolves on registry change, and renders
|
||||
nothing when no chatbot is active.
|
||||
|
||||
**Out of scope:** the contribution location itself (#40439); eager-loading the
|
||||
bundle (#40441); the settings endpoint (#40443, consumed here with silent
|
||||
fallback); admin UI; the lifecycle/teardown contract.
|
||||
|
||||
**Depends on:** #40439 (imports `CHATBOT_LOCATION`). The settings endpoint is a soft
|
||||
forward-dependency — the mount falls back to first-registered-enabled if it 404s.
|
||||
|
||||
**Done when:** the provider accessor and resolver behave per the policy; the mount
|
||||
renders/clears correctly and survives a throwing provider via `ErrorBoundary`;
|
||||
`getViews` stays descriptor-only.
|
||||
|
||||
Base branch: `enxdev/feat/chatbot-contribution-point` (on #40439)
|
||||
|
||||
**External Links:** https://github.com/apache/superset/pull/40440
|
||||
|
||||
---
|
||||
|
||||
## 3. Eager loading & extension lifecycle/teardown
|
||||
|
||||
> Merged: this ticket also covers the **lifecycle & teardown contract** — both are
|
||||
> implemented in the same PR (#40441), so they are tracked together.
|
||||
|
||||
**Goal:** Boot extension bundles at app-shell startup so contributions register
|
||||
before the first route, and define the host contract for tearing those
|
||||
contributions down on uninstall.
|
||||
|
||||
**Build — eager loading:**
|
||||
|
||||
- An `ExtensionsStartup` component that, once the session is confirmed and behind
|
||||
`FeatureFlag.EnableExtensions`, kicks off `initializeExtensions()` in the
|
||||
background. The host renders immediately; the mount re-resolves reactively when
|
||||
registrations land.
|
||||
- Wire `window.superset` so Module-Federation remotes can consume host namespaces.
|
||||
- Mount `<ChatbotMount />` as a sibling of the route switch, inside
|
||||
`ExtensionsStartup`.
|
||||
- On bundle-load failure: a danger toast, host stays interactive, corner stays
|
||||
empty. Add a global `unhandledrejection` logger (log only; do not suppress the
|
||||
browser default).
|
||||
|
||||
**Build — lifecycle/teardown contract (Model A1, per-contribution dispose):**
|
||||
|
||||
- During the `./index` factory call, intercept the public registrars and collect
|
||||
the returned `Disposable`s keyed by extension id.
|
||||
- `deactivateExtension(id)` is the single teardown entrypoint: it fires every
|
||||
collected `Disposable` and removes the extension from the index. A throwing
|
||||
`Disposable` must not block the others (catch per-disposable). Idempotent;
|
||||
unknown id is a no-op.
|
||||
- Trigger semantics to document: **uninstall** → `deactivateExtension(id)`;
|
||||
**disable** → mount filters by `enabledMap`, does NOT fire disposables, re-enable
|
||||
needs no reload; **replace** (singleton) → resolver re-selects, the losing
|
||||
extension is not deactivated. Disposal order is best-effort (registration order),
|
||||
not a contract — consumers must be order-independent.
|
||||
|
||||
**Out of scope:** selective per-type eager loading (not feasible without running the
|
||||
factory); the mount-boundary `ErrorBoundary` (#40440); the settings endpoint and
|
||||
its subscription primitive (#40443); context namespaces (#40444); an async-aware
|
||||
`deactivate(): Promise<void>` — file separately only if a graceful-flush
|
||||
requirement appears.
|
||||
|
||||
**Depends on:** #40440 (`ChatbotMount`, resolver, registry-subscription hook). Soft
|
||||
build-time deps on #40443 (settings subscription) and #40444 (namespaces) — land
|
||||
those first or stub the imports.
|
||||
|
||||
**Done when:** enabled extensions init once at startup behind the flag without
|
||||
gating initial render; the bubble appears reactively on registration;
|
||||
`deactivateExtension(id)` disposes all of an extension's contributions (per-disposable
|
||||
catch, idempotent); load failure toasts without throwing; teardown is verified
|
||||
end-to-end on the reference chatbot (including an abort-registry controller that must
|
||||
still abort on deactivate).
|
||||
|
||||
Base branch: `enxdev/feat/chatbot-frontend-api` (on #40440)
|
||||
|
||||
**External Links:** https://github.com/apache/superset/pull/40441
|
||||
|
||||
---
|
||||
|
||||
## 4. Admin configuration UI
|
||||
|
||||
**Goal:** Let an admin enable/disable the chatbot and, when more than one chatbot is
|
||||
installed, choose which is active.
|
||||
|
||||
**Resolve before building:**
|
||||
|
||||
- Is "disable the chatbot" the existing generic extension-disable, or a
|
||||
chatbot-specific toggle? (Determines the ticket's size — prefer reusing the
|
||||
existing flag.)
|
||||
- Where does the UI live? Default: the existing extensions management surface, not a
|
||||
new page.
|
||||
- How does the "default chatbot" selection persist? Reuse existing extension-state
|
||||
storage or a config value — do not invent a table.
|
||||
- Which permission gates it? Default: the existing Extensions-API write permission.
|
||||
|
||||
**Build:**
|
||||
|
||||
- Enable/disable control that empties the `superset.chatbot` slot when off (no broken
|
||||
placeholder).
|
||||
- (Gated on the singleton-policy decision) A selection control listing candidates via
|
||||
`getViews('superset.chatbot')`, activating the choice through the resolver, falling
|
||||
back to first-to-register when unset.
|
||||
- Switching the active chatbot or disabling it at runtime must dispose the previously
|
||||
active chatbot via its `Disposable` and release its in-flight stream readers (via
|
||||
`AbortController`) — no two bubbles, no leaked readers.
|
||||
|
||||
**Out of scope:** the singleton-policy decision itself; per-page visibility; the
|
||||
resolver implementation (consumed here).
|
||||
|
||||
**Depends on:** #40440 (resolver) for selection; enable/disable does not wait on the
|
||||
policy decision.
|
||||
|
||||
**Done when:** admin can enable/disable (gated by the chosen permission) and the slot
|
||||
empties when off; selection picks the active chatbot with first-to-register fallback;
|
||||
the persistence-mechanism and permission decisions are recorded in the ticket.
|
||||
|
||||
Base branch: `enxdev/chat-prototype`
|
||||
|
||||
**External Links:** https://github.com/apache/superset/pull/40442
|
||||
|
||||
---
|
||||
|
||||
## 5. Permissions
|
||||
|
||||
**Goal:** Guarantee the new page-context surface cannot expose anything the current
|
||||
user can't already access through Superset's standard security model. Chatbot
|
||||
extensions fetch data as any other frontend surface and inherit only the current
|
||||
user's privileges; this ticket covers only the new host → extension context-sharing
|
||||
path.
|
||||
|
||||
**Build:**
|
||||
|
||||
- The page-context namespaces (#40444) must derive entity metadata from the same
|
||||
permission checks that gate the underlying page — not a raw Redux pass-through.
|
||||
- Canonical threat (SIP §2.1): a dashboard the user can view that contains a chart
|
||||
whose dataset they cannot query — that chart's metadata (id, name, datasource,
|
||||
viz type, form_data) must be dropped from the context payload.
|
||||
- Context carries only lightweight semantic data + identifiers that resolve through
|
||||
already-protected APIs; never inline dataset rows or query results.
|
||||
- Filtering applies equally to the initial read and every change-notification
|
||||
payload. A chatbot an admin has disabled receives no context at all.
|
||||
|
||||
**Out of scope:** REST API authorization, RBAC, RLS (already enforced by Superset);
|
||||
LLM/backend auth; the singleton selection policy. The chatbot authenticates via the
|
||||
user's existing session (cookie + CSRF) — no separate credential is issued.
|
||||
|
||||
**Depends on:** the Spike sizing the new namespaces (the per-getter filtering lands
|
||||
with those getters); the Context-sharing ticket consumes the filtered getters this
|
||||
one specifies.
|
||||
|
||||
**Done when:** context never exposes entities/ids/metadata the user can't access
|
||||
(even via a manually-entered URL); the dashboard payload omits charts whose dataset
|
||||
the user can't query; no inline privileged payloads; filtering covers change events
|
||||
as well as the initial read; a disabled chatbot gets nothing.
|
||||
|
||||
Base branch: `enxdev/chat-prototype`
|
||||
|
||||
**External Links:** https://github.com/apache/superset/pull/40443
|
||||
|
||||
---
|
||||
|
||||
## 6. Context sharing
|
||||
|
||||
**Goal:** Let the chatbot read semantic page context and subscribe to changes through
|
||||
public per-surface core namespaces only — never the host Redux store.
|
||||
|
||||
**Approach:** Deliver context through per-surface namespaces — the existing `sqlLab`
|
||||
namespace plus new `dashboard` / `explore` / `navigation` namespaces that mirror its
|
||||
shape (a state getter + an `Event<T>` change subscription). No new aggregate context
|
||||
API. The new namespaces copy `sqlLab`'s shape but must filter the Redux state they
|
||||
read (the permission filtering itself is specified by #40443).
|
||||
|
||||
**Build:**
|
||||
|
||||
- Route all chatbot page-context reads through one narrow adapter module with a fixed
|
||||
interface — the adapter is the deliverable, not scattered call sites — so swapping
|
||||
to core namespaces is a one-line change.
|
||||
- Back the adapter with `sqlLab` immediately; back the dashboard/explore/navigation
|
||||
portions and wire change notifications through `navigation`'s page-change event once
|
||||
those namespaces ship.
|
||||
|
||||
**Out of scope:** the permission-filtering logic (#40443 + upstream namespace work);
|
||||
designing the namespace API surface (upstream OSS work, sized by the Spike).
|
||||
|
||||
**Depends on:** a Spike to size the new namespaces (state getters + events + the
|
||||
per-getter permission filtering). The namespace _shape_ is settled by the `sqlLab`
|
||||
precedent; the filtering is real design work.
|
||||
|
||||
**Done when:** all context reads go through the single adapter (zero direct Redux
|
||||
imports, greppable); SQL Lab context works today; dashboard/explore context is either
|
||||
delivered or explicitly tracked as OSS-blocked (not faked); change notifications need
|
||||
no polling; no extra host re-renders.
|
||||
|
||||
Base branch: `enxdev/chat-prototype`
|
||||
|
||||
**External Links:** https://github.com/apache/superset/pull/40444
|
||||
|
||||
---
|
||||
|
||||
## 7. Import / delete UI
|
||||
|
||||
**Goal:** Add an actions column to the extensions list with buttons to delete an
|
||||
extension, set-as-default (chatbot extensions only), and import a new extension.
|
||||
|
||||
**Build:**
|
||||
|
||||
- Import an extension bundle, refreshing the list on success.
|
||||
- Delete an installed extension.
|
||||
- A "set as default chatbot" control, shown only for chatbot extensions.
|
||||
|
||||
**Out of scope:** the settings endpoint itself (#40443); the resolver (#40440).
|
||||
|
||||
**Depends on:** #40442/#40443 for the settings + chatbot-selection plumbing.
|
||||
|
||||
**Done when:** an admin can import, delete, and set a default chatbot from the
|
||||
actions column, with the list reflecting changes.
|
||||
|
||||
Base branch: `enxdev/chat-prototype`
|
||||
|
||||
**External Links:** https://github.com/apache/superset/pull/40450
|
||||
|
||||
---
|
||||
|
||||
## ~~8. Fault isolation & error boundaries~~ — CLOSED (no ticket needed)
|
||||
|
||||
The protective fault-isolation mechanisms are **already implemented** across the
|
||||
mount and eager-loading PRs, so no standalone ticket is required:
|
||||
|
||||
- Render/lifecycle throw → host `ErrorBoundary` around the `superset.chatbot` slot
|
||||
(#40440, reinforced by the `ChatbotRenderer` wrapper in #40441).
|
||||
- Bundle-load failure → `.catch()` + danger toast in `ExtensionsLoader` (#40441).
|
||||
- `activate()` throw → host try/catch in `ExtensionsLoader` (#40441).
|
||||
- Escaped async rejection → `unhandledrejection` hook in `ExtensionsStartup` (#40441).
|
||||
- Failed-activation cleanup → driven by `deactivateExtension` (ticket 3 / #40441).
|
||||
|
||||
The host stays safe under every failure class today. The only unbuilt pieces were the
|
||||
**optional** "chatbot failed — Reload page" notification and structured
|
||||
failure-class/telemetry logging — both judged not worth a ticket (the original spec
|
||||
itself marked the reload notification "optional"). File a fresh ticket only if that
|
||||
UX is later wanted.
|
||||
|
||||
(Original link, for reference only: PR #40433 `feat(extensions): adds chatbot P1-P2` —
|
||||
closed/superseded; never a dedicated fault-isolation PR.)
|
||||
|
||||
---
|
||||
|
||||
## Notes on consolidation
|
||||
|
||||
- **Lifecycle/teardown** was a separate ticket pointing at the same PR as **Eager
|
||||
loading** (#40441) — merged into ticket 3 above. (This is the only true duplicate.)
|
||||
- The **Permissions** ticket (#40443) is kept as-is. Note its PR also contains
|
||||
backend settings-persistence code, but the original ticket only ever scoped the
|
||||
permission-safe context surface — so the ticket stays "Permissions" and no
|
||||
persistence ticket is invented.
|
||||
- The **Permissions** ticket previously had a truncated base branch
|
||||
(`enxdev/chat-protot`) — corrected to `enxdev/chat-prototype`.
|
||||
- **Fault isolation** was **closed without a ticket** (see the struck-through section
|
||||
above): its protective mechanisms already shipped in #40440/#40441, and the only
|
||||
unbuilt pieces (the optional "Reload page" notification + structured telemetry
|
||||
logging) were judged not worth a ticket.
|
||||
-490
@@ -24,475 +24,12 @@ assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
|
||||
### 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`.
|
||||
|
||||
### `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.
|
||||
|
||||
### 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.
|
||||
|
||||
### 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`.
|
||||
|
||||
Deployments that intentionally point webhooks at internal targets (chatops bridges, internal automation servers, on-premises Mattermost/Rocket.Chat, etc.) can opt out by setting `ALERT_REPORTS_WEBHOOK_ALLOW_INTERNAL_HOSTS = True` in `superset_config.py`. This mirrors the existing `DATASET_IMPORT_ALLOW_INTERNAL_DATA_URLS` opt-out for dataset imports.
|
||||
|
||||
### 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
|
||||
the MapLibre-compatible path. Saved styles such as OpenStreetMap, `tile://`
|
||||
tile templates, generic HTTPS style URLs, and charts without a saved style are
|
||||
not reclassified as Mapbox during migration and do not require
|
||||
`MAPBOX_API_KEY` only because of the migration.
|
||||
|
||||
Saved true Mapbox styles whose value starts with `mapbox://` remain
|
||||
Mapbox-backed. If a Superset deployment does not configure `MAPBOX_API_KEY`,
|
||||
those saved Mapbox charts keep the existing missing-key message instead of
|
||||
silently falling back to MapLibre or another provider. In Explore, deck.gl and
|
||||
point-cluster renderer controls preserve saved Mapbox state, but the Mapbox
|
||||
choice is not available as a new working renderer without a configured key.
|
||||
|
||||
The MapLibre style choices include `Streets (OSM)`, backed by
|
||||
`https://tile.openstreetmap.org/{z}/{x}/{y}.png`. This OpenStreetMap tile
|
||||
service requires visible `© OpenStreetMap contributors` attribution and should
|
||||
be used through normal browser map tile requests and caching; it is not intended
|
||||
for bulk prefetch or offline tile downloads.
|
||||
|
||||
### Password complexity policy enabled by default
|
||||
|
||||
Superset now ships a default password-complexity policy, enforced (via Flask-AppBuilder) across self-registration, the user create/edit/reset forms, and the User REST API. The policy requires a minimum password length of 8 characters and rejects a built-in blocklist of common/guessable passwords.
|
||||
|
||||
This is enabled by default (`FAB_PASSWORD_COMPLEXITY_ENABLED = True`), so new or reset passwords that are too short or appear in the blocklist will be rejected where they were previously accepted. Existing stored passwords are unaffected until they are next changed.
|
||||
|
||||
Operators can tune or disable the policy via config:
|
||||
|
||||
- `AUTH_PASSWORD_MIN_LENGTH` — minimum length (default `8`).
|
||||
- `AUTH_PASSWORD_COMMON_BLOCKLIST` — extra passwords to reject, in addition to the built-in list.
|
||||
- `FAB_PASSWORD_COMPLEXITY_VALIDATOR` — replace with your own callable for custom rules.
|
||||
- `FAB_PASSWORD_COMPLEXITY_ENABLED = False` — disable enforcement entirely.
|
||||
|
||||
### 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
|
||||
|
||||
The `DURATION` number formatter now uses `Intl.DurationFormat` for locale-aware output. By default, sub-second fields are omitted, so values that previously displayed fractional seconds with `pretty-ms`, such as `10500` milliseconds rendering as `10.5s`, now render as `10s`.
|
||||
|
||||
To preserve sub-second precision in custom duration formatters, enable `formatSubMilliseconds`.
|
||||
|
||||
### Cache warmup authenticates via SUPERSET_CACHE_WARMUP_USER
|
||||
|
||||
The `cache-warmup` Celery task now drives a real WebDriver session for reliable authentication and reads the user to authenticate as from the new `SUPERSET_CACHE_WARMUP_USER` config option. It no longer consults `CACHE_WARMUP_EXECUTORS` for the warmup path. `SUPERSET_CACHE_WARMUP_USER` defaults to `None`, so the task fails fast with a clear message until you set it. Operators who previously relied on `CACHE_WARMUP_EXECUTORS` for cache warmup must set `SUPERSET_CACHE_WARMUP_USER` to a dedicated least-privilege user with access to the dashboards they want warmed up before the next warmup run.
|
||||
|
||||
### YDB now uses a native sqlglot dialect
|
||||
|
||||
YDB SQL parsing now relies on the dedicated [`ydb-sqlglot-plugin`](https://pypi.org/project/ydb-sqlglot-plugin/) dialect, which registers itself with sqlglot automatically. YDB users must install this plugin (e.g., via `pip install "apache-superset[ydb]"`) to avoid a `ValueError` when Superset parses YDB queries.
|
||||
|
||||
### Embedded dashboards enforce configured Allowed Domains for postMessage
|
||||
|
||||
The embedded dashboard page now validates the origin of incoming `postMessage` events against the dashboard's configured **Allowed Domains**. The server-rendered embedded page exposes the configured domains in its bootstrap payload, and the frontend rejects message events whose origin is not in that list.
|
||||
|
||||
Enforcement only applies when the Allowed Domains list is non-empty. If the list is empty (the default), any origin is accepted, so there is no behavior change for embeds that did not configure Allowed Domains.
|
||||
|
||||
### Default guest/async JWT secrets are rejected at startup
|
||||
|
||||
Superset already refuses to start in production (non-debug, non-testing) when `SECRET_KEY` is left at its built-in default, and when `GUEST_TOKEN_JWT_SECRET` is left at its default while `EMBEDDED_SUPERSET` is enabled. This behavior is extended to `GLOBAL_ASYNC_QUERIES_JWT_SECRET`: if the `GLOBAL_ASYNC_QUERIES` feature flag is enabled and the secret is still the publicly known default (`test-secret-change-me`), Superset logs a clear error and refuses to start.
|
||||
|
||||
As with the existing `SECRET_KEY` check, this only fails in production. In debug mode, testing mode, or under the test runner, a warning is logged instead of exiting, so local development is unaffected.
|
||||
|
||||
To resolve the error, set a strong random value in `superset_config.py`:
|
||||
|
||||
```python
|
||||
GLOBAL_ASYNC_QUERIES_JWT_SECRET = "<output of: openssl rand -base64 42>"
|
||||
```
|
||||
|
||||
The check is only active when the relevant feature is enabled, so deployments that do not use global async queries (or embedding) are not affected.
|
||||
|
||||
### Guest token revocation (opt-in)
|
||||
|
||||
Embedded guest tokens can be coarsely revoked at runtime via a new opt-in mechanism. A new config flag `GUEST_TOKEN_REVOCATION_ENABLED` (default `False`) gates the feature. When enabled, every minted guest token carries a revocation version, and tokens whose version is below the current expected version (stored in the metadata database) are rejected at validation time.
|
||||
|
||||
Bump the expected version with the new CLI command to invalidate all outstanding guest tokens:
|
||||
|
||||
```bash
|
||||
superset revoke-guest-tokens
|
||||
```
|
||||
|
||||
This change is backward compatible. The feature is off by default, and even when enabled nothing is revoked until an admin explicitly bumps the version: the expected version starts at `0`, and tokens minted before this change (which carry no version claim) are treated as version `0`. No database migration is required.
|
||||
|
||||
### Sessions are terminated when an account is disabled
|
||||
|
||||
Disabling a user account (setting `active` to `False`, via the admin UI, REST API, or CLI) now terminates that user's outstanding sessions on their next request, instead of relying on a passive check. This works for both client-side cookie sessions and server-side session stores via a per-user invalidation epoch (`user_attribute.sessions_invalidated_at`, added by a migration). The mechanism is inert for users that were never disabled (NULL epoch), so there is no behavior change for active users. Re-enabling an account and logging in again starts a fresh, valid session. The migration backfills the epoch for accounts that are already disabled at upgrade time, so re-enabling such an account does not revive a session that predates this feature.
|
||||
|
||||
### Opt-in SSH tunnel server host key verification
|
||||
|
||||
SSH tunnels can now optionally pin the expected SSH server host key as a defense-in-depth measure against man-in-the-middle attacks. paramiko's transport performs no known-hosts checking by default, so previously the SSH server's identity was not verified. This feature is opt-in and off by default; existing tunnels are unaffected.
|
||||
|
||||
- A new nullable `server_host_key` column on the `ssh_tunnels` table stores the expected host key in authorized-key form (e.g. `ssh-ed25519 AAAA...`). It is a public key and is stored in plaintext. It can be set via the SSH tunnel POST/PUT payloads (`ssh_tunnel.server_host_key`).
|
||||
- When a tunnel has `server_host_key` set, Superset connects to the SSH server, reads the host key it presents, and rejects the tunnel if it does not match.
|
||||
- A new config flag `SSH_TUNNEL_STRICT_HOST_KEY_CHECKING` (default `False`) controls fail-closed behavior. When `True`, every tunnel must declare a `server_host_key`; a tunnel without one is rejected.
|
||||
|
||||
Runbook to adopt:
|
||||
|
||||
1. Capture the SSH server's host key, e.g. `ssh-keyscan -t ed25519 ssh.example.com` (verify it out-of-band).
|
||||
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.
|
||||
|
||||
If you relied on importing datasets with a non-default catalog, enable "Allow changing catalogs" on the target connection, or set the dataset's catalog to the connection's default before importing.
|
||||
|
||||
### Extension supply-chain controls (denylist + version policy)
|
||||
|
||||
Two opt-in static gates control which extensions are allowed to load:
|
||||
|
||||
- `EXTENSION_DENYLIST` refuses extensions matching an id (every version) or `id@version` (a single version), e.g. `["compromised-extension", "other-ext@1.2.3"]`.
|
||||
- `EXTENSION_VERSION_POLICY` enforces a minimum version per extension id, e.g. `{"acme.widget": "1.2.0"}` (PEP 440 comparison); a release below the minimum is refused.
|
||||
|
||||
Both default to empty (no behavior change). They apply to both the `LOCAL_EXTENSIONS` and `EXTENSIONS_PATH` load paths.
|
||||
|
||||
### Dynamic Group By respects the sort toggle for display values
|
||||
|
||||
The Dynamic Group By chart customization now orders its display values according to the "Sort display control values" toggle: ascending (A–Z), descending (Z–A), or the dataset's source order when the toggle is unset. Previously the dropdown always sorted alphabetically. Existing dashboards where the toggle was never set will show options in source order instead of A–Z; open the customization and enable the toggle to restore alphabetical ordering.
|
||||
|
||||
### Selectable encryption engine for app-encrypted fields (AES-GCM)
|
||||
|
||||
App-encrypted fields (database passwords, SSH tunnel credentials, OAuth tokens, etc.) can now use authenticated **AES-GCM** encryption instead of the historical unauthenticated **AES-CBC**. A new config selects the engine for the default adapter:
|
||||
|
||||
```python
|
||||
# "aes" (AES-CBC, historical default) | "aes-gcm" (authenticated, recommended for new installs)
|
||||
SQLALCHEMY_ENCRYPTED_FIELD_ENGINE = "aes"
|
||||
```
|
||||
|
||||
**No action required / no behavior change:** the default remains `"aes"`, so existing installs are unaffected.
|
||||
|
||||
**Opting in on an existing install:** flipping the engine on a populated database without re-encrypting first will make stored secrets undecryptable, because the two ciphertext formats are not compatible. A migrator is provided. Recommended runbook:
|
||||
|
||||
1. Take a metadata-DB backup.
|
||||
2. Re-encrypt existing secrets into the new engine (the `SECRET_KEY` is unchanged):
|
||||
```bash
|
||||
superset re-encrypt-secrets --engine aes-gcm
|
||||
```
|
||||
3. Set `SQLALCHEMY_ENCRYPTED_FIELD_ENGINE = "aes-gcm"` in your config.
|
||||
4. Restart Superset.
|
||||
5. Re-run the migrator once more after the restart:
|
||||
```bash
|
||||
superset re-encrypt-secrets --engine aes-gcm
|
||||
```
|
||||
A live instance keeps writing *new* secrets as AES-CBC during the window between step 2 and the restart in step 4; this second pass sweeps those up (it is idempotent, so already-migrated values are skipped).
|
||||
|
||||
Schedule the cutover in a quiet window. Runtime reads use only the single configured engine, so in a multi-worker deployment there is an unavoidable brief decrypt-outage between the migration commit and the last worker restarting with the new config — each migrator run is transactional, but the fleet-wide cutover is not zero-downtime.
|
||||
|
||||
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.
|
||||
|
||||
### Granular Export Controls
|
||||
|
||||
A new feature flag `GRANULAR_EXPORT_CONTROLS` introduces three fine-grained permissions that replace the legacy `can_csv` permission:
|
||||
@@ -522,9 +59,6 @@ Added a new combined datasource list endpoint at `GET /api/v1/datasource/` to se
|
||||
- The endpoint is available to users with at least one of `can_read` on `Dataset` or `SemanticView`.
|
||||
- Semantic views are included only when the `SEMANTIC_LAYERS` feature flag is enabled.
|
||||
- The endpoint enforces strict `order_column` validation and returns `400` for invalid sort columns.
|
||||
|
||||
## 6.1.0
|
||||
|
||||
### ClickHouse minimum driver version bump
|
||||
|
||||
The minimum required version of `clickhouse-connect` has been raised to `>=0.13.0`. If you are using the ClickHouse connector, please upgrade your `clickhouse-connect` package. The `_mutate_label` workaround that appended hash suffixes to column aliases has also been removed, as it is no longer needed with modern versions of the driver.
|
||||
@@ -737,7 +271,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.
|
||||
|
||||
@@ -783,29 +316,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.
|
||||
|
||||
@@ -61,34 +61,6 @@ services:
|
||||
volumes:
|
||||
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./docker/nginx/templates:/etc/nginx/templates:ro
|
||||
# Wait for the webpack dev server's manifest.json to be served before
|
||||
# starting nginx. This prevents 404s on static assets at startup. The
|
||||
# probe targets host.docker.internal so it works regardless of whether
|
||||
# the dev server runs in the superset-node container
|
||||
# (BUILD_SUPERSET_FRONTEND_IN_DOCKER=true, the default) or directly on
|
||||
# the host (BUILD_SUPERSET_FRONTEND_IN_DOCKER=false).
|
||||
command:
|
||||
- /bin/bash
|
||||
- -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..."
|
||||
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
|
||||
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;'
|
||||
|
||||
redis:
|
||||
image: redis:7
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -71,34 +71,16 @@ 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.
|
||||
if [[ "${SUPERSET_DEBUG_ENABLED:-}" == "true" ]]; then
|
||||
DEBUGGER_FLAG="--debugger"
|
||||
echo " ⚠️ Werkzeug debugger enabled (requires PIN for /console access)"
|
||||
else
|
||||
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 --exclude-patterns "*/node_modules/*:*/.venv/*:*/build/*:*/__pycache__/*:*/superset-frontend/*"
|
||||
flask run -p $PORT --reload --debugger --host=0.0.0.0 --exclude-patterns "*/node_modules/*:*/.venv/*:*/build/*:*/__pycache__/*:*/superset-frontend/*"
|
||||
;;
|
||||
app-gunicorn)
|
||||
echo "Starting web app..."
|
||||
|
||||
@@ -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}" \
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../superset-frontend/.nvmrc
|
||||
@@ -0,0 +1 @@
|
||||
v22.22.0
|
||||
@@ -81,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.
|
||||
@@ -135,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
|
||||
@@ -175,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
|
||||
|
||||
@@ -287,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
|
||||
@@ -361,7 +344,7 @@ This is the best source of information about the problem. In a docker compose d
|
||||
|
||||
### Check web browser and webdriver installation
|
||||
|
||||
To take a screenshot, the worker visits the dashboard or chart using a headless browser, then takes a screenshot. If you are able to send a chart as CSV, XLSX, or text but can't send as PNG, your problem may lie with the browser.
|
||||
To take a screenshot, the worker visits the dashboard or chart using a headless browser, then takes a screenshot. If you are able to send a chart as CSV or text but can't send as PNG, your problem may lie with the browser.
|
||||
|
||||
If you are handling the installation of the headless browser on your own, do your own verification to ensure that the headless browser opens successfully in the worker environment.
|
||||
|
||||
|
||||
@@ -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
|
||||
---
|
||||
|
||||
|
||||
@@ -86,39 +86,6 @@ instead requires a cachelib object.
|
||||
|
||||
See [Async Queries via Celery](/admin-docs/configuration/async-queries-celery) for details.
|
||||
|
||||
## Celery beat
|
||||
|
||||
Superset has a Celery task that will periodically warm up the cache based on different strategies.
|
||||
To use it, add the following to your `superset_config.py`:
|
||||
|
||||
```python
|
||||
from celery.schedules import crontab
|
||||
from superset.config import CeleryConfig
|
||||
|
||||
# User that will be used to authenticate and render dashboards for cache warmup
|
||||
SUPERSET_CACHE_WARMUP_USER = "user_with_permission_to_dashboards"
|
||||
|
||||
# Extend the default CeleryConfig to add cache warmup schedule
|
||||
class CustomCeleryConfig(CeleryConfig):
|
||||
beat_schedule = {
|
||||
**CeleryConfig.beat_schedule,
|
||||
'cache-warmup-hourly': {
|
||||
'task': 'cache-warmup',
|
||||
'schedule': crontab(minute=0, hour='*'), # hourly
|
||||
'kwargs': {
|
||||
'strategy_name': 'top_n_dashboards',
|
||||
'top_n': 5,
|
||||
'since': '7 days ago',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
CELERY_CONFIG = CustomCeleryConfig
|
||||
```
|
||||
|
||||
This will cache the top 5 most popular dashboards every hour. For other
|
||||
strategies, check the `superset/tasks/cache.py` file.
|
||||
|
||||
## Caching Thumbnails
|
||||
|
||||
This is an optional feature that can be turned on by activating its [feature flag](/admin-docs/configuration/configuring-superset#feature-flags) on config:
|
||||
@@ -139,10 +106,6 @@ from superset.tasks.types import FixedExecutor
|
||||
THUMBNAIL_EXECUTORS = [FixedExecutor("admin")]
|
||||
```
|
||||
|
||||
When using `ExecutorType.EDITOR`, thumbnails are rendered as a physical user represented by
|
||||
the dashboard or chart editors. Superset prioritizes the last modifier, then the creator,
|
||||
then the first direct user editor, then a deterministic user from editor roles or groups.
|
||||
|
||||
For this feature you will need a cache system and celery workers. All thumbnails are stored on cache
|
||||
and are processed asynchronously by the workers.
|
||||
|
||||
@@ -183,10 +146,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):
|
||||
|
||||
|
||||
@@ -455,51 +455,6 @@ def FLASK_APP_MUTATOR(app: Flask) -> None:
|
||||
app.before_request_funcs.setdefault(None, []).append(make_session_permanent)
|
||||
```
|
||||
|
||||
## Customizing the landing page (index view)
|
||||
|
||||
The page served at `/` is rendered by an index view. By default Superset registers
|
||||
`SupersetIndexView`, which redirects to `/superset/welcome/` and also adds the
|
||||
`/lang/<locale>` locale handler. You can replace it with your own view, for example
|
||||
to send users straight to a specific dashboard or to a chart list.
|
||||
|
||||
Set `FAB_INDEX_VIEW` to the **importable dotted path** of your view class. Flask-AppBuilder
|
||||
resolves this during app initialization and uses it in place of the default:
|
||||
|
||||
```python
|
||||
# my_overrides.py — must be importable on the PYTHONPATH
|
||||
from flask import redirect
|
||||
from superset.initialization import SupersetIndexView
|
||||
from superset.superset_typing import FlaskResponse
|
||||
from flask_appbuilder import expose
|
||||
|
||||
|
||||
class MyIndexView(SupersetIndexView):
|
||||
@expose("/")
|
||||
def index(self) -> FlaskResponse:
|
||||
return redirect("/chart/list/")
|
||||
```
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
FAB_INDEX_VIEW = "my_overrides.MyIndexView"
|
||||
```
|
||||
|
||||
A few things that commonly trip people up:
|
||||
|
||||
- **Subclass `SupersetIndexView`, not Flask-AppBuilder's bare `IndexView`.** Subclassing
|
||||
keeps Superset's `/lang/<locale>` locale handling; replacing it with a bare `IndexView`
|
||||
silently drops that behavior.
|
||||
- **The class must be importable as a real module.** `FAB_INDEX_VIEW` is resolved by
|
||||
importing the dotted path, which is independent of how `superset_config.py` itself is
|
||||
loaded. Superset only copies **uppercase** names out of `superset_config.py` into its
|
||||
runtime config, so a `FAB_INDEX_VIEW = "superset_config.MyIndexView"` reference only works
|
||||
if `superset_config` is itself importable by that name on the `PYTHONPATH`. If you load
|
||||
config via `SUPERSET_CONFIG_PATH` (an arbitrary file path), put the view in a separate
|
||||
importable module instead and reference that module.
|
||||
- **Don't set `appbuilder.indexview` from `FLASK_APP_MUTATOR`.** The mutator runs after
|
||||
routes are already registered, so the assignment has no effect on the `/` route. Use
|
||||
`FAB_INDEX_VIEW` instead.
|
||||
|
||||
## Feature Flags
|
||||
|
||||
To support a diverse set of users, Superset has some features that are not enabled by default. For
|
||||
@@ -549,24 +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/)
|
||||
:::
|
||||
|
||||
@@ -529,7 +529,6 @@ MCP_RESPONSE_SIZE_CONFIG = {
|
||||
"enabled": True,
|
||||
"token_limit": 25000,
|
||||
"warn_threshold_pct": 80,
|
||||
"max_list_items": 100,
|
||||
"excluded_tools": [
|
||||
"health_check",
|
||||
"get_chart_preview",
|
||||
@@ -544,7 +543,6 @@ MCP_RESPONSE_SIZE_CONFIG = {
|
||||
| `enabled` | `True` | Enable response size checking |
|
||||
| `token_limit` | `25000` | Maximum estimated token count per response |
|
||||
| `warn_threshold_pct` | `80` | Warn when response exceeds this percentage of the limit |
|
||||
| `max_list_items` | `100` | Cap on list-field length (e.g. `charts`, `native_filters`) applied to the `get_*_info` tools before falling back to more aggressive truncation. Raised from a hardcoded 30 in earlier versions; the higher default only keeps more data before the same token-budget fallback kicks in, so it's not a breaking change, but tenants that tuned workflows around the old 30-item cap should lower this value explicitly. |
|
||||
| `excluded_tools` | See above | Tools exempt from size checking (e.g., tools that return URLs, not data) |
|
||||
|
||||
### Caching
|
||||
|
||||
@@ -60,9 +60,9 @@ There are two approaches to making dashboards publicly accessible:
|
||||
|
||||
**Option 2: Dashboard-level access (selective control)**
|
||||
1. Set `PUBLIC_ROLE_LIKE = "Public"` in `superset_config.py`
|
||||
2. Add the `'ENABLE_VIEWERS': True` [Feature Flag](/admin-docs/configuration/feature-flags)
|
||||
3. Edit each dashboard's properties and add the "Public" role subject as a viewer
|
||||
4. Only published dashboards with the Public role subject explicitly assigned are visible to anonymous users
|
||||
2. Add the `'DASHBOARD_RBAC': True` [Feature Flag](/admin-docs/configuration/feature-flags)
|
||||
3. Edit each dashboard's properties and add the "Public" role
|
||||
4. Only dashboards with the Public role explicitly assigned are visible to anonymous users
|
||||
|
||||
See the [Public role documentation](/admin-docs/security/#public) for more details.
|
||||
|
||||
|
||||
@@ -223,9 +223,8 @@ compose based installation, edit the `x-superset-image:` line in your `docker-co
|
||||
`docker-compose-non-dev.yml` files, replacing `apachesuperset.docker.scarf.sh/apache/superset` with
|
||||
`apache/superset` to pull the image directly from Docker Hub.
|
||||
|
||||
To disable the Scarf telemetry pixel, set the `SCARF_ANALYTICS` environment variable to `false` in
|
||||
your `docker/.env` file. This is read at runtime, so it disables the pixel on the pre-built image
|
||||
without rebuilding the frontend.
|
||||
To disable the Scarf telemetry pixel, set the `SCARF_ANALYTICS` environment variable to `False` in
|
||||
your terminal and/or in your `docker/.env` file.
|
||||
:::
|
||||
|
||||
## 3. Log in to Superset
|
||||
|
||||
@@ -136,17 +136,7 @@ init:
|
||||
:::note
|
||||
Superset uses [Scarf Gateway](https://about.scarf.sh/scarf-gateway) to collect telemetry data. Knowing the installation counts for different Superset versions informs the project's decisions about patching and long-term support. Scarf purges personally identifiable information (PII) and provides only aggregated statistics.
|
||||
|
||||
There are two independent telemetry channels:
|
||||
|
||||
- **Image pulls** (Scarf Gateway): to opt out, edit the `repository:` line in your `helm/superset/values.yaml` file, replacing `apachesuperset.docker.scarf.sh/apache/superset` with `apache/superset` to pull the image directly from Docker Hub.
|
||||
- **The analytics pixel** rendered in the UI: to opt out, set the `SCARF_ANALYTICS` environment variable to `false` on the Superset containers via `extraEnv` in your `values.yaml`:
|
||||
|
||||
```yaml
|
||||
extraEnv:
|
||||
SCARF_ANALYTICS: "false"
|
||||
```
|
||||
|
||||
This is read at runtime, so it takes effect on the pre-built images without rebuilding the frontend.
|
||||
To opt-out of this data collection in your Helm-based installation, edit the `repository:` line in your `helm/superset/values.yaml` file, replacing `apachesuperset.docker.scarf.sh/apache/superset` with `apache/superset` to pull the image directly from Docker Hub.
|
||||
:::
|
||||
|
||||
### Dependencies
|
||||
|
||||
@@ -22,24 +22,31 @@ level dependencies.
|
||||
|
||||
**Debian and Ubuntu**
|
||||
|
||||
The following command will ensure that the required dependencies are installed (tested on Ubuntu 20.04, 22.04, and 24.04):
|
||||
|
||||
Ubuntu **24.04** uses python 3.12 per default, which currently is not supported by Superset. You need to add a second python installation of 3.11 and install the required additional dependencies.
|
||||
```bash
|
||||
sudo apt-get install build-essential libssl-dev libffi-dev python3-dev python3-pip python3-venv libsasl2-dev libldap2-dev libpq-dev default-libmysqlclient-dev pkg-config
|
||||
sudo add-apt-repository ppa:deadsnakes/ppa
|
||||
sudo apt update
|
||||
sudo apt install python3.11 python3.11-dev python3.11-venv build-essential libssl-dev libffi-dev libsasl2-dev libldap2-dev default-libmysqlclient-dev
|
||||
```
|
||||
|
||||
Refer to the
|
||||
[pyproject.toml](https://github.com/apache/superset/blob/master/pyproject.toml) file for the list of
|
||||
Python versions officially supported by Superset, and install a matching `python3` interpreter for
|
||||
your distribution. The `libpq-dev` package is only needed if you intend to connect to (or use) a
|
||||
PostgreSQL database; you can omit it otherwise.
|
||||
In Ubuntu **20.04 and 22.04** the following command will ensure that the required dependencies are installed:
|
||||
|
||||
```bash
|
||||
sudo apt-get install build-essential libssl-dev libffi-dev python3-dev python3-pip libsasl2-dev libldap2-dev default-libmysqlclient-dev
|
||||
```
|
||||
|
||||
In Ubuntu **before 20.04** the following command will ensure that the required dependencies are installed:
|
||||
|
||||
```bash
|
||||
sudo apt-get install build-essential libssl-dev libffi-dev python-dev python-pip libsasl2-dev libldap2-dev default-libmysqlclient-dev
|
||||
```
|
||||
|
||||
**Fedora and RHEL-derivative Linux distributions**
|
||||
|
||||
Install the following packages using the `yum` package manager:
|
||||
|
||||
```bash
|
||||
sudo yum install gcc gcc-c++ libffi-devel python3-devel python3-pip python3-wheel openssl-devel cyrus-sasl-devel openldap-devel
|
||||
sudo yum install gcc gcc-c++ libffi-devel python-devel python-pip python-wheel openssl-devel cyrus-sasl-devel openldap-devel
|
||||
```
|
||||
|
||||
In more recent versions of CentOS and Fedora, you may need to install a slightly different set of packages using `dnf`:
|
||||
@@ -150,15 +157,8 @@ superset load_examples
|
||||
superset init
|
||||
|
||||
# To start a development web server on port 8088, use -p to bind to another port
|
||||
superset run -p 8088 --with-threads --reload
|
||||
|
||||
# For debugging with interactive console (⚠️ localhost only)
|
||||
# superset run -p 8088 --with-threads --reload --debugger
|
||||
superset run -p 8088 --with-threads --reload --debugger
|
||||
```
|
||||
|
||||
:::warning Security Note
|
||||
The `--debugger` flag enables Werkzeug's interactive console at `/console`. Only use this for local development and never bind to `0.0.0.0` or expose the server to networks when debugging is enabled.
|
||||
:::
|
||||
|
||||
If everything worked, you should be able to navigate to `hostname:port` in your browser (e.g.
|
||||
locally by default at `localhost:8088`) and login using the username and password you created.
|
||||
|
||||
@@ -161,7 +161,6 @@ Here's the documentation section how how to set up Talisman: https://superset.ap
|
||||
|
||||
- [ ] Regularly update to the latest major or minor versions of Superset. Those versions receive up-to-date security patches.
|
||||
- [ ] Rotate the `SUPERSET_SECRET_KEY` periodically (e.g., quarterly) and after any potential security incident.
|
||||
- [ ] Rotate the other security-critical secrets (guest-token and async-query JWT secrets, SMTP and database credentials) on the cadence in Appendix C, and after any potential security incident.
|
||||
- [ ] Conduct quarterly access reviews for all users.
|
||||
- [ ] Assuming logging and monitoring is in place, review security monitoring alerts weekly.
|
||||
|
||||
@@ -174,24 +173,6 @@ Rotating the `SUPERSET_SECRET_KEY` is a critical security procedure. It is manda
|
||||
The procedure for safely rotating the SECRET_KEY must be followed precisely to avoid locking yourself out of your instance. The official Apache Superset documentation maintains the correct, up-to-date procedure. Please follow the official guide here:
|
||||
https://superset.apache.org/admin-docs/configuration/configuring-superset/#rotating-to-a-newer-secret_key
|
||||
|
||||
### **Appendix C: Secrets Register and Rotation Schedule**
|
||||
|
||||
`SUPERSET_SECRET_KEY` is not the only security-critical secret in a Superset deployment. Maintain an inventory of all such secrets, store each in a secrets manager (not in `superset_config.py` or version control), assign a responsible maintainer, and rotate them on a defined cadence as well as after any suspected compromise.
|
||||
|
||||
| Secret | Purpose | Risk if leaked | Suggested rotation |
|
||||
|---|---|---|---|
|
||||
| `SUPERSET_SECRET_KEY` | Signs session cookies; key material for encrypting stored DB credentials (Fernet/AES) | Forged sessions (auth bypass / privilege escalation); decryption of exfiltrated metadata-DB secrets | Quarterly + post-incident |
|
||||
| `GUEST_TOKEN_JWT_SECRET` | Signs embedded-dashboard guest tokens | Forged guest tokens → unauthorized dashboard/data access | Quarterly + post-incident |
|
||||
| `GLOBAL_ASYNC_QUERIES_JWT_SECRET` | Signs the async-query channel JWT | Forged async-query tokens | Quarterly + post-incident |
|
||||
| SMTP password | Outbound email for alerts & reports | Email relay abuse / spoofing | Per organizational policy + post-incident |
|
||||
| Database connection passwords | Access to analytical databases and the metadata DB | Direct database access | Per organizational policy + post-incident |
|
||||
|
||||
Notes:
|
||||
|
||||
- Rotating `GUEST_TOKEN_JWT_SECRET` or `GLOBAL_ASYNC_QUERIES_JWT_SECRET` invalidates outstanding tokens of that type; schedule rotations accordingly.
|
||||
- After a suspected compromise, rotate **all** of the above, not only `SUPERSET_SECRET_KEY`.
|
||||
- Keep the register under change control so new secrets introduced by future features are added to the rotation schedule.
|
||||
|
||||
:::resources
|
||||
- [Blog: Running Apache Superset on the Open Internet](https://preset.io/blog/running-apache-superset-on-the-open-internet-a-report-from-the-fireline/)
|
||||
- [Blog: How Security Vulnerabilities are Reported & Handled in Apache Superset](https://preset.io/blog/how-security-vulnerabilities-are-reported-and-handled-in-apache-superset/)
|
||||
|
||||
@@ -91,24 +91,24 @@ PUBLIC_ROLE_LIKE = "Public"
|
||||
# PUBLIC_ROLE_LIKE = "Gamma"
|
||||
```
|
||||
|
||||
If you prefer to manually configure the Public role's permissions, or use subject-based
|
||||
dashboard viewer assignments, you do not need to set `PUBLIC_ROLE_LIKE`.
|
||||
If you prefer to manually configure the Public role's permissions (or use `DASHBOARD_RBAC` to
|
||||
grant access at the dashboard level), you do not need to set `PUBLIC_ROLE_LIKE`.
|
||||
|
||||
**Important notes:**
|
||||
|
||||
- **Data access is still required by default:** The Public role only grants UI/API permissions.
|
||||
You must also grant access to specific datasets necessary to view a dashboard. As with other
|
||||
roles, this can be done in two ways:
|
||||
- **Data access is still required:** The Public role only grants UI/API permissions. You must
|
||||
also grant access to specific datasets necessary to view a dashboard. As with other roles,
|
||||
this can be done in two ways:
|
||||
|
||||
- **Dataset-based access:** Dashboards only appear in the list and are accessible if the user
|
||||
has permission to at least one of their datasets. Grant dataset access by editing the Public
|
||||
role in the Superset UI (Menu -> Security -> List Roles -> Public) and adding the relevant
|
||||
data sources. All published dashboards using those datasets become visible.
|
||||
- **Without `DASHBOARD_RBAC`:** Dashboards only appear in the list and are accessible if
|
||||
the user has permission to at least one of their datasets. Grant dataset access by editing
|
||||
the Public role in the Superset UI (Menu → Security → List Roles → Public) and adding the
|
||||
relevant data sources. All published dashboards using those datasets become visible.
|
||||
|
||||
- **Subject-based dashboard access:** When `ENABLE_VIEWERS` is enabled, anonymous users can
|
||||
see published dashboards where the "Public" role subject has been explicitly added as a
|
||||
viewer. If `VIEWER_PROMISCUOUS_MODE` is enabled, viewer access can bypass dataset
|
||||
permission checks for dashboard rendering.
|
||||
- **With `DASHBOARD_RBAC` enabled:** Anonymous users will only see dashboards where the
|
||||
"Public" role has been explicitly added in the dashboard's properties. Dataset permissions
|
||||
are not required—DASHBOARD_RBAC handles the cascading permissions check. This provides
|
||||
fine-grained control over which dashboards are publicly visible.
|
||||
|
||||
- **Role synchronization:** Built-in role permissions (Admin, Alpha, Gamma, sql_lab, and Public
|
||||
when `PUBLIC_ROLE_LIKE = "Public"`) are synchronized when you run `superset init`. Any manual
|
||||
@@ -131,76 +131,10 @@ tables in the **Permissions** dropdown. To select the data sources you want to a
|
||||
You can then confirm with users assigned to the **Gamma** role that they see the
|
||||
objects (dashboards and slices) associated with the tables you just extended them.
|
||||
|
||||
### Subjects
|
||||
|
||||
A **subject** is a unified identity that can be granted access to Superset resources such as
|
||||
dashboards, charts, and datasets. Subjects come in three types:
|
||||
|
||||
- **User** — an individual Superset user
|
||||
- **Group** — a FAB group
|
||||
- **Role** — a FAB role (e.g. Alpha, Gamma, or any custom role)
|
||||
|
||||
Subjects are used throughout Superset to assign **editors** (who can modify a resource) and
|
||||
**viewers** (who can view it). For new resource-specific assignments, Superset defaults to
|
||||
**Users** and **Groups** in subject pickers. Groups are the recommended way to grant access to a
|
||||
set of people because they represent organizational membership, while roles remain focused on
|
||||
capability grants such as "can read dashboards" or "can write charts".
|
||||
|
||||
Role subjects are still supported for backwards compatibility with features that previously used
|
||||
roles directly, including Row Level Security role assignments and `DASHBOARD_RBAC`. Existing role
|
||||
subject assignments continue to be enforced even when roles are not exposed in the default picker
|
||||
configuration. However, when an administrator edits a subject list, roles are not available as new
|
||||
dropdown values unless that picker has been configured to expose role subjects. Roles are not
|
||||
recommended for new subject assignments, because using roles for both permissions and resource
|
||||
membership couples two separate concerns.
|
||||
|
||||
Administrators can control which subject types are available in pickers with
|
||||
`SUBJECTS_RELATED_TYPES`. By default, this setting exposes only users and groups:
|
||||
|
||||
```python
|
||||
from superset.subjects.types import SubjectType
|
||||
|
||||
SUBJECTS_RELATED_TYPES = [
|
||||
SubjectType.USER,
|
||||
SubjectType.GROUP,
|
||||
]
|
||||
```
|
||||
|
||||
Set `SUBJECTS_RELATED_TYPES = None` to expose all subject types, including roles. To expose roles
|
||||
only for a specific entity, set that entity's override:
|
||||
|
||||
```python
|
||||
from superset.subjects.types import SubjectType
|
||||
|
||||
SUBJECTS_RELATED_TYPES = [
|
||||
SubjectType.USER,
|
||||
SubjectType.GROUP,
|
||||
]
|
||||
|
||||
# Leave dashboard, chart, and alert/report overrides unset so they inherit
|
||||
# the users + groups default. Expose roles only in the RLS subject picker for
|
||||
# compatibility with existing role-based RLS workflows.
|
||||
SUBJECTS_RELATED_TYPES_RLS = [
|
||||
SubjectType.USER,
|
||||
SubjectType.GROUP,
|
||||
SubjectType.ROLE,
|
||||
]
|
||||
```
|
||||
|
||||
Available per-entity overrides are:
|
||||
|
||||
- `SUBJECTS_RELATED_TYPES_DASHBOARDS` for dashboard editor/viewer pickers
|
||||
- `SUBJECTS_RELATED_TYPES_CHARTS` for chart editor/viewer pickers
|
||||
- `SUBJECTS_RELATED_TYPES_RLS` for Row Level Security subject pickers
|
||||
- `SUBJECTS_RELATED_TYPES_ALERT_REPORTS` for alert/report editor pickers
|
||||
|
||||
When an override is set, it replaces `SUBJECTS_RELATED_TYPES` for that picker. When it is `None`,
|
||||
the picker inherits the global default.
|
||||
|
||||
### Dashboard Access Control
|
||||
|
||||
Access to dashboards is managed via editors (subjects that have edit permissions to the dashboard).
|
||||
Non-editor user access can be managed in two ways. Note that dashboards must be published to be
|
||||
Access to dashboards is managed via owners (users that have edit permissions to the dashboard).
|
||||
Non-owner user access can be managed in two ways. Note that dashboards must be published to be
|
||||
visible to other users.
|
||||
|
||||
#### Dataset-Based Access (Default)
|
||||
@@ -212,40 +146,30 @@ to a role (Menu → Security → List Roles).
|
||||
This is the simplest approach but provides all-or-nothing access based on dataset permissions—
|
||||
if a user has access to a dataset, they can see all published dashboards using that dataset.
|
||||
|
||||
#### Dashboard and Chart-Level Access
|
||||
#### Dashboard-Level Access (DASHBOARD_RBAC)
|
||||
|
||||
For fine-grained control over which dashboards and charts specific users or groups can access,
|
||||
enable subject-based viewers:
|
||||
For fine-grained control over which dashboards specific roles can access, enable the
|
||||
`DASHBOARD_RBAC` feature flag:
|
||||
|
||||
```python
|
||||
FEATURE_FLAGS = {
|
||||
"ENABLE_VIEWERS": True,
|
||||
"DASHBOARD_RBAC": True,
|
||||
}
|
||||
```
|
||||
|
||||
With this enabled, you can assign viewer subjects to each dashboard or chart in its properties.
|
||||
Users will see resources where they are assigned directly, or indirectly through a group subject.
|
||||
Role subjects can also be enabled for compatibility, but groups are recommended for new viewer
|
||||
assignments.
|
||||
|
||||
`ENABLE_VIEWERS` can be adopted incrementally. Dashboards and charts with no assigned viewers keep
|
||||
the existing implicit dataset-access behavior: users who can access the underlying dataset can still
|
||||
see published dashboards that use that dataset and charts backed by that dataset. Assigning one or
|
||||
more viewers changes that specific resource to explicit viewer access for non-editors. Removing all
|
||||
viewers returns the resource to the dataset-based fallback. Explicit Viewers are the preferred model
|
||||
going forward; the implicit fallback may be deprecated and removed in a later major version.
|
||||
With this enabled, you can assign specific roles to each dashboard in its properties. Users
|
||||
will only see dashboards where their role is explicitly added.
|
||||
|
||||
**Important considerations:**
|
||||
- Viewer access uses normal dataset checks unless `VIEWER_PROMISCUOUS_MODE` is enabled
|
||||
- With `VIEWER_PROMISCUOUS_MODE`, dashboard viewer access can bypass dataset-level checks for
|
||||
charts and datasets in that dashboard
|
||||
- Dashboards and charts without assigned viewers fall back to dataset-based access
|
||||
- Dashboard access **bypasses** dataset-level checks—granting a role access to a dashboard
|
||||
implicitly grants read access to all charts and datasets in that dashboard
|
||||
- Dashboards without any assigned roles fall back to dataset-based access
|
||||
- The dashboard must still be published to be visible
|
||||
|
||||
This feature is particularly useful for:
|
||||
- Making specific dashboards public while keeping others private
|
||||
- Granting access to dashboards without exposing the underlying datasets for other uses
|
||||
- Creating dashboard-specific access patterns that don't align with dataset permissions
|
||||
- Creating dashboard-specific access patterns that don't align with dataset ownership
|
||||
|
||||
### SQL Execution Security Considerations
|
||||
|
||||
@@ -377,16 +301,16 @@ based on the roles and permissions that were attributed.
|
||||
### Row Level Security
|
||||
|
||||
Using Row Level Security filters (under the **Security** menu) you can create filters
|
||||
that are assigned to a particular dataset, as well as a set of subjects.
|
||||
that are assigned to a particular dataset, as well as a set of roles.
|
||||
If you want members of the Finance team to only have access to
|
||||
rows where `department = "finance"`, you could:
|
||||
|
||||
- Create a Row Level Security filter with that clause (`department = "finance"`)
|
||||
- Then assign the clause to the **Finance** role subject and the dataset it applies to
|
||||
- Then assign the clause to the **Finance** role and the dataset it applies to
|
||||
|
||||
The **clause** field, which can contain arbitrary text, is then added to the generated
|
||||
SQL statement's WHERE clause. So you could even do something like create a filter
|
||||
for the last 30 days and apply it to a specific subject, with a clause
|
||||
for the last 30 days and apply it to a specific role, with a clause
|
||||
like `date_field > DATE_SUB(NOW(), INTERVAL 30 DAY)`. It can also support
|
||||
multiple conditions: `client_id = 6` AND `advertiser="foo"`, etc.
|
||||
|
||||
@@ -397,14 +321,12 @@ RLS clauses also support **Jinja templating** when `ENABLE_TEMPLATE_PROCESSING`
|
||||
|
||||
There are two types of RLS filters:
|
||||
|
||||
- **Regular** — The filter clause is applied when the querying user matches one of the
|
||||
subjects assigned to the filter. Use this to restrict what specific users, roles, or groups
|
||||
can see.
|
||||
- **Base** — The filter clause is applied to **all** users _except_ those matching the assigned
|
||||
subjects. Use this to define a default restriction that privileged subjects (e.g. the Admin
|
||||
role subject) are exempt from. For example, a Base filter with clause `1 = 0` and the Admin
|
||||
role subject would hide all rows from everyone except Admin - useful as a deny-by-default
|
||||
baseline.
|
||||
- **Regular** — The filter clause is applied when the querying user belongs to one of the
|
||||
roles assigned to the filter. Use this to restrict what specific roles can see.
|
||||
- **Base** — The filter clause is applied to **all** users _except_ those in the assigned
|
||||
roles. Use this to define a default restriction that privileged roles (e.g. Admin) are
|
||||
exempt from. For example, a Base filter with clause `1 = 0` and the Admin role would
|
||||
hide all rows from everyone except Admin — useful as a deny-by-default baseline.
|
||||
|
||||
#### Group Keys and Filter Combination
|
||||
|
||||
@@ -494,20 +416,21 @@ GET /api/v1/rowlevelsecurity/
|
||||
GET /api/v1/rowlevelsecurity/?q=(filters:!((col:tables,opr:rel_m_m,value:<dataset_id>)))
|
||||
```
|
||||
|
||||
**Filter RLS rules by subject:**
|
||||
**Filter RLS rules by role:**
|
||||
|
||||
```
|
||||
GET /api/v1/rowlevelsecurity/?q=(filters:!((col:subjects,opr:rel_m_m,value:<subject_id>)))
|
||||
GET /api/v1/rowlevelsecurity/?q=(filters:!((col:roles,opr:rel_m_m,value:<role_id>)))
|
||||
```
|
||||
|
||||
**View details of a specific rule** (including clause, assigned datasets, and subjects):
|
||||
**View details of a specific rule** (including clause, assigned datasets, and roles):
|
||||
|
||||
```
|
||||
GET /api/v1/rowlevelsecurity/<id>
|
||||
```
|
||||
|
||||
The response includes the filter's `name`, `filter_type` (Regular or Base), `clause`,
|
||||
`group_key`, assigned `tables` (with id, schema, and table\_name), and assigned `subjects`.
|
||||
`group_key`, assigned `tables` (with id, schema, and table\_name), and assigned `roles`
|
||||
(with id and name).
|
||||
|
||||
:::tip Auditing RLS for virtual datasets
|
||||
To find all RLS rules that could affect a particular virtual dataset, query the list
|
||||
|
||||
@@ -157,15 +157,8 @@ superset load_examples
|
||||
superset init
|
||||
|
||||
# To start a development web server on port 8088, use -p to bind to another port
|
||||
superset run -p 8088 --with-threads --reload
|
||||
|
||||
# For debugging with interactive console (⚠️ localhost only)
|
||||
# superset run -p 8088 --with-threads --reload --debugger
|
||||
superset run -p 8088 --with-threads --reload --debugger
|
||||
```
|
||||
|
||||
:::warning Security Note
|
||||
The `--debugger` flag enables Werkzeug's interactive console at `/console`. Only use this for local development and never bind to `0.0.0.0` or expose the server to networks when debugging is enabled.
|
||||
:::
|
||||
|
||||
If everything worked, you should be able to navigate to `hostname:port` in your browser (e.g.
|
||||
locally by default at `localhost:8088`) and login using the username and password you created.
|
||||
|
||||
@@ -26,7 +26,7 @@ import { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# MetadataBar
|
||||
|
||||
MetadataBar displays a row of metadata items (SQL info, editors, last modified, tags, dashboards, etc.) that collapse responsively based on available width.
|
||||
MetadataBar displays a row of metadata items (SQL info, owners, last modified, tags, dashboards, etc.) that collapse responsively based on available width.
|
||||
|
||||
## Live Example
|
||||
|
||||
@@ -43,9 +43,9 @@ MetadataBar displays a row of metadata items (SQL info, editors, last modified,
|
||||
title: "Click to view query"
|
||||
},
|
||||
{
|
||||
type: "editor",
|
||||
type: "owner",
|
||||
createdBy: "Jane Smith",
|
||||
editors: [
|
||||
owners: [
|
||||
"John Doe",
|
||||
"Mary Wilson"
|
||||
],
|
||||
@@ -104,9 +104,9 @@ function Demo() {
|
||||
const items = [
|
||||
{ type: 'sql', title: 'Click to view query' },
|
||||
{
|
||||
type: 'editor',
|
||||
type: 'owner',
|
||||
createdBy: 'Jane Smith',
|
||||
editors: ['John Doe', 'Mary Wilson'],
|
||||
owners: ['John Doe', 'Mary Wilson'],
|
||||
createdOn: 'a week ago',
|
||||
},
|
||||
{
|
||||
@@ -125,7 +125,7 @@ function Demo() {
|
||||
```tsx live
|
||||
function MinimalMetadata() {
|
||||
const items = [
|
||||
{ type: 'editor', createdBy: 'Admin', editors: ['Admin'], createdOn: 'yesterday' },
|
||||
{ type: 'owner', createdBy: 'Admin', owners: ['Admin'], createdOn: 'yesterday' },
|
||||
{ type: 'lastModified', value: '2 hours ago', modifiedBy: 'Admin' },
|
||||
];
|
||||
return <MetadataBar items={items} />;
|
||||
@@ -138,7 +138,7 @@ function MinimalMetadata() {
|
||||
function FullMetadata() {
|
||||
const items = [
|
||||
{ type: 'sql', title: 'SELECT * FROM ...' },
|
||||
{ type: 'editor', createdBy: 'Jane Smith', editors: ['Jane Smith', 'John Doe', 'Bob Wilson'], createdOn: '2 weeks ago' },
|
||||
{ type: 'owner', createdBy: 'Jane Smith', owners: ['Jane Smith', 'John Doe', 'Bob Wilson'], createdOn: '2 weeks ago' },
|
||||
{ type: 'lastModified', value: '3 days ago', modifiedBy: 'John Doe' },
|
||||
{ type: 'tags', values: ['production', 'finance', 'quarterly'] },
|
||||
{ type: 'dashboards', title: 'Used in 12 dashboards' },
|
||||
@@ -158,7 +158,7 @@ function FullMetadata() {
|
||||
| `createdBy` | `string` | `"Jane Smith"` | - |
|
||||
| `modifiedBy` | `string` | `"Jane Smith"` | - |
|
||||
| `description` | `string` | `"To preview the list of dashboards go to More settings."` | - |
|
||||
| `items` | `any` | `[{"type":"sql","title":"Click to view query"},{"type":"editor","createdBy":"Jane Smith","editors":["John Doe","Mary Wilson"],"createdOn":"a week ago"},{"type":"lastModified","value":"a week ago","modifiedBy":"Jane Smith"},{"type":"tags","values":["management","research","poc"]},{"type":"dashboards","title":"Added to 3 dashboards","description":"To preview the list of dashboards go to More settings."}]` | Items displayed in the metadata bar. The `editor` type shows editors/creators. |
|
||||
| `items` | `any` | `[{"type":"sql","title":"Click to view query"},{"type":"owner","createdBy":"Jane Smith","owners":["John Doe","Mary Wilson"],"createdOn":"a week ago"},{"type":"lastModified","value":"a week ago","modifiedBy":"Jane Smith"},{"type":"tags","values":["management","research","poc"]},{"type":"dashboards","title":"Added to 3 dashboards","description":"To preview the list of dashboards go to More settings."}]` | - |
|
||||
|
||||
## Import
|
||||
|
||||
|
||||
@@ -102,8 +102,6 @@ Affecting the Docker build process:
|
||||
save some precious time on startup by `SUPERSET_LOAD_EXAMPLES=no docker compose up`
|
||||
- **SUPERSET_LOG_LEVEL (default=info)**: Can be set to debug, info, warning, error, critical
|
||||
for more verbose logging
|
||||
- **SUPERSET_DEBUG_ENABLED (default=false)**: Enable Werkzeug debugger with interactive console.
|
||||
Set to `true` for debugging: `SUPERSET_DEBUG_ENABLED=true docker compose up`
|
||||
|
||||
For more env vars that affect your configuration, see this
|
||||
[superset_config.py](https://github.com/apache/superset/blob/master/docker/pythonpath_dev/superset_config.py)
|
||||
|
||||
@@ -332,39 +332,15 @@ cd superset-frontend
|
||||
npm run build-translation
|
||||
|
||||
# Backend
|
||||
pybabel compile --use-fuzzy -d superset/translations
|
||||
pybabel compile -d superset/translations
|
||||
```
|
||||
|
||||
`--use-fuzzy` includes `#, fuzzy` entries in the compiled `.mo` files. Superset
|
||||
serves fuzzy translations on purpose: the frontend build (`po2json --fuzzy`)
|
||||
already includes them, `flask fab babel-compile` (used by the release images)
|
||||
compiles with `-f`, and the production `Dockerfile` compiles with `--use-fuzzy`
|
||||
as well. This keeps machine-generated (and other draft) translations visible in
|
||||
the UI rather than falling back to English while they await review.
|
||||
|
||||
### Backfilling missing translations with AI
|
||||
|
||||
For languages with many untranslated strings, the repo includes a script that
|
||||
uses Claude AI to generate draft translations for any missing entries. All
|
||||
AI-generated strings are marked `#, fuzzy` and tagged with an attribution
|
||||
comment so that human reviewers know they need to be checked.
|
||||
|
||||
Note that `#, fuzzy` marks a translation as *needing review*, not as *withheld*:
|
||||
both the frontend and backend builds serve fuzzy entries (see [Applying
|
||||
translations](#applying-translations) above), so an AI-generated string is shown
|
||||
in the UI as soon as it is built and deployed. Reviewers should verify each
|
||||
entry and remove the `#, fuzzy` flag to promote it to a confirmed translation.
|
||||
|
||||
The script never touches entries that must stay literal — icon names, enum
|
||||
values, SQL keywords, API field names, and example placeholders. These are
|
||||
registered in `superset/translations/do-not-translate.txt`;
|
||||
`scripts/translations/apply_do_not_translate.py` stamps them in `messages.pot`
|
||||
with a `#. do-not-translate` extracted comment (run automatically
|
||||
from `babel_update.sh`), which `pybabel update` then propagates to every
|
||||
catalog. To mark a new string do-not-translate, add its msgid to the registry.
|
||||
The backfill also honors that marker and any legacy do-not-translate translator
|
||||
comment (e.g. the `ru` catalog's `# Не переводить`), leaving such entries
|
||||
untranslated so they fall back to the source token.
|
||||
comment so that human reviewers know they need to be checked before merging.
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
|
||||
@@ -34,14 +34,15 @@ Frontend contribution types allow extensions to extend Superset's user interface
|
||||
|
||||
Extensions can add new views or panels to the host application, such as custom SQL Lab panels, dashboards, or other UI components. Contribution areas are uniquely identified (e.g., `sqllab.panels` for SQL Lab panels), enabling seamless integration into specific parts of the application.
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { views } from '@apache-superset/core';
|
||||
import MyPanel from './MyPanel';
|
||||
|
||||
views.registerView(
|
||||
{ id: 'my-extension.main', name: 'My Panel Name' },
|
||||
'sqllab.panels',
|
||||
MyPanel,
|
||||
() => <MyPanel />,
|
||||
);
|
||||
```
|
||||
|
||||
@@ -111,24 +112,6 @@ editors.registerEditor(
|
||||
|
||||
See [Editors Extension Point](./extension-points/editors.md) for implementation details.
|
||||
|
||||
### Chat
|
||||
|
||||
Extensions can add a chat interface to Superset by registering a trigger component and a panel component. The host owns the layout, open/close state, and display mode — the extension only provides the UI. The panel can be displayed as a floating overlay or docked as a resizable sidebar beside the page content, and the user's preference is persisted across reloads.
|
||||
|
||||
```tsx
|
||||
import { chat } from '@apache-superset/core';
|
||||
import ChatTrigger from './ChatTrigger';
|
||||
import ChatPanel from './ChatPanel';
|
||||
|
||||
chat.registerChat(
|
||||
{ id: 'my-org.my-chat', name: 'My Chat' },
|
||||
ChatTrigger,
|
||||
ChatPanel,
|
||||
);
|
||||
```
|
||||
|
||||
See [Chat](./extension-points/chat.md) for implementation details.
|
||||
|
||||
## Backend
|
||||
|
||||
Backend contribution types allow extensions to extend Superset's server-side capabilities. Backend contributions are registered at startup via classes and functions imported from the auto-discovered `entrypoint.py` file.
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
---
|
||||
title: Chat
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Chat Contributions
|
||||
|
||||
Extensions can add a chat interface to Superset by registering a trigger and a panel. The host owns the layout, open/close state, and display mode — the extension only needs to provide the UI components.
|
||||
|
||||
## Overview
|
||||
|
||||
A chat registration consists of two React components:
|
||||
|
||||
| Component | Role |
|
||||
|-----------|------|
|
||||
| **Trigger** | Always-visible entry point (e.g., a floating button). Rendered in the bottom-right corner in floating mode, or as a fixed overlay in panel mode. |
|
||||
| **Panel** | The chat UI itself (message list, input, etc.). Mounted by the host in the active display mode. |
|
||||
|
||||
## Display Modes
|
||||
|
||||
The host supports two display modes, switchable by the user or the extension at runtime:
|
||||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| `floating` | Panel floats above page content, anchored to the bottom-right corner. |
|
||||
| `panel` | Panel is docked to the right side of the application as a resizable sidebar, sitting beside the page content. |
|
||||
|
||||
The user's last selected mode and open/closed state are persisted across page reloads.
|
||||
|
||||
## Registering a Chat
|
||||
|
||||
Call `chat.registerChat` from your extension's entry point with a descriptor, a trigger factory, and a panel factory:
|
||||
|
||||
```tsx
|
||||
import { chat } from '@apache-superset/core';
|
||||
import ChatTrigger from './ChatTrigger';
|
||||
import ChatPanel from './ChatPanel';
|
||||
|
||||
chat.registerChat(
|
||||
{ id: 'my-org.my-chat', name: 'My Chat' },
|
||||
ChatTrigger,
|
||||
ChatPanel,
|
||||
);
|
||||
```
|
||||
|
||||
Only one chat registration is active at a time. If a second extension calls `registerChat`, it replaces the first and a warning is logged.
|
||||
|
||||
## Opening and Closing the Chat
|
||||
|
||||
The trigger component is responsible for toggling the panel. Use `chat.isOpen()`, `chat.open()`, and `chat.close()` to control visibility:
|
||||
|
||||
```tsx
|
||||
import { chat } from '@apache-superset/core';
|
||||
|
||||
export default function ChatTrigger() {
|
||||
return (
|
||||
<button onClick={() => (chat.isOpen() ? chat.close() : chat.open())}>
|
||||
💬
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You can also subscribe to open/close events from any component:
|
||||
|
||||
```tsx
|
||||
useEffect(() => {
|
||||
const { dispose } = chat.onDidOpen(() => console.log('chat opened'));
|
||||
return dispose;
|
||||
}, []);
|
||||
```
|
||||
|
||||
## Changing the Display Mode
|
||||
|
||||
Call `chat.setDisplayMode` to switch between `'floating'` and `'panel'` modes. In your panel component, subscribe to `onDidChangeDisplayMode` to react to changes (including those triggered by the user):
|
||||
|
||||
```tsx
|
||||
import { useState, useEffect } from 'react';
|
||||
import { chat } from '@apache-superset/core';
|
||||
|
||||
export default function ChatPanel() {
|
||||
const [mode, setMode] = useState(chat.getDisplayMode());
|
||||
|
||||
useEffect(() => {
|
||||
const { dispose } = chat.onDidChangeDisplayMode(m => setMode(m));
|
||||
return dispose;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ height: mode === 'panel' ? '100%' : '80vh' }}>
|
||||
<button onClick={() => chat.setDisplayMode(mode === 'panel' ? 'floating' : 'panel')}>
|
||||
{mode === 'panel' ? 'Float' : 'Dock'}
|
||||
</button>
|
||||
{/* message list and input */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Chat API Reference
|
||||
|
||||
All methods are available on the `chat` namespace from `@apache-superset/core`:
|
||||
|
||||
| Method / Event | Description |
|
||||
|----------------|-------------|
|
||||
| `registerChat(descriptor, trigger, panel)` | Register a chat extension. Returns a `Disposable` to unregister. |
|
||||
| `open()` | Open the chat panel. No-op if already open or no registration. |
|
||||
| `close()` | Close the chat panel. |
|
||||
| `isOpen()` | Returns `true` if the panel is currently open. |
|
||||
| `getDisplayMode()` | Returns the current display mode (`'floating'` or `'panel'`). |
|
||||
| `setDisplayMode(mode)` | Switch between `'floating'` and `'panel'` mode. |
|
||||
| `onDidOpen(listener)` | Subscribe to panel open events. Returns a `Disposable`. |
|
||||
| `onDidClose(listener)` | Subscribe to panel close events. Returns a `Disposable`. |
|
||||
| `onDidChangeDisplayMode(listener)` | Subscribe to display mode changes. Returns a `Disposable`. |
|
||||
| `onDidRegisterChat(listener)` | Subscribe to registration events. |
|
||||
| `onDidUnregisterChat(listener)` | Subscribe to unregistration events. |
|
||||
| `onDidResizePanel(listener)` | Subscribe to panel resize events (panel mode only). Not all hosts provide a resizer — do not rely on this firing. Returns a `Disposable`. |
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Contribution Types](../contribution-types.md)** — Explore other contribution types
|
||||
- **[Development](../development.md)** — Set up your development environment
|
||||
@@ -128,7 +128,7 @@ from superset.dashboards.commands.create import CreateDashboardCommand
|
||||
def test_create_dashboard_success():
|
||||
properties = {
|
||||
"dashboard_title": "Test Dashboard",
|
||||
"editors": [1]
|
||||
"owners": [1]
|
||||
}
|
||||
|
||||
command = CreateDashboardCommand(properties)
|
||||
@@ -275,11 +275,9 @@ def handle_validation_error(error):
|
||||
from typing import List, Optional, Dict, Any
|
||||
from superset.models.dashboard import Dashboard
|
||||
|
||||
def get_dashboards_by_editor(editor_id: int) -> List[Dashboard]:
|
||||
"""Get all dashboards editable by a specific subject"""
|
||||
return db.session.query(Dashboard).filter(
|
||||
Dashboard.editors.any(id=editor_id)
|
||||
).all()
|
||||
def get_dashboards_by_owner(owner_id: int) -> List[Dashboard]:
|
||||
"""Get all dashboards owned by a specific user"""
|
||||
return db.session.query(Dashboard).filter_by(owner_id=owner_id).all()
|
||||
|
||||
def create_dashboard(properties: Dict[str, Any]) -> Optional[Dashboard]:
|
||||
"""Create a new dashboard with the given properties"""
|
||||
|
||||
@@ -153,10 +153,10 @@ class DashboardDAO:
|
||||
return db.session.query(Dashboard).filter_by(slug=slug).first()
|
||||
|
||||
@classmethod
|
||||
def find_by_editor(cls, editor_id: int) -> List[Dashboard]:
|
||||
"""Find all dashboards editable by a subject"""
|
||||
return db.session.query(Dashboard).filter(
|
||||
Dashboard.editors.any(id=editor_id)
|
||||
def find_by_owner(cls, owner_id: int) -> List[Dashboard]:
|
||||
"""Find all dashboards owned by a user"""
|
||||
return db.session.query(Dashboard).filter_by(
|
||||
created_by_fk=owner_id
|
||||
).all()
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -47,8 +47,6 @@ module.exports = {
|
||||
collapsed: true,
|
||||
items: [
|
||||
'extensions/extension-points/sqllab',
|
||||
'extensions/extension-points/editors',
|
||||
'extensions/extension-points/chat',
|
||||
],
|
||||
},
|
||||
'extensions/development',
|
||||
|
||||
@@ -120,7 +120,7 @@ SMTP_MAIL_FROM = "superset@localhost"
|
||||
# Must match where your frontend is running
|
||||
WEBDRIVER_BASEURL = "http://localhost:9000/"
|
||||
|
||||
ALERT_REPORTS_EXECUTORS = [ExecutorType.EDITOR]
|
||||
ALERT_REPORTS_EXECUTE_AS = [ExecutorType.OWNER]
|
||||
|
||||
FEATURE_FLAGS = {
|
||||
"ALERT_REPORTS": True,
|
||||
|
||||
+2
-19
@@ -181,23 +181,6 @@ value in milliseconds in the JSON Metadata field:
|
||||
Here, the entire dashboard will refresh at once if periodic refresh is on. The stagger time of 2.5
|
||||
seconds is ignored.
|
||||
|
||||
The manual **Refresh dashboard** button can also stagger its chart requests, controlled by the
|
||||
`SUPERSET_DASHBOARD_MANUAL_REFRESH_STAGGER_MS` server config in `superset_config.py`. This defaults
|
||||
to `0`, which preserves the original behavior where every chart request fires at the same time when
|
||||
the button is clicked. To opt in to staggering, set a positive number of milliseconds; the window
|
||||
then becomes the larger of this value and the per-dashboard `stagger_time` metadata, which itself
|
||||
defaults to `5000` milliseconds when it is not set explicitly. Setting `stagger_refresh` to `false`
|
||||
in a dashboard's JSON Metadata also disables staggering on the manual-refresh path, so its charts
|
||||
fire all at once even when the server config is positive. If the backend does not provide a value at
|
||||
all, for example an older backend that predates this config or a value explicitly set to `None`, the
|
||||
frontend falls back to a built-in default of `5000` milliseconds.
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
# 0 (the default) fires every chart at once; set a positive value to stagger.
|
||||
SUPERSET_DASHBOARD_MANUAL_REFRESH_STAGGER_MS = 0
|
||||
```
|
||||
|
||||
**Why does ‘flask fab’ or superset freeze/hang/not responding when started (my home directory is
|
||||
NFS mounted)?**
|
||||
|
||||
@@ -338,8 +321,8 @@ This can be used, for example, to convert UTC time to local time.
|
||||
Superset uses [Scarf](https://about.scarf.sh/) by default to collect basic telemetry data upon installing and/or running Superset. This data helps the maintainers of Superset better understand which versions of Superset are being used, in order to prioritize patch/minor releases and security fixes.
|
||||
We use the [Scarf Gateway](https://docs.scarf.sh/gateway/) to sit in front of container registries, the [scarf-js](https://about.scarf.sh/package-sdks) package to track `npm` installations, and a Scarf pixel to gather anonymous analytics on Superset page views.
|
||||
Scarf purges PII and provides aggregated statistics. Superset users can easily opt out of analytics in various ways documented [here](https://docs.scarf.sh/gateway/#do-not-track) and [here](https://docs.scarf.sh/package-analytics/#as-a-user-of-a-package-using-scarf-js-how-can-i-opt-out-of-analytics).
|
||||
You can also opt out of the analytics pixel by setting the `SCARF_ANALYTICS` environment variable to `false`. This is read at runtime, so setting it on the Superset container (for example via `extraEnv` in the Helm chart, or `docker/.env` for Docker Compose) disables the pixel on the pre-built images without rebuilding the frontend. Note that this only disables the page-view pixel; the Scarf Gateway (container registry) and `scarf-js` (`npm`) channels are opted out separately, as described above.
|
||||
Additional opt-out instructions are available on the [Docker Compose](/admin-docs/installation/docker-compose) and [Kubernetes](/admin-docs/installation/kubernetes) installation pages.
|
||||
Superset maintainers can also opt out of telemetry data collection by setting the `SCARF_ANALYTICS` environment variable to `false` in the Superset container (or anywhere Superset/webpack are run).
|
||||
Additional opt-out instructions for Docker users are available on the [Docker Installation](/admin-docs/installation/docker-compose) page.
|
||||
|
||||
## Does Superset have an archive panel or trash bin from which a user can recover deleted assets?
|
||||
|
||||
|
||||
+10
-7
@@ -254,13 +254,16 @@ Understanding the Superset Points of View
|
||||
|
||||
- [Superset API](/developer-docs/api)
|
||||
|
||||
<!--
|
||||
The OSS Insight "Repo Activity" widget (https://next.ossinsight.io/) was
|
||||
intentionally removed. This page 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" />
|
||||
|
||||
@@ -211,8 +211,8 @@ slices and dashboards of your own.
|
||||
|
||||
### Manage access to Dashboards
|
||||
|
||||
Access to dashboards is managed via editors and permissions. Non-editor access can be controlled
|
||||
through dataset permissions, or through dashboard viewers when `ENABLE_VIEWERS` is enabled.
|
||||
Access to dashboards is managed via owners and permissions. Non-owner access can be controlled
|
||||
through dataset permissions or dashboard-level roles (using the `DASHBOARD_RBAC` feature flag).
|
||||
|
||||
For detailed information on configuring dashboard access, see the
|
||||
[Dashboard Access Control](/admin-docs/security/#dashboard-access-control) section in the
|
||||
@@ -228,9 +228,7 @@ title of your dashboard.
|
||||
<img src={useBaseUrl("/img/tutorial/publish_button_dashboard.png" )} />
|
||||
|
||||
:::warning
|
||||
Draft dashboards are only visible to dashboard editors and admins. Published dashboards are visible
|
||||
to users with access to the underlying datasets, or, when `ENABLE_VIEWERS` is enabled, to users
|
||||
who have viewer access directly or through an assigned role or group subject.
|
||||
Draft dashboards are only visible to the dashboard owners and admins. Published dashboards are visible to all users with access to the underlying datasets or if RBAC is enabled, to the roles that have been granted access to the dashboard.
|
||||
:::
|
||||
|
||||
### Mark a Dashboard as Favorite
|
||||
|
||||
@@ -749,7 +749,7 @@ const config: Config = {
|
||||
showReadingTime: true,
|
||||
// Please change this to your repo.
|
||||
editUrl:
|
||||
'https://github.com/apache/superset/tree/master/docs',
|
||||
'https://github.com/facebook/docusaurus/edit/main/website/blog/',
|
||||
},
|
||||
theme: {
|
||||
customCss: require.resolve('./src/styles/custom.css'),
|
||||
@@ -917,23 +917,6 @@ const config: Config = {
|
||||
footer: {
|
||||
links: [],
|
||||
copyright: `
|
||||
<div class="footer__social-links">
|
||||
<a href="https://bit.ly/join-superset-slack" target="_blank" rel="noopener noreferrer" title="Join us on Slack" aria-label="Slack">
|
||||
<img src="/img/community/slack-symbol.svg" alt="Slack" />
|
||||
</a>
|
||||
<a href="https://x.com/apachesuperset" target="_blank" rel="noopener noreferrer" title="Follow us on X" aria-label="X">
|
||||
<img src="/img/community/x-symbol.svg" alt="X" />
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/company/apache-superset" target="_blank" rel="noopener noreferrer" title="Follow us on LinkedIn" aria-label="LinkedIn">
|
||||
<img src="/img/community/linkedin-symbol.svg" alt="LinkedIn" />
|
||||
</a>
|
||||
<a href="https://bsky.app/profile/apachesuperset.bsky.social" target="_blank" rel="noopener noreferrer" title="Follow us on Bluesky" aria-label="Bluesky">
|
||||
<img src="/img/community/bluesky-symbol.svg" alt="Bluesky" />
|
||||
</a>
|
||||
<a href="https://reddit.com/r/apache-superset" target="_blank" rel="noopener noreferrer" title="Follow us on Reddit" aria-label="Reddit">
|
||||
<img src="/img/community/reddit-symbol.svg" alt="Reddit" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="footer__ci-services">
|
||||
<span>CI powered by</span>
|
||||
<a href="https://www.netlify.com/" target="_blank" rel="nofollow noopener noreferrer"><img src="/img/netlify.png" alt="Netlify" title="Netlify - Deploy Previews" /></a>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"copyright": {
|
||||
"message": "\n <div class=\"footer__social-links\">\n <a href=\"https://bit.ly/join-superset-slack\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Join us on Slack\" aria-label=\"Slack\">\n <img src=\"/img/community/slack-symbol.svg\" alt=\"Slack\" />\n </a>\n <a href=\"https://x.com/apachesuperset\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Follow us on X\" aria-label=\"X\">\n <img src=\"/img/community/x-symbol.svg\" alt=\"X\" />\n </a>\n <a href=\"https://www.linkedin.com/company/apache-superset\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Follow us on LinkedIn\" aria-label=\"LinkedIn\">\n <img src=\"/img/community/linkedin-symbol.svg\" alt=\"LinkedIn\" />\n </a>\n <a href=\"https://bsky.app/profile/apachesuperset.bsky.social\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Follow us on Bluesky\" aria-label=\"Bluesky\">\n <img src=\"/img/community/bluesky-symbol.svg\" alt=\"Bluesky\" />\n </a>\n <a href=\"https://reddit.com/r/apache-superset\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Follow us on Reddit\" aria-label=\"Reddit\">\n <img src=\"/img/community/reddit-symbol.svg\" alt=\"Reddit\" />\n </a>\n </div>\n <div class=\"footer__ci-services\">\n <span>CI powered by</span>\n <a href=\"https://www.netlify.com/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><img src=\"/img/netlify.png\" alt=\"Netlify\" title=\"Netlify - Deploy Previews\" /></a>\n </div>\n <p>Copyright © 2026,\n The <a href=\"https://www.apache.org/\" target=\"_blank\" rel=\"noreferrer\">Apache Software Foundation</a>,\n Licensed under the Apache <a href=\"https://apache.org/licenses/LICENSE-2.0\" target=\"_blank\" rel=\"noreferrer\">License</a>.</p>\n <p><small>Apache Superset, Apache, Superset, the Superset logo, and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation. All other products or name brands are trademarks of their respective holders, including The Apache Software Foundation.\n <a href=\"https://www.apache.org/\" target=\"_blank\">Apache Software Foundation</a> resources</small></p>\n <img class=\"footer__divider\" src=\"/img/community/line.png\" alt=\"Divider\" />\n <p>\n <small>\n <a href=\"/admin-docs/security/\" target=\"_blank\" rel=\"noreferrer\">Security</a> | \n <a href=\"https://www.apache.org/foundation/sponsorship.html\" target=\"_blank\" rel=\"noreferrer\">Donate</a> | \n <a href=\"https://www.apache.org/foundation/thanks.html\" target=\"_blank\" rel=\"noreferrer\">Thanks</a> | \n <a href=\"https://apache.org/events/current-event\" target=\"_blank\" rel=\"noreferrer\">Events</a> | \n <a href=\"https://apache.org/licenses/\" target=\"_blank\" rel=\"noreferrer\">License</a> | \n <a href=\"https://privacy.apache.org/policies/privacy-policy-public.html\" target=\"_blank\" rel=\"noreferrer\">Privacy</a>\n </small>\n </p>\n <!-- telemetry/analytics pixel: -->\n <img referrerPolicy=\"no-referrer-when-downgrade\" src=\"https://static.scarf.sh/a.png?x-pxid=39ae6855-95fc-4566-86e5-360d542b0a68\" />\n ",
|
||||
"message": "\n <div class=\"footer__ci-services\">\n <span>CI powered by</span>\n <a href=\"https://www.netlify.com/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><img src=\"/img/netlify.png\" alt=\"Netlify\" title=\"Netlify - Deploy Previews\" /></a>\n </div>\n <p>Copyright © 2026,\n The <a href=\"https://www.apache.org/\" target=\"_blank\" rel=\"noreferrer\">Apache Software Foundation</a>,\n Licensed under the Apache <a href=\"https://apache.org/licenses/LICENSE-2.0\" target=\"_blank\" rel=\"noreferrer\">License</a>.</p>\n <p><small>Apache Superset, Apache, Superset, the Superset logo, and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation. All other products or name brands are trademarks of their respective holders, including The Apache Software Foundation.\n <a href=\"https://www.apache.org/\" target=\"_blank\">Apache Software Foundation</a> resources</small></p>\n <img class=\"footer__divider\" src=\"/img/community/line.png\" alt=\"Divider\" />\n <p>\n <small>\n <a href=\"/docs/security/\" target=\"_blank\" rel=\"noreferrer\">Security</a> | \n <a href=\"https://www.apache.org/foundation/sponsorship.html\" target=\"_blank\" rel=\"noreferrer\">Donate</a> | \n <a href=\"https://www.apache.org/foundation/thanks.html\" target=\"_blank\" rel=\"noreferrer\">Thanks</a> | \n <a href=\"https://apache.org/events/current-event\" target=\"_blank\" rel=\"noreferrer\">Events</a> | \n <a href=\"https://apache.org/licenses/\" target=\"_blank\" rel=\"noreferrer\">License</a> | \n <a href=\"https://privacy.apache.org/policies/privacy-policy-public.html\" target=\"_blank\" rel=\"noreferrer\">Privacy</a>\n </small>\n </p>\n <!-- telemetry/analytics pixel: -->\n <img referrerPolicy=\"no-referrer-when-downgrade\" src=\"https://static.scarf.sh/a.png?x-pxid=39ae6855-95fc-4566-86e5-360d542b0a68\" />\n ",
|
||||
"description": "The footer copyright"
|
||||
}
|
||||
}
|
||||
|
||||
+8
-13
@@ -28,19 +28,14 @@
|
||||
# Skip builds when no docs changes (exit 0 = skip, non-zero = build).
|
||||
# Checks for changes in docs/ and README.md (which gets pulled into docs).
|
||||
#
|
||||
# $CACHED_COMMIT_REF is the last *deployed* commit; it is set on incremental
|
||||
# builds (notably the master production deploy) and empty on a context's
|
||||
# first build (every deploy preview). The production path diffs against it
|
||||
# and skips correctly.
|
||||
#
|
||||
# Deploy previews need different handling: Netlify checks out a *merge*
|
||||
# commit, so $COMMIT_REF (the PR head SHA) is frequently not resolvable in
|
||||
# the clone, and on a shallow clone `git merge-base` can fail too -- so the
|
||||
# previous logic fell through to a build on every PR, even non-docs ones.
|
||||
# Instead, always diff the checked-out HEAD against its merge-base with
|
||||
# master, deepening the shallow clone until that merge-base resolves. If it
|
||||
# genuinely can't be determined, exit non-zero to build (fail safe).
|
||||
ignore = 'if [ -n "$CACHED_COMMIT_REF" ]; then git diff --quiet "$CACHED_COMMIT_REF" HEAD -- . ../README.md; else git fetch --no-tags origin master >/dev/null 2>&1 || true; i=0; while [ "$i" -lt 10 ] && ! git merge-base origin/master HEAD >/dev/null 2>&1; do git fetch --deepen=200 origin master >/dev/null 2>&1 || break; i=$((i+1)); done; BASE="$(git merge-base origin/master HEAD 2>/dev/null || true)"; if [ -z "$BASE" ]; then exit 1; fi; git diff --quiet "$BASE" HEAD -- . ../README.md; fi'
|
||||
# $CACHED_COMMIT_REF is the last *deployed* commit. On a PR's first build it
|
||||
# is empty, so the original `git diff` errored and Netlify fell back to
|
||||
# building -- which is why every PR built a docs preview once even with no
|
||||
# docs changes. When it is empty we instead diff the whole branch against its
|
||||
# merge-base with master, so non-docs PRs are skipped from the very first
|
||||
# build. Subsequent builds (and the master production build) keep the cheaper
|
||||
# incremental $CACHED_COMMIT_REF diff. Any failure exits non-zero -> build.
|
||||
ignore = 'if [ -n "$CACHED_COMMIT_REF" ]; then git diff --quiet "$CACHED_COMMIT_REF" "$COMMIT_REF" -- . ../README.md; else git fetch origin master --depth=100 >/dev/null 2>&1; git diff --quiet "$(git merge-base origin/master "$COMMIT_REF" 2>/dev/null || echo origin/master)" "$COMMIT_REF" -- . ../README.md; fi'
|
||||
|
||||
[build.environment]
|
||||
# Node version matching docs/.nvmrc
|
||||
|
||||
+30
-22
@@ -43,7 +43,7 @@
|
||||
"version:remove:components": "node scripts/manage-versions.mjs remove components"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.2.5",
|
||||
"@ant-design/icons": "^6.2.3",
|
||||
"@docusaurus/core": "^3.10.1",
|
||||
"@docusaurus/faster": "^3.10.1",
|
||||
"@docusaurus/plugin-client-redirects": "^3.10.1",
|
||||
@@ -58,15 +58,26 @@
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@saucelabs/theme-github-codeblock": "^0.3.0",
|
||||
"@storybook/addon-docs": "^10.4.5",
|
||||
"@storybook/addon-docs": "^8.6.18",
|
||||
"@storybook/blocks": "^8.6.15",
|
||||
"@storybook/channels": "^8.6.18",
|
||||
"@storybook/client-logger": "^8.6.18",
|
||||
"@storybook/components": "^8.6.18",
|
||||
"@storybook/core": "^8.6.18",
|
||||
"@storybook/core-events": "^8.6.18",
|
||||
"@storybook/csf": "^0.1.13",
|
||||
"@storybook/docs-tools": "^8.6.18",
|
||||
"@storybook/preview-api": "^8.6.18",
|
||||
"@storybook/theming": "^8.6.15",
|
||||
"@superset-ui/core": "^0.20.4",
|
||||
"@swc/core": "^1.15.43",
|
||||
"antd": "^6.5.0",
|
||||
"baseline-browser-mapping": "^2.10.40",
|
||||
"caniuse-lite": "^1.0.30001803",
|
||||
"docusaurus-plugin-openapi-docs": "^5.1.1",
|
||||
"docusaurus-theme-openapi-docs": "^5.1.1",
|
||||
"js-yaml": "^5.2.0",
|
||||
"@swc/core": "^1.15.40",
|
||||
"antd": "^6.4.3",
|
||||
"baseline-browser-mapping": "^2.10.32",
|
||||
"caniuse-lite": "^1.0.30001793",
|
||||
"docusaurus-plugin-openapi-docs": "^5.0.2",
|
||||
"docusaurus-theme-openapi-docs": "^5.0.2",
|
||||
"js-yaml": "^4.1.1",
|
||||
"js-yaml-loader": "^1.2.2",
|
||||
"json-bigint": "^1.0.0",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^18.3.1",
|
||||
@@ -77,8 +88,8 @@
|
||||
"react-table": "^7.8.0",
|
||||
"remark-import-partial": "^0.0.2",
|
||||
"reselect": "^5.2.0",
|
||||
"storybook": "^10.4.5",
|
||||
"swagger-ui-react": "^5.32.8",
|
||||
"storybook": "^8.6.18",
|
||||
"swagger-ui-react": "^5.32.6",
|
||||
"swc-loader": "^0.2.7",
|
||||
"tinycolor2": "^1.4.2",
|
||||
"unist-util-visit": "^5.1.0"
|
||||
@@ -90,16 +101,16 @@
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/react": "^19.1.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.3",
|
||||
"@typescript-eslint/parser": "^8.63.0",
|
||||
"@typescript-eslint/parser": "^8.60.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.6",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^17.7.0",
|
||||
"prettier": "^3.9.5",
|
||||
"globals": "^17.6.0",
|
||||
"prettier": "^3.8.3",
|
||||
"typescript": "~6.0.3",
|
||||
"typescript-eslint": "^8.63.0",
|
||||
"webpack": "^5.108.2"
|
||||
"typescript-eslint": "^8.60.0",
|
||||
"webpack": "^5.107.2"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -117,16 +128,13 @@
|
||||
"react-redux": "^9.2.0",
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"baseline-browser-mapping": "^2.9.19",
|
||||
"swagger-client": "3.37.3",
|
||||
"lodash": "4.18.1",
|
||||
"lodash-es": "4.18.1",
|
||||
"yaml": "1.10.3",
|
||||
"uuid": "11.1.1",
|
||||
"serialize-javascript": "7.0.5",
|
||||
"d3-color": "3.1.0",
|
||||
"ws": "^8.21.0",
|
||||
"@docusaurus/core/@docusaurus/utils/gray-matter/js-yaml": "^3.15.0",
|
||||
"@docusaurus/core/**/js-yaml": "^4.3.0",
|
||||
"docusaurus-plugin-openapi-docs/**/js-yaml": "^4.3.0"
|
||||
"d3-color": "3.1.0"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
|
||||
}
|
||||
|
||||
@@ -1,136 +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.
|
||||
-->
|
||||
|
||||
# SIP: Authenticated encryption (AES-GCM) for app-encrypted fields
|
||||
|
||||
## [DRAFT — proposal for discussion]
|
||||
|
||||
This document is a draft proposal accompanying the code in this PR. It is
|
||||
intended to seed the formal SIP discussion. The code here ships the
|
||||
backward-compatible engine selection **and** the re-encryption migrator
|
||||
(Phases 1–2 below); both are opt-in and change nothing for existing installs by
|
||||
default. Flipping the default for fresh installs (Phase 3) remains future work.
|
||||
|
||||
## Motivation
|
||||
|
||||
Superset app-encrypts a number of sensitive fields before persisting them to
|
||||
the metadata database, including:
|
||||
|
||||
- database connection passwords and `encrypted_extra` (`superset/models/core.py`),
|
||||
- SSH tunnel credentials — password, private key, private-key password
|
||||
(`superset/databases/ssh_tunnel/models.py`),
|
||||
- OAuth2 tokens and other secrets stored via `EncryptedType`.
|
||||
|
||||
These fields are encrypted with `sqlalchemy_utils.EncryptedType`, which
|
||||
**defaults to `AesEngine` (AES-CBC)**. AES-CBC provides confidentiality but is
|
||||
**unauthenticated**: it has no integrity tag. An attacker with write access to
|
||||
the ciphertext (e.g. direct metadata-DB access, a backup, or a compromised
|
||||
replica) can perform **bit-flipping / chosen-ciphertext manipulation** to
|
||||
silently alter the decrypted plaintext of a secret without detection.
|
||||
|
||||
`AesGcmEngine` (AES-GCM) is authenticated encryption: tampering causes
|
||||
decryption to fail loudly rather than yielding attacker-influenced plaintext.
|
||||
Using authenticated encryption for secrets at rest is an ASVS L1 expectation
|
||||
(11.3.2 / cryptography best practice).
|
||||
|
||||
`config.py` already documents that operators *can* switch to GCM by writing a
|
||||
custom `AbstractEncryptedFieldAdapter`, but:
|
||||
|
||||
1. it is opt-in, undocumented as a security recommendation, and easy to miss;
|
||||
2. there is **no migration path** — flipping the engine on a populated database
|
||||
makes every existing secret undecryptable, because GCM ciphertext is not
|
||||
format-compatible with CBC.
|
||||
|
||||
## Proposed change
|
||||
|
||||
A three-part change, delivered incrementally so existing deployments are never
|
||||
broken:
|
||||
|
||||
### Phase 1 — engine selection (this PR)
|
||||
|
||||
- Add a `SQLALCHEMY_ENCRYPTED_FIELD_ENGINE` config (`"aes"` | `"aes-gcm"`),
|
||||
**defaulting to `"aes"`** (no behavior change for existing installs).
|
||||
- Teach the default `SQLAlchemyUtilsAdapter` to honor it (an explicit `engine`
|
||||
kwarg still wins, so the migrator can pin an engine).
|
||||
- This lets **new** deployments choose AES-GCM from day one with a one-line
|
||||
config, instead of writing a custom adapter.
|
||||
|
||||
### Phase 2 — CBC→GCM re-encryption migrator (this PR)
|
||||
|
||||
The existing `SecretsMigrator` (previously only used for `SECRET_KEY` rotation)
|
||||
gains an **engine migration** mode that:
|
||||
|
||||
1. discovers every `EncryptedType` column (via `discover_encrypted_fields()`),
|
||||
2. decrypts each value with the **source** engine (AES-CBC) under the current
|
||||
`SECRET_KEY`,
|
||||
3. re-encrypts with the **target** engine (AES-GCM),
|
||||
4. runs transactionally per the existing all-or-nothing semantics, and is
|
||||
idempotent per column (already-migrated values are skipped), so a run can be
|
||||
safely repeated or resumed.
|
||||
|
||||
Exposed via a new `--engine` option on the existing CLI command:
|
||||
`superset re-encrypt-secrets --engine aes-gcm`, runnable by operators with a DB
|
||||
backup in hand. The `SECRET_KEY` is unchanged; an engine change and a key
|
||||
rotation can also be combined (pass `--previous_secret_key` as well).
|
||||
|
||||
### Phase 3 — flip the default for new installs
|
||||
|
||||
Once the migrator and docs are in place, change the default to `"aes-gcm"` for
|
||||
**fresh** installs only (e.g. keyed off an empty metadata DB / documented in
|
||||
`UPDATING.md`), keeping existing installs on `"aes"` until they run Phase 2.
|
||||
|
||||
## New or changed public interfaces
|
||||
|
||||
- New config: `SQLALCHEMY_ENCRYPTED_FIELD_ENGINE: Literal["aes", "aes-gcm"]`.
|
||||
- New (Phase 2) CLI: `superset re-encrypt-secrets --engine <name>`.
|
||||
- No schema changes; ciphertext format changes per migrated column.
|
||||
|
||||
## Migration plan and compatibility
|
||||
|
||||
- **Backward compatible by default.** Phase 1 changes nothing unless the
|
||||
operator opts in.
|
||||
- Switching an existing deployment to `"aes-gcm"` **without** running the Phase
|
||||
2 migrator will make existing secrets undecryptable — this is called out in
|
||||
the config comment and must be in `UPDATING.md`.
|
||||
- Recommended operator runbook: take a metadata-DB backup → run
|
||||
`re-encrypt-secrets --engine aes-gcm` → set
|
||||
`SQLALCHEMY_ENCRYPTED_FIELD_ENGINE = "aes-gcm"` → restart → re-run
|
||||
`re-encrypt-secrets --engine aes-gcm` once more to sweep up any secrets a live
|
||||
instance wrote as AES-CBC during the cutover window. The canonical, more
|
||||
detailed version of this runbook lives in `UPDATING.md`; this is a summary.
|
||||
- `AesEngine` allows queryability over encrypted fields; AES-GCM does not.
|
||||
Any code that filters/queries on an encrypted column directly must be audited
|
||||
before Phase 3 (none is expected, but it must be verified).
|
||||
|
||||
## Rejected alternatives
|
||||
|
||||
- **Flip the default immediately.** Rejected: bricks every existing
|
||||
deployment's secrets with no migration path.
|
||||
- **Document-only (custom adapter).** Status quo; high friction and no
|
||||
migration tooling — most operators will never do it.
|
||||
|
||||
## Open questions
|
||||
|
||||
- GCM→CBC rollback (for operators who need queryability) already works via the
|
||||
same command (`re-encrypt-secrets --engine aes`), since the migrator is
|
||||
engine-symmetric. Should rollback be documented as a supported path or
|
||||
discouraged?
|
||||
- The migrator already supports a concurrent `SECRET_KEY` rotation + engine
|
||||
change in a single pass (pass `--previous_secret_key` alongside `--engine`).
|
||||
Is that combination worth calling out in the operator docs, or kept advanced?
|
||||
@@ -176,7 +176,7 @@ export interface DatabaseInfo {
|
||||
expressions_in_orderby?: boolean;
|
||||
|
||||
// Platform features
|
||||
limit_method?: number;
|
||||
limit_method?: string;
|
||||
limit_clause?: boolean;
|
||||
max_column_name?: number;
|
||||
supports_file_upload?: boolean;
|
||||
|
||||
@@ -3672,7 +3672,7 @@
|
||||
"drivers": [
|
||||
{
|
||||
"name": "crate",
|
||||
"pypi_package": "sqlalchemy-cratedb",
|
||||
"pypi_package": "crate[sqlalchemy]",
|
||||
"connection_string": "crate://{host}:{port}",
|
||||
"is_recommended": true
|
||||
}
|
||||
@@ -7235,10 +7235,10 @@
|
||||
"pypi_packages": [
|
||||
"oracledb"
|
||||
],
|
||||
"connection_string": "oracle+oracledb://{username}:{password}@{hostname}:{port}",
|
||||
"connection_string": "oracle://{username}:{password}@{hostname}:{port}",
|
||||
"default_port": 1521,
|
||||
"notes": "Previously used cx_Oracle, now uses oracledb.",
|
||||
"docs_url": "https://python-oracledb.readthedocs.io/en/latest/user_guide/installation.html",
|
||||
"docs_url": "https://cx-oracle.readthedocs.io/en/latest/user_guide/installation.html",
|
||||
"category": "Other Databases"
|
||||
},
|
||||
"engine": "oracle",
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
import Layout from '@theme/Layout';
|
||||
import { Avatar, Card, Col, Collapse, Row, Typography } from 'antd';
|
||||
import { load } from 'js-yaml';
|
||||
import BlurredSection from '../components/BlurredSection';
|
||||
import SectionHeader from '../components/SectionHeader';
|
||||
import DataSet from '../../../RESOURCES/INTHEWILD.yaml';
|
||||
@@ -37,7 +36,7 @@ interface DataSetType {
|
||||
categories: Record<string, Organization[]>;
|
||||
}
|
||||
|
||||
const typedDataSet = load(DataSet) as DataSetType;
|
||||
const typedDataSet = DataSet as DataSetType;
|
||||
|
||||
const ContributorAvatars = ({ contributors }: { contributors?: string[] }) => {
|
||||
if (!contributors?.length) return null;
|
||||
|
||||
@@ -22,7 +22,6 @@ import Link from '@docusaurus/Link';
|
||||
import { Card, Carousel, Flex } from 'antd';
|
||||
import styled from '@emotion/styled';
|
||||
import GitHubButton from 'react-github-btn';
|
||||
import { load } from 'js-yaml';
|
||||
import { mq } from '../utils';
|
||||
import SectionHeader from '../components/SectionHeader';
|
||||
import databaseData from '../data/databases.json';
|
||||
@@ -61,7 +60,7 @@ interface DataSetType {
|
||||
categories: Record<string, Organization[]>;
|
||||
}
|
||||
|
||||
const typedDataSet = load(DataSet) as DataSetType;
|
||||
const typedDataSet = DataSet as DataSetType;
|
||||
|
||||
// Extract all organizations with logos for the carousel
|
||||
const companiesWithLogos = Object.values(typedDataSet.categories)
|
||||
|
||||
@@ -260,45 +260,10 @@ a > span > svg {
|
||||
|
||||
.footer {
|
||||
position: relative;
|
||||
padding-top: 130px;
|
||||
padding-top: 90px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.footer__social-links {
|
||||
background-color: #173036;
|
||||
position: absolute;
|
||||
top: 52px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.footer__social-links a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
transition: opacity 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.footer__social-links a:hover {
|
||||
opacity: 0.8;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.footer__social-links img {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
/* The brand SVGs ship in their native colors (e.g. Slack's dark aubergine,
|
||||
X's near-black), which disappear on the dark footer. Render them all as
|
||||
uniform white silhouettes. The icons are single-path glyphs whose
|
||||
counters (the LinkedIn "in", Slack gaps, Reddit face) are transparent
|
||||
cut-outs, so they stay legible against the footer background. */
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.footer__ci-services {
|
||||
background-color: #0d3e49;
|
||||
color: #e1e1e1;
|
||||
@@ -344,21 +309,6 @@ a > span > svg {
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 996px) {
|
||||
.footer {
|
||||
padding-top: 120px;
|
||||
}
|
||||
|
||||
.footer__social-links {
|
||||
top: 44px;
|
||||
gap: 20px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.footer__social-links img {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.footer__ci-services {
|
||||
gap: 12px;
|
||||
padding: 10px 16px;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user