mirror of
https://github.com/apache/superset.git
synced 2026-04-07 10:31:50 +00:00
docs(extensions): fix extension developer documentation and CLI scaffolding (#38472)
This commit is contained in:
committed by
GitHub
parent
5c4bf0f6ea
commit
296bd7e56b
@@ -33,13 +33,15 @@ The extension architecture is built on six core principles that guide all techni
|
||||
### 1. Lean Core
|
||||
|
||||
Superset's core should remain minimal, with many features delegated to extensions. Built-in features use the same APIs and extension mechanisms available to external developers. This approach:
|
||||
|
||||
- Reduces maintenance burden and complexity
|
||||
- Encourages modularity
|
||||
- Allows the community to innovate independently of the main codebase
|
||||
|
||||
### 2. Explicit Contribution Points
|
||||
|
||||
All extension points are clearly defined and documented. Extension authors know exactly where and how they can interact with the host system. Backend contributions are declared in `extension.json`. Frontend contributions are registered directly in code at module load time, giving the host clear visibility into what each extension provides:
|
||||
All extension points are clearly defined and documented. Extension authors know exactly where and how they can interact with the host system. Both backend and frontend contributions are registered directly in code — backend contributions via classes decorated with `@api` (and other decorators) imported from the auto-discovered entrypoint, frontend contributions via calls like `views.registerView` and `commands.registerCommand` executed at module load time in `index.tsx`. This gives the host clear visibility into what each extension provides:
|
||||
|
||||
- Manage the extension lifecycle
|
||||
- Provide a consistent user experience
|
||||
- Validate extension compatibility
|
||||
@@ -47,6 +49,7 @@ All extension points are clearly defined and documented. Extension authors know
|
||||
### 3. Versioned and Stable APIs
|
||||
|
||||
Public interfaces for extensions follow semantic versioning, allowing for:
|
||||
|
||||
- Safe evolution of the platform
|
||||
- Backward compatibility
|
||||
- Clear upgrade paths for extension authors
|
||||
@@ -54,6 +57,7 @@ Public interfaces for extensions follow semantic versioning, allowing for:
|
||||
### 4. Lazy Loading and Activation
|
||||
|
||||
Extensions are loaded and activated only when needed, which:
|
||||
|
||||
- Minimizes performance overhead
|
||||
- Reduces resource consumption
|
||||
- Improves startup time
|
||||
@@ -61,6 +65,7 @@ Extensions are loaded and activated only when needed, which:
|
||||
### 5. Composability and Reuse
|
||||
|
||||
The architecture encourages reusing extension points and patterns across different modules, promoting:
|
||||
|
||||
- Consistency across extensions
|
||||
- Reduced duplication
|
||||
- Shared best practices
|
||||
@@ -80,6 +85,7 @@ Two core packages provide the foundation for extension development:
|
||||
**Frontend: `@apache-superset/core`**
|
||||
|
||||
This package provides essential building blocks for frontend extensions and the host application:
|
||||
|
||||
- Shared UI components
|
||||
- Utility functions
|
||||
- APIs and hooks
|
||||
@@ -90,6 +96,7 @@ By centralizing these resources, both extensions and built-in features use the s
|
||||
**Backend: `apache-superset-core`**
|
||||
|
||||
This package exposes key classes and APIs for backend extensions:
|
||||
|
||||
- Database connectors
|
||||
- API extensions
|
||||
- Security manager customization
|
||||
@@ -102,6 +109,7 @@ It includes dependencies on critical libraries like Flask-AppBuilder and SQLAlch
|
||||
**`apache-superset-extensions-cli`**
|
||||
|
||||
The CLI provides comprehensive commands for extension development:
|
||||
|
||||
- Project scaffolding
|
||||
- Code generation
|
||||
- Building and bundling
|
||||
@@ -114,6 +122,7 @@ By standardizing these processes, the CLI ensures extensions are built consisten
|
||||
The Superset host application serves as the runtime environment for extensions:
|
||||
|
||||
**Extension Management**
|
||||
|
||||
- Exposes `/api/v1/extensions` endpoint for registration and management
|
||||
- Provides a dedicated UI for managing extensions
|
||||
- Stores extension metadata in the `extensions` database table
|
||||
@@ -121,6 +130,7 @@ The Superset host application serves as the runtime environment for extensions:
|
||||
**Extension Storage**
|
||||
|
||||
The extensions table contains:
|
||||
|
||||
- Extension name, version, and author
|
||||
- Metadata and configuration
|
||||
- Built frontend and/or backend code
|
||||
@@ -132,6 +142,7 @@ The following diagram illustrates how these components work together:
|
||||
<img width="955" height="586" alt="Extension System Architecture" src="https://github.com/user-attachments/assets/cc2a41df-55a4-48c8-b056-35f7a1e567c6" />
|
||||
|
||||
The diagram shows:
|
||||
|
||||
1. **Extension projects** depend on core packages for development
|
||||
2. **Core packages** provide APIs and type definitions
|
||||
3. **The host application** implements the APIs and manages extensions
|
||||
@@ -151,23 +162,25 @@ The architecture leverages Webpack's Module Federation to enable dynamic loading
|
||||
|
||||
Extensions configure Webpack to expose their entry points:
|
||||
|
||||
``` typescript
|
||||
new ModuleFederationPlugin({
|
||||
name: 'my_extension',
|
||||
filename: 'remoteEntry.[contenthash].js',
|
||||
exposes: {
|
||||
'./index': './src/index.tsx',
|
||||
},
|
||||
externalsType: 'window',
|
||||
externals: {
|
||||
'@apache-superset/core': 'superset',
|
||||
},
|
||||
shared: {
|
||||
react: { singleton: true },
|
||||
'react-dom': { singleton: true },
|
||||
'antd-v5': { singleton: true }
|
||||
}
|
||||
})
|
||||
```javascript
|
||||
externalsType: 'window',
|
||||
externals: {
|
||||
'@apache-superset/core': 'superset',
|
||||
},
|
||||
plugins: [
|
||||
new ModuleFederationPlugin({
|
||||
name: 'my_extension',
|
||||
filename: 'remoteEntry.[contenthash].js',
|
||||
exposes: {
|
||||
'./index': './src/index.tsx',
|
||||
},
|
||||
shared: {
|
||||
react: { singleton: true, import: false },
|
||||
'react-dom': { singleton: true, import: false },
|
||||
antd: { singleton: true, import: false },
|
||||
},
|
||||
}),
|
||||
]
|
||||
```
|
||||
|
||||
This configuration does several important things:
|
||||
@@ -195,24 +208,12 @@ Here's what happens at runtime:
|
||||
|
||||
On the Superset side, the APIs are mapped to `window.superset` during application bootstrap:
|
||||
|
||||
``` typescript
|
||||
```typescript
|
||||
import * as supersetCore from '@apache-superset/core';
|
||||
import {
|
||||
authentication,
|
||||
core,
|
||||
commands,
|
||||
extensions,
|
||||
sqlLab,
|
||||
} from 'src/extensions';
|
||||
|
||||
export default function setupExtensionsAPI() {
|
||||
window.superset = {
|
||||
...supersetCore,
|
||||
authentication,
|
||||
core,
|
||||
commands,
|
||||
extensions,
|
||||
sqlLab,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
@@ -28,7 +28,7 @@ To facilitate the development of extensions, we define a set of well-defined con
|
||||
|
||||
## Frontend
|
||||
|
||||
Frontend contribution types allow extensions to extend Superset's user interface with new views, commands, and menu items. Frontend contributions are registered directly in code from your extension's `index.tsx` entry point — they do not need to be declared in `extension.json`.
|
||||
Frontend contribution types allow extensions to extend Superset's user interface with new views, commands, and menu items. Frontend contributions are registered directly in code from your extension's `index.tsx` entry point.
|
||||
|
||||
### Views
|
||||
|
||||
@@ -68,25 +68,28 @@ commands.registerCommand(
|
||||
|
||||
### Menus
|
||||
|
||||
Extensions can contribute new menu items or context menus to the host application, providing users with additional actions and options. Each menu item specifies the target area, the command to execute, and its placement (primary, secondary, or context). Menu contribution areas are uniquely identified (e.g., `sqllab.editor` for the SQL Lab editor).
|
||||
Extensions can contribute new menu items or context menus to the host application, providing users with additional actions and options. Each menu item specifies the view and command to execute, the target area, and the location (`primary`, `secondary`, or `context`). Menu contribution areas are uniquely identified (e.g., `sqllab.editor` for the SQL Lab editor).
|
||||
|
||||
```typescript
|
||||
import { menus } from '@apache-superset/core';
|
||||
|
||||
menus.addMenuItem('sqllab.editor', {
|
||||
placement: 'primary',
|
||||
command: 'my-extension.copy-query',
|
||||
});
|
||||
menus.registerMenuItem(
|
||||
{ view: 'sqllab.editor', command: 'my-extension.copy-query' },
|
||||
'sqllab.editor',
|
||||
'primary',
|
||||
);
|
||||
|
||||
menus.addMenuItem('sqllab.editor', {
|
||||
placement: 'secondary',
|
||||
command: 'my-extension.prettify',
|
||||
});
|
||||
menus.registerMenuItem(
|
||||
{ view: 'sqllab.editor', command: 'my-extension.prettify' },
|
||||
'sqllab.editor',
|
||||
'secondary',
|
||||
);
|
||||
|
||||
menus.addMenuItem('sqllab.editor', {
|
||||
placement: 'context',
|
||||
command: 'my-extension.clear',
|
||||
});
|
||||
menus.registerMenuItem(
|
||||
{ view: 'sqllab.editor', command: 'my-extension.clear' },
|
||||
'sqllab.editor',
|
||||
'context',
|
||||
);
|
||||
```
|
||||
|
||||
### Editors
|
||||
@@ -111,24 +114,31 @@ See [Editors Extension Point](./extension-points/editors) for implementation det
|
||||
|
||||
## Backend
|
||||
|
||||
Backend contribution types allow extensions to extend Superset's server-side capabilities with new API endpoints, MCP tools, and MCP prompts.
|
||||
Backend contribution types allow extensions to extend Superset's server-side capabilities. Backend contributions are registered at startup via classes and functions imported from the auto-discovered `entrypoint.py` file.
|
||||
|
||||
### REST API Endpoints
|
||||
|
||||
Extensions can register custom REST API endpoints under the `/extensions/` namespace. This dedicated namespace prevents conflicts with built-in endpoints and provides a clear separation between core and extension functionality.
|
||||
|
||||
```python
|
||||
from superset_core.rest_api.api import RestApi, api
|
||||
from flask_appbuilder.api import expose, protect
|
||||
from flask import Response
|
||||
from flask_appbuilder.api import expose, permission_name, protect, safe
|
||||
from superset_core.rest_api.api import RestApi
|
||||
from superset_core.rest_api.decorators import api
|
||||
|
||||
@api(
|
||||
id="my_extension_api",
|
||||
name="My Extension API",
|
||||
description="Custom API endpoints for my extension"
|
||||
description="Custom API endpoints for my extension",
|
||||
)
|
||||
class MyExtensionAPI(RestApi):
|
||||
openapi_spec_tag = "My Extension"
|
||||
class_permission_name = "my_extension_api"
|
||||
|
||||
@expose("/hello", methods=("GET",))
|
||||
@protect()
|
||||
@safe
|
||||
@permission_name("read")
|
||||
def hello(self) -> Response:
|
||||
return self.response(200, result={"message": "Hello from extension!"})
|
||||
|
||||
@@ -136,7 +146,7 @@ class MyExtensionAPI(RestApi):
|
||||
from .api import MyExtensionAPI
|
||||
```
|
||||
|
||||
**Note**: The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator automatically detects context and generates appropriate paths:
|
||||
**Note**: The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator automatically detects context and generates appropriate paths:
|
||||
|
||||
- **Extension context**: `/extensions/{publisher}/{name}/` with ID prefixed as `extensions.{publisher}.{name}.{id}`
|
||||
- **Host context**: `/api/v1/` with original ID
|
||||
@@ -152,16 +162,65 @@ You can also specify a `resource_name` parameter to add an additional path segme
|
||||
@api(
|
||||
id="analytics_api",
|
||||
name="Analytics API",
|
||||
resource_name="analytics" # Adds /analytics to the path
|
||||
resource_name="analytics", # Adds /analytics to the path
|
||||
)
|
||||
class AnalyticsAPI(RestApi):
|
||||
|
||||
@expose("/insights", methods=("GET",))
|
||||
def insights(self):
|
||||
@protect()
|
||||
@safe
|
||||
@permission_name("read")
|
||||
def insights(self) -> Response:
|
||||
# This endpoint will be available at:
|
||||
# /extensions/my-org/dataset-tools/analytics/insights
|
||||
return self.response(200, result={"insights": []})
|
||||
```
|
||||
|
||||
### MCP Tools and Prompts
|
||||
### MCP Tools
|
||||
|
||||
Extensions can contribute Model Context Protocol (MCP) tools and prompts that AI agents can discover and use. See [MCP Integration](./mcp) for detailed documentation.
|
||||
Extensions can register Python functions as MCP tools that AI agents can discover and call. Tools provide executable functionality such as data processing, custom analytics, or integration with external services. Each tool specifies a unique name and an optional description that helps AI agents decide when to use it.
|
||||
|
||||
```python
|
||||
from superset_core.mcp.decorators import tool
|
||||
|
||||
@tool(
|
||||
name="my-extension.get_summary",
|
||||
description="Get a summary of recent query activity",
|
||||
tags=["analytics", "queries"],
|
||||
)
|
||||
def get_summary() -> dict:
|
||||
"""Returns a summary of recent query activity."""
|
||||
return {"status": "success", "result": {"queries_today": 42}}
|
||||
```
|
||||
|
||||
See [MCP Integration](./mcp) for implementation details.
|
||||
|
||||
### MCP Prompts
|
||||
|
||||
Extensions can register MCP prompts that provide interactive guidance and context to AI agents. Prompts help agents understand domain-specific workflows, best practices, or troubleshooting steps for your extension's use cases.
|
||||
|
||||
```python
|
||||
from superset_core.mcp.decorators import prompt
|
||||
from fastmcp import Context
|
||||
|
||||
@prompt(
|
||||
"my-extension.analysis_guide",
|
||||
title="Analysis Guide",
|
||||
description="Step-by-step guidance for data analysis workflows",
|
||||
)
|
||||
async def analysis_guide(ctx: Context) -> str:
|
||||
"""Provides guidance for data analysis workflows."""
|
||||
return """
|
||||
# Data Analysis Guide
|
||||
|
||||
Follow these steps for effective analysis:
|
||||
|
||||
1. **Explore your data** - Review available datasets and schema
|
||||
2. **Build your query** - Use SQL Lab to craft and test queries
|
||||
3. **Visualize results** - Choose the right chart type for your data
|
||||
|
||||
What would you like to analyze today?
|
||||
"""
|
||||
```
|
||||
|
||||
See [MCP Integration](./mcp) for implementation details.
|
||||
|
||||
@@ -38,12 +38,14 @@ superset-extensions build: Builds extension assets.
|
||||
superset-extensions bundle: Packages the extension into a .supx file.
|
||||
|
||||
superset-extensions dev: Automatically rebuilds the extension as files change.
|
||||
|
||||
superset-extensions validate: Validates the extension structure and metadata.
|
||||
```
|
||||
|
||||
When creating a new extension with `superset-extensions init`, the CLI generates a standardized folder structure:
|
||||
|
||||
```
|
||||
dataset-references/
|
||||
my-org.dataset-references/
|
||||
├── extension.json
|
||||
├── frontend/
|
||||
│ ├── src/
|
||||
@@ -53,8 +55,10 @@ dataset-references/
|
||||
├── backend/
|
||||
│ ├── src/
|
||||
│ │ └── superset_extensions/
|
||||
│ │ └── dataset_references/
|
||||
│ ├── tests/
|
||||
│ │ └── my_org/
|
||||
│ │ └── dataset_references/
|
||||
│ │ ├── api.py
|
||||
│ │ └── entrypoint.py
|
||||
│ ├── pyproject.toml
|
||||
│ └── requirements.txt
|
||||
├── dist/
|
||||
@@ -65,19 +69,20 @@ dataset-references/
|
||||
│ │ └── 900.038b20cdff6d49cfa8d9.js
|
||||
│ └── backend
|
||||
│ └── superset_extensions/
|
||||
│ └── dataset_references/
|
||||
│ ├── __init__.py
|
||||
│ ├── api.py
|
||||
│ └── entrypoint.py
|
||||
├── dataset-references-1.0.0.supx
|
||||
│ └── my_org/
|
||||
│ └── dataset_references/
|
||||
│ ├── api.py
|
||||
│ └── entrypoint.py
|
||||
├── my-org.dataset-references-1.0.0.supx
|
||||
└── README.md
|
||||
```
|
||||
|
||||
**Note**: The extension ID (`dataset-references`) serves as the basis for all technical names:
|
||||
- Directory name: `dataset-references` (kebab-case)
|
||||
- Backend Python package: `dataset_references` (snake_case)
|
||||
- Frontend package name: `dataset-references` (kebab-case)
|
||||
- Module Federation name: `datasetReferences` (camelCase)
|
||||
**Note**: With publisher `my-org` and name `dataset-references`, the technical names are:
|
||||
- Directory name: `my-org.dataset-references` (kebab-case)
|
||||
- Backend Python namespace: `superset_extensions.my_org.dataset_references`
|
||||
- Backend distribution package: `my_org-dataset_references`
|
||||
- Frontend package name: `@my-org/dataset-references` (scoped)
|
||||
- Module Federation name: `myOrg_datasetReferences` (camelCase)
|
||||
|
||||
The `extension.json` file serves as the declared metadata for the extension, containing the extension's name, version, author, description, and a list of capabilities. This file is essential for the host application to understand how to load and manage the extension.
|
||||
|
||||
@@ -203,7 +208,8 @@ Extension endpoints are registered under a dedicated `/extensions` namespace to
|
||||
```python
|
||||
from superset_core.common.models import Database, get_session
|
||||
from superset_core.common.daos import DatabaseDAO
|
||||
from superset_core.rest_api.api import RestApi, api
|
||||
from superset_core.rest_api.api import RestApi
|
||||
from superset_core.rest_api.decorators import api
|
||||
from flask_appbuilder.api import expose, protect
|
||||
|
||||
@api(
|
||||
@@ -244,7 +250,7 @@ class DatasetReferencesAPI(RestApi):
|
||||
|
||||
### Automatic Context Detection
|
||||
|
||||
The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator automatically detects whether it's being used in host or extension code:
|
||||
The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator automatically detects whether it's being used in host or extension code:
|
||||
|
||||
- **Extension APIs**: Registered under `/extensions/{publisher}/{name}/` with IDs prefixed as `extensions.{publisher}.{name}.{id}`
|
||||
- **Host APIs**: Registered under `/api/v1/` with original IDs
|
||||
@@ -262,7 +268,7 @@ LOCAL_EXTENSIONS = [
|
||||
]
|
||||
```
|
||||
|
||||
This instructs Superset to load and serve extensions directly from disk, so you can iterate quickly. Running `superset-extensions dev` watches for file changes and rebuilds assets automatically, while the Webpack development server (started separately with `npm run dev-server`) serves updated files as soon as they're modified. This enables immediate feedback for React components, styles, and other frontend code. Changes to backend files are also detected automatically and immediately synced, ensuring that both frontend and backend updates are reflected in your development environment.
|
||||
This instructs Superset to load and serve extensions directly from disk, so you can iterate quickly. Running `superset-extensions dev` watches for file changes and rebuilds assets automatically, while the Webpack development server (started separately with `npm run start`) serves updated files as soon as they're modified. This enables immediate feedback for React components, styles, and other frontend code. Changes to backend files are also detected automatically and immediately synced, ensuring that both frontend and backend updates are reflected in your development environment.
|
||||
|
||||
Example output when running in development mode:
|
||||
|
||||
|
||||
@@ -37,32 +37,12 @@ Superset uses text editors in various places throughout the application:
|
||||
| `css` | Dashboard Properties, CSS Template Modal |
|
||||
| `markdown` | Dashboard Markdown component |
|
||||
| `yaml` | Template Params Editor |
|
||||
| `javascript` | Custom JavaScript editor contexts |
|
||||
| `python` | Custom Python editor contexts |
|
||||
| `text` | Plain text editor contexts |
|
||||
|
||||
By registering an editor provider for a language, your extension replaces the default Ace editor in **all** locations that use that language.
|
||||
|
||||
## Manifest Configuration
|
||||
|
||||
Declare editor contributions in your `extension.json` manifest:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "monaco-editor",
|
||||
"version": "1.0.0",
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"editors": [
|
||||
{
|
||||
"id": "monaco-editor.sql",
|
||||
"name": "Monaco SQL Editor",
|
||||
"languages": ["sql"],
|
||||
"description": "Monaco-based SQL editor with IntelliSense"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementing an Editor
|
||||
|
||||
Your editor component must implement the `EditorProps` interface and expose an `EditorHandle` via `forwardRef`. For the complete interface definitions, see `@apache-superset/core/api/editors.ts`.
|
||||
@@ -165,21 +145,22 @@ const MonacoSQLEditor = forwardRef<editors.EditorHandle, editors.EditorProps>(
|
||||
export default MonacoSQLEditor;
|
||||
```
|
||||
|
||||
### activate.ts
|
||||
### index.tsx
|
||||
|
||||
Register the editor at module load time from your extension's entry point:
|
||||
|
||||
```typescript
|
||||
import { editors } from '@apache-superset/core';
|
||||
import MonacoSQLEditor from './MonacoSQLEditor';
|
||||
|
||||
export function activate(context) {
|
||||
// Register the Monaco editor for SQL using the contribution ID from extension.json
|
||||
const disposable = editors.registerEditorProvider(
|
||||
'monaco-sql-editor.sql',
|
||||
MonacoSQLEditor,
|
||||
);
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
editors.registerEditor(
|
||||
{
|
||||
id: 'my-extension.monaco-sql',
|
||||
name: 'Monaco SQL Editor',
|
||||
languages: ['sql'],
|
||||
},
|
||||
MonacoSQLEditor,
|
||||
);
|
||||
```
|
||||
|
||||
## Handling Hotkeys
|
||||
|
||||
@@ -86,132 +86,73 @@ Extensions can replace the default SQL editor with custom implementations (Monac
|
||||
|
||||
This example adds a "Data Profiler" panel to SQL Lab:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "data_profiler",
|
||||
"version": "1.0.0",
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab": {
|
||||
"panels": [
|
||||
{
|
||||
"id": "data_profiler.main",
|
||||
"name": "Data Profiler"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { core } from '@apache-superset/core';
|
||||
import React from 'react';
|
||||
import { views } from '@apache-superset/core';
|
||||
import DataProfilerPanel from './DataProfilerPanel';
|
||||
|
||||
export function activate(context) {
|
||||
// Register the panel view with the ID declared in extension.json
|
||||
const disposable = core.registerView('data_profiler.main', <DataProfilerPanel />);
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
views.registerView(
|
||||
{ id: 'my-extension.data-profiler', name: 'Data Profiler' },
|
||||
'sqllab.panels',
|
||||
() => <DataProfilerPanel />,
|
||||
);
|
||||
```
|
||||
|
||||
### Adding Actions to the Editor
|
||||
|
||||
This example adds primary, secondary, and context actions to the editor:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "query_tools",
|
||||
"version": "1.0.0",
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "query_tools.format",
|
||||
"title": "Format Query",
|
||||
"icon": "FormatPainterOutlined"
|
||||
},
|
||||
{
|
||||
"command": "query_tools.explain",
|
||||
"title": "Explain Query"
|
||||
},
|
||||
{
|
||||
"command": "query_tools.copy_as_cte",
|
||||
"title": "Copy as CTE"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"sqllab": {
|
||||
"editor": {
|
||||
"primary": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "query_tools.format"
|
||||
}
|
||||
],
|
||||
"secondary": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "query_tools.explain"
|
||||
}
|
||||
],
|
||||
"context": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "query_tools.copy_as_cte"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { commands, sqlLab } from '@apache-superset/core';
|
||||
import { commands, menus, sqlLab } from '@apache-superset/core';
|
||||
|
||||
export function activate(context) {
|
||||
// Register the commands declared in extension.json
|
||||
const formatCommand = commands.registerCommand(
|
||||
'query_tools.format',
|
||||
async () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab) {
|
||||
const editor = await tab.getEditor();
|
||||
// Format the SQL query
|
||||
}
|
||||
},
|
||||
);
|
||||
commands.registerCommand(
|
||||
{ id: 'my-extension.format', title: 'Format Query', icon: 'FormatPainterOutlined' },
|
||||
async () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab) {
|
||||
const editor = await tab.getEditor();
|
||||
// Format the SQL query
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const explainCommand = commands.registerCommand(
|
||||
'query_tools.explain',
|
||||
async () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab) {
|
||||
const editor = await tab.getEditor();
|
||||
// Show query explanation
|
||||
}
|
||||
},
|
||||
);
|
||||
commands.registerCommand(
|
||||
{ id: 'my-extension.explain', title: 'Explain Query' },
|
||||
async () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab) {
|
||||
const editor = await tab.getEditor();
|
||||
// Show query explanation
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const copyAsCteCommand = commands.registerCommand(
|
||||
'query_tools.copy_as_cte',
|
||||
async () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab) {
|
||||
const editor = await tab.getEditor();
|
||||
// Copy selected text as CTE
|
||||
}
|
||||
},
|
||||
);
|
||||
commands.registerCommand(
|
||||
{ id: 'my-extension.copy-as-cte', title: 'Copy as CTE' },
|
||||
async () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab) {
|
||||
const editor = await tab.getEditor();
|
||||
// Copy selected text as CTE
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
context.subscriptions.push(formatCommand, explainCommand, copyAsCteCommand);
|
||||
}
|
||||
menus.registerMenuItem(
|
||||
{ view: 'builtin.editor', command: 'my-extension.format' },
|
||||
'sqllab.editor',
|
||||
'primary',
|
||||
);
|
||||
menus.registerMenuItem(
|
||||
{ view: 'builtin.editor', command: 'my-extension.explain' },
|
||||
'sqllab.editor',
|
||||
'secondary',
|
||||
);
|
||||
menus.registerMenuItem(
|
||||
{ view: 'builtin.editor', command: 'my-extension.copy-as-cte' },
|
||||
'sqllab.editor',
|
||||
'context',
|
||||
);
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
@@ -64,6 +64,7 @@ Include backend? [Y/n]: Y
|
||||
```
|
||||
|
||||
**Publisher Namespaces**: Extensions use organizational namespaces similar to VS Code extensions, providing collision-safe naming across organizations:
|
||||
|
||||
- **NPM package**: `@my-org/hello-world` (scoped package for frontend distribution)
|
||||
- **Module Federation name**: `myOrg_helloWorld` (collision-safe JavaScript identifier)
|
||||
- **Backend package**: `my_org-hello_world` (collision-safe Python distribution name)
|
||||
@@ -80,9 +81,7 @@ my-org.hello-world/
|
||||
│ ├── src/
|
||||
│ │ └── superset_extensions/
|
||||
│ │ └── my_org/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── hello_world/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── entrypoint.py # Backend registration
|
||||
│ └── pyproject.toml
|
||||
└── frontend/ # Frontend TypeScript/React code
|
||||
@@ -95,7 +94,7 @@ my-org.hello-world/
|
||||
|
||||
## Step 3: Configure Extension Metadata
|
||||
|
||||
The generated `extension.json` contains the extension's metadata. It is used to identify the extension and declare its backend entry points. Frontend contributions are registered directly in code (see Step 5).
|
||||
The generated `extension.json` contains the extension's metadata.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -104,10 +103,6 @@ The generated `extension.json` contains the extension's metadata. It is used to
|
||||
"displayName": "Hello World",
|
||||
"version": "0.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"backend": {
|
||||
"entryPoints": ["superset_extensions.my_org.hello_world.entrypoint"],
|
||||
"files": ["backend/src/superset_extensions/my_org/hello_world/**/*.py"]
|
||||
},
|
||||
"permissions": ["can_read"]
|
||||
}
|
||||
```
|
||||
@@ -117,8 +112,7 @@ The generated `extension.json` contains the extension's metadata. It is used to
|
||||
- `publisher`: Organizational namespace for the extension
|
||||
- `name`: Technical identifier (kebab-case)
|
||||
- `displayName`: Human-readable name shown to users
|
||||
- `backend.entryPoints`: Python modules to load eagerly when the extension starts
|
||||
- `backend.files`: Glob patterns for Python source files to include in the bundle
|
||||
- `permissions`: List of permissions the extension requires
|
||||
|
||||
## Step 4: Create Backend API
|
||||
|
||||
@@ -129,7 +123,8 @@ The CLI generated a basic `backend/src/superset_extensions/my_org/hello_world/en
|
||||
```python
|
||||
from flask import Response
|
||||
from flask_appbuilder.api import expose, protect, safe
|
||||
from superset_core.rest_api.api import RestApi, api
|
||||
from superset_core.rest_api.api import RestApi
|
||||
from superset_core.rest_api.decorators import api
|
||||
|
||||
|
||||
@api(
|
||||
@@ -174,7 +169,7 @@ class HelloWorldAPI(RestApi):
|
||||
|
||||
**Key points:**
|
||||
|
||||
- Uses [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator with automatic context detection
|
||||
- Uses [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator with automatic context detection
|
||||
- Extends `RestApi` from `superset_core.rest_api.api`
|
||||
- Uses Flask-AppBuilder decorators (`@expose`, `@protect`, `@safe`)
|
||||
- Returns responses using `self.response(status_code, result=data)`
|
||||
@@ -187,12 +182,10 @@ Replace the generated print statement with API import to trigger registration:
|
||||
|
||||
```python
|
||||
# Importing the API class triggers the @api decorator registration
|
||||
from .api import HelloWorldAPI
|
||||
|
||||
print("Hello World extension loaded successfully!")
|
||||
from .api import HelloWorldAPI # noqa: F401
|
||||
```
|
||||
|
||||
The [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator automatically detects extension context and registers your API with proper namespacing.
|
||||
The [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator automatically detects extension context and registers your API with proper namespacing.
|
||||
|
||||
## Step 5: Create Frontend Component
|
||||
|
||||
@@ -236,52 +229,53 @@ The webpack configuration requires specific settings for Module Federation. Key
|
||||
**Convention**: Superset always loads extensions by requesting the `./index` module from the Module Federation container. The `exposes` entry must be exactly `'./index': './src/index.tsx'` — do not rename or add additional entries. All API registrations must be reachable from that file. See [Architecture](./architecture#module-federation) for a full explanation.
|
||||
|
||||
```javascript
|
||||
const path = require("path");
|
||||
const { ModuleFederationPlugin } = require("webpack").container;
|
||||
const packageConfig = require("./package.json");
|
||||
const path = require('path');
|
||||
const { ModuleFederationPlugin } = require('webpack').container;
|
||||
const packageConfig = require('./package');
|
||||
const extensionConfig = require('../extension.json');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const isProd = argv.mode === "production";
|
||||
const isProd = argv.mode === 'production';
|
||||
|
||||
return {
|
||||
entry: isProd ? {} : "./src/index.tsx",
|
||||
mode: isProd ? "production" : "development",
|
||||
entry: isProd ? {} : './src/index.tsx',
|
||||
mode: isProd ? 'production' : 'development',
|
||||
devServer: {
|
||||
port: 3001,
|
||||
port: 3000,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
},
|
||||
output: {
|
||||
filename: isProd ? undefined : "[name].[contenthash].js",
|
||||
chunkFilename: "[name].[contenthash].js",
|
||||
clean: true,
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
publicPath: `/api/v1/extensions/my-org/hello-world/`,
|
||||
filename: isProd ? undefined : '[name].[contenthash].js',
|
||||
chunkFilename: '[name].[contenthash].js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
publicPath: `/api/v1/extensions/${extensionConfig.publisher}/${extensionConfig.name}/`,
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
||||
},
|
||||
// Map @apache-superset/core imports to window.superset at runtime
|
||||
externalsType: "window",
|
||||
externalsType: 'window',
|
||||
externals: {
|
||||
"@apache-superset/core": "superset",
|
||||
'@apache-superset/core': 'superset',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: "ts-loader",
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new ModuleFederationPlugin({
|
||||
name: "myOrg_helloWorld",
|
||||
filename: "remoteEntry.[contenthash].js",
|
||||
name: 'myOrg_helloWorld',
|
||||
filename: 'remoteEntry.[contenthash].js',
|
||||
exposes: {
|
||||
"./index": "./src/index.tsx",
|
||||
'./index': './src/index.tsx',
|
||||
},
|
||||
shared: {
|
||||
react: {
|
||||
@@ -289,9 +283,14 @@ module.exports = (env, argv) => {
|
||||
requiredVersion: packageConfig.peerDependencies.react,
|
||||
import: false, // Use host's React, don't bundle
|
||||
},
|
||||
"react-dom": {
|
||||
'react-dom': {
|
||||
singleton: true,
|
||||
requiredVersion: packageConfig.peerDependencies["react-dom"],
|
||||
requiredVersion: packageConfig.peerDependencies['react-dom'],
|
||||
import: false,
|
||||
},
|
||||
antd: {
|
||||
singleton: true,
|
||||
requiredVersion: packageConfig.peerDependencies['antd'],
|
||||
import: false,
|
||||
},
|
||||
},
|
||||
@@ -306,8 +305,9 @@ module.exports = (env, argv) => {
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "node",
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node10",
|
||||
"jsx": "react",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
@@ -332,16 +332,16 @@ const HelloWorldPanel: React.FC = () => {
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMessage = async () => {
|
||||
try {
|
||||
const csrfToken = await authentication.getCSRFToken();
|
||||
const response = await fetch('/extensions/my-org/hello-world/message', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken!,
|
||||
},
|
||||
});
|
||||
const fetchMessage = async () => {
|
||||
try {
|
||||
const csrfToken = await authentication.getCSRFToken();
|
||||
const response = await fetch('/extensions/my-org/hello-world/message', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken!,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server returned ${response.status}`);
|
||||
@@ -496,8 +496,8 @@ Superset will extract and validate the extension metadata, load the assets, regi
|
||||
|
||||
Here's what happens when your extension loads:
|
||||
|
||||
1. **Superset starts**: Reads `extension.json` and loads the backend entrypoint
|
||||
2. **Backend registration**: `entrypoint.py` imports your API class, triggering the [`@api`](superset-core/src/superset_core/api/rest_api.py:59) decorator to register it automatically
|
||||
1. **Superset starts**: Reads `manifest.json` from the `.supx` bundle and loads the backend entrypoint
|
||||
2. **Backend registration**: `entrypoint.py` imports your API class, triggering the [`@api`](superset-core/src/superset_core/rest_api/decorators.py) decorator to register it automatically
|
||||
3. **Frontend loads**: When SQL Lab opens, Superset fetches the remote entry file
|
||||
4. **Module Federation**: Webpack loads your extension module and resolves `@apache-superset/core` to `window.superset`
|
||||
5. **Registration**: The module executes at load time, calling `views.registerView` to register your panel
|
||||
|
||||
@@ -30,15 +30,15 @@ This page serves as a registry of community-created Superset extensions. These e
|
||||
|
||||
| Name | Description | Author | Preview |
|
||||
| ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Extensions API Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api_explorer) | A SQL Lab panel that demonstrates the Extensions API by providing an interactive explorer for testing commands like getTabs, getCurrentTab, and getDatabases. Useful for extension developers to understand and experiment with the available APIs. | Michael S. Molina | <a href="/img/extensions/api-explorer.png" target="_blank"><img src="/img/extensions/api-explorer.png" alt="Extensions API Explorer" width="120" /></a> |
|
||||
| [Extensions API Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api-explorer) | A SQL Lab panel that demonstrates the Extensions API by providing an interactive explorer for testing commands like getTabs, getCurrentTab, and getDatabases. Useful for extension developers to understand and experiment with the available APIs. | Michael S. Molina | <a href="/img/extensions/api-explorer.png" target="_blank"><img src="/img/extensions/api-explorer.png" alt="Extensions API Explorer" width="120" /></a> |
|
||||
| [SQL Query Flow Visualizer](https://github.com/msyavuz/superset-sql-visualizer) | A SQL Lab panel that transforms SQL queries into interactive flow diagrams, helping developers and analysts understand query execution paths and data relationships. | Mehmet Salih Yavuz | <a href="/img/extensions/sql-flow-visualizer.png" target="_blank"><img src="/img/extensions/sql-flow-visualizer.png" alt="SQL Flow Visualizer" width="120" /></a> |
|
||||
| [SQL Lab Export to Google Sheets](https://github.com/michael-s-molina/superset-extensions/tree/main/sqllab_gsheets) | A Superset extension that allows users to export SQL Lab query results directly to Google Sheets. | Michael S. Molina | <a href="/img/extensions/gsheets-export.png" target="_blank"><img src="/img/extensions/gsheets-export.png" alt="SQL Lab Export to Google Sheets" width="120" /></a> |
|
||||
| [SQL Lab Export to Google Sheets](https://github.com/michael-s-molina/superset-extensions/tree/main/sqllab-gsheets) | A Superset extension that allows users to export SQL Lab query results directly to Google Sheets. | Michael S. Molina | <a href="/img/extensions/gsheets-export.png" target="_blank"><img src="/img/extensions/gsheets-export.png" alt="SQL Lab Export to Google Sheets" width="120" /></a> |
|
||||
| [SQL Lab Export to Parquet](https://github.com/rusackas/superset-extensions/tree/main/sqllab_parquet) | Export SQL Lab query results directly to Apache Parquet format with Snappy compression. | Evan Rusackas | <a href="/img/extensions/parquet-export.png" target="_blank"><img src="/img/extensions/parquet-export.png" alt="SQL Lab Export to Parquet" width="120" /></a> |
|
||||
| [SQL Lab Query Comparison](https://github.com/michael-s-molina/superset-extensions/tree/main/query_comparison) | A SQL Lab extension that enables side-by-side comparison of query results across different tabs, with GitHub-style diff visualization showing added/removed rows and columns. | Michael S. Molina | <a href="/img/extensions/query-comparison.png" target="_blank"><img src="/img/extensions/query-comparison.png" alt="Query Comparison" width="120" /></a> |
|
||||
| [SQL Lab Result Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result_stats) | A SQL Lab extension that automatically computes statistics for query results, providing type-aware analysis including numeric metrics (min, max, mean, median, std dev), string analysis (length, empty counts), and date range information. | Michael S. Molina | <a href="/img/extensions/result-stats.png" target="_blank"><img src="/img/extensions/result-stats.png" alt="Result Stats" width="120" /></a> |
|
||||
| [SQL Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/sql_snippets) | A SQL Lab extension that provides reusable SQL code snippets, enabling quick insertion of commonly used code blocks such as license headers, author information, and frequently used SQL patterns. | Michael S. Molina | <a href="/img/extensions/sql-snippets.png" target="_blank"><img src="/img/extensions/sql-snippets.png" alt="SQL Snippets" width="120" /></a> |
|
||||
| [SQL Lab Query Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query_estimator) | A SQL Lab panel that analyzes query execution plans to estimate resource impact, detect performance issues like Cartesian products and high-cost operations, and visualize the query plan tree. | Michael S. Molina | <a href="/img/extensions/query-estimator.png" target="_blank"><img src="/img/extensions/query-estimator.png" alt="Query Estimator" width="120" /></a> |
|
||||
| [Editors Bundle](https://github.com/michael-s-molina/superset-extensions/tree/main/editors_bundle) | A Superset extension that demonstrates how to provide custom code editors for different languages. This extension showcases the editor contribution system by registering alternative editors that can replace Superset's default Ace editor. | Michael S. Molina | <a href="/img/extensions/editors-bundle.png" target="_blank"><img src="/img/extensions/editors-bundle.png" alt="Editors Bundle" width="120" /></a> |
|
||||
| [SQL Lab Query Comparison](https://github.com/michael-s-molina/superset-extensions/tree/main/query-comparison) | A SQL Lab extension that enables side-by-side comparison of query results across different tabs, with GitHub-style diff visualization showing added/removed rows and columns. | Michael S. Molina | <a href="/img/extensions/query-comparison.png" target="_blank"><img src="/img/extensions/query-comparison.png" alt="Query Comparison" width="120" /></a> |
|
||||
| [SQL Lab Result Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result-stats) | A SQL Lab extension that automatically computes statistics for query results, providing type-aware analysis including numeric metrics (min, max, mean, median, std dev), string analysis (length, empty counts), and date range information. | Michael S. Molina | <a href="/img/extensions/result-stats.png" target="_blank"><img src="/img/extensions/result-stats.png" alt="Result Stats" width="120" /></a> |
|
||||
| [Editor Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/editor-snippets) | A SQL Lab extension for managing and inserting reusable code snippets into the editor, with server-side persistence per user. | Michael S. Molina | <a href="/img/extensions/editor-snippets.png" target="_blank"><img src="/img/extensions/editor-snippets.png" alt="Editor Snippets" width="120" /></a> |
|
||||
| [SQL Lab Query Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query-estimator) | A SQL Lab panel that analyzes query execution plans to estimate resource impact, detect performance issues like Cartesian products and high-cost operations, and visualize the query plan tree. | Michael S. Molina | <a href="/img/extensions/query-estimator.png" target="_blank"><img src="/img/extensions/query-estimator.png" alt="Query Estimator" width="120" /></a> |
|
||||
| [Editors Bundle](https://github.com/michael-s-molina/superset-extensions/tree/main/editors-bundle) | A Superset extension that demonstrates how to provide custom code editors for different languages. This extension showcases the editor contribution system by registering alternative editors that can replace Superset's default Ace editor. | Michael S. Molina | <a href="/img/extensions/editors-bundle.png" target="_blank"><img src="/img/extensions/editors-bundle.png" alt="Editors Bundle" width="120" /></a> |
|
||||
|
||||
## How to Add Your Extension
|
||||
|
||||
|
||||
Reference in New Issue
Block a user