From a74d32ab44fd2562fc904c26bb4f0147736970da Mon Sep 17 00:00:00 2001 From: "Michael S. Molina" <70410625+michael-s-molina@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:51:29 -0300 Subject: [PATCH] feat(extensions): code-first frontend contributions (#38346) --- .../developer_docs/extensions/architecture.md | 5 +- .../extensions/contribution-types.md | 133 ++-- docs/developer_docs/extensions/development.md | 81 ++- docs/developer_docs/extensions/quick-start.md | 63 +- .../src/superset_core/extensions/types.py | 100 +-- .../src/superset_extensions_cli/cli.py | 8 +- .../templates/extension.json.j2 | 14 - .../templates/frontend/src/index.tsx.j2 | 17 +- .../templates/frontend/webpack.config.js.j2 | 3 +- .../tests/test_cli_build.py | 31 +- .../tests/test_cli_init.py | 17 +- .../tests/test_templates.py | 19 +- .../superset-core/src/api/commands.ts | 49 +- .../superset-core/src/api/contributions.ts | 92 +-- .../packages/superset-core/src/api/core.ts | 74 +-- .../packages/superset-core/src/api/editors.ts | 55 +- .../packages/superset-core/src/api/index.ts | 2 + .../packages/superset-core/src/api/menus.ts | 102 ++++ .../packages/superset-core/src/api/views.ts | 88 +++ .../spec/helpers/testing-library.tsx | 5 +- .../components/AppLayout/AppLayout.test.tsx | 79 +-- .../src/SqlLab/components/AppLayout/index.tsx | 9 +- .../src/SqlLab/components/SouthPane/index.tsx | 20 +- .../components/StatusBar/StatusBar.test.tsx | 14 +- .../src/SqlLab/components/StatusBar/index.tsx | 9 +- .../src/components/PanelToolbar/index.tsx | 40 +- .../ViewListExtension.test.tsx | 218 +++---- .../components/ViewListExtension/index.tsx | 24 +- superset-frontend/src/core/commands/index.ts | 42 +- .../src/core/editors/EditorHost.tsx | 8 +- .../src/core/editors/EditorProviders.test.ts | 85 ++- .../src/core/editors/EditorProviders.ts | 20 +- superset-frontend/src/core/editors/index.ts | 36 +- .../src/core/extensions/index.ts | 6 +- superset-frontend/src/core/index.ts | 14 +- .../src/core/menus/index.test.ts | 124 ++++ superset-frontend/src/core/menus/index.ts | 83 +++ superset-frontend/src/core/models.ts | 4 - .../src/core/views/index.test.ts | 112 ++++ superset-frontend/src/core/views/index.ts | 83 +++ .../src/extensions/ExtensionsContext.test.tsx | 150 ----- .../src/extensions/ExtensionsContext.tsx | 93 --- .../extensions/ExtensionsContextUtils.test.ts | 74 --- .../src/extensions/ExtensionsContextUtils.ts | 32 - .../src/extensions/ExtensionsList.test.tsx | 17 - .../src/extensions/ExtensionsList.tsx | 29 - .../src/extensions/ExtensionsLoader.test.ts | 113 ++++ .../src/extensions/ExtensionsLoader.ts | 161 +++++ .../src/extensions/ExtensionsManager.test.ts | 572 ------------------ .../src/extensions/ExtensionsManager.ts | 355 ----------- .../src/extensions/ExtensionsStartup.test.tsx | 72 +-- .../src/extensions/ExtensionsStartup.tsx | 51 +- superset-frontend/src/views/App.tsx | 45 +- .../src/views/RootContextProviders.tsx | 17 +- superset/extensions/utils.py | 5 +- tests/unit_tests/extensions/test_types.py | 59 +- 56 files changed, 1430 insertions(+), 2403 deletions(-) create mode 100644 superset-frontend/packages/superset-core/src/api/menus.ts create mode 100644 superset-frontend/packages/superset-core/src/api/views.ts create mode 100644 superset-frontend/src/core/menus/index.test.ts create mode 100644 superset-frontend/src/core/menus/index.ts create mode 100644 superset-frontend/src/core/views/index.test.ts create mode 100644 superset-frontend/src/core/views/index.ts delete mode 100644 superset-frontend/src/extensions/ExtensionsContext.test.tsx delete mode 100644 superset-frontend/src/extensions/ExtensionsContext.tsx delete mode 100644 superset-frontend/src/extensions/ExtensionsContextUtils.test.ts delete mode 100644 superset-frontend/src/extensions/ExtensionsContextUtils.ts create mode 100644 superset-frontend/src/extensions/ExtensionsLoader.test.ts create mode 100644 superset-frontend/src/extensions/ExtensionsLoader.ts delete mode 100644 superset-frontend/src/extensions/ExtensionsManager.test.ts delete mode 100644 superset-frontend/src/extensions/ExtensionsManager.ts 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..e6fddef7abd 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. diff --git a/docs/developer_docs/extensions/development.md b/docs/developer_docs/extensions/development.md index a927e44962e..57810965810 100644 --- a/docs/developer_docs/extensions/development.md +++ b/docs/developer_docs/extensions/development.md @@ -91,39 +91,24 @@ 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. Backend contributions (entry points and files) are declared here. Frontend contributions are registered directly in code from `frontend/src/index.tsx`. ```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" - } - }, + "license": "Apache-2.0", "backend": { "entryPoints": ["superset_extensions.dataset_references.entrypoint"], "files": ["backend/src/superset_extensions/dataset_references/**/*.py"] - } + }, + "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. +The `backend` section specifies Python entry points to load eagerly when the extension starts, and glob patterns for source files to include in the bundle. ## Interacting with the Host @@ -154,34 +139,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',