feat: support filters in EntityHeader blueprint
Signed-off-by: benjdlambert <ben@blam.sh> Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
@@ -19,22 +19,46 @@ import {
|
||||
coreExtensionData,
|
||||
ExtensionBoundary,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { EntityPredicate } from '../predicates/types';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { resolveEntityFilterData } from './resolveEntityFilterData';
|
||||
import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema';
|
||||
import {
|
||||
entityFilterExpressionDataRef,
|
||||
entityFilterFunctionDataRef,
|
||||
} from './extensionData';
|
||||
|
||||
/** @alpha */
|
||||
export const EntityHeaderBlueprint = createExtensionBlueprint({
|
||||
kind: 'entity-header',
|
||||
attachTo: { id: 'page:catalog/entity', input: 'header' },
|
||||
attachTo: { id: 'page:catalog/entity', input: 'headers' },
|
||||
dataRefs: {
|
||||
filterFunction: entityFilterFunctionDataRef,
|
||||
filterExpression: entityFilterExpressionDataRef,
|
||||
element: coreExtensionData.reactElement,
|
||||
},
|
||||
output: [coreExtensionData.reactElement.optional()],
|
||||
config: {
|
||||
schema: {
|
||||
filter: z =>
|
||||
z.union([z.string(), createEntityPredicateSchema(z)]).optional(),
|
||||
},
|
||||
},
|
||||
output: [
|
||||
entityFilterFunctionDataRef.optional(),
|
||||
entityFilterExpressionDataRef.optional(),
|
||||
coreExtensionData.reactElement.optional(),
|
||||
],
|
||||
*factory(
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
},
|
||||
{ node },
|
||||
{ node, config },
|
||||
) {
|
||||
const { loader } = params;
|
||||
const { loader, filter } = params;
|
||||
|
||||
yield* resolveEntityFilterData(filter, config, node);
|
||||
|
||||
if (loader) {
|
||||
yield coreExtensionData.reactElement(
|
||||
ExtensionBoundary.lazy(node, loader),
|
||||
|
||||
@@ -102,13 +102,21 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
UNSTABLE_contextMenuOptions,
|
||||
contextMenuItems,
|
||||
children,
|
||||
header,
|
||||
NotFoundComponent,
|
||||
parentEntityRelations,
|
||||
} = props;
|
||||
const { kind } = useRouteRefParams(entityRouteRef);
|
||||
const { entity, loading, error } = useAsyncEntity();
|
||||
|
||||
const header = props.header ?? (
|
||||
<EntityHeader
|
||||
parentEntityRelations={parentEntityRelations}
|
||||
UNSTABLE_contextMenuOptions={UNSTABLE_contextMenuOptions}
|
||||
UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}
|
||||
contextMenuItems={contextMenuItems}
|
||||
/>
|
||||
);
|
||||
|
||||
const routes = useElementFilter(
|
||||
children,
|
||||
elements =>
|
||||
@@ -142,14 +150,7 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
|
||||
return (
|
||||
<Page themeId={entity?.spec?.type?.toString() ?? 'home'}>
|
||||
{header ?? (
|
||||
<EntityHeader
|
||||
parentEntityRelations={parentEntityRelations}
|
||||
UNSTABLE_contextMenuOptions={UNSTABLE_contextMenuOptions}
|
||||
UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}
|
||||
contextMenuItems={contextMenuItems}
|
||||
/>
|
||||
)}
|
||||
{!loading && header}
|
||||
|
||||
{loading && <Progress />}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
import { rootRouteRef } from '../routes';
|
||||
import { useEntityFromUrl } from '../components/CatalogEntityPage/useEntityFromUrl';
|
||||
import { buildFilterFn } from './filter/FilterWrapper';
|
||||
import { Progress } from '@backstage/core-components';
|
||||
|
||||
export const catalogPage = PageBlueprint.makeWithOverrides({
|
||||
inputs: {
|
||||
@@ -79,10 +80,10 @@ export const catalogPage = PageBlueprint.makeWithOverrides({
|
||||
export const catalogEntityPage = PageBlueprint.makeWithOverrides({
|
||||
name: 'entity',
|
||||
inputs: {
|
||||
header: createExtensionInput(
|
||||
[EntityHeaderBlueprint.dataRefs.element.optional()],
|
||||
{ singleton: true, optional: true },
|
||||
),
|
||||
headers: createExtensionInput([
|
||||
EntityHeaderBlueprint.dataRefs.element.optional(),
|
||||
EntityHeaderBlueprint.dataRefs.filterFunction.optional(),
|
||||
]),
|
||||
contents: createExtensionInput([
|
||||
coreExtensionData.reactElement,
|
||||
coreExtensionData.routePath,
|
||||
@@ -124,9 +125,19 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({
|
||||
{ title: string; items: Array<(typeof inputs.contents)[0]> }
|
||||
>;
|
||||
|
||||
const header = inputs.header?.get(
|
||||
EntityHeaderBlueprint.dataRefs.element,
|
||||
);
|
||||
// Get available headers, sorted by if they have a filter function or not.
|
||||
// TODO(blam): we should really have priority or some specificity here which can be used to sort the headers.
|
||||
// That can be done with embedding the priority in the dataRef alongside the filter function.
|
||||
const headers = inputs.headers
|
||||
.map(header => ({
|
||||
element: header.get(EntityHeaderBlueprint.dataRefs.element),
|
||||
filter: header.get(EntityHeaderBlueprint.dataRefs.filterFunction),
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
if (a.filter && !b.filter) return -1;
|
||||
if (!a.filter && b.filter) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
let groups = Object.entries(defaultEntityContentGroups).reduce<Groups>(
|
||||
(rest, group) => {
|
||||
@@ -169,6 +180,10 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({
|
||||
? menuItems.filter(i => i.filter(entity)).map(i => i.element)
|
||||
: [];
|
||||
|
||||
const header = headers.find(
|
||||
h => !h.filter || h.filter(entity!),
|
||||
)?.element;
|
||||
|
||||
return (
|
||||
<AsyncEntityProvider {...entityFromUrl}>
|
||||
<EntityLayout
|
||||
|
||||
Reference in New Issue
Block a user