feat(extensions): migrate to NFS#2527
Conversation
Review Summary by QodoMigrate extensions plugin to new frontend system with dual app mode support
WalkthroughsDescription• **Migrated extensions plugin to new frontend system (NFS)**: Restructured the extensions workspace by moving the existing app to app-legacy and introducing a new NFS-based app package with modern frontend architecture • **Implemented comprehensive E2E test suite**: Added extensive Playwright tests for extensions catalog with support for multiple locales, filters, badges, and accessibility validation • **Created NFS app modules**: Developed sign-in, navigation, and app shell modules using the new frontend plugin API with lazy-loaded components and modular architecture • **Added alpha components and APIs**: Implemented API blueprints for extensionApi and dynamicPluginsInfoApi with dependency injection, and created NFS-compatible page and nav item blueprints • **Dual app mode support**: Added configuration for running both legacy and NFS apps with separate dev entry points, mock data, and test scripts • **Updated plugin dependencies**: Migrated from @backstage/app-defaults to @backstage/frontend-defaults and added NFS-specific dependencies • **Comprehensive documentation**: Updated README with setup instructions for both new frontend system and legacy configurations • **Fixed error page handling**: Simplified error page component usage by removing useApp() hook dependency Diagramflowchart LR
A["Legacy App<br/>app-legacy/"] -->|"preserved"| B["Original Routes<br/>& Plugins"]
C["NFS App<br/>app/"] -->|"new"| D["Frontend Modules<br/>nav, signIn, app"]
E["Extensions Plugin"] -->|"alpha exports"| F["NFS Blueprints<br/>page, navItem"]
E -->|"legacy support"| G["Dev Entry Points<br/>legacy.tsx, index.tsx"]
H["E2E Tests"] -->|"covers"| I["Both App Modes<br/>Legacy & NFS"]
J["Mock APIs"] -->|"provides"| K["Dev & Test Data<br/>extensions, plugins"]
File Changes1. workspaces/extensions/plugins/extensions/dev/__data__/mockExtensions.ts
|
Code Review by Qodo
1. Extensions plugin omitted
|
88dd164 to
d258b37
Compare
|
Important This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior. Changed Packages
|
d258b37 to
b0b32e2
Compare
| import { createApp } from '@backstage/frontend-defaults'; | ||
| import { extensionsTranslationsModule } from '@red-hat-developer-hub/backstage-plugin-extensions/alpha'; | ||
| import { navModule } from './modules/nav'; | ||
| import { signInModule } from './modules/signIn'; | ||
|
|
||
| import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; | ||
| import { orgPlugin } from '@backstage/plugin-org'; | ||
| import { SearchPage } from '@backstage/plugin-search'; | ||
| import { | ||
| TechDocsIndexPage, | ||
| techdocsPlugin, | ||
| TechDocsReaderPage, | ||
| } from '@backstage/plugin-techdocs'; | ||
| import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; | ||
| import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; | ||
| import { UserSettingsPage } from '@backstage/plugin-user-settings'; | ||
| import { | ||
| AlertDisplay, | ||
| OAuthRequestDialog, | ||
| SignInPage, | ||
| } from '@backstage/core-components'; | ||
| import { createApp } from '@backstage/app-defaults'; | ||
| import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; | ||
| import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; | ||
| import { RequirePermission } from '@backstage/plugin-permission-react'; | ||
| import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; | ||
| import { githubAuthApiRef } from '@backstage/core-plugin-api'; | ||
|
|
||
| import { getAllThemes } from '@red-hat-developer-hub/backstage-plugin-theme'; | ||
|
|
||
| import { DynamicExtensionsPluginRouter as Extensions } from '@red-hat-developer-hub/backstage-plugin-extensions'; | ||
| import { extensionsTranslations } from '@red-hat-developer-hub/backstage-plugin-extensions/alpha'; | ||
|
|
||
| import { apis } from './apis'; | ||
| import { entityPage } from './components/catalog/EntityPage'; | ||
| import { searchPage } from './components/search/SearchPage'; | ||
| import { Root } from './components/Root'; | ||
|
|
||
| const app = createApp({ | ||
| apis, | ||
| __experimentalTranslations: { | ||
| availableLanguages: ['en', 'de', 'es', 'fr', 'it', 'ja'], | ||
| resources: [extensionsTranslations], | ||
| }, | ||
| bindRoutes({ bind }) { | ||
| bind(catalogPlugin.externalRoutes, { | ||
| createComponent: scaffolderPlugin.routes.root, | ||
| viewTechDoc: techdocsPlugin.routes.docRoot, | ||
| createFromTemplate: scaffolderPlugin.routes.selectedTemplate, | ||
| }); | ||
| bind(apiDocsPlugin.externalRoutes, { | ||
| registerApi: catalogImportPlugin.routes.importPage, | ||
| }); | ||
| bind(scaffolderPlugin.externalRoutes, { | ||
| registerComponent: catalogImportPlugin.routes.importPage, | ||
| viewTechDoc: techdocsPlugin.routes.docRoot, | ||
| }); | ||
| bind(orgPlugin.externalRoutes, { | ||
| catalogIndex: catalogPlugin.routes.catalogIndex, | ||
| }); | ||
| }, | ||
| components: { | ||
| SignInPage: props => ( | ||
| <SignInPage | ||
| {...props} | ||
| auto | ||
| providers={[ | ||
| 'guest', | ||
| { | ||
| id: 'github-auth-provider', | ||
| title: 'GitHub', | ||
| message: 'Sign in using GitHub', | ||
| apiRef: githubAuthApiRef, | ||
| }, | ||
| ]} | ||
| /> | ||
| ), | ||
| }, | ||
| themes: getAllThemes(), | ||
| export default createApp({ | ||
| features: [ navModule, extensionsTranslationsModule, signInModule], | ||
| }); |
There was a problem hiding this comment.
1. Extensions plugin omitted 🐞 Bug ✓ Correctness
The NFS app only registers nav/sign-in/translations modules and never registers the Extensions NFS plugin, so the Extensions page/nav-item extensions are not created in NFS mode and NFS e2e tests that navigate via the “Extensions” link will fail.
Agent Prompt
## Issue description
The NFS app (`workspaces/extensions/packages/app`) does not include the Extensions NFS plugin in its `createApp({ features: [...] })` list, so the Extensions page and sidebar nav item won’t be registered in NFS mode.
## Issue Context
The plugin README states that adding `extensionsPlugin` to the `features` array is what provides `/extensions` and the sidebar item. The NFS e2e tests navigate via an "Extensions" link.
## Fix Focus Areas
- workspaces/extensions/packages/app/src/App.tsx[17-24]
- workspaces/extensions/plugins/extensions/README.md[54-76]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
I agree with the Quodo review comment. There's no extensions plugin getting registered in the App.
There was a problem hiding this comment.
Code Implementation 🛠️
Implementation: Register the Extensions plugin in the NFS app by importing extensionsPlugin and adding it to the features array in createApp, alongside the existing nav, translations, and sign-in modules.
| import { createApp } from '@backstage/frontend-defaults'; | |
| import { extensionsTranslationsModule } from '@red-hat-developer-hub/backstage-plugin-extensions/alpha'; | |
| import { navModule } from './modules/nav'; | |
| import { signInModule } from './modules/signIn'; | |
| import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; | |
| import { orgPlugin } from '@backstage/plugin-org'; | |
| import { SearchPage } from '@backstage/plugin-search'; | |
| import { | |
| TechDocsIndexPage, | |
| techdocsPlugin, | |
| TechDocsReaderPage, | |
| } from '@backstage/plugin-techdocs'; | |
| import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; | |
| import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; | |
| import { UserSettingsPage } from '@backstage/plugin-user-settings'; | |
| import { | |
| AlertDisplay, | |
| OAuthRequestDialog, | |
| SignInPage, | |
| } from '@backstage/core-components'; | |
| import { createApp } from '@backstage/app-defaults'; | |
| import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; | |
| import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; | |
| import { RequirePermission } from '@backstage/plugin-permission-react'; | |
| import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; | |
| import { githubAuthApiRef } from '@backstage/core-plugin-api'; | |
| import { getAllThemes } from '@red-hat-developer-hub/backstage-plugin-theme'; | |
| import { DynamicExtensionsPluginRouter as Extensions } from '@red-hat-developer-hub/backstage-plugin-extensions'; | |
| import { extensionsTranslations } from '@red-hat-developer-hub/backstage-plugin-extensions/alpha'; | |
| import { apis } from './apis'; | |
| import { entityPage } from './components/catalog/EntityPage'; | |
| import { searchPage } from './components/search/SearchPage'; | |
| import { Root } from './components/Root'; | |
| const app = createApp({ | |
| apis, | |
| __experimentalTranslations: { | |
| availableLanguages: ['en', 'de', 'es', 'fr', 'it', 'ja'], | |
| resources: [extensionsTranslations], | |
| }, | |
| bindRoutes({ bind }) { | |
| bind(catalogPlugin.externalRoutes, { | |
| createComponent: scaffolderPlugin.routes.root, | |
| viewTechDoc: techdocsPlugin.routes.docRoot, | |
| createFromTemplate: scaffolderPlugin.routes.selectedTemplate, | |
| }); | |
| bind(apiDocsPlugin.externalRoutes, { | |
| registerApi: catalogImportPlugin.routes.importPage, | |
| }); | |
| bind(scaffolderPlugin.externalRoutes, { | |
| registerComponent: catalogImportPlugin.routes.importPage, | |
| viewTechDoc: techdocsPlugin.routes.docRoot, | |
| }); | |
| bind(orgPlugin.externalRoutes, { | |
| catalogIndex: catalogPlugin.routes.catalogIndex, | |
| }); | |
| }, | |
| components: { | |
| SignInPage: props => ( | |
| <SignInPage | |
| {...props} | |
| auto | |
| providers={[ | |
| 'guest', | |
| { | |
| id: 'github-auth-provider', | |
| title: 'GitHub', | |
| message: 'Sign in using GitHub', | |
| apiRef: githubAuthApiRef, | |
| }, | |
| ]} | |
| /> | |
| ), | |
| }, | |
| themes: getAllThemes(), | |
| export default createApp({ | |
| features: [ navModule, extensionsTranslationsModule, signInModule], | |
| }); | |
| import { createApp } from '@backstage/frontend-defaults'; | |
| import { extensionsPlugin } from '@red-hat-developer-hub/backstage-plugin-extensions'; | |
| import { extensionsTranslationsModule } from '@red-hat-developer-hub/backstage-plugin-extensions/alpha'; | |
| import { navModule } from './modules/nav'; | |
| import { signInModule } from './modules/signIn'; | |
| export default createApp({ | |
| features: [navModule, extensionsPlugin, extensionsTranslationsModule, signInModule], | |
| }); |
📄 References
- No matching references available
See review comment here
| pluginsWithPermission: | ||
| - catalog | ||
| - extensions | ||
| admin: | ||
| superUsers: | ||
| - name: user:development/guest | ||
| users: | ||
| - name: user:development/guest |
There was a problem hiding this comment.
2. Guest rbac superuser 🐞 Bug ⛨ Security
The base app-config grants user:development/guest RBAC superuser/admin privileges, and the production config explicitly layers on top of app-config.yaml, meaning a production setup can unintentionally grant full admin to an unauthenticated guest.
Agent Prompt
## Issue description
`workspaces/extensions/app-config.yaml` grants RBAC superuser/admin privileges to the guest user. Since production config layers on top of `app-config.yaml`, this can leak into production.
## Issue Context
Guest auth is enabled in configs; RBAC superUsers should never include an unauthenticated guest identity in shared configs.
## Fix Focus Areas
- workspaces/extensions/app-config.yaml[135-147]
- workspaces/extensions/app-config.production.yaml[1-55]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
e7340c8 to
10e2ec8
Compare
10e2ec8 to
2557353
Compare
| import { createApp } from '@backstage/frontend-defaults'; | ||
| import { extensionsTranslationsModule } from '@red-hat-developer-hub/backstage-plugin-extensions/alpha'; | ||
| import { navModule } from './modules/nav'; | ||
| import { signInModule } from './modules/signIn'; | ||
|
|
||
| import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; | ||
| import { orgPlugin } from '@backstage/plugin-org'; | ||
| import { SearchPage } from '@backstage/plugin-search'; | ||
| import { | ||
| TechDocsIndexPage, | ||
| techdocsPlugin, | ||
| TechDocsReaderPage, | ||
| } from '@backstage/plugin-techdocs'; | ||
| import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; | ||
| import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; | ||
| import { UserSettingsPage } from '@backstage/plugin-user-settings'; | ||
| import { | ||
| AlertDisplay, | ||
| OAuthRequestDialog, | ||
| SignInPage, | ||
| } from '@backstage/core-components'; | ||
| import { createApp } from '@backstage/app-defaults'; | ||
| import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; | ||
| import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; | ||
| import { RequirePermission } from '@backstage/plugin-permission-react'; | ||
| import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; | ||
| import { githubAuthApiRef } from '@backstage/core-plugin-api'; | ||
|
|
||
| import { getAllThemes } from '@red-hat-developer-hub/backstage-plugin-theme'; | ||
|
|
||
| import { DynamicExtensionsPluginRouter as Extensions } from '@red-hat-developer-hub/backstage-plugin-extensions'; | ||
| import { extensionsTranslations } from '@red-hat-developer-hub/backstage-plugin-extensions/alpha'; | ||
|
|
||
| import { apis } from './apis'; | ||
| import { entityPage } from './components/catalog/EntityPage'; | ||
| import { searchPage } from './components/search/SearchPage'; | ||
| import { Root } from './components/Root'; | ||
|
|
||
| const app = createApp({ | ||
| apis, | ||
| __experimentalTranslations: { | ||
| availableLanguages: ['en', 'de', 'es', 'fr', 'it', 'ja'], | ||
| resources: [extensionsTranslations], | ||
| }, | ||
| bindRoutes({ bind }) { | ||
| bind(catalogPlugin.externalRoutes, { | ||
| createComponent: scaffolderPlugin.routes.root, | ||
| viewTechDoc: techdocsPlugin.routes.docRoot, | ||
| createFromTemplate: scaffolderPlugin.routes.selectedTemplate, | ||
| }); | ||
| bind(apiDocsPlugin.externalRoutes, { | ||
| registerApi: catalogImportPlugin.routes.importPage, | ||
| }); | ||
| bind(scaffolderPlugin.externalRoutes, { | ||
| registerComponent: catalogImportPlugin.routes.importPage, | ||
| viewTechDoc: techdocsPlugin.routes.docRoot, | ||
| }); | ||
| bind(orgPlugin.externalRoutes, { | ||
| catalogIndex: catalogPlugin.routes.catalogIndex, | ||
| }); | ||
| }, | ||
| components: { | ||
| SignInPage: props => ( | ||
| <SignInPage | ||
| {...props} | ||
| auto | ||
| providers={[ | ||
| 'guest', | ||
| { | ||
| id: 'github-auth-provider', | ||
| title: 'GitHub', | ||
| message: 'Sign in using GitHub', | ||
| apiRef: githubAuthApiRef, | ||
| }, | ||
| ]} | ||
| /> | ||
| ), | ||
| }, | ||
| themes: getAllThemes(), | ||
| export default createApp({ | ||
| features: [ navModule, extensionsTranslationsModule, signInModule], | ||
| }); |
There was a problem hiding this comment.
I agree with the Quodo review comment. There's no extensions plugin getting registered in the App.
2557353 to
5092c4a
Compare
|
@rohitkrai03 the extensions plugin is exported as a default and I have the following in my app-config |
|
@debsmita1 Home page is changed to catalog in the nfs, which is prompting the tests to fail. So, will you make it consistent, or do you want me to change it in the tests
|
5092c4a to
e518a52
Compare
e518a52 to
ebe7847
Compare
There was a problem hiding this comment.
@Debsmita Shouldn't the extension tests skip E2E for DE/ES locales? Based on my findings from PR 2502, I’ve logged a Jira bug RHDHBUGS-2793 which hasn't been fixed yet.
Please take a look at the changes in the extensions E2E from PR#2502 and apply similar adjustments to resolve the E2E CI issue.
Fix tests for extensions nfs
|






Hey, I just made a Pull Request!
Resolves:
https://issues.redhat.com/browse/RHIDP-11629
Solution description:
Screenshots:

dev mode
✔️ Checklist