plugin-catalog-graph: nfs page variant and plugin title/icon

- Add title, icon, and NfsCatalogGraphPage to the new frontend plugin alpha entry
- Refactor catalog graph page with headerVariant for legacy vs Backstage UI header
- Export NfsCatalogGraphPage from the CatalogGraphPage module

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-25 16:50:57 +01:00
parent 36d205a455
commit 0e147e8e45
4 changed files with 150 additions and 102 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-graph': patch
---
The new frontend plugin entry now exposes a display title and icon for navigation, and the catalog graph page uses the Backstage UI header when rendered in the new frontend system. A new `NfsCatalogGraphPage` export is available for the same layout without the legacy `Page` shell.
+4 -1
View File
@@ -19,6 +19,7 @@ import {
createFrontendPlugin,
PageBlueprint,
} from '@backstage/frontend-plugin-api';
import { RiMindMap } from '@remixicon/react';
import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha';
import { catalogGraphRouteRef, catalogEntityRouteRef } from './routes';
import {
@@ -80,7 +81,7 @@ const CatalogGraphPage = PageBlueprint.makeWithOverrides({
routeRef: catalogGraphRouteRef,
loader: () =>
import('./components/CatalogGraphPage').then(m => (
<m.CatalogGraphPage {...config} />
<m.NfsCatalogGraphPage {...config} />
)),
});
},
@@ -97,6 +98,8 @@ const CatalogGraphApi = ApiBlueprint.make({
export default createFrontendPlugin({
pluginId: 'catalog-graph',
title: 'Catalog Graph',
icon: <RiMindMap />,
info: { packageJson: () => import('../package.json') },
routes: {
catalogGraph: catalogGraphRouteRef,
@@ -27,6 +27,7 @@ import {
entityRouteRef,
humanizeEntityRef,
} from '@backstage/plugin-catalog-react';
import { Header as BuiHeader } from '@backstage/ui';
import Grid from '@material-ui/core/Grid';
import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography';
@@ -116,22 +117,27 @@ const useStyles = makeStyles(
{ name: 'PluginCatalogGraphCatalogGraphPage' },
);
export const CatalogGraphPage = (
props: {
initialState?: {
selectedRelations?: string[];
selectedKinds?: string[];
rootEntityRefs?: string[];
maxDepth?: number;
unidirectional?: boolean;
mergeRelations?: boolean;
direction?: Direction;
showFilters?: boolean;
curve?: 'curveStepBefore' | 'curveMonotoneX';
};
} & Partial<EntityRelationsGraphProps>,
) => {
const { relationPairs, initialState, entityFilter } = props;
type CatalogGraphPageProps = {
initialState?: {
selectedRelations?: string[];
selectedKinds?: string[];
rootEntityRefs?: string[];
maxDepth?: number;
unidirectional?: boolean;
mergeRelations?: boolean;
direction?: Direction;
showFilters?: boolean;
curve?: 'curveStepBefore' | 'curveMonotoneX';
};
} & Partial<EntityRelationsGraphProps>;
type CatalogGraphPageContentProps = CatalogGraphPageProps & {
headerVariant: 'legacy' | 'bui';
};
function CatalogGraphPageContent(props: CatalogGraphPageContentProps) {
const { headerVariant, ...graphPageProps } = props;
const { relationPairs, initialState, entityFilter } = graphPageProps;
const { t } = useTranslationRef(catalogGraphTranslationRef);
const navigate = useNavigate();
const classes = useStyles();
@@ -185,93 +191,127 @@ export const CatalogGraphPage = (
[catalogEntityRoute, navigate, setRootEntityNames, analytics],
);
const subtitle = rootEntityNames.map(e => humanizeEntityRef(e)).join(', ');
const filterToggle = (
<ToggleButton
value="show filters"
selected={showFilters}
onChange={() => toggleShowFilters()}
>
<FilterListIcon /> {t('catalogGraphPage.filterToggleButtonTitle')}
</ToggleButton>
);
const supportButton = (
<SupportButton>
{t('catalogGraphPage.supportButtonDescription')}
</SupportButton>
);
const graphBody = (
<Grid container alignItems="stretch" className={classes.container}>
{showFilters && (
<Grid item xs={12} lg={2} className={classes.filters}>
<MaxDepthFilter value={maxDepth} onChange={setMaxDepth} />
<SelectedKindsFilter
value={selectedKinds}
onChange={setSelectedKinds}
/>
<SelectedRelationsFilter
value={selectedRelations}
onChange={setSelectedRelations}
/>
<DirectionFilter value={direction} onChange={setDirection} />
<CurveFilter value={curve} onChange={setCurve} />
<SwitchFilter
value={unidirectional}
onChange={setUnidirectional}
label={t('catalogGraphPage.simplifiedSwitchLabel')}
/>
<SwitchFilter
value={mergeRelations}
onChange={setMergeRelations}
label={t('catalogGraphPage.mergeRelationsSwitchLabel')}
/>
</Grid>
)}
<Grid item xs className={classes.fullHeight}>
<Paper className={classes.graphWrapper}>
<Typography
variant="caption"
color="textSecondary"
display="block"
className={classes.legend}
>
<ZoomOutMap className="icon" />{' '}
{t('catalogGraphPage.zoomOutDescription')}
</Typography>
<EntityRelationsGraph
{...graphPageProps}
rootEntityNames={rootEntityNames}
maxDepth={maxDepth}
kinds={
selectedKinds && selectedKinds.length > 0
? selectedKinds
: undefined
}
relations={
selectedRelations && selectedRelations.length > 0
? selectedRelations
: undefined
}
mergeRelations={mergeRelations}
unidirectional={unidirectional}
onNodeClick={onNodeClick}
direction={direction}
relationPairs={relationPairs}
entityFilter={entityFilter}
className={classes.graph}
zoom="enabled"
curve={curve}
/>
</Paper>
</Grid>
</Grid>
);
if (headerVariant === 'legacy') {
return (
<Page themeId="home">
<Header title={t('catalogGraphPage.title')} subtitle={subtitle} />
<Content stretch className={classes.content}>
<ContentHeader titleComponent={filterToggle}>
{supportButton}
</ContentHeader>
{graphBody}
</Content>
</Page>
);
}
return (
<Page themeId="home">
<Header
title={t('catalogGraphPage.title')}
subtitle={rootEntityNames.map(e => humanizeEntityRef(e)).join(', ')}
<>
<BuiHeader
title={subtitle}
customActions={
<>
{filterToggle}
{supportButton}
</>
}
/>
<Content stretch className={classes.content}>
<ContentHeader
titleComponent={
<ToggleButton
value="show filters"
selected={showFilters}
onChange={() => toggleShowFilters()}
>
<FilterListIcon /> {t('catalogGraphPage.filterToggleButtonTitle')}
</ToggleButton>
}
>
<SupportButton>
{t('catalogGraphPage.supportButtonDescription')}
</SupportButton>
</ContentHeader>
<Grid container alignItems="stretch" className={classes.container}>
{showFilters && (
<Grid item xs={12} lg={2} className={classes.filters}>
<MaxDepthFilter value={maxDepth} onChange={setMaxDepth} />
<SelectedKindsFilter
value={selectedKinds}
onChange={setSelectedKinds}
/>
<SelectedRelationsFilter
value={selectedRelations}
onChange={setSelectedRelations}
/>
<DirectionFilter value={direction} onChange={setDirection} />
<CurveFilter value={curve} onChange={setCurve} />
<SwitchFilter
value={unidirectional}
onChange={setUnidirectional}
label={t('catalogGraphPage.simplifiedSwitchLabel')}
/>
<SwitchFilter
value={mergeRelations}
onChange={setMergeRelations}
label={t('catalogGraphPage.mergeRelationsSwitchLabel')}
/>
</Grid>
)}
<Grid item xs className={classes.fullHeight}>
<Paper className={classes.graphWrapper}>
<Typography
variant="caption"
color="textSecondary"
display="block"
className={classes.legend}
>
<ZoomOutMap className="icon" />{' '}
{t('catalogGraphPage.zoomOutDescription')}
</Typography>
<EntityRelationsGraph
{...props}
rootEntityNames={rootEntityNames}
maxDepth={maxDepth}
kinds={
selectedKinds && selectedKinds.length > 0
? selectedKinds
: undefined
}
relations={
selectedRelations && selectedRelations.length > 0
? selectedRelations
: undefined
}
mergeRelations={mergeRelations}
unidirectional={unidirectional}
onNodeClick={onNodeClick}
direction={direction}
relationPairs={relationPairs}
entityFilter={entityFilter}
className={classes.graph}
zoom="enabled"
curve={curve}
/>
</Paper>
</Grid>
</Grid>
{graphBody}
</Content>
</Page>
</>
);
}
export const CatalogGraphPage = (props: CatalogGraphPageProps) => {
return <CatalogGraphPageContent headerVariant="legacy" {...props} />;
};
export const NfsCatalogGraphPage = (props: CatalogGraphPageProps) => {
return <CatalogGraphPageContent headerVariant="bui" {...props} />;
};
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { CatalogGraphPage } from './CatalogGraphPage';
export { CatalogGraphPage, NfsCatalogGraphPage } from './CatalogGraphPage';
export type { CatalogGraphPageClassKey } from './CatalogGraphPage';
export type { MaxDepthFilterClassKey } from './MaxDepthFilter';
export type { SelectedKindsFilterClassKey } from './SelectedKindsFilter';