add EntityContextMenuItemBlueprint
Signed-off-by: Mark Dunphy <markd@spotify.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 {
|
||||
ExtensionBoundary,
|
||||
coreExtensionData,
|
||||
createExtensionBlueprint,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
|
||||
/** @alpha */
|
||||
export type FactoryLoaderParams = {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
export type FactoryHrefParams =
|
||||
| {
|
||||
title: string;
|
||||
icon: JSX.Element;
|
||||
useHref: () => string;
|
||||
}
|
||||
| {
|
||||
title: string;
|
||||
icon: JSX.Element;
|
||||
href: string;
|
||||
};
|
||||
|
||||
/** @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 }) {
|
||||
const loaderFactory = () => {
|
||||
if ('loader' in params) {
|
||||
return params.loader;
|
||||
}
|
||||
|
||||
const useHref = 'useHref' in params ? params.useHref : () => params.href;
|
||||
|
||||
return async () => {
|
||||
const href = useHref();
|
||||
|
||||
return (
|
||||
<MenuItem component="a" href={href}>
|
||||
<ListItemIcon>{params.icon}</ListItemIcon>
|
||||
<ListItemText primary={params.title} />
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
yield coreExtensionData.reactElement(
|
||||
ExtensionBoundary.lazy(node, loaderFactory()),
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -23,3 +23,8 @@ export {
|
||||
export { EntityHeaderBlueprint } from './EntityHeaderBlueprint';
|
||||
export { defaultEntityContentGroups } from './extensionData';
|
||||
export type { EntityCardType } from './extensionData';
|
||||
export {
|
||||
EntityContextMenuItemBlueprint,
|
||||
type FactoryHrefParams,
|
||||
type FactoryLoaderParams,
|
||||
} from './EntityContextMenuItemBlueprint';
|
||||
|
||||
@@ -178,6 +178,7 @@ export function EntityHeader(props: {
|
||||
UNSTABLE_contextMenuOptions?: {
|
||||
disableUnregister: boolean | 'visible' | 'hidden' | 'disable';
|
||||
};
|
||||
extraMenuItems?: JSX.Element[];
|
||||
/**
|
||||
* An array of relation types used to determine the parent entities in the hierarchy.
|
||||
* These relations are prioritized in the order provided, allowing for flexible
|
||||
@@ -195,6 +196,7 @@ export function EntityHeader(props: {
|
||||
const {
|
||||
UNSTABLE_extraContextMenuItems,
|
||||
UNSTABLE_contextMenuOptions,
|
||||
extraMenuItems,
|
||||
parentEntityRelations,
|
||||
title,
|
||||
subtitle,
|
||||
@@ -281,6 +283,7 @@ export function EntityHeader(props: {
|
||||
<EntityContextMenu
|
||||
UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}
|
||||
UNSTABLE_contextMenuOptions={UNSTABLE_contextMenuOptions}
|
||||
extraMenuItems={extraMenuItems}
|
||||
onInspectEntity={openInspectEntityDialog}
|
||||
onUnregisterEntity={openUnregisterEntityDialog}
|
||||
/>
|
||||
|
||||
@@ -62,6 +62,7 @@ export interface EntityLayoutProps {
|
||||
UNSTABLE_extraContextMenuItems?: ComponentProps<
|
||||
typeof EntityHeader
|
||||
>['UNSTABLE_extraContextMenuItems'];
|
||||
extraMenuItems?: ComponentProps<typeof EntityHeader>['extraMenuItems'];
|
||||
children?: ReactNode;
|
||||
header?: JSX.Element;
|
||||
NotFoundComponent?: ReactNode;
|
||||
@@ -99,6 +100,7 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
const {
|
||||
UNSTABLE_extraContextMenuItems,
|
||||
UNSTABLE_contextMenuOptions,
|
||||
extraMenuItems,
|
||||
children,
|
||||
header,
|
||||
NotFoundComponent,
|
||||
@@ -145,6 +147,7 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
parentEntityRelations={parentEntityRelations}
|
||||
UNSTABLE_contextMenuOptions={UNSTABLE_contextMenuOptions}
|
||||
UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}
|
||||
extraMenuItems={extraMenuItems}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -72,6 +72,9 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({
|
||||
EntityContentBlueprint.dataRefs.filterExpression.optional(),
|
||||
EntityContentBlueprint.dataRefs.group.optional(),
|
||||
]),
|
||||
extraContextMenuItems: createExtensionInput([
|
||||
coreExtensionData.reactElement,
|
||||
]),
|
||||
},
|
||||
config: {
|
||||
schema: {
|
||||
@@ -88,6 +91,10 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({
|
||||
loader: async () => {
|
||||
const { EntityLayout } = await import('./components/EntityLayout');
|
||||
|
||||
const extraMenuItems = inputs.extraContextMenuItems.map(item =>
|
||||
item.get(coreExtensionData.reactElement),
|
||||
);
|
||||
|
||||
type Groups = Record<
|
||||
string,
|
||||
{ title: string; items: Array<(typeof inputs.contents)[0]> }
|
||||
@@ -95,7 +102,7 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({
|
||||
|
||||
const header = inputs.header?.get(
|
||||
EntityHeaderBlueprint.dataRefs.element,
|
||||
) ?? <EntityHeader />;
|
||||
) ?? <EntityHeader extraMenuItems={extraMenuItems} />;
|
||||
|
||||
let groups = Object.entries(defaultEntityContentGroups).reduce<Groups>(
|
||||
(rest, group) => {
|
||||
@@ -134,7 +141,7 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({
|
||||
const Component = () => {
|
||||
return (
|
||||
<AsyncEntityProvider {...useEntityFromUrl()}>
|
||||
<EntityLayout header={header}>
|
||||
<EntityLayout header={header} extraMenuItems={extraMenuItems}>
|
||||
{Object.values(groups).flatMap(({ title, items }) =>
|
||||
items.map(output => (
|
||||
<EntityLayout.Route
|
||||
|
||||
@@ -61,6 +61,7 @@ interface ExtraContextMenuItem {
|
||||
interface EntityContextMenuProps {
|
||||
UNSTABLE_extraContextMenuItems?: ExtraContextMenuItem[];
|
||||
UNSTABLE_contextMenuOptions?: UnregisterEntityOptions;
|
||||
extraMenuItems?: JSX.Element[];
|
||||
onUnregisterEntity: () => void;
|
||||
onInspectEntity: () => void;
|
||||
}
|
||||
@@ -69,6 +70,7 @@ export function EntityContextMenu(props: EntityContextMenuProps) {
|
||||
const {
|
||||
UNSTABLE_extraContextMenuItems,
|
||||
UNSTABLE_contextMenuOptions,
|
||||
extraMenuItems,
|
||||
onUnregisterEntity,
|
||||
onInspectEntity,
|
||||
} = props;
|
||||
@@ -145,6 +147,7 @@ export function EntityContextMenu(props: EntityContextMenuProps) {
|
||||
>
|
||||
<MenuList autoFocusItem={Boolean(anchorEl)}>
|
||||
{extraItems}
|
||||
{extraMenuItems}
|
||||
<UnregisterEntity
|
||||
unregisterEntityOptions={UNSTABLE_contextMenuOptions}
|
||||
isUnregisterAllowed={isAllowed}
|
||||
|
||||
@@ -181,6 +181,7 @@ export interface EntityLayoutProps {
|
||||
UNSTABLE_contextMenuOptions?: EntityContextMenuOptions;
|
||||
children?: ReactNode;
|
||||
NotFoundComponent?: ReactNode;
|
||||
extraMenuItems?: JSX.Element[];
|
||||
/**
|
||||
* An array of relation types used to determine the parent entities in the hierarchy.
|
||||
* These relations are prioritized in the order provided, allowing for flexible
|
||||
@@ -245,6 +246,7 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
const {
|
||||
UNSTABLE_extraContextMenuItems,
|
||||
UNSTABLE_contextMenuOptions,
|
||||
extraMenuItems,
|
||||
children,
|
||||
NotFoundComponent,
|
||||
parentEntityRelations,
|
||||
@@ -360,6 +362,7 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
<EntityContextMenu
|
||||
UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}
|
||||
UNSTABLE_contextMenuOptions={UNSTABLE_contextMenuOptions}
|
||||
extraMenuItems={extraMenuItems}
|
||||
onUnregisterEntity={() => setConfirmationDialogOpen(true)}
|
||||
onInspectEntity={() => setSearchParams('inspect')}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user