diff --git a/docs/.claude/instructions.md b/docs/.claude/instructions.md deleted file mode 100644 index 4e3b5ccd538..00000000000 --- a/docs/.claude/instructions.md +++ /dev/null @@ -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 Content; -}`, - }, -}; -``` - -### 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_docs/components/` -- **Stories**: `superset-frontend/packages/superset-ui-core/src/components/*/` diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index d5cf1228397..00000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,46 +0,0 @@ -# Dependencies -/node_modules - -# Production -/build - -# Generated files -.docusaurus -.cache-loader - -# Misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -docs/.zshrc - -# Gets copied from the root of the project at build time (yarn start / yarn build) -docs/intro.md - -# Generated badge images (downloaded at build time by remark-localize-badges plugin) -static/badges/ - -# Generated database documentation MDX files (regenerated at build time) -# 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 -developer_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_docs/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 - -# Generated component metadata JSON (regenerated by generate-superset-components.mjs) -static/data/components.json diff --git a/docs/.nvmrc b/docs/.nvmrc deleted file mode 100644 index 42a1c98ac5a..00000000000 --- a/docs/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v22.22.0 diff --git a/docs/.prettierrc b/docs/.prettierrc deleted file mode 100644 index 5393aaf2da6..00000000000 --- a/docs/.prettierrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "singleQuote": true, - "trailingComma": "all", - "arrowParens": "avoid" -} diff --git a/docs/DOCS_CLAUDE.md b/docs/DOCS_CLAUDE.md deleted file mode 100644 index 3e60b73b0d3..00000000000 --- a/docs/DOCS_CLAUDE.md +++ /dev/null @@ -1,649 +0,0 @@ - - -# LLM Context Guide for Apache Superset Documentation - -This guide helps LLMs work with the Apache Superset documentation site built with Docusaurus 3. - -## 📍 Current Directory Context - -You are currently in the `/docs` subdirectory of the Apache Superset repository. When referencing files from the main codebase, use `../` to access the parent directory. - -``` -/Users/evan_1/GitHub/superset/ # Main repository root -├── superset/ # Python backend code -├── superset-frontend/ # React/TypeScript frontend -└── docs/ # Documentation site (YOU ARE HERE) - ├── docs/ # Main documentation content - ├── admin_docs/ # Admin-focused guides - ├── developer_docs/ # Developer guides - ├── components/ # Component playground - └── docusaurus.config.ts # Site configuration -``` - -## 🚀 Quick Commands - -```bash -# Development -yarn start # Start dev server on http://localhost:3000 -yarn stop # Stop running dev server -yarn build # Build production site -yarn serve # Serve built site locally - -# Version Management (USE THESE, NOT docusaurus commands) -# The add scripts auto-run `generate:smart` so auto-gen content (database -# pages, API reference, component pages) is fresh before snapshotting. -# For maximum-detail databases.json, drop the `database-diagnostics` -# artifact from Python-Integration CI at src/data/databases.json before -# cutting. See README.md "Before You Cut". -yarn version:add:docs # Add new docs version -yarn version:add:admin_docs # Add admin docs version -yarn version:add:developer_docs # Add developer docs version -yarn version:add:components # Add components version -yarn version:remove:docs # Remove docs version -yarn version:remove:admin_docs # Remove admin docs version -yarn version:remove:developer_docs # Remove developer docs version -yarn version:remove:components # Remove components version - -# Quality Checks -yarn typecheck # TypeScript validation -yarn eslint # Lint TypeScript/JavaScript files -``` - -## 📁 Documentation Structure - -### Main Documentation (`/docs`) -The primary documentation lives in `/docs` with this structure: - -``` -docs/ -├── intro.md # Auto-generated from ../README.md -├── quickstart.mdx # Getting started guide -├── api.mdx # API reference with Swagger UI -├── faq.mdx # Frequently asked questions -├── installation/ # Installation guides -│ ├── installation-methods.mdx -│ ├── docker-compose.mdx -│ ├── docker-builds.mdx -│ ├── kubernetes.mdx -│ ├── pypi.mdx -│ └── architecture.mdx -├── configuration/ # Configuration guides -│ ├── configuring-superset.mdx -│ ├── alerts-reports.mdx -│ ├── caching.mdx -│ ├── databases.mdx -│ └── [more config docs] -├── using-superset/ # User guides -│ ├── creating-your-first-dashboard.md -│ ├── exploring-data.mdx -│ └── [more user docs] -├── contributing/ # Contributor guides -│ ├── development.mdx -│ ├── testing-locally.mdx -│ └── [more contributor docs] -└── security/ # Security documentation - ├── security.mdx - └── [security guides] -``` - -### Admin Docs (`/admin_docs`) -Admin-focused content: installation, configuration, security. - -### Developer Docs (`/developer_docs`) -Developer-focused content: API documentation, architecture guides, CLI tools, code examples. - -### Component Playground (`/components`) -Interactive component examples for UI development. - -## 📝 Documentation Standards - -### File Types -- **`.md` files**: Basic Markdown documents -- **`.mdx` files**: Markdown with JSX - can include React components -- **`.tsx` files in `/src`**: Custom React components and pages - -### Frontmatter Structure -Every documentation page should have frontmatter: - -```yaml ---- -title: Page Title -description: Brief description for SEO -sidebar_position: 1 # Optional: controls order in sidebar ---- -``` - -### MDX Component Usage -MDX files can import and use React components: - -```mdx -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - - - - ```bash - npm install superset - ``` - - - ```bash - yarn add superset - ``` - - -``` - -### Code Blocks -Use triple backticks with language identifiers: - -````markdown -```python -def hello_world(): - print("Hello, Superset!") -``` - -```sql title="Example Query" -SELECT * FROM users WHERE active = true; -``` - -```bash -# Installation command -pip install apache-superset -``` -```` - -### Admonitions -Docusaurus supports various admonition types: - -```markdown -:::note -This is a note -::: - -:::tip -This is a tip -::: - -:::warning -This is a warning -::: - -:::danger -This is a danger warning -::: - -:::info -This is an info box -::: -``` - -## 🔄 Version Management - -### Version Configuration -Versions are managed through `versions-config.json`: - -```json -{ - "docs": { - "disabled": false, - "lastVersion": "6.0.0", // Default version shown - "includeCurrentVersion": true, // Show "Next" version - "onlyIncludeVersions": ["current", "6.0.0"], - "versions": { - "current": { - "label": "Next", - "path": "", - "banner": "unreleased" // Shows warning banner - }, - "6.0.0": { - "label": "6.0.0", - "path": "6.0.0", - "banner": "none" - } - } - } -} -``` - -### Creating New Versions -**IMPORTANT**: Always use the custom scripts, NOT native Docusaurus commands: - -```bash -# ✅ CORRECT - Updates both Docusaurus and versions-config.json -yarn version:add:docs 6.1.0 - -# ❌ WRONG - Only updates Docusaurus, breaks version dropdown -yarn docusaurus docs:version 6.1.0 -``` - -### Version Files Created -When versioning, these files are created: -- `versioned_docs/version-X.X.X/` - Snapshot of current docs -- `versioned_sidebars/version-X.X.X-sidebars.json` - Sidebar config -- `versions.json` - List of all versions - -## 🎨 Styling and Theming - -### Custom CSS -Add custom styles in `/src/css/custom.css`: - -```css -:root { - --ifm-color-primary: #20a7c9; - --ifm-code-font-size: 95%; -} -``` - -### Custom Components -Create React components in `/src/components/`: - -```tsx -// src/components/FeatureCard.tsx -import React from 'react'; - -export default function FeatureCard({title, description}) { - return ( -
-

{title}

-

{description}

-
- ); -} -``` - -Use in MDX: - -```mdx -import FeatureCard from '@site/src/components/FeatureCard'; - - -``` - -## 📦 Key Dependencies - -- **Docusaurus 3.8.1**: Static site generator -- **React 18.3**: UI framework -- **Ant Design 5.26**: Component library -- **@superset-ui/core**: Superset UI components -- **Swagger UI React**: API documentation -- **Prism**: Syntax highlighting - -## 🔗 Linking Strategies - -### Internal Links -Use relative paths for internal documentation: - -```markdown -[Installation Guide](./installation/docker-compose) -[Configuration](../configuration/configuring-superset) -``` - -### External Links -Always use full URLs: - -```markdown -[Apache Superset GitHub](https://github.com/apache/superset) -``` - -### Linking to Code -Reference code in the main repository: - -```markdown -See the [main configuration file](https://github.com/apache/superset/blob/master/superset/config.py) -``` - -## 🛠️ Common Documentation Tasks - -### Adding a New Guide -1. Create the `.mdx` file in the appropriate directory -2. Add frontmatter with title and description -3. Update sidebar if needed (for manual sidebar configs) - -### Adding API Documentation -The API docs use Swagger UI embedded in `/docs/api.mdx`: - -```mdx -import SwaggerUI from "swagger-ui-react"; -import "swagger-ui-react/swagger-ui.css"; - - -``` - -### Adding Interactive Examples -Use MDX to create interactive documentation: - -```mdx -import CodeBlock from '@theme/CodeBlock'; -import MyComponentExample from '!!raw-loader!../examples/MyComponent.tsx'; - -{MyComponentExample} -``` - -## 📋 Documentation Checklist - -When creating or updating documentation: - -- [ ] Clear, descriptive title in frontmatter -- [ ] Description for SEO in frontmatter -- [ ] Proper heading hierarchy (h1 -> h2 -> h3) -- [ ] Code examples with language identifiers -- [ ] Links verified (internal and external) -- [ ] Images have alt text -- [ ] Admonitions used for important notes -- [ ] Tested locally with `yarn start` -- [ ] No broken links (check with `yarn build`) - -## 🔍 Searching and Navigation - -### Sidebar Configuration -Sidebars are configured in `/sidebars.js`: - -```javascript -module.exports = { - CustomSidebar: [ - { - type: 'doc', - label: 'Introduction', - id: 'intro', - }, - { - type: 'category', - label: 'Installation', - items: [ - { - type: 'autogenerated', - dirName: 'installation', - }, - ], - }, - ], -}; -``` - -### Search -Docusaurus includes Algolia DocSearch integration configured in `docusaurus.config.ts`. - -## 🚫 Common Pitfalls to Avoid - -1. **Never use `yarn docusaurus docs:version`** - Use `yarn version:add:docs` instead -2. **Don't edit versioned docs directly** - Edit current docs and create new version -3. **Avoid absolute paths in links** - Use relative paths for maintainability -4. **Don't forget frontmatter** - Every doc needs title and description -5. **Test builds locally** - Run `yarn build` before committing - -## 🔧 Troubleshooting - -### Dev Server Issues -```bash -yarn stop # Kill any running servers -yarn clear # Clear cache -yarn start # Restart -``` - -### Build Failures -```bash -# Check for broken links -yarn build - -# TypeScript issues -yarn typecheck - -# Linting issues -yarn eslint -``` - -### Version Issues -If versions don't appear in dropdown: -1. Check `versions-config.json` includes the version -2. Verify version files exist in `versioned_docs/` -3. Restart dev server - -## 📚 Resources - -- [Docusaurus Documentation](https://docusaurus.io/docs) -- [MDX Documentation](https://mdxjs.com/) -- [Superset Developer Docs](https://superset.apache.org/developer-docs/) -- [Main Superset Documentation](https://superset.apache.org/docs/intro) - -## 📖 Real Examples and Patterns - -### Example: Configuration Documentation Pattern -From `docs/configuration/configuring-superset.mdx`: - -```mdx ---- -title: Configuring Superset -hide_title: true -sidebar_position: 1 -version: 1 ---- - -# Configuring Superset - -## superset_config.py - -Superset exposes hundreds of configurable parameters through its -[config.py module](https://github.com/apache/superset/blob/master/superset/config.py). - -```bash -export SUPERSET_CONFIG_PATH=/app/superset_config.py -``` -``` - -**Key patterns:** -- Links to source code for reference -- Code blocks with bash/python examples -- Environment variable documentation -- Step-by-step configuration instructions - -### Example: Tutorial Documentation Pattern -From `docs/using-superset/creating-your-first-dashboard.mdx`: - -```mdx -import useBaseUrl from "@docusaurus/useBaseUrl"; - -## Creating Your First Dashboard - -:::tip -In addition to this site, [Preset.io](http://preset.io/) maintains an updated set of end-user -documentation at [docs.preset.io](https://docs.preset.io/). -::: - -### Connecting to a new database - - -``` - -**Key patterns:** -- Import Docusaurus hooks for dynamic URLs -- Use of admonitions (:::tip) for helpful information -- Screenshots with useBaseUrl for proper path resolution -- Clear section hierarchy with ### subheadings -- Step-by-step visual guides - -### Example: API Documentation Pattern -From `docs/api.mdx`: - -```mdx -import SwaggerUI from "swagger-ui-react"; -import "swagger-ui-react/swagger-ui.css"; - -## API Documentation - - -``` - -**Key patterns:** -- Embedding interactive Swagger UI -- Importing necessary CSS -- Direct API spec integration - -### Common Image Patterns - -```mdx -// For images in static folder -import useBaseUrl from "@docusaurus/useBaseUrl"; - - - -// With caption -
- Dashboard view -
Superset Dashboard Interface
-
-``` - -### Multi-Tab Code Examples - -```mdx -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - - - - ```bash - docker-compose up - ``` - - - ```bash - kubectl apply -f superset.yaml - ``` - - - ```bash - pip install apache-superset - ``` - - -``` - -### Configuration File Examples - -```mdx -```python title="superset_config.py" -# Database connection example -SQLALCHEMY_DATABASE_URI = 'postgresql://user:password@localhost/superset' - -# Security configuration -SECRET_KEY = 'YOUR_SECRET_KEY_HERE' -WTF_CSRF_ENABLED = True - -# Feature flags -FEATURE_FLAGS = { - 'ENABLE_TEMPLATE_PROCESSING': True, - 'DASHBOARD_NATIVE_FILTERS': True, -} -``` -``` - -### Cross-Referencing Pattern - -```mdx -For detailed configuration options, see: -- [Configuring Superset](./configuration/configuring-superset) -- [Database Connections](./configuration/databases) -- [Security Settings](./security/security) - -External resources: -- [SQLAlchemy Documentation](https://docs.sqlalchemy.org/) -- [Flask Configuration](https://flask.palletsprojects.com/config/) -``` - -### Writing Installation Guides - -```mdx -## Prerequisites - -:::warning -Ensure you have Python 3.9+ and Node.js 16+ installed before proceeding. -::: - -## Installation Steps - -1. **Clone the repository** - ```bash - git clone https://github.com/apache/superset.git - cd superset - ``` - -2. **Install Python dependencies** - ```bash - pip install -e . - ``` - -3. **Initialize the database** - ```bash - superset db upgrade - superset init - ``` - -:::tip Success Check -Navigate to http://localhost:8088 and login with admin/admin -::: -``` - -### Documenting API Endpoints - -```mdx -## Chart API - -### GET /api/v1/chart/ - -Returns a list of charts. - -**Parameters:** -- `page` (optional): Page number -- `page_size` (optional): Number of items per page - -**Example Request:** -```bash -curl -X GET "http://localhost:8088/api/v1/chart/" \ - -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -``` - -**Example Response:** -```json -{ - "count": 42, - "result": [ - { - "id": 1, - "slice_name": "Sales Dashboard", - "viz_type": "line" - } - ] -} -``` -``` - ---- - -**Note**: This documentation site serves as the primary resource for Superset users, administrators, and contributors. Always prioritize clarity, accuracy, and completeness when creating or updating documentation. diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index f5b8e719445..00000000000 --- a/docs/README.md +++ /dev/null @@ -1,232 +0,0 @@ - - -This is the public documentation site for Superset, built using -[Docusaurus 3](https://docusaurus.io/). See the -[Developer Docs](https://superset.apache.org/developer-docs/contributing/development-setup#documentation) -for documentation on contributing to documentation. - -## Version Management - -The Superset documentation site uses Docusaurus versioning with four independent sections: - -- **User Documentation** (`/user-docs/`) - End-user guides and tutorials -- **Admin Documentation** (`/admin-docs/`) - Installation, configuration, and security -- **Developer Docs** (`/developer-docs/`) - Developer guides, contributing, and extensions -- **Component Playground** (`/components/`) - Interactive component examples (currently disabled) - -Each section maintains its own version history and can be versioned independently. - -### Creating a New Version - -To create a new version for any section, use the Docusaurus version command with the appropriate plugin ID or use our automated scripts: - -#### Before You Cut - -The cut snapshots whatever's on disk into a frozen historical version, including auto-generated content (database pages from `superset/db_engine_specs/`, API reference from `static/resources/openapi.json`, component pages from Storybook stories). The cut script refreshes these via `generate:smart` before snapshotting, but the **`databases.json` diagnostics file** needs special care to capture full detail: - -1. **Canonical release cut**: download the `database-diagnostics` artifact from a green `Python-Integration` run on master, place it at `docs/src/data/databases.json`, then run the cut script with `--skip-generate` to preserve it. This is what the production deploy uses and includes full Flask-context diagnostics (driver versions, feature support matrix, etc.). -2. **Local dev cut**: just run the script normally. `generate:smart` will regenerate `databases.json` using your local Flask environment — accurate to whatever drivers/extras you have installed, but typically less complete than the CI artifact. -3. **No Flask available**: also fine — the database generator falls back to AST parsing of engine spec files. The MDX pages are still correct; only the diagnostics JSON is leaner. - -Also: confirm `master` CI is green, and that your local checkout matches the SHA you intend to cut from. - -#### Using Automated Scripts (Required) - -**⚠️ Important:** Always use these custom commands instead of the native Docusaurus commands. These scripts ensure that both the Docusaurus versioning system AND the `versions-config.json` file are updated correctly, AND that auto-generated content is refreshed before snapshotting. - -```bash -# Main Documentation -yarn version:add:docs 1.2.0 - -# Admin Docs -yarn version:add:admin_docs 1.2.0 - -# Developer Docs -yarn version:add:developer_docs 1.2.0 - -# Component Playground -yarn version:add:components 1.2.0 -``` - -What the script does: -1. Refreshes auto-generated content via `generate:smart` (database pages, API reference, component pages). -2. Calls `yarn docusaurus docs:version` (or the per-section equivalent) to snapshot the section. -3. Freezes any data-file imports (`@site/static/*.json`, `../../data/*.json`) into a snapshot-local `_versioned_data/` dir so the historical version doesn't silently mutate when the source files change. -4. Adjusts relative import paths (`../../src/...` → `../../../src/...`) for files now one directory deeper. -5. Updates `versions-config.json` and `
_versions.json`. - -**Do NOT use** the native Docusaurus commands directly (`yarn docusaurus docs:version`), as they will: -- ❌ Create version files but NOT update `versions-config.json` -- ❌ Skip auto-gen refresh, freezing whatever was on disk -- ❌ Skip data-import freezing, leaving the snapshot pointed at live data -- ❌ Cause versions to not appear in dropdown menus -- ❌ Require manual fixes to synchronize the configuration - -### Managing Versions - -#### With Automated Scripts -The automated scripts handle all configuration updates automatically. No manual editing required! - -#### Manual Configuration -If creating versions manually, you'll need to: - -1. **Update `versions-config.json`** (or `docusaurus.config.ts` if not using dynamic config): - - Add version to `onlyIncludeVersions` array - - Add version metadata to `versions` object - - Update `lastVersion` if needed - -2. **Files Created by Versioning**: - When a new version is created, Docusaurus generates: - - **Versioned docs folder**: `[section]_versioned_docs/version-X.X.X/` - - **Versioned sidebars**: `[section]_versioned_sidebars/version-X.X.X-sidebars.json` - - **Versions list**: `[section]_versions.json` - - Note: For main docs, the prefix is omitted (e.g., `versioned_docs/` instead of `docs_versioned_docs/`) - -3. **Important**: After adding a version, restart the development server to see changes: - ```bash - yarn stop - yarn start - ``` - -### Removing a Version - -#### Using Automated Scripts (Recommended) -```bash -# Main Documentation -yarn version:remove:docs 1.0.0 - -# Admin Docs -yarn version:remove:admin_docs 1.0.0 - -# Developer Docs -yarn version:remove:developer_docs 1.0.0 - -# Component Playground -yarn version:remove:components 1.0.0 -``` - -#### Manual Removal -To manually remove a version: - -1. **Delete the version folder** from the appropriate location: - - Main docs: `versioned_docs/version-X.X.X/` (no prefix for main) - - Admin Docs: `admin_docs_versioned_docs/version-X.X.X/` - - Developer Docs: `developer_docs_versioned_docs/version-X.X.X/` - - Components: `components_versioned_docs/version-X.X.X/` - -2. **Delete the version metadata file**: - - Main docs: `versioned_sidebars/version-X.X.X-sidebars.json` (no prefix) - - Admin Docs: `admin_docs_versioned_sidebars/version-X.X.X-sidebars.json` - - Developer Docs: `developer_docs_versioned_sidebars/version-X.X.X-sidebars.json` - - Components: `components_versioned_sidebars/version-X.X.X-sidebars.json` - -3. **Update the versions list file**: - - Main docs: `versions.json` - - Admin Docs: `admin_docs_versions.json` - - Developer Docs: `developer_docs_versions.json` - - Components: `components_versions.json` - -4. **Update configuration**: - - If using dynamic config: Update `versions-config.json` - - If using static config: Update `docusaurus.config.ts` - -5. **Restart the server** to see changes - -### Version Configuration Examples - -#### Main Documentation (default plugin) -```typescript -docs: { - includeCurrentVersion: true, - lastVersion: 'current', // Makes /docs/ show Next version - onlyIncludeVersions: ['current', '1.1.0', '1.0.0'], - versions: { - current: { - label: 'Next', - path: '', // Empty path for default routing - banner: 'unreleased', - }, - '1.1.0': { - label: '1.1.0', - path: '1.1.0', - banner: 'none', - }, - }, -} -``` - -#### Developer Docs & Components (custom plugins) -```typescript -{ - id: 'developer_docs', - path: 'developer_docs', - routeBasePath: 'developer-docs', - includeCurrentVersion: true, - lastVersion: '1.1.0', // Default version - onlyIncludeVersions: ['current', '1.1.0', '1.0.0'], - versions: { - current: { - label: 'Next', - path: 'next', - banner: 'unreleased', - }, - '1.1.0': { - label: '1.1.0', - path: '1.1.0', - banner: 'none', - }, - }, -} -``` - -### Best Practices - -1. **Version naming**: Use semantic versioning (e.g., 1.0.0, 1.1.0, 2.0.0) -2. **Version banners**: Use `'unreleased'` for development versions, `'none'` for stable releases -3. **Limit displayed versions**: Use `onlyIncludeVersions` to show only relevant versions -4. **Test locally**: Always test version changes locally before deploying -5. **Independent versioning**: Each section can have different version numbers and release cycles - -### Troubleshooting - -#### Version Not Showing After Creation - -If you accidentally used `yarn docusaurus docs:version` instead of `yarn version:add`: -1. **Problem**: The version files were created but `versions-config.json` wasn't updated -2. **Solution**: Either: - - Revert the changes: `git restore versions.json && rm -rf versioned_docs/ versioned_sidebars/` - - Then use the correct command: `yarn version:add:docs ` - -For other issues: -- **Restart the server**: Changes to version configuration require a server restart -- **Check config file**: Ensure `versions-config.json` includes the new version -- **Verify files exist**: Check that versioned docs folder was created - -#### Broken Links in Versioned Documentation -When creating a new version, links in the documentation are preserved as-is. Common issues: -- **Cross-section links**: Links between sections (e.g., from developer_docs to docs) need to be version-aware -- **Absolute vs relative paths**: Use relative paths within the same section -- **Version-specific URLs**: Update hardcoded URLs to use version variables - -To fix broken links: -1. Use `type: 'doc'` with `docId` for version-aware navigation in navbar -2. Use relative paths within the same documentation section -3. Test all versions after creation to identify broken links diff --git a/docs/admin_docs/configuration/alerts-reports.mdx b/docs/admin_docs/configuration/alerts-reports.mdx deleted file mode 100644 index f5c4efc42ed..00000000000 --- a/docs/admin_docs/configuration/alerts-reports.mdx +++ /dev/null @@ -1,500 +0,0 @@ ---- -title: Alerts and Reports -hide_title: true -sidebar_position: 2 -version: 2 ---- - -# Alerts and Reports - -Users can configure automated alerts and reports to send dashboards or charts to an email recipient or Slack channel. - -- *Alerts* are sent when a SQL condition is reached -- *Reports* are sent on a schedule - -Alerts and reports are disabled by default. To turn them on, you'll need to change configuration settings and install a suitable headless browser in your environment. - -## Requirements - -### Commons - -#### In your `superset_config.py` or `superset_config_docker.py` - -- `"ALERT_REPORTS"` [feature flag](/admin-docs/configuration/configuring-superset#feature-flags) must be turned to True. -- `beat_schedule` in CeleryConfig must contain schedule for `reports.scheduler`. -- At least one of those must be configured, depending on what you want to use: - - emails: `SMTP_*` settings - - Slack messages: `SLACK_API_TOKEN` -- Users can customize the email subject by including date code placeholders, which will automatically be replaced with the corresponding UTC date when the email is sent. To enable this functionality, activate the `"DATE_FORMAT_IN_EMAIL_SUBJECT"` [feature flag](/admin-docs/configuration/configuring-superset#feature-flags). This enables date formatting in email subjects, preventing all reporting emails from being grouped into the same thread (optional for the reporting feature). - - Use date codes from [strftime.org](https://strftime.org/) to create the email subject. - - If no date code is provided, the original string will be used as the email subject. - -##### Disable dry-run mode - -Screenshots will be taken but no messages actually sent as long as `ALERT_REPORTS_NOTIFICATION_DRY_RUN = True`, its default value in `docker/pythonpath_dev/superset_config.py`. To disable dry-run mode and start receiving email/Slack notifications, set `ALERT_REPORTS_NOTIFICATION_DRY_RUN` to `False` in [superset config](https://github.com/apache/superset/blob/master/docker/pythonpath_dev/superset_config.py). - -#### In your `Dockerfile` - -You'll need to extend the Superset image to include a headless browser. Your options include: -- Use Playwright with Chrome: this is the recommended approach as of version 4.1.x or greater. A working example of a Dockerfile that installs these tools is provided under "Building your own production Docker image" on the [Docker Builds](/admin-docs/installation/docker-builds#building-your-own-production-docker-image) page. Read the code comments there as you'll also need to change a feature flag in your config. -- Use Firefox: you'll need to install geckodriver and Firefox. -- Use Chrome without Playwright: you'll need to install Chrome and set the value of `WEBDRIVER_TYPE` to `"chrome"` in your `superset_config.py`. - -In Superset versions <=4.0x, users installed Firefox or Chrome and that was documented here. - -Only the worker container needs the browser. - -### Slack integration - -To send alerts and reports to Slack channels, you need to create a new Slack Application on your workspace. - -1. Connect to your Slack workspace, then head to [https://api.slack.com/apps]. -2. Create a new app. -3. Go to "OAuth & Permissions" section, and give the following scopes to your app: - - `incoming-webhook` - - `files:write` - - `chat:write` - - `channels:read` - - `groups:read` -4. At the top of the "OAuth and Permissions" section, click "install to workspace". -5. Select a default channel for your app and continue. - (You can post to any channel by inviting your Superset app into that channel). -6. The app should now be installed in your workspace, and a "Bot User OAuth Access Token" should have been created. Copy that token in the `SLACK_API_TOKEN` variable of your `superset_config.py`. -7. Ensure the feature flag `ALERT_REPORT_SLACK_V2` is set to True in `superset_config.py` -8. Restart the service (or run `superset init`) to pull in the new configuration. - -Note: when you configure an alert or a report, the Slack channel list takes channel names without the leading '#' e.g. use `alerts` instead of `#alerts`. - -#### Large Slack Workspaces (10k+ channels) - -For workspaces with many channels, fetching the complete channel list can take several minutes and may encounter Slack API rate limits. Add the following to your `superset_config.py`: - -```python -from datetime import timedelta - -# Increase cache timeout to reduce API calls -# Default: 1 day (86400 seconds) -SLACK_CACHE_TIMEOUT = int(timedelta(days=2).total_seconds()) - -# Increase retry count for rate limit errors -# Default: 2 -SLACK_API_RATE_LIMIT_RETRY_COUNT = 5 -``` - -### Webhook integration - -Superset can send alert and report notifications to any HTTP endpoint — useful for chat platforms, incident management tools, or custom automation. - -#### Enabling Webhooks - -Enable the feature flag in `superset_config.py`: - -```python -FEATURE_FLAGS = { - "ALERT_REPORTS": True, - "ALERT_REPORT_WEBHOOK": True, -} -``` - -#### Configuring a Webhook Recipient - -When creating or editing an alert or report, select **Webhook** as the notification method and enter your endpoint URL. - -#### Payload Format - -Superset sends an HTTP POST with `Content-Type: application/json`: - -```json -{ - "name": "My Alert", - "header": { - "notification_format": "JSON", - "notification_type": "Alert", - "notification_source": "Alert", - "chart_id": 42, - "dashboard_id": null - }, - "text": "Alert condition met: value exceeded threshold", - "description": "Monthly revenue dropped below target", - "url": "https://your-superset-host/superset/dashboard/1/" -} -``` - -When a report includes file attachments (CSV, PDF, or PNG screenshots), the request is sent as `multipart/form-data` instead. In that case, each top-level payload field (`name`, `text`, `description`, `url`) becomes its own form field, and nested structures like `header` are serialized as a JSON-encoded string in their own field. Every attachment is added as a repeated form field named `files`: - -``` -POST /webhook HTTP/1.1 -Content-Type: multipart/form-data; boundary=... - ---... -Content-Disposition: form-data; name="name" - -My Alert ---... -Content-Disposition: form-data; name="header" - -{"notification_format": "JSON", "notification_type": "Alert", ...} ---... -Content-Disposition: form-data; name="text" - -Alert condition met: value exceeded threshold ---... -Content-Disposition: form-data; name="files"; filename="report.csv" -Content-Type: text/csv - - ---... -``` - -Webhook consumers should branch on `Content-Type`: parse the body as JSON when `application/json`, or read the individual form fields (decoding `header` as JSON) when `multipart/form-data`. - -#### HTTPS Enforcement - -To require HTTPS webhook URLs (recommended for production), set: - -```python -ALERT_REPORTS_WEBHOOK_HTTPS_ONLY = True -``` - -When enabled, Superset rejects webhook configurations that use `http://` URLs. - -#### Retry Behavior - -Superset automatically retries webhook deliveries on `429 Too Many Requests` and `5xx` server errors using exponential backoff. - -### Kubernetes-specific - -- You must have a `celery beat` pod running. If you're using the chart included in the GitHub repository under [helm/superset](https://github.com/apache/superset/tree/master/helm/superset), you need to put `supersetCeleryBeat.enabled = true` in your values override. -- You can see the dedicated docs about [Kubernetes installation](/admin-docs/installation/kubernetes) for more details. - -### Docker Compose specific - -#### You must have in your `docker-compose.yml` - -- A Redis message broker -- PostgreSQL DB instead of SQLlite -- One or more `celery worker` -- A single `celery beat` - -This process also works in a Docker swarm environment, you would just need to add `Deploy:` to the Superset, Redis and Postgres services along with your specific configs for your swarm. - -### Detailed config - -The following configurations need to be added to the `superset_config.py` file. This file is loaded when the image runs, and any configurations in it will override the default configurations found in the `config.py`. - -You can find documentation about each field in the default `config.py` in the GitHub repository under [superset/config.py](https://github.com/apache/superset/blob/master/superset/config.py). - -You need to replace default values with your custom Redis, Slack and/or SMTP config. - -Superset uses Celery beat and Celery worker(s) to send alerts and reports. - -- The beat is the scheduler that tells the worker when to perform its tasks. This schedule is defined when you create the alert or report. -- The worker will process the tasks that need to be performed when an alert or report is fired. - -In the `CeleryConfig`, only the `beat_schedule` is relevant to this feature, the rest of the `CeleryConfig` can be changed for your needs. - -```python -from celery.schedules import crontab - -FEATURE_FLAGS = { - "ALERT_REPORTS": True -} - -REDIS_HOST = "superset_cache" -REDIS_PORT = "6379" - -class CeleryConfig: - broker_url = f"redis://{REDIS_HOST}:{REDIS_PORT}/0" - imports = ( - "superset.sql_lab", - "superset.tasks.scheduler", - ) - result_backend = f"redis://{REDIS_HOST}:{REDIS_PORT}/0" - worker_prefetch_multiplier = 10 - task_acks_late = True - task_annotations = { - "sql_lab.get_sql_results": { - "rate_limit": "100/s", - }, - } - beat_schedule = { - "reports.scheduler": { - "task": "reports.scheduler", - "schedule": crontab(minute="*", hour="*"), - }, - "reports.prune_log": { - "task": "reports.prune_log", - "schedule": crontab(minute=0, hour=0), - }, - } -CELERY_CONFIG = CeleryConfig - -SCREENSHOT_LOCATE_WAIT = 100 -SCREENSHOT_LOAD_WAIT = 600 - -# Slack configuration -SLACK_API_TOKEN = "xoxb-" - -# Email configuration -SMTP_HOST = "smtp.sendgrid.net" # change to your host -SMTP_PORT = 2525 # your port, e.g. 587 -SMTP_STARTTLS = True -SMTP_SSL_SERVER_AUTH = True # If you're using an SMTP server with a valid certificate -SMTP_SSL = False -SMTP_USER = "your_user" # use the empty string "" if using an unauthenticated SMTP server -SMTP_PASSWORD = "your_password" # use the empty string "" if using an unauthenticated SMTP server -SMTP_MAIL_FROM = "noreply@youremail.com" -EMAIL_REPORTS_SUBJECT_PREFIX = "[Superset] " # optional - overwrites default value in config.py of "[Report] " - -# WebDriver configuration -# If you use Firefox or Playwright with Chrome, you can stick with default values -# If you use Chrome and are *not* using Playwright, then add the following WEBDRIVER_TYPE and WEBDRIVER_OPTION_ARGS -WEBDRIVER_TYPE = "chrome" -WEBDRIVER_OPTION_ARGS = [ - "--force-device-scale-factor=2.0", - "--high-dpi-support=2.0", - "--headless", - "--disable-gpu", - "--disable-dev-shm-usage", - "--no-sandbox", - "--disable-setuid-sandbox", - "--disable-extensions", -] - -# This is for internal use, you can keep http -WEBDRIVER_BASEURL = "http://superset:8088" # When running using docker compose use "http://superset_app:8088' -# This is the link sent to the recipient. Change to your domain, e.g. https://superset.mydomain.com -WEBDRIVER_BASEURL_USER_FRIENDLY = "http://localhost:8088" -``` - -You also need -to specify on behalf of which username to render the dashboards. In general, dashboards and charts -are not accessible to unauthorized requests, that is why the worker needs to take over credentials -of an existing user to take a snapshot. - -By default, Alerts and Reports are executed as the owner of the alert/report object. To use a fixed user account, -just change the config as follows (`admin` in this example): - -```python -from superset.tasks.types import FixedExecutor - -ALERT_REPORTS_EXECUTORS = [FixedExecutor("admin")] -``` - -Please refer to `ExecutorType` in the codebase for other executor types. - -**Important notes** - -- Be mindful of the concurrency setting for celery (using `-c 4`). Selenium/webdriver instances can - consume a lot of CPU / memory on your servers. -- In some cases, if you notice a lot of leaked geckodriver processes, try running your celery - processes with `celery worker --pool=prefork --max-tasks-per-child=128 ...` -- It is recommended to run separate workers for the `sql_lab` and `email_reports` tasks. This can be - done using the `queue` field in `task_annotations`. -- Adjust `WEBDRIVER_BASEURL` in your configuration file if celery workers can’t access Superset via - its default value of `http://0.0.0.0:8080/`. - -It's also possible to specify a minimum interval between each report's execution through the config file: - -``` python -# Set a minimum interval threshold between executions (for each Alert/Report) -# Value should be an integer -ALERT_MINIMUM_INTERVAL = int(timedelta(minutes=10).total_seconds()) -REPORT_MINIMUM_INTERVAL = int(timedelta(minutes=5).total_seconds()) -``` - -Alternatively, you can assign a function to `ALERT_MINIMUM_INTERVAL` and/or `REPORT_MINIMUM_INTERVAL`. This is useful to dynamically retrieve a value as needed: - -``` python -def alert_dynamic_minimal_interval(**kwargs) -> int: - """ - Define logic here to retrieve the value dynamically - """ - -ALERT_MINIMUM_INTERVAL = alert_dynamic_minimal_interval -``` - -## External Link Redirection - -For security, Superset rewrites external links in alert/report email HTML so -they go through a warning page before the user is navigated to the external -site. Internal links (matching your configured base URL) are not affected. - -```python -# Disable external link redirection entirely (default: True) -ALERT_REPORTS_ENABLE_LINK_REDIRECT = False -``` - -The feature uses `WEBDRIVER_BASEURL_USER_FRIENDLY` (or `WEBDRIVER_BASEURL`) -to determine which hosts are internal. - -## Troubleshooting - -There are many reasons that reports might not be working. Try these steps to check for specific issues. - -### Confirm feature flag is enabled and you have sufficient permissions - -If you don't see "Alerts & Reports" under the *Manage* section of the Settings dropdown in the Superset UI, you need to enable the `ALERT_REPORTS` feature flag (see above). Enable another feature flag and check to see that it took effect, to verify that your config file is getting loaded. - -Log in as an admin user to ensure you have adequate permissions. - -### Check the logs of your Celery worker - -This is the best source of information about the problem. In a docker compose deployment, you can do this with a command like `docker logs superset_worker --since 1h`. - -### Check web browser and webdriver installation - -To take a screenshot, the worker visits the dashboard or chart using a headless browser, then takes a screenshot. If you are able to send a chart as CSV or text but can't send as PNG, your problem may lie with the browser. - -If you are handling the installation of the headless browser on your own, do your own verification to ensure that the headless browser opens successfully in the worker environment. - -### Send a test email - -One symptom of an invalid connection to an email server is receiving an error of `[Errno 110] Connection timed out` in your logs when the report tries to send. - -Confirm via testing that your outbound email configuration is correct. Here is the simplest test, for an un-authenticated email SMTP email service running on port 25. If you are sending over SSL, for instance, study how [Superset's codebase sends emails](https://github.com/apache/superset/blob/master/superset/utils/core.py#L818) and then test with those commands and arguments. - -Start Python in your worker environment, replace all example values, and run: - -```python -import smtplib -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText - -from_email = 'superset_emails@example.com' -to_email = 'your_email@example.com' -msg = MIMEMultipart() -msg['From'] = from_email -msg['To'] = to_email -msg['Subject'] = 'Superset SMTP config test' -message = 'It worked' -msg.attach(MIMEText(message)) -mailserver = smtplib.SMTP('smtpmail.example.com', 25) -mailserver.sendmail(from_email, to_email, msg.as_string()) -mailserver.quit() -``` - -This should send an email. - -Possible fixes: - -- Some cloud hosts disable outgoing unauthenticated SMTP email to prevent spam. For instance, [Azure blocks port 25 by default on some machines](https://learn.microsoft.com/en-us/azure/virtual-network/troubleshoot-outbound-smtp-connectivity). Enable that port or use another sending method. -- Use another set of SMTP credentials that you verify works in this setup. - -### Browse to your report from the worker - -The worker may be unable to reach the report. It will use the value of `WEBDRIVER_BASEURL` to browse to the report. If that route is invalid, or presents an authentication challenge that the worker can't pass, the report screenshot will fail. - -Check this by attempting to `curl` the URL of a report that you see in the error logs of your worker. For instance, from the worker environment, run `curl http://superset_app:8088/superset/dashboard/1/`. You may get different responses depending on whether the dashboard exists - for example, you may need to change the `1` in that URL. If there's a URL in your logs from a failed report screenshot, that's a good place to start. The goal is to determine a valid value for `WEBDRIVER_BASEURL` and determine if an issue like HTTPS or authentication is redirecting your worker. - -In a deployment with authentication measures enabled like HTTPS and Single Sign-On, it may make sense to have the worker navigate directly to the Superset application running in the same location, avoiding the need to sign in. For instance, you could use `WEBDRIVER_BASEURL="http://superset_app:8088"` for a docker compose deployment, and set `"force_https": False,` in your `TALISMAN_CONFIG`. - -### Duplicate report deliveries - -In some deployment configurations a scheduled report can be delivered more than once around its planned time. This typically happens when more than one process is responsible for running the alerts & reports schedule (for example, multiple schedulers or Celery beat instances). To avoid duplicate emails or notifications: - -- Ensure that only a **single scheduler/beat process** is configured to trigger alerts and reports for a given environment. -- If you run **multiple Celery workers**, verify that there is still only one component responsible for scheduling the report tasks (workers should execute tasks, not schedule them independently). -- Review your deployment/orchestration setup (for example systemd, Docker, or Kubernetes) to make sure the alerts & reports scheduler is **not started from multiple places by accident**. - -## Scheduling Queries as Reports - -You can optionally allow your users to schedule queries directly in SQL Lab. This is done by adding -extra metadata to saved queries, which are then picked up by an external scheduled (like -[Apache Airflow](https://airflow.apache.org/)). - -To allow scheduled queries, add the following to `SCHEDULED_QUERIES` in your configuration file: - -```python -SCHEDULED_QUERIES = { - # This information is collected when the user clicks "Schedule query", - # and saved into the `extra` field of saved queries. - # See: https://github.com/mozilla-services/react-jsonschema-form - 'JSONSCHEMA': { - 'title': 'Schedule', - 'description': ( - 'In order to schedule a query, you need to specify when it ' - 'should start running, when it should stop running, and how ' - 'often it should run. You can also optionally specify ' - 'dependencies that should be met before the query is ' - 'executed. Please read the documentation for best practices ' - 'and more information on how to specify dependencies.' - ), - 'type': 'object', - 'properties': { - 'output_table': { - 'type': 'string', - 'title': 'Output table name', - }, - 'start_date': { - 'type': 'string', - 'title': 'Start date', - # date-time is parsed using the chrono library, see - # https://www.npmjs.com/package/chrono-node#usage - 'format': 'date-time', - 'default': 'tomorrow at 9am', - }, - 'end_date': { - 'type': 'string', - 'title': 'End date', - # date-time is parsed using the chrono library, see - # https://www.npmjs.com/package/chrono-node#usage - 'format': 'date-time', - 'default': '9am in 30 days', - }, - 'schedule_interval': { - 'type': 'string', - 'title': 'Schedule interval', - }, - 'dependencies': { - 'type': 'array', - 'title': 'Dependencies', - 'items': { - 'type': 'string', - }, - }, - }, - }, - 'UISCHEMA': { - 'schedule_interval': { - 'ui:placeholder': '@daily, @weekly, etc.', - }, - 'dependencies': { - 'ui:help': ( - 'Check the documentation for the correct format when ' - 'defining dependencies.' - ), - }, - }, - 'VALIDATION': [ - # ensure that start_date <= end_date - { - 'name': 'less_equal', - 'arguments': ['start_date', 'end_date'], - 'message': 'End date cannot be before start date', - # this is where the error message is shown - 'container': 'end_date', - }, - ], - # link to the scheduler; this example links to an Airflow pipeline - # that uses the query id and the output table as its name - 'linkback': ( - 'https://airflow.example.com/admin/airflow/tree?' - 'dag_id=query_${id}_${extra_json.schedule_info.output_table}' - ), -} -``` - -This configuration is based on -[react-jsonschema-form](https://github.com/mozilla-services/react-jsonschema-form) and will add a -menu item called “Schedule” to SQL Lab. When the menu item is clicked, a modal will show up where -the user can add the metadata required for scheduling the query. - -This information can then be retrieved from the endpoint `/api/v1/saved_query/` and used to -schedule the queries that have `schedule_info` in their JSON metadata. For schedulers other than -Airflow, additional fields can be easily added to the configuration file above. - -:::resources -- [Tutorial: Automated Alerts and Reporting via Slack/Email in Superset](https://dev.to/ngtduc693/apache-superset-topic-5-automated-alerts-and-reporting-via-slackemail-in-superset-2gbe) -- [Blog: Integrating Slack alerts and Apache Superset for better data observability](https://medium.com/affinityanswers-tech/integrating-slack-alerts-and-apache-superset-for-better-data-observability-fd2f9a12c350) -::: diff --git a/docs/admin_docs/configuration/async-queries-celery.mdx b/docs/admin_docs/configuration/async-queries-celery.mdx deleted file mode 100644 index ee2d2c28624..00000000000 --- a/docs/admin_docs/configuration/async-queries-celery.mdx +++ /dev/null @@ -1,108 +0,0 @@ ---- -title: Async Queries via Celery -hide_title: true -sidebar_position: 4 -version: 1 ---- - -# Async Queries via Celery - -## Celery - -On large analytic databases, it’s common to run queries that execute for minutes or hours. To enable -support for long running queries that execute beyond the typical web request’s timeout (30-60 -seconds), it is necessary to configure an asynchronous backend for Superset which consists of: - -- one or many Superset workers (which is implemented as a Celery worker), and can be started with - the `celery worker` command, run `celery worker --help` to view the related options. -- a celery broker (message queue) for which we recommend using Redis or RabbitMQ -- a results backend that defines where the worker will persist the query results - -Configuring Celery requires defining a `CELERY_CONFIG` in your `superset_config.py`. Both the worker -and web server processes should have the same configuration. - -```python -class CeleryConfig(object): - broker_url = "redis://localhost:6379/0" - imports = ( - "superset.sql_lab", - "superset.tasks.scheduler", - ) - result_backend = "redis://localhost:6379/0" - worker_prefetch_multiplier = 10 - task_acks_late = True - task_annotations = { - "sql_lab.get_sql_results": { - "rate_limit": "100/s", - }, - } - -CELERY_CONFIG = CeleryConfig -``` - -To start a Celery worker to leverage the configuration, run the following command: - -```bash -celery --app=superset.tasks.celery_app:app worker --pool=prefork -O fair -c 4 -``` - -To start a job which schedules periodic background jobs, run the following command: - -```bash -celery --app=superset.tasks.celery_app:app beat -``` - -To setup a result backend, you need to pass an instance of a derivative of `BaseCache` (`from -flask_caching.backends.base import BaseCache`) to the RESULTS_BACKEND configuration key in your -superset_config.py. You can use Memcached, Redis, S3 (https://pypi.python.org/pypi/s3werkzeugcache), -memory or the file system (in a single server-type setup or for testing), or to write your own -caching interface. Your `superset_config.py` may look something like: - -```python -# On S3 -from s3cache.s3cache import S3Cache -S3_CACHE_BUCKET = 'foobar-superset' -S3_CACHE_KEY_PREFIX = 'sql_lab_result' -RESULTS_BACKEND = S3Cache(S3_CACHE_BUCKET, S3_CACHE_KEY_PREFIX) - -# On Redis -from flask_caching.backends.rediscache import RedisCache -RESULTS_BACKEND = RedisCache( - host='localhost', port=6379, key_prefix='superset_results') -``` - -For performance gains, [MessagePack](https://github.com/msgpack/msgpack-python) and -[PyArrow](https://arrow.apache.org/docs/python/) are now used for results serialization. This can be -disabled by setting `RESULTS_BACKEND_USE_MSGPACK = False` in your `superset_config.py`, should any -issues arise. Please clear your existing results cache store when upgrading an existing environment. - -**Important Notes** - -- It is important that all the worker nodes and web servers in the Superset cluster _share a common - metadata database_. This means that SQLite will not work in this context since it has limited - support for concurrency and typically lives on the local file system. - -- There should _only be one instance of celery beat running_ in your entire setup. If not, - background jobs can get scheduled multiple times resulting in weird behaviors like duplicate - delivery of reports, higher than expected load / traffic etc. - -- SQL Lab will _only run your queries asynchronously if_ you enable **Asynchronous Query Execution** - in your database settings (Sources > Databases > Edit record). - -## Celery Flower - -Flower is a web based tool for monitoring the Celery cluster which you can install from pip: - -```bash -pip install flower -``` - -You can run flower using: - -```bash -celery --app=superset.tasks.celery_app:app flower -``` - -:::resources -- [Blog: How to Set Up Global Async Queries (GAQ) in Apache Superset](https://medium.com/@ngigilevis/how-to-set-up-global-async-queries-gaq-in-apache-superset-a-complete-guide-9d2f4a047559) -::: diff --git a/docs/admin_docs/configuration/aws-iam.mdx b/docs/admin_docs/configuration/aws-iam.mdx deleted file mode 100644 index e3fac57508f..00000000000 --- a/docs/admin_docs/configuration/aws-iam.mdx +++ /dev/null @@ -1,162 +0,0 @@ -{/* -Licensed to the Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. -*/} - ---- -title: AWS IAM Authentication -sidebar_label: AWS IAM Authentication -sidebar_position: 15 ---- - -# AWS IAM Authentication for AWS Databases - -Superset supports IAM-based authentication for **Amazon Aurora** (PostgreSQL and MySQL) and **Amazon Redshift**. IAM auth eliminates the need for database passwords — Superset generates a short-lived auth token using temporary AWS credentials instead. - -Cross-account IAM role assumption via STS `AssumeRole` is supported, allowing a Superset deployment in one AWS account to connect to databases in a different account. - -## Prerequisites - -- Enable the `AWS_DATABASE_IAM_AUTH` feature flag in `superset_config.py`. IAM authentication is gated behind this flag; if it is disabled, connections using `aws_iam` fail with *"AWS IAM database authentication is not enabled."* - ```python - FEATURE_FLAGS = { - "AWS_DATABASE_IAM_AUTH": True, - } - ``` -- `boto3` must be installed in your Superset environment: - ```bash - pip install boto3 - ``` -- The Superset server's IAM role (or static credentials) must have permission to call `sts:AssumeRole` (for cross-account) or the same-account permissions for the target service: - - **Aurora (RDS)**: `rds-db:connect` - - **Redshift provisioned**: `redshift:GetClusterCredentials` - - **Redshift Serverless**: `redshift-serverless:GetCredentials` and `redshift-serverless:GetWorkgroup` -- SSL must be enabled on the Aurora / Redshift endpoint (required for IAM token auth). - -## Configuration - -IAM authentication is configured via the **encrypted_extra** field of the database connection. Access this field in the **Advanced** → **Security** section of the database connection form, under **Secure Extra**. - -### Aurora PostgreSQL or Aurora MySQL - -```json -{ - "aws_iam": { - "enabled": true, - "role_arn": "arn:aws:iam::222222222222:role/SupersetDatabaseAccess", - "external_id": "superset-prod-12345", - "region": "us-east-1", - "db_username": "superset_iam_user", - "session_duration": 3600 - } -} -``` - -| Field | Required | Description | -|-------|----------|-------------| -| `enabled` | Yes | Set to `true` to activate IAM auth | -| `role_arn` | No | ARN of the cross-account IAM role to assume via STS. Omit for same-account auth | -| `external_id` | No | External ID for the STS `AssumeRole` call, if required by the target role's trust policy | -| `region` | Yes | AWS region of the database cluster | -| `db_username` | Yes | The database username associated with the IAM identity | -| `session_duration` | No | STS session duration in seconds (default: `3600`) | - -### Redshift (Serverless) - -```json -{ - "aws_iam": { - "enabled": true, - "role_arn": "arn:aws:iam::222222222222:role/SupersetRedshiftAccess", - "region": "us-east-1", - "workgroup_name": "my-workgroup", - "db_name": "dev" - } -} -``` - -### Redshift (Provisioned Cluster) - -```json -{ - "aws_iam": { - "enabled": true, - "role_arn": "arn:aws:iam::222222222222:role/SupersetRedshiftAccess", - "region": "us-east-1", - "cluster_identifier": "my-cluster", - "db_username": "superset_iam_user", - "db_name": "dev" - } -} -``` - -## Cross-Account IAM Setup - -To connect to a database in Account B from a Superset deployment in Account A: - -**1. In Account B — create a database-access role:** - -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["rds-db:connect"], - "Resource": "arn:aws:rds-db:us-east-1:222222222222:dbuser/db-XXXXXXXXXXXX/superset_iam_user" - } - ] -} -``` - -**Trust policy** (allows Account A's Superset role to assume it): - -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:role/SupersetInstanceRole" - }, - "Action": "sts:AssumeRole", - "Condition": { - "StringEquals": { - "sts:ExternalId": "superset-prod-12345" - } - } - } - ] -} -``` - -**2. In Account A — grant Superset's role permission to assume the Account B role:** - -```json -{ - "Effect": "Allow", - "Action": "sts:AssumeRole", - "Resource": "arn:aws:iam::222222222222:role/SupersetDatabaseAccess" -} -``` - -**3. Configure the database connection in Superset** using the `role_arn` and `external_id` from the trust policy (as shown in the configuration example above). - -## Credential Caching - -STS credentials are cached in memory keyed by `(role_arn, region, external_id)` with a 10-minute TTL. This reduces the number of STS API calls when multiple queries are executed with the same connection. Tokens are refreshed automatically before expiry. diff --git a/docs/admin_docs/configuration/cache.mdx b/docs/admin_docs/configuration/cache.mdx deleted file mode 100644 index ef3fbe1161c..00000000000 --- a/docs/admin_docs/configuration/cache.mdx +++ /dev/null @@ -1,276 +0,0 @@ ---- -title: Caching -hide_title: true -sidebar_position: 3 -version: 1 ---- - -# Caching - -:::note -When a cache backend is configured, Superset expects it to remain available. Operations will -fail if the configured backend becomes unavailable rather than silently degrading. This -fail-fast behavior ensures operators are immediately aware of infrastructure issues. -::: - -Superset uses [Flask-Caching](https://flask-caching.readthedocs.io/) for caching purposes. -Flask-Caching supports various caching backends, including Redis (recommended), Memcached, -SimpleCache (in-memory), or the local filesystem. -[Custom cache backends](https://flask-caching.readthedocs.io/en/latest/#custom-cache-backends) -are also supported. - -Caching can be configured by providing dictionaries in -`superset_config.py` that comply with [the Flask-Caching config specifications](https://flask-caching.readthedocs.io/en/latest/#configuring-flask-caching). - -The following cache configurations can be customized in this way: - -- Dashboard filter state (required): `FILTER_STATE_CACHE_CONFIG`. -- Explore chart form data (required): `EXPLORE_FORM_DATA_CACHE_CONFIG` -- Metadata cache (optional): `CACHE_CONFIG` -- Charting data queried from datasets (optional): `DATA_CACHE_CONFIG` - -For example, to configure the filter state cache using Redis: - -```python -FILTER_STATE_CACHE_CONFIG = { - 'CACHE_TYPE': 'RedisCache', - 'CACHE_DEFAULT_TIMEOUT': 86400, - 'CACHE_KEY_PREFIX': 'superset_filter_cache', - 'CACHE_REDIS_URL': 'redis://localhost:6379/0' -} -``` - -## Dependencies - -In order to use dedicated cache stores, additional python libraries must be installed - -- For Redis: we recommend the [redis](https://pypi.python.org/pypi/redis) Python package -- Memcached: we recommend using [pylibmc](https://pypi.org/project/pylibmc/) client library as - `python-memcached` does not handle storing binary data correctly. - -These libraries can be installed using pip. - -## Fallback Metastore Cache - -Note, that some form of Filter State and Explore caching are required. If either of these caches -are undefined, Superset falls back to using a built-in cache that stores data in the metadata -database. While it is recommended to use a dedicated cache, the built-in cache can also be used -to cache other data. - -For example, to use the built-in cache to store chart data, use the following config: - -```python -DATA_CACHE_CONFIG = { - "CACHE_TYPE": "SupersetMetastoreCache", - "CACHE_KEY_PREFIX": "superset_results", # make sure this string is unique to avoid collisions - "CACHE_DEFAULT_TIMEOUT": 86400, # 60 seconds * 60 minutes * 24 hours -} -``` - -## Chart Cache Timeout - -The cache timeout for charts may be overridden by the settings for an individual chart, dataset, or -database. Each of these configurations will be checked in order before falling back to the default -value defined in `DATA_CACHE_CONFIG`. - -Note, that by setting the cache timeout to `-1`, caching for charting data can be disabled, either -per chart, dataset or database, or by default if set in `DATA_CACHE_CONFIG`. - -## SQL Lab Query Results - -Caching for SQL Lab query results is used when async queries are enabled and is configured using -`RESULTS_BACKEND`. - -Note that this configuration does not use a flask-caching dictionary for its configuration, but -instead requires a cachelib object. - -See [Async Queries via Celery](/admin-docs/configuration/async-queries-celery) for details. - -## Caching Thumbnails - -This is an optional feature that can be turned on by activating its [feature flag](/admin-docs/configuration/configuring-superset#feature-flags) on config: - -``` -FEATURE_FLAGS = { - "THUMBNAILS": True, - "THUMBNAILS_SQLA_LISTENERS": True, -} -``` - -By default thumbnails are rendered per user, and will fall back to the Selenium user for anonymous users. -To always render thumbnails as a fixed user (`admin` in this example), use the following configuration: - -```python -from superset.tasks.types import FixedExecutor - -THUMBNAIL_EXECUTORS = [FixedExecutor("admin")] -``` - -For this feature you will need a cache system and celery workers. All thumbnails are stored on cache -and are processed asynchronously by the workers. - -An example config where images are stored on S3 could be: - -```python -from flask import Flask -from s3cache.s3cache import S3Cache - -... - -class CeleryConfig(object): - broker_url = "redis://localhost:6379/0" - imports = ( - "superset.sql_lab", - "superset.tasks.thumbnails", - ) - result_backend = "redis://localhost:6379/0" - worker_prefetch_multiplier = 10 - task_acks_late = True - - -CELERY_CONFIG = CeleryConfig - -def init_thumbnail_cache(app: Flask) -> S3Cache: - return S3Cache("bucket_name", 'thumbs_cache/') - - -THUMBNAIL_CACHE_CONFIG = init_thumbnail_cache -``` - -Using the above example cache keys for dashboards will be `superset_thumb__dashboard__{ID}`. You can -override the base URL for Selenium using: - -``` -WEBDRIVER_BASEURL = "https://superset.company.com" -``` - -To control which user account is used for rendering thumbnails and warming up caches, configure -`THUMBNAIL_EXECUTORS` and `CACHE_WARMUP_EXECUTORS`. Each accepts a list of executor types (which -resolve to an owner, creator, modifier, or the currently-logged-in user) and/or a `FixedExecutor` -pinned to a specific username. By default, thumbnails render as the current user -(`ExecutorType.CURRENT_USER`) and cache warmup runs as the chart/dashboard owner -(`ExecutorType.OWNER`). - -To force both to run as a dedicated service account (`admin` in this example): - -```python -from superset.tasks.types import ExecutorType, FixedExecutor - -THUMBNAIL_EXECUTORS = [FixedExecutor("admin")] -CACHE_WARMUP_EXECUTORS = [FixedExecutor("admin")] -``` - -Use a dedicated read-only service account here rather than a personal admin account, so that -thumbnail rendering and cache warmup tasks don't fail if a specific user's credentials change. - -Additional Selenium WebDriver configuration can be set using `WEBDRIVER_CONFIGURATION`. You can -implement a custom function to authenticate Selenium. The default function uses the `flask-login` -session cookie. Here's an example of a custom function signature: - -```python -def auth_driver(driver: WebDriver, user: "User") -> WebDriver: - pass -``` - -Then on configuration: - -``` -WEBDRIVER_AUTH_FUNC = auth_driver -``` - -## ETag Support for Thumbnails - -Thumbnail and screenshot endpoints return `ETag` response headers based on the cached content digest. Clients can use conditional requests to avoid downloading unchanged images: - -``` -GET /api/v1/chart/42/thumbnail/ -If-None-Match: "abc123..." - -→ 304 Not Modified (if unchanged) -→ 200 OK (with new image if changed) -``` - -This is particularly useful for embedded dashboards and external integrations that periodically poll for updated screenshots — unchanged thumbnails return immediately with no payload. - -## Distributed Coordination Backend - -Superset supports an optional distributed coordination (`DISTRIBUTED_COORDINATION_CONFIG`) for -high-performance distributed operations. This configuration enables: - -- **Distributed locking**: Moves lock operations from the metadata database to Redis, improving - performance and reducing metastore load -- **Real-time event notifications**: Enables instant pub/sub messaging for task abort signals and - completion notifications instead of polling-based approaches - -:::note -This requires Redis or Valkey specifically—it uses Redis-specific features (pub/sub, `SET NX EX`) -that are not available in general Flask-Caching backends. -::: - -### Configuration - -The distributed coordination uses Flask-Caching style configuration for consistency with other cache -backends. Configure `DISTRIBUTED_COORDINATION_CONFIG` in `superset_config.py`: - -```python -DISTRIBUTED_COORDINATION_CONFIG = { - "CACHE_TYPE": "RedisCache", - "CACHE_REDIS_HOST": "localhost", - "CACHE_REDIS_PORT": 6379, - "CACHE_REDIS_DB": 0, - "CACHE_REDIS_PASSWORD": "", # Optional -} -``` - -For Redis Sentinel deployments: - -```python -DISTRIBUTED_COORDINATION_CONFIG = { - "CACHE_TYPE": "RedisSentinelCache", - "CACHE_REDIS_SENTINELS": [("sentinel1", 26379), ("sentinel2", 26379)], - "CACHE_REDIS_SENTINEL_MASTER": "mymaster", - "CACHE_REDIS_SENTINEL_PASSWORD": None, # Sentinel password (if different) - "CACHE_REDIS_PASSWORD": "", # Redis password - "CACHE_REDIS_DB": 0, -} -``` - -For SSL/TLS connections: - -```python -DISTRIBUTED_COORDINATION_CONFIG = { - "CACHE_TYPE": "RedisCache", - "CACHE_REDIS_HOST": "redis.example.com", - "CACHE_REDIS_PORT": 6380, - "CACHE_REDIS_SSL": True, - "CACHE_REDIS_SSL_CERTFILE": "/path/to/client.crt", - "CACHE_REDIS_SSL_KEYFILE": "/path/to/client.key", - "CACHE_REDIS_SSL_CA_CERTS": "/path/to/ca.crt", -} -``` - -### Distributed Lock TTL - -You can configure the default lock TTL (time-to-live) in seconds. Locks automatically expire after -this duration to prevent deadlocks from crashed processes: - -```python -DISTRIBUTED_LOCK_DEFAULT_TTL = 30 # Default: 30 seconds -``` - -Individual lock acquisitions can override this value when needed. - -### Database-Only Mode - -When `DISTRIBUTED_COORDINATION_CONFIG` is not configured, Superset uses database-backed operations: - -- **Locking**: Uses the KeyValue table with periodic cleanup of expired entries -- **Event notifications**: Uses database polling instead of pub/sub - -While database-backed operations work reliably, the Redis backend is recommended for production -deployments where low latency and reduced database load are important. - -:::resources -- [Blog: The Data Engineer's Guide to Lightning-Fast Superset Dashboards](https://preset.io/blog/the-data-engineers-guide-to-lightning-fast-apache-superset-dashboards/) -- [Blog: Accelerating Dashboards with Materialized Views](https://preset.io/blog/accelerating-apache-superset-dashboards-with-materialized-views/) -::: diff --git a/docs/admin_docs/configuration/configuring-superset.mdx b/docs/admin_docs/configuration/configuring-superset.mdx deleted file mode 100644 index daf92b1c943..00000000000 --- a/docs/admin_docs/configuration/configuring-superset.mdx +++ /dev/null @@ -1,509 +0,0 @@ ---- -title: Configuring Superset -hide_title: true -sidebar_position: 1 -version: 1 ---- - -# Configuring Superset - -## superset_config.py - -Superset exposes hundreds of configurable parameters through its -[config.py module](https://github.com/apache/superset/blob/master/superset/config.py). The -variables and objects exposed act as a public interface of the bulk of what you may want -to configure, alter and interface with. In this python module, you'll find all these -parameters, sensible defaults, as well as rich documentation in the form of comments - -To configure your application, you need to create your own configuration module, which -will allow you to override few or many of these parameters. Instead of altering the core module, -you'll want to define your own module (typically a file named `superset_config.py`). -Add this file to your `PYTHONPATH` or create an environment variable -`SUPERSET_CONFIG_PATH` specifying the full path of the `superset_config.py`. - -For example, if deploying on Superset directly on a Linux-based system where your -`superset_config.py` is under `/app` directory, you can run: - -```bash -export SUPERSET_CONFIG_PATH=/app/superset_config.py -``` - -If you are using your own custom Dockerfile with the official Superset image as base image, -then you can add your overrides as shown below: - -```bash -COPY --chown=superset superset_config.py /app/ -ENV SUPERSET_CONFIG_PATH /app/superset_config.py -``` - -Docker compose deployments handle application configuration differently using specific conventions. -Refer to the [docker compose tips & configuration](/admin-docs/installation/docker-compose#docker-compose-tips--configuration) -for details. - -The following is an example of just a few of the parameters you can set in your `superset_config.py` file: - -``` -# Superset specific config -ROW_LIMIT = 5000 - -# Flask App Builder configuration -# Your App secret key will be used for securely signing the session cookie -# and encrypting sensitive information on the database -# Make sure you are changing this key for your deployment with a strong key. -# Alternatively you can set it with `SUPERSET_SECRET_KEY` environment variable. -# You MUST set this for production environments or the server will refuse -# to start and you will see an error in the logs accordingly. -SECRET_KEY = 'YOUR_OWN_RANDOM_GENERATED_SECRET_KEY' - -# The SQLAlchemy connection string to your database backend -# This connection defines the path to the database that stores your -# superset metadata (slices, connections, tables, dashboards, ...). -# Note that the connection information to connect to the datasources -# you want to explore are managed directly in the web UI -# The check_same_thread=false property ensures the sqlite client does not attempt -# to enforce single-threaded access, which may be problematic in some edge cases -SQLALCHEMY_DATABASE_URI = 'sqlite:////path/to/superset.db?check_same_thread=false' - -# Flask-WTF flag for CSRF -WTF_CSRF_ENABLED = True -# Add endpoints that need to be exempt from CSRF protection -WTF_CSRF_EXEMPT_LIST = [] -# A CSRF token that expires in 1 year -WTF_CSRF_TIME_LIMIT = 60 * 60 * 24 * 365 - -# Set this API key to enable Mapbox visualizations -MAPBOX_API_KEY = '' -``` - -:::tip -Note that it is typical to copy and paste [only] the portions of the -core [superset/config.py](https://github.com/apache/superset/blob/master/superset/config.py) that -you want to alter, along with the related comments into your own `superset_config.py` file. -::: - -All the parameters and default values defined -in [superset/config.py](https://github.com/apache/superset/blob/master/superset/config.py) -can be altered in your local `superset_config.py`. Administrators will want to read through the file -to understand what can be configured locally as well as the default values in place. - -Since `superset_config.py` acts as a Flask configuration module, it can be used to alter the -settings of Flask itself, as well as Flask extensions that Superset bundles like -`flask-wtf`, `flask-caching`, `flask-migrate`, -and `flask-appbuilder`. Each one of these extensions offers intricate configurability. -Flask App Builder, the web framework used by Superset, also offers many -configuration settings. Please consult the -[Flask App Builder Documentation](https://flask-appbuilder.readthedocs.org/en/latest/config.html) -for more information on how to configure it. - -At the very least, you'll want to change `SECRET_KEY` and `SQLALCHEMY_DATABASE_URI`. Continue reading for more about each of these. - -## Specifying a SECRET_KEY - -### Adding an initial SECRET_KEY - -Superset requires a user-specified SECRET_KEY to start up. This requirement was [added in version 2.1.0 to force secure configurations](https://preset.io/blog/superset-security-update-default-secret_key-vulnerability/). Add a strong SECRET_KEY to your `superset_config.py` file like: - -```python -SECRET_KEY = 'YOUR_OWN_RANDOM_GENERATED_SECRET_KEY' -``` - -You can generate a strong secure key with `openssl rand -base64 42`. - -Alternatively, you can set the secret key using `SUPERSET_SECRET_KEY` environment variable: - -On a Unix-based system, such as Linux or macOS, you can do so by running the following command in your terminal: - -```bash -export SUPERSET_SECRET_KEY=$(openssl rand -base64 42) -``` - -:::caution Use a strong secret key -This key will be used for securely signing session cookies and encrypting sensitive information stored in Superset's application metadata database. -Your deployment must use a complex, unique key. -::: - -### Rotating to a newer SECRET_KEY - -If you wish to change your existing SECRET_KEY, add the existing SECRET_KEY to your `superset_config.py` file as -`PREVIOUS_SECRET_KEY =`and provide your new key as `SECRET_KEY =`. You can find your current SECRET_KEY with these -commands - if running Superset with Docker, execute from within the Superset application container: - -```python -superset shell -from flask import current_app; print(current_app.config["SECRET_KEY"]) -``` - -Save your `superset_config.py` with these values and then run `superset re-encrypt-secrets`. - -## Setting up a production metadata database - -Superset needs a database to store the information it manages, like the definitions of -charts, dashboards, and many other things. - -By default, Superset is configured to use [SQLite](https://www.sqlite.org/), -a self-contained, single-file database that offers a simple and fast way to get started -(without requiring any installation). However, for production environments, -using SQLite is highly discouraged due to security, scalability, and data integrity reasons. -It's important to use only the supported database engines and consider using a different -database engine on a separate host or container. - -Superset supports the following database engines/versions: - -| Database Engine | Supported Versions | -| ----------------------------------------- | ---------------------------------------- | -| [PostgreSQL](https://www.postgresql.org/) | 10.X, 11.X, 12.X, 13.X, 14.X, 15.X, 16.X | -| [MySQL](https://www.mysql.com/) | 5.7, 8.X | - -Use the following database drivers and connection strings: - -| Database | PyPI package | Connection String | -| ----------------------------------------- | ------------------------- | ---------------------------------------------------------------------- | -| [PostgreSQL](https://www.postgresql.org/) | `pip install psycopg2` | `postgresql://:@/` | -| [MySQL](https://www.mysql.com/) | `pip install mysqlclient` | `mysql://:@/` | - -:::tip -Properly setting up metadata store is beyond the scope of this documentation. We recommend -using a hosted managed service such as [Amazon RDS](https://aws.amazon.com/rds/) or -[Google Cloud Databases](https://cloud.google.com/products/databases?hl=en) to handle -service and supporting infrastructure and backup strategy. -::: - -To configure Superset metastore set `SQLALCHEMY_DATABASE_URI` config key on `superset_config` -to the appropriate connection string. - -## Running on a WSGI HTTP Server - -While you can run Superset on NGINX or Apache, we recommend using Gunicorn in async mode. This -enables impressive concurrency even and is fairly easy to install and configure. Please refer to the -documentation of your preferred technology to set up this Flask WSGI application in a way that works -well in your environment. Here’s an async setup known to work well in production: - -``` - -w 10 \ - -k gevent \ - --worker-connections 1000 \ - --timeout 120 \ - -b 0.0.0.0:6666 \ - --limit-request-line 0 \ - --limit-request-field_size 0 \ - --statsd-host localhost:8125 \ - "superset.app:create_app()" -``` - -Refer to the [Gunicorn documentation](https://docs.gunicorn.org/en/stable/design.html) for more -information. _Note that the development web server (`superset run` or `flask run`) is not intended -for production use._ - -If you're not using Gunicorn, you may want to disable the use of `flask-compress` by setting -`COMPRESS_REGISTER = False` in your `superset_config.py`. - -Currently, the Google BigQuery Python SDK is not compatible with `gevent`, due to some dynamic monkeypatching on python core library by `gevent`. -So, when you use `BigQuery` datasource on Superset, you have to use `gunicorn` worker type except `gevent`. - -## HTTPS Configuration - -You can configure HTTPS upstream via a load balancer or a reverse proxy (such as nginx) and do SSL/TLS Offloading before traffic reaches the Superset application. In this setup, local traffic from a Celery worker taking a snapshot of a chart for Alerts & Reports can access Superset at a `http://` URL, from behind the ingress point. -You can also configure [SSL in Gunicorn](https://docs.gunicorn.org/en/stable/settings.html#ssl) (the Python webserver) if you are using an official Superset Docker image. - -## Configuration Behind a Load Balancer - -If you are running superset behind a load balancer or reverse proxy (e.g. NGINX or ELB on AWS), you -may need to utilize a healthcheck endpoint so that your load balancer knows if your superset -instance is running. This is provided at `/health` which will return a 200 response containing “OK” -if the webserver is running. - -If the load balancer is inserting `X-Forwarded-For/X-Forwarded-Proto` headers, you should set -`ENABLE_PROXY_FIX = True` in the superset config file (`superset_config.py`) to extract and use the -headers. - -In case the reverse proxy is used for providing SSL encryption, an explicit definition of the -`X-Forwarded-Proto` may be required. For the Apache webserver this can be set as follows: - -``` -RequestHeader set X-Forwarded-Proto "https" -``` - -## Configuring the application root - -*Please be advised that this feature is in BETA.* - -Superset supports running the application under a non-root path. The root path -prefix can be specified in one of three ways: - -- Customizing the [Flask entrypoint](https://github.com/apache/superset/blob/master/superset/app.py#L29) - by passing the `superset_app_root` variable; or -- Setting the `SUPERSET_APP_ROOT` environment variable to the desired prefix; or -- Setting the `APPLICATION_ROOT` config in your `superset_config.py` file. - -Note, the prefix should start with a `/`. - -### Customizing the Flask entrypoint - -To configure a prefix, e.g `/analytics`, pass the `superset_app_root` argument to -`create_app` when calling flask run either through the `FLASK_APP` -environment variable: - -```sh -FLASK_APP="superset:create_app(superset_app_root='/analytics')" -``` - -or as part of the `--app` argument to `flask run`: - -```sh -flask --app "superset.app:create_app(superset_app_root='/analytics')" -``` - -### Docker builds - -The [docker compose](/admin-docs/installation/docker-compose#configuring-further) developer -configuration includes an additional environmental variable, -[`SUPERSET_APP_ROOT`](https://github.com/apache/superset/blob/master/docker/.env), -to simplify the process of setting up a non-default root path across the services. - -In `docker/.env-local` set `SUPERSET_APP_ROOT` to the desired prefix and then bring the -services up with `docker compose up --detach`. - -## Custom OAuth2 Configuration - -Superset is built on Flask-AppBuilder (FAB), which supports many providers out of the box -(GitHub, Twitter, LinkedIn, Google, Azure, etc). Beyond those, Superset can be configured to connect -with other OAuth2 Authorization Server implementations that support “code” authorization. - -Make sure the pip package [`Authlib`](https://authlib.org/) is installed on the webserver. - -First, configure authorization in Superset `superset_config.py`. - -```python -from flask_appbuilder.security.manager import AUTH_OAUTH - -# Set the authentication type to OAuth -AUTH_TYPE = AUTH_OAUTH - -OAUTH_PROVIDERS = [ - { 'name':'egaSSO', - 'token_key':'access_token', # Name of the token in the response of access_token_url - 'icon':'fa-address-card', # Icon for the provider - 'remote_app': { - 'client_id':'myClientId', # Client Id (Identify Superset application) - 'client_secret':'MySecret', # Secret for this Client Id (Identify Superset application) - 'client_kwargs':{ - 'scope': 'read' # Scope for the Authorization - }, - 'access_token_method':'POST', # HTTP Method to call access_token_url - 'access_token_params':{ # Additional parameters for calls to access_token_url - 'client_id':'myClientId' - }, - 'jwks_uri':'https://myAuthorizationServe/adfs/discovery/keys', # may be required to generate token - 'access_token_headers':{ # Additional headers for calls to access_token_url - 'Authorization': 'Basic Base64EncodedClientIdAndSecret' - }, - 'api_base_url':'https://myAuthorizationServer/oauth2AuthorizationServer/', - 'access_token_url':'https://myAuthorizationServer/oauth2AuthorizationServer/token', - 'authorize_url':'https://myAuthorizationServer/oauth2AuthorizationServer/authorize' - } - } -] - -# Will allow user self registration, allowing to create Flask users from Authorized User -AUTH_USER_REGISTRATION = True - -# The default user self registration role -AUTH_USER_REGISTRATION_ROLE = "Public" -``` - -In case you want to assign the `Admin` role on new user registration, it can be assigned as follows: -```python -AUTH_USER_REGISTRATION_ROLE = "Admin" -``` -If you encounter the [issue](https://github.com/apache/superset/issues/13243) of not being able to list users from the Superset main page settings, although a newly registered user has an `Admin` role, please re-run `superset init` to sync the required permissions. Below is the command to re-run `superset init` using docker compose. -``` -docker-compose exec superset superset init -``` - -Then, create a `CustomSsoSecurityManager` that extends `SupersetSecurityManager` and overrides -`oauth_user_info`: - -```python -import logging -from superset.security import SupersetSecurityManager - -class CustomSsoSecurityManager(SupersetSecurityManager): - - def oauth_user_info(self, provider, response=None): - logging.debug("Oauth2 provider: {0}.".format(provider)) - if provider == 'egaSSO': - # As example, this line request a GET to base_url + '/' + userDetails with Bearer Authentication, - # and expects that authorization server checks the token, and response with user details - me = self.appbuilder.sm.oauth_remotes[provider].get('userDetails').data - logging.debug("user_data: {0}".format(me)) - return { 'name' : me['name'], 'email' : me['email'], 'id' : me['user_name'], 'username' : me['user_name'], 'first_name':'', 'last_name':''} - ... -``` - -This file must be located in the same directory as `superset_config.py` with the name -`custom_sso_security_manager.py`. Finally, add the following 2 lines to `superset_config.py`: - -``` -from custom_sso_security_manager import CustomSsoSecurityManager -CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager -``` - -**Notes** - -- The redirect URL will be `https:///oauth-authorized/` - When configuring an OAuth2 authorization provider if needed. For instance, the redirect URL will - be `https:///oauth-authorized/egaSSO` for the above configuration. - -- If an OAuth2 authorization server supports OpenID Connect 1.0, you could configure its configuration - document URL only without providing `api_base_url`, `access_token_url`, `authorize_url` and other - required options like user info endpoint, jwks uri etc. For instance: - - ```python - OAUTH_PROVIDERS = [ - { 'name':'egaSSO', - 'token_key':'access_token', # Name of the token in the response of access_token_url - 'icon':'fa-address-card', # Icon for the provider - 'remote_app': { - 'client_id':'myClientId', # Client Id (Identify Superset application) - 'client_secret':'MySecret', # Secret for this Client Id (Identify Superset application) - 'server_metadata_url': 'https://myAuthorizationServer/.well-known/openid-configuration' - } - } - ] - ``` - -### PKCE Support - -For public OAuth2 clients that cannot securely store a client secret, enable Proof Key for Code Exchange (PKCE) by adding `code_challenge_method` to the `remote_app` configuration: - -```python -OAUTH_PROVIDERS = [ - { - 'name': 'myProvider', - 'remote_app': { - 'client_id': 'myClientId', - 'client_secret': 'mySecret', # may be empty for pure public clients - 'code_challenge_method': 'S256', # enables PKCE - 'server_metadata_url': 'https://myAuthorizationServer/.well-known/openid-configuration' - } - } -] -``` - -PKCE (`S256`) is recommended for all OAuth2 flows, even when a client secret is present, as it protects against authorization code interception attacks. - -## LDAP Authentication - -FAB supports authenticating user credentials against an LDAP server. -To use LDAP you must install the [python-ldap](https://www.python-ldap.org/en/latest/installing.html) package. -See [FAB's LDAP documentation](https://flask-appbuilder.readthedocs.io/en/latest/security.html#authentication-ldap) -for details. - -## Mapping LDAP or OAUTH groups to Superset roles - -AUTH_ROLES_MAPPING in Flask-AppBuilder is a dictionary that maps from LDAP/OAUTH group names to FAB roles. -It is used to assign roles to users who authenticate using LDAP or OAuth. - -### Mapping OAUTH groups to Superset roles - -The following `AUTH_ROLES_MAPPING` dictionary would map the OAUTH group "superset_users" to the Superset roles "Gamma" as well as "Alpha", and the OAUTH group "superset_admins" to the Superset role "Admin". - -```python -AUTH_ROLES_MAPPING = { -"superset_users": ["Gamma","Alpha"], -"superset_admins": ["Admin"], -} -``` - -### Mapping LDAP groups to Superset roles - -The following `AUTH_ROLES_MAPPING` dictionary would map the LDAP DN "cn=superset_users,ou=groups,dc=example,dc=com" to the Superset roles "Gamma" as well as "Alpha", and the LDAP DN "cn=superset_admins,ou=groups,dc=example,dc=com" to the Superset role "Admin". - -```python -AUTH_ROLES_MAPPING = { -"cn=superset_users,ou=groups,dc=example,dc=com": ["Gamma","Alpha"], -"cn=superset_admins,ou=groups,dc=example,dc=com": ["Admin"], -} -``` - -Note: This requires `AUTH_LDAP_SEARCH` to be set. For more details, please see the [FAB Security documentation](https://flask-appbuilder.readthedocs.io/en/latest/security.html). - -### Syncing roles at login - -You can also use the `AUTH_ROLES_SYNC_AT_LOGIN` configuration variable to control how often Flask-AppBuilder syncs the user's roles with the LDAP/OAUTH groups. If `AUTH_ROLES_SYNC_AT_LOGIN` is set to True, Flask-AppBuilder will sync the user's roles each time they log in. If `AUTH_ROLES_SYNC_AT_LOGIN` is set to False, Flask-AppBuilder will only sync the user's roles when they first register. - -## Flask app Configuration Hook - -`FLASK_APP_MUTATOR` is a configuration function that can be provided in your environment, receives -the app object and can alter it in any way. For example, add `FLASK_APP_MUTATOR` into your -`superset_config.py` to setup session cookie expiration time to 24 hours: - -```python -from flask import session -from flask import Flask - - -def make_session_permanent(): - ''' - Enable maxAge for the cookie 'session' - ''' - session.permanent = True - -# Set up max age of session to 24 hours -PERMANENT_SESSION_LIFETIME = timedelta(hours=24) -def FLASK_APP_MUTATOR(app: Flask) -> None: - app.before_request_funcs.setdefault(None, []).append(make_session_permanent) -``` - -## Feature Flags - -To support a diverse set of users, Superset has some features that are not enabled by default. For -example, some users have stronger security restrictions, while some others may not. So Superset -allows users to enable or disable some features by config. For feature owners, you can add optional -functionalities in Superset, but will be only affected by a subset of users. - -You can enable or disable features with flag from `superset_config.py`: - -```python -FEATURE_FLAGS = { - 'PRESTO_EXPAND_DATA': False, -} -``` - -A current list of feature flags can be found in the [Feature Flags](/admin-docs/configuration/feature-flags) documentation. - -## Security Configuration - -### HASH_ALGORITHM - -Controls the hashing algorithm used for internal checksums and cache keys (thumbnails, cache keys, etc.). The default is `sha256`, which satisfies environments with stricter compliance requirements (e.g., FedRAMP). Set it to `md5` to retain the legacy behavior from older Superset deployments: - -```python -HASH_ALGORITHM = "sha256" # default; set to "md5" for legacy behavior -``` - -A companion `HASH_ALGORITHM_FALLBACKS` list (default: `["md5"]`) lets UUID lookups fall back to older algorithms, which enables gradual migration without breaking existing entries. Set it to `[]` for strict mode (use only `HASH_ALGORITHM`). - -:::note -This setting affects internal Superset operations only, not user passwords or authentication tokens. Changing it in an existing deployment may invalidate cached values but does not require a database migration. -::: - -## SQL Lab Query History Pruning - -SQL Lab query history is stored in the metadata database and is **not** pruned by default. To trim older rows, enable the `prune_query` Celery beat task by uncommenting it in `CELERY_BEAT_SCHEDULE` and choosing a retention window: - -```python -CELERY_BEAT_SCHEDULE = { - "prune_query": { - "task": "prune_query", - "schedule": crontab(minute=0, hour=0, day_of_month=1), - "kwargs": {"retention_period_days": 180}, - }, -} -``` - -Adjust `retention_period_days` to control how long query rows are kept. Companion opt-in tasks (`prune_logs`, `prune_tasks`) exist for pruning the logs and tasks tables; see the commented-out examples in `superset/config.py`. Without enabling these tasks, the metadata database will grow unbounded over time. - -:::resources -- [Blog: Feature Flags in Apache Superset](https://preset.io/blog/feature-flags-in-apache-superset-and-preset/) -::: diff --git a/docs/admin_docs/configuration/country-map-tools.mdx b/docs/admin_docs/configuration/country-map-tools.mdx deleted file mode 100644 index ae128ed3b7c..00000000000 --- a/docs/admin_docs/configuration/country-map-tools.mdx +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: Country Map Tools -sidebar_position: 10 -version: 1 ---- - -import countriesData from '../../data/countries.json'; - -# The Country Map Visualization - -The Country Map visualization allows you to plot lightweight choropleth maps of -your countries by province, states, or other subdivision types. It does not rely -on any third-party map services but would require you to provide the -[ISO-3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) codes of your country's -top-level subdivisions. Comparing to a province or state's full names, the ISO -code is less ambiguous and is unique to all regions in the world. - -## Included Maps - -The current list of countries can be found in the src -[legacy-plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries.ts) - -The Country Maps visualization already ships with the maps for the following countries: - -
    -{countriesData.countries.map((country, index) => ( -
  • {country}
  • -))} -
- -## Adding a New Country - -To add a new country to the list, you'd have to edit files in -[@superset-ui/legacy-plugin-chart-country-map](https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-country-map). - -1. Generate a new GeoJSON file for your country following the guide in [this Jupyter notebook](https://github.com/apache/superset/blob/master/superset-frontend/plugins/legacy-plugin-chart-country-map/scripts/Country%20Map%20GeoJSON%20Generator.ipynb). -2. Edit the countries list in [legacy-plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries.ts). -3. Install superset-frontend dependencies: `cd superset-frontend && npm install` -4. Verify your countries in Superset plugins storybook: `npm run plugins:storybook`. -5. Build and install Superset from source code. diff --git a/docs/admin_docs/configuration/event-logging.mdx b/docs/admin_docs/configuration/event-logging.mdx deleted file mode 100644 index ceb751af650..00000000000 --- a/docs/admin_docs/configuration/event-logging.mdx +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Event Logging -sidebar_position: 9 -version: 1 ---- - -# Logging - -## Event Logging - -Superset by default logs special action events in its internal database (DBEventLogger). These logs can be accessed -on the UI by navigating to **Security > Action Log**. You can freely customize these logs by -implementing your own event log class. -**When custom log class is enabled DBEventLogger is disabled and logs -stop being populated in UI logs view.** -To achieve both, custom log class should extend built-in DBEventLogger log class. - -Here's an example of a simple JSON-to-stdout class: - -```python - def log(self, user_id, action, *args, **kwargs): - records = kwargs.get('records', list()) - dashboard_id = kwargs.get('dashboard_id') - slice_id = kwargs.get('slice_id') - duration_ms = kwargs.get('duration_ms') - referrer = kwargs.get('referrer') - - for record in records: - log = dict( - action=action, - json=record, - dashboard_id=dashboard_id, - slice_id=slice_id, - duration_ms=duration_ms, - referrer=referrer, - user_id=user_id - ) - print(json.dumps(log)) -``` - -End by updating your config to pass in an instance of the logger you want to use: - -``` -EVENT_LOGGER = JSONStdOutEventLogger() -``` - -## StatsD Logging - -Superset can be configured to log events to [StatsD](https://github.com/statsd/statsd) -if desired. Most endpoints hit are logged as -well as key events like query start and end in SQL Lab. - -To setup StatsD logging, it’s a matter of configuring the logger in your `superset_config.py`. -If not already present, you need to ensure that the `statsd`-package is installed in Superset's python environment. - -```python -from superset.stats_logger import StatsdStatsLogger -STATS_LOGGER = StatsdStatsLogger(host='localhost', port=8125, prefix='superset') -``` - -Note that it’s also possible to implement your own logger by deriving -`superset.stats_logger.BaseStatsLogger`. diff --git a/docs/admin_docs/configuration/feature-flags.mdx b/docs/admin_docs/configuration/feature-flags.mdx deleted file mode 100644 index b573b07d326..00000000000 --- a/docs/admin_docs/configuration/feature-flags.mdx +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: Feature Flags -hide_title: true -sidebar_position: 2 -version: 1 ---- - -import featureFlags from '@site/static/feature-flags.json'; - -export const FlagTable = ({flags}) => ( - - - - - - - - - - {flags.map((flag) => ( - - - - - - ))} - -
FlagDefaultDescription
{flag.name}{flag.default ? 'True' : 'False'} - {flag.description} - {flag.docs && ( - <> (docs) - )} -
-); - -# Feature Flags - -Superset uses feature flags to control the availability of features. Feature flags allow -gradual rollout of new functionality and provide a way to enable experimental features. - -To enable a feature flag, add it to your `superset_config.py`: - -```python -FEATURE_FLAGS = { - "ENABLE_TEMPLATE_PROCESSING": True, -} -``` - -## Lifecycle - -Feature flags progress through lifecycle stages: - -| Stage | Description | -|-------|-------------| -| **Development** | Experimental features under active development. May be incomplete or unstable. | -| **Testing** | Feature complete but undergoing testing. Usable but may contain bugs. | -| **Stable** | Production-ready features. Safe for all deployments. | -| **Deprecated** | Features scheduled for removal. Migrate away from these. | - ---- - -## Development - -These features are experimental and under active development. Use only in development environments. - - - ---- - -## Testing - -These features are complete but still being tested. They are usable but may have bugs. - - - ---- - -## Stable - -These features are production-ready and safe to enable. - - - ---- - -## Deprecated - -These features are scheduled for removal. Plan to migrate away from them. - - - ---- - -## Adding New Feature Flags - -When adding a new feature flag to `superset/config.py`, include the following annotations: - -```python -# Description of what the feature does -# @lifecycle: development | testing | stable | deprecated -# @docs: https://superset.apache.org/docs/... (optional) -# @category: runtime_config | path_to_deprecation (optional, for stable flags) -"MY_NEW_FEATURE": False, -``` - -This documentation is auto-generated from the annotations in -[config.py](https://github.com/apache/superset/blob/master/superset/config.py). diff --git a/docs/admin_docs/configuration/importing-exporting-datasources.mdx b/docs/admin_docs/configuration/importing-exporting-datasources.mdx deleted file mode 100644 index 6fc7ceea9ff..00000000000 --- a/docs/admin_docs/configuration/importing-exporting-datasources.mdx +++ /dev/null @@ -1,157 +0,0 @@ ---- -title: Importing and Exporting Datasources -hide_title: true -sidebar_position: 11 -version: 1 ---- - -# Importing and Exporting Datasources - -The superset cli allows you to import and export datasources from and to YAML. Datasources include -databases. The data is expected to be organized in the following hierarchy: - -:::info -Superset's ZIP-based import/export also covers **dashboards**, **charts**, and **saved queries**, exercised through the UI and REST API. The [Dashboard Import Overwrite Behavior](#dashboard-import-overwrite-behavior) and [UUIDs in API Responses](#uuids-in-api-responses) sections below document the behavior shared across all asset types. -::: - -```text -├──databases -| ├──database_1 -| | ├──table_1 -| | | ├──columns -| | | | ├──column_1 -| | | | ├──column_2 -| | | | └──... (more columns) -| | | └──metrics -| | | ├──metric_1 -| | | ├──metric_2 -| | | └──... (more metrics) -| | └── ... (more tables) -| └── ... (more databases) -``` - -:::note -When you export a database connection, the `masked_encrypted_extra` field (used for sensitive connection parameters such as service account JSON, OAuth tokens, and other encrypted credentials) is included in the export. When importing on another instance, these values are decrypted and re-encrypted using the destination instance's `SECRET_KEY`. Ensure the receiving instance has a valid `SECRET_KEY` configured before importing. -::: - -## Exporting Datasources to YAML - -You can print your current datasources to stdout by running: - -```bash -superset export_datasources -``` - -To save your datasources to a ZIP file run: - -```bash -superset export_datasources -f -``` - -By default, default (null) values will be omitted. Use the -d flag to include them. If you want back -references to be included (e.g. a column to include the table id it belongs to) use the -b flag. - -Alternatively, you can export datasources using the UI: - -1. Open **Sources -> Databases** to export all tables associated to a single or multiple databases. - (**Tables** for one or more tables) -2. Select the items you would like to export. -3. Click **Actions -> Export** to YAML -4. If you want to import an item that you exported through the UI, you will need to nest it inside - its parent element, e.g. a database needs to be nested under databases a table needs to be nested - inside a database element. - -In order to obtain an **exhaustive list of all fields** you can import using the YAML import run: - -```bash -superset export_datasource_schema -``` - -As a reminder, you can use the `-b` flag to include back references. - -## Importing Datasources - -In order to import datasources from a ZIP file, run: - -```bash -superset import_datasources -p -``` - -The optional username flag **-u** sets the user used for the datasource import. The default is 'admin'. Example: - -```bash -superset import_datasources -p -u 'admin' -``` - -## Dashboard Import Overwrite Behavior - -When importing a dashboard ZIP with the **overwrite** option enabled, any existing charts that are part of the dashboard are **replaced** rather than duplicated. This applies to: - -- Charts whose UUID matches a chart already present in the target instance -- The full chart configuration (query, visualization type, columns, metrics) is replaced by the imported version - -If you import without the overwrite flag, existing charts with conflicting UUIDs are left unchanged and the import skips those objects. Use overwrite when you want to push a fully updated dashboard (including chart definitions) from a development or staging environment to production. - -## UUIDs in API Responses - -The REST API POST endpoints for **datasets**, **charts**, and **dashboards** include the auto-generated `uuid` field in the response body: - -```json -{ - "id": 42, - "uuid": "b8a8d5c3-1234-4abc-8def-0123456789ab", - ... -} -``` - -UUIDs remain stable across import/export cycles and can be used for cross-environment workflows — for example, recording a UUID when creating a chart in development and using it to identify the matching chart after importing into production. - -## Legacy Importing Datasources - -### From older versions of Superset to current version - -When using Superset version 4.x.x to import from an older version (2.x.x or 3.x.x) importing is supported as the command `legacy_import_datasources` and expects a JSON or directory of JSONs. The options are `-r` for recursive and `-u` for specifying a user. Example of legacy import without options: - -```bash -superset legacy_import_datasources -p -``` - -### From older versions of Superset to older versions - -When using an older Superset version (2.x.x & 3.x.x) of Superset, the command is `import_datasources`. ZIP and YAML files are supported and to switch between them the feature flag `VERSIONED_EXPORT` is used. When `VERSIONED_EXPORT` is `True`, `import_datasources` expects a ZIP file, otherwise YAML. Example: - -```bash -superset import_datasources -p -``` - -When `VERSIONED_EXPORT` is `False`, if you supply a path all files ending with **yaml** or **yml** will be parsed. You can apply -additional flags (e.g. to search the supplied path recursively): - -```bash -superset import_datasources -p -r -``` - -The sync flag **-s** takes parameters in order to sync the supplied elements with your file. Be -careful this can delete the contents of your meta database. Example: - -```bash -superset import_datasources -p -s columns,metrics -``` - -This will sync all metrics and columns for all datasources found in the `` in the -Superset meta database. This means columns and metrics not specified in YAML will be deleted. If you -would add tables to columns,metrics those would be synchronised as well. - -If you don’t supply the sync flag (**-s**) importing will only add and update (override) fields. -E.g. you can add a verbose_name to the column ds in the table random_time_series from the example -datasets by saving the following YAML to file and then running the **import_datasources** command. - -```yaml -databases: -- database_name: main - tables: - - table_name: random_time_series - columns: - - column_name: ds - verbose_name: datetime -``` diff --git a/docs/admin_docs/configuration/map-tiles.mdx b/docs/admin_docs/configuration/map-tiles.mdx deleted file mode 100644 index e83608c38bb..00000000000 --- a/docs/admin_docs/configuration/map-tiles.mdx +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: Map Tiles -sidebar_position: 12 -version: 1 ---- - -# Map tiles - -Superset uses OSM and Mapbox tiles by default. OSM is free but you still need setting your MAPBOX_API_KEY if you want to use mapbox maps. - -## Setting map tiles - -Map tiles can be set with `DECKGL_BASE_MAP` in your `superset_config.py` or `superset_config_docker.py` -For adding your own map tiles, you can use the following format. - -```python -DECKGL_BASE_MAP = [ - ['tile://https://your_personal_url/{z}/{x}/{y}.png', 'MyTile'] -] -``` -Openstreetmap tiles url can be added without prefix. -```python -DECKGL_BASE_MAP = [ - ['https://c.tile.openstreetmap.org/{z}/{x}/{y}.png', 'OpenStreetMap'] -] -``` - -Default values are: -```python -DECKGL_BASE_MAP = [ - ['https://tile.openstreetmap.org/{z}/{x}/{y}.png', 'Streets (OSM)'], - ['https://tile.osm.ch/osm-swiss-style/{z}/{x}/{y}.png', 'Topography (OSM)'], - ['mapbox://styles/mapbox/streets-v9', 'Streets'], - ['mapbox://styles/mapbox/dark-v9', 'Dark'], - ['mapbox://styles/mapbox/light-v9', 'Light'], - ['mapbox://styles/mapbox/satellite-streets-v9', 'Satellite Streets'], - ['mapbox://styles/mapbox/satellite-v9', 'Satellite'], - ['mapbox://styles/mapbox/outdoors-v9', 'Outdoors'], -] -``` - -It is possible to set only mapbox by removing osm tiles and other way around. - -:::warning -Setting `DECKGL_BASE_MAP` overwrite default values -::: - -After defining your map tiles, set them in these variables: -- `CORS_OPTIONS` -- `connect-src` of `TALISMAN_CONFIG` and `TALISMAN_CONFIG_DEV` variables. - -```python -ENABLE_CORS = True -CORS_OPTIONS: dict[Any, Any] = { - "origins": [ - "https://tile.openstreetmap.org", - "https://tile.osm.ch", - "https://your_personal_url/{z}/{x}/{y}.png", - ] -} - -. -. - -TALISMAN_CONFIG = { - "content_security_policy": { - ... - "connect-src": [ - "'self'", - "https://api.mapbox.com", - "https://events.mapbox.com", - "https://tile.openstreetmap.org", - "https://tile.osm.ch", - "https://your_personal_url/{z}/{x}/{y}.png", - ], - ... -} -``` diff --git a/docs/admin_docs/configuration/mcp-server.mdx b/docs/admin_docs/configuration/mcp-server.mdx deleted file mode 100644 index df299acaf8d..00000000000 --- a/docs/admin_docs/configuration/mcp-server.mdx +++ /dev/null @@ -1,845 +0,0 @@ ---- -title: MCP Server Deployment & Authentication -hide_title: true -sidebar_position: 14 -version: 1 ---- - - - -# MCP Server Deployment & Authentication - -Superset includes a built-in [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that lets AI assistants -- Claude, ChatGPT, and other MCP-compatible clients -- interact with your Superset instance. Through MCP, clients can list dashboards, query datasets, execute SQL, create charts, and more. - -This guide covers how to run, secure, and deploy the MCP server. - -:::tip Looking for user docs? -See **[Using AI with Superset](/user-docs/using-superset/using-ai-with-superset)** for a guide on what AI can do with Superset and how to connect your AI client. -::: - -```mermaid -flowchart LR - A["AI Client
(Claude, ChatGPT, etc.)"] -- "MCP protocol
(HTTP + JSON-RPC)" --> B["MCP Server
(:5008/mcp)"] - B -- "Superset context
(app, db, RBAC)" --> C["Superset
(:8088)"] - C --> D[("Database
(Postgres)")] -``` - ---- - -## Quick Start - -Get the MCP server running locally and connect an AI client in three steps. - -### 1. Start the MCP server - -The MCP server runs as a separate process alongside Superset: - -```bash -superset mcp run --host 127.0.0.1 --port 5008 -``` - -| Flag | Default | Description | -|------|---------|-------------| -| `--host` | `127.0.0.1` | Host to bind to | -| `--port` | `5008` | Port to bind to | -| `--debug` | off | Enable debug logging | - -The endpoint is available at `http://:/mcp`. - -### 2. Set a development user - -For local development, tell the MCP server which Superset user to impersonate (the user must already exist in your database): - -```python -# superset_config.py -MCP_DEV_USERNAME = "admin" -``` - -### 3. Connect an AI client - -Point your MCP client at the server. For **Claude Desktop**, edit the config file: - -- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` -- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` -- **Linux**: `~/.config/Claude/claude_desktop_config.json` - -```json -{ - "mcpServers": { - "superset": { - "url": "http://localhost:5008/mcp" - } - } -} -``` - -Restart Claude Desktop. The hammer icon in the chat bar confirms the connection. - -See [Connecting AI Clients](#connecting-ai-clients) for Claude Code, Claude Web, ChatGPT, and raw HTTP examples. - ---- - -## Prerequisites - -- Apache Superset 5.0+ running and accessible -- Python 3.10+ -- The `fastmcp` package (`pip install fastmcp`) - ---- - -## Authentication - -The MCP server supports multiple authentication methods depending on your deployment scenario. - -```mermaid -flowchart TD - R["Incoming MCP Request"] --> F{"MCP_AUTH_FACTORY
set?"} - F -- Yes --> CF["Custom Auth Provider"] - F -- No --> AE{"MCP_AUTH_ENABLED?"} - AE -- "True" --> JWT["JWT Validation"] - AE -- "False" --> DU["Dev Mode
(MCP_DEV_USERNAME)"] - - JWT --> ALG{"MCP_JWT_ALGORITHM"} - ALG -- "RS256 + JWKS" --> JWKS["Fetch keys from
MCP_JWKS_URI"] - ALG -- "RS256 + static" --> PK["Use
MCP_JWT_PUBLIC_KEY"] - ALG -- "HS256" --> SEC["Use
MCP_JWT_SECRET"] - - JWKS --> V["Validate token
(exp, iss, aud, scopes)"] - PK --> V - SEC --> V - V --> UR["Resolve Superset user
from token claims"] - UR --> OK["Authenticated request"] - CF --> OK - DU --> OK -``` - -### Development Mode (No Auth) - -Disable authentication and use a fixed user: - -```python -# superset_config.py -MCP_AUTH_ENABLED = False -MCP_DEV_USERNAME = "admin" -``` - -All operations run as the configured user. - -:::warning -Never use development mode in production. Always enable authentication for any internet-facing deployment. -::: - -### JWT Authentication - -For production, enable JWT-based authentication. The MCP server validates a Bearer token on every request. - -#### Option A: RS256 with JWKS endpoint - -The most common setup for OAuth 2.0 / OIDC providers that publish a JWKS (JSON Web Key Set) endpoint: - -```python -# superset_config.py -MCP_AUTH_ENABLED = True -MCP_JWT_ALGORITHM = "RS256" -MCP_JWKS_URI = "https://your-identity-provider.com/.well-known/jwks.json" -MCP_JWT_ISSUER = "https://your-identity-provider.com/" -MCP_JWT_AUDIENCE = "your-superset-instance" -``` - -#### Option B: RS256 with static public key - -Use this when you have a fixed RSA key pair (e.g., self-signed tokens): - -```python -# superset_config.py -MCP_AUTH_ENABLED = True -MCP_JWT_ALGORITHM = "RS256" -MCP_JWT_PUBLIC_KEY = """-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... ------END PUBLIC KEY-----""" -MCP_JWT_ISSUER = "your-issuer" -MCP_JWT_AUDIENCE = "your-audience" -``` - -#### Option C: HS256 with shared secret - -Use this when both the token issuer and the MCP server share a symmetric secret: - -```python -# superset_config.py -MCP_AUTH_ENABLED = True -MCP_JWT_ALGORITHM = "HS256" -MCP_JWT_SECRET = "your-shared-secret-key" -MCP_JWT_ISSUER = "your-issuer" -MCP_JWT_AUDIENCE = "your-audience" -``` - -:::warning -Store `MCP_JWT_SECRET` securely. Never commit it to version control. Use environment variables: -```python -import os -MCP_JWT_SECRET = os.environ.get("MCP_JWT_SECRET") -``` -::: - -#### JWT claims - -The MCP server validates these standard claims: - -| Claim | Config Key | Description | -|-------|-----------|-------------| -| `exp` | -- | Expiration time (always validated) | -| `iss` | `MCP_JWT_ISSUER` | Token issuer (optional but recommended) | -| `aud` | `MCP_JWT_AUDIENCE` | Token audience (optional but recommended) | -| `sub` | -- | Subject -- primary claim used to resolve the Superset user | - -#### User resolution - -After validating the token, the MCP server resolves a Superset username from the claims. It checks these in order, using the first non-empty value: - -1. `subject` -- the standard `sub` claim (via the access token object) -2. `client_id` -- for machine-to-machine tokens -3. `payload["sub"]` -- fallback to raw payload -4. `payload["email"]` -- email-based lookup -5. `payload["username"]` -- explicit username claim - -The resolved value must match a `username` in the Superset `ab_user` table. - -#### Scoped access - -Require specific scopes in the JWT to limit what MCP operations a token can perform: - -```python -# superset_config.py -MCP_REQUIRED_SCOPES = ["mcp:read", "mcp:write"] -``` - -Only tokens that include **all** required scopes are accepted. - -### Custom Auth Provider - -For advanced scenarios (e.g., a proprietary auth system), provide a factory function. This takes precedence over all built-in JWT configuration: - -```python -# superset_config.py -def my_custom_auth_factory(app): - """Return a FastMCP auth provider instance.""" - from fastmcp.server.auth.providers.jwt import JWTVerifier - return JWTVerifier( - jwks_uri="https://my-auth.example.com/.well-known/jwks.json", - issuer="https://my-auth.example.com/", - audience="superset-mcp", - ) - -MCP_AUTH_FACTORY = my_custom_auth_factory -``` - ---- - -## Connecting AI Clients - -### Claude Desktop - -**Local development (no auth):** - -```json -{ - "mcpServers": { - "superset": { - "url": "http://localhost:5008/mcp" - } - } -} -``` - -**With JWT authentication:** - -```json -{ - "mcpServers": { - "superset": { - "command": "npx", - "args": [ - "-y", - "mcp-remote@latest", - "http://your-superset-host:5008/mcp", - "--header", - "Authorization: Bearer YOUR_TOKEN" - ] - } - } -} -``` - -### Claude Code (CLI) - -Add to your project's `.mcp.json`: - -```json -{ - "mcpServers": { - "superset": { - "type": "url", - "url": "http://localhost:5008/mcp" - } - } -} -``` - -With authentication: - -```json -{ - "mcpServers": { - "superset": { - "type": "url", - "url": "http://localhost:5008/mcp", - "headers": { - "Authorization": "Bearer YOUR_TOKEN" - } - } - } -} -``` - -### Claude Web (claude.ai) - -1. Open [claude.ai](https://claude.ai) -2. Click the **+** button (or your profile icon) -3. Select **Connectors** -4. Click **Manage Connectors** > **Add custom connector** -5. Enter a name and your MCP URL (e.g., `https://your-superset-host/mcp`) -6. Click **Add** - -:::info -Custom connectors on Claude Web require a Pro, Max, Team, or Enterprise plan. -::: - -### ChatGPT - -1. Click your profile icon > **Settings** > **Apps and Connectors** -2. Enable **Developer Mode** in Advanced Settings -3. In the chat composer, press **+** > **Add sources** > **App** > **Connect more** > **Create app** -4. Enter a name and your MCP server URL -5. Click **I understand and continue** - -:::info -ChatGPT MCP connectors require a Pro, Team, Enterprise, or Edu plan. -::: - -### Direct HTTP requests - -Call the MCP server directly with any HTTP client: - -```bash -curl -X POST http://localhost:5008/mcp \ - -H 'Content-Type: application/json' \ - -H 'Authorization: Bearer YOUR_JWT_TOKEN' \ - -d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}' -``` - ---- - -## Deployment - -### Single Process - -The simplest setup: run the MCP server alongside Superset on the same host. - -```mermaid -flowchart TD - subgraph host["Host / VM"] - direction TB - S["Superset
:8088"] --> DB[("Postgres")] - M["MCP Server
:5008"] --> DB - end - C["AI Client"] -- "HTTPS" --> P["Reverse Proxy
(Nginx / Caddy)"] - U["Browser"] -- "HTTPS" --> P - P -- ":8088" --> S - P -- ":5008/mcp" --> M -``` - -**superset_config.py:** - -```python -MCP_SERVICE_HOST = "0.0.0.0" -MCP_SERVICE_PORT = 5008 -MCP_DEV_USERNAME = "admin" # or enable JWT auth - -# If behind a reverse proxy, set the public-facing URL so -# MCP-generated links (chart previews, SQL Lab URLs) resolve correctly: -MCP_SERVICE_URL = "https://superset.example.com" -``` - -**Start both processes:** - -```bash -# Terminal 1 -- Superset web server -superset run -h 0.0.0.0 -p 8088 - -# Terminal 2 -- MCP server -superset mcp run --host 0.0.0.0 --port 5008 -``` - -**Nginx reverse proxy with TLS:** - -```nginx -server { - listen 443 ssl; - server_name superset.example.com; - - ssl_certificate /path/to/cert.pem; - ssl_certificate_key /path/to/key.pem; - - # Superset web UI - location / { - proxy_pass http://127.0.0.1:8088; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - - # MCP endpoint - location /mcp { - proxy_pass http://127.0.0.1:5008/mcp; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header Authorization $http_authorization; - } -} -``` - -### Docker Compose - -Run Superset and the MCP server as separate containers sharing the same config: - -```yaml -# docker-compose.yml -services: - superset: - image: apache/superset:latest - ports: - - "8088:8088" - volumes: - - ./superset_config.py:/app/superset_config.py - environment: - - SUPERSET_CONFIG_PATH=/app/superset_config.py - - mcp: - image: apache/superset:latest - command: ["superset", "mcp", "run", "--host", "0.0.0.0", "--port", "5008"] - ports: - - "5008:5008" - volumes: - - ./superset_config.py:/app/superset_config.py - environment: - - SUPERSET_CONFIG_PATH=/app/superset_config.py - depends_on: - - superset -``` - -Both containers share the same `superset_config.py`, so authentication settings, database connections, and feature flags stay in sync. - -### Multi-Pod (Kubernetes) - -For high-availability deployments, configure Redis so that replicas share session state: - -```mermaid -flowchart TD - LB["Load Balancer"] --> M1["MCP Pod 1"] - LB --> M2["MCP Pod 2"] - LB --> M3["MCP Pod 3"] - M1 --> R[("Redis
(session store)")] - M2 --> R - M3 --> R - M1 --> DB[("Postgres")] - M2 --> DB - M3 --> DB -``` - -**superset_config.py:** - -```python -MCP_STORE_CONFIG = { - "enabled": True, - "CACHE_REDIS_URL": "redis://redis-host:6379/0", - "event_store_max_events": 100, - "event_store_ttl": 3600, -} -``` - -When `CACHE_REDIS_URL` is set, the MCP server uses a Redis-backed EventStore for session management, allowing replicas to share state. Without Redis, each pod manages its own in-memory sessions and stateful MCP interactions may fail when requests hit different replicas. - ---- - -## Configuration Reference - -All MCP settings go in `superset_config.py`. Defaults are defined in `superset/mcp_service/mcp_config.py`. - -### Core - -| Setting | Default | Description | -|---------|---------|-------------| -| `MCP_SERVICE_HOST` | `"localhost"` | Host the MCP server binds to | -| `MCP_SERVICE_PORT` | `5008` | Port the MCP server binds to | -| `MCP_SERVICE_URL` | `None` | Public base URL for MCP-generated links (set this when behind a reverse proxy) | -| `MCP_DEBUG` | `False` | Enable debug logging | -| `MCP_DEV_USERNAME` | -- | Superset username for development mode (no auth) | -| `MCP_RBAC_ENABLED` | `True` | Enforce Superset's role-based access control on MCP tool calls. When `True`, each tool checks that the authenticated user has the required FAB permission before executing. Disable only for testing or trusted-network deployments. | - -### Authentication - -| Setting | Default | Description | -|---------|---------|-------------| -| `MCP_AUTH_ENABLED` | `False` | Enable JWT authentication | -| `MCP_JWT_ALGORITHM` | `"RS256"` | JWT signing algorithm (`RS256` or `HS256`) | -| `MCP_JWKS_URI` | `None` | JWKS endpoint URL (RS256) | -| `MCP_JWT_PUBLIC_KEY` | `None` | Static RSA public key string (RS256) | -| `MCP_JWT_SECRET` | `None` | Shared secret string (HS256) | -| `MCP_JWT_ISSUER` | `None` | Expected `iss` claim | -| `MCP_JWT_AUDIENCE` | `None` | Expected `aud` claim | -| `MCP_REQUIRED_SCOPES` | `[]` | Required JWT scopes | -| `MCP_JWT_DEBUG_ERRORS` | `False` | Log detailed JWT errors server-side (never exposed in HTTP responses per RFC 6750) | -| `MCP_AUTH_FACTORY` | `None` | Custom auth provider factory `(flask_app) -> auth_provider`. Takes precedence over built-in JWT | -| `MCP_USER_RESOLVER` | `None` | Custom function `(app, access_token) -> username` to extract a Superset username from a validated JWT token. When `None`, the default resolver checks `preferred_username`, `username`, `email`, and `sub` claims in that order. | - -### Response Size Guard - -Limits response sizes to prevent exceeding LLM context windows: - -```python -MCP_RESPONSE_SIZE_CONFIG = { - "enabled": True, - "token_limit": 25000, - "warn_threshold_pct": 80, - "excluded_tools": [ - "health_check", - "get_chart_preview", - "generate_explore_link", - "open_sql_lab_with_context", - ], -} -``` - -| Key | Default | Description | -|-----|---------|-------------| -| `enabled` | `True` | Enable response size checking | -| `token_limit` | `25000` | Maximum estimated token count per response | -| `warn_threshold_pct` | `80` | Warn when response exceeds this percentage of the limit | -| `excluded_tools` | See above | Tools exempt from size checking (e.g., tools that return URLs, not data) | - -### Caching - -Optional response caching for read-heavy workloads. Requires Redis when used with multiple replicas. - -```python -MCP_CACHE_CONFIG = { - "enabled": False, - "CACHE_KEY_PREFIX": None, - "list_tools_ttl": 300, # 5 min - "list_resources_ttl": 300, - "list_prompts_ttl": 300, - "read_resource_ttl": 3600, # 1 hour - "get_prompt_ttl": 3600, - "call_tool_ttl": 3600, - "max_item_size": 1048576, # 1 MB - "excluded_tools": [ - "execute_sql", - "generate_dashboard", - "generate_chart", - "update_chart", - ], -} -``` - -| Key | Default | Description | -|-----|---------|-------------| -| `enabled` | `False` | Enable response caching | -| `CACHE_KEY_PREFIX` | `None` | Optional prefix for cache keys (useful for shared Redis) | -| `list_tools_ttl` | `300` | Cache TTL in seconds for `tools/list` | -| `list_resources_ttl` | `300` | Cache TTL for `resources/list` | -| `list_prompts_ttl` | `300` | Cache TTL for `prompts/list` | -| `read_resource_ttl` | `3600` | Cache TTL for `resources/read` | -| `get_prompt_ttl` | `3600` | Cache TTL for `prompts/get` | -| `call_tool_ttl` | `3600` | Cache TTL for `tools/call` | -| `max_item_size` | `1048576` | Maximum cached item size in bytes (1 MB) | -| `excluded_tools` | See above | Tools that are never cached (mutating or non-deterministic) | - -### Redis Store (Multi-Pod) - -Enables Redis-backed session and event storage for multi-replica deployments: - -```python -MCP_STORE_CONFIG = { - "enabled": False, - "CACHE_REDIS_URL": None, - "event_store_max_events": 100, - "event_store_ttl": 3600, -} -``` - -| Key | Default | Description | -|-----|---------|-------------| -| `enabled` | `False` | Enable Redis-backed store | -| `CACHE_REDIS_URL` | `None` | Redis connection URL (e.g., `redis://redis-host:6379/0`) | -| `event_store_max_events` | `100` | Maximum events retained per session | -| `event_store_ttl` | `3600` | Event TTL in seconds | - -### Tool Search - -By default the MCP server exposes a lightweight tool-search interface instead of advertising every tool at once. This reduces the initial context sent to the LLM by ~70%, which lowers cost and latency. The AI client discovers tools on demand by calling `search_tools` and then invokes them via `call_tool`. - -```python -MCP_TOOL_SEARCH_CONFIG = { - "enabled": True, - "strategy": "bm25", # "bm25" (natural language) or "regex" - "max_results": 5, - "always_visible": [ # Tools always listed (pinned) - "health_check", - "get_instance_info", - ], - "search_tool_name": "search_tools", - "call_tool_name": "call_tool", - "include_schemas": False, # False=summary mode (name + parameters_hint) - "compact_schemas": True, # Strip $defs (only applies when include_schemas=True) - "max_description_length": 300, -} -``` - -| Key | Default | Description | -|-----|---------|-------------| -| `enabled` | `True` | Enable tool search. When `False`, all tools are listed upfront | -| `strategy` | `"bm25"` | Search ranking algorithm. `"bm25"` supports natural language; `"regex"` supports pattern matching | -| `max_results` | `5` | Maximum tools returned per search query | -| `always_visible` | See above | Tools that always appear in `list_tools`, regardless of search | -| `include_schemas` | `False` | When `False` (default, "summary mode"), search results omit `inputSchema` entirely and include a lightweight `parameters_hint` listing top-level parameter names. Set to `True` to include the full `inputSchema` in search results. Full schemas are always used when a tool is actually invoked via `call_tool`. | -| `compact_schemas` | `True` | Strip `$defs` / `$ref` and replace with `{"type": "object"}` in search results to reduce token cost. Only takes effect when `include_schemas=True` — ignored in summary mode. | -| `max_description_length` | `300` | Truncate tool descriptions in search results (0 = no truncation). Applies in both summary and full-schema modes. | - -:::tip -Set `enabled: False` to revert to the traditional "show all tools at once" behavior, which some clients or workflows may prefer. -::: - -Tool search reduces the initial token cost from ~15–20K tokens (full catalog) down to ~4–5K tokens (pinned tools + search interface) — roughly 85% savings at the start of each conversation. - -### Session & CSRF - -These values are flat-merged into the Flask app config used by the MCP server process: - -```python -MCP_SESSION_CONFIG = { - "SESSION_COOKIE_HTTPONLY": True, - "SESSION_COOKIE_SECURE": False, - "SESSION_COOKIE_SAMESITE": "Lax", - "SESSION_COOKIE_NAME": "superset_session", - "PERMANENT_SESSION_LIFETIME": 86400, -} - -MCP_CSRF_CONFIG = { - "WTF_CSRF_ENABLED": True, - "WTF_CSRF_TIME_LIMIT": None, -} -``` - ---- - -## Access Control - -### RBAC Enforcement - -The MCP server respects Superset's full role-based access control (RBAC). Every authenticated user can only access the data and operations their Superset roles permit — the same rules that apply in the Superset UI apply through MCP. - -Each tool declares one or more required FAB permissions. The table below maps tool groups to their permission requirements: - -| Tool group | Required FAB permission | -|------------|------------------------| -| `list_charts`, `get_chart_info`, `get_chart_data`, `get_chart_preview`, `generate_chart`, `update_chart` | `can_read` on `Chart` (read), `can_write` on `Chart` (mutate) | -| `list_dashboards`, `get_dashboard_info`, `generate_dashboard`, `add_chart_to_existing_dashboard` | `can_read` on `Dashboard` (read), `can_write` on `Dashboard` (mutate) | -| `list_datasets`, `get_dataset_info`, `create_virtual_dataset` | `can_read` on `Dataset` (read), `can_write` on `Dataset` (mutate) | -| `list_databases`, `get_database_info` | `can_read` on `Database` | -| `execute_sql` | `can_execute_sql_query` on `SQLLab` | -| `open_sql_lab_with_context` | `can_read` on `SQLLab` | -| `save_sql_query` | `can_write` on `SavedQuery` | -| `health_check` | None (public) | - -To disable RBAC checking globally (for trusted-network deployments or testing), set: - -```python -# superset_config.py -MCP_RBAC_ENABLED = False -``` - -:::warning -Disabling RBAC removes all permission checks from MCP tool calls. Only do this on isolated, internal deployments where all MCP users are trusted admins. -::: - -### Audit Log - -All MCP tool calls are recorded in Superset's action log. You can view them at **Settings → Action Log** (admin only). Each log entry records: - -- The tool name (e.g., `mcp.generate_chart.db_write`) -- The authenticated user -- A timestamp - -This makes MCP activity fully auditable alongside regular Superset activity. The action log uses the same event logger as the rest of Superset, so existing log ingestion pipelines (e.g., sending logs to Elasticsearch or a SIEM) capture MCP events automatically. - -### Middleware Pipeline - -Every MCP request passes through a middleware stack before reaching the tool function. The default stack (assembled in `build_middleware_list()` in `server.py`) is: - -| Middleware | Purpose | Default | -|------------|---------|---------| -| `StructuredContentStripperMiddleware` | Strips `structuredContent` from responses for Claude.ai bridge compatibility | Enabled | -| `LoggingMiddleware` | Logs each tool call with user, parameters, and duration | Enabled | -| `GlobalErrorHandlerMiddleware` | Catches unhandled exceptions and sanitizes sensitive data before it reaches the client | Enabled | -| `ResponseSizeGuardMiddleware` | Estimates token count, warns at 80% of limit, blocks at limit | Enabled (configurable via `MCP_RESPONSE_SIZE_CONFIG`) | -| `ResponseCachingMiddleware` | Caches read-heavy tool responses (in-memory or Redis) | Disabled (enable via `MCP_CACHE_CONFIG`) | - -Additional middleware classes (`RateLimitMiddleware`, `FieldPermissionsMiddleware`, `PrivateToolMiddleware`) are implemented in `superset/mcp_service/middleware.py` but are not added to the default pipeline. They are available for operators who want to layer them in via a custom startup path. - -### Error Sanitization - -The `GlobalErrorHandlerMiddleware` automatically redacts sensitive information from all error messages before they reach the LLM client. The following are replaced with generic messages: - -- **Database connection strings** — replaced with a generic connection error message -- **API keys and tokens** — redacted from error traces -- **File system paths** — stripped to prevent information disclosure -- **IP addresses** — removed from error context - -This ensures that a misconfigured database connection or an unexpected exception never leaks credentials or internal topology to the LLM or its users. All regex patterns used for redaction are bounded to prevent ReDoS attacks. - ---- - -## Performance - -### Connection Pooling - -Each MCP server process maintains its own SQLAlchemy connection pool to the database. For multi-worker deployments, total open connections = **workers × pool size**. - -```python -# superset_config.py -SQLALCHEMY_POOL_SIZE = 5 -SQLALCHEMY_MAX_OVERFLOW = 10 -SQLALCHEMY_POOL_TIMEOUT = 30 -SQLALCHEMY_POOL_RECYCLE = 3600 # Recycle connections after 1 hour -``` - -For a 3-pod Kubernetes deployment with the defaults above, expect up to 3 × (5 + 10) = 45 connections. Size your database's `max_connections` accordingly. - -### Response Caching - -Enable response caching for read-heavy workloads (dashboards/datasets that don't change frequently). With the in-memory backend (default when `MCP_STORE_CONFIG` is disabled), caching is per-process. Use Redis-backed caching for consistent cache hits across multiple pods: - -```python -MCP_CACHE_CONFIG = {"enabled": True, "call_tool_ttl": 3600} -MCP_STORE_CONFIG = {"enabled": True, "CACHE_REDIS_URL": "redis://redis:6379/0"} -``` - -Mutating tools (`generate_chart`, `update_chart`, `execute_sql`, `generate_dashboard`) are always excluded from caching regardless of this setting. - ---- - -## Troubleshooting - -### Server won't start - -- Verify `fastmcp` is installed: `pip install fastmcp` -- Check that `MCP_DEV_USERNAME` is set if auth is disabled -- the server requires a user identity -- Confirm the port is not already in use: `lsof -i :5008` - -### 401 Unauthorized - -- Verify your JWT token has not expired (`exp` claim) -- Check that `MCP_JWT_ISSUER` and `MCP_JWT_AUDIENCE` match the token's `iss` and `aud` claims exactly -- For RS256 with JWKS: confirm the JWKS URI is reachable from the MCP server -- For RS256 with static key: confirm the public key string includes the `BEGIN`/`END` markers -- For HS256: confirm the secret matches between the token issuer and `MCP_JWT_SECRET` -- Enable `MCP_JWT_DEBUG_ERRORS = True` for detailed server-side logging (errors are never leaked to the client) - -### Tool not found - -- Ensure the MCP server and Superset share the same `superset_config.py` -- Check server logs at startup -- tool registration errors are logged with the tool name and reason - -### Client can't connect - -- Verify the MCP server URL is reachable from the client machine -- For Claude Desktop: fully quit the app (not just close the window) and restart after config changes -- For remote access: ensure your firewall and reverse proxy allow traffic to the MCP port -- Confirm the URL path ends with `/mcp` (e.g., `http://localhost:5008/mcp`) - -### Permission errors on tool calls - -- The MCP server enforces Superset's RBAC permissions -- the authenticated user must have the required roles -- In development mode, ensure `MCP_DEV_USERNAME` maps to a user with appropriate roles (e.g., Admin) -- Check `superset/security/manager.py` for the specific permission tuples required by each tool domain (e.g., `("can_execute_sql_query", "SQLLab")`) - -### Response too large - -- If a tool call returns an error about exceeding token limits, the response size guard is blocking an oversized result -- Reduce `page_size` or `limit` parameters, use `select_columns` to exclude large fields, or add filters to narrow results -- To adjust the threshold, change `token_limit` in `MCP_RESPONSE_SIZE_CONFIG` -- To disable the guard entirely, set `MCP_RESPONSE_SIZE_CONFIG = {"enabled": False}` - ---- - -## Audit Events - -All MCP tool calls are logged to Superset's event logger, the same system used by the web UI (viewable at **Settings → Action Log**). Each event captures: - -- **Action**: `mcp..` (e.g., `mcp.list_databases.query`) -- **User**: the resolved Superset username from the JWT or dev config -- **Timestamp**: when the operation ran - -This means MCP activity is auditable alongside normal user activity. No additional configuration is required — logging is on by default whenever the event logger is enabled in your Superset deployment. - -## Tool Pagination - -MCP list tools (`list_datasets`, `list_charts`, `list_dashboards`, `list_databases`) use **offset pagination** via `page` (1-based) and `page_size` parameters. Responses include `page`, `page_size`, `total_count`, `total_pages`, `has_previous`, and `has_next`. To iterate through all results: - -```python -# Example: fetch all charts across pages -all_charts = [] -page = 1 -while True: - result = mcp.list_charts(page=page, page_size=50) - all_charts.extend(result["charts"]) - if not result.get("has_next"): - break - page += 1 -``` - -## Security Best Practices - -- **Use TLS** for all production MCP endpoints -- place the server behind a reverse proxy with HTTPS -- **Enable JWT authentication** for any internet-facing deployment -- **RBAC enforcement** -- The MCP server respects Superset's role-based access control. Users can only access data their roles permit -- **Secrets management** -- Store `MCP_JWT_SECRET`, database credentials, and API keys in environment variables or a secrets manager, never in config files committed to version control -- **Scoped tokens** -- Use `MCP_REQUIRED_SCOPES` to limit what operations a token can perform -- **Network isolation** -- In Kubernetes, restrict MCP pod network policies to only allow traffic from your AI client endpoints -- Review the **[Security documentation](/developer-docs/extensions/security)** for additional extension security guidance - ---- - -## Next Steps - -- **[Using AI with Superset](/user-docs/using-superset/using-ai-with-superset)** -- What AI can do with Superset and how to get started -- **[MCP Integration](/developer-docs/extensions/mcp)** -- Build custom MCP tools and prompts via Superset extensions -- **[Security](/developer-docs/extensions/security)** -- Security best practices for extensions -- **[Deployment](/developer-docs/extensions/deployment)** -- Package and deploy Superset extensions diff --git a/docs/admin_docs/configuration/networking-settings.mdx b/docs/admin_docs/configuration/networking-settings.mdx deleted file mode 100644 index b37d93bedcc..00000000000 --- a/docs/admin_docs/configuration/networking-settings.mdx +++ /dev/null @@ -1,171 +0,0 @@ ---- -title: Network and Security Settings -sidebar_position: 7 -version: 1 ---- - -# Network and Security Settings - -## CORS - - -:::note -In Superset versions prior to `5.x` you have to install to install `flask-cors` with `pip install flask-cors` to enable CORS support. -::: - - -The following keys in `superset_config.py` can be specified to configure CORS: - -- `ENABLE_CORS`: Must be set to `True` in order to enable CORS -- `CORS_OPTIONS`: options passed to Flask-CORS - ([documentation](https://flask-cors.readthedocs.io/en/latest/api.html#extension)) - -## HTTP headers - -Note that Superset bundles [flask-talisman](https://pypi.org/project/talisman/) -Self-described as a small Flask extension that handles setting HTTP headers that can help -protect against a few common web application security issues. - -## HTML Embedding of Dashboards and Charts - -There are two ways to embed a dashboard: Using the [SDK](https://www.npmjs.com/package/@superset-ui/embedded-sdk) or embedding a direct link. Note that in the latter case everybody who knows the link is able to access the dashboard. - -### Embedding a Public Direct Link to a Dashboard - -This works by first changing the content security policy (CSP) of [flask-talisman](https://github.com/GoogleCloudPlatform/flask-talisman) to allow for certain domains to display Superset content. Then a dashboard can be made publicly accessible, i.e. **bypassing authentication**. Once made public, the dashboard's URL can be added to an iframe in another website's HTML code. - -#### Changing flask-talisman CSP - -Add to `superset_config.py` the entire `TALISMAN_CONFIG` section from `config.py` and include a `frame-ancestors` section: - -```python -TALISMAN_ENABLED = True -TALISMAN_CONFIG = { - "content_security_policy": { - ... - "frame-ancestors": ["*.my-domain.com", "*.another-domain.com"], - ... -``` - -Restart Superset for this configuration change to take effect. - -#### Making a Dashboard Public - -There are two approaches to making dashboards publicly accessible: - -**Option 1: Dataset-based access (simpler)** -1. Set `PUBLIC_ROLE_LIKE = "Public"` in `superset_config.py` -2. Grant the Public role access to the relevant datasets (Menu → Security → List Roles → Public) -3. All published dashboards using those datasets become visible to anonymous users - -**Option 2: Dashboard-level access (selective control)** -1. Set `PUBLIC_ROLE_LIKE = "Public"` in `superset_config.py` -2. Add the `'DASHBOARD_RBAC': True` [Feature Flag](/admin-docs/configuration/feature-flags) -3. Edit each dashboard's properties and add the "Public" role -4. Only dashboards with the Public role explicitly assigned are visible to anonymous users - -See the [Public role documentation](/admin-docs/security/#public) for more details. - -#### Embedding a Public Dashboard - -Now anybody can directly access the dashboard's URL. You can embed it in an iframe like so: - -```html - -``` - -#### Embedding a Chart - -A chart's embed code can be generated by going to a chart's edit view and then clicking at the top right on `...` > `Share` > `Embed code` - -### Enabling Embedding via the SDK - -Clicking on `...` next to `EDIT DASHBOARD` on the top right of the dashboard's overview page should yield a drop-down menu including the entry "Embed dashboard". - -To enable this entry, add the following line to the `.env` file: - -```text -SUPERSET_FEATURE_EMBEDDED_SUPERSET=true -``` - -### Hiding the Logout Button in Embedded Contexts - -When Superset is embedded in an application that manages authentication via SSO (OAuth2, SAML, or JWT), the logout button should be hidden since session management is handled by the parent application. - -To hide the logout button in embedded contexts, add to `superset_config.py`: - -```python -FEATURE_FLAGS = { - "DISABLE_EMBEDDED_SUPERSET_LOGOUT": True, -} -``` - -This flag only hides the logout button when Superset detects it is running inside an iframe. Users accessing Superset directly (not embedded) will still see the logout button regardless of this setting. - -:::note -When embedding with SSO, also set `SESSION_COOKIE_SAMESITE = 'None'` and `SESSION_COOKIE_SECURE = True`. See [Security documentation](/admin-docs/security/securing_superset) for details. -::: - -## CSRF settings - -Similarly, [flask-wtf](https://flask-wtf.readthedocs.io/en/0.15.x/config/) is used to manage -some CSRF configurations. If you need to exempt endpoints from CSRF (e.g. if you are -running a custom auth postback endpoint), you can add the endpoints to `WTF_CSRF_EXEMPT_LIST`: - -## SSH Tunneling - -1. Turn on feature flag - - Change [`SSH_TUNNELING`](https://github.com/apache/superset/blob/eb8386e3f0647df6d1bbde8b42073850796cc16f/superset/config.py#L489) to `True` - - If you want to add more security when establishing the tunnel we allow users to overwrite the `SSHTunnelManager` class [here](https://github.com/apache/superset/blob/eb8386e3f0647df6d1bbde8b42073850796cc16f/superset/config.py#L507) - - You can also set the [`SSH_TUNNEL_LOCAL_BIND_ADDRESS`](https://github.com/apache/superset/blob/eb8386e3f0647df6d1bbde8b42073850796cc16f/superset/config.py#L508) this the host address where the tunnel will be accessible on your VPC - -2. Create database w/ ssh tunnel enabled - - With the feature flag enabled you should now see ssh tunnel toggle. - - Click the toggle to enable SSH tunneling and add your credentials accordingly. - - Superset allows for two different types of authentication (Basic + Private Key). These credentials should come from your service provider. - -3. Verify data is flowing - - Once SSH tunneling has been enabled, go to SQL Lab and write a query to verify data is properly flowing. - -## Domain Sharding - -:::note -Domain Sharding is deprecated as of Superset 5.0.0, and will be removed in Superset 6.0.0. Please Enable HTTP2 to keep more open connections per domain. -::: - -Chrome allows up to 6 open connections per domain at a time. When there are more than 6 slices in -dashboard, a lot of time fetch requests are queued up and wait for next available socket. -[PR 5039](https://github.com/apache/superset/pull/5039) adds domain sharding to Superset, -and this feature will be enabled by configuration only (by default Superset doesn’t allow -cross-domain request). - -Add the following setting in your `superset_config.py` file: - -- `SUPERSET_WEBSERVER_DOMAINS`: list of allowed hostnames for domain sharding feature. - -Please create your domain shards as subdomains of your main domain for authorization to -work properly on new domains. For Example: - -- `SUPERSET_WEBSERVER_DOMAINS=['superset-1.mydomain.com','superset-2.mydomain.com','superset-3.mydomain.com','superset-4.mydomain.com']` - -or add the following setting in your `superset_config.py` file if domain shards are not subdomains of main domain. - -- `SESSION_COOKIE_DOMAIN = '.mydomain.com'` - -## Middleware - -Superset allows you to add your own middleware. To add your own middleware, update the -`ADDITIONAL_MIDDLEWARE` key in your `superset_config.py`. `ADDITIONAL_MIDDLEWARE` should be a list -of your additional middleware classes. - -For example, to use `AUTH_REMOTE_USER` from behind a proxy server like nginx, you have to add a -simple middleware class to add the value of `HTTP_X_PROXY_REMOTE_USER` (or any other custom header -from the proxy) to Gunicorn’s `REMOTE_USER` environment variable. diff --git a/docs/admin_docs/configuration/sql-templating.mdx b/docs/admin_docs/configuration/sql-templating.mdx deleted file mode 100644 index f2c21bcef79..00000000000 --- a/docs/admin_docs/configuration/sql-templating.mdx +++ /dev/null @@ -1,629 +0,0 @@ ---- -title: SQL Templating -hide_title: true -sidebar_position: 5 -version: 1 ---- - -# SQL Templating - -:::tip Looking to use SQL templating? -For a user-focused guide on writing Jinja templates in SQL Lab and virtual datasets, see the [SQL Templating User Guide](/user-docs/using-superset/sql-templating). This page covers administrator configuration options. -::: - -## Jinja Templates - -SQL Lab and Explore supports [Jinja templating](https://jinja.palletsprojects.com/en/2.11.x/) in queries. -To enable templating, the `ENABLE_TEMPLATE_PROCESSING` [feature flag](/admin-docs/configuration/configuring-superset#feature-flags) needs to be enabled in `superset_config.py`. - -:::warning[Security Warning] - -While powerful, this feature executes template code on the server. Within the Superset security model, this is **intended functionality**, as users with permissions to edit charts and virtual datasets are considered **trusted users**. - -If you grant these permissions to untrusted users, this feature can be exploited as a **Server-Side Template Injection (SSTI)** vulnerability. Do not enable `ENABLE_TEMPLATE_PROCESSING` unless you fully understand and accept the associated security risks. - -Additionally: - -- The `url_param()` macro allows URL parameters to influence the rendered SQL. Always validate or restrict `url_param()` values in your templates rather than interpolating them directly. -- `filter.get('val')` returns raw filter values without escaping. Use the safe helpers described below (`|where_in`, `| replace("'", "''")`) rather than concatenating values directly into SQL strings. - -::: - -:::tip -`ENABLE_TEMPLATE_PROCESSING` defaults to `False`. Only enable it if your deployment requires Jinja templates and all users with dataset/chart edit access are administrators or fully trusted internal users. -::: - -When templating is enabled, python code can be embedded in virtual datasets and -in Custom SQL in the filter and metric controls in Explore. By default, the following variables are -made available in the Jinja context: - -- `columns`: columns which to group by in the query -- `filter`: filters applied in the query -- `from_dttm`: start `datetime` value from the selected time range (`None` if undefined). **Note:** Only available in virtual datasets when a time range filter is applied in Explore/Chart views—not available in standalone SQL Lab queries. (deprecated beginning in version 5.0, use `get_time_filter` instead) -- `to_dttm`: end `datetime` value from the selected time range (`None` if undefined). **Note:** Only available in virtual datasets when a time range filter is applied in Explore/Chart views—not available in standalone SQL Lab queries. (deprecated beginning in version 5.0, use `get_time_filter` instead) -- `groupby`: columns which to group by in the query (deprecated) -- `metrics`: aggregate expressions in the query -- `row_limit`: row limit of the query -- `row_offset`: row offset of the query -- `table_columns`: columns available in the dataset -- `time_column`: temporal column of the query (`None` if undefined) -- `time_grain`: selected time grain (`None` if undefined) - -For example, to add a time range to a virtual dataset, you can write the following: - -```sql -SELECT * -FROM tbl -WHERE dttm_col > '{{ from_dttm }}' and dttm_col < '{{ to_dttm }}' -``` - -You can also use [Jinja's logic](https://jinja.palletsprojects.com/en/2.11.x/templates/#tests) -to make your query robust to clearing the timerange filter: - -```sql -SELECT * -FROM tbl -WHERE ( - {% if from_dttm is not none %} - dttm_col > '{{ from_dttm }}' AND - {% endif %} - {% if to_dttm is not none %} - dttm_col < '{{ to_dttm }}' AND - {% endif %} - 1 = 1 -) -``` - -The `1 = 1` at the end ensures a value is present for the `WHERE` clause even when -the time filter is not set. For many database engines, this could be replaced with `true`. - -Note that the Jinja parameters are called within _double_ brackets in the query and with -_single_ brackets in the logic blocks. - -### Understanding Context Availability - -Some Jinja variables like `from_dttm`, `to_dttm`, and `filter` are **only available when a chart or dashboard provides them**. They are populated from: - -- Time range filters applied in Explore/Chart views -- Dashboard native filters -- Filter components - -**These variables are NOT available in standalone SQL Lab queries** because there's no filter context. If you try to use `{{ from_dttm }}` directly in SQL Lab, you'll get an "undefined parameter" error. - -#### Testing Time-Filtered Queries in SQL Lab - -To test queries that use time variables in SQL Lab, you have several options: - -**Option 1: Use Jinja defaults (recommended)** - -```sql -SELECT * -FROM tbl -WHERE dttm_col > '{{ from_dttm | default("2024-01-01", true) }}' - AND dttm_col < '{{ to_dttm | default("2024-12-31", true) }}' -``` - -**Option 2: Use SQL Lab Parameters** - -Set parameters in the SQL Lab UI (Parameters menu): -```json -{ - "from_dttm": "2024-01-01", - "to_dttm": "2024-12-31" -} -``` - -**Option 3: Use `{% set %}` for testing** - -```sql -{% set from_dttm = "2024-01-01" %} -{% set to_dttm = "2024-12-31" %} -SELECT * -FROM tbl -WHERE dttm_col > '{{ from_dttm }}' AND dttm_col < '{{ to_dttm }}' -``` - -:::tip -When you save a SQL Lab query as a virtual dataset and use it in a chart with time filters, -the actual filter values will override any defaults or test values you set. -::: - -To add custom functionality to the Jinja context, you need to overload the default Jinja -context in your environment by defining the `JINJA_CONTEXT_ADDONS` in your superset configuration -(`superset_config.py`). Objects referenced in this dictionary are made available for users to use -where the Jinja context is made available. - -```python -JINJA_CONTEXT_ADDONS = { - 'my_crazy_macro': lambda x: x*2, -} -``` - -Default values for jinja templates can be specified via `Parameters` menu in the SQL Lab user interface. -In the UI you can assign a set of parameters as JSON - -```json -{ - "my_table": "foo" -} -``` - -The parameters become available in your SQL (example: `SELECT * FROM {{ my_table }}` ) by using Jinja templating syntax. -SQL Lab template parameters are stored with the dataset as `TEMPLATE PARAMETERS`. - -There is a special ``_filters`` parameter which can be used to test filters used in the jinja template. - -```json -{ - "_filters": [ - { - "col": "action_type", - "op": "IN", - "val": ["sell", "buy"] - } - ] -} -``` - -```sql -SELECT action, count(*) as times -FROM logs -WHERE action in {{ filter_values('action_type')|where_in }} -GROUP BY action -``` - -Note ``_filters`` is not stored with the dataset. It's only used within the SQL Lab UI. - -Besides default Jinja templating, SQL lab also supports self-defined template processor by setting -the `CUSTOM_TEMPLATE_PROCESSORS` in your superset configuration. The values in this dictionary -overwrite the default Jinja template processors of the specified database engine. The example below -configures a custom presto template processor which implements its own logic of processing macro -template with regex parsing. It uses the `$` style macro instead of `{{ }}` style in Jinja -templating. - -By configuring it with `CUSTOM_TEMPLATE_PROCESSORS`, a SQL template on a presto database is -processed by the custom one rather than the default one. - -```python -def DATE( - ts: datetime, day_offset: SupportsInt = 0, hour_offset: SupportsInt = 0 -) -> str: - """Current day as a string.""" - day_offset, hour_offset = int(day_offset), int(hour_offset) - offset_day = (ts + timedelta(days=day_offset, hours=hour_offset)).date() - return str(offset_day) - -class CustomPrestoTemplateProcessor(PrestoTemplateProcessor): - """A custom presto template processor.""" - - engine = "presto" - - def process_template(self, sql: str, **kwargs) -> str: - """Processes a sql template with $ style macro using regex.""" - # Add custom macros functions. - macros = { - "DATE": partial(DATE, datetime.utcnow()) - } # type: Dict[str, Any] - # Update with macros defined in context and kwargs. - macros.update(self.context) - macros.update(kwargs) - - def replacer(match): - """Expand $ style macros with corresponding function calls.""" - macro_name, args_str = match.groups() - args = [a.strip() for a in args_str.split(",")] - if args == [""]: - args = [] - f = macros[macro_name[1:]] - return f(*args) - - macro_names = ["$" + name for name in macros.keys()] - pattern = r"(%s)\s*\(([^()]*)\)" % "|".join(map(re.escape, macro_names)) - return re.sub(pattern, replacer, sql) - -CUSTOM_TEMPLATE_PROCESSORS = { - CustomPrestoTemplateProcessor.engine: CustomPrestoTemplateProcessor -} -``` - -SQL Lab also includes a live query validation feature with pluggable backends. You can configure -which validation implementation is used with which database engine by adding a block like the -following to your configuration file: - -```python -FEATURE_FLAGS = { - 'SQL_VALIDATORS_BY_ENGINE': { - 'presto': 'PrestoDBSQLValidator', - } -} -``` - -The available validators and names can be found in -[sql_validators](https://github.com/apache/superset/tree/master/superset/sql_validators). - -## Available Macros - -In this section, we'll walkthrough the pre-defined Jinja macros in Superset. - -### Current Username - -The `{{ current_username() }}` macro returns the `username` of the currently logged in user. - -If you have caching enabled in your Superset configuration, then by default the `username` value will be used -by Superset when calculating the cache key. A cache key is a unique identifier that determines if there's a -cache hit in the future and Superset can retrieve cached data. - -You can disable the inclusion of the `username` value in the calculation of the -cache key by adding the following parameter to your Jinja code: - -```python -{{ current_username(add_to_cache_keys=False) }} -``` - -### Current User ID - -The `{{ current_user_id() }}` macro returns the account ID of the currently logged in user. - -If you have caching enabled in your Superset configuration, then by default the account `id` value will be used -by Superset when calculating the cache key. A cache key is a unique identifier that determines if there's a -cache hit in the future and Superset can retrieve cached data. - -You can disable the inclusion of the account `id` value in the calculation of the -cache key by adding the following parameter to your Jinja code: - -```python -{{ current_user_id(add_to_cache_keys=False) }} -``` - -### Current User Email - -The `{{ current_user_email() }}` macro returns the email address of the currently logged in user. - -If you have caching enabled in your Superset configuration, then by default the email address value will be used -by Superset when calculating the cache key. A cache key is a unique identifier that determines if there's a -cache hit in the future and Superset can retrieve cached data. - -You can disable the inclusion of the email value in the calculation of the -cache key by adding the following parameter to your Jinja code: - -```python -{{ current_user_email(add_to_cache_keys=False) }} -``` - -### Current User Roles - -The `{{ current_user_roles() }}` macro returns an array of roles for the logged in user. - -If you have caching enabled in your Superset configuration, then by default the roles value will be used -by Superset when calculating the cache key. A cache key is a unique identifier that determines if there's a -cache hit in the future and Superset can retrieve cached data. - -You can disable the inclusion of the roles value in the calculation of the -cache key by adding the following parameter to your Jinja code: - -```python -{{ current_user_roles(add_to_cache_keys=False) }} -``` - -You can json-stringify the array by adding `|tojson` to your Jinja code: -```python -{{ current_user_roles()|tojson }} -``` - -You can use the `|where_in` filter to use your roles in a SQL statement. For example, if `current_user_roles()` returns `['admin', 'viewer']`, the following template: -```python -SELECT * FROM users WHERE role IN {{ current_user_roles()|where_in }} -``` - -Will be rendered as: -```sql -SELECT * FROM users WHERE role IN ('admin', 'viewer') -``` - -### Current User RLS Rules - -The `{{ current_user_rls_rules() }}` macro returns an array of RLS rules applied to the current dataset for the logged in user. - -If you have caching enabled in your Superset configuration, then the list of RLS Rules will be used -by Superset when calculating the cache key. A cache key is a unique identifier that determines if there's a -cache hit in the future and Superset can retrieve cached data. - -### Custom URL Parameters - -The `{{ url_param('custom_variable') }}` macro lets you define arbitrary URL -parameters and reference them in your SQL code. - -:::warning -Always treat `url_param()` values as untrusted input. Escaping behaviour varies by context and configuration, so do not rely on it. Restrict values to an explicit allowlist before using them in SQL: - -```sql -{% set cc = url_param('countrycode') %} -{% if cc not in ('US', 'ES', 'FR') %}{% set cc = 'US' %}{% endif %} -WHERE country_code = '{{ cc }}' -``` -::: - -Here's a concrete example: - -- You write the following query in SQL Lab: - - ```sql - SELECT count(*) - FROM ORDERS - WHERE country_code = '{{ url_param('countrycode') }}' - ``` - -- You're hosting Superset at the domain www.example.com and you send your - coworker in Spain the following SQL Lab URL `www.example.com/superset/sqllab?countrycode=ES` - and your coworker in the USA the following SQL Lab URL `www.example.com/superset/sqllab?countrycode=US` -- For your coworker in Spain, the SQL Lab query will be rendered as: - - ```sql - SELECT count(*) - FROM ORDERS - WHERE country_code = 'ES' - ``` - -- For your coworker in the USA, the SQL Lab query will be rendered as: - - ```sql - SELECT count(*) - FROM ORDERS - WHERE country_code = 'US' - ``` - -### Explicitly Including Values in Cache Key - -The `{{ cache_key_wrapper() }}` function explicitly instructs Superset to add a value to the -accumulated list of values used in the calculation of the cache key. - -This function is only needed when you want to wrap your own custom function return values -in the cache key. You can gain more context -[here](https://github.com/apache/superset/blob/efd70077014cbed62e493372d33a2af5237eaadf/superset/jinja_context.py#L133-L148). - -Note that this function powers the caching of the `user_id` and `username` values -in the `current_user_id()` and `current_username()` function calls (if you have caching enabled). - -### Filter Values - -You can retrieve the value for a specific filter as a list using `{{ filter_values() }}`. - -This is useful if: - -- You want to use a filter component to filter a query where the name of filter component column doesn't match the one in the select statement -- You want to have the ability to filter inside the main query for performance purposes - -Here's a concrete example: - -```sql -SELECT action, count(*) as times -FROM logs -WHERE - action in {{ filter_values('action_type')|where_in }} -GROUP BY action -``` - -There `where_in` filter converts the list of values from `filter_values('action_type')` into a string suitable for an `IN` expression. - -### Filters for a Specific Column - -The `{{ get_filters() }}` macro returns the filters applied to a given column. In addition to -returning the values (similar to how `filter_values()` does), the `get_filters()` macro -returns the operator specified in the Explore UI. - -This is useful if: - -- You want to handle more than the IN operator in your SQL clause -- You want to handle generating custom SQL conditions for a filter -- You want to have the ability to filter inside the main query for speed purposes - -:::warning -`filter.get('val')` returns the raw filter value without escaping. For multi-value filters, use the `|where_in` Jinja filter, which handles quoting safely. For single-value operators like `LIKE`, escape single quotes before interpolating: - -```sql -{%- if filter.get('op') == 'LIKE' -%} - AND full_name LIKE '{{ filter.get('val') | replace("'", "''") }}' -{%- endif -%} -``` -::: - -Here's a concrete example: - -```sql - WITH RECURSIVE - superiors(employee_id, manager_id, full_name, level, lineage) AS ( - SELECT - employee_id, - manager_id, - full_name, - 1 as level, - employee_id as lineage - FROM - employees - WHERE - 1=1 - - {# Render a blank line #} - {%- for filter in get_filters('full_name', remove_filter=True) -%} - - {%- if filter.get('op') == 'IN' -%} - AND - full_name IN {{ filter.get('val')|where_in }} - {%- endif -%} - - {%- if filter.get('op') == 'LIKE' -%} - AND - full_name LIKE '{{ filter.get('val') | replace("'", "''") }}' - {%- endif -%} - - {%- endfor -%} - UNION ALL - SELECT - e.employee_id, - e.manager_id, - e.full_name, - s.level + 1 as level, - s.lineage - FROM - employees e, - superiors s - WHERE s.manager_id = e.employee_id - ) - - SELECT - employee_id, manager_id, full_name, level, lineage - FROM - superiors - order by lineage, level -``` - -### Time Filter - -The `{{ get_time_filter() }}` macro returns the time filter applied to a specific column. This is useful if you want -to handle time filters inside the virtual dataset, as by default the time filter is placed on the outer query. This can -considerably improve performance, as many databases and query engines are able to optimize the query better -if the temporal filter is placed on the inner query, as opposed to the outer query. - -The macro takes the following parameters: - -- `column`: Name of the temporal column. Leave undefined to reference the time range from a Dashboard Native Time Range - filter (when present). -- `default`: The default value to fall back to if the time filter is not present, or has the value `No filter` -- `target_type`: The target temporal type as recognized by the target database (e.g. `TIMESTAMP`, `DATE` or - `DATETIME`). If `column` is defined, the format will default to the type of the column. This is used to produce - the format of the `from_expr` and `to_expr` properties of the returned `TimeFilter` object. -- `strftime`: format using the `strftime` method of `datetime` for custom time formatting. - ([see docs for valid format codes](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes)). - When defined `target_type` will be ignored. -- `remove_filter`: When set to true, mark the filter as processed, removing it from the outer query. Useful when a - filter should only apply to the inner query. - -The return type has the following properties: - -- `from_expr`: the start of the time filter (if any) -- `to_expr`: the end of the time filter (if any) -- `time_range`: The applied time range - -Here's a concrete example using the `logs` table from the Superset metastore: - -``` -{% set time_filter = get_time_filter("dttm", remove_filter=True) %} -{% set from_expr = time_filter.from_expr %} -{% set to_expr = time_filter.to_expr %} -{% set time_range = time_filter.time_range %} -SELECT - *, - '{{ time_range }}' as time_range -FROM logs -{% if from_expr or to_expr %}WHERE 1 = 1 -{% if from_expr %}AND dttm >= {{ from_expr }}{% endif %} -{% if to_expr %}AND dttm < {{ to_expr }}{% endif %} -{% endif %} -``` - -Assuming we are creating a table chart with a simple `COUNT(*)` as the metric with a time filter `Last week` on the -`dttm` column, this would render the following query on Postgres (note the formatting of the temporal filters, and -the absence of time filters on the outer query): - -``` -SELECT COUNT(*) AS count -FROM - (SELECT *, - 'Last week' AS time_range - FROM public.logs - WHERE 1 = 1 - AND dttm >= TO_TIMESTAMP('2024-08-27 00:00:00.000000', 'YYYY-MM-DD HH24:MI:SS.US') - AND dttm < TO_TIMESTAMP('2024-09-03 00:00:00.000000', 'YYYY-MM-DD HH24:MI:SS.US')) AS virtual_table -ORDER BY count DESC -LIMIT 1000; -``` - -When using the `default` parameter, the templated query can be simplified, as the endpoints will always be defined -(to use a fixed time range, you can also use something like `default="2024-08-27 : 2024-09-03"`) - -``` -{% set time_filter = get_time_filter("dttm", default="Last week", remove_filter=True) %} -SELECT - *, - '{{ time_filter.time_range }}' as time_range -FROM logs -WHERE - dttm >= {{ time_filter.from_expr }} - AND dttm < {{ time_filter.to_expr }} -``` - -### Datasets - -It's possible to query physical and virtual datasets using the `dataset` macro. This is useful if you've defined computed columns and metrics on your datasets, and want to reuse the definition in adhoc SQL Lab queries. - -To use the macro, first you need to find the ID of the dataset. This can be done by going to the view showing all the datasets, hovering over the dataset you're interested in, and looking at its URL. For example, if the URL for a dataset is https://superset.example.org/explore/?dataset_type=table&dataset_id=42 its ID is 42. - -Once you have the ID you can query it as if it were a table: - -```sql -SELECT * FROM {{ dataset(42) }} LIMIT 10 -``` - -If you want to select the metric definitions as well, in addition to the columns, you need to pass an additional keyword argument: - -```sql -SELECT * FROM {{ dataset(42, include_metrics=True) }} LIMIT 10 -``` - -Since metrics are aggregations, the resulting SQL expression will be grouped by all non-metric columns. You can specify a subset of columns to group by instead: - -```sql -SELECT * FROM {{ dataset(42, include_metrics=True, columns=["ds", "category"]) }} LIMIT 10 -``` - -### Metrics - -The `{{ metric('metric_key', dataset_id) }}` macro can be used to retrieve the metric SQL syntax from a dataset. This can be useful for different purposes: - -- Override the metric label in the chart level -- Combine multiple metrics in a calculation -- Retrieve a metric syntax in SQL lab -- Re-use metrics across datasets - -This macro avoids copy/paste, allowing users to centralize the metric definition in the dataset layer. - -The `dataset_id` parameter is optional, and if not provided Superset will use the current dataset from context (for example, when using this macro in the Chart Builder, by default the `macro_key` will be searched in the dataset powering the chart). -The parameter can be used in SQL Lab, or when fetching a metric from another dataset. - -## Available Filters - -Superset supports [builtin filters from the Jinja2 templating package](https://jinja.palletsprojects.com/en/stable/templates/#builtin-filters). Custom filters have also been implemented: - -### Where In -Parses a list into a SQL-compatible statement. This is useful with macros that return an array (for example the `filter_values` macro): - -``` -Dashboard filter with "First", "Second" and "Third" options selected -{{ filter_values('column') }} => ["First", "Second", "Third"] -{{ filter_values('column')|where_in }} => ('First', 'Second', 'Third') -``` - -By default, this filter returns `()` (as a string) in case the value is null. The `default_to_none` parameter can be se to `True` to return null in this case: - -``` -Dashboard filter without any value applied -{{ filter_values('column') }} => () -{{ filter_values('column')|where_in(default_to_none=True) }} => None -``` - -### To Datetime - -Loads a string as a `datetime` object. This is useful when performing date operations. For example: -``` -{% set from_expr = get_time_filter("dttm", strftime="%Y-%m-%d").from_expr %} -{% set to_expr = get_time_filter("dttm", strftime="%Y-%m-%d").to_expr %} -{% if (to_expr|to_datetime(format="%Y-%m-%d") - from_expr|to_datetime(format="%Y-%m-%d")).days > 100 %} - do something -{% else %} - do something else -{% endif %} -``` - -:::resources -- [Blog: Intro to Jinja Templating in Apache Superset](https://preset.io/blog/intro-jinja-templating-apache-superset/) -::: diff --git a/docs/admin_docs/configuration/theming.mdx b/docs/admin_docs/configuration/theming.mdx deleted file mode 100644 index 92e4fcd8e59..00000000000 --- a/docs/admin_docs/configuration/theming.mdx +++ /dev/null @@ -1,463 +0,0 @@ ---- -title: Theming -hide_title: true -sidebar_position: 12 -version: 1 ---- -# Theming Superset - -:::note -`apache-superset>=6.0` -::: - -Superset now rides on **Ant Design v5's token-based theming**. -Every Antd token works, plus a handful of Superset-specific ones for charts and dashboard chrome. - -## Managing Themes via UI - -Superset includes a built-in **Theme Management** interface accessible from the admin menu under **Settings > Themes**. - -### Creating a New Theme - -1. Navigate to **Settings > Themes** in the Superset interface -2. Click **+ Theme** to create a new theme -3. Use the [Ant Design Theme Editor](https://ant.design/theme-editor) to design your theme: - - Design your palette, typography, and component overrides - - Open the `CONFIG` modal and copy the JSON configuration -4. Paste the JSON into the theme definition field in Superset -5. Give your theme a descriptive name and save - -You can also extend with Superset-specific tokens (documented in the default theme object) before you import. - -### System Theme Administration - -When `ENABLE_UI_THEME_ADMINISTRATION = True` is configured, administrators can manage system-wide themes directly from the UI: - -#### Setting System Themes -- **System Default Theme**: Click the sun icon on any theme to set it as the system-wide default -- **System Dark Theme**: Click the moon icon on any theme to set it as the system dark mode theme -- **Automatic OS Detection**: When both default and dark themes are set, Superset automatically detects and applies the appropriate theme based on OS preferences - -#### Managing System Themes -- System themes are indicated with special badges in the theme list -- Only administrators with write permissions can modify system theme settings -- Removing a system theme designation reverts to configuration file defaults - -### Applying Themes to Dashboards - -Once created, themes can be applied to individual dashboards: -- Edit any dashboard and select your custom theme from the theme dropdown -- Each dashboard can have its own theme, allowing for branded or context-specific styling - -## Configuration Options - -### Python Configuration - -Configure theme behavior via `superset_config.py`: - -```python -# Enable UI-based theme administration for admins -ENABLE_UI_THEME_ADMINISTRATION = True - -# Optional: Set initial default themes via configuration -# These can be overridden via the UI when ENABLE_UI_THEME_ADMINISTRATION = True -THEME_DEFAULT = { - "token": { - "colorPrimary": "#2893B3", - "colorSuccess": "#5ac189", - # ... your theme JSON configuration - } -} - -# Optional: Dark theme configuration -THEME_DARK = { - "algorithm": "dark", - "token": { - "colorPrimary": "#2893B3", - # ... your dark theme overrides - } -} - -# To force a single theme on all users, set THEME_DARK = None -# When both themes are defined (via UI or config): -# - Users can manually switch between themes -# - OS preference detection is automatically enabled -``` - -### App Branding - -The application name shown in the browser title bar and navigation can be -set via the `brandAppName` theme token: - -```python -THEME_DEFAULT = { - "token": { - "brandAppName": "Acme Analytics", - # ... other tokens - } -} -``` - -Or in the theme CRUD UI JSON editor: - -```json -{ - "token": { - "brandAppName": "Acme Analytics" - } -} -``` - -The existing `APP_NAME` Python config key continues to work for backward compatibility. -`brandAppName` takes precedence when both are set, and allows different themes to carry different brand names. -Email and alert/report notification subjects are driven by backend settings such as -`EMAIL_REPORTS_SUBJECT_PREFIX` and `APP_NAME`, not by this theme token. - -### Migration from Configuration to UI - -When `ENABLE_UI_THEME_ADMINISTRATION = True`: - -1. System themes set via the UI take precedence over configuration file settings -2. The UI shows which themes are currently set as system defaults -3. Administrators can change system themes without restarting Superset -4. Configuration file themes serve as fallbacks when no UI themes are set - -### Theme Validation and Fallback - -Superset validates theme JSON when it is saved, either through the UI or via configuration. If a theme contains invalid tokens or an unrecognized structure, Superset logs a warning and falls back to the built-in default theme rather than applying a broken configuration. This prevents a bad theme from rendering the application unusable. - -The fallback order is: -1. **UI-configured system theme** (highest priority, if `ENABLE_UI_THEME_ADMINISTRATION = True`) -2. **`THEME_DEFAULT` / `THEME_DARK`** from `superset_config.py` -3. **Built-in Superset default theme** (always present as a safety net) - -If you see unexpected styling after a config change, check the Superset server logs for theme validation warnings. - -### Copying Themes Between Systems - -To export a theme for use in configuration files or another instance: - -1. Navigate to **Settings > Themes** and click the export icon on your desired theme -2. Extract the JSON configuration from the exported YAML file -3. Use this JSON in your `superset_config.py` or import it into another Superset instance - -## Theme Development Workflow - -1. **Design**: Use the [Ant Design Theme Editor](https://ant.design/theme-editor) to iterate on your design -2. **Test**: Create themes in Superset's CRUD interface for testing -3. **Apply**: Assign themes to specific dashboards or configure instance-wide -4. **Iterate**: Modify theme JSON directly in the CRUD interface or re-import from the theme editor - -## Custom Fonts - -Superset supports custom fonts through the theme configuration, allowing you to use branded or custom typefaces without rebuilding the application. - -### Default Fonts - -By default, Superset uses **Inter** for UI text and **IBM Plex Mono** for code (SQL editors, JSON fields, and other monospace contexts). Both fonts are bundled with the application via `@fontsource` packages and work offline without any external network calls. - -:::note -IBM Plex Mono replaced Fira Code as the default code font in Superset 6.1. If you have an existing theme that explicitly sets `fontFamilyCode: "Fira Code, ..."`, you may want to update it. -::: - -### Configuring Custom Fonts - -To use custom fonts, add font URLs to your theme configuration using the `fontUrls` token: - -```python -THEME_DEFAULT = { - "token": { - # Load fonts from external sources (e.g., Google Fonts, Adobe Fonts) - "fontUrls": [ - "https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;600;700&display=swap", - "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap", - ], - # Reference the loaded fonts - "fontFamily": "Roboto, -apple-system, BlinkMacSystemFont, sans-serif", - "fontFamilyCode": "JetBrains Mono, Monaco, monospace", - # ... other theme tokens - } -} - -# Update CSP to allow font sources -TALISMAN_CONFIG = { - "content_security_policy": { - "font-src": ["'self'", "https://fonts.googleapis.com", "https://fonts.gstatic.com"], - "style-src": ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"], - } -} -``` - -Or in the CRUD interface theme JSON: - -```json -{ - "token": { - "fontUrls": [ - "https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;600;700&display=swap" - ], - "fontFamily": "Roboto, -apple-system, BlinkMacSystemFont, sans-serif", - "fontFamilyCode": "JetBrains Mono, Monaco, monospace" - } -} -``` - -:::note -Font URLs are validated against a configurable allowlist. By default, fonts from `fonts.googleapis.com`, `fonts.gstatic.com`, and `use.typekit.net` are allowed. Configure `THEME_FONT_URL_ALLOWED_DOMAINS` to customize the allowed domains. -::: - -### Font Sources - -- **Google Fonts**: Free, CDN-hosted fonts with wide variety -- **Adobe Fonts**: Premium fonts (requires subscription and kit ID) -- **Self-hosted**: Place font files in `/static/assets/fonts/` and reference via CSS - -This feature works with the stock Docker image - no custom build required! - -## ECharts Configuration Overrides - -:::note -Available since Superset 6.0 -::: - -Superset provides fine-grained control over ECharts visualizations through theme-level configuration overrides. This allows you to customize the appearance and behavior of all ECharts-based charts without modifying individual chart configurations. - -### Global ECharts Overrides - -Apply settings to all ECharts visualizations using `echartsOptionsOverrides`: - -```python -THEME_DEFAULT = { - "token": { - "colorPrimary": "#2893B3", - # ... other Ant Design tokens - }, - "echartsOptionsOverrides": { - "grid": { - "left": "10%", - "right": "10%", - "top": "15%", - "bottom": "15%" - }, - "tooltip": { - "backgroundColor": "rgba(0, 0, 0, 0.8)", - "borderColor": "#ccc", - "textStyle": { - "color": "#fff" - } - }, - "legend": { - "textStyle": { - "fontSize": 14, - "fontWeight": "bold" - } - } - } -} -``` - -### Chart-Specific Overrides - -Target specific chart types using `echartsOptionsOverridesByChartType`: - -```python -THEME_DEFAULT = { - "token": { - "colorPrimary": "#2893B3", - # ... other tokens - }, - "echartsOptionsOverridesByChartType": { - "echarts_pie": { - "legend": { - "orient": "vertical", - "right": 10, - "top": "center" - } - }, - "echarts_timeseries": { - "xAxis": { - "axisLabel": { - "rotate": 45, - "fontSize": 12 - } - }, - "dataZoom": [{ - "type": "slider", - "show": True, - "start": 0, - "end": 100 - }] - }, - "echarts_bubble": { - "grid": { - "left": "15%", - "bottom": "20%" - } - } - } -} -``` - -### UI Configuration - -You can also configure ECharts overrides through the theme CRUD interface: - -```json -{ - "token": { - "colorPrimary": "#2893B3" - }, - "echartsOptionsOverrides": { - "grid": { - "left": "10%", - "right": "10%" - }, - "tooltip": { - "backgroundColor": "rgba(0, 0, 0, 0.8)" - } - }, - "echartsOptionsOverridesByChartType": { - "echarts_pie": { - "legend": { - "orient": "vertical", - "right": 10 - } - } - } -} -``` - -### Override Precedence - -The system applies overrides in the following order (last wins): - -1. **Base ECharts theme** - Default Superset styling -2. **Plugin options** - Chart-specific configurations -3. **Global overrides** - `echartsOptionsOverrides` -4. **Chart-specific overrides** - `echartsOptionsOverridesByChartType[chartType]` - -This ensures chart-specific overrides take precedence over global ones. - -### Common Chart Types - -Available chart types for `echartsOptionsOverridesByChartType`: - -- `echarts_timeseries` - Time series/line charts -- `echarts_pie` - Pie and donut charts -- `echarts_bubble` - Bubble/scatter charts -- `echarts_funnel` - Funnel charts -- `echarts_gauge` - Gauge charts -- `echarts_radar` - Radar charts -- `echarts_boxplot` - Box plot charts -- `echarts_treemap` - Treemap charts -- `echarts_sunburst` - Sunburst charts -- `echarts_graph` - Network/graph charts -- `echarts_sankey` - Sankey diagrams -- `echarts_heatmap` - Heatmaps -- `echarts_mixed_timeseries` - Mixed time series - -### Array Property Overrides - -Array properties (such as color palettes) are fully supported in overrides. Arrays are **replaced entirely** rather than merged, so specify the complete array: - -```python -THEME_DEFAULT = { - "token": { ... }, - "echartsOptionsOverrides": { - # Replace the default color palette for all ECharts visualizations - "color": ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b"] - } -} -``` - -### Best Practices - -1. **Start with global overrides** for consistent styling across all charts -2. **Use chart-specific overrides** for unique requirements per visualization type -3. **Test thoroughly** as overrides use deep merge for objects, but arrays are completely replaced — always specify the full array value -4. **Document your overrides** to help team members understand custom styling -5. **Consider performance** - complex overrides may impact chart rendering speed - -### Example: Corporate Branding - -```python -# Complete corporate theme with ECharts customization -THEME_DEFAULT = { - "token": { - "colorPrimary": "#1B4D3E", - "fontFamily": "Corporate Sans, Arial, sans-serif" - }, - "echartsOptionsOverrides": { - "grid": { - "left": "8%", - "right": "8%", - "top": "12%", - "bottom": "12%" - }, - "textStyle": { - "fontFamily": "Corporate Sans, Arial, sans-serif" - }, - "title": { - "textStyle": { - "color": "#1B4D3E", - "fontSize": 18, - "fontWeight": "bold" - } - } - }, - "echartsOptionsOverridesByChartType": { - "echarts_timeseries": { - "xAxis": { - "axisLabel": { - "color": "#666", - "fontSize": 11 - } - } - }, - "echarts_pie": { - "legend": { - "textStyle": { - "fontSize": 12 - }, - "itemGap": 20 - } - } - } -} -``` - -This feature provides powerful theming capabilities while maintaining the flexibility of ECharts' extensive configuration options. - -## Advanced Features - -- **System Themes**: Manage system-wide default and dark themes via UI or configuration -- **Per-Dashboard Theming**: Each dashboard can have its own visual identity -- **JSON Editor**: Edit theme configurations directly within Superset's interface -- **Custom Fonts**: Load external fonts via configuration without rebuilding -- **OS Dark Mode Detection**: Automatically switches themes based on system preferences -- **Theme Import/Export**: Share themes between instances via YAML files - -## API Access - -For programmatic theme management, Superset provides REST endpoints: - -- `GET /api/v1/theme/` - List all themes -- `POST /api/v1/theme/` - Create a new theme -- `PUT /api/v1/theme/{id}` - Update a theme -- `DELETE /api/v1/theme/{id}` - Delete a theme -- `PUT /api/v1/theme/{id}/set_system_default` - Set as system default theme (admin only) -- `PUT /api/v1/theme/{id}/set_system_dark` - Set as system dark theme (admin only) -- `DELETE /api/v1/theme/unset_system_default` - Remove system default designation -- `DELETE /api/v1/theme/unset_system_dark` - Remove system dark designation -- `GET /api/v1/theme/export/` - Export themes as YAML -- `POST /api/v1/theme/import/` - Import themes from YAML - -These endpoints require appropriate permissions and are subject to RBAC controls. - -:::resources -- [Video: Live Demo — Theming Apache Superset](https://www.youtube.com/watch?v=XsZAsO9tC3o) -- [CSS and Theming](https://docs.preset.io/docs/css-and-theming) - Additional theming techniques and CSS customization -- [Blog: Customizing Apache Superset Dashboards with CSS](https://preset.io/blog/customizing-superset-dashboards-with-css/) -- [Blog: Customizing Dashboards with CSS — Tips and Tricks](https://preset.io/blog/customizing-apache-superset-dashboards-with-css-additional-tips-and-tricks/) -- [Blog: Customizing Chart Colors](https://preset.io/blog/customizing-chart-colors-with-superset-and-preset/) -::: diff --git a/docs/admin_docs/configuration/timezones.mdx b/docs/admin_docs/configuration/timezones.mdx deleted file mode 100644 index 2e2a239f1e2..00000000000 --- a/docs/admin_docs/configuration/timezones.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Timezones -hide_title: true -sidebar_position: 6 -version: 1 ---- - -# Timezones - -There are four distinct timezone components which relate to Apache Superset, - -1. The timezone that the underlying data is encoded in. -2. The timezone of the database engine. -3. The timezone of the Apache Superset backend. -4. The timezone of the Apache Superset client. - -where if a temporal field (`DATETIME`, `TIME`, `TIMESTAMP`, etc.) does not explicitly define a timezone it defaults to the underlying timezone of the component. - -To help make the problem somewhat tractable—given that Apache Superset has no control on either how the data is ingested (1) or the timezone of the client (4)—from a consistency standpoint it is highly recommended that both (2) and (3) are configured to use the same timezone with a strong preference given to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) to ensure temporal fields without an explicit timestamp are not incorrectly coerced into the wrong timezone. Actually Apache Superset currently has implicit assumptions that timestamps are in UTC and thus configuring (3) to a non-UTC timezone could be problematic. - -To strive for data consistency (regardless of the timezone of the client) the Apache Superset backend tries to ensure that any timestamp sent to the client has an explicit (or semi-explicit as in the case with [Epoch time](https://en.wikipedia.org/wiki/Unix_time) which is always in reference to UTC) timezone encoded within. - -The challenge however lies with the slew of [database engines](/user-docs/databases#installing-drivers-in-docker) which Apache Superset supports and various inconsistencies between their [Python Database API (DB-API)](https://www.python.org/dev/peps/pep-0249/) implementations combined with the fact that we use [Pandas](https://pandas.pydata.org/) to read SQL into a DataFrame prior to serializing to JSON. Regrettably Pandas ignores the DB-API [type_code](https://www.python.org/dev/peps/pep-0249/#type-objects) relying by default on the underlying Python type returned by the DB-API. Currently only a subset of the supported database engines work correctly with Pandas, i.e., ensuring timestamps without an explicit timestamp are serialized to JSON with the server timezone, thus guaranteeing the client will display timestamps in a consistent manner irrespective of the client's timezone. - -For example the following is a comparison of MySQL and Presto, - -```python -import pandas as pd -from sqlalchemy import create_engine - -pd.read_sql_query( - sql="SELECT TIMESTAMP('2022-01-01 00:00:00') AS ts", - con=create_engine("mysql://root@localhost:3360"), -).to_json() - -pd.read_sql_query( - sql="SELECT TIMESTAMP '2022-01-01 00:00:00' AS ts", - con=create_engine("presto://localhost:8080"), -).to_json() -``` - -which outputs `{"ts":{"0":1640995200000}}` (which infers the UTC timezone per the Epoch time definition) and `{"ts":{"0":"2022-01-01 00:00:00.000"}}` (without an explicit timezone) respectively and thus are treated differently in JavaScript: - -```js -new Date(1640995200000) -> Sat Jan 01 2022 13:00:00 GMT+1300 (New Zealand Daylight Time) - -new Date("2022-01-01 00:00:00.000") -> Sat Jan 01 2022 00:00:00 GMT+1300 (New Zealand Daylight Time) -``` diff --git a/docs/admin_docs/index.md b/docs/admin_docs/index.md deleted file mode 100644 index 8b1c1de4b1a..00000000000 --- a/docs/admin_docs/index.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Admin Documentation -description: Administrator guides for installing, configuring, and managing Apache Superset ---- - - - -# Admin Documentation - -This section contains documentation for system administrators and operators who deploy and manage Apache Superset installations. - -## What's in this section - -- **[Installation](/admin-docs/installation/installation-methods)** - Deploy Superset using Docker, Kubernetes, or PyPI -- **[Configuration](/admin-docs/configuration/configuring-superset)** - Configure authentication, caching, feature flags, and more -- **[Security](/admin-docs/security/)** - Set up roles, permissions, and secure your deployment - -## Related - -- **[Database Drivers](/user-docs/databases/)** - See User Docs for database connection setup (admins may need to install drivers) - -## Looking for something else? - -- **[User Documentation](/user-docs/)** - Guides for analysts and business users -- **[Developer Documentation](/developer-docs)** - Contributing, extensions, and development guides diff --git a/docs/admin_docs/installation/architecture.mdx b/docs/admin_docs/installation/architecture.mdx deleted file mode 100644 index e531b30017e..00000000000 --- a/docs/admin_docs/installation/architecture.mdx +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: Architecture -hide_title: true -sidebar_position: 1 -version: 1 ---- - -import useBaseUrl from "@docusaurus/useBaseUrl"; - -# Architecture - -This page is meant to give new administrators an understanding of Superset's components. - -## Components - -A Superset installation is made up of these components: - -1. The Superset application itself -2. A metadata database -3. A caching layer (optional, but necessary for some features) -4. A worker & beat (optional, but necessary for some features) - -### Optional components and associated features - -The optional components above are necessary to enable these features: - -- [Alerts and Reports](/admin-docs/configuration/alerts-reports) -- [Caching](/admin-docs/configuration/cache) -- [Async Queries](/admin-docs/configuration/async-queries-celery/) -- [Dashboard Thumbnails](/admin-docs/configuration/cache/#caching-thumbnails) - -If you install with Kubernetes or Docker Compose, all of these components will be created. - -However, installing from PyPI only creates the application itself. Users installing from PyPI will need to configure a caching layer, worker, and beat on their own if they wish to enable the above features. Configuration of those components for a PyPI install is not currently covered in this documentation. - -Here are further details on each component. - -### The Superset Application - -This is the core application. Superset operates like this: - -- A user visits a chart or dashboard -- That triggers a SQL query to the data warehouse holding the underlying dataset -- The resulting data is served up in a data visualization -- The Superset application is comprised of the Python (Flask) backend application (server), API layer, and the React frontend, built via Webpack, and static assets needed for the application to work - -### Metadata Database - -This is where chart and dashboard definitions, user information, logs, etc. are stored. Superset is tested to work with PostgreSQL and MySQL databases as the metadata database (not be confused with a data source like your data warehouse, which could be a much greater variety of options like Snowflake, Redshift, etc.). - -Some installation methods like our Quickstart and PyPI come configured by default to use a SQLite on-disk database. And in a Docker Compose installation, the data would be stored in a PostgreSQL container volume. Neither of these cases are recommended for production instances of Superset. - -For production, a properly-configured, managed, standalone database is recommended. No matter what database you use, you should plan to back it up regularly. - -### Caching Layer - -The caching layer serves two main functions: - -- Store the results of queries to your data warehouse so that when a chart is loaded twice, it pulls from the cache the second time, speeding up the application and reducing load on your data warehouse. -- Act as a message broker for the worker, enabling the Alerts & Reports, async queries, and thumbnail caching features. - -Most people use Redis for their cache, but Superset supports other options too. See the [cache docs](/admin-docs/configuration/cache/) for more. - -### Worker and Beat - -This is one or more workers who execute tasks like run async queries or take snapshots of reports and send emails, and a "beat" that acts as the scheduler and tells workers when to perform their tasks. Most installations use Celery for these components. - -## Other components - -Other components can be incorporated into Superset. The best place to learn about additional configurations is the [Configuration page](/admin-docs/configuration/configuring-superset). For instance, you could set up a load balancer or reverse proxy to implement HTTPS in front of your Superset application, or specify a Mapbox URL to enable geospatial charts, etc. - -Superset won't even start without certain configuration settings established, so it's essential to review that page. diff --git a/docs/admin_docs/installation/docker-builds.mdx b/docs/admin_docs/installation/docker-builds.mdx deleted file mode 100644 index ffd523b906d..00000000000 --- a/docs/admin_docs/installation/docker-builds.mdx +++ /dev/null @@ -1,173 +0,0 @@ ---- -title: Docker Builds -hide_title: true -sidebar_position: 7 -version: 1 ---- - -# Docker builds, images and tags - -The Apache Superset community extensively uses Docker for development, release, -and productionizing Superset. This page details our Docker builds and tag naming -schemes to help users navigate our offerings. - -Images are built and pushed to the [Superset Docker Hub repository]( -https://hub.docker.com/r/apache/superset) using GitHub Actions. -Different sets of images are built and/or published at different times: - -- **Published releases** (`release`): published using - tags like `5.0.0` and the `latest` tag. -- **Pull request iterations** (`pull_request`): for each pull request, while - we actively build the docker to validate the build, we do - not publish those images for security reasons, we simply `docker build --load` -- **Merges to the main branch** (`push`): resulting in new SHAs, with tags - prefixed with `master` for the latest `master` version. - -## Build presets - -We have a set of build "presets" that each represent a combination of -parameters for the build, mostly pointing to either different target layer -for the build, and/or base image. - -Here are the build presets that are exposed through the `supersetbot docker` utility: - -- `lean`: The default Docker image, including both frontend and backend. Tags - without a build_preset are lean builds (ie: `latest`, `5.0.0`, `4.1.2`, ...). `lean` - builds do not contain database - drivers, meaning you need to install your own. That applies to analytics databases **AND - the metadata database**. You'll likely want to layer either `mysqlclient` or `psycopg2-binary` - depending on the metadata database you choose for your installation, plus the required - drivers to connect to your analytics database(s). -- `dev`: For development, with a headless browser, dev-related utilities and root access. This - includes some commonly used database drivers like `mysqlclient`, `psycopg2-binary` and - some other used for development/CI -- `py311`, e.g., Py311: Similar to lean but with a different Python version (in this example, 3.11). -- `ci`: For certain CI workloads. -- `websocket`: For Superset clusters supporting advanced features. -- `dockerize`: Used by Helm in initContainers to wait for database dependencies to be available. - -## Key tags examples - -- `latest`: The latest official release build -- `latest-dev`: the `-dev` image of the latest official release build, with a - headless browser and root access. -- `master`: The latest build from the `master` branch, implicitly the lean build - preset -- `master-dev`: Similar to `master` but includes a headless browser and root access. -- `pr-5252`: The latest commit in PR 5252. -- `30948dc401b40982cb7c0dbf6ebbe443b2748c1b-dev`: A build for - this specific SHA, which could be from a `master` merge, or release. -- `websocket-latest`: The WebSocket image for use in a Superset cluster. - -For insights or modifications to the build matrix and tagging conventions, -check the [supersetbot docker](https://github.com/apache-superset/supersetbot) -subcommand and the [docker.yml](https://github.com/apache/superset/blob/master/.github/workflows/docker.yml) -GitHub action. - -## Building your own production Docker image - -Every Superset deployment will require its own set of drivers depending on the data warehouse(s), -etc. so we recommend that users build their own Docker image by extending the `lean` image. - -Here's an example Dockerfile that does this. Follow the in-line comments to customize it for -your desired Superset version and database drivers. The comments also note that a certain feature flag will -have to be enabled in your config file. - -You would build the image with `docker build -t mysuperset:latest .` or `docker build -t ourcompanysuperset:5.0.0 .` - -```Dockerfile -# change this to apache/superset:5.0.0 or whatever version you want to build from; -# otherwise the default is the latest commit on GitHub master branch -FROM apache/superset:master - -USER root - -# Set environment variable for Playwright -ENV PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/playwright-browsers - -# Install packages using uv into the virtual environment -RUN . /app/.venv/bin/activate && \ - uv pip install \ - # install psycopg2 for using PostgreSQL metadata store - could be a MySQL package if using that backend: - psycopg2-binary \ - # add the driver(s) for your data warehouse(s), in this example we're showing for Microsoft SQL Server: - pymssql \ - # package needed for using single-sign on authentication: - Authlib \ - # openpyxl to be able to upload Excel files - openpyxl \ - # Pillow for Alerts & Reports to generate PDFs of dashboards - Pillow \ - # install Playwright for taking screenshots for Alerts & Reports. This assumes the feature flag PLAYWRIGHT_REPORTS_AND_THUMBNAILS is enabled - # That feature flag will default to True starting in 6.0.0 - # Playwright works only with Chrome. - # If you are still using Selenium instead of Playwright, you would instead install here the selenium package and a headless browser & webdriver - playwright \ - && playwright install-deps \ - && PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/playwright-browsers playwright install chromium - -# Switch back to the superset user -USER superset - -CMD ["/app/docker/entrypoints/run-server.sh"] -``` - -## Key ARGs in Dockerfile - -- `BUILD_TRANSLATIONS`: whether to build the translations into the image. For the - frontend build this tells webpack to strip out all locales other than `en` from - the `moment-timezone` library. For the backendthis skips compiling the - `*.po` translation files -- `DEV_MODE`: whether to skip the frontend build, this is used by our `docker-compose` dev setup - where we mount the local volume and build using `webpack` in `--watch` mode, meaning as you - alter the code in the local file system, webpack, from within a docker image used for this - purpose, will constantly rebuild the frontend as you go. This ARG enables the initial - `docker-compose` build to take much less time and resources -- `INCLUDE_CHROMIUM`: whether to include chromium in the backend build so that it can be - used as a headless browser for workloads related to "Alerts & Reports" and thumbnail generation -- `INCLUDE_FIREFOX`: same as above, but for firefox -- `PY_VER`: specifying the base image for the python backend, we don't recommend altering - this setting if you're not working on forwards or backwards compatibility - -## Caching - -To accelerate builds, we follow Docker best practices and use `apache/superset-cache`. - -## About database drivers - -Our docker images come with little to zero database driver support since -each environment requires different drivers, and maintaining a build with -wide database support would be both challenging (dozens of databases, -python drivers, and os dependencies) and inefficient (longer -build times, larger images, lower layer cache hit rate, ...). - -For production use cases, we recommend that you derive our `lean` image(s) and -add database support for the database you need. - -## On supporting different platforms (namely arm64 AND amd64) - -Currently all automated builds are multi-platform, supporting both `linux/arm64` -and `linux/amd64`. This enables higher level constructs like `helm` and -`docker compose` to point to these images and effectively be multi-platform -as well. - -Pull requests and master builds -are one-image-per-platform so that they can be parallelized and the -build matrix for those is more sparse as we don't need to build every -build preset on every platform, and generally can be more selective here. -For those builds, we suffix tags with `-arm` where it applies. - -### Working with Apple silicon - -Apple's current generation of computers uses ARM-based CPUs, and Docker -running on MACs seem to require `linux/arm64/v8` (at least one user's M2 was -configured in that way). Setting the environment -variable `DOCKER_DEFAULT_PLATFORM` to `linux/amd64` seems to function in -term of leveraging, and building upon the Superset builds provided here. - -```bash -export DOCKER_DEFAULT_PLATFORM=linux/amd64 -``` - -Presumably, `linux/arm64/v8` would be more optimized for this generation -of chips, but less compatible across the ARM ecosystem. diff --git a/docs/admin_docs/installation/docker-compose.mdx b/docs/admin_docs/installation/docker-compose.mdx deleted file mode 100644 index 04d14915860..00000000000 --- a/docs/admin_docs/installation/docker-compose.mdx +++ /dev/null @@ -1,286 +0,0 @@ ---- -title: Docker Compose -hide_title: true -sidebar_position: 5 -version: 1 ---- - -import useBaseUrl from "@docusaurus/useBaseUrl"; - -# Using Docker Compose - - -

- -:::caution -Since `docker compose` is primarily designed to run a set of containers on **a single host** -and can't support requirements for **high availability**, we do not support nor recommend -using our `docker compose` constructs to support production-type use-cases. For single host -environments, we recommend using [minikube](https://minikube.sigs.k8s.io/docs/start/) along -with our [installing on k8s](https://superset.apache.org/admin-docs/installation/running-on-kubernetes) -documentation. -::: - -As mentioned in our [quickstart guide](/user-docs/quickstart), the fastest way to try -Superset locally is using Docker Compose on a Linux or Mac OSX -computer. Superset does not have official support for Windows. It's also the easiest -way to launch a fully functioning **development environment** quickly. - -Note that there are 4 major ways we support to run `docker compose`: - -1. **docker-compose.yml:** for interactive development, where we mount your local folder with the - frontend/backend files that you can edit and experience the changes you - make in the app in real time -1. **docker-compose-light.yml:** a lightweight configuration with minimal services (database, - Superset app, and frontend dev server) for development. Uses in-memory caching instead of Redis - and is designed for running multiple instances simultaneously -1. **docker-compose-non-dev.yml** where we just build a more immutable image based on the - local branch and get all the required images running. Changes in the local branch - at the time you fire this up will be reflected, but changes to the code - while `up` won't be reflected in the app -1. **docker-compose-image-tag.yml** where we fetch an image from docker-hub say for the - `5.0.0` release for instance, and fire it up so you can try it. Here what's in - the local branch has no effects on what's running, we just fetch and run - pre-built images from docker-hub. For `docker compose` to work along with the - Postgres image it boots up, you'll want to point to a `-dev`-suffixed TAG, as in - `export TAG=5.0.0-dev` or `export TAG=4.1.2-dev`, with `latest-dev` being the default. - The `dev` builds include the `psycopg2-binary` required to connect - to the Postgres database launched as part of the `docker compose` builds. - -More on these approaches after setting up the requirements for either. - -## Requirements - -Note that this documentation assumes that you have [Docker](https://www.docker.com) and -[git](https://git-scm.com/) installed. Note also that we used to use `docker-compose` but that -is on the path to deprecation so we now use `docker compose` instead. - -## 1. Clone Superset's GitHub repository - -[Clone Superset's repo](https://github.com/apache/superset) in your terminal with the -following command: - -```bash -git clone --depth=1 https://github.com/apache/superset.git -``` - -Once that command completes successfully, you should see a new `superset` folder in your -current directory. - -## 2. Launch Superset Through Docker Compose - -First let's assume you're familiar with `docker compose` mechanics. Here we'll refer generally -to `docker compose up` even though in some cases you may want to force a check for newer remote -images using `docker compose pull`, force a build with `docker compose build` or force a build -on latest base images using `docker compose build --pull`. In most cases though, the simple -`up` command should do just fine. Refer to docker compose docs for more information on the topic. - -### Option #1 - for an interactive development environment - -```bash -# The --build argument insures all the layers are up-to-date -docker compose up --build -``` - -:::tip -When running in development mode the `superset-node` -container needs to finish building assets in order for the UI to render properly. If you would just -like to try out Superset without making any code changes follow the steps documented for -`production` or a specific version below. -::: - -:::tip -By default, we mount the local superset-frontend folder here and run `npm install` as well -as `npm run dev` which triggers webpack to compile/bundle the frontend code. Depending -on your local setup, especially if you have less than 16GB of memory, it may be very slow to -perform those operations. In this case, we recommend you set the env var -`BUILD_SUPERSET_FRONTEND_IN_DOCKER` to `false`, and to run this locally instead in a terminal. -Simply trigger `npm i && npm run dev`, this should be MUCH faster. -::: - -:::tip -Sometimes, your npm-related state can get out-of-wack, running `npm run prune` from -the `superset-frontend/` folder will nuke the various' packages `node_module/` folders -and help you start fresh. In the context of `docker compose` setting -`export NPM_RUN_PRUNE=true` prior to running `docker compose up` will trigger that -from within docker. This will slow down the startup, but will fix various npm-related issues. -::: - -### Option #2 - lightweight development with multiple instances - -For a lighter development setup that uses fewer resources and supports running multiple instances: - -```bash -# Single lightweight instance (default port 9001) -docker compose -f docker-compose-light.yml up - -# Multiple instances with different ports -NODE_PORT=9001 docker compose -p superset-1 -f docker-compose-light.yml up -NODE_PORT=9002 docker compose -p superset-2 -f docker-compose-light.yml up -NODE_PORT=9003 docker compose -p superset-3 -f docker-compose-light.yml up -``` - -This configuration includes: -- PostgreSQL database (internal network only) -- Superset application server -- Frontend development server with webpack hot reloading -- In-memory caching (no Redis) -- Isolated volumes and networks per instance - -Access each instance at `http://localhost:{NODE_PORT}` (e.g., `http://localhost:9001`). - -### Option #3 - build a set of immutable images from the local branch - -```bash -docker compose -f docker-compose-non-dev.yml up -``` - -### Option #4 - boot up an official release - -```bash -# Set the version you want to run -export TAG=5.0.0 -# Fetch the tag you're about to check out (assuming you shallow-cloned the repo) -git fetch --depth=1 origin tag $TAG -# Could also fetch all tags too if you've got bandwidth to spare -# git fetch --tags -# Checkout the corresponding git ref -git checkout $TAG -# Fire up docker compose -docker compose -f docker-compose-image-tag.yml up -``` - -Here various release tags, github SHA, and latest `master` can be referenced by the TAG env var. -Refer to the docker-related documentation to learn more about existing tags you can point to -from Docker Hub. - -:::note -For option #2 and #3, we recommend checking out the release tag from the git repository -(ie: `git checkout 5.0.0`) for more guaranteed results. This ensures that the `docker-compose.*.yml` -configurations and that the mounted `docker/` scripts are in sync with the image you are -looking to fire up. -::: - -## `docker compose` tips & configuration - -:::caution -All of the content belonging to a Superset instance - charts, dashboards, users, etc. - is stored in -its metadata database. In production, this database should be backed up. The default installation -with docker compose will store that data in a PostgreSQL database contained in a Docker -[volume](https://docs.docker.com/storage/volumes/), which is not backed up. - -Again, **THE DOCKER-COMPOSE INSTALLATION IS NOT PRODUCTION-READY OUT OF THE BOX.** - -::: - -You should see a stream of logging output from the containers being launched on your machine. Once -this output slows, you should have a running instance of Superset on your local machine! To avoid -the wall of text on future runs, add the `-d` option to the end of the `docker compose up` command. - -### Configuring Further - -The following is for users who want to configure how Superset runs in Docker Compose; otherwise, you -can skip to the next section. - -You can install additional python packages and apply config overrides by following the steps -mentioned in [docker/README.md](https://github.com/apache/superset/tree/master/docker#configuration) - -Note that `docker/.env` sets the default environment variables for all the docker images -used by `docker compose`, and that `docker/.env-local` can be used to override those defaults. -Also note that `docker/.env-local` is referenced in our `.gitignore`, -preventing developers from risking committing potentially sensitive configuration to the repository. - -One important variable is `SUPERSET_LOAD_EXAMPLES` which determines whether the `superset_init` -container will populate example data and visualizations into the metadata database. These examples -are helpful for learning and testing out Superset but unnecessary for experienced users and -production deployments. The loading process can sometimes take a few minutes and a good amount of -CPU, so you may want to disable it on a resource-constrained device. - -For more advanced or dynamic configurations that are typically managed in a `superset_config.py` file -located in your `PYTHONPATH`, note that it can be done by providing a -`docker/pythonpath_dev/superset_config_docker.py` that will be ignored by git -(preventing you to commit/push your local configuration back to the repository). -The mechanics of this are in `docker/pythonpath_dev/superset_config.py` where you can see -that the logic runs a `from superset_config_docker import *` - -:::note -Users often want to connect to other databases from Superset. Currently, the easiest way to -do this is to modify the `docker-compose-non-dev.yml` file and add your database as a service that -the other services depend on (via `x-superset-depends-on`). Others have attempted to set -`network_mode: host` on the Superset services, but these generally break the installation, -because the configuration requires use of the Docker Compose DNS resolver for the service names. -If you have a good solution for this, let us know! -::: - -:::note -Superset uses [Scarf Gateway](https://about.scarf.sh/scarf-gateway) to collect telemetry -data. Knowing the installation counts for different Superset versions informs the project's -decisions about patching and long-term support. Scarf purges personally identifiable information -(PII) and provides only aggregated statistics. - -To opt-out of this data collection for packages downloaded through the Scarf Gateway by your docker -compose based installation, edit the `x-superset-image:` line in your `docker-compose.yml` and -`docker-compose-non-dev.yml` files, replacing `apachesuperset.docker.scarf.sh/apache/superset` with -`apache/superset` to pull the image directly from Docker Hub. - -To disable the Scarf telemetry pixel, set the `SCARF_ANALYTICS` environment variable to `False` in -your terminal and/or in your `docker/.env` file. -::: - -## 3. Log in to Superset - -Your local Superset instance also includes a Postgres server to store your data and is already -pre-loaded with some example datasets that ship with Superset. You can access Superset now via your -web browser by visiting `http://localhost:8088`. Note that many browsers now default to `https` - if -yours is one of them, please make sure it uses `http`. - -Log in with the default username and password: - -```bash -username: admin -``` - -```bash -password: admin -``` - -## 4. Connecting Superset to your local database instance - -When running Superset using `docker` or `docker compose` it runs in its own docker container, as if -the Superset was running in a separate machine entirely. Therefore attempts to connect to your local -database with the hostname `localhost` won't work as `localhost` refers to the docker container -Superset is running in, and not your actual host machine. Fortunately, docker provides an easy way -to access network resources in the host machine from inside a container, and we will leverage this -capability to connect to our local database instance. - -Here the instructions are for connecting to postgresql (which is running on your host machine) from -Superset (which is running in its docker container). Other databases may have slightly different -configurations but gist would be same and boils down to 2 steps - - -1. **(Mac users may skip this step)** Configuring the local postgresql/database instance to accept -public incoming connections. By default, postgresql only allows incoming connections from -`localhost` and under Docker, unless you use `--network=host`, `localhost` will refer to different -endpoints on the host machine and in a docker container respectively. Allowing postgresql to accept -connections from the Docker involves making one-line changes to the files `postgresql.conf` and -`pg_hba.conf`; you can find helpful links tailored to your OS / PG version on the web easily for -this task. For Docker it suffices to only whitelist IPs `172.0.0.0/8` instead of `*`, but in any -case you are _warned_ that doing this in a production database _may_ have disastrous consequences as -you are opening your database to the public internet. -1. Instead of `localhost`, try using `host.docker.internal` (Mac users, Ubuntu) or `172.18.0.1` -(Linux users) as the hostname when attempting to connect to the database. This is a Docker internal -detail -- what is happening is that, in Mac systems, Docker Desktop creates a dns entry for the -hostname `host.docker.internal` which resolves to the correct address for the host machine, whereas -in Linux this is not the case (at least by default). If neither of these 2 hostnames work then you -may want to find the exact hostname you want to use, for that you can do `ifconfig` or -`ip addr show` and look at the IP address of `docker0` interface that must have been created by -Docker for you. Alternately if you don't even see the `docker0` interface try (if needed with sudo) -`docker network inspect bridge` and see if there is an entry for `"Gateway"` and note the IP -address. - -## 4. To build or not to build - -When running `docker compose up`, docker will build what is required behind the scene, but -may use the docker cache if assets already exist. Running `docker compose build` prior to -`docker compose up` or the equivalent shortcut `docker compose up --build` ensures that your -docker images match the definition in the repository. This should only apply to the main -docker-compose.yml file (default) and not to the alternative methods defined above. diff --git a/docs/admin_docs/installation/installation-methods.mdx b/docs/admin_docs/installation/installation-methods.mdx deleted file mode 100644 index 0e8f11b7d57..00000000000 --- a/docs/admin_docs/installation/installation-methods.mdx +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Installation Methods -hide_title: true -sidebar_position: 2 -version: 1 ---- - -import useBaseUrl from "@docusaurus/useBaseUrl"; - -# Installation Methods - -How should you install Superset? Here's a comparison of the different options. It will help if you've first read the [Architecture](/admin-docs/installation/architecture) page to understand Superset's different components. - -The fundamental trade-off is between you needing to do more of the detail work yourself vs. using a more complex deployment route that handles those details. - -## [Docker Compose](/admin-docs/installation/docker-compose) - -**Summary:** This takes advantage of containerization while remaining simpler than Kubernetes. This is the best way to try out Superset; it's also useful for developing & contributing back to Superset. - -If you're not just demoing the software, you'll need a moderate understanding of Docker to customize your deployment and avoid a few risks. Even when fully-optimized this is not as robust a method as Kubernetes when it comes to large-scale production deployments. - -You manage a superset-config.py file and a docker-compose.yml file. Docker Compose brings up all the needed services - the Superset application, a Postgres metadata DB, Redis cache, Celery worker and beat. They are automatically connected to each other. - -**Responsibilities** - -You will need to back up your metadata DB. That could mean backing up the service running as a Docker container and its volume; ideally you are running Postgres as a service outside of that container and backing up that service. - -You will also need to extend the Superset docker image. The default `lean` images do not contain drivers needed to access your metadata database (Postgres or MySQL), nor to access your data warehouse, nor the headless browser needed for Alerts & Reports. You could run a `-dev` image while demoing Superset, which has some of this, but you'll still need to install the driver for your data warehouse. The `-dev` images run as root, which is not recommended for production. - -Ideally you will build your own image of Superset that extends `lean`, adding what your deployment needs. See [Building your own production Docker image](/admin-docs/installation/docker-builds/#building-your-own-production-docker-image). - -## [Kubernetes (K8s)](/admin-docs/installation/kubernetes) - -**Summary:** This is the best-practice way to deploy a production instance of Superset, but has the steepest skill requirement - someone who knows Kubernetes. - -You will deploy Superset into a K8s cluster. The most common method is using the community-maintained Helm chart, though work is now underway to implement [SIP-149 - a Kubernetes Operator for Superset](https://github.com/apache/superset/issues/31408). - -A K8s deployment can scale up and down based on usage and deploy rolling updates with zero downtime - features that big deployments appreciate. - -**Responsibilities** - -You will need to build your own Docker image, and back up your metadata DB, both as described in Docker Compose above. You'll also need to customize your Helm chart values and deploy and maintain your Kubernetes cluster. - -## [PyPI (Python)](/admin-docs/installation/pypi) - -**Summary:** This is the only method that requires no knowledge of containers. It requires the most hands-on work to deploy, connect, and maintain each component. - -You install Superset as a Python package and run it that way, providing your own metadata database. Superset has documentation on how to install this way, but it is updated infrequently. - -If you want caching, you'll set up Redis or RabbitMQ. If you want Alerts & Reports, you'll set up Celery. - -**Responsibilities** - -You will need to get the component services running and communicating with each other. You'll need to arrange backups of your metadata database. - -When upgrading, you'll need to manage the system environment and packages and ensure all components have functional dependencies. diff --git a/docs/admin_docs/installation/kubernetes.mdx b/docs/admin_docs/installation/kubernetes.mdx deleted file mode 100644 index c18bf803f92..00000000000 --- a/docs/admin_docs/installation/kubernetes.mdx +++ /dev/null @@ -1,451 +0,0 @@ ---- -title: Kubernetes -hide_title: true -sidebar_position: 3 -version: 1 ---- - -import useBaseUrl from "@docusaurus/useBaseUrl"; - -# Installing on Kubernetes - - -

- -Running Superset on Kubernetes is supported with the provided [Helm](https://helm.sh/) chart -found in the official [Superset helm repository](https://apache.github.io/superset/index.yaml). - -## Prerequisites - -- A Kubernetes cluster -- Helm installed - -:::note -For simpler, single host environments, we recommend using -[minikube](https://minikube.sigs.k8s.io/docs/start/) which is easy to setup on many platforms -and works fantastically well with the Helm chart referenced here. -::: - -## Running - -1. Add the Superset helm repository - -```sh -helm repo add superset https://apache.github.io/superset -"superset" has been added to your repositories -``` - -2. View charts in repo - -```sh -helm search repo superset -NAME CHART VERSION APP VERSION DESCRIPTION -superset/superset 0.1.1 1.0 Apache Superset is a modern, enterprise-ready b... -``` - -3. Configure your setting overrides - -Just like any typical Helm chart, you'll need to craft a `values.yaml` file that would define/override any of the values exposed into the default [values.yaml](https://github.com/apache/superset/tree/master/helm/superset/values.yaml), or from any of the dependent charts it depends on: - -- [bitnami/redis](https://artifacthub.io/packages/helm/bitnami/redis) -- [bitnami/postgresql](https://artifacthub.io/packages/helm/bitnami/postgresql) - -More info down below on some important overrides you might need. - -4. Install and run - -```sh -helm upgrade --install --values my-values.yaml superset superset/superset -``` - -You should see various pods popping up, such as: - -```sh -kubectl get pods -NAME READY STATUS RESTARTS AGE -superset-celerybeat-7cdcc9575f-k6xmc 1/1 Running 0 119s -superset-f5c9c667-dw9lp 1/1 Running 0 4m7s -superset-f5c9c667-fk8bk 1/1 Running 0 4m11s -superset-init-db-zlm9z 0/1 Completed 0 111s -superset-postgresql-0 1/1 Running 0 6d20h -superset-redis-master-0 1/1 Running 0 6d20h -superset-worker-75b48bbcc-jmmjr 1/1 Running 0 4m8s -superset-worker-75b48bbcc-qrq49 1/1 Running 0 4m12s -``` - -The exact list will depend on some of your specific configuration overrides but you should generally expect: - -- N `superset-xxxx-yyyy` and `superset-worker-xxxx-yyyy` pods (depending on your `supersetNode.replicaCount` and `supersetWorker.replicaCount` values) -- 1 `superset-postgresql-0` depending on your postgres settings -- 1 `superset-redis-master-0` depending on your redis settings -- 1 `superset-celerybeat-xxxx-yyyy` pod if you have `supersetCeleryBeat.enabled = true` in your values overrides - -1. Access it - -The chart will publish appropriate services to expose the Superset UI internally within your k8s cluster. To access it externally you will have to either: - -- Configure the Service as a `LoadBalancer` or `NodePort` -- Set up an `Ingress` for it - the chart includes a definition, but will need to be tuned to your needs (hostname, tls, annotations etc...) -- Run `kubectl port-forward superset-xxxx-yyyy :8088` to directly tunnel one pod's port into your localhost - -Depending how you configured external access, the URL will vary. Once you've identified the appropriate URL you can log in with: - -- user: `admin` -- password: `admin` - -## Important settings - -### Security settings - -Default security settings and passwords are included but you **MUST** update them to run `prod` instances, in particular: - -```yaml -postgresql: - postgresqlPassword: superset -``` - -Make sure, you set a unique strong complex alphanumeric string for your SECRET_KEY and use a tool to help you generate -a sufficiently random sequence. - -- To generate a good key you can run, `openssl rand -base64 42` - -```yaml -configOverrides: - secret: | - SECRET_KEY = 'YOUR_OWN_RANDOM_GENERATED_SECRET_KEY' -``` - -If you want to change the previous secret key then you should rotate the keys. -Default secret key for kubernetes deployment is `thisISaSECRET_1234` - -```yaml -configOverrides: - my_override: | - PREVIOUS_SECRET_KEY = 'YOUR_PREVIOUS_SECRET_KEY' - SECRET_KEY = 'YOUR_OWN_RANDOM_GENERATED_SECRET_KEY' -init: - command: - - /bin/sh - - -c - - | - . {{ .Values.configMountPath }}/superset_bootstrap.sh - superset re-encrypt-secrets - . {{ .Values.configMountPath }}/superset_init.sh -``` - -:::note -Superset uses [Scarf Gateway](https://about.scarf.sh/scarf-gateway) to collect telemetry data. Knowing the installation counts for different Superset versions informs the project's decisions about patching and long-term support. Scarf purges personally identifiable information (PII) and provides only aggregated statistics. - -To opt-out of this data collection in your Helm-based installation, edit the `repository:` line in your `helm/superset/values.yaml` file, replacing `apachesuperset.docker.scarf.sh/apache/superset` with `apache/superset` to pull the image directly from Docker Hub. -::: - -### Dependencies - -Install additional packages and do any other bootstrap configuration in the bootstrap script. -For production clusters it's recommended to build own image with this step done in CI. - -:::note - -Superset requires a Python DB-API database driver and a SQLAlchemy -dialect to be installed for each datastore you want to connect to. - -See [Install Database Drivers](/user-docs/databases#installing-database-drivers) for more information. -It is recommended that you refer to versions listed in -[pyproject.toml](https://github.com/apache/superset/blob/master/pyproject.toml) -instead of hard-coding them in your bootstrap script, as seen below. - -::: - -The following example installs the drivers for BigQuery and Elasticsearch, allowing you to connect to these data sources within your Superset setup: - -```yaml -bootstrapScript: | - #!/bin/bash - uv pip install .[postgres] \ - .[bigquery] \ - .[elasticsearch] &&\ - if [ ! -f ~/bootstrap ]; then echo "Running Superset with uid {{ .Values.runAsUser }}" > ~/bootstrap; fi -``` - -### superset_config.py - -The default `superset_config.py` is fairly minimal and you will very likely need to extend it. This is done by specifying one or more key/value entries in `configOverrides`, e.g.: - -```yaml -configOverrides: - my_override: | - # This will make sure the redirect_uri is properly computed, even with SSL offloading - ENABLE_PROXY_FIX = True - FEATURE_FLAGS = { - "DYNAMIC_PLUGINS": True - } -``` - -Those will be evaluated as Helm templates and therefore will be able to reference other `values.yaml` variables e.g. `{{ .Values.ingress.hosts[0] }}` will resolve to your ingress external domain. - -The entire `superset_config.py` will be installed as a secret, so it is safe to pass sensitive parameters directly... however it might be more readable to use secret env variables for that. - -Full python files can be provided by running `helm upgrade --install --values my-values.yaml --set-file configOverrides.oauth=set_oauth.py` - -### Environment Variables - -Those can be passed as key/values either with `extraEnv` or `extraSecretEnv` if they're sensitive. They can then be referenced from `superset_config.py` using e.g. `os.environ.get("VAR")`. - -```yaml -extraEnv: - SMTP_HOST: smtp.gmail.com - SMTP_USER: user@gmail.com - SMTP_PORT: "587" - SMTP_MAIL_FROM: user@gmail.com - -extraSecretEnv: - SMTP_PASSWORD: xxxx - -configOverrides: - smtp: | - import ast - SMTP_HOST = os.getenv("SMTP_HOST","localhost") - SMTP_STARTTLS = ast.literal_eval(os.getenv("SMTP_STARTTLS", "True")) - SMTP_SSL = ast.literal_eval(os.getenv("SMTP_SSL", "False")) - SMTP_USER = os.getenv("SMTP_USER","superset") - SMTP_PORT = os.getenv("SMTP_PORT",25) - SMTP_PASSWORD = os.getenv("SMTP_PASSWORD","superset") -``` - -### System packages - -If new system packages are required, they can be installed before application startup by overriding the container's `command`, e.g.: - -```yaml -supersetWorker: - command: - - /bin/sh - - -c - - | - apt update - apt install -y somepackage - apt autoremove -yqq --purge - apt clean - - # Run celery worker - . {{ .Values.configMountPath }}/superset_bootstrap.sh; celery --app=superset.tasks.celery_app:app worker -``` - -### Data sources - -Data source definitions can be automatically declared by providing key/value yaml definitions in `extraConfigs`: - -```yaml -extraConfigs: - import_datasources.yaml: | - databases: - - allow_file_upload: true - allow_ctas: true - allow_cvas: true - database_name: example-db - extra: "{\r\n \"metadata_params\": {},\r\n \"engine_params\": {},\r\n \"\ - metadata_cache_timeout\": {},\r\n \"schemas_allowed_for_file_upload\": []\r\n\ - }" - sqlalchemy_uri: example://example-db.local - tables: [] -``` - -Those will also be mounted as secrets and can include sensitive parameters. - -## Configuration Examples - -### Setting up OAuth - -:::note - -OAuth setup requires that the [authlib](https://authlib.org/) Python library is installed. This can -be done using `pip` by updating the `bootstrapScript`. See the [Dependencies](#dependencies) section -for more information. - -::: - -```yaml -extraEnv: - AUTH_DOMAIN: example.com - -extraSecretEnv: - GOOGLE_KEY: xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com - GOOGLE_SECRET: xxxxxxxxxxxxxxxxxxxxxxxx - -configOverrides: - enable_oauth: | - # This will make sure the redirect_uri is properly computed, even with SSL offloading - ENABLE_PROXY_FIX = True - - from flask_appbuilder.security.manager import AUTH_OAUTH - AUTH_TYPE = AUTH_OAUTH - OAUTH_PROVIDERS = [ - { - "name": "google", - "icon": "fa-google", - "token_key": "access_token", - "remote_app": { - "client_id": os.getenv("GOOGLE_KEY"), - "client_secret": os.getenv("GOOGLE_SECRET"), - "api_base_url": "https://www.googleapis.com/oauth2/v2/", - "client_kwargs": {"scope": "email profile"}, - "request_token_url": None, - "access_token_url": "https://accounts.google.com/o/oauth2/token", - "authorize_url": "https://accounts.google.com/o/oauth2/auth", - "authorize_params": {"hd": os.getenv("AUTH_DOMAIN", "")} - }, - } - ] - - # Map Authlib roles to superset roles - AUTH_ROLE_ADMIN = 'Admin' - AUTH_ROLE_PUBLIC = 'Public' - - # Will allow user self registration, allowing to create Flask users from Authorized User - AUTH_USER_REGISTRATION = True - - # The default user self registration role - AUTH_USER_REGISTRATION_ROLE = "Admin" -``` - -### Enable Alerts and Reports - -For this, as per the [Alerts and Reports doc](/admin-docs/configuration/alerts-reports), you will need to: - -#### Install a supported webdriver in the Celery worker - -This is done either by using a custom image that has the webdriver pre-installed, or installing at startup time by overriding the `command`. Here's a working example for `chromedriver`: - -```yaml -supersetWorker: - command: - - /bin/sh - - -c - - | - # Install chrome webdriver - # See https://github.com/apache/superset/blob/4fa3b6c7185629b87c27fc2c0e5435d458f7b73d/docs/src/pages/admin-docs/installation/email_reports.mdx - apt-get update - apt-get install -y wget - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - apt-get install -y --no-install-recommends ./google-chrome-stable_current_amd64.deb - wget https://chromedriver.storage.googleapis.com/88.0.4324.96/chromedriver_linux64.zip - apt-get install -y zip - unzip chromedriver_linux64.zip - chmod +x chromedriver - mv chromedriver /usr/bin - apt-get autoremove -yqq --purge - apt-get clean - rm -f google-chrome-stable_current_amd64.deb chromedriver_linux64.zip - - # Run - . {{ .Values.configMountPath }}/superset_bootstrap.sh; celery --app=superset.tasks.celery_app:app worker -``` - -#### Run the Celery beat - -This pod will trigger the scheduled tasks configured in the alerts and reports UI section: - -```yaml -supersetCeleryBeat: - enabled: true -``` - -#### Configure the appropriate Celery jobs and SMTP/Slack settings - -```yaml -extraEnv: - SMTP_HOST: smtp.gmail.com - SMTP_USER: user@gmail.com - SMTP_PORT: "587" - SMTP_MAIL_FROM: user@gmail.com - -extraSecretEnv: - SLACK_API_TOKEN: xoxb-xxxx-yyyy - SMTP_PASSWORD: xxxx-yyyy - -configOverrides: - feature_flags: | - import ast - - FEATURE_FLAGS = { - "ALERT_REPORTS": True - } - - SMTP_HOST = os.getenv("SMTP_HOST","localhost") - SMTP_STARTTLS = ast.literal_eval(os.getenv("SMTP_STARTTLS", "True")) - SMTP_SSL = ast.literal_eval(os.getenv("SMTP_SSL", "False")) - SMTP_USER = os.getenv("SMTP_USER","superset") - SMTP_PORT = os.getenv("SMTP_PORT",25) - SMTP_PASSWORD = os.getenv("SMTP_PASSWORD","superset") - SMTP_MAIL_FROM = os.getenv("SMTP_MAIL_FROM","superset@superset.com") - - SLACK_API_TOKEN = os.getenv("SLACK_API_TOKEN",None) - celery_conf: | - from celery.schedules import crontab - - class CeleryConfig: - broker_url = f"redis://{env('REDIS_HOST')}:{env('REDIS_PORT')}/0" - imports = ( - "superset.sql_lab", - "superset.tasks.cache", - "superset.tasks.scheduler", - ) - result_backend = f"redis://{env('REDIS_HOST')}:{env('REDIS_PORT')}/0" - task_annotations = { - "sql_lab.get_sql_results": { - "rate_limit": "100/s", - }, - } - beat_schedule = { - "reports.scheduler": { - "task": "reports.scheduler", - "schedule": crontab(minute="*", hour="*"), - }, - "reports.prune_log": { - "task": "reports.prune_log", - 'schedule': crontab(minute=0, hour=0), - }, - 'cache-warmup-hourly': { - "task": "cache-warmup", - "schedule": crontab(minute="*/30", hour="*"), - "kwargs": { - "strategy_name": "top_n_dashboards", - "top_n": 10, - "since": "7 days ago", - }, - } - } - - CELERY_CONFIG = CeleryConfig - reports: | - EMAIL_PAGE_RENDER_WAIT = 60 - WEBDRIVER_BASEURL = "http://{{ template "superset.fullname" . }}:{{ .Values.service.port }}/" - WEBDRIVER_BASEURL_USER_FRIENDLY = "https://www.example.com/" - WEBDRIVER_TYPE= "chrome" - WEBDRIVER_OPTION_ARGS = [ - "--force-device-scale-factor=2.0", - "--high-dpi-support=2.0", - "--headless", - "--disable-gpu", - "--disable-dev-shm-usage", - # This is required because our process runs as root (in order to install pip packages) - "--no-sandbox", - "--disable-setuid-sandbox", - "--disable-extensions", - ] -``` - -### Load the Examples data and dashboards - -If you are trying Superset out and want some data and dashboards to explore, you can load some examples by creating a `my_values.yaml` and deploying it as described above in the **Configure your setting overrides** step of the **Running** section. -To load the examples, add the following to the `my_values.yaml` file: - -```yaml -init: - loadExamples: true -``` - -:::resources -- [Tutorial: Mastering Data Visualization — Installing Superset on Kubernetes with Helm Chart](https://mahira-technology.medium.com/mastering-data-visualization-installing-superset-on-kubernetes-cluster-using-helm-chart-e4ec99199e1e) -- [Tutorial: Installing Apache Superset in Kubernetes](https://aws.plainenglish.io/installing-apache-superset-in-kubernetes-1aec192ac495) -::: diff --git a/docs/admin_docs/installation/pypi.mdx b/docs/admin_docs/installation/pypi.mdx deleted file mode 100644 index 820ac776b95..00000000000 --- a/docs/admin_docs/installation/pypi.mdx +++ /dev/null @@ -1,164 +0,0 @@ ---- -title: PyPI -hide_title: true -sidebar_position: 4 -version: 1 ---- - -import useBaseUrl from "@docusaurus/useBaseUrl"; - -# Installing Superset from PyPI - - -

- -This page describes how to install Superset using the `apache_superset` package [published on PyPI](https://pypi.org/project/apache_superset/). - -## OS Dependencies - -Superset stores database connection information in its metadata database. For that purpose, we use -the cryptography Python library to encrypt connection passwords. Unfortunately, this library has OS -level dependencies. - -**Debian and Ubuntu** - -Ubuntu **24.04** uses python 3.12 per default, which currently is not supported by Superset. You need to add a second python installation of 3.11 and install the required additional dependencies. -```bash -sudo add-apt-repository ppa:deadsnakes/ppa -sudo apt update -sudo apt install python3.11 python3.11-dev python3.11-venv build-essential libssl-dev libffi-dev libsasl2-dev libldap2-dev default-libmysqlclient-dev -``` - -In Ubuntu **20.04 and 22.04** the following command will ensure that the required dependencies are installed: - -```bash -sudo apt-get install build-essential libssl-dev libffi-dev python3-dev python3-pip libsasl2-dev libldap2-dev default-libmysqlclient-dev -``` - -In Ubuntu **before 20.04** the following command will ensure that the required dependencies are installed: - -```bash -sudo apt-get install build-essential libssl-dev libffi-dev python-dev python-pip libsasl2-dev libldap2-dev default-libmysqlclient-dev -``` - -**Fedora and RHEL-derivative Linux distributions** - -Install the following packages using the `yum` package manager: - -```bash -sudo yum install gcc gcc-c++ libffi-devel python-devel python-pip python-wheel openssl-devel cyrus-sasl-devel openldap-devel -``` - -In more recent versions of CentOS and Fedora, you may need to install a slightly different set of packages using `dnf`: - -```bash -sudo dnf install gcc gcc-c++ libffi-devel python3-devel python3-pip python3-wheel openssl-devel cyrus-sasl-devel openldap-devel -``` - -Also, on CentOS, you may need to upgrade pip for the install to work: - -```bash -pip3 install --upgrade pip -``` - -**Mac OS X** - -If you're not on the latest version of OS X, we recommend upgrading because we've found that many -issues people have run into are linked to older versions of Mac OS X. After updating, install the -latest version of XCode command line tools: - -```bash -xcode-select --install -``` - -We don't recommend using the system installed Python. Instead, first install the -[homebrew](https://brew.sh/) manager and then run the following commands: - -```bash -brew install readline pkg-config libffi openssl mysql postgresql@14 -``` - -You should install a recent version of Python. Refer to the -[pyproject.toml](https://github.com/apache/superset/blob/master/pyproject.toml) file for a list of Python -versions officially supported by Superset. We'd recommend using a Python version manager -like [pyenv](https://github.com/pyenv/pyenv) -(and also [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv)). - -Let's also make sure we have the latest version of `pip` and `setuptools`: - -```bash -pip install --upgrade setuptools pip -``` - -Lastly, you may need to set LDFLAGS and CFLAGS for certain Python packages to properly build. You can export these variables with: - -```bash -export LDFLAGS="-L$(brew --prefix openssl)/lib" -export CFLAGS="-I$(brew --prefix openssl)/include" -``` - -These will now be available when pip installing requirements. - -## Python Virtual Environment - -We highly recommend installing Superset inside of a virtual environment. - -You can create and activate a virtual environment using the following commands. Ensure you are using a compatible version of python. You might have to explicitly use for example `python3.11` instead of `python3`. - -```bash -# virtualenv is shipped in Python 3.6+ as venv instead of pyvenv. -# See https://docs.python.org/3.6/library/venv.html -python3 -m venv venv -. venv/bin/activate -``` - -Or with pyenv-virtualenv: - -```bash -# Here we name the virtual env 'superset' -pyenv virtualenv superset -pyenv activate superset -``` - -Once you activated your virtual environment, all of the Python packages you install or uninstall -will be confined to this environment. You can exit the environment by running `deactivate` on the -command line. - -### Installing and Initializing Superset - -First, start by installing `apache_superset`: - -```bash -pip install apache_superset -``` - -Then, define mandatory configurations, SECRET_KEY and FLASK_APP: -```bash -export SUPERSET_SECRET_KEY=YOUR-SECRET-KEY # For production use, make sure this is a strong key, for example generated using `openssl rand -base64 42`. See https://superset.apache.org/admin-docs/configuration/configuring-superset#specifying-a-secret_key -export FLASK_APP=superset -``` - -Then, you need to initialize the database: - -```bash -superset db upgrade -``` - -Finish installing by running through the following commands: - -```bash -# Create an admin user in your metadata database (use `admin` as username to be able to load the examples) -superset fab create-admin - -# Load some data to play with -superset load_examples - -# Create default roles and permissions -superset init - -# To start a development web server on port 8088, use -p to bind to another port -superset run -p 8088 --with-threads --reload --debugger -``` - -If everything worked, you should be able to navigate to `hostname:port` in your browser (e.g. -locally by default at `localhost:8088`) and login using the username and password you created. diff --git a/docs/admin_docs/installation/upgrading-superset.mdx b/docs/admin_docs/installation/upgrading-superset.mdx deleted file mode 100644 index 0cf092a5a06..00000000000 --- a/docs/admin_docs/installation/upgrading-superset.mdx +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: Upgrading Superset -hide_title: true -sidebar_position: 6 -version: 1 ---- - -# Upgrading Superset - -## Docker Compose - -First, make sure to shut down the running containers in Docker Compose: - -```bash -docker compose down -``` - -Next, update the folder that mirrors the `superset` repo through git: - -```bash -git pull origin master -``` - -Then, restart the containers and any changed Docker images will be automatically pulled down: - -```bash -docker compose up -``` - -## Updating Superset Manually - -To upgrade superset in a native installation, run the following commands: - -```bash -pip install apache_superset --upgrade -``` - -## Upgrading the Metadata Database - -Migrate the metadata database by running: - -```bash -superset db upgrade -superset init -``` - -While upgrading superset should not delete your charts and dashboards, we recommend following best -practices and to backup your metadata database before upgrading. Before upgrading production, we -recommend upgrading in a staging environment and upgrading production finally during off-peak usage. - -## Breaking Changes - -For a detailed list of breaking changes and migration notes for each version, see -[UPDATING.md](https://github.com/apache/superset/blob/master/UPDATING.md). - -This file documents backwards-incompatible changes and provides guidance for migrating between -major versions, including: -- Configuration changes -- API changes -- Database migrations -- Deprecated features diff --git a/docs/admin_docs/security/cves.mdx b/docs/admin_docs/security/cves.mdx deleted file mode 100644 index a8c2cbb95c2..00000000000 --- a/docs/admin_docs/security/cves.mdx +++ /dev/null @@ -1,140 +0,0 @@ ---- -title: CVEs fixed by release -sidebar_position: 2 ---- -#### Version 6.0.0 - -| CVE | Title | Affected | -|:---------------|:-----------------------------------------------------------------------------------|---------:| -| CVE-2026-23980 | Improper Neutralization of Special Elements used in a SQL Command | < 6.0.0 | -| CVE-2026-23982 | Improper Authorization in Dataset Creation Allows Access Control Bypass | < 6.0.0 | -| CVE-2026-23983 | Information Disclosure of sensitive user info via Tags | < 6.0.0 | -| CVE-2026-23984 | SQLLab Read-Only Bypass on PostgreSQL (DML execution) | < 6.0.0 | - -#### Version 5.0.0 - -| CVE | Title | Affected | -|:---------------|:-----------------------------------------------------------------------------------|---------:| -| CVE-2025-55673 | Exposure of Sensitive Information to an Unauthorized Actor | < 5.0.0 | -| CVE-2025-55674 | Improper Neutralization of Special Elements used in an SQL Command | < 5.0.0 | -| CVE-2025-55675 | Improper Access Control leading to Information Disclosure | < 5.0.0 | - -#### Version 4.1.3 - -| CVE | Title | Affected | -|:---------------|:-----------------------------------------------------------------------------------|---------:| -| CVE-2025-55672 | Improper Neutralization of Input During Web Page Generation | < 4.1.3 | - -#### Version 4.1.2 - -| CVE | Title | Affected | -|:---------------|:-----------------------------------------------------------------------------------|---------:| -| CVE-2025-27696 | Improper authorization leading to resource ownership takeover | < 4.1.2 | -| CVE-2025-48912 | Improper authorization bypass on row level security via SQL Injection | < 4.1.2 | -| CVE-2026-23969 | Exposure of Sensitive Information via Incomplete ClickHouse Function Filtering | < 4.1.2 | - -#### Version 4.1.0 - -| CVE | Title | Affected | -|:---------------|:-----------------------------------------------------------------------------------|---------:| -| CVE-2024-53947 | Improper SQL authorisation, parse for specific postgres functions | < 4.1.0 | -| CVE-2024-53948 | Error verbosity exposes metadata in analytics databases | < 4.1.0 | -| CVE-2024-53949 | Lower privilege users are able to create Role when FAB_ADD_SECURITY_API is enabled | < 4.1.0 | -| CVE-2024-55633 | SQLLab Improper readonly query validation allows unauthorized write access | < 4.1.0 | - -#### Version 4.0.2 - -| CVE | Title | Affected | -|:---------------|:----------------------------|---------:| -| CVE-2024-39887 | Improper SQL authorization | < 4.0.1 | - -#### Version 3.1.3, 4.0.1 - -| CVE | Title | Affected | -|:---------------|:----------------------------|----------------------------:| -| CVE-2024-34693 | Server arbitrary file read | < 3.1.3, >= 4.0.0, < 4.0.1 | - -#### Version 3.1.2 - -| CVE | Title | Affected | -|:---------------|:--------------------------------------------------------|---------:| -| CVE-2024-28148 | Incorrect datasource authorization on explore REST API | < 3.1.2 | - -#### Version 3.0.4, 3.1.1 - -| CVE | Title | Affected | -|:---------------|:-----------------------------------------------------------------------------|----------------------------:| -| CVE-2024-27315 | Improper error handling on alerts | < 3.0.4, >= 3.1.0, < 3.1.1 | -| CVE-2024-24773 | Improper validation of SQL statements allows for unauthorized access to data | < 3.0.4, >= 3.1.0, < 3.1.1 | -| CVE-2024-24772 | Improper Neutralisation of custom SQL on embedded context | < 3.0.4, >= 3.1.0, < 3.1.1 | -| CVE-2024-24779 | Improper data authorization when creating a new dataset | < 3.0.4, >= 3.1.0, < 3.1.1 | -| CVE-2024-26016 | Improper authorization validation on dashboards and charts import | < 3.0.4, >= 3.1.0, < 3.1.1 | - -#### Version 3.0.3 - -| CVE | Title | Affected | -|:---------------|:----------------------------------------------|---------:| -| CVE-2023-49657 | Stored XSS in Dashboard Title and Chart Title | < 3.0.3 | - -#### Version 3.0.2, 2.1.3 - -| CVE | Title | Affected | -|:---------------|:------------------------------------------------------------|---------------------------:| -| CVE-2023-46104 | Allows for uncontrolled resource consumption via a ZIP bomb | < 2.1.3, >= 3.0.0, < 3.0.2 | -| CVE-2023-49736 | SQL Injection on where_in JINJA macro | < 2.1.3, >= 3.0.0, < 3.0.2 | -| CVE-2023-49734 | Privilege Escalation Vulnerability | < 2.1.3, >= 3.0.0, < 3.0.2 | - -#### Version 3.0.0 - -| CVE | Title | Affected | -|:---------------|:------------------------------------------------------------------------|---------:| -| CVE-2023-42502 | Open Redirect Vulnerability | < 3.0.0 | -| CVE-2023-42505 | Sensitive information disclosure on db connection details | < 3.0.0 | - -#### Version 2.1.3 - -| CVE | Title | Affected | -|:---------------|:------------------------------------------------------------------------|---------:| -| CVE-2023-42504 | Lack of rate limiting allows for possible denial of service | < 2.1.3 | - -#### Version 2.1.2 - -| CVE | Title | Affected | -|:---------------|:------------------------------------------------------------------------|---------:| -| CVE-2023-40610 | Privilege escalation with default examples database | < 2.1.2 | -| CVE-2023-42501 | Unnecessary read permissions within the Gamma role | < 2.1.2 | -| CVE-2023-43701 | Stored XSS on API endpoint | < 2.1.2 | - -#### Version 2.1.1 - -| CVE | Title | Affected | -|:---------------|:------------------------------------------------------------------------|---------:| -| CVE-2023-36387 | Improper API permission for low privilege users | < 2.1.1 | -| CVE-2023-36388 | Improper API permission for low privilege users allows for SSRF | < 2.1.1 | -| CVE-2023-27523 | Improper data permission validation on Jinja templated queries | < 2.1.1 | -| CVE-2023-27526 | Improper Authorization check on import charts | < 2.1.1 | -| CVE-2023-39264 | Stack traces enabled by default | < 2.1.1 | -| CVE-2023-39265 | Possible Unauthorized Registration of SQLite Database Connections | < 2.1.1 | -| CVE-2023-37941 | Metadata db write access can lead to remote code execution | < 2.1.1 | -| CVE-2023-32672 | SQL parser edge case bypasses data access authorization | < 2.1.1 | - -#### Version 2.1.0 - -| CVE | Title | Affected | -|:---------------|:------------------------------------------------------------------------|---------:| -| CVE-2023-25504 | Possible SSRF on import datasets | < 2.1.0 | -| CVE-2023-27524 | Session validation vulnerability when using provided default SECRET_KEY | < 2.1.0 | -| CVE-2023-27525 | Incorrect default permissions for Gamma role | < 2.1.0 | -| CVE-2023-30776 | Database connection password leak | < 2.1.0 | - -#### Version 2.0.1 - -| CVE | Title | Affected | -|:---------------|:------------------------------------------------------------|------------------: | -| CVE-2022-41703 | SQL injection vulnerability in adhoc clauses | < 2.0.1 or < 1.5.2 | -| CVE-2022-43717 | Cross-Site Scripting on dashboards | < 2.0.1 or < 1.5.2 | -| CVE-2022-43718 | Cross-Site Scripting vulnerability on upload forms | < 2.0.1 or < 1.5.2 | -| CVE-2022-43719 | Cross Site Request Forgery (CSRF) on accept, request access | < 2.0.1 or < 1.5.2 | -| CVE-2022-43720 | Improper rendering of user input | < 2.0.1 or < 1.5.2 | -| CVE-2022-43721 | Open Redirect Vulnerability | < 2.0.1 or < 1.5.2 | -| CVE-2022-45438 | Dashboard metadata information leak | < 2.0.1 or < 1.5.2 | diff --git a/docs/admin_docs/security/securing_superset.mdx b/docs/admin_docs/security/securing_superset.mdx deleted file mode 100644 index 92d42e385e0..00000000000 --- a/docs/admin_docs/security/securing_superset.mdx +++ /dev/null @@ -1,179 +0,0 @@ ---- -title: Securing Your Superset Installation for Production -sidebar_position: 3 ---- - -> *This guide applies to Apache Superset version 4.0 and later and is an evolving set of best practices that administrators should adapt to their specific deployment architecture.* - -The default Apache Superset configuration is optimized for ease of use and development, not for security. For any production deployment, it is **critical** that you review and apply the following security configurations to harden your instance, protect user data, and prevent unauthorized access. - -This guide provides a comprehensive checklist of essential security configurations and best practices. - -### **Critical Prerequisites: HTTPS/TLS Configuration** - -Running Superset without HTTPS (TLS) is not secure. Without it, all network traffic—including user credentials, session tokens, and sensitive data—is sent in cleartext and can be easily intercepted. - -* **Use a Reverse Proxy:** Your Superset instance should always be deployed behind a reverse proxy (e.g., Nginx, Traefik) or a load balancer (e.g., AWS ALB, Google Cloud Load Balancer) that is configured to handle HTTPS termination. -* **Enforce Modern TLS:** Configure your proxy to enforce TLS 1.2 or higher with strong, industry-standard cipher suites. -* **Implement HSTS:** Use the HTTP Strict Transport Security (HSTS) header to ensure browsers only connect to your Superset instance over HTTPS. This can be configured in your reverse proxy or within Superset's Talisman settings. - -### **`SUPERSET_SECRET_KEY` Management (CRITICAL)** - -This is the most critical security setting for your Superset instance. It is used to sign all session cookies and encrypt sensitive information in the metadata database, such as database connection credentials. - -* **Generate a Unique, Strong Key:** A unique key must be generated for every Superset instance. Use a cryptographically secure method to create it. - ```bash - # Example using openssl to generate a strong key - openssl rand -base64 42 - ``` -* **Store the Key Securely:** The key must be kept confidential. The recommended approach is to store it as an environment variable or in a secrets management system (e.g., AWS Secrets Manager, HashiCorp Vault). **Do not hardcode the key in `superset_config.py` or commit it to version control.** - ```python - # In superset_config.py - import os - SECRET_KEY = os.environ.get('SUPERSET_SECRET_KEY') - ``` - -> #### ⚠️ Warning: Your `SUPERSET_SECRET_KEY` Must Be Unique -> -> **NEVER** reuse the same `SUPERSET_SECRET_KEY` across different environments (e.g., development, staging, production) or different Superset instances. Reusing a key allows cryptographically signed session cookies to be used across those instances, which can lead to a full authentication bypass if a cookie is compromised. Treat this key like a master password. - -### **Session Management Security (CRITICAL)** - -Properly configuring user sessions is essential to prevent session hijacking and ensure that sessions are terminated correctly. - -#### **Use a Server-Side Session Backend (Strongly Recommended for Production)** - -The default stateless cookie-based session handling presents challenges for immediate session invalidation upon logout. For all production deployments, we strongly recommend configuring an optional server-side session backend like Redis, Memcached, or a database. This ensures that session data is stored securely on the server and can be instantly destroyed upon logout, rendering any copied session cookies immediately useless. - -**Example `superset_config.py` for Redis:** - -```python -# superset_config.py -from redis import Redis -import os - -# 1. Enable server-side sessions -SESSION_SERVER_SIDE = True - -# 2. Choose your backend (e.g., 'redis', 'memcached', 'filesystem', 'sqlalchemy') -SESSION_TYPE = 'redis' - -# 3. Configure your Redis connection -# Use environment variables for sensitive details -SESSION_REDIS = Redis( - host=os.environ.get('REDIS_HOST', 'localhost'), - port=int(os.environ.get('REDIS_PORT', 6379)), - password=os.environ.get('REDIS_PASSWORD'), - db=int(os.environ.get('REDIS_DB', 0)), - ssl=os.environ.get('REDIS_SSL_ENABLED', 'True').lower() == 'true', - ssl_cert_reqs='required' # Or another appropriate SSL setting -) - -# 4. Ensure the session cookie is signed for integrity -SESSION_USE_SIGNER = True -``` - -#### **Configure Session Lifetime and Cookie Security Flags** - -This is mandatory for *all* deployments, whether stateless or server-side. - -```python -# superset_config.py -from datetime import timedelta - -# Set a short absolute session timeout -# The default is 31 days, which is NOT recommended for production. -PERMANENT_SESSION_LIFETIME = timedelta(hours=8) - -# Enforce secure cookie flags to prevent browser-based attacks -SESSION_COOKIE_SECURE = True # Transmit cookie only over HTTPS -SESSION_COOKIE_HTTPONLY = True # Prevent client-side JS from accessing the cookie -SESSION_COOKIE_SAMESITE = 'Lax' # Provide protection against CSRF attacks -``` - -> ##### Note on iFrame Embedding and `SESSION_COOKIE_SAMESITE` ->The recommended default setting `'Lax'` provides good CSRF protection for most use cases. However, if you need to embed Superset dashboards into other applications using an iFrame, you will need to change this setting to `'None'`. - -SESSION_COOKIE_SAMESITE = 'None' - -Setting SameSite to 'None' requires that SESSION_COOKIE_SECURE is also set to True. Be aware that this configuration disables some of the browser's built-in CSRF protections to allow for cross-domain functionality, so it should only be used when iFrame embedding is necessary. - -### **Authentication and Authorization** - -While Superset's built-in database authentication is convenient, for production it's highly recommended to integrate with an enterprise-grade identity provider (IdP). - - * **Use an Enterprise IdP:** Configure authentication via OAuth or LDAP to leverage your organization's existing identity management system. This provides benefits like Single Sign-On (SSO), Multi-Factor Authentication (MFA), and centralized user provisioning/deprovisioning. - * **Principle of Least Privilege:** Assign users to the most restrictive roles necessary for their jobs. Avoid over-provisioning users with Admin or Alpha roles, and ensure row-level security is applied where appropriate. - * **Admin Accounts:** Delete or disable the default admin user after a new administrative account has been configured. - -### **Content Security Policy (CSP) and Other Headers** - -Superset can use Flask-Talisman to set security headers. However, it must be explicitly enabled. - -> #### ⚠️ Important: Talisman is Disabled by Default -> -> In Superset 4.0 and later, Talisman is disabled by default (`TALISMAN_ENABLED = False`). You **must** explicitly enable it in your `superset_config.py` for the security headers defined in `TALISMAN_CONFIG` to take effect. - -Here's the documentation section how how to set up Talisman: https://superset.apache.org/admin-docs/security/#content-security-policy-csp - -### **Database Security** - -> #### ❗ Superset is Not a Database Firewall -> -> It is essential to understand that **Apache Superset is a data visualization and exploration platform, not a database firewall or a comprehensive security solution for your data warehouse.** While Superset provides features to help manage data access, the ultimate responsibility for securing your underlying databases lies with your database administrators (DBAs) and security teams. This includes managing network access, user privileges, and fine-grained permissions directly within the database. The configurations below are an important secondary layer of security but should not be your only line of defense. - - * **Use a Dedicated Database User:** The database connection configured in Superset should use a dedicated, limited-privilege database user. This user should only have the minimum required permissions (e.g., `SELECT` on specific schemas) for the data sources it needs to query. It should **not** have `INSERT`, `UPDATE`, `DELETE`, or administrative privileges. - * **Restrict Dangerous SQL Functions:** To mitigate potential SQL injection risks, configure the `DISALLOWED_SQL_FUNCTIONS` list in your `superset_config.py`. Be aware that this is a defense-in-depth measure, not a substitute for proper database permissions. - -### **Additional Security Layers** - - * **Web Application Firewall (WAF):** Deploying Superset behind a WAF (e.g., Cloudflare, AWS WAF) is strongly recommended. A WAF with a standard ruleset (like the OWASP Core Rule Set) provides a critical layer of defense against common attacks like SQL Injection, XSS, and remote code execution. - -### **Monitoring and Logging** - - * **Configure Structured Logging:** Set up a robust logging configuration to capture important security events. - * **Centralize Logs:** Ship logs from all Superset components (frontend, worker, etc.) to a centralized SIEM (Security Information and Event Management) system for analysis and alerting. - * **Monitor Key Events:** Create alerts for suspicious activities, including: - * Multiple failed login attempts for a single user or from a single IP address. - * Changes to user roles or permissions. - * Creation or deletion of high-privilege users. - * Attempts to use disallowed SQL functions. - ------ - -### **Appendix A: Production Deployment Checklist** - -#### **Initial Setup:** - - - [ ] HTTPS/TLS is configured and enforced via a reverse proxy. - - [ ] A unique, strong `SUPERSET_SECRET_KEY` is generated and secured in an environment variable or secrets vault. - - [ ] Server-side session management is configured (e.g., Redis). - - [ ] `PERMANENT_SESSION_LIFETIME` is set to a short duration (e.g., 8 hours). - - [ ] All session cookie security flags (`Secure`, `HttpOnly`, `SameSite`) are enabled. - - [ ] `DEBUG` mode is set to `False`. - - [ ] Talisman is explicitly enabled and configured with a strict Content Security Policy. - - [ ] Database connections use dedicated, limited-privilege accounts. - - [ ] Authentication is integrated with an enterprise identity provider (OAuth/LDAP). - - [ ] A Web Application Firewall (WAF) is deployed in front of Superset. - - [ ] Logging is configured and logs are shipped to a central monitoring system. - -#### **Ongoing Maintenance:** - - - [ ] Regularly update to the latest major or minor versions of Superset. Those versions receive up-to-date security patches. - - [ ] Rotate the `SUPERSET_SECRET_KEY` periodically (e.g., quarterly) and after any potential security incident. - - [ ] Conduct quarterly access reviews for all users. - - [ ] Assuming logging and monitoring is in place, review security monitoring alerts weekly. - -### **Appendix B: `SECRET_KEY` Rotation and Compromise Response** - -**Why and When to Rotate the `SECRET_KEY`** -Rotating the `SUPERSET_SECRET_KEY` is a critical security procedure. It is mandatory after a known or suspected compromise and is a best practice when an employee with access to the key departs. While periodic rotation can limit the window of exposure for an unknown leak, it is a high-impact operation that will invalidate all user sessions and requires careful execution to avoid breaking your instance. The principles behind managing this key align with general best practices for cryptographic storage, which are further detailed in the OWASP Cryptographic Storage Cheat Sheet here: https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html - -**Procedure for Rotating the Key** -The procedure for safely rotating the SECRET_KEY must be followed precisely to avoid locking yourself out of your instance. The official Apache Superset documentation maintains the correct, up-to-date procedure. Please follow the official guide here: -https://superset.apache.org/admin-docs/configuration/configuring-superset/#rotating-to-a-newer-secret_key - -:::resources -- [Blog: Running Apache Superset on the Open Internet](https://preset.io/blog/running-apache-superset-on-the-open-internet-a-report-from-the-fireline/) -- [Blog: How Security Vulnerabilities are Reported & Handled in Apache Superset](https://preset.io/blog/how-security-vulnerabilities-are-reported-and-handled-in-apache-superset/) -::: diff --git a/docs/admin_docs/security/security.mdx b/docs/admin_docs/security/security.mdx deleted file mode 100644 index 6190925d8ab..00000000000 --- a/docs/admin_docs/security/security.mdx +++ /dev/null @@ -1,639 +0,0 @@ ---- -title: Security Configurations -sidebar_position: 1 ---- - -Authentication and authorization in Superset is handled by Flask AppBuilder (FAB), an application development framework -built on top of Flask. FAB provides authentication, user management, permissions and roles. -Please read its [Security documentation](https://flask-appbuilder.readthedocs.io/en/latest/security.html). - -### Provided Roles - -Superset ships with a set of roles that are handled by Superset itself. You can assume -that these roles will stay up-to-date as Superset evolves (and as you update Superset versions). - -Even though **Admin** users have the ability, we don't recommend altering the -permissions associated with each role (e.g. by removing or adding permissions to them). The permissions -associated with each role will be re-synchronized to their original values when you run -the **superset init** command (often done between Superset versions). - -A table with the permissions for these roles can be found at [/RESOURCES/STANDARD_ROLES.md](https://github.com/apache/superset/blob/master/RESOURCES/STANDARD_ROLES.md). - -### Admin - -Admins have all possible rights, including granting or revoking rights from other -users and altering other people’s slices and dashboards. - ->#### Threat Model and Privilege Boundaries: The Admin Role -> ->Apache Superset is built with a granular permission model where users assigned the Admin role are considered fully trusted. Admins possess complete control over the application's configuration, UI rendering, and access controls. -> ->Consequently, actions performed by an Admin that alter the application's behavior or presentation—such as injecting custom CSS, modifying Jinja templates, or altering security flags—are intended administrative capabilities by design. -> ->In accordance with MITRE CNA Rule 4.1, a vulnerability must represent a violation of an explicit security policy. Because the Admin role is defined as a trusted operational boundary, actions executed with Admin privileges do not cross a security perimeter. Therefore, exploit vectors that strictly require Admin access are not classified as security vulnerabilities and are ineligible for CVE assignment. - -### Alpha - -Alpha users have access to all data sources, but they cannot grant or revoke access -from other users. They are also limited to altering the objects that they own. Alpha users can add and alter data sources. - -### Gamma - -Gamma users have limited access. They can only consume data coming from data sources -they have been given access to through another complementary role. They only have access to -view the slices and dashboards made from data sources that they have access to. Currently Gamma -users are not able to alter or add data sources. We assume that they are mostly content consumers, though they can create slices and dashboards. - -Also note that when Gamma users look at the dashboards and slices list view, they will -only see the objects that they have access to. - -### sql_lab - -The **sql_lab** role grants access to SQL Lab. Note that while **Admin** users have access -to all databases by default, both **Alpha** and **Gamma** users need to be given access on a per database basis. - -Beyond the base `sql_lab` role, two additional SQL Lab permissions must be explicitly granted for users who need these capabilities: - -| Permission | Feature | -|------------|---------| -| `can_estimate_query_cost` on `SQLLab` | Estimate query cost before running | -| `can_format_sql` on `SQLLab` | Format SQL using the database's dialect | - -Grant these in **Security → List Roles** by adding the permissions to the relevant role. - -### Public - -The **Public** role is the most restrictive built-in role, designed specifically for anonymous/unauthenticated -users who need to view dashboards. It provides minimal read-only access for: - -- Viewing dashboards and charts -- Using interactive dashboard filters -- Accessing dashboard and chart permalinks -- Reading embedded dashboards -- Viewing annotations on charts - -The Public role explicitly excludes: -- Any write permissions on dashboards, charts, or datasets -- SQL Lab access -- Share functionality -- User profile or admin features -- Menu access to most Superset features - -Anonymous users are automatically assigned the Public role when `AUTH_ROLE_PUBLIC` is configured -(a Flask-AppBuilder setting). The `PUBLIC_ROLE_LIKE` setting is **optional** and controls what -permissions are synced to the Public role when you run `superset init`: - -```python -# Optional: Sync sensible default permissions to the Public role -PUBLIC_ROLE_LIKE = "Public" - -# Alternative: Copy permissions from Gamma for broader access -# PUBLIC_ROLE_LIKE = "Gamma" -``` - -If you prefer to manually configure the Public role's permissions (or use `DASHBOARD_RBAC` to -grant access at the dashboard level), you do not need to set `PUBLIC_ROLE_LIKE`. - -**Important notes:** - -- **Data access is still required:** The Public role only grants UI/API permissions. You must - also grant access to specific datasets necessary to view a dashboard. As with other roles, - this can be done in two ways: - - - **Without `DASHBOARD_RBAC`:** Dashboards only appear in the list and are accessible if - the user has permission to at least one of their datasets. Grant dataset access by editing - the Public role in the Superset UI (Menu → Security → List Roles → Public) and adding the - relevant data sources. All published dashboards using those datasets become visible. - - - **With `DASHBOARD_RBAC` enabled:** Anonymous users will only see dashboards where the - "Public" role has been explicitly added in the dashboard's properties. Dataset permissions - are not required—DASHBOARD_RBAC handles the cascading permissions check. This provides - fine-grained control over which dashboards are publicly visible. - -- **Role synchronization:** Built-in role permissions (Admin, Alpha, Gamma, sql_lab, and Public - when `PUBLIC_ROLE_LIKE = "Public"`) are synchronized when you run `superset init`. Any manual - permission edits to these roles may be overwritten during upgrades. To customize the Public - role permissions, you can either: - - Edit the Public role directly and avoid setting `PUBLIC_ROLE_LIKE` (permissions won't be - overwritten by `superset init`) - - Copy the Public role via "Copy Role" in the Superset web UI, save it under a different name - (e.g., "Public_Custom"), customize the permissions, then update **both** configs: - `PUBLIC_ROLE_LIKE = "Public_Custom"` and `AUTH_ROLE_PUBLIC = "Public_Custom"` - -### Managing Data Source Access for Gamma Roles - -Here’s how to provide users access to only specific datasets. First make sure the users with -limited access have [only] the Gamma role assigned to them. Second, create a new role (Menu -> Security -> List Roles) and click the + sign. - -This new window allows you to give this new role a name, attribute it to users and select the -tables in the **Permissions** dropdown. To select the data sources you want to associate with this role, simply click on the dropdown and use the typeahead to search for your table names. - -You can then confirm with users assigned to the **Gamma** role that they see the -objects (dashboards and slices) associated with the tables you just extended them. - -### Dashboard Access Control - -Access to dashboards is managed via owners (users that have edit permissions to the dashboard). -Non-owner user access can be managed in two ways. Note that dashboards must be published to be -visible to other users. - -#### Dataset-Based Access (Default) - -By default, users can view published dashboards if they have access to at least one dataset -used in that dashboard. Grant dataset access by adding the relevant data source permissions -to a role (Menu → Security → List Roles). - -This is the simplest approach but provides all-or-nothing access based on dataset permissions— -if a user has access to a dataset, they can see all published dashboards using that dataset. - -#### Dashboard-Level Access (DASHBOARD_RBAC) - -For fine-grained control over which dashboards specific roles can access, enable the -`DASHBOARD_RBAC` feature flag: - -```python -FEATURE_FLAGS = { - "DASHBOARD_RBAC": True, -} -``` - -With this enabled, you can assign specific roles to each dashboard in its properties. Users -will only see dashboards where their role is explicitly added. - -**Important considerations:** -- Dashboard access **bypasses** dataset-level checks—granting a role access to a dashboard - implicitly grants read access to all charts and datasets in that dashboard -- Dashboards without any assigned roles fall back to dataset-based access -- The dashboard must still be published to be visible - -This feature is particularly useful for: -- Making specific dashboards public while keeping others private -- Granting access to dashboards without exposing the underlying datasets for other uses -- Creating dashboard-specific access patterns that don't align with dataset ownership - -### SQL Execution Security Considerations - -Apache Superset includes features designed to provide safeguards when interacting with connected databases, such as the `DISALLOWED_SQL_FUNCTIONS` configuration setting. This aims to prevent the execution of potentially harmful database functions or system variables directly from Superset interfaces like SQL Lab. - -However, it is crucial to understand the following: - -**Superset is Not a Database Firewall**: Superset's built-in checks, like `DISALLOWED_SQL_FUNCTIONS`, provide a layer of protection but cannot guarantee complete security against all database-level threats or advanced bypass techniques (like specific comment injection methods). They should be viewed as a supplement to, not a replacement for, robust database security. - -**Configuration is Key**: The effectiveness of Superset's safeguards heavily depends on proper configuration by the Superset administrator. This includes maintaining the `DISALLOWED_SQL_FUNCTIONS` list, carefully managing feature flags (like `ENABLE_TEMPLATE_PROCESSING`), and configuring other security settings appropriately. - -**Database Security is Paramount**: The ultimate responsibility for securing database access, controlling permissions, and preventing unauthorized function execution lies with the database administrators (DBAs) and security teams managing the underlying database instance. - -**Recommended Database Practices**: We strongly recommend implementing security best practices at the database level, including: -* **Least Privilege**: Connecting Superset using dedicated database user accounts with the minimum permissions required for Superset's operation (typically read-only access to necessary schemas/tables). -* **Database Roles & Permissions**: Utilizing database-native roles and permissions to restrict access to sensitive functions, system variables (like `@@hostname`), schemas, or tables. -* **Network Security**: Employing network-level controls like database firewalls or proxies to restrict connections. -* **Auditing**: Enabling database-level auditing to monitor executed queries and access patterns. - -By combining Superset's configurable safeguards with strong database-level security practices, you can achieve a more robust and layered security posture. - -**Dataset Sample Access**: The `get_samples()` endpoint now enforces datasource-level access control. Users can only fetch sample rows from datasets they have been explicitly granted access to — the same permission check applied when running chart queries. This closes a prior gap where unauthenticated or under-privileged access could retrieve sample data. - -### REST API for user & role management - -Flask-AppBuilder supports a REST API for user CRUD, -but this feature is in beta and is not enabled by default in Superset. -To enable this feature, set the following in your Superset configuration: - -```python -FAB_ADD_SECURITY_API = True -``` - -Once configured, the documentation for additional "Security" endpoints will be visible in Swagger for you to explore. - -### API Key Authentication - -Superset supports long-lived API keys for service accounts, CI/CD pipelines, and programmatic integrations (including MCP clients). - -#### Enabling API Key Authentication - -API key authentication is **disabled by default**. To turn it on, set the Flask-AppBuilder config value in `superset_config.py` and also enable the matching feature flag so the management UI is exposed: - -```python -FAB_API_KEY_ENABLED = True - -FEATURE_FLAGS = { - "FAB_API_KEY_ENABLED": True, -} -``` - -The config value registers the `ApiKeyApi` blueprint on the backend; the feature flag controls whether the UI for managing keys appears for the user. See the [Feature Flags](/admin-docs/configuration/feature-flags) documentation for more on feature flag configuration. - -#### Creating an API Key - -Once enabled, each user manages their own keys from their profile page: - -1. Open the user menu (top-right) and click **Info** to navigate to the User Info page -2. Expand the **API Keys** section -3. Click **+ API Key** -4. Enter a name and (optionally) an expiration date -5. Copy the generated token — it is shown only once - -Only users with the `can_read` and `can_write` permissions on `ApiKey` (granted by default to Admins) can manage API keys. - -#### Using an API Key - -Pass the key as a Bearer token in the `Authorization` header: - -``` -Authorization: Bearer -``` - -This works for all REST API endpoints and the MCP server. The request is executed with the permissions of the user who created the key. - -#### Use Cases - -- **CI/CD pipelines** — automated chart/dashboard exports and imports -- **MCP integrations** — connect AI assistants without interactive login -- **External services** — dashboards embedded in other applications -- **Service accounts** — long-lived credentials that don't expire with session cookies - -:::caution -Store API keys securely. Anyone with a valid key can make requests on behalf of the creating user. Revoke keys promptly if they are compromised by deleting them from the **API Keys** section of your User Info page. -::: - -### Customizing Permissions - -The permissions exposed by FAB are very granular and allow for a great level of -customization. FAB creates many permissions automatically for each model that is -created (can_add, can_delete, can_show, can_edit, …) as well as for each view. -On top of that, Superset can expose more granular permissions like **all_datasource_access**. - -**We do not recommend altering the 3 base roles as there are a set of assumptions that -Superset is built upon**. It is possible though for you to create your own roles, and union them to existing ones. - -### Permissions - -Roles are composed of a set of permissions, and Superset has many categories of -permissions. Here are the different categories of permissions: - -- Model & Action: models are entities like Dashboard, Slice, or User. Each model has - a fixed set of permissions, like **can_edit**, **can_show**, **can_delete**, **can_list**, **can_add**, - and so on. For example, you can allow a user to delete dashboards by adding **can_delete** on - Dashboard entity to a role and granting this user that role. -- Views: views are individual web pages, like the Explore view or the SQL Lab view. - When granted to a user, they will see that view in its menu items, and be able to load that page. -- Data source: For each data source, a permission is created. If the user does not have the - `all_datasource_access permission` granted, the user will only be able to see Slices or explore the data sources that are granted to them -- Database: Granting access to a database allows for the user to access all - data sources within that database, and will enable the user to query that - database in SQL Lab, provided that the SQL Lab specific permission have been granted to the user - -### Restricting Access to a Subset of Data Sources - -We recommend giving a user the **Gamma** role plus any other roles that would add -access to specific data sources. We recommend that you create individual roles for -each access profile. For example, the users on the Finance team might have access to a set of -databases and data sources; these permissions can be consolidated in a single role. -Users with this profile then need to be assigned the **Gamma** role as a foundation to -the models and views they can access, and that Finance role that is a collection of permissions to data objects. - -A user can have multiple roles associated with them. For example, an executive on the Finance -team could be granted **Gamma**, **Finance**, and the **Executive** roles. The **Executive** -role could provide access to a set of data sources and dashboards made available only to executives. -In the **Dashboards** view, a user can only see the ones they have access to -based on the roles and permissions that were attributed. - -### Row Level Security - -Using Row Level Security filters (under the **Security** menu) you can create filters -that are assigned to a particular dataset, as well as a set of roles. -If you want members of the Finance team to only have access to -rows where `department = "finance"`, you could: - -- Create a Row Level Security filter with that clause (`department = "finance"`) -- Then assign the clause to the **Finance** role and the dataset it applies to - -The **clause** field, which can contain arbitrary text, is then added to the generated -SQL statement's WHERE clause. So you could even do something like create a filter -for the last 30 days and apply it to a specific role, with a clause -like `date_field > DATE_SUB(NOW(), INTERVAL 30 DAY)`. It can also support -multiple conditions: `client_id = 6` AND `advertiser="foo"`, etc. - -RLS clauses also support **Jinja templating** when `ENABLE_TEMPLATE_PROCESSING` is enabled, so you can write dynamic filters such as -`user_id = '{{ current_username() }}'` to restrict rows based on the logged-in user. - -#### Filter Types - -There are two types of RLS filters: - -- **Regular** — The filter clause is applied when the querying user belongs to one of the - roles assigned to the filter. Use this to restrict what specific roles can see. -- **Base** — The filter clause is applied to **all** users _except_ those in the assigned - roles. Use this to define a default restriction that privileged roles (e.g. Admin) are - exempt from. For example, a Base filter with clause `1 = 0` and the Admin role would - hide all rows from everyone except Admin — useful as a deny-by-default baseline. - -#### Group Keys and Filter Combination - -All applicable RLS filters are combined before being added to the query. The combination -rules are: - -- Filters that share the **same group key** are combined with **OR** (any match within - the group is sufficient). -- Different filter groups (different group keys, or no group key) are combined with - **AND** (all groups must match). -- Filters with **no group key** are each treated as their own group and are always AND'd. - -For example, if a dataset has three filters: - -| Filter | Clause | Group Key | -|--------|--------|-----------| -| F1 | `department = 'Finance'` | `department` | -| F2 | `department = 'Marketing'` | `department` | -| F3 | `region = 'Europe'` | `region` | - -The resulting WHERE clause would be: - -```sql -(department = 'Finance' OR department = 'Marketing') AND (region = 'Europe') -``` - -:::caution Conflicting filters -It is possible to create filters that conflict and produce an empty result set. For -example, the filters `client_id = 4` and `client_id = 5` **without a shared group key** -will be AND'd together, producing `client_id = 4 AND client_id = 5`, which can never -be true. - -If you intend for these to be alternatives, assign them the **same group key** so they -are OR'd instead. -::: - -#### RLS and Virtual (SQL-Based) Datasets - -RLS filters are assigned to **datasets**, not to underlying database tables directly. This -has important implications when working with virtual (SQL-based) datasets: - -- **Physical datasets** (backed directly by a table or view) — RLS filters assigned to - the dataset are added as WHERE clauses to the query. -- **Virtual datasets** (defined by a custom SQL query) — RLS filters assigned directly to - the virtual dataset are applied to the _outer_ query that wraps the dataset's SQL. - Additionally, RLS filters on the **underlying physical datasets** referenced by the - virtual dataset's SQL are injected into the inner subquery for each referenced table. - -For example, if you have: - -1. A physical dataset `orders` with RLS filter `region = 'US'` -2. A virtual dataset defined as `SELECT * FROM orders WHERE status = 'active'` - -A user affected by the RLS filter will effectively see: - -```sql -SELECT * FROM ( - SELECT * FROM orders WHERE (region = 'US') AND status = 'active' -) ... -``` - -**Key considerations for virtual datasets:** - -- You generally do **not** need to duplicate RLS filters on both the physical and virtual - dataset — filters on the physical dataset are applied automatically at query time. -- If you assign an RLS filter directly to a virtual dataset, the clause must reference - columns available in the virtual dataset's _output_, not necessarily the underlying - table's columns. -- In **SQL Lab**, RLS is enforced only when the `RLS_IN_SQLLAB` feature flag is enabled: - queries run against tables that have associated datasets with RLS filters will then have - the appropriate predicates injected automatically. - -#### Checking RLS Filters via the API - -You can use the RLS REST API to audit which filters are configured and which datasets -they affect. This requires the `can_read` permission on the `Row Level Security` resource. - -**List all RLS rules:** - -``` -GET /api/v1/rowlevelsecurity/ -``` - -**Filter RLS rules for a specific dataset** (using [Rison](https://github.com/Nanonid/rison) query syntax): - -``` -GET /api/v1/rowlevelsecurity/?q=(filters:!((col:tables,opr:rel_m_m,value:))) -``` - -**Filter RLS rules by role:** - -``` -GET /api/v1/rowlevelsecurity/?q=(filters:!((col:roles,opr:rel_m_m,value:))) -``` - -**View details of a specific rule** (including clause, assigned datasets, and roles): - -``` -GET /api/v1/rowlevelsecurity/ -``` - -The response includes the filter's `name`, `filter_type` (Regular or Base), `clause`, -`group_key`, assigned `tables` (with id, schema, and table\_name), and assigned `roles` -(with id and name). - -:::tip Auditing RLS for virtual datasets -To find all RLS rules that could affect a particular virtual dataset, query the list -endpoint filtered by that dataset's ID for any directly-assigned rules. Then also check -the physical datasets referenced in the virtual dataset's SQL, since their RLS filters -are applied at query time too. -::: - -### User Sessions - -Superset uses [Flask](https://pypi.org/project/Flask/) -and [Flask-Login](https://pypi.org/project/Flask-Login/) for user session management. - -Session cookies are used to maintain session info and user state between requests, -although they do not contain personal user information they serve the purpose of identifying -a user session on the server side. -The session cookie is encrypted with the application `SECRET_KEY` and cannot be read by the client. -So it's very important to keep the `SECRET_KEY` secret and set to a secure unique complex random value. - -Flask and Flask-Login offer a number of configuration options to control session behavior. - -- Relevant Flask settings: - -`SESSION_COOKIE_HTTPONLY`: (default: `False`): Controls if cookies should be set with the `HttpOnly` flag. - -`SESSION_COOKIE_SECURE`: (default: `False`) Browsers will only send cookies with requests over -HTTPS if the cookie is marked “secure”. The application must be served over HTTPS for this to make sense. - -`SESSION_COOKIE_SAMESITE`: (default: "Lax") Prevents the browser from sending this cookie along with cross-site requests. - -`PERMANENT_SESSION_LIFETIME`: (default: "31 days") The lifetime of a permanent session as a `datetime.timedelta` object. - -#### Switching to server side sessions - -Server side sessions offer benefits over client side sessions on security and performance. -By enabling server side sessions, the session data is stored server side and only a session ID -is sent to the client. When a user logs in, a session is created server side and the session ID -is sent to the client in a cookie. The client will send the session ID with each request and the -server will use it to retrieve the session data. -On logout, the session is destroyed server side and the session cookie is deleted on the client side. -This reduces the risk for replay attacks and session hijacking. - -Superset uses [Flask-Session](https://flask-session.readthedocs.io/en/latest/) to manage server side sessions. -To enable this extension you have to set: - -``` python -SESSION_SERVER_SIDE = True -``` - -Flask-Session offers multiple backend session interfaces for Flask, here's an example for Redis: - -``` python -from redis import Redis - -SESSION_TYPE = "redis" -SESSION_REDIS = Redis(host="redis", port=6379, db=0) -# sign the session cookie sid -SESSION_USE_SIGNER = True -``` - -### Content Security Policy (CSP) - -Superset uses the [Talisman](https://pypi.org/project/flask-talisman/) extension to enable implementation of a -[Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), an added -layer of security that helps to detect and mitigate certain types of attacks, including -Cross-Site Scripting (XSS) and data injection attacks. - -A CSP makes it possible for server administrators to reduce or eliminate the vectors by which XSS can -occur by specifying the domains that the browser should consider to be valid sources of executable scripts. -A CSP-compatible browser will then only execute scripts loaded in source files received from those allowed domains, -ignoring all other scripts (including inline scripts and event-handling HTML attributes). - -A policy is described using a series of policy directives, each of which describes the policy for -a certain resource type or policy area. You can check possible directives -[here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy). - -It's extremely important to correctly configure a Content Security Policy when deploying Superset to -prevent many types of attacks. Superset provides two variables in `config.py` for deploying a CSP: - -- `TALISMAN_ENABLED` defaults to `True`; set this to `False` in order to disable CSP -- `TALISMAN_CONFIG` holds the actual the policy definition (*see example below*) as well as any -other arguments to be passed to Talisman. - -When running in production mode, Superset will check at startup for the presence -of a CSP. If one is not found, it will issue a warning with the security risks. For environments -where CSP policies are defined outside of Superset using other software, administrators can disable -this warning using the `CONTENT_SECURITY_POLICY_WARNING` key in `config.py`. - -#### CSP Requirements - -- Superset needs the `style-src unsafe-inline` CSP directive in order to operate. - - ``` - style-src 'self' 'unsafe-inline' - ``` - -- Only scripts marked with a [nonce](https://content-security-policy.com/nonce/) can be loaded and executed. -Nonce is a random string automatically generated by Talisman on each page load. -You can get current nonce value by calling jinja macro `csp_nonce()`. - - ```html - - ``` - -- Some dashboards load images using data URIs and require `data:` in their `img-src` - - ``` - img-src 'self' data: - ``` - -- MapBox charts use workers and need to connect to MapBox servers in addition to the Superset origin - - ``` - worker-src 'self' blob: - connect-src 'self' https://api.mapbox.com https://events.mapbox.com - ``` - -- Cartodiagram charts request map data (image and json) from external resources that can be edited by users, -and therefore either require a list of allowed domains to request from or a wildcard (`'*'`) for `img-src` and `connect-src`. - -- Other CSP directives default to `'self'` to limit content to the same origin as the Superset server. - -In order to adjust provided CSP configuration to your needs, follow the instructions and examples provided in -[Content Security Policy Reference](https://content-security-policy.com/) - -#### Other Talisman security considerations - -Setting `TALISMAN_ENABLED = True` will invoke Talisman's protection with its default arguments, -of which `content_security_policy` is only one. Those can be found in the -[Talisman documentation](https://pypi.org/project/flask-talisman/) under *Options*. -These generally improve security, but administrators should be aware of their existence. - -In particular, the option of `force_https = True` (`False` by default) may break Superset's Alerts & Reports -if workers are configured to access charts via a `WEBDRIVER_BASEURL` beginning -with `http://`. As long as a Superset deployment enforces https upstream, e.g., -through a load balancer or application gateway, it should be acceptable to keep this -option disabled. Otherwise, you may want to enable `force_https` like this: - -```python -TALISMAN_CONFIG = { - "force_https": True, - "content_security_policy": { ... -``` - -#### Configuring Talisman in Superset - -Talisman settings in Superset can be modified using superset_config.py. If you need to adjust security policies, you can override the default configuration. - -Example: Overriding Talisman Configuration in superset_config.py for loading images form s3 or other external sources. - -```python -TALISMAN_CONFIG = { - "content_security_policy": { - "base-uri": ["'self'"], - "default-src": ["'self'"], - "img-src": [ - "'self'", - "blob:", - "data:", - "https://apachesuperset.gateway.scarf.sh", - "https://static.scarf.sh/", - # "https://cdn.brandfolder.io", # Uncomment when SLACK_ENABLE_AVATARS is True # noqa: E501 - "ows.terrestris.de", - "aws.s3.com", # Add Your Bucket or external data source - ], - "worker-src": ["'self'", "blob:"], - "connect-src": [ - "'self'", - "https://api.mapbox.com", - "https://events.mapbox.com", - ], - "object-src": "'none'", - "style-src": [ - "'self'", - "'unsafe-inline'", - ], - "script-src": ["'self'", "'strict-dynamic'"], - }, - "content_security_policy_nonce_in": ["script-src"], - "force_https": False, - "session_cookie_secure": False, -} -``` - -For more information on setting up Talisman, please refer to -https://superset.apache.org/admin-docs/configuration/networking-settings/#changing-flask-talisman-csp. - -### Reporting Security Vulnerabilities - -Apache Software Foundation takes a rigorous standpoint in annihilating the security issues in its -software projects. Apache Superset is highly sensitive and forthcoming to issues pertaining to its -features and functionality. - -If you have apprehensions regarding Superset security or you discover vulnerability or potential -threat, don’t hesitate to get in touch with the Apache Security Team by dropping a mail at -security@apache.org. In the mail, specify the project name Superset with the description of the -issue or potential threat. You are also urged to recommend the way to reproduce and replicate the -issue. The security team and the Superset community will get back to you after assessing and -analysing the findings. - -PLEASE PAY ATTENTION to report the security issue on the security email before disclosing it on -public domain. The ASF Security Team maintains a page with the description of how vulnerabilities -and potential threats are handled, check [their web page](https://apache.org/security/committers.html) -for more details. diff --git a/docs/components/chart-components/bar-chart.md b/docs/components/chart-components/bar-chart.md deleted file mode 100644 index 2b8e336e6ea..00000000000 --- a/docs/components/chart-components/bar-chart.md +++ /dev/null @@ -1,105 +0,0 @@ - ---- -title: Bar Chart -sidebar_position: 1 ---- - -# Bar Chart Component - -The Bar Chart component is used to visualize categorical data with rectangular bars. - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `data` | `array` | `[]` | Array of data objects to visualize | -| `width` | `number` | `800` | Width of the chart in pixels | -| `height` | `number` | `600` | Height of the chart in pixels | -| `xField` | `string` | - | Field name for x-axis values | -| `yField` | `string` | - | Field name for y-axis values | -| `colorField` | `string` | - | Field name for color encoding | -| `colorScheme` | `string` | `'supersetColors'` | Color scheme to use | -| `showLegend` | `boolean` | `true` | Whether to show the legend | -| `showGrid` | `boolean` | `true` | Whether to show grid lines | -| `labelPosition` | `string` | `'top'` | Position of bar labels: 'top', 'middle', 'bottom' | - -## Examples - -### Basic Bar Chart - -```jsx -import { BarChart } from '@superset-ui/chart-components'; - -const data = [ - { category: 'A', value: 10 }, - { category: 'B', value: 20 }, - { category: 'C', value: 15 }, - { category: 'D', value: 25 }, -]; - -function Example() { - return ( - - ); -} -``` - -### Grouped Bar Chart - -```jsx -import { BarChart } from '@superset-ui/chart-components'; - -const data = [ - { category: 'A', group: 'Group 1', value: 10 }, - { category: 'A', group: 'Group 2', value: 15 }, - { category: 'B', group: 'Group 1', value: 20 }, - { category: 'B', group: 'Group 2', value: 25 }, - { category: 'C', group: 'Group 1', value: 15 }, - { category: 'C', group: 'Group 2', value: 10 }, -]; - -function Example() { - return ( - - ); -} -``` - -## Best Practices - -- Use bar charts when comparing quantities across categories -- Sort bars by value for better readability, unless there's a natural order to the categories -- Use consistent colors for the same categories across different charts -- Consider using horizontal bar charts when category labels are long diff --git a/docs/components/index.md b/docs/components/index.md deleted file mode 100644 index 77fb2a9cca9..00000000000 --- a/docs/components/index.md +++ /dev/null @@ -1,59 +0,0 @@ - ---- -title: Component Library -sidebar_position: 1 ---- - -# Superset Component Library - -Welcome to the Apache Superset Component Library documentation. This section provides comprehensive documentation for all the UI components, chart components, and layout components used in Superset. - -## What is the Component Library? - -The Component Library is a collection of reusable UI components that are used to build the Superset user interface. These components are designed to be consistent, accessible, and easy to use. - -## Component Categories - -The Component Library is organized into the following categories: - -### UI Components - -Basic UI components like buttons, inputs, dropdowns, and other form elements. - -### Chart Components - -Visualization components used to render different types of charts and graphs. - -### Layout Components - -Components used for page layout, such as containers, grids, and navigation elements. - -## Versioning - -The Component Library documentation follows its own versioning scheme, independent from the main Superset documentation. This allows us to update the component documentation as the components evolve, without affecting the main documentation. - -## Getting Started - -Browse the sidebar to explore the different components available in the library. Each component documentation includes: - -- Component description and purpose -- Props and configuration options -- Usage examples -- Best practices diff --git a/docs/components/layout-components/grid.md b/docs/components/layout-components/grid.md deleted file mode 100644 index a0980d2bdcf..00000000000 --- a/docs/components/layout-components/grid.md +++ /dev/null @@ -1,113 +0,0 @@ - ---- -title: Grid -sidebar_position: 1 ---- - -# Grid Component - -The Grid component provides a flexible layout system for arranging content in rows and columns. - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `gutter` | `number` or `[number, number]` | `0` | Grid spacing between items, can be a single number or [horizontal, vertical] | -| `columns` | `number` | `12` | Number of columns in the grid | -| `justify` | `string` | `'start'` | Horizontal alignment: 'start', 'center', 'end', 'space-between', 'space-around' | -| `align` | `string` | `'top'` | Vertical alignment: 'top', 'middle', 'bottom' | -| `wrap` | `boolean` | `true` | Whether to wrap items when they overflow | - -### Row Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `gutter` | `number` or `[number, number]` | `0` | Spacing between items in the row | -| `justify` | `string` | `'start'` | Horizontal alignment for this row | -| `align` | `string` | `'top'` | Vertical alignment for this row | - -### Col Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `span` | `number` | - | Number of columns the grid item spans | -| `offset` | `number` | `0` | Number of columns the grid item is offset | -| `xs`, `sm`, `md`, `lg`, `xl` | `number` or `object` | - | Responsive props for different screen sizes | - -## Examples - -### Basic Grid - -```jsx -import { Grid, Row, Col } from '@superset-ui/core'; - -function Example() { - return ( - - - -
Column 1
- - -
Column 2
- - -
Column 3
- -
-
- ); -} -``` - -### Responsive Grid - -```jsx -import { Grid, Row, Col } from '@superset-ui/core'; - -function Example() { - return ( - - - -
Responsive Column 1
- - -
Responsive Column 2
- - -
Responsive Column 3
- - -
Responsive Column 4
- -
-
- ); -} -``` - -## Best Practices - -- Use the Grid system for complex layouts that need to be responsive -- Specify column widths for different screen sizes to ensure proper responsive behavior -- Use gutters to create appropriate spacing between grid items -- Keep the grid structure consistent throughout your application -- Consider using the grid system for dashboard layouts to ensure consistent spacing and alignment diff --git a/docs/components/test.mdx b/docs/components/test.mdx deleted file mode 100644 index d5f842ad631..00000000000 --- a/docs/components/test.mdx +++ /dev/null @@ -1,35 +0,0 @@ - ---- -title: Test ---- - -import { StoryExample } from '../src/components/StorybookWrapper'; - -# Test - -This is a test using our custom StorybookWrapper component. - - ( -
- This is a simple example component -
- )} -/> diff --git a/docs/components/ui-components/button.mdx b/docs/components/ui-components/button.mdx deleted file mode 100644 index 2102d146efe..00000000000 --- a/docs/components/ui-components/button.mdx +++ /dev/null @@ -1,146 +0,0 @@ - ---- -title: Button Component -sidebar_position: 1 ---- - -import { StoryExample, StoryWithControls } from '../../src/components/StorybookWrapper'; -import { Button } from '../../../superset-frontend/packages/superset-ui-core/src/components/Button'; - -# Button Component - -The Button component is a fundamental UI element used throughout Superset for user interactions. - -## Basic Usage - -The default button with primary styling: - ( - - )} -/> - -## Interactive Example - - ( - - )} - props={{ - buttonStyle: 'primary', - buttonSize: 'default', - label: 'Click Me', - disabled: false - }} - controls={[ - { - name: 'buttonStyle', - label: 'Button Style', - type: 'select', - options: ['primary', 'secondary', 'tertiary', 'success', 'warning', 'danger', 'default', 'link', 'dashed'] - }, - { - name: 'buttonSize', - label: 'Button Size', - type: 'select', - options: ['default', 'small', 'xsmall'] - }, - { - name: 'label', - label: 'Button Text', - type: 'text' - }, - { - name: 'disabled', - label: 'Disabled', - type: 'boolean' - } - ]} -/> - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `buttonStyle` | `'primary' \| 'secondary' \| 'tertiary' \| 'success' \| 'warning' \| 'danger' \| 'default' \| 'link' \| 'dashed'` | `'default'` | Button style | -| `buttonSize` | `'default' \| 'small' \| 'xsmall'` | `'default'` | Button size | -| `disabled` | `boolean` | `false` | Whether the button is disabled | -| `cta` | `boolean` | `false` | Whether the button is a call-to-action button | -| `tooltip` | `ReactNode` | - | Tooltip content | -| `placement` | `TooltipProps['placement']` | - | Tooltip placement | -| `onClick` | `function` | - | Callback when button is clicked | -| `href` | `string` | - | Turns button into an anchor link | -| `target` | `string` | - | Target attribute for anchor links | - -## Usage - -```jsx -import Button from 'src/components/Button'; - -function MyComponent() { - return ( - - ); -} -``` - -## Button Styles - -Superset provides a variety of button styles for different purposes: - -- **Primary**: Used for primary actions -- **Secondary**: Used for secondary actions -- **Tertiary**: Used for less important actions -- **Success**: Used for successful or confirming actions -- **Warning**: Used for actions that require caution -- **Danger**: Used for destructive actions -- **Link**: Used for navigation -- **Dashed**: Used for adding new items or features - -## Button Sizes - -Buttons come in three sizes: - -- **Default**: Standard size for most use cases -- **Small**: Compact size for tight spaces -- **XSmall**: Extra small size for very limited spaces - -## Best Practices - -- Use primary buttons for the main action in a form or page -- Use secondary buttons for alternative actions -- Use danger buttons for destructive actions -- Limit the number of primary buttons on a page to avoid confusion -- Use consistent button styles throughout your application -- Add tooltips to buttons when their purpose might not be immediately clear diff --git a/docs/components_versions.json b/docs/components_versions.json deleted file mode 100644 index fe51488c706..00000000000 --- a/docs/components_versions.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/docs/data/countries.json b/docs/data/countries.json deleted file mode 100644 index 396b001fdf8..00000000000 --- a/docs/data/countries.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "countries": [ - "Afghanistan", - "Aland", - "Albania", - "Algeria", - "American Samoa", - "Andorra", - "Angola", - "Anguilla", - "Antarctica", - "Antigua And Barbuda", - "Argentina", - "Armenia", - "Australia", - "Austria", - "Azerbaijan", - "Bahrain", - "Bangladesh", - "Barbados", - "Belarus", - "Belgium", - "Belize", - "Benin", - "Bermuda", - "Bhutan", - "Bolivia", - "Bosnia And Herzegovina", - "Botswana", - "Brazil", - "Brunei", - "Bulgaria", - "Burkina Faso", - "Burundi", - "Cambodia", - "Cameroon", - "Canada", - "Cape Verde", - "Central African Republic", - "Chad", - "Chile", - "China", - "Colombia", - "Comoros", - "Cook Islands", - "Costa Rica", - "Croatia", - "Cuba", - "Cyprus", - "Czech Republic", - "Democratic Republic Of The Congo", - "Denmark", - "Djibouti", - "Dominica", - "Dominican Republic", - "Ecuador", - "Egypt", - "El Salvador", - "Equatorial Guinea", - "Eritrea", - "Estonia", - "Ethiopia", - "Fiji", - "Finland", - "France", - "France (with overseas)", - "France (regions)", - "French Polynesia", - "Gabon", - "Gambia", - "Germany", - "Ghana", - "Greece", - "Greenland", - "Grenada", - "Guatemala", - "Guinea", - "Guyana", - "Haiti", - "Honduras", - "Hungary", - "Iceland", - "India", - "Indonesia", - "Iran", - "Israel", - "Italy", - "Italy (regions)", - "Ivory Coast", - "Japan", - "Jordan", - "Kazakhstan", - "Kenya", - "Korea", - "Kuwait", - "Kyrgyzstan", - "Laos", - "Latvia", - "Lebanon", - "Lesotho", - "Liberia", - "Libya", - "Liechtenstein", - "Lithuania", - "Luxembourg", - "Macedonia", - "Madagascar", - "Malawi", - "Malaysia", - "Maldives", - "Mali", - "Malta", - "Marshall Islands", - "Mauritania", - "Mauritius", - "Mexico", - "Moldova", - "Mongolia", - "Montenegro", - "Montserrat", - "Morocco", - "Mozambique", - "Myanmar", - "Namibia", - "Nauru", - "Nepal", - "Netherlands", - "New Caledonia", - "New Zealand", - "Nicaragua", - "Niger", - "Nigeria", - "Northern Mariana Islands", - "Norway", - "Oman", - "Pakistan", - "Palau", - "Panama", - "Papua New Guinea", - "Paraguay", - "Peru", - "Philippines", - "Philippines (regions)", - "Poland", - "Portugal", - "Qatar", - "Republic Of Serbia", - "Romania", - "Russia", - "Rwanda", - "Saint Lucia", - "Saint Pierre And Miquelon", - "Saint Vincent And The Grenadines", - "Samoa", - "San Marino", - "Sao Tome And Principe", - "Saudi Arabia", - "Senegal", - "Seychelles", - "Sierra Leone", - "Singapore", - "Slovakia", - "Slovenia", - "Solomon Islands", - "Somalia", - "South Africa", - "Spain", - "Sri Lanka", - "Sudan", - "Suriname", - "Sweden", - "Switzerland", - "Syria", - "Taiwan", - "Tajikistan", - "Tanzania", - "Thailand", - "The Bahamas", - "Timorleste", - "Togo", - "Tonga", - "Trinidad And Tobago", - "Tunisia", - "Turkey", - "Turkey (regions)", - "Turkmenistan", - "Turks And Caicos Islands", - "Uganda", - "UK", - "Ukraine", - "United Arab Emirates", - "United States Minor Outlying Islands", - "United States Virgin Islands", - "Uruguay", - "USA", - "Uzbekistan", - "Vanuatu", - "Venezuela", - "Vietnam", - "Wallis And Futuna", - "Yemen", - "Zambia", - "Zimbabwe" - ] -} diff --git a/docs/developer_docs/api.mdx b/docs/developer_docs/api.mdx deleted file mode 100644 index 64f1b28b885..00000000000 --- a/docs/developer_docs/api.mdx +++ /dev/null @@ -1,650 +0,0 @@ ---- -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. - - - Each endpoint includes ready-to-use code samples in cURL, Python, and JavaScript. - The sidebar includes Schema definitions for detailed data model documentation. - - } - 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](/developer-docs/api/get-the-csrf-token) | `/api/v1/security/csrf_token/` | -| `POST` | [Get a guest token](/developer-docs/api/get-a-guest-token) | `/api/v1/security/guest_token/` | -| `POST` | [Create security login](/developer-docs/api/create-security-login) | `/api/v1/security/login` | -| `POST` | [Create security refresh](/developer-docs/api/create-security-refresh) | `/api/v1/security/refresh` | - ---- - -### API Endpoints - -#### Core Resources - -
-Dashboards (28 endpoints) — Create, read, update, and delete dashboards. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `DELETE` | [Bulk delete dashboards](/developer-docs/api/bulk-delete-dashboards) | `/api/v1/dashboard/` | -| `GET` | [Get a list of dashboards](/developer-docs/api/get-a-list-of-dashboards) | `/api/v1/dashboard/` | -| `POST` | [Create a new dashboard](/developer-docs/api/create-a-new-dashboard) | `/api/v1/dashboard/` | -| `GET` | [Get metadata information about this API resource (dashboard--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-dashboard-info) | `/api/v1/dashboard/_info` | -| `GET` | [Get a dashboard detail information](/developer-docs/api/get-a-dashboard-detail-information) | `/api/v1/dashboard/{id_or_slug}` | -| `GET` | [Get a dashboard's chart definitions.](/developer-docs/api/get-a-dashboards-chart-definitions) | `/api/v1/dashboard/{id_or_slug}/charts` | -| `POST` | [Create a copy of an existing dashboard](/developer-docs/api/create-a-copy-of-an-existing-dashboard) | `/api/v1/dashboard/{id_or_slug}/copy/` | -| `GET` | [Get dashboard's datasets](/developer-docs/api/get-dashboards-datasets) | `/api/v1/dashboard/{id_or_slug}/datasets` | -| `DELETE` | [Delete a dashboard's embedded configuration](/developer-docs/api/delete-a-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` | -| `GET` | [Get the dashboard's embedded configuration](/developer-docs/api/get-the-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` | -| `POST` | [Set a dashboard's embedded configuration](/developer-docs/api/set-a-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` | -| `PUT` | [Update dashboard by id_or_slug embedded](/developer-docs/api/update-dashboard-by-id-or-slug-embedded) | `/api/v1/dashboard/{id_or_slug}/embedded` | -| `GET` | [Get dashboard's tabs](/developer-docs/api/get-dashboards-tabs) | `/api/v1/dashboard/{id_or_slug}/tabs` | -| `DELETE` | [Delete a dashboard](/developer-docs/api/delete-a-dashboard) | `/api/v1/dashboard/{pk}` | -| `PUT` | [Update a dashboard](/developer-docs/api/update-a-dashboard) | `/api/v1/dashboard/{pk}` | -| `POST` | [Compute and cache a screenshot (dashboard-pk-cache-dashboard-screenshot)](/developer-docs/api/compute-and-cache-a-screenshot-dashboard-pk-cache-dashboard-screenshot) | `/api/v1/dashboard/{pk}/cache_dashboard_screenshot/` | -| `PUT` | [Update chart customizations configuration for a dashboard.](/developer-docs/api/update-chart-customizations-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/chart_customizations` | -| `PUT` | [Update colors configuration for a dashboard.](/developer-docs/api/update-colors-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/colors` | -| `GET` | [Export dashboard as example bundle](/developer-docs/api/export-dashboard-as-example-bundle) | `/api/v1/dashboard/{pk}/export_as_example/` | -| `DELETE` | [Remove the dashboard from the user favorite list](/developer-docs/api/remove-the-dashboard-from-the-user-favorite-list) | `/api/v1/dashboard/{pk}/favorites/` | -| `POST` | [Mark the dashboard as favorite for the current user](/developer-docs/api/mark-the-dashboard-as-favorite-for-the-current-user) | `/api/v1/dashboard/{pk}/favorites/` | -| `PUT` | [Update native filters configuration for a dashboard.](/developer-docs/api/update-native-filters-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/filters` | -| `GET` | [Get a computed screenshot from cache (dashboard-pk-screenshot-digest)](/developer-docs/api/get-a-computed-screenshot-from-cache-dashboard-pk-screenshot-digest) | `/api/v1/dashboard/{pk}/screenshot/{digest}/` | -| `GET` | [Get dashboard's thumbnail](/developer-docs/api/get-dashboards-thumbnail) | `/api/v1/dashboard/{pk}/thumbnail/{digest}/` | -| `GET` | [Download multiple dashboards as YAML files](/developer-docs/api/download-multiple-dashboards-as-yaml-files) | `/api/v1/dashboard/export/` | -| `GET` | [Check favorited dashboards for current user](/developer-docs/api/check-favorited-dashboards-for-current-user) | `/api/v1/dashboard/favorite_status/` | -| `POST` | [Import dashboard(s) with associated charts/datasets/databases](/developer-docs/api/import-dashboard-s-with-associated-charts-datasets-databases) | `/api/v1/dashboard/import/` | -| `GET` | [Get related fields data (dashboard-related-column-name)](/developer-docs/api/get-related-fields-data-dashboard-related-column-name) | `/api/v1/dashboard/related/{column_name}` | - -
- -
-Charts (20 endpoints) — Create, read, update, and delete charts (slices). - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `DELETE` | [Bulk delete charts](/developer-docs/api/bulk-delete-charts) | `/api/v1/chart/` | -| `GET` | [Get a list of charts](/developer-docs/api/get-a-list-of-charts) | `/api/v1/chart/` | -| `POST` | [Create a new chart](/developer-docs/api/create-a-new-chart) | `/api/v1/chart/` | -| `GET` | [Get metadata information about this API resource (chart--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-chart-info) | `/api/v1/chart/_info` | -| `GET` | [Get a chart detail information](/developer-docs/api/get-a-chart-detail-information) | `/api/v1/chart/{id_or_uuid}` | -| `DELETE` | [Delete a chart](/developer-docs/api/delete-a-chart) | `/api/v1/chart/{pk}` | -| `PUT` | [Update a chart](/developer-docs/api/update-a-chart) | `/api/v1/chart/{pk}` | -| `GET` | [Compute and cache a screenshot (chart-pk-cache-screenshot)](/developer-docs/api/compute-and-cache-a-screenshot-chart-pk-cache-screenshot) | `/api/v1/chart/{pk}/cache_screenshot/` | -| `GET` | [Return payload data response for a chart](/developer-docs/api/return-payload-data-response-for-a-chart) | `/api/v1/chart/{pk}/data/` | -| `DELETE` | [Remove the chart from the user favorite list](/developer-docs/api/remove-the-chart-from-the-user-favorite-list) | `/api/v1/chart/{pk}/favorites/` | -| `POST` | [Mark the chart as favorite for the current user](/developer-docs/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)](/developer-docs/api/get-a-computed-screenshot-from-cache-chart-pk-screenshot-digest) | `/api/v1/chart/{pk}/screenshot/{digest}/` | -| `GET` | [Get chart thumbnail](/developer-docs/api/get-chart-thumbnail) | `/api/v1/chart/{pk}/thumbnail/{digest}/` | -| `POST` | [Return payload data response for the given query (chart-data)](/developer-docs/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)](/developer-docs/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](/developer-docs/api/download-multiple-charts-as-yaml-files) | `/api/v1/chart/export/` | -| `GET` | [Check favorited charts for current user](/developer-docs/api/check-favorited-charts-for-current-user) | `/api/v1/chart/favorite_status/` | -| `POST` | [Import chart(s) with associated datasets and databases](/developer-docs/api/import-chart-s-with-associated-datasets-and-databases) | `/api/v1/chart/import/` | -| `GET` | [Get related fields data (chart-related-column-name)](/developer-docs/api/get-related-fields-data-chart-related-column-name) | `/api/v1/chart/related/{column_name}` | -| `PUT` | [Warm up the cache for the chart](/developer-docs/api/warm-up-the-cache-for-the-chart) | `/api/v1/chart/warm_up_cache` | - -
- -
-Datasets (19 endpoints) — Manage datasets (tables) used for building charts. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `DELETE` | [Bulk delete datasets](/developer-docs/api/bulk-delete-datasets) | `/api/v1/dataset/` | -| `GET` | [Get a list of datasets](/developer-docs/api/get-a-list-of-datasets) | `/api/v1/dataset/` | -| `POST` | [Create a new dataset](/developer-docs/api/create-a-new-dataset) | `/api/v1/dataset/` | -| `GET` | [Get metadata information about this API resource (dataset--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-dataset-info) | `/api/v1/dataset/_info` | -| `GET` | [Get a dataset](/developer-docs/api/get-a-dataset) | `/api/v1/dataset/{id_or_uuid}` | -| `GET` | [Get charts and dashboards count associated to a dataset](/developer-docs/api/get-charts-and-dashboards-count-associated-to-a-dataset) | `/api/v1/dataset/{id_or_uuid}/related_objects` | -| `DELETE` | [Delete a dataset](/developer-docs/api/delete-a-dataset) | `/api/v1/dataset/{pk}` | -| `PUT` | [Update a dataset](/developer-docs/api/update-a-dataset) | `/api/v1/dataset/{pk}` | -| `DELETE` | [Delete a dataset column](/developer-docs/api/delete-a-dataset-column) | `/api/v1/dataset/{pk}/column/{column_id}` | -| `GET` | [Get dataset drill info](/developer-docs/api/get-dataset-drill-info) | `/api/v1/dataset/{pk}/drill_info/` | -| `DELETE` | [Delete a dataset metric](/developer-docs/api/delete-a-dataset-metric) | `/api/v1/dataset/{pk}/metric/{metric_id}` | -| `PUT` | [Refresh and update columns of a dataset](/developer-docs/api/refresh-and-update-columns-of-a-dataset) | `/api/v1/dataset/{pk}/refresh` | -| `GET` | [Get distinct values from field data (dataset-distinct-column-name)](/developer-docs/api/get-distinct-values-from-field-data-dataset-distinct-column-name) | `/api/v1/dataset/distinct/{column_name}` | -| `POST` | [Duplicate a dataset](/developer-docs/api/duplicate-a-dataset) | `/api/v1/dataset/duplicate` | -| `GET` | [Download multiple datasets as YAML files](/developer-docs/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](/developer-docs/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](/developer-docs/api/import-dataset-s-with-associated-databases) | `/api/v1/dataset/import/` | -| `GET` | [Get related fields data (dataset-related-column-name)](/developer-docs/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](/developer-docs/api/warm-up-the-cache-for-each-chart-powered-by-the-given-table) | `/api/v1/dataset/warm_up_cache` | - -
- -
-Database (30 endpoints) — Manage database connections and metadata. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get a list of databases](/developer-docs/api/get-a-list-of-databases) | `/api/v1/database/` | -| `POST` | [Create a new database](/developer-docs/api/create-a-new-database) | `/api/v1/database/` | -| `GET` | [Get metadata information about this API resource (database--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-database-info) | `/api/v1/database/_info` | -| `DELETE` | [Delete a database](/developer-docs/api/delete-a-database) | `/api/v1/database/{pk}` | -| `GET` | [Get a database](/developer-docs/api/get-a-database) | `/api/v1/database/{pk}` | -| `PUT` | [Change a database](/developer-docs/api/change-a-database) | `/api/v1/database/{pk}` | -| `GET` | [Get all catalogs from a database](/developer-docs/api/get-all-catalogs-from-a-database) | `/api/v1/database/{pk}/catalogs/` | -| `GET` | [Get a database connection info](/developer-docs/api/get-a-database-connection-info) | `/api/v1/database/{pk}/connection` | -| `GET` | [Get function names supported by a database](/developer-docs/api/get-function-names-supported-by-a-database) | `/api/v1/database/{pk}/function_names/` | -| `GET` | [Get charts and dashboards count associated to a database](/developer-docs/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](/developer-docs/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](/developer-docs/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)](/developer-docs/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)](/developer-docs/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}/` | -| `POST` | [Re-sync all permissions for a database connection](/developer-docs/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)](/developer-docs/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](/developer-docs/api/get-table-metadata) | `/api/v1/database/{pk}/table_metadata/` | -| `GET` | [Get table extra metadata (database-pk-table-metadata-extra)](/developer-docs/api/get-table-extra-metadata-database-pk-table-metadata-extra) | `/api/v1/database/{pk}/table_metadata/extra/` | -| `GET` | [Get database table metadata](/developer-docs/api/get-database-table-metadata) | `/api/v1/database/{pk}/table/{table_name}/{schema_name}/` | -| `GET` | [Get a list of tables for given database](/developer-docs/api/get-a-list-of-tables-for-given-database) | `/api/v1/database/{pk}/tables/` | -| `POST` | [Upload a file to a database table](/developer-docs/api/upload-a-file-to-a-database-table) | `/api/v1/database/{pk}/upload/` | -| `POST` | [Validate arbitrary SQL](/developer-docs/api/validate-arbitrary-sql) | `/api/v1/database/{pk}/validate_sql/` | -| `GET` | [Get names of databases currently available](/developer-docs/api/get-names-of-databases-currently-available) | `/api/v1/database/available/` | -| `GET` | [Download database(s) and associated dataset(s) as a zip file](/developer-docs/api/download-database-s-and-associated-dataset-s-as-a-zip-file) | `/api/v1/database/export/` | -| `POST` | [Import database(s) with associated datasets](/developer-docs/api/import-database-s-with-associated-datasets) | `/api/v1/database/import/` | -| `GET` | [Receive personal access tokens from OAuth2](/developer-docs/api/receive-personal-access-tokens-from-o-auth-2) | `/api/v1/database/oauth2/` | -| `GET` | [Get related fields data (database-related-column-name)](/developer-docs/api/get-related-fields-data-database-related-column-name) | `/api/v1/database/related/{column_name}` | -| `POST` | [Test a database connection](/developer-docs/api/test-a-database-connection) | `/api/v1/database/test_connection/` | -| `POST` | [Upload a file and returns file metadata](/developer-docs/api/upload-a-file-and-returns-file-metadata) | `/api/v1/database/upload_metadata/` | -| `POST` | [Validate database connection parameters](/developer-docs/api/validate-database-connection-parameters) | `/api/v1/database/validate_parameters/` | - -
- -#### Data Exploration - -
-Explore (1 endpoints) — Chart exploration and data querying endpoints. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Assemble Explore related information in a single endpoint](/developer-docs/api/assemble-explore-related-information-in-a-single-endpoint) | `/api/v1/explore/` | - -
- -
-SQL Lab (7 endpoints) — Execute SQL queries and manage SQL Lab sessions. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get the bootstrap data for SqlLab page](/developer-docs/api/get-the-bootstrap-data-for-sql-lab-page) | `/api/v1/sqllab/` | -| `POST` | [Estimate the SQL query execution cost](/developer-docs/api/estimate-the-sql-query-execution-cost) | `/api/v1/sqllab/estimate/` | -| `POST` | [Execute a SQL query](/developer-docs/api/execute-a-sql-query) | `/api/v1/sqllab/execute/` | -| `POST` | [Export SQL query results to CSV with streaming](/developer-docs/api/export-sql-query-results-to-csv-with-streaming) | `/api/v1/sqllab/export_streaming/` | -| `GET` | [Export the SQL query results to a CSV](/developer-docs/api/export-the-sql-query-results-to-a-csv) | `/api/v1/sqllab/export/{client_id}/` | -| `POST` | [Format SQL code](/developer-docs/api/format-sql-code) | `/api/v1/sqllab/format_sql/` | -| `GET` | [Get the result of a SQL query execution](/developer-docs/api/get-the-result-of-a-sql-query-execution) | `/api/v1/sqllab/results/` | - -
- -
-Queries (17 endpoints) — View and manage SQL Lab query history. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get a list of queries](/developer-docs/api/get-a-list-of-queries) | `/api/v1/query/` | -| `GET` | [Get query detail information](/developer-docs/api/get-query-detail-information) | `/api/v1/query/{pk}` | -| `GET` | [Get distinct values from field data (query-distinct-column-name)](/developer-docs/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)](/developer-docs/api/get-related-fields-data-query-related-column-name) | `/api/v1/query/related/{column_name}` | -| `POST` | [Manually stop a query with client_id](/developer-docs/api/manually-stop-a-query-with-client-id) | `/api/v1/query/stop` | -| `GET` | [Get a list of queries that changed after last_updated_ms](/developer-docs/api/get-a-list-of-queries-that-changed-after-last-updated-ms) | `/api/v1/query/updated_since` | -| `DELETE` | [Bulk delete saved queries](/developer-docs/api/bulk-delete-saved-queries) | `/api/v1/saved_query/` | -| `GET` | [Get a list of saved queries](/developer-docs/api/get-a-list-of-saved-queries) | `/api/v1/saved_query/` | -| `POST` | [Create a saved query](/developer-docs/api/create-a-saved-query) | `/api/v1/saved_query/` | -| `GET` | [Get metadata information about this API resource (saved-query--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-saved-query-info) | `/api/v1/saved_query/_info` | -| `DELETE` | [Delete a saved query](/developer-docs/api/delete-a-saved-query) | `/api/v1/saved_query/{pk}` | -| `GET` | [Get a saved query](/developer-docs/api/get-a-saved-query) | `/api/v1/saved_query/{pk}` | -| `PUT` | [Update a saved query](/developer-docs/api/update-a-saved-query) | `/api/v1/saved_query/{pk}` | -| `GET` | [Get distinct values from field data (saved-query-distinct-column-name)](/developer-docs/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](/developer-docs/api/download-multiple-saved-queries-as-yaml-files) | `/api/v1/saved_query/export/` | -| `POST` | [Import saved queries with associated databases](/developer-docs/api/import-saved-queries-with-associated-databases) | `/api/v1/saved_query/import/` | -| `GET` | [Get related fields data (saved-query-related-column-name)](/developer-docs/api/get-related-fields-data-saved-query-related-column-name) | `/api/v1/saved_query/related/{column_name}` | - -
- -
-Datasources (2 endpoints) — Query datasource metadata and column values. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get possible values for a datasource column](/developer-docs/api/get-possible-values-for-a-datasource-column) | `/api/v1/datasource/{datasource_type}/{datasource_id}/column/{column_name}/values/` | -| `POST` | [Validate a SQL expression against a datasource](/developer-docs/api/validate-a-sql-expression-against-a-datasource) | `/api/v1/datasource/{datasource_type}/{datasource_id}/validate_expression/` | - -
- -
-Advanced Data Type (2 endpoints) — Advanced data type operations and conversions. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Return an AdvancedDataTypeResponse](/developer-docs/api/return-an-advanced-data-type-response) | `/api/v1/advanced_data_type/convert` | -| `GET` | [Return a list of available advanced data types](/developer-docs/api/return-a-list-of-available-advanced-data-types) | `/api/v1/advanced_data_type/types` | - -
- -#### Organization & Customization - -
-Tags (15 endpoints) — Organize assets with tags. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `DELETE` | [Bulk delete tags](/developer-docs/api/bulk-delete-tags) | `/api/v1/tag/` | -| `GET` | [Get a list of tags](/developer-docs/api/get-a-list-of-tags) | `/api/v1/tag/` | -| `POST` | [Create a tag](/developer-docs/api/create-a-tag) | `/api/v1/tag/` | -| `GET` | [Get metadata information about tag API endpoints](/developer-docs/api/get-metadata-information-about-tag-api-endpoints) | `/api/v1/tag/_info` | -| `POST` | [Add tags to an object](/developer-docs/api/add-tags-to-an-object) | `/api/v1/tag/{object_type}/{object_id}/` | -| `DELETE` | [Delete a tagged object](/developer-docs/api/delete-a-tagged-object) | `/api/v1/tag/{object_type}/{object_id}/{tag}/` | -| `DELETE` | [Delete a tag](/developer-docs/api/delete-a-tag) | `/api/v1/tag/{pk}` | -| `GET` | [Get a tag detail information](/developer-docs/api/get-a-tag-detail-information) | `/api/v1/tag/{pk}` | -| `PUT` | [Update a tag](/developer-docs/api/update-a-tag) | `/api/v1/tag/{pk}` | -| `DELETE` | [Delete tag by pk favorites](/developer-docs/api/delete-tag-by-pk-favorites) | `/api/v1/tag/{pk}/favorites/` | -| `POST` | [Create tag by pk favorites](/developer-docs/api/create-tag-by-pk-favorites) | `/api/v1/tag/{pk}/favorites/` | -| `POST` | [Bulk create tags and tagged objects](/developer-docs/api/bulk-create-tags-and-tagged-objects) | `/api/v1/tag/bulk_create` | -| `GET` | [Get tag favorite status](/developer-docs/api/get-tag-favorite-status) | `/api/v1/tag/favorite_status/` | -| `GET` | [Get all objects associated with a tag](/developer-docs/api/get-all-objects-associated-with-a-tag) | `/api/v1/tag/get_objects/` | -| `GET` | [Get related fields data (tag-related-column-name)](/developer-docs/api/get-related-fields-data-tag-related-column-name) | `/api/v1/tag/related/{column_name}` | - -
- -
-Annotation Layers (14 endpoints) — Manage annotation layers and annotations for charts. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `DELETE` | [Delete multiple annotation layers in a bulk operation](/developer-docs/api/delete-multiple-annotation-layers-in-a-bulk-operation) | `/api/v1/annotation_layer/` | -| `GET` | [Get a list of annotation layers (annotation-layer)](/developer-docs/api/get-a-list-of-annotation-layers-annotation-layer) | `/api/v1/annotation_layer/` | -| `POST` | [Create an annotation layer (annotation-layer)](/developer-docs/api/create-an-annotation-layer-annotation-layer) | `/api/v1/annotation_layer/` | -| `GET` | [Get metadata information about this API resource (annotation-layer--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-annotation-layer-info) | `/api/v1/annotation_layer/_info` | -| `DELETE` | [Delete annotation layer (annotation-layer-pk)](/developer-docs/api/delete-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` | -| `GET` | [Get an annotation layer (annotation-layer-pk)](/developer-docs/api/get-an-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` | -| `PUT` | [Update an annotation layer (annotation-layer-pk)](/developer-docs/api/update-an-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` | -| `DELETE` | [Bulk delete annotation layers](/developer-docs/api/bulk-delete-annotation-layers) | `/api/v1/annotation_layer/{pk}/annotation/` | -| `GET` | [Get a list of annotation layers (annotation-layer-pk-annotation)](/developer-docs/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)](/developer-docs/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)](/developer-docs/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)](/developer-docs/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)](/developer-docs/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)](/developer-docs/api/get-related-fields-data-annotation-layer-related-column-name) | `/api/v1/annotation_layer/related/{column_name}` | - -
- -
-CSS Templates (8 endpoints) — Manage CSS templates for custom dashboard styling. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `DELETE` | [Bulk delete CSS templates](/developer-docs/api/bulk-delete-css-templates) | `/api/v1/css_template/` | -| `GET` | [Get a list of CSS templates](/developer-docs/api/get-a-list-of-css-templates) | `/api/v1/css_template/` | -| `POST` | [Create a CSS template](/developer-docs/api/create-a-css-template) | `/api/v1/css_template/` | -| `GET` | [Get metadata information about this API resource (css-template--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-css-template-info) | `/api/v1/css_template/_info` | -| `DELETE` | [Delete a CSS template](/developer-docs/api/delete-a-css-template) | `/api/v1/css_template/{pk}` | -| `GET` | [Get a CSS template](/developer-docs/api/get-a-css-template) | `/api/v1/css_template/{pk}` | -| `PUT` | [Update a CSS template](/developer-docs/api/update-a-css-template) | `/api/v1/css_template/{pk}` | -| `GET` | [Get related fields data (css-template-related-column-name)](/developer-docs/api/get-related-fields-data-css-template-related-column-name) | `/api/v1/css_template/related/{column_name}` | - -
- -#### Sharing & Embedding - -
-Dashboard Permanent Link (2 endpoints) — Permanent links to dashboard states. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `POST` | [Create a new dashboard's permanent link](/developer-docs/api/create-a-new-dashboards-permanent-link) | `/api/v1/dashboard/{pk}/permalink` | -| `GET` | [Get dashboard's permanent link state](/developer-docs/api/get-dashboards-permanent-link-state) | `/api/v1/dashboard/permalink/{key}` | - -
- -
-Explore Permanent Link (2 endpoints) — Permanent links to chart explore states. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `POST` | [Create a new permanent link (explore-permalink)](/developer-docs/api/create-a-new-permanent-link-explore-permalink) | `/api/v1/explore/permalink` | -| `GET` | [Get chart's permanent link state](/developer-docs/api/get-charts-permanent-link-state) | `/api/v1/explore/permalink/{key}` | - -
- -
-SQL Lab Permanent Link (2 endpoints) — Permanent links to SQL Lab states. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `POST` | [Create a new permanent link (sqllab-permalink)](/developer-docs/api/create-a-new-permanent-link-sqllab-permalink) | `/api/v1/sqllab/permalink` | -| `GET` | [Get permanent link state for SQLLab editor.](/developer-docs/api/get-permanent-link-state-for-sql-lab-editor) | `/api/v1/sqllab/permalink/{key}` | - -
- -
-Embedded Dashboard (1 endpoints) — Configure embedded dashboard settings. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get a report schedule log (embedded-dashboard-uuid)](/developer-docs/api/get-a-report-schedule-log-embedded-dashboard-uuid) | `/api/v1/embedded_dashboard/{uuid}` | - -
- -
-Dashboard Filter State (4 endpoints) — Manage temporary filter state for dashboards. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `POST` | [Create a dashboard's filter state](/developer-docs/api/create-a-dashboards-filter-state) | `/api/v1/dashboard/{pk}/filter_state` | -| `DELETE` | [Delete a dashboard's filter state value](/developer-docs/api/delete-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` | -| `GET` | [Get a dashboard's filter state value](/developer-docs/api/get-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` | -| `PUT` | [Update a dashboard's filter state value](/developer-docs/api/update-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` | - -
- -
-Explore Form Data (4 endpoints) — Manage temporary form data for chart exploration. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `POST` | [Create a new form_data](/developer-docs/api/create-a-new-form-data) | `/api/v1/explore/form_data` | -| `DELETE` | [Delete a form_data](/developer-docs/api/delete-a-form-data) | `/api/v1/explore/form_data/{key}` | -| `GET` | [Get a form_data](/developer-docs/api/get-a-form-data) | `/api/v1/explore/form_data/{key}` | -| `PUT` | [Update an existing form_data](/developer-docs/api/update-an-existing-form-data) | `/api/v1/explore/form_data/{key}` | - -
- -#### Scheduling & Alerts - -
-Report Schedules (11 endpoints) — Configure scheduled reports and alerts. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `DELETE` | [Bulk delete report schedules](/developer-docs/api/bulk-delete-report-schedules) | `/api/v1/report/` | -| `GET` | [Get a list of report schedules](/developer-docs/api/get-a-list-of-report-schedules) | `/api/v1/report/` | -| `POST` | [Create a report schedule](/developer-docs/api/create-a-report-schedule) | `/api/v1/report/` | -| `GET` | [Get metadata information about this API resource (report--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-report-info) | `/api/v1/report/_info` | -| `DELETE` | [Delete a report schedule](/developer-docs/api/delete-a-report-schedule) | `/api/v1/report/{pk}` | -| `GET` | [Get a report schedule](/developer-docs/api/get-a-report-schedule) | `/api/v1/report/{pk}` | -| `PUT` | [Update a report schedule](/developer-docs/api/update-a-report-schedule) | `/api/v1/report/{pk}` | -| `GET` | [Get a list of report schedule logs](/developer-docs/api/get-a-list-of-report-schedule-logs) | `/api/v1/report/{pk}/log/` | -| `GET` | [Get a report schedule log (report-pk-log-log-id)](/developer-docs/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)](/developer-docs/api/get-related-fields-data-report-related-column-name) | `/api/v1/report/related/{column_name}` | -| `GET` | [Get slack channels](/developer-docs/api/get-slack-channels) | `/api/v1/report/slack_channels/` | - -
- -#### Security & Access Control - -
-Security Roles (11 endpoints) — Manage security roles and their permissions. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get security roles](/developer-docs/api/get-security-roles) | `/api/v1/security/roles/` | -| `POST` | [Create security roles](/developer-docs/api/create-security-roles) | `/api/v1/security/roles/` | -| `GET` | [Get security roles info](/developer-docs/api/get-security-roles-info) | `/api/v1/security/roles/_info` | -| `DELETE` | [Delete security roles by pk](/developer-docs/api/delete-security-roles-by-pk) | `/api/v1/security/roles/{pk}` | -| `GET` | [Get security roles by pk](/developer-docs/api/get-security-roles-by-pk) | `/api/v1/security/roles/{pk}` | -| `PUT` | [Update security roles by pk](/developer-docs/api/update-security-roles-by-pk) | `/api/v1/security/roles/{pk}` | -| `PUT` | [Update security roles by role_id groups](/developer-docs/api/update-security-roles-by-role-id-groups) | `/api/v1/security/roles/{role_id}/groups` | -| `POST` | [Create security roles by role_id permissions](/developer-docs/api/create-security-roles-by-role-id-permissions) | `/api/v1/security/roles/{role_id}/permissions` | -| `GET` | [Get security roles by role_id permissions](/developer-docs/api/get-security-roles-by-role-id-permissions) | `/api/v1/security/roles/{role_id}/permissions/` | -| `PUT` | [Update security roles by role_id users](/developer-docs/api/update-security-roles-by-role-id-users) | `/api/v1/security/roles/{role_id}/users` | -| `GET` | [List roles](/developer-docs/api/list-roles) | `/api/v1/security/roles/search/` | - -
- -
-Security Users (6 endpoints) — Manage user accounts. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get security users](/developer-docs/api/get-security-users) | `/api/v1/security/users/` | -| `POST` | [Create security users](/developer-docs/api/create-security-users) | `/api/v1/security/users/` | -| `GET` | [Get security users info](/developer-docs/api/get-security-users-info) | `/api/v1/security/users/_info` | -| `DELETE` | [Delete security users by pk](/developer-docs/api/delete-security-users-by-pk) | `/api/v1/security/users/{pk}` | -| `GET` | [Get security users by pk](/developer-docs/api/get-security-users-by-pk) | `/api/v1/security/users/{pk}` | -| `PUT` | [Update security users by pk](/developer-docs/api/update-security-users-by-pk) | `/api/v1/security/users/{pk}` | - -
- -
-Security Permissions (3 endpoints) — View available permissions. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get security permissions](/developer-docs/api/get-security-permissions) | `/api/v1/security/permissions/` | -| `GET` | [Get security permissions info](/developer-docs/api/get-security-permissions-info) | `/api/v1/security/permissions/_info` | -| `GET` | [Get security permissions by pk](/developer-docs/api/get-security-permissions-by-pk) | `/api/v1/security/permissions/{pk}` | - -
- -
-Security Resources (View Menus) (6 endpoints) — Manage security resources (view menus). - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get security resources](/developer-docs/api/get-security-resources) | `/api/v1/security/resources/` | -| `POST` | [Create security resources](/developer-docs/api/create-security-resources) | `/api/v1/security/resources/` | -| `GET` | [Get security resources info](/developer-docs/api/get-security-resources-info) | `/api/v1/security/resources/_info` | -| `DELETE` | [Delete security resources by pk](/developer-docs/api/delete-security-resources-by-pk) | `/api/v1/security/resources/{pk}` | -| `GET` | [Get security resources by pk](/developer-docs/api/get-security-resources-by-pk) | `/api/v1/security/resources/{pk}` | -| `PUT` | [Update security resources by pk](/developer-docs/api/update-security-resources-by-pk) | `/api/v1/security/resources/{pk}` | - -
- -
-Security Permissions on Resources (View Menus) (6 endpoints) — Permission-resource mappings. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get security permissions resources](/developer-docs/api/get-security-permissions-resources) | `/api/v1/security/permissions-resources/` | -| `POST` | [Create security permissions resources](/developer-docs/api/create-security-permissions-resources) | `/api/v1/security/permissions-resources/` | -| `GET` | [Get security permissions resources info](/developer-docs/api/get-security-permissions-resources-info) | `/api/v1/security/permissions-resources/_info` | -| `DELETE` | [Delete security permissions resources by pk](/developer-docs/api/delete-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` | -| `GET` | [Get security permissions resources by pk](/developer-docs/api/get-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` | -| `PUT` | [Update security permissions resources by pk](/developer-docs/api/update-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` | - -
- -
-Row Level Security (8 endpoints) — Manage row-level security rules for data access. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `DELETE` | [Bulk delete RLS rules](/developer-docs/api/bulk-delete-rls-rules) | `/api/v1/rowlevelsecurity/` | -| `GET` | [Get a list of RLS](/developer-docs/api/get-a-list-of-rls) | `/api/v1/rowlevelsecurity/` | -| `POST` | [Create a new RLS rule](/developer-docs/api/create-a-new-rls-rule) | `/api/v1/rowlevelsecurity/` | -| `GET` | [Get metadata information about this API resource (rowlevelsecurity--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-rowlevelsecurity-info) | `/api/v1/rowlevelsecurity/_info` | -| `DELETE` | [Delete an RLS](/developer-docs/api/delete-an-rls) | `/api/v1/rowlevelsecurity/{pk}` | -| `GET` | [Get an RLS](/developer-docs/api/get-an-rls) | `/api/v1/rowlevelsecurity/{pk}` | -| `PUT` | [Update an RLS rule](/developer-docs/api/update-an-rls-rule) | `/api/v1/rowlevelsecurity/{pk}` | -| `GET` | [Get related fields data (rowlevelsecurity-related-column-name)](/developer-docs/api/get-related-fields-data-rowlevelsecurity-related-column-name) | `/api/v1/rowlevelsecurity/related/{column_name}` | - -
- -#### Import/Export & Administration - -
-Import/export (2 endpoints) — Import and export Superset assets. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Export all assets](/developer-docs/api/export-all-assets) | `/api/v1/assets/export/` | -| `POST` | [Import multiple assets](/developer-docs/api/import-multiple-assets) | `/api/v1/assets/import/` | - -
- -
-CacheRestApi (1 endpoints) — Cache management and invalidation operations. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `POST` | [Invalidate cache records and remove the database records](/developer-docs/api/invalidate-cache-records-and-remove-the-database-records) | `/api/v1/cachekey/invalidate` | - -
- -
-LogRestApi (4 endpoints) — Access audit logs and activity history. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get a list of logs](/developer-docs/api/get-a-list-of-logs) | `/api/v1/log/` | -| `POST` | [Create log](/developer-docs/api/create-log) | `/api/v1/log/` | -| `GET` | [Get a log detail information](/developer-docs/api/get-a-log-detail-information) | `/api/v1/log/{pk}` | -| `GET` | [Get recent activity data for a user](/developer-docs/api/get-recent-activity-data-for-a-user) | `/api/v1/log/recent_activity/` | - -
- -#### User & System - -
-Current User (3 endpoints) — Get information about the authenticated user. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get the user object](/developer-docs/api/get-the-user-object) | `/api/v1/me/` | -| `PUT` | [Update the current user](/developer-docs/api/update-the-current-user) | `/api/v1/me/` | -| `GET` | [Get the user roles](/developer-docs/api/get-the-user-roles) | `/api/v1/me/roles/` | - -
- -
-User (1 endpoints) — User profile and preferences. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get the user avatar](/developer-docs/api/get-the-user-avatar) | `/api/v1/user/{user_id}/avatar.png` | - -
- -
-Menu (1 endpoints) — Get the Superset menu structure. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get menu](/developer-docs/api/get-menu) | `/api/v1/menu/` | - -
- -
-Available Domains (1 endpoints) — Get available domains for the Superset instance. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get all available domains](/developer-docs/api/get-all-available-domains) | `/api/v1/available_domains/` | - -
- -
-AsyncEventsRestApi (1 endpoints) — Real-time event streaming via Server-Sent Events (SSE). - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Read off of the Redis events stream](/developer-docs/api/read-off-of-the-redis-events-stream) | `/api/v1/async_event/` | - -
- -
-OpenApi (1 endpoints) — Access the OpenAPI specification. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get api by version openapi](/developer-docs/api/get-api-by-version-openapi) | `/api/{version}/_openapi` | - -
- -#### Other - -
-Security Groups (6 endpoints) — Endpoints related to Security Groups. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get security groups](/developer-docs/api/get-security-groups) | `/api/v1/security/groups/` | -| `POST` | [Create security groups](/developer-docs/api/create-security-groups) | `/api/v1/security/groups/` | -| `GET` | [Get security groups info](/developer-docs/api/get-security-groups-info) | `/api/v1/security/groups/_info` | -| `DELETE` | [Delete security groups by pk](/developer-docs/api/delete-security-groups-by-pk) | `/api/v1/security/groups/{pk}` | -| `GET` | [Get security groups by pk](/developer-docs/api/get-security-groups-by-pk) | `/api/v1/security/groups/{pk}` | -| `PUT` | [Update security groups by pk](/developer-docs/api/update-security-groups-by-pk) | `/api/v1/security/groups/{pk}` | - -
- -
-Themes (14 endpoints) — Manage UI themes for customizing Superset's appearance. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `DELETE` | [Bulk delete themes](/developer-docs/api/bulk-delete-themes) | `/api/v1/theme/` | -| `GET` | [Get a list of themes](/developer-docs/api/get-a-list-of-themes) | `/api/v1/theme/` | -| `POST` | [Create a theme](/developer-docs/api/create-a-theme) | `/api/v1/theme/` | -| `GET` | [Get metadata information about this API resource (theme--info)](/developer-docs/api/get-metadata-information-about-this-api-resource-theme-info) | `/api/v1/theme/_info` | -| `DELETE` | [Delete a theme](/developer-docs/api/delete-a-theme) | `/api/v1/theme/{pk}` | -| `GET` | [Get a theme](/developer-docs/api/get-a-theme) | `/api/v1/theme/{pk}` | -| `PUT` | [Update a theme](/developer-docs/api/update-a-theme) | `/api/v1/theme/{pk}` | -| `PUT` | [Set a theme as the system dark theme](/developer-docs/api/set-a-theme-as-the-system-dark-theme) | `/api/v1/theme/{pk}/set_system_dark` | -| `PUT` | [Set a theme as the system default theme](/developer-docs/api/set-a-theme-as-the-system-default-theme) | `/api/v1/theme/{pk}/set_system_default` | -| `GET` | [Download multiple themes as YAML files](/developer-docs/api/download-multiple-themes-as-yaml-files) | `/api/v1/theme/export/` | -| `POST` | [Import themes from a ZIP file](/developer-docs/api/import-themes-from-a-zip-file) | `/api/v1/theme/import/` | -| `GET` | [Get related fields data (theme-related-column-name)](/developer-docs/api/get-related-fields-data-theme-related-column-name) | `/api/v1/theme/related/{column_name}` | -| `DELETE` | [Clear the system dark theme](/developer-docs/api/clear-the-system-dark-theme) | `/api/v1/theme/unset_system_dark` | -| `DELETE` | [Clear the system default theme](/developer-docs/api/clear-the-system-default-theme) | `/api/v1/theme/unset_system_default` | - -
- -
-UserRegistrationsRestAPI (8 endpoints) — Endpoints related to UserRegistrationsRestAPI. - -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | [Get security user registrations](/developer-docs/api/get-security-user-registrations) | `/api/v1/security/user_registrations/` | -| `POST` | [Create security user registrations](/developer-docs/api/create-security-user-registrations) | `/api/v1/security/user_registrations/` | -| `GET` | [Get security user registrations info](/developer-docs/api/get-security-user-registrations-info) | `/api/v1/security/user_registrations/_info` | -| `DELETE` | [Delete security user registrations by pk](/developer-docs/api/delete-security-user-registrations-by-pk) | `/api/v1/security/user_registrations/{pk}` | -| `GET` | [Get security user registrations by pk](/developer-docs/api/get-security-user-registrations-by-pk) | `/api/v1/security/user_registrations/{pk}` | -| `PUT` | [Update security user registrations by pk](/developer-docs/api/update-security-user-registrations-by-pk) | `/api/v1/security/user_registrations/{pk}` | -| `GET` | [Get distinct values from field data (security-user-registrations-distinct-column-name)](/developer-docs/api/get-distinct-values-from-field-data-security-user-registrations-distinct-column-name) | `/api/v1/security/user_registrations/distinct/{column_name}` | -| `GET` | [Get related fields data (security-user-registrations-related-column-name)](/developer-docs/api/get-related-fields-data-security-user-registrations-related-column-name) | `/api/v1/security/user_registrations/related/{column_name}` | - -
- ---- - -### 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/) diff --git a/docs/developer_docs/components/TODO.md b/docs/developer_docs/components/TODO.md deleted file mode 100644 index c3564104795..00000000000 --- a/docs/developer_docs/components/TODO.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: Components TODO -sidebar_class_name: hidden ---- - -# Components TODO - -These components were found but not yet supported for documentation generation. -Future phases will add support for these sources. - -## Summary - -- **Total skipped:** 19 story files -- **Reason:** Import path resolution not yet implemented - -## Skipped by Source - -### App Components - -9 components - -- [ ] `superset-frontend/src/components/AlteredSliceTag/AlteredSliceTag.stories.tsx` -- [ ] `superset-frontend/src/components/Chart/DrillDetail/DrillDetailTableControls.stories.tsx` -- [ ] `superset-frontend/src/components/CopyToClipboard/CopyToClipboard.stories.tsx` -- [ ] `superset-frontend/src/components/ErrorMessage/ErrorAlert.stories.tsx` -- [ ] `superset-frontend/src/components/FacePile/FacePile.stories.tsx` -- [ ] `superset-frontend/src/components/FilterableTable/FilterableTable.stories.tsx` -- [ ] `superset-frontend/src/components/RowCountLabel/RowCountLabel.stories.tsx` -- [ ] `superset-frontend/src/components/Tag/Tag.stories.tsx` -- [ ] `superset-frontend/src/components/TagsList/TagsList.stories.tsx` - -### Dashboard Components - -2 components - -- [ ] `superset-frontend/src/dashboard/components/AnchorLink/AnchorLink.stories.tsx` -- [ ] `superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.stories.tsx` - -### Explore Components - -4 components - -- [ ] `superset-frontend/src/explore/components/ControlHeader.stories.tsx` -- [ ] `superset-frontend/src/explore/components/RunQueryButton/RunQueryButton.stories.tsx` -- [ ] `superset-frontend/src/explore/components/controls/BoundsControl.stories.tsx` -- [ ] `superset-frontend/src/explore/components/controls/SliderControl.stories.tsx` - -### Feature Components - -2 components - -- [ ] `superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.stories.tsx` -- [ ] `superset-frontend/src/features/home/LanguagePicker.stories.tsx` - -### Filter Components - -2 components - -- [ ] `superset-frontend/src/filters/components/Range/RangeFilterPlugin.stories.tsx` -- [ ] `superset-frontend/src/filters/components/Select/SelectFilterPlugin.stories.tsx` - -## How to Add Support - -1. Determine the correct import path for the source -2. Update `generate-superset-components.mjs` to handle the source -3. Add source to `SUPPORTED_SOURCES` array -4. Re-run the generator - ---- - -*Auto-generated by generate-superset-components.mjs* diff --git a/docs/developer_docs/components/design-system/dropdowncontainer.mdx b/docs/developer_docs/components/design-system/dropdowncontainer.mdx deleted file mode 100644 index 1d6b5e20772..00000000000 --- a/docs/developer_docs/components/design-system/dropdowncontainer.mdx +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: DropdownContainer -sidebar_label: DropdownContainer ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# DropdownContainer - -DropdownContainer arranges items horizontally and moves overflowing items into a dropdown popover. Resize the container to see the overflow behavior. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - const items = Array.from({ length: 6 }, (_, i) => ({ - id: 'item-' + i, - element: React.createElement('div', { - style: { - minWidth: 120, - padding: '4px 12px', - background: '#e6f4ff', - border: '1px solid #91caff', - borderRadius: 4, - }, - }, 'Filter ' + (i + 1)), - })); - return ( -
- -
- Drag the right edge to resize and see items overflow into a dropdown -
-
- ); -} -``` - -## With Select Filters - -```tsx live -function SelectFilters() { - const items = ['Region', 'Category', 'Date Range', 'Status', 'Owner'].map( - (label, i) => ({ - id: 'filter-' + i, - element: React.createElement('div', { - style: { minWidth: 150, padding: '4px 12px', background: '#f5f5f5', border: '1px solid #d9d9d9', borderRadius: 4 }, - }, label + ': All'), - }) - ); - return ( -
- -
- ); -} -``` - - - -## Import - -```tsx -import { DropdownContainer } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.stories.tsx). -::: diff --git a/docs/developer_docs/components/design-system/flex.mdx b/docs/developer_docs/components/design-system/flex.mdx deleted file mode 100644 index b74f06c7f4e..00000000000 --- a/docs/developer_docs/components/design-system/flex.mdx +++ /dev/null @@ -1,197 +0,0 @@ ---- -title: Flex -sidebar_label: Flex ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Flex - -The Flex component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - {['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'].map(item => ( -
- {item} -
- ))} -
- ); -} -``` - -## Vertical Layout - -```tsx live -function VerticalFlex() { - return ( - - - - - - ); -} -``` - -## Justify and Align - -```tsx live -function JustifyAlign() { - const boxStyle = { - width: '100%', - height: 120, - borderRadius: 6, - border: '1px solid #40a9ff', - }; - const itemStyle = { - width: 60, - height: 40, - backgroundColor: '#1677ff', - borderRadius: 4, - }; - return ( -
- {['flex-start', 'center', 'flex-end', 'space-between', 'space-around'].map(justify => ( -
- {justify} - -
-
-
- -
- ))} -
- ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `vertical` | `boolean` | `false` | - | -| `wrap` | `string` | `"nowrap"` | - | -| `justify` | `string` | `"normal"` | - | -| `align` | `string` | `"normal"` | - | -| `flex` | `string` | `"normal"` | - | -| `gap` | `string` | `"small"` | - | - -## Import - -```tsx -import { Flex } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Flex/Flex.stories.tsx). -::: diff --git a/docs/developer_docs/components/design-system/grid.mdx b/docs/developer_docs/components/design-system/grid.mdx deleted file mode 100644 index a7fea3d3dc1..00000000000 --- a/docs/developer_docs/components/design-system/grid.mdx +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: Grid -sidebar_label: Grid ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Grid - -The Grid system of Ant Design is based on a 24-grid layout. The `Row` and `Col` components are used to create flexible and responsive grid layouts. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - -
col-12
- - -
col-12
- - -
col-8
- - -
col-8
- - -
col-8
- -
- ); -} -``` - -## Responsive Grid - -```tsx live -function ResponsiveGrid() { - return ( - - -
- Responsive -
- - -
- Responsive -
- - -
- Responsive -
- - -
- Responsive -
- -
- ); -} -``` - -## Alignment - -```tsx live -function AlignmentDemo() { - const boxStyle = { background: '#e6f4ff', padding: '16px 0', border: '1px solid #91caff', textAlign: 'center' }; - return ( -
- -
start
-
start
-
- -
center
-
center
-
- -
end
-
end
-
- -
between
-
between
-
-
- ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `align` | `string` | `"top"` | Vertical alignment of columns within the row. | -| `justify` | `string` | `"start"` | Horizontal distribution of columns within the row. | -| `wrap` | `boolean` | `true` | Whether columns are allowed to wrap to the next line. | -| `gutter` | `number` | `16` | Spacing between columns in pixels. | - -## Import - -```tsx -import { Grid } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Grid/Grid.stories.tsx). -::: diff --git a/docs/developer_docs/components/design-system/index.mdx b/docs/developer_docs/components/design-system/index.mdx deleted file mode 100644 index 03eb7f824fb..00000000000 --- a/docs/developer_docs/components/design-system/index.mdx +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Layout Components -sidebar_label: Layout Components -sidebar_position: 1 ---- - - - -# Layout Components - -7 components available in this category. - -## Components - -- [DropdownContainer](./dropdowncontainer.mdx) -- [Flex](./flex.mdx) -- [Grid](./grid.mdx) -- [Layout](./layout.mdx) -- [MetadataBar](./metadatabar.mdx) -- [Space](./space.mdx) -- [Table](./table.mdx) diff --git a/docs/developer_docs/components/design-system/layout.mdx b/docs/developer_docs/components/design-system/layout.mdx deleted file mode 100644 index 1b0d76e0ad1..00000000000 --- a/docs/developer_docs/components/design-system/layout.mdx +++ /dev/null @@ -1,139 +0,0 @@ ---- -title: Layout -sidebar_label: Layout ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Layout - -Ant Design Layout component with configurable Sider, Header, Footer, and Content. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - -
Sidebar
-
- - - Header - - - Content - - - Footer - - -
- ); -} -``` - -## Content Only - -```tsx live -function ContentOnly() { - return ( - - - Application Header - - - Main content area without a sidebar - - - Footer Content - - - ); -} -``` - -## Right Sidebar - -```tsx live -function RightSidebar() { - return ( - - - - Header - - - Content with right sidebar - - - -
Right Sidebar
-
-
- ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `hasSider` | `boolean` | `false` | Whether the layout contains a Sider sub-component. | -| `style` | `any` | `{"minHeight":200}` | - | - -## Import - -```tsx -import { Layout } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Layout/Layout.stories.tsx). -::: diff --git a/docs/developer_docs/components/design-system/metadatabar.mdx b/docs/developer_docs/components/design-system/metadatabar.mdx deleted file mode 100644 index 2f667cfae4e..00000000000 --- a/docs/developer_docs/components/design-system/metadatabar.mdx +++ /dev/null @@ -1,174 +0,0 @@ ---- -title: MetadataBar -sidebar_label: MetadataBar ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# MetadataBar - -MetadataBar displays a row of metadata items (SQL info, owners, last modified, tags, dashboards, etc.) that collapse responsively based on available width. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - const items = [ - { type: 'sql', title: 'Click to view query' }, - { - type: 'owner', - createdBy: 'Jane Smith', - owners: ['John Doe', 'Mary Wilson'], - createdOn: 'a week ago', - }, - { - type: 'lastModified', - value: 'a week ago', - modifiedBy: 'Jane Smith', - }, - { type: 'tags', values: ['management', 'research', 'poc'] }, - ]; - return ; -} -``` - -## Minimal Metadata - -```tsx live -function MinimalMetadata() { - const items = [ - { type: 'owner', createdBy: 'Admin', owners: ['Admin'], createdOn: 'yesterday' }, - { type: 'lastModified', value: '2 hours ago', modifiedBy: 'Admin' }, - ]; - return ; -} -``` - -## Full Metadata - -```tsx live -function FullMetadata() { - const items = [ - { type: 'sql', title: 'SELECT * FROM ...' }, - { type: 'owner', createdBy: 'Jane Smith', owners: ['Jane Smith', 'John Doe', 'Bob Wilson'], createdOn: '2 weeks ago' }, - { type: 'lastModified', value: '3 days ago', modifiedBy: 'John Doe' }, - { type: 'tags', values: ['production', 'finance', 'quarterly'] }, - { type: 'dashboards', title: 'Used in 12 dashboards' }, - { type: 'description', value: 'This chart shows quarterly revenue breakdown by region and product line.' }, - { type: 'rows', title: '1.2M rows' }, - { type: 'table', title: 'public.revenue_data' }, - ]; - return ; -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `title` | `string` | `"Added to 3 dashboards"` | - | -| `createdBy` | `string` | `"Jane Smith"` | - | -| `modifiedBy` | `string` | `"Jane Smith"` | - | -| `description` | `string` | `"To preview the list of dashboards go to More settings."` | - | -| `items` | `any` | `[{"type":"sql","title":"Click to view query"},{"type":"owner","createdBy":"Jane Smith","owners":["John Doe","Mary Wilson"],"createdOn":"a week ago"},{"type":"lastModified","value":"a week ago","modifiedBy":"Jane Smith"},{"type":"tags","values":["management","research","poc"]},{"type":"dashboards","title":"Added to 3 dashboards","description":"To preview the list of dashboards go to More settings."}]` | - | - -## Import - -```tsx -import { MetadataBar } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/MetadataBar/MetadataBar.stories.tsx). -::: diff --git a/docs/developer_docs/components/design-system/space.mdx b/docs/developer_docs/components/design-system/space.mdx deleted file mode 100644 index 264667a9fb0..00000000000 --- a/docs/developer_docs/components/design-system/space.mdx +++ /dev/null @@ -1,168 +0,0 @@ ---- -title: Space -sidebar_label: Space ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Space - -The Space component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - {['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'].map(item => ( -
- {item} -
- ))} -
- ); -} -``` - -## Vertical Space - -```tsx live -function VerticalSpace() { - return ( - - - - - - ); -} -``` - -## Space Sizes - -```tsx live -function SpaceSizes() { - const items = ['Item 1', 'Item 2', 'Item 3']; - const itemStyle = { - padding: '8px 16px', - background: '#e6f4ff', - border: '1px solid #91caff', - borderRadius: 4, - }; - return ( -
- {['small', 'middle', 'large'].map(size => ( -
-

{size}

- - {items.map(item => ( -
{item}
- ))} -
-
- ))} -
- ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `direction` | `string` | `"horizontal"` | - | -| `size` | `string` | `"small"` | - | -| `wrap` | `boolean` | `false` | - | - -## Import - -```tsx -import { Space } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Space/Space.stories.tsx). -::: diff --git a/docs/developer_docs/components/design-system/table.mdx b/docs/developer_docs/components/design-system/table.mdx deleted file mode 100644 index db0be495173..00000000000 --- a/docs/developer_docs/components/design-system/table.mdx +++ /dev/null @@ -1,311 +0,0 @@ ---- -title: Table -sidebar_label: Table ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Table - -A data table component with sorting, pagination, row selection, resizable columns, reorderable columns, and virtualization for large datasets. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - const data = [ - { key: 1, name: 'PostgreSQL', type: 'Database', status: 'Active' }, - { key: 2, name: 'MySQL', type: 'Database', status: 'Active' }, - { key: 3, name: 'SQLite', type: 'Database', status: 'Inactive' }, - { key: 4, name: 'Presto', type: 'Query Engine', status: 'Active' }, - ]; - const columns = [ - { title: 'Name', dataIndex: 'name', key: 'name', width: 150 }, - { title: 'Type', dataIndex: 'type', key: 'type' }, - { title: 'Status', dataIndex: 'status', key: 'status', width: 100 }, - ]; - return ; -} -``` - -## With Pagination - -```tsx live -function PaginatedTable() { - const data = Array.from({ length: 20 }, (_, i) => ({ - key: i, - name: 'Record ' + (i + 1), - value: Math.round(Math.random() * 1000), - category: ['A', 'B', 'C'][i % 3], - })); - const columns = [ - { title: 'Name', dataIndex: 'name', key: 'name' }, - { title: 'Value', dataIndex: 'value', key: 'value', width: 100 }, - { title: 'Category', dataIndex: 'category', key: 'category', width: 100 }, - ]; - return ( -
- ); -} -``` - -## Loading State - -```tsx live -function LoadingTable() { - const columns = [ - { title: 'Name', dataIndex: 'name', key: 'name' }, - { title: 'Status', dataIndex: 'status', key: 'status' }, - ]; - return
; -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `size` | `string` | `"small"` | Table size. | -| `bordered` | `boolean` | `false` | Whether to show all table borders. | -| `loading` | `boolean` | `false` | Whether the table is in a loading state. | -| `sticky` | `boolean` | `true` | Whether the table header is sticky. | -| `resizable` | `boolean` | `false` | Whether columns can be resized by dragging column edges. | -| `reorderable` | `boolean` | `false` | EXPERIMENTAL: Whether columns can be reordered by dragging. May not work in all contexts. | -| `usePagination` | `boolean` | `false` | Whether to enable pagination. When enabled, the table displays 5 rows per page. | -| `key` | `number` | `5` | - | -| `name` | `string` | `"1GB USB Flash Drive"` | - | -| `category` | `string` | `"Portable Storage"` | - | -| `price` | `number` | `9.99` | - | -| `height` | `number` | `350` | - | -| `defaultPageSize` | `number` | `5` | - | -| `pageSizeOptions` | `any` | `["5","10"]` | - | -| `data` | `any` | `[{"key":1,"name":"Floppy Disk 10 pack","category":"Disk Storage","price":9.99},{"key":2,"name":"DVD 100 pack","category":"Optical Storage","price":27.99},{"key":3,"name":"128 GB SSD","category":"Harddrive","price":49.99},{"key":4,"name":"4GB 144mhz","category":"Memory","price":19.99},{"key":5,"name":"1GB USB Flash Drive","category":"Portable Storage","price":9.99},{"key":6,"name":"256 GB SSD","category":"Harddrive","price":89.99},{"key":7,"name":"1 TB SSD","category":"Harddrive","price":349.99},{"key":8,"name":"16 GB DDR4","category":"Memory","price":59.99},{"key":9,"name":"32 GB DDR5","category":"Memory","price":129.99},{"key":10,"name":"Blu-ray 50 pack","category":"Optical Storage","price":34.99},{"key":11,"name":"64 GB USB Drive","category":"Portable Storage","price":14.99},{"key":12,"name":"2 TB HDD","category":"Harddrive","price":59.99}]` | - | -| `columns` | `any` | `[{"title":"Name","dataIndex":"name","key":"name","width":200},{"title":"Category","dataIndex":"category","key":"category","width":150},{"title":"Price","dataIndex":"price","key":"price","width":100}]` | - | - -## Import - -```tsx -import { Table } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Table/Table.stories.tsx). -::: diff --git a/docs/developer_docs/components/index.mdx b/docs/developer_docs/components/index.mdx deleted file mode 100644 index ee90cdcd45a..00000000000 --- a/docs/developer_docs/components/index.mdx +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: UI Components Overview -sidebar_label: Overview -sidebar_position: 0 ---- - - - -import { ComponentIndex } from '@site/src/components/ui-components'; -import componentData from '@site/static/data/components.json'; - -# UI Components - - - ---- - -## Design System - -A design system is a complete set of standards intended to manage design at scale using reusable components and patterns. - -The Superset Design System uses [Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/) principles with adapted terminology: - -| Atomic Design | Atoms | Molecules | Organisms | Templates | Pages / Screens | -|---|:---:|:---:|:---:|:---:|:---:| -| **Superset Design** | Foundations | Components | Patterns | Templates | Features | - -Atoms = Foundations, Molecules = Components, Organisms = Patterns, Templates = Templates, Pages / Screens = Features - -## Usage - -All components are exported from `@superset-ui/core/components`: - -```tsx -import { Button, Modal, Select } from '@superset-ui/core/components'; -``` - -## Contributing - -This documentation is auto-generated from Storybook stories. To add or update component documentation: - -1. Create or update the component's `.stories.tsx` file -2. Add a descriptive `title` and `description` in the story meta -3. Export an interactive story with `args` for configurable props -4. Run `yarn generate:superset-components` in the `docs/` directory - -:::info Work in Progress -This component library is actively being documented. See the [Components TODO](./TODO.md) page for a list of components awaiting documentation. -::: - ---- - -*Auto-generated from Storybook stories in the [Design System/Introduction](https://github.com/apache/superset/blob/master/superset-frontend/packages/superset-ui-core/src/components/DesignSystem.stories.tsx) story.* diff --git a/docs/developer_docs/components/ui/autocomplete.mdx b/docs/developer_docs/components/ui/autocomplete.mdx deleted file mode 100644 index 789883dbb4c..00000000000 --- a/docs/developer_docs/components/ui/autocomplete.mdx +++ /dev/null @@ -1,215 +0,0 @@ ---- -title: AutoComplete -sidebar_label: AutoComplete ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# AutoComplete - -AutoComplete component for search functionality. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `placeholder` | `string` | `"Type to search..."` | Placeholder text for AutoComplete | -| `options` | `any` | `[{"value":"Dashboard","label":"Dashboard"},{"value":"Chart","label":"Chart"},{"value":"Dataset","label":"Dataset"},{"value":"Database","label":"Database"},{"value":"Query","label":"Query"}]` | The dropdown options | -| `style` | `any` | `{"width":300}` | Custom styles for AutoComplete | -| `filterOption` | `boolean` | `true` | Enable filtering of options based on input | - -## Import - -```tsx -import { AutoComplete } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/AutoComplete/AutoComplete.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/avatar.mdx b/docs/developer_docs/components/ui/avatar.mdx deleted file mode 100644 index b2b7458cd4b..00000000000 --- a/docs/developer_docs/components/ui/avatar.mdx +++ /dev/null @@ -1,140 +0,0 @@ ---- -title: Avatar -sidebar_label: Avatar ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Avatar - -The Avatar component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - AB - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `children` | `string` | `"AB"` | Text or initials to display inside the avatar. | -| `alt` | `string` | `""` | - | -| `gap` | `number` | `4` | Letter spacing inside the avatar. | -| `shape` | `string` | `"circle"` | The shape of the avatar. | -| `size` | `string` | `"default"` | The size of the avatar. | -| `src` | `string` | `""` | Image URL for the avatar. If provided, overrides children. | -| `draggable` | `boolean` | `false` | - | - -## Import - -```tsx -import { Avatar } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Avatar/Avatar.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/badge.mdx b/docs/developer_docs/components/ui/badge.mdx deleted file mode 100644 index 69531a428b1..00000000000 --- a/docs/developer_docs/components/ui/badge.mdx +++ /dev/null @@ -1,160 +0,0 @@ ---- -title: Badge -sidebar_label: Badge ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Badge - -The Badge component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## Status Badge - -```tsx live -function StatusBadgeDemo() { - const statuses = ['default', 'success', 'processing', 'warning', 'error']; - return ( -
- {statuses.map(status => ( - - ))} -
- ); -} -``` - -## Color Gallery - -```tsx live -function ColorGallery() { - const colors = ['pink', 'red', 'orange', 'green', 'cyan', 'blue', 'purple']; - return ( -
- {colors.map(color => ( - - ))} -
- ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `count` | `number` | `5` | Number to show in the badge. | -| `size` | `string` | `"default"` | Size of the badge. | -| `showZero` | `boolean` | `false` | Whether to show badge when count is zero. | -| `overflowCount` | `number` | `99` | Max count to show. Shows count+ when exceeded (e.g., 99+). | - -## Import - -```tsx -import { Badge } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Badge/Badge.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/breadcrumb.mdx b/docs/developer_docs/components/ui/breadcrumb.mdx deleted file mode 100644 index 5591b2e089f..00000000000 --- a/docs/developer_docs/components/ui/breadcrumb.mdx +++ /dev/null @@ -1,93 +0,0 @@ ---- -title: Breadcrumb -sidebar_label: Breadcrumb ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Breadcrumb - -Breadcrumb component for displaying navigation paths. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - - - -## Import - -```tsx -import { Breadcrumb } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Breadcrumb/Breadcrumb.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/button.mdx b/docs/developer_docs/components/ui/button.mdx deleted file mode 100644 index 0b704f4b666..00000000000 --- a/docs/developer_docs/components/ui/button.mdx +++ /dev/null @@ -1,142 +0,0 @@ ---- -title: Button -sidebar_label: Button ---- - - - -import { StoryWithControls, ComponentGallery } from '../../../src/components/StorybookWrapper'; - -# Button - -The Button component from Superset's UI library. - -## All Variants - - - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `buttonStyle` | `string` | `"primary"` | The style variant of the button. | -| `buttonSize` | `string` | `"default"` | The size of the button. | -| `children` | `string` | `"Button!"` | The button text or content. | - -## Import - -```tsx -import { Button } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Button/Button.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/buttongroup.mdx b/docs/developer_docs/components/ui/buttongroup.mdx deleted file mode 100644 index 233993c4d19..00000000000 --- a/docs/developer_docs/components/ui/buttongroup.mdx +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: ButtonGroup -sidebar_label: ButtonGroup ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# ButtonGroup - -ButtonGroup is a container that groups multiple Button components together with consistent spacing and styling. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - - - - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `expand` | `boolean` | `false` | When true, buttons expand to fill available width. | - -## Import - -```tsx -import { ButtonGroup } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/ButtonGroup/ButtonGroup.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/cachedlabel.mdx b/docs/developer_docs/components/ui/cachedlabel.mdx deleted file mode 100644 index 7f115914456..00000000000 --- a/docs/developer_docs/components/ui/cachedlabel.mdx +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: CachedLabel -sidebar_label: CachedLabel ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# CachedLabel - -The CachedLabel component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - - - -## Import - -```tsx -import { CachedLabel } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/CachedLabel/CachedLabel.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/card.mdx b/docs/developer_docs/components/ui/card.mdx deleted file mode 100644 index 50bf93dbf6e..00000000000 --- a/docs/developer_docs/components/ui/card.mdx +++ /dev/null @@ -1,142 +0,0 @@ ---- -title: Card -sidebar_label: Card ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Card - -A container component for grouping related content. Supports titles, borders, loading states, and hover effects. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - This card displays a summary of your dashboard metrics and recent activity. - - ); -} -``` - -## Card States - -```tsx live -function CardStates() { - return ( -
- - Default card content. - - - Hover over this card. - - - This content is hidden while loading. - - - Borderless card. - -
- ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `padded` | `boolean` | `true` | Whether the card content has padding. | -| `title` | `string` | `"Dashboard Overview"` | Title text displayed at the top of the card. | -| `children` | `string` | `"This card displays a summary of your dashboard metrics and recent activity."` | The content inside the card. | -| `bordered` | `boolean` | `true` | Whether to show a border around the card. | -| `loading` | `boolean` | `false` | Whether to show a loading skeleton. | -| `hoverable` | `boolean` | `false` | Whether the card lifts on hover. | - -## Import - -```tsx -import { Card } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Card/Card.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/checkbox.mdx b/docs/developer_docs/components/ui/checkbox.mdx deleted file mode 100644 index 13709c36130..00000000000 --- a/docs/developer_docs/components/ui/checkbox.mdx +++ /dev/null @@ -1,141 +0,0 @@ ---- -title: Checkbox -sidebar_label: Checkbox ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Checkbox - -Checkbox component that supports both regular and indeterminate states, built on top of Ant Design v5 Checkbox. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## All Checkbox States - -```tsx live -function AllStates() { - return ( -
- Unchecked - Checked - Indeterminate - Disabled unchecked - Disabled checked -
- ); -} -``` - -## Select All Pattern - -```tsx live -function SelectAllDemo() { - const [selected, setSelected] = React.useState([]); - const options = ['Option A', 'Option B', 'Option C']; - - const allSelected = selected.length === options.length; - const indeterminate = selected.length > 0 && !allSelected; - - return ( -
- setSelected(e.target.checked ? [...options] : [])} - > - Select All - -
- {options.map(opt => ( -
- setSelected(prev => - prev.includes(opt) ? prev.filter(x => x !== opt) : [...prev, opt] - )} - > - {opt} - -
- ))} -
-
- ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `checked` | `boolean` | `false` | Whether the checkbox is checked. | -| `indeterminate` | `boolean` | `false` | Whether the checkbox is in indeterminate state (partially selected). | - -## Import - -```tsx -import { Checkbox } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Checkbox/Checkbox.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/collapse.mdx b/docs/developer_docs/components/ui/collapse.mdx deleted file mode 100644 index 4e35c7b4353..00000000000 --- a/docs/developer_docs/components/ui/collapse.mdx +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: Collapse -sidebar_label: Collapse ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Collapse - -The Collapse component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `ghost` | `boolean` | `false` | - | -| `bordered` | `boolean` | `true` | - | -| `accordion` | `boolean` | `false` | - | -| `animateArrows` | `boolean` | `false` | - | -| `modalMode` | `boolean` | `false` | - | - -## Import - -```tsx -import { Collapse } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Collapse/Collapse.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/datepicker.mdx b/docs/developer_docs/components/ui/datepicker.mdx deleted file mode 100644 index 4fc45cb6ecb..00000000000 --- a/docs/developer_docs/components/ui/datepicker.mdx +++ /dev/null @@ -1,110 +0,0 @@ ---- -title: DatePicker -sidebar_label: DatePicker ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# DatePicker - -The DatePicker component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `placeholder` | `string` | `"Select date"` | - | -| `showNow` | `boolean` | `true` | Show "Now" button to select current date and time. | -| `allowClear` | `boolean` | `false` | - | -| `autoFocus` | `boolean` | `true` | - | -| `disabled` | `boolean` | `false` | - | -| `format` | `string` | `"YYYY-MM-DD hh:mm a"` | - | -| `inputReadOnly` | `boolean` | `false` | - | -| `picker` | `string` | `"date"` | - | -| `placement` | `string` | `"bottomLeft"` | - | -| `size` | `string` | `"middle"` | - | -| `showTime` | `any` | `{"format":"hh:mm a","needConfirm":false}` | - | - -## Import - -```tsx -import { DatePicker } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/DatePicker/DatePicker.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/divider.mdx b/docs/developer_docs/components/ui/divider.mdx deleted file mode 100644 index 0ea8c54bb6e..00000000000 --- a/docs/developer_docs/components/ui/divider.mdx +++ /dev/null @@ -1,144 +0,0 @@ ---- -title: Divider -sidebar_label: Divider ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Divider - -The Divider component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - <> -

Horizontal divider with title (orientationMargin applies here):

- Left Title - Right Title - Center Title -

Vertical divider (use container gap for spacing):

-
- Link - - Link - - Link -
- - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `dashed` | `boolean` | `false` | Whether line is dashed (deprecated, use variant). | -| `variant` | `string` | `"solid"` | Line style of the divider. | -| `orientation` | `string` | `"center"` | Position of title inside divider. | -| `orientationMargin` | `string` | `""` | Margin from divider edge to title. | -| `plain` | `boolean` | `true` | Use plain style without bold title. | -| `type` | `string` | `"horizontal"` | Direction of the divider. | - -## Import - -```tsx -import { Divider } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Divider/Divider.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/editabletitle.mdx b/docs/developer_docs/components/ui/editabletitle.mdx deleted file mode 100644 index bea676b0401..00000000000 --- a/docs/developer_docs/components/ui/editabletitle.mdx +++ /dev/null @@ -1,172 +0,0 @@ ---- -title: EditableTitle -sidebar_label: EditableTitle ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# EditableTitle - -The EditableTitle component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - console.log('Saved:', newTitle)} - /> - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `canEdit` | `boolean` | `true` | Whether the title can be edited. | -| `editing` | `boolean` | `false` | Whether the title is currently in edit mode. | -| `emptyText` | `string` | `"Empty text"` | Text to display when title is empty. | -| `noPermitTooltip` | `string` | `"Not permitted"` | Tooltip shown when user lacks edit permission. | -| `showTooltip` | `boolean` | `true` | Whether to show tooltip on hover. | -| `title` | `string` | `"Title"` | The title text to display. | -| `defaultTitle` | `string` | `"Default title"` | Default title when none is provided. | -| `placeholder` | `string` | `"Placeholder"` | Placeholder text when editing. | -| `certifiedBy` | `string` | `""` | Name of person/team who certified this item. | -| `certificationDetails` | `string` | `""` | Additional certification details or description. | -| `maxWidth` | `number` | `100` | Maximum width of the title in pixels. | -| `autoSize` | `boolean` | `true` | Whether to auto-size based on content. | - -## Import - -```tsx -import { EditableTitle } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/EditableTitle/EditableTitle.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/emptystate.mdx b/docs/developer_docs/components/ui/emptystate.mdx deleted file mode 100644 index b0e8171c16d..00000000000 --- a/docs/developer_docs/components/ui/emptystate.mdx +++ /dev/null @@ -1,147 +0,0 @@ ---- -title: EmptyState -sidebar_label: EmptyState ---- - - - -import { StoryWithControls, ComponentGallery } from '../../../src/components/StorybookWrapper'; - -# EmptyState - -The EmptyState component from Superset's UI library. - -## All Variants - - - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - alert('Filters cleared!')} - /> - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `size` | `string` | `"medium"` | Size of the empty state component. | -| `title` | `string` | `"No Data Available"` | Main title text. | -| `description` | `string` | `"There is no data to display at this time."` | Description text below the title. | -| `image` | `string` | `"empty.svg"` | Predefined image to display. | -| `buttonText` | `string` | `""` | Text for optional action button. | - -## Import - -```tsx -import { EmptyState } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/EmptyState/EmptyState.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/favestar.mdx b/docs/developer_docs/components/ui/favestar.mdx deleted file mode 100644 index adb610620d8..00000000000 --- a/docs/developer_docs/components/ui/favestar.mdx +++ /dev/null @@ -1,96 +0,0 @@ ---- -title: FaveStar -sidebar_label: FaveStar ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# FaveStar - -FaveStar component for marking items as favorites - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `itemId` | `number` | `1` | Unique identifier for the item | -| `isStarred` | `boolean` | `false` | Whether the item is currently starred. | -| `showTooltip` | `boolean` | `true` | Show tooltip on hover. | - -## Import - -```tsx -import { FaveStar } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/FaveStar/FaveStar.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/iconbutton.mdx b/docs/developer_docs/components/ui/iconbutton.mdx deleted file mode 100644 index 5cfc6e90f55..00000000000 --- a/docs/developer_docs/components/ui/iconbutton.mdx +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: IconButton -sidebar_label: IconButton ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# IconButton - -The IconButton component is a versatile button that allows you to combine an icon with a text label. It is designed for use in situations where you want to display an icon along with some text in a single clickable element. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `buttonText` | `string` | `"IconButton"` | The text inside the button. | -| `altText` | `string` | `"Icon button alt text"` | The alt text for the button. If not provided, the button text is used as the alt text by default. | -| `padded` | `boolean` | `true` | Add padding between icon and button text. | -| `icon` | `string` | `"https://superset.apache.org/img/superset-logo-horiz.svg"` | Icon inside the button (URL or path). | - -## Import - -```tsx -import { IconButton } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/IconButton/IconButton.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/icons.mdx b/docs/developer_docs/components/ui/icons.mdx deleted file mode 100644 index ac90f06c7e7..00000000000 --- a/docs/developer_docs/components/ui/icons.mdx +++ /dev/null @@ -1,252 +0,0 @@ ---- -title: Icons -sidebar_label: Icons ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Icons - -Icon library for Apache Superset. Contains over 200 icons based on Ant Design icons with consistent sizing and theming support. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( -
- - - - -
- ); -} -``` - -## Icon Sizes - -```tsx live -function IconSizes() { - const sizes = ['s', 'm', 'l', 'xl', 'xxl']; - return ( -
- {sizes.map(size => ( -
- -
{size}
-
- ))} -
- ); -} -``` - -## Icon Gallery - -```tsx live -function IconGallery() { - const Section = ({ title, children }) => ( -
-
{title}
-
{children}
-
- ); - return ( -
-
- - - - - - -
-
- - - - - - - - -
-
- - - - - - - - - - - - - - -
-
- - - - - - - - - - -
-
- - - - - - - - - -
-
- - - - - - - - - - - - - - - -
-
- - - - -
-
- - - - - - - - -
-
- ); -} -``` - -## Icon with Text - -```tsx live -function IconWithText() { - return ( -
-
- - Success message -
-
- - Information message -
-
- - Warning message -
-
- - Error message -
-
- ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `iconSize` | `string` | `"xl"` | Size of the icons: s (12px), m (16px), l (20px), xl (24px), xxl (32px). | - -## Import - -```tsx -import { Icons } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Icons/Icons.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/icontooltip.mdx b/docs/developer_docs/components/ui/icontooltip.mdx deleted file mode 100644 index 4d1fdde8699..00000000000 --- a/docs/developer_docs/components/ui/icontooltip.mdx +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: IconTooltip -sidebar_label: IconTooltip ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# IconTooltip - -The IconTooltip component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `tooltip` | `string` | `"Tooltip"` | Text content to display in the tooltip. | - -## Import - -```tsx -import { IconTooltip } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/IconTooltip/IconTooltip.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/index.mdx b/docs/developer_docs/components/ui/index.mdx deleted file mode 100644 index 1bcc81edc15..00000000000 --- a/docs/developer_docs/components/ui/index.mdx +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: Core Components -sidebar_label: Core Components -sidebar_position: 1 ---- - - - -# Core Components - -46 components available in this category. - -## Components - -- [AutoComplete](./autocomplete.mdx) -- [Avatar](./avatar.mdx) -- [Badge](./badge.mdx) -- [Breadcrumb](./breadcrumb.mdx) -- [Button](./button.mdx) -- [ButtonGroup](./buttongroup.mdx) -- [CachedLabel](./cachedlabel.mdx) -- [Card](./card.mdx) -- [Checkbox](./checkbox.mdx) -- [Collapse](./collapse.mdx) -- [DatePicker](./datepicker.mdx) -- [Divider](./divider.mdx) -- [EditableTitle](./editabletitle.mdx) -- [EmptyState](./emptystate.mdx) -- [FaveStar](./favestar.mdx) -- [IconButton](./iconbutton.mdx) -- [Icons](./icons.mdx) -- [IconTooltip](./icontooltip.mdx) -- [InfoTooltip](./infotooltip.mdx) -- [Input](./input.mdx) -- [Label](./label.mdx) -- [List](./list.mdx) -- [ListViewCard](./listviewcard.mdx) -- [Loading](./loading.mdx) -- [Menu](./menu.mdx) -- [Modal](./modal.mdx) -- [ModalTrigger](./modaltrigger.mdx) -- [Popover](./popover.mdx) -- [ProgressBar](./progressbar.mdx) -- [Radio](./radio.mdx) -- [SafeMarkdown](./safemarkdown.mdx) -- [Select](./select.mdx) -- [Skeleton](./skeleton.mdx) -- [Slider](./slider.mdx) -- [Steps](./steps.mdx) -- [Switch](./switch.mdx) -- [TableCollection](./tablecollection.mdx) -- [TableView](./tableview.mdx) -- [Tabs](./tabs.mdx) -- [Timer](./timer.mdx) -- [Tooltip](./tooltip.mdx) -- [Tree](./tree.mdx) -- [TreeSelect](./treeselect.mdx) -- [Typography](./typography.mdx) -- [UnsavedChangesModal](./unsavedchangesmodal.mdx) -- [Upload](./upload.mdx) diff --git a/docs/developer_docs/components/ui/infotooltip.mdx b/docs/developer_docs/components/ui/infotooltip.mdx deleted file mode 100644 index 42e6341ea90..00000000000 --- a/docs/developer_docs/components/ui/infotooltip.mdx +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: InfoTooltip -sidebar_label: InfoTooltip ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# InfoTooltip - -The InfoTooltip component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `tooltip` | `string` | `"This is the text that will display!"` | - | - -## Import - -```tsx -import { InfoTooltip } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/InfoTooltip/InfoTooltip.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/input.mdx b/docs/developer_docs/components/ui/input.mdx deleted file mode 100644 index 2bc8706fe63..00000000000 --- a/docs/developer_docs/components/ui/input.mdx +++ /dev/null @@ -1,162 +0,0 @@ ---- -title: Input -sidebar_label: Input ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Input - -The Input component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `allowClear` | `boolean` | `false` | - | -| `disabled` | `boolean` | `false` | - | -| `showCount` | `boolean` | `false` | - | -| `type` | `string` | `"text"` | HTML input type | -| `variant` | `string` | `"outlined"` | Input style variant | - -## Import - -```tsx -import { Input } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Input/Input.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/label.mdx b/docs/developer_docs/components/ui/label.mdx deleted file mode 100644 index 816d449770f..00000000000 --- a/docs/developer_docs/components/ui/label.mdx +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: Label -sidebar_label: Label ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Label - -The Label component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `type` | `string` | `"default"` | The visual style of the label. | -| `children` | `string` | `"Label text"` | The label text content. | -| `monospace` | `boolean` | `false` | Use monospace font. | - -## Import - -```tsx -import { Label } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Label/Label.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/list.mdx b/docs/developer_docs/components/ui/list.mdx deleted file mode 100644 index 06a3b028965..00000000000 --- a/docs/developer_docs/components/ui/list.mdx +++ /dev/null @@ -1,117 +0,0 @@ ---- -title: List -sidebar_label: List ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# List - -The List component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - const data = ['Dashboard Analytics', 'User Management', 'Data Sources']; - return ( - {item}} - /> - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `bordered` | `boolean` | `false` | Whether to show a border around the list. | -| `split` | `boolean` | `true` | Whether to show a divider between items. | -| `size` | `string` | `"default"` | Size of the list. | -| `loading` | `boolean` | `false` | Whether to show a loading indicator. | -| `dataSource` | `any` | `["Dashboard Analytics","User Management","Data Sources"]` | - | - -## Import - -```tsx -import { List } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/List/List.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/listviewcard.mdx b/docs/developer_docs/components/ui/listviewcard.mdx deleted file mode 100644 index 63af1c14f34..00000000000 --- a/docs/developer_docs/components/ui/listviewcard.mdx +++ /dev/null @@ -1,132 +0,0 @@ ---- -title: ListViewCard -sidebar_label: ListViewCard ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# ListViewCard - -ListViewCard is a card component used to display items in list views with an image, title, description, and optional cover sections. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `title` | `string` | `"Superset Card Title"` | Title displayed on the card. | -| `loading` | `boolean` | `false` | Whether the card is in loading state. | -| `url` | `string` | `"/superset/dashboard/births/"` | URL the card links to. | -| `imgURL` | `string` | `"https://picsum.photos/seed/superset/300/200"` | Primary image URL for the card. | -| `description` | `string` | `"Lorem ipsum dolor sit amet, consectetur adipiscing elit..."` | Description text displayed on the card. | -| `coverLeft` | `string` | `"Left Section"` | Content for the left section of the cover. | -| `coverRight` | `string` | `"Right Section"` | Content for the right section of the cover. | - -## Import - -```tsx -import { ListViewCard } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/ListViewCard/ListViewCard.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/loading.mdx b/docs/developer_docs/components/ui/loading.mdx deleted file mode 100644 index 0326c19e90a..00000000000 --- a/docs/developer_docs/components/ui/loading.mdx +++ /dev/null @@ -1,187 +0,0 @@ ---- -title: Loading -sidebar_label: Loading ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Loading - -The Loading component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( -
- {['normal', 'floating', 'inline'].map(position => ( -
-

{position}

- -
- ))} -
- ); -} -``` - -## Size and Opacity Showcase - -```tsx live -function SizeShowcase() { - const sizes = ['s', 'm', 'l']; - return ( -
-
-
Size
-
Normal
-
Muted
-
Usage
- {sizes.map(size => ( - -
- {size.toUpperCase()} ({size === 's' ? '40px' : size === 'm' ? '70px' : '100px'}) -
-
- -
-
- -
-
- {size === 's' && 'Filter bars, inline'} - {size === 'm' && 'Explore pages'} - {size === 'l' && 'Full page loading'} -
-
- ))} -
-
- ); -} -``` - -## Contextual Examples - -```tsx live -function ContextualDemo() { - return ( -
-

Filter Bar (size="s", muted)

-
- Filter 1: - - Filter 2: - -
- -

Dashboard Grid (size="s", muted)

-
- {[1, 2, 3].map(i => ( -
- -
- ))} -
- -

Main Loading (size="l")

-
- -
-
- ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `size` | `string` | `"m"` | Size of the spinner: s (40px), m (70px), or l (100px). | -| `position` | `string` | `"normal"` | Position style: normal (inline flow), floating (overlay), or inline. | -| `muted` | `boolean` | `false` | Whether to show a muted/subtle version of the spinner. | - -## Import - -```tsx -import { Loading } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Loading/Loading.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/menu.mdx b/docs/developer_docs/components/ui/menu.mdx deleted file mode 100644 index c088eb538c7..00000000000 --- a/docs/developer_docs/components/ui/menu.mdx +++ /dev/null @@ -1,174 +0,0 @@ ---- -title: Menu -sidebar_label: Menu ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Menu - -Navigation menu component supporting horizontal, vertical, and inline modes. Based on Ant Design Menu with Superset styling. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## Vertical Menu - -```tsx live -function VerticalMenu() { - return ( - - ); -} -``` - -## Menu with Icons - -```tsx live -function MenuWithIcons() { - return ( - Dashboards, key: 'dashboards' }, - { label: <> Charts, key: 'charts' }, - { label: <> Datasets, key: 'datasets' }, - { label: <> SQL Lab, key: 'sqllab' }, - ]} - /> - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `mode` | `string` | `"horizontal"` | Menu display mode: horizontal navbar, vertical sidebar, or inline collapsible. | -| `selectable` | `boolean` | `true` | Whether menu items can be selected. | -| `items` | `any` | `[{"label":"Dashboards","key":"dashboards"},{"label":"Charts","key":"charts"},{"label":"Datasets","key":"datasets"},{"label":"SQL Lab","key":"sqllab"}]` | - | - -## Import - -```tsx -import { Menu } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Menu/Menu.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/modal.mdx b/docs/developer_docs/components/ui/modal.mdx deleted file mode 100644 index d1ed9b48234..00000000000 --- a/docs/developer_docs/components/ui/modal.mdx +++ /dev/null @@ -1,207 +0,0 @@ ---- -title: Modal -sidebar_label: Modal ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Modal - -Modal dialog component for displaying content that requires user attention or interaction. Supports customizable buttons, drag/resize, and confirmation dialogs. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function ModalDemo() { - const [isOpen, setIsOpen] = React.useState(false); - return ( - <> - - setIsOpen(false)} - title="Example Modal" - primaryButtonName="Submit" - onHandledPrimaryAction={() => { - alert('Submitted!'); - setIsOpen(false); - }} - > -

This is the modal content. Click Submit or close the modal.

-
- - ); -} -``` - -## Danger Modal - -```tsx live -function DangerModal() { - const [isOpen, setIsOpen] = React.useState(false); - return ( - <> - - setIsOpen(false)} - title="Confirm Delete" - primaryButtonName="Delete" - primaryButtonStyle="danger" - onHandledPrimaryAction={() => { - alert('Deleted!'); - setIsOpen(false); - }} - > -

Are you sure you want to delete this item? This action cannot be undone.

-
- - ); -} -``` - -## Confirmation Dialogs - -```tsx live -function ConfirmationDialogs() { - return ( -
- - - -
- ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `disablePrimaryButton` | `boolean` | `false` | Whether the primary button is disabled. | -| `primaryButtonName` | `string` | `"Submit"` | Text for the primary action button. | -| `primaryButtonStyle` | `string` | `"primary"` | The style of the primary action button. | -| `show` | `boolean` | `false` | Whether the modal is visible. Use the "Try It" example below for a working demo. | -| `title` | `string` | `"I'm a modal!"` | Title displayed in the modal header. | -| `resizable` | `boolean` | `false` | Whether the modal can be resized by dragging corners. | -| `draggable` | `boolean` | `false` | Whether the modal can be dragged by its header. | -| `width` | `number` | `500` | Width of the modal in pixels. | - -## Import - -```tsx -import { Modal } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Modal/Modal.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/modaltrigger.mdx b/docs/developer_docs/components/ui/modaltrigger.mdx deleted file mode 100644 index b9f5bd81b7d..00000000000 --- a/docs/developer_docs/components/ui/modaltrigger.mdx +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: ModalTrigger -sidebar_label: ModalTrigger ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# ModalTrigger - -A component that renders a trigger element which opens a modal when clicked. Useful for actions that need confirmation or additional input. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - Click to Open} - modalTitle="Example Modal" - modalBody={

This is the modal content. You can put any React elements here.

} - width="500px" - responsive - /> - ); -} -``` - -## With Custom Trigger - -```tsx live -function CustomTrigger() { - return ( - - Add New Item - - } - modalTitle="Add New Item" - modalBody={ -
-

Fill out the form to add a new item.

- -
- } - width="400px" - /> - ); -} -``` - -## Draggable & Resizable - -```tsx live -function DraggableModal() { - return ( - Open Draggable Modal} - modalTitle="Draggable & Resizable" - modalBody={

Try dragging the header or resizing from the corners!

} - draggable - resizable - width="500px" - /> - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `isButton` | `boolean` | `true` | Whether to wrap the trigger in a button element. | -| `modalTitle` | `string` | `"Modal Title"` | Title displayed in the modal header. | -| `modalBody` | `string` | `"This is the modal body content."` | Content displayed in the modal body. | -| `tooltip` | `string` | `"Click to open modal"` | Tooltip text shown on hover over the trigger. | -| `width` | `string` | `"600px"` | Width of the modal (e.g., "600px", "80%"). | -| `maxWidth` | `string` | `"1000px"` | Maximum width of the modal. | -| `responsive` | `boolean` | `true` | Whether the modal should be responsive. | -| `draggable` | `boolean` | `false` | Whether the modal can be dragged by its header. | -| `resizable` | `boolean` | `false` | Whether the modal can be resized by dragging corners. | -| `triggerNode` | `string` | `"Click to Open Modal"` | The clickable element that opens the modal when clicked. | - -## Import - -```tsx -import { ModalTrigger } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/ModalTrigger/ModalTrigger.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/popover.mdx b/docs/developer_docs/components/ui/popover.mdx deleted file mode 100644 index 0a3ad4b2d10..00000000000 --- a/docs/developer_docs/components/ui/popover.mdx +++ /dev/null @@ -1,199 +0,0 @@ ---- -title: Popover -sidebar_label: Popover ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Popover - -A floating card that appears when hovering or clicking a trigger element. Supports configurable placement, trigger behavior, and custom content. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - - - ); -} -``` - -## Click Trigger - -```tsx live -function ClickPopover() { - return ( - - - - ); -} -``` - -## Placements - -```tsx live -function PlacementsDemo() { - return ( -
- {['top', 'right', 'bottom', 'left'].map(placement => ( - - - - ))} -
- ); -} -``` - -## Rich Content - -```tsx live -function RichPopover() { - return ( - -

Created by: Admin

-

Last modified: Jan 2025

-

Charts: 12

- - } - > - -
- ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `content` | `string` | `"Popover sample content"` | Content displayed inside the popover body. | -| `title` | `string` | `"Popover title"` | Title displayed in the popover header. | -| `arrow` | `boolean` | `true` | Whether to show the popover's arrow pointing to the trigger. | -| `color` | `string` | `"#fff"` | The background color of the popover. | - -## Import - -```tsx -import { Popover } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Popover/Popover.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/progressbar.mdx b/docs/developer_docs/components/ui/progressbar.mdx deleted file mode 100644 index 4b68653ee11..00000000000 --- a/docs/developer_docs/components/ui/progressbar.mdx +++ /dev/null @@ -1,206 +0,0 @@ ---- -title: ProgressBar -sidebar_label: ProgressBar ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# ProgressBar - -Progress bar component for displaying completion status. Supports line, circle, and dashboard display types. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## All Progress Types - -```tsx live -function AllTypesDemo() { - return ( -
-
-

Line

- -
-
-

Circle

- -
-
-

Dashboard

- -
-
- ); -} -``` - -## Status Variants - -```tsx live -function StatusDemo() { - const statuses = ['normal', 'success', 'exception', 'active']; - return ( -
- {statuses.map(status => ( -
- {status} - -
- ))} -
- ); -} -``` - -## Custom Colors - -```tsx live -function CustomColors() { - return ( -
- - - - -
- ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `percent` | `number` | `75` | Completion percentage (0-100). | -| `status` | `string` | `"normal"` | Current status of the progress bar. | -| `type` | `string` | `"line"` | Display type: line, circle, or dashboard gauge. | -| `striped` | `boolean` | `false` | Whether to show striped animation on the bar. | -| `showInfo` | `boolean` | `true` | Whether to show the percentage text. | -| `strokeLinecap` | `string` | `"round"` | Shape of the progress bar endpoints. | - -## Import - -```tsx -import { ProgressBar } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/ProgressBar/ProgressBar.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/radio.mdx b/docs/developer_docs/components/ui/radio.mdx deleted file mode 100644 index 69cdfcf2c06..00000000000 --- a/docs/developer_docs/components/ui/radio.mdx +++ /dev/null @@ -1,137 +0,0 @@ ---- -title: Radio -sidebar_label: Radio ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Radio - -Radio button component for selecting one option from a set. Supports standalone radio buttons, radio buttons styled as buttons, and grouped radio buttons with layout configuration. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - Radio - - ); -} -``` - -## Radio Button Variants - -```tsx live -function RadioButtonDemo() { - const [value, setValue] = React.useState('line'); - return ( - setValue(e.target.value)}> - Line Chart - Bar Chart - Pie Chart - - ); -} -``` - -## Vertical Radio Group - -```tsx live -function VerticalDemo() { - const [value, setValue] = React.useState('option1'); - return ( - setValue(e.target.value)}> -
- First option - Second option - Third option -
-
- ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `value` | `string` | `"radio1"` | The value associated with this radio button. | -| `disabled` | `boolean` | `false` | Whether the radio button is disabled. | -| `checked` | `boolean` | `false` | Whether the radio button is checked (controlled mode). | -| `children` | `string` | `"Radio"` | Label text displayed next to the radio button. | - -## Import - -```tsx -import { Radio } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Radio/Radio.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/safemarkdown.mdx b/docs/developer_docs/components/ui/safemarkdown.mdx deleted file mode 100644 index 8cb741b962c..00000000000 --- a/docs/developer_docs/components/ui/safemarkdown.mdx +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: SafeMarkdown -sidebar_label: SafeMarkdown ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# SafeMarkdown - -The SafeMarkdown component from Superset's UI library. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( - - ); -} -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `htmlSanitization` | `boolean` | `true` | Enable HTML sanitization (recommended for user input) | - -## Import - -```tsx -import { SafeMarkdown } from '@superset-ui/core/components'; -``` - ---- - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/SafeMarkdown/SafeMarkdown.stories.tsx). -::: diff --git a/docs/developer_docs/components/ui/select.mdx b/docs/developer_docs/components/ui/select.mdx deleted file mode 100644 index 262196d4ccc..00000000000 --- a/docs/developer_docs/components/ui/select.mdx +++ /dev/null @@ -1,308 +0,0 @@ ---- -title: Select -sidebar_label: Select ---- - - - -import { StoryWithControls } from '../../../src/components/StorybookWrapper'; - -# Select - -A versatile select component supporting single and multi-select modes, search filtering, option creation, and both synchronous and asynchronous data sources. - -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -```tsx live -function Demo() { - return ( -
- -
- ); -} -``` - -## Allow New Options - -```tsx live -function AllowNewDemo() { - return ( -
- -
- ); -} -``` - -## One Line Mode - -```tsx live -function OneLineDemo() { - return ( -
-
- - - - - - - - -
- -**I want to contribute code** -1. [Set up development environment](/developer-docs/contributing/development-setup) -2. [Find a good first issue](https://github.com/apache/superset/labels/good%20first%20issue) -3. [Submit your first PR](/developer-docs/contributing/submitting-pr) - - - -**I want to build an extension** -1. [Start with Quick Start](/developer-docs/extensions/quick-start) -2. [Learn extension development](/developer-docs/extensions/development) -3. [Explore architecture](/developer-docs/extensions/architecture) - -
- -**I found a bug** -1. [Search existing issues](https://github.com/apache/superset/issues) -2. [Report the bug](/developer-docs/contributing/issue-reporting) -3. [Submit a fix](/developer-docs/contributing/submitting-pr) - - - -**I need help** -1. [Check the FAQ](https://superset.apache.org/docs/frequently-asked-questions) -2. [Ask in Slack](https://apache-superset.slack.com) -3. [Start a discussion](https://github.com/apache/superset/discussions) - -
- ---- - -Welcome to the Apache Superset community! We're excited to have you contribute. diff --git a/docs/developer_docs/sidebars.js b/docs/developer_docs/sidebars.js deleted file mode 100644 index 7926d80cf61..00000000000 --- a/docs/developer_docs/sidebars.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -module.exports = { - developerPortalSidebar: [ - 'index', - { - type: 'category', - label: 'Contributing', - collapsed: true, - items: [ - 'contributing/overview', - 'guidelines/design-guidelines', - 'guidelines/frontend-style-guidelines', - 'guidelines/backend-style-guidelines', - ], - }, - { - type: 'category', - label: 'Extensions', - collapsed: true, - items: [ - 'extensions/overview', - 'extensions/quick-start', - 'extensions/architecture', - 'extensions/dependencies', - 'extensions/contribution-types', - { - type: 'category', - label: 'Extension Points', - collapsed: true, - items: [ - 'extensions/extension-points/sqllab', - ], - }, - 'extensions/development', - 'extensions/deployment', - 'extensions/mcp', - 'extensions/security', - 'extensions/tasks', - 'extensions/registry', - ], - }, - { - type: 'category', - label: 'Testing', - collapsed: true, - items: [ - 'testing/overview', - ], - }, - { - type: 'category', - label: 'UI Components', - collapsed: true, - link: { - type: 'doc', - id: 'components/index', - }, - items: [ - { - type: 'autogenerated', - dirName: 'components', - }, - ], - }, - ], -}; diff --git a/docs/developer_docs/testing/backend-testing.md b/docs/developer_docs/testing/backend-testing.md deleted file mode 100644 index e8c8d229fcb..00000000000 --- a/docs/developer_docs/testing/backend-testing.md +++ /dev/null @@ -1,171 +0,0 @@ ---- -title: Backend Testing -sidebar_position: 3 ---- - - - -# Backend Testing - -🚧 **Coming Soon** 🚧 - -Complete guide for testing Superset's Python backend, APIs, and database interactions. - -## Topics to be covered: - -- Pytest configuration and fixtures -- Unit testing best practices -- Integration testing with databases -- API endpoint testing -- Mocking strategies and patterns -- Testing async operations with Celery -- Security testing guidelines -- Performance and load testing -- Test database setup and teardown -- Coverage requirements - -## Quick Commands - -```bash -# Run all backend tests -pytest - -# Run specific test file -pytest tests/unit_tests/specific_test.py - -# Run with coverage -pytest --cov=superset - -# Run tests in parallel -pytest -n auto - -# Run only unit tests -pytest tests/unit_tests/ - -# Run only integration tests -pytest tests/integration_tests/ -``` - -## Testing Alerts & Reports with Celery and MailHog - -The Alerts & Reports feature relies on Celery for task scheduling and execution. To test it locally, you need Redis (message broker), Celery Beat (scheduler), a Celery Worker (executor), and an SMTP server to receive email notifications. - -### Prerequisites - -- Redis running on `localhost:6379` -- [MailHog](https://github.com/mailhog/MailHog) installed (a local SMTP server with a web UI for viewing caught emails) - -### superset_config.py - -Your `CeleryConfig` **must** include `beat_schedule`. When you define a custom `CeleryConfig` class in `superset_config.py`, it replaces the default entirely. If you omit `beat_schedule`, Celery Beat will start but never schedule any report tasks. - -```python -from celery.schedules import crontab -from superset.tasks.types import ExecutorType - -REDIS_HOST = "localhost" -REDIS_PORT = "6379" - -class CeleryConfig: - broker_url = f"redis://{REDIS_HOST}:{REDIS_PORT}/0" - result_backend = f"redis://{REDIS_HOST}:{REDIS_PORT}/0" - broker_connection_retry_on_startup = True - imports = ( - "superset.sql_lab", - "superset.tasks.scheduler", - "superset.tasks.thumbnails", - "superset.tasks.cache", - ) - worker_prefetch_multiplier = 10 - task_acks_late = True - beat_schedule = { - "reports.scheduler": { - "task": "reports.scheduler", - "schedule": crontab(minute="*", hour="*"), - }, - "reports.prune_log": { - "task": "reports.prune_log", - "schedule": crontab(minute=0, hour=0), - }, - } - -CELERY_CONFIG = CeleryConfig - -# SMTP settings pointing to MailHog -SMTP_HOST = "localhost" -SMTP_PORT = 1025 -SMTP_STARTTLS = False -SMTP_SSL = False -SMTP_USER = "" -SMTP_PASSWORD = "" -SMTP_MAIL_FROM = "superset@localhost" - -# Must match where your frontend is running -WEBDRIVER_BASEURL = "http://localhost:9000/" - -ALERT_REPORTS_EXECUTE_AS = [ExecutorType.OWNER] - -FEATURE_FLAGS = { - "ALERT_REPORTS": True, - # Recommended for better screenshot support (WebGL/DeckGL charts) - "PLAYWRIGHT_REPORTS_AND_THUMBNAILS": True, -} -``` - -:::note -Do not include `"superset.tasks.async_queries"` in `CeleryConfig.imports` unless you need Global Async Queries. That module accesses `current_app.config` at import time and will crash the worker with a "Working outside of application context" error. -::: - -### Starting the Services - -Start MailHog, then Celery Beat and Worker in separate terminals: - -```bash -# Terminal 1 - MailHog (SMTP on :1025, Web UI on :8025) -MailHog - -# Terminal 2 - Celery Beat (scheduler) -celery --app=superset.tasks.celery_app:app beat --loglevel=info - -# Terminal 3 - Celery Worker (executor) -celery --app=superset.tasks.celery_app:app worker --concurrency=1 --loglevel=info -``` - -Use `--concurrency=1` to limit resource usage on your dev machine. - -### Verifying the Setup - -1. **Beat** should log `Scheduler: Sending due task reports.scheduler (reports.scheduler)` once per minute -2. **Worker** should log `Scheduling alert eta: ` for each active report -3. Create a test report in **Settings > Alerts & Reports** with a `* * * * *` cron schedule -4. Check **http://localhost:8025** (MailHog web UI) for the email within 1-2 minutes - -### Troubleshooting - -| Problem | Solution | -|---|---| -| Beat shows no output | Ensure `beat_schedule` is defined in your `CeleryConfig` and `--loglevel=info` is set | -| "Report Schedule is still working, refusing to re-compute" | Previous executions are stuck. Reset with: `UPDATE report_schedule SET last_state = 'Not triggered' WHERE id = ;` | -| Task backlog overwhelming the worker | Flush Redis: `redis-cli FLUSHDB`, then restart Beat and Worker | -| Screenshot timeout | Ensure your frontend dev server is running and `WEBDRIVER_BASEURL` matches its URL | - ---- - -*This documentation is under active development. Check back soon for updates!* diff --git a/docs/developer_docs/testing/ci-cd.md b/docs/developer_docs/testing/ci-cd.md deleted file mode 100644 index baefc13cbee..00000000000 --- a/docs/developer_docs/testing/ci-cd.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: CI/CD and Automation -sidebar_position: 5 ---- - - - -# CI/CD and Automation - -🚧 **Coming Soon** 🚧 - -Understanding Superset's continuous integration and deployment pipelines. - -## Topics to be covered: - -- GitHub Actions workflows -- Pre-commit hooks configuration -- Automated testing pipelines -- Code quality checks (ESLint, Prettier, Black, MyPy) -- Security scanning (Dependabot, CodeQL) -- Docker image building and publishing -- Release automation -- Performance benchmarking -- Coverage reporting and tracking - -## Pre-commit Hooks - -```bash -# Install pre-commit hooks -pre-commit install - -# Run all hooks on staged files -pre-commit run - -# Run specific hook -pre-commit run mypy - -# Run on all files (not just staged) -pre-commit run --all-files -``` - -## GitHub Actions - -Key workflows: -- `test-frontend.yml` - Frontend tests -- `test-backend.yml` - Backend tests -- `docker.yml` - Docker image builds -- `codeql.yml` - Security analysis -- `release.yml` - Release automation - ---- - -*This documentation is under active development. Check back soon for updates!* diff --git a/docs/developer_docs/testing/e2e-testing.md b/docs/developer_docs/testing/e2e-testing.md deleted file mode 100644 index a4536123767..00000000000 --- a/docs/developer_docs/testing/e2e-testing.md +++ /dev/null @@ -1,227 +0,0 @@ ---- -title: End-to-End Testing -sidebar_position: 4 ---- - - - -# End-to-End Testing - -Apache Superset uses Playwright for end-to-end testing, migrating from the legacy Cypress tests. - -## Running Tests - -### Playwright (Recommended) - -```bash -cd superset-frontend - -# Run all tests -npm run playwright:test -# or: npx playwright test - -# Run specific test file -npx playwright test tests/auth/login.spec.ts - -# Run with UI mode for debugging -npm run playwright:ui -# or: npx playwright test --ui - -# Run in headed mode (see browser) -npm run playwright:headed -# or: npx playwright test --headed - -# Debug specific test file -npm run playwright:debug tests/auth/login.spec.ts -# or: npx playwright test --debug tests/auth/login.spec.ts -``` - -### Cypress (Deprecated) - -Cypress tests are being migrated to Playwright. For legacy tests: - -```bash -cd superset-frontend/cypress-base -npm run cypress-run-chrome # Headless -npm run cypress-debug # Interactive UI -``` - -## Project Architecture - -``` -superset-frontend/playwright/ -├── components/core/ # Reusable UI components -├── pages/ # Page Object Models -├── tests/ # Test files organized by feature -├── utils/ # Shared constants and utilities -└── playwright.config.ts -``` - -## Design Principles - -We follow **YAGNI** (You Aren't Gonna Need It), **DRY** (Don't Repeat Yourself), and **KISS** (Keep It Simple, Stupid) principles: - -- Build only what's needed now -- Reuse existing patterns and components -- Keep solutions simple and maintainable - -## Page Object Pattern - -Each page object encapsulates: - -- **Actions**: What you can do on the page -- **Queries**: Information you can get from the page -- **Selectors**: Centralized in private static SELECTORS constant -- **NO Assertions**: Keep assertions in test files - -**Example Page Object:** - -```typescript -export class AuthPage { - // Selectors centralized in the page object - private static readonly SELECTORS = { - LOGIN_FORM: '[data-test="login-form"]', - USERNAME_INPUT: '[data-test="username-input"]', - } as const; - - // Actions - what you can do - async loginWithCredentials(username: string, password: string) {} - - // Queries - information you can get - async getCurrentUrl(): Promise {} - - // NO assertions - those belong in tests -} -``` - -**Example Test:** - -```typescript -import { test, expect } from '@playwright/test'; -import { AuthPage } from '../../pages/AuthPage'; -import { LOGIN } from '../../utils/urls'; - -test('should login with correct credentials', async ({ page }) => { - const authPage = new AuthPage(page); - await authPage.goto(); - await authPage.loginWithCredentials('admin', 'general'); - - // Assertions belong in tests, not page objects - expect(await authPage.getCurrentUrl()).not.toContain(LOGIN); -}); -``` - -## Core Components - -Reusable UI interaction classes for common elements (`components/core/`): - -- **Form**: Container with properly scoped child element access -- **Input**: Supports `fill()`, `type()`, and `pressSequentially()` methods -- **Button**: Standard click, hover, focus interactions - -**Usage Example:** - -```typescript -import { Form } from '../components/core'; - -const loginForm = new Form(page, '[data-test="login-form"]'); -const usernameInput = loginForm.getInput('[data-test="username-input"]'); -await usernameInput.fill('admin'); -``` - -## Test Reports - -Playwright generates multiple reports for better visibility: - -```bash -# View interactive HTML report (opens automatically on failure) -npm run playwright:report -# or: npx playwright show-report - -# View test trace for debugging failures -npx playwright show-trace test-results/[test-name]/trace.zip -``` - -### Report Types - -- **List Reporter**: Shows progress and summary table in terminal -- **HTML Report**: Interactive web interface with screenshots, videos, and traces -- **JSON Report**: Machine-readable format in `test-results/results.json` -- **GitHub Actions**: Annotations in CI for failed tests - -### Debugging Failed Tests - -When tests fail, Playwright automatically captures: - -- **Screenshots** at the point of failure -- **Videos** of the entire test run -- **Traces** with timeline and network activity -- **Error context** with detailed debugging information - -All debugging artifacts are available in the HTML report for easy analysis. - -## Configuration - -- **Config**: `playwright.config.ts` - matches Cypress settings -- **Base URL**: `http://localhost:8088` (assumes Superset running) -- **Browsers**: Chrome only for Phase 1 (YAGNI) -- **Retries**: 2 in CI, 0 locally (matches Cypress) - -## Contributing Guidelines - -### Adding New Tests - -1. **Check existing components** before creating new ones -2. **Use page objects** for page interactions -3. **Keep assertions in tests**, not page objects -4. **Follow naming conventions**: `feature.spec.ts` - -### Adding New Components - -1. **Follow YAGNI**: Only build what's immediately needed -2. **Use Locator-based scoping** for proper element isolation -3. **Support both string selectors and Locator objects** via constructor overloads -4. **Add to `components/core/index.ts`** for easy importing - -### Adding New Page Objects - -1. **Centralize selectors** in private static SELECTORS constant -2. **Import shared constants** from `utils/urls.ts` -3. **Actions and queries only** - no assertions -4. **Use existing components** for DOM interactions - -## Migration from Cypress - -When porting Cypress tests: - -1. **Port the logic**, not the implementation -2. **Use page objects** instead of inline selectors -3. **Replace `cy.intercept/cy.wait`** with `page.waitForRequest()` -4. **Use shared constants** from `utils/urls.ts` -5. **Follow the established patterns** shown in `tests/auth/login.spec.ts` - -## Best Practices - -- **Centralize selectors** in page objects -- **Centralize URLs** in `utils/urls.ts` -- **Use meaningful test descriptions** -- **Keep page objects action-focused** -- **Put assertions in tests, not page objects** -- **Follow the existing patterns** for consistency diff --git a/docs/developer_docs/testing/frontend-testing.md b/docs/developer_docs/testing/frontend-testing.md deleted file mode 100644 index e33bb1095bc..00000000000 --- a/docs/developer_docs/testing/frontend-testing.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: Frontend Testing -sidebar_position: 2 ---- - - - -# Frontend Testing - -🚧 **Coming Soon** 🚧 - -Comprehensive guide for testing Superset's frontend components and features. - -## Topics to be covered: - -- Jest configuration and setup -- React Testing Library best practices -- Component testing strategies -- Redux store testing -- Async operations and API mocking -- Snapshot testing guidelines -- Coverage requirements and reporting -- Debugging test failures -- Performance testing for UI components - -## Quick Commands - -```bash -# Run all frontend tests -npm run test - -# Run tests in watch mode -npm run test -- --watch - -# Run tests with coverage -npm run test -- --coverage - -# Run specific test file -npm run test -- MyComponent.test.tsx -``` - ---- - -*This documentation is under active development. Check back soon for updates!* diff --git a/docs/developer_docs/testing/overview.md b/docs/developer_docs/testing/overview.md deleted file mode 100644 index 0fc04399958..00000000000 --- a/docs/developer_docs/testing/overview.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -title: Overview -sidebar_position: 1 ---- - - - -# Overview - -Apache Superset follows a comprehensive testing strategy that ensures code quality, reliability, and maintainability. This section covers all aspects of testing in the Superset ecosystem, from unit tests to end-to-end testing workflows. - -## Testing Philosophy - -Superset embraces a testing pyramid approach: - -- **Unit Tests**: Fast, isolated tests for individual components and functions -- **Integration Tests**: Tests that verify component interactions and API endpoints -- **End-to-End Tests**: Full user journey testing in browser environments - -## Testing Documentation - -### Frontend Testing -- **[Frontend Testing](./frontend-testing.md)** - Jest, React Testing Library, and component testing strategies - -### Backend Testing -- **[Backend Testing](./backend-testing.md)** - pytest, database testing, and API testing patterns - -### End-to-End Testing -- **[E2E Testing](./e2e-testing.md)** - Playwright testing for complete user workflows - -### CI/CD Integration -- **[CI/CD](./ci-cd.md)** - Continuous integration, automated testing, and deployment pipelines - -## Testing Tools & Frameworks - -### Frontend -- **Jest**: JavaScript testing framework for unit and integration tests -- **React Testing Library**: Component testing utilities focused on user behavior -- **Playwright**: Modern end-to-end testing for web applications -- **Storybook**: Component development and visual testing environment - -### Backend -- **pytest**: Python testing framework with powerful fixtures and plugins -- **SQLAlchemy Test Utilities**: Database testing and transaction management -- **Flask Test Client**: API endpoint testing and request simulation - -## Best Practices - -### Writing Effective Tests -1. **Test Behavior, Not Implementation**: Focus on what the code should do, not how it does it -2. **Keep Tests Independent**: Each test should be able to run in isolation -3. **Use Descriptive Names**: Test names should clearly describe what is being tested -4. **Arrange, Act, Assert**: Structure tests with clear setup, execution, and verification phases - -### Test Organization -- **Colocation**: Place test files near the code they test -- **Naming Conventions**: Use consistent naming patterns for test files and functions -- **Test Categories**: Organize tests by type (unit, integration, e2e) -- **Test Data Management**: Use factories and fixtures for consistent test data - -## Running Tests - -### Quick Commands -```bash -# Frontend unit tests -npm run test - -# Backend unit tests -pytest tests/unit_tests/ - -# End-to-end tests -npm run playwright:test - -# All tests with coverage -npm run test:coverage -``` - -### Test Development Workflow -1. **Write Failing Test**: Start with a test that describes the desired behavior -2. **Implement Feature**: Write the minimum code to make the test pass -3. **Refactor**: Improve code quality while keeping tests green -4. **Verify Coverage**: Ensure adequate test coverage for new code - -## Testing in Development - -### Test-Driven Development (TDD) -- Write tests before implementation -- Use tests to guide design decisions -- Maintain fast feedback loops - -### Continuous Testing -- Run tests automatically on code changes -- Integrate testing into development workflow -- Use pre-commit hooks for test validation - -## Getting Started - -1. **Set up Testing Environment**: Follow the development setup guide -2. **Run Existing Tests**: Familiarize yourself with the test suite -3. **Write Your First Test**: Start with a simple unit test -4. **Learn Testing Patterns**: Study existing tests for patterns and conventions - -## Topics to be covered: - -- Testing strategy and pyramid -- Test-driven development (TDD) for plugins -- Continuous integration and automated testing -- Code coverage and quality metrics -- Testing tools and frameworks overview -- Mock data and test fixtures -- Performance testing and benchmarking -- Accessibility testing automation -- Cross-browser and device testing -- Regression testing strategies - -## Testing Levels - -### Unit Testing -- **Component testing** - Individual React components -- **Function testing** - Data transformation and utility functions -- **Hook testing** - Custom React hooks -- **Service testing** - API clients and business logic - -### Integration Testing -- **API integration** - Backend service communication -- **Component integration** - Multi-component workflows -- **Data flow testing** - End-to-end data processing -- **Plugin lifecycle testing** - Installation and activation - -### End-to-End Testing -- **User workflow testing** - Complete user journeys -- **Cross-browser testing** - Browser compatibility -- **Performance testing** - Load and stress testing -- **Accessibility testing** - Screen reader and keyboard navigation - -## Testing Tools - -- **Jest** - Unit and integration testing framework -- **React Testing Library** - Component testing utilities -- **Playwright** - End-to-end testing (replacing Cypress) -- **Storybook** - Component development and testing - ---- - -*This documentation is under active development. Check back soon for updates!* diff --git a/docs/developer_docs/testing/storybook.md b/docs/developer_docs/testing/storybook.md deleted file mode 100644 index 0e190220f15..00000000000 --- a/docs/developer_docs/testing/storybook.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -title: Storybook -sidebar_position: 5 ---- - - - -# 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//index.js -``` - -### To a New Package - -1. Add package dependencies: - - ```bash - npm install - ``` - -2. Create a story folder matching the package name: - - ```bash - mkdir storybook/stories/superset-ui-/ - ``` - -3. Create an `index.js` file with the story configuration: - - ```javascript - export default { - examples: [ - { - storyPath: '@superset-ui/package', - storyName: 'My Story', - renderStory: () => , - }, - ], - }; - ``` - - 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 diff --git a/docs/developer_docs/testing/testing-guidelines.md b/docs/developer_docs/testing/testing-guidelines.md deleted file mode 100644 index 5fb8424449a..00000000000 --- a/docs/developer_docs/testing/testing-guidelines.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -title: Testing Guidelines and Best Practices -sidebar_position: 1 ---- - - - -# Testing Guidelines and Best Practices - -We feel that tests are an important part of a feature and not an additional or optional effort. That's why we colocate test files with functionality and sometimes write tests upfront to help validate requirements and shape the API of our components. Every new component or file added should have an associated test file with the `.test` extension. - -We use Jest, React Testing Library (RTL), and Cypress to write our unit, integration, and end-to-end tests. For each type, we have a set of best practices/tips described below: - -## Jest - -### Write simple, standalone tests - -The importance of simplicity is often overlooked in test cases. Clear, dumb code should always be preferred over complex ones. The test cases should be pretty much standalone and should not involve any external logic if not absolutely necessary. That's because you want the corpus of the tests to be easy to read and understandable at first sight. - -### Avoid nesting when you're testing - -Avoid the use of `describe` blocks in favor of inlined tests. If your tests start to grow and you feel the need to group tests, prefer to break them into multiple test files. Check this awesome [article](https://kentcdodds.com/blog/avoid-nesting-when-youre-testing) written by [Kent C. Dodds](https://kentcdodds.com/) about this topic. - -### No warnings! - -Your tests shouldn't trigger warnings. This is really common when testing async functionality. It's really difficult to read test results when we have a bunch of warnings. - -### Example - -You can find an example of a test [here](https://github.com/apache/superset/blob/e6c5bf4/superset-frontend/src/common/hooks/useChangeEffect/useChangeEffect.test.ts). - -## React Testing Library (RTL) - -### Keep accessibility in mind when writing your tests - -One of the most important points of RTL is accessibility and this is also a very important point for us. We should try our best to follow the RTL [Priority](https://testing-library.com/docs/queries/about/#priority) when querying for elements in our tests. `getByTestId` is not viewable by the user and should only be used when the element isn't accessible in any other way. - -### Don't use `act` unnecessarily - -`render` and `fireEvent` are already wrapped in `act`, so wrapping them in `act` again is a common mistake. Some solutions to the warnings related to `act` might be found [here](https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning). - -### Be specific when using *ByRole - -By using the `name` option we can point to the items by their accessible name. For example: - -```jsx -screen.getByRole('button', { name: /hello world/i }) -``` - -Using the `name` property also avoids breaking the tests in the future if other components with the same role are added. - -### userEvent vs fireEvent - -Prefer the [user-event](https://github.com/testing-library/user-event) library, which provides a more advanced simulation of browser interactions than the built-in [fireEvent](https://testing-library.com/docs/dom-testing-library/api-events/#fireevent) method. - -### Usage of waitFor - -- [Prefer to use `find`](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#using-waitfor-to-wait-for-elements-that-can-be-queried-with-find) over `waitFor` when you're querying for elements. Even though both achieve the same objective, the `find` version is simpler and you'll get better error messages. -- Prefer to use only [one assertion at a time](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#having-multiple-assertions-in-a-single-waitfor-callback). If you put multiple assertions inside a `waitFor` we could end up waiting for the whole block timeout before seeing a test failure even if the failure occurred at the first assertion. By putting a single assertion in there, we can improve on test execution time. -- Do not perform [side-effects](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#performing-side-effects-in-waitfor) inside `waitFor`. The callback can be called a non-deterministic number of times and frequency (it's called both on an interval as well as when there are DOM mutations). So this means that your side-effect could run multiple times. - -### Example - -You can find an example of a test [here](https://github.com/apache/superset/blob/master/superset-frontend/src/dashboard/components/PublishedStatus/PublishedStatus.test.tsx). - -## Cypress - -### Prefer to use Cypress for e2e testing and RTL for integration testing - -Here it's important to make the distinction between e2e and integration testing. This [article](https://www.onpathtesting.com/blog/end-to-end-vs-integration-testing) gives an excellent definition: - -> End-to-end testing verifies that your software works correctly from the beginning to the end of a particular user flow. It replicates expected user behavior and various usage scenarios to ensure that your software works as whole. End-to-end testing uses a production equivalent environment and data to simulate real-world situations and may also involve the integrations your software has with external applications. - -> A typical software project consists of multiple software units, usually coded by different developers. Integration testing combines those software units logically and tests them as a group. -> Essentially, integration testing verifies whether or not the individual modules or services that make up your application work well together. The purpose of this level of testing is to expose defects in the interaction between these software modules when they are integrated. - -Do not use Cypress when RTL can do it better and faster. Many of the Cypress tests that we have right now, fall into the integration testing category and can be ported to RTL which is much faster and gives more immediate feedback. Cypress should be used mainly for end-to-end testing, replicating the user experience, with positive and negative flows. - -### Isolated and standalone tests - -Tests should never rely on other tests to pass. This might be hard when a single user is used for testing as data will be stored in the database. At every new test, we should reset the database. - -### Cleaning state - -Cleaning the state of the application, such as resetting the DB, or in general, any state that might affect consequent tests should always be done in the `beforeEach` hook and never in the `afterEach` one as the `beforeEach` is guaranteed to run, while the test might never reach the point to run the `afterEach` hook. One example would be if you refresh Cypress in the middle of the test. At this point, you will have built up a partial state in the database, and your clean-up function will never get called. You can read more about it [here](https://docs.cypress.io/guides/references/best-practices#Using-after-or-afterEach-hooks). - -### Unnecessary use of `cy.wait` - -- Unnecessary when using `cy.request()` as it will resolve when a response is received from the server -- Unnecessary when using `cy.visit()` as it resolves only when the page fires the load event -- Unnecessary when using `cy.get()`. When the selector should wait for a request to happen, aliases would come in handy: - -```js -cy.intercept('GET', '/users', [{ name: 'Maggy' }, { name: 'Joan' }]).as('getUsers') -cy.get('#fetch').click() -cy.wait('@getUsers') // <--- wait explicitly for this route to finish -cy.get('table tr').should('have.length', 2) -``` - -### Accessibility and Resilience - -The same accessibility principles in the RTL section apply here. Use accessible selectors when querying for elements. Those principles also help to isolate selectors from eventual CSS and JS changes and improve the resilience of your tests. - -## References - -- [Avoid Nesting when you're Testing](https://kentcdodds.com/blog/avoid-nesting-when-youre-testing) -- [RTL - Queries](https://testing-library.com/docs/queries/about/#priority) -- [Fix the "not wrapped in act(...)" warning](https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning) -- [Common mistakes with React Testing Library](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library) -- [ARIA Roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) -- [Cypress - Best Practices](https://docs.cypress.io/guides/references/best-practices) -- [End-to-end vs integration tests: what's the difference?](https://www.onpathtesting.com/blog/end-to-end-vs-integration-testing) diff --git a/docs/docs/faq.mdx b/docs/docs/faq.mdx deleted file mode 100644 index 2e6ff2fefa8..00000000000 --- a/docs/docs/faq.mdx +++ /dev/null @@ -1,341 +0,0 @@ ---- -sidebar_position: 9 -title: Frequently Asked Questions -description: Common questions about Apache Superset including performance, database support, visualizations, and configuration. -keywords: [superset faq, superset questions, superset help, data visualization faq] ---- - -import FAQSchema from '@site/src/components/FAQSchema'; - - - -# FAQ - -## How big of a dataset can Superset handle? - -Superset can work with even gigantic databases! Superset acts as a thin layer above your underlying -databases or data engines, which do all the processing. Superset simply visualizes the results of -the query. - -The key to achieving acceptable performance in Superset is whether your database can execute queries -and return results at a speed that is acceptable to your users. If you experience slow performance with -Superset, benchmark and tune your data warehouse. - -## What are the computing specifications required to run Superset? - -The specs of your Superset installation depend on how many users you have and what their activity is, not -on the size of your data. Superset admins in the community have reported 8GB RAM, 2vCPUs as adequate to -run a moderately-sized instance. To develop Superset, e.g., compile code or build images, you may -need more power. - -Monitor your resource usage and increase or decrease as needed. Note that Superset usage has a tendency -to occur in spikes, e.g., if everyone in a meeting loads the same dashboard at once. - -Superset's application metadata does not require a very large database to store it, though -the log file grows over time. - -## Can I join / query multiple tables at one time? - -Not in the Explore or Visualization UI. A Superset SQLAlchemy datasource can only be a single table -or a view. - -When working with tables, the solution would be to create a table that contains all the fields -needed for your analysis, most likely through some scheduled batch process. - -A view is a simple logical layer that abstracts an arbitrary SQL query as a virtual table. This can -allow you to join and union multiple tables and to apply some transformation using arbitrary SQL -expressions. The limitation there is your database performance, as Superset effectively will run a -query on top of your query (view). A good practice may be to limit yourself to joining your main -large table to one or many small tables only, and avoid using _GROUP BY_ where possible as Superset -will do its own _GROUP BY_ and doing the work twice might slow down performance. - -Whether you use a table or a view, performance depends on how fast your database can deliver -the result to users interacting with Superset. - -However, if you are using SQL Lab, there is no such limitation. You can write SQL queries to join -multiple tables as long as your database account has access to the tables. - -## How do I create my own visualization? - -We recommend reading the instructions in -[Creating Visualization Plugins](/developer-docs/contributing/howtos#creating-visualization-plugins). - -## Can I upload and visualize CSV data? - -Absolutely! Read the instructions [here](/user-docs/using-superset/exploring-data) to learn -how to enable and use CSV upload. - -## Why are my queries timing out? - -There are many possible causes for why a long-running query might time out. - -For running long query from Sql Lab, by default Superset allows it run as long as 6 hours before it -being killed by celery. If you want to increase the time for running query, you can specify the -timeout in configuration. For example: - -```python -SQLLAB_ASYNC_TIME_LIMIT_SEC = 60 * 60 * 6 -``` - -If you are seeing timeouts (504 Gateway Time-out) when loading dashboard or explore slice, you are -probably behind gateway or proxy server (such as Nginx). If it did not receive a timely response -from Superset server (which is processing long queries), these web servers will send 504 status code -to clients directly. Superset has a client-side timeout limit to address this issue. If query didn’t -come back within client-side timeout (60 seconds by default), Superset will display warning message -to avoid gateway timeout message. If you have a longer gateway timeout limit, you can change the -timeout settings in **superset_config.py**: - -```python -SUPERSET_WEBSERVER_TIMEOUT = 60 -``` - -## Why is the map not visible in the geospatial visualization? - -You need to register a free account at [Mapbox.com](https://www.mapbox.com), obtain an API key, and add it -to **.env** at the key MAPBOX_API_KEY: - -```python -MAPBOX_API_KEY = "longstringofalphanumer1c" -``` - -## How to limit the timed refresh on a dashboard? - -By default, the dashboard timed refresh feature allows you to automatically re-query every slice on -a dashboard according to a set schedule. Sometimes, however, you won’t want all of the slices to be -refreshed - especially if some data is slow moving, or run heavy queries. To exclude specific slices -from the timed refresh process, add the `timed_refresh_immune_slices` key to the dashboard JSON -Metadata field: - -```json -{ - "filter_immune_slices": [], - "expanded_slices": {}, - "filter_immune_slice_fields": {}, - "timed_refresh_immune_slices": [324] -} -``` - -In the example above, if a timed refresh is set for the dashboard, then every slice except 324 will -be automatically re-queried on schedule. - -Slice refresh will also be staggered over the specified period. You can turn off this staggering by -setting the `stagger_refresh` to false and modify the stagger period by setting `stagger_time` to a -value in milliseconds in the JSON Metadata field: - -```json -{ - "stagger_refresh": false, - "stagger_time": 2500 -} -``` - -Here, the entire dashboard will refresh at once if periodic refresh is on. The stagger time of 2.5 -seconds is ignored. - -**Why does ‘flask fab’ or superset freeze/hang/not responding when started (my home directory is -NFS mounted)?** - -By default, Superset creates and uses an SQLite database at `~/.superset/superset.db`. SQLite is -known to [not work well if used on NFS](https://www.sqlite.org/lockingv3.html) due to broken file -locking implementation on NFS. - -You can override this path using the **SUPERSET_HOME** environment variable. - -Another workaround is to change where superset stores the sqlite database by adding the following in -`superset_config.py`: - -```python -SQLALCHEMY_DATABASE_URI = 'sqlite:////new/location/superset.db?check_same_thread=false' -``` - -You can read more about customizing Superset using the configuration file -[here](/admin-docs/configuration/configuring-superset). - -## What if the table schema changed? - -Table schemas evolve, and Superset needs to reflect that. It’s pretty common in the life cycle of a -dashboard to want to add a new dimension or metric. To get Superset to discover your new columns, -all you have to do is to go to **Data -> Datasets**, click the edit icon next to the dataset -whose schema has changed, and hit **Sync columns from source** from the **Columns** tab. -Behind the scene, the new columns will get merged. Following this, you may want to re-edit the -table afterwards to configure the Columns tab, check the appropriate boxes and save again. - -## What database engine can I use as a backend for Superset? - -To clarify, the database backend is an OLTP database used by Superset to store its internal -information like your list of users and dashboard definitions. While Superset supports a -[variety of databases as data _sources_](/user-docs/databases/#installing-database-drivers), -only a few database engines are supported for use as the OLTP backend / metadata store. - -Superset is tested using MySQL, PostgreSQL, and SQLite backends. It’s recommended you install -Superset on one of these database servers for production. Installation on other OLTP databases -may work but isn’t tested. It has been reported that [Microsoft SQL Server does _not_ -work as a Superset backend](https://github.com/apache/superset/issues/18961). Column-store, -non-OLTP databases are not designed for this type of workload. - -## How can I configure OAuth authentication and authorization? - -You can take a look at this Flask-AppBuilder -[configuration example](https://github.com/dpgaspar/Flask-AppBuilder/blob/master/examples/oauth/config.py). - -## Is there a way to force the dashboard to use specific colors? - -It is possible on a per-dashboard basis by providing a mapping of labels to colors in the JSON -Metadata attribute using the `label_colors` key. You can use either the full hex color, a named color, -like `red`, `coral` or `lightblue`, or the index in the current color palette (0 for first color, 1 for -second etc). Example: - -```json -{ - "label_colors": { - "foo": "#FF69B4", - "bar": "lightblue", - "baz": 0 - } -} -``` - -## Does Superset work with [insert database engine here]? - -The [Connecting to Databases section](/user-docs/databases/) provides the best -overview for supported databases. Database engines not listed on that page may work too. We rely on -the community to contribute to this knowledge base. - -For a database engine to be supported in Superset through the SQLAlchemy connector, it requires -having a Python compliant [SQLAlchemy dialect](https://docs.sqlalchemy.org/en/13/dialects/) as well -as a [DBAPI driver](https://www.python.org/dev/peps/pep-0249/) defined. Database that have limited -SQL support may work as well. For instance it’s possible to connect to Druid through the SQLAlchemy -connector even though Druid does not support joins and subqueries. Another key element for a -database to be supported is through the Superset Database Engine Specification interface. This -interface allows for defining database-specific configurations and logic that go beyond the -SQLAlchemy and DBAPI scope. This includes features like: - -- date-related SQL function that allow Superset to fetch different time granularities when running - time-series queries -- whether the engine supports subqueries. If false, Superset may run 2-phase queries to compensate - for the limitation -- methods around processing logs and inferring the percentage of completion of a query -- technicalities as to how to handle cursors and connections if the driver is not standard DBAPI - -Beyond the SQLAlchemy connector, it’s also possible, though much more involved, to extend Superset -and write your own connector. The only example of this at the moment is the Druid connector, which -is getting superseded by Druid’s growing SQL support and the recent availability of a DBAPI and -SQLAlchemy driver. If the database you are considering integrating has any kind of SQL support, -it’s probably preferable to go the SQLAlchemy route. Note that for a native connector to be possible -the database needs to have support for running OLAP-type queries and should be able to do things that -are typical in basic SQL: - -- aggregate data -- apply filters -- apply HAVING-type filters -- be schema-aware, expose columns and types - -## Does Superset offer a public API? - -Yes, a public REST API, and the surface of that API formal is expanding steadily. You can read more about this API and -interact with it using Swagger [here](/developer-docs/api). - -Some of the -original vision for the collection of endpoints under **/api/v1** was originally specified in -[SIP-17](https://github.com/apache/superset/issues/7259) and constant progress has been -made to cover more and more use cases. - -The API available is documented using [Swagger](https://swagger.io/) and the documentation can be -made available under **/swagger/v1** by enabling the following flag in `superset_config.py`: - -```python -FAB_API_SWAGGER_UI = True -``` - -There are other undocumented [private] ways to interact with Superset programmatically that offer no -guarantees and are not recommended but may fit your use case temporarily: - -- using the ORM (SQLAlchemy) directly -- using the internal FAB ModelView API (to be deprecated in Superset) -- altering the source code in your fork - -## How can I see usage statistics (e.g., monthly active users)? - -This functionality is not included with Superset, but you can extract and analyze Superset's application -metadata to see what actions have occurred. By default, user activities are logged in the `logs` table -in Superset's metadata database. One company has published a write-up of [how they analyzed Superset -usage, including example queries](https://engineering.hometogo.com/monitor-superset-usage-via-superset-c7f9fba79525). - -## What Does Hours Offset in the Edit Dataset view do? - -In the Edit Dataset view, you can specify a time offset. This field lets you configure the -number of hours to be added or subtracted from the time column. -This can be used, for example, to convert UTC time to local time. - -## Does Superset collect any telemetry data? - -Superset uses [Scarf](https://about.scarf.sh/) by default to collect basic telemetry data upon installing and/or running Superset. This data helps the maintainers of Superset better understand which versions of Superset are being used, in order to prioritize patch/minor releases and security fixes. -We use the [Scarf Gateway](https://docs.scarf.sh/gateway/) to sit in front of container registries, the [scarf-js](https://about.scarf.sh/package-sdks) package to track `npm` installations, and a Scarf pixel to gather anonymous analytics on Superset page views. -Scarf purges PII and provides aggregated statistics. Superset users can easily opt out of analytics in various ways documented [here](https://docs.scarf.sh/gateway/#do-not-track) and [here](https://docs.scarf.sh/package-analytics/#as-a-user-of-a-package-using-scarf-js-how-can-i-opt-out-of-analytics). -Superset maintainers can also opt out of telemetry data collection by setting the `SCARF_ANALYTICS` environment variable to `false` in the Superset container (or anywhere Superset/webpack are run). -Additional opt-out instructions for Docker users are available on the [Docker Installation](/admin-docs/installation/docker-compose) page. - -## Does Superset have an archive panel or trash bin from which a user can recover deleted assets? - -No. Currently, there is no way to recover a deleted Superset dashboard/chart/dataset/database from the UI. However, there is an [ongoing discussion](https://github.com/apache/superset/discussions/18386) about implementing such a feature. - -Hence, it is recommended to take periodic backups of the metadata database. For recovery, you can launch a recovery instance of a Superset server with the backed-up copy of the DB attached and use the Export Dashboard button in the Superset UI (or the `superset export-dashboards` CLI command). Then, take the .zip file and import it into the current Superset instance. - -Alternatively, you can programmatically take regular exports of the assets as a backup. - -## I ran a security scan of the Superset container image and it showed dozens of "high" and "critical" vulnerabilities! Can you release a version of Superset without these? - -You are talking about dependency CVEs: identified vulnerabilities in software that Superset uses. Most of these CVEs are in the Linux kernel or Python, both of which have many other people working on their security. - -We address these dependency CVEs as best we can by regularly updating our dependencies to newer versions. We use bots to assist with that and cheerfully welcome pull requests from humans that fix dependency CVEs. - -The Superset [security team](https://superset.apache.org/docs/security/#reporting-security-vulnerabilities) focuses primarily on vulnerabilities _in Superset itself_. See our [CVEs page](https://superset.apache.org/docs/security/cves) for a list of past Superset CVEs. diff --git a/docs/docs/index.mdx b/docs/docs/index.mdx deleted file mode 100644 index 681acd6d2d5..00000000000 --- a/docs/docs/index.mdx +++ /dev/null @@ -1,269 +0,0 @@ ---- -hide_title: true -sidebar_position: 1 ---- - -import DatabaseLogoWall from '@site/src/components/databases/DatabaseLogoWall'; - - - -# Superset - -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/license/apache-2-0) -[![Latest Release on Github](https://img.shields.io/github/v/release/apache/superset?sort=semver)](https://github.com/apache/superset/releases/latest) -[![Build Status](https://github.com/apache/superset/actions/workflows/superset-python-unittest.yml/badge.svg)](https://github.com/apache/superset/actions) -[![PyPI version](https://badge.fury.io/py/apache_superset.svg)](https://badge.fury.io/py/apache_superset) -[![PyPI](https://img.shields.io/pypi/pyversions/apache_superset.svg?maxAge=2592000)](https://pypi.python.org/pypi/apache_superset) -[![GitHub Stars](https://img.shields.io/github/stars/apache/superset?style=social)](https://github.com/apache/superset/stargazers) -[![Contributors](https://img.shields.io/github/contributors/apache/superset)](https://github.com/apache/superset/graphs/contributors) -[![Last Commit](https://img.shields.io/github/last-commit/apache/superset)](https://github.com/apache/superset/commits/master) -[![Open Issues](https://img.shields.io/github/issues/apache/superset)](https://github.com/apache/superset/issues) -[![Open PRs](https://img.shields.io/github/issues-pr/apache/superset)](https://github.com/apache/superset/pulls) -[![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](http://bit.ly/join-superset-slack) -[![Documentation](https://img.shields.io/badge/docs-apache.org-blue.svg)](https://superset.apache.org) - - - - Superset logo (light) - - -A modern, enterprise-ready business intelligence web application. - -[**Why Superset?**](#why-superset) | -[**Supported Databases**](#supported-databases) | -[**Installation and Configuration**](#installation-and-configuration) | -[**Release Notes**](https://github.com/apache/superset/blob/master/RELEASING/README.md#release-notes-for-recent-releases) | -[**Get Involved**](#get-involved) | -[**Contributor Guide**](#contributor-guide) | -[**Resources**](#resources) | -[**Organizations Using Superset**](https://superset.apache.org/inTheWild) - -## Why Superset? - -Superset is a modern data exploration and data visualization platform. Superset can replace or augment proprietary business intelligence tools for many teams. Superset integrates well with a variety of data sources. - -Superset provides: - -- A **no-code interface** for building charts quickly -- A powerful, web-based **SQL Editor** for advanced querying -- A **lightweight semantic layer** for quickly defining custom dimensions and metrics -- Out of the box support for **nearly any SQL** database or data engine -- A wide array of **beautiful visualizations** to showcase your data, ranging from simple bar charts to geospatial visualizations -- Lightweight, configurable **caching layer** to help ease database load -- Highly extensible **security roles and authentication** options -- An **API** for programmatic customization -- A **cloud-native architecture** designed from the ground up for scale - -## Screenshots & Gifs - -**Video Overview** - - - -[superset-video-1080p.webm](https://github.com/user-attachments/assets/b37388f7-a971-409c-96a7-90c4e31322e6) - -
- -**Large Gallery of Visualizations** - -
- -**Craft Beautiful, Dynamic Dashboards** - -
- -**No-Code Chart Builder** - -
- -**Powerful SQL Editor** - -
- -## Supported Databases - -Superset can query data from any SQL-speaking datastore or data engine (Presto, Trino, Athena, [and more](/user-docs/databases/)) that has a Python DB-API driver and a SQLAlchemy dialect. - -Here are some of the major database solutions that are supported: - - - - - - - -**A more comprehensive list of supported databases** along with the configuration instructions can be found [here](/user-docs/databases/). - -Want to add support for your datastore or data engine? Read more [here](/user-docs/faq#does-superset-work-with-insert-database-engine-here) about the technical requirements. - -## Installation and Configuration - -Try out Superset's [quickstart](/user-docs/quickstart) guide or learn about [the options for production deployments](/admin-docs/installation/installation-methods). - -## Get Involved - -- Ask and answer questions on [StackOverflow](https://stackoverflow.com/questions/tagged/apache-superset) using the **apache-superset** tag -- [Join our community's Slack](http://bit.ly/join-superset-slack) - and please read our [Slack Community Guidelines](https://github.com/apache/superset/blob/master/CODE_OF_CONDUCT.md#slack-community-guidelines) -- [Join our dev@superset.apache.org Mailing list](https://lists.apache.org/list.html?dev@superset.apache.org). To join, simply send an email to [dev-subscribe@superset.apache.org](mailto:dev-subscribe@superset.apache.org) -- If you want to help troubleshoot GitHub Issues involving the numerous database drivers that Superset supports, please consider adding your name and the databases you have access to on the [Superset Database Familiarity Rolodex](https://docs.google.com/spreadsheets/d/1U1qxiLvOX0kBTUGME1AHHi6Ywel6ECF8xk_Qy-V9R8c/edit#gid=0) -- Join Superset's Town Hall and [Operational Model](https://preset.io/blog/the-superset-operational-model-wants-you/) recurring meetings. Meeting info is available on the [Superset Community Calendar](https://superset.apache.org/community) - -## Contributor Guide - -Interested in contributing? Check out our -[Developer Docs](https://superset.apache.org/developer-docs/) -to find resources around contributing along with a detailed guide on -how to set up a development environment. - -## Resources - -- [Superset "In the Wild"](https://superset.apache.org/inTheWild) - see who's using Superset, and [add your organization](https://github.com/apache/superset/edit/master/RESOURCES/INTHEWILD.yaml) to the list! -- [Feature Flags](/admin-docs/configuration/feature-flags) - the status of Superset's Feature Flags. -- [Standard Roles](https://github.com/apache/superset/blob/master/RESOURCES/STANDARD_ROLES.md) - How RBAC permissions map to roles. -- [Superset Wiki](https://github.com/apache/superset/wiki) - Tons of additional community resources: best practices, community content and other information. -- [Superset SIPs](https://github.com/orgs/apache/projects/170) - The status of Superset's SIPs (Superset Improvement Proposals) for both consensus and implementation status. - -Understanding the Superset Points of View - -- [The Case for Dataset-Centric Visualization](https://preset.io/blog/dataset-centric-visualization/) -- [Understanding the Superset Semantic Layer](https://preset.io/blog/understanding-superset-semantic-layer/) - -- Getting Started with Superset - - [Superset in 2 Minutes using Docker Compose](/admin-docs/installation/docker-compose#installing-superset-locally-using-docker-compose) - - [Installing Database Drivers](/admin-docs/configuration/configuring-superset#installing-database-drivers) - - [Building New Database Connectors](https://preset.io/blog/building-database-connector/) - - [Create Your First Dashboard](/user-docs/using-superset/creating-your-first-dashboard) - - [Comprehensive Tutorial for Contributing Code to Apache Superset - ](https://preset.io/blog/tutorial-contributing-code-to-apache-superset/) -- [Resources to master Superset by Preset](https://preset.io/resources/) - -- Deploying Superset - - - [Official Docker image](https://hub.docker.com/r/apache/superset) - - [Helm Chart](https://github.com/apache/superset/tree/master/helm/superset) - -- Recordings of Past [Superset Community Events](https://preset.io/events) - - - [Mixed Time Series Charts](https://preset.io/events/mixed-time-series-visualization-in-superset-workshop/) - - [How the Bing Team Customized Superset for the Internal Self-Serve Data & Analytics Platform](https://preset.io/events/how-the-bing-team-heavily-customized-superset-for-their-internal-data/) - - [Live Demo: Visualizing MongoDB and Pinot Data using Trino](https://preset.io/events/2021-04-13-visualizing-mongodb-and-pinot-data-using-trino/) - - [Introduction to the Superset API](https://preset.io/events/introduction-to-the-superset-api/) - - [Building a Database Connector for Superset](https://preset.io/events/2021-02-16-building-a-database-connector-for-superset/) - -- Visualizations - - - [Creating Viz Plugins](https://superset.apache.org/developer-docs/contributing/howtos#creating-visualization-plugins) - - [Managing and Deploying Custom Viz Plugins](https://medium.com/nmc-techblog/apache-superset-manage-custom-viz-plugins-in-production-9fde1a708e55) - - [Why Apache Superset is Betting on Apache ECharts](https://preset.io/blog/2021-4-1-why-echarts/) - -- [Superset API](/developer-docs/api) - -## Repo Activity - - - - - Performance Stats of apache/superset - Last 28 days - - - - - - - diff --git a/docs/docs/quickstart.mdx b/docs/docs/quickstart.mdx deleted file mode 100644 index 2eee4d09eca..00000000000 --- a/docs/docs/quickstart.mdx +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: Quickstart -hide_title: false -sidebar_position: 2 ---- - -**Ready to try Apache Superset?** This quickstart guide will help you -get up and running on your local machine in **3 simple steps**. Note that -it assumes that you have [Docker](https://www.docker.com), -[Docker Compose](https://docs.docker.com/compose/), and -[Git](https://git-scm.com/) installed. - -:::caution -Although we recommend using `Docker Compose` for a quick start in a sandbox-type -environment and for other development-type use cases, **we -do not recommend this setup for production**. For this purpose please -refer to our -[Installing on Kubernetes](/admin-docs/installation/kubernetes) -page. -::: - -### 1. Get Superset - -```bash -git clone https://github.com/apache/superset -``` - -### 2. Start the latest official release of Superset - -```bash -# Enter the repository you just cloned -$ cd superset - -# Set the repo to the state associated with the latest official version -$ git checkout tags/6.0.0 - -# Fire up Superset using Docker Compose -$ docker compose -f docker-compose-image-tag.yml up -``` - -This may take a moment as Docker Compose will fetch the underlying -container images and will load up some examples. Once all containers -are downloaded and the output settles, you're ready to log in. - -⚠️ If you get an error message like `validating superset\docker-compose-image-tag.yml: services.superset-worker-beat.env_file.0 must be a string`, you need to update your version of `docker-compose`. -Note that `docker-compose` is on the path to deprecation and you should now use `docker compose` instead. - -### 3. Log into Superset - -Now head over to [http://localhost:8088](http://localhost:8088) and log in with the default created account: - -```bash -username: admin -password: admin -``` - -#### 🎉 Congratulations! Superset is now up and running on your machine! 🎉 - -### Wrapping Up - -Once you're done with Superset, you can stop and delete just like any other container environment: - -```bash -docker compose down -``` - -:::tip -You can use the same environment more than once, as Superset will persist data locally. However, make sure to properly stop all -processes by running Docker Compose `stop` command. By doing so, you can avoid data corruption and/or loss of data. -::: - -## What's next? - -From this point on, you can head on to: - -- [Create your first Dashboard](/user-docs/using-superset/creating-your-first-dashboard) -- [Connect to a Database](/user-docs/databases/) -- [Using Docker Compose](/admin-docs/installation/docker-compose) -- [Configure Superset](/admin-docs/configuration/configuring-superset) -- [Installing on Kubernetes](/admin-docs/installation/kubernetes) - -Or just explore our [Documentation](https://superset.apache.org/docs/intro)! - -:::resources -- [Video: Superset in 2 Minutes](https://www.youtube.com/watch?v=AqousXQ7YHw) -- [Video: Superset 101](https://www.youtube.com/watch?v=mAIH3hUoxEE) -- [Tutorial: Creating Your First Dashboard](/user-docs/using-superset/creating-your-first-dashboard) -::: diff --git a/docs/docs/security/granular-export-controls.mdx b/docs/docs/security/granular-export-controls.mdx deleted file mode 100644 index 806e21e02cf..00000000000 --- a/docs/docs/security/granular-export-controls.mdx +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: Granular Export Controls -sidebar_position: 4 ---- - -# Granular Export Controls - -Superset provides granular, permission-based controls for data export, image export, and clipboard operations. These replace the legacy `can_csv` permission with three fine-grained permissions that can be assigned independently to roles. - -## Feature Flag - -Granular export controls are gated behind the `GRANULAR_EXPORT_CONTROLS` feature flag. When the flag is disabled, the legacy `can_csv` permission behavior is preserved. - -```python -FEATURE_FLAGS = { - "GRANULAR_EXPORT_CONTROLS": True, -} -``` - -## Permissions - -| Permission | Resource | Controls | -| -------------------- | ---------- | ---------------------------------------------------------------------- | -| `can_export_data` | `Superset` | CSV, Excel, and JSON data exports from charts, dashboards, and SQL Lab | -| `can_export_image` | `Superset` | Screenshot (JPEG/PNG) and PDF exports from charts and dashboards | -| `can_copy_clipboard` | `Superset` | Copy-to-clipboard operations in SQL Lab and the Explore data pane | - -## Default Role Assignments - -The migration grants all three new permissions (`can_export_data`, `can_export_image`, `can_copy_clipboard`) to every role that currently has `can_csv`. This preserves existing behavior — no role loses access during the upgrade. - -After the migration, admins can selectively revoke individual export permissions from any role to restrict access. For example, to prevent Gamma users from exporting data or images while still allowing clipboard operations, revoke `can_export_data` and `can_export_image` from the Gamma role. - -## Configuration Steps - -1. **Enable the feature flag** in `superset_config.py`: - - ```python - FEATURE_FLAGS = { - "GRANULAR_EXPORT_CONTROLS": True, - } - ``` - -2. **Run the database migration** to register the new permissions: - - ```bash - superset db upgrade - ``` - -3. **Initialize permissions** so roles are populated: - - ```bash - superset init - ``` - -4. **Verify role assignments** in **Settings > List Roles**. Confirm that each role has the expected permissions from the table above. - -5. **Customize as needed**: Grant or revoke individual export permissions on any role through the role editor. - -## User Experience - -When a user lacks a required export permission: - -- **Menu items** (CSV, Excel, JSON, screenshot) appear **disabled** with an info tooltip icon explaining the restriction -- **Buttons** (SQL Lab download, clipboard copy) appear **disabled** with a tooltip on hover -- **API endpoints** return **403 Forbidden** when the corresponding permission is missing - -## API Enforcement - -The following API endpoints enforce granular export permissions when the feature flag is enabled: - -| Endpoint | Required Permission | -| --------------------------------------------------------- | ------------------- | -| `GET /api/v1/chart/{id}/data/` (CSV/Excel format) | `can_export_data` | -| `GET /api/v1/chart/{id}/cache_screenshot/` | `can_export_image` | -| `POST /api/v1/dashboard/{id}/cache_dashboard_screenshot/` | `can_export_image` | -| `GET /api/v1/sqllab/export/{client_id}/` | `can_export_data` | -| `POST /api/v1/sqllab/export_streaming/` | `can_export_data` | diff --git a/docs/docs/using-superset/creating-your-first-dashboard.mdx b/docs/docs/using-superset/creating-your-first-dashboard.mdx deleted file mode 100644 index f83982385c4..00000000000 --- a/docs/docs/using-superset/creating-your-first-dashboard.mdx +++ /dev/null @@ -1,368 +0,0 @@ ---- -title: Creating Your First Dashboard -hide_title: true -sidebar_position: 1 -version: 1 ---- - -import useBaseUrl from "@docusaurus/useBaseUrl"; - -## Creating Your First Dashboard - -This section is focused on documentation for end-users who will be using Superset -for the data analysis and exploration workflow - (data analysts, business analysts, data -scientists, etc). - -:::tip -In addition to this site, [Preset.io](http://preset.io/) maintains an updated set of end-user -documentation at [docs.preset.io](https://docs.preset.io/). -::: - -This tutorial targets someone who wants to create charts and dashboards in Superset. We’ll show you -how to connect Superset to a new database and configure a table in that database for analysis. -You’ll also explore the data you’ve exposed and add a visualization to a dashboard so that you get a -feel for the end-to-end user experience. - -### Connecting to a new database - -Superset itself doesn't have a storage layer to store your data but instead pairs with -your existing SQL-speaking database or data store. - -First things first, we need to add the connection credentials to your database to be able -to query and visualize data from it. If you're using Superset locally via -[Docker compose](/admin-docs/installation/docker-compose), you can -skip this step because a Postgres database, named **examples**, is included and -pre-configured in Superset for you. - -Under the **+** menu in the top right, select Data, and then the _Connect Database_ option: - -{" "}

- -Then select your database type in the resulting modal: - -{" "}

- -Once you've selected a database, you can configure a number of advanced options in this window, -or for the purposes of this walkthrough, you can click the link below all these fields: - -{" "}

- -Please note, if you are trying to connect to another locally running database (whether on host or another container), and you get the message `The port is closed.`, then you need to adjust the HOST to `host.docker.internal` - -Once you've clicked that link you only need to specify two things (the database name and SQLAlchemy URI): - -{" "}

- -As noted in the text below the form, you should refer to the SQLAlchemy documentation on -[creating new connection URIs](https://docs.sqlalchemy.org/en/12/core/engines.html#database-urls) -for your target database. - -Click the **Test Connection** button to confirm things work end to end. If the connection looks good, save the configuration -by clicking the **Connect** button in the bottom right corner of the modal window: - -Congratulations, you've just added a new data source in Superset! - -### Sharing a Database Connection - -When adding a new database, you can share the connection with other Superset users. Shared connections appear in other users' database lists, making it easier to collaborate on the same data without requiring each user to configure the same connection separately. - -To share a connection, enable the **Share connection with other users** option in the **Advanced** tab of the database connection modal before saving. You can change sharing settings later by editing the database connection. - -### Registering a new table - -Now that you’ve configured a data source, you can select specific tables (called **Datasets** in Superset) -that you want exposed in Superset for querying. - -Navigate to **Data ‣ Datasets** and select the **+ Dataset** button in the top right corner. - - - -A modal window should pop up in front of you. Select your **Database**, -**Schema**, and **Table** using the drop downs that appear. In the following example, -we register the **cleaned_sales_data** table from the **examples** database. - - - -To finish, click the **Add** button in the bottom right corner. You should now see your dataset in the list of datasets. - -### Organizing Datasets into Folders - -The Datasets list view supports **folders** for organizing datasets into groups. To create and manage folders: - -1. In the **Datasets** list, click the **Folders** panel on the left sidebar. -2. Click **+ New Folder** to create a top-level folder, or drag an existing folder to nest it. -3. Drag dataset rows onto a folder to move them in, or right-click a dataset and select **Move to folder**. - -Folders are per-user organizational aids — they do not affect dataset access permissions or how other users see the datasets. - -### Uploading Files via the OS File Manager (PWA) - -When Superset is installed as a **Progressive Web App (PWA)** from your browser, your operating system will offer Superset as an option when opening CSV, Excel (`.xls`/`.xlsx`), and Parquet files. Double-clicking or right-clicking a supported file and selecting "Open with Superset" navigates directly to the upload workflow for that file. - -To install Superset as a PWA, look for the install icon in your browser's address bar (Chrome, Edge) when visiting your Superset instance over HTTPS. PWA installation requires HTTPS and a valid manifest — your admin needs to confirm the app manifest is served correctly. - -### Customizing column properties - -Now that you've registered your dataset, you can configure column properties - for how the column should be treated in the Explore workflow: - -- Is the column temporal? (should it be used for slicing & dicing in time series charts?) -- Should the column be filterable? -- Is the column dimensional? -- If it's a datetime column, how should Superset parse -the datetime format? (using the [ISO-8601 string pattern](https://en.wikipedia.org/wiki/ISO_8601)) - - - -### Superset semantic layer - -Superset has a thin semantic layer that adds many quality of life improvements for analysts. -The Superset semantic layer can store 2 types of computed data: - -1. Virtual metrics: you can write SQL queries that aggregate values -from multiple column (e.g. `SUM(recovered) / SUM(confirmed)`) and make them -available as columns for (e.g. `recovery_rate`) visualization in Explore. -Aggregate functions are allowed and encouraged for metrics. - - - -You can also certify metrics if you'd like for your team in this view. - -1. Virtual calculated columns: you can write SQL queries that -customize the appearance and behavior -of a specific column (e.g. `CAST(recovery_rate as float)`). -Aggregate functions aren't allowed in calculated columns. - - - -:::resources -- [Using Metrics and Calculated Columns](https://docs.preset.io/docs/using-metrics-and-calculated-columns) - In-depth guide to the semantic layer -- [Blog: Understanding the Superset Semantic Layer](https://preset.io/blog/understanding-superset-semantic-layer/) -- [Blog: Unlocking the Power of Virtual Datasets](https://preset.io/blog/unlocking-the-power-of-virtual-datasets-in-apache-superset/) -::: - -### Creating charts in Explore view - -Superset has 2 main interfaces for exploring data: - -- **Explore**: no-code viz builder. Select your dataset, select the chart, -customize the appearance, and publish. -- **SQL Lab**: SQL IDE for cleaning, joining, and preparing data for Explore workflow - -We'll focus on the Explore view for creating charts right now. -To start the Explore workflow from the **Datasets** tab, start by clicking the name -of the dataset that will be powering your chart. - -

- -You're now presented with a powerful workflow for exploring data and iterating on charts. - -- The **Dataset** view on the left-hand side has a list of columns and metrics, -scoped to the current dataset you selected. -- The **Data** preview below the chart area also gives you helpful data context. -- Using the **Data** tab and **Customize** tabs, you can change the visualization type, -select the temporal column, select the metric to group by, and customize -the aesthetics of the chart. - -As you customize your chart using drop-down menus, make sure to click the **Run** button -to get visual feedback. - - - -In the following screenshot, we craft a grouped Time-series Bar Chart to visualize -our quarterly sales data by product line just by clicking options in drop-down menus. - - - -### Creating a slice and dashboard - -To save your chart, first click the **Save** button. You can either: - -- Save your chart and add it to an existing dashboard -- Save your chart and add it to a new dashboard - -In the following screenshot, we save the chart to a new "Superset Duper Sales Dashboard": - - - -To publish, click **Save and goto Dashboard**. - -Behind the scenes, Superset will create a slice and store all the information needed -to create your chart in its thin data layer - (the query, chart type, options selected, name, etc). - - - - To resize the chart, start by clicking the Edit Dashboard button in the top right corner. - - - -Then, click and drag the bottom right corner of the chart until the chart layout snaps -into a position you like onto the underlying grid. - - - - Click **Save** to persist the changes. - -Congrats! You’ve successfully linked, analyzed, and visualized data in Superset. There are a wealth -of other table configuration and visualization options, so please start exploring and creating -slices and dashboards of your own. - -### Manage access to Dashboards - -Access to dashboards is managed via owners and permissions. Non-owner access can be controlled -through dataset permissions or dashboard-level roles (using the `DASHBOARD_RBAC` feature flag). - -For detailed information on configuring dashboard access, see the -[Dashboard Access Control](/admin-docs/security/#dashboard-access-control) section in the -Security documentation. - - - -### Publishing a Dashboard - -If you would like to make your dashboard available to other users, click on the `Draft` button next to the -title of your dashboard. - - - -:::warning -Draft dashboards are only visible to the dashboard owners and admins. Published dashboards are visible to all users with access to the underlying datasets or if RBAC is enabled, to the roles that have been granted access to the dashboard. -::: - -### Mark a Dashboard as Favorite - -You can mark a dashboard as a favorite by clicking on the star icon next to the title of your dashboard. This makes it easier to find it in the list of dashboards or on the home page. - -### Customizing dashboard - -The following URL parameters can be used to modify how the dashboard is rendered: - -- `standalone`: - - `0` (default): dashboard is displayed normally - - `1`: Top Navigation is hidden - - `2`: Top Navigation + title is hidden - - `3`: Top Navigation + title + top level tabs are hidden -- `show_filters`: - - `0`: render dashboard without Filter Bar - - `1` (default): render dashboard with Filter Bar if native filters are enabled -- `expand_filters`: - - (default): render dashboard with Filter Bar expanded if there are native filters - - `0`: render dashboard with Filter Bar collapsed - - `1`: render dashboard with Filter Bar expanded - -For example, when running the local development build, the following will disable the -Top Nav and remove the Filter Bar: -`http://localhost:8088/superset/dashboard/my-dashboard/?standalone=1&show_filters=0` - -### Table Chart Features - -The **Table** chart type has several advanced capabilities worth knowing: - -#### Conditional Formatting - -Conditional formatting rules highlight cells based on their values. Rules can be applied to: -- **Numeric columns** — color cells above/below a threshold, or use a gradient across a range -- **String columns** — highlight cells matching specific text values or patterns -- **Boolean columns** — color cells that are `true` or `false`, or `null`/`not null` - -Each rule has a **"Use gradient"** toggle: enabled applies a varying opacity (lighter = further from threshold), disabled applies a solid fill at full opacity regardless of value. - -#### HTML Rendering in Table Cells - -Table chart cells can render raw HTML, enabling rich formatting such as hyperlinks, colored badges, and icons directly in the data. Enable this per-column in the chart's **Column Configuration** panel by toggling **Render HTML**. - -:::caution -Only enable HTML rendering for columns sourced from data you control. Rendering untrusted HTML can expose users to cross-site scripting (XSS) risks. -::: - -#### Column Header Tooltips - -Column headers display a tooltip with the column's **Description** from the dataset editor when the user hovers over them. Keep dataset column descriptions up to date to improve chart discoverability. - -#### Display Controls - -In dashboard view mode (without entering Edit mode), charts with configurable display options expose a **Display Controls** panel accessible from the chart's context menu. This surfaces controls such as Time Grain, Time Column, and layer visibility for applicable chart types — making it easy to adjust a chart's view without going to Explore. -### AG Grid Interactive Table - -The **AG Grid Interactive Table** chart type is Superset's fully-featured data grid, suitable for large paginated datasets where the standard Table chart is not enough. - -#### Server-Side Column Filters - -AG Grid supports server-side column filters that query the full dataset — not just the loaded page. Filters are applied before data is sent to the browser, so results are correct even across millions of rows. - -**Available filter types:** - -| Column type | Filter options | -|---|---| -| Text | Contains, equals, starts with, ends with | -| Number | Equals, not equal, less than, greater than, between | -| Date | Before, after, between, blank | -| Set | Select from a list of distinct values | - -**AND / OR logic:** Each column supports combining multiple conditions with AND or OR. Filters from different columns are always combined with AND. - -**Interaction with pagination:** Server-side filters run as WHERE clauses in the underlying SQL query, so pagination always operates over the already-filtered result set. - -#### Time Shift (Time Comparison) - -AG Grid Interactive Table supports **Time Shift** (time comparison), matching the behavior of the standard Table chart. In the **Advanced Analytics** → **Time Comparison** section of the chart configuration, enter a shift expression (e.g., `1 year ago`, `minus 7 days`) to add comparison columns showing values from the offset period. Dashboard-level time range overrides apply to both the base and comparison periods. - -### Dynamic Currency Formatting - -Chart metric values can display currencies dynamically rather than using a fixed currency code. To enable: - -1. Open the dataset editor for your dataset (**Datasets → Edit**). -2. In the **Advanced** tab, set **Currency Code Column** to the name of a column in your dataset that contains ISO 4217 currency codes (e.g., `USD`, `EUR`, `GBP`). -3. In the Explore chart configuration, open the metric's **Number format** section and select **Auto-detect** for currency. - -When Auto-detect is active, each row uses the currency code from the designated column, so a single chart can display values in multiple currencies — each formatted correctly for its currency. - -### ECharts Option Editor - -For ECharts-based chart types (line, bar, area, scatter, pie, and others), Explore includes an advanced **ECharts Option Editor** that accepts raw JSON overrides for the underlying ECharts configuration. - -Access it via the **Customize** tab → **ECharts Options** section at the bottom of the panel. The JSON you enter is deep-merged on top of Superset's generated ECharts config, so you can override specific options without rewriting the entire config. - -**Example:** override the legend position and add a custom title: - -```json -{ - "legend": { "orient": "vertical", "right": "5%", "top": "middle" }, - "title": { "text": "My Custom Title", "left": "center" } -} -``` - -:::caution -ECharts option overrides bypass Superset's validation layer. Invalid option keys are silently ignored by ECharts. Overrides that conflict with Superset-generated options (e.g., `series`) may produce unexpected results. -::: - -### Table Chart: Exporting Filtered Data - -When the **Search Box** is visible in a Table chart, the **Download** action exports only the rows currently visible after the search filter is applied — not the full underlying dataset. This matches the visual output and is intentional. To export the full dataset regardless of search state, use the **Download as CSV** option from the chart's three-dot menu in the dashboard or from the Explore chart toolbar before applying a search filter. - -### Sharing a Specific Tab - -When a dashboard has tabs, each tab gets its own shareable URL. Navigate to the tab you want to share and copy the URL from your browser's address bar — the tab anchor is encoded in the URL so that anyone opening the link lands directly on that tab. - -### Auto-Refresh - -Dashboards can be configured to refresh automatically at a fixed interval without user interaction. Open a dashboard, click the **⋮** (more options) menu in the top-right, and select **Set auto-refresh interval**. Choose an interval (e.g., every 10 seconds, 1 minute, or 10 minutes). The setting is per-session and resets when you close the tab. - -:::note -Auto-refresh triggers a full data reload for all charts on the dashboard. For dashboards with expensive queries, choose longer intervals to avoid overloading your database. -::: - -### Last Queried Timestamp - -Charts can display a "Last queried at" timestamp showing when the chart data was last fetched. This is useful on auto-refreshing dashboards to confirm data freshness. Enable it in **Dashboard Properties → Styling → Show last queried time**. - -### Saving a Chart to a Specific Tab - -When saving or adding a chart to a dashboard from Explore, you can select which tab it should land on using the tab tree-select dropdown in the "Add to dashboard" modal. - -:::resources -- [Dashboard Customization](https://docs.preset.io/docs/dashboard-customization) - Advanced dashboard styling and layout options -- [Blog: BI Dashboard Best Practices](https://preset.io/blog/bi-dashboard-best-practices/) -::: diff --git a/docs/docs/using-superset/embedding.mdx b/docs/docs/using-superset/embedding.mdx deleted file mode 100644 index f03c931e331..00000000000 --- a/docs/docs/using-superset/embedding.mdx +++ /dev/null @@ -1,131 +0,0 @@ ---- -title: Embedding Superset -sidebar_position: 6 ---- - -{/* -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. -*/} - - -# Embedding Superset - -Superset dashboards can be embedded directly in host applications using the `@superset-ui/embedded-sdk` package. - -:::info Prerequisites -- The `EMBEDDED_SUPERSET` feature flag must be enabled. -- The embedding domain and allowed origins must be configured by an admin. -::: - -## Quick Start - -Install the SDK: - -```bash -npm install @superset-ui/embedded-sdk -``` - -Embed a dashboard: - -```javascript -import { embedDashboard } from '@superset-ui/embedded-sdk'; - -embedDashboard({ - id: 'dashboard-uuid-here', // from Dashboard → Embed - supersetDomain: 'https://superset.example.com', - mountPoint: document.getElementById('superset-container'), - fetchGuestToken: () => fetchTokenFromYourBackend(), - dashboardUiConfig: { - hideTitle: true, - filters: { expanded: false }, - }, -}); -``` - -`fetchGuestToken` must return a **guest token** obtained from your server by calling Superset's `/api/v1/security/guest_token/` endpoint with a service account. Do not call this endpoint from client-side code. - ---- - -## Callbacks - -### `resolvePermalinkUrl` - -When a user copies a permalink from an embedded dashboard, Superset generates a URL on its own domain. In an embedded context this URL is usually not meaningful to the host application's users — the dashboard is rendered inside the host app, not at the Superset URL. - -The `resolvePermalinkUrl` callback lets the host app intercept permalink generation and return a URL on the host domain instead: - -```javascript -embedDashboard({ - id: 'my-dashboard-uuid', - supersetDomain: 'https://superset.example.com', - mountPoint: document.getElementById('superset-container'), - fetchGuestToken: () => fetchGuestToken(), - /** - * Called when Superset generates a permalink. - * @param {Object} args - { key: string } — the permalink key - * @returns {string | null} - your host URL, or null to use Superset's default - */ - resolvePermalinkUrl: ({ key }) => { - return `https://myapp.example.com/dashboard?permalink=${key}`; - }, -}); -``` - -If the callback returns `null` or is not provided, Superset uses its own permalink URL as a fallback. - ---- - -## Feature Flags for Embedded Mode - -### `DISABLE_EMBEDDED_SUPERSET_LOGOUT` - -Hides the logout button when Superset is embedded in a host application. This is useful when the host application manages the session lifecycle and you do not want users to accidentally log out of the embedded Superset session: - -```python -# superset_config.py -FEATURE_FLAGS = { - "EMBEDDED_SUPERSET": True, - "DISABLE_EMBEDDED_SUPERSET_LOGOUT": True, -} -``` - -When enabled, the **Logout** menu item is removed from the user avatar dropdown in the embedded view. The session can still be invalidated server-side by revoking the guest token. - -### `EMBEDDED_SUPERSET` - -Must be `True` to enable the embedded SDK and the guest token endpoint. Without this flag, `embedDashboard` will fail to load. - ---- - -## URL Parameters - -The following URL parameters can be passed through the `urlParams` option in `dashboardUiConfig` or appended to the embedded iframe URL: - -| Parameter | Values | Effect | -|-----------|--------|--------| -| `standalone` | `0`, `1`, `2`, `3` | `0`: normal; `1`: hide nav; `2`: hide nav + title; `3`: hide nav + title + tabs | -| `show_filters` | `0`, `1` | Show or hide the native filter bar | -| `expand_filters` | `0`, `1` | Start with filter bar expanded or collapsed | - ---- - -## Security Notes - -- **Guest tokens expire** — their lifetime is controlled by the `GUEST_TOKEN_JWT_EXP_SECONDS` config (default: 5 minutes). Refresh tokens before they expire using a token refresh mechanism in your host app. -- **Row-level security** — pass `rls` rules in the guest token request to restrict which rows are visible to the embedded user. -- **Allowed domains** — restrict which host origins can embed a dashboard by setting **Allowed Domains** per-dashboard in the *Embed* settings modal. Superset checks the request's `Referer` header against this list before serving the embedded view; an empty list allows any origin, so configure this explicitly for production. diff --git a/docs/docs/using-superset/exploring-data.mdx b/docs/docs/using-superset/exploring-data.mdx deleted file mode 100644 index 8d83a0c2c63..00000000000 --- a/docs/docs/using-superset/exploring-data.mdx +++ /dev/null @@ -1,360 +0,0 @@ ---- -title: Exploring Data in Superset -hide_title: true -sidebar_position: 2 -version: 1 ---- - -import useBaseUrl from "@docusaurus/useBaseUrl"; - -## Exploring Data in Superset - -Apache Superset enables users to explore data interactively through SQL queries, visual query builders, and rich visualizations, making it easier to understand datasets before building charts and dashboards. - -In this tutorial, we will introduce key concepts in Apache Superset through the exploration of a real dataset which contains the flights made by employees of a UK-based organization in 2011. - -The following information about each flight is given: - -- The traveler’s department. For the purposes of this tutorial the departments have been renamed - Orange, Yellow and Purple. -- The cost of the ticket. -- The travel class (Economy, Premium Economy, Business and First Class). -- Whether the ticket was a single or return. -- The date of travel. -- Information about the origin and destination. -- The distance between the origin and destination, in kilometers (km). - -### Enabling Data Upload Functionality - -You may need to enable the functionality to upload a CSV or Excel file to your database. The following section -explains how to enable this functionality for the examples database. - -In the top menu, select **Settings ‣ Data ‣ Database Connections**. Find the **examples** database in the list and -select the **Edit** button. - - - -In the resulting modal window, switch to the **Advanced** tab and open **Security** section. -Then, tick the checkbox for **Allow file uploads to database**. End by clicking the **Finish** button. - - - -### Loading CSV Data - -Download the CSV dataset to your computer from -[GitHub](https://raw.githubusercontent.com/apache-superset/examples-data/master/tutorial_flights.csv). -In the top menu, select **Settings ‣ Data ‣ Database Connections**. Then, **Upload file to database ‣ Upload CSV**. - - - -Then, select select the CSV file from your computer, select **Database** and **Schema**, and enter the **Table Name** -as _tutorial_flights_. - - - -Next enter the text _Travel Date_ into the **File settings ‣ Columns to be parsed as dates** field. - - - -Leaving all the other options in their default settings, select **Upload** at the bottom of the page. - -### Table Visualization - -You should now see _tutorial_flights_ as a dataset in the **Datasets** tab. Click on the entry to -launch an Explore workflow using this dataset. - -In this section, we'll create a table visualization -to show the number of flights and cost per travel class. - -By default, Apache Superset only shows the last week of data. In our example, we want to visualize all -of the data in the dataset. Click the **Time ‣ Time Range** section and change -the **Range Type** to **No Filter**. - - - -Click **Apply** to save. - -Now, we want to specify the rows in our table by using the **Group by** option. Since in this -example, we want to understand different Travel Classes, we select **Travel Class** in this menu. - -Next, we can specify the metrics we would like to see in our table with the **Metrics** option. - -- `COUNT(*)`, which represents the number of rows in the table -(in this case, quantity of flights in each Travel Class) -- `SUM(Cost)`, which represents the total cost spent by each Travel Class - - - -Finally, select **Run Query** to see the results of the table. - - - -To save the visualization, click on **Save** in the top left of the screen. In the following modal, - -- Select the **Save as** -option and enter the chart name as Tutorial Table (you will be able to find it again through the -**Charts** screen, accessible in the top menu). -- Select **Add To Dashboard** and enter -Tutorial Dashboard. Finally, select **Save & Go To Dashboard**. - - - -### Dashboard Basics - -Next, we are going to explore the dashboard interface. If you’ve followed the previous section, you -should already have the dashboard open. Otherwise, you can navigate to the dashboard by selecting -Dashboards on the top menu, then Tutorial dashboard from the list of dashboards. - -On this dashboard you should see the table you created in the previous section. Select **Edit -dashboard** and then hover over the table. By selecting the bottom right hand corner of the table -(the cursor will change too), you can resize it by dragging and dropping. - - - -Finally, save your changes by selecting Save changes in the top right. - -### Pivot Table - -In this section, we will extend our analysis using a more complex visualization, Pivot Table. By the -end of this section, you will have created a table that shows the monthly spend on flights for the -first six months, by department, by travel class. - -Create a new chart by selecting **+ ‣ Chart** from the top right corner. Choose -tutorial_flights again as a datasource, then click on the visualization type to get to the -visualization menu. Select the **Pivot Table** visualization (you can filter by entering text in the -search box) and then **Create New Chart**. - - - -In the **Time** section, keep the Time Column as Travel Date (this is selected automatically as we -only have one time column in our dataset). Then select Time Grain to be month as having daily data -would be too granular to see patterns from. Then select the time range to be the first six months of -2011 by click on Last week in the Time Range section, then in Custom selecting a Start / end of 1st -January 2011 and 30th June 2011 respectively by either entering directly the dates or using the -calendar widget (by selecting the month name and then the year, you can move more quickly to far -away dates). - - - -Next, within the **Query** section, remove the default COUNT(\*) and add Cost, keeping the default -SUM aggregate. Note that Apache Superset will indicate the type of the metric by the symbol on the -left hand column of the list (ABC for string, # for number, a clock face for time, etc.). - -In **Group by**, select **Time**: this will automatically use the Time Column and Time Grain -selections we defined in the Time section. - -Within **Columns**, first select Department and then Travel Class. All set – let’s **Run Query** to -see some data! - - - -You should see months in the rows and Department and Travel Class in the columns. Publish this chart -to your existing Tutorial Dashboard you created earlier. - -### Line Chart - -In this section, we are going to create a line chart to understand the average price of a ticket by -month across the entire dataset. - -In the Time section, as before, keep the Time Column as Travel Date and Time Grain as month but this -time for the Time range select No filter as we want to look at entire dataset. - -Within Metrics, remove the default `COUNT(*)` metric and instead add `AVG(Cost)`, to show the mean value. - - - -Next, select **Run Query** to show the data on the chart. - -How does this look? Well, we can see that the average cost goes up in December. However, perhaps it -doesn’t make sense to combine both single and return tickets, but rather show two separate lines for -each ticket type. - -Let’s do this by selecting Ticket Single or Return in the Group by box, and the selecting **Run -Query** again. Nice! We can see that on average single tickets are cheaper than returns and that the -big spike in December is caused by return tickets. - -Our chart is looking pretty good already, but let’s customize some more by going to the Customize -tab on the left hand pane. Within this pane, try changing the Color Scheme, removing the range -filter by selecting No in the Show Range Filter drop down and adding some labels using X Axis Label -and Y Axis Label. - - - -Once you’re done, publish the chart in your Tutorial Dashboard. - -### Markup - -In this section, we will add some text to our dashboard. If you’re there already, you can navigate -to the dashboard by selecting Dashboards on the top menu, then Tutorial dashboard from the list of -dashboards. Got into edit mode by selecting **Edit dashboard**. - -Within the Insert components pane, drag and drop a Markdown box on the dashboard. Look for the blue -lines which indicate the anchor where the box will go. - - - -Now, to edit the text, select the box. You can enter text, in markdown format (see -[this Markdown Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) for -more information about this format). You can toggle between Edit and Preview using the menu on the -top of the box. - - - -To exit, select any other part of the dashboard. Finally, don’t forget to keep your changes using -**Save changes**. - -### Publishing Your Dashboard - -If you have followed all of the steps outlined in the previous section, you should have a dashboard -that looks like the below. If you would like, you can rearrange the elements of the dashboard by -selecting **Edit dashboard** and dragging and dropping. - -If you would like to make your dashboard available to other users, simply select Draft next to the -title of your dashboard on the top left to change your dashboard to be in Published state. You can -also favorite this dashboard by selecting the star. - - - -### Annotations - -Annotations allow you to add additional context to your chart. In this section, we will add an -annotation to the Tutorial Line Chart we made in a previous section. Specifically, we will add the -dates when some flights were cancelled by the UK’s Civil Aviation Authority in response to the -eruption of the Grímsvötn volcano in Iceland (23-25 May 2011). - -First, add an annotation layer by navigating to Manage ‣ Annotation Layers. Add a new annotation -layer by selecting the green plus sign to add a new record. Enter the name Volcanic Eruptions and -save. We can use this layer to refer to a number of different annotations. - -Next, add an annotation by navigating to Manage ‣ Annotations and then create a new annotation by -selecting the green plus sign. Then, select the Volcanic Eruptions layer, add a short description -Grímsvötn and the eruption dates (23-25 May 2011) before finally saving. - - - -Then, navigate to the line chart by going to Charts then selecting Tutorial Line Chart from the -list. Next, go to the Annotations and Layers section and select Add Annotation Layer. Within this -dialogue: - -- Name the layer as Volcanic Eruptions -- Change the Annotation Layer Type to Event -- Set the Annotation Source as Superset annotation -- Specify the Annotation Layer as Volcanic Eruptions - - - -Select **Apply** to see your annotation shown on the chart. - - - -If you wish, you can change how your annotation looks by changing the settings in the Display -configuration section. Otherwise, select **OK** and finally **Save** to save your chart. If you keep -the default selection to overwrite the chart, your annotation will be saved to the chart and also -appear automatically in the Tutorial Dashboard. - -### Advanced Analytics - -In this section, we are going to explore the Advanced Analytics feature of Apache Superset that -allows you to apply additional transformations to your data. The three types of transformation are: - -**Setting up the base chart** - -In this section, we’re going to set up a base chart which we can then apply the different **Advanced -Analytics** features to. Start off by creating a new chart using the same _tutorial_flights_ -datasource and the **Line Chart** visualization type. Within the Time section, set the Time Range as -1st October 2011 and 31st October 2011. - -Next, in the query section, change the Metrics to the sum of Cost. Select **Run Query** to show the -chart. You should see the total cost per day for each month in October 2011. - - - -Finally, save the visualization as Tutorial Advanced Analytics Base, adding it to the Tutorial -Dashboard. - -### Rolling Mean - -There is quite a lot of variation in the data, which makes it difficult to identify any trend. One -approach we can take is to show instead a rolling average of the time series. To do this, in the -**Moving Average** subsection of **Advanced Analytics**, select mean in the **Rolling** box and -enter 7 into both Periods and Min Periods. The period is the length of the rolling period expressed -as a multiple of the Time Grain. In our example, the Time Grain is day, so the rolling period is 7 -days, such that on the 7th October 2011 the value shown would correspond to the first seven days of -October 2011. Lastly, by specifying Min Periods as 7, we ensure that our mean is always calculated -on 7 days and we avoid any ramp up period. - -After displaying the chart by selecting **Run Query** you will see that the data is less variable -and that the series starts later as the ramp up period is excluded. - - - -Save the chart as Tutorial Rolling Mean and add it to the Tutorial Dashboard. - -### Time Comparison - -In this section, we will compare values in our time series to the value a week before. Start off by -opening the Tutorial Advanced Analytics Base chart, by going to **Charts** in the top menu and then -selecting the visualization name in the list (alternatively, find the chart in the Tutorial -Dashboard and select Explore chart from the menu for that visualization). - -Next, in the Time Comparison subsection of **Advanced Analytics**, enter the Time Shift by typing in -“minus 1 week” (note this box accepts input in natural language). Run Query to see the new chart, -which has an additional series with the same values, shifted a week back in time. - - - -Then, change the **Calculation type** to Absolute difference and select **Run Query**. We can now -see only one series again, this time showing the difference between the two series we saw -previously. - - - -Save the chart as Tutorial Time Comparison and add it to the Tutorial Dashboard. - -### Resampling the data - -In this section, we’ll resample the data so that rather than having daily data we have weekly data. -As in the previous section, reopen the Tutorial Advanced Analytics Base chart. - -Next, in the Python Functions subsection of **Advanced Analytics**, enter 7D, corresponding to seven -days, in the Rule and median as the Method and show the chart by selecting **Run Query**. - - - -Note that now we have a single data point every 7 days. In our case, the value showed corresponds to -the median value within the seven daily data points. For more information on the meaning of the -various options in this section, refer to the -[Pandas documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.resample.html). - -Lastly, save your chart as Tutorial Resample and add it to the Tutorial Dashboard. Go to the -tutorial dashboard to see the four charts side by side and compare the different outputs. - -### SQL Lab Tips - -**Schema and table browser**: The left-side table browser uses a collapsible treeview — click a schema to expand its tables, and click a table to see its columns and sample data inline. This makes navigating large schemas much faster than the previous flat list. - -**Find in editor**: Press **Ctrl+F** (or **Cmd+F** on Mac) to open the Monaco find/replace widget inside the SQL editor without leaving the editor. - -**Resizable panels**: The dividers between the SQL editor, schema browser, and results pane are draggable. Adjust them to match your workflow and screen size. - -**Dialect-aware Format SQL**: The **Format SQL** button applies the SQL dialect of the currently selected database — Trino, Presto, MySQL, PostgreSQL, etc. — rather than a generic formatter. Switch to a different database in the toolbar and re-format to get dialect-specific output. Jinja template syntax (`{{ }}`, `{% %}`) is preserved during formatting and will not cause format errors. - -### Time Range Natural Language Expressions - -The **Custom** time range picker accepts natural language expressions alongside specific dates: - -- **Relative**: `Last 7 days`, `Last month`, `Last quarter`, `Last year` -- **Anchored**: `previous calendar week`, `previous calendar month` -- **"First of" expressions**: `first day of this week`, `first day of this month`, `first day of this quarter`, `first day of this year`, `first week of this year` -- **Offsets**: `30 days ago`, `1 year ago`, `next week` - -These expressions are evaluated at query time, so saved charts always display data relative to the current date. - -:::resources -- [Chart Walkthroughs](https://docs.preset.io/docs/chart-walkthroughs) - Detailed guides for most chart types -- [Blog: Why Apache ECharts is the Future of Apache Superset](https://preset.io/blog/2021-4-1-why-echarts/) -- [Blog: ECharts Time-Series Visualizations in Superset](https://preset.io/blog/echarts-time-series-visualizations-in-superset/) -- [Blog: Finding New Insights with Drill By](https://preset.io/blog/drill-by/) -- [Blog: From Drill Down to Drill By](https://preset.io/blog/drill-down-and-drill-by/) -- [Blog: Cross-Filtering in Apache Superset](https://preset.io/blog/cross-filtering-in-Superset-and-Preset/) -::: diff --git a/docs/docs/using-superset/handlebars-chart.mdx b/docs/docs/using-superset/handlebars-chart.mdx deleted file mode 100644 index f560f923f51..00000000000 --- a/docs/docs/using-superset/handlebars-chart.mdx +++ /dev/null @@ -1,143 +0,0 @@ ---- -title: Handlebars Chart -hide_title: true -sidebar_position: 10 -version: 1 ---- - -## Handlebars Chart - -The Handlebars chart lets you render query results using a custom [Handlebars](https://handlebarsjs.com/) template. This gives you full control over how your data is displayed — from simple tables to rich HTML layouts. - -### Basic Usage - -In the chart editor, write a Handlebars template in the **Template** field. Your query results are available as `data`, an array of row objects. - -```handlebars -{{#each data}} -

{{this.name}}: {{this.value}}

-{{/each}} -``` - -### Built-in Helpers - -Superset registers several custom helpers on top of the standard Handlebars built-ins. - -#### `dateFormat` - -Formats a date value using [Day.js](https://day.js.org/) format strings. - -```handlebars -{{dateFormat my_date format="MMMM YYYY"}} -``` - -| Option | Default | Description | -|--------|---------|-------------| -| `format` | `YYYY-MM-DD` | A Day.js-compatible format string | - ---- - -#### `stringify` - -Converts an object to a JSON string, or any other value to its string representation. - -```handlebars -{{stringify myObj}} -``` - ---- - -#### `formatNumber` - -Formats a number using locale-aware formatting. - -```handlebars -{{formatNumber myNumber "en-US"}} -``` - -| Option | Default | Description | -|--------|---------|-------------| -| `locale` | `en-US` | A BCP 47 language tag | - ---- - -#### `parseJson` - -Parses a JSON string into an object that can be used in your template. - -```handlebars -{{parseJson myJsonString}} -``` - ---- - -#### `groupBy` - -Groups an array of objects by a key, powered by [handlebars-group-by](https://github.com/nicktindall/handlebars-group-by). - -```handlebars -{{#groupBy data "department"}} -

{{value}}

- {{#each items}} -

{{this.name}}

- {{/each}} -{{/groupBy}} -``` - ---- - -### Helpers from just-handlebars-helpers - -Superset also registers all helpers from the [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) library. These include a wide range of comparison, math, string, and conditional helpers. Commonly used ones include: - -#### Comparison - -| Helper | Description | Example | -|--------|-------------|---------| -| `eq` | Strict equality | `{{#if (eq status "active")}}` | -| `eqw` | Weak equality | `{{#if (eqw count "5")}}` | -| `neq` | Strict inequality | `{{#if (neq role "admin")}}` | -| `lt` | Less than | `{{#if (lt score 50)}}` | -| `lte` | Less than or equal | `{{#if (lte score 100)}}` | -| `gt` | Greater than | `{{#if (gt price 0)}}` | -| `gte` | Greater than or equal | `{{#if (gte age 18)}}` | - -#### Logical - -| Helper | Description | Example | -|--------|-------------|---------| -| `and` | Logical AND | `{{#if (and isActive isVerified)}}` | -| `or` | Logical OR | `{{#if (or isAdmin isMod)}}` | -| `not` | Logical NOT | `{{#if (not isDisabled)}}` | -| `ifx` | Inline conditional | `{{ifx isActive "Yes" "No"}}` | -| `coalesce` | Returns first non-falsy value | `{{coalesce nickname name "Anonymous"}}` | - -#### String - -| Helper | Description | Example | -|--------|-------------|---------| -| `capitalize` | Capitalizes first letter | `{{capitalize name}}` | -| `uppercase` | Converts to uppercase | `{{uppercase status}}` | -| `lowercase` | Converts to lowercase | `{{lowercase email}}` | -| `truncate` | Truncates a string | `{{truncate description 100}}` | -| `contains` | Checks if string contains substring | `{{#if (contains tag "urgent")}}` | - -#### Math - -| Helper | Description | Example | -|--------|-------------|---------| -| `add` | Addition | `{{add a b}}` | -| `subtract` | Subtraction | `{{subtract total discount}}` | -| `multiply` | Multiplication | `{{multiply price quantity}}` | -| `divide` | Division | `{{divide total count}}` | -| `ceil` | Ceiling | `{{ceil value}}` | -| `floor` | Floor | `{{floor value}}` | -| `round` | Round | `{{round value}}` | - -For the full list of available helpers, see the [just-handlebars-helpers documentation](https://github.com/leapfrogtechnology/just-handlebars-helpers). - -### Tips - -- Use raw blocks to escape Handlebars syntax if you need to display double curly braces literally. -- Comparison helpers like `eq` must be wrapped in a subexpression when used with `#if`: `{{#if (eq myVal "foo")}}`. -- HTML output is sanitized by default based on your Superset configuration (`HTML_SANITIZATION`). diff --git a/docs/docs/using-superset/issue-codes.mdx b/docs/docs/using-superset/issue-codes.mdx deleted file mode 100644 index 59b7fdaa224..00000000000 --- a/docs/docs/using-superset/issue-codes.mdx +++ /dev/null @@ -1,334 +0,0 @@ ---- -title: Issue Codes -sidebar_position: 5 -version: 1 ---- - -# Issue Code Reference - -This page lists issue codes that may be displayed in -Superset and provides additional context. - -## Issue 1000 - -``` -The datasource is too large to query. -``` - -It's likely your datasource has grown too large to run the current -query, and is timing out. You can resolve this by reducing the -size of your datasource or by modifying your query to only process a -subset of your data. - -## Issue 1001 - -``` -The database is under an unusual load. -``` - -Your query may have timed out because of unusually high load on the -database engine. You can make your query simpler, or wait until the -database is under less load and try again. - -## Issue 1002 - -``` -The database returned an unexpected error. -``` - -Your query failed because of an error that occurred on the database. -This may be due to a syntax error, a bug in your query, or some other -internal failure within the database. This is usually not an -issue within Superset, but instead a problem with the underlying -database that serves your query. - -## Issue 1003 - -``` -There is a syntax error in the SQL query. Perhaps there was a misspelling or a typo. -``` - -Your query failed because of a syntax error within the underlying query. Please -validate that all columns or tables referenced within the query exist and are spelled -correctly. - -## Issue 1004 - -``` -The column was deleted or renamed in the database. -``` - -Your query failed because it is referencing a column that no longer exists in -the underlying datasource. You should modify the query to reference the -replacement column, or remove this column from your query. - -## Issue 1005 - -``` -The table was deleted or renamed in the database. -``` - -Your query failed because it is referencing a table that no longer exists in -the underlying database. You should modify your query to reference the correct -table. - -## Issue 1006 - -``` -One or more parameters specified in the query are missing. -``` - -Your query was not submitted to the database because it's missing one or more -parameters. You should define all the parameters referenced in the query in a -valid JSON document. Check that the parameters are spelled correctly and that -the document has a valid syntax. - -## Issue 1007 - -``` -The hostname provided can't be resolved. -``` - -The hostname provided when adding a new database is invalid and cannot be -resolved. Please check that there are no typos in the hostname. - -## Issue 1008 - -``` -The port is closed. -``` - -The port provided when adding a new database is not open. Please check that -the port number is correct, and that the database is running and listening on -that port. - -## Issue 1009 - -``` -The host might be down, and cannot be reached on the provided port. -``` - -The host provided when adding a new database doesn't seem to be up. -Additionally, it cannot be reached on the provided port. Please check that -there are no firewall rules preventing access to the host. - -## Issue 1010 - -``` -Superset encountered an error while running a command. -``` - -Something unexpected happened, and Superset encountered an error while -running a command. Please reach out to your administrator. - -## Issue 1011 - -``` -Superset encountered an unexpected error. -``` - -Something unexpected happened in the Superset backend. Please reach out -to your administrator. - -## Issue 1012 - -``` -The username provided when connecting to a database is not valid. -``` - -The user provided a username that doesn't exist in the database. Please check -that the username is typed correctly and exists in the database. - -## Issue 1013 - -``` -The password provided when connecting to a database is not valid. -``` - -The user provided a password that is incorrect. Please check that the -password is typed correctly. - -## Issue 1014 - -``` -Either the username or the password used are incorrect. -``` - -Either the username provided does not exist or the password was written incorrectly. Please -check that the username and password were typed correctly. - -## Issue 1015 - -``` -Either the database is spelled incorrectly or does not exist. -``` - -Either the database was written incorrectly or it does not exist. Check that it was typed correctly. - -## Issue 1016 - -``` -The schema was deleted or renamed in the database. -``` - -The schema was either removed or renamed. Check that the schema is typed correctly and exists. - -## Issue 1017 - -``` -The user doesn't have the proper permissions to connect to the database -``` - -We were unable to connect to your database. Please confirm that your service account has the Viewer and Job User roles on the project. - -## Issue 1018 - -``` -One or more parameters needed to configure a database are missing. -``` - -Not all parameters required to test, create, or edit a database were present. Please double check which parameters are needed, and that they are present. - -## Issue 1019 - -``` -The submitted payload has the incorrect format. -``` - -Please check that the request payload has the correct format (eg, JSON). - -## Issue 1020 - -``` -The submitted payload has the incorrect schema. -``` - -Please check that the request payload has the expected schema. - -## Issue 1021 - -``` -Results backend needed for asynchronous queries is not configured. -``` - -Your instance of Superset doesn't have a results backend configured, which is needed for asynchronous queries. Please contact an administrator for further assistance. - -## Issue 1022 - -``` -Database does not allow data manipulation. -``` - -Only `SELECT` statements are allowed against this database. Please contact an administrator if you need to run DML (data manipulation language) on this database. - -## Issue 1023 - -``` -CTAS (create table as select) doesn't have a SELECT statement at the end. -``` - -The last statement in a query run as CTAS (create table as select) MUST be a SELECT statement. Please make sure the last statement in the query is a SELECT. - -## Issue 1024 - -``` -CVAS (create view as select) query has more than one statement. -``` - -When running a CVAS (create view as select) the query should have a single statement. Please make sure the query has a single statement, and no extra semi-colons other than the last one. - -## Issue 1025 - -``` -CVAS (create view as select) query is not a SELECT statement. -``` - -When running a CVAS (create view as select) the query should be a SELECT statement. Please make sure the query has a single statement and it's a SELECT statement. - -## Issue 1026 - -``` -Query is too complex and takes too long to run. -``` - -The submitted query might be too complex to run under the time limit defined by your Superset administrator. Please double check your query and verify if it can be optimized. Alternatively, contact your administrator to increase the timeout period. - -## Issue 1027 - -``` -The database is currently running too many queries. -``` - -The database might be under heavy load, running too many queries. Please try again later, or contact an administrator for further assistance. - -## Issue 1028 - -``` -One or more parameters specified in the query are malformed. -``` - -The query contains one or more malformed template parameters. Please check your query and confirm that all template parameters are surround by double braces, for example, "\{\{ ds \}\}". Then, try running your query again. - -## Issue 1029 - -``` -The object does not exist in this database. -``` - -Either the schema, column, or table do not exist in the database. - -## Issue 1030 - -``` -The query potentially has a syntax error. -``` - -The query might have a syntax error. Please check and run again. - -## Issue 1031 - -``` -The results backend no longer has the data from the query. -``` - -The results from the query might have been deleted from the results backend after some period. Please re-run your query. - -## Issue 1032 - -``` -The query associated with the results was deleted. -``` - -The query associated with the stored results no longer exists. Please re-run your query. - -## Issue 1033 - -``` -The results stored in the backend were stored in a different format, and no longer can be deserialized. -``` - -The query results were stored in a format that is no longer supported. Please re-run your query. - -## Issue 1034 - -``` -The database port provided is invalid. -``` - -Please check that the provided database port is an integer between 0 and 65535 (inclusive). - -## Issue 1035 - -``` -Failed to start remote query on a worker. -``` - -The query was not started by an asynchronous worker. Please reach out to your administrator for further assistance. - -## Issue 1036 - -``` -The database was deleted. -``` - -The operation failed because the database referenced no longer exists. Please reach out to your administrator for further assistance. diff --git a/docs/docs/using-superset/sql-templating.mdx b/docs/docs/using-superset/sql-templating.mdx deleted file mode 100644 index 44566604565..00000000000 --- a/docs/docs/using-superset/sql-templating.mdx +++ /dev/null @@ -1,274 +0,0 @@ ---- -title: SQL Templating -sidebar_position: 4 -description: Use Jinja templates in SQL Lab and virtual datasets to create dynamic queries -keywords: [sql templating, jinja, sql lab, virtual datasets, dynamic queries] ---- - -{/* -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. -*/} - -# SQL Templating - -Superset supports [Jinja templating](https://jinja.palletsprojects.com/) in SQL Lab queries and virtual datasets. This allows you to write dynamic SQL that responds to filters, user context, and URL parameters. - -:::note -SQL templating must be enabled by your administrator via the `ENABLE_TEMPLATE_PROCESSING` feature flag. -For advanced configuration options, see the [SQL Templating Configuration Guide](/admin-docs/configuration/sql-templating). -::: - -## Using Jinja in Calculated Columns - -Jinja template macros are available in calculated column expressions in the dataset editor — not just in SQL Lab queries and virtual datasets. This allows column expressions to reference the current user or dynamic context. - -**Example: User-scoped calculated column** - -```sql -CASE WHEN sales_rep = '{{ current_username() }}' THEN amount ELSE 0 END -``` - -**Example: Conditional display based on role** - -Because `current_user_roles()` returns a Python list, test role membership with a Jinja -conditional at template time rather than matching against the list's string representation: - -```sql -{% if 'Finance' in current_user_roles() %}revenue{% else %}NULL{% endif %} AS finance_revenue -``` - -:::note -The `ENABLE_TEMPLATE_PROCESSING` feature flag must be enabled by your administrator for Jinja in calculated columns to work. -::: - -## Basic Usage - -Jinja templates use double curly braces `{{ }}` for expressions and `{% %}` for logic blocks. - -### Using Parameters - -You can define parameters in SQL Lab via the **Parameters** menu as JSON: - -```json -{ - "my_table": "sales", - "start_date": "2024-01-01" -} -``` - -Then reference them in your query: - -```sql -SELECT * -FROM {{ my_table }} -WHERE order_date >= '{{ start_date }}' -``` - -### Conditional Logic - -Use Jinja's logic blocks for conditional SQL: - -```sql -SELECT * -FROM orders -WHERE 1 = 1 -{% if start_date %} - AND order_date >= '{{ start_date }}' -{% endif %} -{% if end_date %} - AND order_date < '{{ end_date }}' -{% endif %} -``` - -## Available Macros - -Superset provides built-in macros for common use cases. - -### User Context - -| Macro | Description | -|-------|-------------| -| `{{ current_username() }}` | Returns the logged-in user's username | -| `{{ current_user_id() }}` | Returns the logged-in user's account ID | -| `{{ current_user_email() }}` | Returns the logged-in user's email | -| `{{ current_user_roles() }}` | Returns an array of the user's roles | - -**Example: Row-level filtering by user** - -```sql -SELECT * -FROM sales_data -WHERE sales_rep = '{{ current_username() }}' -``` - -**Example: Role-based access** - -```sql -SELECT * -FROM users -WHERE role IN {{ current_user_roles()|where_in }} -``` - -### Filter Values - -Access dashboard and chart filter values in your queries: - -| Macro | Description | -|-------|-------------| -| `{{ filter_values('column') }}` | Returns filter values as a list | -| `{{ get_filters('column') }}` | Returns filters with operators | - -**Example: Using filter values** - -```sql -SELECT product, SUM(revenue) as total -FROM sales -WHERE region IN {{ filter_values('region')|where_in }} -GROUP BY product -``` - -The `where_in` filter converts the list to SQL format: `('value1', 'value2', 'value3')` - -### Time Filters - -For charts with time range filters: - -| Macro | Description | -|-------|-------------| -| `{{ get_time_filter('column') }}` | Returns time filter with `from_expr` and `to_expr` | - -**Example: Time-filtered virtual dataset** - -```sql -{% set time_filter = get_time_filter("order_date", default="Last 7 days") %} -SELECT * -FROM orders -WHERE order_date >= {{ time_filter.from_expr }} - AND order_date < {{ time_filter.to_expr }} -``` - -### URL Parameters - -Pass custom values via URL query strings: - -```sql -SELECT * -FROM orders -WHERE country = '{{ url_param('country') }}' -``` - -Access via: `superset.example.com/sqllab?country=US` - -### Reusing Dataset Definitions - -Query existing datasets by ID: - -```sql --- Query a dataset (ID 42) as a table -SELECT * FROM {{ dataset(42) }} LIMIT 100 - --- Include computed metrics -SELECT * FROM {{ dataset(42, include_metrics=True) }} -``` - -Reuse metric definitions across queries: - -```sql -SELECT - category, - {{ metric('total_revenue') }} as revenue -FROM sales -GROUP BY category -``` - -## Testing Templates in SQL Lab - -Some variables like `from_dttm` and `filter_values()` only work when filters are applied from dashboards or charts. To test in SQL Lab: - -**Option 1: Use defaults** - -```sql -SELECT * -FROM orders -WHERE date >= '{{ from_dttm | default("2024-01-01", true) }}' -``` - -**Option 2: Set test parameters** - -Add to the Parameters menu: - -```json -{ - "_filters": [ - {"col": "region", "op": "IN", "val": ["US", "EU"]} - ] -} -``` - -**Option 3: Use `{% set %}`** - -```sql -{% set start_date = "2024-01-01" %} -SELECT * FROM orders WHERE date >= '{{ start_date }}' -``` - -## Common Patterns - -### Dynamic Table Selection - -```sql -{% set table_name = url_param('table') or 'default_table' %} -SELECT * FROM {{ table_name }} -``` - -### User-Specific Data Access - -```sql -SELECT * -FROM sensitive_data -WHERE department IN ( - SELECT department - FROM user_permissions - WHERE username = '{{ current_username() }}' -) -``` - -### Time-Based Partitioning - -```sql -{% set time_filter = get_time_filter("event_date", remove_filter=True) %} -SELECT * -FROM events -WHERE event_date >= {{ time_filter.from_expr }} - AND event_date < {{ time_filter.to_expr }} -``` - -Using `remove_filter=True` applies the filter in the inner query for better performance. - -## Tips - -- Use `|where_in` filter to convert lists to SQL `IN` clauses -- Use `|tojson` to serialize arrays as JSON strings -- Test queries with explicit parameter values before relying on filter context -- For complex templating needs, ask your administrator about custom Jinja macros -- **Format SQL is Jinja-aware**: The "Format SQL" button in SQL Lab correctly preserves `{{ }}` and `{% %}` template syntax and applies your selected database's SQL dialect when formatting. - -:::resources -- [Admin Guide: SQL Templating Configuration](/admin-docs/configuration/sql-templating) -- [Blog: Intro to Jinja Templating in Apache Superset](https://preset.io/blog/intro-jinja-templating-apache-superset/) -::: diff --git a/docs/docs/using-superset/using-ai-with-superset.mdx b/docs/docs/using-superset/using-ai-with-superset.mdx deleted file mode 100644 index b419ccc9269..00000000000 --- a/docs/docs/using-superset/using-ai-with-superset.mdx +++ /dev/null @@ -1,312 +0,0 @@ ---- -title: Using AI with Superset -hide_title: true -sidebar_position: 5 -version: 1 ---- - - - -# Using AI with Superset - -Superset supports AI assistants through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/). Connect Claude, ChatGPT, or other MCP-compatible clients to explore your data, build charts, create dashboards, and run SQL -- all through natural language. - -:::info -Requires Superset 5.0+. Your admin must enable and deploy the MCP server before you can connect. -See the **[MCP Server admin guide](/admin-docs/configuration/mcp-server)** for setup instructions. -::: - ---- - -## What Can AI Do with Superset? - -### Explore Your Data - -Ask your AI assistant to browse what's available in your Superset instance: - -- **List datasets** -- see all datasets you have access to, with filtering and search -- **Get dataset details** -- column names, types, available metrics, and filters -- **List charts and dashboards** -- find existing visualizations by name or keyword -- **Get chart and dashboard details** -- understand what a chart shows, its query, and configuration - -**Example prompts:** -> "What datasets are available?" -> "Show me the columns in the sales_orders dataset" -> "Find dashboards related to revenue" - -### Build Charts - -Describe the visualization you want and AI creates it for you: - -- **Preview-first workflow** -- by default AI generates an Explore link so you can review the chart before it is saved. Say "save it" to commit permanently -- **Create charts from natural language** -- describe what you want to see and AI picks the right chart type, metrics, and dimensions -- **Preview before saving** -- `generate_chart` defaults to `save_chart=False`, showing the chart in Explore before it's committed. Ask AI to save once you're satisfied. -- **Modify existing charts** -- `update_chart` also supports preview mode so you can review changes before saving (update filters, change chart types, add metrics) -- **Get Explore links** -- open any chart in Superset's Explore view for further refinement - -**Example prompts:** -> "Create a bar chart showing monthly revenue by region from the sales dataset" -> "Update chart 42 to use a line chart instead" -> "Give me a link to explore this chart further" - -:::tip Preview-first workflow -Charts are **not saved by default**. The workflow is intentionally iterative: - -1. **Explore** — AI generates an Explore link so you can see the chart before it exists in Superset -2. **Iterate** — ask the AI to adjust the chart; changes are previewed without touching the database -3. **Save** — when you're happy, say "save it" and the chart is permanently stored - -To skip the preview and save immediately, include "and save it" in your prompt. -::: - -### Create Dashboards - -Build dashboards from a collection of charts: - -- **Generate dashboards** -- create a new dashboard with a set of charts, automatically laid out -- **Add charts to existing dashboards** -- place a chart on an existing dashboard with automatic positioning - -**Example prompts:** -> "Create a dashboard called 'Q4 Sales Overview' with charts 10, 15, and 22" -> "Add the revenue trend chart to the executive dashboard" - -### Browse Databases - -Discover what database connections are configured in your Superset instance: - -- **List databases** -- see all database connections you have access to -- **Get database details** -- name, backend type (PostgreSQL, Snowflake, etc.), and connection status - -**Example prompts:** -> "What databases are connected to Superset?" -> "Show me details about the data warehouse connection" - -### Create Virtual Datasets - -Build ad-hoc SQL datasets that can be used as the basis for charts: - -- **Create virtual datasets** -- write a SQL query and save it as a reusable dataset -- **Use immediately in charts** -- the returned dataset ID can be passed directly to chart creation - -**Example prompts:** -> "Create a dataset from: SELECT region, SUM(revenue) as total_revenue FROM orders GROUP BY region" -> "Make a virtual dataset called 'monthly_signups' from the users table filtered to last 12 months" - -### Run SQL Queries - -Execute SQL directly through your AI assistant: - -- **Run queries** -- execute SQL with full Superset RBAC enforcement (you can only query data your roles allow) -- **Open SQL Lab** -- get a link to SQL Lab pre-populated with a query, ready to run and explore -- **Save queries** -- save a SQL query to SQL Lab's Saved Queries for later reuse - -**Example prompts:** -> "Run this query: SELECT region, SUM(revenue) FROM sales GROUP BY region" -> "Open SQL Lab with a query to show the top 10 customers by order count" -> "Save this query as 'Weekly Revenue Report'" - -### Analyze Chart Data - -Pull the raw data behind any chart: - -- **Get chart data** -- retrieve the data a chart displays, with support for JSON, CSV, and Excel export formats -- **Inspect results** -- useful for verifying what a visualization shows or feeding data into other tools - -**Example prompts:** -> "Get the data behind chart 42" -> "Export chart 15 data as CSV" - -### Check Instance Status - -- **Health check** -- verify your Superset instance is up and the MCP connection is working -- **Instance info** -- get high-level statistics about your Superset instance (number of datasets, charts, dashboards) - -**Example prompts:** -> "Is Superset healthy?" -> "How many dashboards are in this instance?" - ---- - -## Connecting Your AI Client - -Once your admin has deployed the MCP server, connect your AI client using the instructions below. - -### Claude Desktop - -Edit your Claude Desktop config file: - -- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` -- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` -- **Linux**: `~/.config/Claude/claude_desktop_config.json` - -```json -{ - "mcpServers": { - "superset": { - "url": "http://localhost:5008/mcp" - } - } -} -``` - -Restart Claude Desktop. The hammer icon in the chat bar confirms the connection. - -If your admin has enabled JWT authentication, you may need to include a token: - -```json -{ - "mcpServers": { - "superset": { - "command": "npx", - "args": [ - "-y", - "mcp-remote@latest", - "http://your-superset-host:5008/mcp", - "--header", - "Authorization: Bearer YOUR_TOKEN" - ] - } - } -} -``` - -### Claude Code (CLI) - -Add to your project's `.mcp.json`: - -```json -{ - "mcpServers": { - "superset": { - "type": "url", - "url": "http://localhost:5008/mcp" - } - } -} -``` - -### ChatGPT - -1. Click your profile icon > **Settings** > **Apps and Connectors** -2. Enable **Developer Mode** in Advanced Settings -3. In the chat composer, press **+** > **Add sources** > **App** > **Connect more** > **Create app** -4. Enter a name and your MCP server URL -5. Click **I understand and continue** - -:::info -ChatGPT MCP connectors require a Pro, Team, Enterprise, or Edu plan. -::: - -Ask your admin for the MCP server URL and any authentication tokens you need. - ---- - -## Tips for Best Results - -- **Be specific** -- "Create a bar chart of monthly revenue by region from the sales dataset" works better than "Make me a chart" -- **Start with exploration** -- ask what datasets and charts exist before creating new ones -- **Review AI-generated content** -- always check chart configurations and SQL before saving or sharing -- **Use Explore for refinement** -- ask AI for an Explore link, then fine-tune interactively in the Superset UI -- **Check permissions if you get errors** -- AI respects Superset's RBAC, so you can only access data your roles allow - ---- - -## Available Tools Reference - -### Exploration & Discovery - -| Tool | Description | -|------|-------------| -| `health_check` | Verify the MCP server is running and connected | -| `get_instance_info` | Get instance statistics (dataset, chart, dashboard counts) | -| `get_schema` | Discover available charts, datasets, and dashboards with schema info | - -### Datasets - -| Tool | Description | -|------|-------------| -| `list_datasets` | List datasets with filtering and search | -| `get_dataset_info` | Get dataset metadata (columns, metrics, filters) | -| `create_virtual_dataset` | Create a virtual dataset from a SQL query | - -### Charts - -| Tool | Description | -|------|-------------| -| `list_charts` | List charts with filtering and search | -| `get_chart_info` | Get chart metadata and configuration | -| `get_chart_data` | Retrieve chart data (JSON, CSV, or Excel) | -| `get_chart_preview` | Generate a chart preview (URL, ASCII, table, or Vega-Lite) | -| `get_chart_type_schema` | Get the configuration schema for a chart type | -| `generate_chart` | Create a new chart from a specification (defaults to preview mode — review before saving) | -| `update_chart` | Modify an existing chart's configuration (pass `generate_preview=False` to persist immediately instead of returning a preview URL) | -| `update_chart_preview` | Update a cached chart preview without saving | -| `generate_explore_link` | Generate an Explore URL for interactive visualization | - -### Dashboards - -| Tool | Description | -|------|-------------| -| `list_dashboards` | List dashboards with filtering and search | -| `get_dashboard_info` | Get dashboard metadata and layout | -| `generate_dashboard` | Create a new dashboard with specified charts | -| `add_chart_to_existing_dashboard` | Add a chart to an existing dashboard | - -### SQL - -| Tool | Description | -|------|-------------| -| `execute_sql` | Run a SQL query with RBAC enforcement | -| `save_sql_query` | Persist a SQL query to SQL Lab's saved queries | -| `open_sql_lab_with_context` | Open SQL Lab with a pre-populated query | - -### Databases - -| Tool | Description | -|------|-------------| -| `list_databases` | List configured database connections | -| `get_database_info` | Get details about a specific database connection | - ---- - -## Troubleshooting - -### "Connection refused" or "Cannot connect" - -- Confirm the MCP server URL with your admin -- For Claude Desktop: fully quit the app (not just close the window) and restart after config changes -- Check that the URL path ends with `/mcp` (e.g., `http://localhost:5008/mcp`) - -### "Permission denied" or missing data - -- Superset's RBAC controls what you can access through AI, just like in the Superset UI -- Ask your admin to verify your roles and permissions -- Try accessing the same data through the Superset web UI to confirm your access - -### "Response too large" - -- Ask for smaller result sets: use filters, reduce `page_size`, or request specific columns -- Example: "Show me the top 10 rows from the sales dataset" instead of "Show me all sales data" - -### AI doesn't see Superset tools - -- Verify the connection in your AI client (e.g., the hammer icon in Claude Desktop) -- Ask the AI "What Superset tools are available?" to confirm the connection -- Restart your AI client if you recently changed the configuration diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts deleted file mode 100644 index 5a6c5b023c7..00000000000 --- a/docs/docusaurus.config.ts +++ /dev/null @@ -1,973 +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 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'; -import remarkTechArticleSchema from './plugins/remark-tech-article-schema.mjs'; -import * as fs from 'fs'; -import * as path from 'path'; - -const { github: lightCodeTheme, vsDark: darkCodeTheme } = themes; - -// Load version configuration from external file -const versionsConfigPath = path.join(__dirname, 'versions-config.json'); -const versionsConfig = JSON.parse(fs.readFileSync(versionsConfigPath, 'utf8')); - -// Build plugins array dynamically based on disabled flags -const dynamicPlugins = []; - -// Add components plugin if not disabled -if (!versionsConfig.components.disabled) { - dynamicPlugins.push([ - '@docusaurus/plugin-content-docs', - { - id: 'components', - path: 'components', - routeBasePath: 'components', - sidebarPath: require.resolve('./sidebarComponents.js'), - editUrl: - 'https://github.com/apache/superset/edit/master/docs/components', - remarkPlugins: [remarkImportPartial, remarkLocalizeBadges, remarkTechArticleSchema], - admonitions: { - keywords: ['note', 'tip', 'info', 'warning', 'danger', 'resources'], - extendDefaults: true, - }, - docItemComponent: '@theme/DocItem', - includeCurrentVersion: versionsConfig.components.includeCurrentVersion, - lastVersion: versionsConfig.components.lastVersion, - onlyIncludeVersions: versionsConfig.components.onlyIncludeVersions, - versions: versionsConfig.components.versions, - disableVersioning: false, - showLastUpdateAuthor: true, - showLastUpdateTime: true, - }, - ]); -} - -// Add admin_docs plugin if not disabled -if (!versionsConfig.admin_docs.disabled) { - dynamicPlugins.push([ - '@docusaurus/plugin-content-docs', - { - id: 'admin_docs', - path: 'admin_docs', - routeBasePath: 'admin-docs', - sidebarPath: require.resolve('./sidebarAdminDocs.js'), - editUrl: - 'https://github.com/apache/superset/edit/master/docs/admin_docs', - remarkPlugins: [remarkImportPartial, remarkLocalizeBadges, remarkTechArticleSchema], - admonitions: { - keywords: ['note', 'tip', 'info', 'warning', 'danger', 'resources'], - extendDefaults: true, - }, - docItemComponent: '@theme/DocItem', - includeCurrentVersion: versionsConfig.admin_docs.includeCurrentVersion, - lastVersion: versionsConfig.admin_docs.lastVersion, - onlyIncludeVersions: versionsConfig.admin_docs.onlyIncludeVersions, - versions: versionsConfig.admin_docs.versions, - disableVersioning: false, - showLastUpdateAuthor: true, - showLastUpdateTime: true, - }, - ]); -} - -// Add developer_docs plugin if not disabled -if (!versionsConfig.developer_docs.disabled) { - dynamicPlugins.push([ - '@docusaurus/plugin-content-docs', - { - id: 'developer_docs', - path: 'developer_docs', - routeBasePath: 'developer-docs', - sidebarPath: require.resolve('./sidebarTutorials.js'), - editUrl: - 'https://github.com/apache/superset/edit/master/docs/developer_docs', - remarkPlugins: [remarkImportPartial, remarkLocalizeBadges, remarkTechArticleSchema], - admonitions: { - keywords: ['note', 'tip', 'info', 'warning', 'danger', 'resources'], - extendDefaults: true, - }, - docItemComponent: '@theme/ApiItem', // Required for OpenAPI docs - includeCurrentVersion: versionsConfig.developer_docs.includeCurrentVersion, - lastVersion: versionsConfig.developer_docs.lastVersion, - onlyIncludeVersions: versionsConfig.developer_docs.onlyIncludeVersions, - versions: versionsConfig.developer_docs.versions, - disableVersioning: false, - showLastUpdateAuthor: true, - showLastUpdateTime: true, - }, - ]); -} - -// Build navbar items dynamically based on disabled flags -const dynamicNavbarItems = []; - -// Add Component Playground navbar item if not disabled -if (!versionsConfig.components.disabled) { - dynamicNavbarItems.push({ - label: 'Component Playground', - to: '/components', - items: [ - { - label: 'Introduction', - to: '/components', - }, - { - label: 'UI Components', - to: '/components/ui-components/button', - }, - { - label: 'Chart Components', - to: '/components/chart-components/bar-chart', - }, - { - label: 'Layout Components', - to: '/components/layout-components/grid', - }, - ], - }); -} - -// Add Admin Docs navbar item if not disabled -if (!versionsConfig.admin_docs.disabled) { - dynamicNavbarItems.push({ - label: 'Admins', - to: '/admin-docs/', - position: 'left', - activeBaseRegex: '^/admin-docs/', - items: [ - { - label: 'Overview', - to: '/admin-docs/', - activeBaseRegex: '^/admin-docs/$', - }, - { - label: 'Installation', - to: '/admin-docs/installation/installation-methods', - activeBaseRegex: '^/admin-docs/installation/', - }, - { - label: 'Configuration', - to: '/admin-docs/configuration/configuring-superset', - activeBaseRegex: '^/admin-docs/configuration/', - }, - { - label: 'Database Drivers', - href: '/user-docs/databases/', - }, - { - label: 'Security', - to: '/admin-docs/security/', - activeBaseRegex: '^/admin-docs/security/', - }, - ], - }); -} - -// Add Developer Docs navbar item if not hidden from nav -if (!versionsConfig.developer_docs.disabled && !versionsConfig.developer_docs.hideFromNav) { - dynamicNavbarItems.push({ - label: 'Developers', - to: '/developer-docs/', - position: 'left', - activeBaseRegex: '^/developer-docs/', - items: [ - { - label: 'Overview', - to: '/developer-docs/', - activeBaseRegex: '^/developer-docs/$', - }, - { - label: 'Contributing', - to: '/developer-docs/contributing/overview', - activeBaseRegex: '^/developer-docs/contributing/', - }, - { - label: 'Extensions', - to: '/developer-docs/extensions/overview', - activeBaseRegex: '^/developer-docs/extensions/', - }, - { - label: 'Testing', - to: '/developer-docs/testing/overview', - activeBaseRegex: '^/developer-docs/testing/', - }, - { - label: 'UI Components', - to: '/developer-docs/components/', - activeBaseRegex: '^/developer-docs/components/', - }, - { - label: 'API Reference', - to: '/developer-docs/api', - activeBaseRegex: '^/developer-docs/api', - }, - ], - }); -} - - -const config: Config = { - future: { - v4: { - removeLegacyPostBuildHeadAttribute: true, - // Disabled: CSS cascade layers change specificity and cause antd - // styles (from Storybook component pages) to override theme styles - useCssCascadeLayers: false, - }, - faster: { - swcJsLoader: false, - swcJsMinimizer: true, - swcHtmlMinimizer: true, - lightningCssMinimizer: true, - rspackBundler: true, - mdxCrossCompilerCache: true, - rspackPersistentCache: true, - // SSG worker threads spawn parallel Node processes, each consuming - // significant memory. Disabled to keep total usage reasonable. - ssgWorkerThreads: false, - }, - }, - title: 'Superset', - tagline: - 'Apache Superset is a modern data exploration and visualization platform', - url: 'https://superset.apache.org', - baseUrl: '/', - onBrokenLinks: 'throw', - markdown: { - mermaid: true, - hooks: { - onBrokenMarkdownLinks: 'throw', - }, - }, - favicon: '/img/favicon.ico', - organizationName: 'apache', - projectName: 'superset', - - // SEO: Structured data (Organization, Software, WebSite with SearchAction) - headTags: [ - // SoftwareApplication schema - { - tagName: 'script', - attributes: { - type: 'application/ld+json', - }, - innerHTML: JSON.stringify({ - '@context': 'https://schema.org', - '@type': 'SoftwareApplication', - name: 'Apache Superset', - applicationCategory: 'BusinessApplication', - operatingSystem: 'Cross-platform', - description: 'Apache Superset is a modern, enterprise-ready business intelligence web application for data exploration and visualization.', - url: 'https://superset.apache.org', - license: 'https://www.apache.org/licenses/LICENSE-2.0', - author: { - '@type': 'Organization', - name: 'Apache Software Foundation', - url: 'https://www.apache.org/', - logo: 'https://www.apache.org/foundation/press/kit/asf_logo.png', - }, - offers: { - '@type': 'Offer', - price: '0', - priceCurrency: 'USD', - }, - featureList: [ - 'Interactive dashboards', - 'SQL IDE', - '40+ visualization types', - 'Semantic layer', - 'Role-based access control', - 'REST API', - ], - }), - }, - // WebSite schema with SearchAction (enables sitelinks search box in Google) - { - tagName: 'script', - attributes: { - type: 'application/ld+json', - }, - innerHTML: JSON.stringify({ - '@context': 'https://schema.org', - '@type': 'WebSite', - name: 'Apache Superset', - url: 'https://superset.apache.org', - potentialAction: { - '@type': 'SearchAction', - target: { - '@type': 'EntryPoint', - urlTemplate: 'https://superset.apache.org/search?q={search_term_string}', - }, - 'query-input': 'required name=search_term_string', - }, - }), - }, - // Preconnect hints for faster external resource loading - { - tagName: 'link', - attributes: { - rel: 'preconnect', - href: 'https://WR5FASX5ED-dsn.algolia.net', - crossorigin: 'anonymous', - }, - }, - { - tagName: 'link', - attributes: { - rel: 'preconnect', - href: 'https://analytics.apache.org', - }, - }, - ], - themes: [ - '@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', - { - id: 'api', - docsPluginId: 'developer_docs', - config: { - superset: { - specPath: 'static/resources/openapi.json', - outputDir: 'developer_docs/api', - sidebarOptions: { - groupPathsBy: 'tag', - categoryLinkSource: 'tag', - sidebarCollapsible: true, - sidebarCollapsed: true, - }, - showSchemas: true, - hideSendButton: true, - showInfoPage: false, - showExtensions: true, - } satisfies OpenApiPlugin.Options, - }, - }, - ], - // SEO: Generate robots.txt during build - [ - require.resolve('./plugins/robots-txt-plugin.js'), - { - policies: [ - { - userAgent: '*', - allow: '/', - disallow: ['/api/v1/', '/_next/', '/static/js/*.map'], - }, - ], - }, - ], - [ - '@docusaurus/plugin-client-redirects', - { - redirects: [ - // Legacy HTML page redirects - { - to: '/admin-docs/installation/docker-compose', - from: '/installation.html', - }, - { - to: '/user-docs/', - from: '/tutorials.html', - }, - { - to: '/user-docs/using-superset/creating-your-first-dashboard', - from: '/admintutorial.html', - }, - { - to: '/user-docs/using-superset/creating-your-first-dashboard', - from: '/usertutorial.html', - }, - { - to: '/admin-docs/security/', - from: '/security.html', - }, - { - to: '/admin-docs/configuration/sql-templating', - from: '/sqllab.html', - }, - { - to: '/user-docs/', - from: '/gallery.html', - }, - { - to: '/user-docs/databases/', - from: '/druid.html', - }, - { - to: '/admin-docs/configuration/country-map-tools', - from: '/misc.html', - }, - { - to: '/admin-docs/configuration/country-map-tools', - from: '/visualization.html', - }, - { - to: '/user-docs/faq', - from: '/videos.html', - }, - { - to: '/user-docs/faq', - from: '/faq.html', - }, - { - to: '/user-docs/using-superset/creating-your-first-dashboard', - from: '/tutorial.html', - }, - { - to: '/user-docs/using-superset/creating-your-first-dashboard', - from: '/docs/creating-charts-dashboards/first-dashboard', - }, - { - to: '/developer-docs/api', - from: '/docs/rest-api', - }, - { - to: '/admin-docs/configuration/alerts-reports', - from: '/docs/installation/alerts-reports', - }, - { - to: '/developer-docs/contributing/development-setup', - from: '/docs/contributing/hooks-and-linting', - }, - { - to: '/user-docs/', - from: '/docs/roadmap', - }, - { - to: '/user-docs/', - from: '/user-docs/intro', - }, - { - to: '/developer-docs/contributing/overview', - from: '/docs/contributing/contribution-guidelines', - }, - { - to: '/developer-docs/contributing/overview', - from: '/docs/contributing/contribution-page', - }, - { - to: '/user-docs/databases/', - from: '/docs/databases/yugabyte/', - }, - { - to: '/user-docs/faq', - from: '/docs/frequently-asked-questions', - }, - // Redirect old user-docs/api to developer-docs/api - { - to: '/developer-docs/api', - from: '/user-docs/api', - }, - // Redirects from old /docs/ paths to new /admin-docs/ paths - { - to: '/admin-docs/installation/installation-methods', - from: '/docs/installation/installation-methods', - }, - { - to: '/admin-docs/installation/docker-compose', - from: '/docs/installation/docker-compose', - }, - { - to: '/admin-docs/installation/docker-builds', - from: '/docs/installation/docker-builds', - }, - { - to: '/admin-docs/installation/kubernetes', - from: '/docs/installation/kubernetes', - }, - { - to: '/admin-docs/installation/pypi', - from: '/docs/installation/pypi', - }, - { - to: '/admin-docs/installation/architecture', - from: '/docs/installation/architecture', - }, - { - to: '/admin-docs/installation/upgrading-superset', - from: '/docs/installation/upgrading-superset', - }, - { - to: '/admin-docs/configuration/configuring-superset', - from: '/docs/configuration/configuring-superset', - }, - { - to: '/admin-docs/configuration/alerts-reports', - from: '/docs/configuration/alerts-reports', - }, - { - to: '/admin-docs/configuration/async-queries-celery', - from: '/docs/configuration/async-queries-celery', - }, - { - to: '/admin-docs/configuration/cache', - from: '/docs/configuration/cache', - }, - { - to: '/admin-docs/configuration/event-logging', - from: '/docs/configuration/event-logging', - }, - { - to: '/admin-docs/configuration/feature-flags', - from: '/docs/configuration/feature-flags', - }, - { - to: '/admin-docs/configuration/sql-templating', - from: '/docs/configuration/sql-templating', - }, - { - to: '/admin-docs/configuration/theming', - from: '/docs/configuration/theming', - }, - { - to: '/admin-docs/security/', - from: '/docs/security', - }, - { - to: '/admin-docs/security/', - from: '/docs/security/security', - }, - // Redirects from old /docs/contributing/ to Developer Portal - { - to: '/developer-docs/contributing/overview', - from: '/docs/contributing', - }, - { - to: '/developer-docs/contributing/overview', - from: '/docs/contributing/contributing', - }, - { - to: '/developer-docs/contributing/development-setup', - from: '/docs/contributing/development', - }, - { - to: '/developer-docs/contributing/guidelines', - from: '/docs/contributing/guidelines', - }, - { - to: '/developer-docs/contributing/howtos', - from: '/docs/contributing/howtos', - }, - { - to: '/admin-docs/installation/kubernetes', - from: '/docs/installation/running-on-kubernetes/', - }, - { - to: '/developer-docs/contributing/howtos', - from: '/docs/contributing/testing-locally/', - }, - { - to: '/user-docs/using-superset/creating-your-first-dashboard', - from: '/docs/creating-charts-dashboards/creating-your-first-dashboard/', - }, - { - to: '/user-docs/using-superset/creating-your-first-dashboard', - from: '/docs/creating-charts-dashboards/exploring-data/', - }, - { - to: '/admin-docs/installation/docker-compose', - from: '/docs/installation/installing-superset-using-docker-compose/', - }, - { - to: '/developer-docs/contributing/howtos', - from: '/docs/contributing/creating-viz-plugins/', - }, - { - to: '/admin-docs/installation/kubernetes', - from: '/docs/installation/', - }, - { - to: '/admin-docs/installation/pypi', - from: '/docs/installation/installing-superset-from-pypi/', - }, - { - to: '/admin-docs/configuration/configuring-superset', - from: '/docs/installation/configuring-superset/', - }, - { - to: '/admin-docs/configuration/cache', - from: '/docs/installation/cache/', - }, - { - to: '/admin-docs/configuration/async-queries-celery', - from: '/docs/installation/async-queries-celery/', - }, - { - to: '/admin-docs/configuration/event-logging', - from: '/docs/installation/event-logging/', - }, - { - to: '/developer-docs/contributing/howtos', - from: '/docs/contributing/translations/', - }, - // Additional configuration redirects - { - to: '/admin-docs/configuration/country-map-tools', - from: '/docs/configuration/country-map-tools', - }, - { - to: '/admin-docs/configuration/importing-exporting-datasources', - from: '/docs/configuration/importing-exporting-datasources', - }, - { - to: '/admin-docs/configuration/map-tiles', - from: '/docs/configuration/map-tiles', - }, - { - to: '/admin-docs/configuration/networking-settings', - from: '/docs/configuration/networking-settings', - }, - { - to: '/admin-docs/configuration/timezones', - from: '/docs/configuration/timezones', - }, - // Additional security redirects - { - to: '/admin-docs/security/cves', - from: '/docs/security/cves', - }, - { - to: '/admin-docs/security/securing_superset', - from: '/docs/security/securing_superset', - }, - // Additional contributing redirects - { - to: '/developer-docs/contributing/resources', - from: '/docs/contributing/resources', - }, - { - to: '/developer-docs/contributing/howtos', - from: '/docs/contributing/misc', - }, - { - to: '/developer-docs/contributing/overview', - from: '/docs/contributing/pkg-resources-migration', - }, - ], - // Use createRedirects for pattern-based redirects - createRedirects(existingPath) { - const redirects = []; - - // Redirect all /developer_portal/* paths to /developer-docs/* - if (existingPath.startsWith('/developer-docs/')) { - redirects.push(existingPath.replace('/developer-docs/', '/developer_portal/')); - } - - // Redirect all /docs/* paths to /user-docs/* for user documentation - if (existingPath.startsWith('/user-docs/')) { - redirects.push(existingPath.replace('/user-docs/', '/docs/')); - } - - // Redirect /docs/api/* to /developer-docs/api/* (API moved to developer docs) - if (existingPath.startsWith('/developer-docs/api')) { - redirects.push(existingPath.replace('/developer-docs/', '/docs/')); - } - - return redirects.length > 0 ? redirects : undefined; - }, - }, - ], - ], - - presets: [ - [ - '@docusaurus/preset-classic', - { - docs: { - routeBasePath: 'user-docs', - sidebarPath: require.resolve('./sidebars.js'), - editUrl: ({ versionDocsDirPath, docPath }) => { - if (docPath === 'intro.md') { - return 'https://github.com/apache/superset/edit/master/README.md'; - } - return `https://github.com/apache/superset/edit/master/docs/${versionDocsDirPath}/${docPath}`; - }, - remarkPlugins: [remarkImportPartial, remarkLocalizeBadges, remarkTechArticleSchema], - admonitions: { - keywords: ['note', 'tip', 'info', 'warning', 'danger', 'resources'], - extendDefaults: true, - }, - includeCurrentVersion: versionsConfig.docs.includeCurrentVersion, - lastVersion: versionsConfig.docs.lastVersion, // Make 'next' the default - onlyIncludeVersions: versionsConfig.docs.onlyIncludeVersions, - versions: versionsConfig.docs.versions, - disableVersioning: false, - showLastUpdateAuthor: true, - showLastUpdateTime: true, - docItemComponent: '@theme/DocItem', - }, - blog: { - showReadingTime: true, - // Please change this to your repo. - editUrl: - 'https://github.com/facebook/docusaurus/edit/main/website/blog/', - }, - theme: { - customCss: require.resolve('./src/styles/custom.css'), - }, - // SEO: Sitemap configuration with priorities - sitemap: { - lastmod: 'date', - changefreq: 'weekly', - priority: 0.5, - ignorePatterns: ['/tags/**'], - filename: 'sitemap.xml', - createSitemapItems: async (params) => { - const { defaultCreateSitemapItems, ...rest } = params; - const items = await defaultCreateSitemapItems(rest); - return items.map((item) => { - // Boost priority for key pages - if (item.url.endsWith('/user-docs/')) { - return { ...item, priority: 1.0, changefreq: 'daily' }; - } - if (item.url.includes('/user-docs/quickstart')) { - return { ...item, priority: 0.9, changefreq: 'weekly' }; - } - if (item.url.includes('/admin-docs/installation/')) { - return { ...item, priority: 0.8, changefreq: 'weekly' }; - } - if (item.url.includes('/user-docs/databases')) { - return { ...item, priority: 0.8, changefreq: 'weekly' }; - } - if (item.url.includes('/admin-docs/')) { - return { ...item, priority: 0.8, changefreq: 'weekly' }; - } - if (item.url.includes('/user-docs/faq')) { - return { ...item, priority: 0.7, changefreq: 'monthly' }; - } - if (item.url === 'https://superset.apache.org/') { - return { ...item, priority: 1.0, changefreq: 'daily' }; - } - return item; - }); - }, - }, - } satisfies Options, - ], - ], - - themeConfig: { - // SEO: OpenGraph and Twitter meta tags - metadata: [ - { name: 'keywords', content: 'data visualization, business intelligence, BI, dashboards, SQL, analytics, open source, Apache, charts, reporting' }, - { property: 'og:type', content: 'website' }, - { property: 'og:site_name', content: 'Apache Superset' }, - { property: 'og:image', content: 'https://superset.apache.org/img/superset-og-image.png' }, - { property: 'og:image:width', content: '1200' }, - { property: 'og:image:height', content: '630' }, - { name: 'twitter:card', content: 'summary_large_image' }, - { name: 'twitter:image', content: 'https://superset.apache.org/img/superset-og-image.png' }, - { name: 'twitter:site', content: '@ApacheSuperset' }, - ], - colorMode: { - defaultMode: 'dark', - disableSwitch: false, - respectPrefersColorScheme: true, - }, - algolia: { - appId: 'WR5FASX5ED', - apiKey: 'd0d22810f2e9b614ffac3a73b26891fe', - indexName: 'superset-apache', - }, - mermaid: { - theme: { light: 'neutral', dark: 'dark' }, - options: { - // Any Mermaid config options go here... - maxTextSize: 100000, - }, - }, - navbar: { - logo: { - alt: 'Superset Logo', - src: '/img/superset-logo-horiz.svg', - srcDark: '/img/superset-logo-horiz-dark.svg', - }, - items: [ - // Users docs - mirrors sidebar structure - { - label: 'Users', - to: '/user-docs/', - position: 'left', - activeBaseRegex: '^/user-docs/', - items: [ - { - label: 'Overview', - to: '/user-docs/', - activeBaseRegex: '^/user-docs/$', - }, - { - label: 'Quickstart', - to: '/user-docs/quickstart', - }, - { - label: 'Using Superset', - to: '/user-docs/using-superset/creating-your-first-dashboard', - activeBaseRegex: '^/user-docs/using-superset/', - }, - { - label: 'Connecting to Databases', - to: '/user-docs/databases/', - activeBaseRegex: '^/user-docs/databases/', - }, - { - label: 'FAQ', - to: '/user-docs/faq', - }, - ], - }, - ...dynamicNavbarItems, - // Community section - { - label: 'Community', - to: '/community', - position: 'left', - activeBaseRegex: '^/community', - items: [ - { - label: 'Resources', - to: '/community', - activeBaseRegex: '^/community$', - }, - { - label: 'GitHub', - href: 'https://github.com/apache/superset', - }, - { - label: 'Slack', - href: 'http://bit.ly/join-superset-slack', - }, - { - label: 'Mailing List', - href: 'https://lists.apache.org/list.html?dev@superset.apache.org', - }, - { - label: 'Stack Overflow', - href: 'https://stackoverflow.com/questions/tagged/apache-superset', - }, - { - label: 'Community Calendar', - href: '/community#superset-community-calendar', - }, - { - label: 'In the Wild', - href: '/inTheWild', - }, - ], - }, - { - type: 'custom-getStartedSplit', - position: 'right', - }, - { - href: 'https://github.com/apache/superset', - position: 'right', - className: 'github-button', - }, - ], - }, - footer: { - links: [], - copyright: ` - -

Copyright © ${new Date().getFullYear()}, - The Apache Software Foundation, - Licensed under the Apache License.

-

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. - Apache Software Foundation resources

- Divider -

- - Security |  - Donate |  - Thanks |  - Events |  - License |  - Privacy - -

- - - `, - }, - prism: { - theme: lightCodeTheme, - darkTheme: darkCodeTheme, - }, - docs: { - sidebar: { - hideable: true, - }, - }, - liveCodeBlock: { - playgroundPosition: 'bottom', - }, - } satisfies ThemeConfig, - scripts: [ - // { - // src: 'https://www.bugherd.com/sidebarv2.js?apikey=enilpiu7bgexxsnoqfjtxa', - // async: true, - // }, - { - src: 'https://widget.kapa.ai/kapa-widget.bundle.js', - async: true, - 'data-website-id': 'c6a8a8b8-3127-48f9-97a7-51e9e10d20d0', - 'data-project-name': 'Apache Superset', - 'data-project-color': '#FFFFFF', - 'data-project-logo': - 'https://superset.apache.org/img/superset-logo-icon-only.png', - 'data-modal-override-open-id': 'ask-ai-input', - 'data-modal-override-open-class': 'search-input', - 'data-modal-disclaimer': - 'This is a custom LLM for Apache Superset with access to all [documentation](superset.apache.org/docs/intro/), [GitHub Open Issues, PRs and READMEs](github.com/apache/superset). Companies deploy assistants like this ([built by kapa.ai](https://kapa.ai)) on docs via [website widget](https://docs.kapa.ai/integrations/website-widget) (Docker, Reddit), in [support forms](https://docs.kapa.ai/integrations/support-form-deflector) for ticket deflection (Monday.com, Mapbox), or as [Slack bots](https://docs.kapa.ai/integrations/slack-bot) with private sources.', - 'data-modal-example-questions': - 'How do I install Superset?,How can I contribute to Superset?', - 'data-button-text-color': 'rgb(81,166,197)', - 'data-modal-header-bg-color': '#ffffff', - 'data-modal-title-color': 'rgb(81,166,197)', - 'data-modal-title': 'Apache Superset AI', - 'data-modal-disclaimer-text-color': '#000000', - 'data-consent-required': 'true', - 'data-consent-screen-disclaimer': - "By clicking \"I agree, let's chat\", you consent to the use of the AI assistant in accordance with kapa.ai's [Privacy Policy](https://www.kapa.ai/content/privacy-policy). This service uses reCAPTCHA, which requires your consent to Google's [Privacy Policy](https://policies.google.com/privacy) and [Terms of Service](https://policies.google.com/terms). By proceeding, you explicitly agree to both kapa.ai's and Google's privacy policies.", - }, - ], - customFields: { - matomoUrl: 'https://analytics.apache.org', - matomoSiteId: '22', - }, -}; - -export default config; diff --git a/docs/eslint.config.js b/docs/eslint.config.js deleted file mode 100644 index 2f0d01513ec..00000000000 --- a/docs/eslint.config.js +++ /dev/null @@ -1,75 +0,0 @@ -/* eslint-env node */ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -const typescriptEslintParser = require('@typescript-eslint/parser'); -const typescriptEslintPlugin = require('@typescript-eslint/eslint-plugin'); -const eslintConfigPrettier = require('eslint-config-prettier'); -const prettierEslintPlugin = require('eslint-plugin-prettier'); -const js = require('@eslint/js'); -const ts = require('typescript-eslint'); -const react = require('eslint-plugin-react'); -const globals = require('globals'); -const { defineConfig, globalIgnores } = require('eslint/config'); - -module.exports = defineConfig([ - { - files: ['**/*.{js,jsx,ts,tsx}'], - }, - globalIgnores(['build/**/*', '.docusaurus/**/*', 'node_modules/**/*']), - js.configs.recommended, - ...ts.configs.recommended, - eslintConfigPrettier, - { - files: ['eslint.config.js'], - rules: { - '@typescript-eslint/no-require-imports': 'off', - }, - }, - { - languageOptions: { - parser: typescriptEslintParser, - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - ecmaVersion: 2020, - sourceType: 'module', - }, - globals: { - ...globals.browser, - ...globals.node, - }, - }, - plugins: { - typescript: typescriptEslintPlugin, - react, - prettier: prettierEslintPlugin, - }, - rules: { - 'react/react-in-jsx-scope': 'off', - 'react/prop-types': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - }, - settings: { - react: { - version: 'detect', - }, - }, - }, -]); diff --git a/docs/filter-screenshots/00-dashboard-full.png b/docs/filter-screenshots/00-dashboard-full.png deleted file mode 100644 index 4e86da093a2..00000000000 Binary files a/docs/filter-screenshots/00-dashboard-full.png and /dev/null differ diff --git a/docs/filter-screenshots/01-pills-inactive.png b/docs/filter-screenshots/01-pills-inactive.png deleted file mode 100644 index 52da9cc890e..00000000000 Binary files a/docs/filter-screenshots/01-pills-inactive.png and /dev/null differ diff --git a/docs/filter-screenshots/02-dropdown-open.png b/docs/filter-screenshots/02-dropdown-open.png deleted file mode 100644 index 0ef4912c603..00000000000 Binary files a/docs/filter-screenshots/02-dropdown-open.png and /dev/null differ diff --git a/docs/filter-screenshots/03-pill-active-clearall.png b/docs/filter-screenshots/03-pill-active-clearall.png deleted file mode 100644 index c26c5bebe38..00000000000 Binary files a/docs/filter-screenshots/03-pill-active-clearall.png and /dev/null differ diff --git a/docs/filter-screenshots/03-pill-active.png b/docs/filter-screenshots/03-pill-active.png deleted file mode 100644 index 52a841f9bb7..00000000000 Binary files a/docs/filter-screenshots/03-pill-active.png and /dev/null differ diff --git a/docs/filter-screenshots/04-owner-dropdown.png b/docs/filter-screenshots/04-owner-dropdown.png deleted file mode 100644 index 712a6ac0db0..00000000000 Binary files a/docs/filter-screenshots/04-owner-dropdown.png and /dev/null differ diff --git a/docs/filter-screenshots/04-tooltip.png b/docs/filter-screenshots/04-tooltip.png deleted file mode 100644 index a0d4fda9c93..00000000000 Binary files a/docs/filter-screenshots/04-tooltip.png and /dev/null differ diff --git a/docs/filter-screenshots/05-chart-list.png b/docs/filter-screenshots/05-chart-list.png deleted file mode 100644 index a5c4f771367..00000000000 Binary files a/docs/filter-screenshots/05-chart-list.png and /dev/null differ diff --git a/docs/filter-screenshots/05-owner-dropdown.png b/docs/filter-screenshots/05-owner-dropdown.png deleted file mode 100644 index ae4623eb027..00000000000 Binary files a/docs/filter-screenshots/05-owner-dropdown.png and /dev/null differ diff --git a/docs/filter-screenshots/05-tooltip.png b/docs/filter-screenshots/05-tooltip.png deleted file mode 100644 index f841db419eb..00000000000 Binary files a/docs/filter-screenshots/05-tooltip.png and /dev/null differ diff --git a/docs/filter-screenshots/06-chart-list.png b/docs/filter-screenshots/06-chart-list.png deleted file mode 100644 index e9676fdbbf2..00000000000 Binary files a/docs/filter-screenshots/06-chart-list.png and /dev/null differ diff --git a/docs/filter-screenshots/06-dataset-list.png b/docs/filter-screenshots/06-dataset-list.png deleted file mode 100644 index 54eaa835b38..00000000000 Binary files a/docs/filter-screenshots/06-dataset-list.png and /dev/null differ diff --git a/docs/i18n/en/code.json b/docs/i18n/en/code.json deleted file mode 100644 index ac2f7565695..00000000000 --- a/docs/i18n/en/code.json +++ /dev/null @@ -1,420 +0,0 @@ -{ - "theme.ErrorPageContent.title": { - "message": "This page crashed.", - "description": "The title of the fallback page when the page crashed" - }, - "theme.blog.archive.title": { - "message": "Archive", - "description": "The page & hero title of the blog archive page" - }, - "theme.blog.archive.description": { - "message": "Archive", - "description": "The page & hero description of the blog archive page" - }, - "theme.BackToTopButton.buttonAriaLabel": { - "message": "Scroll back to top", - "description": "The ARIA label for the back to top button" - }, - "theme.blog.paginator.navAriaLabel": { - "message": "Blog list page navigation", - "description": "The ARIA label for the blog pagination" - }, - "theme.blog.paginator.newerEntries": { - "message": "Newer Entries", - "description": "The label used to navigate to the newer blog posts page (previous page)" - }, - "theme.blog.paginator.olderEntries": { - "message": "Older Entries", - "description": "The label used to navigate to the older blog posts page (next page)" - }, - "theme.blog.post.paginator.navAriaLabel": { - "message": "Blog post page navigation", - "description": "The ARIA label for the blog posts pagination" - }, - "theme.blog.post.paginator.newerPost": { - "message": "Newer Post", - "description": "The blog post button label to navigate to the newer/previous post" - }, - "theme.blog.post.paginator.olderPost": { - "message": "Older Post", - "description": "The blog post button label to navigate to the older/next post" - }, - "theme.blog.post.plurals": { - "message": "One post|{count} posts", - "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" - }, - "theme.blog.tagTitle": { - "message": "{nPosts} tagged with \"{tagName}\"", - "description": "The title of the page for a blog tag" - }, - "theme.tags.tagsPageLink": { - "message": "View All Tags", - "description": "The label of the link targeting the tag list page" - }, - "theme.colorToggle.ariaLabel": { - "message": "Switch between dark and light mode (currently {mode})", - "description": "The ARIA label for the navbar color mode toggle" - }, - "theme.colorToggle.ariaLabel.mode.dark": { - "message": "dark mode", - "description": "The name for the dark color mode" - }, - "theme.colorToggle.ariaLabel.mode.light": { - "message": "light mode", - "description": "The name for the light color mode" - }, - "theme.docs.breadcrumbs.navAriaLabel": { - "message": "Breadcrumbs", - "description": "The ARIA label for the breadcrumbs" - }, - "theme.docs.DocCard.categoryDescription.plurals": { - "message": "{count} items", - "description": "The default description for a category card in the generated index about how many items this category includes" - }, - "theme.docs.paginator.navAriaLabel": { - "message": "Docs pages", - "description": "The ARIA label for the docs pagination" - }, - "theme.docs.paginator.previous": { - "message": "Previous", - "description": "The label used to navigate to the previous doc" - }, - "theme.docs.paginator.next": { - "message": "Next", - "description": "The label used to navigate to the next doc" - }, - "theme.docs.tagDocListPageTitle.nDocsTagged": { - "message": "One doc tagged|{count} docs tagged", - "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" - }, - "theme.docs.tagDocListPageTitle": { - "message": "{nDocsTagged} with \"{tagName}\"", - "description": "The title of the page for a docs tag" - }, - "theme.docs.versions.unreleasedVersionLabel": { - "message": "This is unreleased documentation for {siteTitle} {versionLabel} version.", - "description": "The label used to tell the user that he's browsing an unreleased doc version" - }, - "theme.docs.versions.unmaintainedVersionLabel": { - "message": "This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.", - "description": "The label used to tell the user that he's browsing an unmaintained doc version" - }, - "theme.docs.versions.latestVersionSuggestionLabel": { - "message": "For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).", - "description": "The label used to tell the user to check the latest version" - }, - "theme.docs.versions.latestVersionLinkLabel": { - "message": "latest version", - "description": "The label used for the latest version suggestion link label" - }, - "theme.docs.versionBadge.label": { - "message": "Version: {versionLabel}" - }, - "theme.common.editThisPage": { - "message": "Edit this page", - "description": "The link label to edit the current page" - }, - "theme.common.headingLinkTitle": { - "message": "Direct link to {heading}", - "description": "Title for link to heading" - }, - "theme.lastUpdated.atDate": { - "message": " on {date}", - "description": "The words used to describe on which date a page has been last updated" - }, - "theme.lastUpdated.byUser": { - "message": " by {user}", - "description": "The words used to describe by who the page has been last updated" - }, - "theme.lastUpdated.lastUpdatedAtBy": { - "message": "Last updated{atDate}{byUser}", - "description": "The sentence used to display when a page has been last updated, and by who" - }, - "theme.NotFound.title": { - "message": "Page Not Found", - "description": "The title of the 404 page" - }, - "theme.navbar.mobileVersionsDropdown.label": { - "message": "Versions", - "description": "The label for the navbar versions dropdown on mobile view" - }, - "theme.tags.tagsListLabel": { - "message": "Tags:", - "description": "The label alongside a tag list" - }, - "theme.admonition.caution": { - "message": "caution", - "description": "The default label used for the Caution admonition (:::caution)" - }, - "theme.admonition.danger": { - "message": "danger", - "description": "The default label used for the Danger admonition (:::danger)" - }, - "theme.admonition.info": { - "message": "info", - "description": "The default label used for the Info admonition (:::info)" - }, - "theme.admonition.note": { - "message": "note", - "description": "The default label used for the Note admonition (:::note)" - }, - "theme.admonition.tip": { - "message": "tip", - "description": "The default label used for the Tip admonition (:::tip)" - }, - "theme.admonition.warning": { - "message": "warning", - "description": "The default label used for the Warning admonition (:::warning)" - }, - "theme.AnnouncementBar.closeButtonAriaLabel": { - "message": "Close", - "description": "The ARIA label for close button of announcement bar" - }, - "theme.blog.sidebar.navAriaLabel": { - "message": "Blog recent posts navigation", - "description": "The ARIA label for recent posts in the blog sidebar" - }, - "theme.CodeBlock.copied": { - "message": "Copied", - "description": "The copied button label on code blocks" - }, - "theme.CodeBlock.copyButtonAriaLabel": { - "message": "Copy code to clipboard", - "description": "The ARIA label for copy code blocks button" - }, - "theme.CodeBlock.copy": { - "message": "Copy", - "description": "The copy button label on code blocks" - }, - "theme.CodeBlock.wordWrapToggle": { - "message": "Toggle word wrap", - "description": "The title attribute for toggle word wrapping button of code block lines" - }, - "theme.DocSidebarItem.expandCategoryAriaLabel": { - "message": "Expand sidebar category '{label}'", - "description": "The ARIA label to expand the sidebar category" - }, - "theme.DocSidebarItem.collapseCategoryAriaLabel": { - "message": "Collapse sidebar category '{label}'", - "description": "The ARIA label to collapse the sidebar category" - }, - "theme.NavBar.navAriaLabel": { - "message": "Main", - "description": "The ARIA label for the main navigation" - }, - "theme.NotFound.p1": { - "message": "We could not find what you were looking for.", - "description": "The first paragraph of the 404 page" - }, - "theme.NotFound.p2": { - "message": "Please contact the owner of the site that linked you to the original URL and let them know their link is broken.", - "description": "The 2nd paragraph of the 404 page" - }, - "theme.navbar.mobileLanguageDropdown.label": { - "message": "Languages", - "description": "The label for the mobile language switcher dropdown" - }, - "theme.TOCCollapsible.toggleButtonLabel": { - "message": "On this page", - "description": "The label used by the button on the collapsible TOC component" - }, - "theme.blog.post.readMore": { - "message": "Read More", - "description": "The label used in blog post item excerpts to link to full blog posts" - }, - "theme.blog.post.readMoreLabel": { - "message": "Read more about {title}", - "description": "The ARIA label for the link to full blog posts from excerpts" - }, - "theme.blog.post.readingTime.plurals": { - "message": "One min read|{readingTime} min read", - "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" - }, - "theme.docs.breadcrumbs.home": { - "message": "Home page", - "description": "The ARIA label for the home page in the breadcrumbs" - }, - "theme.docs.sidebar.collapseButtonTitle": { - "message": "Collapse sidebar", - "description": "The title attribute for collapse button of doc sidebar" - }, - "theme.docs.sidebar.collapseButtonAriaLabel": { - "message": "Collapse sidebar", - "description": "The title attribute for collapse button of doc sidebar" - }, - "theme.docs.sidebar.navAriaLabel": { - "message": "Docs sidebar", - "description": "The ARIA label for the sidebar navigation" - }, - "theme.docs.sidebar.closeSidebarButtonAriaLabel": { - "message": "Close navigation bar", - "description": "The ARIA label for close button of mobile sidebar" - }, - "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": { - "message": "← Back to main menu", - "description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)" - }, - "theme.docs.sidebar.toggleSidebarButtonAriaLabel": { - "message": "Toggle navigation bar", - "description": "The ARIA label for hamburger menu button of mobile navigation" - }, - "theme.docs.sidebar.expandButtonTitle": { - "message": "Expand sidebar", - "description": "The ARIA label and title attribute for expand button of doc sidebar" - }, - "theme.docs.sidebar.expandButtonAriaLabel": { - "message": "Expand sidebar", - "description": "The ARIA label and title attribute for expand button of doc sidebar" - }, - "theme.SearchBar.seeAll": { - "message": "See all {count} results" - }, - "theme.SearchPage.documentsFound.plurals": { - "message": "One document found|{count} documents found", - "description": "Pluralized label for \"{count} documents found\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" - }, - "theme.SearchPage.existingResultsTitle": { - "message": "Search results for \"{query}\"", - "description": "The search page title for non-empty query" - }, - "theme.SearchPage.emptyResultsTitle": { - "message": "Search the documentation", - "description": "The search page title for empty query" - }, - "theme.SearchPage.inputPlaceholder": { - "message": "Type your search here", - "description": "The placeholder for search page input" - }, - "theme.SearchPage.inputLabel": { - "message": "Search", - "description": "The ARIA label for search page input" - }, - "theme.SearchPage.algoliaLabel": { - "message": "Search by Algolia", - "description": "The ARIA label for Algolia mention" - }, - "theme.SearchPage.noResultsText": { - "message": "No results were found", - "description": "The paragraph for empty search result" - }, - "theme.SearchPage.fetchingNewResults": { - "message": "Fetching new results...", - "description": "The paragraph for fetching new search results" - }, - "theme.SearchBar.label": { - "message": "Search", - "description": "The ARIA label and placeholder for search button" - }, - "theme.SearchModal.searchBox.resetButtonTitle": { - "message": "Clear the query", - "description": "The label and ARIA label for search box reset button" - }, - "theme.SearchModal.searchBox.cancelButtonText": { - "message": "Cancel", - "description": "The label and ARIA label for search box cancel button" - }, - "theme.SearchModal.startScreen.recentSearchesTitle": { - "message": "Recent", - "description": "The title for recent searches" - }, - "theme.SearchModal.startScreen.noRecentSearchesText": { - "message": "No recent searches", - "description": "The text when no recent searches" - }, - "theme.SearchModal.startScreen.saveRecentSearchButtonTitle": { - "message": "Save this search", - "description": "The label for save recent search button" - }, - "theme.SearchModal.startScreen.removeRecentSearchButtonTitle": { - "message": "Remove this search from history", - "description": "The label for remove recent search button" - }, - "theme.SearchModal.startScreen.favoriteSearchesTitle": { - "message": "Favorite", - "description": "The title for favorite searches" - }, - "theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle": { - "message": "Remove this search from favorites", - "description": "The label for remove favorite search button" - }, - "theme.SearchModal.errorScreen.titleText": { - "message": "Unable to fetch results", - "description": "The title for error screen of search modal" - }, - "theme.SearchModal.errorScreen.helpText": { - "message": "You might want to check your network connection.", - "description": "The help text for error screen of search modal" - }, - "theme.SearchModal.footer.selectText": { - "message": "to select", - "description": "The explanatory text of the action for the enter key" - }, - "theme.SearchModal.footer.selectKeyAriaLabel": { - "message": "Enter key", - "description": "The ARIA label for the Enter key button that makes the selection" - }, - "theme.SearchModal.footer.navigateText": { - "message": "to navigate", - "description": "The explanatory text of the action for the Arrow up and Arrow down key" - }, - "theme.SearchModal.footer.navigateUpKeyAriaLabel": { - "message": "Arrow up", - "description": "The ARIA label for the Arrow up key button that makes the navigation" - }, - "theme.SearchModal.footer.navigateDownKeyAriaLabel": { - "message": "Arrow down", - "description": "The ARIA label for the Arrow down key button that makes the navigation" - }, - "theme.SearchModal.footer.closeText": { - "message": "to close", - "description": "The explanatory text of the action for Escape key" - }, - "theme.SearchModal.footer.closeKeyAriaLabel": { - "message": "Escape key", - "description": "The ARIA label for the Escape key button that close the modal" - }, - "theme.SearchModal.footer.searchByText": { - "message": "Search by", - "description": "The text explain that the search is making by Algolia" - }, - "theme.SearchModal.noResultsScreen.noResultsText": { - "message": "No results for", - "description": "The text explains that there are no results for the following search" - }, - "theme.SearchModal.noResultsScreen.suggestedQueryText": { - "message": "Try searching for", - "description": "The text for the suggested query when no results are found for the following search" - }, - "theme.SearchModal.noResultsScreen.reportMissingResultsText": { - "message": "Believe this query should return results?", - "description": "The text for the question where the user thinks there are missing results" - }, - "theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": { - "message": "Let us know.", - "description": "The text for the link to report missing results" - }, - "theme.SearchModal.placeholder": { - "message": "Search docs", - "description": "The placeholder of the input of the DocSearch pop-up modal" - }, - "theme.ErrorPageContent.tryAgain": { - "message": "Try again", - "description": "The label of the button to try again rendering when the React error boundary captures an error" - }, - "theme.common.skipToMainContent": { - "message": "Skip to main content", - "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" - }, - "theme.tags.tagsPageTitle": { - "message": "Tags", - "description": "The title of the tag list page" - }, - "theme.unlistedContent.title": { - "message": "Unlisted page", - "description": "The unlisted content banner title" - }, - "theme.unlistedContent.message": { - "message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.", - "description": "The unlisted content banner message" - } -} diff --git a/docs/i18n/en/docusaurus-plugin-content-blog/options.json b/docs/i18n/en/docusaurus-plugin-content-blog/options.json deleted file mode 100644 index 9239ff706c2..00000000000 --- a/docs/i18n/en/docusaurus-plugin-content-blog/options.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "title": { - "message": "Blog", - "description": "The title for the blog used in SEO" - }, - "description": { - "message": "Blog", - "description": "The description for the blog used in SEO" - }, - "sidebar.title": { - "message": "Recent posts", - "description": "The label for the left sidebar" - } -} diff --git a/docs/i18n/en/docusaurus-plugin-content-docs/current.json b/docs/i18n/en/docusaurus-plugin-content-docs/current.json deleted file mode 100644 index c315f571daa..00000000000 --- a/docs/i18n/en/docusaurus-plugin-content-docs/current.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "version.label": { - "message": "Next", - "description": "The label for version current" - }, - "sidebar.CustomSidebar.category.Installation": { - "message": "Installation", - "description": "The label for category Installation in sidebar CustomSidebar" - }, - "sidebar.CustomSidebar.category.Configuration": { - "message": "Configuration", - "description": "The label for category Configuration in sidebar CustomSidebar" - }, - "sidebar.CustomSidebar.category.Using Superset": { - "message": "Using Superset", - "description": "The label for category Using Superset in sidebar CustomSidebar" - }, - "sidebar.CustomSidebar.category.Contributing": { - "message": "Contributing", - "description": "The label for category Contributing in sidebar CustomSidebar" - }, - "sidebar.CustomSidebar.category.Security": { - "message": "Security", - "description": "The label for category Security in sidebar CustomSidebar" - }, - "sidebar.CustomSidebar.doc.Introduction": { - "message": "Introduction", - "description": "The label for the doc item Introduction in sidebar CustomSidebar, linking to the doc intro" - }, - "sidebar.CustomSidebar.doc.Quickstart": { - "message": "Quickstart", - "description": "The label for the doc item Quickstart in sidebar CustomSidebar, linking to the doc quickstart" - }, - "sidebar.CustomSidebar.doc.FAQ": { - "message": "FAQ", - "description": "The label for the doc item FAQ in sidebar CustomSidebar, linking to the doc faq" - }, - "sidebar.CustomSidebar.doc.API": { - "message": "API", - "description": "The label for the doc item API in sidebar CustomSidebar, linking to the doc api" - } -} diff --git a/docs/i18n/en/docusaurus-theme-classic/footer.json b/docs/i18n/en/docusaurus-theme-classic/footer.json deleted file mode 100644 index af0cb99f294..00000000000 --- a/docs/i18n/en/docusaurus-theme-classic/footer.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "copyright": { - "message": "\n
\n CI powered by\n \"Netlify\"\n
\n

Copyright © 2026,\n The Apache Software Foundation,\n Licensed under the Apache License.

\n

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 Apache Software Foundation resources

\n \"Divider\"\n

\n \n Security | \n Donate | \n Thanks | \n Events | \n License | \n Privacy\n \n

\n \n \n ", - "description": "The footer copyright" - } -} diff --git a/docs/i18n/en/docusaurus-theme-classic/navbar.json b/docs/i18n/en/docusaurus-theme-classic/navbar.json deleted file mode 100644 index ccddc595ad9..00000000000 --- a/docs/i18n/en/docusaurus-theme-classic/navbar.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "logo.alt": { - "message": "Superset Logo", - "description": "The alt text of navbar logo" - }, - "item.label.Documentation": { - "message": "Documentation", - "description": "Navbar item with label Documentation" - }, - "item.label.Community": { - "message": "Community", - "description": "Navbar item with label Community" - }, - "item.label.Get Started": { - "message": "Get Started", - "description": "Navbar item with label Get Started" - }, - "item.label.Getting Started": { - "message": "Getting Started", - "description": "Navbar item with label Getting Started" - }, - "item.label.FAQ": { - "message": "FAQ", - "description": "Navbar item with label FAQ" - }, - "item.label.Resources": { - "message": "Resources", - "description": "Navbar item with label Resources" - }, - "item.label.GitHub": { - "message": "GitHub", - "description": "Navbar item with label GitHub" - }, - "item.label.Slack": { - "message": "Slack", - "description": "Navbar item with label Slack" - }, - "item.label.Mailing List": { - "message": "Mailing List", - "description": "Navbar item with label Mailing List" - }, - "item.label.Stack Overflow": { - "message": "Stack Overflow", - "description": "Navbar item with label Stack Overflow" - } -} diff --git a/docs/netlify.toml b/docs/netlify.toml deleted file mode 100644 index 881fc40438d..00000000000 --- a/docs/netlify.toml +++ /dev/null @@ -1,58 +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. - -# Netlify configuration for Superset documentation -# This enables automatic deploy previews for PRs that modify docs - -[build] - # Base directory is the docs folder - base = "docs" - # Build command for Docusaurus - command = "yarn install && yarn build" - # Output directory (relative to base) - publish = "build" - # Skip builds when no docs changes (exit 0 = skip, exit 1 = build) - # Checks for changes in docs/ and README.md (which gets pulled into docs) - ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF -- . ../README.md" - -[build.environment] - # Node version matching docs/.nvmrc - 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] - command = "yarn install && yarn build" - -# Branch deploy settings (for feature branches) -[context.branch-deploy] - command = "yarn install && yarn build" - -# Redirect /docs to the main docs page -[[redirects]] - from = "/docs" - to = "/docs/intro" - status = 301 - -# Handle SPA routing for Docusaurus -[[redirects]] - from = "/*" - to = "/index.html" - status = 200 diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index 250ba939580..00000000000 --- a/docs/package.json +++ /dev/null @@ -1,133 +0,0 @@ -{ - "name": "docs-v-2", - "version": "0.0.0", - "private": true, - "license": "Apache-2.0", - "scripts": { - "docusaurus": "docusaurus", - "_init": "cat src/intro_header.txt ../README.md > docs/intro.md", - "start": "yarn run _init && yarn run generate:smart && 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:full": "yarn run _init && yarn run generate:all && NODE_OPTIONS='--max-old-space-size=8192' NODE_ENV=development docusaurus start", - "stop": "pkill -9 -f 'docusaurus start' || pkill -9 -f 'docusaurus serve' || echo 'No docusaurus server running'", - "build": "yarn run _init && yarn run generate:smart && NODE_OPTIONS='--max-old-space-size=8192' docusaurus build", - "build:full": "yarn run _init && yarn run generate:all && NODE_OPTIONS='--max-old-space-size=8192' 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", - "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", - "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:smart": "node scripts/generate-if-changed.mjs", - "generate:all": "node scripts/generate-if-changed.mjs --force", - "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", - "eslint": "eslint .", - "lint:docs-links": "node scripts/lint-docs-links.mjs", - "version:add": "node scripts/manage-versions.mjs add", - "version:remove": "node scripts/manage-versions.mjs remove", - "version:add:docs": "node scripts/manage-versions.mjs add docs", - "version:add:admin_docs": "node scripts/manage-versions.mjs add admin_docs", - "version:add:developer_docs": "node scripts/manage-versions.mjs add developer_docs", - "version:add:components": "node scripts/manage-versions.mjs add components", - "version:remove:docs": "node scripts/manage-versions.mjs remove docs", - "version:remove:admin_docs": "node scripts/manage-versions.mjs remove admin_docs", - "version:remove:developer_docs": "node scripts/manage-versions.mjs remove developer_docs", - "version:remove:components": "node scripts/manage-versions.mjs remove components" - }, - "dependencies": { - "@ant-design/icons": "^6.2.3", - "@docusaurus/core": "^3.10.1", - "@docusaurus/faster": "^3.10.1", - "@docusaurus/plugin-client-redirects": "^3.10.1", - "@docusaurus/preset-classic": "3.10.1", - "@docusaurus/theme-live-codeblock": "^3.10.1", - "@docusaurus/theme-mermaid": "^3.10.1", - "@emotion/core": "^11.0.0", - "@emotion/react": "^11.13.3", - "@emotion/styled": "^11.14.1", - "@fontsource/fira-code": "^5.2.7", - "@fontsource/ibm-plex-mono": "^5.2.7", - "@fontsource/inter": "^5.2.8", - "@mdx-js/react": "^3.1.1", - "@saucelabs/theme-github-codeblock": "^0.3.0", - "@storybook/addon-docs": "^8.6.18", - "@storybook/blocks": "^8.6.15", - "@storybook/channels": "^8.6.18", - "@storybook/client-logger": "^8.6.18", - "@storybook/components": "^8.6.18", - "@storybook/core": "^8.6.18", - "@storybook/core-events": "^8.6.18", - "@storybook/csf": "^0.1.13", - "@storybook/docs-tools": "^8.6.18", - "@storybook/preview-api": "^8.6.18", - "@storybook/theming": "^8.6.15", - "@superset-ui/core": "^0.20.4", - "@swc/core": "^1.15.33", - "antd": "^6.3.7", - "baseline-browser-mapping": "^2.10.29", - "caniuse-lite": "^1.0.30001792", - "docusaurus-plugin-openapi-docs": "^5.0.2", - "docusaurus-theme-openapi-docs": "^5.0.2", - "js-yaml": "^4.1.1", - "js-yaml-loader": "^1.2.2", - "json-bigint": "^1.0.0", - "prism-react-renderer": "^2.4.1", - "react": "^18.3.1", - "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.18", - "swagger-ui-react": "^5.32.5", - "swc-loader": "^0.2.7", - "tinycolor2": "^1.4.2", - "unist-util-visit": "^5.1.0" - }, - "devDependencies": { - "@docusaurus/module-type-aliases": "^3.10.1", - "@docusaurus/tsconfig": "^3.10.1", - "@eslint/js": "^9.39.2", - "@types/js-yaml": "^4.0.9", - "@types/react": "^19.1.8", - "@typescript-eslint/eslint-plugin": "^8.59.3", - "@typescript-eslint/parser": "^8.59.3", - "eslint": "^9.39.2", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.5", - "eslint-plugin-react": "^7.37.5", - "globals": "^17.6.0", - "prettier": "^3.8.3", - "typescript": "~6.0.3", - "typescript-eslint": "^8.59.3", - "webpack": "^5.106.2" - }, - "browserslist": { - "production": [ - ">0.5%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "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" -} diff --git a/docs/plugins/remark-localize-badges.mjs b/docs/plugins/remark-localize-badges.mjs deleted file mode 100644 index 757b400b350..00000000000 --- a/docs/plugins/remark-localize-badges.mjs +++ /dev/null @@ -1,286 +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. - */ - -/** - * Remark plugin to localize badge images from shields.io and similar services. - * - * This plugin downloads badge SVGs at build time and serves them locally, - * avoiding external dependencies and caching issues with dynamic badges. - * - * Inspired by Apache Commons' fixshields.py approach. - */ - -import { visit } from 'unist-util-visit'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as crypto from 'crypto'; - -// Badge domains to localize (always localize all URLs from these domains) -const BADGE_DOMAINS = [ - 'img.shields.io', - 'badge.fury.io', - 'codecov.io', - 'badgen.net', - 'nodei.co', -]; - -// Patterns for badge URLs on other domains (e.g., GitHub Actions badges) -const BADGE_PATH_PATTERNS = [ - /github\.com\/.*\/actions\/workflows\/.*\/badge\.svg/, - /github\.com\/.*\/badge\.svg/, -]; - -// Cache for downloaded badges (persists across files in a single build) -const badgeCache = new Map(); - -// Track in-flight downloads to prevent duplicate concurrent requests -const inFlightDownloads = new Map(); - -// Track if we've already ensured the badges directory exists -let badgesDirCreated = false; - -// Retry configuration -const MAX_RETRIES = 3; -const RETRY_DELAY_MS = 1000; - -/** - * Sleep for a given number of milliseconds - */ -function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -/** - * Generate a stable filename for a badge URL - */ -function getBadgeFilename(url) { - const hash = crypto.createHash('md5').update(url).digest('hex').slice(0, 12); - // Extract a readable name from the URL - const urlPath = new URL(url).pathname; - const readablePart = urlPath - .replace(/^\//, '') - .replace(/[^a-zA-Z0-9-]/g, '_') - .slice(0, 40); - return `${readablePart}_${hash}.svg`; -} - -/** - * Check if a URL is a badge we should localize - */ -function isBadgeUrl(url) { - if (!url) return false; - try { - const parsed = new URL(url); - // Check if it's from a known badge domain - if (BADGE_DOMAINS.some(domain => parsed.hostname.includes(domain))) { - return true; - } - // Check if it matches a badge path pattern - return BADGE_PATH_PATTERNS.some(pattern => pattern.test(url)); - } catch { - return false; - } -} - -/** - * Fetch a badge with retry logic - */ -async function fetchWithRetry(url, retries = MAX_RETRIES) { - let lastError; - - for (let attempt = 1; attempt <= retries; attempt++) { - try { - const response = await fetch(url, { - headers: { - // Some services need a user agent - 'User-Agent': 'Mozilla/5.0 (compatible; DocusaurusBuild/1.0)', - Accept: 'image/svg+xml,image/*,*/*', - }, - // Follow redirects - redirect: 'follow', - // Add timeout to prevent hanging - signal: AbortSignal.timeout(30000), - }); - - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - - return response; - } catch (error) { - lastError = error; - if (attempt < retries) { - const delay = RETRY_DELAY_MS * attempt; // Exponential backoff - console.log( - `[remark-localize-badges] Retry ${attempt}/${retries} for ${url} after ${delay}ms...`, - ); - await sleep(delay); - } - } - } - - throw lastError; -} - -/** - * Download a badge and return the local path - */ -async function downloadBadge(url, staticDir) { - // Check memory cache first - if (badgeCache.has(url)) { - return badgeCache.get(url); - } - - const badgesDir = path.join(staticDir, 'badges'); - - // Ensure badges directory exists - if (!badgesDirCreated) { - fs.mkdirSync(badgesDir, { recursive: true }); - badgesDirCreated = true; - } - - const filename = getBadgeFilename(url); - const localPath = path.join(badgesDir, filename); - const webPath = `/badges/${filename}`; - - // Check if already downloaded in a previous build or by another concurrent request - if (fs.existsSync(localPath)) { - badgeCache.set(url, webPath); - return webPath; - } - - // Check if there's already an in-flight download for this URL - // This prevents duplicate concurrent downloads of the same badge - if (inFlightDownloads.has(url)) { - return inFlightDownloads.get(url); - } - - // Create the download promise and store it - const downloadPromise = (async () => { - // Double-check file existence after acquiring the "lock" - if (fs.existsSync(localPath)) { - badgeCache.set(url, webPath); - return webPath; - } - - console.log(`[remark-localize-badges] Downloading: ${url}`); - - try { - const response = await fetchWithRetry(url); - - const contentType = response.headers.get('content-type') || ''; - const content = await response.text(); - - // Validate it's actually an SVG or image - if ( - !contentType.includes('svg') && - !contentType.includes('image') && - !content.trim().startsWith(' { - if (isBadgeUrl(node.url)) { - promises.push( - downloadBadge(node.url, staticDir).then(localPath => { - node.url = localPath; - }), - ); - } - }); - - // Also handle HTML img tags in raw HTML or JSX - visit(tree, ['html', 'jsx'], node => { - if (!node.value) return; - - // Find img src attributes pointing to badge URLs - const imgRegex = /]+src=["']([^"']+)["'][^>]*>/gi; - let match; - - while ((match = imgRegex.exec(node.value)) !== null) { - const url = match[1]; - if (isBadgeUrl(url)) { - promises.push( - downloadBadge(url, staticDir).then(localPath => { - node.value = node.value.replace(url, localPath); - }), - ); - } - } - }); - - // Also handle markdown link images: [![alt](img-url)](link-url) - visit(tree, 'link', node => { - if (node.children) { - node.children.forEach(child => { - if (child.type === 'image' && isBadgeUrl(child.url)) { - promises.push( - downloadBadge(child.url, staticDir).then(localPath => { - child.url = localPath; - }), - ); - } - }); - } - }); - - // Wait for all downloads to complete - await Promise.all(promises); - }; -} diff --git a/docs/plugins/remark-tech-article-schema.mjs b/docs/plugins/remark-tech-article-schema.mjs deleted file mode 100644 index 44c505ac3fb..00000000000 --- a/docs/plugins/remark-tech-article-schema.mjs +++ /dev/null @@ -1,153 +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. - */ - -// Note: visit from unist-util-visit is available if needed for tree traversal - -/** - * Remark plugin that automatically injects TechArticle schema import and component - * into documentation MDX files based on frontmatter. - * - * This enables rich snippets for technical documentation in search results. - * - * Frontmatter options: - * - title: (required) Article headline - * - description: (required) Article description - * - keywords: (optional) Array of keywords - * - seo_proficiency: (optional) 'Beginner' or 'Expert', defaults to 'Beginner' - * - seo_schema: (optional) Set to false to disable schema injection - */ -export default function remarkTechArticleSchema() { - return (tree, file) => { - const frontmatter = file.data.frontMatter || {}; - - // Skip if explicitly disabled or missing required fields - if (frontmatter.seo_schema === false) { - return; - } - - // Only add schema if we have title and description - if (!frontmatter.title || !frontmatter.description) { - return; - } - - const title = frontmatter.title; - const description = frontmatter.description; - const keywords = Array.isArray(frontmatter.keywords) ? frontmatter.keywords : []; - const proficiencyLevel = frontmatter.seo_proficiency || 'Beginner'; - - // Create the import statement - const importNode = { - type: 'mdxjsEsm', - value: `import TechArticleSchema from '@site/src/components/TechArticleSchema';`, - data: { - estree: { - type: 'Program', - sourceType: 'module', - body: [ - { - type: 'ImportDeclaration', - specifiers: [ - { - type: 'ImportDefaultSpecifier', - local: { type: 'Identifier', name: 'TechArticleSchema' }, - }, - ], - source: { - type: 'Literal', - value: '@site/src/components/TechArticleSchema', - }, - }, - ], - }, - }, - }; - - // Create the component node for MDX - const componentNode = { - type: 'mdxJsxFlowElement', - name: 'TechArticleSchema', - attributes: [ - { - type: 'mdxJsxAttribute', - name: 'title', - value: title, - }, - { - type: 'mdxJsxAttribute', - name: 'description', - value: description, - }, - ...(keywords.length > 0 - ? [ - { - type: 'mdxJsxAttribute', - name: 'keywords', - value: { - type: 'mdxJsxAttributeValueExpression', - value: JSON.stringify(keywords), - data: { - estree: { - type: 'Program', - sourceType: 'module', - body: [ - { - type: 'ExpressionStatement', - expression: { - type: 'ArrayExpression', - elements: keywords.map((k) => ({ - type: 'Literal', - value: k, - })), - }, - }, - ], - }, - }, - }, - }, - ] - : []), - ...(proficiencyLevel !== 'Beginner' - ? [ - { - type: 'mdxJsxAttribute', - name: 'proficiencyLevel', - value: proficiencyLevel, - }, - ] - : []), - ], - children: [], - }; - - // Insert import at the beginning - tree.children.unshift(importNode); - - // Find the first heading and insert component after it - let insertIndex = 1; // Default: after import - for (let i = 1; i < tree.children.length; i++) { - if (tree.children[i].type === 'heading') { - insertIndex = i + 1; - break; - } - } - - tree.children.splice(insertIndex, 0, componentNode); - }; -} diff --git a/docs/plugins/robots-txt-plugin.js b/docs/plugins/robots-txt-plugin.js deleted file mode 100644 index 0b9bf348a12..00000000000 --- a/docs/plugins/robots-txt-plugin.js +++ /dev/null @@ -1,83 +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. - */ - -/* eslint-disable @typescript-eslint/no-require-imports */ -const fs = require('fs'); -const path = require('path'); -/* eslint-enable @typescript-eslint/no-require-imports */ - -/** - * Docusaurus plugin to generate robots.txt during build - * Configuration is passed via plugin options - */ -module.exports = function robotsTxtPlugin(context, options = {}) { - const { siteConfig } = context; - const { - policies = [{ userAgent: '*', allow: '/' }], - additionalSitemaps = [], - } = options; - - return { - name: 'robots-txt-plugin', - - async postBuild({ outDir }) { - const sitemapUrl = `${siteConfig.url}/sitemap.xml`; - - // Build robots.txt content - const lines = []; - - // Add policies - for (const policy of policies) { - lines.push(`User-agent: ${policy.userAgent}`); - - if (policy.allow) { - const allows = Array.isArray(policy.allow) ? policy.allow : [policy.allow]; - for (const allow of allows) { - lines.push(`Allow: ${allow}`); - } - } - - if (policy.disallow) { - const disallows = Array.isArray(policy.disallow) ? policy.disallow : [policy.disallow]; - for (const disallow of disallows) { - lines.push(`Disallow: ${disallow}`); - } - } - - if (policy.crawlDelay) { - lines.push(`Crawl-delay: ${policy.crawlDelay}`); - } - - lines.push(''); // Empty line between policies - } - - // Add sitemaps - lines.push(`Sitemap: ${sitemapUrl}`); - for (const sitemap of additionalSitemaps) { - lines.push(`Sitemap: ${sitemap}`); - } - - // Write robots.txt - const robotsPath = path.join(outDir, 'robots.txt'); - fs.writeFileSync(robotsPath, lines.join('\n')); - - console.log('Generated robots.txt'); - }, - }; -}; diff --git a/docs/scripts/convert-api-sidebar.mjs b/docs/scripts/convert-api-sidebar.mjs deleted file mode 100644 index f3db2a125fa..00000000000 --- a/docs/scripts/convert-api-sidebar.mjs +++ /dev/null @@ -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, '..', 'developer_docs', 'api', 'sidebar.ts'); -const sidebarJsPath = path.join(__dirname, '..', 'developer_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'); diff --git a/docs/scripts/extract_custom_errors.py b/docs/scripts/extract_custom_errors.py deleted file mode 100644 index 35aee1cdbc2..00000000000 --- a/docs/scripts/extract_custom_errors.py +++ /dev/null @@ -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() diff --git a/docs/scripts/fix-openapi-spec.py b/docs/scripts/fix-openapi-spec.py deleted file mode 100644 index dc850a143ef..00000000000 --- a/docs/scripts/fix-openapi-spec.py +++ /dev/null @@ -1,853 +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") - - # DashboardChartCustomizationsConfigUpdateSchema (dashboards/schemas.py) - if "DashboardChartCustomizationsConfigUpdateSchema" not in schemas: - schemas["DashboardChartCustomizationsConfigUpdateSchema"] = { - "type": "object", - "properties": { - "deleted": { - "type": "array", - "items": {"type": "string"}, - "description": "List of deleted chart customization IDs.", - }, - "modified": { - "type": "array", - "items": {"type": "object"}, - "description": "List of modified chart customizations.", - }, - "reordered": { - "type": "array", - "items": {"type": "string"}, - "description": "List of chart customization IDs in new order.", - }, - }, - } - fixed.append("DashboardChartCustomizationsConfigUpdateSchema") - - # 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.", - "Themes": "Manage UI themes for customizing Superset's appearance.", - "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() diff --git a/docs/scripts/generate-api-index.mjs b/docs/scripts/generate-api-index.mjs deleted file mode 100644 index 2fd9fddac63..00000000000 --- a/docs/scripts/generate-api-index.mjs +++ /dev/null @@ -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, '..', 'developer_docs', 'api', 'sidebar.js'); -const OUTPUT_PATH = path.join(__dirname, '..', 'developer_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. - - - Each endpoint includes ready-to-use code samples in cURL, Python, and JavaScript. - The sidebar includes Schema definitions for detailed data model documentation. - - } - 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}](/developer-docs/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 += `
\n`; - mdx += `${tag} (${endpoints.length} endpoints) — ${description}\n\n`; - mdx += `| Method | Endpoint | Description |\n`; - mdx += `|--------|----------|-------------|\n`; - - for (const ep of endpoints) { - mdx += `| \`${ep.method}\` | [${ep.summary}](/developer-docs/api/${ep.slug}) | \`${ep.path}\` |\n`; - } - - mdx += `\n
\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 += `
\n`; - mdx += `${tag} (${endpoints.length} endpoints) — ${description}\n\n`; - mdx += `| Method | Endpoint | Description |\n`; - mdx += `|--------|----------|-------------|\n`; - - for (const ep of endpoints) { - mdx += `| \`${ep.method}\` | [${ep.summary}](/developer-docs/api/${ep.slug}) | \`${ep.path}\` |\n`; - } - - mdx += `\n
\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(); diff --git a/docs/scripts/generate-api-tag-pages.mjs b/docs/scripts/generate-api-tag-pages.mjs deleted file mode 100644 index 23ecda248b8..00000000000 --- a/docs/scripts/generate-api-tag-pages.mjs +++ /dev/null @@ -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, '..', 'developer_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(); diff --git a/docs/scripts/generate-database-docs.mjs b/docs/scripts/generate-database-docs.mjs deleted file mode 100644 index 93cb5d766b8..00000000000 --- a/docs/scripts/generate-database-docs.mjs +++ /dev/null @@ -1,1267 +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. - */ - -/** - * This script generates database documentation data from engine spec metadata. - * It outputs a JSON file that can be imported by React components for rendering. - * - * Usage: node scripts/generate-database-docs.mjs - * - * The script can run in two modes: - * 1. With Flask app (full diagnostics) - requires superset to be installed - * 2. Fallback mode (documentation only) - parses engine spec `metadata` attributes via AST - */ - -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, '../..'); -const DOCS_DIR = path.resolve(__dirname, '..'); -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 - */ -function tryRunFullScript() { - try { - console.log('Attempting to run lib.py with Flask context...'); - const pythonCode = ` -import sys -import json -sys.path.insert(0, '.') -from superset.app import create_app -from superset.db_engine_specs.lib import generate_yaml_docs -app = create_app() -with app.app_context(): - docs = generate_yaml_docs() - print(json.dumps(docs, default=str)) -`; - const result = spawnSync('python', ['-c', pythonCode], { - cwd: ROOT_DIR, - encoding: 'utf-8', - timeout: 60000, - maxBuffer: 10 * 1024 * 1024, - env: { ...process.env, SUPERSET_SECRET_KEY: 'docs-build-key' }, - }); - - if (result.error) { - throw result.error; - } - if (result.status !== 0) { - throw new Error(result.stderr || 'Python script failed'); - } - return JSON.parse(result.stdout); - } catch (error) { - console.log('Full script execution failed, using fallback mode...'); - console.log(' Reason:', error.message?.split('\n')[0] || 'Unknown error'); - return null; - } -} - -/** - * Extract metadata from individual engine spec files using AST parsing - * This is the preferred approach - reads directly from spec.metadata attributes - * Supports metadata inheritance - child classes inherit and merge with parent metadata - */ -function extractEngineSpecMetadata() { - console.log('Extracting metadata from engine spec files...'); - console.log(` ROOT_DIR: ${ROOT_DIR}`); - - try { - const pythonCode = ` -import sys -import json -import ast -import os - -def eval_node(node): - """Safely evaluate an AST node as a Python literal.""" - if node is None: - return None - if isinstance(node, ast.Constant): - return node.value - elif isinstance(node, ast.List): - return [eval_node(e) for e in node.elts] - elif isinstance(node, ast.Dict): - result = {} - for k, v in zip(node.keys, node.values): - if k is not None: - key = eval_node(k) - if key is not None: - result[key] = eval_node(v) - return result - elif isinstance(node, ast.Name): - # Handle True, False, None constants - if node.id == 'True': - return True - elif node.id == 'False': - return False - elif node.id == 'None': - return None - return node.id - elif isinstance(node, ast.Attribute): - # Handle DatabaseCategory.SOMETHING - return just the attribute name - return node.attr - elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add): - left, right = eval_node(node.left), eval_node(node.right) - if isinstance(left, str) and isinstance(right, str): - return left + right - return None - elif isinstance(node, ast.Tuple): - return tuple(eval_node(e) for e in node.elts) - elif isinstance(node, ast.JoinedStr): - # f-strings - just return a placeholder - return "" - return None - -def static_return_bool(func_node): - """ - Statically resolve a method's return value to a bool when possible. - - Returns True/False for functions whose body is (effectively) a single - \`return True\` / \`return False\` — allowing a leading docstring and - ignoring pure-comment/pass statements. Returns None for anything more - complex (conditional returns, computed values, no return, etc.). - - Used by \`has_implicit_cancel\` handling: \`diagnose()\` in lib.py calls - the method and checks the return value, so an override that explicitly - returns False must NOT be treated as enabling query cancelation. - """ - returns = [] - other_logic = False - docstring_skipped = False - for stmt in func_node.body: - # Skip docstring (only the FIRST expression statement that is a - # string constant — later bare string literals are not docstrings - # and should count as non-trivial logic). - if (not docstring_skipped - and isinstance(stmt, ast.Expr) - and isinstance(stmt.value, ast.Constant) - and isinstance(stmt.value.value, str)): - docstring_skipped = True - continue - if isinstance(stmt, ast.Pass): - continue - if isinstance(stmt, ast.Return): - returns.append(stmt) - continue - # Any other statement (if/for/assign/etc.) means control flow is - # non-trivial; bail out to be conservative. - other_logic = True - break - if other_logic or len(returns) != 1: - return None - val = eval_node(returns[0].value) - return val if isinstance(val, bool) else None - - -def deep_merge(base, override): - """Deep merge two dictionaries. Override values take precedence.""" - if base is None: - return override - if override is None: - return base - if not isinstance(base, dict) or not isinstance(override, dict): - return override - - # Fields that should NOT be inherited from parent classes - # - compatible_databases: Each class defines its own compatible DBs - # - categories: Each class defines its own categories (not extended from parent) - NON_INHERITABLE_FIELDS = {'compatible_databases', 'categories'} - - result = base.copy() - # Remove non-inheritable fields from base (they should only come from the class that defines them) - for field in NON_INHERITABLE_FIELDS: - result.pop(field, None) - - for key, value in override.items(): - if key in result and isinstance(result[key], dict) and isinstance(value, dict): - result[key] = deep_merge(result[key], value) - elif key in result and isinstance(result[key], list) and isinstance(value, list): - # Extend lists from parent (e.g., drivers) - result[key] = result[key] + value - else: - result[key] = value - return result - -databases = {} -specs_dir = 'superset/db_engine_specs' -errors = [] -debug_info = { - "cwd": os.getcwd(), - "specs_dir_exists": os.path.isdir(specs_dir), - "files_checked": 0, - "classes_found": 0, - "classes_with_metadata": 0, - "inherited_metadata": 0, -} - -if not os.path.isdir(specs_dir): - print(json.dumps({"error": f"Directory not found: {specs_dir}", "cwd": os.getcwd()})) - sys.exit(1) - -# Capability flag attributes with their defaults from BaseEngineSpec -CAP_ATTR_DEFAULTS = { - 'supports_dynamic_schema': False, - 'supports_catalog': False, - 'supports_dynamic_catalog': False, - 'disable_ssh_tunneling': False, - 'supports_file_upload': True, - 'allows_joins': True, - 'allows_subqueries': True, -} - -# Maps source capability attribute -> output field name used in databases.json. -# When a cap attr is assigned an unevaluable expression (e.g. -# allows_joins = is_feature_enabled("DRUID_JOINS")), the JS layer uses this -# mapping to preserve the corresponding field from the previously-generated -# JSON rather than silently inheriting an incorrect parent default. -CAP_ATTR_TO_OUTPUT_FIELD = { - 'allows_joins': 'joins', - 'allows_subqueries': 'subqueries', - 'supports_dynamic_schema': 'supports_dynamic_schema', - 'supports_catalog': 'supports_catalog', - 'supports_dynamic_catalog': 'supports_dynamic_catalog', - 'disable_ssh_tunneling': 'ssh_tunneling', - 'supports_file_upload': 'supports_file_upload', -} - -# Methods that indicate a capability when overridden by a non-BaseEngineSpec class. -# Mirrors the has_custom_method checks in superset/db_engine_specs/lib.py. -# cancel_query / has_implicit_cancel -> query_cancelation -# (diagnose() checks cancel_query override OR has_implicit_cancel() == True; -# base has_implicit_cancel returns False, so overriding it is the static -# equivalent of that method returning True. get_cancel_query_id is NOT -# part of the diagnose() heuristic and is intentionally excluded.) -# estimate_statement_cost / estimate_query_cost -> query_cost_estimation -# impersonate_user / update_impersonation_config / get_url_for_impersonation -> user_impersonation -# validate_sql -> sql_validation (not used yet; validation is engine-based) -CAP_METHODS = { - 'cancel_query', 'has_implicit_cancel', - 'estimate_statement_cost', 'estimate_query_cost', - 'impersonate_user', 'update_impersonation_config', 'get_url_for_impersonation', - 'validate_sql', -} - -# Only the literal BaseEngineSpec is excluded from method-override tracking. -# Intermediate base classes (e.g. PrestoBaseEngineSpec) do count as overrides. -TRUE_BASE_CLASS = 'BaseEngineSpec' - -# First pass: collect all class info (name, bases, metadata, cap_attrs, direct_methods) -class_info = {} # class_name -> {bases: [], metadata: {}, engine_name: str, filename: str, ...} - -for filename in sorted(os.listdir(specs_dir)): - if not filename.endswith('.py') or filename in ('__init__.py', 'lib.py', 'lint_metadata.py'): - continue - - debug_info["files_checked"] += 1 - filepath = os.path.join(specs_dir, filename) - try: - with open(filepath) as f: - source = f.read() - tree = ast.parse(source) - - for node in ast.walk(tree): - if not isinstance(node, ast.ClassDef): - continue - - # Get base class names - base_names = [] - for b in node.bases: - if isinstance(b, ast.Name): - base_names.append(b.id) - elif isinstance(b, ast.Attribute): - base_names.append(b.attr) - - is_engine_spec = any('EngineSpec' in name or 'Mixin' in name for name in base_names) - if not is_engine_spec: - continue - - # Extract class attributes - engine_name = None - engine_attr = None - metadata = None - cap_attrs = {} # capability flag attributes defined directly in this class - # Cap attrs assigned via expressions we can't statically resolve - # (e.g. is_feature_enabled("FLAG")). Tracked so the JS layer can - # fall back to the previously-generated databases.json value - # rather than inherit a parent default that would be wrong. - unresolved_cap_attrs = set() - direct_methods = set() # capability methods defined directly in this class - - for item in node.body: - if isinstance(item, ast.Assign): - for target in item.targets: - if not isinstance(target, ast.Name): - continue - if target.id == 'engine_name': - val = eval_node(item.value) - if isinstance(val, str): - engine_name = val - elif target.id == 'engine': - val = eval_node(item.value) - if isinstance(val, str): - engine_attr = val - elif target.id == 'metadata': - metadata = eval_node(item.value) - elif target.id in CAP_ATTR_DEFAULTS: - val = eval_node(item.value) - if isinstance(val, bool): - cap_attrs[target.id] = val - else: - # Unevaluable expression — defer to JS fallback. - unresolved_cap_attrs.add(target.id) - elif isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)): - if item.name in CAP_METHODS: - # has_implicit_cancel is special: diagnose() uses the - # method's RETURN VALUE, not just its presence. If the - # override statically returns False, treat it as if - # the method weren't overridden so query_cancelation - # matches diagnose(). Unresolvable / True / anything - # else falls through as an override (conservative). - if item.name == 'has_implicit_cancel': - if static_return_bool(item) is False: - continue - direct_methods.add(item.name) - - # Check for engine attribute with non-empty value to distinguish - # true base classes from product classes like OceanBaseEngineSpec - has_non_empty_engine = engine_attr is not None and bool(engine_attr) - - # True base classes: end with BaseEngineSpec AND don't define engine - # or have empty engine (like PostgresBaseEngineSpec with engine = "") - is_true_base = ( - node.name.endswith('BaseEngineSpec') and not has_non_empty_engine - ) or 'Mixin' in node.name - - # Store class info for inheritance resolution - class_info[node.name] = { - 'bases': base_names, - 'metadata': metadata, - 'engine_name': engine_name, - 'engine': engine_attr, - 'filename': filename, - 'is_base_or_mixin': is_true_base, - 'cap_attrs': cap_attrs, - 'unresolved_cap_attrs': unresolved_cap_attrs, - 'direct_methods': direct_methods, - } - except Exception as e: - errors.append(f"{filename}: {str(e)}") - -# Second pass: resolve inheritance and build final metadata + capability flags - -def get_inherited_metadata(class_name, visited=None): - """Recursively get metadata from parent classes.""" - if visited is None: - visited = set() - if class_name in visited: - return {} # Prevent circular inheritance - visited.add(class_name) - - info = class_info.get(class_name) - if not info: - return {} - - # Start with parent metadata - inherited = {} - for base_name in info['bases']: - parent_metadata = get_inherited_metadata(base_name, visited.copy()) - if parent_metadata: - inherited = deep_merge(inherited, parent_metadata) - - # Merge with own metadata (own takes precedence) - if info['metadata']: - inherited = deep_merge(inherited, info['metadata']) - - return inherited - -def get_resolved_caps(class_name, visited=None): - """ - Resolve capability flags and method overrides with inheritance. - - Returns (attr_values, unresolved, methods): - - attr_values: {attr: bool} for attrs where the nearest MRO assignment - was a literal bool. Defaults are applied at the call site. - - unresolved: attrs where the nearest MRO assignment was an unevaluable - expression (e.g. is_feature_enabled("FLAG")). The JS layer falls - back to the previously-generated JSON value for these. - - methods: capability methods defined directly in some non-base ancestor, - matching the has_custom_method() logic in db_engine_specs/lib.py. - - attr_values and unresolved are disjoint — an attr is in at most one. - """ - if visited is None: - visited = set() - if class_name in visited: - return {}, set(), set() - visited.add(class_name) - - info = class_info.get(class_name) - if not info: - return {}, set(), set() - - attr_values = {} - unresolved = set() - resolved_methods = set() - - # Collect from parents, iterating right-to-left so leftmost bases win - # (matches Python MRO: for class C(A, B), A's attributes take precedence). - for base_name in reversed(info['bases']): - p_vals, p_unres, p_meth = get_resolved_caps(base_name, visited.copy()) - # A parent's literal assignments overwrite whatever we inherited so far. - for attr, val in p_vals.items(): - attr_values[attr] = val - unresolved.discard(attr) - # A parent's unresolved assignments likewise take precedence. - for attr in p_unres: - unresolved.add(attr) - attr_values.pop(attr, None) - resolved_methods.update(p_meth) - - # Apply this class's own assignments (override parents). - for attr, val in info['cap_attrs'].items(): - attr_values[attr] = val - unresolved.discard(attr) - for attr in info['unresolved_cap_attrs']: - unresolved.add(attr) - attr_values.pop(attr, None) - - # Accumulate method overrides, but skip the literal BaseEngineSpec - # (its implementations are stubs; only non-base overrides count). - if class_name != TRUE_BASE_CLASS: - resolved_methods.update(info['direct_methods']) - - return attr_values, unresolved, resolved_methods - -for class_name, info in class_info.items(): - # Skip base classes and mixins - if info['is_base_or_mixin']: - continue - - debug_info["classes_found"] += 1 - - # Get final metadata with inheritance - final_metadata = get_inherited_metadata(class_name) - - # Remove compatible_databases if not defined by this class (it's not inheritable) - own_metadata = info['metadata'] or {} - if 'compatible_databases' not in own_metadata and 'compatible_databases' in final_metadata: - del final_metadata['compatible_databases'] - - # Track if we inherited anything - if final_metadata and final_metadata != own_metadata: - debug_info["inherited_metadata"] += 1 - - # Use class name as fallback for engine_name - display_name = info['engine_name'] or class_name.replace('EngineSpec', '').replace('_', ' ') - - if final_metadata and isinstance(final_metadata, dict) and display_name: - debug_info["classes_with_metadata"] += 1 - - # Resolve capability flags from Python source - attr_values, unresolved_caps, cap_methods = get_resolved_caps(class_name) - cap_attrs = dict(CAP_ATTR_DEFAULTS) - cap_attrs.update(attr_values) - engine_attr = info.get('engine') or '' - - entry = { - 'engine': display_name.lower().replace(' ', '_'), - 'engine_name': display_name, - 'module': info['filename'][:-3], # Remove .py extension - 'documentation': final_metadata, - 'time_grains': {}, - 'score': 0, - 'max_score': 0, - # Capability flags read from engine spec class attributes/methods - 'joins': cap_attrs['allows_joins'], - 'subqueries': cap_attrs['allows_subqueries'], - 'supports_dynamic_schema': cap_attrs['supports_dynamic_schema'], - 'supports_catalog': cap_attrs['supports_catalog'], - 'supports_dynamic_catalog': cap_attrs['supports_dynamic_catalog'], - 'ssh_tunneling': not cap_attrs['disable_ssh_tunneling'], - 'supports_file_upload': cap_attrs['supports_file_upload'], - # Method-based flags: True only when a non-base class overrides them. - # Matches diagnose() in lib.py: cancel_query override OR - # has_implicit_cancel() returning True (which, given the base - # returns False, is equivalent to overriding has_implicit_cancel). - 'query_cancelation': bool({'cancel_query', 'has_implicit_cancel'} & cap_methods), - 'query_cost_estimation': bool({'estimate_statement_cost', 'estimate_query_cost'} & cap_methods), - # SQL validation is implemented in external validator classes keyed by engine name - 'sql_validation': engine_attr in {'presto', 'postgresql'}, - 'user_impersonation': bool( - {'impersonate_user', 'update_impersonation_config', 'get_url_for_impersonation'} & cap_methods - ), - } - - # Tell the JS layer which output fields were populated from the - # BaseEngineSpec default because the source assignment was an - # unevaluable expression; those get overridden from existing JSON. - unresolved_fields = sorted( - CAP_ATTR_TO_OUTPUT_FIELD[attr] - for attr in unresolved_caps - if attr in CAP_ATTR_TO_OUTPUT_FIELD - ) - if unresolved_fields: - entry['_unresolved_cap_fields'] = unresolved_fields - - databases[display_name] = entry - -if errors and not databases: - print(json.dumps({"error": "Parse errors", "details": errors, "debug": debug_info}), file=sys.stderr) - -# Print debug info to stderr for troubleshooting -print(json.dumps(debug_info), file=sys.stderr) - -print(json.dumps(databases, default=str)) -`; - const result = spawnSync('python3', ['-c', pythonCode], { - cwd: ROOT_DIR, - encoding: 'utf-8', - timeout: 30000, - maxBuffer: 10 * 1024 * 1024, - }); - - if (result.error) { - throw result.error; - } - // Log debug info from stderr - if (result.stderr) { - console.log('Python debug info:', result.stderr.trim()); - } - if (result.status !== 0) { - throw new Error(result.stderr || 'Python script failed'); - } - const databases = JSON.parse(result.stdout); - if (Object.keys(databases).length === 0) { - throw new Error('No metadata found in engine specs'); - } - - console.log(`Extracted metadata from ${Object.keys(databases).length} engine specs`); - return databases; - } catch (err) { - console.log('Engine spec metadata extraction failed:', err.message); - return null; - } -} - -/** - * Build statistics from the database data - */ -function buildStatistics(databases) { - const stats = { - totalDatabases: Object.keys(databases).length, - withDocumentation: 0, - withConnectionString: 0, - withDrivers: 0, - withAuthMethods: 0, - supportsJoins: 0, - supportsSubqueries: 0, - supportsDynamicSchema: 0, - supportsCatalog: 0, - averageScore: 0, - maxScore: 0, - byCategory: {}, - }; - - let totalScore = 0; - - for (const [name, db] of Object.entries(databases)) { - const docs = db.documentation || {}; - - if (Object.keys(docs).length > 0) stats.withDocumentation++; - if (docs.connection_string || docs.drivers?.length > 0) - stats.withConnectionString++; - if (docs.drivers?.length > 0) stats.withDrivers++; - if (docs.authentication_methods?.length > 0) stats.withAuthMethods++; - if (db.joins) stats.supportsJoins++; - if (db.subqueries) stats.supportsSubqueries++; - if (db.supports_dynamic_schema) stats.supportsDynamicSchema++; - if (db.supports_catalog) stats.supportsCatalog++; - - totalScore += db.score || 0; - if (db.max_score > stats.maxScore) stats.maxScore = db.max_score; - - // Use categories from documentation metadata (computed by Python) - // Each database can belong to multiple categories - const categories = docs.categories || ['OTHER']; - for (const cat of categories) { - // Map category constant names to display names - const categoryDisplayNames = { - 'CLOUD_AWS': 'Cloud - AWS', - 'CLOUD_GCP': 'Cloud - Google', - 'CLOUD_AZURE': 'Cloud - Azure', - 'CLOUD_DATA_WAREHOUSES': 'Cloud Data Warehouses', - 'APACHE_PROJECTS': 'Apache Projects', - 'TRADITIONAL_RDBMS': 'Traditional RDBMS', - 'ANALYTICAL_DATABASES': 'Analytical Databases', - 'SEARCH_NOSQL': 'Search & NoSQL', - 'QUERY_ENGINES': 'Query Engines', - 'TIME_SERIES': 'Time Series Databases', - 'OTHER': 'Other Databases', - 'OPEN_SOURCE': 'Open Source', - 'HOSTED_OPEN_SOURCE': 'Hosted Open Source', - 'PROPRIETARY': 'Proprietary', - }; - const displayName = categoryDisplayNames[cat] || cat; - if (!stats.byCategory[displayName]) { - stats.byCategory[displayName] = []; - } - stats.byCategory[displayName].push(name); - } - } - - stats.averageScore = Math.round(totalScore / stats.totalDatabases); - - return stats; -} - -/** - * Convert database name to a URL-friendly slug - */ -function toSlug(name) { - return name - .toLowerCase() - .replace(/[^a-z0-9]+/g, '-') - .replace(/^-|-$/g, ''); -} - -/** - * Generate MDX content for a single database page - */ -function generateDatabaseMDX(name, db) { - const description = db.documentation?.description || `Documentation for ${name} database connection.`; - const shortDesc = description - .slice(0, 160) - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"'); - - // Inline the database data directly to avoid importing the full databases.json - const inlineData = JSON.stringify(db); - - return `--- -title: ${name} -sidebar_label: ${name} -description: "${shortDesc}" -hide_title: true ---- - -{/* -Licensed to the Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. -*/} - -import { DatabasePage } from '@site/src/components/databases'; - -export const databaseInfo = ${inlineData}; - - -`; -} - -/** - * Generate the index MDX for the databases overview - */ -function generateIndexMDX(statistics, usedFlaskContext = true) { - const fallbackNotice = usedFlaskContext ? '' : ` -:::info Developer Note -This documentation was built without Flask context, so feature diagnostics (scores, time grain support, etc.) -may not reflect actual database capabilities. For full diagnostics, build docs locally with: - -\`\`\`bash -cd docs && npm run gen-db-docs -\`\`\` - -This requires a working Superset development environment. -::: - -`; - - return `--- -title: Connecting to Databases -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. -*/} - -import { DatabaseIndex } from '@site/src/components/databases'; -import databaseData from '@site/src/data/databases.json'; - -# Connecting to Databases - -Superset does not ship bundled with connectivity to databases. The main step in connecting -Superset to a database is to **install the proper database driver(s)** in your environment. - -:::note -You'll need to install the required packages for the database you want to use as your metadata database -as well as the packages needed to connect to the databases you want to access through Superset. -For information about setting up Superset's metadata database, please refer to -installation documentations ([Docker Compose](/admin-docs/installation/docker-compose), [Kubernetes](/admin-docs/installation/kubernetes)) -::: - -## Supported Databases - -Superset supports **${statistics.totalDatabases} databases** with varying levels of feature support. -Click on any database name to see detailed documentation including connection strings, -authentication methods, and configuration options. - - - -## Installing Database Drivers - -Superset requires a Python [DB-API database driver](https://peps.python.org/pep-0249/) -and a [SQLAlchemy dialect](https://docs.sqlalchemy.org/en/20/dialects/) to be installed for -each database engine you want to connect to. - -### Installing Drivers in Docker - -For Docker deployments, create a \`requirements-local.txt\` file in the \`docker\` directory: - -\`\`\`bash -# Create the requirements file -touch ./docker/requirements-local.txt - -# Add your driver (e.g., for PostgreSQL) -echo "psycopg2-binary" >> ./docker/requirements-local.txt -\`\`\` - -Then restart your containers. The drivers will be installed automatically. - -### Installing Drivers with pip - -For non-Docker installations: - -\`\`\`bash -pip install -\`\`\` - -See individual database pages for the specific driver packages needed. - -## Connecting Through the UI - -1. Go to **Settings → Data: Database Connections** -2. Click **+ DATABASE** -3. Select your database type or enter a SQLAlchemy URI -4. Click **Test Connection** to verify -5. Click **Connect** to save - -## Contributing - -To add or update database documentation, add a \`metadata\` attribute to your engine spec class in -\`superset/db_engine_specs/\`. Documentation is auto-generated from these metadata attributes. - -See [METADATA_STATUS.md](https://github.com/apache/superset/blob/master/superset/db_engine_specs/METADATA_STATUS.md) -for the current status of database documentation and the [README](https://github.com/apache/superset/blob/master/superset/db_engine_specs/README.md) for the metadata schema. -${fallbackNotice}`; -} - -const README_PATH = path.join(ROOT_DIR, 'README.md'); -const README_START_MARKER = ''; -const README_END_MARKER = ''; - -/** - * 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. - */ -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(); - 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; - }); - - 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 - 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 = `${name}`; - return ` ${img}`; - }); - - // Use   between logos for spacing (GitHub strips style/class attributes) - return `

-${logoTags.join('  \n')} -

`; -} - -/** - * Update the README.md with generated database logos - */ -function updateReadme(databases) { - if (!fs.existsSync(README_PATH)) { - console.log('README.md not found, skipping update'); - return false; - } - - const content = fs.readFileSync(README_PATH, 'utf-8'); - - // Check if markers exist - if (!content.includes(README_START_MARKER) || !content.includes(README_END_MARKER)) { - console.log('README.md missing database markers, skipping update'); - console.log(` Add ${README_START_MARKER} and ${README_END_MARKER} to enable auto-generation`); - return false; - } - - // Generate new logos section - const logosHtml = generateReadmeLogos(databases); - - // Replace content between markers - const pattern = new RegExp( - `${README_START_MARKER}[\\s\\S]*?${README_END_MARKER}`, - 'g' - ); - const newContent = content.replace( - pattern, - `${README_START_MARKER}\n${logosHtml}\n${README_END_MARKER}` - ); - - if (newContent !== content) { - fs.writeFileSync(README_PATH, newContent); - console.log('Updated README.md database logos'); - return true; - } - - console.log('README.md database logos unchanged'); - 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 - */ -function loadExistingData() { - if (!fs.existsSync(DATA_OUTPUT_FILE)) { - return null; - } - - try { - const content = fs.readFileSync(DATA_OUTPUT_FILE, 'utf-8'); - return JSON.parse(content); - } catch (error) { - console.log('Could not load existing data:', error.message); - return null; - } -} - -/** - * Fall back to the previously-generated databases.json for capability flags - * whose source assignment couldn't be statically resolved (e.g. - * `allows_joins = is_feature_enabled("DRUID_JOINS")`). The Python extractor - * flags these via the internal `_unresolved_cap_fields` marker; without this - * fallback those fields would silently inherit the BaseEngineSpec default - * and disagree with runtime behavior. The marker is stripped before output. - */ -function fallbackUnresolvedCaps(newDatabases, existingData) { - for (const [name, db] of Object.entries(newDatabases)) { - const unresolved = db._unresolved_cap_fields; - if (!unresolved || unresolved.length === 0) { - delete db._unresolved_cap_fields; - continue; - } - const existingDb = existingData?.databases?.[name]; - if (existingDb) { - for (const field of unresolved) { - if (existingDb[field] !== undefined) { - db[field] = existingDb[field]; - } - } - } - delete db._unresolved_cap_fields; - } - return newDatabases; -} - -/** - * Merge new documentation with existing diagnostics - * Preserves score, max_score, and time_grains from existing data (these require - * Flask context to generate and cannot be derived from static source analysis). - * Capability flags (joins, supports_catalog, etc.) are NOT preserved here — they - * are read fresh from the Python engine spec source by extractEngineSpecMetadata(), - * with a separate fallback for expression-based assignments (see fallbackUnresolvedCaps). - */ -function mergeWithExistingDiagnostics(newDatabases, existingData) { - if (!existingData?.databases) return newDatabases; - - // Only preserve fields that require Flask/runtime context to generate - const diagnosticFields = ['score', 'max_score', 'time_grains']; - - for (const [name, db] of Object.entries(newDatabases)) { - const existingDb = existingData.databases[name]; - if (existingDb && existingDb.score > 0) { - // Preserve score/time_grain diagnostics from existing data - for (const field of diagnosticFields) { - if (existingDb[field] !== undefined) { - db[field] = existingDb[field]; - } - } - } - } - - const preserved = Object.values(newDatabases).filter(d => d.score > 0).length; - if (preserved > 0) { - console.log(`Preserved score/time_grains for ${preserved} databases from existing data`); - } - - return newDatabases; -} - -/** - * Main function - */ -async function main() { - console.log('Generating database documentation...\n'); - - // Ensure output directories exist - if (!fs.existsSync(DATA_OUTPUT_DIR)) { - fs.mkdirSync(DATA_OUTPUT_DIR, { recursive: true }); - } - if (!fs.existsSync(MDX_OUTPUT_DIR)) { - fs.mkdirSync(MDX_OUTPUT_DIR, { recursive: true }); - } - - // Load existing data for potential merge - const existingData = loadExistingData(); - - // Try sources in order of preference: - // 1. Full script with Flask context (richest data with diagnostics) - // 2. Engine spec metadata files (works in CI without Flask) - let databases = tryRunFullScript(); - let usedFlaskContext = !!databases; - - if (!databases) { - // Extract from engine spec metadata (preferred for CI) - databases = extractEngineSpecMetadata(); - } - - if (!databases || Object.keys(databases).length === 0) { - console.error('Failed to generate database documentation data.'); - console.error('Could not extract from Flask app or engine spec metadata.'); - process.exit(1); - } - - console.log(`Processed ${Object.keys(databases).length} databases\n`); - - // Check if new data has scores; if not, preserve existing diagnostics - const hasNewScores = Object.values(databases).some((db) => db.score > 0); - if (!hasNewScores && existingData) { - databases = mergeWithExistingDiagnostics(databases, existingData); - } - - // For cap flags assigned via unevaluable expressions (e.g. - // `is_feature_enabled(...)`), prefer the value from a previously-generated - // JSON. Runs regardless of scores since it addresses static-analysis gaps, - // not missing Flask diagnostics. Always strips the internal marker. - databases = fallbackUnresolvedCaps(databases, existingData); - - // Extract and merge custom_errors for troubleshooting documentation - const customErrors = extractCustomErrors(); - mergeCustomErrors(databases, customErrors); - - // Build statistics - const statistics = buildStatistics(databases); - - // Create the final output structure - const output = { - generated: new Date().toISOString(), - statistics, - databases, - }; - - // Write the JSON file (with trailing newline for POSIX compliance) - fs.writeFileSync(DATA_OUTPUT_FILE, JSON.stringify(output, null, 2) + '\n'); - console.log(`Generated: ${path.relative(DOCS_DIR, DATA_OUTPUT_FILE)}`); - - - // Ensure supported directory exists - if (!fs.existsSync(MDX_SUPPORTED_DIR)) { - fs.mkdirSync(MDX_SUPPORTED_DIR, { recursive: true }); - } - - // Clean up old MDX files that are no longer in the database list - console.log(`\nCleaning up old MDX files in ${path.relative(DOCS_DIR, MDX_SUPPORTED_DIR)}/`); - const existingMdxFiles = fs.readdirSync(MDX_SUPPORTED_DIR).filter(f => f.endsWith('.mdx')); - const validSlugs = new Set(Object.keys(databases).map(name => `${toSlug(name)}.mdx`)); - let removedCount = 0; - for (const file of existingMdxFiles) { - if (!validSlugs.has(file)) { - fs.unlinkSync(path.join(MDX_SUPPORTED_DIR, file)); - removedCount++; - } - } - if (removedCount > 0) { - console.log(` Removed ${removedCount} outdated MDX files`); - } - - // Generate individual MDX files for each database in supported/ subdirectory - console.log(`\nGenerating MDX files in ${path.relative(DOCS_DIR, MDX_SUPPORTED_DIR)}/`); - - let mdxCount = 0; - for (const [name, db] of Object.entries(databases)) { - const slug = toSlug(name); - const mdxContent = generateDatabaseMDX(name, db); - const mdxPath = path.join(MDX_SUPPORTED_DIR, `${slug}.mdx`); - fs.writeFileSync(mdxPath, mdxContent); - mdxCount++; - } - console.log(` Generated ${mdxCount} database pages`); - - // Generate index page in parent databases/ directory - const indexContent = generateIndexMDX(statistics, usedFlaskContext); - const indexPath = path.join(MDX_OUTPUT_DIR, 'index.mdx'); - fs.writeFileSync(indexPath, indexContent); - console.log(` Generated index page`); - - // Generate _category_.json for databases/ directory - const categoryJson = { - label: 'Databases', - position: 1, - link: { - type: 'doc', - id: 'databases/index', - }, - }; - fs.writeFileSync( - path.join(MDX_OUTPUT_DIR, '_category_.json'), - JSON.stringify(categoryJson, null, 2) + '\n' - ); - - // Generate _category_.json for supported/ subdirectory (collapsible) - const supportedCategoryJson = { - label: 'Supported Databases', - position: 2, - collapsed: true, - collapsible: true, - }; - fs.writeFileSync( - path.join(MDX_SUPPORTED_DIR, '_category_.json'), - JSON.stringify(supportedCategoryJson, null, 2) + '\n' - ); - console.log(` Generated _category_.json files`); - - // Update README.md database logos (only when explicitly requested) - if (process.env.UPDATE_README === 'true' || process.argv.includes('--update-readme')) { - console.log(''); - updateReadme(databases); - } - - console.log(`\nStatistics:`); - console.log(` Total databases: ${statistics.totalDatabases}`); - console.log(` With documentation: ${statistics.withDocumentation}`); - console.log(` With connection strings: ${statistics.withConnectionString}`); - console.log(` Categories: ${Object.keys(statistics.byCategory).length}`); - - console.log('\nDone!'); -} - -main().catch(console.error); diff --git a/docs/scripts/generate-if-changed.mjs b/docs/scripts/generate-if-changed.mjs deleted file mode 100644 index b44d5e77ff5..00000000000 --- a/docs/scripts/generate-if-changed.mjs +++ /dev/null @@ -1,307 +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. - */ - -/** - * Smart generator wrapper: only runs generators whose input files have changed. - * - * Computes a hash of each generator's input files (stories, engine specs, - * openapi.json, and the generator scripts themselves). Compares against a - * stored cache. Skips generators whose inputs and outputs are unchanged. - * - * Usage: - * node scripts/generate-if-changed.mjs # smart mode (default) - * node scripts/generate-if-changed.mjs --force # force regenerate all - */ - -import { createHash } from 'crypto'; -import { execSync, spawn } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const DOCS_DIR = path.resolve(__dirname, '..'); -const ROOT_DIR = path.resolve(DOCS_DIR, '..'); -const CACHE_FILE = path.join(DOCS_DIR, '.docusaurus', 'generator-hashes.json'); - -const FORCE = process.argv.includes('--force'); - -// Ensure local node_modules/.bin is on PATH (needed for docusaurus CLI) -const localBin = path.join(DOCS_DIR, 'node_modules', '.bin'); -process.env.PATH = `${localBin}${path.delimiter}${process.env.PATH}`; - -// --------------------------------------------------------------------------- -// Generator definitions -// --------------------------------------------------------------------------- - -const GENERATORS = [ - { - name: 'superset-components', - command: 'node scripts/generate-superset-components.mjs', - inputs: [ - { - type: 'glob', - base: path.join(ROOT_DIR, 'superset-frontend/packages/superset-ui-core/src/components'), - pattern: '**/*.stories.tsx', - }, - { - type: 'glob', - base: path.join(ROOT_DIR, 'superset-frontend/packages/superset-core/src'), - pattern: '**/*.stories.tsx', - }, - { type: 'file', path: path.join(DOCS_DIR, 'scripts/generate-superset-components.mjs') }, - { type: 'file', path: path.join(DOCS_DIR, 'src/components/StorybookWrapper.jsx') }, - ], - outputs: [ - path.join(DOCS_DIR, 'developer_docs/components/index.mdx'), - path.join(DOCS_DIR, 'static/data/components.json'), - path.join(DOCS_DIR, 'src/types/apache-superset-core/index.d.ts'), - ], - }, - { - name: 'database-docs', - command: 'node scripts/generate-database-docs.mjs', - inputs: [ - { - type: 'glob', - base: path.join(ROOT_DIR, 'superset/db_engine_specs'), - pattern: '**/*.py', - }, - { type: 'file', path: path.join(DOCS_DIR, 'scripts/generate-database-docs.mjs') }, - ], - outputs: [ - path.join(DOCS_DIR, 'src/data/databases.json'), - path.join(DOCS_DIR, 'docs/databases/supported'), - ], - }, - { - name: 'api-docs', - command: - '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', - inputs: [ - { type: 'file', path: path.join(DOCS_DIR, 'static/resources/openapi.json') }, - { type: 'file', path: path.join(DOCS_DIR, 'scripts/fix-openapi-spec.py') }, - { type: 'file', path: path.join(DOCS_DIR, 'scripts/convert-api-sidebar.mjs') }, - { type: 'file', path: path.join(DOCS_DIR, 'scripts/generate-api-index.mjs') }, - { type: 'file', path: path.join(DOCS_DIR, 'scripts/generate-api-tag-pages.mjs') }, - ], - outputs: [ - path.join(DOCS_DIR, 'docs/api.mdx'), - ], - }, -]; - -// --------------------------------------------------------------------------- -// Hashing utilities -// --------------------------------------------------------------------------- - -function walkDir(dir, pattern) { - const results = []; - if (!fs.existsSync(dir)) return results; - - const regex = globToRegex(pattern); - function walk(currentDir) { - for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) { - const fullPath = path.join(currentDir, entry.name); - if (entry.isDirectory()) { - if (entry.name === 'node_modules' || entry.name === '__pycache__') continue; - walk(fullPath); - } else { - // Normalize to forward slashes so glob patterns work on all platforms - const relativePath = path.relative(dir, fullPath).split(path.sep).join('/'); - if (regex.test(relativePath)) { - results.push(fullPath); - } - } - } - } - walk(dir); - return results.sort(); -} - -function globToRegex(pattern) { - // Simple glob-to-regex: ** matches any path, * matches anything except / - const escaped = pattern - .replace(/[.+^${}()|[\]\\]/g, '\\$&') - .replace(/\*\*/g, '<<>>') - .replace(/\*/g, '[^/]*') - .replace(/<<>>/g, '.*'); - return new RegExp(`^${escaped}$`); -} - -function hashFile(filePath) { - if (!fs.existsSync(filePath)) return 'missing'; - const stat = fs.statSync(filePath); - // Use mtime + size for speed (avoids reading file contents) - return `${stat.mtimeMs}:${stat.size}`; -} - -function computeInputHash(inputs) { - const hash = createHash('md5'); - for (const input of inputs) { - if (input.type === 'file') { - hash.update(`file:${input.path}:${hashFile(input.path)}\n`); - } else if (input.type === 'glob') { - const files = walkDir(input.base, input.pattern); - hash.update(`glob:${input.base}:${input.pattern}:count=${files.length}\n`); - for (const file of files) { - hash.update(` ${path.relative(input.base, file)}:${hashFile(file)}\n`); - } - } - } - return hash.digest('hex'); -} - -function outputsExist(outputs) { - return outputs.every((p) => fs.existsSync(p)); -} - -// --------------------------------------------------------------------------- -// Cache management -// --------------------------------------------------------------------------- - -function loadCache() { - try { - if (fs.existsSync(CACHE_FILE)) { - return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')); - } - } catch { - // Corrupted cache — start fresh - } - return {}; -} - -function saveCache(cache) { - const dir = path.dirname(CACHE_FILE); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2)); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -async function main() { - const cache = loadCache(); - const updatedCache = { ...cache }; - let skipped = 0; - let ran = 0; - - // First pass: determine which generators need to run - // Run independent generators (superset-components, database-docs) in - // parallel, then api-docs sequentially (it depends on docusaurus CLI - // being available, not on other generators). - - const independent = GENERATORS.filter((g) => g.name !== 'api-docs'); - const sequential = GENERATORS.filter((g) => g.name === 'api-docs'); - - // Check and run independent generators in parallel - const parallelPromises = independent.map((gen) => { - const currentHash = computeInputHash(gen.inputs); - const cachedHash = cache[gen.name]; - const hasOutputs = outputsExist(gen.outputs); - - if (!FORCE && currentHash === cachedHash && hasOutputs) { - console.log(` ✓ ${gen.name} — no changes, skipping`); - skipped++; - return Promise.resolve(); - } - - const reason = FORCE - ? 'forced' - : !hasOutputs - ? 'output missing' - : 'inputs changed'; - console.log(` ↻ ${gen.name} — ${reason}, regenerating...`); - ran++; - - return new Promise((resolve, reject) => { - const child = spawn('sh', ['-c', gen.command], { - cwd: DOCS_DIR, - stdio: 'inherit', - env: process.env, - }); - child.on('close', (code) => { - if (code === 0) { - updatedCache[gen.name] = currentHash; - resolve(); - } else { - console.error(` ✗ ${gen.name} failed (exit ${code})`); - reject(new Error(`${gen.name} failed with exit code ${code}`)); - } - }); - child.on('error', (err) => { - console.error(` ✗ ${gen.name} failed to start`); - reject(err); - }); - }); - }); - - await Promise.all(parallelPromises); - - // Run sequential generators (api-docs) - for (const gen of sequential) { - const currentHash = computeInputHash(gen.inputs); - const cachedHash = cache[gen.name]; - const hasOutputs = outputsExist(gen.outputs); - - if (!FORCE && currentHash === cachedHash && hasOutputs) { - console.log(` ✓ ${gen.name} — no changes, skipping`); - skipped++; - continue; - } - - const reason = FORCE - ? 'forced' - : !hasOutputs - ? 'output missing' - : 'inputs changed'; - console.log(` ↻ ${gen.name} — ${reason}, regenerating...`); - ran++; - - try { - execSync(gen.command, { - cwd: DOCS_DIR, - stdio: 'inherit', - timeout: 300_000, - }); - updatedCache[gen.name] = currentHash; - } catch (err) { - console.error(` ✗ ${gen.name} failed`); - throw err; - } - } - - saveCache(updatedCache); - - if (ran === 0) { - console.log(`\nAll ${skipped} generators up-to-date — nothing to do!\n`); - } else { - console.log(`\nDone: ${ran} regenerated, ${skipped} skipped.\n`); - } -} - -console.log('Checking generators for changes...\n'); -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/docs/scripts/generate-superset-components.mjs b/docs/scripts/generate-superset-components.mjs deleted file mode 100644 index 48e55855b5c..00000000000 --- a/docs/scripts/generate-superset-components.mjs +++ /dev/null @@ -1,1704 +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. - */ - -/** - * ============================================================================ - * PHILOSOPHY: STORIES ARE THE SINGLE SOURCE OF TRUTH - * ============================================================================ - * - * When something doesn't render correctly in the docs, FIX THE STORY FIRST. - * Do NOT add special cases or workarounds to this generator. - * - * This generator should be as lightweight as possible - it extracts data from - * stories and passes it through to MDX. All configuration belongs in stories: - * - * - Use `export default { title: '...' }` (inline export, not variable) - * - Name stories `Interactive${ComponentName}` for docs generation - * - Define `args` and `argTypes` at the story level (not meta level) - * - Use `parameters.docs.gallery` for variant grids - * - Use `parameters.docs.sampleChildren` for components needing children - * - Use `parameters.docs.liveExample` for custom code examples - * - Use `parameters.docs.staticProps` for complex props - * - * If a story doesn't work with this generator, fix the story to match the - * expected patterns rather than adding complexity here. - * ============================================================================ - */ - -/** - * This script scans for ALL Storybook stories and generates MDX documentation - * pages for the "Superset Components" section of the developer portal. - * - * Supports multiple source directories with different import paths and categories. - * - * Usage: node scripts/generate-superset-components.mjs - */ - -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const ROOT_DIR = path.resolve(__dirname, '../..'); -const DOCS_DIR = path.resolve(__dirname, '..'); -const OUTPUT_DIR = path.join(DOCS_DIR, 'developer_docs/components'); -const JSON_OUTPUT_PATH = path.join(DOCS_DIR, 'static/data/components.json'); -const TYPES_OUTPUT_DIR = path.join(DOCS_DIR, 'src/types/apache-superset-core'); -const TYPES_OUTPUT_PATH = path.join(TYPES_OUTPUT_DIR, 'index.d.ts'); -const FRONTEND_DIR = path.join(ROOT_DIR, 'superset-frontend'); - -// Source configurations with import paths and categories -const SOURCES = [ - { - name: 'UI Core Components', - path: 'packages/superset-ui-core/src/components', - importPrefix: '@superset/components', - docImportPrefix: '@superset-ui/core/components', - category: 'ui', - enabled: true, - // Components that require complex function props or aren't exported properly - skipComponents: new Set([ - // Complex function props (require callbacks, async data, or render props) - 'AsyncSelect', 'ConfirmStatusChange', 'CronPicker', 'LabeledErrorBoundInput', - 'AsyncAceEditor', 'AsyncEsmComponent', 'TimezoneSelector', - // Not exported from @superset/components index or have export mismatches - 'ActionCell', 'BooleanCell', 'ButtonCell', 'NullCell', 'NumericCell', 'TimeCell', - 'CertifiedBadgeWithTooltip', 'CodeSyntaxHighlighter', 'DynamicTooltip', - 'PopoverDropdown', 'PopoverSection', 'WarningIconWithTooltip', 'RefreshLabel', - // Components with complex nested props (JSX children, overlay, items arrays) - 'Dropdown', 'DropdownButton', - ]), - }, - { - name: 'App Components', - path: 'src/components', - importPrefix: 'src/components', - docImportPrefix: 'src/components', - category: 'app', - enabled: false, // Requires app context (Redux, routing, etc.) - skipComponents: new Set([]), - }, - { - name: 'Dashboard Components', - path: 'src/dashboard/components', - importPrefix: 'src/dashboard/components', - docImportPrefix: 'src/dashboard/components', - category: 'dashboard', - enabled: false, // Requires app context - skipComponents: new Set([]), - }, - { - name: 'Explore Components', - path: 'src/explore/components', - importPrefix: 'src/explore/components', - docImportPrefix: 'src/explore/components', - category: 'explore', - enabled: false, // Requires app context - skipComponents: new Set([]), - }, - { - name: 'Feature Components', - path: 'src/features', - importPrefix: 'src/features', - docImportPrefix: 'src/features', - category: 'features', - enabled: false, // Requires app context - skipComponents: new Set([]), - }, - { - name: 'Filter Components', - path: 'src/filters/components', - importPrefix: 'src/filters/components', - docImportPrefix: 'src/filters/components', - category: 'filters', - enabled: false, // Requires app context - skipComponents: new Set([]), - }, - { - name: 'Chart Plugins', - path: 'packages/superset-ui-demo/storybook/stories/plugins', - importPrefix: '@superset-ui/demo', - docImportPrefix: '@superset-ui/demo', - category: 'chart-plugins', - enabled: false, // Requires chart infrastructure - skipComponents: new Set([]), - }, - { - name: 'Core Packages', - path: 'packages/superset-ui-demo/storybook/stories/superset-ui-chart', - importPrefix: '@superset-ui/core', - docImportPrefix: '@superset-ui/core', - category: 'core-packages', - enabled: false, // Requires specific setup - skipComponents: new Set([]), - }, - { - name: 'Extension Components', - path: 'packages/superset-core/src', - importPrefix: '@apache-superset/core/components', - docImportPrefix: '@apache-superset/core/components', - category: 'extension', - enabled: true, - extensionCompatible: true, - skipComponents: new Set([]), - }, -]; - -// Category mapping from story title prefixes to output directories -const CATEGORY_MAP = { - 'Components/': 'ui', - 'Design System/': 'design-system', - 'Chart Plugins/': 'chart-plugins', - 'Legacy Chart Plugins/': 'legacy-charts', - 'Core Packages/': 'core-packages', - 'Others/': 'utilities', - 'Extension Components/': 'extension', - 'Superset App/': 'app', -}; - -// Documentation-only stories to skip (not actual components) -const SKIP_STORIES = [ - 'Introduction', // Design System intro page - 'Overview', // Category overview pages - 'Examples', // Example collections - 'DesignSystem', // Meta design system page - 'MetadataBarOverview', // Overview page - 'TableOverview', // Overview page - 'Filter Plugins', // Collection story, not a component -]; - - -/** - * Collect the set of value names exported from a barrel file, following - * `export * from './X'` re-exports one level deep. Used to verify that a - * component the docs claim is importable is actually re-exported from the - * public package entry point. - */ -function collectBarrelExports(barrelPath, visited = new Set()) { - const exports = new Set(); - if (!fs.existsSync(barrelPath) || visited.has(barrelPath)) return exports; - visited.add(barrelPath); - - const content = fs.readFileSync(barrelPath, 'utf8'); - - for (const m of content.matchAll(/export\s+\{([\s\S]*?)\}(?:\s+from\s+['"][^'"]+['"])?/g)) { - for (const part of m[1].split(',')) { - const cleaned = part.trim().replace(/^type\s+/, ''); - if (!cleaned) continue; - const asMatch = cleaned.match(/(?:^|\s)as\s+([A-Za-z_]\w*)\s*$/); - if (asMatch) { - exports.add(asMatch[1]); - } else { - const plain = cleaned.match(/^([A-Za-z_]\w*)\s*$/); - if (plain) exports.add(plain[1]); - } - } - } - - for (const m of content.matchAll( - /export\s+(?:const|let|var|function|class)\s+([A-Za-z_]\w*)/g - )) { - exports.add(m[1]); - } - - for (const m of content.matchAll(/export\s+\*\s+from\s+['"]([^'"]+)['"]/g)) { - const target = m[1]; - if (!target.startsWith('.')) continue; - const baseDir = path.dirname(barrelPath); - const candidates = [ - path.resolve(baseDir, `${target}.ts`), - path.resolve(baseDir, `${target}.tsx`), - path.resolve(baseDir, target, 'index.ts'), - path.resolve(baseDir, target, 'index.tsx'), - ]; - const resolved = candidates.find(p => fs.existsSync(p)); - if (resolved) { - for (const name of collectBarrelExports(resolved, visited)) { - exports.add(name); - } - } - } - - return exports; -} - -const SOURCE_PUBLIC_EXPORTS = new Map(); -function getPublicExports(sourceConfig) { - if (SOURCE_PUBLIC_EXPORTS.has(sourceConfig)) { - return SOURCE_PUBLIC_EXPORTS.get(sourceConfig); - } - const sourceDir = path.join(FRONTEND_DIR, sourceConfig.path); - const candidates = [ - path.join(sourceDir, 'index.ts'), - path.join(sourceDir, 'index.tsx'), - ]; - const barrel = candidates.find(p => fs.existsSync(p)); - const result = barrel ? collectBarrelExports(barrel) : null; - SOURCE_PUBLIC_EXPORTS.set(sourceConfig, result); - return result; -} - -/** - * Recursively find all story files in a directory - */ -function walkDir(dir, files = []) { - if (!fs.existsSync(dir)) return files; - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - walkDir(fullPath, files); - } else if (entry.name.endsWith('.stories.tsx') || entry.name.endsWith('.stories.ts')) { - files.push(fullPath); - } - } - return files; -} - -/** - * Find all story files from enabled sources - */ -function findEnabledStoryFiles() { - const files = []; - for (const source of SOURCES.filter(s => s.enabled)) { - const dir = path.join(FRONTEND_DIR, source.path); - const sourceFiles = walkDir(dir, []); - // Attach source config to each file - for (const file of sourceFiles) { - files.push({ file, source }); - } - } - return files; -} - -/** - * Find all story files from disabled sources (for tracking) - */ -function findDisabledStoryFiles() { - const files = []; - for (const source of SOURCES.filter(s => !s.enabled)) { - const dir = path.join(FRONTEND_DIR, source.path); - walkDir(dir, files); - } - return files; -} - -/** - * Parse a story file and extract metadata - */ -function parseStoryFile(filePath, sourceConfig) { - const content = fs.readFileSync(filePath, 'utf-8'); - - // Extract title from story meta (in export default block, not from data objects) - // Look for title in the export default section, which typically starts with "export default {" - const metaMatch = content.match(/export\s+default\s*\{[\s\S]*?title:\s*['"]([^'"]+)['"]/); - const title = metaMatch ? metaMatch[1] : null; - - if (!title) return null; - - // Extract component name (last part of title path) - const titleParts = title.split('/'); - const componentName = titleParts.pop(); - - // Skip documentation-only stories - if (SKIP_STORIES.includes(componentName)) { - return null; - } - - // Skip components in the source's skip list - if (sourceConfig.skipComponents.has(componentName)) { - return null; - } - - // Determine category - use source's default category unless title has a specific prefix - let category = sourceConfig.category; - for (const [prefix, cat] of Object.entries(CATEGORY_MAP)) { - if (title.startsWith(prefix)) { - category = cat; - break; - } - } - - // Extract description from parameters - let description = ''; - const descBlockMatch = content.match( - /description:\s*{\s*component:\s*([\s\S]*?)\s*},?\s*}/ - ); - if (descBlockMatch) { - const descBlock = descBlockMatch[1]; - const stringParts = []; - const stringMatches = descBlock.matchAll(/['"]([^'"]*)['"]/g); - for (const match of stringMatches) { - stringParts.push(match[1]); - } - description = stringParts.join('').trim(); - } - - // Extract story exports - const storyExports = []; - const exportMatches = content.matchAll(/export\s+(?:const|function)\s+(\w+)/g); - for (const match of exportMatches) { - if (match[1] !== 'default') { - storyExports.push(match[1]); - } - } - - // Extract component import path from the story file - // Look for: import ComponentName from './path' (default export) - // or: import { ComponentName } from './path' (named export) - let componentImportPath = null; - let isDefaultExport = true; - - // Try to find default import matching the component name - // Handles: import Component from 'path' - // and: import Component, { OtherExport } from 'path' - const defaultImportMatch = content.match( - new RegExp(`import\\s+${componentName}(?:\\s*,\\s*{[^}]*})?\\s+from\\s+['"]([^'"]+)['"]`) - ); - if (defaultImportMatch) { - componentImportPath = defaultImportMatch[1]; - isDefaultExport = true; - } else { - // Try named import - const namedImportMatch = content.match( - new RegExp(`import\\s*{[^}]*\\b${componentName}\\b[^}]*}\\s*from\\s+['"]([^'"]+)['"]`) - ); - if (namedImportMatch) { - componentImportPath = namedImportMatch[1]; - isDefaultExport = false; - } - } - - // Calculate full import path if we found a relative import - // For UI core components with aliases, keep using the alias - let resolvedImportPath = sourceConfig.importPrefix; - const useAlias = sourceConfig.importPrefix.startsWith('@superset/'); - - if (componentImportPath && componentImportPath.startsWith('.') && !useAlias) { - const storyDir = path.dirname(filePath); - const resolvedPath = path.resolve(storyDir, componentImportPath); - // Get path relative to frontend root, then convert to import path - const frontendRelative = path.relative(FRONTEND_DIR, resolvedPath); - resolvedImportPath = frontendRelative.replace(/\\/g, '/'); - } else if (!componentImportPath && !useAlias) { - // Fallback: assume component is in same dir as story, named same as component - const storyDir = path.dirname(filePath); - const possibleComponentPath = path.join(storyDir, componentName); - const frontendRelative = path.relative(FRONTEND_DIR, possibleComponentPath); - resolvedImportPath = frontendRelative.replace(/\\/g, '/'); - } - - return { - filePath, - title, - titleParts, - componentName, - category, - description, - storyExports, - relativePath: path.relative(ROOT_DIR, filePath), - sourceConfig, - resolvedImportPath, - isDefaultExport, - extensionCompatible: Boolean(sourceConfig.extensionCompatible), - }; -} - -/** - * Parse args content and extract key-value pairs - * Handles strings with apostrophes correctly - */ -function parseArgsContent(argsContent, args) { - // Split into lines and process each line for simple key-value pairs - const lines = argsContent.split('\n'); - - for (let i = 0; i < lines.length; i++) { - const trimmed = lines[i].trim(); - if (!trimmed || trimmed.startsWith('//')) continue; - - // Match: key: value pattern at start of line - const propMatch = trimmed.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*):\s*(.+?)[\s,]*$/); - // Also match key with value on the next line (e.g., prettier wrapping long strings) - const keyOnlyMatch = !propMatch && trimmed.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*):$/); - if (!propMatch && !keyOnlyMatch) continue; - - let key, valueStr; - if (propMatch) { - key = propMatch[1]; - valueStr = propMatch[2]; - } else { - // Value is on the next line - key = keyOnlyMatch[1]; - const nextLine = i + 1 < lines.length ? lines[i + 1].trim().replace(/,\s*$/, '') : ''; - if (!nextLine) continue; - valueStr = nextLine; - i++; // Skip the next line since we consumed it - } - - // Parse the value - // Double-quoted string (handles apostrophes inside) - const doubleQuoteMatch = valueStr.match(/^"([^"]*)"$/); - if (doubleQuoteMatch) { - args[key] = doubleQuoteMatch[1]; - continue; - } - - // Single-quoted string - const singleQuoteMatch = valueStr.match(/^'([^']*)'$/); - if (singleQuoteMatch) { - args[key] = singleQuoteMatch[1]; - continue; - } - - // Template literal - const templateMatch = valueStr.match(/^`([^`]*)`$/); - if (templateMatch) { - args[key] = templateMatch[1].replace(/\s+/g, ' ').trim(); - continue; - } - - // Boolean - if (valueStr === 'true' || valueStr === 'true,') { - args[key] = true; - continue; - } - if (valueStr === 'false' || valueStr === 'false,') { - args[key] = false; - continue; - } - - // Number (including decimals and negative) - const numMatch = valueStr.match(/^(-?\d+\.?\d*),?$/); - if (numMatch) { - args[key] = Number(numMatch[1]); - continue; - } - - // Skip complex values (objects, arrays, function calls, expressions) - } -} - -/** - * Extract variable arrays from file content (for options references) - */ -function extractVariableArrays(content) { - const variableArrays = {}; - - // Pattern 1: const varName = ['a', 'b', 'c']; - // Also handles: export const varName: Type[] = ['a', 'b', 'c']; - const varMatches = content.matchAll(/(?:export\s+)?(?:const|let)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?::\s*[^=]+)?\s*=\s*\[([^\]]+)\]/g); - for (const varMatch of varMatches) { - const varName = varMatch[1]; - const arrayContent = varMatch[2]; - const values = []; - const valMatches = arrayContent.matchAll(/['"]([^'"]+)['"]/g); - for (const val of valMatches) { - values.push(val[1]); - } - if (values.length > 0) { - variableArrays[varName] = values; - } - } - - // Pattern 2: const VAR = { options: [...] } - for SIZES.options, COLORS.options patterns - const objWithOptionsMatches = content.matchAll(/(?:const|let)\s+([A-Z][A-Z_0-9]*)\s*=\s*\{[^}]*options:\s*([a-zA-Z_$][a-zA-Z0-9_$]*)/g); - for (const match of objWithOptionsMatches) { - const objName = match[1]; - const optionsVarName = match[2]; - // Link the object's options to the underlying array - if (variableArrays[optionsVarName]) { - variableArrays[objName] = variableArrays[optionsVarName]; - } - } - - return variableArrays; -} - -/** - * Extract a string value from content, handling quotes properly - */ -function extractStringValue(content, startIndex) { - const remaining = content.slice(startIndex).trim(); - - // Single-quoted string - if (remaining.startsWith("'")) { - let i = 1; - while (i < remaining.length) { - if (remaining[i] === "'" && remaining[i - 1] !== '\\') { - return remaining.slice(1, i); - } - i++; - } - } - - // Double-quoted string - if (remaining.startsWith('"')) { - let i = 1; - while (i < remaining.length) { - if (remaining[i] === '"' && remaining[i - 1] !== '\\') { - return remaining.slice(1, i); - } - i++; - } - } - - // Template literal - if (remaining.startsWith('`')) { - let i = 1; - while (i < remaining.length) { - if (remaining[i] === '`' && remaining[i - 1] !== '\\') { - return remaining.slice(1, i).replace(/\s+/g, ' ').trim(); - } - i++; - } - } - - return null; -} - -/** - * Parse argTypes content and populate the argTypes object - */ -function parseArgTypes(argTypesContent, argTypes, fullContent) { - const variableArrays = extractVariableArrays(fullContent); - - // Match argType definitions - find each property block - // Use balanced brace extraction for each property - const propPattern = /([a-zA-Z_$][a-zA-Z0-9_$]*):\s*\{/g; - let propMatch; - - while ((propMatch = propPattern.exec(argTypesContent)) !== null) { - const propName = propMatch[1]; - const propStartIndex = propMatch.index + propMatch[0].length - 1; - const propConfig = extractBalancedBraces(argTypesContent, propStartIndex); - - if (!propConfig) continue; - - // Initialize argTypes entry if not exists - if (!argTypes[propName]) { - argTypes[propName] = {}; - } - - // Extract description - find the position and extract properly - const descIndex = propConfig.indexOf('description:'); - if (descIndex !== -1) { - const descValue = extractStringValue(propConfig, descIndex + 'description:'.length); - if (descValue) { - argTypes[propName].description = descValue; - } - } - - // Check for inline options array - const optionsMatch = propConfig.match(/options:\s*\[([^\]]+)\]/); - if (optionsMatch) { - const optionsStr = optionsMatch[1]; - const options = []; - const optionMatches = optionsStr.matchAll(/['"]([^'"]+)['"]/g); - for (const opt of optionMatches) { - options.push(opt[1]); - } - if (options.length > 0) { - argTypes[propName].type = 'select'; - argTypes[propName].options = options; - } - } else { - // Check for variable reference: options: variableName or options: VAR.options - const varRefMatch = propConfig.match(/options:\s*([a-zA-Z_$][a-zA-Z0-9_$]*(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)?)/); - if (varRefMatch) { - const varRef = varRefMatch[1]; - // Handle VAR.options pattern - const dotMatch = varRef.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)\.options$/); - if (dotMatch && variableArrays[dotMatch[1]]) { - argTypes[propName].type = 'select'; - argTypes[propName].options = variableArrays[dotMatch[1]]; - } else if (variableArrays[varRef]) { - argTypes[propName].type = 'select'; - argTypes[propName].options = variableArrays[varRef]; - } - } else { - // Check for ES6 shorthand: options, (same as options: options) - const shorthandMatch = propConfig.match(/(?:^|[,\s])options(?:[,\s]|$)/); - if (shorthandMatch && variableArrays['options']) { - argTypes[propName].type = 'select'; - argTypes[propName].options = variableArrays['options']; - } - } - } - - // Check for control type (radio, select, boolean, etc.) - // Supports both: control: 'boolean' (shorthand) and control: { type: 'boolean' } (object) - const controlShorthandMatch = propConfig.match(/control:\s*['"]([^'"]+)['"]/); - const controlObjectMatch = propConfig.match(/control:\s*\{[^}]*type:\s*['"]([^'"]+)['"]/); - if (controlShorthandMatch) { - argTypes[propName].type = controlShorthandMatch[1]; - } else if (controlObjectMatch) { - argTypes[propName].type = controlObjectMatch[1]; - } - - // Clear options for non-select/radio types (the shorthand "options" detection - // can false-positive when the word "options" appears in description text) - const finalType = argTypes[propName].type; - if (finalType && !['select', 'radio', 'inline-radio'].includes(finalType)) { - delete argTypes[propName].options; - } - } -} - -/** - * Helper to find balanced braces content - */ -function extractBalancedBraces(content, startIndex) { - let depth = 0; - let start = -1; - for (let i = startIndex; i < content.length; i++) { - if (content[i] === '{') { - if (depth === 0) start = i + 1; - depth++; - } else if (content[i] === '}') { - depth--; - if (depth === 0) { - return content.slice(start, i); - } - } - } - return null; -} - -/** - * Helper to find balanced brackets content (for arrays) - */ -function extractBalancedBrackets(content, startIndex) { - let depth = 0; - let start = -1; - for (let i = startIndex; i < content.length; i++) { - if (content[i] === '[') { - if (depth === 0) start = i + 1; - depth++; - } else if (content[i] === ']') { - depth--; - if (depth === 0) { - return content.slice(start, i); - } - } - } - return null; -} - -/** - * Convert camelCase prop name to human-readable label - * Handles acronyms properly: imgURL -> "Image URL", coverLeft -> "Cover Left" - */ -function propNameToLabel(name) { - return name - // Insert space before uppercase letters that follow lowercase (camelCase boundary) - .replace(/([a-z])([A-Z])/g, '$1 $2') - // Handle common acronyms - keep them together - .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') - // Capitalize first letter - .replace(/^./, s => s.toUpperCase()) - // Fix common acronyms display - .replace(/\bUrl\b/g, 'URL') - .replace(/\bImg\b/g, 'Image') - .replace(/\bId\b/g, 'ID'); -} - -/** - * Convert JS object literal syntax to JSON - * Handles: single quotes, unquoted keys, trailing commas - */ -function jsToJson(jsStr) { - try { - // Remove comments - let str = jsStr.replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, ''); - - // Replace single quotes with double quotes (but not inside already double-quoted strings) - str = str.replace(/'/g, '"'); - - // Add quotes around unquoted keys: { foo: -> { "foo": - str = str.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)(\s*:)/g, '$1"$2"$3'); - - // Remove trailing commas before } or ] - str = str.replace(/,(\s*[}\]])/g, '$1'); - - return JSON.parse(str); - } catch { - return null; - } -} - -/** - * Extract docs config from story parameters - * Looks for: StoryName.parameters = { docs: { sampleChildren, sampleChildrenStyle, gallery, staticProps, liveExample } } - * Uses generic JSON parsing for inline data - */ -function extractDocsConfig(content, storyNames) { - // Extract variable arrays for gallery config (sizes, styles) - const variableArrays = extractVariableArrays(content); - - let sampleChildren = null; - let sampleChildrenStyle = null; - let gallery = null; - let staticProps = null; - let liveExample = null; - let examples = null; - let renderComponent = null; - let triggerProp = null; - let onHideProp = null; - - for (const storyName of storyNames) { - // Look for parameters block - const parametersPattern = new RegExp(`${storyName}\\.parameters\\s*=\\s*\\{`, 's'); - const parametersMatch = content.match(parametersPattern); - - if (parametersMatch) { - const parametersContent = extractBalancedBraces(content, parametersMatch.index + parametersMatch[0].length - 1); - if (parametersContent) { - // Extract sampleChildren - inline array using generic JSON parser - const sampleChildrenArrayMatch = parametersContent.match(/sampleChildren:\s*\[/); - if (sampleChildrenArrayMatch) { - const arrayStartIndex = sampleChildrenArrayMatch.index + sampleChildrenArrayMatch[0].length - 1; - const arrayContent = extractBalancedBrackets(parametersContent, arrayStartIndex); - if (arrayContent) { - const parsed = jsToJson('[' + arrayContent + ']'); - if (parsed && parsed.length > 0) { - sampleChildren = parsed; - } - } - } - - // Extract sampleChildrenStyle - inline object using generic JSON parser - const sampleChildrenStyleMatch = parametersContent.match(/sampleChildrenStyle:\s*\{/); - if (sampleChildrenStyleMatch) { - const styleContent = extractBalancedBraces(parametersContent, sampleChildrenStyleMatch.index + sampleChildrenStyleMatch[0].length - 1); - if (styleContent) { - const parsed = jsToJson('{' + styleContent + '}'); - if (parsed) { - sampleChildrenStyle = parsed; - } - } - } - - // Extract staticProps - generic JSON-like object extraction - const staticPropsMatch = parametersContent.match(/staticProps:\s*\{/); - if (staticPropsMatch) { - const staticPropsContent = extractBalancedBraces(parametersContent, staticPropsMatch.index + staticPropsMatch[0].length - 1); - if (staticPropsContent) { - // Try to parse as JSON (handles inline data) - const parsed = jsToJson('{' + staticPropsContent + '}'); - if (parsed) { - staticProps = parsed; - } - } - } - - // Extract gallery config - const galleryMatch = parametersContent.match(/gallery:\s*\{/); - if (galleryMatch) { - const galleryContent = extractBalancedBraces(parametersContent, galleryMatch.index + galleryMatch[0].length - 1); - if (galleryContent) { - gallery = {}; - - // Extract component name - const compMatch = galleryContent.match(/component:\s*['"]([^'"]+)['"]/); - if (compMatch) gallery.component = compMatch[1]; - - // Extract sizes - variable reference - const sizesVarMatch = galleryContent.match(/sizes:\s*([a-zA-Z_$][a-zA-Z0-9_$]*)/); - if (sizesVarMatch && variableArrays[sizesVarMatch[1]]) { - gallery.sizes = variableArrays[sizesVarMatch[1]]; - } - - // Extract styles - variable reference - const stylesVarMatch = galleryContent.match(/styles:\s*([a-zA-Z_$][a-zA-Z0-9_$]*)/); - if (stylesVarMatch && variableArrays[stylesVarMatch[1]]) { - gallery.styles = variableArrays[stylesVarMatch[1]]; - } - - // Extract sizeProp - const sizePropMatch = galleryContent.match(/sizeProp:\s*['"]([^'"]+)['"]/); - if (sizePropMatch) gallery.sizeProp = sizePropMatch[1]; - - // Extract styleProp - const stylePropMatch = galleryContent.match(/styleProp:\s*['"]([^'"]+)['"]/); - if (stylePropMatch) gallery.styleProp = stylePropMatch[1]; - } - } - - // Extract liveExample - template literal for custom live code block - const liveExampleMatch = parametersContent.match(/liveExample:\s*`/); - if (liveExampleMatch) { - // Find the closing backtick - const startIndex = liveExampleMatch.index + liveExampleMatch[0].length; - let endIndex = startIndex; - while (endIndex < parametersContent.length && parametersContent[endIndex] !== '`') { - // Handle escaped backticks - if (parametersContent[endIndex] === '\\' && parametersContent[endIndex + 1] === '`') { - endIndex += 2; - } else { - endIndex++; - } - } - if (endIndex < parametersContent.length) { - // Unescape template literal escapes (source text has \` and \$ for literal backticks/dollars) - liveExample = parametersContent.slice(startIndex, endIndex).replace(/\\`/g, '`').replace(/\\\$/g, '$'); - } - } - - // Extract renderComponent - allows overriding which component to render - // Useful when the title-derived component (e.g., 'Icons') is a namespace, not a component - const renderComponentMatch = parametersContent.match(/renderComponent:\s*['"]([^'"]+)['"]/); - if (renderComponentMatch) { - renderComponent = renderComponentMatch[1]; - } - - // Extract triggerProp/onHideProp - for components like Modal that need a trigger button - const triggerPropMatch = parametersContent.match(/triggerProp:\s*['"]([^'"]+)['"]/); - if (triggerPropMatch) { - triggerProp = triggerPropMatch[1]; - } - const onHidePropMatch = parametersContent.match(/onHideProp:\s*['"]([^'"]+)['"]/); - if (onHidePropMatch) { - onHideProp = onHidePropMatch[1]; - } - - // Extract examples array - for multiple code examples - // Format: examples: [{ title: 'Title', code: `...` }, ...] - const examplesMatch = parametersContent.match(/examples:\s*\[/); - if (examplesMatch) { - const examplesStartIndex = examplesMatch.index + examplesMatch[0].length - 1; - const examplesArrayContent = extractBalancedBrackets(parametersContent, examplesStartIndex); - if (examplesArrayContent) { - examples = []; - // Find each example object { title: '...', code: `...` } - const exampleObjPattern = /\{\s*title:\s*['"]([^'"]+)['"]\s*,\s*code:\s*`/g; - let exampleMatch; - while ((exampleMatch = exampleObjPattern.exec(examplesArrayContent)) !== null) { - const title = exampleMatch[1]; - const codeStartIndex = exampleMatch.index + exampleMatch[0].length; - // Find closing backtick for code - let codeEndIndex = codeStartIndex; - while (codeEndIndex < examplesArrayContent.length && examplesArrayContent[codeEndIndex] !== '`') { - if (examplesArrayContent[codeEndIndex] === '\\' && examplesArrayContent[codeEndIndex + 1] === '`') { - codeEndIndex += 2; - } else { - codeEndIndex++; - } - } - // Unescape template literal escapes (source text has \` and \$ for literal backticks/dollars) - const code = examplesArrayContent.slice(codeStartIndex, codeEndIndex).replace(/\\`/g, '`').replace(/\\\$/g, '$'); - examples.push({ title, code }); - } - } - } - } - } - - if (sampleChildren || gallery || staticProps || liveExample || examples || renderComponent || triggerProp) break; - } - - return { sampleChildren, sampleChildrenStyle, gallery, staticProps, liveExample, examples, renderComponent, triggerProp, onHideProp }; -} - -/** - * Extract args and controls from story content - */ -function extractArgsAndControls(content, componentName) { - const args = {}; - const argTypes = {}; - - // First, extract argTypes from the default export meta (shared across all stories) - // Pattern: export default { argTypes: {...} } - const defaultExportMatch = content.match(/export\s+default\s*\{/); - if (defaultExportMatch) { - const metaContent = extractBalancedBraces(content, defaultExportMatch.index + defaultExportMatch[0].length - 1); - if (metaContent) { - const metaArgTypesMatch = metaContent.match(/\bargTypes:\s*\{/); - if (metaArgTypesMatch) { - const metaArgTypesContent = extractBalancedBraces(metaContent, metaArgTypesMatch.index + metaArgTypesMatch[0].length - 1); - if (metaArgTypesContent) { - parseArgTypes(metaArgTypesContent, argTypes, content); - } - } - } - } - - // Then, try to find the Interactive story block (CSF 3.0 or CSF 2.0) - // Support multiple naming conventions: - // - InteractiveComponentName (CSF 2.0 convention) - // - ComponentNameStory (CSF 3.0 convention) - // - ComponentName (fallback) - const storyNames = [`Interactive${componentName}`, `${componentName}Story`, componentName]; - - // Extract docs config (sampleChildren, sampleChildrenStyle, gallery, staticProps, liveExample) from parameters.docs - const { sampleChildren, sampleChildrenStyle, gallery, staticProps, liveExample, examples, renderComponent, triggerProp, onHideProp } = extractDocsConfig(content, storyNames); - - for (const storyName of storyNames) { - // Try CSF 3.0 format: export const StoryName: StoryObj = { args: {...}, argTypes: {...} } - const csf3Pattern = new RegExp(`export\\s+const\\s+${storyName}[^=]*=[^{]*\\{`, 's'); - const csf3Match = content.match(csf3Pattern); - - if (csf3Match) { - const storyStartIndex = csf3Match.index + csf3Match[0].length - 1; - const storyContent = extractBalancedBraces(content, storyStartIndex); - - if (storyContent) { - // Extract args from story content - const argsMatch = storyContent.match(/\bargs:\s*\{/); - if (argsMatch) { - const argsContent = extractBalancedBraces(storyContent, argsMatch.index + argsMatch[0].length - 1); - if (argsContent) { - parseArgsContent(argsContent, args); - } - } - - // Extract argTypes from story content - const argTypesMatch = storyContent.match(/\bargTypes:\s*\{/); - if (argTypesMatch) { - const argTypesContent = extractBalancedBraces(storyContent, argTypesMatch.index + argTypesMatch[0].length - 1); - if (argTypesContent) { - parseArgTypes(argTypesContent, argTypes, content); - } - } - - if (Object.keys(args).length > 0 || Object.keys(argTypes).length > 0) { - break; // Found a matching story - } - } - } - - // Try CSF 2.0 format: StoryName.args = {...} - const csf2ArgsPattern = new RegExp(`${storyName}\\.args\\s*=\\s*\\{`, 's'); - const csf2ArgsMatch = content.match(csf2ArgsPattern); - if (csf2ArgsMatch) { - const argsContent = extractBalancedBraces(content, csf2ArgsMatch.index + csf2ArgsMatch[0].length - 1); - if (argsContent) { - parseArgsContent(argsContent, args); - } - } - - // Try CSF 2.0 argTypes: StoryName.argTypes = {...} - const csf2ArgTypesPattern = new RegExp(`${storyName}\\.argTypes\\s*=\\s*\\{`, 's'); - const csf2ArgTypesMatch = content.match(csf2ArgTypesPattern); - if (csf2ArgTypesMatch) { - const argTypesContent = extractBalancedBraces(content, csf2ArgTypesMatch.index + csf2ArgTypesMatch[0].length - 1); - if (argTypesContent) { - parseArgTypes(argTypesContent, argTypes, content); - } - } - - if (Object.keys(args).length > 0 || Object.keys(argTypes).length > 0) { - break; // Found a matching story - } - } - - // Generate controls from args first, then add any argTypes-only props - const controls = []; - const processedProps = new Set(); - - // First pass: props that have default values in args - for (const [key, value] of Object.entries(args)) { - processedProps.add(key); - const label = propNameToLabel(key); - const argType = argTypes[key] || {}; - - if (argType.type) { - // Use argTypes override (select, radio with options) - controls.push({ - name: key, - label, - type: argType.type, - options: argType.options, - description: argType.description - }); - } else if (typeof value === 'boolean') { - controls.push({ name: key, label, type: 'boolean', description: argType.description }); - } else if (typeof value === 'string') { - controls.push({ name: key, label, type: 'text', description: argType.description }); - } else if (typeof value === 'number') { - controls.push({ name: key, label, type: 'number', description: argType.description }); - } - } - - // Second pass: props defined only in argTypes (no explicit value in args) - // Add controls for these, but don't set default values on the component - // (setting defaults like open: false or status: 'error' breaks component behavior) - for (const [key, argType] of Object.entries(argTypes)) { - if (processedProps.has(key)) continue; - if (!argType.type) continue; // Skip if no control type defined - - const label = propNameToLabel(key); - - // Don't add to args - let the component use its own defaults - - controls.push({ - name: key, - label, - type: argType.type, - options: argType.options, - description: argType.description - }); - } - - return { args, argTypes, controls, sampleChildren, sampleChildrenStyle, gallery, staticProps, liveExample, examples, renderComponent, triggerProp, onHideProp }; -} - -/** - * Generate MDX content for a component - */ -function generateMDX(component, storyContent) { - const { componentName, description, relativePath, category, sourceConfig, resolvedImportPath, isDefaultExport } = component; - - const { args, argTypes, controls, sampleChildren, sampleChildrenStyle, gallery, staticProps, liveExample, examples, renderComponent, triggerProp, onHideProp } = extractArgsAndControls(storyContent, componentName); - - // Merge staticProps into args for complex values (arrays, objects) that can't be parsed from inline args - const mergedArgs = { ...args, ...staticProps }; - - // Format JSON: unquote property names but keep double quotes for string values - // This avoids issues with single quotes in strings breaking MDX parsing - const controlsJson = JSON.stringify(controls, null, 2) - .replace(/"(\w+)":/g, '$1:'); - - const propsJson = JSON.stringify(mergedArgs, null, 2) - .replace(/"(\w+)":/g, '$1:'); - - // Format sampleChildren if present (from story's parameters.docs.sampleChildren) - const sampleChildrenJson = sampleChildren - ? JSON.stringify(sampleChildren) - : null; - - // Format sampleChildrenStyle if present (from story's parameters.docs.sampleChildrenStyle) - const sampleChildrenStyleJson = sampleChildrenStyle - ? JSON.stringify(sampleChildrenStyle).replace(/"(\w+)":/g, '$1:') - : null; - - // Format gallery config if present - const hasGallery = gallery && gallery.sizes && gallery.styles; - - // Extract children for proper JSX rendering - const childrenValue = mergedArgs.children; - - const liveExampleProps = Object.entries(mergedArgs) - .filter(([key]) => key !== 'children') - .map(([key, value]) => { - if (typeof value === 'string') return `${key}="${value}"`; - if (typeof value === 'boolean') return value ? key : null; - return `${key}={${JSON.stringify(value)}}`; - }) - .filter(Boolean) - .join('\n '); - - // Generate props table with descriptions from argTypes - const propsTable = Object.entries(mergedArgs).map(([key, value]) => { - const type = typeof value === 'boolean' ? 'boolean' : typeof value === 'string' ? 'string' : typeof value === 'number' ? 'number' : 'any'; - const desc = argTypes[key]?.description || '-'; - return `| \`${key}\` | \`${type}\` | \`${JSON.stringify(value)}\` | ${desc} |`; - }).join('\n'); - - // Calculate relative import path based on category depth - const importDepth = category.includes('/') ? 4 : 3; - const wrapperImportPrefix = '../'.repeat(importDepth); - - // Use resolved import path if available, otherwise fall back to source config - const componentImportPath = resolvedImportPath || sourceConfig.importPrefix; - - // The displayed import in user docs should reflect the public package path, - // not the internal storybook alias. - const docImportPath = sourceConfig.importPrefix.startsWith('@superset/') - ? sourceConfig.docImportPrefix - : componentImportPath; - - // When the source uses the internal storybook alias, the public package - // re-exports components as named exports (e.g. `export { default as Foo }`), - // so users must use named imports even when the story uses a default import. - const useDefaultImport = - isDefaultExport && !sourceConfig.importPrefix.startsWith('@superset/'); - - // Only render the import snippet if the component is actually re-exported - // from the public package barrel; otherwise the snippet would mislead users - // copy-pasting it (e.g. TableCollection, which has a story but is not - // re-exported from `@superset-ui/core/components`). - const publicExports = sourceConfig.importPrefix.startsWith('@superset/') - ? getPublicExports(sourceConfig) - : null; - const isPubliclyExported = - !publicExports || publicExports.has(componentName); - - // Determine component description based on source - const defaultDesc = sourceConfig.category === 'ui' - ? `The ${componentName} component from Superset's UI library.` - : `The ${componentName} component from Superset.`; - - return `--- -title: ${componentName} -sidebar_label: ${componentName} ---- - - - -import { StoryWithControls${hasGallery ? ', ComponentGallery' : ''} } from '${wrapperImportPrefix}src/components/StorybookWrapper'; - -# ${componentName} - -${description || defaultDesc} -${hasGallery ? ` -## All Variants - - -` : ''} -## Live Example - - - -## Try It - -Edit the code below to experiment with the component: - -\`\`\`tsx live -${liveExample || `function Demo() { - return ( - <${componentName} - ${liveExampleProps || '// Add props here'} - ${childrenValue ? `> - ${childrenValue} - ` : '/>'} - ); -}`} -\`\`\` -${examples && examples.length > 0 ? examples.map(ex => ` -## ${ex.title} - -\`\`\`tsx live -${ex.code} -\`\`\` -`).join('') : ''} -${Object.keys(args).length > 0 ? `## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -${propsTable}` : ''} - -${isPubliclyExported ? `## Import - -\`\`\`tsx -${useDefaultImport ? `import ${componentName} from '${docImportPath}';` : `import { ${componentName} } from '${docImportPath}';`} -\`\`\` - ----` : '---'} - -:::tip[Improve this page] -This documentation is auto-generated from the component's Storybook story. -Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/${relativePath}). -::: -`; -} - -/** - * Category display names for sidebar - */ -const CATEGORY_LABELS = { - ui: { title: 'Core Components', sidebarLabel: 'Core Components', description: 'Buttons, inputs, modals, selects, and other fundamental UI elements.' }, - 'design-system': { title: 'Layout Components', sidebarLabel: 'Layout Components', description: 'Grid, Layout, Table, Flex, Space, and container components for page structure.' }, - extension: { title: 'Extension Components', sidebarLabel: 'Extension Components', description: 'Components available to extension developers via @apache-superset/core/components.' }, -}; - -/** - * Generate category index page - */ -function generateCategoryIndex(category, components) { - const labels = CATEGORY_LABELS[category] || { - title: category.charAt(0).toUpperCase() + category.slice(1).replace(/-/g, ' '), - sidebarLabel: category.charAt(0).toUpperCase() + category.slice(1).replace(/-/g, ' '), - }; - const componentList = components - .sort((a, b) => a.componentName.localeCompare(b.componentName)) - // `.mdx` suffix matches the actual component page files emitted - // by this generator (see the MDX wrappers below). The extension - // is required: Docusaurus only validates and rewrites *file-based* - // references (.md/.mdx). Bare relative paths bypass the file - // resolver and get emitted as raw HTML hrefs that the browser - // resolves against the current URL — which gives the wrong - // directory for trailing-slash routes and breaks SPA navigation. - // See docs/scripts/lint-docs-links.mjs. - .map(c => `- [${c.componentName}](./${c.componentName.toLowerCase()}.mdx)`) - .join('\n'); - - return `--- -title: ${labels.title} -sidebar_label: ${labels.sidebarLabel} -sidebar_position: 1 ---- - - - -# ${labels.title} - -${components.length} components available in this category. - -## Components - -${componentList} -`; -} - -/** - * Generate main overview page - */ -function generateOverviewIndex() { - return `--- -title: UI Components Overview -sidebar_label: Overview -sidebar_position: 0 ---- - - - -import { ComponentIndex } from '@site/src/components/ui-components'; -import componentData from '@site/static/data/components.json'; - -# UI Components - - - ---- - -## Design System - -A design system is a complete set of standards intended to manage design at scale using reusable components and patterns. - -The Superset Design System uses [Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/) principles with adapted terminology: - -| Atomic Design | Atoms | Molecules | Organisms | Templates | Pages / Screens | -|---|:---:|:---:|:---:|:---:|:---:| -| **Superset Design** | Foundations | Components | Patterns | Templates | Features | - -Atoms = Foundations, Molecules = Components, Organisms = Patterns, Templates = Templates, Pages / Screens = Features - -## Usage - -All components are exported from \`@superset-ui/core/components\`: - -\`\`\`tsx -import { Button, Modal, Select } from '@superset-ui/core/components'; -\`\`\` - -## Contributing - -This documentation is auto-generated from Storybook stories. To add or update component documentation: - -1. Create or update the component's \`.stories.tsx\` file -2. Add a descriptive \`title\` and \`description\` in the story meta -3. Export an interactive story with \`args\` for configurable props -4. Run \`yarn generate:superset-components\` in the \`docs/\` directory - -:::info Work in Progress -This component library is actively being documented. See the [Components TODO](./TODO.md) page for a list of components awaiting documentation. -::: - ---- - -*Auto-generated from Storybook stories in the [Design System/Introduction](https://github.com/apache/superset/blob/master/superset-frontend/packages/superset-ui-core/src/components/DesignSystem.stories.tsx) story.* -`; -} - -/** - * Generate TODO.md tracking skipped components - */ -function generateTodoMd(skippedFiles) { - const disabledSources = SOURCES.filter(s => !s.enabled); - const grouped = {}; - for (const file of skippedFiles) { - const source = disabledSources.find(s => file.includes(s.path)); - const sourceName = source ? source.name : 'unknown'; - if (!grouped[sourceName]) grouped[sourceName] = []; - grouped[sourceName].push(file); - } - - const sections = Object.entries(grouped) - .map(([source, files]) => { - const fileList = files.map(f => `- [ ] \`${path.relative(ROOT_DIR, f)}\``).join('\n'); - return `### ${source}\n\n${files.length} components\n\n${fileList}`; - }) - .join('\n\n'); - - return `--- -title: Components TODO -sidebar_class_name: hidden ---- - -# Components TODO - -These components were found but not yet supported for documentation generation. -Future phases will add support for these sources. - -## Summary - -- **Total skipped:** ${skippedFiles.length} story files -- **Reason:** Import path resolution not yet implemented - -## Skipped by Source - -${sections} - -## How to Add Support - -1. Determine the correct import path for the source -2. Update \`generate-superset-components.mjs\` to handle the source -3. Add source to \`SUPPORTED_SOURCES\` array -4. Re-run the generator - ---- - -*Auto-generated by generate-superset-components.mjs* -`; -} - -/** - * Build metadata for a component (for JSON output) - */ -function buildComponentMetadata(component, storyContent) { - const { componentName, description, category, sourceConfig, resolvedImportPath, extensionCompatible } = component; - const { args, controls, gallery, liveExample } = extractArgsAndControls(storyContent, componentName); - const labels = CATEGORY_LABELS[category] || { - title: category.charAt(0).toUpperCase() + category.slice(1).replace(/-/g, ' '), - }; - - return { - name: componentName, - category, - categoryLabel: labels.title || category, - description: description || '', - importPath: resolvedImportPath || sourceConfig.importPrefix, - package: sourceConfig.docImportPrefix, - extensionCompatible: Boolean(extensionCompatible), - propsCount: Object.keys(args).length, - controlsCount: controls.length, - hasGallery: Boolean(gallery && gallery.sizes && gallery.styles), - hasLiveExample: Boolean(liveExample), - docPath: `developer-docs/components/${category}/${componentName.toLowerCase()}`, - storyFile: component.relativePath, - }; -} - -/** - * Extract type and component export declarations from a component source file. - * Used to generate .d.ts type declarations for extension-compatible components. - */ -function extractComponentTypes(componentPath) { - if (!fs.existsSync(componentPath)) return null; - const content = fs.readFileSync(componentPath, 'utf-8'); - - const types = []; - // Match "export type Name = ;" handling nested braces - // so object types like { a: string; b: number } are captured fully. - const typeRegex = /export\s+type\s+(\w+)\s*=\s*/g; - let typeMatch; - while ((typeMatch = typeRegex.exec(content)) !== null) { - const start = typeMatch.index + typeMatch[0].length; - let depth = 0; - let end = start; - for (let i = start; i < content.length; i++) { - const ch = content[i]; - if (ch === '{' || ch === '<' || ch === '(') depth++; - else if (ch === '}' || ch === '>' || ch === ')') depth--; - else if (ch === ';' && depth === 0) { - end = i; - break; - } - } - const definition = content.slice(start, end).trim(); - if (definition) { - types.push({ name: typeMatch[1], definition }); - } - } - - const components = []; - for (const match of content.matchAll(/export\s+const\s+(\w+)\s*[=:]/g)) { - components.push(match[1]); - } - - return { types, components }; -} - -/** - * Generate TypeScript type declarations for extension-compatible components. - * Produces a .d.ts file that downstream consumers can reference. - */ -function generateExtensionTypeDeclarations(extensionComponents) { - const imports = new Set(); - const typeDeclarations = []; - const componentDeclarations = []; - - for (const comp of extensionComponents) { - const componentDir = path.dirname(comp.filePath); - const componentFile = path.join(componentDir, 'index.tsx'); - const extracted = extractComponentTypes(componentFile); - if (!extracted) continue; - - for (const type of extracted.types) { - if (type.definition.includes('AntdAlertProps') || type.definition.includes('AlertProps')) { - imports.add("import type { AlertProps as AntdAlertProps } from 'antd/es/alert';"); - } - if (type.definition.includes('PropsWithChildren') || type.definition.includes('FC')) { - imports.add("import type { PropsWithChildren, FC } from 'react';"); - } - typeDeclarations.push(`export type ${type.name} = ${type.definition};`); - } - - for (const name of extracted.components) { - const propsType = `${name}Props`; - const hasPropsType = extracted.types.some(t => t.name === propsType); - componentDeclarations.push( - hasPropsType - ? `export const ${name}: FC<${propsType}>;` - : `export const ${name}: FC>;` - ); - } - } - - // Always import FC when we have component declarations that reference it - if (componentDeclarations.length > 0) { - imports.add("import type { PropsWithChildren, FC } from 'react';"); - } - - return `/** - * 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. - */ - -/** - * Type declarations for @apache-superset/core/components - * - * AUTO-GENERATED by scripts/generate-superset-components.mjs - * Do not edit manually - regenerate by running: yarn generate:superset-components - */ -${Array.from(imports).join('\n')} - -${typeDeclarations.join('\n')} - -${componentDeclarations.join('\n')} -`; -} - -/** - * Main function - */ -async function main() { - console.log('Generating Superset Components documentation...\n'); - - // Find enabled story files - const enabledFiles = findEnabledStoryFiles(); - console.log(`Found ${enabledFiles.length} story files from enabled sources\n`); - - // Find disabled story files (for tracking) - const disabledFiles = findDisabledStoryFiles(); - console.log(`Found ${disabledFiles.length} story files from disabled sources (tracking only)\n`); - - // Parse enabled files - const components = []; - for (const { file, source } of enabledFiles) { - const parsed = parseStoryFile(file, source); - if (parsed && parsed.componentName) { - components.push(parsed); - } - } - - console.log(`Parsed ${components.length} components\n`); - - // Group by category - const categories = {}; - for (const component of components) { - if (!categories[component.category]) { - categories[component.category] = []; - } - categories[component.category].push(component); - } - - // Ensure output directory exists - if (!fs.existsSync(OUTPUT_DIR)) { - fs.mkdirSync(OUTPUT_DIR, { recursive: true }); - } - - // Generate MDX files by category - let generatedCount = 0; - for (const [category, categoryComponents] of Object.entries(categories)) { - const categoryDir = path.join(OUTPUT_DIR, category); - if (!fs.existsSync(categoryDir)) { - fs.mkdirSync(categoryDir, { recursive: true }); - } - - // Generate component pages - for (const component of categoryComponents) { - const storyContent = fs.readFileSync(component.filePath, 'utf-8'); - const mdxContent = generateMDX(component, storyContent); - const outputPath = path.join(categoryDir, `${component.componentName.toLowerCase()}.mdx`); - fs.writeFileSync(outputPath, mdxContent); - console.log(` ✓ ${category}/${component.componentName}`); - generatedCount++; - } - - // Generate category index - const indexContent = generateCategoryIndex(category, categoryComponents); - const indexPath = path.join(categoryDir, 'index.mdx'); - fs.writeFileSync(indexPath, indexContent); - console.log(` ✓ ${category}/index`); - } - - // Build JSON metadata for all components - console.log('\nBuilding component metadata JSON...'); - const componentMetadata = []; - for (const component of components) { - const storyContent = fs.readFileSync(component.filePath, 'utf-8'); - componentMetadata.push(buildComponentMetadata(component, storyContent)); - } - - // Build statistics - const byCategory = {}; - for (const comp of componentMetadata) { - if (!byCategory[comp.category]) byCategory[comp.category] = 0; - byCategory[comp.category]++; - } - const jsonData = { - generated: new Date().toISOString(), - statistics: { - totalComponents: componentMetadata.length, - byCategory, - extensionCompatible: componentMetadata.filter(c => c.extensionCompatible).length, - withGallery: componentMetadata.filter(c => c.hasGallery).length, - withLiveExample: componentMetadata.filter(c => c.hasLiveExample).length, - }, - components: componentMetadata, - }; - - // Ensure data directory exists and write JSON - const jsonDir = path.dirname(JSON_OUTPUT_PATH); - if (!fs.existsSync(jsonDir)) { - fs.mkdirSync(jsonDir, { recursive: true }); - } - fs.writeFileSync(JSON_OUTPUT_PATH, JSON.stringify(jsonData, null, 2)); - console.log(` ✓ components.json (${componentMetadata.length} components)`); - - // Generate type declarations for extension-compatible components - const extensionComponents = components.filter(c => c.extensionCompatible); - if (extensionComponents.length > 0) { - if (!fs.existsSync(TYPES_OUTPUT_DIR)) { - fs.mkdirSync(TYPES_OUTPUT_DIR, { recursive: true }); - } - const typesContent = generateExtensionTypeDeclarations(extensionComponents); - fs.writeFileSync(TYPES_OUTPUT_PATH, typesContent); - console.log(` ✓ extension types (${extensionComponents.length} components)`); - } - - // Generate main overview - const overviewContent = generateOverviewIndex(); - const overviewPath = path.join(OUTPUT_DIR, 'index.mdx'); - fs.writeFileSync(overviewPath, overviewContent); - console.log(` ✓ index (overview)`); - - // Generate TODO.md - const todoContent = generateTodoMd(disabledFiles); - const todoPath = path.join(OUTPUT_DIR, 'TODO.md'); - fs.writeFileSync(todoPath, todoContent); - console.log(` ✓ TODO.md`); - - console.log(`\nDone! Generated ${generatedCount} component pages.`); - console.log(`Tracked ${disabledFiles.length} components for future implementation.`); -} - -main().catch(console.error); diff --git a/docs/scripts/lint-docs-links.mjs b/docs/scripts/lint-docs-links.mjs deleted file mode 100644 index 7d29ba59e2d..00000000000 --- a/docs/scripts/lint-docs-links.mjs +++ /dev/null @@ -1,230 +0,0 @@ -#!/usr/bin/env node -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * lint-docs-links — source-level checks for internal markdown links. - * - * Catches three failure modes that combine to break SPA navigation in - * a Docusaurus build: - * - * 1. BARE — `[X](../foo)` with no extension. Skips - * Docusaurus's file resolver entirely. Emitted - * as a raw href and resolved by the browser - * against the current page URL — usually the - * wrong directory for trailing-slash routes. - * `onBrokenLinks: 'throw'` cannot catch this. - * - * 2. MISSING_TARGET — `[X](./gone.md)` with an extension, but no - * file at that path. The Docusaurus build - * catches this too (via - * `onBrokenMarkdownLinks: 'throw'`) but only - * after a multi-minute build. This script - * flags it in ~1s. - * - * 3. WRONG_EXTENSION — `[X](./foo.md)` where the file is actually - * `foo.mdx` (or vice versa). Same end result - * as MISSING_TARGET, but the fix is one - * character — so we report it as its own - * category with the actual extension on disk. - * - * Skips: fenced code blocks, asset-style targets (.png/.json/etc.), - * external URLs, in-page anchors, and the `versioned_docs/` - * snapshots (those are frozen historical content). - * - * Run from `docs/`: - * node scripts/lint-docs-links.mjs - * - * Exits 0 on clean, 1 on any finding. - */ - -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const docsRoot = path.join(__dirname, '..'); - -const ROOTS = ['docs', 'admin_docs', 'developer_docs', 'components']; - -const NON_DOC_EXTENSIONS = new Set([ - '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico', - '.json', '.yaml', '.yml', '.txt', '.csv', - '.zip', '.tar', '.gz', - '.pdf', - '.mp4', '.webm', '.mov', -]); - -const LINK_RE = /\[[^\]\n]+?\]\((?\.{1,2}\/[^)\s]+?)\)/g; - -/** - * Classify a single markdown link from a source file. - * Returns one of: ok / bare / asset / missing-target / wrong-extension. - */ -function classifyLink(sourceFile, url) { - const stripped = url.split('#', 1)[0].split('?', 1)[0]; - const ext = path.extname(stripped).toLowerCase(); - - // Non-doc assets — legit bare extensions, leave alone. - if (ext && NON_DOC_EXTENSIONS.has(ext)) { - return { kind: 'asset' }; - } - - // Anything that doesn't end in .md/.mdx is a bare relative URL. - if (ext !== '.md' && ext !== '.mdx') { - return { kind: 'bare' }; - } - - // Has a .md/.mdx extension — make sure the target exists. - const target = path.normalize(path.join(path.dirname(sourceFile), stripped)); - if (fs.existsSync(target)) { - return { kind: 'ok' }; - } - - // Target doesn't exist — check if the OTHER extension does. - const otherExt = ext === '.md' ? '.mdx' : '.md'; - const otherTarget = target.slice(0, -ext.length) + otherExt; - if (fs.existsSync(otherTarget)) { - return { kind: 'wrong-extension', actualExt: otherExt }; - } - - return { kind: 'missing-target' }; -} - -function* walk(dir) { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) { - if ( - entry.name.startsWith('.') || - entry.name === 'node_modules' || - entry.name.endsWith('_versioned_docs') || - entry.name === 'versioned_docs' - ) { - continue; - } - yield* walk(full); - } else if (entry.isFile()) { - if (entry.name.endsWith('.md') || entry.name.endsWith('.mdx')) { - yield full; - } - } - } -} - -function lintFile(file) { - const src = fs.readFileSync(file, 'utf8'); - const findings = []; - let inFence = false; - const lines = src.split('\n'); - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (line.trimStart().startsWith('```')) { - inFence = !inFence; - continue; - } - if (inFence) continue; - for (const m of line.matchAll(LINK_RE)) { - const url = m.groups.url; - const result = classifyLink(file, url); - if (result.kind !== 'ok' && result.kind !== 'asset') { - findings.push({ line: i + 1, url, ...result }); - } - } - } - return findings; -} - -const findings = []; -for (const root of ROOTS) { - const abs = path.join(docsRoot, root); - if (!fs.existsSync(abs)) continue; - for (const file of walk(abs)) { - for (const f of lintFile(file)) { - findings.push({ file: path.relative(docsRoot, file), ...f }); - } - } -} - -if (findings.length === 0) { - console.log('✓ lint-docs-links: no broken internal links found'); - process.exit(0); -} - -// Group by kind for readable output. -const groups = { - bare: [], - 'wrong-extension': [], - 'missing-target': [], -}; -for (const f of findings) { - groups[f.kind].push(f); -} - -console.error( - `✗ lint-docs-links: found ${findings.length} broken internal link(s)` -); -console.error(''); - -if (groups.bare.length) { - console.error( - ` ${groups.bare.length} bare relative link(s) (no .md/.mdx extension)` - ); - console.error( - " Docusaurus's file resolver skips these; the browser resolves them" - ); - console.error( - ' against the current page URL — wrong directory for trailing-slash routes.' - ); - console.error(' Add the extension so the file resolver picks them up.'); - console.error(''); - for (const f of groups.bare) { - console.error(` ${f.file}:${f.line} ${f.url}`); - } - console.error(''); -} - -if (groups['wrong-extension'].length) { - console.error( - ` ${groups['wrong-extension'].length} wrong-extension link(s) (.md vs .mdx mismatch)` - ); - console.error(' The target file exists with the other extension on disk.'); - console.error(''); - for (const f of groups['wrong-extension']) { - console.error( - ` ${f.file}:${f.line} ${f.url} → use ${f.actualExt}` - ); - } - console.error(''); -} - -if (groups['missing-target'].length) { - console.error( - ` ${groups['missing-target'].length} missing-target link(s) (file doesn't exist)` - ); - console.error(''); - for (const f of groups['missing-target']) { - console.error(` ${f.file}:${f.line} ${f.url}`); - } - console.error(''); -} - -process.exit(1); diff --git a/docs/scripts/manage-versions.mjs b/docs/scripts/manage-versions.mjs deleted file mode 100644 index 2318344157c..00000000000 --- a/docs/scripts/manage-versions.mjs +++ /dev/null @@ -1,407 +0,0 @@ -#!/usr/bin/env node - -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import fs from 'fs'; -import path from 'path'; -import { execSync } from 'child_process'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const CONFIG_FILE = path.join(__dirname, '..', 'versions-config.json'); - -// Parse command line arguments -const rawArgs = process.argv.slice(2); -const skipGenerate = rawArgs.includes('--skip-generate'); -const args = rawArgs.filter((a) => a !== '--skip-generate'); -const command = args[0]; // 'add' or 'remove' -const section = args[1]; // 'docs', 'admin_docs', 'developer_docs', or 'components' -const version = args[2]; // version string like '1.2.0' - -function loadConfig() { - return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); -} - -function saveConfig(config) { - fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n'); -} - -function freezeDataImports(section, version) { - // MDX files can `import` JSON/YAML data from outside the section, either - // via escaping relative paths (e.g. country-map-tools.mdx imports - // `../../data/countries.json`) or via the `@site/` alias (e.g. - // feature-flags.mdx imports `@site/static/feature-flags.json`). Without - // intervention the snapshot keeps reading the live file, so the - // historical version's content silently changes whenever the data file - // is updated. Copy each escaping data import into a snapshot-local - // `_versioned_data/` dir and rewrite the import to point there. - const sectionRoot = section === 'docs' - ? path.join(__dirname, '..', 'docs') - : path.join(__dirname, '..', section); - const docsRoot = path.join(__dirname, '..'); - const versionedDocsDir = section === 'docs' - ? `versioned_docs/version-${version}` - : `${section}_versioned_docs/version-${version}`; - const versionedDocsPath = path.join(__dirname, '..', versionedDocsDir); - const frozenDataDir = path.join(versionedDocsPath, '_versioned_data'); - - if (!fs.existsSync(versionedDocsPath)) { - return; - } - - console.log(` Freezing data imports in ${versionedDocsDir}...`); - - // Matches data file imports in two flavors: - // `from '../../foo/bar.json'` (relative, must escape one or more dirs) - // `from '@site/static/foo.json'` (Docusaurus site-root alias) - const dataImportRe = /(from\s+['"])((?:\.\.\/)+|@site\/)([^'"\s]+\.(?:json|ya?ml))(['"])/g; - - function freezeOne(fullPath, depth, prefix, pathSpec, importPath, suffix) { - let resolvedSource; - if (pathSpec === '@site/') { - // `@site/...` always resolves relative to the docs root. - resolvedSource = path.join(docsRoot, importPath); - } else { - // Relative path — must escape the file's depth within the section - // to point at content outside the section. Imports that stay inside - // are copied wholesale by Docusaurus, so we leave them alone. - const upCount = pathSpec.match(/\.\.\//g).length; - if (upCount <= depth) return null; - const relativeFromVersioned = path.relative(versionedDocsPath, fullPath); - const originalDir = path.dirname(path.join(sectionRoot, relativeFromVersioned)); - resolvedSource = path.resolve(originalDir, pathSpec + importPath); - } - // Skip imports that land inside the section root — those get copied - // with the section snapshot already. - const relFromSection = path.relative(sectionRoot, resolvedSource); - if (!relFromSection.startsWith('..')) return null; - const relFromDocsRoot = path.relative(docsRoot, resolvedSource); - if (relFromDocsRoot.startsWith('..') || !fs.existsSync(resolvedSource)) { - return null; - } - const destPath = path.join(frozenDataDir, relFromDocsRoot); - fs.mkdirSync(path.dirname(destPath), { recursive: true }); - fs.copyFileSync(resolvedSource, destPath); - const rewritten = path - .relative(path.dirname(fullPath), destPath) - .split(path.sep) - .join('/'); - const finalImport = rewritten.startsWith('.') ? rewritten : `./${rewritten}`; - return `${prefix}${finalImport}${suffix}`; - } - - function walk(dir, depth) { - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - if (entry.name.startsWith('_')) continue; - walk(fullPath, depth + 1); - } else if (entry.isFile() && /\.(md|mdx)$/.test(entry.name)) { - const original = fs.readFileSync(fullPath, 'utf8'); - let inFence = false; - let mutated = false; - const updated = original.split('\n').map(line => { - if (/^\s*(```|~~~)/.test(line)) { - inFence = !inFence; - return line; - } - if (inFence) return line; - return line.replace(dataImportRe, (match, prefix, pathSpec, importPath, suffix) => { - const rewritten = freezeOne(fullPath, depth, prefix, pathSpec, importPath, suffix); - if (rewritten === null) return match; - mutated = true; - return rewritten; - }); - }).join('\n'); - if (mutated) { - fs.writeFileSync(fullPath, updated); - const rel = path.relative(versionedDocsPath, fullPath); - console.log(` Froze data imports in ${rel}`); - } - } - } - } - - walk(versionedDocsPath, 0); -} - -function fixVersionedImports(section, version) { - // Versioned content lands one directory deeper than the source content, - // so any `../../src/` or `../../data/` imports in .md/.mdx files need - // an extra `../` to keep reaching docs/src and docs/data. - const versionedDocsDir = section === 'docs' - ? `versioned_docs/version-${version}` - : `${section}_versioned_docs/version-${version}`; - const versionedDocsPath = path.join(__dirname, '..', versionedDocsDir); - - if (!fs.existsSync(versionedDocsPath)) { - return; - } - - console.log(` Fixing relative imports in ${versionedDocsDir}...`); - - // Imports whose `../` count exceeds the file's depth within the section - // escape the section root, so they need one extra `../` once the file - // lives one level deeper inside the snapshot dir. Imports that stay - // inside the section are unaffected (the section copies wholesale). - function walk(dir, depth) { - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - walk(fullPath, depth + 1); - } else if (entry.isFile() && /\.(md|mdx)$/.test(entry.name)) { - const original = fs.readFileSync(fullPath, 'utf8'); - // Track fenced code blocks so we don't rewrite import samples inside - // ```ts / ```js (etc.) blocks that are documentation, not real imports. - let inFence = false; - const updated = original.split('\n').map(line => { - if (/^\s*(```|~~~)/.test(line)) { - inFence = !inFence; - return line; - } - if (inFence) return line; - return line.replace( - /(from\s+['"])((?:\.\.\/)+)/g, - (match, prefix, dots) => { - const upCount = dots.match(/\.\.\//g).length; - return upCount > depth ? `${prefix}../${dots}` : match; - }, - ); - }).join('\n'); - if (updated !== original) { - fs.writeFileSync(fullPath, updated); - const rel = path.relative(versionedDocsPath, fullPath); - console.log(` Fixed imports in ${rel}`); - } - } - } - } - - walk(versionedDocsPath, 0); -} - -function addVersion(section, version) { - const config = loadConfig(); - - if (!config[section]) { - console.error(`Section '${section}' not found in config`); - process.exit(1); - } - - // Check if version already exists - if (config[section].onlyIncludeVersions.includes(version)) { - console.error(`Version ${version} already exists in ${section}`); - process.exit(1); - } - - console.log(`Creating version ${version} for ${section}...`); - - // Refresh auto-generated content (database pages, API reference, - // component playground) so the snapshot captures the current state of - // master rather than whatever happened to be on disk. `generate:smart` - // hashes its inputs and skips unchanged generators, so this is cheap - // when the dev already has fresh output. - // - // Use --skip-generate if you've placed a CI-artifact databases.json - // (the `database-diagnostics` artifact from Python-Integration) and - // want to preserve it instead of letting the local env regenerate it. - // See docs/README.md "Before You Cut" for the canonical release flow. - if (skipGenerate) { - console.log(` Skipping auto-gen refresh (--skip-generate set)`); - } else { - console.log(` Refreshing auto-generated docs...`); - try { - execSync('yarn run generate:smart', { stdio: 'inherit' }); - } catch (error) { - console.error(`Failed to refresh auto-generated docs: ${error.message}`); - process.exit(1); - } - } - - // Run Docusaurus version command - const docusaurusCommand = section === 'docs' - ? `yarn docusaurus docs:version ${version}` - : `yarn docusaurus docs:version:${section} ${version}`; - - try { - execSync(docusaurusCommand, { stdio: 'inherit' }); - } catch (error) { - console.error(`Failed to create version: ${error.message}`); - process.exit(1); - } - - // Freeze data imports BEFORE adjusting paths, so the depth-aware rewriter - // doesn't process the now-local imports we just rewrote. - freezeDataImports(section, version); - - // Fix relative imports in versioned content - fixVersionedImports(section, version); - - // Update config - // Add to onlyIncludeVersions array (after 'current') - const versionIndex = config[section].onlyIncludeVersions.indexOf('current') + 1; - config[section].onlyIncludeVersions.splice(versionIndex, 0, version); - - // Add version metadata - const versionPath = section === 'docs' ? version : version; - config[section].versions[version] = { - label: version, - path: versionPath, - banner: 'none' - }; - - // Note: we deliberately do NOT auto-bump `lastVersion` to the new - // version. Superset's docs site keeps `lastVersion: 'current'` so - // the canonical URLs (`/user-docs/...`, `/admin-docs/...`, - // `/developer-docs/...`, `/components/...`) always render master - // content; cut versions are accessed only via their explicit version - // segment. (`/docs/...` paths are legacy and handled via per-page - // redirects in docusaurus.config.ts — not a current canonical - // form.) If you want a different policy, edit versions-config.json - // after cutting. - - saveConfig(config); - console.log(`✅ Version ${version} added successfully to ${section}`); - console.log(`📝 Updated versions-config.json`); -} - -function removeVersion(section, version) { - const config = loadConfig(); - - if (!config[section]) { - console.error(`Section '${section}' not found in config`); - process.exit(1); - } - - if (version === 'current') { - console.error(`Cannot remove 'current' version`); - process.exit(1); - } - - if (!config[section].onlyIncludeVersions.includes(version)) { - console.error(`Version ${version} not found in ${section}`); - process.exit(1); - } - - console.log(`Removing version ${version} from ${section}...`); - - // Determine file paths based on section - const versionedDocsDir = section === 'docs' - ? `versioned_docs/version-${version}` - : `${section}_versioned_docs/version-${version}`; - - const versionedSidebarsFile = section === 'docs' - ? `versioned_sidebars/version-${version}-sidebars.json` - : `${section}_versioned_sidebars/version-${version}-sidebars.json`; - - // Remove versioned files - const docsPath = path.join(__dirname, '..', versionedDocsDir); - const sidebarsPath = path.join(__dirname, '..', versionedSidebarsFile); - - if (fs.existsSync(docsPath)) { - fs.rmSync(docsPath, { recursive: true }); - console.log(` Removed ${versionedDocsDir}`); - } - - if (fs.existsSync(sidebarsPath)) { - fs.unlinkSync(sidebarsPath); - console.log(` Removed ${versionedSidebarsFile}`); - } - - // Update versions.json file - const versionsJsonFile = section === 'docs' - ? 'versions.json' - : `${section}_versions.json`; - const versionsJsonPath = path.join(__dirname, '..', versionsJsonFile); - - if (fs.existsSync(versionsJsonPath)) { - const versions = JSON.parse(fs.readFileSync(versionsJsonPath, 'utf8')); - const versionIndex = versions.indexOf(version); - if (versionIndex > -1) { - versions.splice(versionIndex, 1); - if (versions.length === 0) { - // Sections with no versions shouldn't carry an empty versions file - // on disk — Docusaurus doesn't require it, and an empty `[]` file - // gets picked up by `docusaurus version` and snapshotted into the - // next cut. - fs.unlinkSync(versionsJsonPath); - console.log(` Removed empty ${versionsJsonFile}`); - } else { - fs.writeFileSync(versionsJsonPath, JSON.stringify(versions, null, 2) + '\n'); - console.log(` Updated ${versionsJsonFile}`); - } - } - } - - // Update config - const versionIndex = config[section].onlyIncludeVersions.indexOf(version); - config[section].onlyIncludeVersions.splice(versionIndex, 1); - delete config[section].versions[version]; - - // Update lastVersion if needed - if (config[section].lastVersion === version) { - // Set to the next available version or 'current' - const remainingVersions = config[section].onlyIncludeVersions.filter(v => v !== 'current'); - config[section].lastVersion = remainingVersions.length > 0 ? remainingVersions[0] : 'current'; - console.log(` Updated lastVersion to ${config[section].lastVersion}`); - } - - saveConfig(config); - console.log(`✅ Version ${version} removed successfully from ${section}`); - console.log(`📝 Updated versions-config.json`); -} - -function printUsage() { - console.log(` -Usage: - node scripts/manage-versions.mjs add
[--skip-generate] - node scripts/manage-versions.mjs remove
- -Where: - - section: 'docs', 'developer_docs', 'admin_docs', or 'components' - - version: version string (e.g., '1.2.0', '2.0.0') - - --skip-generate: skip refreshing auto-generated docs before snapshotting - (use when you've already placed a fresh databases.json - from CI and want to preserve it) - -Examples: - node scripts/manage-versions.mjs add docs 2.0.0 - node scripts/manage-versions.mjs add developer_docs 1.3.0 - node scripts/manage-versions.mjs remove components 1.0.0 -`); -} - -// Main execution -if (!command || !section || !version) { - printUsage(); - process.exit(1); -} - -if (command === 'add') { - addVersion(section, version); -} else if (command === 'remove') { - removeVersion(section, version); -} else { - console.error(`Unknown command: ${command}`); - printUsage(); - process.exit(1); -} diff --git a/docs/sidebarAdminDocs.js b/docs/sidebarAdminDocs.js deleted file mode 100644 index b9d941a7318..00000000000 --- a/docs/sidebarAdminDocs.js +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-env node */ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// @ts-check - -/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ -const sidebars = { - AdminDocsSidebar: [ - { - type: 'doc', - label: 'Overview', - id: 'index', - }, - { - type: 'category', - label: 'Installation', - collapsed: false, - items: [ - { - type: 'autogenerated', - dirName: 'installation', - }, - ], - }, - { - type: 'category', - label: 'Configuration', - collapsed: true, - items: [ - { - type: 'autogenerated', - dirName: 'configuration', - }, - ], - }, - { - type: 'link', - label: 'Database Drivers', - href: '/user-docs/databases/', - description: 'See User Docs for database connection guides', - }, - { - type: 'category', - label: 'Security', - collapsed: true, - items: [ - { - type: 'autogenerated', - dirName: 'security', - }, - ], - }, - ], -}; - -module.exports = sidebars; diff --git a/docs/sidebarComponents.js b/docs/sidebarComponents.js deleted file mode 100644 index f618dfa66d5..00000000000 --- a/docs/sidebarComponents.js +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-env node */ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// @ts-check - -/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ -const sidebars = { - // By default, Docusaurus generates a sidebar from the docs folder structure - //tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], - - // But we're not doing that. - ComponentSidebar: [ - { - type: 'doc', - label: 'Introduction', - id: 'index', - }, - { - type: 'category', - label: 'UI Components', - items: [ - { - type: 'autogenerated', - dirName: 'ui-components', - }, - ], - }, - { - type: 'category', - label: 'Chart Components', - items: [ - { - type: 'autogenerated', - dirName: 'chart-components', - }, - ], - }, - { - type: 'category', - label: 'Layout Components', - items: [ - { - type: 'autogenerated', - dirName: 'layout-components', - }, - ], - }, - ], -}; - -module.exports = sidebars; diff --git a/docs/sidebarTutorials.js b/docs/sidebarTutorials.js deleted file mode 100644 index 807b1976091..00000000000 --- a/docs/sidebarTutorials.js +++ /dev/null @@ -1,140 +0,0 @@ -/* eslint-env node */ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// @ts-check - -/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ -const sidebars = { - DeveloperPortalSidebar: [ - { - type: 'doc', - label: 'Overview', - id: 'index', - }, - { - type: 'category', - label: 'Contributing', - collapsed: true, - items: [ - 'contributing/overview', - 'contributing/development-setup', - 'contributing/submitting-pr', - 'contributing/guidelines', - 'contributing/code-review', - 'contributing/issue-reporting', - 'contributing/howtos', - 'contributing/release-process', - 'contributing/resources', - 'contributing/pkg-resources-migration', - 'guidelines/design-guidelines', - { - type: 'category', - label: 'Frontend Style Guidelines', - collapsed: true, - items: [ - 'guidelines/frontend-style-guidelines', - 'guidelines/frontend/component-style-guidelines', - 'guidelines/frontend/emotion-styling-guidelines', - ], - }, - { - type: 'category', - label: 'Backend Style Guidelines', - collapsed: true, - items: [ - 'guidelines/backend-style-guidelines', - 'guidelines/backend/dao-style-guidelines', - ], - }, - ], - }, - { - type: 'category', - label: 'Extensions', - collapsed: true, - items: [ - 'extensions/overview', - 'extensions/quick-start', - 'extensions/architecture', - 'extensions/dependencies', - 'extensions/contribution-types', - { - type: 'category', - label: 'Extension Points', - collapsed: true, - items: [ - 'extensions/extension-points/sqllab', - 'extensions/extension-points/editors', - ], - }, - 'extensions/development', - 'extensions/deployment', - 'extensions/mcp', - 'extensions/security', - 'extensions/tasks', - 'extensions/registry', - ], - }, - { - type: 'category', - label: 'Testing', - collapsed: true, - items: [ - 'testing/overview', - 'testing/testing-guidelines', - '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: 'category', - label: 'API Reference', - link: { - type: 'doc', - id: 'api', - }, - items: (() => { - try { - // eslint-disable-next-line @typescript-eslint/no-require-imports - return require('./developer_docs/api/sidebar.js'); - } catch { - // Generated by `yarn generate:api-docs`; empty until then - return []; - } - })(), - }, - ], -}; - -module.exports = sidebars; diff --git a/docs/sidebars.js b/docs/sidebars.js deleted file mode 100644 index fa7c7c5ef53..00000000000 --- a/docs/sidebars.js +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint-env node */ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// @ts-check - -/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ -const sidebars = { - // User Docs sidebar - for analysts and business users - CustomSidebar: [ - { - type: 'doc', - label: 'Overview', - id: 'index', - }, - { - type: 'doc', - label: 'Quickstart', - id: 'quickstart', - }, - { - type: 'category', - label: 'Using Superset', - collapsed: false, - items: [ - { - type: 'autogenerated', - dirName: 'using-superset', - }, - ], - }, - { - type: 'category', - label: 'Connecting to Databases', - collapsed: true, - link: { - type: 'doc', - id: 'databases/index', - }, - items: [ - { - type: 'autogenerated', - dirName: 'databases', - }, - ], - }, - { - type: 'doc', - label: 'FAQ', - id: 'faq', - }, - ], -}; - -module.exports = sidebars; diff --git a/docs/src/components/BlurredSection.tsx b/docs/src/components/BlurredSection.tsx deleted file mode 100644 index 712a36fa8c5..00000000000 --- a/docs/src/components/BlurredSection.tsx +++ /dev/null @@ -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. - */ -import { ReactNode } from 'react'; -import styled from '@emotion/styled'; -import { mq } from '../utils'; - -const StyledBlurredSection = styled('section')` - text-align: center; - border-bottom: 1px solid var(--ifm-border-color); - overflow: hidden; - .blur { - max-width: 635px; - width: 100%; - margin-top: -70px; - margin-bottom: -35px; - position: relative; - z-index: -1; - ${mq[1]} { - margin-top: -40px; - } - } -`; - -interface BlurredSectionProps { - children: ReactNode; - id?: string; -} - -const BlurredSection = ({ children, id }: BlurredSectionProps) => { - return ( - - {children} - Blur - - ); -}; - -export default BlurredSection; diff --git a/docs/src/components/Button.jsx b/docs/src/components/Button.jsx deleted file mode 100644 index 39af33f58c1..00000000000 --- a/docs/src/components/Button.jsx +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// Re-export the Button component as a default export -import { Button } from '../../../superset-frontend/packages/superset-ui-core/src/components/Button'; - -export default Button; diff --git a/docs/src/components/FAQSchema.tsx b/docs/src/components/FAQSchema.tsx deleted file mode 100644 index 45a56b424b7..00000000000 --- a/docs/src/components/FAQSchema.tsx +++ /dev/null @@ -1,66 +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 type { JSX } from 'react'; -import Head from '@docusaurus/Head'; - -interface FAQItem { - question: string; - answer: string; -} - -interface FAQSchemaProps { - faqs: FAQItem[]; -} - -/** - * Component that injects FAQPage JSON-LD structured data - * Use this on FAQ pages to enable rich snippets in search results - * - * @example - * - */ -export default function FAQSchema({ faqs }: FAQSchemaProps): JSX.Element | null { - // FAQPage schema requires a non-empty mainEntity array per schema.org specs - if (!faqs || faqs.length === 0) { - return null; - } - - const schema = { - '@context': 'https://schema.org', - '@type': 'FAQPage', - mainEntity: faqs.map((faq) => ({ - '@type': 'Question', - name: faq.question, - acceptedAnswer: { - '@type': 'Answer', - text: faq.answer, - }, - })), - }; - - return ( - - - - ); -} diff --git a/docs/src/components/GetStartedSplitButton.tsx b/docs/src/components/GetStartedSplitButton.tsx deleted file mode 100644 index 9f4a541884c..00000000000 --- a/docs/src/components/GetStartedSplitButton.tsx +++ /dev/null @@ -1,155 +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 { DownOutlined } from '@ant-design/icons'; -import Link from '@docusaurus/Link'; -import { Dropdown } from 'antd'; -import type { MenuProps } from 'antd'; -import styled from '@emotion/styled'; -import { mq } from '../utils.js'; - -const getStartedMenuItems: MenuProps['items'] = [ - { key: 'users', label: Users }, - { key: 'admins', label: Admins }, - { key: 'developers', label: Developers }, -]; - -const Root = styled.div<{ $variant: 'hero' | 'navbar' }>` - display: flex; - align-items: stretch; - border-radius: 10px; - overflow: hidden; - position: relative; - z-index: 2; - font-weight: bold; - - ${({ $variant }) => - $variant === 'hero' - ? ` - width: 208px; - margin: 15px auto 0; - font-size: 20px; - ${mq[1]} { - font-size: 19px; - width: 214px; - } - ` - : ` - width: 176px; - margin-right: 20px; - font-size: 18px; - `} - - .split-main { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - color: #ffffff; - text-decoration: none; - min-width: 0; - ${({ $variant }) => - $variant === 'hero' - ? `padding: 10px 10px;` - : `padding: 7px 8px;`} - } - - .split-main:hover { - color: #ffffff; - } - - .split-divider { - width: 1px; - flex-shrink: 0; - align-self: stretch; - background: rgba(255, 255, 255, 0.38); - ${({ $variant }) => - $variant === 'hero' - ? `margin: 8px 0;` - : `margin: 6px 0;`} - } - - .split-dropdown-trigger { - flex-shrink: 0; - border: none; - padding: 0; - margin: 0; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - background: transparent; - color: #ffffff; - ${({ $variant }) => - $variant === 'hero' - ? ` - width: 44px; - font-size: 11px; - ${mq[1]} { - width: 46px; - } - ` - : ` - width: 38px; - font-size: 10px; - `} - } - - .split-dropdown-trigger:hover { - color: #ffffff; - } -`; - -export type GetStartedSplitButtonProps = { - variant: 'hero' | 'navbar'; - /** Classes for the outer control (include default-button-theme get-started-split) */ - rootClassName: string; -}; - -export default function GetStartedSplitButton({ - variant, - rootClassName, -}: GetStartedSplitButtonProps) { - const menuClassName = `get-started-split-dropdown-menu get-started-split-dropdown-menu--${variant}`; - - return ( - - - Get Started - - - - - - - ); -} diff --git a/docs/src/components/InteractiveERDSVG.jsx b/docs/src/components/InteractiveERDSVG.jsx deleted file mode 100644 index 6dd0d9e1718..00000000000 --- a/docs/src/components/InteractiveERDSVG.jsx +++ /dev/null @@ -1,37 +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 { UncontrolledReactSVGPanZoom } from 'react-svg-pan-zoom'; -import ErdSvg from '../../static/img/erd.svg'; - -function InteractiveERDSVG() { - return ( - - - - - - ); -} - -export default InteractiveERDSVG; diff --git a/docs/src/components/SectionHeader.tsx b/docs/src/components/SectionHeader.tsx deleted file mode 100644 index d0cc50e6cda..00000000000 --- a/docs/src/components/SectionHeader.tsx +++ /dev/null @@ -1,133 +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 { ReactNode } from 'react'; -import styled from '@emotion/styled'; -import { mq } from '../utils'; - -type StyledSectionHeaderProps = { - dark: boolean; -}; - -const StyledSectionHeader = styled('div')` - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - padding: 75px 20px 0; - max-width: 720px; - margin: 0 auto; - ${mq[1]} { - padding-top: 55px; - } - .title, - .subtitle { - color: ${props => - props.dark - ? 'var(--ifm-font-base-color-inverse)' - : 'var(--ifm-font-base-color)'}; - } -`; - -const StyledSectionHeaderH1 = styled(StyledSectionHeader)` - .title { - font-size: 96px; - ${mq[1]} { - font-size: 46px; - } - } - .line { - margin-top: -45px; - margin-bottom: 15px; - ${mq[1]} { - margin-top: -20px; - margin-bottom: 30px; - } - } - .subtitle { - font-size: 30px; - line-height: 40px; - ${mq[1]} { - font-size: 25px; - line-height: 29px; - } - } -`; - -const StyledSectionHeaderH2 = styled(StyledSectionHeader)` - .title { - font-size: 48px; - ${mq[1]} { - font-size: 34px; - } - } - .line { - margin-top: -15px; - margin-bottom: 15px; - ${mq[1]} { - margin-top: -5px; - } - } - .subtitle { - font-size: 24px; - line-height: 32px; - ${mq[1]} { - font-size: 18px; - line-height: 26px; - } - } -`; - -interface SectionHeaderProps { - level: 'h1' | 'h2'; - title: string; - subtitle?: string | ReactNode; - dark?: boolean; - link?: string; -} - -const SectionHeader = ({ - level, - title, - subtitle, - dark, - link, -}: SectionHeaderProps) => { - const Heading = level; - - const StyledRoot = - level === 'h1' ? StyledSectionHeaderH1 : StyledSectionHeaderH2; - - const titleContent = link ? ( - - {title} - - ) : ( - title - ); - - return ( - - {titleContent} - line - {subtitle &&
{subtitle}
} -
- ); -}; - -export default SectionHeader; diff --git a/docs/src/components/StorybookWrapper.jsx b/docs/src/components/StorybookWrapper.jsx deleted file mode 100644 index 1220b56add5..00000000000 --- a/docs/src/components/StorybookWrapper.jsx +++ /dev/null @@ -1,530 +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 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/components'); - - // 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/theme'); - // 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 }) => ( - - document.body} - > - {children} - - - ); - 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 ( -
- Loading component... -
- ); -} - -// A simple component to display a story example -export function StoryExample({ component, props = {} }) { - return ( - }> - {() => { - const Component = resolveComponent(component); - const Providers = getProviders(); - const { children, restProps } = extractChildren(props); - return ( - -
- {Component ? ( - {children} - ) : ( -
- Component "{String(component)}" not found -
- )} -
-
- ); - }} -
- ); -} - -// 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 ; - } - // Fallback if component not found - return
{item.props?.children || `Unknown: ${item.component}`}
; - } - // Simple string - return ( -
- {item} -
- ); - }); - } - - // If it's a number, generate that many sample items - if (typeof sampleChildren === 'number') { - return new Array(sampleChildren).fill(null).map((_, i) => ( -
- Item {i + 1} -
- )); - } - - // 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(); - const [stateProps, setStateProps] = React.useState(props); - - const updateProp = (key, value) => { - setStateProps(prev => ({ - ...prev, - [key]: value, - })); - }; - - // 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] = ; - } - } - 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 ( - -
-
- {Component ? ( - <> - {/* Show a trigger button for components like Modal */} - {triggerProp && ButtonComponent && ( - updateProp(triggerProp, true)}> - Open {component} - - )} - {children} - - ) : ( -
- Component "{String(componentToRender)}" not found -
- )} -
- - {controls.length > 0 && ( -
-

Controls

- {controls.map(control => ( -
- - {control.type === 'select' ? ( - - ) : control.type === 'inline-radio' || control.type === 'radio' ? ( -
- {control.options?.map(option => ( - - ))} -
- ) : control.type === 'boolean' ? ( - updateProp(control.name, e.target.checked)} - /> - ) : control.type === 'number' ? ( - updateProp(control.name, Number(e.target.value))} - style={{ width: '100%', padding: '5px' }} - /> - ) : control.type === 'color' ? ( - updateProp(control.name, e.target.value)} - style={{ - width: '50px', - height: '30px', - padding: '2px', - cursor: 'pointer', - }} - /> - ) : ( - updateProp(control.name, e.target.value)} - style={{ width: '100%', padding: '5px' }} - /> - )} -
- ))} -
- )} -
-
- ); -} - -// 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 ( - }> - {() => ( - - )} - - ); -} - -// Inner component for ComponentGallery (browser-only) -function ComponentGalleryInner({ component, sizes, styles, sizeProp, styleProp }) { - const Component = resolveComponent(component); - const Providers = getProviders(); - - if (!Component) { - return ( -
- Component "{String(component)}" not found -
- ); - } - - return ( - -
- {sizes.map(size => ( -
-

{size}

-
- {styles.map(style => ( - - {style} - - ))} -
-
- ))} -
-
- ); -} - -// A component to display a gallery of all variants (sizes x styles) -export function ComponentGallery({ component, sizes = [], styles = [], sizeProp = 'size', styleProp = 'variant' }) { - return ( - }> - {() => ( - - )} - - ); -} diff --git a/docs/src/components/TechArticleSchema.tsx b/docs/src/components/TechArticleSchema.tsx deleted file mode 100644 index 9a0fc049b4e..00000000000 --- a/docs/src/components/TechArticleSchema.tsx +++ /dev/null @@ -1,91 +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 type { JSX } from 'react'; -import Head from '@docusaurus/Head'; -import { useLocation } from '@docusaurus/router'; - -interface TechArticleSchemaProps { - title: string; - description: string; - datePublished?: string; - dateModified?: string; - keywords?: string[]; - proficiencyLevel?: 'Beginner' | 'Expert'; -} - -/** - * Component that injects TechArticle JSON-LD structured data for documentation pages. - * This helps search engines understand technical documentation content. - * - * @example - * - */ -export default function TechArticleSchema({ - title, - description, - datePublished, - dateModified, - keywords = [], - proficiencyLevel = 'Beginner', -}: TechArticleSchemaProps): JSX.Element { - const location = useLocation(); - const url = `https://superset.apache.org${location.pathname}`; - - const schema = { - '@context': 'https://schema.org', - '@type': 'TechArticle', - headline: title, - description, - url, - proficiencyLevel, - author: { - '@type': 'Organization', - name: 'Apache Superset Contributors', - url: 'https://github.com/apache/superset/graphs/contributors', - }, - publisher: { - '@type': 'Organization', - name: 'Apache Software Foundation', - url: 'https://www.apache.org/', - logo: { - '@type': 'ImageObject', - url: 'https://www.apache.org/foundation/press/kit/asf_logo.png', - }, - }, - mainEntityOfPage: { - '@type': 'WebPage', - '@id': url, - }, - ...(datePublished && { datePublished }), - ...(dateModified && { dateModified }), - ...(keywords.length > 0 && { keywords: keywords.join(', ') }), - }; - - return ( - - - - ); -} diff --git a/docs/src/components/databases/DatabaseIndex.tsx b/docs/src/components/databases/DatabaseIndex.tsx deleted file mode 100644 index 91c58964a31..00000000000 --- a/docs/src/components/databases/DatabaseIndex.tsx +++ /dev/null @@ -1,593 +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, { useState, useMemo } from 'react'; -import { Card, Row, Col, Statistic, Table, Tag, Input, Select, Tooltip } from 'antd'; -import { - DatabaseOutlined, - CheckCircleOutlined, - ApiOutlined, - KeyOutlined, - SearchOutlined, - LinkOutlined, - BugOutlined, -} from '@ant-design/icons'; -import type { DatabaseData, DatabaseInfo, TimeGrains } from './types'; - -interface DatabaseIndexProps { - data: DatabaseData; -} - -// Type for table entries (includes both regular DBs and compatible DBs) -interface TableEntry { - name: string; - categories: string[]; // Multiple categories supported - score: number; - max_score: number; - timeGrainCount: number; - time_grains?: TimeGrains; - hasDrivers: boolean; - hasAuthMethods: boolean; - hasConnectionString: boolean; - hasCustomErrors: boolean; - customErrorCount: number; - joins?: boolean; - subqueries?: boolean; - supports_dynamic_schema?: boolean; - supports_catalog?: boolean; - ssh_tunneling?: boolean; - supports_file_upload?: boolean; - query_cancelation?: boolean; - query_cost_estimation?: boolean; - user_impersonation?: boolean; - sql_validation?: boolean; - documentation?: DatabaseInfo['documentation']; - // For compatible databases - isCompatible?: boolean; - compatibleWith?: string; - compatibleDescription?: string; -} - -// Map category constant names to display names -const CATEGORY_DISPLAY_NAMES: Record = { - 'CLOUD_AWS': 'Cloud - AWS', - 'CLOUD_GCP': 'Cloud - Google', - 'CLOUD_AZURE': 'Cloud - Azure', - 'CLOUD_DATA_WAREHOUSES': 'Cloud Data Warehouses', - 'APACHE_PROJECTS': 'Apache Projects', - 'TRADITIONAL_RDBMS': 'Traditional RDBMS', - 'ANALYTICAL_DATABASES': 'Analytical Databases', - 'SEARCH_NOSQL': 'Search & NoSQL', - 'QUERY_ENGINES': 'Query Engines', - 'TIME_SERIES': 'Time Series Databases', - 'OTHER': 'Other Databases', - 'OPEN_SOURCE': 'Open Source', - 'HOSTED_OPEN_SOURCE': 'Hosted Open Source', - 'PROPRIETARY': 'Proprietary', -}; - -// Category colors for visual distinction -const CATEGORY_COLORS: Record = { - 'Cloud - AWS': 'orange', - 'Cloud - Google': 'blue', - 'Cloud - Azure': 'cyan', - 'Cloud Data Warehouses': 'purple', - 'Apache Projects': 'red', - 'Traditional RDBMS': 'green', - 'Analytical Databases': 'magenta', - 'Search & NoSQL': 'gold', - 'Query Engines': 'lime', - 'Time Series Databases': 'volcano', - 'Other Databases': 'default', - // Licensing categories - 'Open Source': 'geekblue', - 'Hosted Open Source': 'cyan', - 'Proprietary': 'default', -}; - -// Convert category constant to display name -function getCategoryDisplayName(cat: string): string { - return CATEGORY_DISPLAY_NAMES[cat] || cat; -} - -// Get categories for a database - uses categories from metadata when available -// Falls back to name-based inference for compatible databases without categories -function getCategories( - name: string, - documentationCategories?: string[] -): string[] { - // Prefer categories from documentation metadata (computed by Python) - if (documentationCategories && documentationCategories.length > 0) { - return documentationCategories.map(getCategoryDisplayName); - } - - // Fallback: infer from name (for compatible databases without categories) - const nameLower = name.toLowerCase(); - - if (nameLower.includes('aws') || nameLower.includes('amazon')) - return ['Cloud - AWS']; - if (nameLower.includes('google') || nameLower.includes('bigquery')) - return ['Cloud - Google']; - if (nameLower.includes('azure') || nameLower.includes('microsoft')) - return ['Cloud - Azure']; - if (nameLower.includes('snowflake') || nameLower.includes('databricks')) - return ['Cloud Data Warehouses']; - if ( - nameLower.includes('apache') || - nameLower.includes('druid') || - nameLower.includes('hive') || - nameLower.includes('spark') - ) - return ['Apache Projects']; - if ( - nameLower.includes('postgres') || - nameLower.includes('mysql') || - nameLower.includes('sqlite') || - nameLower.includes('mariadb') - ) - return ['Traditional RDBMS']; - if ( - nameLower.includes('clickhouse') || - nameLower.includes('vertica') || - nameLower.includes('starrocks') - ) - return ['Analytical Databases']; - if ( - nameLower.includes('elastic') || - nameLower.includes('solr') || - nameLower.includes('couchbase') - ) - return ['Search & NoSQL']; - if (nameLower.includes('trino') || nameLower.includes('presto')) - return ['Query Engines']; - - return ['Other Databases']; -} - -// Count supported time grains -function countTimeGrains(db: DatabaseInfo): number { - if (!db.time_grains) return 0; - return Object.values(db.time_grains).filter(Boolean).length; -} - -// Format time grain name for display (e.g., FIVE_MINUTES -> "5 min") -function formatTimeGrain(grain: string): string { - const mapping: Record = { - SECOND: 'Second', - FIVE_SECONDS: '5 sec', - THIRTY_SECONDS: '30 sec', - MINUTE: 'Minute', - FIVE_MINUTES: '5 min', - TEN_MINUTES: '10 min', - FIFTEEN_MINUTES: '15 min', - THIRTY_MINUTES: '30 min', - HALF_HOUR: '30 min', - HOUR: 'Hour', - SIX_HOURS: '6 hours', - DAY: 'Day', - WEEK: 'Week', - WEEK_STARTING_SUNDAY: 'Week (Sun)', - WEEK_STARTING_MONDAY: 'Week (Mon)', - WEEK_ENDING_SATURDAY: 'Week (→Sat)', - WEEK_ENDING_SUNDAY: 'Week (→Sun)', - MONTH: 'Month', - QUARTER: 'Quarter', - QUARTER_YEAR: 'Quarter', - YEAR: 'Year', - }; - return mapping[grain] || grain; -} - -// Get list of supported time grains for tooltip -function getSupportedTimeGrains(timeGrains?: TimeGrains): string[] { - if (!timeGrains) return []; - return Object.entries(timeGrains) - .filter(([, supported]) => supported) - .map(([grain]) => formatTimeGrain(grain)); -} - -const DatabaseIndex: React.FC = ({ data }) => { - const [searchText, setSearchText] = useState(''); - const [categoryFilter, setCategoryFilter] = useState(null); - - const { statistics, databases } = data; - - // Convert databases object to array, including compatible databases - const databaseList = useMemo(() => { - const entries: TableEntry[] = []; - - Object.entries(databases).forEach(([name, db]) => { - // Add the main database - // Use categories from documentation metadata (computed by Python) when available - entries.push({ - ...db, - name, - categories: getCategories(name, db.documentation?.categories), - timeGrainCount: countTimeGrains(db), - hasDrivers: (db.documentation?.drivers?.length ?? 0) > 0, - hasAuthMethods: (db.documentation?.authentication_methods?.length ?? 0) > 0, - hasConnectionString: Boolean( - 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, - }); - - // Add compatible databases from this database's documentation - const compatibleDbs = db.documentation?.compatible_databases ?? []; - compatibleDbs.forEach((compat) => { - // Check if this compatible DB already exists as a main entry - const existsAsMain = Object.keys(databases).some( - (dbName) => dbName.toLowerCase() === compat.name.toLowerCase() - ); - - if (!existsAsMain) { - // Compatible databases: use their categories if defined, or infer from name - entries.push({ - name: compat.name, - categories: getCategories(compat.name, compat.categories), - // Compatible DBs inherit scores from parent - score: db.score, - max_score: db.max_score, - timeGrainCount: countTimeGrains(db), - 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, - supports_catalog: db.supports_catalog, - ssh_tunneling: db.ssh_tunneling, - documentation: { - description: compat.description, - connection_string: compat.connection_string, - pypi_packages: compat.pypi_packages, - }, - isCompatible: true, - compatibleWith: name, - compatibleDescription: `Uses ${name} driver`, - }); - } - }); - }); - - return entries; - }, [databases]); - - // Filter and sort databases - const filteredDatabases = useMemo(() => { - return databaseList - .filter((db) => { - const matchesSearch = - !searchText || - db.name.toLowerCase().includes(searchText.toLowerCase()) || - db.documentation?.description - ?.toLowerCase() - .includes(searchText.toLowerCase()); - const matchesCategory = !categoryFilter || db.categories.includes(categoryFilter); - return matchesSearch && matchesCategory; - }) - .sort((a, b) => b.score - a.score); - }, [databaseList, searchText, categoryFilter]); - - // Get unique categories and counts for filter - const { categories, categoryCounts } = useMemo(() => { - const counts: Record = {}; - databaseList.forEach((db) => { - // Count each category the database belongs to - db.categories.forEach((cat) => { - counts[cat] = (counts[cat] || 0) + 1; - }); - }); - return { - categories: Object.keys(counts).sort(), - categoryCounts: counts, - }; - }, [databaseList]); - - // Table columns - const columns = [ - { - title: 'Database', - dataIndex: 'name', - key: 'name', - sorter: (a: TableEntry, b: TableEntry) => a.name.localeCompare(b.name), - render: (name: string, record: TableEntry) => { - // Convert name to URL slug - const toSlug = (n: string) => n.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); - - // Link to parent for compatible DBs, otherwise to own page - const linkTarget = record.isCompatible && record.compatibleWith - ? `/docs/databases/supported/${toSlug(record.compatibleWith)}` - : `/docs/databases/supported/${toSlug(name)}`; - - return ( -
- - {name} - - {record.isCompatible && record.compatibleWith && ( - } - color="geekblue" - style={{ marginLeft: 8, fontSize: '11px' }} - > - {record.compatibleWith} compatible - - )} -
- {record.documentation?.description?.slice(0, 80)} - {(record.documentation?.description?.length ?? 0) > 80 ? '...' : ''} -
-
- ); - }, - }, - { - title: 'Categories', - dataIndex: 'categories', - key: 'categories', - width: 220, - filters: categories.map((cat) => ({ text: cat, value: cat })), - onFilter: (value: React.Key | boolean, record: TableEntry) => - record.categories.includes(value as string), - render: (cats: string[]) => ( -
- {cats.map((cat) => ( - {cat} - ))} -
- ), - }, - { - title: 'Score', - dataIndex: 'score', - key: 'score', - width: 80, - sorter: (a: TableEntry, b: TableEntry) => a.score - b.score, - defaultSortOrder: 'descend' as const, - render: (score: number, record: TableEntry) => ( - 150 ? '#52c41a' : score > 100 ? '#1890ff' : '#666', - fontWeight: score > 150 ? 'bold' : 'normal', - }} - > - {score}/{record.max_score} - - ), - }, - { - title: 'Time Grains', - dataIndex: 'timeGrainCount', - key: 'timeGrainCount', - width: 100, - sorter: (a: TableEntry, b: TableEntry) => a.timeGrainCount - b.timeGrainCount, - render: (count: number, record: TableEntry) => { - if (count === 0) return -; - const grains = getSupportedTimeGrains(record.time_grains); - return ( - - {grains.map((grain) => ( - {grain} - ))} -
- } - placement="top" - > - - {count} grains - - - ); - }, - }, - { - title: 'Features', - key: 'features', - width: 280, - filters: [ - { text: 'JOINs', value: 'joins' }, - { text: 'Subqueries', value: 'subqueries' }, - { text: 'Dynamic Schema', value: 'dynamic_schema' }, - { text: 'Catalog', value: 'catalog' }, - { text: 'SSH Tunneling', value: 'ssh' }, - { text: 'File Upload', value: 'file_upload' }, - { text: 'Query Cancel', value: 'query_cancel' }, - { text: 'Cost Estimation', value: 'cost_estimation' }, - { text: 'User Impersonation', value: 'impersonation' }, - { text: 'SQL Validation', value: 'sql_validation' }, - ], - onFilter: (value: React.Key | boolean, record: TableEntry) => { - switch (value) { - case 'joins': - return Boolean(record.joins); - case 'subqueries': - return Boolean(record.subqueries); - case 'dynamic_schema': - return Boolean(record.supports_dynamic_schema); - case 'catalog': - return Boolean(record.supports_catalog); - case 'ssh': - return Boolean(record.ssh_tunneling); - case 'file_upload': - return Boolean(record.supports_file_upload); - case 'query_cancel': - return Boolean(record.query_cancelation); - case 'cost_estimation': - return Boolean(record.query_cost_estimation); - case 'impersonation': - return Boolean(record.user_impersonation); - case 'sql_validation': - return Boolean(record.sql_validation); - default: - return true; - } - }, - render: (_: unknown, record: TableEntry) => ( -
- {record.joins && JOINs} - {record.subqueries && Subqueries} - {record.supports_dynamic_schema && Dynamic Schema} - {record.supports_catalog && Catalog} - {record.ssh_tunneling && SSH} - {record.supports_file_upload && File Upload} - {record.query_cancelation && Query Cancel} - {record.query_cost_estimation && Cost Est.} - {record.user_impersonation && Impersonation} - {record.sql_validation && SQL Validation} -
- ), - }, - { - title: 'Documentation', - key: 'docs', - width: 180, - render: (_: unknown, record: TableEntry) => ( -
- {record.hasConnectionString && ( - } color="default"> - Connection - - )} - {record.hasDrivers && ( - } color="default"> - Drivers - - )} - {record.hasAuthMethods && ( - } color="default"> - Auth - - )} - {record.hasCustomErrors && ( - - } color="volcano"> - Errors - - - )} -
- ), - }, - ]; - - return ( -
- {/* Statistics Cards */} - {/* Statistics Cards */} - - - - } - /> - - - - - } - suffix={`/ ${statistics.totalDatabases}`} - /> - - - - - } - /> - - - - - } - /> - - - - - {/* Filters */} - - - } - value={searchText} - onChange={(e) => setSearchText(e.target.value)} - allowClear - /> - - - } - value={searchText} - onChange={(e) => setSearchText(e.target.value)} - allowClear - /> - - -