mirror of
https://github.com/apache/superset.git
synced 2026-05-22 08:15:36 +00:00
docs: cut 6.1.0 versions for docs, admin_docs, developer_docs, components
Snapshots all four versioned Docusaurus sections at v6.1.0, cut from master after the version-cutting tooling (#39837) and broken-internal- links fixes (#40102) landed. Captures fresh auto-generated content and freezes data dependencies so the historical snapshot stays correct. Versioning behavior: lastVersion stays at current for every section, so the canonical URLs (/docs/..., /admin-docs/..., /developer-docs/..., /components/...) continue to render content from master. The current version is consistently labeled "Next" with an unreleased banner, and 6.1.0 is a historical pin accessible only via its explicit version segment. Component playground: previously disabled: true in versions-config.json, now enabled and versioned. The plugin block in docusaurus.config.ts was already gated only by the disabled flag, so no other code changes were needed to bring it back online. Snapshot includes: - All MDX content for the four sections. - Auto-gen captured fresh: 74 database pages (engine spec metadata), ~1,800 API reference files (openapi.json), 59 component pages (Storybook stories). - Data imports frozen at cut time into snapshot-local _versioned_data/ dirs: versioned_docs/version-6.1.0/_versioned_data/src/data/databases.json (canonical 80-database diagnostics from master, preserved by the generator's input-hash cache) admin_docs_versioned_docs/version-6.1.0/_versioned_data/data/countries.json admin_docs_versioned_docs/version-6.1.0/_versioned_data/static/feature-flags.json developer_docs_versioned_docs/version-6.1.0/_versioned_data/static/data/components.json - Import paths in deeply-nested files rewritten so they still resolve from one directory deeper inside the snapshot. Verified via full yarn build: exit 0, no broken links surfaced by onBrokenLinks: throw. Anchor warnings present are pre-existing on master (community#superset-community-calendar) and unrelated.
This commit is contained in:
@@ -0,0 +1,517 @@
|
||||
---
|
||||
title: Quick Start
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
# Quick Start
|
||||
|
||||
This guide walks you through creating your first Superset extension - a simple "Hello World" panel that displays a message fetched from a backend API endpoint. You'll learn the essential structure and patterns for building full-stack Superset extensions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting, ensure you have:
|
||||
|
||||
- Node.js and npm compatible with your Superset version
|
||||
- Python compatible with your Superset version
|
||||
- A running Superset development environment
|
||||
- Basic knowledge of React, TypeScript, and Flask
|
||||
|
||||
## Step 1: Install the Extensions CLI
|
||||
|
||||
First, install the Apache Superset Extensions CLI:
|
||||
|
||||
```bash
|
||||
pip install apache-superset-extensions-cli
|
||||
```
|
||||
|
||||
## Step 2: Create a New Extension
|
||||
|
||||
Use the CLI to scaffold a new extension project. Extensions can include frontend functionality, backend functionality, or both, depending on your needs. This quickstart demonstrates a full-stack extension with both frontend UI components and backend API endpoints to show the complete integration pattern.
|
||||
|
||||
```bash
|
||||
superset-extensions init
|
||||
```
|
||||
|
||||
The CLI will prompt you for information using a three-step publisher workflow:
|
||||
|
||||
```
|
||||
Extension display name: Hello World
|
||||
Extension name (hello-world): hello-world
|
||||
Publisher (e.g., my-org): my-org
|
||||
Initial version [0.1.0]: 0.1.0
|
||||
License [Apache-2.0]: Apache-2.0
|
||||
Include frontend? [Y/n]: Y
|
||||
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)
|
||||
- **Python namespace**: `my_org.hello_world`
|
||||
|
||||
This approach ensures that extensions from different organizations cannot conflict, even if they use the same technical name (e.g., both `acme.dashboard-widgets` and `corp.dashboard-widgets` can coexist).
|
||||
|
||||
This creates a complete project structure:
|
||||
|
||||
```
|
||||
hello-world/
|
||||
├── extension.json # Extension metadata and configuration
|
||||
├── backend/ # Backend Python code
|
||||
│ ├── src/
|
||||
│ │ └── my_org/
|
||||
│ │ └── hello_world/
|
||||
│ │ └── entrypoint.py # Backend registration
|
||||
│ └── pyproject.toml
|
||||
└── frontend/ # Frontend TypeScript/React code
|
||||
├── src/
|
||||
│ └── index.tsx # Frontend entry point
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── webpack.config.js
|
||||
```
|
||||
|
||||
## Step 3: Configure Extension Metadata
|
||||
|
||||
The generated `extension.json` contains the extension's metadata.
|
||||
|
||||
```json
|
||||
{
|
||||
"publisher": "my-org",
|
||||
"name": "hello-world",
|
||||
"displayName": "Hello World",
|
||||
"version": "0.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"permissions": ["can_read"]
|
||||
}
|
||||
```
|
||||
|
||||
**Key fields:**
|
||||
|
||||
- `publisher`: Organizational namespace for the extension
|
||||
- `name`: Technical identifier (kebab-case)
|
||||
- `displayName`: Human-readable name shown to users
|
||||
- `permissions`: List of permissions the extension requires
|
||||
|
||||
## Step 4: Create Backend API
|
||||
|
||||
The CLI generated a basic `backend/src/my_org/hello_world/entrypoint.py`. We'll create an API endpoint.
|
||||
|
||||
**Create `backend/src/my_org/hello_world/api.py`**
|
||||
|
||||
```python
|
||||
from flask import Response
|
||||
from flask_appbuilder.api import expose, protect, safe
|
||||
from superset_core.rest_api.api import RestApi
|
||||
from superset_core.rest_api.decorators import api
|
||||
|
||||
|
||||
@api(
|
||||
id="hello_world_api",
|
||||
name="Hello World API",
|
||||
description="API endpoints for the Hello World extension"
|
||||
)
|
||||
class HelloWorldAPI(RestApi):
|
||||
openapi_spec_tag = "Hello World"
|
||||
class_permission_name = "hello_world"
|
||||
|
||||
@expose("/message", methods=("GET",))
|
||||
@protect()
|
||||
@safe
|
||||
def get_message(self) -> Response:
|
||||
"""Gets a hello world message
|
||||
---
|
||||
get:
|
||||
description: >-
|
||||
Get a hello world message from the backend
|
||||
responses:
|
||||
200:
|
||||
description: Hello world message
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
result:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
"""
|
||||
return self.response(
|
||||
200,
|
||||
result={"message": "Hello from the backend!"}
|
||||
)
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
|
||||
- Uses [`@api`](https://github.com/apache/superset/blob/master/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)`
|
||||
- The endpoint will be accessible at `/extensions/my-org/hello-world/message` (automatic extension context)
|
||||
- OpenAPI docstrings are crucial - Flask-AppBuilder uses them to automatically generate interactive API documentation at `/swagger/v1`, allowing developers to explore endpoints, understand schemas, and test the API directly from the browser
|
||||
|
||||
**Update `backend/src/my_org/hello_world/entrypoint.py`**
|
||||
|
||||
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 # noqa: F401
|
||||
```
|
||||
|
||||
The [`@api`](https://github.com/apache/superset/blob/master/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
|
||||
|
||||
The CLI generates the frontend configuration files. Below are the key configurations that enable Module Federation integration with Superset.
|
||||
|
||||
**`frontend/package.json`**
|
||||
|
||||
The `@apache-superset/core` package must be listed in both `peerDependencies` (to declare runtime compatibility) and `devDependencies` (to provide TypeScript types during build):
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@my-org/hello-world",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"start": "webpack serve --mode development",
|
||||
"build": "webpack --stats-error-details --mode production"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "^x.x.x",
|
||||
"react": "^x.x.x",
|
||||
"react-dom": "^x.x.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apache-superset/core": "^x.x.x",
|
||||
"@types/react": "^x.x.x",
|
||||
"ts-loader": "^x.x.x",
|
||||
"typescript": "^x.x.x",
|
||||
"webpack": "^5.x.x",
|
||||
"webpack-cli": "^x.x.x",
|
||||
"webpack-dev-server": "^x.x.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`frontend/webpack.config.js`**
|
||||
|
||||
The webpack configuration requires specific settings for Module Federation. Key settings include `externalsType: "window"` and `externals` to map `@apache-superset/core` to `window.superset` at runtime, `import: false` for shared modules to use the host's React instead of bundling a separate copy, and `remoteEntry.[contenthash].js` for cache busting.
|
||||
|
||||
**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.md#module-federation) for a full explanation.
|
||||
|
||||
```javascript
|
||||
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';
|
||||
|
||||
return {
|
||||
entry: isProd ? {} : './src/index.tsx',
|
||||
mode: isProd ? 'production' : 'development',
|
||||
devServer: {
|
||||
port: 3000,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
},
|
||||
output: {
|
||||
clean: true,
|
||||
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'],
|
||||
},
|
||||
// Map @apache-superset/core imports to window.superset at runtime
|
||||
externalsType: 'window',
|
||||
externals: {
|
||||
'@apache-superset/core': 'superset',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new ModuleFederationPlugin({
|
||||
name: 'myOrg_helloWorld',
|
||||
filename: 'remoteEntry.[contenthash].js',
|
||||
exposes: {
|
||||
'./index': './src/index.tsx',
|
||||
},
|
||||
shared: {
|
||||
react: {
|
||||
singleton: true,
|
||||
requiredVersion: packageConfig.peerDependencies.react,
|
||||
import: false, // Use host's React, don't bundle
|
||||
},
|
||||
'react-dom': {
|
||||
singleton: true,
|
||||
requiredVersion: packageConfig.peerDependencies['react-dom'],
|
||||
import: false,
|
||||
},
|
||||
antd: {
|
||||
singleton: true,
|
||||
requiredVersion: packageConfig.peerDependencies['antd'],
|
||||
import: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**`frontend/tsconfig.json`**
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node10",
|
||||
"jsx": "react",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
```
|
||||
|
||||
**Create `frontend/src/HelloWorldPanel.tsx`**
|
||||
|
||||
Create a new file for the component implementation:
|
||||
|
||||
```tsx
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { authentication } from '@apache-superset/core';
|
||||
|
||||
const HelloWorldPanel: React.FC = () => {
|
||||
const [message, setMessage] = useState<string>('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
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!,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server returned ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setMessage(data.result.message);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An error occurred');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMessage();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ padding: '20px', textAlign: 'center' }}>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div style={{ padding: '20px', color: 'red' }}>
|
||||
<strong>Error:</strong> {error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<h3>Hello World Extension</h3>
|
||||
<div
|
||||
style={{
|
||||
padding: '16px',
|
||||
backgroundColor: '#f6ffed',
|
||||
border: '1px solid #b7eb8f',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '16px',
|
||||
}}
|
||||
>
|
||||
<strong>{message}</strong>
|
||||
</div>
|
||||
<p>This message was fetched from the backend API! 🎉</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HelloWorldPanel;
|
||||
```
|
||||
|
||||
**Update `frontend/src/index.tsx`**
|
||||
|
||||
This file is the single entry point Superset loads from every extension. All registrations — views, commands, menus, editors, event listeners — must be made here (or imported and executed from here). Replace the generated code with:
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { views } from '@apache-superset/core';
|
||||
import HelloWorldPanel from './HelloWorldPanel';
|
||||
|
||||
views.registerView(
|
||||
{ id: 'my-org.hello-world.main', name: 'Hello World' },
|
||||
'sqllab.panels',
|
||||
() => <HelloWorldPanel />,
|
||||
);
|
||||
```
|
||||
|
||||
**Key patterns:**
|
||||
|
||||
- `views.registerView` is called at module load time — no `activate`/`deactivate` lifecycle needed
|
||||
- The first argument is a `{ id, name }` descriptor; the second is the contribution area (e.g., `sqllab.panels`); the third is a factory returning the React component
|
||||
- `authentication.getCSRFToken()` retrieves the CSRF token for API calls (used inside components)
|
||||
- Fetch calls to `/extensions/{publisher}/{name}/{endpoint}` reach your backend API
|
||||
|
||||
## Step 6: Install Dependencies
|
||||
|
||||
Install the frontend dependencies:
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
cd ..
|
||||
```
|
||||
|
||||
## Step 7: Package the Extension
|
||||
|
||||
Create a `.supx` bundle for deployment:
|
||||
|
||||
```bash
|
||||
superset-extensions bundle
|
||||
```
|
||||
|
||||
This command automatically:
|
||||
|
||||
- Builds frontend assets using Webpack with Module Federation
|
||||
- Collects backend Python source files
|
||||
- Creates a `dist/` directory with:
|
||||
- `manifest.json` - Build metadata and asset references
|
||||
- `frontend/dist/` - Built frontend assets (remoteEntry.js, chunks)
|
||||
- `backend/` - Python source files
|
||||
- Packages everything into `my-org.hello-world-0.1.0.supx` - a zip archive with the specific structure required by Superset
|
||||
|
||||
## Step 8: Deploy to Superset
|
||||
|
||||
To deploy your extension, you need to enable extensions support and configure where Superset should load them from.
|
||||
|
||||
**Configure Superset**
|
||||
|
||||
Add the following to your `superset_config.py`:
|
||||
|
||||
```python
|
||||
# Enable extensions feature
|
||||
FEATURE_FLAGS = {
|
||||
"ENABLE_EXTENSIONS": True,
|
||||
}
|
||||
|
||||
# Set the directory where extensions are stored
|
||||
EXTENSIONS_PATH = "/path/to/extensions/folder"
|
||||
```
|
||||
|
||||
**Copy Extension Bundle**
|
||||
|
||||
Copy your `.supx` file to the configured extensions path:
|
||||
|
||||
```bash
|
||||
cp my-org.hello-world-0.1.0.supx /path/to/extensions/folder/
|
||||
```
|
||||
|
||||
**Restart Superset**
|
||||
|
||||
Restart your Superset instance to load the extension:
|
||||
|
||||
```bash
|
||||
# Restart your Superset server
|
||||
superset run
|
||||
```
|
||||
|
||||
Superset will extract and validate the extension metadata, load the assets, register the extension with its capabilities, and make it available for use.
|
||||
|
||||
## Step 9: Test Your Extension
|
||||
|
||||
1. **Open SQL Lab** in Superset
|
||||
2. Look for the **"Hello World"** panel in the panels dropdown or sidebar
|
||||
3. Open the panel - it should display "Hello from the backend!"
|
||||
4. Check that the message was fetched from your API endpoint
|
||||
|
||||
## Understanding the Flow
|
||||
|
||||
Here's what happens when your extension loads:
|
||||
|
||||
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`](https://github.com/apache/superset/blob/master/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
|
||||
6. **Rendering**: When the user opens your panel, React renders `<HelloWorldPanel />`
|
||||
7. **API call**: The component fetches data from `/extensions/my-org/hello-world/message`
|
||||
8. **Backend response**: Your Flask API returns the hello world message
|
||||
9. **Display**: The component shows the message to the user
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you have a working extension, explore:
|
||||
|
||||
- **[Development](./development.md)** - Project structure, APIs, and development workflow
|
||||
- **[Contribution Types](./contribution-types.md)** - Other contribution points beyond panels
|
||||
- **[Deployment](./deployment.md)** - Packaging and deploying your extension
|
||||
- **[Security](./security.md)** - Security best practices for extensions
|
||||
|
||||
For a complete real-world example, examine the query insights extension in the Superset codebase.
|
||||
Reference in New Issue
Block a user