feat(home): add new frontend system support

Migrates home plugin to support the new frontend system architecture by
introducing extension blueprints for composable homepage functionality.
Key changes:
- Add CustomHomepageWidgetBlueprint for creating installable homepage widgets
- Add CustomHomepageBlueprint for composing pages from widget extensions
- Introduce titleExtensionDataRef for NFS title handling
This attempts to bring  the home plugin up to par with other core plugins that have migrated
to the new frontend system
Signed-off-by: Adam Kunicki <kunickiaj@gmail.com>
This commit is contained in:
Adam Kunicki
2025-09-22 10:00:11 -07:00
parent 2218061089
commit 90956a61bd
22 changed files with 1085 additions and 60 deletions
+202 -11
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 The Backstage Authors
* 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.
@@ -13,14 +13,205 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createDevApp } from '@backstage/dev-utils';
import { homePlugin, HomepageCompositionRoot } from '../src/plugin';
createDevApp()
.registerPlugin(homePlugin)
.addPage({
element: <HomepageCompositionRoot />,
title: 'Root Page',
path: '/',
})
.render();
import { Content, Header, Page } from '@backstage/core-components';
import { createApp } from '@backstage/frontend-defaults';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
import catalogPlugin from '@backstage/plugin-catalog/alpha';
import HomeIcon from '@material-ui/icons/Home';
import ReactDOM from 'react-dom/client';
import {
ApiBlueprint,
createFrontendModule,
} from '@backstage/frontend-plugin-api';
import { HomepageWidgetBlueprint } from '@backstage/plugin-home-react/alpha';
import { HeaderWorldClock, WelcomeTitle, type ClockConfig } from '../src';
import homePlugin, {
HomepageBlueprint,
type HomepageGridProps,
} from '../src/alpha';
const clockConfigs: ClockConfig[] = [
{ label: 'NYC', timeZone: 'America/New_York' },
{ label: 'UTC', timeZone: 'UTC' },
{ label: 'STO', timeZone: 'Europe/Stockholm' },
{ label: 'TYO', timeZone: 'Asia/Tokyo' },
];
const timeFormat: Intl.DateTimeFormatOptions = {
hour: '2-digit',
minute: '2-digit',
hour12: false,
};
const defaultGridConfig: NonNullable<HomepageGridProps['config']> = [
{
component: 'HomePageToolkit',
x: 0,
y: 0,
width: 12,
height: 4,
movable: false,
resizable: false,
},
{
component: 'HomePageStarredEntities',
x: 0,
y: 4,
width: 6,
height: 5,
},
{
component: 'HomePageRandomJoke',
x: 6,
y: 4,
width: 6,
height: 5,
},
];
const homePage = HomepageBlueprint.make({
params: {
title: 'Home',
grid: {
config: defaultGridConfig,
},
render: ({ grid }) => (
<Page themeId="home">
<Header title={<WelcomeTitle />} pageTitleOverride="Home">
<HeaderWorldClock
clockConfigs={clockConfigs}
customTimeFormat={timeFormat}
/>
</Header>
<Content>{grid}</Content>
</Page>
),
},
});
const homePageToolkitWidget = HomepageWidgetBlueprint.make({
name: 'home-toolkit',
params: {
name: 'HomePageToolkit',
title: 'Toolkit',
components: () =>
import('../src/homePageComponents/Toolkit').then(m => ({
Content: m.Content,
ContextProvider: m.ContextProvider,
})),
componentProps: {
tools: [
{
url: 'https://backstage.io',
label: 'Backstage Homepage',
icon: <HomeIcon />,
},
],
},
},
});
const homePageStarredEntitiesWidget = HomepageWidgetBlueprint.make({
name: 'home-starred-entities',
params: {
name: 'HomePageStarredEntities',
title: 'Your Starred Entities',
components: () =>
import('../src/homePageComponents/StarredEntities').then(m => ({
Content: m.Content,
})),
},
});
const homePageRandomJokeWidget = HomepageWidgetBlueprint.make({
name: 'home-random-joke',
params: {
name: 'HomePageRandomJoke',
title: 'Random Joke',
description: 'Shows a random programming joke',
components: () =>
import('../src/homePageComponents/RandomJoke').then(m => ({
Content: m.Content,
Settings: m.Settings,
Actions: m.Actions,
ContextProvider: m.ContextProvider,
})),
layout: {
height: { minRows: 4 },
width: { minColumns: 3 },
},
settings: {
schema: {
title: 'Random Joke settings',
type: 'object',
properties: {
defaultCategory: {
title: 'Category',
type: 'string',
enum: ['any', 'programming', 'dad'],
default: 'any',
},
},
},
},
},
});
const homeDevModule = createFrontendModule({
pluginId: 'home',
extensions: [
homePage,
homePageToolkitWidget,
homePageStarredEntitiesWidget,
homePageRandomJokeWidget,
],
});
const entities = [
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'example',
annotations: {
'backstage.io/managed-by-location': 'file:/path/to/catalog-info.yaml',
},
},
spec: {
type: 'service',
lifecycle: 'production',
owner: 'guest',
},
},
];
const catalogApi = catalogApiMock({ entities });
const catalogPluginOverrides = createFrontendModule({
pluginId: 'catalog',
extensions: [
ApiBlueprint.make({
params: defineParams =>
defineParams({
api: catalogApiRef,
deps: {},
factory: () => catalogApi,
}),
}),
],
});
const app = createApp({
features: [
catalogPlugin,
catalogPluginOverrides,
homePlugin, // Load the home plugin
homeDevModule, // Load the widgets and homepage content
],
});
const root = app.createRoot();
ReactDOM.createRoot(document.getElementById('root')!).render(root);