diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 5f47e964d31..6dc643315bd 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -13,7 +13,7 @@ "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": { - "moby": true, + "moby": false, "dockerDashComposeVersion": "v2" }, "ghcr.io/devcontainers/features/node:1": { diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6b9c16b82f6..e17da498eeb 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -104,7 +104,7 @@ jobs: # Scan for vulnerabilities in built container image after pushes to mainline branch. - name: Run Trivy container image vulnerabity scan if: github.event_name == 'push' && github.ref == 'refs/heads/master' && (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker) && matrix.build_preset == 'lean' - uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518 # v0.34.1 + uses: aquasecurity/trivy-action@97e0b3872f55f89b95b2f65b3dbab56962816478 # v0.34.2 with: image-ref: ${{ env.IMAGE_TAG }} format: 'sarif' diff --git a/.gitignore b/.gitignore index f86395f0a5c..c9699e830dc 100644 --- a/.gitignore +++ b/.gitignore @@ -68,11 +68,11 @@ superset-websocket/config.json *.js.map node_modules npm-debug.log* +superset/static/* superset/static/assets/* !superset/static/assets/.gitkeep superset/static/uploads/* !superset/static/uploads/.gitkeep -superset/static/version_info.json yarn-error.log *.map *.min.js diff --git a/UPDATING.md b/UPDATING.md index 7b887b1aa36..455e9a10ba4 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -64,19 +64,19 @@ ORDER BY total_calls DESC; **Security note:** Sensitive parameters (passwords, API keys, tokens) are automatically redacted in logs as `[REDACTED]`. -### Signal Cache Backend +### Distributed Coordination Backend -A new `SIGNAL_CACHE_CONFIG` configuration provides a unified Redis-based backend for real-time coordination features in Superset. This backend enables: +A new `DISTRIBUTED_COORDINATION_CONFIG` configuration provides a unified Redis-based backend for real-time coordination features in Superset. This backend enables: - **Pub/sub messaging** for real-time event notifications between workers - **Atomic distributed locking** using Redis SET NX EX (more performant than database-backed locks) - **Event-based coordination** for background task management -The signal cache is used by the Global Task Framework (GTF) for abort notifications and task completion signaling, and will eventually replace `GLOBAL_ASYNC_QUERIES_CACHE_BACKEND` as the standard signaling backend. Configuring this is recommended for Redis enabled production deployments. +The distributed coordination is used by the Global Task Framework (GTF) for abort notifications and task completion signaling, and will eventually replace `GLOBAL_ASYNC_QUERIES_CACHE_BACKEND` as the standard signaling backend. Configuring this is recommended for Redis enabled production deployments. Example configuration in `superset_config.py`: ```python -SIGNAL_CACHE_CONFIG = { +DISTRIBUTED_COORDINATION_CONFIG = { "CACHE_TYPE": "RedisCache", "CACHE_KEY_PREFIX": "signal_", "CACHE_REDIS_URL": "redis://localhost:6379/1", diff --git a/docs/.gitignore b/docs/.gitignore index bdf3078bb36..d5cf1228397 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -37,10 +37,10 @@ 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_portal/components/ - -# Generated extension component documentation (regenerated at build time) -developer_portal/extensions/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/admin_docs/configuration/cache.mdx b/docs/admin_docs/configuration/cache.mdx index 4bb37483181..be1459f09f1 100644 --- a/docs/admin_docs/configuration/cache.mdx +++ b/docs/admin_docs/configuration/cache.mdx @@ -159,9 +159,9 @@ Then on configuration: WEBDRIVER_AUTH_FUNC = auth_driver ``` -## Signal Cache Backend +## Distributed Coordination Backend -Superset supports an optional signal cache (`SIGNAL_CACHE_CONFIG`) for +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 @@ -176,11 +176,11 @@ that are not available in general Flask-Caching backends. ### Configuration -The signal cache uses Flask-Caching style configuration for consistency with other cache -backends. Configure `SIGNAL_CACHE_CONFIG` in `superset_config.py`: +The distributed coordination uses Flask-Caching style configuration for consistency with other cache +backends. Configure `DISTRIBUTED_COORDINATION_CONFIG` in `superset_config.py`: ```python -SIGNAL_CACHE_CONFIG = { +DISTRIBUTED_COORDINATION_CONFIG = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_HOST": "localhost", "CACHE_REDIS_PORT": 6379, @@ -192,7 +192,7 @@ SIGNAL_CACHE_CONFIG = { For Redis Sentinel deployments: ```python -SIGNAL_CACHE_CONFIG = { +DISTRIBUTED_COORDINATION_CONFIG = { "CACHE_TYPE": "RedisSentinelCache", "CACHE_REDIS_SENTINELS": [("sentinel1", 26379), ("sentinel2", 26379)], "CACHE_REDIS_SENTINEL_MASTER": "mymaster", @@ -205,7 +205,7 @@ SIGNAL_CACHE_CONFIG = { For SSL/TLS connections: ```python -SIGNAL_CACHE_CONFIG = { +DISTRIBUTED_COORDINATION_CONFIG = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_HOST": "redis.example.com", "CACHE_REDIS_PORT": 6380, @@ -229,7 +229,7 @@ Individual lock acquisitions can override this value when needed. ### Database-Only Mode -When `SIGNAL_CACHE_CONFIG` is not configured, Superset uses database-backed operations: +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 diff --git a/docs/developer_docs/api.mdx b/docs/developer_docs/api.mdx index ff563805a8e..09f3843087f 100644 --- a/docs/developer_docs/api.mdx +++ b/docs/developer_docs/api.mdx @@ -68,14 +68,14 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ | `POST` | [Create a new dashboard](./api/create-a-new-dashboard) | `/api/v1/dashboard/` | | `GET` | [Get metadata information about this API resource (dashboard--info)](./api/get-metadata-information-about-this-api-resource-dashboard-info) | `/api/v1/dashboard/_info` | | `GET` | [Get a dashboard detail information](./api/get-a-dashboard-detail-information) | `/api/v1/dashboard/{id_or_slug}` | -| `GET` | [Get a dashboard's chart definitions.](./api/get-a-dashboards-chart-definitions) | `/api/v1/dashboard/{id_or_slug}/charts` | +| `GET` | [Get a dashboard's chart definitions.](./api/get-a-dashboard-s-chart-definitions) | `/api/v1/dashboard/{id_or_slug}/charts` | | `POST` | [Create a copy of an existing dashboard](./api/create-a-copy-of-an-existing-dashboard) | `/api/v1/dashboard/{id_or_slug}/copy/` | -| `GET` | [Get dashboard's datasets](./api/get-dashboards-datasets) | `/api/v1/dashboard/{id_or_slug}/datasets` | -| `DELETE` | [Delete a dashboard's embedded configuration](./api/delete-a-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` | -| `GET` | [Get the dashboard's embedded configuration](./api/get-the-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` | -| `POST` | [Set a dashboard's embedded configuration](./api/set-a-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` | +| `GET` | [Get dashboard's datasets](./api/get-dashboard-s-datasets) | `/api/v1/dashboard/{id_or_slug}/datasets` | +| `DELETE` | [Delete a dashboard's embedded configuration](./api/delete-a-dashboard-s-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` | +| `GET` | [Get the dashboard's embedded configuration](./api/get-the-dashboard-s-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` | +| `POST` | [Set a dashboard's embedded configuration](./api/set-a-dashboard-s-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` | | `PUT` | [Update dashboard by id_or_slug embedded](./api/update-dashboard-by-id-or-slug-embedded) | `/api/v1/dashboard/{id_or_slug}/embedded` | -| `GET` | [Get dashboard's tabs](./api/get-dashboards-tabs) | `/api/v1/dashboard/{id_or_slug}/tabs` | +| `GET` | [Get dashboard's tabs](./api/get-dashboard-s-tabs) | `/api/v1/dashboard/{id_or_slug}/tabs` | | `DELETE` | [Delete a dashboard](./api/delete-a-dashboard) | `/api/v1/dashboard/{pk}` | | `PUT` | [Update a dashboard](./api/update-a-dashboard) | `/api/v1/dashboard/{pk}` | | `POST` | [Compute and cache a screenshot (dashboard-pk-cache-dashboard-screenshot)](./api/compute-and-cache-a-screenshot-dashboard-pk-cache-dashboard-screenshot) | `/api/v1/dashboard/{pk}/cache_dashboard_screenshot/` | @@ -84,7 +84,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ | `POST` | [Mark the dashboard as favorite for the current user](./api/mark-the-dashboard-as-favorite-for-the-current-user) | `/api/v1/dashboard/{pk}/favorites/` | | `PUT` | [Update native filters configuration for a dashboard.](./api/update-native-filters-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/filters` | | `GET` | [Get a computed screenshot from cache (dashboard-pk-screenshot-digest)](./api/get-a-computed-screenshot-from-cache-dashboard-pk-screenshot-digest) | `/api/v1/dashboard/{pk}/screenshot/{digest}/` | -| `GET` | [Get dashboard's thumbnail](./api/get-dashboards-thumbnail) | `/api/v1/dashboard/{pk}/thumbnail/{digest}/` | +| `GET` | [Get dashboard's thumbnail](./api/get-dashboard-s-thumbnail) | `/api/v1/dashboard/{pk}/thumbnail/{digest}/` | | `GET` | [Download multiple dashboards as YAML files](./api/download-multiple-dashboards-as-yaml-files) | `/api/v1/dashboard/export/` | | `GET` | [Check favorited dashboards for current user](./api/check-favorited-dashboards-for-current-user) | `/api/v1/dashboard/favorite_status/` | | `POST` | [Import dashboard(s) with associated charts/datasets/databases](./api/import-dashboard-s-with-associated-charts-datasets-databases) | `/api/v1/dashboard/import/` | @@ -177,7 +177,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ | `GET` | [Get names of databases currently available](./api/get-names-of-databases-currently-available) | `/api/v1/database/available/` | | `GET` | [Download database(s) and associated dataset(s) as a zip file](./api/download-database-s-and-associated-dataset-s-as-a-zip-file) | `/api/v1/database/export/` | | `POST` | [Import database(s) with associated datasets](./api/import-database-s-with-associated-datasets) | `/api/v1/database/import/` | -| `GET` | [Receive personal access tokens from OAuth2](./api/receive-personal-access-tokens-from-o-auth-2) | `/api/v1/database/oauth2/` | +| `GET` | [Receive personal access tokens from OAuth2](./api/receive-personal-access-tokens-from-oauth2) | `/api/v1/database/oauth2/` | | `GET` | [Get related fields data (database-related-column-name)](./api/get-related-fields-data-database-related-column-name) | `/api/v1/database/related/{column_name}` | | `POST` | [Test a database connection](./api/test-a-database-connection) | `/api/v1/database/test_connection/` | | `POST` | [Upload a file and returns file metadata](./api/upload-a-file-and-returns-file-metadata) | `/api/v1/database/upload_metadata/` | @@ -201,7 +201,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ | Method | Endpoint | Description | |--------|----------|-------------| -| `GET` | [Get the bootstrap data for SqlLab page](./api/get-the-bootstrap-data-for-sql-lab-page) | `/api/v1/sqllab/` | +| `GET` | [Get the bootstrap data for SqlLab page](./api/get-the-bootstrap-data-for-sqllab-page) | `/api/v1/sqllab/` | | `POST` | [Estimate the SQL query execution cost](./api/estimate-the-sql-query-execution-cost) | `/api/v1/sqllab/estimate/` | | `POST` | [Execute a SQL query](./api/execute-a-sql-query) | `/api/v1/sqllab/execute/` | | `GET` | [Export the SQL query results to a CSV](./api/export-the-sql-query-results-to-a-csv) | `/api/v1/sqllab/export/{client_id}/` | @@ -249,7 +249,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ | Method | Endpoint | Description | |--------|----------|-------------| -| `GET` | [Return an AdvancedDataTypeResponse](./api/return-an-advanced-data-type-response) | `/api/v1/advanced_data_type/convert` | +| `GET` | [Return an AdvancedDataTypeResponse](./api/return-an-advanceddatatyperesponse) | `/api/v1/advanced_data_type/convert` | | `GET` | [Return a list of available advanced data types](./api/return-a-list-of-available-advanced-data-types) | `/api/v1/advanced_data_type/types` | @@ -324,8 +324,8 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ | Method | Endpoint | Description | |--------|----------|-------------| -| `POST` | [Create a new dashboard's permanent link](./api/create-a-new-dashboards-permanent-link) | `/api/v1/dashboard/{pk}/permalink` | -| `GET` | [Get dashboard's permanent link state](./api/get-dashboards-permanent-link-state) | `/api/v1/dashboard/permalink/{key}` | +| `POST` | [Create a new dashboard's permanent link](./api/create-a-new-dashboard-s-permanent-link) | `/api/v1/dashboard/{pk}/permalink` | +| `GET` | [Get dashboard's permanent link state](./api/get-dashboard-s-permanent-link-state) | `/api/v1/dashboard/permalink/{key}` | @@ -335,7 +335,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ | Method | Endpoint | Description | |--------|----------|-------------| | `POST` | [Create a new permanent link (explore-permalink)](./api/create-a-new-permanent-link-explore-permalink) | `/api/v1/explore/permalink` | -| `GET` | [Get chart's permanent link state](./api/get-charts-permanent-link-state) | `/api/v1/explore/permalink/{key}` | +| `GET` | [Get chart's permanent link state](./api/get-chart-s-permanent-link-state) | `/api/v1/explore/permalink/{key}` | @@ -345,7 +345,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ | Method | Endpoint | Description | |--------|----------|-------------| | `POST` | [Create a new permanent link (sqllab-permalink)](./api/create-a-new-permanent-link-sqllab-permalink) | `/api/v1/sqllab/permalink` | -| `GET` | [Get permanent link state for SQLLab editor.](./api/get-permanent-link-state-for-sql-lab-editor) | `/api/v1/sqllab/permalink/{key}` | +| `GET` | [Get permanent link state for SQLLab editor.](./api/get-permanent-link-state-for-sqllab-editor) | `/api/v1/sqllab/permalink/{key}` | @@ -363,10 +363,10 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ | Method | Endpoint | Description | |--------|----------|-------------| -| `POST` | [Create a dashboard's filter state](./api/create-a-dashboards-filter-state) | `/api/v1/dashboard/{pk}/filter_state` | -| `DELETE` | [Delete a dashboard's filter state value](./api/delete-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` | -| `GET` | [Get a dashboard's filter state value](./api/get-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` | -| `PUT` | [Update a dashboard's filter state value](./api/update-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` | +| `POST` | [Create a dashboard's filter state](./api/create-a-dashboard-s-filter-state) | `/api/v1/dashboard/{pk}/filter_state` | +| `DELETE` | [Delete a dashboard's filter state value](./api/delete-a-dashboard-s-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` | +| `GET` | [Get a dashboard's filter state value](./api/get-a-dashboard-s-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` | +| `PUT` | [Update a dashboard's filter state value](./api/update-a-dashboard-s-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` | diff --git a/docs/developer_docs/extensions/architecture.md b/docs/developer_docs/extensions/architecture.md index b13091bfa3d..568f8feff47 100644 --- a/docs/developer_docs/extensions/architecture.md +++ b/docs/developer_docs/extensions/architecture.md @@ -39,7 +39,7 @@ Superset's core should remain minimal, with many features delegated to extension ### 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. Each extension declares its capabilities in a metadata file, enabling the host to: +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: - Manage the extension lifecycle - Provide a consistent user experience - Validate extension compatibility @@ -122,7 +122,6 @@ The Superset host application serves as the runtime environment for extensions: The extensions table contains: - Extension name, version, and author -- Contributed features and exposed modules - Metadata and configuration - Built frontend and/or backend code @@ -173,7 +172,7 @@ new ModuleFederationPlugin({ This configuration does several important things: -**`exposes`** - Declares which modules are available to the host application. The extension makes `./index` available as its entry point. +**`exposes`** - Declares which modules are available to the host application. Superset always loads extensions by requesting the `./index` module from the remote container — this is a fixed convention, not a configurable value. Extensions must expose exactly `'./index': './src/index.tsx'` and place all API registrations (views, commands, menus, editors, event listeners) in that file. The module is executed as a side effect when the extension loads, so any call to `views.registerView`, `commands.registerCommand`, etc. made at the top level of `index.tsx` will run automatically. **`externals` and `externalsType`** - Tell Webpack that when the extension imports `@apache-superset/core`, it should use `window.superset` at runtime instead of bundling its own copy. This ensures extensions use the host's implementation of shared packages. diff --git a/docs/developer_docs/extensions/contribution-types.md b/docs/developer_docs/extensions/contribution-types.md index f802bd609cb..945e288b932 100644 --- a/docs/developer_docs/extensions/contribution-types.md +++ b/docs/developer_docs/extensions/contribution-types.md @@ -28,100 +28,83 @@ 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 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`. ### Views -Extensions can add new views or panels to the host application, such as custom SQL Lab panels, dashboards, or other UI components. Each view is registered with a unique ID and can be activated or deactivated as needed. Contribution areas are uniquely identified (e.g., `sqllab.panels` for SQL Lab panels), enabling seamless integration into specific parts of the application. +Extensions can add new views or panels to the host application, such as custom SQL Lab panels, dashboards, or other UI components. Contribution areas are uniquely identified (e.g., `sqllab.panels` for SQL Lab panels), enabling seamless integration into specific parts of the application. -```json -"frontend": { - "contributions": { - "views": { - "sqllab": { - "panels": [ - { - "id": "my_extension.main", - "name": "My Panel Name" - } - ] - } - } - } -} +```tsx +import React from 'react'; +import { views } from '@apache-superset/core'; +import MyPanel from './MyPanel'; + +views.registerView( + { id: 'my-extension.main', name: 'My Panel Name' }, + 'sqllab.panels', + () => , +); ``` ### Commands -Extensions can define custom commands that can be executed within the host application, such as context-aware actions or menu options. Each command can specify properties like a unique command identifier, an icon, a title, and a description. These commands can be invoked by users through menus, keyboard shortcuts, or other UI elements, enabling extensions to add rich, interactive functionality to Superset. +Extensions can define custom commands that can be executed within the host application, such as context-aware actions or menu options. Each command specifies a unique identifier, a title, an optional icon, and a description. Commands can be invoked by users through menus, keyboard shortcuts, or other UI elements. -```json -"frontend": { - "contributions": { - "commands": [ - { - "command": "my_extension.copy_query", - "icon": "CopyOutlined", - "title": "Copy Query", - "description": "Copy the current query to clipboard" - } - ] - } -} +```typescript +import { commands } from '@apache-superset/core'; + +commands.registerCommand( + { + id: 'my-extension.copy-query', + title: 'Copy Query', + icon: 'CopyOutlined', + description: 'Copy the current query to clipboard', + }, + () => { + // Command implementation + }, +); ``` ### Menus -Extensions can contribute new menu items or context menus to the host application, providing users with additional actions and options. Each menu item can specify properties such as the target view, the command to execute, its placement (primary, secondary, or context), and conditions for when it should be displayed. Menu contribution areas are uniquely identified (e.g., `sqllab.editor` for the SQL Lab editor), allowing extensions to seamlessly integrate their functionality into specific menus and workflows within Superset. +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). -```json -"frontend": { - "contributions": { - "menus": { - "sqllab": { - "editor": { - "primary": [ - { - "view": "builtin.editor", - "command": "my_extension.copy_query" - } - ], - "secondary": [ - { - "view": "builtin.editor", - "command": "my_extension.prettify" - } - ], - "context": [ - { - "view": "builtin.editor", - "command": "my_extension.clear" - } - ] - } - } - } - } -} +```typescript +import { menus } from '@apache-superset/core'; + +menus.addMenuItem('sqllab.editor', { + placement: 'primary', + command: 'my-extension.copy-query', +}); + +menus.addMenuItem('sqllab.editor', { + placement: 'secondary', + command: 'my-extension.prettify', +}); + +menus.addMenuItem('sqllab.editor', { + placement: 'context', + command: 'my-extension.clear', +}); ``` ### Editors -Extensions can replace Superset's default text editors with custom implementations. This enables enhanced editing experiences using alternative editor frameworks like Monaco, CodeMirror, or custom solutions. When an extension registers an editor for a language, it replaces the default Ace editor in all locations that use that language (SQL Lab, Dashboard Properties, CSS editors, etc.). +Extensions can replace Superset's default text editors with custom implementations. This enables enhanced editing experiences using alternative editor frameworks like Monaco, CodeMirror, or custom solutions. When an extension registers an editor for a language, it replaces the default editor in all locations that use that language (SQL Lab, Dashboard Properties, CSS editors, etc.). -```json -"frontend": { - "contributions": { - "editors": [ - { - "id": "my_extension.monaco_sql", - "name": "Monaco SQL Editor", - "languages": ["sql"], - "description": "Monaco-based SQL editor with IntelliSense" - } - ] - } -} +```typescript +import { editors } from '@apache-superset/core'; +import MonacoSQLEditor from './MonacoSQLEditor'; + +editors.registerEditor( + { + id: 'my-extension.monaco-sql', + name: 'Monaco SQL Editor', + languages: ['sql'], + }, + MonacoSQLEditor, +); ``` See [Editors Extension Point](./extension-points/editors) for implementation details. @@ -132,22 +115,51 @@ Backend contribution types allow extensions to extend Superset's server-side cap ### REST API Endpoints -Extensions can register custom REST API endpoints under the `/api/v1/extensions/` namespace. This dedicated namespace prevents conflicts with built-in endpoints and provides a clear separation between core and extension functionality. - -```json -"backend": { - "entryPoints": ["my_extension.entrypoint"], - "files": ["backend/src/my_extension/**/*.py"] -} -``` - -The entry point module registers the API with Superset: +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.api.rest_api import add_extension_api -from .api import MyExtensionAPI +from superset_core.api.rest_api import RestApi, api +from flask_appbuilder.api import expose, protect -add_extension_api(MyExtensionAPI) +@api( + id="my_extension_api", + name="My Extension API", + description="Custom API endpoints for my extension" +) +class MyExtensionAPI(RestApi): + @expose("/hello", methods=("GET",)) + @protect() + def hello(self) -> Response: + return self.response(200, result={"message": "Hello from extension!"}) + +# Import the class in entrypoint.py to register it +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: + +- **Extension context**: `/extensions/{publisher}/{name}/` with ID prefixed as `extensions.{publisher}.{name}.{id}` +- **Host context**: `/api/v1/` with original ID + +For an extension with publisher `my-org` and name `dataset-tools`, the endpoint above would be accessible at: +``` +/extensions/my-org/dataset-tools/hello +``` + +You can also specify a `resource_name` parameter to add an additional path segment: + +```python +@api( + id="analytics_api", + name="Analytics API", + resource_name="analytics" # Adds /analytics to the path +) +class AnalyticsAPI(RestApi): + @expose("/insights", methods=("GET",)) + def insights(self): + # This endpoint will be available at: + # /extensions/my-org/dataset-tools/analytics/insights + return self.response(200, result={"insights": []}) ``` ### MCP Tools and Prompts diff --git a/docs/developer_docs/extensions/development.md b/docs/developer_docs/extensions/development.md index a927e44962e..c6baa635986 100644 --- a/docs/developer_docs/extensions/development.md +++ b/docs/developer_docs/extensions/development.md @@ -91,39 +91,45 @@ The `README.md` file provides documentation and instructions for using the exten ## Extension Metadata -The `extension.json` file contains all metadata necessary for the host application to understand and manage the extension: +The `extension.json` file contains the metadata necessary for the host application to identify and load the extension. Extensions follow a **convention-over-configuration** approach where entry points and build configuration are determined by standardized file locations rather than explicit declarations. ```json { - "id": "dataset-references", - "name": "Dataset References", + "publisher": "my-org", + "name": "dataset-references", + "displayName": "Dataset References", "version": "1.0.0", - "frontend": { - "contributions": { - "views": { - "sqllab": { - "panels": [ - { - "id": "dataset-references.main", - "name": "Dataset References" - } - ] - } - } - }, - "moduleFederation": { - "exposes": ["./index"], - "name": "datasetReferences" - } - }, - "backend": { - "entryPoints": ["superset_extensions.dataset_references.entrypoint"], - "files": ["backend/src/superset_extensions/dataset_references/**/*.py"] - } + "license": "Apache-2.0", + "permissions": [] } ``` -The `contributions` section declares how the extension extends Superset's functionality through views, commands, menus, and other contribution types. The `backend` section specifies entry points and files to include in the bundle. +### Convention-Based Entry Points + +Extensions use standardized entry point locations: + +- **Backend**: `backend/src/superset_extensions/{publisher}/{name}/entrypoint.py` +- **Frontend**: `frontend/src/index.tsx` + +### Build Configuration + +Backend build configuration is specified in `backend/pyproject.toml`: + +```toml +[project] +name = "my_org-dataset_references" +version = "1.0.0" +license = "Apache-2.0" + +[tool.apache_superset_extensions.build] +# Files to include in the extension build/bundle +include = [ + "src/superset_extensions/my_org/dataset_references/**/*.py", +] +exclude = [] +``` + +The `include` patterns specify which files to bundle, while `exclude` patterns can filter out unwanted files (e.g., test files, cache directories). ## Interacting with the Host @@ -154,34 +160,38 @@ export const onDidQueryStop: Event; The following code demonstrates more examples of the existing frontend APIs: ```typescript -import { core, commands, sqlLab, authentication, Button } from '@apache-superset/core'; +import React from 'react'; +import { views, commands, sqlLab, authentication, Button } from '@apache-superset/core'; import MyPanel from './MyPanel'; -export function activate(context) { - // Register a new panel (view) in SQL Lab and use shared UI components in your extension's React code - const panelDisposable = core.registerView('my_extension.panel',