Compare commits

..

11 Commits

Author SHA1 Message Date
Maxime Beauchemin
9a82c2015c trying further 2025-08-14 13:17:43 -07:00
Maxime Beauchemin
489d8a4b83 refactor: Consolidate applyDatasetChartDefaults tests into hydrateExplore.test.ts
Move all 15 test cases for applyDatasetChartDefaults from separate test file
into the main hydrateExplore.test.ts file where the function is defined.

This follows standard practice of keeping tests close to their source code
for better maintainability and discoverability.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 10:43:19 -07:00
Maxime Beauchemin
cb993639ce feat(explore): Add temporal column support and comprehensive test coverage for chart defaults
## Changes
- Added default_temporal_column property to backend model for X-axis defaults
- Added temporal column selector in Dataset Editor UI (datetime columns only)
- Integrated temporal column application in chart creation flow
- Added comprehensive test coverage for all new functionality

## Test Coverage Added

### Backend Tests (7 new tests)
- Properties correctly parse JSON from extra field
- Invalid/null JSON handling
- set_default_chart_metadata functionality
- Preservation of other extra field data

### Frontend Tests (15+ new tests)
- applyDatasetChartDefaults function unit tests
- Validation of metrics, dimensions, and temporal columns
- Error handling for malformed data
- DatasourceEditor component tests
- Integration with existing hydrateExplore tests

## Test Results
 All backend tests passing (pytest)
 All frontend tests passing (Jest)
 Pre-commit checks passing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 10:43:19 -07:00
Maxime Beauchemin
031938edb8 refactor(explore): Extract chart defaults logic into testable function
- Extract applyDatasetChartDefaults function for better testability
- Add comprehensive test coverage for all edge cases
- Handle extra field as both string and object types
- Ensure malformed JSON doesn't break the application

This refactoring makes the chart defaults logic easier to test in isolation
and improves code maintainability.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 10:43:00 -07:00
Maxime Beauchemin
6e735d40a8 feat(datasets): Replace chart defaults with Explore controls
- Replace time range Select with DateFilterControl for rich time selection UI
- Make time grains database-aware, fetching options from database engine
- Add AdhocFilterControl for setting default filters on datasets
- Update hydrateExplore to apply default filters for new charts

These changes give users the same powerful controls they use in Explore
when setting chart defaults, ensuring consistency and familiarity.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 10:43:00 -07:00
Maxime Beauchemin
377bccd464 fix: Move Chart Defaults outside Fieldset to prevent onChange override
CRITICAL FIX: The Fieldset component was also overriding our onChange
handlers, preventing chart defaults from working.

Root cause: Fieldset uses recurseReactClone to inject its own onChange
handler that expects simple fields like item.fieldKey, but our chart
defaults are nested inside item.extra.default_chart_metadata.

Solution:
- Moved Chart Defaults section outside of Fieldset component
- Used Form.Item with Typography.Title for the section header
- This allows Field components to use our custom onChange handlers

Now the data flow works correctly:
Field onChange → onChartDefaultChange → parse/update extra JSON →
onDatasourcePropChange → state update → modal notification

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 10:43:00 -07:00
Maxime Beauchemin
8bdb41674c fix: Chart Defaults onChange handlers not being called
MAJOR BUG FIX: The Field component was overriding our manual onChange
handlers, preventing chart defaults from being saved.

Root cause: Field component uses cloneElement to override the control's
onChange prop with its own onControlChange handler.

Solution:
- Added onChartDefaultChange helper method to handle chart defaults logic
- Updated Default Metric field to use proper Field pattern:
  - Set value prop on Field (not control)
  - Set onChange prop on Field (not control)
  - Let Field component handle the control binding

This allows the Field component to properly call our chart defaults
update logic when users interact with the form controls.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 10:43:00 -07:00
Maxime Beauchemin
51b887901f debug: Add comprehensive logging for Chart Defaults data flow
Added debug logs to trace how chart defaults flow through the system:

DatasourceEditor.jsx:
- Log onDatasourcePropChange calls with attr and value
- Log updated datasource.extra after setState
- Log what gets sent to modal via onDatasourceChange

DatasourceModal.tsx:
- Log what data.extra is received in onDatasourceChange callback
- Log what currentDatasource.extra gets set to
- Log currentDatasource.extra right before save

This will help identify exactly where chart defaults data gets lost
in the DatasourceEditor → DatasourceModal → API save flow.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 10:43:00 -07:00
Maxime Beauchemin
46924ad89e refactor: Simplify Chart Defaults extra field parsing
Replaced complex IIFE pattern with a clean helper function for parsing
the datasource.extra field in Chart Defaults form controls.

Changes:
- Added parseExtra() helper function to handle string/object parsing
- Simplified all value props to use parseExtra(datasource.extra).default_chart_metadata?.field
- Streamlined onChange handlers to use the same helper consistently
- Improved code readability and maintainability
- Fixed issue with saved defaults not displaying when reopening DatasetEditor

The helper function handles all edge cases (null, string, object) and provides
safe fallbacks, making the Chart Defaults section more robust.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 10:43:00 -07:00
Maxime Beauchemin
1d3c186fec fix: DatasetEditor Chart Defaults not displaying saved values
Fixed an issue where the Chart Defaults section in the DatasetEditor
would not display previously saved values when reopening the editor.

The problem was that the component assumed `datasource.extra` would
always be a string that needed JSON parsing, but it could also be an
already-parsed object depending on the component's state.

Changes:
- Added proper type checking for `datasource.extra` (string vs object)
- Used IIFE with try-catch for safe value extraction in Select components
- Fixed onChange handlers to handle both string and object types
- Ensured saved chart defaults now properly display on editor reload

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 10:43:00 -07:00
Maxime Beauchemin
03ba5b6949 feat(dataset): Add chart creation defaults to improve UX
## Summary
Introduces dataset-level chart defaults that automatically pre-populate common settings when creating new charts, significantly improving the user experience by reducing repetitive configuration steps.

## Features Added

### Backend
- Added computed properties to `SqlaTable` model for reading/writing chart defaults from the `extra` JSON field
- Properties: `default_metric`, `default_dimension`, `default_time_grain`, `default_time_range`, `default_row_limit`
- Added `set_default_chart_metadata()` method for updating defaults

### Frontend UI
- Added new "Chart Defaults" section in Dataset Editor (Settings tab)
- Includes dropdowns for:
  - Default Metric (from available metrics)
  - Default Dimension (from groupable columns)
  - Default Time Grain (common intervals)
  - Default Time Range (common presets)
  - Default Row Limit (numeric input)

### Chart Creation Integration
- Modified `hydrateExplore` action to apply defaults when creating new charts
- Only applies to new charts (no existing slice_id)
- Validates that referenced metrics/columns exist before applying
- Falls back gracefully if defaults can't be applied

### Metadata Sync Protection
- Added `cleanupChartDefaults()` to handle column removal during metadata refresh
- Shows warning if default dimension is removed
- Preserves all other defaults safely

## Technical Details

### Storage
- Uses existing `extra` JSON column - no database migration needed
- Structure: `{"default_chart_metadata": {"default_metric": "count", ...}}`

### Best-Effort Resolution
- String-based references (not foreign keys) for flexibility
- Graceful degradation if referenced objects are deleted
- No cascading failures

### Validation
- Backend: Type hints and property methods ensure data consistency
- Frontend: Dropdowns only show valid options
- Chart creation: Validates existence before applying

## Benefits
- **Improved UX**: Users don't need to repeatedly select the same metric/dimension
- **Time Savings**: Especially helpful for datasets with many metrics/columns
- **Consistency**: Encourages use of primary metrics across charts
- **Flexibility**: Optional and can be overridden per chart

## Future Opportunities
- SQL Lab → Explore flow integration
- Dashboard quick chart creation
- API exposure for external tools
- Viz-type specific defaults

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 10:42:59 -07:00
1362 changed files with 16504 additions and 77177 deletions

View File

@@ -1,5 +1,5 @@
# Keep this in sync with the base image in the main Dockerfile (ARG PY_VER)
FROM python:3.11.13-trixie AS base
FROM python:3.11.13-bookworm AS base
# Install system dependencies that Superset needs
# This layer will be cached across Codespace sessions

10
.github/CODEOWNERS vendored
View File

@@ -30,13 +30,3 @@
**/*.geojson @villebro @rusackas
/superset-frontend/plugins/legacy-plugin-chart-country-map/ @villebro @rusackas
# Notify PMC members of changes to extension-related files
/superset-core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset-extensions-cli/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset/core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset/extensions/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset-frontend/src/packages/superset-core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset-frontend/src/core/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje
/superset-frontend/src/extensions/ @michael-s-molina @villebro @geido @eschutho @rusackas @kgabryje

View File

@@ -1,27 +1,24 @@
name: Change Detector
description: Detects file changes for pull request and push events
name: 'Change Detector'
description: 'Detects file changes for pull request and push events'
inputs:
token:
description: GitHub token for authentication
description: 'GitHub token for authentication'
required: true
outputs:
python:
description: Whether Python-related files were changed
description: 'Whether Python-related files were changed'
value: ${{ steps.change-detector.outputs.python }}
frontend:
description: Whether frontend-related files were changed
description: 'Whether frontend-related files were changed'
value: ${{ steps.change-detector.outputs.frontend }}
docker:
description: Whether docker-related files were changed
description: 'Whether docker-related files were changed'
value: ${{ steps.change-detector.outputs.docker }}
docs:
description: Whether docs-related files were changed
description: 'Whether docs-related files were changed'
value: ${{ steps.change-detector.outputs.docs }}
superset-extensions-cli:
description: Whether superset-extensions-cli package-related files were changed
value: ${{ steps.change-detector.outputs.superset-extensions-cli }}
runs:
using: composite
using: 'composite'
steps:
- name: Detect file changes
id: change-detector

View File

@@ -182,76 +182,6 @@ cypress-run-all() {
kill $flaskProcessId
}
playwright-install() {
cd "$GITHUB_WORKSPACE/superset-frontend"
say "::group::Install Playwright browsers"
npx playwright install --with-deps chromium
# Create output directories for test results and debugging
mkdir -p playwright-results
mkdir -p test-results
say "::endgroup::"
}
playwright-run() {
local APP_ROOT=$1
# Start Flask from the project root (same as Cypress)
cd "$GITHUB_WORKSPACE"
local flasklog="${HOME}/flask-playwright.log"
local port=8081
PLAYWRIGHT_BASE_URL="http://localhost:${port}"
if [ -n "$APP_ROOT" ]; then
export SUPERSET_APP_ROOT=$APP_ROOT
PLAYWRIGHT_BASE_URL=${PLAYWRIGHT_BASE_URL}${APP_ROOT}/
fi
export PLAYWRIGHT_BASE_URL
nohup flask run --no-debugger -p $port >"$flasklog" 2>&1 </dev/null &
local flaskProcessId=$!
# Ensure cleanup on exit
trap "kill $flaskProcessId 2>/dev/null || true" EXIT
# Wait for server to be ready with health check
local timeout=60
say "Waiting for Flask server to start on port $port..."
while [ $timeout -gt 0 ]; do
if curl -f ${PLAYWRIGHT_BASE_URL}/health >/dev/null 2>&1; then
say "Flask server is ready"
break
fi
sleep 1
timeout=$((timeout - 1))
done
if [ $timeout -eq 0 ]; then
echo "::error::Flask server failed to start within 60 seconds"
echo "::group::Flask startup log"
cat "$flasklog"
echo "::endgroup::"
return 1
fi
# Change to frontend directory for Playwright execution
cd "$GITHUB_WORKSPACE/superset-frontend"
say "::group::Run Playwright tests"
echo "Running Playwright with baseURL: ${PLAYWRIGHT_BASE_URL}"
npx playwright test auth/login --reporter=github --output=playwright-results
local status=$?
say "::endgroup::"
# After job is done, print out Flask log for debugging
echo "::group::Flask log for Playwright run"
cat "$flasklog"
echo "::endgroup::"
# make sure the program exits
kill $flaskProcessId
return $status
}
eyes-storybook-dependencies() {
say "::group::install eyes-storyook dependencies"
sudo apt-get update -y && sudo apt-get -y install gconf-service ca-certificates libxshmfence-dev fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 libglib2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libnspr4 libnss3 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release xdg-utils libappindicator1

View File

@@ -32,7 +32,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: true
ref: master

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ jobs:
pull-requests: write
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Check and notify
uses: actions/github-script@v7
with:

View File

@@ -71,7 +71,7 @@ jobs:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 1

View File

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

View File

@@ -27,7 +27,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout Repository"
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: "Dependency Review"
uses: actions/dependency-review-action@v4
continue-on-error: true
@@ -53,7 +53,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: "Checkout Repository"
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Setup Python
uses: ./.github/actions/setup-backend/

View File

@@ -22,7 +22,7 @@ jobs:
steps:
- id: set_matrix
run: |
MATRIX_CONFIG=$(if [ "${{ github.event_name }}" == "pull_request" ]; then echo '["dev", "lean"]'; else echo '["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]'; fi)
MATRIX_CONFIG=$(if [ "${{ github.event_name }}" == "pull_request" ]; then echo '["dev", "lean"]'; else echo '["dev", "lean", "py310", "websocket", "dockerize", "py311"]'; fi)
echo "matrix_config=${MATRIX_CONFIG}" >> $GITHUB_OUTPUT
echo $GITHUB_OUTPUT
@@ -42,7 +42,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
@@ -102,7 +102,7 @@ jobs:
docker history $IMAGE_TAG
- name: docker-compose sanity check
if: (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker) && matrix.build_preset == 'dev'
if: (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker) && (matrix.build_preset == 'dev' || matrix.build_preset == 'lean')
shell: bash
run: |
export SUPERSET_BUILD_TARGET=${{ matrix.build_preset }}
@@ -117,7 +117,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Check for file changes

View File

@@ -28,7 +28,7 @@ jobs:
run:
working-directory: superset-embedded-sdk
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: './superset-embedded-sdk/.nvmrc'

View File

@@ -18,7 +18,7 @@ jobs:
run:
working-directory: superset-embedded-sdk
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: './superset-embedded-sdk/.nvmrc'

View File

@@ -1,10 +1,4 @@
name: Cleanup ephemeral envs (PR close) [DEPRECATED]
# ⚠️ DEPRECATION NOTICE ⚠️
# This workflow is deprecated and will be removed in a future version.
# The new Superset Showtime workflow handles cleanup automatically.
# See .github/workflows/showtime.yml and showtime-cleanup.yml for replacements.
# Migration guide: https://github.com/mistercrunch/superset-showtime
name: Cleanup ephemeral envs (PR close)
on:
pull_request_target:
@@ -77,5 +71,5 @@ jobs:
issue_number: ${{ github.event.number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: '⚠️ **DEPRECATED WORKFLOW** - Ephemeral environment shutdown and build artifacts deleted. Please migrate to the new Superset Showtime system for future PRs.'
body: 'Ephemeral environment shutdown and build artifacts deleted.'
})

View File

@@ -1,12 +1,4 @@
name: Ephemeral env workflow [DEPRECATED]
# ⚠️ DEPRECATION NOTICE ⚠️
# This workflow is deprecated and will be removed in a future version.
# Please use the new Superset Showtime workflow instead:
# - Use label "🎪 trigger-start" instead of "testenv-up"
# - Showtime provides better reliability and easier management
# - See .github/workflows/showtime.yml for the replacement
# - Migration guide: https://github.com/mistercrunch/superset-showtime
name: Ephemeral env workflow
# Example manual trigger:
# gh workflow run ephemeral-env.yml --ref fix_ephemerals --field label_name="testenv-up" --field issue_number=666
@@ -134,11 +126,8 @@ jobs:
throw new Error("Issue number is not available.");
}
const body = `⚠️ **DEPRECATED WORKFLOW** ⚠️\n\n@${user} This workflow is deprecated! Please use the new **Superset Showtime** system instead:\n\n` +
`- Replace "testenv-up" label with "🎪 trigger-start"\n` +
`- Better reliability and easier management\n` +
`- See https://github.com/mistercrunch/superset-showtime for details\n\n` +
`Processing your ephemeral environment request [here](${workflowUrl}). Action: **${action}**.` +
const body = `@${user} Processing your ephemeral environment request [here](${workflowUrl}).` +
` Action: **${action}**.` +
` More information on [how to use or configure ephemeral environments]` +
`(https://superset.apache.org/docs/contributing/howtos/#github-ephemeral-environments)`;
@@ -160,7 +149,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ needs.ephemeral-env-label.outputs.sha }} : ${{steps.get-sha.outputs.sha}} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
ref: ${{ needs.ephemeral-env-label.outputs.sha }}
persist-credentials: false
@@ -220,7 +209,7 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
persist-credentials: false

View File

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

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout Repository
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ jobs:
python-version: ["current", "previous", "next"]
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive

View File

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

View File

@@ -26,7 +26,7 @@ jobs:
name: Bump version and publish package(s)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
# pulls all commits (needed for lerna / semantic release to correctly version)
fetch-depth: 0

View File

@@ -1,50 +0,0 @@
name: 🎪 Showtime Cleanup
# Scheduled cleanup of expired environments
on:
schedule:
- cron: '0 */6 * * *' # Every 6 hours
# Manual trigger for testing
workflow_dispatch:
inputs:
max_age_hours:
description: 'Maximum age in hours before cleanup'
required: false
default: '48'
type: string
# Common environment variables
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ vars.AWS_REGION || 'us-west-2' }}
GITHUB_ORG: ${{ github.repository_owner }}
GITHUB_REPO: ${{ github.event.repository.name }}
jobs:
cleanup-expired:
name: Clean up expired showtime environments
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Install Superset Showtime
run: pip install superset-showtime
- name: Cleanup expired environments
run: |
MAX_AGE="${{ github.event.inputs.max_age_hours || '48' }}"
# Validate max_age is numeric
if [[ ! "$MAX_AGE" =~ ^[0-9]+$ ]]; then
echo "❌ Invalid max_age_hours format: $MAX_AGE (must be numeric)"
exit 1
fi
echo "Cleaning up environments older than ${MAX_AGE}h"
python -m showtime cleanup --older-than "${MAX_AGE}h"

View File

@@ -1,179 +0,0 @@
name: 🎪 Superset Showtime
# Ultra-simple: just sync on any PR state change
on:
pull_request_target:
types: [labeled, unlabeled, synchronize, closed]
# Manual testing
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to sync'
required: true
type: number
sha:
description: 'Specific SHA to deploy (optional, defaults to latest)'
required: false
type: string
# Common environment variables for all jobs (non-sensitive only)
env:
AWS_REGION: us-west-2
GITHUB_ORG: ${{ github.repository_owner }}
GITHUB_REPO: ${{ github.event.repository.name }}
GITHUB_ACTOR: ${{ github.actor }}
jobs:
sync:
name: 🎪 Sync PR to desired state
runs-on: ubuntu-latest
timeout-minutes: 90
permissions:
contents: read
pull-requests: write
steps:
- name: Security Check - Authorize Maintainers Only
id: auth
uses: actions/github-script@v7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
script: |
const actor = context.actor;
console.log(`🔍 Checking authorization for ${actor}`);
// Early exit for workflow_dispatch - assume authorized since it's manually triggered
if (context.eventName === 'workflow_dispatch') {
console.log(`✅ Workflow dispatch event - assuming authorized for ${actor}`);
core.setOutput('authorized', 'true');
return;
}
const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: actor
});
console.log(`📊 Permission level for ${actor}: ${permission.permission}`);
const authorized = ['write', 'admin'].includes(permission.permission);
// If this is a synchronize event from unauthorized user, check if Showtime is active and set blocked label
if (!authorized && context.eventName === 'pull_request_target' && context.payload.action === 'synchronize') {
console.log(`🔒 Synchronize event detected - checking if Showtime is active`);
// Check if PR has any circus tent labels (Showtime is in use)
const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number
});
const hasCircusLabels = issue.labels.some(label => label.name.startsWith('🎪 '));
if (hasCircusLabels) {
console.log(`🎪 Circus labels found - setting blocked label to prevent auto-deployment`);
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: ['🎪 🔒 showtime-blocked']
});
console.log(`✅ Blocked label set - Showtime will detect and skip operations`);
} else {
console.log(` No circus labels found - Showtime not in use, skipping block`);
}
}
if (!authorized) {
console.log(`🚨 Unauthorized user ${actor} - skipping all operations`);
core.setOutput('authorized', 'false');
return;
}
console.log(`✅ Authorized maintainer: ${actor}`);
core.setOutput('authorized', 'true');
- name: Install Superset Showtime
if: steps.auth.outputs.authorized == 'true'
run: |
echo "::notice::Maintainer ${{ github.actor }} triggered deploy for PR ${{ github.event.pull_request.number || github.event.inputs.pr_number }}"
pip install --upgrade superset-showtime
showtime version
- name: Check what actions are needed
if: steps.auth.outputs.authorized == 'true'
id: check
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Bulletproof PR number extraction
if [[ -n "${{ github.event.pull_request.number }}" ]]; then
PR_NUM="${{ github.event.pull_request.number }}"
elif [[ -n "${{ github.event.inputs.pr_number }}" ]]; then
PR_NUM="${{ github.event.inputs.pr_number }}"
else
echo "❌ No PR number found in event or inputs"
exit 1
fi
echo "Using PR number: $PR_NUM"
# Run sync check-only with optional SHA override
if [[ -n "${{ github.event.inputs.sha }}" ]]; then
OUTPUT=$(python -m showtime sync $PR_NUM --check-only --sha "${{ github.event.inputs.sha }}")
else
OUTPUT=$(python -m showtime sync $PR_NUM --check-only)
fi
echo "$OUTPUT"
# Extract the outputs we need for conditional steps
BUILD=$(echo "$OUTPUT" | grep "build_needed=" | cut -d'=' -f2)
SYNC=$(echo "$OUTPUT" | grep "sync_needed=" | cut -d'=' -f2)
PR_NUM_OUT=$(echo "$OUTPUT" | grep "pr_number=" | cut -d'=' -f2)
TARGET_SHA=$(echo "$OUTPUT" | grep "target_sha=" | cut -d'=' -f2)
echo "build_needed=$BUILD" >> $GITHUB_OUTPUT
echo "sync_needed=$SYNC" >> $GITHUB_OUTPUT
echo "pr_number=$PR_NUM_OUT" >> $GITHUB_OUTPUT
echo "target_sha=$TARGET_SHA" >> $GITHUB_OUTPUT
- name: Checkout PR code (only if build needed)
if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.build_needed == 'true'
uses: actions/checkout@v5
with:
ref: ${{ steps.check.outputs.target_sha }}
persist-credentials: false
- name: Setup Docker Environment (only if build needed)
if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.build_needed == 'true'
uses: ./.github/actions/setup-docker
with:
dockerhub-user: ${{ secrets.DOCKERHUB_USER }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
build: "true"
install-docker-compose: "false"
- name: Execute sync (handles everything)
if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.sync_needed == 'true'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
PR_NUM="${{ steps.check.outputs.pr_number }}"
TARGET_SHA="${{ steps.check.outputs.target_sha }}"
if [[ -n "$TARGET_SHA" ]]; then
python -m showtime sync $PR_NUM --sha "$TARGET_SHA"
else
python -m showtime sync $PR_NUM
fi

View File

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

View File

@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive

View File

@@ -1,4 +1,4 @@
name: Superset App CLI tests
name: Superset CLI tests
on:
push:
@@ -37,7 +37,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive

View File

@@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive
@@ -41,7 +41,7 @@ jobs:
node-version-file: './docs/.nvmrc'
- name: Setup Python
uses: ./.github/actions/setup-backend/
- uses: actions/setup-java@v5
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '21'

View File

@@ -18,7 +18,7 @@ jobs:
name: Link Checking
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
# Do not bump this linkinator-action version without opening
# an ASF Infra ticket to allow the new version first!
- uses: JustinBeckwith/linkinator-action@v1.11.0
@@ -56,7 +56,7 @@ jobs:
working-directory: docs
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive

View File

@@ -69,21 +69,21 @@ jobs:
# Conditional checkout based on context
- name: Checkout for push or pull_request event
if: github.event_name == 'push' || github.event_name == 'pull_request'
uses: actions/checkout@v5
uses: actions/checkout@v4
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@v5
uses: actions/checkout@v4
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@v5
uses: actions/checkout@v4
with:
persist-credentials: false
ref: refs/pull/${{ github.event.inputs.pr_id }}/merge

View File

@@ -1,64 +0,0 @@
name: Superset Extensions CLI Package Tests
on:
push:
branches:
- "master"
- "[0-9].[0-9]*"
pull_request:
types: [synchronize, opened, reopened, ready_for_review]
# cancel previous workflow jobs for PRs
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
test-superset-extensions-cli-package:
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: ["previous", "current", "next"]
defaults:
run:
working-directory: superset-extensions-cli
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
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
if: steps.check.outputs.superset-extensions-cli
uses: ./.github/actions/setup-backend/
with:
python-version: ${{ matrix.python-version }}
requirements-type: dev
- name: Run pytest with coverage
if: steps.check.outputs.superset-extensions-cli
run: |
pytest --cov=superset_extensions_cli --cov-report=xml --cov-report=term-missing --cov-report=html -v --tb=short
- name: Upload coverage reports to Codecov
if: steps.check.outputs.superset-extensions-cli
uses: codecov/codecov-action@v5
with:
file: ./coverage.xml
flags: superset-extensions-cli
name: superset-extensions-cli-coverage
fail_ci_if_error: false
- name: Upload HTML coverage report
if: steps.check.outputs.superset-extensions-cli
uses: actions/upload-artifact@v4
with:
name: superset-extensions-cli-coverage-html
path: htmlcov/

View File

@@ -23,7 +23,7 @@ jobs:
should-run: ${{ steps.check.outputs.frontend }}
steps:
- name: Checkout Code
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
@@ -47,7 +47,7 @@ jobs:
git show -s --format=raw HEAD
docker buildx build \
-t $TAG \
--cache-from=type=registry,ref=apache/superset-cache:3.10-slim-trixie \
--cache-from=type=registry,ref=apache/superset-cache:3.10-slim-bookworm \
--target superset-node-ci \
.
@@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@v5
uses: actions/download-artifact@v4
with:
name: docker-image
@@ -101,7 +101,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Coverage Artifacts
uses: actions/download-artifact@v5
uses: actions/download-artifact@v4
with:
pattern: coverage-artifacts-*
path: coverage/
@@ -127,7 +127,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@v5
uses: actions/download-artifact@v4
with:
name: docker-image
@@ -143,7 +143,7 @@ jobs:
- name: tsc
run: |
docker run --rm $TAG bash -c \
"npm run plugins:build && npm run type"
"npm run type"
validate-frontend:
needs: frontend-build
@@ -151,7 +151,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Download Docker Image Artifact
uses: actions/download-artifact@v5
uses: actions/download-artifact@v4
with:
name: docker-image

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive

View File

@@ -29,7 +29,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.ref_name }}
persist-credentials: true

View File

@@ -1,141 +0,0 @@
name: Playwright E2E Tests
on:
push:
branches:
- "master"
- "[0-9].[0-9]*"
pull_request:
types: [synchronize, opened, reopened, ready_for_review]
workflow_dispatch:
inputs:
ref:
description: 'The branch or tag to checkout'
required: false
default: ''
pr_id:
description: 'The pull request ID to checkout'
required: false
default: ''
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
playwright-tests:
runs-on: ubuntu-22.04
# Allow workflow to succeed even if tests fail during shadow mode
continue-on-error: true
permissions:
contents: read
pull-requests: read
strategy:
fail-fast: false
matrix:
browser: ["chromium"]
app_root: ["", "/app/prefix"]
env:
SUPERSET_ENV: development
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
SUPERSET__SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://superset:superset@127.0.0.1:15432/superset
PYTHONPATH: ${{ github.workspace }}
REDIS_PORT: 16379
GITHUB_TOKEN: ${{ github.token }}
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: superset
POSTGRES_PASSWORD: superset
ports:
- 15432:5432
redis:
image: redis:7-alpine
ports:
- 16379:6379
steps:
# -------------------------------------------------------
# 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@v5
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@v5
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@v5
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
if: steps.check.outputs.python || steps.check.outputs.frontend
uses: actions/setup-node@v4
with:
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 Playwright
if: steps.check.outputs.python || steps.check.outputs.frontend
uses: ./.github/actions/cached-dependencies
with:
run: playwright-install
- name: Run Playwright
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 }}
- name: Set safe app root
if: failure()
id: set-safe-app-root
run: |
APP_ROOT="${{ matrix.app_root }}"
SAFE_APP_ROOT=${APP_ROOT//\//_}
echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT
- name: Upload Playwright Artifacts
uses: actions/upload-artifact@v4
if: failure()
with:
path: |
${{ 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 }}

View File

@@ -41,7 +41,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive
@@ -99,7 +99,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive
@@ -152,7 +152,7 @@ jobs:
- 16379:6379
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive

View File

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

View File

@@ -24,7 +24,7 @@ jobs:
PYTHONPATH: ${{ github.workspace }}
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive

View File

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

View File

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

View File

@@ -38,7 +38,7 @@ jobs:
});
- name: "Checkout ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false

View File

@@ -42,12 +42,12 @@ jobs:
runs-on: ubuntu-24.04
strategy:
matrix:
build_preset: ["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]
build_preset: ["dev", "lean", "py310", "websocket", "dockerize", "py311"]
fail-fast: false
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -107,7 +107,7 @@ jobs:
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0

View File

@@ -27,7 +27,7 @@ jobs:
name: Generate Reports
steps:
- name: Checkout Repository
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: Welcome Message
uses: actions/first-interaction@v3
uses: actions/first-interaction@v2
continue-on-error: true
with:
repo-token: ${{ github.token }}

4
.gitignore vendored
View File

@@ -43,7 +43,7 @@ _modules
_static
build
app.db
*.egg-info/
apache_superset.egg-info/
changelog.sh
dist
dump.rdb
@@ -130,7 +130,7 @@ superset/static/stats/statistics.html
# LLM-related
CLAUDE.local.md
PROJECT.md
.aider*
.claude_rc*
.env.local
PROJECT.md

View File

@@ -23,9 +23,7 @@ repos:
rev: v1.15.0
hooks:
- id: mypy
name: mypy (main)
args: [--check-untyped-defs]
exclude: ^superset-extensions-cli/
additional_dependencies: [
types-simplejson,
types-python-dateutil,
@@ -40,10 +38,6 @@ repos:
types-paramiko,
types-Markdown,
]
- id: mypy
name: mypy (superset-extensions-cli)
args: [--check-untyped-defs]
files: ^superset-extensions-cli/
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
@@ -60,25 +54,25 @@ repos:
args: ["--markdown-linebreak-ext=md"]
- repo: local
hooks:
- id: eslint-frontend
name: eslint (frontend)
entry: ./scripts/eslint.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=$(echo "$@" | sed "s|docs/||g") && yarn eslint --fix --ext .js,.jsx,.ts,.tsx --quiet $FILES'
language: system
pass_filenames: true
files: ^docs/.*\.(js|jsx|ts|tsx)$
- id: type-checking-frontend
name: Type-Checking (Frontend)
entry: ./scripts/check-type.js package=superset-frontend excludeDeclarationDir=cypress-base
language: system
files: ^superset-frontend\/.*\.(js|jsx|ts|tsx)$
exclude: ^superset-frontend/cypress-base\/
require_serial: true
- id: eslint-frontend
name: eslint (frontend)
entry: ./scripts/eslint.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=$(echo "$@" | sed "s|docs/||g") && yarn eslint --fix --ext .js,.jsx,.ts,.tsx --quiet $FILES'
language: system
pass_filenames: true
files: ^docs/.*\.(js|jsx|ts|tsx)$
- id: type-checking-frontend
name: Type-Checking (Frontend)
entry: ./scripts/check-type.js package=superset-frontend excludeDeclarationDir=cypress-base
language: system
files: ^superset-frontend\/.*\.(js|jsx|ts|tsx)$
exclude: ^superset-frontend/cypress-base\/
require_serial: true
# blacklist unsafe functions like make_url (see #19526)
- repo: https://github.com/skorokithakis/blacklist-pre-commit-hook
rev: e2f070289d8eddcaec0b580d3bde29437e7c8221
@@ -100,21 +94,21 @@ repos:
args: [--fix]
- repo: local
hooks:
- id: pylint
name: pylint with custom Superset plugins
entry: bash
language: system
types: [python]
exclude: ^(tests/|superset/migrations/|scripts/|RELEASING/|docker/)
args:
- -c
- |
TARGET_BRANCH=${GITHUB_BASE_REF:-master}
git fetch origin "$TARGET_BRANCH"
BASE=$(git merge-base origin/"$TARGET_BRANCH" HEAD)
files=$(git diff --name-only --diff-filter=ACM "$BASE"..HEAD | grep '^superset/.*\.py$' || true)
if [ -n "$files" ]; then
pylint --rcfile=.pylintrc --load-plugins=superset.extensions.pylint --reports=no $files
else
echo "No Python files to lint."
fi
- id: pylint
name: pylint with custom Superset plugins
entry: bash
language: system
types: [python]
exclude: ^(tests/|superset/migrations/|scripts/|RELEASING/|docker/)
args:
- -c
- |
TARGET_BRANCH=${GITHUB_BASE_REF:-master}
git fetch origin "$TARGET_BRANCH"
BASE=$(git merge-base origin/"$TARGET_BRANCH" HEAD)
files=$(git diff --name-only --diff-filter=ACM "$BASE"..HEAD | grep '^superset/.*\.py$' || true)
if [ -n "$files" ]; then
pylint --rcfile=.pylintrc --load-plugins=superset.extensions.pylint --reports=no $files
else
echo "No Python files to lint."
fi

View File

@@ -32,8 +32,6 @@ apache_superset.egg-info
# json and csv in general cannot have comments
.*json
.*csv
# jinja templates often need to be as-is
.*j2
# Generated doc files
env/*
docs/.htaccess*
@@ -73,7 +71,6 @@ ibm-db2.svg
postgresql.svg
snowflake.svg
ydb.svg
loading.svg
# docs-related
erd.puml

View File

@@ -44,8 +44,4 @@ under the License.
- [4.0.1](./CHANGELOG/4.0.1.md)
- [4.0.2](./CHANGELOG/4.0.2.md)
- [4.1.0](./CHANGELOG/4.1.0.md)
- [4.1.1](./CHANGELOG/4.1.1.md)
- [4.1.2](./CHANGELOG/4.1.2.md)
- [4.1.3](./CHANGELOG/4.1.3.md)
- [4.1.4](./CHANGELOG/4.1.4.md)
- [5.0.0](./CHANGELOG/5.0.0.md)

View File

@@ -1,33 +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.
-->
## Change Log
### 4.1.4 (Thu Jul 24 08:30:04 2025 -0300)
**Database Migrations**
**Features**
**Fixes**
- [#34289](https://github.com/apache/superset/pull/34289) fix: Saved queries list break if one query can't be parsed (@michael-s-molina)
- [#33059](https://github.com/apache/superset/pull/33059) fix: Adds missing __init__ file to commands/logs (@michael-s-molina)
**Others**
- [#32236](https://github.com/apache/superset/pull/32236) chore(deps): bump cryptography from 43.0.3 to 44.0.1 (@dependabot[bot])

View File

@@ -18,7 +18,7 @@
######################################################################
# Node stage to deal with static asset construction
######################################################################
ARG PY_VER=3.11.13-slim-trixie
ARG PY_VER=3.11.13-slim-bookworm
# If BUILDPLATFORM is null, set it to 'amd64' (or leave as is otherwise).
ARG BUILDPLATFORM=${BUILDPLATFORM:-amd64}
@@ -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:20-trixie-slim AS superset-node-ci
FROM --platform=${BUILDPLATFORM} node:20-bookworm-slim AS superset-node-ci
ARG BUILD_TRANSLATIONS
ENV BUILD_TRANSLATIONS=${BUILD_TRANSLATIONS}
ARG DEV_MODE="false" # Skip frontend build in dev mode
@@ -64,7 +64,7 @@ RUN --mount=type=bind,source=./superset-frontend/package.json,target=./package.j
--mount=type=bind,source=./superset-frontend/package-lock.json,target=./package-lock.json \
--mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/root/.npm \
if [ "${DEV_MODE}" = "false" ]; then \
if [ "$DEV_MODE" = "false" ]; then \
npm ci; \
else \
echo "Skipping 'npm ci' in dev mode"; \
@@ -80,7 +80,7 @@ FROM superset-node-ci AS superset-node
# Build the frontend if not in dev mode
RUN --mount=type=cache,target=/root/.npm \
if [ "${DEV_MODE}" = "false" ]; then \
if [ "$DEV_MODE" = "false" ]; then \
echo "Running 'npm run ${BUILD_CMD}'"; \
npm run ${BUILD_CMD}; \
else \
@@ -91,10 +91,11 @@ RUN --mount=type=cache,target=/root/.npm \
COPY superset/translations /app/superset/translations
# Build translations if enabled, then cleanup localization files
RUN if [ "${BUILD_TRANSLATIONS}" = "true" ]; then \
RUN if [ "$BUILD_TRANSLATIONS" = "true" ]; then \
npm run build-translation; \
fi; \
rm -rf /app/superset/translations/*/*/*.[po,mo];
rm -rf /app/superset/translations/*/*/*.po; \
rm -rf /app/superset/translations/*/*/*.mo;
######################################################################
@@ -105,10 +106,10 @@ FROM python:${PY_VER} AS python-base
ARG SUPERSET_HOME="/app/superset_home"
ENV SUPERSET_HOME=${SUPERSET_HOME}
RUN mkdir -p ${SUPERSET_HOME}
RUN mkdir -p $SUPERSET_HOME
RUN useradd --user-group -d ${SUPERSET_HOME} -m --no-log-init --shell /bin/bash superset \
&& chmod -R 1777 ${SUPERSET_HOME} \
&& chown -R superset:superset ${SUPERSET_HOME}
&& chmod -R 1777 $SUPERSET_HOME \
&& chown -R superset:superset $SUPERSET_HOME
# Some bash scripts needed throughout the layers
COPY --chmod=755 docker/*.sh /app/docker/
@@ -133,19 +134,17 @@ RUN --mount=type=cache,target=/root/.cache/uv \
. /app/.venv/bin/activate && /app/docker/pip-install.sh --requires-build-essential -r requirements/translations.txt
COPY superset/translations/ /app/translations_mo/
RUN if [ "${BUILD_TRANSLATIONS}" = "true" ]; then \
RUN if [ "$BUILD_TRANSLATIONS" = "true" ]; then \
pybabel compile -d /app/translations_mo | true; \
fi; \
rm -f /app/translations_mo/*/*/*.[po,json]
rm -f /app/translations_mo/*/*/*.po; \
rm -f /app/translations_mo/*/*/*.json;
######################################################################
# Python APP common layer
######################################################################
FROM python-base AS python-common
# Build arg to pre-populate examples DuckDB file
ARG LOAD_EXAMPLES_DUCKDB="false"
ENV SUPERSET_HOME="/app/superset_home" \
HOME="/app/superset_home" \
SUPERSET_ENV="production" \
@@ -171,11 +170,11 @@ RUN mkdir -p \
ARG INCLUDE_CHROMIUM="false"
ARG INCLUDE_FIREFOX="false"
RUN --mount=type=cache,target=${SUPERSET_HOME}/.cache/uv \
if [ "${INCLUDE_CHROMIUM}" = "true" ] || [ "${INCLUDE_FIREFOX}" = "true" ]; then \
if [ "$INCLUDE_CHROMIUM" = "true" ] || [ "$INCLUDE_FIREFOX" = "true" ]; then \
uv pip install playwright && \
playwright install-deps && \
if [ "${INCLUDE_CHROMIUM}" = "true" ]; then playwright install chromium; fi && \
if [ "${INCLUDE_FIREFOX}" = "true" ]; then playwright install firefox; fi; \
if [ "$INCLUDE_CHROMIUM" = "true" ]; then playwright install chromium; fi && \
if [ "$INCLUDE_FIREFOX" = "true" ]; then playwright install firefox; fi; \
else \
echo "Skipping browser installation"; \
fi
@@ -197,18 +196,6 @@ RUN /app/docker/apt-install.sh \
libecpg-dev \
libldap2-dev
# Pre-load examples DuckDB file if requested
RUN if [ "$LOAD_EXAMPLES_DUCKDB" = "true" ]; then \
mkdir -p /app/data && \
echo "Downloading pre-built examples.duckdb..." && \
curl -L -o /app/data/examples.duckdb \
"https://raw.githubusercontent.com/apache-superset/examples-data/master/examples.duckdb" && \
chown -R superset:superset /app/data; \
else \
mkdir -p /app/data && \
chown -R superset:superset /app/data; \
fi
# Copy compiled things from previous stages
COPY --from=superset-node /app/superset/static/assets superset/static/assets
@@ -232,10 +219,6 @@ FROM python-common AS lean
# Install Python dependencies using docker/pip-install.sh
COPY requirements/base.txt requirements/
# Copy superset-core package needed for editable install in base.txt
COPY superset-core superset-core
RUN --mount=type=cache,target=${SUPERSET_HOME}/.cache/uv \
/app/docker/pip-install.sh --requires-build-essential -r requirements/base.txt
# Install the superset package
@@ -258,11 +241,6 @@ RUN /app/docker/apt-install.sh \
# Copy development requirements and install them
COPY requirements/*.txt requirements/
# Copy local packages needed for editable installs in development.txt
COPY superset-core superset-core
COPY superset-extensions-cli superset-extensions-cli
# Install Python dependencies using docker/pip-install.sh
RUN --mount=type=cache,target=${SUPERSET_HOME}/.cache/uv \
/app/docker/pip-install.sh --requires-build-essential -r requirements/development.txt
@@ -280,15 +258,6 @@ USER superset
######################################################################
FROM lean AS ci
USER root
RUN uv pip install .[postgres,duckdb]
USER superset
CMD ["/app/docker/entrypoints/docker-ci.sh"]
######################################################################
# Showtime image - lean + DuckDB for examples database
######################################################################
FROM lean AS showtime
USER root
RUN uv pip install .[duckdb]
RUN uv pip install .[postgres]
USER superset
CMD ["/app/docker/entrypoints/docker-ci.sh"]

35
LLMS.md
View File

@@ -9,17 +9,13 @@ Apache Superset is a data visualization platform with Flask/Python backend and R
### Frontend Modernization
- **NO `any` types** - Use proper TypeScript types
- **NO JavaScript files** - Convert to TypeScript (.ts/.tsx)
- **Use @superset-ui/core** - Don't import Ant Design directly, prefer Ant Design component wrappers from @superset-ui/core/components
- **Use antd theming tokens** - Prefer antd tokens over legacy theming tokens
- **Avoid custom css and styles** - Follow antd best practices and avoid styling and custom CSS whenever possible
- **Use @superset-ui/core** - Don't import Ant Design directly
### Testing Strategy Migration
- **Prefer unit tests** over integration tests
- **Prefer integration tests** over end-to-end tests
- **Use Playwright for E2E tests** - Migrating from Cypress
- **Cypress is deprecated** - Will be removed once migration is completed
- **Prefer integration tests** over Cypress end-to-end tests
- **Cypress is last resort** - Actively moving away from Cypress
- **Use Jest + React Testing Library** for component testing
- **Use `test()` instead of `describe()`** - Follow [avoid nesting when testing](https://kentcdodds.com/blog/avoid-nesting-when-youre-testing) principles
### Backend Type Safety
- **Add type hints** - All new Python code needs proper typing
@@ -108,18 +104,6 @@ superset/
npm run test # All tests
npm run test -- filename.test.tsx # Single file
# E2E Tests (Playwright - NEW)
npm run playwright:test # All Playwright tests
npm run playwright:ui # Interactive UI mode
npm run playwright:headed # See browser during tests
npx playwright test tests/auth/login.spec.ts # Single file
npm run playwright:debug tests/auth/login.spec.ts # Debug specific file
# E2E Tests (Cypress - DEPRECATED)
cd superset-frontend/cypress-base
npm run cypress-run-chrome # All Cypress tests (headless)
npm run cypress-debug # Interactive Cypress UI
# Backend
pytest # All tests
pytest tests/unit_tests/specific_test.py # Single file
@@ -149,19 +133,6 @@ curl -f http://localhost:8088/health || echo "❌ Setup required - see https://s
- **Use negation operator**: `~Model.field` instead of `== False` to avoid ruff E712 errors
- **Example**: `~Model.is_active` instead of `Model.is_active == False`
## Pull Request Guidelines
**When creating pull requests:**
1. **Read the current PR template**: Always check `.github/PULL_REQUEST_TEMPLATE.md` for the latest format
2. **Use the template sections**: Include all sections from the template (SUMMARY, BEFORE/AFTER, TESTING INSTRUCTIONS, ADDITIONAL INFORMATION)
3. **Follow PR title conventions**: Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
- Format: `type(scope): description`
- Example: `fix(dashboard): load charts correctly`
- Types: `fix`, `feat`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`
**Important**: Always reference the actual template file at `.github/PULL_REQUEST_TEMPLATE.md` instead of using cached content, as the template may be updated over time.
## Pre-commit Validation
**Use pre-commit hooks for quality validation:**

View File

@@ -91,7 +91,7 @@ js-format:
cd superset-frontend; npm run prettier
flask-app:
flask run -p 8088 --reload --debugger
flask run -p 8088 --with-threads --reload --debugger
node-app:
cd superset-frontend; npm run dev-server

View File

@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
FROM python:3.10-slim-trixie
FROM python:3.10-slim-bookworm
RUN useradd --user-group --create-home --no-log-init --shell /bin/bash superset

View File

@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
FROM python:3.10-slim-trixie
FROM python:3.10-slim-bookworm
RUN useradd --user-group --create-home --no-log-init --shell /bin/bash superset

View File

@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
FROM python:3.10-slim-trixie
FROM python:3.10-slim-bookworm
ARG VERSION
RUN git clone --depth 1 --branch ${VERSION} https://github.com/apache/superset.git /superset

View File

@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
FROM python:3.10-slim-trixie
FROM python:3.10-slim-bookworm
RUN apt-get update -y
RUN apt-get install -y \

View File

@@ -469,10 +469,6 @@ an account first if you don't have one, and reference your username
while requesting access to push packages.
```bash
# Run this first to make sure you are uploading the right version.
# Pypi does not allow you to delete or retract once uplaoded.
twine check dist/*
twine upload dist/*
```
@@ -522,8 +518,6 @@ takes the version (ie `3.1.1`), the git reference (any SHA, tag or branch
reference), and whether to force the `latest` Docker tag on the
generated images.
**NOTE:** If the docker image isn't built, you'll need to run this [GH action](https://github.com/apache/superset/actions/workflows/tag-release.yml) where you provide it the tag sha.
### Npm Release
You might want to publish the latest @superset-ui release to npm

View File

@@ -23,10 +23,6 @@ This file documents any backwards-incompatible changes in Superset and
assists people when migrating to a new version.
## Next
- [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.
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
- [34782](https://github.com/apache/superset/pull/34782): Dataset exports now include the dataset ID in their file name (similar to charts and dashboards). If managing assets as code, make sure to rename existing dataset YAMLs to include the ID (and avoid duplicated files).
- [34536](https://github.com/apache/superset/pull/34536): The `ENVIRONMENT_TAG_CONFIG` color values have changed to support only Ant Design semantic colors. Update your `superset_config.py`:
- Change `"error.base"` to just `"error"` after this PR
- Change any hex color values to one of: `"success"`, `"processing"`, `"error"`, `"warning"`, `"default"`

View File

@@ -71,13 +71,12 @@ x-common-build: &common-build
context: .
target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean`
cache_from:
- apache/superset-cache:3.10-slim-trixie
- apache/superset-cache:3.10-slim-bookworm
args:
DEV_MODE: "true"
INCLUDE_CHROMIUM: ${INCLUDE_CHROMIUM:-false}
INCLUDE_FIREFOX: ${INCLUDE_FIREFOX:-false}
BUILD_TRANSLATIONS: ${BUILD_TRANSLATIONS:-false}
LOAD_EXAMPLES_DUCKDB: ${LOAD_EXAMPLES_DUCKDB:-true}
services:
db-light:
@@ -92,7 +91,9 @@ services:
- db_home_light:/var/lib/postgresql/data
- ./docker/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
environment:
# Override database name to avoid conflicts
POSTGRES_DB: superset_light
# Increase max connections for test runs
command: postgres -c max_connections=200
superset-light:
@@ -105,6 +106,7 @@ services:
<<: *common-build
command: ["/app/docker/docker-bootstrap.sh", "app"]
restart: unless-stopped
# No host port mapping - accessed via webpack dev server proxy
extra_hosts:
- "host.docker.internal:host-gateway"
user: *superset-user
@@ -113,13 +115,16 @@ services:
condition: service_completed_successfully
volumes: *superset-volumes
environment:
# Override DB connection for light service
DATABASE_HOST: db-light
DATABASE_DB: superset_light
POSTGRES_DB: superset_light
SUPERSET__SQLALCHEMY_EXAMPLES_URI: "duckdb:////app/data/examples.duckdb"
EXAMPLES_HOST: db-light
EXAMPLES_DB: superset_light
EXAMPLES_USER: superset
EXAMPLES_PASSWORD: superset
# Use light-specific config that disables Redis
SUPERSET_CONFIG_PATH: /app/docker/pythonpath_dev/superset_config_docker_light.py
GITHUB_HEAD_REF: ${GITHUB_HEAD_REF:-}
GITHUB_SHA: ${GITHUB_SHA:-}
superset-init-light:
build:
@@ -130,16 +135,21 @@ services:
required: true
- path: docker/.env-local # optional override
required: false
user: *superset-user
depends_on:
db-light:
condition: service_started
user: *superset-user
volumes: *superset-volumes
environment:
# Override DB connection for light service
DATABASE_HOST: db-light
DATABASE_DB: superset_light
POSTGRES_DB: superset_light
SUPERSET__SQLALCHEMY_EXAMPLES_URI: "duckdb:////app/data/examples.duckdb"
EXAMPLES_HOST: db-light
EXAMPLES_DB: superset_light
EXAMPLES_USER: superset
EXAMPLES_PASSWORD: superset
# Use light-specific config that disables Redis
SUPERSET_CONFIG_PATH: /app/docker/pythonpath_dev/superset_config_docker_light.py
healthcheck:
disable: true
@@ -162,11 +172,8 @@ services:
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
# configuring the dev-server to use the host.docker.internal to connect to the backend
superset: "http://superset-light:8088"
# Webpack dev server configuration
WEBPACK_DEVSERVER_HOST: "${WEBPACK_DEVSERVER_HOST:-0.0.0.0}"
WEBPACK_DEVSERVER_PORT: "${WEBPACK_DEVSERVER_PORT:-9000}"
ports:
- "${NODE_PORT:-9001}:9000" # Parameterized port, accessible on all interfaces
- "127.0.0.1:${NODE_PORT:-9001}:9000" # Parameterized port
command: ["/app/docker/docker-frontend.sh"]
env_file:
- path: docker/.env # default
@@ -192,12 +199,15 @@ services:
user: *superset-user
volumes: *superset-volumes
environment:
# Test-specific database configuration
DATABASE_HOST: db-light
DATABASE_DB: test
POSTGRES_DB: test
# Point to test database
SUPERSET__SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://superset:superset@db-light:5432/test
SUPERSET__SQLALCHEMY_EXAMPLES_URI: "duckdb:////app/data/examples.duckdb"
# Use the light test config that doesn't require Redis
SUPERSET_CONFIG: superset_test_config_light
# Python path includes test directory
PYTHONPATH: /app/pythonpath:/app/docker/pythonpath_dev:/app
volumes:

View File

@@ -33,7 +33,7 @@ x-common-build: &common-build
context: .
target: dev
cache_from:
- apache/superset-cache:3.10-slim-trixie
- apache/superset-cache:3.10-slim-bookworm
services:
redis:

View File

@@ -36,13 +36,12 @@ x-common-build: &common-build
context: .
target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean`
cache_from:
- apache/superset-cache:3.10-slim-trixie
- apache/superset-cache:3.10-slim-bookworm
args:
DEV_MODE: "true"
INCLUDE_CHROMIUM: ${INCLUDE_CHROMIUM:-false}
INCLUDE_FIREFOX: ${INCLUDE_FIREFOX:-false}
BUILD_TRANSLATIONS: ${BUILD_TRANSLATIONS:-false}
LOAD_EXAMPLES_DUCKDB: ${LOAD_EXAMPLES_DUCKDB:-true}
services:
nginx:
@@ -108,8 +107,6 @@ services:
superset-init:
condition: service_completed_successfully
volumes: *superset-volumes
environment:
SUPERSET__SQLALCHEMY_EXAMPLES_URI: "duckdb:////app/data/examples.duckdb"
superset-websocket:
container_name: superset_websocket
@@ -161,8 +158,6 @@ services:
condition: service_started
user: *superset-user
volumes: *superset-volumes
environment:
SUPERSET__SQLALCHEMY_EXAMPLES_URI: "duckdb:////app/data/examples.duckdb"
healthcheck:
disable: true

View File

@@ -18,7 +18,7 @@
set -euo pipefail
# Ensure this script is run as root
if [[ ${EUID} -ne 0 ]]; then
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root" >&2
exit 1
fi
@@ -42,7 +42,7 @@ echo -e "${GREEN}Installing packages: $@${RESET}"
apt-get install -yqq --no-install-recommends "$@"
echo -e "${GREEN}Autoremoving unnecessary packages...${RESET}"
apt-get autoremove -yqq --purge
apt-get autoremove -y
echo -e "${GREEN}Cleaning up package cache and metadata...${RESET}"
apt-get clean

View File

@@ -72,7 +72,7 @@ case "${1}" in
;;
app)
echo "Starting web app (using development server)..."
flask run -p $PORT --reload --debugger --without-threads --host=0.0.0.0
flask run -p $PORT --with-threads --reload --debugger --host=0.0.0.0
;;
app-gunicorn)
echo "Starting web app..."

View File

@@ -69,8 +69,6 @@ echo_step "3" "Complete" "Setting up roles and perms"
if [ "$SUPERSET_LOAD_EXAMPLES" = "yes" ]; then
# Load some data to play with
echo_step "4" "Starting" "Loading examples"
# If Cypress run which consumes superset_test_config load required data for tests
if [ "$CYPRESS_CONFIG" == "true" ]; then
superset load_examples --load-test-data

View File

@@ -26,7 +26,7 @@ gunicorn \
--workers ${SERVER_WORKER_AMOUNT:-1} \
--worker-class ${SERVER_WORKER_CLASS:-gthread} \
--threads ${SERVER_THREADS_AMOUNT:-20} \
--log-level "${GUNICORN_LOGLEVEL:-info}" \
--log-level "${GUNICORN_LOGLEVEL:info}" \
--timeout ${GUNICORN_TIMEOUT:-60} \
--keep-alive ${GUNICORN_KEEPALIVE:-2} \
--max-requests ${WORKER_MAX_REQUESTS:-0} \

View File

@@ -38,14 +38,14 @@ for arg in "$@"; do
done
# Install build-essential if required
if ${REQUIRES_BUILD_ESSENTIAL}; then
if $REQUIRES_BUILD_ESSENTIAL; then
echo "Installing build-essential for package builds..."
apt-get update -qq \
&& apt-get install -yqq --no-install-recommends build-essential
fi
# Choose whether to use pip cache
if ${USE_CACHE}; then
if $USE_CACHE; then
echo "Using pip cache..."
uv pip install "${ARGS[@]}"
else
@@ -54,7 +54,7 @@ else
fi
# Remove build-essential if it was installed
if ${REQUIRES_BUILD_ESSENTIAL}; then
if $REQUIRES_BUILD_ESSENTIAL; then
echo "Removing build-essential to keep the image lean..."
apt-get autoremove -yqq --purge build-essential \
&& apt-get clean \

View File

@@ -49,18 +49,12 @@ SQLALCHEMY_DATABASE_URI = (
f"{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_DB}"
)
# Use environment variable if set, otherwise construct from components
# This MUST take precedence over any other configuration
SQLALCHEMY_EXAMPLES_URI = os.getenv(
"SUPERSET__SQLALCHEMY_EXAMPLES_URI",
(
f"{DATABASE_DIALECT}://"
f"{EXAMPLES_USER}:{EXAMPLES_PASSWORD}@"
f"{EXAMPLES_HOST}:{EXAMPLES_PORT}/{EXAMPLES_DB}"
),
SQLALCHEMY_EXAMPLES_URI = (
f"{DATABASE_DIALECT}://"
f"{EXAMPLES_USER}:{EXAMPLES_PASSWORD}@"
f"{EXAMPLES_HOST}:{EXAMPLES_PORT}/{EXAMPLES_DB}"
)
REDIS_HOST = os.getenv("REDIS_HOST", "redis")
REDIS_PORT = os.getenv("REDIS_PORT", "6379")
REDIS_CELERY_DB = os.getenv("REDIS_CELERY_DB", "0")
@@ -138,7 +132,7 @@ try:
from superset_config_docker import * # noqa: F403
logger.info(
"Loaded your Docker configuration at [%s]", superset_config_docker.__file__
f"Loaded your Docker configuration at [{superset_config_docker.__file__}]"
)
except ImportError:
logger.info("Using default Docker config...")

View File

@@ -21,183 +21,3 @@ This is the public documentation site for Superset, built using
[Docusaurus 3](https://docusaurus.io/). See
[CONTRIBUTING.md](../CONTRIBUTING.md#documentation) for documentation on
contributing to documentation.
## Version Management
The Superset documentation site uses Docusaurus versioning with three independent versioned sections:
- **Main Documentation** (`/docs/`) - Core Superset documentation
- **Developer Portal** (`/developer_portal/`) - Developer guides and tutorials
- **Component Playground** (`/components/`) - Interactive component examples (currently disabled)
Each section maintains its own version history and can be versioned independently.
### Creating a New Version
To create a new version for any section, use the Docusaurus version command with the appropriate plugin ID or use our automated scripts:
#### Using Automated Scripts (Required)
**⚠️ Important:** Always use these custom commands instead of the native Docusaurus commands. These scripts ensure that both the Docusaurus versioning system AND the `versions-config.json` file are updated correctly.
```bash
# Main Documentation
yarn version:add:docs 1.2.0
# Developer Portal
yarn version:add:developer_portal 1.2.0
# Component Playground (when enabled)
yarn version:add:components 1.2.0
```
**Do NOT use** the native Docusaurus commands directly (`yarn docusaurus docs:version`), as they will:
- ❌ Create version files but NOT update `versions-config.json`
- ❌ Cause versions to not appear in dropdown menus
- ❌ Require manual fixes to synchronize the configuration
### Managing Versions
#### With Automated Scripts
The automated scripts handle all configuration updates automatically. No manual editing required!
#### Manual Configuration
If creating versions manually, you'll need to:
1. **Update `versions-config.json`** (or `docusaurus.config.ts` if not using dynamic config):
- Add version to `onlyIncludeVersions` array
- Add version metadata to `versions` object
- Update `lastVersion` if needed
2. **Files Created by Versioning**:
When a new version is created, Docusaurus generates:
- **Versioned docs folder**: `[section]_versioned_docs/version-X.X.X/`
- **Versioned sidebars**: `[section]_versioned_sidebars/version-X.X.X-sidebars.json`
- **Versions list**: `[section]_versions.json`
Note: For main docs, the prefix is omitted (e.g., `versioned_docs/` instead of `docs_versioned_docs/`)
3. **Important**: After adding a version, restart the development server to see changes:
```bash
yarn stop
yarn start
```
### Removing a Version
#### Using Automated Scripts (Recommended)
```bash
# Main Documentation
yarn version:remove:docs 1.0.0
# Developer Portal
yarn version:remove:developer_portal 1.0.0
# Component Playground
yarn version:remove:components 1.0.0
```
#### Manual Removal
To manually remove a version:
1. **Delete the version folder** from the appropriate location:
- Main docs: `versioned_docs/version-X.X.X/` (no prefix for main)
- Developer Portal: `developer_portal_versioned_docs/version-X.X.X/`
- Components: `components_versioned_docs/version-X.X.X/`
2. **Delete the version metadata file**:
- Main docs: `versioned_sidebars/version-X.X.X-sidebars.json` (no prefix)
- Developer Portal: `developer_portal_versioned_sidebars/version-X.X.X-sidebars.json`
- Components: `components_versioned_sidebars/version-X.X.X-sidebars.json`
3. **Update the versions list file**:
- Main docs: `versions.json`
- Developer Portal: `developer_portal_versions.json`
- Components: `components_versions.json`
4. **Update configuration**:
- If using dynamic config: Update `versions-config.json`
- If using static config: Update `docusaurus.config.ts`
5. **Restart the server** to see changes
### Version Configuration Examples
#### Main Documentation (default plugin)
```typescript
docs: {
includeCurrentVersion: true,
lastVersion: 'current', // Makes /docs/ show Next version
onlyIncludeVersions: ['current', '1.1.0', '1.0.0'],
versions: {
current: {
label: 'Next',
path: '', // Empty path for default routing
banner: 'unreleased',
},
'1.1.0': {
label: '1.1.0',
path: '1.1.0',
banner: 'none',
},
},
}
```
#### Developer Portal & Components (custom plugins)
```typescript
{
id: 'developer_portal',
path: 'developer_portal',
routeBasePath: 'developer_portal',
includeCurrentVersion: true,
lastVersion: '1.1.0', // Default version
onlyIncludeVersions: ['current', '1.1.0', '1.0.0'],
versions: {
current: {
label: 'Next',
path: 'next',
banner: 'unreleased',
},
'1.1.0': {
label: '1.1.0',
path: '1.1.0',
banner: 'none',
},
},
}
```
### Best Practices
1. **Version naming**: Use semantic versioning (e.g., 1.0.0, 1.1.0, 2.0.0)
2. **Version banners**: Use `'unreleased'` for development versions, `'none'` for stable releases
3. **Limit displayed versions**: Use `onlyIncludeVersions` to show only relevant versions
4. **Test locally**: Always test version changes locally before deploying
5. **Independent versioning**: Each section can have different version numbers and release cycles
### Troubleshooting
#### Version Not Showing After Creation
If you accidentally used `yarn docusaurus docs:version` instead of `yarn version:add`:
1. **Problem**: The version files were created but `versions-config.json` wasn't updated
2. **Solution**: Either:
- Revert the changes: `git restore versions.json && rm -rf versioned_docs/ versioned_sidebars/`
- Then use the correct command: `yarn version:add:docs <version>`
For other issues:
- **Restart the server**: Changes to version configuration require a server restart
- **Check config file**: Ensure `versions-config.json` includes the new version
- **Verify files exist**: Check that versioned docs folder was created
#### Broken Links in Versioned Documentation
When creating a new version, links in the documentation are preserved as-is. Common issues:
- **Cross-section links**: Links between sections (e.g., from developer_portal to docs) need to be version-aware
- **Absolute vs relative paths**: Use relative paths within the same section
- **Version-specific URLs**: Update hardcoded URLs to use version variables
To fix broken links:
1. Use `type: 'doc'` with `docId` for version-aware navigation in navbar
2. Use relative paths within the same documentation section
3. Test all versions after creation to identify broken links

View File

@@ -1,105 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
---
title: Bar Chart
sidebar_position: 1
---
# Bar Chart Component
The Bar Chart component is used to visualize categorical data with rectangular bars.
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `data` | `array` | `[]` | Array of data objects to visualize |
| `width` | `number` | `800` | Width of the chart in pixels |
| `height` | `number` | `600` | Height of the chart in pixels |
| `xField` | `string` | - | Field name for x-axis values |
| `yField` | `string` | - | Field name for y-axis values |
| `colorField` | `string` | - | Field name for color encoding |
| `colorScheme` | `string` | `'supersetColors'` | Color scheme to use |
| `showLegend` | `boolean` | `true` | Whether to show the legend |
| `showGrid` | `boolean` | `true` | Whether to show grid lines |
| `labelPosition` | `string` | `'top'` | Position of bar labels: 'top', 'middle', 'bottom' |
## Examples
### Basic Bar Chart
```jsx
import { BarChart } from '@superset-ui/chart-components';
const data = [
{ category: 'A', value: 10 },
{ category: 'B', value: 20 },
{ category: 'C', value: 15 },
{ category: 'D', value: 25 },
];
function Example() {
return (
<BarChart
data={data}
width={800}
height={400}
xField="category"
yField="value"
colorScheme="supersetColors"
/>
);
}
```
### Grouped Bar Chart
```jsx
import { BarChart } from '@superset-ui/chart-components';
const data = [
{ category: 'A', group: 'Group 1', value: 10 },
{ category: 'A', group: 'Group 2', value: 15 },
{ category: 'B', group: 'Group 1', value: 20 },
{ category: 'B', group: 'Group 2', value: 25 },
{ category: 'C', group: 'Group 1', value: 15 },
{ category: 'C', group: 'Group 2', value: 10 },
];
function Example() {
return (
<BarChart
data={data}
width={800}
height={400}
xField="category"
yField="value"
colorField="group"
colorScheme="supersetColors"
/>
);
}
```
## Best Practices
- Use bar charts when comparing quantities across categories
- Sort bars by value for better readability, unless there's a natural order to the categories
- Use consistent colors for the same categories across different charts
- Consider using horizontal bar charts when category labels are long

View File

@@ -1,59 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
---
title: Component Library
sidebar_position: 1
---
# Superset Component Library
Welcome to the Apache Superset Component Library documentation. This section provides comprehensive documentation for all the UI components, chart components, and layout components used in Superset.
## What is the Component Library?
The Component Library is a collection of reusable UI components that are used to build the Superset user interface. These components are designed to be consistent, accessible, and easy to use.
## Component Categories
The Component Library is organized into the following categories:
### UI Components
Basic UI components like buttons, inputs, dropdowns, and other form elements.
### Chart Components
Visualization components used to render different types of charts and graphs.
### Layout Components
Components used for page layout, such as containers, grids, and navigation elements.
## Versioning
The Component Library documentation follows its own versioning scheme, independent from the main Superset documentation. This allows us to update the component documentation as the components evolve, without affecting the main documentation.
## Getting Started
Browse the sidebar to explore the different components available in the library. Each component documentation includes:
- Component description and purpose
- Props and configuration options
- Usage examples
- Best practices

View File

@@ -1,113 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
---
title: Grid
sidebar_position: 1
---
# Grid Component
The Grid component provides a flexible layout system for arranging content in rows and columns.
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `gutter` | `number` or `[number, number]` | `0` | Grid spacing between items, can be a single number or [horizontal, vertical] |
| `columns` | `number` | `12` | Number of columns in the grid |
| `justify` | `string` | `'start'` | Horizontal alignment: 'start', 'center', 'end', 'space-between', 'space-around' |
| `align` | `string` | `'top'` | Vertical alignment: 'top', 'middle', 'bottom' |
| `wrap` | `boolean` | `true` | Whether to wrap items when they overflow |
### Row Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `gutter` | `number` or `[number, number]` | `0` | Spacing between items in the row |
| `justify` | `string` | `'start'` | Horizontal alignment for this row |
| `align` | `string` | `'top'` | Vertical alignment for this row |
### Col Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `span` | `number` | - | Number of columns the grid item spans |
| `offset` | `number` | `0` | Number of columns the grid item is offset |
| `xs`, `sm`, `md`, `lg`, `xl` | `number` or `object` | - | Responsive props for different screen sizes |
## Examples
### Basic Grid
```jsx
import { Grid, Row, Col } from '@superset-ui/core';
function Example() {
return (
<Grid>
<Row gutter={16}>
<Col span={8}>
<div>Column 1</div>
</Col>
<Col span={8}>
<div>Column 2</div>
</Col>
<Col span={8}>
<div>Column 3</div>
</Col>
</Row>
</Grid>
);
}
```
### Responsive Grid
```jsx
import { Grid, Row, Col } from '@superset-ui/core';
function Example() {
return (
<Grid>
<Row gutter={[16, 24]}>
<Col xs={24} sm={12} md={8} lg={6}>
<div>Responsive Column 1</div>
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<div>Responsive Column 2</div>
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<div>Responsive Column 3</div>
</Col>
<Col xs={24} sm={12} md={8} lg={6}>
<div>Responsive Column 4</div>
</Col>
</Row>
</Grid>
);
}
```
## Best Practices
- Use the Grid system for complex layouts that need to be responsive
- Specify column widths for different screen sizes to ensure proper responsive behavior
- Use gutters to create appropriate spacing between grid items
- Keep the grid structure consistent throughout your application
- Consider using the grid system for dashboard layouts to ensure consistent spacing and alignment

View File

@@ -1,35 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
---
title: Test
---
import { StoryExample } from '../src/components/StorybookWrapper';
# Test
This is a test using our custom StorybookWrapper component.
<StoryExample
component={() => (
<div style={{ padding: '10px', background: '#f0f0f0', borderRadius: '4px' }}>
This is a simple example component
</div>
)}
/>

View File

@@ -1,146 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
---
title: Button Component
sidebar_position: 1
---
import { StoryExample, StoryWithControls } from '../../src/components/StorybookWrapper';
import { Button } from '../../../superset-frontend/packages/superset-ui-core/src/components/Button';
# Button Component
The Button component is a fundamental UI element used throughout Superset for user interactions.
## Basic Usage
The default button with primary styling:
<StoryExample
component={() => (
<Button buttonStyle="primary" onClick={() => console.log('Clicked!')}>
Click Me
</Button>
)}
/>
## Interactive Example
<StoryWithControls
component={({ buttonStyle, buttonSize, label, disabled }) => (
<Button
buttonStyle={buttonStyle}
buttonSize={buttonSize}
disabled={disabled}
onClick={() => console.log('Clicked!')}
>
{label}
</Button>
)}
props={{
buttonStyle: 'primary',
buttonSize: 'default',
label: 'Click Me',
disabled: false
}}
controls={[
{
name: 'buttonStyle',
label: 'Button Style',
type: 'select',
options: ['primary', 'secondary', 'tertiary', 'success', 'warning', 'danger', 'default', 'link', 'dashed']
},
{
name: 'buttonSize',
label: 'Button Size',
type: 'select',
options: ['default', 'small', 'xsmall']
},
{
name: 'label',
label: 'Button Text',
type: 'text'
},
{
name: 'disabled',
label: 'Disabled',
type: 'boolean'
}
]}
/>
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `buttonStyle` | `'primary' \| 'secondary' \| 'tertiary' \| 'success' \| 'warning' \| 'danger' \| 'default' \| 'link' \| 'dashed'` | `'default'` | Button style |
| `buttonSize` | `'default' \| 'small' \| 'xsmall'` | `'default'` | Button size |
| `disabled` | `boolean` | `false` | Whether the button is disabled |
| `cta` | `boolean` | `false` | Whether the button is a call-to-action button |
| `tooltip` | `ReactNode` | - | Tooltip content |
| `placement` | `TooltipProps['placement']` | - | Tooltip placement |
| `onClick` | `function` | - | Callback when button is clicked |
| `href` | `string` | - | Turns button into an anchor link |
| `target` | `string` | - | Target attribute for anchor links |
## Usage
```jsx
import Button from 'src/components/Button';
function MyComponent() {
return (
<Button
buttonStyle="primary"
onClick={() => console.log('Button clicked')}
>
Click Me
</Button>
);
}
```
## Button Styles
Superset provides a variety of button styles for different purposes:
- **Primary**: Used for primary actions
- **Secondary**: Used for secondary actions
- **Tertiary**: Used for less important actions
- **Success**: Used for successful or confirming actions
- **Warning**: Used for actions that require caution
- **Danger**: Used for destructive actions
- **Link**: Used for navigation
- **Dashed**: Used for adding new items or features
## Button Sizes
Buttons come in three sizes:
- **Default**: Standard size for most use cases
- **Small**: Compact size for tight spaces
- **XSmall**: Extra small size for very limited spaces
## Best Practices
- Use primary buttons for the main action in a form or page
- Use secondary buttons for alternative actions
- Use danger buttons for destructive actions
- Limit the number of primary buttons on a page to avoid confusion
- Use consistent button styles throughout your application
- Add tooltips to buttons when their purpose might not be immediately clear

View File

@@ -1 +0,0 @@
[]

View File

@@ -1 +0,0 @@
[]

View File

@@ -1,477 +0,0 @@
---
title: Frontend API Reference
sidebar_position: 1
hide_title: true
---
<!--
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.
-->
# Frontend API Reference
The `@apache-superset/core` package provides comprehensive APIs for frontend extension development. All APIs are organized into logical namespaces for easy discovery and use.
## Core API
The core namespace provides fundamental extension functionality.
### registerView
Registers a new view or panel in the specified contribution point.
```typescript
core.registerView(
id: string,
component: React.ComponentType
): Disposable
```
**Example:**
```typescript
const panel = context.core.registerView('my-extension.panel', () => (
<MyPanelComponent />
));
```
### getActiveView
Gets the currently active view in a contribution area.
```typescript
core.getActiveView(area: string): View | undefined
```
## Commands API
Manages command registration and execution.
### registerCommand
Registers a new command that can be triggered by menus, shortcuts, or programmatically.
```typescript
commands.registerCommand(
id: string,
handler: CommandHandler
): Disposable
interface CommandHandler {
title: string;
icon?: string;
execute: (...args: any[]) => any;
isEnabled?: (...args: any[]) => boolean;
}
```
**Example:**
```typescript
const cmd = context.commands.registerCommand('my-extension.analyze', {
title: 'Analyze Query',
icon: 'BarChartOutlined',
execute: () => {
const query = context.sqlLab.getCurrentQuery();
// Perform analysis
},
isEnabled: () => {
return context.sqlLab.hasActiveEditor();
}
});
```
### executeCommand
Executes a registered command by ID.
```typescript
commands.executeCommand(id: string, ...args: any[]): Promise<any>
```
## SQL Lab API
Provides access to SQL Lab functionality and events.
### Query Access
```typescript
// Get current tab
sqlLab.getCurrentTab(): Tab | undefined
// Get all tabs
sqlLab.getTabs(): Tab[]
// Get current query
sqlLab.getCurrentQuery(): string
// Get selected text
sqlLab.getSelectedText(): string | undefined
```
### Database Access
```typescript
// Get available databases
sqlLab.getDatabases(): Database[]
// Get database by ID
sqlLab.getDatabase(id: number): Database | undefined
// Get schemas for database
sqlLab.getSchemas(databaseId: number): Promise<string[]>
// Get tables for schema
sqlLab.getTables(
databaseId: number,
schema: string
): Promise<Table[]>
```
### Events
```typescript
// Query execution events
sqlLab.onDidQueryRun: Event<QueryResult>
sqlLab.onDidQueryStop: Event<QueryResult>
sqlLab.onDidQueryFail: Event<QueryError>
// Editor events
sqlLab.onDidChangeEditorContent: Event<string>
sqlLab.onDidChangeSelection: Event<Selection>
// Tab events
sqlLab.onDidChangeActiveTab: Event<Tab>
sqlLab.onDidCloseTab: Event<Tab>
sqlLab.onDidChangeTabTitle: Event<{tab: Tab, title: string}>
// Panel events
sqlLab.onDidOpenPanel: Event<Panel>
sqlLab.onDidClosePanel: Event<Panel>
sqlLab.onDidChangeActivePanel: Event<Panel>
```
**Event Usage Example:**
```typescript
const disposable = context.sqlLab.onDidQueryRun((result) => {
console.log('Query executed:', result.query);
console.log('Rows returned:', result.rowCount);
console.log('Execution time:', result.executionTime);
});
// Remember to dispose when done
context.subscriptions.push(disposable);
```
## Authentication API
Handles authentication and security tokens.
### getCSRFToken
Gets the current CSRF token for API requests.
```typescript
authentication.getCSRFToken(): Promise<string>
```
### getCurrentUser
Gets information about the current user.
```typescript
authentication.getCurrentUser(): User
interface User {
id: number;
username: string;
email: string;
roles: Role[];
permissions: Permission[];
}
```
### hasPermission
Checks if the current user has a specific permission.
```typescript
authentication.hasPermission(permission: string): boolean
```
## Extensions API
Manages extension lifecycle and inter-extension communication.
### getExtension
Gets information about an installed extension.
```typescript
extensions.getExtension(id: string): Extension | undefined
interface Extension {
id: string;
name: string;
version: string;
isActive: boolean;
metadata: ExtensionMetadata;
}
```
### getActiveExtensions
Gets all currently active extensions.
```typescript
extensions.getActiveExtensions(): Extension[]
```
### Events
```typescript
// Extension lifecycle events
extensions.onDidActivateExtension: Event<Extension>
extensions.onDidDeactivateExtension: Event<Extension>
```
## UI Components
Import pre-built UI components from `@apache-superset/core`:
```typescript
import {
Button,
Select,
Input,
Table,
Modal,
Alert,
Tabs,
Card,
Dropdown,
Menu,
Tooltip,
Icon,
// ... many more
} from '@apache-superset/core';
```
### Example Component Usage
```typescript
import { Button, Alert } from '@apache-superset/core';
function MyExtensionPanel() {
return (
<div>
<Alert
message="Extension Loaded"
description="Your extension is ready to use"
type="success"
/>
<Button
type="primary"
onClick={() => console.log('Clicked!')}
>
Execute Action
</Button>
</div>
);
}
```
## Storage API
Provides persistent storage for extension data.
### Local Storage
```typescript
// Store data
storage.local.set(key: string, value: any): Promise<void>
// Retrieve data
storage.local.get(key: string): Promise<any>
// Remove data
storage.local.remove(key: string): Promise<void>
// Clear all extension data
storage.local.clear(): Promise<void>
```
### Workspace Storage
Workspace storage is shared across all users for collaborative features.
```typescript
storage.workspace.set(key: string, value: any): Promise<void>
storage.workspace.get(key: string): Promise<any>
storage.workspace.remove(key: string): Promise<void>
```
## Network API
Utilities for making API calls to Superset.
### fetch
Enhanced fetch with CSRF token handling.
```typescript
network.fetch(url: string, options?: RequestInit): Promise<Response>
```
### API Client
Type-safe API client for Superset endpoints.
```typescript
// Get chart data
network.api.charts.get(id: number): Promise<Chart>
// Query database
network.api.sqlLab.execute(
databaseId: number,
query: string
): Promise<QueryResult>
// Get datasets
network.api.datasets.list(): Promise<Dataset[]>
```
## Utility Functions
### Formatting
```typescript
// Format numbers
utils.formatNumber(value: number, format?: string): string
// Format dates
utils.formatDate(date: Date, format?: string): string
// Format SQL
utils.formatSQL(sql: string): string
```
### Validation
```typescript
// Validate SQL syntax
utils.validateSQL(sql: string): ValidationResult
// Check if valid database ID
utils.isValidDatabaseId(id: any): boolean
```
## TypeScript Types
Import common types for type safety:
```typescript
import type {
Database,
Dataset,
Chart,
Dashboard,
Query,
QueryResult,
Tab,
Panel,
User,
Role,
Permission,
ExtensionContext,
Disposable,
Event,
// ... more types
} from '@apache-superset/core';
```
## Extension Context
The context object passed to your extension's `activate` function:
```typescript
interface ExtensionContext {
// Subscription management
subscriptions: Disposable[];
// Extension metadata
extensionId: string;
extensionPath: string;
// API namespaces
core: CoreAPI;
commands: CommandsAPI;
sqlLab: SqlLabAPI;
authentication: AuthenticationAPI;
extensions: ExtensionsAPI;
storage: StorageAPI;
network: NetworkAPI;
utils: UtilsAPI;
// Logging
logger: Logger;
}
```
## Event Handling
Events follow the VS Code pattern with subscribe/dispose:
```typescript
// Subscribe to event
const disposable = sqlLab.onDidQueryRun((result) => {
// Handle event
});
// Dispose when done
disposable.dispose();
// Or add to context for automatic cleanup
context.subscriptions.push(disposable);
```
## Best Practices
1. **Always dispose subscriptions** to prevent memory leaks
2. **Use TypeScript** for better IDE support and type safety
3. **Handle errors gracefully** with try-catch blocks
4. **Check permissions** before sensitive operations
5. **Use provided UI components** for consistency
6. **Cache API responses** when appropriate
7. **Validate user input** before processing
## Version Compatibility
The frontend API follows semantic versioning:
- **Major version**: Breaking changes
- **Minor version**: New features, backward compatible
- **Patch version**: Bug fixes
Check compatibility in your `extension.json`:
```json
{
"engines": {
"@apache-superset/core": "^1.0.0"
}
}
```

View File

@@ -1,348 +0,0 @@
---
title: Architecture Overview
sidebar_position: 1
hide_title: true
---
<!--
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.
-->
# Extension Architecture Overview
The Superset extension architecture is designed to be modular, secure, and performant. This document provides a comprehensive overview of how extensions work and interact with the Superset host application.
## Core Principles
### 1. Lean Core
Superset's core remains minimal, with features delegated to extensions wherever possible. Built-in features use the same APIs as external extensions, ensuring API quality through dogfooding.
### 2. Explicit Contribution Points
All extension points are clearly defined and documented. Extensions declare their capabilities in metadata files, enabling predictable lifecycle management.
### 3. Versioned APIs
Public interfaces follow semantic versioning, ensuring backward compatibility and safe evolution of the platform.
### 4. Lazy Loading
Extensions load only when needed, minimizing performance impact and resource consumption.
### 5. Composability
Architecture patterns and APIs are reusable across different Superset modules, promoting consistency.
### 6. Community-Driven
The system evolves based on real-world feedback, with new extension points added as needs emerge.
## System Architecture
```mermaid
graph TB
subgraph "Superset Host Application"
Core[Core Application]
API[Extension APIs]
Loader[Extension Loader]
Manager[Extension Manager]
end
subgraph "Core Packages"
FrontendCore["@apache-superset/core<br/>(Frontend)"]
BackendCore["apache-superset-core<br/>(Backend)"]
CLI["apache-superset-extensions-cli"]
end
subgraph "Extension"
Metadata[extension.json]
Frontend[Frontend Code]
Backend[Backend Code]
Bundle[.supx Bundle]
end
Core --> API
API --> FrontendCore
API --> BackendCore
Loader --> Manager
Manager --> Bundle
Frontend --> FrontendCore
Backend --> BackendCore
CLI --> Bundle
```
## Key Components
### Host Application
The Superset host application provides:
- **Extension APIs**: Well-defined interfaces for extensions to interact with Superset
- **Extension Manager**: Handles lifecycle, activation, and deactivation
- **Module Loader**: Dynamically loads extension code using Webpack Module Federation
- **Security Context**: Manages permissions and sandboxing for extensions
### Core Packages
#### @apache-superset/core (Frontend)
- Shared UI components and utilities
- TypeScript type definitions
- Frontend API implementations
- Event system and command registry
#### apache-superset-core (Backend)
- Python base classes and utilities
- Database access APIs
- Security and permission helpers
- REST API registration
#### apache-superset-extensions-cli
- Project scaffolding
- Build and bundling tools
- Development server
- Package management
### Extension Structure
Each extension consists of:
- **Metadata** (`extension.json`): Declares capabilities and requirements
- **Frontend**: React components and TypeScript code
- **Backend**: Python modules and API endpoints
- **Assets**: Styles, images, and other resources
- **Bundle** (`.supx`): Packaged distribution format
## Module Federation
Extensions use Webpack Module Federation for dynamic loading:
```javascript
// Extension webpack.config.js
new ModuleFederationPlugin({
name: 'my_extension',
filename: 'remoteEntry.[contenthash].js',
exposes: {
'./index': './src/index.tsx',
},
externals: {
'@apache-superset/core': 'superset',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
}
})
```
This allows:
- **Independent builds**: Extensions compile separately from Superset
- **Shared dependencies**: Common libraries like React aren't duplicated
- **Dynamic loading**: Extensions load at runtime without rebuilding Superset
- **Version compatibility**: Extensions declare compatible core versions
## Extension Lifecycle
### 1. Registration
```typescript
// Extension registered with host
extensionManager.register({
name: 'my-extension',
version: '1.0.0',
manifest: manifestData
});
```
### 2. Activation
```typescript
// activate() called when extension loads
export function activate(context: ExtensionContext) {
// Register contributions
const disposables = [];
// Add panel
disposables.push(
context.core.registerView('my-panel', MyPanel)
);
// Register command
disposables.push(
context.commands.registerCommand('my-command', {
execute: () => { /* ... */ }
})
);
// Store for cleanup
context.subscriptions.push(...disposables);
}
```
### 3. Runtime
- Extension responds to events
- Provides UI components when requested
- Executes commands when triggered
- Accesses APIs as needed
### 4. Deactivation
```typescript
// Automatic cleanup of registered items
export function deactivate() {
// context.subscriptions automatically disposed
// Additional cleanup if needed
}
```
## Contribution Types
### Views
Extensions can add panels and UI components:
```json
{
"views": {
"sqllab.panels": [{
"id": "my-panel",
"name": "My Panel",
"icon": "ToolOutlined"
}]
}
}
```
### Commands
Define executable actions:
```json
{
"commands": [{
"command": "my-extension.run",
"title": "Run Analysis",
"icon": "PlayCircleOutlined"
}]
}
```
### Menus
Add items to existing menus:
```json
{
"menus": {
"sqllab.editor": {
"primary": [{
"command": "my-extension.run",
"when": "editorHasSelection"
}]
}
}
}
```
### API Endpoints
Register backend REST endpoints:
```python
from superset_core.api import rest_api
@rest_api.route('/my-endpoint')
def my_endpoint():
return {'data': 'value'}
```
## Security Model
### Permissions
- Extensions run with user's permissions
- No elevation of privileges
- Access controlled by Superset's RBAC
### Sandboxing
- Frontend code runs in browser context
- Backend code runs in Python process
- Future: Optional sandboxed execution
### Validation
- Manifest validation on upload
- Signature verification (future)
- Dependency scanning
## Performance Considerations
### Lazy Loading
- Extensions load only when features are accessed
- Code splitting for large extensions
- Cached after first load
### Bundle Optimization
- Tree shaking removes unused code
- Minification reduces size
- Compression for network transfer
### Resource Management
- Automatic cleanup on deactivation
- Memory leak prevention
- Event listener management
## Development vs Production
### Development Mode
```python
# superset_config.py
ENABLE_EXTENSIONS = True
LOCAL_EXTENSIONS = ['/path/to/extension']
```
- Hot reloading
- Source maps
- Debug logging
### Production Mode
- Optimized bundles
- Cached assets
- Performance monitoring
## Future Enhancements
### Planned Features
- Enhanced sandboxing
- Extension marketplace
- Inter-extension communication
- Theme contributions
- Chart type extensions
### API Expansion
- Dashboard extensions
- Database connector API
- Security provider interface
- Workflow automation
## Best Practices
### Do's
- ✅ Use TypeScript for type safety
- ✅ Follow semantic versioning
- ✅ Handle errors gracefully
- ✅ Clean up resources properly
- ✅ Document your extension
### Don'ts
- ❌ Access private APIs
- ❌ Modify global state directly
- ❌ Block the main thread
- ❌ Store sensitive data insecurely
- ❌ Assume API stability in 0.x versions
## Learn More
- [API Reference](../api/frontend)
- [Development Guide](../getting-started)
- [Security Guidelines](./security)
- [Performance Optimization](./performance)

View File

@@ -1,466 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
---
title: CLI Documentation
sidebar_position: 1
hide_title: true
---
# Superset Extensions CLI
The `apache-superset-extensions-cli` provides command-line tools for creating, developing, and packaging Superset extensions.
## Installation
```bash
pip install apache-superset-extensions-cli
```
## Commands
### init
Creates a new extension project with the standard folder structure.
```bash
superset-extensions init <extension-name> [options]
```
**Options:**
- `--template <template>`: Use a specific template (default: basic)
- `--author <name>`: Set the author name
- `--description <text>`: Set the extension description
- `--with-backend`: Include backend code structure
**Example:**
```bash
superset-extensions init my-extension \
--author "John Doe" \
--description "Adds custom analytics to SQL Lab" \
--with-backend
```
**Generated Structure:**
```
my-extension/
├── extension.json
├── frontend/
│ ├── src/
│ │ └── index.tsx
│ ├── package.json
│ ├── tsconfig.json
│ └── webpack.config.js
├── backend/
│ ├── src/
│ │ └── my_extension/
│ │ ├── __init__.py
│ │ └── entrypoint.py
│ ├── tests/
│ ├── pyproject.toml
│ └── requirements.txt
└── README.md
```
### dev
Starts the development server with hot reloading.
```bash
superset-extensions dev [options]
```
**Options:**
- `--port <port>`: Development server port (default: 9001)
- `--host <host>`: Development server host (default: localhost)
- `--no-watch`: Disable file watching
- `--verbose`: Show detailed output
**Example:**
```bash
# Start development server
superset-extensions dev
# Output:
⚙️ Building frontend assets...
✅ Frontend rebuilt
✅ Backend files synced
✅ Manifest updated
👀 Watching for changes...
```
### build
Builds the extension for production.
```bash
superset-extensions build [options]
```
**Options:**
- `--mode <mode>`: Build mode (development | production)
- `--analyze`: Generate bundle analysis
- `--source-maps`: Include source maps
**Example:**
```bash
# Production build
superset-extensions build --mode production
# With analysis
superset-extensions build --analyze
```
### bundle
Creates a `.supx` package for distribution.
```bash
superset-extensions bundle [options]
```
**Options:**
- `--output <path>`: Output directory (default: current)
- `--sign`: Sign the package (requires certificate)
- `--compress`: Compression level (0-9, default: 6)
**Example:**
```bash
# Create bundle
superset-extensions bundle
# Creates: my-extension-1.0.0.supx
```
### validate
Validates extension configuration and structure.
```bash
superset-extensions validate [options]
```
**Options:**
- `--fix`: Auto-fix common issues
- `--strict`: Enable strict validation
**Checks:**
- Valid extension.json syntax
- Required files present
- Dependency versions
- Module exports
- TypeScript configuration
**Example:**
```bash
superset-extensions validate --strict
# Output:
✅ extension.json valid
✅ Frontend structure valid
✅ Backend structure valid
⚠️ Warning: Missing LICENSE file
✅ Validation passed with warnings
```
### test
Runs extension tests.
```bash
superset-extensions test [options]
```
**Options:**
- `--coverage`: Generate coverage report
- `--watch`: Run in watch mode
- `--frontend-only`: Run only frontend tests
- `--backend-only`: Run only backend tests
**Example:**
```bash
# Run all tests
superset-extensions test --coverage
# Watch mode for frontend
superset-extensions test --frontend-only --watch
```
### publish
Publishes extension to a registry (future feature).
```bash
superset-extensions publish [options]
```
**Options:**
- `--registry <url>`: Registry URL
- `--token <token>`: Authentication token
- `--dry-run`: Simulate publish
## Configuration
### Project Configuration
The CLI reads configuration from multiple sources:
1. **extension.json** - Extension metadata
2. **package.json** - Frontend dependencies
3. **pyproject.toml** - Backend configuration
4. **.extensionrc** - CLI-specific settings
### .extensionrc Example
```json
{
"dev": {
"port": 9001,
"host": "localhost",
"autoReload": true
},
"build": {
"mode": "production",
"sourceMaps": false,
"optimization": true
},
"test": {
"coverage": true,
"threshold": {
"statements": 80,
"branches": 70,
"functions": 80,
"lines": 80
}
}
}
```
## Templates
### Available Templates
- **basic**: Simple extension with frontend only
- **full-stack**: Frontend and backend components
- **sql-panel**: SQL Lab panel extension
- **api-only**: Backend API extension
- **chart-plugin**: Custom chart visualization
### Using Templates
```bash
# Use specific template
superset-extensions init my-chart --template chart-plugin
# List available templates
superset-extensions init --list-templates
```
### Custom Templates
Create custom templates in `~/.superset-extensions/templates/`:
```
~/.superset-extensions/templates/
└── my-template/
├── template.json
└── files/
└── ... template files ...
```
## Development Workflow
### 1. Create Extension
```bash
superset-extensions init awesome-feature
cd awesome-feature
```
### 2. Install Dependencies
```bash
# Frontend
cd frontend && npm install
# Backend (if applicable)
cd ../backend && pip install -r requirements.txt
```
### 3. Configure Superset
```python
# superset_config.py
ENABLE_EXTENSIONS = True
LOCAL_EXTENSIONS = [
"/path/to/awesome-feature"
]
```
### 4. Start Development
```bash
# Terminal 1: Extension dev server
superset-extensions dev
# Terminal 2: Superset
superset run -p 8088 --reload
```
### 5. Test Changes
Make changes to your code and see them reflected immediately in Superset.
### 6. Build and Package
```bash
# Validate
superset-extensions validate
# Test
superset-extensions test
# Build
superset-extensions build --mode production
# Bundle
superset-extensions bundle
```
### 7. Deploy
Upload the `.supx` file to your Superset instance.
## Environment Variables
The CLI respects these environment variables:
- `SUPERSET_EXTENSIONS_DEV_PORT`: Development server port
- `SUPERSET_EXTENSIONS_DEV_HOST`: Development server host
- `SUPERSET_BASE_URL`: Superset instance URL
- `NODE_ENV`: Node environment (development/production)
- `PYTHONPATH`: Python module search path
## Troubleshooting
### Common Issues
#### Port Already in Use
```bash
# Use different port
superset-extensions dev --port 9002
```
#### Module Federation Errors
```bash
# Rebuild with clean cache
rm -rf dist/ node_modules/.cache
superset-extensions build
```
#### Python Import Errors
```bash
# Ensure virtual environment is activated
source venv/bin/activate
superset-extensions dev
```
### Debug Mode
Enable verbose output for troubleshooting:
```bash
# Verbose output
superset-extensions dev --verbose
# Debug webpack
DEBUG=webpack:* superset-extensions build
```
## Best Practices
1. **Version Control**: Commit `extension.json` but not `dist/`
2. **Dependencies**: Pin versions in package.json
3. **Testing**: Write tests for critical functionality
4. **Documentation**: Keep README.md updated
5. **Validation**: Run validate before bundling
6. **Semantic Versioning**: Follow semver for releases
## Advanced Usage
### Custom Webpack Configuration
Extend the default webpack config:
```javascript
// webpack.config.js
const baseConfig = require('./webpack.base.config');
module.exports = {
...baseConfig,
// Custom modifications
resolve: {
...baseConfig.resolve,
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
};
```
### CI/CD Integration
```yaml
# .github/workflows/extension.yml
name: Extension CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/setup-python@v2
- name: Install CLI
run: pip install apache-superset-extensions-cli
- name: Validate
run: superset-extensions validate --strict
- name: Test
run: superset-extensions test --coverage
- name: Build
run: superset-extensions build --mode production
- name: Bundle
run: superset-extensions bundle
```
## Getting Help
- **Documentation**: [Developer Portal](../)
- **Examples**: [GitHub Repository](https://github.com/apache/superset/tree/master/extensions)
- **Issues**: [GitHub Issues](https://github.com/apache/superset/issues)
- **Community**: [Slack Channel](https://apache-superset.slack.com)

View File

@@ -1,464 +0,0 @@
---
title: Extension Examples
sidebar_position: 1
hide_title: true
---
<!--
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.
-->
# Extension Examples
Learn from real-world extension implementations that showcase different capabilities of the Superset extension system.
## Dataset References Panel
A SQL Lab panel that analyzes queries and displays information about referenced tables.
### Features
- Parses SQL to extract table references
- Shows table owners and permissions
- Displays last partition information
- Provides row count estimates
### Key Implementation
```typescript
// Parse SQL and extract tables
function extractTables(sql: string): TableReference[] {
const tables = [];
const tableRegex = /FROM\s+(\w+\.?\w+)/gi;
let match;
while ((match = tableRegex.exec(sql)) !== null) {
tables.push({
schema: match[1].split('.')[0],
table: match[1].split('.')[1] || match[1],
});
}
return tables;
}
// Register panel
export function activate(context: ExtensionContext) {
const panel = context.core.registerView('dataset-references.panel', () => (
<DatasetReferencesPanel />
));
// Listen for query changes
const listener = context.sqlLab.onDidChangeEditorContent((content) => {
const tables = extractTables(content);
updatePanelWithTables(tables);
});
context.subscriptions.push(panel, listener);
}
```
### Manifest
```json
{
"name": "dataset-references",
"contributions": {
"views": {
"sqllab.panels": [{
"id": "dataset-references.panel",
"name": "Dataset References",
"icon": "DatabaseOutlined",
"location": "right"
}]
}
}
}
```
## Query Optimizer
Analyzes SQL queries and suggests optimizations.
### Features
- Detects missing indexes
- Suggests query rewrites
- Identifies expensive operations
- Provides execution plan analysis
### Implementation Highlights
```typescript
// Register optimization command
const optimizeCommand = context.commands.registerCommand('query-optimizer.analyze', {
title: 'Analyze Query Performance',
icon: 'ThunderboltOutlined',
execute: async () => {
const query = context.sqlLab.getCurrentQuery();
const database = context.sqlLab.getCurrentDatabase();
// Get execution plan
const plan = await getExecutionPlan(database.id, query);
// Analyze and suggest improvements
const suggestions = analyzeExecutionPlan(plan);
// Show results in panel
showOptimizationResults(suggestions);
}
});
// Add to editor menu
"menus": {
"sqllab.editor": {
"primary": [{
"command": "query-optimizer.analyze",
"when": "editorHasContent"
}]
}
}
```
## Natural Language to SQL
Converts natural language questions to SQL queries using LLM integration.
### Features
- Natural language input
- Context-aware SQL generation
- Query validation
- History tracking
### Key Components
```typescript
// Backend API endpoint
@rest_api.route('/nl2sql/generate')
def generate_sql(prompt: str, context: dict):
# Use LLM to generate SQL
sql = llm_client.generate(
prompt=prompt,
schema=context['schema'],
examples=context['examples']
)
# Validate generated SQL
validation = validate_sql(sql)
return {
'sql': sql,
'valid': validation.is_valid,
'errors': validation.errors
}
```
```typescript
// Frontend integration
function NL2SQLPanel() {
const [prompt, setPrompt] = useState('');
const [loading, setLoading] = useState(false);
const generateSQL = async () => {
setLoading(true);
const response = await context.network.api.post('/extensions/nl2sql/generate', {
prompt,
context: {
database: context.sqlLab.getCurrentDatabase(),
schema: await context.sqlLab.getCurrentSchema(),
}
});
if (response.valid) {
// Insert SQL into editor
context.sqlLab.insertText(response.sql);
}
setLoading(false);
};
return (
<div>
<Input.TextArea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Describe what data you want..."
/>
<Button onClick={generateSQL} loading={loading}>
Generate SQL
</Button>
</div>
);
}
```
## Schema Visualizer
Interactive database schema visualization.
### Features
- Visual ERD diagram
- Table relationships
- Column details on hover
- Export to image
### Implementation
```typescript
import { Graph } from '@antv/g6';
function SchemaVisualizer() {
const containerRef = useRef<HTMLDivElement>(null);
const [graph, setGraph] = useState<Graph>();
useEffect(() => {
if (!containerRef.current) return;
const g = new Graph({
container: containerRef.current,
layout: {
type: 'dagre',
rankdir: 'LR',
},
defaultNode: {
type: 'sql-table-node',
},
defaultEdge: {
type: 'sql-relation-edge',
},
});
setGraph(g);
loadSchemaData(g);
return () => g.destroy();
}, []);
const loadSchemaData = async (g: Graph) => {
const tables = await context.sqlLab.getTables();
const nodes = tables.map(table => ({
id: table.name,
label: table.name,
columns: table.columns,
}));
const edges = extractRelationships(tables);
g.data({ nodes, edges });
g.render();
};
return <div ref={containerRef} style={{ height: '100%' }} />;
}
```
## SQL Formatter
Formats and beautifies SQL code with customizable rules.
### Features
- Multiple formatting styles
- Custom rule configuration
- Batch formatting
- Format on save
### Simple Implementation
```typescript
import { format } from 'sql-formatter';
const formatCommand = context.commands.registerCommand('sql-formatter.format', {
title: 'Format SQL',
execute: () => {
const sql = context.sqlLab.getCurrentQuery();
const formatted = format(sql, {
language: 'sql',
indent: ' ',
uppercase: true,
linesBetweenQueries: 2,
});
context.sqlLab.replaceQuery(formatted);
}
});
// Auto-format on save
context.sqlLab.onWillSaveQuery((event) => {
if (context.storage.local.get('autoFormat')) {
const formatted = format(event.query);
event.waitUntil(Promise.resolve(formatted));
}
});
```
## Query History Search
Enhanced query history with advanced search and filtering.
### Features
- Full-text search
- Filter by date, user, database
- Query statistics
- Export capabilities
### UI Component
```typescript
function QueryHistoryPanel() {
const [queries, setQueries] = useState<Query[]>([]);
const [filters, setFilters] = useState<Filters>({});
useEffect(() => {
loadQueries();
}, [filters]);
const loadQueries = async () => {
const history = await context.network.api.get('/api/v1/query', {
params: {
...filters,
page_size: 100,
}
});
setQueries(history.result);
};
return (
<div>
<SearchFilters onChange={setFilters} />
<Table
dataSource={queries}
columns={[
{ title: 'Query', dataIndex: 'sql', ellipsis: true },
{ title: 'Database', dataIndex: 'database' },
{ title: 'Status', dataIndex: 'status' },
{ title: 'Duration', dataIndex: 'duration' },
{ title: 'User', dataIndex: 'user' },
{
title: 'Actions',
render: (query) => (
<Button
icon={<CopyOutlined />}
onClick={() => context.sqlLab.insertText(query.sql)}
/>
),
},
]}
/>
</div>
);
}
```
## Git Integration
Version control for SQL queries and dashboards.
### Features
- Save queries to Git
- Track changes
- Collaborative editing
- Branch management
### Backend Integration
```python
from git import Repo
class GitExtension:
def __init__(self, repo_path):
self.repo = Repo(repo_path)
def save_query(self, query, message):
# Save query to file
path = f"queries/{query.name}.sql"
with open(path, 'w') as f:
f.write(query.sql)
# Commit to Git
self.repo.index.add([path])
self.repo.index.commit(message)
return {
'status': 'success',
'commit': self.repo.head.commit.hexsha
}
```
## Best Practices from Examples
### 1. User Experience
- Provide clear feedback for async operations
- Handle errors gracefully
- Include loading states
- Add keyboard shortcuts
### 2. Performance
- Debounce expensive operations
- Cache API responses
- Use virtual scrolling for large lists
- Lazy load heavy components
### 3. Integration
- Respect Superset's theme
- Use provided UI components
- Follow existing UX patterns
- Integrate with existing menus
### 4. Code Organization
```
extension/
├── frontend/
│ ├── src/
│ │ ├── components/ # UI components
│ │ ├── hooks/ # Custom hooks
│ │ ├── services/ # API services
│ │ ├── utils/ # Utilities
│ │ └── index.tsx # Entry point
│ └── tests/
├── backend/
│ ├── src/
│ │ ├── api/ # REST endpoints
│ │ ├── models/ # Data models
│ │ ├── services/ # Business logic
│ │ └── entrypoint.py
│ └── tests/
```
### 5. Testing
```typescript
// Test example
describe('DatasetReferences', () => {
it('should extract tables from SQL', () => {
const sql = 'SELECT * FROM users JOIN orders ON users.id = orders.user_id';
const tables = extractTables(sql);
expect(tables).toEqual([
{ schema: 'public', table: 'users' },
{ schema: 'public', table: 'orders' },
]);
});
});
```
## Learn More
- [API Reference](../api/frontend)
- [Architecture Overview](../architecture/overview)
- [Getting Started Guide](../getting-started)
- [CLI Documentation](../cli/overview)

View File

@@ -1,248 +0,0 @@
---
title: Getting Started
sidebar_position: 2
hide_title: true
---
<!--
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.
-->
# Getting Started with Extensions
This guide will walk you through creating, developing, and deploying your first Superset extension.
## Prerequisites
Before you begin, make sure you have:
- **Node.js 16+** and npm/yarn installed
- **Python 3.9+** and pip installed
- **Superset** running locally or access to a Superset instance
- **Git** for version control
- Basic knowledge of React and TypeScript
## Quick Start
### 1. Install the CLI
First, install the Superset Extensions CLI globally:
```bash
pip install apache-superset-extensions-cli
```
### 2. Create Your First Extension
Use the CLI to scaffold a new extension project:
```bash
superset-extensions init my-first-extension
cd my-first-extension
```
This creates the following structure:
```
my-first-extension/
├── extension.json # Extension metadata
├── frontend/ # Frontend code
│ ├── src/
│ │ └── index.tsx # Main entry point
│ ├── package.json
│ └── webpack.config.js
├── backend/ # Backend code (optional)
│ ├── src/
│ └── requirements.txt
└── README.md
```
### 3. Configure Your Extension
Edit `extension.json` to define your extension's capabilities:
```json
{
"name": "my-first-extension",
"version": "1.0.0",
"description": "My first Superset extension",
"author": "Your Name",
"frontend": {
"contributions": {
"views": {
"sqllab.panels": [
{
"id": "my-extension.main",
"name": "My Panel",
"icon": "ToolOutlined"
}
]
}
}
}
}
```
### 4. Develop Your Extension
Edit `frontend/src/index.tsx` to implement your extension:
```typescript
import React from 'react';
import { ExtensionContext } from '@apache-superset/core';
export function activate(context: ExtensionContext) {
// Register your panel component
const panel = context.core.registerView('my-extension.main', () => (
<div style={{ padding: 20 }}>
<h2>Hello from My Extension!</h2>
<p>This is my first Superset extension.</p>
</div>
));
// Clean up on deactivation
context.subscriptions.push(panel);
}
export function deactivate() {
// Cleanup code if needed
}
```
### 5. Test Locally
Enable development mode in your Superset configuration:
```python
# superset_config.py
ENABLE_EXTENSIONS = True
LOCAL_EXTENSIONS = [
"/path/to/my-first-extension"
]
```
Run the development server:
```bash
# In your extension directory
superset-extensions dev
# In a separate terminal, start Superset
superset run -p 8088 --with-threads --reload
```
Your extension will now appear in SQL Lab!
### 6. Build and Package
When ready to distribute your extension:
```bash
superset-extensions build
superset-extensions bundle
```
This creates a `my-first-extension-1.0.0.supx` file that can be uploaded to any Superset instance.
### 7. Deploy
Upload your extension via the Superset UI or API:
```bash
curl -X POST http://localhost:8088/api/v1/extensions/import/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@my-first-extension-1.0.0.supx"
```
## What's Next?
Now that you have a basic extension working, explore:
- [Extension Architecture](../architecture/overview) - Understand how extensions work
- [API Reference](../api/frontend) - Learn about available APIs
- [Examples](../examples) - See more complex extension examples
- [Best Practices](./best-practices) - Learn extension development best practices
## Common Patterns
### Adding a SQL Lab Panel
```typescript
const panel = context.core.registerView('my-extension.panel', () => (
<MyPanelComponent />
));
```
### Registering Commands
```typescript
const command = context.commands.registerCommand('my-extension.run', {
title: 'Run My Command',
execute: () => {
// Command logic
}
});
```
### Listening to Events
```typescript
const listener = context.sqlLab.onDidQueryRun((query) => {
console.log('Query executed:', query);
});
```
### Adding Menu Items
```json
{
"menus": {
"sqllab.editor": {
"primary": [{
"command": "my-extension.run",
"when": "editorHasSelection"
}]
}
}
}
```
## Troubleshooting
### Extension Not Loading
- Ensure `ENABLE_EXTENSIONS = True` in your config
- Check the browser console for errors
- Verify the extension path in `LOCAL_EXTENSIONS`
### Module Not Found Errors
- Run `npm install` in the frontend directory
- Check that `@apache-superset/core` is properly externalized
### Build Failures
- Ensure all dependencies are installed
- Check webpack.config.js for proper Module Federation setup
- Verify extension.json is valid JSON
## Get Help
- Join the [Superset Slack](https://join.slack.com/t/apache-superset/shared_invite/zt-16jvzmoi8-sI1TY1Pm~y_RnSiUAN0jqQ)
- Ask questions on [GitHub Discussions](https://github.com/apache/superset/discussions)
- Report issues on [GitHub Issues](https://github.com/apache/superset/issues)

View File

@@ -1,126 +0,0 @@
---
title: Developer Portal
sidebar_position: 1
hide_title: true
---
<!--
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.
-->
# Superset Developer Portal
Welcome to the Apache Superset Developer Portal! This is your comprehensive guide to extending and customizing Superset through our new extension architecture.
## What are Superset Extensions?
Superset Extensions provide a powerful way to enhance and customize Apache Superset without modifying the core codebase. Following the successful model pioneered by VS Code, our extension architecture enables developers to:
- **Add custom features** to SQL Lab and other Superset modules
- **Create reusable components** that can be shared across organizations
- **Integrate external tools** and services seamlessly
- **Customize workflows** to match your team's specific needs
## Why Extensions?
As Superset has grown, we've recognized the need for a more modular architecture that allows:
- **Innovation without fragmentation** - Build features without forking the codebase
- **Community-driven development** - Share and reuse extensions across organizations
- **Stable APIs** - Develop against versioned, well-documented interfaces
- **Rapid iteration** - Deploy custom features without waiting for core releases
## Key Features
### 🎯 Well-Defined Extension Points
Extensions can contribute to specific areas of Superset:
- SQL Lab panels (left, right, bottom, editor)
- Custom commands and menu items
- Status bar components
- API endpoints and backend functionality
### 🔧 Modern Development Experience
- **CLI tools** for scaffolding, building, and packaging
- **Hot reloading** during development
- **TypeScript support** with full type safety
- **Module Federation** for dynamic loading
### 📦 Simple Distribution
- Package extensions as `.supx` files
- Upload via REST API or UI
- Automatic activation and lifecycle management
- Version compatibility checking
## Getting Started
Ready to build your first extension? Check out our [Getting Started Guide](./getting-started) to:
1. Set up your development environment
2. Create your first extension
3. Test it locally
4. Package and deploy it
## Architecture Overview
Our extension architecture is built on several key principles:
- **Lean Core**: Keep Superset's core minimal and delegate features to extensions
- **Explicit APIs**: Clear, versioned interfaces for extension interactions
- **Lazy Loading**: Extensions load only when needed for optimal performance
- **Security First**: Extensions run with appropriate permissions and sandboxing
Learn more in our [Architecture Documentation](./architecture/overview).
## Current Status
The extension architecture is currently in active development. The initial focus is on SQL Lab extensions, with plans to expand to:
- Dashboard extensions
- Chart plugins (enhanced from current system)
- Database connectors
- Security providers
## Example: Dataset References Extension
See the extension system in action with our Dataset References example, which adds a SQL Lab panel showing:
- Tables referenced in queries
- Table owners for permission requests
- Last available partitions
- Estimated row counts
## Join the Community
- **Contribute**: Help shape the future of Superset extensions
- **Share**: Publish your extensions for others to use
- **Learn**: Explore extensions built by the community
## Quick Links
- [Getting Started Guide](./getting-started)
- [Extension Architecture](./architecture/overview)
- [API Reference](./api/frontend)
- [CLI Documentation](./cli/overview)
- [Examples](./examples)
---
*The Superset extension architecture is inspired by the successful model of VS Code Extensions, bringing similar flexibility and power to the data exploration domain.*

View File

@@ -1,59 +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.
*/
module.exports = {
developerPortalSidebar: [
'index',
{
type: 'category',
label: 'Getting Started',
items: [
'getting-started/index',
],
},
{
type: 'category',
label: 'Architecture',
items: [
'architecture/overview',
],
},
{
type: 'category',
label: 'API Reference',
items: [
'api/frontend',
],
},
{
type: 'category',
label: 'CLI',
items: [
'cli/overview',
],
},
{
type: 'category',
label: 'Examples',
items: [
'examples/index',
],
},
],
};

View File

@@ -1 +0,0 @@
[]

View File

@@ -1 +0,0 @@
[]

View File

@@ -12,7 +12,7 @@ Users can configure automated alerts and reports to send dashboards or charts to
- *Alerts* are sent when a SQL condition is reached
- *Reports* are sent on a schedule
Alerts and reports are disabled by default. To turn them on, you'll need to change configuration settings and install a suitable headless browser in your environment.
Alerts and reports are disabled by default. To turn them on, you need to do some setup, described here.
## Requirements
@@ -35,14 +35,16 @@ Screenshots will be taken but no messages actually sent as long as `ALERT_REPORT
#### In your `Dockerfile`
You'll need to extend the Superset image to include a headless browser. Your options include:
- Use Playwright with Chrome: this is the recommended approach as of version >=4.1.x. A working example of a Dockerfile that installs these tools is provided under “Building your own production Docker image” on the [Docker Builds](/docs/installation/docker-builds#building-your-own-production-docker-image) page. Read the code comments there as you'll also need to change a feature flag in your config.
- Use Firefox: you'll need to install geckodriver and Firefox.
- Use Chrome without Playwright: you'll need to install Chrome and set the value of `WEBDRIVER_TYPE` to `"chrome"` in your `superset_config.py`.
- You must install a headless browser, for taking screenshots of the charts and dashboards. Only Firefox and Chrome are currently supported.
> If you choose Chrome, you must also change the value of `WEBDRIVER_TYPE` to `"chrome"` in your `superset_config.py`.
In Superset versions <=4.0x, users installed Firefox or Chrome and that was documented here.
Note: All the components required (Firefox headless browser, Redis, Postgres db, celery worker and celery beat) are present in the *dev* docker image if you are following [Installing Superset Locally](/docs/installation/docker-compose/).
All you need to do is add the required config variables described in this guide (See `Detailed Config`).
Only the worker container needs the browser.
If you are running a non-dev docker image, e.g., a stable release like `apache/superset:3.1.0`, that image does not include a headless browser. Only the `superset_worker` container needs this headless browser to browse to the target chart or dashboard.
You can either install and configure the headless browser - see "Custom Dockerfile" section below - or when deploying via `docker compose`, modify your `docker-compose.yml` file to use a dev image for the worker container and a stable release image for the `superset_app` container.
*Note*: In this context, a "dev image" is the same application software as its corresponding non-dev image, just bundled with additional tools. So an image like `3.1.0-dev` is identical to `3.1.0` when it comes to stability, functionality, and running in production. The actual "in-development" versions of Superset - cutting-edge and unstable - are not tagged with version numbers on Docker Hub and will display version `0.0.0-dev` within the Superset UI.
### Slack integration
@@ -150,8 +152,8 @@ SMTP_MAIL_FROM = "noreply@youremail.com"
EMAIL_REPORTS_SUBJECT_PREFIX = "[Superset] " # optional - overwrites default value in config.py of "[Report] "
# WebDriver configuration
# If you use Firefox or Playwright with Chrome, you can stick with default values
# If you use Chrome and are *not* using Playwright, then add the following WEBDRIVER_TYPE and WEBDRIVER_OPTION_ARGS
# If you use Firefox, you can stick with default values
# If you use Chrome, then add the following WEBDRIVER_TYPE and WEBDRIVER_OPTION_ARGS
WEBDRIVER_TYPE = "chrome"
WEBDRIVER_OPTION_ARGS = [
"--force-device-scale-factor=2.0",
@@ -217,6 +219,62 @@ def alert_dynamic_minimal_interval(**kwargs) -> int:
ALERT_MINIMUM_INTERVAL = alert_dynamic_minimal_interval
```
## Custom Dockerfile
If you're running the dev version of a released Superset image, like `apache/superset:3.1.0-dev`, you should be set with the above.
But if you're building your own image, or starting with a non-dev version, a webdriver (and headless browser) is needed to capture screenshots of the charts and dashboards which are then sent to the recipient.
Here's how you can modify your Dockerfile to take the screenshots either with Firefox or Chrome.
### Using Firefox
```docker
FROM apache/superset:3.1.0
USER root
RUN apt-get update && \
apt-get install --no-install-recommends -y firefox-esr
ENV GECKODRIVER_VERSION=0.29.0
RUN wget -q https://github.com/mozilla/geckodriver/releases/download/v${GECKODRIVER_VERSION}/geckodriver-v${GECKODRIVER_VERSION}-linux64.tar.gz && \
tar -x geckodriver -zf geckodriver-v${GECKODRIVER_VERSION}-linux64.tar.gz -O > /usr/bin/geckodriver && \
chmod 755 /usr/bin/geckodriver && \
rm geckodriver-v${GECKODRIVER_VERSION}-linux64.tar.gz
RUN pip install --no-cache gevent psycopg2 redis
USER superset
```
### Using Chrome
```docker
FROM apache/superset:3.1.0
USER root
RUN apt-get update && \
apt-get install -y wget zip libaio1
RUN export CHROMEDRIVER_VERSION=$(curl --silent https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_116) && \
wget -O google-chrome-stable_current_amd64.deb -q http://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROMEDRIVER_VERSION}-1_amd64.deb && \
apt-get install -y --no-install-recommends ./google-chrome-stable_current_amd64.deb && \
rm -f google-chrome-stable_current_amd64.deb
RUN export CHROMEDRIVER_VERSION=$(curl --silent https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_116) && \
wget -q https://storage.googleapis.com/chrome-for-testing-public/${CHROMEDRIVER_VERSION}/linux64/chromedriver-linux64.zip && \
unzip -j chromedriver-linux64.zip -d /usr/bin && \
chmod 755 /usr/bin/chromedriver && \
rm -f chromedriver-linux64.zip
RUN pip install --no-cache gevent psycopg2 redis
USER superset
```
Don't forget to set `WEBDRIVER_TYPE` and `WEBDRIVER_OPTION_ARGS` in your config if you use Chrome.
## Troubleshooting
There are many reasons that reports might not be working. Try these steps to check for specific issues.
@@ -235,7 +293,9 @@ This is the best source of information about the problem. In a docker compose d
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.
Superset docker images that have a tag ending with `-dev` have the Firefox headless browser and geckodriver already installed. You can test that these are installed and in the proper path by entering your Superset worker and running `firefox --headless` and then `geckodriver`. Both commands should start those applications.
If you are handling the installation of that software on your own, or wish to use Chromium instead, do your own verification to ensure that the headless browser opens successfully in the worker environment.
### Send a test email

View File

@@ -363,6 +363,110 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
]
```
### Keycloak-Specific Configuration using Flask-OIDC
If you are using Keycloak as OpenID Connect 1.0 Provider, the above configuration based on [`Authlib`](https://authlib.org/) might not work. In this case using [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is a viable option.
Make sure the pip package [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is installed on the webserver. This was successfully tested using version 2.2.0. This package requires [`Flask-OpenID`](https://pypi.org/project/Flask-OpenID/) as a dependency.
The following code defines a new security manager. Add it to a new file named `keycloak_security_manager.py`, placed in the same directory as your `superset_config.py` file.
```python
from flask_appbuilder.security.manager import AUTH_OID
from superset.security import SupersetSecurityManager
from flask_oidc import OpenIDConnect
from flask_appbuilder.security.views import AuthOIDView
from flask_login import login_user
from urllib.parse import quote
from flask_appbuilder.views import ModelView, SimpleFormView, expose
from flask import (
redirect,
request
)
import logging
class OIDCSecurityManager(SupersetSecurityManager):
def __init__(self, appbuilder):
super(OIDCSecurityManager, self).__init__(appbuilder)
if self.auth_type == AUTH_OID:
self.oid = OpenIDConnect(self.appbuilder.get_app)
self.authoidview = AuthOIDCView
class AuthOIDCView(AuthOIDView):
@expose('/login/', methods=['GET', 'POST'])
def login(self, flag=True):
sm = self.appbuilder.sm
oidc = sm.oid
@self.appbuilder.sm.oid.require_login
def handle_login():
user = sm.auth_user_oid(oidc.user_getfield('email'))
if user is None:
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email'])
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'),
info.get('email'), sm.find_role('Gamma'))
login_user(user, remember=False)
return redirect(self.appbuilder.get_url_for_index)
return handle_login()
@expose('/logout/', methods=['GET', 'POST'])
def logout(self):
oidc = self.appbuilder.sm.oid
oidc.logout()
super(AuthOIDCView, self).logout()
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
return redirect(
oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))
```
Then add to your `superset_config.py` file:
```python
from keycloak_security_manager import OIDCSecurityManager
from flask_appbuilder.security.manager import AUTH_OID, AUTH_REMOTE_USER, AUTH_DB, AUTH_LDAP, AUTH_OAUTH
import os
AUTH_TYPE = AUTH_OID
SECRET_KEY: 'SomethingNotEntirelySecret'
OIDC_CLIENT_SECRETS = '/path/to/client_secret.json'
OIDC_ID_TOKEN_COOKIE_SECURE = False
OIDC_OPENID_REALM: '<myRealm>'
OIDC_INTROSPECTION_AUTH_METHOD: 'client_secret_post'
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
# Will allow user self registration, allowing to create Flask users from Authorized User
AUTH_USER_REGISTRATION = True
# The default user self registration role
AUTH_USER_REGISTRATION_ROLE = 'Public'
```
Store your client-specific OpenID information in a file called `client_secret.json`. Create this file in the same directory as `superset_config.py`:
```json
{
"<myOpenIDProvider>": {
"issuer": "https://<myKeycloakDomain>/realms/<myRealm>",
"auth_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/auth",
"client_id": "https://<myKeycloakDomain>",
"client_secret": "<myClientSecret>",
"redirect_uris": [
"https://<SupersetWebserver>/oauth-authorized/<myOpenIDProvider>"
],
"userinfo_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/userinfo",
"token_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token",
"token_introspection_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token/introspect"
}
}
```
## LDAP Authentication
FAB supports authenticating user credentials against an LDAP server.

View File

@@ -10,15 +10,8 @@ version: 1
## Jinja Templates
SQL Lab and Explore supports [Jinja templating](https://jinja.palletsprojects.com/en/2.11.x/) in queries.
To enable templating, the `ENABLE_TEMPLATE_PROCESSING` [feature flag](/docs/configuration/configuring-superset#feature-flags) needs to be enabled in `superset_config.py`.
> #### ⚠️ Security Warning
>
> While powerful, this feature executes template code on the server. Within the Superset security model, this is **intended functionality**, as users with permissions to edit charts and virtual datasets are considered **trusted users**.
>
> If you grant these permissions to untrusted users, this feature can be exploited as a **Server-Side Template Injection (SSTI)** vulnerability. Do not enable `ENABLE_TEMPLATE_PROCESSING` unless you fully understand and accept the associated security risks.
When templating is enabled, python code can be embedded in virtual datasets and
To enable templating, the `ENABLE_TEMPLATE_PROCESSING` [feature flag](/docs/configuration/configuring-superset#feature-flags) needs to be enabled in
`superset_config.py`. When templating is enabled, python code can be embedded in virtual datasets and
in Custom SQL in the filter and metric controls in Explore. By default, the following variables are
made available in the Jinja context:

View File

@@ -165,206 +165,6 @@ Or in the CRUD interface theme JSON:
This feature works with the stock Docker image - no custom build required!
## ECharts Configuration Overrides
:::note
Available since Superset 6.0
:::
Superset provides fine-grained control over ECharts visualizations through theme-level configuration overrides. This allows you to customize the appearance and behavior of all ECharts-based charts without modifying individual chart configurations.
### Global ECharts Overrides
Apply settings to all ECharts visualizations using `echartsOptionsOverrides`:
```python
THEME_DEFAULT = {
"token": {
"colorPrimary": "#2893B3",
# ... other Ant Design tokens
},
"echartsOptionsOverrides": {
"grid": {
"left": "10%",
"right": "10%",
"top": "15%",
"bottom": "15%"
},
"tooltip": {
"backgroundColor": "rgba(0, 0, 0, 0.8)",
"borderColor": "#ccc",
"textStyle": {
"color": "#fff"
}
},
"legend": {
"textStyle": {
"fontSize": 14,
"fontWeight": "bold"
}
}
}
}
```
### Chart-Specific Overrides
Target specific chart types using `echartsOptionsOverridesByChartType`:
```python
THEME_DEFAULT = {
"token": {
"colorPrimary": "#2893B3",
# ... other tokens
},
"echartsOptionsOverridesByChartType": {
"echarts_pie": {
"legend": {
"orient": "vertical",
"right": 10,
"top": "center"
}
},
"echarts_timeseries": {
"xAxis": {
"axisLabel": {
"rotate": 45,
"fontSize": 12
}
},
"dataZoom": [{
"type": "slider",
"show": True,
"start": 0,
"end": 100
}]
},
"echarts_bubble": {
"grid": {
"left": "15%",
"bottom": "20%"
}
}
}
}
```
### UI Configuration
You can also configure ECharts overrides through the theme CRUD interface:
```json
{
"token": {
"colorPrimary": "#2893B3"
},
"echartsOptionsOverrides": {
"grid": {
"left": "10%",
"right": "10%"
},
"tooltip": {
"backgroundColor": "rgba(0, 0, 0, 0.8)"
}
},
"echartsOptionsOverridesByChartType": {
"echarts_pie": {
"legend": {
"orient": "vertical",
"right": 10
}
}
}
}
```
### Override Precedence
The system applies overrides in the following order (last wins):
1. **Base ECharts theme** - Default Superset styling
2. **Plugin options** - Chart-specific configurations
3. **Global overrides** - `echartsOptionsOverrides`
4. **Chart-specific overrides** - `echartsOptionsOverridesByChartType[chartType]`
This ensures chart-specific overrides take precedence over global ones.
### Common Chart Types
Available chart types for `echartsOptionsOverridesByChartType`:
- `echarts_timeseries` - Time series/line charts
- `echarts_pie` - Pie and donut charts
- `echarts_bubble` - Bubble/scatter charts
- `echarts_funnel` - Funnel charts
- `echarts_gauge` - Gauge charts
- `echarts_radar` - Radar charts
- `echarts_boxplot` - Box plot charts
- `echarts_treemap` - Treemap charts
- `echarts_sunburst` - Sunburst charts
- `echarts_graph` - Network/graph charts
- `echarts_sankey` - Sankey diagrams
- `echarts_heatmap` - Heatmaps
- `echarts_mixed_timeseries` - Mixed time series
### Best Practices
1. **Start with global overrides** for consistent styling across all charts
2. **Use chart-specific overrides** for unique requirements per visualization type
3. **Test thoroughly** as overrides use deep merge - nested objects are combined, but arrays are completely replaced
4. **Document your overrides** to help team members understand custom styling
5. **Consider performance** - complex overrides may impact chart rendering speed
### Example: Corporate Branding
```python
# Complete corporate theme with ECharts customization
THEME_DEFAULT = {
"token": {
"colorPrimary": "#1B4D3E",
"fontFamily": "Corporate Sans, Arial, sans-serif"
},
"echartsOptionsOverrides": {
"grid": {
"left": "8%",
"right": "8%",
"top": "12%",
"bottom": "12%"
},
"textStyle": {
"fontFamily": "Corporate Sans, Arial, sans-serif"
},
"title": {
"textStyle": {
"color": "#1B4D3E",
"fontSize": 18,
"fontWeight": "bold"
}
}
},
"echartsOptionsOverridesByChartType": {
"echarts_timeseries": {
"xAxis": {
"axisLabel": {
"color": "#666",
"fontSize": 11
}
}
},
"echarts_pie": {
"legend": {
"textStyle": {
"fontSize": 12
},
"itemGap": 20
}
}
}
}
```
This feature provides powerful theming capabilities while maintaining the flexibility of ECharts' extensive configuration options.
## Advanced Features
- **System Themes**: Manage system-wide default and dark themes via UI or configuration

View File

@@ -620,10 +620,10 @@ See [how tos](/docs/contributing/howtos#linting)
:::tip
`act` compatibility of Superset's GHAs is not fully tested. Running `act` locally may or may not
work for different actions, and may require fine tuning and local secret-handling.
work for different actions, and may require fine tunning and local secret-handling.
For those more intricate GHAs that are tricky to run locally, we recommend iterating
directly on GHA's infrastructure, by pushing directly on a branch and monitoring GHA logs.
For more targeted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
For more targetted iteration, see the `gh workflow run --ref {BRANCH}` subcommand of the GitHub CLI.
:::
For automation and CI/CD, Superset makes extensive use of GitHub Actions (GHA). You
@@ -631,7 +631,7 @@ can find all of the workflows and other assets under the `.github/` folder. This
- running the backend unit test suites (`tests/`)
- running the frontend test suites (`superset-frontend/src/**.*.test.*`)
- running our Playwright end-to-end tests (`superset-frontend/playwright/`) and legacy Cypress tests (`superset-frontend/cypress-base/`)
- running our Cypress end-to-end tests (`superset-frontend/cypress-base/`)
- linting the codebase, including all Python, Typescript and Javascript, yaml and beyond
- checking for all sorts of other rules conventions
@@ -747,26 +747,6 @@ To run a single test file:
npm run test -- path/to/file.js
```
#### Known Issues and Workarounds
**Jest Test Hanging (MessageChannel Issue)**
If Jest tests hang with "Jest did not exit one second after the test run has completed", this is likely due to the MessageChannel issue from rc-overflow (Ant Design v5 components).
**Root Cause**: `rc-overflow@1.4.1` creates MessageChannel handles for responsive overflow detection that remain open after test completion.
**Current Workaround**: MessageChannel is mocked as undefined in `spec/helpers/jsDomWithFetchAPI.ts`, forcing rc-overflow to use requestAnimationFrame fallback.
**To verify if still needed**: Remove the MessageChannel mocking lines and run `npm test -- --shard=4/8`. If tests hang, the workaround is still required.
**Future removal conditions**: This workaround can be removed when:
- rc-overflow updates to properly clean up MessagePorts in test environments
- Jest updates to handle MessageChannel/MessagePort cleanup better
- Ant Design switches away from rc-overflow
- We switch away from Ant Design v5
**See**: [PR #34871](https://github.com/apache/superset/pull/34871) for full technical details.
### Debugging Server App
#### Local

View File

@@ -225,57 +225,21 @@ npm run test -- path/to/file.js
### E2E Integration Testing
**Note: We are migrating from Cypress to Playwright. Use Playwright for new tests.**
#### Playwright (Recommended - NEW)
For E2E testing with Playwright, use the same `docker compose` backend:
For E2E testing, we recommend that you use a `docker compose` backend
```bash
CYPRESS_CONFIG=true docker compose up --build
```
`docker compose` will get to work and expose a Cypress-ready Superset app.
This app uses a different database schema (`superset_cypress`) to keep it isolated from
your other dev environmen(s)t, a specific set of examples, and a set of configurations that
aligns with the expectations within the end-to-end tests. Also note that it's served on a
different port than the default port for the backend (`8088`).
The backend setup is identical - this exposes a test-ready Superset app on port 8081 with isolated database schema (`superset_cypress`), test data, and configurations.
Now in another terminal, let's get ready to execute some Cypress commands. First, tell cypress
to connect to the Cypress-ready Superset backend.
Now in another terminal, run Playwright tests:
```bash
# Navigate to frontend directory (Playwright config is here)
cd superset-frontend
# Run all Playwright tests
npm run playwright:test
# or: npx playwright test
# Run with interactive UI for debugging
npm run playwright:ui
# or: npx playwright test --ui
# Run in headed mode (see browser)
npm run playwright:headed
# or: npx playwright test --headed
# Run specific test file
npx playwright test tests/auth/login.spec.ts
# Run with debug mode (step through tests)
npm run playwright:debug tests/auth/login.spec.ts
# or: npx playwright test --debug tests/auth/login.spec.ts
# Generate test report
npx playwright show-report
```
Configuration is in `superset-frontend/playwright.config.ts`. Base URL is automatically set to `http://localhost:8088` but will use `PLAYWRIGHT_BASE_URL` if provided.
#### Cypress (DEPRECATED - will be removed in Phase 5)
:::warning
Cypress is being phased out in favor of Playwright. Use Playwright for all new tests.
:::
```bash
# Set base URL for Cypress
CYPRESS_BASE_URL=http://localhost:8081
```
@@ -663,7 +627,7 @@ feature flag to `true`, you can add the following line to the PR body/descriptio
FEATURE_TAGGING_SYSTEM=true
```
Similarly, it's possible to disable feature flags with:
Simarly, it's possible to disable feature flags with:
```
FEATURE_TAGGING_SYSTEM=false

View File

@@ -86,6 +86,8 @@ USER root
ENV PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/playwright-browsers
# Install packages using uv into the virtual environment
# Superset started using uv after the 4.1 branch; if you are building from apache/superset:4.1.x or an older version,
# replace the first two lines with RUN pip install \
RUN . /app/.venv/bin/activate && \
uv pip install \
# install psycopg2 for using PostgreSQL metadata store - could be a MySQL package if using that backend:

View File

@@ -282,5 +282,5 @@ address.
When running `docker compose up`, docker will build what is required behind the scene, but
may use the docker cache if assets already exist. Running `docker compose build` prior to
`docker compose up` or the equivalent shortcut `docker compose up --build` ensures that your
docker images match the definition in the repository. This should only apply to the main
docker images matche the definition in the repository. This should only apply to the main
docker-compose.yml file (default) and not to the alternative methods defined above.

View File

@@ -20,125 +20,16 @@
import type { Config } from '@docusaurus/types';
import type { Options, ThemeConfig } from '@docusaurus/preset-classic';
import { themes } from 'prism-react-renderer';
import remarkImportPartial from 'remark-import-partial';
import * as fs from 'fs';
import * as path from 'path';
const { github: lightCodeTheme, vsDark: darkCodeTheme } = themes;
// Load version configuration from external file
const versionsConfigPath = path.join(__dirname, 'versions-config.json');
const versionsConfig = JSON.parse(fs.readFileSync(versionsConfigPath, 'utf8'));
// Build plugins array dynamically based on disabled flags
const dynamicPlugins = [];
// Add components plugin if not disabled
if (!versionsConfig.components.disabled) {
dynamicPlugins.push([
'@docusaurus/plugin-content-docs',
{
id: 'components',
path: 'components',
routeBasePath: 'components',
sidebarPath: require.resolve('./sidebarComponents.js'),
editUrl:
'https://github.com/apache/superset/edit/master/docs/components',
remarkPlugins: [remarkImportPartial],
docItemComponent: '@theme/DocItem',
includeCurrentVersion: versionsConfig.components.includeCurrentVersion,
lastVersion: versionsConfig.components.lastVersion,
onlyIncludeVersions: versionsConfig.components.onlyIncludeVersions,
versions: versionsConfig.components.versions,
disableVersioning: false,
showLastUpdateAuthor: true,
showLastUpdateTime: true,
},
]);
}
// Add developer_portal plugin if not disabled
if (!versionsConfig.developer_portal.disabled) {
dynamicPlugins.push([
'@docusaurus/plugin-content-docs',
{
id: 'developer_portal',
path: 'developer_portal',
routeBasePath: 'developer_portal',
sidebarPath: require.resolve('./sidebarTutorials.js'),
editUrl:
'https://github.com/apache/superset/edit/master/docs/developer_portal',
remarkPlugins: [remarkImportPartial],
docItemComponent: '@theme/DocItem',
includeCurrentVersion: versionsConfig.developer_portal.includeCurrentVersion,
lastVersion: versionsConfig.developer_portal.lastVersion,
onlyIncludeVersions: versionsConfig.developer_portal.onlyIncludeVersions,
versions: versionsConfig.developer_portal.versions,
disableVersioning: false,
showLastUpdateAuthor: true,
showLastUpdateTime: true,
},
]);
}
// Build navbar items dynamically based on disabled flags
const dynamicNavbarItems = [];
// Add Component Playground navbar item if not disabled
if (!versionsConfig.components.disabled) {
dynamicNavbarItems.push({
label: 'Component Playground',
to: '/components',
items: [
{
label: 'Introduction',
to: '/components',
},
{
label: 'UI Components',
to: '/components/ui-components/button',
},
{
label: 'Chart Components',
to: '/components/chart-components/bar-chart',
},
{
label: 'Layout Components',
to: '/components/layout-components/grid',
},
],
});
}
// Add Developer Portal navbar item if not disabled
if (!versionsConfig.developer_portal.disabled) {
dynamicNavbarItems.push({
label: 'Developer Portal',
position: 'left',
items: [
{
type: 'doc',
docsPluginId: 'developer_portal',
docId: 'index',
label: 'Introduction',
},
{
type: 'doc',
docsPluginId: 'developer_portal',
docId: 'getting-started/index',
label: 'Getting Started',
},
],
});
}
const config: Config = {
title: 'Superset',
tagline:
'Apache Superset is a modern data exploration and visualization platform',
url: 'https://superset.apache.org',
baseUrl: '/',
onBrokenLinks: 'warn',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'throw',
markdown: {
mermaid: true,
@@ -148,7 +39,6 @@ const config: Config = {
projectName: 'superset',
themes: ['@saucelabs/theme-github-codeblock', '@docusaurus/theme-mermaid'],
plugins: [
require.resolve('./src/webpack.extend.ts'),
[
'docusaurus-plugin-less',
{
@@ -157,10 +47,11 @@ const config: Config = {
},
},
],
...dynamicPlugins,
[
'@docusaurus/plugin-client-redirects',
{
fromExtensions: ['html', 'htm'],
toExtensions: ['exe', 'zip'],
redirects: [
{
to: '/docs/installation/docker-compose',
@@ -319,13 +210,6 @@ const config: Config = {
}
return `https://github.com/apache/superset/edit/master/docs/${versionDocsDirPath}/${docPath}`;
},
includeCurrentVersion: versionsConfig.docs.includeCurrentVersion,
lastVersion: versionsConfig.docs.lastVersion, // Make 'next' the default
onlyIncludeVersions: versionsConfig.docs.onlyIncludeVersions,
versions: versionsConfig.docs.versions,
disableVersioning: false,
showLastUpdateAuthor: true,
showLastUpdateTime: true,
},
blog: {
showReadingTime: true,
@@ -351,13 +235,6 @@ const config: Config = {
apiKey: 'd0d22810f2e9b614ffac3a73b26891fe',
indexName: 'superset-apache',
},
mermaid: {
theme: { light: 'neutral', dark: 'dark' },
options: {
// Any Mermaid config options go here...
maxTextSize: 100000,
},
},
navbar: {
logo: {
alt: 'Superset Logo',
@@ -367,22 +244,20 @@ const config: Config = {
items: [
{
label: 'Documentation',
position: 'left',
to: '/docs/intro',
items: [
{
type: 'doc',
docId: 'intro',
label: 'Getting Started',
to: '/docs/intro',
},
{
type: 'doc',
docId: 'faq',
label: 'FAQ',
to: '/docs/faq',
},
],
},
{
label: 'Community Resources',
label: 'Community',
to: '/community',
items: [
{
@@ -407,7 +282,6 @@ const config: Config = {
},
],
},
...dynamicNavbarItems,
{
href: '/docs/intro',
position: 'right',
@@ -462,6 +336,7 @@ const config: Config = {
// src: 'https://www.bugherd.com/sidebarv2.js?apikey=enilpiu7bgexxsnoqfjtxa',
// async: true,
// },
'/script/matomo.js',
{
src: 'https://widget.kapa.ai/kapa-widget.bundle.js',
async: true,

View File

@@ -6,8 +6,7 @@
"scripts": {
"docusaurus": "docusaurus",
"_init": "cat src/intro_header.txt ../README.md > docs/intro.md",
"start": "yarn run _init && NODE_ENV=development docusaurus start",
"stop": "pkill -f 'docusaurus start' || echo 'No docusaurus server running'",
"start": "yarn run _init && docusaurus start",
"build": "yarn run _init && DEBUG=docusaurus:* docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
@@ -16,43 +15,19 @@
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "tsc",
"eslint": "eslint .",
"version:add": "node scripts/manage-versions.mjs add",
"version:remove": "node scripts/manage-versions.mjs remove",
"version:add:docs": "node scripts/manage-versions.mjs add docs",
"version:add:developer_portal": "node scripts/manage-versions.mjs add developer_portal",
"version:add:components": "node scripts/manage-versions.mjs add components",
"version:remove:docs": "node scripts/manage-versions.mjs remove docs",
"version:remove:developer_portal": "node scripts/manage-versions.mjs remove developer_portal",
"version:remove:components": "node scripts/manage-versions.mjs remove components"
"eslint": "eslint ."
},
"dependencies": {
"@ant-design/icons": "^6.0.0",
"@docusaurus/core": "3.8.1",
"@docusaurus/plugin-client-redirects": "3.8.1",
"@docusaurus/preset-classic": "3.8.1",
"@docusaurus/theme-mermaid": "^3.8.1",
"@emotion/core": "^10.0.27",
"@emotion/react": "^11.13.3",
"@docusaurus/theme-mermaid": "3.8.1",
"@emotion/styled": "^10.0.27",
"@mdx-js/react": "^3.1.1",
"@saucelabs/theme-github-codeblock": "^0.3.0",
"@storybook/addon-docs": "^8.6.11",
"@storybook/blocks": "^8.6.11",
"@storybook/channels": "^8.6.11",
"@storybook/client-logger": "^8.6.11",
"@storybook/components": "^8.6.11",
"@storybook/core": "^8.6.11",
"@storybook/core-events": "^8.6.11",
"@storybook/csf": "^0.1.13",
"@storybook/docs-tools": "^8.6.11",
"@storybook/preview-api": "^8.6.11",
"@storybook/theming": "^8.6.11",
"@superset-ui/core": "^0.20.4",
"@superset-ui/style": "^0.14.23",
"antd": "^5.26.7",
"caniuse-lite": "^1.0.30001739",
"docusaurus-plugin-less": "^2.0.2",
"json-bigint": "^1.0.0",
"less": "^4.4.0",
"less-loader": "^12.3.0",
"prism-react-renderer": "^2.4.1",
@@ -60,12 +35,7 @@
"react-dom": "^18.3.1",
"react-github-btn": "^1.4.0",
"react-svg-pan-zoom": "^3.13.1",
"remark-import-partial": "^0.0.2",
"reselect": "^5.1.1",
"storybook": "^8.6.11",
"swagger-ui-react": "^5.27.1",
"tinycolor2": "^1.4.2",
"ts-loader": "^9.5.4"
"swagger-ui-react": "^5.27.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.8.1",
@@ -73,14 +43,14 @@
"@eslint/js": "^9.32.0",
"@types/react": "^19.1.8",
"@typescript-eslint/eslint-plugin": "^8.37.0",
"@typescript-eslint/parser": "^8.42.0",
"eslint": "^9.34.0",
"@typescript-eslint/parser": "^8.37.0",
"eslint": "^9.32.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.3.0",
"prettier": "^3.6.2",
"typescript": "~5.9.2",
"typescript": "~5.8.3",
"typescript-eslint": "^8.39.0",
"webpack": "^5.101.0"
},

View File

@@ -1,242 +0,0 @@
#!/usr/bin/env node
/**
* 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.
*/
import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const CONFIG_FILE = path.join(__dirname, '..', 'versions-config.json');
// Parse command line arguments
const args = process.argv.slice(2);
const command = args[0]; // 'add' or 'remove'
const section = args[1]; // 'docs', 'developer_portal', or 'components'
const version = args[2]; // version string like '1.2.0'
function loadConfig() {
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
}
function saveConfig(config) {
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
}
function fixVersionedImports(version) {
const versionedDocsPath = path.join(__dirname, '..', 'versioned_docs', `version-${version}`);
// Files that need import path fixes
const filesToFix = [
'contributing/resources.mdx',
'configuration/country-map-tools.mdx'
];
console.log(` Fixing relative imports in versioned docs...`);
filesToFix.forEach(filePath => {
const fullPath = path.join(versionedDocsPath, filePath);
if (fs.existsSync(fullPath)) {
let content = fs.readFileSync(fullPath, 'utf8');
// Fix imports that go up two directories to go up three instead
content = content.replace(
/from ['"]\.\.\/\.\.\/src\//g,
"from '../../../src/"
);
content = content.replace(
/from ['"]\.\.\/\.\.\/data\//g,
"from '../../../data/"
);
fs.writeFileSync(fullPath, content);
console.log(` Fixed imports in ${filePath}`);
}
});
}
function addVersion(section, version) {
const config = loadConfig();
if (!config[section]) {
console.error(`Section '${section}' not found in config`);
process.exit(1);
}
// Check if version already exists
if (config[section].onlyIncludeVersions.includes(version)) {
console.error(`Version ${version} already exists in ${section}`);
process.exit(1);
}
console.log(`Creating version ${version} for ${section}...`);
// Run Docusaurus version command
const docusaurusCommand = section === 'docs'
? `yarn docusaurus docs:version ${version}`
: `yarn docusaurus docs:version:${section} ${version}`;
try {
execSync(docusaurusCommand, { stdio: 'inherit' });
} catch (error) {
console.error(`Failed to create version: ${error.message}`);
process.exit(1);
}
// Fix relative imports in versioned docs (for main docs section only)
if (section === 'docs') {
fixVersionedImports(version);
}
// Update config
// Add to onlyIncludeVersions array (after 'current')
const versionIndex = config[section].onlyIncludeVersions.indexOf('current') + 1;
config[section].onlyIncludeVersions.splice(versionIndex, 0, version);
// Add version metadata
const versionPath = section === 'docs' ? version : version;
config[section].versions[version] = {
label: version,
path: versionPath,
banner: 'none'
};
// Optionally update lastVersion if this is the first non-current version
if (config[section].onlyIncludeVersions.length === 2) {
config[section].lastVersion = version;
}
saveConfig(config);
console.log(`✅ Version ${version} added successfully to ${section}`);
console.log(`📝 Updated versions-config.json`);
}
function removeVersion(section, version) {
const config = loadConfig();
if (!config[section]) {
console.error(`Section '${section}' not found in config`);
process.exit(1);
}
if (version === 'current') {
console.error(`Cannot remove 'current' version`);
process.exit(1);
}
if (!config[section].onlyIncludeVersions.includes(version)) {
console.error(`Version ${version} not found in ${section}`);
process.exit(1);
}
console.log(`Removing version ${version} from ${section}...`);
// Determine file paths based on section
const versionedDocsDir = section === 'docs'
? `versioned_docs/version-${version}`
: `${section}_versioned_docs/version-${version}`;
const versionedSidebarsFile = section === 'docs'
? `versioned_sidebars/version-${version}-sidebars.json`
: `${section}_versioned_sidebars/version-${version}-sidebars.json`;
// Remove versioned files
const docsPath = path.join(__dirname, '..', versionedDocsDir);
const sidebarsPath = path.join(__dirname, '..', versionedSidebarsFile);
if (fs.existsSync(docsPath)) {
fs.rmSync(docsPath, { recursive: true });
console.log(` Removed ${versionedDocsDir}`);
}
if (fs.existsSync(sidebarsPath)) {
fs.unlinkSync(sidebarsPath);
console.log(` Removed ${versionedSidebarsFile}`);
}
// Update versions.json file
const versionsJsonFile = section === 'docs'
? 'versions.json'
: `${section}_versions.json`;
const versionsJsonPath = path.join(__dirname, '..', versionsJsonFile);
if (fs.existsSync(versionsJsonPath)) {
const versions = JSON.parse(fs.readFileSync(versionsJsonPath, 'utf8'));
const versionIndex = versions.indexOf(version);
if (versionIndex > -1) {
versions.splice(versionIndex, 1);
fs.writeFileSync(versionsJsonPath, JSON.stringify(versions, null, 2) + '\n');
console.log(` Updated ${versionsJsonFile}`);
}
}
// Update config
const versionIndex = config[section].onlyIncludeVersions.indexOf(version);
config[section].onlyIncludeVersions.splice(versionIndex, 1);
delete config[section].versions[version];
// Update lastVersion if needed
if (config[section].lastVersion === version) {
// Set to the next available version or 'current'
const remainingVersions = config[section].onlyIncludeVersions.filter(v => v !== 'current');
config[section].lastVersion = remainingVersions.length > 0 ? remainingVersions[0] : 'current';
console.log(` Updated lastVersion to ${config[section].lastVersion}`);
}
saveConfig(config);
console.log(`✅ Version ${version} removed successfully from ${section}`);
console.log(`📝 Updated versions-config.json`);
}
function printUsage() {
console.log(`
Usage:
node scripts/manage-versions.js add <section> <version>
node scripts/manage-versions.js remove <section> <version>
Where:
- section: 'docs', 'developer_portal', or 'components'
- version: version string (e.g., '1.2.0', '2.0.0')
Examples:
node scripts/manage-versions.js add docs 2.0.0
node scripts/manage-versions.js add developer_portal 1.3.0
node scripts/manage-versions.js remove components 1.0.0
`);
}
// Main execution
if (!command || !section || !version) {
printUsage();
process.exit(1);
}
if (command === 'add') {
addVersion(section, version);
} else if (command === 'remove') {
removeVersion(section, version);
} else {
console.error(`Unknown command: ${command}`);
printUsage();
process.exit(1);
}

View File

@@ -1,68 +0,0 @@
/* eslint-env node */
/**
* 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.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
//tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
// But we're not doing that.
ComponentSidebar: [
{
type: 'doc',
label: 'Introduction',
id: 'index',
},
{
type: 'category',
label: 'UI Components',
items: [
{
type: 'autogenerated',
dirName: 'ui-components',
},
],
},
{
type: 'category',
label: 'Chart Components',
items: [
{
type: 'autogenerated',
dirName: 'chart-components',
},
],
},
{
type: 'category',
label: 'Layout Components',
items: [
{
type: 'autogenerated',
dirName: 'layout-components',
},
],
},
],
};
module.exports = sidebars;

View File

@@ -1,23 +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.
*/
// Re-export the Button component as a default export
import { Button } from '../../../superset-frontend/packages/superset-ui-core/src/components/Button';
export default Button;

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