Compare commits

..

1 Commits

Author SHA1 Message Date
Evan Rusackas
4ae0bc9ade feat(extensions): add security trust configuration and signature verification
Implements a comprehensive security system for Superset extensions:

Backend:
- Add EXTENSIONS_TRUST_CONFIG to superset_config.py for admin control
- Create ExtensionSecurityManager for trust validation and signature verification
- Support Ed25519 signatures for extension manifests
- Integrate trust validation into extension loading pipeline

CLI:
- Add `generate-keys` command for creating Ed25519 signing keypairs
- Add `sign` command and `--sign` option to `bundle` for manifest signing

Frontend:
- Add WASM support to webpack config for QuickJS sandbox
- Update Extension interface with trust-related fields
- ExtensionsManager now uses backend-validated trust levels

Documentation:
- Add Administrator Configuration guide for trust settings
- Add Extension Signing guide for developers
- Update security.md and sandbox.md with cross-references
- Add Security subcategory to sidebar

Tests:
- Add 21 unit tests for trust validation and signature verification

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-07 23:43:07 -08:00
736 changed files with 18566 additions and 82759 deletions

2
.github/CODEOWNERS vendored
View File

@@ -20,7 +20,7 @@
# Notify PMC members of changes to GitHub Actions
/.github/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar @sadpandajoe @hainenber
/.github/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar @sadpandajoe
# Notify PMC members of changes to required GitHub Actions

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- name: Check if the PR is a draft
id: check-draft
uses: actions/github-script@v8
uses: actions/github-script@v6
with:
script: |
const isDraft = context.payload.pull_request.draft;

View File

@@ -19,10 +19,6 @@ updates:
# Source: https://jestjs.io/blog#known-issues
# GH thread: https://github.com/jsdom/jsdom/issues/3492
- dependency-name: "jest-environment-jsdom"
# `@swc/plugin-transform-imports` doesn't work with current Webpack-SWC hybrid setup
# See https://github.com/apache/superset/pull/37384#issuecomment-3793991389
# TODO: remove the plugin once Lodash usage has been migrated to a more readily tree-shakeable alternative
- dependency-name: "@swc/plugin-transform-imports"
directory: "/superset-frontend/"
schedule:
interval: "daily"

View File

@@ -33,7 +33,7 @@ jobs:
pull-requests: write
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
uses: aws-actions/configure-aws-credentials@v5
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

View File

@@ -189,7 +189,7 @@ jobs:
--extra-flags "--build-arg INCLUDE_CHROMIUM=false"
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
uses: aws-actions/configure-aws-credentials@v5
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
@@ -225,7 +225,7 @@ jobs:
persist-credentials: false
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
uses: aws-actions/configure-aws-credentials@v5
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

View File

@@ -68,7 +68,7 @@ jobs:
yarn install --check-cache
- name: Download database diagnostics (if triggered by integration tests)
if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success'
uses: dawidd6/action-download-artifact@v14
uses: dawidd6/action-download-artifact@v6
continue-on-error: true
with:
workflow: superset-python-integrationtest.yml
@@ -77,7 +77,7 @@ jobs:
path: docs/src/data/
- name: Try to download latest diagnostics (for push/dispatch triggers)
if: github.event_name != 'workflow_run'
uses: dawidd6/action-download-artifact@v14
uses: dawidd6/action-download-artifact@v6
continue-on-error: true
with:
workflow: superset-python-integrationtest.yml

View File

@@ -27,7 +27,7 @@ jobs:
- uses: actions/checkout@v6
# Do not bump this linkinator-action version without opening
# an ASF Infra ticket to allow the new version first!
- uses: JustinBeckwith/linkinator-action@f62ba0c110a76effb2ee6022cc6ce4ab161085e3 # v2.4
- uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
continue-on-error: true # This will make the job advisory (non-blocking, no red X)
with:
paths: "**/*.md, **/*.mdx"
@@ -111,7 +111,7 @@ jobs:
run: |
yarn install --check-cache
- name: Download database diagnostics from integration tests
uses: dawidd6/action-download-artifact@v14
uses: dawidd6/action-download-artifact@v6
with:
workflow: superset-python-integrationtest.yml
run_id: ${{ github.event.workflow_run.id }}

View File

@@ -98,7 +98,7 @@ jobs:
"
- name: Upload database diagnostics artifact
if: steps.check.outputs.python
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: database-diagnostics
path: databases-diagnostics.json

View File

@@ -52,7 +52,6 @@ jobs:
SUPERSET_SECRET_KEY: not-a-secret
run: |
pytest --durations-min=0.5 --cov=superset/sql/ ./tests/unit_tests/sql/ --cache-clear --cov-fail-under=100
pytest --durations-min=0.5 --cov=superset/semantic_layers/ ./tests/unit_tests/semantic_layers/ --cache-clear --cov-fail-under=100
- name: Upload code coverage
uses: codecov/codecov-action@v5
with:

5
.gitignore vendored
View File

@@ -61,7 +61,6 @@ tmp
rat-results.txt
superset/app/
superset-websocket/config.json
.direnv
# Node.js, webpack artifacts, storybook
*.entry.js
@@ -73,6 +72,10 @@ superset/static/assets/*
superset/static/uploads/*
!superset/static/uploads/.gitkeep
superset/static/version_info.json
superset-frontend/**/esm/*
superset-frontend/**/lib/*
superset-frontend/**/storybook-static/*
superset-frontend/migration-storybook.log
yarn-error.log
*.map
*.min.js

View File

@@ -27,7 +27,6 @@ repos:
args: [--check-untyped-defs]
exclude: ^superset-extensions-cli/
additional_dependencies: [
types-cachetools,
types-simplejson,
types-python-dateutil,
types-requests,

View File

@@ -67,15 +67,25 @@ temporary_superset_ui/*
# skip license checks for auto-generated test snapshots
.*snap
# docs third-party logos (database logos, org logos, etc.)
databases/*
# docs overrides for third party logos we don't have the rights to
google-big-query.svg
google-sheets.svg
ibm-db2.svg
postgresql.svg
snowflake.svg
ydb.svg
loading.svg
apache-solr.svg
azure.svg
superset.svg
# docs third-party logos, i.e. docs/static/img/logos/*
logos/*
# docs-related
erd.puml
erd.svg
intro_header.txt
TODO.md
# for LLMs
llm-context.md

View File

@@ -101,30 +101,6 @@ superset/
- **UPDATING.md**: Add breaking changes here
- **Docstrings**: Required for new functions/classes
## Developer Portal: Storybook-to-MDX Documentation
The Developer Portal auto-generates MDX documentation from Storybook stories. **Stories are the single source of truth.**
### Core Philosophy
- **Fix issues in the STORY, not the generator** - When something doesn't render correctly, update the story file first
- **Generator should be lightweight** - It extracts and passes through data; avoid special cases
- **Stories define everything** - Props, controls, galleries, examples all come from story metadata
### Story Requirements for Docs Generation
- Use `export default { title: '...' }` (inline), not `const meta = ...; export default meta;`
- Name interactive stories `Interactive${ComponentName}` (e.g., `InteractiveButton`)
- Define `args` for default prop values
- Define `argTypes` at the story level (not meta level) with control types and descriptions
- Use `parameters.docs.gallery` for size×style variant grids
- Use `parameters.docs.sampleChildren` for components that need children
- Use `parameters.docs.liveExample` for custom live code blocks
- Use `parameters.docs.staticProps` for complex object props that can't be parsed inline
### Generator Location
- Script: `docs/scripts/generate-superset-components.mjs`
- Wrapper: `docs/src/components/StorybookWrapper.jsx`
- Output: `docs/developer_portal/components/`
## Architecture Patterns
### Security & Features

101
README.md
View File

@@ -89,7 +89,7 @@ Superset provides:
**Craft Beautiful, Dynamic Dashboards**
<kbd><img title="View Dashboards" src="https://superset.apache.org/img/screenshots/dashboard.jpg"/></kbd><br/>
<kbd><img title="View Dashboards" src="https://superset.apache.org/img/screenshots/slack_dash.jpg"/></kbd><br/>
**No-Code Chart Builder**
@@ -107,67 +107,44 @@ Here are some of the major database solutions that are supported:
<!-- SUPPORTED_DATABASES_START -->
<p align="center">
<a href="https://superset.apache.org/docs/databases/supported/amazon-athena" title="Amazon Athena"><img src="docs/static/img/databases/amazon-athena.jpg" alt="Amazon Athena" width="76" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/amazon-dynamodb" title="Amazon DynamoDB"><img src="docs/static/img/databases/aws.png" alt="Amazon DynamoDB" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/amazon-redshift" title="Amazon Redshift"><img src="docs/static/img/databases/redshift.png" alt="Amazon Redshift" width="100" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-doris" title="Apache Doris"><img src="docs/static/img/databases/doris.png" alt="Apache Doris" width="103" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-drill" title="Apache Drill"><img src="docs/static/img/databases/apache-drill.png" alt="Apache Drill" width="81" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-druid" title="Apache Druid"><img src="docs/static/img/databases/druid.png" alt="Apache Druid" width="117" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-hive" title="Apache Hive"><img src="docs/static/img/databases/apache-hive.svg" alt="Apache Hive" width="44" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-impala" title="Apache Impala"><img src="docs/static/img/databases/apache-impala.png" alt="Apache Impala" width="21" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-kylin" title="Apache Kylin"><img src="docs/static/img/databases/apache-kylin.png" alt="Apache Kylin" width="44" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-pinot" title="Apache Pinot"><img src="docs/static/img/databases/apache-pinot.svg" alt="Apache Pinot" width="76" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-solr" title="Apache Solr"><img src="docs/static/img/databases/apache-solr.png" alt="Apache Solr" width="79" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/apache-spark-sql" title="Apache Spark SQL"><img src="docs/static/img/databases/apache-spark.png" alt="Apache Spark SQL" width="75" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/ascend" title="Ascend"><img src="docs/static/img/databases/ascend.webp" alt="Ascend" width="117" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/aurora-mysql-data-api" title="Aurora MySQL (Data API)"><img src="docs/static/img/databases/mysql.png" alt="Aurora MySQL (Data API)" width="77" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/aurora-postgresql-data-api" title="Aurora PostgreSQL (Data API)"><img src="docs/static/img/databases/postgresql.svg" alt="Aurora PostgreSQL (Data API)" width="76" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/azure-data-explorer" title="Azure Data Explorer"><img src="docs/static/img/databases/kusto.png" alt="Azure Data Explorer" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/azure-synapse" title="Azure Synapse"><img src="docs/static/img/databases/azure.svg" alt="Azure Synapse" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/clickhouse" title="ClickHouse"><img src="docs/static/img/databases/clickhouse.png" alt="ClickHouse" width="150" height="37" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/cloudflare-d1" title="Cloudflare D1"><img src="docs/static/img/databases/cloudflare.png" alt="Cloudflare D1" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/cockroachdb" title="CockroachDB"><img src="docs/static/img/databases/cockroachdb.png" alt="CockroachDB" width="150" height="24" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/couchbase" title="Couchbase"><img src="docs/static/img/databases/couchbase.svg" alt="Couchbase" width="150" height="35" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/cratedb" title="CrateDB"><img src="docs/static/img/databases/cratedb.svg" alt="CrateDB" width="180" height="24" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/databend" title="Databend"><img src="docs/static/img/databases/databend.png" alt="Databend" width="100" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/databricks" title="Databricks"><img src="docs/static/img/databases/databricks.png" alt="Databricks" width="152" height="24" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/denodo" title="Denodo"><img src="docs/static/img/databases/denodo.png" alt="Denodo" width="138" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/dremio" title="Dremio"><img src="docs/static/img/databases/dremio.png" alt="Dremio" width="126" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/duckdb" title="DuckDB"><img src="docs/static/img/databases/duckdb.png" alt="DuckDB" width="52" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/elasticsearch" title="Elasticsearch"><img src="docs/static/img/databases/elasticsearch.png" alt="Elasticsearch" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/exasol" title="Exasol"><img src="docs/static/img/databases/exasol.png" alt="Exasol" width="72" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/firebird" title="Firebird"><img src="docs/static/img/databases/firebird.png" alt="Firebird" width="100" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/firebolt" title="Firebolt"><img src="docs/static/img/databases/firebolt.png" alt="Firebolt" width="100" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/google-bigquery" title="Google BigQuery"><img src="docs/static/img/databases/google-big-query.svg" alt="Google BigQuery" width="76" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/google-sheets" title="Google Sheets"><img src="docs/static/img/databases/google-sheets.svg" alt="Google Sheets" width="76" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/greenplum" title="Greenplum"><img src="docs/static/img/databases/greenplum.png" alt="Greenplum" width="124" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/hologres" title="Hologres"><img src="docs/static/img/databases/hologres.png" alt="Hologres" width="44" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/ibm-db2" title="IBM Db2"><img src="docs/static/img/databases/ibm-db2.svg" alt="IBM Db2" width="91" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/ibm-netezza-performance-server" title="IBM Netezza Performance Server"><img src="docs/static/img/databases/netezza.png" alt="IBM Netezza Performance Server" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/mariadb" title="MariaDB"><img src="docs/static/img/databases/mariadb.png" alt="MariaDB" width="150" height="37" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/microsoft-sql-server" title="Microsoft SQL Server"><img src="docs/static/img/databases/msql.png" alt="Microsoft SQL Server" width="50" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/monetdb" title="MonetDB"><img src="docs/static/img/databases/monet-db.png" alt="MonetDB" width="100" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/mongodb" title="MongoDB"><img src="docs/static/img/databases/mongodb.png" alt="MongoDB" width="150" height="38" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/motherduck" title="MotherDuck"><img src="docs/static/img/databases/motherduck.png" alt="MotherDuck" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/oceanbase" title="OceanBase"><img src="docs/static/img/databases/oceanbase.svg" alt="OceanBase" width="175" height="24" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/oracle" title="Oracle"><img src="docs/static/img/databases/oraclelogo.png" alt="Oracle" width="111" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/presto" title="Presto"><img src="docs/static/img/databases/presto-og.png" alt="Presto" width="127" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/risingwave" title="RisingWave"><img src="docs/static/img/databases/risingwave.svg" alt="RisingWave" width="147" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/sap-hana" title="SAP HANA"><img src="docs/static/img/databases/sap-hana.png" alt="SAP HANA" width="137" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/sap-sybase" title="SAP Sybase"><img src="docs/static/img/databases/sybase.png" alt="SAP Sybase" width="100" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/shillelagh" title="Shillelagh"><img src="docs/static/img/databases/shillelagh.png" alt="Shillelagh" width="40" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/singlestore" title="SingleStore"><img src="docs/static/img/databases/singlestore.png" alt="SingleStore" width="150" height="31" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/snowflake" title="Snowflake"><img src="docs/static/img/databases/snowflake.svg" alt="Snowflake" width="76" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/sqlite" title="SQLite"><img src="docs/static/img/databases/sqlite.png" alt="SQLite" width="84" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/starrocks" title="StarRocks"><img src="docs/static/img/databases/starrocks.png" alt="StarRocks" width="149" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/superset-meta-database" title="Superset meta database"><img src="docs/static/img/databases/superset.svg" alt="Superset meta database" width="150" height="39" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/tdengine" title="TDengine"><img src="docs/static/img/databases/tdengine.png" alt="TDengine" width="140" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/teradata" title="Teradata"><img src="docs/static/img/databases/teradata.png" alt="Teradata" width="124" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/timescaledb" title="TimescaleDB"><img src="docs/static/img/databases/timescale.png" alt="TimescaleDB" width="150" height="36" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/trino" title="Trino"><img src="docs/static/img/databases/trino.png" alt="Trino" width="89" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/vertica" title="Vertica"><img src="docs/static/img/databases/vertica.png" alt="Vertica" width="128" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/ydb" title="YDB"><img src="docs/static/img/databases/ydb.svg" alt="YDB" width="110" height="40" /></a> &nbsp;
<a href="https://superset.apache.org/docs/databases/supported/yugabytedb" title="YugabyteDB"><img src="docs/static/img/databases/yugabyte.png" alt="YugabyteDB" width="150" height="26" /></a>
<img src="https://superset.apache.org/img/databases/doris.png" alt="apache-doris" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/apache-drill.png" alt="apache-drill" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/druid.png" alt="apache-druid" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/apache-hive.svg" alt="apache-hive" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/apache-impala.png" alt="apache-impala" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/apache-kylin.png" alt="apache-kylin" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/apache-pinot.svg" alt="apache-pinot" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/amazon-athena.jpg" alt="aws-athena" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/redshift.png" alt="aws-redshift" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/clickhouse.png" alt="clickhouse" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/couchbase.svg" alt="couchbase" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/databend.png" alt="databend" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/databricks.png" alt="databricks" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/denodo.png" alt="denodo" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/dremio.png" alt="dremio" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/exasol.png" alt="exasol" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/firebird.png" alt="firebird" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/firebolt.png" alt="firebolt" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/google-big-query.svg" alt="google-bigquery" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/google-sheets.svg" alt="google-sheets" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/ibm-db2.svg" alt="ibm-db2" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/netezza.png" alt="ibm-netezza" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/mariadb.png" alt="mariadb" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/msql.png" alt="microsoft-sql-server" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/mysql.png" alt="mysql" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/oceanbase.svg" alt="oceanbase" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/oraclelogo.png" alt="oracle" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/postgresql.svg" alt="postgresql" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/presto-og.png" alt="presto" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/sap-hana.png" alt="sap-hana" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/snowflake.svg" alt="snowflake" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/sqlite.png" alt="sqlite" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/starrocks.png" alt="starrocks" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/tdengine.png" alt="tdengine" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/teradata.png" alt="teradata" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/trino.png" alt="trino" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/vertica.png" alt="vertica" border="0" width="120" height="60" class="database-logo" />
<img src="https://superset.apache.org/img/databases/ydb.svg" alt="ydb" border="0" width="120" height="60" class="database-logo" />
</p>
<!-- SUPPORTED_DATABASES_END -->

View File

@@ -136,6 +136,10 @@ categories:
url: https://www.dropit.shop/
contributors: ["@dropit-dev"]
- name: Fanatics
url: https://www.fanatics.com/
contributors: ["@coderfender"]
- name: Fordeal
url: https://www.fordeal.com
contributors: ["@Renkai"]
@@ -287,10 +291,8 @@ categories:
url: https://www.gfk.com/home
contributors: ["@mherr"]
# Logo approved by @anmol-hpe on behalf of HPE
- name: HPE
url: https://www.hpe.com/in/en/home.html
logo: hpe.png
contributors: ["@anmol-hpe"]
- name: Hydrolix
@@ -430,11 +432,6 @@ categories:
url: https://brandct.cn/
contributors: ["@wenbinye"]
- name: XNET
url: https://xnetmobile.com/
logo: xnet.png
contributors: ["@deuspt"]
- name: Zeta
url: https://www.zeta.tech/
contributors: ["@shaikidris"]
@@ -625,20 +622,6 @@ categories:
- name: Stockarea
url: https://stockarea.io
Sports:
- name: Club 25 de Agosto (Femenino / Women's Team)
url: https://www.instagram.com/25deagosto.basketfemenino/
contributors: [ "@lion90" ]
logo: club25deagosto.svg
- name: Fanatics
url: https://www.fanatics.com/
contributors: [ "@coderfender" ]
- name: komoot
url: https://www.komoot.com/
contributors: [ "@christophlingg" ]
Others:
- name: 10Web
url: https://10web.io/
@@ -674,6 +657,10 @@ categories:
url: https://www.increff.com/
contributors: ["@ishansinghania"]
- name: komoot
url: https://www.komoot.com/
contributors: ["@christophlingg"]
- name: Let's Roam
url: https://www.letsroam.com/

View File

@@ -24,21 +24,6 @@ assists people when migrating to a new version.
## Next
### WebSocket config for GAQ with Docker
[35896](https://github.com/apache/superset/pull/35896) and [37624](https://github.com/apache/superset/pull/37624) updated documentation on how to run and configure Superset with Docker. Specifically for the WebSocket configuration, a new `docker/superset-websocket/config.example.json` was added to the repo, so that users could copy it to create a `docker/superset-websocket/config.json` file. The existing `docker/superset-websocket/config.json` was removed and git-ignored, so if you're using GAQ / WebSocket make sure to:
- Stash/backup your existing `config.json` file, to re-apply it after (will get git-ignored going forward)
- Update the `volumes` configuration for the `superset-websocket` service in your `docker-compose.override.yml` file, to include the `docker/superset-websocket/config.json` file. For example:
``` yaml
services:
superset-websocket:
volumes:
- ./superset-websocket:/home/superset-websocket
- /home/superset-websocket/node_modules
- /home/superset-websocket/dist
- ./docker/superset-websocket/config.json:/home/superset-websocket/config.json:ro
```
### Example Data Loading Improvements
#### New Directory Structure
@@ -178,28 +163,6 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
### Breaking Changes
- [37370](https://github.com/apache/superset/pull/37370): The `APP_NAME` configuration variable no longer controls the browser window/tab title or other frontend branding. Application names should now be configured using the theme system with the `brandAppName` token. The `APP_NAME` config is still used for backend contexts (MCP service, logs, etc.) and serves as a fallback if `brandAppName` is not set.
- **Migration:**
```python
# Before (Superset 5.x)
APP_NAME = "My Custom App"
# After (Superset 6.x) - Option 1: Use theme system (recommended)
THEME_DEFAULT = {
"token": {
"brandAppName": "My Custom App", # Window titles
"brandLogoAlt": "My Custom App", # Logo alt text
"brandLogoUrl": "/static/assets/images/custom_logo.png"
}
}
# After (Superset 6.x) - Option 2: Temporary fallback
# Keep APP_NAME for now (will be used as fallback for brandAppName)
APP_NAME = "My Custom App"
# But you should migrate to THEME_DEFAULT.token.brandAppName
```
- **Note:** For dark mode, set the same tokens in `THEME_DARK` configuration.
- [36317](https://github.com/apache/superset/pull/36317): The `CUSTOM_FONT_URLS` configuration option has been removed. Use the new per-theme `fontUrls` token in `THEME_DEFAULT` or database-managed themes instead.
- **Before:**
```python
@@ -214,7 +177,7 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
"fontUrls": [
"https://fonts.example.com/myfont.css",
],
# ... other tokens
# ... other tokens
}
}
```

View File

@@ -159,8 +159,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 must bind to 0.0.0.0 to be accessible from outside the container
WEBPACK_DEVSERVER_HOST: "${WEBPACK_DEVSERVER_HOST:-0.0.0.0}"
# Webpack dev server configuration
WEBPACK_DEVSERVER_HOST: "${WEBPACK_DEVSERVER_HOST:-127.0.0.1}"
WEBPACK_DEVSERVER_PORT: "${WEBPACK_DEVSERVER_PORT:-9000}"
ports:
- "${NODE_PORT:-9001}:9000" # Parameterized port, accessible on all interfaces

View File

@@ -175,7 +175,7 @@ services:
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
# configuring the dev-server to use the host.docker.internal to connect to the backend
superset: "http://superset:8088"
# Webpack dev server must bind to 0.0.0.0 to be accessible from outside the container
# Bind to all interfaces so Docker port mapping works
WEBPACK_DEVSERVER_HOST: "0.0.0.0"
ports:
- "127.0.0.1:${NODE_PORT:-9000}:9000" # exposing the dynamic webpack dev server

View File

@@ -28,11 +28,11 @@ if [ "$BUILD_SUPERSET_FRONTEND_IN_DOCKER" = "true" ]; then
cd /app/superset-frontend
if [ "$NPM_RUN_PRUNE" = "true" ]; then
echo "Running \"npm run prune\""
echo "Running `npm run prune`"
npm run prune
fi
echo "Running \"npm install\""
echo "Running `npm install`"
npm install
echo "Start webpack dev server"

View File

@@ -105,12 +105,7 @@ class CeleryConfig:
CELERY_CONFIG = CeleryConfig
FEATURE_FLAGS = {
"ALERT_REPORTS": True,
"DATASET_FOLDERS": True,
"ENABLE_EXTENSIONS": True,
}
EXTENSIONS_PATH = "/app/docker/extensions"
FEATURE_FLAGS = {"ALERT_REPORTS": True}
ALERT_REPORTS_NOTIFICATION_DRY_RUN = True
WEBDRIVER_BASEURL = f"http://superset_app{os.environ.get('SUPERSET_APP_ROOT', '/')}/" # When using docker compose baseurl should be http://superset_nginx{ENV{BASEPATH}}/ # noqa: E501
# The base URL for the email report hyperlinks.

View File

@@ -0,0 +1,22 @@
{
"port": 8080,
"logLevel": "info",
"logToFile": false,
"logFilename": "app.log",
"statsd": {
"host": "127.0.0.1",
"port": 8125,
"globalTags": []
},
"redis": {
"port": 6379,
"host": "127.0.0.1",
"password": "",
"db": 0,
"ssl": false
},
"redisStreamPrefix": "async-events-",
"jwtAlgorithms": ["HS256"],
"jwtSecret": "CHANGE-ME-IN-PRODUCTION-GOTTA-BE-LONG-AND-SECRET",
"jwtCookieName": "async-token"
}

View File

@@ -1,115 +0,0 @@
# Developer Portal Documentation Instructions
## Core Principle: Stories Are the Single Source of Truth
When working on the Storybook-to-MDX documentation system:
**ALWAYS fix the story first. NEVER add workarounds to the generator.**
## Why This Matters
The generator (`scripts/generate-superset-components.mjs`) should be lightweight - it extracts data from stories and passes it through. When you add special cases to the generator:
- It becomes harder to maintain
- Stories diverge from their docs representation
- Future stories need to know about generator quirks
When you fix stories to match the expected patterns:
- Stories work identically in Storybook and Docs
- The generator stays simple and predictable
- Patterns are consistent and learnable
## Story Patterns for Docs Generation
### Required Structure
```tsx
// Use inline export default (NOT const meta = ...; export default meta)
export default {
title: 'Components/MyComponent',
component: MyComponent,
};
// Name interactive stories with Interactive prefix
export const InteractiveMyComponent: Story = {
args: {
// Default prop values
},
argTypes: {
// Control definitions - MUST be at story level, not meta level
propName: {
control: { type: 'select' },
options: ['a', 'b', 'c'],
description: 'What this prop does',
},
},
};
```
### For Components with Variants (size × style grids)
```tsx
const sizes = ['small', 'medium', 'large'];
const variants = ['primary', 'secondary', 'danger'];
InteractiveButton.parameters = {
docs: {
gallery: {
component: 'Button',
sizes,
styles: variants,
sizeProp: 'size',
styleProp: 'variant',
},
},
};
```
### For Components Requiring Children
```tsx
InteractiveIconTooltip.parameters = {
docs: {
// Component descriptors with dot notation for nested components
sampleChildren: [{ component: 'Icons.InfoCircleOutlined', props: { iconSize: 'l' } }],
},
};
```
### For Custom Live Code Examples
```tsx
InteractiveMyComponent.parameters = {
docs: {
liveExample: `function Demo() {
return <MyComponent prop="value">Content</MyComponent>;
}`,
},
};
```
### For Complex Props (objects, arrays)
```tsx
InteractiveMenu.parameters = {
docs: {
staticProps: {
items: [
{ key: '1', label: 'Item 1' },
{ key: '2', label: 'Item 2' },
],
},
},
};
```
## Common Issues and How to Fix Them (in the Story)
| Issue | Wrong Approach | Right Approach |
|-------|---------------|----------------|
| Component not generated | Add pattern to generator | Change story to use inline `export default` |
| Control shows as text instead of select | Add special case in generator | Add `argTypes` with `control: { type: 'select' }` |
| Missing children/content | Modify StorybookWrapper | Add `parameters.docs.sampleChildren` |
| Gallery not showing | Add to generator output | Add `parameters.docs.gallery` config |
| Wrong live example | Hardcode in generator | Add `parameters.docs.liveExample` |
## Files
- **Generator**: `docs/scripts/generate-superset-components.mjs`
- **Wrapper**: `docs/src/components/StorybookWrapper.jsx`
- **Output**: `docs/developer_portal/components/`
- **Stories**: `superset-frontend/packages/superset-ui-core/src/components/*/`

11
docs/.gitignore vendored
View File

@@ -31,16 +31,5 @@ static/badges/
# Source of truth is in superset/db_engine_specs/*.py metadata attributes
docs/databases/
# Generated API documentation (regenerated at build time from openapi.json)
# Source of truth is static/resources/openapi.json
docs/api/
# Generated component documentation MDX files (regenerated at build time)
# Source of truth is Storybook stories in superset-frontend/packages/superset-ui-core/src/components/
developer_portal/components/
# Generated extension component documentation (regenerated at build time)
developer_portal/extensions/components/
# Note: src/data/databases.json is COMMITTED (not ignored) to preserve feature diagnostics
# that require Flask context to generate. Update it locally with: npm run gen-db-docs

View File

@@ -19,14 +19,5 @@
*/
module.exports = {
presets: [
[
require.resolve('@docusaurus/core/lib/babel/preset'),
{
runtime: 'automatic',
importSource: '@emotion/react',
},
],
],
plugins: ['@emotion/babel-plugin'],
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};

View File

@@ -258,7 +258,19 @@ For debugging the Flask backend:
### Storybook
See the dedicated [Storybook documentation](../testing/storybook) for information on running Storybook locally and adding new stories.
Storybook is used for developing and testing UI components in isolation:
```bash
cd superset-frontend
# Start Storybook
npm run storybook
# Build static Storybook
npm run build-storybook
```
Access Storybook at http://localhost:6006
## Contributing Translations

View File

@@ -0,0 +1,248 @@
---
title: Administrator Configuration
sidebar_position: 12
---
<!--
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 Administrator Configuration
This guide covers how to configure extension security for production deployments. As an administrator, you control which extensions can run and at what trust level.
## Trust Configuration
Configure extension trust in `superset_config.py`:
```python
EXTENSIONS_TRUST_CONFIG = {
# Extensions that can run with full privileges ('core' trust level)
"trusted_extensions": [
"official-parquet-export",
"enterprise-sso-plugin",
],
# Allow any extension to run as 'core' without signature verification
# WARNING: NEVER enable in production - development use only!
"allow_unsigned_core": False,
# Default sandbox for extensions without explicit trust configuration
# Options: 'core', 'iframe', 'worker', 'wasm'
"default_trust_level": "iframe",
# Require valid signatures for extensions requesting 'core' trust
# Recommended for production deployments
"require_core_signatures": True,
# Public keys for verified publishers (file paths or PEM strings)
"trusted_signers": [
"/etc/superset/keys/apache-official.pub",
"/etc/superset/keys/enterprise-team.pub",
],
}
```
## Configuration Options
### `trusted_extensions`
A list of extension IDs that are allowed to run as `core` trust level without signature verification. Use this for extensions you've reviewed and trust completely.
```python
"trusted_extensions": [
"my-company-plugin",
"approved-community-extension",
],
```
### `allow_unsigned_core`
When `True`, allows any extension to run as `core` trust level regardless of signatures or trusted list. **Never enable this in production** - it's intended only for development environments.
```python
# Development only!
"allow_unsigned_core": True,
```
### `default_trust_level`
The trust level assigned to extensions that don't specify one in their manifest. The safest option is `iframe`, which provides browser-enforced isolation.
| Level | Description |
|-------|-------------|
| `iframe` | Browser-sandboxed iframe with controlled API access (recommended default) |
| `worker` | Web Worker sandbox for command-only extensions |
| `wasm` | WASM sandbox with no DOM access (most restrictive) |
| `core` | Full access to main context (not recommended as default) |
```python
"default_trust_level": "iframe",
```
### `require_core_signatures`
When `True`, extensions requesting `core` trust level must have a valid signature from a trusted signer. Extensions without valid signatures are downgraded to `default_trust_level`.
```python
"require_core_signatures": True,
```
### `trusted_signers`
A list of public keys authorized to sign extensions. Keys can be specified as file paths or inline PEM strings.
```python
"trusted_signers": [
# File path to public key
"/etc/superset/keys/publisher.pub",
# Inline PEM string
"""-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA...
-----END PUBLIC KEY-----""",
],
```
## Signature Verification
### How It Works
1. Extension developers generate a signing keypair using the CLI
2. They sign their extension's manifest during the build process
3. The signed bundle includes `manifest.sig` alongside `manifest.json`
4. When Superset loads the extension, it verifies the signature against `trusted_signers`
5. If verification passes, the extension can run at its requested trust level
### Configuring Trusted Signers
1. Obtain the publisher's public key file (`.pub` extension)
2. Place it in a secure location on your server (e.g., `/etc/superset/keys/`)
3. Add the path to `trusted_signers` in your configuration
```python
EXTENSIONS_TRUST_CONFIG = {
"trusted_signers": [
"/etc/superset/keys/acme-corp.pub",
],
"require_core_signatures": True,
}
```
### Verifying a Key Fingerprint
Before adding a public key to your trusted signers, verify its fingerprint with the publisher:
```bash
# On the publisher's machine
superset-extensions generate-keys --output my-key.pem
# Output: Fingerprint: MCowBQYDK2Vw...
```
Compare this fingerprint with what you receive to ensure authenticity.
## Security Recommendations
### Production Deployments
1. **Set `require_core_signatures: True`** - Ensures core extensions are verified
2. **Set `allow_unsigned_core: False`** - Never allow unsigned core extensions
3. **Use `iframe` as default** - Provides strong browser isolation
4. **Limit `trusted_extensions`** - Only add extensions you've thoroughly reviewed
5. **Secure key storage** - Store public keys in protected directories
### Development Environments
For local development, you may relax some restrictions:
```python
# Development configuration
EXTENSIONS_TRUST_CONFIG = {
"trusted_extensions": [],
"allow_unsigned_core": True, # OK for development
"default_trust_level": "core", # Easier debugging
"require_core_signatures": False,
"trusted_signers": [],
}
```
## Extension Installation
### From Trusted Sources
1. Download the `.supx` bundle from a trusted source
2. Verify any checksums or signatures provided by the publisher
3. Place the bundle in your `EXTENSIONS_PATH` directory
4. If the extension requires `core` trust, add it to `trusted_extensions` or configure signature verification
### From Community Registry
Extensions from the community registry should be treated as semi-trusted at best. Consider:
1. Using `iframe` sandbox for community extensions
2. Reviewing the extension's source code before installation
3. Testing in a staging environment first
## Monitoring Extensions
### Logging
Extension trust decisions are logged at the INFO level:
```
INFO: Extension my-extension granted core trust (trusted + valid signature)
WARNING: Extension unknown-ext trust downgraded from core to iframe: Extension not in trusted list
```
Review these logs to monitor extension behavior and identify potential issues.
### Trust Downgrades
If an extension's trust is downgraded, you'll see a warning in the logs. Common reasons:
| Reason | Meaning |
|--------|---------|
| "Extension not in trusted list" | Extension requests core but isn't in `trusted_extensions` |
| "Core trust requires a valid signature" | `require_core_signatures` is enabled but signature is missing |
| "Signature verification failed" | Signature doesn't match any trusted signer |
## Troubleshooting
### Extension Not Loading as Core
1. Check if the extension ID is in `trusted_extensions`
2. If using signatures, verify the public key is in `trusted_signers`
3. Check logs for trust downgrade messages
4. Verify the extension bundle contains `manifest.sig`
### Signature Verification Failing
1. Ensure the public key file is readable by the Superset process
2. Verify the key is in PEM format with correct Ed25519 type
3. Check that the manifest wasn't modified after signing
4. Confirm the signature was created with the matching private key
### Permission Denied Errors
Sandboxed extensions may encounter permission errors if:
1. The extension's declared permissions don't match its API calls
2. The sandbox is blocking access correctly (working as intended)
3. The extension was downgraded to a more restrictive sandbox
Check the extension's `sandbox.permissions` configuration against its actual needs.

View File

@@ -0,0 +1,131 @@
---
title: Alert
sidebar_label: Alert
---
<!--
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
import { Alert } from '@apache-superset/core/ui';
# Alert
Alert component for displaying important messages to users. Wraps Ant Design Alert with sensible defaults and improved accessibility.
## Live Example
<StoryWithControls
component={Alert}
props={{
closable: true,
type: 'info',
message: 'This is a sample alert message.',
description: 'Sample description for additional context.',
showIcon: true
}}
controls={[
{
name: 'type',
label: 'Type',
type: 'select',
options: [
'info',
'error',
'warning',
'success'
]
},
{
name: 'closable',
label: 'Closable',
type: 'boolean'
},
{
name: 'showIcon',
label: 'Show Icon',
type: 'boolean'
},
{
name: 'message',
label: 'Message',
type: 'text'
},
{
name: 'description',
label: 'Description',
type: 'text'
}
]}
/>
## Try It
Edit the code below to experiment with the component:
```tsx live
function Demo() {
return (
<Alert
closable
type="info"
message="This is a sample alert message."
description="Sample description for additional context."
showIcon
/>
);
}
```
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `closable` | `boolean` | `true` | Whether the Alert can be closed with a close button. |
| `type` | `string` | `"info"` | Type of the alert (e.g., info, error, warning, success). |
| `message` | `string` | `"This is a sample alert message."` | Message |
| `description` | `string` | `"Sample description for additional context."` | Description |
| `showIcon` | `boolean` | `true` | Whether to display an icon in the Alert. |
## Usage in Extensions
This component is available in the `@apache-superset/core/ui` package, which is automatically available to Superset extensions.
```tsx
import { Alert } from '@apache-superset/core/ui';
function MyExtension() {
return (
<Alert
closable
type="info"
message="This is a sample alert message."
/>
);
}
```
## Source Links
- [Story file](https://github.com/apache/superset/blob/master/superset-frontend/packages/superset-core/src/ui/components/Alert/Alert.stories.tsx)
- [Component source](https://github.com/apache/superset/blob/master/superset-frontend/packages/superset-core/src/ui/components/Alert/index.tsx)
---
*This page was auto-generated from the component's Storybook story.*

View File

@@ -0,0 +1,93 @@
---
title: Extension Components
sidebar_label: Overview
sidebar_position: 1
---
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Extension Components
These UI components are available to Superset extension developers through the `@apache-superset/core/ui` package. They provide a consistent look and feel with the rest of Superset and are designed to be used in extension panels, views, and other UI elements.
## Available Components
- [Alert](./alert)
## Usage
All components are exported from the `@apache-superset/core/ui` package:
```tsx
import { Alert } from '@apache-superset/core/ui';
export function MyExtensionPanel() {
return (
<Alert type="info">
Welcome to my extension!
</Alert>
);
}
```
## Adding New Components
Components in `@apache-superset/core/ui` are automatically documented here. To add a new extension component:
1. Add the component to `superset-frontend/packages/superset-core/src/ui/components/`
2. Export it from `superset-frontend/packages/superset-core/src/ui/components/index.ts`
3. Create a Storybook story with an `Interactive` export:
```tsx
export default {
title: 'Extension Components/MyComponent',
component: MyComponent,
parameters: {
docs: {
description: {
component: 'Description of the component...',
},
},
},
};
export const InteractiveMyComponent = (args) => <MyComponent {...args} />;
InteractiveMyComponent.args = {
variant: 'primary',
disabled: false,
};
InteractiveMyComponent.argTypes = {
variant: {
control: { type: 'select' },
options: ['primary', 'secondary'],
},
disabled: {
control: { type: 'boolean' },
},
};
```
4. Run `yarn start` in `docs/` - the page generates automatically!
## Interactive Documentation
For interactive examples with controls, visit the [Storybook](/storybook/?path=/docs/extension-components--docs).

View File

@@ -34,7 +34,7 @@ Frontend contribution types allow extensions to extend Superset's user interface
Extensions can add new views or panels to the host application, such as custom SQL Lab panels, dashboards, or other UI components. Each view is registered with a unique ID and can be activated or deactivated as needed. Contribution areas are uniquely identified (e.g., `sqllab.panels` for SQL Lab panels), enabling seamless integration into specific parts of the application.
```json
``` json
"frontend": {
"contributions": {
"views": {
@@ -53,7 +53,7 @@ Extensions can add new views or panels to the host application, such as custom S
Extensions can define custom commands that can be executed within the host application, such as context-aware actions or menu options. Each command can specify properties like a unique command identifier, an icon, a title, and a description. These commands can be invoked by users through menus, keyboard shortcuts, or other UI elements, enabling extensions to add rich, interactive functionality to Superset.
```json
``` json
"frontend": {
"contributions": {
"commands": [
@@ -72,7 +72,7 @@ Extensions can define custom commands that can be executed within the host appli
Extensions can contribute new menu items or context menus to the host application, providing users with additional actions and options. Each menu item can specify properties such as the target view, the command to execute, its placement (primary, secondary, or context), and conditions for when it should be displayed. Menu contribution areas are uniquely identified (e.g., `sqllab.editor` for the SQL Lab editor), allowing extensions to seamlessly integrate their functionality into specific menus and workflows within Superset.
```json
``` json
"frontend": {
"contributions": {
"menus": {
@@ -101,27 +101,6 @@ Extensions can contribute new menu items or context menus to the host applicatio
}
```
### Editors
Extensions can replace Superset's default text editors with custom implementations. This enables enhanced editing experiences using alternative editor frameworks like Monaco, CodeMirror, or custom solutions. When an extension registers an editor for a language, it replaces the default Ace editor in all locations that use that language (SQL Lab, Dashboard Properties, CSS editors, etc.).
```json
"frontend": {
"contributions": {
"editors": [
{
"id": "my_extension.monaco_sql",
"name": "Monaco SQL Editor",
"languages": ["sql"],
"description": "Monaco-based SQL editor with IntelliSense"
}
]
}
}
```
See [Editors Extension Point](./extension-points/editors) for implementation details.
## Backend
Backend contribution types allow extensions to extend Superset's server-side capabilities with new API endpoints, MCP tools, and MCP prompts.
@@ -130,7 +109,7 @@ Backend contribution types allow extensions to extend Superset's server-side cap
Extensions can register custom REST API endpoints under the `/api/v1/extensions/` namespace. This dedicated namespace prevents conflicts with built-in endpoints and provides a clear separation between core and extension functionality.
```json
``` json
"backend": {
"entryPoints": ["my_extension.entrypoint"],
"files": ["backend/src/my_extension/**/*.py"]
@@ -139,7 +118,7 @@ Extensions can register custom REST API endpoints under the `/api/v1/extensions/
The entry point module registers the API with Superset:
```python
``` python
from superset_core.api.rest_api import add_extension_api
from .api import MyExtensionAPI

View File

@@ -1,245 +0,0 @@
---
title: Editors
sidebar_position: 2
---
<!--
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.
-->
# Editor Contributions
Extensions can replace Superset's default text editors with custom implementations. This allows you to provide enhanced editing experiences using alternative editor frameworks like Monaco, CodeMirror, or custom solutions.
## Overview
Superset uses text editors in various places throughout the application:
| Language | Locations |
|----------|-----------|
| `sql` | SQL Lab, Metric/Filter Popovers |
| `json` | Dashboard Properties, Annotation Modal, Theme Modal |
| `css` | Dashboard Properties, CSS Template Modal |
| `markdown` | Dashboard Markdown component |
| `yaml` | Template Params Editor |
By registering an editor provider for a language, your extension replaces the default Ace editor in **all** locations that use that language.
## Manifest Configuration
Declare editor contributions in your `extension.json` manifest:
```json
{
"name": "monaco-editor",
"version": "1.0.0",
"frontend": {
"contributions": {
"editors": [
{
"id": "monaco-editor.sql",
"name": "Monaco SQL Editor",
"languages": ["sql"],
"description": "Monaco-based SQL editor with IntelliSense"
}
]
}
}
}
```
## Implementing an Editor
Your editor component must implement the `EditorProps` interface and expose an `EditorHandle` via `forwardRef`. For the complete interface definitions, see `@apache-superset/core/api/editors.ts`.
### Key EditorProps
```typescript
interface EditorProps {
/** Controlled value */
value: string;
/** Content change handler */
onChange: (value: string) => void;
/** Language mode for syntax highlighting */
language: EditorLanguage;
/** Keyboard shortcuts to register */
hotkeys?: EditorHotkey[];
/** Callback when editor is ready with imperative handle */
onReady?: (handle: EditorHandle) => void;
/** Host-specific context (e.g., database info from SQL Lab) */
metadata?: Record<string, unknown>;
// ... additional props for styling, annotations, etc.
}
```
### Key EditorHandle Methods
```typescript
interface EditorHandle {
/** Focus the editor */
focus(): void;
/** Get the current editor content */
getValue(): string;
/** Get the current cursor position */
getCursorPosition(): Position;
/** Move the cursor to a specific position */
moveCursorToPosition(position: Position): void;
/** Set the selection range */
setSelection(selection: Range): void;
/** Scroll to a specific line */
scrollToLine(line: number): void;
// ... additional methods for text manipulation, annotations, etc.
}
```
## Example Implementation
Here's an example of a Monaco-based SQL editor implementing the key interfaces shown above:
### MonacoSQLEditor.tsx
```typescript
import { forwardRef, useRef, useImperativeHandle, useEffect } from 'react';
import * as monaco from 'monaco-editor';
import type { editors } from '@apache-superset/core';
const MonacoSQLEditor = forwardRef<editors.EditorHandle, editors.EditorProps>(
(props, ref) => {
const { value, onChange, hotkeys, onReady } = props;
const containerRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
// Implement EditorHandle interface
const handle: editors.EditorHandle = {
focus: () => editorRef.current?.focus(),
getValue: () => editorRef.current?.getValue() ?? '',
getCursorPosition: () => {
const pos = editorRef.current?.getPosition();
return { line: (pos?.lineNumber ?? 1) - 1, column: (pos?.column ?? 1) - 1 };
},
// ... implement remaining methods
};
useImperativeHandle(ref, () => handle, []);
useEffect(() => {
if (!containerRef.current) return;
const editor = monaco.editor.create(containerRef.current, { value, language: 'sql' });
editorRef.current = editor;
editor.onDidChangeModelContent(() => onChange(editor.getValue()));
// Register hotkeys
hotkeys?.forEach(hotkey => {
editor.addAction({
id: hotkey.name,
label: hotkey.name,
run: () => hotkey.exec(handle),
});
});
onReady?.(handle);
return () => editor.dispose();
}, []);
return <div ref={containerRef} style={{ height: '100%', width: '100%' }} />;
},
);
export default MonacoSQLEditor;
```
### activate.ts
```typescript
import { editors } from '@apache-superset/core';
import MonacoSQLEditor from './MonacoSQLEditor';
export function activate(context) {
// Register the Monaco editor for SQL
const disposable = editors.registerEditorProvider(
{
id: 'monaco-sql-editor.sql',
name: 'Monaco SQL Editor',
languages: ['sql'],
},
MonacoSQLEditor,
);
context.subscriptions.push(disposable);
}
```
## Handling Hotkeys
Superset passes keyboard shortcuts via the `hotkeys` prop. Each hotkey includes an `exec` function that receives the `EditorHandle`:
```typescript
interface EditorHotkey {
name: string;
key: string; // e.g., "Ctrl-Enter", "Alt-Shift-F"
description?: string;
exec: (handle: EditorHandle) => void;
}
```
Your editor must register these hotkeys with your editor framework and call `exec(handle)` when triggered.
## Keywords
Superset passes static autocomplete suggestions via the `keywords` prop. These include table names, column names, and SQL functions based on the current database context:
```typescript
interface EditorKeyword {
name: string;
value?: string; // Text to insert (defaults to name)
meta?: string; // Category like "table", "column", "function"
score?: number; // Sorting priority
}
```
Your editor should convert these to your framework's completion format and register them for autocomplete.
## Completion Providers
For dynamic autocomplete (e.g., fetching suggestions as the user types), implement and register a `CompletionProvider` via the `EditorHandle`:
```typescript
const provider: CompletionProvider = {
id: 'my-sql-completions',
triggerCharacters: ['.', ' '],
provideCompletions: async (content, position, context) => {
// Use context.metadata for database info
// Return array of CompletionItem
return [
{ label: 'SELECT', insertText: 'SELECT', kind: 'keyword' },
// ...
];
},
};
// Register during editor initialization
const disposable = handle.registerCompletionProvider(provider);
```
## Next Steps
- **[SQL Lab Extension Points](./sqllab)** - Learn about other SQL Lab customizations
- **[Contribution Types](../contribution-types)** - Explore other contribution types
- **[Development](../development)** - Set up your development environment

View File

@@ -38,7 +38,6 @@ This page serves as a registry of community-created Superset extensions. These e
| [SQL Lab Result Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result_stats) | A SQL Lab extension that automatically computes statistics for query results, providing type-aware analysis including numeric metrics (min, max, mean, median, std dev), string analysis (length, empty counts), and date range information. | Michael S. Molina | <a href="/img/extensions/result-stats.png" target="_blank"><img src="/img/extensions/result-stats.png" alt="Result Stats" width="120" /></a> |
| [SQL Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/sql_snippets) | A SQL Lab extension that provides reusable SQL code snippets, enabling quick insertion of commonly used code blocks such as license headers, author information, and frequently used SQL patterns. | Michael S. Molina | <a href="/img/extensions/sql-snippets.png" target="_blank"><img src="/img/extensions/sql-snippets.png" alt="SQL Snippets" width="120" /></a> |
| [SQL Lab Query Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query_estimator) | A SQL Lab panel that analyzes query execution plans to estimate resource impact, detect performance issues like Cartesian products and high-cost operations, and visualize the query plan tree. | Michael S. Molina | <a href="/img/extensions/query-estimator.png" target="_blank"><img src="/img/extensions/query-estimator.png" alt="Query Estimator" width="120" /></a> |
| [Editors Bundle](https://github.com/michael-s-molina/superset-extensions/tree/main/editors_bundle) | A Superset extension that demonstrates how to provide custom code editors for different languages. This extension showcases the editor contribution system by registering alternative editors that can replace Superset's default Ace editor. | Michael S. Molina | <a href="/img/extensions/editors-bundle.png" target="_blank"><img src="/img/extensions/editors-bundle.png" alt="Editors Bundle" width="120" /></a> |
## How to Add Your Extension

View File

@@ -0,0 +1,416 @@
---
title: Extension Sandboxing
sidebar_position: 10
---
<!--
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 Sandboxing
Superset provides a tiered sandbox architecture for running extensions with varying levels of trust and isolation. This system balances security with functionality, allowing extensions to be safely executed based on their trust level and requirements.
## Overview
The sandbox system supports three tiers of trust:
| Tier | Trust Level | Isolation | Use Case |
|------|-------------|-----------|----------|
| **Tier 1** | `core` | None (main context) | Official/signed extensions |
| **Tier 2** | `iframe` | Browser sandbox | Community UI extensions |
| **Tier 3** | `wasm` | WASM sandbox | Logic-only extensions |
## Trust Levels
### Tier 1: Core (Trusted)
Core extensions run in the main JavaScript context with full access to Superset APIs, DOM, and browser capabilities. This is the same behavior as legacy extensions.
**Requirements:**
- Must be in the trusted extensions list, OR
- `allowUnsignedCore` configuration must be enabled
**Use cases:**
- Official Apache Superset extensions
- Enterprise-verified plugins
- Extensions from trusted sources
```json
{
"id": "official-extension",
"sandbox": {
"trustLevel": "core",
"requiresSignature": true
}
}
```
### Tier 2: Iframe (Semi-Trusted)
Iframe-sandboxed extensions run in isolated browser sandboxes with controlled API access via postMessage. This provides strong browser-enforced isolation while still allowing full UI rendering.
**Security features:**
- Browser-enforced same-origin isolation
- Content Security Policy (CSP) restrictions
- Permission-based API access
- No access to parent window's cookies, localStorage, or DOM
**Use cases:**
- Community-contributed extensions
- Third-party plugins
- Extensions that render custom UI
```json
{
"id": "community-extension",
"sandbox": {
"trustLevel": "iframe",
"permissions": ["sqllab:read", "notification:show"],
"csp": {
"connectSrc": ["https://api.example.com"]
}
}
}
```
### Tier 3: WASM (Untrusted)
WASM-sandboxed extensions run in a QuickJS WebAssembly sandbox with no DOM access. Only explicitly injected APIs are available. This provides the highest level of isolation.
**Security features:**
- Complete isolation from browser APIs
- Memory limits to prevent DoS
- Execution time limits
- No network or DOM access
**Use cases:**
- Custom data transformations
- Calculated fields and formatters
- Data validation rules
- Custom aggregation functions
```json
{
"id": "formatter-extension",
"sandbox": {
"trustLevel": "wasm",
"resourceLimits": {
"maxMemory": 10485760,
"maxExecutionTime": 5000
}
}
}
```
## Permissions
Sandboxed extensions (Tier 2 and 3) must declare the permissions they need. Permissions follow a least-privilege model.
### Available Permissions
| Permission | Description |
|------------|-------------|
| `api:read` | Read-only access to Superset APIs |
| `api:write` | Write access to Superset APIs |
| `sqllab:read` | Read SQL Lab state (queries, results) |
| `sqllab:execute` | Execute SQL queries |
| `dashboard:read` | Read dashboard data |
| `dashboard:write` | Modify dashboards |
| `chart:read` | Read chart data |
| `chart:write` | Modify charts |
| `user:read` | Read current user info |
| `notification:show` | Show notifications to user |
| `modal:open` | Open modal dialogs |
| `navigation:redirect` | Navigate to other pages |
| `clipboard:write` | Write to clipboard |
| `download:file` | Trigger file downloads |
### Example Permission Declaration
```json
{
"sandbox": {
"trustLevel": "iframe",
"permissions": [
"sqllab:read",
"notification:show",
"download:file"
]
}
}
```
## Sandboxed Extension API
Extensions running in iframe sandboxes have access to a controlled API through the `window.superset` object.
### SQL Lab API
```typescript
// Get the current SQL Lab tab (requires sqllab:read)
const tab = await window.superset.sqlLab.getCurrentTab();
// Get query results (requires sqllab:read)
const results = await window.superset.sqlLab.getQueryResults(queryId);
```
### Dashboard API
```typescript
// Get dashboard context (requires dashboard:read)
const context = await window.superset.dashboard.getContext();
// Get dashboard filters (requires dashboard:read)
const filters = await window.superset.dashboard.getFilters();
```
### Chart API
```typescript
// Get chart data (requires chart:read)
const chartData = await window.superset.chart.getData(chartId);
```
### User API
```typescript
// Get current user (requires user:read)
const user = await window.superset.user.getCurrentUser();
```
### UI API
```typescript
// Show notification (requires notification:show)
window.superset.ui.showNotification('Success!', 'success');
// Open modal (requires modal:open)
const result = await window.superset.ui.openModal({
title: 'Confirm',
content: 'Are you sure?',
type: 'confirm'
});
// Navigate (requires navigation:redirect)
window.superset.ui.navigateTo('/dashboard/1');
```
### Utility API
```typescript
// Copy to clipboard (requires clipboard:write)
await window.superset.utils.copyToClipboard('text');
// Download file (requires download:file)
window.superset.utils.downloadFile(blob, 'filename.csv');
// Get CSRF token (no permission required)
const token = await window.superset.utils.getCSRFToken();
```
### Event Subscriptions
```typescript
// Subscribe to events
const unsubscribe = window.superset.on('dashboard:filterChange', (filters) => {
console.log('Filters changed:', filters);
});
// Later, unsubscribe
unsubscribe();
```
## Content Security Policy
Iframe-sandboxed extensions can customize their Content Security Policy through the `csp` configuration:
```json
{
"sandbox": {
"trustLevel": "iframe",
"csp": {
"defaultSrc": ["'none'"],
"scriptSrc": ["'unsafe-inline'"],
"styleSrc": ["'unsafe-inline'"],
"imgSrc": ["data:", "blob:", "https://cdn.example.com"],
"connectSrc": ["https://api.example.com"],
"fontSrc": ["data:"]
}
}
}
```
### Default CSP
By default, iframe sandboxes use a restrictive CSP:
```
default-src 'none';
script-src 'unsafe-inline';
style-src 'unsafe-inline';
img-src data: blob:;
font-src data:;
connect-src 'none';
frame-src 'none';
```
## WASM Resource Limits
WASM-sandboxed extensions can configure resource limits:
```json
{
"sandbox": {
"trustLevel": "wasm",
"resourceLimits": {
"maxMemory": 10485760, // 10MB max memory
"maxExecutionTime": 5000, // 5 second timeout
"maxStackSize": 1000 // Max call stack depth
}
}
}
```
### Defaults
- **maxMemory**: 10MB
- **maxExecutionTime**: 5000ms (5 seconds)
- **maxStackSize**: 1000 calls
## Migration Guide
### Migrating from Legacy Extensions
Existing extensions that don't specify a `sandbox` configuration will continue to run as `core` extensions for backward compatibility. To migrate to a sandboxed model:
1. **Assess your extension's requirements**:
- Does it need to render UI? Use `iframe`
- Is it logic-only (formatters, validators)? Use `wasm`
- Does it need full access? Keep as `core` (requires trust)
2. **Add sandbox configuration to extension.json**:
```json
{
"sandbox": {
"trustLevel": "iframe",
"permissions": ["sqllab:read"]
}
}
```
3. **Update your code to use the sandboxed API**:
Before (core extension):
```typescript
import { sqlLab } from '@apache-superset/core';
const tab = sqlLab.getCurrentTab();
```
After (sandboxed extension):
```typescript
const tab = await window.superset.sqlLab.getCurrentTab();
```
4. **Test thoroughly** to ensure all functionality works within the sandbox
## Security Comparison
| Aspect | Core | Iframe | WASM |
|--------|------|--------|------|
| DOM Access | Full | Own iframe only | None |
| Network | Full | Restricted (CSP) | None |
| Cookies | Full | None | None |
| localStorage | Full | None | None |
| Superset APIs | Full | Controlled bridge | Injected only |
| Performance | Native | Near-native | ~40% slower |
| React rendering | Full | Own instance | Via descriptors |
## Administrator Configuration
Administrators can configure trust settings for their Superset deployment:
```python
# In superset_config.py
EXTENSIONS_TRUST_CONFIG = {
# Extensions allowed to run as 'core'
"trusted_extensions": [
"official-extension-1",
"enterprise-plugin",
],
# Allow unsigned extensions to run as core (not recommended for production)
"allow_unsigned_core": False,
# Default trust level for extensions without sandbox config
"default_trust_level": "iframe",
}
```
## Best Practices
1. **Request minimal permissions** - Only request the permissions your extension actually needs
2. **Prefer iframe over core** - Unless your extension requires deep integration, use iframe sandboxing
3. **Use WASM for pure logic** - If your extension doesn't need UI, WASM provides the best isolation
4. **Handle permission denials gracefully** - Your extension should degrade gracefully if a permission is not granted
5. **Don't store sensitive data** - Sandboxed extensions should not store sensitive user data
6. **Test in sandboxed mode** - Always test your extension in its intended sandbox environment
## Troubleshooting
### Permission Denied Errors
If you see "Permission denied" errors, verify that:
1. The permission is declared in your extension.json
2. The permission was granted by the administrator
3. You're calling the correct API method for that permission
### Timeout Errors (WASM)
If your WASM extension times out:
1. Optimize your code for faster execution
2. Request a higher `maxExecutionTime` limit
3. Break large operations into smaller chunks
### CSP Violations (Iframe)
If resources fail to load due to CSP:
1. Add the required domains to your CSP configuration
2. Ensure you're using HTTPS for external resources
3. Avoid inline scripts and styles where possible
### Core Trust Denied
If your extension is downgraded from `core` to another trust level:
1. Check if the extension ID is in the administrator's `trusted_extensions` list
2. If signature verification is required, ensure the extension is signed
3. Verify the signing key is in the administrator's `trusted_signers`
See [Extension Signing](./signing) for how to sign your extension.
## Related Documentation
- [Security Overview](./security) - Extension security fundamentals
- [Extension Signing](./signing) - How to sign extensions for core trust
- [Administrator Configuration](./admin-configuration) - Trust configuration for admins

View File

@@ -26,9 +26,44 @@ under the License.
By default, extensions are disabled and must be explicitly enabled by setting the `ENABLE_EXTENSIONS` feature flag. Built-in extensions are included as part of the Superset codebase and are held to the same security standards and review processes as the rest of the application.
For external extensions, administrators are responsible for evaluating and verifying the security of any extensions they choose to install, just as they would when installing third-party NPM or PyPI packages. At this stage, all extensions run in the same context as the host application, without additional sandboxing. This means that external extensions can impact the security and performance of a Superset environment in the same way as any other installed dependency.
## Extension Sandboxing
We plan to introduce an optional sandboxed execution model for extensions in the future (as part of an additional SIP). Until then, administrators should exercise caution and follow best practices when selecting and deploying third-party extensions. A directory of community extensions is available in the [Community Extensions](./registry) page. Note that these extensions are not vetted by the Apache Superset project—administrators must evaluate each extension before installation.
Superset provides a tiered sandbox architecture for running extensions with varying levels of trust and isolation. Extensions can declare their trust level and permissions in their manifest, and Superset will load them in the appropriate sandbox:
- **Core (Tier 1)**: Trusted extensions run in the main context with full access
- **Iframe (Tier 2)**: Semi-trusted extensions run in browser-sandboxed iframes
- **WASM (Tier 3)**: Untrusted logic runs in WebAssembly sandboxes
For detailed information about the sandbox system, see [Extension Sandboxing](./sandbox).
## Trust Model
Administrators are responsible for evaluating and verifying the security of any extensions they choose to install. Superset's sandbox system provides defense-in-depth:
1. **Core extensions** require explicit trust configuration and optionally signature verification
2. **Iframe-sandboxed extensions** are isolated by the browser's same-origin policy
3. **WASM-sandboxed extensions** have no access to browser APIs
A directory of community extensions is available in the [Community Extensions](./registry) page. Note that these extensions are not vetted by the Apache Superset project—administrators must evaluate each extension before installation.
## Extension Signing
Extensions can be cryptographically signed to verify their authenticity and integrity. This is required for extensions that need `core` trust level in production environments with signature verification enabled.
- **Developers**: See [Extension Signing](./signing) to learn how to sign your extensions
- **Administrators**: See [Administrator Configuration](./admin-configuration) to configure trusted signers
## Administrator Configuration
Superset provides extensive configuration options for controlling extension trust levels, signature verification, and security policies. Key settings include:
- **Trusted extensions list**: Extensions allowed to run as `core`
- **Signature verification**: Require valid signatures for core trust
- **Default trust level**: Sandbox level for unlisted extensions
For complete configuration details, see [Administrator Configuration](./admin-configuration).
## Security Reporting
**Any performance or security vulnerabilities introduced by external extensions should be reported directly to the extension author, not as Superset vulnerabilities.**

View File

@@ -0,0 +1,236 @@
---
title: Extension Signing
sidebar_position: 11
---
<!--
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 Signing
Signing your extension allows administrators to verify its authenticity and integrity. Signed extensions can run as `core` trust level in production environments where signature verification is required.
## Why Sign Extensions?
- **Trust**: Administrators can verify your extension comes from a known source
- **Integrity**: Ensures the extension hasn't been modified since you signed it
- **Core Access**: Required for extensions needing `core` trust level in secured deployments
- **Distribution**: Makes your extension suitable for enterprise environments
## Generating Signing Keys
Generate a new Ed25519 keypair for signing your extensions:
```bash
superset-extensions generate-keys --output my-signing-key.pem
```
This creates two files:
| File | Purpose | Share? |
|------|---------|--------|
| `my-signing-key.pem` | Private key for signing | **Never share!** |
| `my-signing-key.pub` | Public key for verification | Share with administrators |
**Output example:**
```
✅ Private key: my-signing-key.pem
✅ Public key: my-signing-key.pub
Fingerprint: MCowBQYDK2Vw...
⚠️ Keep the private key secure! Only share the public key with administrators.
Usage:
Sign an extension: superset-extensions bundle --sign my-signing-key.pem
Share with admins: my-signing-key.pub
```
## Signing an Extension
### During Bundle
The easiest way to sign is during the bundle step:
```bash
superset-extensions bundle --sign my-signing-key.pem
```
This builds, signs the manifest, and creates the `.supx` bundle in one command.
**Output:**
```
✅ Full build completed in dist/
✅ Manifest signed
✅ Bundle created (signed): my-extension-1.0.0.supx
```
### Signing Existing Manifest
To sign an already-built manifest:
```bash
superset-extensions sign --key my-signing-key.pem --manifest dist/manifest.json
```
This creates `dist/manifest.sig` containing the signature.
## Bundle Structure
A signed extension bundle contains:
```
my-extension-1.0.0.supx
├── manifest.json # Extension manifest
├── manifest.sig # Ed25519 signature (base64-encoded)
├── frontend/dist/ # Frontend assets
└── backend/src/ # Backend code (if applicable)
```
The signature file (`manifest.sig`) contains a base64-encoded Ed25519 signature of the manifest content.
## Distributing Your Public Key
Share your public key (`.pub` file) with administrators who want to trust your extensions:
1. **Direct sharing**: Send the `.pub` file via secure channels
2. **Documentation**: Include in your extension's README
3. **Website**: Host on your organization's website with HTTPS
Administrators will add your public key to their `EXTENSIONS_TRUST_CONFIG.trusted_signers` configuration.
### Key Fingerprint
The fingerprint helps administrators verify they have the correct key. Include it in your documentation:
```
Public Key Fingerprint: MCowBQYDK2Vw...
```
Administrators should verify this fingerprint matches when adding your key.
## Security Best Practices
### Protect Your Private Key
- **Never commit** private keys to version control
- **Use secure storage** like hardware security modules (HSM) for production keys
- **Limit access** to the private key to authorized personnel only
- **Back up securely** in case of key loss
### Key Rotation
Consider rotating keys periodically:
1. Generate a new keypair
2. Notify administrators of the new public key
3. Sign new releases with the new key
4. Keep the old key available for verifying existing releases
### Multiple Keys
For organizations, consider separate keys for:
- Development/testing releases
- Production releases
- Different product teams
## Requesting Core Trust
If your extension needs `core` trust level:
1. **Sign your extension** using the process above
2. **Document your public key** with fingerprint
3. **Explain why core is needed** in your extension documentation
4. **Provide your public key** to administrators
Administrators will then:
1. Add your public key to `trusted_signers`
2. Enable `require_core_signatures: True`
3. Your signed extension can now run as `core`
## Verification Process
When Superset loads your extension:
1. Reads `manifest.json` and `manifest.sig` from the bundle
2. Checks if the extension requests `core` trust level
3. If `require_core_signatures` is enabled, verifies the signature
4. Checks the signature against all keys in `trusted_signers`
5. If verification passes, grants the requested trust level
6. If verification fails, downgrades to `default_trust_level`
## Troubleshooting
### "Signature verification failed"
- Ensure you're using the matching private key for the public key given to admins
- Verify the manifest wasn't modified after signing
- Check that the `.sig` file was included in the bundle
### "Private key must be Ed25519"
- The signing system only supports Ed25519 keys
- Generate a new key using `superset-extensions generate-keys`
### Administrator Reports Invalid Signature
- Verify the public key file wasn't corrupted during transfer
- Confirm the fingerprint matches between your key and theirs
- Re-sign the extension and redistribute
## Technical Details
### Signature Algorithm
Extensions use **Ed25519** signatures:
- Fast signature generation and verification
- Small signature size (64 bytes)
- Strong security guarantees
- Deterministic signatures (same input always produces same output)
### Signature Format
The `manifest.sig` file contains:
```
<base64-encoded Ed25519 signature>
```
The signature is computed over the raw bytes of `manifest.json`.
### Key Format
Keys are stored in PEM format:
**Private key:**
```
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEI...
-----END PRIVATE KEY-----
```
**Public key:**
```
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA...
-----END PUBLIC KEY-----
```

View File

@@ -26,9 +26,6 @@ module.exports = {
collapsed: true,
items: [
'contributing/overview',
'guidelines/design-guidelines',
'guidelines/frontend-style-guidelines',
'guidelines/backend-style-guidelines',
],
},
{
@@ -52,7 +49,17 @@ module.exports = {
'extensions/development',
'extensions/deployment',
'extensions/mcp',
'extensions/security',
{
type: 'category',
label: 'Security',
collapsed: true,
items: [
'extensions/security',
'extensions/sandbox',
'extensions/signing',
'extensions/admin-configuration',
],
},
'extensions/registry',
],
},
@@ -64,20 +71,5 @@ module.exports = {
'testing/overview',
],
},
{
type: 'category',
label: 'UI Components',
collapsed: true,
link: {
type: 'doc',
id: 'components/index',
},
items: [
{
type: 'autogenerated',
dirName: 'components',
},
],
},
],
};

View File

@@ -1,114 +0,0 @@
---
title: Storybook
sidebar_position: 5
---
<!--
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.
-->
# Storybook
Superset uses [Storybook](https://storybook.js.org/) for developing and testing UI components in isolation. Storybook provides a sandbox to build components independently, outside of the main application.
## Public Storybook
A public Storybook with components from the `master` branch is available at:
**[apache-superset.github.io/superset-ui](https://apache-superset.github.io/superset-ui/?path=/story/*)**
## Running Locally
### Main Superset Storybook
To run the main Superset Storybook locally:
```bash
cd superset-frontend
# Start Storybook (opens at http://localhost:6006)
npm run storybook
# Build static Storybook
npm run build-storybook
```
### @superset-ui Package Storybook
The `@superset-ui` packages have a separate Storybook for component library development:
```bash
cd superset-frontend
# Install dependencies and bootstrap packages
npm ci && npm run bootstrap
# Start the @superset-ui Storybook (opens at http://localhost:9001)
cd packages/superset-ui-demo
npm run storybook
```
## Adding Stories
### To an Existing Package
If stories already exist for the package, extend the `examples` array in the package's story file:
```
storybook/stories/<package>/index.js
```
### To a New Package
1. Add package dependencies:
```bash
npm install <package>
```
2. Create a story folder matching the package name:
```bash
mkdir storybook/stories/superset-ui-<package>/
```
3. Create an `index.js` file with the story configuration:
```javascript
export default {
examples: [
{
storyPath: '@superset-ui/package',
storyName: 'My Story',
renderStory: () => <MyComponent />,
},
],
};
```
Use the `|` separator for nested stories:
```javascript
storyPath: '@superset-ui/package|Category|Subcategory'
```
## Best Practices
- **Isolate components**: Stories should render components in isolation, without application context
- **Show variations**: Create stories for different states, sizes, and configurations
- **Document props**: Use Storybook's controls to expose configurable props
- **Test edge cases**: Include stories for loading states, error states, and empty states

View File

@@ -1,590 +1,40 @@
---
title: API Reference
title: API
hide_title: true
sidebar_position: 10
---
import SwaggerUI from 'swagger-ui-react';
import openapi from '/resources/openapi.json';
import 'swagger-ui-react/swagger-ui.css';
import { Alert } from 'antd';
## REST API Reference
## API
Superset exposes a comprehensive **REST API** that follows the [OpenAPI specification](https://swagger.io/specification/).
You can use this API to programmatically interact with Superset for automation, integrations, and custom applications.
Superset's public **REST API** follows the
[OpenAPI specification](https://swagger.io/specification/), and is
documented here. The docs below are generated using
[Swagger React UI](https://www.npmjs.com/package/swagger-ui-react).
:::resources
- [Blog: The Superset REST API](https://preset.io/blog/2020-10-01-superset-api/)
- [Blog: Accessing APIs with Superset](https://preset.io/blog/accessing-apis-with-superset/)
:::
<Alert
type="info"
showIcon
message="Code Samples & Schema Documentation"
description={
<span>
Each endpoint includes ready-to-use code samples in <strong>cURL</strong>, <strong>Python</strong>, and <strong>JavaScript</strong>.
The sidebar includes <strong>Schema definitions</strong> for detailed data model documentation.
</span>
message={
<div>
<strong>NOTE! </strong>
You can find an interactive version of this documentation on your local Superset
instance at <strong>/swagger/v1</strong> (unless disabled)
</div>
}
style={{ marginBottom: '24px' }}
/>
---
### Authentication
Most API endpoints require authentication via JWT tokens.
#### Quick Start
```bash
# 1. Get a JWT token
curl -X POST http://localhost:8088/api/v1/security/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "admin", "provider": "db"}'
# 2. Use the access_token from the response
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
http://localhost:8088/api/v1/dashboard/
```
#### Security Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get the CSRF token](./api/get-the-csrf-token) | `/api/v1/security/csrf_token/` |
| `POST` | [Get a guest token](./api/get-a-guest-token) | `/api/v1/security/guest_token/` |
| `POST` | [Create security login](./api/create-security-login) | `/api/v1/security/login` |
| `POST` | [Create security refresh](./api/create-security-refresh) | `/api/v1/security/refresh` |
---
### API Endpoints
#### Core Resources
<details>
<summary><strong>Dashboards</strong> (26 endpoints) — Create, read, update, and delete dashboards.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete dashboards](./api/bulk-delete-dashboards) | `/api/v1/dashboard/` |
| `GET` | [Get a list of dashboards](./api/get-a-list-of-dashboards) | `/api/v1/dashboard/` |
| `POST` | [Create a new dashboard](./api/create-a-new-dashboard) | `/api/v1/dashboard/` |
| `GET` | [Get metadata information about this API resource (dashboard--info)](./api/get-metadata-information-about-this-api-resource-dashboard-info) | `/api/v1/dashboard/_info` |
| `GET` | [Get a dashboard detail information](./api/get-a-dashboard-detail-information) | `/api/v1/dashboard/{id_or_slug}` |
| `GET` | [Get a dashboard's chart definitions.](./api/get-a-dashboards-chart-definitions) | `/api/v1/dashboard/{id_or_slug}/charts` |
| `POST` | [Create a copy of an existing dashboard](./api/create-a-copy-of-an-existing-dashboard) | `/api/v1/dashboard/{id_or_slug}/copy/` |
| `GET` | [Get dashboard's datasets](./api/get-dashboards-datasets) | `/api/v1/dashboard/{id_or_slug}/datasets` |
| `DELETE` | [Delete a dashboard's embedded configuration](./api/delete-a-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `GET` | [Get the dashboard's embedded configuration](./api/get-the-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `POST` | [Set a dashboard's embedded configuration](./api/set-a-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `PUT` | [Update dashboard by id_or_slug embedded](./api/update-dashboard-by-id-or-slug-embedded) | `/api/v1/dashboard/{id_or_slug}/embedded` |
| `GET` | [Get dashboard's tabs](./api/get-dashboards-tabs) | `/api/v1/dashboard/{id_or_slug}/tabs` |
| `DELETE` | [Delete a dashboard](./api/delete-a-dashboard) | `/api/v1/dashboard/{pk}` |
| `PUT` | [Update a dashboard](./api/update-a-dashboard) | `/api/v1/dashboard/{pk}` |
| `POST` | [Compute and cache a screenshot (dashboard-pk-cache-dashboard-screenshot)](./api/compute-and-cache-a-screenshot-dashboard-pk-cache-dashboard-screenshot) | `/api/v1/dashboard/{pk}/cache_dashboard_screenshot/` |
| `PUT` | [Update colors configuration for a dashboard.](./api/update-colors-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/colors` |
| `DELETE` | [Remove the dashboard from the user favorite list](./api/remove-the-dashboard-from-the-user-favorite-list) | `/api/v1/dashboard/{pk}/favorites/` |
| `POST` | [Mark the dashboard as favorite for the current user](./api/mark-the-dashboard-as-favorite-for-the-current-user) | `/api/v1/dashboard/{pk}/favorites/` |
| `PUT` | [Update native filters configuration for a dashboard.](./api/update-native-filters-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/filters` |
| `GET` | [Get a computed screenshot from cache (dashboard-pk-screenshot-digest)](./api/get-a-computed-screenshot-from-cache-dashboard-pk-screenshot-digest) | `/api/v1/dashboard/{pk}/screenshot/{digest}/` |
| `GET` | [Get dashboard's thumbnail](./api/get-dashboards-thumbnail) | `/api/v1/dashboard/{pk}/thumbnail/{digest}/` |
| `GET` | [Download multiple dashboards as YAML files](./api/download-multiple-dashboards-as-yaml-files) | `/api/v1/dashboard/export/` |
| `GET` | [Check favorited dashboards for current user](./api/check-favorited-dashboards-for-current-user) | `/api/v1/dashboard/favorite_status/` |
| `POST` | [Import dashboard(s) with associated charts/datasets/databases](./api/import-dashboard-s-with-associated-charts-datasets-databases) | `/api/v1/dashboard/import/` |
| `GET` | [Get related fields data (dashboard-related-column-name)](./api/get-related-fields-data-dashboard-related-column-name) | `/api/v1/dashboard/related/{column_name}` |
</details>
<details>
<summary><strong>Charts</strong> (20 endpoints) — Create, read, update, and delete charts (slices).</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete charts](./api/bulk-delete-charts) | `/api/v1/chart/` |
| `GET` | [Get a list of charts](./api/get-a-list-of-charts) | `/api/v1/chart/` |
| `POST` | [Create a new chart](./api/create-a-new-chart) | `/api/v1/chart/` |
| `GET` | [Get metadata information about this API resource (chart--info)](./api/get-metadata-information-about-this-api-resource-chart-info) | `/api/v1/chart/_info` |
| `DELETE` | [Delete a chart](./api/delete-a-chart) | `/api/v1/chart/{pk}` |
| `GET` | [Get a chart detail information](./api/get-a-chart-detail-information) | `/api/v1/chart/{pk}` |
| `PUT` | [Update a chart](./api/update-a-chart) | `/api/v1/chart/{pk}` |
| `GET` | [Compute and cache a screenshot (chart-pk-cache-screenshot)](./api/compute-and-cache-a-screenshot-chart-pk-cache-screenshot) | `/api/v1/chart/{pk}/cache_screenshot/` |
| `GET` | [Return payload data response for a chart](./api/return-payload-data-response-for-a-chart) | `/api/v1/chart/{pk}/data/` |
| `DELETE` | [Remove the chart from the user favorite list](./api/remove-the-chart-from-the-user-favorite-list) | `/api/v1/chart/{pk}/favorites/` |
| `POST` | [Mark the chart as favorite for the current user](./api/mark-the-chart-as-favorite-for-the-current-user) | `/api/v1/chart/{pk}/favorites/` |
| `GET` | [Get a computed screenshot from cache (chart-pk-screenshot-digest)](./api/get-a-computed-screenshot-from-cache-chart-pk-screenshot-digest) | `/api/v1/chart/{pk}/screenshot/{digest}/` |
| `GET` | [Get chart thumbnail](./api/get-chart-thumbnail) | `/api/v1/chart/{pk}/thumbnail/{digest}/` |
| `POST` | [Return payload data response for the given query (chart-data)](./api/return-payload-data-response-for-the-given-query-chart-data) | `/api/v1/chart/data` |
| `GET` | [Return payload data response for the given query (chart-data-cache-key)](./api/return-payload-data-response-for-the-given-query-chart-data-cache-key) | `/api/v1/chart/data/{cache_key}` |
| `GET` | [Download multiple charts as YAML files](./api/download-multiple-charts-as-yaml-files) | `/api/v1/chart/export/` |
| `GET` | [Check favorited charts for current user](./api/check-favorited-charts-for-current-user) | `/api/v1/chart/favorite_status/` |
| `POST` | [Import chart(s) with associated datasets and databases](./api/import-chart-s-with-associated-datasets-and-databases) | `/api/v1/chart/import/` |
| `GET` | [Get related fields data (chart-related-column-name)](./api/get-related-fields-data-chart-related-column-name) | `/api/v1/chart/related/{column_name}` |
| `PUT` | [Warm up the cache for the chart](./api/warm-up-the-cache-for-the-chart) | `/api/v1/chart/warm_up_cache` |
</details>
<details>
<summary><strong>Datasets</strong> (18 endpoints) — Manage datasets (tables) used for building charts.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete datasets](./api/bulk-delete-datasets) | `/api/v1/dataset/` |
| `GET` | [Get a list of datasets](./api/get-a-list-of-datasets) | `/api/v1/dataset/` |
| `POST` | [Create a new dataset](./api/create-a-new-dataset) | `/api/v1/dataset/` |
| `GET` | [Get metadata information about this API resource (dataset--info)](./api/get-metadata-information-about-this-api-resource-dataset-info) | `/api/v1/dataset/_info` |
| `DELETE` | [Delete a dataset](./api/delete-a-dataset) | `/api/v1/dataset/{pk}` |
| `GET` | [Get a dataset](./api/get-a-dataset) | `/api/v1/dataset/{pk}` |
| `PUT` | [Update a dataset](./api/update-a-dataset) | `/api/v1/dataset/{pk}` |
| `DELETE` | [Delete a dataset column](./api/delete-a-dataset-column) | `/api/v1/dataset/{pk}/column/{column_id}` |
| `DELETE` | [Delete a dataset metric](./api/delete-a-dataset-metric) | `/api/v1/dataset/{pk}/metric/{metric_id}` |
| `PUT` | [Refresh and update columns of a dataset](./api/refresh-and-update-columns-of-a-dataset) | `/api/v1/dataset/{pk}/refresh` |
| `GET` | [Get charts and dashboards count associated to a dataset](./api/get-charts-and-dashboards-count-associated-to-a-dataset) | `/api/v1/dataset/{pk}/related_objects` |
| `GET` | [Get distinct values from field data (dataset-distinct-column-name)](./api/get-distinct-values-from-field-data-dataset-distinct-column-name) | `/api/v1/dataset/distinct/{column_name}` |
| `POST` | [Duplicate a dataset](./api/duplicate-a-dataset) | `/api/v1/dataset/duplicate` |
| `GET` | [Download multiple datasets as YAML files](./api/download-multiple-datasets-as-yaml-files) | `/api/v1/dataset/export/` |
| `POST` | [Retrieve a table by name, or create it if it does not exist](./api/retrieve-a-table-by-name-or-create-it-if-it-does-not-exist) | `/api/v1/dataset/get_or_create/` |
| `POST` | [Import dataset(s) with associated databases](./api/import-dataset-s-with-associated-databases) | `/api/v1/dataset/import/` |
| `GET` | [Get related fields data (dataset-related-column-name)](./api/get-related-fields-data-dataset-related-column-name) | `/api/v1/dataset/related/{column_name}` |
| `PUT` | [Warm up the cache for each chart powered by the given table](./api/warm-up-the-cache-for-each-chart-powered-by-the-given-table) | `/api/v1/dataset/warm_up_cache` |
</details>
<details>
<summary><strong>Database</strong> (31 endpoints) — Manage database connections and metadata.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get a list of databases](./api/get-a-list-of-databases) | `/api/v1/database/` |
| `POST` | [Create a new database](./api/create-a-new-database) | `/api/v1/database/` |
| `GET` | [Get metadata information about this API resource (database--info)](./api/get-metadata-information-about-this-api-resource-database-info) | `/api/v1/database/_info` |
| `DELETE` | [Delete a database](./api/delete-a-database) | `/api/v1/database/{pk}` |
| `GET` | [Get a database](./api/get-a-database) | `/api/v1/database/{pk}` |
| `PUT` | [Change a database](./api/change-a-database) | `/api/v1/database/{pk}` |
| `GET` | [Get all catalogs from a database](./api/get-all-catalogs-from-a-database) | `/api/v1/database/{pk}/catalogs/` |
| `GET` | [Get a database connection info](./api/get-a-database-connection-info) | `/api/v1/database/{pk}/connection` |
| `GET` | [Get function names supported by a database](./api/get-function-names-supported-by-a-database) | `/api/v1/database/{pk}/function_names/` |
| `GET` | [Get charts and dashboards count associated to a database](./api/get-charts-and-dashboards-count-associated-to-a-database) | `/api/v1/database/{pk}/related_objects/` |
| `GET` | [The list of the database schemas where to upload information](./api/the-list-of-the-database-schemas-where-to-upload-information) | `/api/v1/database/{pk}/schemas_access_for_file_upload/` |
| `GET` | [Get all schemas from a database](./api/get-all-schemas-from-a-database) | `/api/v1/database/{pk}/schemas/` |
| `GET` | [Get database select star for table (database-pk-select-star-table-name)](./api/get-database-select-star-for-table-database-pk-select-star-table-name) | `/api/v1/database/{pk}/select_star/{table_name}/` |
| `GET` | [Get database select star for table (database-pk-select-star-table-name-schema-name)](./api/get-database-select-star-for-table-database-pk-select-star-table-name-schema-name) | `/api/v1/database/{pk}/select_star/{table_name}/{schema_name}/` |
| `DELETE` | [Delete a SSH tunnel](./api/delete-a-ssh-tunnel) | `/api/v1/database/{pk}/ssh_tunnel/` |
| `POST` | [Re-sync all permissions for a database connection](./api/re-sync-all-permissions-for-a-database-connection) | `/api/v1/database/{pk}/sync_permissions/` |
| `GET` | [Get table extra metadata (database-pk-table-extra-table-name-schema-name)](./api/get-table-extra-metadata-database-pk-table-extra-table-name-schema-name) | `/api/v1/database/{pk}/table_extra/{table_name}/{schema_name}/` |
| `GET` | [Get table metadata](./api/get-table-metadata) | `/api/v1/database/{pk}/table_metadata/` |
| `GET` | [Get table extra metadata (database-pk-table-metadata-extra)](./api/get-table-extra-metadata-database-pk-table-metadata-extra) | `/api/v1/database/{pk}/table_metadata/extra/` |
| `GET` | [Get database table metadata](./api/get-database-table-metadata) | `/api/v1/database/{pk}/table/{table_name}/{schema_name}/` |
| `GET` | [Get a list of tables for given database](./api/get-a-list-of-tables-for-given-database) | `/api/v1/database/{pk}/tables/` |
| `POST` | [Upload a file to a database table](./api/upload-a-file-to-a-database-table) | `/api/v1/database/{pk}/upload/` |
| `POST` | [Validate arbitrary SQL](./api/validate-arbitrary-sql) | `/api/v1/database/{pk}/validate_sql/` |
| `GET` | [Get names of databases currently available](./api/get-names-of-databases-currently-available) | `/api/v1/database/available/` |
| `GET` | [Download database(s) and associated dataset(s) as a zip file](./api/download-database-s-and-associated-dataset-s-as-a-zip-file) | `/api/v1/database/export/` |
| `POST` | [Import database(s) with associated datasets](./api/import-database-s-with-associated-datasets) | `/api/v1/database/import/` |
| `GET` | [Receive personal access tokens from OAuth2](./api/receive-personal-access-tokens-from-o-auth-2) | `/api/v1/database/oauth2/` |
| `GET` | [Get related fields data (database-related-column-name)](./api/get-related-fields-data-database-related-column-name) | `/api/v1/database/related/{column_name}` |
| `POST` | [Test a database connection](./api/test-a-database-connection) | `/api/v1/database/test_connection/` |
| `POST` | [Upload a file and returns file metadata](./api/upload-a-file-and-returns-file-metadata) | `/api/v1/database/upload_metadata/` |
| `POST` | [Validate database connection parameters](./api/validate-database-connection-parameters) | `/api/v1/database/validate_parameters/` |
</details>
#### Data Exploration
<details>
<summary><strong>Explore</strong> (1 endpoints) — Chart exploration and data querying endpoints.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Assemble Explore related information in a single endpoint](./api/assemble-explore-related-information-in-a-single-endpoint) | `/api/v1/explore/` |
</details>
<details>
<summary><strong>SQL Lab</strong> (6 endpoints) — Execute SQL queries and manage SQL Lab sessions.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get the bootstrap data for SqlLab page](./api/get-the-bootstrap-data-for-sql-lab-page) | `/api/v1/sqllab/` |
| `POST` | [Estimate the SQL query execution cost](./api/estimate-the-sql-query-execution-cost) | `/api/v1/sqllab/estimate/` |
| `POST` | [Execute a SQL query](./api/execute-a-sql-query) | `/api/v1/sqllab/execute/` |
| `GET` | [Export the SQL query results to a CSV](./api/export-the-sql-query-results-to-a-csv) | `/api/v1/sqllab/export/{client_id}/` |
| `POST` | [Format SQL code](./api/format-sql-code) | `/api/v1/sqllab/format_sql/` |
| `GET` | [Get the result of a SQL query execution](./api/get-the-result-of-a-sql-query-execution) | `/api/v1/sqllab/results/` |
</details>
<details>
<summary><strong>Queries</strong> (17 endpoints) — View and manage SQL Lab query history.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get a list of queries](./api/get-a-list-of-queries) | `/api/v1/query/` |
| `GET` | [Get query detail information](./api/get-query-detail-information) | `/api/v1/query/{pk}` |
| `GET` | [Get distinct values from field data (query-distinct-column-name)](./api/get-distinct-values-from-field-data-query-distinct-column-name) | `/api/v1/query/distinct/{column_name}` |
| `GET` | [Get related fields data (query-related-column-name)](./api/get-related-fields-data-query-related-column-name) | `/api/v1/query/related/{column_name}` |
| `POST` | [Manually stop a query with client_id](./api/manually-stop-a-query-with-client-id) | `/api/v1/query/stop` |
| `GET` | [Get a list of queries that changed after last_updated_ms](./api/get-a-list-of-queries-that-changed-after-last-updated-ms) | `/api/v1/query/updated_since` |
| `DELETE` | [Bulk delete saved queries](./api/bulk-delete-saved-queries) | `/api/v1/saved_query/` |
| `GET` | [Get a list of saved queries](./api/get-a-list-of-saved-queries) | `/api/v1/saved_query/` |
| `POST` | [Create a saved query](./api/create-a-saved-query) | `/api/v1/saved_query/` |
| `GET` | [Get metadata information about this API resource (saved-query--info)](./api/get-metadata-information-about-this-api-resource-saved-query-info) | `/api/v1/saved_query/_info` |
| `DELETE` | [Delete a saved query](./api/delete-a-saved-query) | `/api/v1/saved_query/{pk}` |
| `GET` | [Get a saved query](./api/get-a-saved-query) | `/api/v1/saved_query/{pk}` |
| `PUT` | [Update a saved query](./api/update-a-saved-query) | `/api/v1/saved_query/{pk}` |
| `GET` | [Get distinct values from field data (saved-query-distinct-column-name)](./api/get-distinct-values-from-field-data-saved-query-distinct-column-name) | `/api/v1/saved_query/distinct/{column_name}` |
| `GET` | [Download multiple saved queries as YAML files](./api/download-multiple-saved-queries-as-yaml-files) | `/api/v1/saved_query/export/` |
| `POST` | [Import saved queries with associated databases](./api/import-saved-queries-with-associated-databases) | `/api/v1/saved_query/import/` |
| `GET` | [Get related fields data (saved-query-related-column-name)](./api/get-related-fields-data-saved-query-related-column-name) | `/api/v1/saved_query/related/{column_name}` |
</details>
<details>
<summary><strong>Datasources</strong> (1 endpoints) — Query datasource metadata and column values.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get possible values for a datasource column](./api/get-possible-values-for-a-datasource-column) | `/api/v1/datasource/{datasource_type}/{datasource_id}/column/{column_name}/values/` |
</details>
<details>
<summary><strong>Advanced Data Type</strong> (2 endpoints) — Endpoints for advanced data type operations and conversions.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Return an AdvancedDataTypeResponse](./api/return-an-advanced-data-type-response) | `/api/v1/advanced_data_type/convert` |
| `GET` | [Return a list of available advanced data types](./api/return-a-list-of-available-advanced-data-types) | `/api/v1/advanced_data_type/types` |
</details>
#### Organization & Customization
<details>
<summary><strong>Tags</strong> (15 endpoints) — Organize assets with tags.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete tags](./api/bulk-delete-tags) | `/api/v1/tag/` |
| `GET` | [Get a list of tags](./api/get-a-list-of-tags) | `/api/v1/tag/` |
| `POST` | [Create a tag](./api/create-a-tag) | `/api/v1/tag/` |
| `GET` | [Get metadata information about tag API endpoints](./api/get-metadata-information-about-tag-api-endpoints) | `/api/v1/tag/_info` |
| `POST` | [Add tags to an object](./api/add-tags-to-an-object) | `/api/v1/tag/{object_type}/{object_id}/` |
| `DELETE` | [Delete a tagged object](./api/delete-a-tagged-object) | `/api/v1/tag/{object_type}/{object_id}/{tag}/` |
| `DELETE` | [Delete a tag](./api/delete-a-tag) | `/api/v1/tag/{pk}` |
| `GET` | [Get a tag detail information](./api/get-a-tag-detail-information) | `/api/v1/tag/{pk}` |
| `PUT` | [Update a tag](./api/update-a-tag) | `/api/v1/tag/{pk}` |
| `DELETE` | [Delete tag by pk favorites](./api/delete-tag-by-pk-favorites) | `/api/v1/tag/{pk}/favorites/` |
| `POST` | [Create tag by pk favorites](./api/create-tag-by-pk-favorites) | `/api/v1/tag/{pk}/favorites/` |
| `POST` | [Bulk create tags and tagged objects](./api/bulk-create-tags-and-tagged-objects) | `/api/v1/tag/bulk_create` |
| `GET` | [Get tag favorite status](./api/get-tag-favorite-status) | `/api/v1/tag/favorite_status/` |
| `GET` | [Get all objects associated with a tag](./api/get-all-objects-associated-with-a-tag) | `/api/v1/tag/get_objects/` |
| `GET` | [Get related fields data (tag-related-column-name)](./api/get-related-fields-data-tag-related-column-name) | `/api/v1/tag/related/{column_name}` |
</details>
<details>
<summary><strong>Annotation Layers</strong> (14 endpoints) — Manage annotation layers and annotations for charts.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Delete multiple annotation layers in a bulk operation](./api/delete-multiple-annotation-layers-in-a-bulk-operation) | `/api/v1/annotation_layer/` |
| `GET` | [Get a list of annotation layers (annotation-layer)](./api/get-a-list-of-annotation-layers-annotation-layer) | `/api/v1/annotation_layer/` |
| `POST` | [Create an annotation layer (annotation-layer)](./api/create-an-annotation-layer-annotation-layer) | `/api/v1/annotation_layer/` |
| `GET` | [Get metadata information about this API resource (annotation-layer--info)](./api/get-metadata-information-about-this-api-resource-annotation-layer-info) | `/api/v1/annotation_layer/_info` |
| `DELETE` | [Delete annotation layer (annotation-layer-pk)](./api/delete-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
| `GET` | [Get an annotation layer (annotation-layer-pk)](./api/get-an-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
| `PUT` | [Update an annotation layer (annotation-layer-pk)](./api/update-an-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
| `DELETE` | [Bulk delete annotation layers](./api/bulk-delete-annotation-layers) | `/api/v1/annotation_layer/{pk}/annotation/` |
| `GET` | [Get a list of annotation layers (annotation-layer-pk-annotation)](./api/get-a-list-of-annotation-layers-annotation-layer-pk-annotation) | `/api/v1/annotation_layer/{pk}/annotation/` |
| `POST` | [Create an annotation layer (annotation-layer-pk-annotation)](./api/create-an-annotation-layer-annotation-layer-pk-annotation) | `/api/v1/annotation_layer/{pk}/annotation/` |
| `DELETE` | [Delete annotation layer (annotation-layer-pk-annotation-annotation-id)](./api/delete-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
| `GET` | [Get an annotation layer (annotation-layer-pk-annotation-annotation-id)](./api/get-an-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
| `PUT` | [Update an annotation layer (annotation-layer-pk-annotation-annotation-id)](./api/update-an-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
| `GET` | [Get related fields data (annotation-layer-related-column-name)](./api/get-related-fields-data-annotation-layer-related-column-name) | `/api/v1/annotation_layer/related/{column_name}` |
</details>
<details>
<summary><strong>CSS Templates</strong> (8 endpoints) — Manage CSS templates for custom dashboard styling.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete CSS templates](./api/bulk-delete-css-templates) | `/api/v1/css_template/` |
| `GET` | [Get a list of CSS templates](./api/get-a-list-of-css-templates) | `/api/v1/css_template/` |
| `POST` | [Create a CSS template](./api/create-a-css-template) | `/api/v1/css_template/` |
| `GET` | [Get metadata information about this API resource (css-template--info)](./api/get-metadata-information-about-this-api-resource-css-template-info) | `/api/v1/css_template/_info` |
| `DELETE` | [Delete a CSS template](./api/delete-a-css-template) | `/api/v1/css_template/{pk}` |
| `GET` | [Get a CSS template](./api/get-a-css-template) | `/api/v1/css_template/{pk}` |
| `PUT` | [Update a CSS template](./api/update-a-css-template) | `/api/v1/css_template/{pk}` |
| `GET` | [Get related fields data (css-template-related-column-name)](./api/get-related-fields-data-css-template-related-column-name) | `/api/v1/css_template/related/{column_name}` |
</details>
#### Sharing & Embedding
<details>
<summary><strong>Dashboard Permanent Link</strong> (2 endpoints) — Create and retrieve permanent links to dashboard states.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a new dashboard's permanent link](./api/create-a-new-dashboards-permanent-link) | `/api/v1/dashboard/{pk}/permalink` |
| `GET` | [Get dashboard's permanent link state](./api/get-dashboards-permanent-link-state) | `/api/v1/dashboard/permalink/{key}` |
</details>
<details>
<summary><strong>Explore Permanent Link</strong> (2 endpoints) — Create and retrieve permanent links to chart explore states.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a new permanent link (explore-permalink)](./api/create-a-new-permanent-link-explore-permalink) | `/api/v1/explore/permalink` |
| `GET` | [Get chart's permanent link state](./api/get-charts-permanent-link-state) | `/api/v1/explore/permalink/{key}` |
</details>
<details>
<summary><strong>SQL Lab Permanent Link</strong> (2 endpoints) — Create and retrieve permanent links to SQL Lab states.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a new permanent link (sqllab-permalink)](./api/create-a-new-permanent-link-sqllab-permalink) | `/api/v1/sqllab/permalink` |
| `GET` | [Get permanent link state for SQLLab editor.](./api/get-permanent-link-state-for-sql-lab-editor) | `/api/v1/sqllab/permalink/{key}` |
</details>
<details>
<summary><strong>Embedded Dashboard</strong> (1 endpoints) — Configure embedded dashboard settings.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get a report schedule log (embedded-dashboard-uuid)](./api/get-a-report-schedule-log-embedded-dashboard-uuid) | `/api/v1/embedded_dashboard/{uuid}` |
</details>
<details>
<summary><strong>Dashboard Filter State</strong> (4 endpoints) — Manage temporary filter state for dashboards.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a dashboard's filter state](./api/create-a-dashboards-filter-state) | `/api/v1/dashboard/{pk}/filter_state` |
| `DELETE` | [Delete a dashboard's filter state value](./api/delete-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
| `GET` | [Get a dashboard's filter state value](./api/get-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
| `PUT` | [Update a dashboard's filter state value](./api/update-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
</details>
<details>
<summary><strong>Explore Form Data</strong> (4 endpoints) — Manage temporary form data for chart exploration.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Create a new form_data](./api/create-a-new-form-data) | `/api/v1/explore/form_data` |
| `DELETE` | [Delete a form_data](./api/delete-a-form-data) | `/api/v1/explore/form_data/{key}` |
| `GET` | [Get a form_data](./api/get-a-form-data) | `/api/v1/explore/form_data/{key}` |
| `PUT` | [Update an existing form_data](./api/update-an-existing-form-data) | `/api/v1/explore/form_data/{key}` |
</details>
#### Scheduling & Alerts
<details>
<summary><strong>Report Schedules</strong> (11 endpoints) — Configure scheduled reports and alerts.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete report schedules](./api/bulk-delete-report-schedules) | `/api/v1/report/` |
| `GET` | [Get a list of report schedules](./api/get-a-list-of-report-schedules) | `/api/v1/report/` |
| `POST` | [Create a report schedule](./api/create-a-report-schedule) | `/api/v1/report/` |
| `GET` | [Get metadata information about this API resource (report--info)](./api/get-metadata-information-about-this-api-resource-report-info) | `/api/v1/report/_info` |
| `DELETE` | [Delete a report schedule](./api/delete-a-report-schedule) | `/api/v1/report/{pk}` |
| `GET` | [Get a report schedule](./api/get-a-report-schedule) | `/api/v1/report/{pk}` |
| `PUT` | [Update a report schedule](./api/update-a-report-schedule) | `/api/v1/report/{pk}` |
| `GET` | [Get a list of report schedule logs](./api/get-a-list-of-report-schedule-logs) | `/api/v1/report/{pk}/log/` |
| `GET` | [Get a report schedule log (report-pk-log-log-id)](./api/get-a-report-schedule-log-report-pk-log-log-id) | `/api/v1/report/{pk}/log/{log_id}` |
| `GET` | [Get related fields data (report-related-column-name)](./api/get-related-fields-data-report-related-column-name) | `/api/v1/report/related/{column_name}` |
| `GET` | [Get slack channels](./api/get-slack-channels) | `/api/v1/report/slack_channels/` |
</details>
#### Security & Access Control
<details>
<summary><strong>Security Roles</strong> (10 endpoints) — Manage security roles and their permissions.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security roles](./api/get-security-roles) | `/api/v1/security/roles/` |
| `POST` | [Create security roles](./api/create-security-roles) | `/api/v1/security/roles/` |
| `GET` | [Get security roles info](./api/get-security-roles-info) | `/api/v1/security/roles/_info` |
| `DELETE` | [Delete security roles by pk](./api/delete-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
| `GET` | [Get security roles by pk](./api/get-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
| `PUT` | [Update security roles by pk](./api/update-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
| `POST` | [Create security roles by role_id permissions](./api/create-security-roles-by-role-id-permissions) | `/api/v1/security/roles/{role_id}/permissions` |
| `GET` | [Get security roles by role_id permissions](./api/get-security-roles-by-role-id-permissions) | `/api/v1/security/roles/{role_id}/permissions/` |
| `PUT` | [Update security roles by role_id users](./api/update-security-roles-by-role-id-users) | `/api/v1/security/roles/{role_id}/users` |
| `GET` | [List roles](./api/list-roles) | `/api/v1/security/roles/search/` |
</details>
<details>
<summary><strong>Security Users</strong> (6 endpoints) — Manage user accounts.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security users](./api/get-security-users) | `/api/v1/security/users/` |
| `POST` | [Create security users](./api/create-security-users) | `/api/v1/security/users/` |
| `GET` | [Get security users info](./api/get-security-users-info) | `/api/v1/security/users/_info` |
| `DELETE` | [Delete security users by pk](./api/delete-security-users-by-pk) | `/api/v1/security/users/{pk}` |
| `GET` | [Get security users by pk](./api/get-security-users-by-pk) | `/api/v1/security/users/{pk}` |
| `PUT` | [Update security users by pk](./api/update-security-users-by-pk) | `/api/v1/security/users/{pk}` |
</details>
<details>
<summary><strong>Security Permissions</strong> (3 endpoints) — View available permissions.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security permissions](./api/get-security-permissions) | `/api/v1/security/permissions/` |
| `GET` | [Get security permissions info](./api/get-security-permissions-info) | `/api/v1/security/permissions/_info` |
| `GET` | [Get security permissions by pk](./api/get-security-permissions-by-pk) | `/api/v1/security/permissions/{pk}` |
</details>
<details>
<summary><strong>Security Resources (View Menus)</strong> (6 endpoints) — Manage security resources (view menus).</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security resources](./api/get-security-resources) | `/api/v1/security/resources/` |
| `POST` | [Create security resources](./api/create-security-resources) | `/api/v1/security/resources/` |
| `GET` | [Get security resources info](./api/get-security-resources-info) | `/api/v1/security/resources/_info` |
| `DELETE` | [Delete security resources by pk](./api/delete-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
| `GET` | [Get security resources by pk](./api/get-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
| `PUT` | [Update security resources by pk](./api/update-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
</details>
<details>
<summary><strong>Security Permissions on Resources (View Menus)</strong> (6 endpoints) — Manage permission-resource mappings.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get security permissions resources](./api/get-security-permissions-resources) | `/api/v1/security/permissions-resources/` |
| `POST` | [Create security permissions resources](./api/create-security-permissions-resources) | `/api/v1/security/permissions-resources/` |
| `GET` | [Get security permissions resources info](./api/get-security-permissions-resources-info) | `/api/v1/security/permissions-resources/_info` |
| `DELETE` | [Delete security permissions resources by pk](./api/delete-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
| `GET` | [Get security permissions resources by pk](./api/get-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
| `PUT` | [Update security permissions resources by pk](./api/update-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
</details>
<details>
<summary><strong>Row Level Security</strong> (8 endpoints) — Manage row-level security rules for data access control.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `DELETE` | [Bulk delete RLS rules](./api/bulk-delete-rls-rules) | `/api/v1/rowlevelsecurity/` |
| `GET` | [Get a list of RLS](./api/get-a-list-of-rls) | `/api/v1/rowlevelsecurity/` |
| `POST` | [Create a new RLS rule](./api/create-a-new-rls-rule) | `/api/v1/rowlevelsecurity/` |
| `GET` | [Get metadata information about this API resource (rowlevelsecurity--info)](./api/get-metadata-information-about-this-api-resource-rowlevelsecurity-info) | `/api/v1/rowlevelsecurity/_info` |
| `DELETE` | [Delete an RLS](./api/delete-an-rls) | `/api/v1/rowlevelsecurity/{pk}` |
| `GET` | [Get an RLS](./api/get-an-rls) | `/api/v1/rowlevelsecurity/{pk}` |
| `PUT` | [Update an RLS rule](./api/update-an-rls-rule) | `/api/v1/rowlevelsecurity/{pk}` |
| `GET` | [Get related fields data (rowlevelsecurity-related-column-name)](./api/get-related-fields-data-rowlevelsecurity-related-column-name) | `/api/v1/rowlevelsecurity/related/{column_name}` |
</details>
#### Import/Export & Administration
<details>
<summary><strong>Import/export</strong> (2 endpoints) — Import and export Superset assets (dashboards, charts, databases).</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Export all assets](./api/export-all-assets) | `/api/v1/assets/export/` |
| `POST` | [Import multiple assets](./api/import-multiple-assets) | `/api/v1/assets/import/` |
</details>
<details>
<summary><strong>CacheRestApi</strong> (1 endpoints) — Cache management and invalidation operations.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | [Invalidate cache records and remove the database records](./api/invalidate-cache-records-and-remove-the-database-records) | `/api/v1/cachekey/invalidate` |
</details>
<details>
<summary><strong>LogRestApi</strong> (4 endpoints) — Access audit logs and activity history.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get a list of logs](./api/get-a-list-of-logs) | `/api/v1/log/` |
| `POST` | [Create log](./api/create-log) | `/api/v1/log/` |
| `GET` | [Get a log detail information](./api/get-a-log-detail-information) | `/api/v1/log/{pk}` |
| `GET` | [Get recent activity data for a user](./api/get-recent-activity-data-for-a-user) | `/api/v1/log/recent_activity/` |
</details>
#### User & System
<details>
<summary><strong>Current User</strong> (2 endpoints) — Get information about the currently authenticated user.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get the user object](./api/get-the-user-object) | `/api/v1/me/` |
| `GET` | [Get the user roles](./api/get-the-user-roles) | `/api/v1/me/roles/` |
</details>
<details>
<summary><strong>User</strong> (1 endpoints) — User profile and preferences.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get the user avatar](./api/get-the-user-avatar) | `/api/v1/user/{user_id}/avatar.png` |
</details>
<details>
<summary><strong>Menu</strong> (1 endpoints) — Get the Superset menu structure.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get menu](./api/get-menu) | `/api/v1/menu/` |
</details>
<details>
<summary><strong>Available Domains</strong> (1 endpoints) — Get available domains for the Superset instance.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get all available domains](./api/get-all-available-domains) | `/api/v1/available_domains/` |
</details>
<details>
<summary><strong>AsyncEventsRestApi</strong> (1 endpoints) — Real-time event streaming via Server-Sent Events (SSE).</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Read off of the Redis events stream](./api/read-off-of-the-redis-events-stream) | `/api/v1/async_event/` |
</details>
<details>
<summary><strong>OpenApi</strong> (1 endpoints) — Access the OpenAPI specification.</summary>
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [Get api by version openapi](./api/get-api-by-version-openapi) | `/api/{version}/_openapi` |
</details>
---
### Additional Resources
- [Superset REST API Blog Post](https://preset.io/blog/2020-10-01-superset-api/)
- [Accessing APIs with Superset](https://preset.io/blog/accessing-apis-with-superset/)
<br />
<br />
<hr />
<div className="swagger-container">
<SwaggerUI spec={openapi} />
</div>

View File

@@ -32,7 +32,7 @@ git clone https://github.com/apache/superset
$ cd superset
# Set the repo to the state associated with the latest official version
$ git checkout tags/6.0.0
$ git checkout tags/5.0.0
# Fire up Superset using Docker Compose
$ docker compose -f docker-compose-image-tag.yml up

View File

@@ -19,7 +19,6 @@
import type { Config } from '@docusaurus/types';
import type { Options, ThemeConfig } from '@docusaurus/preset-classic';
import type * as OpenApiPlugin from 'docusaurus-plugin-openapi-docs';
import { themes } from 'prism-react-renderer';
import remarkImportPartial from 'remark-import-partial';
import remarkLocalizeBadges from './plugins/remark-localize-badges.mjs';
@@ -132,12 +131,6 @@ if (!versionsConfig.developer_portal.disabled && !versionsConfig.developer_porta
docId: 'index',
label: 'Overview',
},
{
type: 'doc',
docsPluginId: 'developer_portal',
docId: 'contributing/overview',
label: 'Contributing',
},
{
type: 'doc',
docsPluginId: 'developer_portal',
@@ -153,12 +146,14 @@ if (!versionsConfig.developer_portal.disabled && !versionsConfig.developer_porta
{
type: 'doc',
docsPluginId: 'developer_portal',
docId: 'components/index',
label: 'UI Components',
docId: 'guidelines/design-guidelines',
label: 'Guidelines',
},
{
label: 'API Reference',
href: '/docs/api',
type: 'doc',
docsPluginId: 'developer_portal',
docId: 'contributing/overview',
label: 'Contributing',
},
],
});
@@ -171,11 +166,9 @@ const config: Config = {
url: 'https://superset.apache.org',
baseUrl: '/',
onBrokenLinks: 'warn',
onBrokenMarkdownLinks: 'throw',
markdown: {
mermaid: true,
hooks: {
onBrokenMarkdownLinks: 'throw',
},
},
favicon: '/img/favicon.ico',
organizationName: 'apache',
@@ -184,34 +177,18 @@ const config: Config = {
'@saucelabs/theme-github-codeblock',
'@docusaurus/theme-mermaid',
'@docusaurus/theme-live-codeblock',
'docusaurus-theme-openapi-docs',
],
plugins: [
require.resolve('./src/webpack.extend.ts'),
...dynamicPlugins,
[
'docusaurus-plugin-openapi-docs',
'docusaurus-plugin-less',
{
id: 'api',
docsPluginId: 'classic',
config: {
superset: {
specPath: 'static/resources/openapi.json',
outputDir: 'docs/api',
sidebarOptions: {
groupPathsBy: 'tag',
categoryLinkSource: 'tag',
sidebarCollapsible: true,
sidebarCollapsed: true,
},
showSchemas: true,
hideSendButton: true,
showInfoPage: false,
showExtensions: true,
} satisfies OpenApiPlugin.Options,
lessOptions: {
javascriptEnabled: true,
},
},
],
...dynamicPlugins,
[
'@docusaurus/plugin-client-redirects',
{
@@ -385,7 +362,6 @@ const config: Config = {
disableVersioning: false,
showLastUpdateAuthor: true,
showLastUpdateTime: true,
docItemComponent: '@theme/ApiItem', // Required for OpenAPI docs
},
blog: {
showReadingTime: true,
@@ -497,10 +473,8 @@ const config: Config = {
footer: {
links: [],
copyright: `
<div class="footer__ci-services">
<span>CI powered by</span>
<a href="https://applitools.com/" target="_blank" rel="nofollow noopener noreferrer"><img src="/img/applitools.png" alt="Applitools" title="Applitools - Visual Testing" /></a>
<a href="https://www.netlify.com/" target="_blank" rel="nofollow noopener noreferrer"><img src="/img/netlify.png" alt="Netlify" title="Netlify - Deploy Previews" /></a>
<div class="footer__applitools">
We use &nbsp;<a href="https://applitools.com/" target="_blank" rel="nofollow"><img src="/img/applitools.png" title="Applitools" /></a>
</div>
<p>Copyright © ${new Date().getFullYear()},
The <a href="https://www.apache.org/" target="_blank" rel="noreferrer">Apache Software Foundation</a>,

View File

@@ -1,6 +1,6 @@
{
"copyright": {
"message": "\n <div class=\"footer__ci-services\">\n <span>CI powered by</span>\n <a href=\"https://applitools.com/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><img src=\"/img/applitools.png\" alt=\"Applitools\" title=\"Applitools - Visual Testing\" /></a>\n <a href=\"https://www.netlify.com/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><img src=\"/img/netlify.png\" alt=\"Netlify\" title=\"Netlify - Deploy Previews\" /></a>\n </div>\n <p>Copyright © 2026,\n The <a href=\"https://www.apache.org/\" target=\"_blank\" rel=\"noreferrer\">Apache Software Foundation</a>,\n Licensed under the Apache <a href=\"https://apache.org/licenses/LICENSE-2.0\" target=\"_blank\" rel=\"noreferrer\">License</a>.</p>\n <p><small>Apache Superset, Apache, Superset, the Superset logo, and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation. All other products or name brands are trademarks of their respective holders, including The Apache Software Foundation.\n <a href=\"https://www.apache.org/\" target=\"_blank\">Apache Software Foundation</a> resources</small></p>\n <img class=\"footer__divider\" src=\"/img/community/line.png\" alt=\"Divider\" />\n <p>\n <small>\n <a href=\"/docs/security/\" target=\"_blank\" rel=\"noreferrer\">Security</a>&nbsp;|&nbsp;\n <a href=\"https://www.apache.org/foundation/sponsorship.html\" target=\"_blank\" rel=\"noreferrer\">Donate</a>&nbsp;|&nbsp;\n <a href=\"https://www.apache.org/foundation/thanks.html\" target=\"_blank\" rel=\"noreferrer\">Thanks</a>&nbsp;|&nbsp;\n <a href=\"https://apache.org/events/current-event\" target=\"_blank\" rel=\"noreferrer\">Events</a>&nbsp;|&nbsp;\n <a href=\"https://apache.org/licenses/\" target=\"_blank\" rel=\"noreferrer\">License</a>&nbsp;|&nbsp;\n <a href=\"https://privacy.apache.org/policies/privacy-policy-public.html\" target=\"_blank\" rel=\"noreferrer\">Privacy</a>\n </small>\n </p>\n <!-- telemetry/analytics pixel: -->\n <img referrerPolicy=\"no-referrer-when-downgrade\" src=\"https://static.scarf.sh/a.png?x-pxid=39ae6855-95fc-4566-86e5-360d542b0a68\" />\n ",
"message": "\n <div class=\"footer__applitools\">\n We use &nbsp;<a href=\"https://applitools.com/\" target=\"_blank\" rel=\"nofollow\"><img src=\"/img/applitools.png\" title=\"Applitools\" /></a>\n </div>\n <p>Copyright © 2024,\n The <a href=\"https://www.apache.org/\" target=\"_blank\" rel=\"noreferrer\">Apache Software Foundation</a>,\n Licensed under the Apache <a href=\"https://apache.org/licenses/LICENSE-2.0\" target=\"_blank\" rel=\"noreferrer\">License</a>.</p>\n <p><small>Apache Superset, Apache, Superset, the Superset logo, and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation. All other products or name brands are trademarks of their respective holders, including The Apache Software Foundation.\n <a href=\"https://www.apache.org/\" target=\"_blank\">Apache Software Foundation</a> resources</small></p>\n <img class=\"footer__divider\" src=\"/img/community/line.png\" alt=\"Divider\" />\n <p>\n <small>\n <a href=\"/docs/security/\" target=\"_blank\" rel=\"noreferrer\">Security</a>&nbsp;|&nbsp;\n <a href=\"https://www.apache.org/foundation/sponsorship.html\" target=\"_blank\" rel=\"noreferrer\">Donate</a>&nbsp;|&nbsp;\n <a href=\"https://www.apache.org/foundation/thanks.html\" target=\"_blank\" rel=\"noreferrer\">Thanks</a>&nbsp;|&nbsp;\n <a href=\"https://apache.org/events/current-event\" target=\"_blank\" rel=\"noreferrer\">Events</a>&nbsp;|&nbsp;\n <a href=\"https://apache.org/licenses/\" target=\"_blank\" rel=\"noreferrer\">License</a>&nbsp;|&nbsp;\n <a href=\"https://privacy.apache.org/policies/privacy-policy-public.html\" target=\"_blank\" rel=\"noreferrer\">Privacy</a>\n </small>\n </p>\n <!-- telemetry/analytics pixel: -->\n <img referrerPolicy=\"no-referrer-when-downgrade\" src=\"https://static.scarf.sh/a.png?x-pxid=39ae6855-95fc-4566-86e5-360d542b0a68\" />\n ",
"description": "The footer copyright"
}
}

View File

@@ -34,8 +34,6 @@
NODE_VERSION = "20"
# Yarn version
YARN_VERSION = "1.22.22"
# Increase heap size for webpack bundling of Superset UI components
NODE_OPTIONS = "--max-old-space-size=8192"
# Deploy preview settings
[context.deploy-preview]

View File

@@ -6,24 +6,19 @@
"scripts": {
"docusaurus": "docusaurus",
"_init": "cat src/intro_header.txt ../README.md > docs/intro.md",
"start": "yarn run _init && yarn run generate:all && NODE_OPTIONS='--max-old-space-size=8192' NODE_ENV=development docusaurus start",
"start:quick": "yarn run _init && NODE_OPTIONS='--max-old-space-size=8192' NODE_ENV=development docusaurus start",
"start": "yarn run _init && yarn run generate:extension-components && yarn run generate:database-docs && NODE_ENV=development docusaurus start",
"stop": "pkill -f 'docusaurus start' || pkill -f 'docusaurus serve' || echo 'No docusaurus server running'",
"build": "yarn run _init && yarn run generate:all && NODE_OPTIONS='--max-old-space-size=8192' DEBUG=docusaurus:* docusaurus build",
"generate:api-docs": "python3 scripts/fix-openapi-spec.py && docusaurus gen-api-docs superset && node scripts/convert-api-sidebar.mjs && node scripts/generate-api-index.mjs && node scripts/generate-api-tag-pages.mjs",
"clean:api-docs": "docusaurus clean-api-docs superset",
"build": "yarn run _init && yarn run generate:extension-components && yarn run generate:database-docs && DEBUG=docusaurus:* docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "yarn run _init && docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "yarn run generate:all && tsc",
"typecheck": "yarn run generate:extension-components && yarn run generate:database-docs && tsc",
"generate:extension-components": "node scripts/generate-extension-components.mjs",
"generate:superset-components": "node scripts/generate-superset-components.mjs",
"generate:database-docs": "node scripts/generate-database-docs.mjs",
"gen-db-docs": "node scripts/generate-database-docs.mjs",
"generate:all": "yarn run generate:extension-components & yarn run generate:superset-components & yarn run generate:database-docs & wait && yarn run generate:api-docs",
"lint:db-metadata": "python3 ../superset/db_engine_specs/lint_metadata.py",
"lint:db-metadata:report": "python3 ../superset/db_engine_specs/lint_metadata.py --markdown -o ../superset/db_engine_specs/METADATA_STATUS.md",
"update:readme-db-logos": "node scripts/generate-database-docs.mjs --update-readme",
@@ -47,45 +42,40 @@
"@emotion/core": "^11.0.0",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.14.1",
"@fontsource/fira-code": "^5.2.7",
"@fontsource/inter": "^5.2.8",
"@mdx-js/react": "^3.1.1",
"@saucelabs/theme-github-codeblock": "^0.3.0",
"@storybook/addon-docs": "^8.6.15",
"@storybook/blocks": "^8.6.15",
"@storybook/channels": "^8.6.15",
"@storybook/client-logger": "^8.6.15",
"@storybook/components": "^8.6.15",
"@storybook/core": "^8.6.15",
"@storybook/core-events": "^8.6.15",
"@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.15",
"@storybook/preview-api": "^8.6.15",
"@storybook/theming": "^8.6.15",
"@storybook/docs-tools": "^8.6.11",
"@storybook/preview-api": "^8.6.11",
"@storybook/theming": "^8.6.11",
"@superset-ui/core": "^0.20.4",
"@swc/core": "^1.15.11",
"antd": "^6.2.3",
"baseline-browser-mapping": "^2.9.19",
"caniuse-lite": "^1.0.30001769",
"docusaurus-plugin-openapi-docs": "^4.6.0",
"docusaurus-theme-openapi-docs": "^4.6.0",
"antd": "^6.2.1",
"caniuse-lite": "^1.0.30001765",
"docusaurus-plugin-less": "^2.0.2",
"js-yaml": "^4.1.1",
"js-yaml-loader": "^1.2.2",
"json-bigint": "^1.0.0",
"less": "^4.5.1",
"less-loader": "^12.3.0",
"prism-react-renderer": "^2.4.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-github-btn": "^1.4.0",
"react-resize-detector": "^9.1.1",
"react-svg-pan-zoom": "^3.13.1",
"react-table": "^7.8.0",
"remark-import-partial": "^0.0.2",
"reselect": "^5.1.1",
"storybook": "^8.6.15",
"swagger-ui-react": "^5.31.0",
"swc-loader": "^0.2.7",
"tinycolor2": "^1.4.2",
"unist-util-visit": "^5.1.0"
"ts-loader": "^9.5.4",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.9.1",
@@ -99,11 +89,11 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-react": "^7.37.5",
"globals": "^17.3.0",
"globals": "^17.0.0",
"prettier": "^3.8.1",
"typescript": "~5.9.3",
"typescript-eslint": "^8.54.0",
"webpack": "^5.105.0"
"typescript-eslint": "^8.53.1",
"webpack": "^5.104.1"
},
"browserslist": {
"production": [
@@ -117,10 +107,5 @@
"last 1 safari version"
]
},
"resolutions": {
"react-redux": "^9.2.0",
"@reduxjs/toolkit": "^2.5.0",
"baseline-browser-mapping": "^2.9.19"
},
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
}

View File

@@ -1,123 +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.
*/
/**
* Convert the generated TypeScript API sidebar to CommonJS format.
* This allows the sidebar to be imported by sidebars.js.
* Also adds unique keys to duplicate labels to avoid translation conflicts.
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const sidebarTsPath = path.join(__dirname, '..', 'docs', 'api', 'sidebar.ts');
const sidebarJsPath = path.join(__dirname, '..', 'docs', 'api', 'sidebar.js');
if (!fs.existsSync(sidebarTsPath)) {
console.log('No sidebar.ts found, skipping conversion');
process.exit(0);
}
let content = fs.readFileSync(sidebarTsPath, 'utf8');
// Remove TypeScript import
content = content.replace(/import type.*\n/g, '');
// Remove type annotation
content = content.replace(/: SidebarsConfig/g, '');
// Change export default to module.exports
content = content.replace(
/export default sidebar\.apisidebar;/,
'module.exports = sidebar.apisidebar;'
);
// Parse the sidebar to add unique keys for duplicate labels
// This avoids translation key conflicts when the same label appears multiple times
try {
// Extract the sidebar object
const sidebarMatch = content.match(/const sidebar = (\{[\s\S]*\});/);
if (sidebarMatch) {
// Use Function constructor instead of eval for safer evaluation
const sidebarObj = new Function(`return ${sidebarMatch[1]}`)();
// First pass: count labels
const countLabels = (items) => {
const counts = {};
const count = (item) => {
if (item.type === 'doc' && item.label) {
counts[item.label] = (counts[item.label] || 0) + 1;
}
if (item.items) {
item.items.forEach(count);
}
};
items.forEach(count);
return counts;
};
const counts = countLabels(sidebarObj.apisidebar);
// Second pass: add keys to items with duplicate labels
const addKeys = (items, prefix = 'api') => {
for (const item of items) {
if (item.type === 'doc' && item.label && counts[item.label] > 1) {
item.key = item.id;
}
// Also add keys to categories to avoid conflicts with main sidebar categories
if (item.type === 'category' && item.label) {
item.key = `${prefix}-category-${item.label.toLowerCase().replace(/\s+/g, '-')}`;
}
if (item.items) {
addKeys(item.items, prefix);
}
}
};
addKeys(sidebarObj.apisidebar);
// Regenerate the content with the updated sidebar
content = `const sidebar = ${JSON.stringify(sidebarObj, null, 2)};
module.exports = sidebar.apisidebar;
`;
}
} catch (e) {
console.warn('Could not add unique keys to sidebar:', e.message);
// Fall back to simple conversion
content = content.replace(
/export default sidebar\.apisidebar;/,
'module.exports = sidebar.apisidebar;'
);
}
// Add header with eslint-disable to allow @ts-nocheck
const header = `/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
/**
* Auto-generated CommonJS sidebar from sidebar.ts
* Do not edit directly - run 'yarn generate:api-docs' to regenerate
*/
`;
fs.writeFileSync(sidebarJsPath, header + content);
console.log('Converted sidebar.ts to sidebar.js');

View File

@@ -1,296 +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.
"""
Extract custom_errors from database engine specs for documentation.
This script parses engine spec files to extract error handling information
that can be displayed on database documentation pages.
Usage: python scripts/extract_custom_errors.py
Output: JSON mapping of engine spec module names to their custom errors
"""
import ast
import json # noqa: TID251 - standalone docs script, not part of superset
import sys
from pathlib import Path
from typing import Any
# Map SupersetErrorType values to human-readable categories and issue codes
ERROR_TYPE_INFO = {
"CONNECTION_INVALID_USERNAME_ERROR": {
"category": "Authentication",
"description": "Invalid username",
"issue_codes": [1012],
},
"CONNECTION_INVALID_PASSWORD_ERROR": {
"category": "Authentication",
"description": "Invalid password",
"issue_codes": [1013],
},
"CONNECTION_ACCESS_DENIED_ERROR": {
"category": "Authentication",
"description": "Access denied",
"issue_codes": [1014, 1015],
},
"CONNECTION_INVALID_HOSTNAME_ERROR": {
"category": "Connection",
"description": "Invalid hostname",
"issue_codes": [1007],
},
"CONNECTION_PORT_CLOSED_ERROR": {
"category": "Connection",
"description": "Port closed or refused",
"issue_codes": [1008],
},
"CONNECTION_HOST_DOWN_ERROR": {
"category": "Connection",
"description": "Host unreachable",
"issue_codes": [1009],
},
"CONNECTION_UNKNOWN_DATABASE_ERROR": {
"category": "Connection",
"description": "Unknown database",
"issue_codes": [1015],
},
"CONNECTION_DATABASE_PERMISSIONS_ERROR": {
"category": "Permissions",
"description": "Insufficient permissions",
"issue_codes": [1017],
},
"CONNECTION_MISSING_PARAMETERS_ERROR": {
"category": "Configuration",
"description": "Missing parameters",
"issue_codes": [1018],
},
"CONNECTION_DATABASE_TIMEOUT": {
"category": "Connection",
"description": "Connection timeout",
"issue_codes": [1001, 1009],
},
"COLUMN_DOES_NOT_EXIST_ERROR": {
"category": "Query",
"description": "Column not found",
"issue_codes": [1003, 1004],
},
"TABLE_DOES_NOT_EXIST_ERROR": {
"category": "Query",
"description": "Table not found",
"issue_codes": [1003, 1005],
},
"SCHEMA_DOES_NOT_EXIST_ERROR": {
"category": "Query",
"description": "Schema not found",
"issue_codes": [1003, 1016],
},
"SYNTAX_ERROR": {
"category": "Query",
"description": "SQL syntax error",
"issue_codes": [1030],
},
"OBJECT_DOES_NOT_EXIST_ERROR": {
"category": "Query",
"description": "Object not found",
"issue_codes": [1029],
},
"GENERIC_DB_ENGINE_ERROR": {
"category": "General",
"description": "Database engine error",
"issue_codes": [1002],
},
}
def extract_string_from_call(node: ast.Call) -> str | None:
"""Extract string from __() or _() translation calls."""
if not node.args:
return None
arg = node.args[0]
if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
return arg.value
elif isinstance(arg, ast.JoinedStr):
# f-string - try to reconstruct
parts = []
for value in arg.values:
if isinstance(value, ast.Constant):
parts.append(str(value.value))
elif isinstance(value, ast.FormattedValue):
# Just use a placeholder
parts.append("{...}")
return "".join(parts)
return None
def extract_custom_errors_from_file(filepath: Path) -> dict[str, list[dict[str, Any]]]:
"""
Extract custom_errors definitions from a Python engine spec file.
Returns a dict mapping class names to their custom errors list.
"""
results = {}
try:
with open(filepath, "r", encoding="utf-8") as f:
source = f.read()
tree = ast.parse(source)
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
class_name = node.name
for item in node.body:
# Look for custom_errors = { ... }
if (
isinstance(item, ast.AnnAssign)
and isinstance(item.target, ast.Name)
and item.target.id == "custom_errors"
and isinstance(item.value, ast.Dict)
):
errors = extract_errors_from_dict(item.value, source)
if errors:
results[class_name] = errors
# Also handle simple assignment: custom_errors = { ... }
elif (
isinstance(item, ast.Assign)
and len(item.targets) == 1
and isinstance(item.targets[0], ast.Name)
and item.targets[0].id == "custom_errors"
and isinstance(item.value, ast.Dict)
):
errors = extract_errors_from_dict(item.value, source)
if errors:
results[class_name] = errors
except (OSError, SyntaxError, ValueError) as e:
print(f"Error parsing {filepath}: {e}", file=sys.stderr)
return results
def extract_regex_info(key: ast.expr) -> dict[str, Any]:
"""Extract regex pattern info from the dict key."""
if isinstance(key, ast.Name):
return {"regex_name": key.id}
if isinstance(key, ast.Call):
if (
isinstance(key.func, ast.Attribute)
and key.func.attr == "compile"
and key.args
and isinstance(key.args[0], ast.Constant)
):
return {"regex_pattern": key.args[0].value}
return {}
def extract_invalid_fields(extra_node: ast.Dict) -> list[str]:
"""Extract invalid fields from the extra dict."""
for k, v in zip(extra_node.keys, extra_node.values, strict=False):
if (
isinstance(k, ast.Constant)
and k.value == "invalid"
and isinstance(v, ast.List)
):
return [elem.value for elem in v.elts if isinstance(elem, ast.Constant)]
return []
def extract_error_tuple_info(value: ast.Tuple) -> dict[str, Any]:
"""Extract error info from the (message, error_type, extra) tuple."""
result: dict[str, Any] = {}
# First element: message template
msg_node = value.elts[0]
if isinstance(msg_node, ast.Call):
message = extract_string_from_call(msg_node)
if message:
result["message_template"] = message
elif isinstance(msg_node, ast.Constant):
result["message_template"] = msg_node.value
# Second element: SupersetErrorType.SOMETHING
type_node = value.elts[1]
if isinstance(type_node, ast.Attribute):
error_type = type_node.attr
result["error_type"] = error_type
if error_type in ERROR_TYPE_INFO:
type_info = ERROR_TYPE_INFO[error_type]
result["category"] = type_info["category"]
result["description"] = type_info["description"]
result["issue_codes"] = type_info["issue_codes"]
# Third element: extra dict with invalid fields
if len(value.elts) >= 3 and isinstance(value.elts[2], ast.Dict):
invalid_fields = extract_invalid_fields(value.elts[2])
if invalid_fields:
result["invalid_fields"] = invalid_fields
return result
def extract_errors_from_dict(dict_node: ast.Dict, source: str) -> list[dict[str, Any]]:
"""Extract error information from a custom_errors dict AST node."""
errors = []
for key, value in zip(dict_node.keys, dict_node.values, strict=False):
if key is None or value is None:
continue
error_info = extract_regex_info(key)
if isinstance(value, ast.Tuple) and len(value.elts) >= 2:
error_info.update(extract_error_tuple_info(value))
if error_info.get("error_type") and error_info.get("message_template"):
errors.append(error_info)
return errors
def main() -> None:
"""Main function to extract custom_errors from all engine specs."""
# Find the superset root directory
script_dir = Path(__file__).parent
root_dir = script_dir.parent.parent
specs_dir = root_dir / "superset" / "db_engine_specs"
if not specs_dir.exists():
print(f"Error: Engine specs directory not found: {specs_dir}", file=sys.stderr)
sys.exit(1)
all_errors = {}
# Process each Python file in the specs directory
for filepath in sorted(specs_dir.glob("*.py")):
if filepath.name.startswith("_"):
continue
module_name = filepath.stem
class_errors = extract_custom_errors_from_file(filepath)
if class_errors:
# Store errors by module and class
all_errors[module_name] = class_errors
# Output as JSON
print(json.dumps(all_errors, indent=2))
if __name__ == "__main__":
main()

View File

@@ -1,828 +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.
"""
Fix missing schema references in the OpenAPI spec.
This script patches the openapi.json file to add any missing schemas
that are referenced but not defined.
"""
import json # noqa: TID251 - standalone docs script
import sys
from pathlib import Path
from typing import Any
def add_missing_schemas(spec: dict[str, Any]) -> tuple[dict[str, Any], list[str]]:
"""Add missing schema definitions to the OpenAPI spec."""
schemas = spec.get("components", {}).get("schemas", {})
fixed = []
# DashboardScreenshotPostSchema - based on superset/dashboards/schemas.py
if "DashboardScreenshotPostSchema" not in schemas:
schemas["DashboardScreenshotPostSchema"] = {
"type": "object",
"properties": {
"dataMask": {
"type": "object",
"description": "An object representing the data mask.",
"additionalProperties": True,
},
"activeTabs": {
"type": "array",
"items": {"type": "string"},
"description": "A list representing active tabs.",
},
"anchor": {
"type": "string",
"description": "A string representing the anchor.",
},
"urlParams": {
"type": "array",
"items": {
"type": "array",
"items": {"type": "string"},
"minItems": 2,
"maxItems": 2,
},
"description": "A list of tuples, each containing two strings.",
},
},
}
fixed.append("DashboardScreenshotPostSchema")
# DashboardNativeFiltersConfigUpdateSchema - based on superset/dashboards/schemas.py
if "DashboardNativeFiltersConfigUpdateSchema" not in schemas:
schemas["DashboardNativeFiltersConfigUpdateSchema"] = {
"type": "object",
"properties": {
"deleted": {
"type": "array",
"items": {"type": "string"},
"description": "List of deleted filter IDs.",
},
"modified": {
"type": "array",
"items": {"type": "object"},
"description": "List of modified filter configurations.",
},
"reordered": {
"type": "array",
"items": {"type": "string"},
"description": "List of filter IDs in new order.",
},
},
}
fixed.append("DashboardNativeFiltersConfigUpdateSchema")
# DashboardColorsConfigUpdateSchema - based on superset/dashboards/schemas.py
if "DashboardColorsConfigUpdateSchema" not in schemas:
schemas["DashboardColorsConfigUpdateSchema"] = {
"type": "object",
"properties": {
"color_namespace": {
"type": "string",
"nullable": True,
"description": "The color namespace.",
},
"color_scheme": {
"type": "string",
"nullable": True,
"description": "The color scheme name.",
},
"map_label_colors": {
"type": "object",
"additionalProperties": {"type": "string"},
"description": "Mapping of labels to colors.",
},
"shared_label_colors": {
"type": "object",
"additionalProperties": {"type": "string"},
"description": "Shared label colors across charts.",
},
"label_colors": {
"type": "object",
"additionalProperties": {"type": "string"},
"description": "Label to color mapping.",
},
"color_scheme_domain": {
"type": "array",
"items": {"type": "string"},
"description": "Color scheme domain values.",
},
},
}
fixed.append("DashboardColorsConfigUpdateSchema")
# FormatQueryPayloadSchema - based on superset/sqllab/schemas.py
if "FormatQueryPayloadSchema" not in schemas:
schemas["FormatQueryPayloadSchema"] = {
"type": "object",
"required": ["sql"],
"properties": {
"sql": {
"type": "string",
"description": "The SQL query to format.",
},
"engine": {
"type": "string",
"nullable": True,
"description": "The database engine.",
},
"database_id": {
"type": "integer",
"nullable": True,
"description": "The database id.",
},
"template_params": {
"type": "string",
"nullable": True,
"description": "The SQL query template params as JSON string.",
},
},
}
fixed.append("FormatQueryPayloadSchema")
# get_slack_channels_schema - based on superset/reports/schemas.py
if "get_slack_channels_schema" not in schemas:
schemas["get_slack_channels_schema"] = {
"type": "object",
"properties": {
"search_string": {
"type": "string",
"description": "String to search for in channel names.",
},
"types": {
"type": "array",
"items": {
"type": "string",
"enum": ["public_channel", "private_channel"],
},
"description": "Types of channels to search.",
},
"exact_match": {
"type": "boolean",
"description": "Whether to match channel names exactly.",
},
},
}
fixed.append("get_slack_channels_schema")
if "components" not in spec:
spec["components"] = {}
spec["components"]["schemas"] = schemas
return spec, fixed
def path_to_operation_id(path: str, method: str) -> str:
"""Convert a path and method to an operationId."""
# Remove /api/v1/ prefix
clean_path = path.replace("/api/v1/", "").strip("/")
# Replace path parameters
clean_path = clean_path.replace("{", "by_").replace("}", "")
# Create operation name
method_prefix = {
"get": "get",
"post": "create",
"put": "update",
"delete": "delete",
"patch": "patch",
}.get(method.lower(), method.lower())
return f"{method_prefix}_{clean_path}".replace("/", "_").replace("-", "_")
def path_to_summary(path: str, method: str) -> str:
"""Generate a human-readable summary from path and method."""
# Remove /api/v1/ prefix
clean_path = path.replace("/api/v1/", "").strip("/")
# Handle path parameters
parts = []
for part in clean_path.split("/"):
if part.startswith("{") and part.endswith("}"):
param = part[1:-1]
parts.append(f"by {param}")
else:
parts.append(part.replace("_", " ").replace("-", " "))
resource = " ".join(parts)
method_verb = {
"get": "Get",
"post": "Create",
"put": "Update",
"delete": "Delete",
"patch": "Update",
}.get(method.lower(), method.capitalize())
return f"{method_verb} {resource}"
def add_missing_operation_ids(spec: dict[str, Any]) -> int:
"""Add operationId and summary to operations that are missing them."""
fixed_count = 0
for path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["get", "post", "put", "delete", "patch"]:
continue
if not isinstance(details, dict):
continue
summary = details.get("summary")
operation_id = details.get("operationId")
if not summary and not operation_id:
details["operationId"] = path_to_operation_id(path, method)
details["summary"] = path_to_summary(path, method)
fixed_count += 1
return fixed_count
TAG_DESCRIPTIONS = {
"Advanced Data Type": "Advanced data type operations and conversions.",
"Annotation Layers": "Manage annotation layers and annotations for charts.",
"AsyncEventsRestApi": "Real-time event streaming via Server-Sent Events (SSE).",
"Available Domains": "Get available domains for the Superset instance.",
"CSS Templates": "Manage CSS templates for custom dashboard styling.",
"CacheRestApi": "Cache management and invalidation operations.",
"Charts": "Create, read, update, and delete charts (slices).",
"Current User": "Get information about the authenticated user.",
"Dashboard Filter State": "Manage temporary filter state for dashboards.",
"Dashboard Permanent Link": "Permanent links to dashboard states.",
"Dashboards": "Create, read, update, and delete dashboards.",
"Database": "Manage database connections and metadata.",
"Datasets": "Manage datasets (tables) used for building charts.",
"Datasources": "Query datasource metadata and column values.",
"Embedded Dashboard": "Configure embedded dashboard settings.",
"Explore": "Chart exploration and data querying endpoints.",
"Explore Form Data": "Manage temporary form data for chart exploration.",
"Explore Permanent Link": "Permanent links to chart explore states.",
"Import/export": "Import and export Superset assets.",
"LogRestApi": "Access audit logs and activity history.",
"Menu": "Get the Superset menu structure.",
"OpenApi": "Access the OpenAPI specification.",
"Queries": "View and manage SQL Lab query history.",
"Report Schedules": "Configure scheduled reports and alerts.",
"Row Level Security": "Manage row-level security rules for data access.",
"SQL Lab": "Execute SQL queries and manage SQL Lab sessions.",
"SQL Lab Permanent Link": "Permanent links to SQL Lab states.",
"Security": "Authentication and token management.",
"Security Permissions": "View available permissions.",
"Security Permissions on Resources (View Menus)": "Permission-resource mappings.",
"Security Resources (View Menus)": "Manage security resources (view menus).",
"Security Roles": "Manage security roles and their permissions.",
"Security Users": "Manage user accounts.",
"Tags": "Organize assets with tags.",
"User": "User profile and preferences.",
}
def generate_code_sample(
method: str, path: str, has_body: bool = False
) -> list[dict[str, str]]:
"""Generate code samples for an endpoint in multiple languages."""
# Clean up path for display
example_path = path.replace("{pk}", "1").replace("{id_or_slug}", "1")
samples = []
# cURL sample
curl_cmd = f'curl -X {method.upper()} "http://localhost:8088{example_path}"'
curl_cmd += ' \\\n -H "Authorization: Bearer $ACCESS_TOKEN"'
if has_body:
curl_cmd += ' \\\n -H "Content-Type: application/json"'
curl_cmd += ' \\\n -d \'{"key": "value"}\''
samples.append(
{
"lang": "cURL",
"label": "cURL",
"source": curl_cmd,
}
)
# Python sample
if method.lower() == "get":
python_code = f"""import requests
response = requests.get(
"http://localhost:8088{example_path}",
headers={{"Authorization": "Bearer " + access_token}}
)
print(response.json())"""
elif method.lower() == "post":
python_code = f"""import requests
response = requests.post(
"http://localhost:8088{example_path}",
headers={{"Authorization": "Bearer " + access_token}},
json={{"key": "value"}}
)
print(response.json())"""
elif method.lower() == "put":
python_code = f"""import requests
response = requests.put(
"http://localhost:8088{example_path}",
headers={{"Authorization": "Bearer " + access_token}},
json={{"key": "value"}}
)
print(response.json())"""
elif method.lower() == "delete":
python_code = f"""import requests
response = requests.delete(
"http://localhost:8088{example_path}",
headers={{"Authorization": "Bearer " + access_token}}
)
print(response.status_code)"""
else:
python_code = f"""import requests
response = requests.{method.lower()}(
"http://localhost:8088{example_path}",
headers={{"Authorization": "Bearer " + access_token}}
)
print(response.json())"""
samples.append(
{
"lang": "Python",
"label": "Python",
"source": python_code,
}
)
# JavaScript sample
if method.lower() == "get":
js_code = f"""const response = await fetch(
"http://localhost:8088{example_path}",
{{
headers: {{
"Authorization": `Bearer ${{accessToken}}`
}}
}}
);
const data = await response.json();
console.log(data);"""
elif method.lower() in ["post", "put", "patch"]:
js_code = f"""const response = await fetch(
"http://localhost:8088{example_path}",
{{
method: "{method.upper()}",
headers: {{
"Authorization": `Bearer ${{accessToken}}`,
"Content-Type": "application/json"
}},
body: JSON.stringify({{ key: "value" }})
}}
);
const data = await response.json();
console.log(data);"""
else:
js_code = f"""const response = await fetch(
"http://localhost:8088{example_path}",
{{
method: "{method.upper()}",
headers: {{
"Authorization": `Bearer ${{accessToken}}`
}}
}}
);
console.log(response.status);"""
samples.append(
{
"lang": "JavaScript",
"label": "JavaScript",
"source": js_code,
}
)
return samples
def add_code_samples(spec: dict[str, Any]) -> int:
"""Add code samples to all endpoints."""
count = 0
for path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["get", "post", "put", "delete", "patch"]:
continue
if not isinstance(details, dict):
continue
# Skip if already has code samples
if "x-codeSamples" in details:
continue
# Check if endpoint has a request body
has_body = "requestBody" in details
details["x-codeSamples"] = generate_code_sample(method, path, has_body)
count += 1
return count
def configure_servers(spec: dict[str, Any]) -> bool:
"""Configure server URLs with variables for flexible API testing."""
new_servers = [
{
"url": "http://localhost:8088",
"description": "Local development server",
},
{
"url": "{protocol}://{host}:{port}",
"description": "Custom server",
"variables": {
"protocol": {
"default": "http",
"enum": ["http", "https"],
"description": "HTTP protocol",
},
"host": {
"default": "localhost",
"description": "Server hostname or IP",
},
"port": {
"default": "8088",
"description": "Server port",
},
},
},
]
# Check if already configured
existing = spec.get("servers", [])
if len(existing) >= 2 and any("variables" in s for s in existing):
return False
spec["servers"] = new_servers
return True
def add_tag_definitions(spec: dict[str, Any]) -> int:
"""Add tag definitions with descriptions to the OpenAPI spec."""
# Collect all unique tags used in operations
used_tags: set[str] = set()
for _path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["get", "post", "put", "delete", "patch"]:
continue
if not isinstance(details, dict):
continue
tags = details.get("tags", [])
used_tags.update(tags)
# Create tag definitions
tag_definitions = []
for tag in sorted(used_tags):
tag_def = {"name": tag}
if tag in TAG_DESCRIPTIONS:
tag_def["description"] = TAG_DESCRIPTIONS[tag]
else:
# Generate a generic description
tag_def["description"] = f"Endpoints related to {tag}."
tag_definitions.append(tag_def)
# Only update if we have new tags
existing_tags = {t.get("name") for t in spec.get("tags", [])}
new_tags = [t for t in tag_definitions if t["name"] not in existing_tags]
if new_tags or not spec.get("tags"):
spec["tags"] = tag_definitions
return len(tag_definitions)
return 0
def generate_example_from_schema( # noqa: C901
schema: dict[str, Any],
spec: dict[str, Any],
depth: int = 0,
max_depth: int = 5,
) -> dict[str, Any] | list[Any] | str | int | float | bool | None:
"""Generate an example value from an OpenAPI schema definition."""
if depth > max_depth:
return None
# Handle $ref
if "$ref" in schema:
ref_path = schema["$ref"]
if ref_path.startswith("#/components/schemas/"):
schema_name = ref_path.split("/")[-1]
ref_schema = (
spec.get("components", {}).get("schemas", {}).get(schema_name, {})
)
return generate_example_from_schema(ref_schema, spec, depth + 1, max_depth)
return None
# If schema already has an example, use it
if "example" in schema:
return schema["example"]
schema_type = schema.get("type", "object")
if schema_type == "object":
properties = schema.get("properties", {})
if not properties:
# Check for additionalProperties
if schema.get("additionalProperties"):
return {"key": "value"}
return {}
result = {}
for prop_name, prop_schema in properties.items():
# Limit object depth and skip large nested objects
if depth < max_depth:
example_val = generate_example_from_schema(
prop_schema, spec, depth + 1, max_depth
)
if example_val is not None:
result[prop_name] = example_val
return result
elif schema_type == "array":
items_schema = schema.get("items", {})
if items_schema:
item_example = generate_example_from_schema(
items_schema, spec, depth + 1, max_depth
)
if item_example is not None:
return [item_example]
return []
elif schema_type == "string":
# Check for enum
if "enum" in schema:
return schema["enum"][0]
# Check for format
fmt = schema.get("format", "")
if fmt == "date-time":
return "2024-01-15T10:30:00Z"
elif fmt == "date":
return "2024-01-15"
elif fmt == "email":
return "user@example.com"
elif fmt == "uri" or fmt == "url":
return "https://example.com"
elif fmt == "uuid":
return "550e8400-e29b-41d4-a716-446655440000"
# Use description hints or prop name
return "string"
elif schema_type == "integer":
if "minimum" in schema:
return schema["minimum"]
return 1
elif schema_type == "number":
if "minimum" in schema:
return schema["minimum"]
return 1.0
elif schema_type == "boolean":
return True
elif schema_type == "null":
return None
# Handle oneOf, anyOf
if "oneOf" in schema and schema["oneOf"]:
return generate_example_from_schema(
schema["oneOf"][0], spec, depth + 1, max_depth
)
if "anyOf" in schema and schema["anyOf"]:
return generate_example_from_schema(
schema["anyOf"][0], spec, depth + 1, max_depth
)
return None
def add_response_examples(spec: dict[str, Any]) -> int: # noqa: C901
"""Add example values to API responses for better documentation."""
count = 0
# First, add examples to standard error responses in components
standard_errors = {
"400": {"message": "Bad request: Invalid parameters provided"},
"401": {"message": "Unauthorized: Authentication required"},
"403": {
"message": "Forbidden: You don't have permission to access this resource"
},
"404": {"message": "Not found: The requested resource does not exist"},
"422": {"message": "Unprocessable entity: Validation error"},
"500": {"message": "Internal server error: An unexpected error occurred"},
}
responses = spec.get("components", {}).get("responses", {})
for code, example_value in standard_errors.items():
if code in responses:
response = responses[code]
content = response.get("content", {}).get("application/json", {})
if content and "example" not in content:
content["example"] = example_value
count += 1
# Now add examples to inline response schemas in operations
for _path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["get", "post", "put", "delete", "patch"]:
continue
if not isinstance(details, dict):
continue
responses_dict = details.get("responses", {})
for _status_code, response in responses_dict.items():
# Skip $ref responses (already handled above)
if "$ref" in response:
continue
content = response.get("content", {}).get("application/json", {})
if not content:
continue
# Skip if already has an example
if "example" in content:
continue
schema = content.get("schema", {})
if schema:
example = generate_example_from_schema(
schema, spec, depth=0, max_depth=3
)
if example is not None and example != {}:
content["example"] = example
count += 1
return count
def add_request_body_examples(spec: dict[str, Any]) -> int:
"""Add example values to API request bodies for better documentation."""
count = 0
for _path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["post", "put", "patch"]:
continue
if not isinstance(details, dict):
continue
request_body = details.get("requestBody", {})
if not request_body or "$ref" in request_body:
continue
content = request_body.get("content", {}).get("application/json", {})
if not content:
continue
# Skip if already has an example
if "example" in content:
continue
schema = content.get("schema", {})
if schema:
example = generate_example_from_schema(
schema, spec, depth=0, max_depth=4
)
if example is not None and example != {}:
content["example"] = example
count += 1
return count
def make_summaries_unique(spec: dict[str, Any]) -> int: # noqa: C901
"""Make duplicate summaries unique by adding context from the path."""
summary_info: dict[str, list[tuple[str, str]]] = {}
fixed_count = 0
# First pass: collect all summaries and their paths (regardless of method)
for path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["get", "post", "put", "delete", "patch"]:
continue
if not isinstance(details, dict):
continue
summary = details.get("summary")
if summary:
if summary not in summary_info:
summary_info[summary] = []
summary_info[summary].append((path, method))
# Second pass: make duplicate summaries unique
for path, methods in spec.get("paths", {}).items():
for method, details in methods.items():
if method not in ["get", "post", "put", "delete", "patch"]:
continue
if not isinstance(details, dict):
continue
summary = details.get("summary")
if summary and len(summary_info.get(summary, [])) > 1:
# Create a unique suffix from the full path
# e.g., /api/v1/chart/{pk}/cache_screenshot/ -> "chart-cache-screenshot"
clean_path = path.replace("/api/v1/", "").strip("/")
# Remove parameter placeholders and convert to slug
clean_path = clean_path.replace("{", "").replace("}", "")
path_slug = clean_path.replace("/", "-").replace("_", "-")
# Check if this suffix is already in the summary
if path_slug not in summary.lower():
new_summary = f"{summary} ({path_slug})"
details["summary"] = new_summary
fixed_count += 1
return fixed_count
def main() -> None: # noqa: C901
"""Main function to fix the OpenAPI spec."""
script_dir = Path(__file__).parent
spec_path = script_dir.parent / "static" / "resources" / "openapi.json"
if not spec_path.exists():
print(f"Error: OpenAPI spec not found at {spec_path}", file=sys.stderr)
sys.exit(1)
print(f"Reading OpenAPI spec from {spec_path}")
with open(spec_path, encoding="utf-8") as f:
spec = json.load(f)
spec, fixed_schemas = add_missing_schemas(spec)
fixed_ops = add_missing_operation_ids(spec)
fixed_tags = add_tag_definitions(spec)
fixed_servers = configure_servers(spec)
changes_made = False
if fixed_servers:
print("Configured server URLs with variables for flexible API testing")
changes_made = True
if fixed_samples := add_code_samples(spec):
print(f"Added code samples to {fixed_samples} endpoints")
changes_made = True
if fixed_examples := add_response_examples(spec):
print(f"Added example JSON responses to {fixed_examples} response schemas")
changes_made = True
if fixed_request_examples := add_request_body_examples(spec):
print(f"Added example JSON to {fixed_request_examples} request bodies")
changes_made = True
if fixed_schemas:
print(f"Added missing schemas: {', '.join(fixed_schemas)}")
changes_made = True
if fixed_ops:
print(f"Added operationId/summary to {fixed_ops} operations")
changes_made = True
if fixed_tags:
print(f"Added {fixed_tags} tag definitions with descriptions")
changes_made = True
if fixed_summaries := make_summaries_unique(spec):
print(f"Made {fixed_summaries} duplicate summaries unique")
changes_made = True
if changes_made:
with open(spec_path, "w", encoding="utf-8") as f:
json.dump(spec, f, indent=2)
f.write("\n") # Ensure trailing newline for pre-commit
print(f"Updated {spec_path}")
else:
print("No fixes needed")
if __name__ == "__main__":
main()

View File

@@ -1,277 +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.
*/
/**
* Generates a comprehensive API index MDX file from the OpenAPI spec.
* This creates the api.mdx landing page with all endpoints organized by category.
*
* Uses the generated sidebar to get correct endpoint slugs (the plugin's
* slug algorithm differs from a simple slugify, e.g. handling apostrophes
* and camelCase differently).
*/
import fs from 'fs';
import path from 'path';
import { createRequire } from 'module';
import { fileURLToPath } from 'url';
const require = createRequire(import.meta.url);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SPEC_PATH = path.join(__dirname, '..', 'static', 'resources', 'openapi.json');
const SIDEBAR_PATH = path.join(__dirname, '..', 'docs', 'api', 'sidebar.js');
const OUTPUT_PATH = path.join(__dirname, '..', 'docs', 'api.mdx');
// Category groupings for better organization
const CATEGORY_GROUPS = {
'Authentication': ['Security'],
'Core Resources': ['Dashboards', 'Charts', 'Datasets', 'Database'],
'Data Exploration': ['Explore', 'SQL Lab', 'Queries', 'Datasources', 'Advanced Data Type'],
'Organization & Customization': ['Tags', 'Annotation Layers', 'CSS Templates'],
'Sharing & Embedding': [
'Dashboard Permanent Link', 'Explore Permanent Link', 'SQL Lab Permanent Link',
'Embedded Dashboard', 'Dashboard Filter State', 'Explore Form Data'
],
'Scheduling & Alerts': ['Report Schedules'],
'Security & Access Control': [
'Security Roles', 'Security Users', 'Security Permissions',
'Security Resources (View Menus)', 'Security Permissions on Resources (View Menus)',
'Row Level Security'
],
'Import/Export & Administration': ['Import/export', 'CacheRestApi', 'LogRestApi'],
'User & System': ['Current User', 'User', 'Menu', 'Available Domains', 'AsyncEventsRestApi', 'OpenApi'],
};
/**
* Build a map from sidebar label → doc slug by reading the generated sidebar.
* This ensures we use the exact same slugs that docusaurus-openapi-docs generated.
*/
function buildSlugMap() {
const labelToSlug = {};
try {
const sidebar = require(SIDEBAR_PATH);
const extractDocs = (items) => {
for (const item of items) {
if (item.type === 'doc' && item.label && item.id) {
// id is like "api/create-security-login" → slug "create-security-login"
const slug = item.id.replace(/^api\//, '');
labelToSlug[item.label] = slug;
}
if (item.items) extractDocs(item.items);
}
};
extractDocs(sidebar);
console.log(`Loaded ${Object.keys(labelToSlug).length} slug mappings from sidebar`);
} catch {
console.warn('Could not read sidebar, will use computed slugs');
}
return labelToSlug;
}
function slugify(text) {
return text
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '');
}
function main() {
console.log(`Reading OpenAPI spec from ${SPEC_PATH}`);
const spec = JSON.parse(fs.readFileSync(SPEC_PATH, 'utf-8'));
// Build slug map from the generated sidebar
const labelToSlug = buildSlugMap();
// Build a map of tag -> endpoints
const tagEndpoints = {};
const tagDescriptions = {};
// Get tag descriptions
for (const tag of spec.tags || []) {
tagDescriptions[tag.name] = tag.description || '';
}
// Collect endpoints by tag
for (const [pathUrl, methods] of Object.entries(spec.paths || {})) {
for (const [method, details] of Object.entries(methods)) {
if (!['get', 'post', 'put', 'delete', 'patch'].includes(method)) continue;
const tags = details.tags || ['Untagged'];
const summary = details.summary || `${method.toUpperCase()} ${pathUrl}`;
// Use sidebar slug if available, fall back to computed slug
const slug = labelToSlug[summary] || slugify(summary);
for (const tag of tags) {
if (!tagEndpoints[tag]) {
tagEndpoints[tag] = [];
}
tagEndpoints[tag].push({
method: method.toUpperCase(),
path: pathUrl,
summary,
slug,
});
}
}
}
// Sort endpoints within each tag by path
for (const tag of Object.keys(tagEndpoints)) {
tagEndpoints[tag].sort((a, b) => a.path.localeCompare(b.path));
}
// Generate MDX content
let mdx = `---
title: API Reference
hide_title: true
sidebar_position: 10
---
import { Alert } from 'antd';
## REST API Reference
Superset exposes a comprehensive **REST API** that follows the [OpenAPI specification](https://swagger.io/specification/).
You can use this API to programmatically interact with Superset for automation, integrations, and custom applications.
<Alert
type="info"
showIcon
message="Code Samples & Schema Documentation"
description={
<span>
Each endpoint includes ready-to-use code samples in <strong>cURL</strong>, <strong>Python</strong>, and <strong>JavaScript</strong>.
The sidebar includes <strong>Schema definitions</strong> for detailed data model documentation.
</span>
}
style={{ marginBottom: '24px' }}
/>
---
`;
// Track which tags we've rendered
const renderedTags = new Set();
// Render Authentication first (it's critical for using the API)
mdx += `### Authentication
Most API endpoints require authentication via JWT tokens.
#### Quick Start
\`\`\`bash
# 1. Get a JWT token
curl -X POST http://localhost:8088/api/v1/security/login \\
-H "Content-Type: application/json" \\
-d '{"username": "admin", "password": "admin", "provider": "db"}'
# 2. Use the access_token from the response
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \\
http://localhost:8088/api/v1/dashboard/
\`\`\`
#### Security Endpoints
`;
// Render Security tag endpoints
if (tagEndpoints['Security']) {
mdx += `| Method | Endpoint | Description |\n`;
mdx += `|--------|----------|-------------|\n`;
for (const ep of tagEndpoints['Security']) {
mdx += `| \`${ep.method}\` | [${ep.summary}](./api/${ep.slug}) | \`${ep.path}\` |\n`;
}
mdx += '\n';
renderedTags.add('Security');
}
mdx += `---\n\n### API Endpoints\n\n`;
// Render each category group
for (const [groupName, groupTags] of Object.entries(CATEGORY_GROUPS)) {
if (groupName === 'Authentication') continue; // Already rendered
const tagsInGroup = groupTags.filter(tag => tagEndpoints[tag] && !renderedTags.has(tag));
if (tagsInGroup.length === 0) continue;
mdx += `#### ${groupName}\n\n`;
for (const tag of tagsInGroup) {
const description = tagDescriptions[tag] || '';
const endpoints = tagEndpoints[tag];
mdx += `<details>\n`;
mdx += `<summary><strong>${tag}</strong> (${endpoints.length} endpoints) — ${description}</summary>\n\n`;
mdx += `| Method | Endpoint | Description |\n`;
mdx += `|--------|----------|-------------|\n`;
for (const ep of endpoints) {
mdx += `| \`${ep.method}\` | [${ep.summary}](./api/${ep.slug}) | \`${ep.path}\` |\n`;
}
mdx += `\n</details>\n\n`;
renderedTags.add(tag);
}
}
// Render any remaining tags not in a group
const remainingTags = Object.keys(tagEndpoints).filter(tag => !renderedTags.has(tag));
if (remainingTags.length > 0) {
mdx += `#### Other\n\n`;
for (const tag of remainingTags.sort()) {
const description = tagDescriptions[tag] || '';
const endpoints = tagEndpoints[tag];
mdx += `<details>\n`;
mdx += `<summary><strong>${tag}</strong> (${endpoints.length} endpoints) — ${description}</summary>\n\n`;
mdx += `| Method | Endpoint | Description |\n`;
mdx += `|--------|----------|-------------|\n`;
for (const ep of endpoints) {
mdx += `| \`${ep.method}\` | [${ep.summary}](./api/${ep.slug}) | \`${ep.path}\` |\n`;
}
mdx += `\n</details>\n\n`;
}
}
mdx += `---
### Additional Resources
- [Superset REST API Blog Post](https://preset.io/blog/2020-10-01-superset-api/)
- [Accessing APIs with Superset](https://preset.io/blog/accessing-apis-with-superset/)
`;
// Write output
fs.writeFileSync(OUTPUT_PATH, mdx);
console.log(`Generated API index at ${OUTPUT_PATH}`);
console.log(`Total tags: ${Object.keys(tagEndpoints).length}`);
console.log(`Total endpoints: ${Object.values(tagEndpoints).flat().length}`);
}
main();

View File

@@ -1,176 +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.
*/
/**
* Replaces auto-generated tag pages (DocCardList cards) with endpoint tables
* showing HTTP method, endpoint name, and URI path for each endpoint in the tag.
*
* Runs after `docusaurus gen-api-docs` and `convert-api-sidebar.mjs`.
* Uses the generated sidebar to get correct endpoint slugs.
*/
import fs from 'fs';
import path from 'path';
import { createRequire } from 'module';
import { fileURLToPath } from 'url';
const require = createRequire(import.meta.url);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SPEC_PATH = path.join(__dirname, '..', 'static', 'resources', 'openapi.json');
const API_DOCS_DIR = path.join(__dirname, '..', 'docs', 'api');
const SIDEBAR_PATH = path.join(API_DOCS_DIR, 'sidebar.js');
function slugify(text) {
return text
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '');
}
/**
* Build a map from sidebar label → doc slug by reading the generated sidebar.
*/
function buildSlugMap() {
const labelToSlug = {};
try {
const sidebar = require(SIDEBAR_PATH);
const extractDocs = (items) => {
for (const item of items) {
if (item.type === 'doc' && item.label && item.id) {
const slug = item.id.replace(/^api\//, '');
labelToSlug[item.label] = slug;
}
if (item.items) extractDocs(item.items);
}
};
extractDocs(sidebar);
} catch {
console.warn('Could not read sidebar, will use computed slugs');
}
return labelToSlug;
}
function main() {
console.log('Generating API tag pages with endpoint tables...');
const spec = JSON.parse(fs.readFileSync(SPEC_PATH, 'utf-8'));
const labelToSlug = buildSlugMap();
// Build tag descriptions from the spec
const tagDescriptions = {};
for (const tag of spec.tags || []) {
tagDescriptions[tag.name] = tag.description || '';
}
// Build tag → endpoints map
const tagEndpoints = {};
for (const [pathUrl, methods] of Object.entries(spec.paths || {})) {
for (const [method, details] of Object.entries(methods)) {
if (!['get', 'post', 'put', 'delete', 'patch'].includes(method)) continue;
const tags = details.tags || ['Untagged'];
const summary = details.summary || `${method.toUpperCase()} ${pathUrl}`;
const slug = labelToSlug[summary] || slugify(summary);
for (const tag of tags) {
if (!tagEndpoints[tag]) {
tagEndpoints[tag] = [];
}
tagEndpoints[tag].push({
method: method.toUpperCase(),
path: pathUrl,
summary,
slug,
});
}
}
}
// Sort endpoints within each tag by path then method
for (const tag of Object.keys(tagEndpoints)) {
tagEndpoints[tag].sort((a, b) =>
a.path.localeCompare(b.path) || a.method.localeCompare(b.method)
);
}
// Scan existing .tag.mdx files and match by frontmatter title
const tagFiles = fs.readdirSync(API_DOCS_DIR)
.filter(f => f.endsWith('.tag.mdx'));
let updated = 0;
for (const tagFile of tagFiles) {
const tagFilePath = path.join(API_DOCS_DIR, tagFile);
const existing = fs.readFileSync(tagFilePath, 'utf-8');
// Extract frontmatter
const frontmatterMatch = existing.match(/^---\n([\s\S]*?)\n---/);
if (!frontmatterMatch) {
console.warn(` No frontmatter in ${tagFile}, skipping`);
continue;
}
const frontmatter = frontmatterMatch[1];
// Extract the title from frontmatter (this matches the spec tag name)
const titleMatch = frontmatter.match(/title:\s*"([^"]+)"/);
if (!titleMatch) {
console.warn(` No title in ${tagFile}, skipping`);
continue;
}
const tagName = titleMatch[1];
const endpoints = tagEndpoints[tagName];
if (!endpoints || endpoints.length === 0) {
console.warn(` No endpoints found for tag "${tagName}" (${tagFile})`);
continue;
}
const description = tagDescriptions[tagName] || '';
// Build the endpoint table
let table = '| Method | Endpoint | Path |\n';
table += '|--------|----------|------|\n';
for (const ep of endpoints) {
table += `| \`${ep.method}\` | [${ep.summary}](./${ep.slug}) | \`${ep.path}\` |\n`;
}
// Generate the new MDX content
const mdx = `---
${frontmatter}
---
${description}
${table}
`;
fs.writeFileSync(tagFilePath, mdx);
updated++;
}
console.log(`Updated ${updated} tag pages with endpoint tables`);
}
main();

View File

@@ -30,12 +30,9 @@
import { spawnSync } from 'child_process';
import fs from 'fs';
import { createRequire } from 'module';
import path from 'path';
import { fileURLToPath } from 'url';
const require = createRequire(import.meta.url);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const ROOT_DIR = path.resolve(__dirname, '../..');
@@ -44,7 +41,6 @@ const DATA_OUTPUT_DIR = path.join(DOCS_DIR, 'src/data');
const DATA_OUTPUT_FILE = path.join(DATA_OUTPUT_DIR, 'databases.json');
const MDX_OUTPUT_DIR = path.join(DOCS_DIR, 'docs/databases');
const MDX_SUPPORTED_DIR = path.join(MDX_OUTPUT_DIR, 'supported');
const IMAGES_DIR = path.join(DOCS_DIR, 'static/img/databases');
/**
* Try to run the full lib.py script with Flask context
@@ -611,109 +607,30 @@ const README_PATH = path.join(ROOT_DIR, 'README.md');
const README_START_MARKER = '<!-- SUPPORTED_DATABASES_START -->';
const README_END_MARKER = '<!-- SUPPORTED_DATABASES_END -->';
/**
* Read image dimensions, with fallback SVG viewBox parsing for cases where
* image-size can't handle SVG width/height attributes (e.g., scientific notation).
*/
function getImageDimensions(imgPath) {
const sizeOf = require('image-size');
try {
const dims = sizeOf(imgPath);
// image-size may misparse SVG attributes (e.g. width="1e3" → 1).
// Fall back to viewBox parsing if a dimension looks wrong.
if (dims.type === 'svg' && (dims.width < 2 || dims.height < 2)) {
const content = fs.readFileSync(imgPath, 'utf-8');
const vbMatch = content.match(/viewBox=["']([^"']+)["']/);
if (vbMatch) {
const parts = vbMatch[1].trim().split(/[\s,]+/).map(Number);
if (parts.length >= 4 && parts[2] > 0 && parts[3] > 0) {
return { width: parts[2], height: parts[3] };
}
}
}
if (dims.width > 0 && dims.height > 0) {
return { width: dims.width, height: dims.height };
}
} catch { /* fall through */ }
return null;
}
/**
* Compute display dimensions that fit within a bounding box while preserving
* the image's aspect ratio. Enforces a minimum height so very wide logos
* remain legible.
*/
function fitToBoundingBox(imgWidth, imgHeight, maxWidth, maxHeight, minHeight) {
const ratio = imgWidth / imgHeight;
// Start at max height, compute width
let h = maxHeight;
let w = h * ratio;
// If too wide, cap width and reduce height
if (w > maxWidth) {
w = maxWidth;
h = w / ratio;
}
// If height fell below minimum, enforce minimum (allow width to exceed max)
if (h < minHeight) {
h = minHeight;
w = h * ratio;
}
return { width: Math.round(w), height: Math.round(h) };
}
/**
* Generate the database logos HTML for README.md
* Only includes databases that have logos and homepage URLs.
* Deduplicates by logo filename to match the docs homepage behavior.
* Reads actual image dimensions to preserve aspect ratios.
* Only includes databases that have logos defined
*/
function generateReadmeLogos(databases) {
// Get databases with logos and homepage URLs, sorted alphabetically,
// deduplicated by logo filename (matches docs homepage logic in index.tsx)
const seenLogos = new Set();
// Get databases with logos, sorted alphabetically
const dbsWithLogos = Object.entries(databases)
.filter(([, db]) => db.documentation?.logo && db.documentation?.homepage_url)
.sort(([a], [b]) => a.localeCompare(b))
.filter(([, db]) => {
const logo = db.documentation.logo;
if (seenLogos.has(logo)) return false;
seenLogos.add(logo);
return true;
});
.filter(([, db]) => db.documentation?.logo)
.sort(([a], [b]) => a.localeCompare(b));
if (dbsWithLogos.length === 0) {
return '';
}
const MAX_WIDTH = 150;
const MAX_HEIGHT = 40;
const MIN_HEIGHT = 24;
const DOCS_BASE = 'https://superset.apache.org/docs/databases/supported';
// Generate linked logo tags with aspect-ratio-preserving dimensions
// Generate HTML img tags
const logoTags = dbsWithLogos.map(([name, db]) => {
const logo = db.documentation.logo;
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
const imgPath = path.join(IMAGES_DIR, logo);
const dims = getImageDimensions(imgPath);
let sizeAttrs;
if (dims) {
const { width, height } = fitToBoundingBox(dims.width, dims.height, MAX_WIDTH, MAX_HEIGHT, MIN_HEIGHT);
sizeAttrs = `width="${width}" height="${height}"`;
} else {
console.warn(` Could not read dimensions for ${logo}, using height-only fallback`);
sizeAttrs = `height="${MAX_HEIGHT}"`;
}
const img = `<img src="docs/static/img/databases/${logo}" alt="${name}" ${sizeAttrs} />`;
return ` <a href="${DOCS_BASE}/${slug}" title="${name}">${img}</a>`;
const alt = name.toLowerCase().replace(/\s+/g, '-');
// Use docs site URL for logos
return ` <img src="https://superset.apache.org/img/databases/${logo}" alt="${alt}" border="0" width="80" height="40" class="database-logo" />`;
});
// Use &nbsp; between logos for spacing (GitHub strips style/class attributes)
return `<p align="center">
${logoTags.join(' &nbsp;\n')}
${logoTags.join('\n')}
</p>`;
}
@@ -758,78 +675,6 @@ function updateReadme(databases) {
return false;
}
/**
* Extract custom_errors from engine specs for troubleshooting documentation
* Returns a map of module names to their custom errors
*/
function extractCustomErrors() {
console.log('Extracting custom_errors from engine specs...');
try {
const scriptPath = path.join(__dirname, 'extract_custom_errors.py');
const result = spawnSync('python3', [scriptPath], {
cwd: ROOT_DIR,
encoding: 'utf-8',
timeout: 30000,
maxBuffer: 10 * 1024 * 1024,
});
if (result.error) {
throw result.error;
}
if (result.status !== 0) {
throw new Error(result.stderr || 'Python script failed');
}
const customErrors = JSON.parse(result.stdout);
const moduleCount = Object.keys(customErrors).length;
const errorCount = Object.values(customErrors).reduce((sum, classes) =>
sum + Object.values(classes).reduce((s, errs) => s + errs.length, 0), 0);
console.log(` Found ${errorCount} custom errors across ${moduleCount} modules`);
return customErrors;
} catch (err) {
console.log(' Could not extract custom_errors:', err.message);
return null;
}
}
/**
* Merge custom_errors into database documentation
* Maps by module name since that's how both datasets are keyed
*/
function mergeCustomErrors(databases, customErrors) {
if (!customErrors) return;
let mergedCount = 0;
for (const [, db] of Object.entries(databases)) {
if (!db.module) continue;
// Normalize module name: Flask mode uses full path (superset.db_engine_specs.postgres),
// but customErrors is keyed by file stem (postgres)
const moduleName = db.module.split('.').pop();
if (!customErrors[moduleName]) continue;
// Get all errors from all classes in this module
const moduleErrors = customErrors[moduleName];
const allErrors = [];
for (const classErrors of Object.values(moduleErrors)) {
allErrors.push(...classErrors);
}
if (allErrors.length > 0) {
// Add to documentation
db.documentation = db.documentation || {};
db.documentation.custom_errors = allErrors;
mergedCount++;
}
}
if (mergedCount > 0) {
console.log(` Merged custom_errors into ${mergedCount} database docs`);
}
}
/**
* Load existing database data if available
*/
@@ -923,10 +768,6 @@ async function main() {
databases = mergeWithExistingDiagnostics(databases, existingData);
}
// Extract and merge custom_errors for troubleshooting documentation
const customErrors = extractCustomErrors();
mergeCustomErrors(databases, customErrors);
// Build statistics
const statistics = buildStatistics(databases);

File diff suppressed because it is too large Load Diff

View File

@@ -110,26 +110,9 @@ const sidebars = {
'testing/frontend-testing',
'testing/backend-testing',
'testing/e2e-testing',
'testing/storybook',
'testing/ci-cd',
],
},
{
type: 'category',
label: 'UI Components',
collapsed: true,
items: [
{
type: 'autogenerated',
dirName: 'components',
},
],
},
{
type: 'link',
label: 'API Reference',
href: '/docs/api',
},
],
};

View File

@@ -107,21 +107,9 @@ const sidebars = {
id: 'faq',
},
{
type: 'category',
label: 'API Reference',
link: {
type: 'doc',
id: 'api',
},
items: (() => {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require('./docs/api/sidebar.js');
} catch {
// Generated by `yarn generate:api-docs`; empty until then
return [];
}
})(),
type: 'doc',
label: 'API',
id: 'api',
},
],
};

View File

@@ -39,12 +39,11 @@ const StyledBlurredSection = styled('section')`
interface BlurredSectionProps {
children: ReactNode;
id?: string;
}
const BlurredSection = ({ children, id }: BlurredSectionProps) => {
const BlurredSection = ({ children }: BlurredSectionProps) => {
return (
<StyledBlurredSection id={id}>
<StyledBlurredSection>
{children}
<img className="blur" src="/img/community/blur.png" alt="Blur" />
</StyledBlurredSection>

View File

@@ -18,245 +18,33 @@
*/
import React from 'react';
import BrowserOnly from '@docusaurus/BrowserOnly';
// Lazy-loaded component registry - populated on first use in browser
let componentRegistry = null;
let SupersetProviders = null;
function getComponentRegistry() {
if (typeof window === 'undefined') {
return {}; // SSR - return empty
}
if (componentRegistry !== null) {
return componentRegistry; // Already loaded
}
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const antd = require('antd');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const SupersetComponents = require('@superset/components');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const CoreUI = require('@apache-superset/core/ui');
// Build component registry with antd as base fallback layer.
// Some Superset components (e.g., Typography) use styled-components that may
// fail to initialize in the docs build. Antd originals serve as fallbacks.
componentRegistry = { ...antd, ...SupersetComponents, ...CoreUI };
return componentRegistry;
} catch (error) {
console.error('[StorybookWrapper] Failed to load components:', error);
componentRegistry = {};
return componentRegistry;
}
}
function getProviders() {
if (typeof window === 'undefined') {
return ({ children }) => children; // SSR
}
if (SupersetProviders !== null) {
return SupersetProviders;
}
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { themeObject } = require('@apache-superset/core/ui');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { App, ConfigProvider } = require('antd');
// Configure Ant Design to render portals (tooltips, dropdowns, etc.)
// inside the closest .storybook-example container instead of document.body
// This fixes positioning issues in the docs pages
const getPopupContainer = (triggerNode) => {
// Find the closest .storybook-example container
const container = triggerNode?.closest?.('.storybook-example');
return container || document.body;
};
SupersetProviders = ({ children }) => (
<themeObject.SupersetThemeProvider>
<ConfigProvider
getPopupContainer={getPopupContainer}
getTargetContainer={() => document.body}
>
<App>{children}</App>
</ConfigProvider>
</themeObject.SupersetThemeProvider>
);
return SupersetProviders;
} catch (error) {
console.error('[StorybookWrapper] Failed to load providers:', error);
return ({ children }) => children;
}
}
// Check if a value is a valid React component (function, forwardRef, memo, etc.)
function isReactComponent(value) {
if (!value) return false;
// Function/class components
if (typeof value === 'function') return true;
// forwardRef, memo, lazy — React wraps these as objects with $$typeof
if (typeof value === 'object' && value.$$typeof) return true;
return false;
}
// Resolve component from string name or React component
// Supports dot notation for nested components (e.g., 'Icons.InfoCircleOutlined')
function resolveComponent(component) {
if (!component) return null;
// If already a component (function/class/forwardRef), return as-is
if (isReactComponent(component)) return component;
// If string, look up in registry
if (typeof component === 'string') {
const registry = getComponentRegistry();
// Handle dot notation (e.g., 'Icons.InfoCircleOutlined')
if (component.includes('.')) {
const parts = component.split('.');
let current = registry[parts[0]];
for (let i = 1; i < parts.length && current; i++) {
current = current[parts[i]];
}
return isReactComponent(current) ? current : null;
}
return registry[component] || null;
}
return null;
}
// Loading placeholder for SSR
function LoadingPlaceholder() {
return (
<div
style={{
border: '1px solid #e8e8e8',
borderRadius: '4px',
padding: '20px',
marginBottom: '20px',
minHeight: '100px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#999',
}}
>
Loading component...
</div>
);
}
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
// A simple component to display a story example
export function StoryExample({ component, props = {} }) {
export function StoryExample({ component: Component, props = {} }) {
return (
<BrowserOnly fallback={<LoadingPlaceholder />}>
{() => {
const Component = resolveComponent(component);
const Providers = getProviders();
const { children, restProps } = extractChildren(props);
return (
<Providers>
<div
className="storybook-example"
style={{
border: '1px solid #e8e8e8',
borderRadius: '4px',
padding: '20px',
marginBottom: '20px',
position: 'relative', // Required for portal positioning
}}
>
{Component ? (
<Component {...restProps}>{children}</Component>
) : (
<div style={{ color: '#999' }}>
Component &quot;{String(component)}&quot; not found
</div>
)}
</div>
</Providers>
);
}}
</BrowserOnly>
<ThemeProvider theme={supersetTheme}>
<div
className="storybook-example"
style={{
border: '1px solid #e8e8e8',
borderRadius: '4px',
padding: '20px',
marginBottom: '20px',
}}
>
{Component && <Component {...props} />}
</div>
</ThemeProvider>
);
}
// Props that should be rendered as children rather than passed as props
const CHILDREN_PROP_NAMES = ['label', 'children', 'text', 'content'];
// Extract children from props based on common conventions
function extractChildren(props) {
for (const propName of CHILDREN_PROP_NAMES) {
if (props[propName] !== undefined && props[propName] !== null && props[propName] !== '') {
const { [propName]: childContent, ...restProps } = props;
return { children: childContent, restProps };
}
}
return { children: null, restProps: props };
}
// Generate sample children for layout components
// Supports:
// - Array of strings: ['Item 1', 'Item 2'] - renders as styled divs
// - Array of component descriptors: [{ component: 'Button', props: { children: 'Click' } }]
// - Number: 3 - generates that many sample items
// - String: 'content' - renders as literal content
function generateSampleChildren(sampleChildren, sampleChildrenStyle) {
if (!sampleChildren) return null;
// Default style if none provided (minimal, just enough to see items)
const itemStyle = sampleChildrenStyle || {};
// If it's an array, check if items are component descriptors or strings
if (Array.isArray(sampleChildren)) {
return sampleChildren.map((item, i) => {
// Component descriptor: { component: 'Button', props: { ... } }
if (item && typeof item === 'object' && item.component) {
const ChildComponent = resolveComponent(item.component);
if (ChildComponent) {
return <ChildComponent key={i} {...item.props} />;
}
// Fallback if component not found
return <div key={i}>{item.props?.children || `Unknown: ${item.component}`}</div>;
}
// Simple string
return (
<div key={i} style={itemStyle}>
{item}
</div>
);
});
}
// If it's a number, generate that many sample items
if (typeof sampleChildren === 'number') {
return new Array(sampleChildren).fill(null).map((_, i) => (
<div key={i} style={itemStyle}>
Item {i + 1}
</div>
));
}
// If it's a string, treat as literal content
if (typeof sampleChildren === 'string') {
return sampleChildren;
}
return sampleChildren;
}
// Inner component for StoryWithControls (browser-only)
// renderComponent allows overriding which component to actually render (useful when the named
// component is a namespace object like Icons, not a React component)
// triggerProp: for components like Modal that need a trigger, specify the boolean prop that controls visibility
function StoryWithControlsInner({ component, renderComponent, props, controls, sampleChildren, sampleChildrenStyle, triggerProp, onHideProp }) {
// Use renderComponent if provided, otherwise use the main component name
const componentToRender = renderComponent || component;
const Component = resolveComponent(componentToRender);
const Providers = getProviders();
// A simple component to display a story with controls
export function StoryWithControls({
component: Component,
props = {},
controls = [],
}) {
const [stateProps, setStateProps] = React.useState(props);
const updateProp = (key, value) => {
@@ -266,77 +54,8 @@ function StoryWithControlsInner({ component, renderComponent, props, controls, s
}));
};
// Extract children from props (label, children, text, content)
// When sampleChildren is explicitly provided, skip extraction so all props
// (like 'content') stay as component props rather than becoming children
const { children: propsChildren, restProps } = sampleChildren
? { children: null, restProps: stateProps }
: extractChildren(stateProps);
// Filter out undefined values so they don't override component defaults
const filteredProps = Object.fromEntries(
Object.entries(restProps).filter(([, v]) => v !== undefined)
);
// Resolve any prop values that are component descriptors
// e.g., { component: 'Button', props: { children: 'Click' } }
// Also resolves descriptors nested inside array items:
// e.g., items: [{ id: 'x', element: { component: 'div', props: { children: 'text' } } }]
Object.keys(filteredProps).forEach(key => {
const value = filteredProps[key];
if (value && typeof value === 'object' && !Array.isArray(value) && value.component) {
const PropComponent = resolveComponent(value.component);
if (PropComponent) {
filteredProps[key] = <PropComponent {...value.props} />;
}
}
if (Array.isArray(value)) {
filteredProps[key] = value.map((item, idx) => {
if (item && typeof item === 'object') {
const resolved = { ...item };
Object.keys(resolved).forEach(field => {
const fieldValue = resolved[field];
if (fieldValue && typeof fieldValue === 'object' && !Array.isArray(fieldValue) && fieldValue.component) {
const FieldComponent = resolveComponent(fieldValue.component);
if (FieldComponent) {
resolved[field] = React.createElement(FieldComponent, { key: `${key}-${idx}`, ...fieldValue.props });
}
}
});
return resolved;
}
return item;
});
}
});
// For List-like components with dataSource but no renderItem, provide a default
if (filteredProps.dataSource && !filteredProps.renderItem) {
const ListItem = resolveComponent('List')?.Item;
filteredProps.renderItem = (item) =>
ListItem
? React.createElement(ListItem, null, String(item))
: React.createElement('div', null, String(item));
}
// Use sample children if provided, otherwise use props children
const children = generateSampleChildren(sampleChildren, sampleChildrenStyle) || propsChildren;
// For components with a trigger (like Modal with show/onHide), add handlers.
// onHideProp supports comma-separated names for components with multiple close
// callbacks (e.g., "onHide,handleSave,onConfirmNavigation").
const triggerProps = {};
if (triggerProp && onHideProp) {
const closeHandler = () => updateProp(triggerProp, false);
onHideProp.split(',').forEach(prop => {
triggerProps[prop.trim()] = closeHandler;
});
}
// Get the Button component for trigger buttons
const ButtonComponent = resolveComponent('Button');
return (
<Providers>
<ThemeProvider theme={supersetTheme}>
<div className="storybook-with-controls">
<div
className="storybook-example"
@@ -345,24 +64,9 @@ function StoryWithControlsInner({ component, renderComponent, props, controls, s
borderRadius: '4px',
padding: '20px',
marginBottom: '20px',
position: 'relative', // Required for portal positioning
}}
>
{Component ? (
<>
{/* Show a trigger button for components like Modal */}
{triggerProp && ButtonComponent && (
<ButtonComponent onClick={() => updateProp(triggerProp, true)}>
Open {component}
</ButtonComponent>
)}
<Component {...filteredProps} {...triggerProps}>{children}</Component>
</>
) : (
<div style={{ color: '#999' }}>
Component &quot;{String(componentToRender)}&quot; not found
</div>
)}
{Component && <Component {...stateProps} />}
</div>
{controls.length > 0 && (
@@ -383,64 +87,26 @@ function StoryWithControlsInner({ component, renderComponent, props, controls, s
</label>
{control.type === 'select' ? (
<select
value={stateProps[control.name] ?? ''}
onChange={e => updateProp(control.name, e.target.value || undefined)}
value={stateProps[control.name]}
onChange={e => updateProp(control.name, e.target.value)}
style={{ width: '100%', padding: '5px' }}
>
<option value=""> None </option>
{control.options?.map(option => (
{control.options.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
) : control.type === 'inline-radio' || control.type === 'radio' ? (
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
{control.options?.map(option => (
<label
key={option}
style={{ display: 'flex', alignItems: 'center', gap: '4px' }}
>
<input
type="radio"
name={control.name}
value={option}
checked={stateProps[control.name] === option}
onChange={e => updateProp(control.name, e.target.value)}
/>
{option}
</label>
))}
</div>
) : control.type === 'boolean' ? (
<input
type="checkbox"
checked={stateProps[control.name]}
onChange={e => updateProp(control.name, e.target.checked)}
/>
) : control.type === 'number' ? (
<input
type="number"
value={stateProps[control.name]}
onChange={e => updateProp(control.name, Number(e.target.value))}
style={{ width: '100%', padding: '5px' }}
/>
) : control.type === 'color' ? (
<input
type="color"
value={stateProps[control.name] || '#000000'}
onChange={e => updateProp(control.name, e.target.value)}
style={{
width: '50px',
height: '30px',
padding: '2px',
cursor: 'pointer',
}}
/>
) : (
<input
type="text"
value={stateProps[control.name] ?? ''}
value={stateProps[control.name]}
onChange={e => updateProp(control.name, e.target.value)}
style={{ width: '100%', padding: '5px' }}
/>
@@ -450,81 +116,6 @@ function StoryWithControlsInner({ component, renderComponent, props, controls, s
</div>
)}
</div>
</Providers>
);
}
// A simple component to display a story with controls
// renderComponent: optional override for which component to render (e.g., 'Icons.InfoCircleOutlined' when component='Icons')
// triggerProp/onHideProp: for components like Modal that need a button to open (e.g., triggerProp="show", onHideProp="onHide")
export function StoryWithControls({ component: Component, renderComponent, props = {}, controls = [], sampleChildren, sampleChildrenStyle, triggerProp, onHideProp }) {
return (
<BrowserOnly fallback={<LoadingPlaceholder />}>
{() => (
<StoryWithControlsInner
component={Component}
renderComponent={renderComponent}
props={props}
controls={controls}
sampleChildren={sampleChildren}
sampleChildrenStyle={sampleChildrenStyle}
triggerProp={triggerProp}
onHideProp={onHideProp}
/>
)}
</BrowserOnly>
);
}
// Inner component for ComponentGallery (browser-only)
function ComponentGalleryInner({ component, sizes, styles, sizeProp, styleProp }) {
const Component = resolveComponent(component);
const Providers = getProviders();
if (!Component) {
return (
<div style={{ color: '#999' }}>
Component &quot;{String(component)}&quot; not found
</div>
);
}
return (
<Providers>
<div className="component-gallery">
{sizes.map(size => (
<div key={size} style={{ marginBottom: 40 }}>
<h4 style={{ marginBottom: 16, color: '#666' }}>{size}</h4>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '12px', alignItems: 'center' }}>
{styles.map(style => (
<Component
key={`${style}_${size}`}
{...{ [sizeProp]: size, [styleProp]: style }}
>
{style}
</Component>
))}
</div>
</div>
))}
</div>
</Providers>
);
}
// A component to display a gallery of all variants (sizes x styles)
export function ComponentGallery({ component, sizes = [], styles = [], sizeProp = 'size', styleProp = 'variant' }) {
return (
<BrowserOnly fallback={<LoadingPlaceholder />}>
{() => (
<ComponentGalleryInner
component={component}
sizes={sizes}
styles={styles}
sizeProp={sizeProp}
styleProp={styleProp}
/>
)}
</BrowserOnly>
</ThemeProvider>
);
}

View File

@@ -26,7 +26,6 @@ import {
KeyOutlined,
SearchOutlined,
LinkOutlined,
BugOutlined,
} from '@ant-design/icons';
import type { DatabaseData, DatabaseInfo, TimeGrains } from './types';
@@ -45,8 +44,6 @@ interface TableEntry {
hasDrivers: boolean;
hasAuthMethods: boolean;
hasConnectionString: boolean;
hasCustomErrors: boolean;
customErrorCount: number;
joins?: boolean;
subqueries?: boolean;
supports_dynamic_schema?: boolean;
@@ -226,8 +223,6 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
db.documentation?.connection_string ||
(db.documentation?.drivers?.length ?? 0) > 0
),
hasCustomErrors: (db.documentation?.custom_errors?.length ?? 0) > 0,
customErrorCount: db.documentation?.custom_errors?.length ?? 0,
isCompatible: false,
});
@@ -251,8 +246,6 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
hasDrivers: false,
hasAuthMethods: false,
hasConnectionString: Boolean(compat.connection_string),
hasCustomErrors: false,
customErrorCount: 0,
joins: db.joins,
subqueries: db.subqueries,
supports_dynamic_schema: db.supports_dynamic_schema,
@@ -464,7 +457,7 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
{
title: 'Documentation',
key: 'docs',
width: 180,
width: 150,
render: (_: unknown, record: TableEntry) => (
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
{record.hasConnectionString && (
@@ -482,13 +475,6 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
Auth
</Tag>
)}
{record.hasCustomErrors && (
<Tooltip title={`${record.customErrorCount} troubleshooting tips`}>
<Tag icon={<BugOutlined />} color="volcano">
Errors
</Tag>
</Tooltip>
)}
</div>
),
},

View File

@@ -39,7 +39,6 @@ import {
BookOutlined,
EditOutlined,
GithubOutlined,
BugOutlined,
} from '@ant-design/icons';
import type { DatabaseInfo } from './types';
@@ -60,6 +59,8 @@ const CodeBlock: React.FC<{ children: React.ReactNode }> = ({ children }) => (
);
const { Title, Paragraph, Text } = Typography;
const { Panel } = Collapse;
const { TabPane } = Tabs;
interface DatabasePageProps {
database: DatabaseInfo;
@@ -110,20 +111,21 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
return (
<Card title="Drivers" style={{ marginBottom: 16 }}>
<Tabs
items={docs.drivers.map((driver, idx) => ({
key: String(idx),
label: (
<span>
{driver.name}
{driver.is_recommended && (
<Tag color="green" style={{ marginLeft: 8 }}>
Recommended
</Tag>
)}
</span>
),
children: (
<Tabs>
{docs.drivers.map((driver, idx) => (
<TabPane
tab={
<span>
{driver.name}
{driver.is_recommended && (
<Tag color="green" style={{ marginLeft: 8 }}>
Recommended
</Tag>
)}
</span>
}
key={idx}
>
<Space direction="vertical" style={{ width: '100%' }}>
{driver.pypi_package && (
<div>
@@ -142,9 +144,9 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
</a>
)}
</Space>
),
}))}
/>
</TabPane>
))}
</Tabs>
</Card>
);
};
@@ -162,51 +164,46 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
}
style={{ marginBottom: 16 }}
>
<Collapse
accordion
items={docs.authentication_methods.map((auth, idx) => ({
key: String(idx),
label: auth.name,
children: (
<>
{auth.description && <Paragraph>{auth.description}</Paragraph>}
{auth.requirements && (
<Alert
message="Requirements"
description={auth.requirements}
type="warning"
showIcon
style={{ marginBottom: 16 }}
/>
<Collapse accordion>
{docs.authentication_methods.map((auth, idx) => (
<Panel header={auth.name} key={idx}>
{auth.description && <Paragraph>{auth.description}</Paragraph>}
{auth.requirements && (
<Alert
message="Requirements"
description={auth.requirements}
type="warning"
showIcon
style={{ marginBottom: 16 }}
/>
)}
{auth.connection_string &&
renderConnectionString(
auth.connection_string,
'Connection String'
)}
{auth.connection_string &&
renderConnectionString(
auth.connection_string,
'Connection String',
)}
{auth.secure_extra && (
<div>
<Text strong>Secure Extra Configuration:</Text>
<CodeBlock>
{JSON.stringify(auth.secure_extra, null, 2)}
</CodeBlock>
</div>
)}
{auth.engine_parameters && (
<div>
<Text strong>Engine Parameters:</Text>
<CodeBlock>
{JSON.stringify(auth.engine_parameters, null, 2)}
</CodeBlock>
</div>
)}
{auth.notes && (
<Alert message={auth.notes} type="info" showIcon />
)}
</>
),
}))}
/>
{auth.secure_extra && (
<div>
<Text strong>Secure Extra Configuration:</Text>
<CodeBlock>
{JSON.stringify(auth.secure_extra, null, 2)}
</CodeBlock>
</div>
)}
{auth.engine_parameters && (
<div>
<Text strong>Engine Parameters:</Text>
<CodeBlock>
{JSON.stringify(auth.engine_parameters, null, 2)}
</CodeBlock>
</div>
)}
{auth.notes && (
<Alert message={auth.notes} type="info" showIcon />
)}
</Panel>
))}
</Collapse>
</Card>
);
};
@@ -224,27 +221,23 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
}
style={{ marginBottom: 16 }}
>
<Collapse
items={docs.engine_parameters.map((param, idx) => ({
key: String(idx),
label: param.name,
children: (
<>
{param.description && (
<Paragraph>{param.description}</Paragraph>
)}
{param.json && (
<CodeBlock>{JSON.stringify(param.json, null, 2)}</CodeBlock>
)}
{param.docs_url && (
<a href={param.docs_url} target="_blank" rel="noreferrer">
<LinkOutlined /> Learn more
</a>
)}
</>
),
}))}
/>
<Collapse>
{docs.engine_parameters.map((param, idx) => (
<Panel header={param.name} key={idx}>
{param.description && <Paragraph>{param.description}</Paragraph>}
{param.json && (
<CodeBlock>
{JSON.stringify(param.json, null, 2)}
</CodeBlock>
)}
{param.docs_url && (
<a href={param.docs_url} target="_blank" rel="noreferrer">
<LinkOutlined /> Learn more
</a>
)}
</Panel>
))}
</Collapse>
</Card>
);
};
@@ -253,81 +246,75 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
const renderCompatibleDatabases = () => {
if (!docs?.compatible_databases?.length) return null;
// Create array of all item keys to expand by default
const allItemKeys = docs.compatible_databases.map((_, idx) => String(idx));
// Create array of all panel keys to expand by default
const allPanelKeys = docs.compatible_databases.map((_, idx) => idx);
return (
<Card title="Compatible Databases" style={{ marginBottom: 16 }}>
<Paragraph>
The following databases are compatible with the {name} driver:
</Paragraph>
<Collapse
defaultActiveKey={allItemKeys}
items={docs.compatible_databases.map((compat, idx) => ({
key: String(idx),
label: (
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
{compat.logo && (
<img
src={`/img/databases/${compat.logo}`}
alt={compat.name}
style={{
width: 28,
height: 28,
objectFit: 'contain',
}}
/>
)}
<span>{compat.name}</span>
</div>
),
children: (
<>
{compat.description && (
<Paragraph>{compat.description}</Paragraph>
)}
{compat.connection_string &&
renderConnectionString(compat.connection_string)}
{compat.parameters && (
<div>
<Text strong>Parameters:</Text>
<Table
dataSource={Object.entries(compat.parameters).map(
([key, value]) => ({
key,
parameter: key,
description: value,
}),
)}
columns={[
{
title: 'Parameter',
dataIndex: 'parameter',
key: 'p',
},
{
title: 'Description',
dataIndex: 'description',
key: 'd',
},
]}
pagination={false}
size="small"
<Collapse defaultActiveKey={allPanelKeys}>
{docs.compatible_databases.map((compat, idx) => (
<Panel
header={
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
{compat.logo && (
<img
src={`/img/databases/${compat.logo}`}
alt={compat.name}
style={{
width: 28,
height: 28,
objectFit: 'contain',
}}
/>
</div>
)}
{compat.notes && (
<Alert
message={compat.notes}
type="info"
showIcon
style={{ marginTop: 16 }}
)}
<span>{compat.name}</span>
</div>
}
key={idx}
>
{compat.description && (
<Paragraph>{compat.description}</Paragraph>
)}
{compat.connection_string &&
renderConnectionString(compat.connection_string)}
{compat.parameters && (
<div>
<Text strong>Parameters:</Text>
<Table
dataSource={Object.entries(compat.parameters).map(
([key, value]) => ({
key,
parameter: key,
description: value,
})
)}
columns={[
{ title: 'Parameter', dataIndex: 'parameter', key: 'p' },
{
title: 'Description',
dataIndex: 'description',
key: 'd',
},
]}
pagination={false}
size="small"
/>
)}
</>
),
}))}
/>
</div>
)}
{compat.notes && (
<Alert
message={compat.notes}
type="info"
showIcon
style={{ marginTop: 16 }}
/>
)}
</Panel>
))}
</Collapse>
</Card>
);
};
@@ -388,7 +375,7 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
'YEAR',
];
const extendedGrains = Object.keys(database.time_grains).filter(
g => !commonGrains.includes(g),
(g) => !commonGrains.includes(g)
);
return (
@@ -396,14 +383,12 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
<div style={{ marginBottom: 16 }}>
<Text strong>Common Time Grains:</Text>
<div style={{ marginTop: 8 }}>
{commonGrains.map(grain => (
{commonGrains.map((grain) => (
<TimeGrainBadge
key={grain}
grain={grain}
supported={Boolean(
database.time_grains[
grain as keyof typeof database.time_grains
],
database.time_grains[grain as keyof typeof database.time_grains]
)}
/>
))}
@@ -413,14 +398,12 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
<div>
<Text strong>Extended Time Grains:</Text>
<div style={{ marginTop: 8 }}>
{extendedGrains.map(grain => (
{extendedGrains.map((grain) => (
<TimeGrainBadge
key={grain}
grain={grain}
supported={Boolean(
database.time_grains[
grain as keyof typeof database.time_grains
],
database.time_grains[grain as keyof typeof database.time_grains]
)}
/>
))}
@@ -431,139 +414,11 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
);
};
// Render troubleshooting / custom errors section
const renderTroubleshooting = () => {
if (!docs?.custom_errors?.length) return null;
// Group errors by category
const errorsByCategory: Record<string, typeof docs.custom_errors> = {};
for (const error of docs.custom_errors) {
const category = error.category || 'General';
if (!errorsByCategory[category]) {
errorsByCategory[category] = [];
}
errorsByCategory[category].push(error);
}
// Define category order for consistent display
const categoryOrder = [
'Authentication',
'Connection',
'Permissions',
'Query',
'Configuration',
'General',
];
const sortedCategories = Object.keys(errorsByCategory).sort((a, b) => {
const aIdx = categoryOrder.indexOf(a);
const bIdx = categoryOrder.indexOf(b);
if (aIdx === -1 && bIdx === -1) return a.localeCompare(b);
if (aIdx === -1) return 1;
if (bIdx === -1) return -1;
return aIdx - bIdx;
});
// Category colors
const categoryColors: Record<string, string> = {
Authentication: 'orange',
Connection: 'red',
Permissions: 'purple',
Query: 'blue',
Configuration: 'cyan',
General: 'default',
};
return (
<Card
title={
<>
<BugOutlined /> Troubleshooting
</>
}
style={{ marginBottom: 16 }}
>
<Paragraph type="secondary">
Common error messages you may encounter when connecting to or querying{' '}
{name}, along with their causes and solutions.
</Paragraph>
<Collapse
accordion
items={sortedCategories.map(category => ({
key: category,
label: (
<span>
<Tag color={categoryColors[category] || 'default'}>
{category}
</Tag>
{errorsByCategory[category].length} error
{errorsByCategory[category].length !== 1 ? 's' : ''}
</span>
),
children: (
<>
{errorsByCategory[category].map((error, idx) => (
<div
key={idx}
style={{
marginBottom:
idx < errorsByCategory[category].length - 1 ? 16 : 0,
paddingBottom:
idx < errorsByCategory[category].length - 1 ? 16 : 0,
borderBottom:
idx < errorsByCategory[category].length - 1
? '1px solid var(--ifm-color-emphasis-200)'
: 'none',
}}
>
<div style={{ marginBottom: 8 }}>
<Text strong>
{error.description || error.error_type}
</Text>
</div>
<Alert
message={error.message_template}
type="error"
style={{ marginBottom: 8 }}
/>
{error.invalid_fields &&
error.invalid_fields.length > 0 && (
<div style={{ marginBottom: 8 }}>
<Text type="secondary">Check these fields: </Text>
{error.invalid_fields.map(field => (
<Tag key={field} color="warning">
{field}
</Tag>
))}
</div>
)}
{error.issue_codes && error.issue_codes.length > 0 && (
<div>
<Text type="secondary">Related issue codes: </Text>
{error.issue_codes.map(code => (
<Tag key={code}>
<a
href={`/docs/using-superset/issue-codes#issue-${code}`}
style={{ color: 'inherit' }}
>
Issue {code}
</a>
</Tag>
))}
</div>
)}
</div>
))}
</>
),
}))}
/>
</Card>
);
};
return (
<div className="database-page" id={name.toLowerCase().replace(/\s+/g, '-')}>
<div
className="database-page"
id={name.toLowerCase().replace(/\s+/g, '-')}
>
<div style={{ marginBottom: 16 }}>
{docs?.logo && (
<img
@@ -576,9 +431,7 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
}}
/>
)}
<Title level={1} style={{ margin: 0 }}>
{name}
</Title>
<Title level={1} style={{ margin: 0 }}>{name}</Title>
{docs?.homepage_url && (
<a
href={docs.homepage_url}
@@ -626,7 +479,7 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
{docs.pypi_packages?.length > 0 && (
<div style={{ marginBottom: 16 }}>
<Text strong>Required packages: </Text>
{docs.pypi_packages.map(pkg => (
{docs.pypi_packages.map((pkg) => (
<Tag key={pkg} color="blue">
{pkg}
</Tag>
@@ -658,7 +511,7 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
key,
parameter: key,
description: value,
}),
})
)}
columns={[
{ title: 'Parameter', dataIndex: 'parameter', key: 'p' },
@@ -684,7 +537,7 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
<div key={idx}>
{renderConnectionString(
example.connection_string,
example.description,
example.description
)}
</div>
))}
@@ -703,9 +556,6 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
{/* Time Grains */}
{renderTimeGrains()}
{/* Troubleshooting / Custom Errors */}
{renderTroubleshooting()}
{/* Compatible Databases */}
{renderCompatibleDatabases()}
@@ -737,11 +587,7 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
</a>
)}
{docs.sqlalchemy_docs_url && (
<a
href={docs.sqlalchemy_docs_url}
target="_blank"
rel="noreferrer"
>
<a href={docs.sqlalchemy_docs_url} target="_blank" rel="noreferrer">
<LinkOutlined /> SQLAlchemy Dialect Documentation
</a>
)}

View File

@@ -86,17 +86,6 @@ export interface CompatibleDatabase {
docs_url?: string;
}
export interface CustomError {
error_type: string; // e.g., "CONNECTION_INVALID_USERNAME_ERROR"
message_template: string; // e.g., 'The username "%(username)s" does not exist.'
regex_pattern?: string; // The regex pattern that matches this error (optional, for reference)
regex_name?: string; // The name of the regex constant (e.g., "CONNECTION_INVALID_USERNAME_REGEX")
invalid_fields?: string[]; // Fields that are invalid, e.g., ["username", "password"]
issue_codes?: number[]; // Related issue codes from ISSUE_CODES mapping
category?: string; // Error category: "Authentication", "Connection", "Query", etc.
description?: string; // Human-readable short description of the error type
}
export interface DatabaseDocumentation {
description?: string;
logo?: string;
@@ -122,7 +111,6 @@ export interface DatabaseDocumentation {
sqlalchemy_docs_url?: string;
advanced_features?: Record<string, string>;
compatible_databases?: CompatibleDatabase[];
custom_errors?: CustomError[]; // Database-specific error messages and troubleshooting info
}
export interface TimeGrains {

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@
*/
import { useState } from 'react';
import styled from '@emotion/styled';
import { List } from 'antd';
import Layout from '@theme/Layout';
import { mq } from '../utils';
import SectionHeader from '../components/SectionHeader';
@@ -91,12 +92,8 @@ const StyledJoinCommunity = styled('section')`
max-width: 540px;
margin: 0 auto;
padding: 40px 20px 20px 35px;
list-style: none;
}
.item {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 0;
border: 0;
}
@@ -192,33 +189,39 @@ const Community = () => {
/>
</BlurredSection>
<StyledJoinCommunity>
<ul className="list">
{communityLinks.map(
({ url, title, description, image, ariaLabel }) => (
<li className="item" key={title}>
<a
className="avatar"
href={url}
target="_blank"
rel="noreferrer"
aria-label={ariaLabel}
>
<img className="icon" src={`/img/community/${image}`} />
</a>
<div>
<List
className="list"
itemLayout="horizontal"
dataSource={communityLinks}
renderItem={({ url, title, description, image, ariaLabel }) => (
<List.Item className="item">
<List.Item.Meta
avatar={
<a
className="title"
href={url}
target="_blank"
rel="noreferrer"
aria-label={ariaLabel}
>
<img className="icon" src={`/img/community/${image}`} />
</a>
}
title={
<a href={url} target="_blank" rel="noreferrer">
<p className="title" style={{ marginBottom: 0 }}>
{title}
</p>
</a>
<p className="description">{description}</p>
</div>
</li>
),
}
description={<p className="description">{description}</p>}
aria-label="Community link"
/>
</List.Item>
)}
</ul>
/>
</StyledJoinCommunity>
<BlurredSection id="superset-community-calendar">
<BlurredSection>
<SectionHeader
level="h2"
title="Superset Community Calendar"

View File

@@ -28,7 +28,7 @@ import databaseData from '../data/databases.json';
import BlurredSection from '../components/BlurredSection';
import DataSet from '../../../RESOURCES/INTHEWILD.yaml';
import type { DatabaseData } from '../components/databases/types';
import '../styles/main.css';
import '../styles/main.less';
// Build database list from databases.json (databases with logos)
// Deduplicate by logo filename to avoid showing the same logo twice
@@ -109,10 +109,13 @@ const StyledMain = styled('main')`
const StyledTitleContainer = styled('div')`
position: relative;
padding: 130px 20px 20px;
margin-bottom: 0;
padding: 130px 20px 0;
margin-bottom: 160px;
background-image: url('/img/grid-background.jpg');
background-size: cover;
${mq[1]} {
margin-bottom: 100px;
}
.info-container {
position: relative;
z-index: 4;
@@ -795,7 +798,7 @@ export default function Home(): JSX.Element {
</StyledIntegrations>
</BlurredSection>
{/* Only show carousel when we have enough logos (>10) for a good display */}
{companiesWithLogos.length > 7 && (
{companiesWithLogos.length > 10 && (
<BlurredSection>
<div style={{ padding: '0 20px' }}>
<SectionHeader

View File

@@ -1,118 +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.
*/
// Null module shim for packages not available in the docs build.
// These are transitive dependencies of superset-frontend components that exist
// in the barrel file but are never rendered on the docs site.
// webpack needs these to resolve at build time even though the code paths
// that use them are never executed at runtime.
//
// This shim uses a recursive Proxy to handle nested property access chains:
// import ace from 'ace-builds'; ace.config.set(...) → works (returns proxy)
// import { useResizeDetector } from 'react-resize-detector' → returns noop hook
// import ReactAce from 'react-ace' → returns NullComponent
const NullComponent = () => null;
// For hooks that return objects/arrays
const useNoop = () => ({});
// Mock for useResizeDetector - returns { ref, width, height } where ref.current exists
const useResizeDetectorMock = () => ({
ref: { current: null },
width: 0,
height: 0,
});
/**
* Creates a recursive proxy that handles any depth of property access.
* This allows patterns like ace.config.set() or ace.config.setModuleUrl() to work.
*
* The proxy is both callable (returns undefined) and accessible (returns another proxy).
*/
function createDeepProxy() {
const handler = {
// Handle property access - return another proxy for chaining
get(target, prop) {
// Standard module properties
if (prop === 'default') return createDeepProxy();
if (prop === '__esModule') return true;
// Symbol properties (used by JS internals)
if (typeof prop === 'symbol') {
if (prop === Symbol.toPrimitive) return () => '';
if (prop === Symbol.toStringTag) return 'NullModule';
if (prop === Symbol.iterator) return undefined;
return undefined;
}
// React-specific properties
if (prop === '$$typeof') return undefined;
if (prop === 'propTypes') return undefined;
if (prop === 'displayName') return 'NullComponent';
// Specific hook mocks for known hooks that need proper return values
if (prop === 'useResizeDetector') {
return useResizeDetectorMock;
}
// Common hook names return useNoop for better compatibility
if (typeof prop === 'string' && prop.startsWith('use')) {
return useNoop;
}
// Return another proxy to allow further chaining (ace.config.set)
return createDeepProxy();
},
// Handle function calls - return undefined (safe default)
apply() {
return undefined;
},
// Handle new ClassName() - return an empty object
construct() {
return {};
},
};
// Create a proxy over a function so it's both callable and has properties
return new Proxy(function NullModule() {}, handler);
}
// Create the main module export as a deep proxy
const nullModule = createDeepProxy();
// Support both CommonJS and ES module patterns
module.exports = nullModule;
module.exports.default = createDeepProxy();
module.exports.__esModule = true;
// Named exports for common patterns (webpack may inline these)
module.exports.useResizeDetector = useResizeDetectorMock;
module.exports.withResizeDetector = createDeepProxy();
module.exports.Resizable = NullComponent;
module.exports.ResizableBox = NullComponent;
module.exports.FixedSizeList = NullComponent;
module.exports.VariableSizeList = NullComponent;
// ace-builds specific exports that CodeEditor uses
module.exports.config = createDeepProxy();
module.exports.require = createDeepProxy();
module.exports.edit = createDeepProxy();

View File

@@ -1,54 +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.
*/
// Shim for react-table to handle CommonJS to ES module interop
// react-table v7 is CommonJS, but Superset components import it with ES module syntax
// Use relative path to avoid circular dependency since webpack aliases 'react-table' to this file
// eslint-disable-next-line @typescript-eslint/no-require-imports -- CJS interop shim for react-table v7
const reactTable = require('../../node_modules/react-table');
// Re-export all named exports
export const {
useTable,
useFilters,
useSortBy,
usePagination,
useGlobalFilter,
useRowSelect,
useRowState,
useColumnOrder,
useExpanded,
useGroupBy,
useResizeColumns,
useBlockLayout,
useAbsoluteLayout,
useFlexLayout,
actions,
defaultColumn,
makePropGetter,
reduceHooks,
loopHooks,
ensurePluginOrder,
functionalUpdate,
useGetLatest,
safeUseLayoutEffect,
} = reactTable;
// Default export
export default reactTable;

View File

@@ -16,6 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
// Base fixture with test asset cleanup
export { test as testWithAssets, expect, type TestAssets } from './testAssets';
@primary-color: #20a7c9;
@info-color: #66bcfe;
@success-color: #59c189;
@processing-color: #66bcfe;
@error-color: #e04355;
@highlight-color: #e04355;
@normal-color: #d9d9d9;
@white: #FFF;
@black: #000;

View File

@@ -187,270 +187,3 @@ ul.dropdown__menu svg {
[data-theme='dark'] .ant-collapse-header {
color: var(--ifm-font-base-color);
}
/* Hide the non-functional "Send API Request" button and Response block in API docs */
/* The interactive API testing doesn't work due to CORS restrictions */
.openapi-explorer__request-btn {
display: none !important;
}
.openapi-explorer__response-container {
display: none !important;
}
/* API Method Badge Colors - Swagger-style color coding */
/* These override Infima badge classes used by docusaurus-openapi-docs */
/* GET - Blue (badge--primary) */
.openapi__method-endpoint .badge--primary {
background-color: #61affe !important;
border-color: #61affe !important;
}
/* POST - Green (badge--success) */
.openapi__method-endpoint .badge--success {
background-color: #49cc90 !important;
border-color: #49cc90 !important;
}
/* PUT - Info/Cyan -> Orange (badge--info) */
.openapi__method-endpoint .badge--info {
background-color: #fca130 !important;
border-color: #fca130 !important;
}
/* PATCH - Warning/Yellow -> Teal (badge--warning) */
.openapi__method-endpoint .badge--warning {
background-color: #50e3c2 !important;
border-color: #50e3c2 !important;
color: #1b1b1d !important;
}
/* DELETE - Red (badge--danger) */
.openapi__method-endpoint .badge--danger {
background-color: #f93e3e !important;
border-color: #f93e3e !important;
}
/* Sidebar method badges - colored dots before endpoint names */
/* The method classes (get, post, etc.) are on the <li> (menu__list-item),
so we target the <a> (menu__link) inside using descendant selector */
.menu__list-item.api-method > .menu__link::before {
content: '';
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 8px;
flex-shrink: 0;
}
.menu__list-item.get.api-method > .menu__link::before {
background-color: #61affe;
}
.menu__list-item.post.api-method > .menu__link::before {
background-color: #49cc90;
}
.menu__list-item.put.api-method > .menu__link::before {
background-color: #fca130;
}
.menu__list-item.patch.api-method > .menu__link::before {
background-color: #50e3c2;
}
.menu__list-item.delete.api-method > .menu__link::before {
background-color: #f93e3e;
}
/* ============================================
Component Example Isolation
Prevents Docusaurus/Infima styles from bleeding into Superset components
============================================ */
/* Reset link styles inside component examples */
.storybook-example a {
color: inherit;
text-decoration: none;
font-weight: inherit;
line-height: inherit;
vertical-align: inherit;
}
.storybook-example a:hover {
color: inherit;
text-decoration: none;
}
/* Reset list styles */
.storybook-example ul,
.storybook-example ol {
margin: 0;
padding: 0;
list-style: none;
}
/* Override Infima's .markdown li + li margin */
.storybook-example li + li,
.markdown .storybook-example li + li {
margin-top: 0;
}
/* Reset heading styles */
.storybook-example h1,
.storybook-example h2,
.storybook-example h3,
.storybook-example h4,
.storybook-example h5,
.storybook-example h6 {
margin: 0;
font-size: inherit;
font-weight: inherit;
}
/* Reset paragraph margins */
.storybook-example p {
margin: 0;
}
/* Reset table margins - Infima applies margin-bottom via --ifm-spacing-vertical */
.storybook-example table {
margin: 0;
display: table;
}
/* Ensure Ant Design components render correctly */
.storybook-example .ant-breadcrumb {
line-height: 1.5715;
}
.storybook-example .ant-breadcrumb a {
color: rgba(0, 0, 0, 0.45);
}
.storybook-example .ant-breadcrumb a:hover {
color: rgba(0, 0, 0, 0.85);
}
/* ============================================
Ant Design Popup/Portal Isolation
These components render outside .storybook-example via portals
============================================ */
/* DatePicker, TimePicker dropdown panels - reset Infima table styles
Using doubled selectors for higher specificity than Infima's defaults */
.ant-picker-dropdown.ant-picker-dropdown table,
.ant-picker-dropdown.ant-picker-dropdown thead,
.ant-picker-dropdown.ant-picker-dropdown tbody,
.ant-picker-dropdown.ant-picker-dropdown tr,
.ant-picker-dropdown.ant-picker-dropdown th,
.ant-picker-dropdown.ant-picker-dropdown td {
border: none;
background: none;
background-color: transparent;
}
.ant-picker-dropdown.ant-picker-dropdown table {
border-collapse: separate;
border-spacing: 0;
width: 100%;
display: table;
}
/* Override Infima's zebra striping with higher specificity */
.ant-picker-dropdown.ant-picker-dropdown tr:nth-child(2n),
.ant-picker-dropdown.ant-picker-dropdown tbody tr:nth-child(2n) {
background: none;
background-color: transparent;
}
.ant-picker-dropdown.ant-picker-dropdown th,
.ant-picker-dropdown.ant-picker-dropdown td {
padding: 0;
}
/* Select, Dropdown, Popover portals */
.ant-select-dropdown.ant-select-dropdown table,
.ant-select-dropdown.ant-select-dropdown thead,
.ant-select-dropdown.ant-select-dropdown tbody,
.ant-select-dropdown.ant-select-dropdown tr,
.ant-select-dropdown.ant-select-dropdown th,
.ant-select-dropdown.ant-select-dropdown td,
.ant-dropdown.ant-dropdown table,
.ant-dropdown.ant-dropdown thead,
.ant-dropdown.ant-dropdown tbody,
.ant-dropdown.ant-dropdown tr,
.ant-dropdown.ant-dropdown th,
.ant-dropdown.ant-dropdown td,
.ant-popover.ant-popover table,
.ant-popover.ant-popover thead,
.ant-popover.ant-popover tbody,
.ant-popover.ant-popover tr,
.ant-popover.ant-popover th,
.ant-popover.ant-popover td {
border: none;
background: none;
background-color: transparent;
}
.ant-select-dropdown.ant-select-dropdown tr:nth-child(2n),
.ant-dropdown.ant-dropdown tr:nth-child(2n),
.ant-popover.ant-popover tr:nth-child(2n) {
background: none;
background-color: transparent;
}
/* Modal portals */
.ant-modal.ant-modal table,
.ant-modal.ant-modal thead,
.ant-modal.ant-modal tbody,
.ant-modal.ant-modal tr,
.ant-modal.ant-modal th,
.ant-modal.ant-modal td {
border: none;
background: none;
background-color: transparent;
}
.ant-modal.ant-modal tr:nth-child(2n) {
background: none;
background-color: transparent;
}
/* ============================================
Live Code Editor Height Limits
Prevents tall code blocks from dominating the page
============================================ */
/* Limit the code editor height and make it scrollable */
/* Target multiple possible class names used by Docusaurus/react-live */
.playgroundEditor,
[class*="playgroundEditor"],
.live-editor,
[class*="liveEditor"] {
max-height: 350px !important;
overflow: auto !important;
}
/* The actual textarea/code area inside the editor */
.playgroundEditor textarea,
.playgroundEditor pre,
[class*="playgroundEditor"] textarea,
[class*="playgroundEditor"] pre {
max-height: 350px !important;
overflow: auto !important;
}
/* Also limit the preview area for consistency */
.playgroundPreview,
[class*="playgroundPreview"] {
max-height: 400px;
overflow: auto;
}
/* Hide sidebar items with sidebar_class_name: hidden in frontmatter */
.menu__list-item.hidden {
display: none;
}

View File

@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
@import 'antd-theme.less';
body {
font-family: var(--ifm-font-family-base);
@@ -56,7 +57,13 @@ html[data-theme='dark'] .docusaurus-highlight-code-line {
font-weight: 400;
}
/* Legacy Swagger UI styles (for versioned docs that still use swagger-ui-react) */
/* Hacks to disable Swagger UI's "try it out" interactive mode */
.try-out,
.auth-wrapper,
.information-container {
display: none !important;
}
.swagger-ui table td,
.swagger-ui table th,
.swagger-ui table tr {
@@ -80,29 +87,26 @@ a > span > svg {
text-align: center;
position: relative;
z-index: 2;
}
.default-button-theme::before {
border-radius: inherit;
background: linear-gradient(180deg, #11b0d8 0%, #116f86 100%);
content: '';
display: block;
height: 100%;
position: absolute;
top: 0;
left: 0;
opacity: 0;
width: 100%;
z-index: -1;
transition: all 0.3s;
}
.default-button-theme:hover {
color: #ffffff;
}
.default-button-theme:hover::before {
opacity: 1;
&::before {
border-radius: inherit;
background: linear-gradient(180deg, #11b0d8 0%, #116f86 100%);
content: '';
display: block;
height: 100%;
position: absolute;
top: 0;
left: 0;
opacity: 0;
width: 100%;
z-index: -1;
transition: all 0.3s;
}
&:hover {
color: #ffffff;
&::before {
opacity: 1;
}
}
}
/* Navbar */
@@ -111,32 +115,32 @@ a > span > svg {
font-size: 14px;
font-weight: 400;
transition: all 0.5s;
}
.navbar .get-started-button {
border-radius: 10px;
font-size: 18px;
font-weight: bold;
width: 142px;
padding: 7px 0;
margin-right: 20px;
}
.get-started-button {
border-radius: 10px;
font-size: 18px;
font-weight: bold;
width: 142px;
padding: 7px 0;
margin-right: 20px;
}
.navbar .github-button {
background-image: url('/img/github.png');
background-size: contain;
width: 30px;
height: 30px;
margin-right: 10px;
.github-button {
background-image: url('/img/github.png');
background-size: contain;
width: 30px;
height: 30px;
margin-right: 10px;
}
}
.navbar--dark {
background-color: transparent;
border-bottom: 1px solid rgba(24, 115, 132, 0.4);
}
.navbar--dark .github-button {
background-image: url('/img/github-dark.png');
.github-button {
background-image: url('/img/github-dark.png');
}
}
.navbar__logo {
@@ -155,11 +159,11 @@ a > span > svg {
.navbar {
padding-right: 8px;
padding-left: 8px;
}
.navbar .get-started-button,
.navbar .github-button {
display: none;
.get-started-button,
.github-button {
display: none;
}
}
.navbar__items {
@@ -188,20 +192,20 @@ a > span > svg {
--docsearch-searchbox-background: var(--ifm-navbar-background-color);
border: 1px solid #187384;
border-radius: 10px;
}
.navbar .DocSearch.DocSearch-Button {
width: 225px;
}
&.DocSearch-Button {
width: 225px;
}
.navbar .DocSearch .DocSearch-Search-Icon {
width: 16px;
height: 16px;
}
.DocSearch-Search-Icon {
width: 16px;
height: 16px;
}
.navbar .DocSearch .DocSearch-Button-Key,
.navbar .DocSearch .DocSearch-Button-Placeholder {
display: none;
.DocSearch-Button-Key,
.DocSearch-Button-Placeholder {
display: none;
}
}
.navbar--dark .DocSearch {
@@ -222,37 +226,18 @@ a > span > svg {
font-size: 15px;
}
.footer__ci-services {
.footer__applitools {
background-color: #0d3e49;
color: #e1e1e1;
position: absolute;
top: 0;
left: 0;
width: 100%;
padding: 12px 0;
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
}
padding: 16px 0;
.footer__ci-services span {
font-size: 13px;
opacity: 0.85;
}
.footer__ci-services a {
display: inline-flex;
align-items: center;
transition: opacity 0.2s;
}
.footer__ci-services a:hover {
opacity: 0.8;
}
.footer__ci-services img {
height: 28px;
img {
height: 34px;
}
}
.footer__divider {
@@ -267,16 +252,7 @@ a > span > svg {
}
@media only screen and (max-width: 996px) {
.footer__ci-services {
gap: 12px;
padding: 10px 16px;
}
.footer__ci-services span {
font-size: 12px;
}
.footer__ci-services img {
height: 22px;
.footer__applitools img {
height: 28px;
}
}

10
docs/src/theme.d.ts vendored
View File

@@ -30,13 +30,3 @@ declare module '@theme/Layout' {
export default function Layout(props: Props): ReactNode;
}
declare module '@theme/Playground/Header' {
import type { ReactNode } from 'react';
export interface Props {
readonly children?: ReactNode;
}
export default function PlaygroundHeader(props: Props): ReactNode;
}

View File

@@ -1,107 +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.
*/
import React, { type ReactNode } from 'react';
import { LiveError, LivePreview } from 'react-live';
import BrowserOnly from '@docusaurus/BrowserOnly';
import { ErrorBoundaryErrorMessageFallback } from '@docusaurus/theme-common';
import ErrorBoundary from '@docusaurus/ErrorBoundary';
import Translate from '@docusaurus/Translate';
import PlaygroundHeader from '@theme/Playground/Header';
import styles from './styles.module.css';
// Get the theme wrapper for Superset components
function getThemeWrapper() {
if (typeof window === 'undefined') {
return ({ children }: { children: React.ReactNode }) => <>{children}</>;
}
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { themeObject } = require('@apache-superset/core/ui');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { App } = require('antd');
if (!themeObject?.SupersetThemeProvider) {
return ({ children }: { children: React.ReactNode }) => <>{children}</>;
}
return ({ children }: { children: React.ReactNode }) => (
<themeObject.SupersetThemeProvider>
<App>{children}</App>
</themeObject.SupersetThemeProvider>
);
} catch (e) {
console.error('[PlaygroundPreview] Failed to load theme provider:', e);
return ({ children }: { children: React.ReactNode }) => <>{children}</>;
}
}
function Loader() {
return <div>Loading...</div>;
}
function ThemedLivePreview(): ReactNode {
const ThemeWrapper = getThemeWrapper();
return (
<ThemeWrapper>
<LivePreview />
</ThemeWrapper>
);
}
function PlaygroundLivePreview(): ReactNode {
// No SSR for the live preview
// See https://github.com/facebook/docusaurus/issues/5747
return (
<BrowserOnly fallback={<Loader />}>
{() => (
<>
<ErrorBoundary
fallback={(params) => (
<ErrorBoundaryErrorMessageFallback {...params} />
)}
>
<ThemedLivePreview />
</ErrorBoundary>
<LiveError />
</>
)}
</BrowserOnly>
);
}
export default function PlaygroundPreview(): ReactNode {
return (
<>
<PlaygroundHeader>
<Translate
id="theme.Playground.result"
description="The result label of the live codeblocks"
>
Result
</Translate>
</PlaygroundHeader>
<div className={styles.playgroundPreview}>
<PlaygroundLivePreview />
</div>
</>
);
}

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.
*/
.playgroundPreview {
padding: 1rem;
background-color: var(--ifm-pre-background);
}

View File

@@ -18,49 +18,36 @@
*/
import React from 'react';
import { Button, Card, Input, Space, Tag, Tooltip } from 'antd';
// Browser-only check for SSR safety
const isBrowser = typeof window !== 'undefined';
// Import extension components from @apache-superset/core/ui
// This matches the established pattern used throughout the Superset codebase
// Resolved via webpack alias to superset-frontend/packages/superset-core/src/ui/components
import { Alert } from '@apache-superset/core/ui';
/**
* ReactLiveScope provides the scope for live code blocks.
* Any component added here will be available in ```tsx live blocks.
*
* Components are conditionally loaded only in the browser to avoid
* SSG issues with Emotion CSS-in-JS jsx runtime.
*
* Components are available by name, e.g.:
* <Button>Click me</Button>
* <Avatar size="large" />
* <Badge count={5} />
* To add more components:
* 1. Import the component from @apache-superset/core above
* 2. Add it to the scope object below
*/
// Base scope with React (always available)
const ReactLiveScope: Record<string, unknown> = {
const ReactLiveScope = {
// React core
React,
...React,
// Extension components from @apache-superset/core
Alert,
// Common Ant Design components (for demos)
Button,
Card,
Input,
Space,
Tag,
Tooltip,
};
// Only load Superset components in browser context
// This prevents SSG errors from Emotion CSS-in-JS
if (isBrowser) {
try {
// Dynamic require for browser-only execution
// eslint-disable-next-line @typescript-eslint/no-require-imports
const SupersetComponents = require('@superset/components');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { Alert } = require('@apache-superset/core/ui');
console.log('[ReactLiveScope] SupersetComponents keys:', Object.keys(SupersetComponents || {}).slice(0, 10));
console.log('[ReactLiveScope] Has Button?', 'Button' in (SupersetComponents || {}));
Object.assign(ReactLiveScope, SupersetComponents, { Alert });
console.log('[ReactLiveScope] Final scope keys:', Object.keys(ReactLiveScope).slice(0, 20));
} catch (e) {
console.error('[ReactLiveScope] Failed to load Superset components:', e);
}
}
export default ReactLiveScope;

View File

@@ -74,14 +74,6 @@ export default function Root({ children }) {
window._paq.push(['trackSiteSearch', keyword, category, resultsCount]);
};
// Helper to track page views
const trackPageView = (url, title) => {
if (devMode) {
console.log('Matomo trackPageView:', { url, title });
}
window._paq.push(['trackPageView']);
};
// Track external link clicks using domain as category (vendor-agnostic)
const handleLinkClick = (event) => {
@@ -229,6 +221,7 @@ export default function Root({ children }) {
trackDocsVersion();
if (devMode) {
console.log('Tracking page view:', currentPath, currentTitle);
window._paq.push(['setDomains', ['superset.apache.org']]);
window._paq.push([
'setCustomUrl',
@@ -240,7 +233,7 @@ export default function Root({ children }) {
window._paq.push(['setReferrerUrl', window.location.href]);
window._paq.push(['setDocumentTitle', currentTitle]);
trackPageView(currentPath, currentTitle);
window._paq.push(['trackPageView']);
// Check for 404 after page renders
setTimeout(track404, 500);

View File

@@ -18,7 +18,6 @@
*/
import path from 'path';
import webpack from 'webpack';
import type { Plugin } from '@docusaurus/types';
export default function webpackExtendPlugin(): Plugin<void> {
@@ -27,86 +26,14 @@ export default function webpackExtendPlugin(): Plugin<void> {
configureWebpack(config) {
const isDev = process.env.NODE_ENV === 'development';
// Use NormalModuleReplacementPlugin to forcefully replace react-table
// This is necessary because regular aliases don't work for modules in nested node_modules
const reactTableShim = path.resolve(__dirname, './shims/react-table.js');
config.plugins?.push(
new webpack.NormalModuleReplacementPlugin(
/^react-table$/,
reactTableShim,
),
);
// Stub out heavy third-party packages that are transitive dependencies of
// superset-frontend components. The barrel file (components/index.ts)
// re-exports all components, so webpack must resolve their imports even
// though these components are never rendered on the docs site.
const nullModuleShim = path.resolve(__dirname, './shims/null-module.js');
const heavyDepsPatterns = [
/^brace(\/|$)/, // ACE editor modes/themes
/^react-ace(\/|$)/,
/^ace-builds(\/|$)/,
/^react-js-cron(\/|$)/, // Cron picker + CSS
// react-resize-detector: NOT shimmed — DropdownContainer needs it at runtime
// for overflow detection. Resolves from superset-frontend/node_modules.
/^react-window(\/|$)/,
/^re-resizable(\/|$)/,
/^react-draggable(\/|$)/,
/^ag-grid-react(\/|$)/,
/^ag-grid-community(\/|$)/,
];
heavyDepsPatterns.forEach(pattern => {
config.plugins?.push(
new webpack.NormalModuleReplacementPlugin(pattern, nullModuleShim),
);
});
// Add YAML loader rule directly to existing rules
config.module?.rules?.push({
test: /\.ya?ml$/,
use: 'js-yaml-loader',
});
// Add swc-loader rule for superset-frontend files
// SWC is a Rust-based transpiler that's significantly faster than babel
const supersetFrontendPath = path.resolve(
__dirname,
'../../superset-frontend',
);
config.module?.rules?.push({
test: /\.(tsx?|jsx?)$/,
include: supersetFrontendPath,
exclude: /node_modules/,
use: {
loader: 'swc-loader',
options: {
// Ignore superset-frontend/.swcrc which references plugins not
// installed in the docs workspace (e.g. @swc/plugin-emotion)
swcrc: false,
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
transform: {
react: {
runtime: 'automatic',
importSource: '@emotion/react',
},
},
},
},
},
});
return {
devtool: isDev ? false : config.devtool,
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
devtool: isDev ? 'eval-source-map' : config.devtool,
...(isDev && {
optimization: {
...config.optimization,
@@ -117,16 +44,8 @@ export default function webpackExtendPlugin(): Plugin<void> {
},
}),
resolve: {
// Add superset-frontend node_modules to module resolution
modules: [
...(config.resolve?.modules || []),
path.resolve(__dirname, '../../superset-frontend/node_modules'),
],
alias: {
...config.resolve.alias,
// Ensure single React instance across all modules (critical for hooks to work)
react: path.resolve(__dirname, '../node_modules/react'),
'react-dom': path.resolve(__dirname, '../node_modules/react-dom'),
// Allow importing from superset-frontend
src: path.resolve(__dirname, '../../superset-frontend/src'),
// '@superset-ui/core': path.resolve(
@@ -139,29 +58,14 @@ export default function webpackExtendPlugin(): Plugin<void> {
__dirname,
'../../superset-frontend/packages/superset-ui-core/src/components',
),
// Also alias the full package path for internal imports within components
'@superset-ui/core/components': path.resolve(
__dirname,
'../../superset-frontend/packages/superset-ui-core/src/components',
),
// Use a shim for react-table to handle CommonJS to ES module interop
// react-table v7 is CommonJS, but Superset components import it with ES module syntax
'react-table': path.resolve(__dirname, './shims/react-table.js'),
// Extension API package - resolve @apache-superset/core and its sub-paths
// to source so the docs build doesn't depend on pre-built lib/ artifacts.
// More specific sub-path aliases must come first; webpack matches the
// longest prefix.
// Extension API package - allows docs to import from @apache-superset/core/ui
// This matches the established pattern used throughout the Superset codebase
// Point directly to components to avoid importing theme (which has font dependencies)
// Note: TypeScript types come from docs/src/types/apache-superset-core (see tsconfig.json)
// This split is intentional: webpack resolves actual source, tsconfig provides simplified types
'@apache-superset/core/ui': path.resolve(
__dirname,
'../../superset-frontend/packages/superset-core/src/ui',
),
'@apache-superset/core/api/core': path.resolve(
__dirname,
'../../superset-frontend/packages/superset-core/src/api/core',
),
'@apache-superset/core': path.resolve(
__dirname,
'../../superset-frontend/packages/superset-core/src',
'../../superset-frontend/packages/superset-core/src/ui/components',
),
// Add proper Storybook aliases
'@storybook/blocks': path.resolve(
@@ -219,6 +123,8 @@ export default function webpackExtendPlugin(): Plugin<void> {
),
},
},
// We're removing the ts-loader rule that was processing superset-frontend files
// This will prevent TypeScript errors from files outside the docs directory
};
},
};

View File

@@ -114,12 +114,6 @@
"lifecycle": "testing",
"description": "Allow users to export full CSV of table viz type. Warning: Could cause server memory/compute issues with large datasets."
},
{
"name": "AWS_DATABASE_IAM_AUTH",
"default": false,
"lifecycle": "testing",
"description": "Enable AWS IAM authentication for database connections (Aurora, Redshift). Allows cross-account role assumption via STS AssumeRole. Security note: When enabled, ensure Superset's IAM role has restricted sts:AssumeRole permissions to prevent unauthorized access."
},
{
"name": "CACHE_IMPERSONATION",
"default": false,
@@ -247,13 +241,6 @@
"description": "Enables dashboard virtualization for improved performance",
"category": "path_to_deprecation"
},
{
"name": "DASHBOARD_VIRTUALIZATION_DEFER_DATA",
"default": false,
"lifecycle": "stable",
"description": "Supports simultaneous data and dashboard virtualization for backend performance",
"category": "runtime_config"
},
{
"name": "DATAPANEL_CLOSED_BY_DEFAULT",
"default": false,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

View File

@@ -1,21 +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.
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 568 501" width="40" height="40" fill="#1185FE">
<path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.889-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C9.945 203.659 0 75.291 0 57.946 0-28.906 76.135-1.612 123.121 33.664z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

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.
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="40" height="40" fill="none" stroke="#484848" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="2" y1="12" x2="22" y2="12"/>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,21 +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.
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="40" height="40" fill="#0A66C2">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,21 +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.
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="40" height="40" fill="#484848">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

View File

@@ -1,57 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="Õ_xBA__x2264__x201E__1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 107.7 107.7"
style="enable-background:new 0 0 107.7 107.7;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#9E2878;}
</style>
<g>
<g id="g1133" transform="translate(-244.51235,-228.78793)">
<path id="path1119" class="st0" d="M340.8,253.8c2.6-1,5.5,0.4,6.2,3c0.7,2.6-1.1,5.7-3.7,6.4c-3.2,0.6-6.2-1.4-9.3,0.2
c-3.1,1.7-4,5.2-4.7,8.3c-1.4,5-8.5,7.3-12.1,4.2c-3.3-2.4-3.4-7.8-0.2-11c2.2-2.5,5.9-3.3,8.8-2c2.7,1.2,6,1.7,8.6-0.4
C337.5,260.1,336.7,255.1,340.8,253.8L340.8,253.8z"/>
<path id="path1121" class="st0" d="M280.5,244.7c4.2-2.2,9.5,1.5,8.2,6.1c-1.4,5.4-0.7,11.5,2.9,15.5c3.4,4,9.8,4.8,14.6,1.9
c3.7-2.1,6-5.8,7.4-9.6c1-3.1,0.6-6.2,1.1-9.3c1-3.8,5.8-6,9.1-4.2c3.2,1.4,4,5.9,1.7,8.8c-1.4,2.2-4.2,2.7-6.3,3.8
c-3.6,1.9-6.5,4.9-8.4,8.6c-1.2,2.1-1.1,4.5-1.7,6.7c-0.9,1.9-3,2.7-4.8,2.9c-4.8,0.6-9.5,3-13,6.7c-1.7,1.7-3.1,4.2-5.6,4.2
c-2.7-0.2-4.7-2.6-7.4-2.7c-4.9-0.7-10.2,0.7-14.4,4c-3.7,3.1-9.4,0.8-9.6-3.7c-0.9-5.1,6.2-9.5,10.1-6.2c4.3,4,10.8,5.6,16.9,3.7
c5.6-1.9,9.7-8.3,8.6-14c-0.9-4.9-4.2-9.3-8.6-11.4c-1.7-0.8-3.6-1.6-4.2-3.5C275.6,250.2,277.3,246.2,280.5,244.7L280.5,244.7z"
/>
<path id="path1123" class="st0" d="M277.8,235.9c2.2-0.8,4.2,1.3,3.3,3.4c-0.6,2.1-3.7,2.7-4.8,1.1
C275,238.9,276,236.4,277.8,235.9z"/>
<path id="path1125" class="st0" d="M246.9,278.8c2.2-0.8,4.2,1.2,3.3,3.4c-0.6,2.1-3.7,2.7-4.8,1C244,281.9,245,279.3,246.9,278.8
L246.9,278.8z"/>
<path id="path1127" class="st0" d="M328.2,236.2c2.2-0.7,4.2,1.3,3.3,3.5c-0.6,2-3.7,2.7-4.8,1
C325.4,239.2,326.3,236.8,328.2,236.2z"/>
<path id="path1129" class="st0" d="M253.6,257.7c0.4-3.7,5.5-5.9,8.1-3.6c1.9,1.1,1.6,3.6,2.2,5.4c0.4,2.4,2.7,4.3,5.2,4.3
c3.2,0.3,6.4-2.3,9.5-1.2c4.8,1.2,6.5,7.5,3.2,11.5c-2.9,4.1-9.3,4.5-12,0.7c-2.4-2.8-0.5-7.1-2.7-10.1c-1.7-2.9-5.4-2.7-8.3-1.9
C255.8,263.8,253,260.7,253.6,257.7L253.6,257.7z"/>
<path id="path1131" class="st0" d="M300.8,230c3.3-1.9,7.5,0.9,6.7,4.6c0,2.3-2.2,3.6-3.5,5.2c-1.9,1.9-2.3,4.9-1.2,7
c1.3,2.9,4.9,4,5.5,7.2c1.2,4.8-3.3,10.1-8.2,9.8c-4.8,0.1-8.3-5-6.3-9.5c1.2-3.8,5.8-4.9,7.2-8.5c1.7-3.2-0.4-6.1-2.4-8.1
C296.7,235.6,297.9,231.4,300.8,230z"/>
</g>
<g id="g1149" transform="translate(-244.51235,-228.78793)">
<path id="path1135" class="st0" d="M256,311.5c-2.6,1-5.5-0.4-6.2-3c-0.7-2.6,1.1-5.7,3.7-6.4c3.2-0.6,6.2,1.4,9.3-0.2
c3.1-1.6,4-5.1,4.7-8.2c1.4-5,8.5-7.3,12.1-4.2c3.3,2.4,3.4,7.8,0.2,10.9c-2.2,2.5-5.9,3.3-8.8,2c-2.7-1.2-6-1.7-8.6,0.4
C259.1,305,259.9,310.2,256,311.5L256,311.5z"/>
<path id="path1137" class="st0" d="M316.1,320.5c-4.2,2.2-9.5-1.5-8.2-6c1.4-5.4,0.7-11.6-2.9-15.6c-3.4-4-9.8-4.7-14.6-1.9
c-3.7,2-6,5.7-7.4,9.5c-1,3.1-0.6,6.2-1.1,9.3c-1,3.8-5.8,6-9.1,4.3c-3.2-1.4-4-5.9-1.7-8.9c1.4-2.2,4.2-2.7,6.3-3.8
c3.6-1.9,6.5-4.9,8.4-8.6c1.2-2,1.1-4.5,1.7-6.6c0.9-1.9,3-2.7,4.8-2.9c4.8-0.6,9.5-3,13-6.7c1.7-1.6,3.1-4.2,5.6-4.1
c2.7,0.1,4.7,2.5,7.4,2.7c4.9,0.6,10.2-0.8,14.4-4c3.7-3.2,9.4-0.9,9.6,3.6c0.9,5.1-6.2,9.5-10.1,6.2c-4.3-4-10.8-5.6-16.9-3.7
c-5.6,1.9-9.7,8.3-8.6,14c0.9,4.8,4.2,9.3,8.6,11.3c1.7,0.8,3.6,1.6,4.2,3.5C321.2,314.9,319.4,319.1,316.1,320.5L316.1,320.5z"/>
<path id="path1139" class="st0" d="M318.9,329.4c-2.2,0.8-4.2-1.3-3.3-3.4c0.6-2.1,3.7-2.7,4.8-1.1
C321.7,326.3,320.7,328.8,318.9,329.4z"/>
<path id="path1141" class="st0" d="M349.9,286.5c-2.2,0.7-4.2-1.3-3.3-3.5c0.6-2.1,3.7-2.7,4.8-1
C352.8,283.5,351.8,285.9,349.9,286.5z"/>
<path id="path1143" class="st0" d="M268.5,329c-2.2,0.7-4.2-1.3-3.3-3.5c0.6-2,3.7-2.7,4.8-1C271.3,325.9,270.3,328.5,268.5,329z"
/>
<path id="path1145" class="st0" d="M343.1,307.4c-0.4,3.7-5.5,5.9-8.1,3.6c-1.9-1.1-1.6-3.6-2.2-5.4c-0.4-2.4-2.7-4.3-5.2-4.3
c-3.2-0.3-6.4,2.3-9.5,1.2c-4.8-1.2-6.5-7.5-3.2-11.5c2.9-4.1,9.3-4.5,12-0.7c2.4,2.8,0.5,7,2.7,10c1.7,2.9,5.4,2.7,8.3,1.9
C341,301.4,343.8,304.4,343.1,307.4L343.1,307.4z"/>
<path id="path1147" class="st0" d="M295.8,335.2c-3.3,2-7.5-0.9-6.7-4.6c0-2.3,2.2-3.6,3.5-5.2c1.9-1.9,2.3-4.9,1.2-7
c-1.3-2.9-4.9-4-5.5-7.2c-1.2-4.8,3.3-10.1,8.2-9.8c4.8-0.1,8.3,5,6.3,9.6c-1.2,3.7-5.8,4.8-7.2,8.4c-1.7,3.2,0.4,6.1,2.4,8.2
C300,329.6,298.8,333.8,295.8,335.2L295.8,335.2z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/static/img/databases/cratedb.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1 +0,0 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 436 58" width="436" height="58"><style>.a{fill:#009dc7}</style><title>CrateDB</title><path class="a" d="m3.6 0h14.5v14.5h-14.5z"/><path class="a" d="m25.4 0h14.5v14.5h-14.5z"/><path class="a" d="m47.2 0h14.5v36.2h-14.5z"/><path class="a" d="m3.6 21.7h36.3v14.5h-36.3z"/><path class="a" d="m3.6 43.4h58.1v14.5h-58.1z"/><path d="m249.5 16.9h12.1v8.4h-12.1v19.3h12.1v8.4h-10.4c-1.5 0.1-2.9-0.2-4.3-0.7-1.3-0.6-2.6-1.3-3.6-2.3-1.1-1-1.9-2.2-2.5-3.6-0.6-1.3-1-2.7-1-4.2v-16.9h-9.7v-8.4h9.7v-12.1h9.7z"/><path d="m159.8 22.9q0-1.2 0.5-2.3 0.4-1.1 1.3-2 0.9-0.8 2-1.3 1.1-0.4 2.3-0.4h12.1v8.4h-13.3q-1 0-1.9 0.4-0.8 0.4-1.5 1-0.7 0.7-1.1 1.6-0.3 0.9-0.3 1.9v22.9h-9.7v-36.2h7.2l1.2 6h1.2z"/><path d="m90.8 16.9c0-1.6 0.3-3.2 0.9-4.6 0.6-1.5 1.5-2.8 2.7-3.9 1.1-1.2 2.4-2 3.9-2.7 1.5-0.6 3-0.9 4.6-0.9h36.4v9.7h-38.8v28.9h38.8v9.7h-36.4c-1.6 0-3.1-0.3-4.6-0.9-1.5-0.6-2.8-1.5-3.9-2.6-1.2-1.2-2.1-2.5-2.7-4-0.6-1.4-0.9-3-0.9-4.6v-24.1z"/><path fill-rule="evenodd" d="m358.9 6.1c2.1 0.9 4 2.1 5.5 3.7 1.6 1.5 2.9 3.4 3.7 5.5 0.9 2 1.3 4.2 1.3 6.4v14.5c0 2.2-0.4 4.4-1.3 6.5-0.8 2-2.1 3.9-3.7 5.4-1.5 1.6-3.4 2.8-5.5 3.7-2 0.8-4.2 1.3-6.4 1.3h-34v-48.3h34c2.2 0 4.4 0.5 6.4 1.3zm0.3 12.8q-0.6-1.3-1.6-2.3-1-1-2.4-1.6-1.3-0.5-2.7-0.5h-24.3v28.9h24.3q1.4 0 2.7-0.5 1.4-0.6 2.4-1.6 1-1 1.6-2.3 0.5-1.4 0.5-2.8v-14.5q0-1.4-0.5-2.8z"/><path fill-rule="evenodd" d="m212.7 16.9c1.3 0 2.5 0.2 3.7 0.7 1.2 0.5 2.3 1.2 3.2 2.1 0.9 0.9 1.7 1.9 2.2 3.1 0.4 1.2 0.7 2.5 0.7 3.7v26.6h-7.3l-1.2-4.8h-1.2q0 0.9-0.4 1.8-0.3 0.9-1 1.6-0.7 0.7-1.6 1-0.9 0.4-1.8 0.4h-14c-1.5 0.1-2.9-0.2-4.3-0.7-1.4-0.5-2.6-1.3-3.7-2.3-1.1-1-1.9-2.2-2.5-3.5-0.6-1.3-1-2.8-1-4.2 0-1.4 0.3-2.8 0.9-4.1 0.6-1.2 1.4-2.4 2.4-3.4 1-0.9 2.2-1.7 3.5-2.2 1.3-0.5 2.6-0.7 4-0.7h19.4v-7.3h-25.4v-7.8zm-3.6 28.9q0.7 0 1.3-0.2 0.7-0.3 1.2-0.8 0.5-0.5 0.8-1.2 0.3-0.7 0.3-1.4v-3.6h-17.6c-0.9 0-1.8 0.4-2.5 1.1-0.7 0.6-1.1 1.6-1.1 2.5 0 1 0.4 1.9 1.1 2.6 0.6 0.7 1.6 1 2.5 1.1h14z"/><path fill-rule="evenodd" d="m296.7 16.9c1.6 0 3.2 0.3 4.7 0.9 1.4 0.6 2.8 1.5 3.9 2.6 1.1 1.1 2 2.5 2.6 3.9 0.6 1.5 0.9 3.1 0.9 4.7v9.6h-30.2v6.6h27.8v7.9h-25.4c-1.6 0-3.2-0.3-4.6-0.9-1.5-0.6-2.8-1.5-4-2.6-1.1-1.2-2-2.5-2.6-4-0.6-1.4-0.9-3-0.9-4.6v-12c0-1.6 0.3-3.2 0.9-4.7 0.6-1.4 1.5-2.8 2.6-3.9 1.2-1.1 2.5-2 4-2.6 1.4-0.6 3-0.9 4.6-0.9h15.7zm3.7 15.1v-3.7q0-0.7-0.3-1.4-0.3-0.6-0.8-1.1-0.5-0.5-1.2-0.8-0.6-0.3-1.3-0.3h-14.6q-0.7 0-1.4 0.3-0.6 0.3-1.1 0.8-0.6 0.5-0.8 1.2-0.3 0.6-0.3 1.4v3.6z"/><path fill-rule="evenodd" d="m427.3 27.7q0 0 0 0 0 0 0 0.1zm0 0c2.3 1.9 3.9 4.4 4.6 7.3 0.8 2.9 0.6 5.9-0.4 8.7-1.1 2.7-2.9 5.1-5.3 6.8-2.5 1.7-5.4 2.6-8.3 2.6h-38.8v-48.2h36.3c2.7 0 5.3 0.7 7.6 2.1 2.3 1.3 4.1 3.3 5.3 5.7 1.3 2.3 1.8 5 1.6 7.6-0.2 2.6-1.1 5.2-2.6 7.4zm-38.5-3.7h26.6c1.3 0 2.5-0.5 3.4-1.4 0.9-0.9 1.4-2.1 1.4-3.4 0-1.2-0.5-2.5-1.4-3.4-0.9-0.9-2.1-1.4-3.4-1.4h-26.6zm32.5 17.5c0.9-1 1.3-2.3 1.3-3.6 0-1.4-0.4-2.6-1.3-3.6-0.9-1-2.1-1.7-3.4-1.9h-29.1v10.9h29.1c1.3-0.2 2.5-0.8 3.4-1.8z"/></svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/static/img/databases/risingwave.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,182 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1100"
height="300"
viewBox="0 0 291.04166 79.375002"
version="1.1"
id="svg93"
inkscape:version="1.1.2 (b8e25be8, 2022-02-05)"
sodipodi:docname="rwreadme.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview95"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
units="px"
inkscape:zoom="0.39692378"
inkscape:cx="578.19665"
inkscape:cy="137.30596"
inkscape:window-width="1584"
inkscape:window-height="1027"
inkscape:window-x="336"
inkscape:window-y="25"
inkscape:window-maximized="0"
inkscape:current-layer="g19" />
<defs
id="defs90">
<style
id="style97">.cls-1{fill:#3a7ceb;}.cls-2{fill:#e23e2b;}.cls-3{fill:#f0b400;}.cls-4{fill:#4acfd2;}</style>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath23">
<path
d="M 0,1080 H 1920 V 0 H 0 Z"
id="path21" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath91">
<path
d="M 0,1080 H 1920 V 0 H 0 Z"
id="path89" />
</clipPath>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g19"
clip-path="url(#clipPath23)"
transform="matrix(0.35277777,0,0,-0.35277777,-280.32591,259.26734)">
<g
id="g355"
transform="matrix(1.7,0,0,1.7,-756.33217,-454.30518)">
<g
id="g25"
transform="translate(1074.2552,637.33841)">
<path
d="m 0,0 h 1.913 c 1.998,0 3.532,0.414 4.601,1.241 1.068,0.827 1.602,2.016 1.602,3.567 0,1.55 -0.534,2.739 -1.602,3.567 -1.069,0.827 -2.603,1.24 -4.601,1.24 H 0 Z M 22.28,-21.557 H 9.667 L 0,-6.565 v -8.311 c 0,-3.689 -2.991,-6.681 -6.681,-6.681 h -3.451 V 17.421 H 5.635 c 2.171,0 4.066,-0.319 5.686,-0.956 1.62,-0.638 2.955,-1.508 4.007,-2.611 1.05,-1.103 1.843,-2.378 2.378,-3.825 0.533,-1.448 0.801,-2.999 0.801,-4.653 0,-2.964 -0.716,-5.368 -2.146,-7.211 -1.43,-1.844 -3.541,-3.094 -6.332,-3.748 z"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path27" />
</g>
<g
id="g29"
transform="translate(1108.2186,638.84481)">
<path
d="m 0,0 v -17.487 c 0,-3.079 -2.497,-5.576 -5.576,-5.576 h -3.78 V 3.456 h 5.9 C -1.547,3.456 0,1.909 0,0 m -9.977,13.64 c 0,0.724 0.138,1.404 0.414,2.042 0.275,0.637 0.654,1.197 1.137,1.68 0.482,0.482 1.042,0.861 1.68,1.138 0.638,0.275 1.318,0.413 2.042,0.413 0.724,0 1.404,-0.138 2.042,-0.413 0.637,-0.277 1.197,-0.656 1.68,-1.138 0.482,-0.483 0.861,-1.043 1.137,-1.68 0.276,-0.638 0.414,-1.318 0.414,-2.042 0,-0.723 -0.138,-1.405 -0.414,-2.042 C -0.121,10.96 -0.5,10.4 -0.982,9.918 -1.465,9.435 -2.025,9.056 -2.662,8.781 -3.3,8.504 -3.98,8.367 -4.704,8.367 c -0.724,0 -1.404,0.137 -2.042,0.414 -0.638,0.275 -1.198,0.654 -1.68,1.137 -0.483,0.482 -0.862,1.042 -1.137,1.68 -0.276,0.637 -0.414,1.319 -0.414,2.042"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path31" />
</g>
<g
id="g33"
transform="translate(1131.1708,635.47761)">
<path
d="m 0,0 c -1.724,0.896 -3.412,1.344 -5.066,1.344 -0.862,0 -1.56,-0.173 -2.094,-0.517 -0.534,-0.345 -0.801,-0.81 -0.801,-1.396 0,-0.31 0.043,-0.56 0.129,-0.749 0.086,-0.19 0.276,-0.371 0.569,-0.543 0.292,-0.173 0.723,-0.328 1.292,-0.466 0.569,-0.137 1.319,-0.31 2.249,-0.516 2.585,-0.517 4.514,-1.44 5.79,-2.766 1.274,-1.327 1.912,-3.024 1.912,-5.092 0,-1.551 -0.31,-2.946 -0.93,-4.187 -0.62,-1.241 -1.482,-2.284 -2.585,-3.128 -1.103,-0.845 -2.429,-1.499 -3.98,-1.964 -1.551,-0.465 -3.257,-0.698 -5.118,-0.698 -3.585,0 -7.117,1.016 -10.597,3.05 l 3.412,6.617 c 2.618,-1.793 5.099,-2.688 7.443,-2.688 0.861,0 1.568,0.189 2.12,0.569 0.551,0.378 0.827,0.86 0.827,1.447 0,0.344 -0.044,0.628 -0.129,0.853 -0.087,0.224 -0.268,0.43 -0.543,0.62 -0.276,0.189 -0.681,0.362 -1.215,0.517 -0.535,0.155 -1.232,0.318 -2.094,0.491 -2.894,0.586 -4.919,1.456 -6.073,2.611 -1.156,1.154 -1.732,2.748 -1.732,4.781 0,1.482 0.275,2.818 0.827,4.007 0.551,1.189 1.344,2.197 2.378,3.024 1.033,0.827 2.282,1.465 3.748,1.913 1.464,0.447 3.093,0.672 4.885,0.672 2.929,0 5.772,-0.569 8.529,-1.706 z"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path35" />
</g>
<g
id="g37"
transform="translate(1148.2289,638.84481)">
<path
d="m 0,0 v -17.487 c 0,-3.079 -2.497,-5.576 -5.576,-5.576 h -3.78 V 3.456 h 5.9 C -1.547,3.456 0,1.909 0,0 m -9.977,13.64 c 0,0.724 0.138,1.404 0.414,2.042 0.275,0.637 0.654,1.197 1.137,1.68 0.482,0.482 1.042,0.861 1.68,1.138 0.638,0.275 1.318,0.413 2.042,0.413 0.724,0 1.404,-0.138 2.042,-0.413 0.637,-0.277 1.197,-0.656 1.68,-1.138 0.482,-0.483 0.861,-1.043 1.137,-1.68 0.276,-0.638 0.414,-1.318 0.414,-2.042 0,-0.723 -0.138,-1.405 -0.414,-2.042 C -0.121,10.96 -0.5,10.4 -0.982,9.918 -1.465,9.435 -2.025,9.056 -2.662,8.781 -3.3,8.504 -3.98,8.367 -4.704,8.367 c -0.724,0 -1.404,0.137 -2.042,0.414 -0.638,0.275 -1.198,0.654 -1.68,1.137 -0.483,0.482 -0.862,1.042 -1.137,1.68 -0.276,0.637 -0.414,1.319 -0.414,2.042"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path39" />
</g>
<g
id="g41"
transform="translate(1153.6049,642.30081)">
<path
d="m 0,0 h 9.356 v -3.36 c 1.275,1.551 2.568,2.593 3.878,3.128 1.309,0.534 2.843,0.801 4.6,0.801 1.862,0 3.455,-0.302 4.782,-0.905 1.326,-0.603 2.456,-1.456 3.386,-2.559 0.758,-0.896 1.275,-1.896 1.551,-2.998 0.276,-1.103 0.414,-2.361 0.414,-3.774 v -11.276 c 0,-3.079 -2.497,-5.576 -5.576,-5.576 H 18.61 v 13.389 c 0,1.309 -0.095,2.369 -0.284,3.179 -0.19,0.809 -0.526,1.455 -1.008,1.939 -0.414,0.413 -0.879,0.705 -1.396,0.878 -0.517,0.173 -1.069,0.259 -1.654,0.259 -1.586,0 -2.801,-0.474 -3.645,-1.422 -0.845,-0.948 -1.267,-2.3 -1.267,-4.058 v -8.588 c 0,-3.079 -2.496,-5.576 -5.575,-5.576 H 0 Z"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path43" />
</g>
<g
id="g45"
transform="translate(1195.58,629.17041)">
<path
d="m 0,0 c 0,-0.862 0.155,-1.663 0.465,-2.404 0.31,-0.741 0.732,-1.388 1.267,-1.938 0.534,-0.552 1.171,-0.982 1.913,-1.292 0.74,-0.311 1.542,-0.466 2.403,-0.466 0.827,0 1.611,0.155 2.352,0.466 0.741,0.31 1.378,0.74 1.913,1.292 0.534,0.55 0.964,1.197 1.292,1.938 0.328,0.741 0.492,1.526 0.492,2.353 0,0.861 -0.164,1.654 -0.492,2.378 C 11.277,3.05 10.838,3.688 10.287,4.239 9.736,4.79 9.089,5.221 8.349,5.532 7.607,5.842 6.841,5.997 6.048,5.997 5.221,5.997 4.446,5.833 3.722,5.506 2.999,5.178 2.36,4.747 1.81,4.213 1.258,3.679 0.818,3.05 0.491,2.327 0.163,1.603 0,0.827 0,0 m 16.926,13.13 c 2.243,0 4.062,-1.818 4.062,-4.062 V -11.58 c 0,-1.482 -0.086,-2.808 -0.259,-3.98 -0.172,-1.171 -0.413,-2.205 -0.723,-3.102 -0.414,-1.137 -1.06,-2.196 -1.939,-3.179 -0.879,-0.982 -1.938,-1.835 -3.179,-2.558 -1.241,-0.725 -2.645,-1.293 -4.213,-1.706 -1.569,-0.414 -3.266,-0.621 -5.092,-0.621 -2.068,0 -3.963,0.259 -5.687,0.776 -1.723,0.516 -3.23,1.232 -4.522,2.145 -1.293,0.913 -2.353,1.982 -3.18,3.205 -0.827,1.224 -1.379,2.559 -1.654,4.006 H 1.085 c 0.655,-1.758 2.137,-2.636 4.446,-2.636 1.965,0 3.472,0.551 4.524,1.654 1.05,1.103 1.576,2.637 1.576,4.601 v 2.533 c -0.69,-0.655 -1.353,-1.198 -1.99,-1.628 -0.638,-0.432 -1.284,-0.785 -1.938,-1.06 -0.656,-0.276 -1.345,-0.474 -2.068,-0.595 -0.724,-0.12 -1.517,-0.181 -2.378,-0.181 -1.896,0 -3.636,0.328 -5.221,0.983 -1.586,0.654 -2.956,1.568 -4.11,2.739 -1.155,1.172 -2.051,2.576 -2.688,4.213 -0.638,1.637 -0.956,3.455 -0.956,5.454 0,2.033 0.343,3.92 1.033,5.661 0.689,1.74 1.629,3.256 2.818,4.549 1.189,1.292 2.601,2.308 4.239,3.05 1.636,0.74 3.42,1.111 5.35,1.111 3.136,0 5.772,-1.172 7.909,-3.515 v 2.791 z"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path47" />
</g>
<g
id="g49"
transform="translate(1230.2025,651.31941)">
<path
d="M 0,0 5.803,-21.735 14.074,3.44 h 8.064 L 30.409,-21.735 36.211,0 c 0.541,2.029 2.379,3.44 4.479,3.44 h 7.037 L 36.096,-35.538 H 25.757 l -7.651,22.591 -7.651,-22.591 H 0.116 L -11.515,3.44 h 7.036 C -2.379,3.44 -0.542,2.029 0,0"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path51" />
</g>
<g
id="g53"
transform="translate(1287.0272,629.11921)">
<path
d="m 0,0 c 0,-0.862 0.155,-1.664 0.465,-2.404 0.31,-0.742 0.732,-1.388 1.267,-1.939 0.534,-0.551 1.171,-0.982 1.913,-1.292 0.74,-0.31 1.542,-0.465 2.403,-0.465 0.827,0 1.611,0.155 2.352,0.465 0.741,0.31 1.378,0.741 1.913,1.292 0.534,0.551 0.964,1.189 1.292,1.913 0.328,0.724 0.492,1.499 0.492,2.326 0,0.828 -0.164,1.611 -0.492,2.353 -0.328,0.74 -0.758,1.386 -1.292,1.938 C 9.778,4.738 9.141,5.169 8.4,5.479 7.659,5.79 6.875,5.945 6.048,5.945 5.187,5.945 4.385,5.79 3.645,5.479 2.903,5.169 2.266,4.738 1.732,4.187 1.197,3.635 0.775,3.006 0.465,2.3 0.155,1.593 0,0.827 0,0 m 11.787,13.182 h 6.423 c 1.648,0 2.985,-1.336 2.985,-2.984 v -23.535 h -9.408 v 2.946 c -2,-2.516 -4.705,-3.774 -8.117,-3.774 -1.93,0 -3.705,0.354 -5.324,1.06 -1.62,0.706 -3.033,1.697 -4.239,2.973 -1.207,1.274 -2.146,2.774 -2.817,4.497 -0.672,1.723 -1.008,3.601 -1.008,5.635 0,1.895 0.327,3.696 0.982,5.402 0.654,1.706 1.568,3.196 2.739,4.472 1.172,1.274 2.567,2.282 4.188,3.023 1.619,0.741 3.412,1.112 5.376,1.112 3.308,0 6.048,-1.155 8.22,-3.464 z"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path55" />
</g>
<g
id="g57"
transform="translate(1322.8539,639.17391)">
<path
d="M 0,0 4.856,-10.468 9.806,0.03 c 0.892,1.891 2.794,3.097 4.884,3.097 h 7.018 L 7.906,-23.392 H 1.651 L -11.996,3.127 h 7.099 C -2.795,3.127 -0.884,1.907 0,0"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path59" />
</g>
<g
id="g61"
transform="translate(1364.8783,632.89261)">
<path
d="M 0,0 C -0.31,1.31 -0.948,2.36 -1.913,3.153 -2.878,3.946 -4.05,4.342 -5.428,4.342 -6.875,4.342 -8.056,3.963 -8.969,3.205 -9.882,2.447 -10.46,1.378 -10.701,0 Z m -10.959,-5.428 c 0,-4.032 1.895,-6.048 5.687,-6.048 2.032,0 3.566,0.827 4.6,2.481 h 9.047 c -1.828,-6.066 -6.394,-9.098 -13.699,-9.098 -2.241,0 -4.291,0.336 -6.152,1.008 -1.861,0.672 -3.456,1.628 -4.782,2.869 -1.327,1.241 -2.352,2.722 -3.075,4.446 -0.724,1.723 -1.086,3.652 -1.086,5.79 0,2.205 0.344,4.195 1.034,5.97 0.689,1.775 1.671,3.283 2.947,4.524 1.274,1.24 2.817,2.196 4.626,2.869 1.809,0.672 3.851,1.008 6.126,1.008 2.24,0 4.256,-0.336 6.048,-1.008 C 2.153,8.71 3.67,7.736 4.911,6.462 6.152,5.187 7.099,3.627 7.754,1.784 8.409,-0.061 8.736,-2.137 8.736,-4.446 v -0.982 z"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path63" />
</g>
</g>
<g
id="g65"
transform="translate(834.46932,615.63096)"
style="fill:#3a7ceb;fill-opacity:1">
<path
d="m 0,0 c 2.631,-2.663 5.385,-5.128 8.245,-7.398 7.412,-5.887 15.531,-10.457 24.042,-13.707 7.92,-3.025 16.177,-4.907 24.518,-5.645 25.774,-2.28 52.344,6.361 72.192,25.97 18.539,18.316 27.468,42.562 26.977,66.71 -1.092,5.997 -3.509,5.222 -3.427,8.318 0.575,-0.466 3.148,-2.951 5.036,-5.649 23.639,-35.608 19.594,-84.095 -11.954,-115.265 -28.918,-28.57 -72.105,-33.857 -106.307,-16.006 -8.184,4.272 -15.854,9.866 -22.69,16.786 -3.119,3.157 -5.96,6.485 -8.524,9.952 C 1.179,-26.566 -3.725,-16.178 -6.608,-5.365 -8.059,0.073 -8.999,5.618 -9.426,11.195 -6.631,7.278 -3.49,3.532 0,0"
style="fill:#3a7ceb;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path67" />
</g>
<g
id="g69"
transform="translate(893.24962,688.94836)">
<path
d="m 0,0 c 1.724,-1.745 2.919,-3.811 3.592,-6.004 0.215,-1.013 0.411,-2.032 0.592,-3.05 3.02,-17.13 1.122,-34.95 -5.679,-51.195 -2.925,13.93 -9.133,27.311 -18.638,38.906 -0.867,1.063 -1.768,2.11 -2.691,3.141 -4.817,5.221 -2.949,13.376 2.064,18.329 C -14.993,5.825 -5.698,5.767 0,0"
style="fill:#e23e2b;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path71" />
</g>
<g
id="g73"
transform="translate(903.34482,697.22136)">
<path
d="m 0,0 c 7.721,7.628 20.162,7.553 27.791,-0.168 4.886,-4.947 7.056,-11 5.637,-17.315 l -0.199,-0.886 C 29.492,-34.683 21.223,-50.161 8.436,-62.794 c -5.719,-5.651 -11.999,-10.39 -18.655,-14.22 -2.539,-1.463 -5.133,-2.791 -7.773,-3.99 2.476,4.025 4.608,8.199 6.402,12.483 6.8,16.244 8.698,34.064 5.678,51.194 -0.022,0.255 -0.235,1.283 -0.235,1.283 C -6.915,-10.163 -4.545,-4.491 0,0"
style="fill:#f0b400;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path75" />
</g>
<g
id="g77"
transform="translate(885.35312,616.21746)">
<path
d="m 0,0 c 2.64,1.199 5.234,2.527 7.773,3.99 6.656,3.83 12.935,8.569 18.655,14.22 12.786,12.633 21.056,28.111 24.792,44.425 0.2,0.873 0.296,1.518 0.473,2.409 1.024,5.123 3.642,9.917 7.64,13.867 10.654,10.527 27.456,11.077 38.355,-0.233 2.733,-2.837 7.101,-6.227 7.53,-15.069 0.007,-3.738 -0.219,-7.479 -0.674,-11.198 C 102.142,32.716 93.324,13.662 78.114,-1.365 58.267,-20.975 31.695,-29.616 5.922,-27.337 c -8.342,0.739 -16.599,2.622 -24.518,5.645 -8.512,3.249 -16.631,7.821 -24.044,13.707 C -28.138,-8.771 -13.481,-6.109 0,0"
style="fill:#4acfd2;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path79" />
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 KiB

View File

@@ -1,220 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="419.92645mm"
height="664.284mm"
viewBox="0 0 419.92645 664.28402"
version="1.1"
id="svg1"
xml:space="preserve"
sodipodi:docname="Logo 25 full transparente.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.28180672"
inkscape:cx="-35.485314"
inkscape:cy="942.13509"
inkscape:window-width="1920"
inkscape:window-height="1008"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2"
showgrid="false"
showguides="true"><inkscape:grid
id="grid792"
units="mm"
originx="127.13166"
originy="294.77868"
spacingx="1"
spacingy="1"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="5"
enabled="true"
visible="false" /></sodipodi:namedview><defs
id="defs1" /><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Capa 2"
transform="matrix(16.785907,0,0,16.785907,-1379.0426,-1310.3372)"
style="display:inline"><path
style="display:none;fill:#ffffff;stroke:#ffffff;stroke-width:33.2612"
d="M 969.02922,898.66558 968.74908,63.873566 h 49.67942 49.6795 v 12.749576 c 0,20.163418 13.2134,79.715458 25.2704,113.891858 41.2493,116.92445 126.0825,213.33066 235.6456,267.79281 33.7227,16.76301 37.3546,19.48775 35.5517,26.67136 -2.4596,9.79977 -383.39445,1214.40233 -390.32118,1234.28433 -4.25551,12.2146 -4.98422,-102.2238 -5.2253,-820.59792 z"
id="path4"
transform="matrix(0.01576223,0,0,0.01576223,82.154786,78.061745)"
inkscape:label="Relleno blanco derecha" /><path
style="display:none;fill:#ffffff;stroke:#ffffff;stroke-width:33.2612"
d="M 418.43781,1108.0289 221.50466,484.37454 255.77157,467.94466 C 342.51452,426.35428 414.22899,360.20279 462.70973,277.05901 493.97419,223.4409 518.51143,148.29568 523.79549,89.984045 l 2.36607,-26.110479 h 45.64145 45.64146 V 897.77845 c 0,458.64765 -0.46653,833.90485 -1.03674,833.90485 -0.57022,0 -89.65669,-280.6444 -197.96992,-623.6544 z"
id="path5"
transform="matrix(0.01576223,0,0,0.01576223,82.154786,78.061745)"
inkscape:label="Relleno blanco izquieda" /><path
style="display:inline;fill:#1d0695;fill-opacity:1;stroke:#1d0695;stroke-width:1.00005;stroke-dasharray:none;stroke-opacity:1"
id="path788-6"
d="m -99.481003,78.564575 a 7,7 0 0 1 -4.836877,6.657396 l -2.16312,-6.657396 z"
sodipodi:type="arc"
sodipodi:arc-type="slice"
sodipodi:start="0"
sodipodi:end="1.2566371"
sodipodi:ry="7"
sodipodi:rx="7"
sodipodi:cy="78.564575"
sodipodi:cx="-106.481"
inkscape:label="Esquina izquierda"
transform="scale(-1,1)" /><path
style="fill:#1d0695;fill-opacity:1;stroke:#1d0695;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
id="path788"
d="m 89.933891,78.65564 a 7,7 0 0 1 -4.856213,6.663646 L 82.933891,78.65564 Z"
sodipodi:type="arc"
sodipodi:arc-type="slice"
sodipodi:start="0"
sodipodi:end="1.2595395"
sodipodi:ry="7"
sodipodi:rx="7"
sodipodi:cy="78.65564"
sodipodi:cx="82.933891"
inkscape:label="Esquina izquierda" /><path
style="display:inline;fill:#ff0000;stroke:#ff0000;stroke-width:0.882544;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 92.343042,79.504907 4.598918,0.0049 0.0017,26.274323 -4.588022,-9.9e-4 z"
id="path792"
inkscape:label="Rectángulo"
sodipodi:nodetypes="ccccc" /><path
style="display:inline;opacity:1;fill:#000000;fill-opacity:0;stroke:#1d0695;stroke-width:1.00005;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 82.837015,78.563312 23.651695,-0.0015 -11.838335,37.414748 z"
id="path786"
sodipodi:nodetypes="cccc"
inkscape:label="Triángulo"
transform="translate(-1.2708333e-6)" /><path
style="fill:#1d0695;fill-opacity:1;stroke:#1d0695;stroke-width:1.03941;stroke-dasharray:none;stroke-opacity:1"
d="m 105.21073,301.76961 c -6.53995,-20.71706 -11.875109,-37.6831 -11.855911,-37.70229 0.0192,-0.0192 10.763101,-0.0223 23.875341,-0.007 l 23.84043,0.028 -11.92048,37.67543 c -6.55626,20.72148 -11.94929,37.67492 -11.98452,37.67432 -0.0352,-6.1e-4 -5.41492,-16.95144 -11.95486,-37.6685 z"
id="path54"
transform="matrix(0.10669253,0,0,0.10669253,82.149909,78.059739)"
inkscape:label="Relleno azul abajo" /></g><g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Texto"
transform="matrix(6.768873,0,0,6.768873,-47.398718,-0.20588049)"><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.2451px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.5938"
x="17.628099"
y="15.110926"
id="text16"
transform="scale(1.8040993,0.55429321)"><tspan
sodipodi:role="line"
id="tspan16"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.2451px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.5938"
x="17.628099"
y="15.110926">C</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:10.8582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.53895"
x="19.495819"
y="25.445749"
id="text17"
transform="scale(1.6638658,0.60101)"><tspan
sodipodi:role="line"
id="tspan17"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:10.8582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.53895"
x="19.495819"
y="25.445749">A</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:7.95775px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.12787"
x="27.980883"
y="25.586826"
id="text18"
transform="scale(1.1569098,0.86437164)"><tspan
sodipodi:role="line"
id="tspan18"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:7.95775px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.12787"
x="27.980883"
y="25.586826">25</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:7.99489px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.13313"
x="27.62228"
y="33.788048"
id="text19"
transform="scale(1.1646165,0.85865179)"><tspan
sodipodi:role="line"
id="tspan19"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:7.99489px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.13313"
x="27.62228"
y="33.788048">DE</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:10.8582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.53895"
x="19.49581"
y="59.22831"
id="text20"
transform="scale(1.6638665,0.60100975)"><tspan
sodipodi:role="line"
id="tspan20"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:10.8582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.53895"
x="19.49581"
y="59.22831">A</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.2553px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.59524"
x="17.650023"
y="76.522369"
id="text21"
transform="scale(1.8063001,0.55361786)"><tspan
sodipodi:role="line"
id="tspan21"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.2553px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.59524"
x="17.650023"
y="76.522369">G</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.0582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.5673"
x="18.147587"
y="86.019577"
id="text22"
transform="scale(1.7637218,0.56698285)"><tspan
sodipodi:role="line"
id="tspan22"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.0582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.5673"
x="18.147587"
y="86.019577">O</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.0296px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.56325"
x="18.269663"
y="97.356186"
id="text23"
transform="scale(1.7575891,0.56896121)"><tspan
sodipodi:role="line"
id="tspan23"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.0296px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.56325"
x="18.269663"
y="97.356186">S</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:10.6716px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.51252"
x="20.083975"
y="100.755"
id="text24"
transform="scale(1.6258383,0.61506731)"><tspan
sodipodi:role="line"
id="tspan24"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:10.6716px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.51252"
x="20.083975"
y="100.755">T</tspan></text><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.0582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke:#ffffff;stroke-width:1.5673"
x="18.147587"
y="119.65324"
id="text25"
transform="scale(1.7637218,0.56698285)"><tspan
sodipodi:role="line"
id="tspan25"
style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:normal;font-size:11.0582px;font-family:'IBM Plex Mono';-inkscape-font-specification:'IBM Plex Mono, Thin';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.5673"
x="18.147587"
y="119.65324">O</tspan></text></g></svg>

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 636 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 943 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 444 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,7 @@ git clone https://github.com/apache/superset
$ cd superset
# Set the repo to the state associated with the latest official version
$ git checkout tags/6.0.0
$ git checkout tags/5.0.0
# Fire up Superset using Docker Compose
$ docker compose -f docker-compose-image-tag.yml up

File diff suppressed because it is too large Load Diff

View File

@@ -144,7 +144,7 @@ solr = ["sqlalchemy-solr >= 0.2.0"]
elasticsearch = ["elasticsearch-dbapi>=0.2.9, <0.3.0"]
exasol = ["sqlalchemy-exasol >= 2.4.0, <3.0"]
excel = ["xlrd>=1.2.0, <1.3"]
fastmcp = ["fastmcp==2.14.3"]
fastmcp = ["fastmcp>=2.14.0"]
firebird = ["sqlalchemy-firebird>=0.7.0, <0.8"]
firebolt = ["firebolt-sqlalchemy>=1.0.0, <2"]
gevent = ["gevent>=23.9.1"]
@@ -160,9 +160,8 @@ hive = [
impala = ["impyla>0.16.2, <0.17"]
kusto = ["sqlalchemy-kusto>=3.0.0, <4"]
kylin = ["kylinpy>=2.8.1, <2.9"]
motherduck = ["duckdb==0.10.2", "duckdb-engine>=0.12.1, <0.13"]
mssql = ["pymssql>=2.2.8, <3"]
# motherduck is an alias for duckdb - MotherDuck works via the duckdb driver
motherduck = ["apache-superset[duckdb]"]
mysql = ["mysqlclient>=2.1.0, <3"]
ocient = [
"sqlalchemy-ocient>=1.0.0",
@@ -174,7 +173,7 @@ oracle = ["cx-Oracle>8.0.0, <8.1"]
parseable = ["sqlalchemy-parseable>=0.1.3,<0.2.0"]
pinot = ["pinotdb>=5.0.0, <6.0.0"]
playwright = ["playwright>=1.37.0, <2"]
postgres = ["psycopg2-binary==2.9.9"]
postgres = ["psycopg2-binary==2.9.6"]
presto = ["pyhive[presto]>=0.6.5"]
trino = ["trino>=0.328.0"]
prophet = ["prophet>=1.1.6, <2"]
@@ -204,7 +203,6 @@ ydb = ["ydb-sqlalchemy>=0.1.2"]
development = [
# no bounds for apache-superset-extensions-cli until a stable version
"apache-superset-extensions-cli",
"boto3",
"docker",
"flask-testing",
"freezegun",
@@ -438,7 +436,6 @@ authorized_licenses = [
"apache software",
"apache software, bsd",
"bsd",
"bsd-2-clause",
"bsd-3-clause",
"isc license (iscl)",
"isc license",

View File

@@ -16,14 +16,8 @@
# specific language governing permissions and limitations
# under the License.
#
# Security: CVE-2026-21441 - decompression bomb bypass on redirects
urllib3>=2.6.3,<3.0.0
# Security: GHSA-87hc-h4r5-73f7 - Windows path traversal fix
werkzeug>=3.1.5,<4.0.0
# Security: CVE-2025-68146 - TOCTOU symlink vulnerability
filelock>=3.20.3,<4.0.0
# Security: decompression bomb fix (required by aiohttp 3.13.3)
brotli>=1.2.0,<2.0.0
urllib3>=2.6.0,<3.0.0
werkzeug>=3.0.1
numexpr>=2.9.0
# 5.0.0 has a sensitive deprecation used in other libs

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