Spaces:
Paused
Paused
|
Download docs/developer_docs/extensions/quick-start.md from Leon4gr45/powerbi: direct link, hf CLI and curl.
- Browser
- Download file 17.4 kB
-
https://huggingface.co/spaces/Leon4gr45/powerbi/resolve/main/docs/developer_docs/extensions/quick-start.md
- Command line
-
hf download hf://spaces/Leon4gr45/powerbi/docs/developer_docs/extensions/quick-start.md
-
curl -L -o quick-start.md https://huggingface.co/spaces/Leon4gr45/powerbi/resolve/main/docs/developer_docs/extensions/quick-start.md
17.4 kB
| 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. `@apache-superset/core` is declared as a `shared` singleton alongside `react`/`react-dom`/`antd` β Superset injects a per-extension instance of the package into the container's share scope at load time, so the extension's own webpack config only needs to mark it `shared` with `singleton: true`, not resolve it itself. `import: false` on each shared entry means the extension doesn't bundle its own copy and instead uses the instance the host (or, for `@apache-superset/core`, the loader) provides. `remoteEntry.[contenthash].js` gives the built remote entry file a content hash 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'], | |
| }, | |
| 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, | |
| }, | |
| '@apache-superset/core': { | |
| singleton: true, | |
| import: false, // Resolved to a per-extension instance by the loader | |
| }, | |
| }, | |
| }), | |
| ], | |
| }; | |
| }; | |
| ``` | |
| **`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; the extension's `@apache-superset/core` import resolves to a per-extension instance injected into its container's share scope by the loader | |
| 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. | |