Skip to content

feat(homepage): add persona-support in NFS components (RHIDP-14651)#3700

Open
christoph-jerolimov wants to merge 5 commits into
mainfrom
RHIDP-14651/homepage-nfs-persona-support
Open

feat(homepage): add persona-support in NFS components (RHIDP-14651)#3700
christoph-jerolimov wants to merge 5 commits into
mainfrom
RHIDP-14651/homepage-nfs-persona-support

Conversation

@christoph-jerolimov

Copy link
Copy Markdown
Member

Summary

  • Adds persona-based (user/group/permission-filtered) default widget support to the New Frontend System (NFS) homepage components
  • The OFS already has this integration; this PR brings NFS to parity by calling the homepage-backend for persona-based defaults
  • Includes implementation plan, unit tests, example app updates, and documentation

Jira

Approach

  1. Create an NFS-compatible useDefaultWidgets hook that calls the existing defaultWidgetsApiRef (already registered in NFS)
  2. Update HomePageLayout to fetch and merge backend-driven persona defaults with extension-provided widgets
  3. Fall back gracefully when the backend is unavailable (current behavior preserved)
  4. Add unit and integration tests
  5. Update example app (NFS)

See workspaces/homepage/plugins/homepage/RHIDP-14651-plan.md for the detailed implementation plan.

Test plan

  • Unit tests for useDefaultWidgets NFS hook
  • Integration tests for HomePageLayout with backend defaults
  • Manual test: NFS homepage loads persona-based defaults from backend
  • Manual test: NFS homepage falls back gracefully when backend is unavailable
  • Verify OFS behavior is unchanged (no regressions)
  • E2E test coverage

🤖 Generated with Claude Code

…P-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) <noreply@anthropic.com>
@rhdh-gh-app

rhdh-gh-app Bot commented Jul 7, 2026

Copy link
Copy Markdown

Missing Changesets

The following package(s) are changed by this PR but do not have a changeset:

  • @red-hat-developer-hub/backstage-plugin-homepage

See CONTRIBUTING.md for more information about how to add changesets.

Changed Packages

Package Name Package Path Changeset Bump Current Version
@red-hat-developer-hub/backstage-plugin-homepage workspaces/homepage/plugins/homepage none v1.15.0

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 17.64706% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.30%. Comparing base (00f80fc) to head (7f10cfc).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3700      +/-   ##
==========================================
- Coverage   54.30%   54.30%   -0.01%     
==========================================
  Files        2349     2349              
  Lines       89718    89736      +18     
  Branches    25119    25115       -4     
==========================================
+ Hits        48722    48728       +6     
- Misses      40648    40660      +12     
  Partials      348      348              
Flag Coverage Δ *Carryforward flag
adoption-insights 83.70% <ø> (ø) Carriedforward from 389f024
ai-integrations 68.35% <ø> (ø) Carriedforward from 389f024
app-defaults 69.79% <ø> (ø) Carriedforward from 389f024
augment 46.39% <ø> (ø) Carriedforward from 389f024
boost 74.68% <ø> (ø) Carriedforward from 389f024
bulk-import 72.46% <ø> (ø) Carriedforward from 389f024
cost-management 14.10% <ø> (ø) Carriedforward from 389f024
dcm 61.81% <ø> (ø) Carriedforward from 389f024
extensions 61.53% <ø> (ø) Carriedforward from 389f024
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 389f024
global-header 59.71% <ø> (ø) Carriedforward from 389f024
homepage 50.00% <17.64%> (-0.24%) ⬇️
install-dynamic-plugins 56.77% <ø> (ø) Carriedforward from 389f024
konflux 91.49% <ø> (ø) Carriedforward from 389f024
lightspeed 68.81% <ø> (ø) Carriedforward from 389f024
mcp-integrations 85.46% <ø> (ø) Carriedforward from 389f024
orchestrator 39.80% <ø> (ø) Carriedforward from 389f024
quickstart 65.63% <ø> (ø) Carriedforward from 389f024
sandbox 79.56% <ø> (ø) Carriedforward from 389f024
scorecard 82.67% <ø> (ø) Carriedforward from 389f024
theme 61.26% <ø> (ø) Carriedforward from 389f024
translations 7.25% <ø> (ø) Carriedforward from 389f024
x2a 78.68% <ø> (ø) Carriedforward from 389f024

*This pull request uses carry forward flags. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 00f80fc...7f10cfc. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

christoph-jerolimov and others added 4 commits July 7, 2026 11:16
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
…romium

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) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Jul 7, 2026

Copy link
Copy Markdown

@christoph-jerolimov christoph-jerolimov marked this pull request as ready for review July 7, 2026 12:07
@rhdh-qodo-merge

Copy link
Copy Markdown

PR Summary by Qodo

Add persona-based default widgets support to NFS homepage layout

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Fetch persona-filtered default widgets from homepage-backend in NFS HomePageLayout.
• Merge backend widget refs/layout with registered NFS widget extensions, with fallback.
• Align NFS widget blueprint names and enable persona E2E coverage in NFS mode.
Diagram

graph TD
  Ext["NFS widget extensions"] --> Layout["HomePageLayout"] --> Hook(["useDefaultWidgets"]) --> Api["defaultWidgetsApiRef"] --> Backend{{"homepage-backend\n/default-widgets"}}
  Backend --> Layout
  Layout --> Grid["Grid render\n(customize/readonly)"]

  subgraph Legend
    direction LR
    _comp["Component"] ~~~ _hook(["Hook"]) ~~~ _ext{{"External service"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Extract merge/mapping into a shared helper (or move into the hook)
  • ➕ Keeps HomePageLayout focused on rendering; reduces branching
  • ➕ Centralizes validation/logging for unknown widget refs and layout shape
  • ➖ Still needs access to extension metadata (mapping ref -> widget), so may require additional inputs
  • ➖ May obscure behavior differences between NFS/OFS if they diverge later
2. Have homepage-backend return fully resolved widget definitions
  • ➕ Frontend no longer maps refs to extensions; fewer runtime mismatches
  • ➕ Backend can enforce schema/versioning centrally
  • ➖ Tight coupling between backend and frontend widget implementations
  • ➖ Reduces frontend extensibility; custom widgets would require backend awareness

Recommendation: The chosen approach (backend returns persona-filtered refs/layout; frontend maps refs to registered widget extensions) is the best fit for Backstage’s extensibility model and keeps the backend generic. If this grows, consider extracting the ref-mapping + layout casting into a small shared utility for clearer testing and validation.

Files changed (9) +106 / -140

Enhancement (5) +96 / -65
HomePageLayout.tsxFetch persona defaults from backend and merge into NFS widget list +64/-3

Fetch persona defaults from backend and merge into NFS widget list

• Integrates 'useDefaultWidgets' and merges backend default widget refs with extension-provided widgets (mapping by extension id suffix). Applies backend-provided breakpoint layouts, adds a loading state, and preserves the existing extension-only behavior when backend defaults are unavailable.

workspaces/homepage/plugins/homepage/src/alpha/components/HomePageLayout.tsx

homePageCards.tsxAlign NFS widget blueprint names with backend refs and disable upstream starred widget +25/-15

Align NFS widget blueprint names with backend refs and disable upstream starred widget

• Renames blueprint names to match 'homepage.defaultWidgets' refs (e.g., quickaccess and *-card suffixes). Replaces prior starred-entities override with a dedicated 'catalogStarredWidget' blueprint and adds 'disableStarredEntities' to turn off the upstream widget to avoid duplicates.

workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx

homePageLayoutExtension.tsxRemove config-driven widgetLayout processing from NFS layout extension +4/-46

Remove config-driven widgetLayout processing from NFS layout extension

• Simplifies the NFS layout extension by removing 'widgetLayout' schema and the per-widget breakpoint/priority processing. Widgets are passed through and backend defaults govern layout.

workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageLayoutExtension.tsx

index.tsRegister disableStarredEntities in NFS home module +2/-0

Register disableStarredEntities in NFS home module

• Adds the new 'disableStarredEntities' extension to the module extension list so the upstream starred-entities widget is disabled when using the NFS replacement widget.

workspaces/homepage/plugins/homepage/src/alpha/index.ts

useDefaultWidgets.tsMake useDefaultWidgets compatible with NFS by using frontend-plugin-api useApi +1/-1

Make useDefaultWidgets compatible with NFS by using frontend-plugin-api useApi

• Switches 'useApi' import to '@backstage/frontend-plugin-api' to work in NFS. Hook continues to call 'defaultWidgetsApiRef.getDefaultWidgets()' and returns items/loading/error.

workspaces/homepage/plugins/homepage/src/hooks/useDefaultWidgets.ts

Tests (2) +8 / -9
homepageCustomizable.test.tsEnable persona-based E2E test path for NFS mode +1/-8

Enable persona-based E2E test path for NFS mode

• Stops skipping the persona-based default widgets test in NFS mode by choosing the appropriate login URL ('/' for NFS, '/customizable' for legacy). This allows persona assertions to run in NFS mode.

workspaces/homepage/e2e-tests/homepageCustomizable.test.ts

alpha.test.tsAdd alpha export assertions for defaultWidgetsApi and disableStarredEntities +7/-1

Add alpha export assertions for defaultWidgetsApi and disableStarredEntities

• Extends NFS alpha module tests to assert 'defaultWidgetsApi' is exported and that 'disableStarredEntities' is present alongside other widget extensions.

workspaces/homepage/plugins/homepage/src/alpha/alpha.test.ts

Other (2) +2 / -66
app-config.yamlRemove static widgetLayout config for NFS dynamic homepage layout +0/-64

Remove static widgetLayout config for NFS dynamic homepage layout

• Deletes the 'widgetLayout' block under the 'home-page-layout:home/dynamic-homepage-layout' extension. Layout is now driven by backend-provided 'homepage.defaultWidgets' rather than extension config overrides.

workspaces/homepage/app-config.yaml

playwright.config.tsSwitch Playwright channel from chrome to chromium +2/-2

Switch Playwright channel from chrome to chromium

• Updates Playwright projects to use the 'chromium' channel instead of 'chrome', improving portability and consistency across environments.

workspaces/homepage/playwright.config.ts

@rhdh-qodo-merge

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
⚠️ Tickets: not configured — ticket URL found in PR but could not be fetched — check ticket provider credentials

Grey Divider


Action required

1. Default widget props ignored 🐞 Bug ≡ Correctness
Description
HomePageLayout merges backend defaults using only defaultWidget.ref and discards
defaultWidget.id and defaultWidget.props, so backend-provided per-widget customization and
multi-instance widgets cannot be represented correctly. This can also lead to React key collisions
in the customizable layout when the same ref appears more than once.
Code

workspaces/homepage/plugins/homepage/src/alpha/components/HomePageLayout.tsx[R54-90]

+  const mergedWidgets = useMemo((): HomePageCardConfig[] | undefined => {
+    if (!defaultWidgets) {
+      return undefined;
+    }
+
+    const widgetsByRef = new Map<string, HomePageCardConfig>();
+    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<string, { x?: number; y?: number; w?: number; h?: number }>
+        | undefined;
+
+      result.push({
+        ...widget,
+        breakpointLayouts: widgetLayout as
+          | Record<Breakpoint, Layout>
+          | undefined,
+      });
+    }
Relevance

⭐⭐⭐ High

Repo previously added unique per-widget ids to support multiple instances; dropping id/props
conflicts with that direction (PR1887).

PR-#1887
PR-#2855

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Backend default widget entries explicitly support unique id and optional props, and the legacy
frontend uses those fields to create distinct cards and inject props. The new NFS merge path only
selects by ref and only applies layout, dropping id/props and risking duplicate keys in the
customizable render loop.

workspaces/homepage/plugins/homepage/src/alpha/components/HomePageLayout.tsx[54-90]
workspaces/homepage/plugins/homepage-common/src/types.ts[43-55]
workspaces/homepage/plugins/homepage-backend/README.md[28-34]
workspaces/homepage/plugins/homepage/src/components/DefaultWidgetsCustomizableGrid.tsx[119-205]
workspaces/homepage/plugins/homepage/src/alpha/components/CustomizableGridLayout.tsx[103-112]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`HomePageLayout` currently treats backend defaults as a simple ordered list of widget *refs* and converts each entry into an existing extension-provided widget config. However, backend default widget entries include `id` and optional `props`, and the legacy implementation uses those fields to support multiple instances and per-instance configuration. NFS merge must not drop `id/props`, otherwise backend-driven customization silently stops working and multi-instance configs can break rendering (e.g., duplicated keys).

### Issue Context
- Backend response items are `VisibleDefaultWidget { id, ref, props?, layout? }`.
- Current NFS merge pushes the same widget config for each matching `ref`, without applying `props` and without using `id` to make instances unique.
- The customizable grid uses `key={card.name ?? index}`, so repeated refs/names can collide.

### Fix Focus Areas
- workspaces/homepage/plugins/homepage/src/alpha/components/HomePageLayout.tsx[54-90]
- workspaces/homepage/plugins/homepage/src/alpha/components/CustomizableGridLayout.tsx[103-112]

### What to change
1. When iterating `defaultWidgets`, create a per-entry card config that:
  - Uses `defaultWidget.id` as the unique identity (e.g., override `name`/`key` source) so duplicates of the same `ref` don’t collide.
  - Preserves `defaultWidget.props` by applying them to the rendered element/component (e.g., `React.cloneElement` when `widget.component` is an element, or wrapping the widget’s `Content` renderer to inject props).
2. Keep using `defaultWidget.layout` to populate `breakpointLayouts`, but tie that layout to the per-entry `id` instance.
3. Ensure `CustomizableGridLayout` uses a stable unique key (prefer `defaultWidget.id`) instead of `card.name` when rendering merged default widgets.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Docs list old widget refs 🐞 Bug ⚙ Maintainability
Description
Widget refs were renamed (e.g., quick-access-cardquickaccess-card, recently-visitedrecently-visited-card), but the homepage plugin README still documents the old extension IDs and
the removed config-driven layout model. This will lead users to configure backend/defaultWidgets
refs that don’t resolve to any NFS widget.
Code

workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx[R94-96]

export const quickAccessCardWidget = HomePageWidgetBlueprint.make({
-  name: 'quick-access-card',
+  name: 'quickaccess-card',
  params: {
Relevance

⭐⭐⭐ High

Team has merged homepage documentation corrections before; likely to fix README/code ref mismatches
causing misconfiguration (PR2944).

PR-#2944
PR-#2423

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The NFS widget blueprint names in code no longer match the README’s documented extension IDs, and
the README still describes a widgetLayout-driven layout. This mismatch directly affects which
ref values users will put into homepage.defaultWidgets.

workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx[90-110]
workspaces/homepage/plugins/homepage/README.md[80-94]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The README documentation for the NFS/alpha module lists widget extension IDs and layout configuration that no longer match the code (widget names changed; `widgetLayout` config removed). This causes misconfiguration during adoption/upgrade.

### Issue Context
- Code now registers widgets with names like `quickaccess-card`, `recently-visited-card`, `top-visited-card`.
- README still instructs users to reference `quick-access-card`, `recently-visited`, `top-visited`, and describes config-driven layout.

### Fix Focus Areas
- workspaces/homepage/plugins/homepage/README.md[80-94]
- workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx[90-110]

### What to change
1. Update the README widget extension ID list to match actual blueprint names.
2. Update the layout extension description to reflect backend-driven defaults and/or document the (reintroduced) `widgetLayout` fallback if you keep it.
3. Include a small migration note mapping old → new widget refs to reduce upgrade friction.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. widgetLayout config removed 🐞 Bug ☼ Reliability
Description
The NFS home layout extension no longer accepts or applies config.widgetLayout, so any existing
NFS layout configs (priorities/breakpoints) will stop being honored. This also weakens the stated
“backend unavailable” fallback, since the fallback path no longer has a config-driven layout
mechanism to rely on.
Code

workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageLayoutExtension.tsx[R32-45]

    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 (
-              <HomePageLayout
-                widgets={processedWidgets}
-                customizable={customizable}
-              />
+              <HomePageLayout widgets={widgets} customizable={customizable} />
            );
Relevance

⭐⭐ Medium

widgetLayout existed and documented in NFS migration (PR2423), but no clear precedent whether
removal is acceptable vs intentional redesign.

PR-#2423

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The NFS layout extension schema now only accepts customizable and the factory passes widgets
directly into HomePageLayout without any config-driven layout processing. The example app config
also no longer provides widgetLayout, confirming the removal of this configuration path.

workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageLayoutExtension.tsx[20-48]
workspaces/homepage/app-config.yaml[13-19]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
NFS previously supported `home-page-layout:home/dynamic-homepage-layout.config.widgetLayout` to drive breakpoint layouts and (in read-only mode) priority ordering. The extension schema and factory logic were changed to drop this configuration entirely, which can break existing deployments and reduces resilience when backend-driven defaults are unavailable.

### Issue Context
- The extension schema no longer declares `widgetLayout`.
- The factory now passes `widgets` through unchanged to `HomePageLayout`.
- Example app config removed `widgetLayout`, but README still describes it.

### Fix Focus Areas
- workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageLayoutExtension.tsx[29-48]
- workspaces/homepage/plugins/homepage/src/alpha/components/HomePageLayout.tsx[94-111]

### What to change
1. Re-introduce `widgetLayout` as an optional config schema field for back-compat.
2. If backend defaults are unavailable (`mergedWidgets` is undefined), apply `widgetLayout` processing to the extension-provided `widgets` before rendering (inject `breakpointLayouts`, and preserve previous read-only priority sorting behavior).
3. If you intentionally want to deprecate `widgetLayout`, then:
  - Keep schema accepting it but log a deprecation warning and/or translate it into backend-defaults-like layouts.
  - Update docs to make backend defaults required for deterministic layout.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@rhdh-qodo-merge rhdh-qodo-merge Bot added documentation Improvements or additions to documentation enhancement New feature or request Tests labels Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request Tests workspace/homepage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant