From c32032faeadd44b31c5c8a92efc0bba14c6e0fa1 Mon Sep 17 00:00:00 2001 From: Christoph Jerolimov Date: Tue, 7 Jul 2026 10:41:59 +0200 Subject: [PATCH 1/5] feat(homepage): add implementation plan for NFS persona support (RHIDP-14651) Planning document for integrating homepage-backend persona-based default widgets into the New Frontend System (NFS) components. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plugins/homepage/RHIDP-14651-plan.md | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 workspaces/homepage/plugins/homepage/RHIDP-14651-plan.md diff --git a/workspaces/homepage/plugins/homepage/RHIDP-14651-plan.md b/workspaces/homepage/plugins/homepage/RHIDP-14651-plan.md new file mode 100644 index 0000000000..16893c2aa6 --- /dev/null +++ b/workspaces/homepage/plugins/homepage/RHIDP-14651-plan.md @@ -0,0 +1,111 @@ +# RHIDP-14651: Add persona-support in the NFS components + +**Jira:** [RHIDP-14651](https://redhat.atlassian.net/browse/RHIDP-14651) +**Epic:** [RHIDP-14103](https://redhat.atlassian.net/browse/RHIDP-14103) — Homepage-backend upgrade to GA support +**Feature:** [RHDHPLAN-1371](https://redhat.atlassian.net/browse/RHDHPLAN-1371) — Upgrade homepage related plugins to GA support + +## Problem + +The OFS (Old Frontend System) `HomePage` component already calls the `homepage-backend` via `useDefaultWidgets()` to load persona-based (user/group/permission-filtered) default widgets. The NFS (New Frontend System) `HomePageLayout` does **not** — it only renders whatever widgets the upstream home plugin passes in, with no backend-driven defaults. + +This means RHDH customers using NFS cannot configure persona-based homepages (e.g., different widgets for engineers vs. managers). + +## Current Architecture + +### OFS flow (already working) + +``` +HomePage + → useDefaultWidgets() hook + → DefaultWidgetsApiClient.getDefaultWidgets() + → GET /api/homepage/default-widgets (backend, with user credentials) + → if defaultWidgets returned: + → DefaultWidgetsCustomizableGrid or DefaultWidgetsReadOnlyGrid + else: + → CustomizableGrid or ReadOnlyGrid (mount-point-only fallback) +``` + +### NFS flow (current — no backend integration) + +``` +homePageLayoutExtension (HomePageLayoutBlueprint) + → receives `widgets` from upstream home plugin + → HomePageLayout + → CustomizableGridLayout or ReadOnlyGridLayout + → widgets come only from extension config (widgetLayout), not backend +``` + +### NFS API registration (already exists) + +The `defaultWidgetsApi` is already registered in `alpha/extensions/apis.ts` as an `ApiBlueprint`, so the API client is available in the NFS dependency injection. It is just never consumed by the layout. + +## Implementation Plan + +### 1. Add `useDefaultWidgets` hook for NFS + +Create a new hook (or adapt the existing OFS one) that works with the NFS `useApi` from `@backstage/frontend-plugin-api` instead of `@backstage/core-plugin-api`: + +- File: `src/alpha/hooks/useDefaultWidgets.ts` +- Uses the `defaultWidgetsApiRef` already registered +- Returns `{ defaultWidgets, loading, error }` — same shape as the OFS hook + +### 2. Update `HomePageLayout` to integrate backend defaults + +Modify `src/alpha/components/HomePageLayout.tsx`: + +- Call `useDefaultWidgets()` to fetch persona-based default widgets from the backend +- Show `` while loading +- When `defaultWidgets` is returned from backend: + - Merge backend defaults with the extension-provided `widgets` (backend defaults provide layout/props overrides; extension widgets provide the actual React components) + - Render using existing grid components +- When backend returns no defaults (or backend is unavailable): + - Fall back to the current behavior (extension-only widgets) + +### 3. Add/update unit tests + +- `src/alpha/hooks/useDefaultWidgets.test.ts` — test the NFS hook +- `src/alpha/components/HomePageLayout.test.tsx` — test the integration: + - Backend returns defaults → renders with persona-based config + - Backend returns empty → falls back to extension widgets + - Backend unavailable → falls back gracefully + +### 4. Update example app (NFS) + +Ensure `packages/app/` is configured to demonstrate the NFS homepage with the backend plugin enabled, so the persona flow can be tested end-to-end locally. + +### 5. Documentation + +Add a section to the workspace README or a dedicated doc file describing: + +- How to configure persona-based defaults for NFS +- Configuration example (same YAML format as OFS, via `homepage.defaultWidgets`) +- Differences/parity between OFS and NFS behavior + +## Key Design Decision + +**Merging strategy:** The backend returns `VisibleDefaultWidget[]` which contains `id`, `ref`, `props`, and `layout`. The NFS receives `HomePageCardConfig[]` (widgets) from the upstream home plugin. The merge needs to: + +1. Match backend defaults by `ref` → NFS widget by `name` +2. Apply backend-provided `layout` as `breakpointLayouts` overrides +3. Apply backend-provided `props` as component prop overrides +4. Respect backend ordering/filtering (persona-based visibility is already handled server-side) + +This mirrors how `DefaultWidgetsCustomizableGrid` and `DefaultWidgetsReadOnlyGrid` work in OFS — matching `defaultWidget.ref` to `mountPoint.config.id`. + +## Files to Change + +| File | Change | +| -------------------------------------------------- | ---------------------------------- | +| `src/alpha/hooks/useDefaultWidgets.ts` | **New** — NFS-compatible hook | +| `src/alpha/components/HomePageLayout.tsx` | Integrate backend defaults | +| `src/alpha/extensions/homePageLayoutExtension.tsx` | Pass through API context if needed | +| `src/alpha/alpha.test.ts` | Update module tests | +| `src/alpha/hooks/useDefaultWidgets.test.ts` | **New** — hook tests | +| `src/alpha/components/HomePageLayout.test.tsx` | **New** — integration tests | +| `packages/app/` | Update example app config | + +## Out of Scope + +- Changes to the backend (`homepage-backend`) — persona filtering already works +- Changes to `homepage-common` types — existing types are sufficient +- OFS changes — OFS already has full persona support From b99fbc488e7b7f69fcf3d8301131bb16e73c8a56 Mon Sep 17 00:00:00 2001 From: Christoph Jerolimov Date: Tue, 7 Jul 2026 11:16:47 +0200 Subject: [PATCH 2/5] docs(homepage): update implementation plan for NFS persona support Revised plan based on codebase exploration: - Share existing useDefaultWidgets hook (switch useApi import) - Remove widgetLayout config from homePageLayoutExtension - Rename NFS widget blueprint names to match config refs - Enable persona Playwright test for NFS mode Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plugins/homepage/RHIDP-14651-plan.md | 144 +++++++++--------- 1 file changed, 73 insertions(+), 71 deletions(-) diff --git a/workspaces/homepage/plugins/homepage/RHIDP-14651-plan.md b/workspaces/homepage/plugins/homepage/RHIDP-14651-plan.md index 16893c2aa6..66d7b52d6d 100644 --- a/workspaces/homepage/plugins/homepage/RHIDP-14651-plan.md +++ b/workspaces/homepage/plugins/homepage/RHIDP-14651-plan.md @@ -1,111 +1,113 @@ -# RHIDP-14651: Add persona-support in the NFS components +# RHIDP-14651: Add persona-support in NFS homepage components **Jira:** [RHIDP-14651](https://redhat.atlassian.net/browse/RHIDP-14651) **Epic:** [RHIDP-14103](https://redhat.atlassian.net/browse/RHIDP-14103) — Homepage-backend upgrade to GA support **Feature:** [RHDHPLAN-1371](https://redhat.atlassian.net/browse/RHDHPLAN-1371) — Upgrade homepage related plugins to GA support -## Problem +## Context -The OFS (Old Frontend System) `HomePage` component already calls the `homepage-backend` via `useDefaultWidgets()` to load persona-based (user/group/permission-filtered) default widgets. The NFS (New Frontend System) `HomePageLayout` does **not** — it only renders whatever widgets the upstream home plugin passes in, with no backend-driven defaults. +The OFS (Old Frontend System) `HomePage` already calls the `homepage-backend` via `useDefaultWidgets()` to load persona-based (user/group/permission-filtered) default widgets. The NFS (New Frontend System) `HomePageLayout` does **not** — it only renders widgets from extension config, with no backend integration. -This means RHDH customers using NFS cannot configure persona-based homepages (e.g., different widgets for engineers vs. managers). +The `defaultWidgetsApi` is already registered as an `ApiBlueprint` in NFS (`alpha/extensions/apis.ts`) but never consumed. The existing `useDefaultWidgets` hook uses `useApi` from `@backstage/core-plugin-api`, which is already a re-export of `@backstage/frontend-plugin-api` at runtime — so the same hook can serve both systems without duplication. -## Current Architecture +The NFS layout extension currently has a `widgetLayout` config schema that provides static layout overrides per widget. This will be removed in favor of the backend-driven defaults which provide the same layout data with persona filtering. -### OFS flow (already working) +## Changes -``` -HomePage - → useDefaultWidgets() hook - → DefaultWidgetsApiClient.getDefaultWidgets() - → GET /api/homepage/default-widgets (backend, with user credentials) - → if defaultWidgets returned: - → DefaultWidgetsCustomizableGrid or DefaultWidgetsReadOnlyGrid - else: - → CustomizableGrid or ReadOnlyGrid (mount-point-only fallback) -``` +### 1. Update `useDefaultWidgets` hook — shared between OFS and NFS -### NFS flow (current — no backend integration) +**File:** `plugins/homepage/src/hooks/useDefaultWidgets.ts` -``` -homePageLayoutExtension (HomePageLayoutBlueprint) - → receives `widgets` from upstream home plugin - → HomePageLayout - → CustomizableGridLayout or ReadOnlyGridLayout - → widgets come only from extension config (widgetLayout), not backend -``` +Change `import { useApi } from '@backstage/core-plugin-api'` to `import { useApi } from '@backstage/frontend-plugin-api'`. This is a no-op at runtime (core-plugin-api already re-exports it) but makes the hook explicitly compatible with both frontend systems. No other changes needed — the hook already returns `{ defaultWidgets, loading, error }`. -### NFS API registration (already exists) +### 2. Update `HomePageLayout` to integrate backend defaults -The `defaultWidgetsApi` is already registered in `alpha/extensions/apis.ts` as an `ApiBlueprint`, so the API client is available in the NFS dependency injection. It is just never consumed by the layout. +**File:** `plugins/homepage/src/alpha/components/HomePageLayout.tsx` -## Implementation Plan +- Import and call `useDefaultWidgets()` from `../../hooks/useDefaultWidgets` +- Show `` while loading +- When `defaultWidgets` is returned: + - Match backend defaults (`defaultWidget.ref`) to NFS widgets (`widget.name`) + - Apply backend-provided `layout` as `breakpointLayouts` overrides on matched widgets + - Filter the widget list: only show widgets that have a matching backend default (persona-filtered) + - Apply backend-provided `props` as component prop overrides +- When backend is unavailable (error or no response): + - Fall back to current behavior — render all extension-provided widgets as-is -### 1. Add `useDefaultWidgets` hook for NFS +The merge logic follows the pattern in `DefaultWidgetsCustomizableGrid.tsx` and `DefaultWidgetsReadOnlyGrid.tsx` (OFS), adapted for the NFS `HomePageCardConfig` shape. -Create a new hook (or adapt the existing OFS one) that works with the NFS `useApi` from `@backstage/frontend-plugin-api` instead of `@backstage/core-plugin-api`: +### 3. Remove `widgetLayout` config from `homePageLayoutExtension` -- File: `src/alpha/hooks/useDefaultWidgets.ts` -- Uses the `defaultWidgetsApiRef` already registered -- Returns `{ defaultWidgets, loading, error }` — same shape as the OFS hook +**File:** `plugins/homepage/src/alpha/extensions/homePageLayoutExtension.tsx` -### 2. Update `HomePageLayout` to integrate backend defaults +- Remove the `widgetLayout` config schema entry and all code that reads/applies it (priority sorting, breakpoint mapping) +- Keep the `customizable` config option +- Layout data now comes from backend defaults instead of static extension config -Modify `src/alpha/components/HomePageLayout.tsx`: +### 4. Update `app-config.yaml` — remove NFS `widgetLayout` config -- Call `useDefaultWidgets()` to fetch persona-based default widgets from the backend -- Show `` while loading -- When `defaultWidgets` is returned from backend: - - Merge backend defaults with the extension-provided `widgets` (backend defaults provide layout/props overrides; extension widgets provide the actual React components) - - Render using existing grid components -- When backend returns no defaults (or backend is unavailable): - - Fall back to the current behavior (extension-only widgets) +**File:** `app-config.yaml` -### 3. Add/update unit tests +Remove the `widgetLayout` block under `home-page-layout:home/dynamic-homepage-layout` config since it's no longer supported. The `customizable` option stays. -- `src/alpha/hooks/useDefaultWidgets.test.ts` — test the NFS hook -- `src/alpha/components/HomePageLayout.test.tsx` — test the integration: - - Backend returns defaults → renders with persona-based config - - Backend returns empty → falls back to extension widgets - - Backend unavailable → falls back gracefully +### 5. Rename NFS widget blueprint names to match config `ref` values -### 4. Update example app (NFS) +The backend config `ref` values must match the NFS widget blueprint `name` values exactly. Rename the NFS widget names to match the existing config refs (preserving backwards compatibility with existing configurations): -Ensure `packages/app/` is configured to demonstrate the NFS homepage with the backend plugin enabled, so the persona flow can be tested end-to-end locally. +**File:** `plugins/homepage/src/alpha/extensions/homePageCards.tsx` -### 5. Documentation +| Config `ref` | Current NFS `name` | New NFS `name` | +| ------------------------------- | ------------------------------------- | -------------------------- | +| `quickaccess-card` | `quick-access-card` | `quickaccess-card` | +| `recently-visited-card` | `recently-visited` | `recently-visited-card` | +| `top-visited-card` | `top-visited` | `top-visited-card` | +| `catalog-starred-entities-card` | (override of `home/starred-entities`) | Add explicit name override | -Add a section to the workspace README or a dedicated doc file describing: +The `rhdh-onboarding-section`, `rhdh-entity-section`, `rhdh-template-section`, and `featured-docs-card` already match. -- How to configure persona-based defaults for NFS -- Configuration example (same YAML format as OFS, via `homepage.defaultWidgets`) -- Differences/parity between OFS and NFS behavior +### 6. Update Playwright e2e tests -## Key Design Decision +**File:** `e2e-tests/homepageCustomizable.test.ts` -**Merging strategy:** The backend returns `VisibleDefaultWidget[]` which contains `id`, `ref`, `props`, and `layout`. The NFS receives `HomePageCardConfig[]` (widgets) from the upstream home plugin. The merge needs to: +- Remove the `test.skip()` for NFS mode in the "Groups filters default widgets by persona" test +- Update the login URL logic: NFS uses `'/'` instead of `'/customizable'` — the persona test currently hardcodes `'/customizable'` +- The test should now pass for both `APP_MODE=legacy` and `APP_MODE=nfs` -1. Match backend defaults by `ref` → NFS widget by `name` -2. Apply backend-provided `layout` as `breakpointLayouts` overrides -3. Apply backend-provided `props` as component prop overrides -4. Respect backend ordering/filtering (persona-based visibility is already handled server-side) +### 7. Update unit tests -This mirrors how `DefaultWidgetsCustomizableGrid` and `DefaultWidgetsReadOnlyGrid` work in OFS — matching `defaultWidget.ref` to `mountPoint.config.id`. +**File:** `plugins/homepage/src/alpha/alpha.test.ts` — update to reflect removed `widgetLayout` config + +Add new test for `HomePageLayout` with mocked `useDefaultWidgets`: + +- Backend returns defaults → layout renders persona-filtered widgets +- Backend returns empty → empty state +- Backend unavailable → falls back to extension widgets ## Files to Change -| File | Change | -| -------------------------------------------------- | ---------------------------------- | -| `src/alpha/hooks/useDefaultWidgets.ts` | **New** — NFS-compatible hook | -| `src/alpha/components/HomePageLayout.tsx` | Integrate backend defaults | -| `src/alpha/extensions/homePageLayoutExtension.tsx` | Pass through API context if needed | -| `src/alpha/alpha.test.ts` | Update module tests | -| `src/alpha/hooks/useDefaultWidgets.test.ts` | **New** — hook tests | -| `src/alpha/components/HomePageLayout.test.tsx` | **New** — integration tests | -| `packages/app/` | Update example app config | +| File | Change | +| -------------------------------------------------- | ------------------------------------------------- | +| `src/hooks/useDefaultWidgets.ts` | Switch import to `@backstage/frontend-plugin-api` | +| `src/alpha/components/HomePageLayout.tsx` | Integrate backend defaults | +| `src/alpha/extensions/homePageLayoutExtension.tsx` | Remove `widgetLayout` config | +| `src/alpha/extensions/homePageCards.tsx` | Rename widget names to match config refs | +| `app-config.yaml` | Remove `widgetLayout` block | +| `src/alpha/alpha.test.ts` | Update module tests | +| `src/alpha/components/HomePageLayout.test.tsx` | **New** — integration tests | +| `e2e-tests/homepageCustomizable.test.ts` | Enable persona test for NFS | ## Out of Scope - Changes to the backend (`homepage-backend`) — persona filtering already works - Changes to `homepage-common` types — existing types are sufficient -- OFS changes — OFS already has full persona support +- OFS changes — OFS already has full persona support (only the `useApi` import changes) + +## Verification + +1. Run unit tests: `cd workspaces/homepage && yarn test` +2. Run Playwright tests for both modes: + - `yarn test:e2e:legacy` — verify no regressions + - `yarn test:e2e:nfs` — verify persona test now passes +3. Manual verification: start the NFS app (`yarn start`) and check: + - Guest sees common defaults only (no Featured Docs, no Recently Visited) + - Different users see different widgets based on group membership From 985eccfae6843cf2f9a7c68c33556a15ca244a81 Mon Sep 17 00:00:00 2001 From: Christoph Jerolimov Date: Tue, 7 Jul 2026 11:22:19 +0200 Subject: [PATCH 3/5] feat(homepage): add persona-based default widgets to NFS (RHIDP-14651) Wire the NFS HomePageLayout to call the homepage-backend for persona-based (user/group/permission-filtered) default widgets, bringing NFS to parity with OFS. Key changes: - Share useDefaultWidgets hook between OFS and NFS (switch useApi import to @backstage/frontend-plugin-api) - HomePageLayout now fetches and merges backend defaults with extension-provided widgets, with graceful fallback - Remove widgetLayout config from homePageLayoutExtension (layout data now comes from the backend) - Rename NFS widget blueprint names to match config ref values - Refactor catalogStarredWidget to standalone blueprint - Enable persona Playwright e2e test for NFS mode Co-Authored-By: Claude Opus 4.6 (1M context) --- workspaces/homepage/app-config.yaml | 64 ------------------ .../e2e-tests/homepageCustomizable.test.ts | 9 +-- .../plugins/homepage/src/alpha/alpha.test.ts | 8 ++- .../src/alpha/components/HomePageLayout.tsx | 65 ++++++++++++++++++- .../src/alpha/extensions/homePageCards.tsx | 40 +++++++----- .../extensions/homePageLayoutExtension.tsx | 50 ++------------ .../plugins/homepage/src/alpha/index.ts | 2 + .../homepage/src/hooks/useDefaultWidgets.ts | 2 +- 8 files changed, 102 insertions(+), 138 deletions(-) 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/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..64389f6266 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,65 @@ 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 widgetsByName = new Map(); + for (const widget of widgets) { + if (widget.name) { + widgetsByName.set(widget.name, widget); + } + } + + const result: HomePageCardConfig[] = []; + for (const defaultWidget of defaultWidgets) { + const widget = widgetsByName.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: ${[...widgetsByName.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 { From 389f024c640e4bfeb52928259dbc36a8af95d168 Mon Sep 17 00:00:00 2001 From: Christoph Jerolimov Date: Tue, 7 Jul 2026 12:04:53 +0200 Subject: [PATCH 4/5] fix(homepage): use extension ID for NFS widget matching, switch to chromium Fix widget name resolution in HomePageLayout: use node.spec.id (the extension ID from the NFS app tree) instead of widget.name (which is the display name) when matching backend default widget refs to NFS widgets. Also switch Playwright config from chrome channel to chromium for local test compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) --- workspaces/homepage/playwright.config.ts | 4 ++-- .../homepage/src/alpha/components/HomePageLayout.tsx | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) 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/components/HomePageLayout.tsx b/workspaces/homepage/plugins/homepage/src/alpha/components/HomePageLayout.tsx index 64389f6266..d6030c9f7a 100644 --- a/workspaces/homepage/plugins/homepage/src/alpha/components/HomePageLayout.tsx +++ b/workspaces/homepage/plugins/homepage/src/alpha/components/HomePageLayout.tsx @@ -56,21 +56,23 @@ export const HomePageLayout = ({ widgets, customizable }: HomePageProps) => { return undefined; } - const widgetsByName = new Map(); + const widgetsByRef = new Map(); for (const widget of widgets) { - if (widget.name) { - widgetsByName.set(widget.name, widget); + 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 = widgetsByName.get(defaultWidget.ref); + 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: ${[...widgetsByName.keys()].join(', ')}`, + `No matching NFS widget found. Available widgets: ${[...widgetsByRef.keys()].join(', ')}`, ); continue; } From 7f10cfc2fe51027f95f6540a8cd3ec97c9da6ad0 Mon Sep 17 00:00:00 2001 From: Christoph Jerolimov Date: Tue, 7 Jul 2026 14:02:27 +0200 Subject: [PATCH 5/5] chore(homepage): remove implementation plan file Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plugins/homepage/RHIDP-14651-plan.md | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 workspaces/homepage/plugins/homepage/RHIDP-14651-plan.md diff --git a/workspaces/homepage/plugins/homepage/RHIDP-14651-plan.md b/workspaces/homepage/plugins/homepage/RHIDP-14651-plan.md deleted file mode 100644 index 66d7b52d6d..0000000000 --- a/workspaces/homepage/plugins/homepage/RHIDP-14651-plan.md +++ /dev/null @@ -1,113 +0,0 @@ -# RHIDP-14651: Add persona-support in NFS homepage components - -**Jira:** [RHIDP-14651](https://redhat.atlassian.net/browse/RHIDP-14651) -**Epic:** [RHIDP-14103](https://redhat.atlassian.net/browse/RHIDP-14103) — Homepage-backend upgrade to GA support -**Feature:** [RHDHPLAN-1371](https://redhat.atlassian.net/browse/RHDHPLAN-1371) — Upgrade homepage related plugins to GA support - -## Context - -The OFS (Old Frontend System) `HomePage` already calls the `homepage-backend` via `useDefaultWidgets()` to load persona-based (user/group/permission-filtered) default widgets. The NFS (New Frontend System) `HomePageLayout` does **not** — it only renders widgets from extension config, with no backend integration. - -The `defaultWidgetsApi` is already registered as an `ApiBlueprint` in NFS (`alpha/extensions/apis.ts`) but never consumed. The existing `useDefaultWidgets` hook uses `useApi` from `@backstage/core-plugin-api`, which is already a re-export of `@backstage/frontend-plugin-api` at runtime — so the same hook can serve both systems without duplication. - -The NFS layout extension currently has a `widgetLayout` config schema that provides static layout overrides per widget. This will be removed in favor of the backend-driven defaults which provide the same layout data with persona filtering. - -## Changes - -### 1. Update `useDefaultWidgets` hook — shared between OFS and NFS - -**File:** `plugins/homepage/src/hooks/useDefaultWidgets.ts` - -Change `import { useApi } from '@backstage/core-plugin-api'` to `import { useApi } from '@backstage/frontend-plugin-api'`. This is a no-op at runtime (core-plugin-api already re-exports it) but makes the hook explicitly compatible with both frontend systems. No other changes needed — the hook already returns `{ defaultWidgets, loading, error }`. - -### 2. Update `HomePageLayout` to integrate backend defaults - -**File:** `plugins/homepage/src/alpha/components/HomePageLayout.tsx` - -- Import and call `useDefaultWidgets()` from `../../hooks/useDefaultWidgets` -- Show `` while loading -- When `defaultWidgets` is returned: - - Match backend defaults (`defaultWidget.ref`) to NFS widgets (`widget.name`) - - Apply backend-provided `layout` as `breakpointLayouts` overrides on matched widgets - - Filter the widget list: only show widgets that have a matching backend default (persona-filtered) - - Apply backend-provided `props` as component prop overrides -- When backend is unavailable (error or no response): - - Fall back to current behavior — render all extension-provided widgets as-is - -The merge logic follows the pattern in `DefaultWidgetsCustomizableGrid.tsx` and `DefaultWidgetsReadOnlyGrid.tsx` (OFS), adapted for the NFS `HomePageCardConfig` shape. - -### 3. Remove `widgetLayout` config from `homePageLayoutExtension` - -**File:** `plugins/homepage/src/alpha/extensions/homePageLayoutExtension.tsx` - -- Remove the `widgetLayout` config schema entry and all code that reads/applies it (priority sorting, breakpoint mapping) -- Keep the `customizable` config option -- Layout data now comes from backend defaults instead of static extension config - -### 4. Update `app-config.yaml` — remove NFS `widgetLayout` config - -**File:** `app-config.yaml` - -Remove the `widgetLayout` block under `home-page-layout:home/dynamic-homepage-layout` config since it's no longer supported. The `customizable` option stays. - -### 5. Rename NFS widget blueprint names to match config `ref` values - -The backend config `ref` values must match the NFS widget blueprint `name` values exactly. Rename the NFS widget names to match the existing config refs (preserving backwards compatibility with existing configurations): - -**File:** `plugins/homepage/src/alpha/extensions/homePageCards.tsx` - -| Config `ref` | Current NFS `name` | New NFS `name` | -| ------------------------------- | ------------------------------------- | -------------------------- | -| `quickaccess-card` | `quick-access-card` | `quickaccess-card` | -| `recently-visited-card` | `recently-visited` | `recently-visited-card` | -| `top-visited-card` | `top-visited` | `top-visited-card` | -| `catalog-starred-entities-card` | (override of `home/starred-entities`) | Add explicit name override | - -The `rhdh-onboarding-section`, `rhdh-entity-section`, `rhdh-template-section`, and `featured-docs-card` already match. - -### 6. Update Playwright e2e tests - -**File:** `e2e-tests/homepageCustomizable.test.ts` - -- Remove the `test.skip()` for NFS mode in the "Groups filters default widgets by persona" test -- Update the login URL logic: NFS uses `'/'` instead of `'/customizable'` — the persona test currently hardcodes `'/customizable'` -- The test should now pass for both `APP_MODE=legacy` and `APP_MODE=nfs` - -### 7. Update unit tests - -**File:** `plugins/homepage/src/alpha/alpha.test.ts` — update to reflect removed `widgetLayout` config - -Add new test for `HomePageLayout` with mocked `useDefaultWidgets`: - -- Backend returns defaults → layout renders persona-filtered widgets -- Backend returns empty → empty state -- Backend unavailable → falls back to extension widgets - -## Files to Change - -| File | Change | -| -------------------------------------------------- | ------------------------------------------------- | -| `src/hooks/useDefaultWidgets.ts` | Switch import to `@backstage/frontend-plugin-api` | -| `src/alpha/components/HomePageLayout.tsx` | Integrate backend defaults | -| `src/alpha/extensions/homePageLayoutExtension.tsx` | Remove `widgetLayout` config | -| `src/alpha/extensions/homePageCards.tsx` | Rename widget names to match config refs | -| `app-config.yaml` | Remove `widgetLayout` block | -| `src/alpha/alpha.test.ts` | Update module tests | -| `src/alpha/components/HomePageLayout.test.tsx` | **New** — integration tests | -| `e2e-tests/homepageCustomizable.test.ts` | Enable persona test for NFS | - -## Out of Scope - -- Changes to the backend (`homepage-backend`) — persona filtering already works -- Changes to `homepage-common` types — existing types are sufficient -- OFS changes — OFS already has full persona support (only the `useApi` import changes) - -## Verification - -1. Run unit tests: `cd workspaces/homepage && yarn test` -2. Run Playwright tests for both modes: - - `yarn test:e2e:legacy` — verify no regressions - - `yarn test:e2e:nfs` — verify persona test now passes -3. Manual verification: start the NFS app (`yarn start`) and check: - - Guest sees common defaults only (no Featured Docs, no Recently Visited) - - Different users see different widgets based on group membership