diff --git a/workspaces/homepage/app-config.yaml b/workspaces/homepage/app-config.yaml index b5b2195e5b..1d0c2713da 100644 --- a/workspaces/homepage/app-config.yaml +++ b/workspaces/homepage/app-config.yaml @@ -16,70 +16,6 @@ app: - home-page-layout:home/dynamic-homepage-layout: config: customizable: true # false for read-only homepage layout - widgetLayout: - RhdhTemplateSection: - priority: 300 - breakpoints: - xl: - w: 12 - h: 5 - lg: - w: 12 - h: 5 - md: - w: 12 - h: 5 - sm: - w: 12 - h: 5 - xs: - w: 12 - h: 7.5 - xxs: - w: 12 - h: 13.5 - RhdhEntitySection: - priority: 200 - breakpoints: - xl: - w: 12 - h: 7 - lg: - w: 12 - h: 7 - md: - w: 12 - h: 8 - sm: - w: 12 - h: 9 - xs: - w: 12 - h: 11 - xxs: - w: 12 - h: 15 - RhdhOnboardingSection: - priority: 100 - breakpoints: - xl: - w: 12 - h: 6 - lg: - w: 12 - h: 6 - md: - w: 12 - h: 7 - sm: - w: 12 - h: 8 - xs: - w: 12 - h: 9 - xxs: - w: 12 - h: 14 homepage: # The widget id is required if a user changes the default layout. diff --git a/workspaces/homepage/e2e-tests/homepageCustomizable.test.ts b/workspaces/homepage/e2e-tests/homepageCustomizable.test.ts index 2cbe1785b4..9a364fba79 100644 --- a/workspaces/homepage/e2e-tests/homepageCustomizable.test.ts +++ b/workspaces/homepage/e2e-tests/homepageCustomizable.test.ts @@ -137,14 +137,7 @@ test.describe.serial('Dynamic Home Page Customization', () => { test.describe('Persona-Based Homepages', () => { test('Groups filters default widgets by persona', async ({ browser }) => { - // The `if: groups:` condition in `homepage.defaultWidgets` is a legacy-only - // feature — NFS does not implement group-based widget filtering. - test.skip( - process.env.APP_MODE === 'nfs', - '`if: groups:` filtering is not supported in NFS mode', - ); - - const loginUrl = '/customizable'; + const loginUrl = process.env.APP_MODE === 'nfs' ? '/' : '/customizable'; // Guest (port 3000, no groups): sees common defaults only const guestPage = await ( diff --git a/workspaces/homepage/playwright.config.ts b/workspaces/homepage/playwright.config.ts index 231b374e1f..cca1fd8261 100644 --- a/workspaces/homepage/playwright.config.ts +++ b/workspaces/homepage/playwright.config.ts @@ -80,7 +80,7 @@ export default defineConfig({ { name: 'en', use: { - channel: 'chrome' as const, + channel: 'chromium' as const, locale: 'en', }, }, @@ -89,7 +89,7 @@ export default defineConfig({ name: locale, testMatch: '**/homepageCards.test.ts', use: { - channel: 'chrome' as const, + channel: 'chromium' as const, locale, }, })), diff --git a/workspaces/homepage/plugins/homepage/src/alpha/alpha.test.ts b/workspaces/homepage/plugins/homepage/src/alpha/alpha.test.ts index 86465596d2..2614051841 100644 --- a/workspaces/homepage/plugins/homepage/src/alpha/alpha.test.ts +++ b/workspaces/homepage/plugins/homepage/src/alpha/alpha.test.ts @@ -25,11 +25,12 @@ import { searchBarWidget, featuredDocsCardWidget, catalogStarredWidget, + disableStarredEntities, disableToolkit, RecentlyVisitedWidget, TopVisitedWidget, } from './extensions/homePageCards'; -import { quickAccessApi } from './extensions/apis'; +import { quickAccessApi, defaultWidgetsApi } from './extensions/apis'; describe('Dynamic Home Page plugin Alpha (NFS)', () => { describe('Modules', () => { @@ -75,6 +76,7 @@ describe('Dynamic Home Page plugin Alpha (NFS)', () => { expect(searchBarWidget).toBeDefined(); expect(featuredDocsCardWidget).toBeDefined(); expect(catalogStarredWidget).toBeDefined(); + expect(disableStarredEntities).toBeDefined(); expect(disableToolkit).toBeDefined(); expect(RecentlyVisitedWidget).toBeDefined(); expect(TopVisitedWidget).toBeDefined(); @@ -85,5 +87,9 @@ describe('Dynamic Home Page plugin Alpha (NFS)', () => { it('should export quickAccessApi', () => { expect(quickAccessApi).toBeDefined(); }); + + it('should export defaultWidgetsApi', () => { + expect(defaultWidgetsApi).toBeDefined(); + }); }); }); diff --git a/workspaces/homepage/plugins/homepage/src/alpha/components/HomePageLayout.tsx b/workspaces/homepage/plugins/homepage/src/alpha/components/HomePageLayout.tsx index 56c3a2c205..d6030c9f7a 100644 --- a/workspaces/homepage/plugins/homepage/src/alpha/components/HomePageLayout.tsx +++ b/workspaces/homepage/plugins/homepage/src/alpha/components/HomePageLayout.tsx @@ -14,13 +14,22 @@ * limitations under the License. */ -import { Content, EmptyState, Page } from '@backstage/core-components'; +import { useMemo } from 'react'; + +import { + Content, + EmptyState, + Page, + Progress, +} from '@backstage/core-components'; import { useTranslation } from '../../hooks/useTranslation'; +import { useDefaultWidgets } from '../../hooks/useDefaultWidgets'; import { HeaderProps, Header } from '../../components/Header'; import { HomePageStylesProvider } from '../../components/HomePageStylesProvider'; import { ReadOnlyGridLayout } from './ReadOnlyGirdLayout'; import { CustomizableGridLayout } from './CustomizableGridLayout'; import { HomePageCardConfig } from '../../types'; +import type { Breakpoint, Layout } from '../../types'; /** * Props for the NFS home page layout component. @@ -33,15 +42,67 @@ export interface HomePageProps extends HeaderProps { /** * NFS home page layout that renders widgets in a read-only or customizable grid. - * Used by the dynamic-homepage-layout extension. + * Loads persona-based default widgets from the homepage-backend and merges them + * with extension-provided widgets. * * @alpha */ export const HomePageLayout = ({ widgets, customizable }: HomePageProps) => { const { t } = useTranslation(); + const { defaultWidgets, loading } = useDefaultWidgets(); + + const mergedWidgets = useMemo((): HomePageCardConfig[] | undefined => { + if (!defaultWidgets) { + return undefined; + } + + const widgetsByRef = new Map(); + for (const widget of widgets) { + const extensionId = widget.node?.spec?.id ?? ''; + const refName = extensionId.split('/').pop() ?? ''; + if (refName) { + widgetsByRef.set(refName, widget); + } + } + + const result: HomePageCardConfig[] = []; + for (const defaultWidget of defaultWidgets) { + const widget = widgetsByRef.get(defaultWidget.ref); + if (!widget) { + // eslint-disable-next-line no-console + console.warn( + `Homepage default widget has invalid ref "${defaultWidget.ref}". ` + + `No matching NFS widget found. Available widgets: ${[...widgetsByRef.keys()].join(', ')}`, + ); + continue; + } + + const widgetLayout = defaultWidget.layout as + | Record + | undefined; + + result.push({ + ...widget, + breakpointLayouts: widgetLayout as + | Record + | undefined, + }); + } + return result; + }, [defaultWidgets, widgets]); let content: React.ReactNode; - if (widgets.length === 0) { + if (loading) { + content = ; + } else if (mergedWidgets) { + if (mergedWidgets.length === 0) { + content = ; + } else if (customizable) { + content = ; + } else { + content = ; + } + } else if (widgets.length === 0) { content = ; } else if (customizable) { content = ; diff --git a/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx b/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx index 51690f5a21..d56a3389d6 100644 --- a/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx +++ b/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx @@ -92,7 +92,7 @@ export const templateSectionWidget = HomePageWidgetBlueprint.make({ * @alpha */ export const quickAccessCardWidget = HomePageWidgetBlueprint.make({ - name: 'quick-access-card', + name: 'quickaccess-card', params: { name: 'Quick Access Card', title: homepageMessages.quickAccess.title, @@ -165,20 +165,30 @@ export const featuredDocsCardWidget = HomePageWidgetBlueprint.make({ * NFS widget: CatalogStarred (migrated from mountPoint home.page/cards). * @alpha */ -export const catalogStarredWidget = homePlugin +export const catalogStarredWidget = HomePageWidgetBlueprint.make({ + name: 'catalog-starred-entities-card', + params: { + name: 'CatalogStarred', + title: homepageMessages.starredEntities.title, + layout: defaultCardLayout, + components: () => + import('../../components/legacy/TranslatedUpstreamHomePageCards').then( + m => ({ + Content: m.CatalogStarredEntitiesCard, + Renderer: upstreamHomeCardRenderer, + }), + ), + }, +}); + +/** + * Disables the default home plugin starred-entities widget (replaced by catalogStarredWidget). + * @alpha + */ +export const disableStarredEntities = homePlugin .getExtension('home-page-widget:home/starred-entities') .override({ - params: { - name: 'CatalogStarred', - title: homepageMessages.starredEntities.title, - components: () => - import('../../components/legacy/TranslatedUpstreamHomePageCards').then( - m => ({ - Content: m.CatalogStarredEntitiesCard, - Renderer: upstreamHomeCardRenderer, - }), - ), - }, + disabled: true, }); /** @@ -196,7 +206,7 @@ export const disableToolkit = homePlugin * @alpha */ export const RecentlyVisitedWidget = HomePageWidgetBlueprint.make({ - name: 'recently-visited', + name: 'recently-visited-card', params: { layout: defaultCardLayout, name: 'Recently visited', @@ -216,7 +226,7 @@ export const RecentlyVisitedWidget = HomePageWidgetBlueprint.make({ * @alpha */ export const TopVisitedWidget = HomePageWidgetBlueprint.make({ - name: 'top-visited', + name: 'top-visited-card', params: { layout: defaultCardLayout, name: 'Top visited', diff --git a/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageLayoutExtension.tsx b/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageLayoutExtension.tsx index 1817429261..81f3616141 100644 --- a/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageLayoutExtension.tsx +++ b/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageLayoutExtension.tsx @@ -16,13 +16,13 @@ import { HomePageLayoutBlueprint } from '@backstage/plugin-home-react/alpha'; import { HomePageLayout } from '../components/HomePageLayout'; -import { HomePageCardConfig } from '../../types'; /** * Custom home page layout extension for the New Frontend System. * - * Config-driven layout with `widgetLayout` (priority, breakpoints per widget), - * supports both customizable (drag/drop) and read-only modes. + * Supports both customizable (drag/drop) and read-only modes. + * Default widgets (including persona-based filtering) are loaded from the + * homepage-backend via the defaultWidgetsApi. * * @alpha */ @@ -32,58 +32,16 @@ export const homePageLayoutExtension = config: { schema: { customizable: z => z.boolean().optional(), - widgetLayout: z => - z - .record( - z.object({ - priority: z.number().optional(), - breakpoints: z - .record( - z.object({ - w: z.number().optional(), - h: z.number().optional(), - x: z.number().optional(), - y: z.number().optional(), - }), - ) - .optional(), - }), - ) - .optional(), }, }, factory(originalFactory, { config }) { const customizable = config.customizable ?? true; - const layoutConfig = config.widgetLayout ?? {}; return originalFactory({ loader: async () => function CustomHomePageLayout({ widgets }) { - const processedWidgets: HomePageCardConfig[] = widgets - .map(widget => { - const widgetConfig = layoutConfig[widget.name ?? '']; - const configBreakpoints = widgetConfig?.breakpoints; - - if (!configBreakpoints) return widget; - - return { - ...widget, - breakpointLayouts: configBreakpoints, - }; - }) - .sort((a, b) => { - if (customizable) return 0; // keep original order - - const priorityA = layoutConfig[a.name ?? '']?.priority ?? 0; - const priorityB = layoutConfig[b.name ?? '']?.priority ?? 0; - return priorityB - priorityA; - }); - return ( - + ); }, }); diff --git a/workspaces/homepage/plugins/homepage/src/alpha/index.ts b/workspaces/homepage/plugins/homepage/src/alpha/index.ts index 1b599a1159..6a96a34bd5 100644 --- a/workspaces/homepage/plugins/homepage/src/alpha/index.ts +++ b/workspaces/homepage/plugins/homepage/src/alpha/index.ts @@ -18,6 +18,7 @@ import { TranslationBlueprint } from '@backstage/plugin-app-react'; import { createFrontendModule } from '@backstage/frontend-plugin-api'; import { catalogStarredWidget, + disableStarredEntities, disableToolkit, entitySectionWidget, featuredDocsCardWidget, @@ -57,6 +58,7 @@ export const homePageModule = createFrontendModule({ TopVisitedWidget, RecentlyVisitedWidget, catalogStarredWidget, + disableStarredEntities, disableToolkit, ], }); diff --git a/workspaces/homepage/plugins/homepage/src/hooks/useDefaultWidgets.ts b/workspaces/homepage/plugins/homepage/src/hooks/useDefaultWidgets.ts index 5e77ba4503..07bb72de2d 100644 --- a/workspaces/homepage/plugins/homepage/src/hooks/useDefaultWidgets.ts +++ b/workspaces/homepage/plugins/homepage/src/hooks/useDefaultWidgets.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useApi } from '@backstage/core-plugin-api'; +import { useApi } from '@backstage/frontend-plugin-api'; import useAsync from 'react-use/lib/useAsync'; import {