diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx
new file mode 100644
index 0000000000..466256f25b
--- /dev/null
+++ b/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx
@@ -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 () =>
Test!,
+ },
+ {
+ title: 'Test',
+ href: '/somewhere',
+ icon: Test,
+ },
+ {
+ title: 'Test',
+ useHref() {
+ const r = useRouteRef(routeRef) ?? (() => '/somewhere');
+ return r();
+ },
+ icon: Test,
+ },
+ {
+ title: 'TestDialog',
+ dialogLoader: async () => () => test dialog
,
+ icon: Test,
+ },
+ ];
+
+ 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",
+ }
+ `);
+ });
+});
diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.tsx
index f2c7657927..bfb3eb17c7 100644
--- a/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.tsx
+++ b/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.tsx
@@ -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 (
+
+ );
+ };
+ }
+
const useHref = 'useHref' in params ? params.useHref : () => params.href;
return async () => {