mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
refactor: Make extensions contribution schema consistent (#37856)
This commit is contained in:
committed by
GitHub
parent
7ec5f1d7ec
commit
b98b34a60f
@@ -38,12 +38,14 @@ Extensions can add new views or panels to the host application, such as custom S
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab.panels": [
|
||||
{
|
||||
"id": "my_extension.main",
|
||||
"name": "My Panel Name"
|
||||
}
|
||||
]
|
||||
"sqllab": {
|
||||
"panels": [
|
||||
{
|
||||
"id": "my_extension.main",
|
||||
"name": "My Panel Name"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,25 +78,27 @@ Extensions can contribute new menu items or context menus to the host applicatio
|
||||
"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"
|
||||
}
|
||||
]
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,12 +92,14 @@ The `extension.json` file contains all metadata necessary for the host applicati
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab.panels": [
|
||||
{
|
||||
"id": "dataset_references.main",
|
||||
"name": "Dataset references"
|
||||
}
|
||||
]
|
||||
"sqllab": {
|
||||
"panels": [
|
||||
{
|
||||
"id": "dataset_references.main",
|
||||
"name": "Dataset references"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"moduleFederation": {
|
||||
|
||||
@@ -93,12 +93,14 @@ This example adds a "Data Profiler" panel to SQL Lab:
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab.panels": [
|
||||
{
|
||||
"id": "data_profiler.main",
|
||||
"name": "Data Profiler"
|
||||
}
|
||||
]
|
||||
"sqllab": {
|
||||
"panels": [
|
||||
{
|
||||
"id": "data_profiler.main",
|
||||
"name": "Data Profiler"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,25 +144,27 @@ This example adds primary, secondary, and context actions to the editor:
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
]
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,12 +94,14 @@ The generated `extension.json` contains basic metadata. Update it to register yo
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab.panels": [
|
||||
{
|
||||
"id": "hello_world.main",
|
||||
"name": "Hello World"
|
||||
}
|
||||
]
|
||||
"sqllab": {
|
||||
"panels": [
|
||||
{
|
||||
"id": "hello_world.main",
|
||||
"name": "Hello World"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"moduleFederation": {
|
||||
|
||||
@@ -56,19 +56,37 @@ class ModuleFederationConfig(BaseModel):
|
||||
|
||||
|
||||
class ContributionConfig(BaseModel):
|
||||
"""Configuration for frontend UI contributions."""
|
||||
"""Configuration for frontend UI contributions.
|
||||
|
||||
Views and menus use a nested structure: type -> scope -> location -> contributions.
|
||||
|
||||
Example:
|
||||
{
|
||||
"views": {
|
||||
"sqllab": {
|
||||
"panels": [{"id": "my-ext.panel", "name": "My Panel"}],
|
||||
"leftSidebar": [{"id": "my-ext.sidebar", "name": "Sidebar"}]
|
||||
}
|
||||
},
|
||||
"menus": {
|
||||
"sqllab": {
|
||||
"editor": {"primary": [...], "secondary": [...]}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
commands: list[dict[str, Any]] = Field(
|
||||
default_factory=list,
|
||||
description="Command contributions",
|
||||
)
|
||||
views: dict[str, list[dict[str, Any]]] = Field(
|
||||
views: dict[str, dict[str, list[dict[str, Any]]]] = Field(
|
||||
default_factory=dict,
|
||||
description="View contributions by location",
|
||||
description="View contributions by scope and location",
|
||||
)
|
||||
menus: dict[str, Any] = Field(
|
||||
menus: dict[str, dict[str, Any]] = Field(
|
||||
default_factory=dict,
|
||||
description="Menu contributions",
|
||||
description="Menu contributions by scope and location",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -93,20 +93,55 @@ export interface EditorContribution {
|
||||
*/
|
||||
export type EditorLanguage = 'sql' | 'json' | 'yaml' | 'markdown' | 'css';
|
||||
|
||||
/**
|
||||
* Valid locations within SQL Lab.
|
||||
*/
|
||||
export type SqlLabLocation =
|
||||
| 'leftSidebar'
|
||||
| 'rightSidebar'
|
||||
| 'panels'
|
||||
| 'editor'
|
||||
| 'statusBar'
|
||||
| 'results'
|
||||
| 'queryHistory';
|
||||
|
||||
/**
|
||||
* Nested structure for view contributions by scope and location.
|
||||
* @example
|
||||
* {
|
||||
* sqllab: {
|
||||
* panels: [{ id: "my-ext.panel", name: "My Panel" }],
|
||||
* leftSidebar: [{ id: "my-ext.sidebar", name: "My Sidebar" }]
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export interface ViewContributions {
|
||||
sqllab?: Partial<Record<SqlLabLocation, ViewContribution[]>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested structure for menu contributions by scope and location.
|
||||
* @example
|
||||
* {
|
||||
* sqllab: {
|
||||
* editor: { primary: [...], secondary: [...] }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export interface MenuContributions {
|
||||
sqllab?: Partial<Record<SqlLabLocation, MenuContribution>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates all contributions (commands, menus, views, and editors) provided by an extension or module.
|
||||
*/
|
||||
export interface Contributions {
|
||||
/** List of command contributions. */
|
||||
commands: CommandContribution[];
|
||||
/** Mapping of menu contributions by menu key. */
|
||||
menus: {
|
||||
[key: string]: MenuContribution;
|
||||
};
|
||||
/** Mapping of view contributions by view key. */
|
||||
views: {
|
||||
[key: string]: ViewContribution[];
|
||||
};
|
||||
/** Nested mapping of menu contributions by scope and location. */
|
||||
menus: MenuContributions;
|
||||
/** Nested mapping of view contributions by scope and location. */
|
||||
views: ViewContributions;
|
||||
/** List of editor contributions. */
|
||||
editors?: EditorContribution[];
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import { initialState } from 'src/SqlLab/fixtures';
|
||||
import useStoredSidebarWidth from 'src/components/ResizableSidebar/useStoredSidebarWidth';
|
||||
import type { contributions, core } from '@apache-superset/core';
|
||||
import ExtensionsManager from 'src/extensions/ExtensionsManager';
|
||||
import { ViewContribution } from 'src/SqlLab/contributions';
|
||||
import AppLayout from './index';
|
||||
|
||||
jest.mock('src/components/ResizableSidebar/useStoredSidebarWidth');
|
||||
@@ -156,7 +155,9 @@ test('renders right sidebar when RIGHT_SIDEBAR_VIEW_ID view is contributed', asy
|
||||
commands: [],
|
||||
menus: {},
|
||||
views: {
|
||||
[ViewContribution.RightSidebar]: [createMockView(viewId)],
|
||||
sqllab: {
|
||||
rightSidebar: [createMockView(viewId)],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
SQL_EDITOR_LEFTBAR_WIDTH,
|
||||
SQL_EDITOR_RIGHTBAR_WIDTH,
|
||||
} from 'src/SqlLab/constants';
|
||||
import { ViewContribution } from 'src/SqlLab/contributions';
|
||||
import { ViewLocations } from 'src/SqlLab/contributions';
|
||||
import ViewListExtension from 'src/components/ViewListExtension';
|
||||
|
||||
import SqlEditorLeftBar from '../SqlEditorLeftBar';
|
||||
@@ -98,7 +98,7 @@ const AppLayout: React.FC = ({ children }) => {
|
||||
};
|
||||
const contributions =
|
||||
ExtensionsManager.getInstance().getViewContributions(
|
||||
ViewContribution.RightSidebar,
|
||||
ViewLocations.sqllab.rightSidebar,
|
||||
) || [];
|
||||
|
||||
return (
|
||||
@@ -139,7 +139,7 @@ const AppLayout: React.FC = ({ children }) => {
|
||||
min={SQL_EDITOR_RIGHTBAR_WIDTH}
|
||||
>
|
||||
<ContentWrapper>
|
||||
<ViewListExtension viewId={ViewContribution.RightSidebar} />
|
||||
<ViewListExtension viewId={ViewLocations.sqllab.rightSidebar} />
|
||||
</ContentWrapper>
|
||||
</Splitter.Panel>
|
||||
)}
|
||||
|
||||
@@ -30,7 +30,7 @@ import { useEditorQueriesQuery } from 'src/hooks/apiResources/queries';
|
||||
import useEffectEvent from 'src/hooks/useEffectEvent';
|
||||
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
|
||||
import PanelToolbar from 'src/components/PanelToolbar';
|
||||
import { ViewContribution } from 'src/SqlLab/contributions';
|
||||
import { ViewLocations } from 'src/SqlLab/contributions';
|
||||
|
||||
interface QueryHistoryProps {
|
||||
queryEditorId: string | number;
|
||||
@@ -121,7 +121,7 @@ const QueryHistory = ({
|
||||
|
||||
return editorQueries.length > 0 ? (
|
||||
<>
|
||||
<PanelToolbar viewId={ViewContribution.QueryHistory} />
|
||||
<PanelToolbar viewId={ViewLocations.sqllab.queryHistory} />
|
||||
<QueryTable
|
||||
columns={[
|
||||
'state',
|
||||
|
||||
@@ -90,7 +90,7 @@ import ExploreCtasResultsButton from '../ExploreCtasResultsButton';
|
||||
import ExploreResultsButton from '../ExploreResultsButton';
|
||||
import HighlightedSql from '../HighlightedSql';
|
||||
import PanelToolbar from 'src/components/PanelToolbar';
|
||||
import { ViewContribution } from 'src/SqlLab/contributions';
|
||||
import { ViewLocations } from 'src/SqlLab/contributions';
|
||||
|
||||
enum LimitingFactor {
|
||||
Query = 'QUERY',
|
||||
@@ -466,7 +466,7 @@ const ResultSet = ({
|
||||
datasource={datasource}
|
||||
/>
|
||||
<PanelToolbar
|
||||
viewId={ViewContribution.Results}
|
||||
viewId={ViewLocations.sqllab.results}
|
||||
defaultPrimaryActions={defaultPrimaryActions}
|
||||
/>
|
||||
</ResultSetButtons>
|
||||
|
||||
@@ -28,7 +28,7 @@ import { removeTables, setActiveSouthPaneTab } from 'src/SqlLab/actions/sqlLab';
|
||||
import { Flex, Label } from '@superset-ui/core/components';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import { SqlLabRootState } from 'src/SqlLab/types';
|
||||
import { ViewContribution } from 'src/SqlLab/contributions';
|
||||
import { ViewLocations } from 'src/SqlLab/contributions';
|
||||
import PanelToolbar from 'src/components/PanelToolbar';
|
||||
import { useExtensionsContext } from 'src/extensions/ExtensionsContext';
|
||||
import ExtensionsManager from 'src/extensions/ExtensionsManager';
|
||||
@@ -106,7 +106,7 @@ const SouthPane = ({
|
||||
const dispatch = useDispatch();
|
||||
const contributions =
|
||||
ExtensionsManager.getInstance().getViewContributions(
|
||||
ViewContribution.Panels,
|
||||
ViewLocations.sqllab.panels,
|
||||
) || [];
|
||||
const { getView } = useExtensionsContext();
|
||||
const { offline, tables } = useSelector(
|
||||
@@ -242,7 +242,7 @@ const SouthPane = ({
|
||||
padding: 8px;
|
||||
`}
|
||||
>
|
||||
<PanelToolbar viewId={ViewContribution.Panels} />
|
||||
<PanelToolbar viewId={ViewLocations.sqllab.panels} />
|
||||
</Flex>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import { Flex } from '@superset-ui/core/components';
|
||||
import { styled } from '@apache-superset/core/ui';
|
||||
import { MenuItemType } from '@superset-ui/core/components/Menu';
|
||||
import { ViewContribution } from 'src/SqlLab/contributions';
|
||||
import { ViewLocations } from 'src/SqlLab/contributions';
|
||||
import PanelToolbar from 'src/components/PanelToolbar';
|
||||
|
||||
const StyledFlex = styled(Flex)`
|
||||
@@ -41,7 +41,7 @@ const SqlEditorTopBar = ({
|
||||
<Flex gap="small" align="center">
|
||||
<Flex gap="small" align="center">
|
||||
<PanelToolbar
|
||||
viewId={ViewContribution.Editor}
|
||||
viewId={ViewLocations.sqllab.editor}
|
||||
defaultPrimaryActions={defaultPrimaryActions}
|
||||
defaultSecondaryActions={defaultSecondaryActions}
|
||||
/>
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Flex } from '@superset-ui/core/components';
|
||||
import ViewListExtension from 'src/components/ViewListExtension';
|
||||
import ExtensionsManager from 'src/extensions/ExtensionsManager';
|
||||
import { SQL_EDITOR_STATUSBAR_HEIGHT } from 'src/SqlLab/constants';
|
||||
import { ViewContribution } from 'src/SqlLab/contributions';
|
||||
import { ViewLocations } from 'src/SqlLab/contributions';
|
||||
|
||||
const Container = styled(Flex)`
|
||||
flex-direction: row-reverse;
|
||||
@@ -40,14 +40,14 @@ const Container = styled(Flex)`
|
||||
const StatusBar = () => {
|
||||
const statusBarContributions =
|
||||
ExtensionsManager.getInstance().getViewContributions(
|
||||
ViewContribution.StatusBar,
|
||||
ViewLocations.sqllab.statusBar,
|
||||
) || [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{statusBarContributions.length > 0 && (
|
||||
<Container align="center" justify="space-between">
|
||||
<ViewListExtension viewId={ViewContribution.StatusBar} />
|
||||
<ViewListExtension viewId={ViewLocations.sqllab.statusBar} />
|
||||
</Container>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -40,7 +40,7 @@ import type { SqlLabRootState } from 'src/SqlLab/types';
|
||||
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
|
||||
import { addTable } from 'src/SqlLab/actions/sqlLab';
|
||||
import PanelToolbar from 'src/components/PanelToolbar';
|
||||
import { ViewContribution } from 'src/SqlLab/contributions';
|
||||
import { ViewLocations } from 'src/SqlLab/contributions';
|
||||
import TreeNodeRenderer from './TreeNodeRenderer';
|
||||
import useTreeData, { EMPTY_NODE_ID_PREFIX } from './useTreeData';
|
||||
import type { TreeNodeData } from './types';
|
||||
@@ -244,7 +244,7 @@ const TableExploreTree: React.FC<Props> = ({ queryEditorId }) => {
|
||||
`}
|
||||
>
|
||||
<PanelToolbar
|
||||
viewId={ViewContribution.LeftSidebar}
|
||||
viewId={ViewLocations.sqllab.leftSidebar}
|
||||
defaultPrimaryActions={
|
||||
<>
|
||||
<Button
|
||||
|
||||
@@ -16,12 +16,35 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export enum ViewContribution {
|
||||
LeftSidebar = 'sqllab.leftSidebar',
|
||||
RightSidebar = 'sqllab.rightSidebar',
|
||||
Panels = 'sqllab.panels',
|
||||
Editor = 'sqllab.editor',
|
||||
StatusBar = 'sqllab.statusBar',
|
||||
Results = 'sqllab.results',
|
||||
QueryHistory = 'sqllab.queryHistory',
|
||||
}
|
||||
/**
|
||||
* View locations for SQL Lab extension integration.
|
||||
*
|
||||
* These define the locations where extensions can contribute views and menus.
|
||||
* The nested structure mirrors the extension.json contribution schema.
|
||||
*
|
||||
* @example
|
||||
* // In extension.json:
|
||||
* {
|
||||
* "contributions": {
|
||||
* "views": {
|
||||
* "sqllab": {
|
||||
* "panels": [{ "id": "my-ext.panel", "name": "My Panel" }]
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // In component code:
|
||||
* <ViewListExtension viewId={ViewLocations.sqllab.panels} />
|
||||
*/
|
||||
export const ViewLocations = {
|
||||
sqllab: {
|
||||
leftSidebar: 'sqllab.leftSidebar',
|
||||
rightSidebar: 'sqllab.rightSidebar',
|
||||
panels: 'sqllab.panels',
|
||||
editor: 'sqllab.editor',
|
||||
statusBar: 'sqllab.statusBar',
|
||||
results: 'sqllab.results',
|
||||
queryHistory: 'sqllab.queryHistory',
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -36,7 +36,7 @@ function createMockView(
|
||||
|
||||
function createMockExtension(
|
||||
options: Partial<core.Extension> & {
|
||||
views?: Record<string, contributions.ViewContribution[]>;
|
||||
views?: Record<string, Record<string, contributions.ViewContribution[]>>;
|
||||
} = {},
|
||||
): core.Extension {
|
||||
const {
|
||||
@@ -113,7 +113,9 @@ test('renders placeholder for unregistered view provider', async () => {
|
||||
|
||||
await createActivatedExtension(manager, {
|
||||
views: {
|
||||
[TEST_VIEW_ID]: [createMockView('test-view-1')],
|
||||
test: {
|
||||
view: [createMockView('test-view-1')],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -127,10 +129,9 @@ test('renders multiple view placeholders for multiple contributions', async () =
|
||||
|
||||
await createActivatedExtension(manager, {
|
||||
views: {
|
||||
[TEST_VIEW_ID]: [
|
||||
createMockView('test-view-1'),
|
||||
createMockView('test-view-2'),
|
||||
],
|
||||
test: {
|
||||
view: [createMockView('test-view-1'), createMockView('test-view-2')],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -154,14 +155,18 @@ test('handles multiple extensions with views for same viewId', async () => {
|
||||
await createActivatedExtension(manager, {
|
||||
id: 'extension-1',
|
||||
views: {
|
||||
[TEST_VIEW_ID]: [createMockView('ext1-view')],
|
||||
test: {
|
||||
view: [createMockView('ext1-view')],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await createActivatedExtension(manager, {
|
||||
id: 'extension-2',
|
||||
views: {
|
||||
[TEST_VIEW_ID]: [createMockView('ext2-view')],
|
||||
test: {
|
||||
view: [createMockView('ext2-view')],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -178,8 +183,10 @@ test('renders views for different viewIds independently', async () => {
|
||||
|
||||
await createActivatedExtension(manager, {
|
||||
views: {
|
||||
[VIEW_ID_A]: [createMockView('view-a-component')],
|
||||
[VIEW_ID_B]: [createMockView('view-b-component')],
|
||||
view: {
|
||||
a: [createMockView('view-a-component')],
|
||||
b: [createMockView('view-b-component')],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@ interface MockExtensionOptions {
|
||||
exposedModules?: string[];
|
||||
extensionDependencies?: string[];
|
||||
commands?: contributions.CommandContribution[];
|
||||
menus?: Record<string, contributions.MenuContribution>;
|
||||
views?: Record<string, contributions.ViewContribution[]>;
|
||||
menus?: Record<string, Record<string, contributions.MenuContribution>>;
|
||||
views?: Record<string, Record<string, contributions.ViewContribution[]>>;
|
||||
includeMockFunctions?: boolean;
|
||||
}
|
||||
|
||||
@@ -360,14 +360,14 @@ test('initializeExtension handles extension without remoteEntry', async () => {
|
||||
|
||||
test('getMenuContributions returns undefined initially', () => {
|
||||
const manager = ExtensionsManager.getInstance();
|
||||
const menuContributions = manager.getMenuContributions('nonexistent');
|
||||
const menuContributions = manager.getMenuContributions('non.existent');
|
||||
|
||||
expect(menuContributions).toBeUndefined();
|
||||
});
|
||||
|
||||
test('getViewContributions returns undefined initially', () => {
|
||||
const manager = ExtensionsManager.getInstance();
|
||||
const viewContributions = manager.getViewContributions('nonexistent');
|
||||
const viewContributions = manager.getViewContributions('non.existent');
|
||||
|
||||
expect(viewContributions).toBeUndefined();
|
||||
});
|
||||
@@ -505,16 +505,20 @@ test('handles contributions with menu items', async () => {
|
||||
createMockCommand('ext1.command2'),
|
||||
],
|
||||
menus: {
|
||||
testMenu: createMockMenu({
|
||||
primary: [
|
||||
createMockMenuItem('test-view', 'ext1.command1'),
|
||||
createMockMenuItem('test-view2', 'ext1.command2'),
|
||||
],
|
||||
secondary: [createMockMenuItem('test-view3', 'ext1.command1')],
|
||||
}),
|
||||
test: {
|
||||
menu: createMockMenu({
|
||||
primary: [
|
||||
createMockMenuItem('test-view', 'ext1.command1'),
|
||||
createMockMenuItem('test-view2', 'ext1.command2'),
|
||||
],
|
||||
secondary: [createMockMenuItem('test-view3', 'ext1.command1')],
|
||||
}),
|
||||
},
|
||||
},
|
||||
views: {
|
||||
testView: [createMockView('test-view-1'), createMockView('test-view-2')],
|
||||
test: {
|
||||
view: [createMockView('test-view-1'), createMockView('test-view-2')],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -525,13 +529,13 @@ test('handles contributions with menu items', async () => {
|
||||
expect(commands.find(cmd => cmd.command === 'ext1.command2')).toBeDefined();
|
||||
|
||||
// Test menu contributions
|
||||
const menuContributions = manager.getMenuContributions('testMenu');
|
||||
const menuContributions = manager.getMenuContributions('test.menu');
|
||||
expect(menuContributions).toBeDefined();
|
||||
expect(menuContributions?.primary).toHaveLength(2);
|
||||
expect(menuContributions?.secondary).toHaveLength(1);
|
||||
|
||||
// Test view contributions
|
||||
const viewContributions = manager.getViewContributions('testView');
|
||||
const viewContributions = manager.getViewContributions('test.view');
|
||||
expect(viewContributions).toBeDefined();
|
||||
expect(viewContributions).toHaveLength(2);
|
||||
});
|
||||
@@ -539,8 +543,8 @@ test('handles contributions with menu items', async () => {
|
||||
test('handles non-existent menu and view contributions', () => {
|
||||
const manager = ExtensionsManager.getInstance();
|
||||
|
||||
expect(manager.getMenuContributions('nonexistent')).toBeUndefined();
|
||||
expect(manager.getViewContributions('nonexistent')).toBeUndefined();
|
||||
expect(manager.getMenuContributions('non.existent')).toBeUndefined();
|
||||
expect(manager.getViewContributions('non.existent')).toBeUndefined();
|
||||
expect(manager.getCommandContribution('nonexistent.command')).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@ import type { contributions, core } from '@apache-superset/core';
|
||||
import { ExtensionContext } from '../core/models';
|
||||
|
||||
type MenuContribution = contributions.MenuContribution;
|
||||
type MenuContributions = contributions.MenuContributions;
|
||||
type ViewContribution = contributions.ViewContribution;
|
||||
type ViewContributions = contributions.ViewContributions;
|
||||
type CommandContribution = contributions.CommandContribution;
|
||||
type EditorContribution = contributions.EditorContribution;
|
||||
type Extension = core.Extension;
|
||||
@@ -37,8 +39,8 @@ class ExtensionsManager {
|
||||
private extensionContributions: Map<
|
||||
string,
|
||||
{
|
||||
menus?: Record<string, MenuContribution>;
|
||||
views?: Record<string, ViewContribution[]>;
|
||||
menus?: MenuContributions;
|
||||
views?: ViewContributions;
|
||||
commands?: CommandContribution[];
|
||||
editors?: EditorContribution[];
|
||||
}
|
||||
@@ -232,18 +234,24 @@ class ExtensionsManager {
|
||||
|
||||
/**
|
||||
* Retrieves menu contributions for a specific key.
|
||||
* @param key The key of the menu contributions.
|
||||
* @param key The key of the menu contributions in format "scope.location" (e.g., "sqllab.editor").
|
||||
* @returns The menu contributions matching the key, or undefined if not found.
|
||||
*/
|
||||
public getMenuContributions(key: string): MenuContribution | undefined {
|
||||
const [scope, location] = key.split('.');
|
||||
if (!scope || !location) {
|
||||
return undefined;
|
||||
}
|
||||
const merged: MenuContribution = {
|
||||
context: [],
|
||||
primary: [],
|
||||
secondary: [],
|
||||
};
|
||||
for (const ext of this.extensionContributions.values()) {
|
||||
if (ext.menus && ext.menus[key]) {
|
||||
const menu = ext.menus[key];
|
||||
const scopeMenus = ext.menus?.[scope as keyof MenuContributions];
|
||||
const menu =
|
||||
scopeMenus?.[location as keyof NonNullable<typeof scopeMenus>];
|
||||
if (menu) {
|
||||
if (menu.context) merged.context!.push(...menu.context);
|
||||
if (menu.primary) merged.primary!.push(...menu.primary);
|
||||
if (menu.secondary) merged.secondary!.push(...menu.secondary);
|
||||
@@ -261,14 +269,21 @@ class ExtensionsManager {
|
||||
|
||||
/**
|
||||
* Retrieves view contributions for a specific key.
|
||||
* @param key The key of the view contributions.
|
||||
* @param key The key of the view contributions in format "scope.location" (e.g., "sqllab.panels").
|
||||
* @returns An array of view contributions matching the key, or undefined if not found.
|
||||
*/
|
||||
public getViewContributions(key: string): ViewContribution[] | undefined {
|
||||
const [scope, location] = key.split('.');
|
||||
if (!scope || !location) {
|
||||
return undefined;
|
||||
}
|
||||
let result: ViewContribution[] = [];
|
||||
for (const ext of this.extensionContributions.values()) {
|
||||
if (ext.views && ext.views[key]) {
|
||||
result = result.concat(ext.views[key]);
|
||||
const scopeViews = ext.views?.[scope as keyof ViewContributions];
|
||||
const views =
|
||||
scopeViews?.[location as keyof NonNullable<typeof scopeViews>];
|
||||
if (views) {
|
||||
result = result.concat(views);
|
||||
}
|
||||
}
|
||||
return result.length > 0 ? result : undefined;
|
||||
|
||||
Reference in New Issue
Block a user