add support for dialog api

Signed-off-by: Mark Dunphy <markd@spotify.com>
This commit is contained in:
Mark Dunphy
2025-03-11 11:14:22 -04:00
parent 3e40a77aeb
commit d9895fbd62
2 changed files with 106 additions and 1 deletions
@@ -0,0 +1,76 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed 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.
*/
import React from 'react';
import { EntityContextMenuItemBlueprint } from './EntityContextMenuItemBlueprint';
import { createRouteRef, useRouteRef } from '@backstage/frontend-plugin-api';
describe('EntityContextMenuItemBlueprint', () => {
const routeRef = createRouteRef();
const data = [
{
loader: async () => <li>Test!</li>,
},
{
title: 'Test',
href: '/somewhere',
icon: <span>Test</span>,
},
{
title: 'Test',
useHref() {
const r = useRouteRef(routeRef) ?? (() => '/somewhere');
return r();
},
icon: <span>Test</span>,
},
{
title: 'TestDialog',
dialogLoader: async () => () => <div>test dialog</div>,
icon: <span>Test</span>,
},
];
it.each(data)('should return an extension with sane defaults', params => {
const extension = EntityContextMenuItemBlueprint.make({
name: 'test',
params,
});
expect(extension).toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": {
"id": "page:catalog/entity",
"input": "extraContextMenuItems",
},
"configSchema": undefined,
"disabled": false,
"factory": [Function],
"inputs": {},
"kind": "entity-context-menu-item",
"name": "test",
"output": [
[Function],
],
"override": [Function],
"toString": [Function],
"version": "v2",
}
`);
});
});
@@ -16,9 +16,11 @@
import React from 'react';
import {
DialogApiDialog,
ExtensionBoundary,
coreExtensionData,
createExtensionBlueprint,
dialogApiRef,
} from '@backstage/frontend-plugin-api';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
@@ -42,17 +44,44 @@ export type FactoryHrefParams =
href: string;
};
/** @alpha */
export type FactoryDialogParams = {
dialogLoader: () => Promise<
({ dialog }: { dialog: DialogApiDialog }) => JSX.Element
>;
title: string;
icon: JSX.Element;
};
export type EntityContextMenuItemParams =
| FactoryLoaderParams
| FactoryHrefParams
| FactoryDialogParams;
/** @alpha */
export const EntityContextMenuItemBlueprint = createExtensionBlueprint({
kind: 'entity-context-menu-item',
attachTo: { id: 'page:catalog/entity', input: 'extraContextMenuItems' },
output: [coreExtensionData.reactElement],
*factory(params: FactoryLoaderParams | FactoryHrefParams, { node }) {
*factory(params: EntityContextMenuItemParams, { node, apis }) {
const loaderFactory = () => {
if ('loader' in params) {
return params.loader;
}
if ('dialogLoader' in params) {
const dialogApi = apis.get(dialogApiRef);
return async () => {
const Dialog = await params.dialogLoader();
return (
<MenuItem onClick={() => dialogApi?.show(Dialog)}>
<ListItemIcon>{params.icon}</ListItemIcon>
<ListItemText primary={params.title} />
</MenuItem>
);
};
}
const useHref = 'useHref' in params ? params.useHref : () => params.href;
return async () => {