From 10e5d6f8aa7a04d86f723bdb3f09773ef734cf5f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:00:46 +0200 Subject: [PATCH] Remove NavItemBlueprint in favor of page-based nav discovery Drop the deprecated NavItemBlueprint from the public API and migrate core plugins to set title and icon on PageBlueprint instead. AppNav keeps backward compatibility for legacy nav-item extensions via an internal core.nav-item.target data ref. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .changeset/remove-nav-item-blueprint.md | 17 ++++ app-config.yaml | 7 -- .../app-frontend-system-migration/SKILL.md | 2 +- .../skills/mui-to-bui-migration/SKILL.md | 3 +- .../search/declarative-integration.md | 5 +- .../architecture/15-plugins.md | 24 ++--- .../building-apps/03-built-in-extensions.md | 2 +- .../building-apps/08-migrating.md | 11 +-- .../building-plugins/01-index.md | 19 ++-- .../03-common-extension-blueprints.md | 4 - .../templates/default-app/app-config.yaml.hbs | 6 -- packages/frontend-plugin-api/report.api.md | 37 ------- .../src/blueprints/NavItemBlueprint.test.tsx | 98 ------------------- .../src/blueprints/NavItemBlueprint.ts | 66 ------------- .../src/blueprints/index.ts | 1 - .../src/app/renderInTestApp.tsx | 11 ++- plugins/api-docs/README-alpha.md | 92 ----------------- plugins/api-docs/report-alpha.api.md | 26 ----- plugins/api-docs/src/alpha.tsx | 12 +-- plugins/app-visualizer/README.md | 7 +- plugins/app-visualizer/report.api.md | 26 ----- .../AppVisualizerPage/DetailedVisualizer.tsx | 10 +- plugins/app-visualizer/src/plugin.tsx | 11 +-- plugins/app/src/extensions/AppNav.test.tsx | 70 ++++++------- plugins/app/src/extensions/AppNav.tsx | 6 +- .../src/extensions/legacyNavItem.ts} | 30 +++--- .../report-alpha.api.md | 26 ----- .../src/alpha/plugin.tsx | 13 +-- plugins/catalog/report-alpha.api.md | 26 ----- plugins/catalog/src/alpha/plugin.tsx | 2 - plugins/devtools/report-alpha.api.md | 26 ----- plugins/devtools/src/alpha/plugin.tsx | 12 +-- plugins/home/report-alpha.api.md | 26 ----- plugins/home/src/alpha.test.ts | 5 - plugins/home/src/alpha.tsx | 12 +-- plugins/org/README-alpha.md | 84 +++++----------- plugins/scaffolder/report-alpha.api.md | 26 ----- plugins/scaffolder/src/alpha/extensions.tsx | 12 +-- plugins/scaffolder/src/alpha/plugin.tsx | 2 - plugins/search/report-alpha.api.md | 53 ---------- plugins/search/src/alpha.tsx | 14 +-- plugins/techdocs/report-alpha.api.md | 26 ----- plugins/techdocs/src/alpha/index.tsx | 13 +-- plugins/user-settings/report-alpha.api.md | 77 +++------------ plugins/user-settings/src/alpha.tsx | 12 +-- 45 files changed, 165 insertions(+), 905 deletions(-) create mode 100644 .changeset/remove-nav-item-blueprint.md delete mode 100644 packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx delete mode 100644 packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts rename plugins/{catalog/src/alpha/navItems.tsx => app/src/extensions/legacyNavItem.ts} (53%) diff --git a/.changeset/remove-nav-item-blueprint.md b/.changeset/remove-nav-item-blueprint.md new file mode 100644 index 0000000000..b54e59f74c --- /dev/null +++ b/.changeset/remove-nav-item-blueprint.md @@ -0,0 +1,17 @@ +--- +'@backstage/frontend-plugin-api': minor +'@backstage/plugin-app': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-search': patch +'@backstage/plugin-home': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-user-settings': patch +'@backstage/plugin-devtools': patch +'@backstage/plugin-catalog-unprocessed-entities': patch +'@backstage/plugin-app-visualizer': patch +'@backstage/frontend-test-utils': patch +--- + +Removed the deprecated `NavItemBlueprint`. Navigation items are now discovered from page extensions via their `title` and `icon` params. The app nav extension still accepts legacy `nav-item` extensions for backward compatibility. diff --git a/app-config.yaml b/app-config.yaml index 11f812bb3d..42e7a62966 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -63,13 +63,6 @@ app: # - apis.plugin.graphiql.browse.gitlab: true # - graphiql-endpoint:graphiql/gitlab: true - - nav-item:search: false - - nav-item:user-settings: false - - nav-item:catalog - - nav-item:api-docs - - nav-item:scaffolder - - nav-item:app-visualizer - # Opt in to the experimental BUI scaffolder form theme - sub-page:scaffolder/templates: config: diff --git a/docs/.well-known/skills/app-frontend-system-migration/SKILL.md b/docs/.well-known/skills/app-frontend-system-migration/SKILL.md index 0bfbf0714c..9bb12bd700 100644 --- a/docs/.well-known/skills/app-frontend-system-migration/SKILL.md +++ b/docs/.well-known/skills/app-frontend-system-migration/SKILL.md @@ -60,7 +60,7 @@ Even with feature discovery enabled, you can disable specific extensions via con app: extensions: - page:techdocs: false - - nav-item:search: false + - page:search: false ``` ### How Discovery Works with Manual Imports diff --git a/docs/.well-known/skills/mui-to-bui-migration/SKILL.md b/docs/.well-known/skills/mui-to-bui-migration/SKILL.md index c47286ec30..80ea04ba9c 100644 --- a/docs/.well-known/skills/mui-to-bui-migration/SKILL.md +++ b/docs/.well-known/skills/mui-to-bui-migration/SKILL.md @@ -882,8 +882,7 @@ import { CheckboxGroup, Checkbox } from '@backstage/ui'; Some Backstage APIs still require MUI-compatible icon types: -- **NavItemBlueprint** (`@backstage/frontend-plugin-api`): The `icon` prop expects MUI `IconComponent` type. Remix icons - are not type-compatible. +- **PageBlueprint** (`@backstage/frontend-plugin-api`): The `icon` param on page extensions expects an `IconElement`. MUI icon components can still be used via ``. - **Timeline** (`@material-ui/lab`): No BUI equivalent exists. For these cases, keep using MUI components. diff --git a/docs/features/search/declarative-integration.md b/docs/features/search/declarative-integration.md index cea5225f2f..5be84e34fc 100644 --- a/docs/features/search/declarative-integration.md +++ b/docs/features/search/declarative-integration.md @@ -52,16 +52,15 @@ _Example disabling the search page extension_ app: extensions: - page:search: false # ✨ - - nav-item:search: false # ✨ ``` -_Example setting the search sidebar item title_ +_Example setting the search page title (used in the sidebar)_ ```yaml # app-config.yaml app: extensions: - - nav-item:search: # ✨ + - page:search: # ✨ config: title: 'Search Page' ``` diff --git a/docs/frontend-system/architecture/15-plugins.md b/docs/frontend-system/architecture/15-plugins.md index d124a0e61d..2d16223b68 100644 --- a/docs/frontend-system/architecture/15-plugins.md +++ b/docs/frontend-system/architecture/15-plugins.md @@ -129,26 +129,20 @@ A plugin might not always behave exactly the way you want. It could be that you ```tsx import plugin from '@backstage/plugin-catalog'; +import { PageBlueprint } from '@backstage/frontend-plugin-api'; +import CustomCatalogIcon from '@material-ui/icons/Category'; export default plugin.withOverrides({ // These overrides are merged with the original extensions extensions: [ - // Override the catalog nav item to use a custom icon - plugin.getExtension('nav-item:catalog').override({ - factory: origFactory => [ - NavItemBlueprint.dataRefs.target({ - ...origFactory().get(NavItemBlueprint.dataRefs.target), - icon: CustomCatalogIcon, + // Override the catalog index page with a custom icon and implementation + plugin.getExtension('page:catalog').override({ + factory: origFactory => + origFactory({ + icon: , + loader: () => + import('./CustomCatalogIndexPage').then(m => ), }), - ], - }), - // Override the catalog index page with a completely custom implementation - PageBlueprint.make({ - params: { - path: '/catalog', - routeRef: plugin.routes.catalogIndex, - loader: () => import('./CustomCatalogIndexPage').then(m => ), - }, }), ], }); diff --git a/docs/frontend-system/building-apps/03-built-in-extensions.md b/docs/frontend-system/building-apps/03-built-in-extensions.md index 637aded411..f61f3112bf 100644 --- a/docs/frontend-system/building-apps/03-built-in-extensions.md +++ b/docs/frontend-system/building-apps/03-built-in-extensions.md @@ -164,7 +164,7 @@ Extension responsible for rendering the logo and items in the app's sidebar. | Name | Description | Type | Optional | Default | Extension creator | | ------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------- | | content | Overrides the default content of the navbar. | [NavContentBlueprint.dataRefs.component](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.NavContentBlueprint.html) | true | - | [NavContentBlueprint](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.NavContentBlueprint.html) | -| items | Nav items target objects. | [createNavItemExtension.targetDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension.targetdataref) | true | - | [createNavItemExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) | +| items | Legacy nav item target objects. | `core.nav-item.target` | true | - | Legacy extensions only; nav items are auto-discovered from page extensions. | ### App routes diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 7265cd7cf2..cbdc3d1f92 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -686,7 +686,7 @@ createApp({ #### App Root Sidebar -New apps feature a built-in sidebar extension which is created by using the `NavContentBlueprint` in `src/modules/nav/Sidebar.tsx`. The default implementation of the sidebar in this blueprint will render some items explicitly in different groups, and then render the rest of the items. Nav items are auto-discovered from page extensions registered under `app/routes` (no explicit `NavItemBlueprint` required), with metadata from page config, nav item extensions, or plugin defaults. +New apps feature a built-in sidebar extension which is created by using the `NavContentBlueprint` in `src/modules/nav/Sidebar.tsx`. The default implementation of the sidebar in this blueprint will render some items explicitly in different groups, and then render the rest of the items. Nav items are auto-discovered from page extensions registered under `app/routes`, with metadata from page config or plugin defaults. In order to migrate your existing sidebar, you will want to create an override for the `app/nav` extension. You can do this by copying the standard of having a `src/modules/nav/` folder, which can contain an extension which you can install into the `app` in the form of a `module`. @@ -740,14 +740,7 @@ The deprecated `items` prop (a flat list compatible with `: false`. #### App Root Routes diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index 7b41311ea4..60f6f6ea4a 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -46,7 +46,7 @@ The plugin ID should be a lowercase dash-separated string, while the plugin inst The plugin that we created above is empty, and doesn't provide any actual functionality. To add functionality to a plugin you need to create and provide it with one or more [extensions](../architecture/20-extensions.md). Let's continue by adding a standalone page to our plugin, as well as a navigation item that allows users to navigate to the page. -To create a new extension you typically use pre-defined [extension blueprints](../architecture/23-extension-blueprints.md), provided either by the framework itself or by other plugins. In this case we'll use `PageBlueprint` and `NavItemBlueprint`, both from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/36-routes.md#creating-a-route-reference) to use as a reference for our page, allowing us to dynamically create URLs that link to our page. +To create a new extension you typically use pre-defined [extension blueprints](../architecture/23-extension-blueprints.md), provided either by the framework itself or by other plugins. In this case we'll use `PageBlueprint` from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/36-routes.md#creating-a-route-reference) to use as a reference for our page, allowing us to dynamically create URLs that link to our page. ```tsx title="in src/routes.ts" import { createRouteRef } from '@backstage/frontend-plugin-api'; @@ -62,8 +62,8 @@ export const rootRouteRef = createRouteRef(); import { createFrontendPlugin, PageBlueprint, - NavItemBlueprint, } from '@backstage/frontend-plugin-api'; +import ExampleIcon from '@material-ui/icons/Extension'; import { rootRouteRef } from './routes'; // Note that these extensions aren't exported, only the plugin itself is. @@ -75,6 +75,10 @@ const examplePage = PageBlueprint.make({ // This is the default path of this page, but integrators are free to override it path: '/example', + // The title and icon are used to populate the app sidebar automatically + title: 'Example', + icon: , + // Page extensions are always dynamically loaded using React.lazy(). // All of the functionality of this page is implemented in the // ExamplePage component, which is a regular React component. @@ -84,19 +88,10 @@ const examplePage = PageBlueprint.make({ }, }); -// This nav item is provided to the app.nav extension, and will by default be rendered as a sidebar item -const exampleNavItem = NavItemBlueprint.make({ - params: { - routeRef: rootRouteRef, - title: 'Example', - icon: ExampleIcon, // Custom SvgIcon, or one from the Material UI icon library - }, -}); - // The same plugin as above, now with the extensions added export const examplePlugin = createFrontendPlugin({ pluginId: 'example', - extensions: [examplePage, exampleNavItem], + extensions: [examplePage], // We can also make routes available to other plugins. // highlight-start routes: { diff --git a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md index e8c9ea2ffb..f9a3bb696f 100644 --- a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md +++ b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md @@ -15,10 +15,6 @@ These are the [extension blueprints](../architecture/23-extension-blueprints.md) An API extension is used to add or override [Utility API factories](../utility-apis/01-index.md) in the app. They are commonly used by plugins for both internal and shared APIs. There are also many built-in Api extensions provided by the framework that you are able to override. -### NavItem (deprecated) - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.NavItemBlueprint.html) - -The `NavItemBlueprint` is deprecated. The app now auto-discovers navigation items from page extensions, so explicit nav item extensions are no longer needed. To migrate, ensure your plugin and/or page extensions have a `title` and `icon` set — these are used to populate the sidebar automatically. - ### Page - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.PageBlueprint.html) Page extensions provide content for a particular route in the app. By default pages are attached to the app routes extensions, which renders the root routes. Pages automatically inherit the plugin's `title` and `icon` as defaults, which can be overridden per-page via `PageBlueprint` params. diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index 58a48a3e95..fbcc18165e 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -6,12 +6,6 @@ app: packages: all extensions: - # Disable the nav items that we're manually rendering in packages/app/src/modules/nav/Sidebar.tsx - - nav-item:search: false - - nav-item:user-settings: false - - nav-item:catalog: false - - nav-item:scaffolder: false - # Configure the catalog index page to be the root page, this is normally mounted on /catalog - page:catalog: config: diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index e908bc7184..9c7911b8c8 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -1821,43 +1821,6 @@ export const microsoftAuthApiRef: ApiRef_2< readonly $$type: '@backstage/ApiRef'; }; -// @public @deprecated -export const NavItemBlueprint: ExtensionBlueprint_2<{ - kind: 'nav-item'; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }; - output: ExtensionDataRef_2< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - config: { - title: string | undefined; - }; - configInput: { - title?: string | undefined; - }; - dataRefs: { - target: ConfigurableExtensionDataRef_2< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - }; -}>; - // @public (undocumented) export const NotFoundErrorPage: { (props: NotFoundErrorPageProps): JSX.Element | null; diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx deleted file mode 100644 index 0255d66604..0000000000 --- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { createRouteRef } from '../routing'; -import { NavItemBlueprint } from './NavItemBlueprint'; - -describe('NavItemBlueprint', () => { - const mockRouteRef = createRouteRef(); - const MockIcon = () => null; - - it('should return an extension with sensible defaults', () => { - const extension = NavItemBlueprint.make({ - params: { - icon: MockIcon, - routeRef: mockRouteRef, - title: 'TEST', - }, - }); - - expect(extension).toMatchInlineSnapshot(` - { - "$$type": "@backstage/ExtensionDefinition", - "T": undefined, - "attachTo": { - "id": "app/nav", - "input": "items", - }, - "configSchema": { - "parse": [Function], - "schema": [Function], - }, - "disabled": false, - "factory": [Function], - "if": undefined, - "inputs": {}, - "kind": "nav-item", - "name": undefined, - "output": [ - [Function], - ], - "override": [Function], - "toString": [Function], - "version": "v2", - } - `); - }); - - it('should return the correct extension data', () => { - const extension = NavItemBlueprint.make({ - params: { - icon: MockIcon, - routeRef: mockRouteRef, - title: 'TEST', - }, - }); - - const tester = createExtensionTester(extension); - - expect(tester.get(NavItemBlueprint.dataRefs.target)).toEqual({ - title: 'TEST', - icon: MockIcon, - routeRef: mockRouteRef, - }); - }); - - it('should allow overriding of the title using config', () => { - const extension = NavItemBlueprint.make({ - params: { - icon: MockIcon, - routeRef: mockRouteRef, - title: 'TEST', - }, - }); - - const tester = createExtensionTester(extension, { - config: { title: 'OVERRIDDEN' }, - }); - - expect(tester.get(NavItemBlueprint.dataRefs.target)).toEqual({ - title: 'OVERRIDDEN', - icon: MockIcon, - routeRef: mockRouteRef, - }); - }); -}); diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts deleted file mode 100644 index d3e967fde8..0000000000 --- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { z } from 'zod/v4'; -import { IconComponent } from '../icons/types'; -import { RouteRef } from '../routing'; -import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; - -// TODO(Rugvip): Should this be broken apart into separate refs? title/icon/routeRef -const targetDataRef = createExtensionDataRef<{ - title: string; - icon: IconComponent; - routeRef: RouteRef; -}>().with({ id: 'core.nav-item.target' }); - -/** - * Creates extensions that make up the items of the nav bar. - * - * @public - * @deprecated Nav items are now automatically inferred from `PageBlueprint` - * extensions based on their `title` and `icon` params. You can remove your - * `NavItemBlueprint` usage and instead pass `title` and `icon` directly to - * the `PageBlueprint`. - */ -export const NavItemBlueprint = createExtensionBlueprint({ - kind: 'nav-item', - attachTo: { id: 'app/nav', input: 'items' }, - output: [targetDataRef], - dataRefs: { - target: targetDataRef, - }, - factory: ( - { - icon, - routeRef, - title, - }: { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - { config }, - ) => [ - targetDataRef({ - title: config.title ?? title, - icon, - routeRef, - }), - ], - configSchema: { - title: z.string().optional(), - }, -}); diff --git a/packages/frontend-plugin-api/src/blueprints/index.ts b/packages/frontend-plugin-api/src/blueprints/index.ts index a571f6ef73..df57ebb050 100644 --- a/packages/frontend-plugin-api/src/blueprints/index.ts +++ b/packages/frontend-plugin-api/src/blueprints/index.ts @@ -20,7 +20,6 @@ export { } from './AnalyticsImplementationBlueprint'; export { ApiBlueprint } from './ApiBlueprint'; export { AppRootElementBlueprint } from './AppRootElementBlueprint'; -export { NavItemBlueprint } from './NavItemBlueprint'; export { PageBlueprint } from './PageBlueprint'; export { SubPageBlueprint } from './SubPageBlueprint'; export { PluginHeaderActionBlueprint } from './PluginHeaderActionBlueprint'; diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index fa4dc2023a..3d30ae3d6b 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -22,12 +22,12 @@ import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { createExtension, + createExtensionDataRef, ExtensionDefinition, coreExtensionData, RouteRef, useRouteRef, IconComponent, - NavItemBlueprint, createFrontendPlugin, FrontendFeature, createFrontendModule, @@ -49,6 +49,13 @@ const DEFAULT_MOCK_CONFIG = { backend: { baseUrl: 'http://localhost:7007' }, }; +// Must match the data ref in @backstage/plugin-app/src/extensions/legacyNavItem.ts +const legacyNavItemTargetDataRef = createExtensionDataRef<{ + title: string; + icon: IconComponent; + routeRef: RouteRef; +}>().with({ id: 'core.nav-item.target' }); + /** * Options to customize the behavior of the test app. * @public @@ -143,7 +150,7 @@ const appPluginOverride = appPlugin.withOverrides({ {inputs.items.map( (item: (typeof inputs.items)[number], index: number) => { const { icon, title, routeRef } = item.get( - NavItemBlueprint.dataRefs.target, + legacyNavItemTargetDataRef, ); return ( diff --git a/plugins/api-docs/README-alpha.md b/plugins/api-docs/README-alpha.md index a4c3979787..939999aa71 100644 --- a/plugins/api-docs/README-alpha.md +++ b/plugins/api-docs/README-alpha.md @@ -32,7 +32,6 @@ To link that a component provides or consumes an API, see the [`providesApis`](h - [Packages](#packages) - [Routes](#routes) - [Extensions](#extensions) - - [Apis Nav Item](#apis-nav-item) - [Apis Explorer Page](#apis-explore-page) - [Apis Entity Cards](#apis-entities-cards) - [Has Apis Entity Card](#has-apis-entity-card) @@ -135,94 +134,6 @@ Route binding is also possible through code. For more information, see [this](ht ### Extensions -#### Apis Nav Item - -This [nav item](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) extension adds a link to the Apis Explorer page in the main app sidebar. - -| Kind | Namespace | Name | Id | -| ---------- | ---------- | ---- | ------------------- | -| `nav-item` | `api-docs` | - | `nav-item:api-docs` | - -##### Disable - -This extension is enabled by default when you install the `api-docs` plugin, but you can disable it and prevent it from showing up in the sidebar by setting the following configuration: - -```yaml -# app-config.yaml -app: - extensions: - # this is the extension id and it follows the naming pattern bellow: - # /: - # example disabling the apis docs nav item extension - - nav-item:api-docs: false - # or - # - nav-item:api-docs: - # disabled: true -``` - -To enable the extension again, simple remove the previous `nav-item:api-docs: false` configuration or do: - -```yaml -# app-config.yaml -app: - extensions: - # this is the extension id and it follows the naming pattern bellow: - # /: - - nav-item:api-docs - # or - # - nav-item:api-docs: true - # or - # - nav-item:api-docs: - # disabled: false -``` - -##### Config - -The apis nav item can be customized under the `app.extensions.nav-item:api-docs.config` key in `app-config.yaml`. Configurations include: - -```yaml -# app-config.yaml -# example configuring the apis docs nav item extension -app: - extensions: - # this is the extension id and it follows the naming pattern bellow: - # /: - - nav-item:api-docs: - config: - # The nav item title text, defaults to "APIs" - title: 'Apis Explorer' - # The nav item path text, defaults to "/api-docs" - path: '/apis-explorer' -``` - -##### Override - -The apis nav item icon can only be changed by overriding the extension, as the icon cannot be changed via the `app-config.yaml` file. - -Here is an example overriding the nav item extension with a custom icon component: - -```tsx -import { - createFrontendModule, - createNavItemExtension, -} from '@backstage/backstage-plugin-api'; -import { MyCustomApiDocsIcon } from './components'; - -export default createFrontendModule({ - pluginId: 'api-docs', - extensions: [ - createNavItemExtension({ - // It's your choice whether to use the original extension's title or a different one - title: 'APIs', - // Setting a custom icon component - icon: MyCustomApiDocsIcon, - }), - ], -}); -``` - -For more information about where to place extension overrides, see the official [documentation](https://backstage.io/docs/frontend-system/architecture/extension-overrides). - #### Apis Explore Page This `api-docs` plugin installs an "Apis Explore" page extension that helps you visualize apis registered in the Backstage software catalog. @@ -235,9 +146,6 @@ This `api-docs` plugin installs an "Apis Explore" page extension that helps you The explore page extension is enable by default when you install the `api-docs` plugin, for disabling it, set the configuration below: -> [!CAUTION] -> The `api-docs` plugin also install a sidebar item that points to this page, remember to disable the nav item as well otherwise it will point to a not found page. - ```yaml # app-config.yaml # example disabling the apis docs explorer page diff --git a/plugins/api-docs/report-alpha.api.md b/plugins/api-docs/report-alpha.api.md index 630e6aef89..4e3930d765 100644 --- a/plugins/api-docs/report-alpha.api.md +++ b/plugins/api-docs/report-alpha.api.md @@ -15,7 +15,6 @@ import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FilterPredicate } from '@backstage/filter-predicates'; -import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { JSXElementConstructor } from 'react'; @@ -472,31 +471,6 @@ const _default: OverridableFrontendPlugin< filter?: string | FilterPredicate | ((entity: Entity) => boolean); }; }>; - 'nav-item:api-docs': OverridableExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: { - title: string | undefined; - }; - configInput: { - title?: string | undefined; - }; - output: ExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef_2; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef_2; - }; - }>; 'page:api-docs': OverridableExtensionDefinition<{ config: { initiallySelectedFilter: 'all' | 'owned' | 'starred' | undefined; diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index af4190bd13..b810be016e 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -18,7 +18,6 @@ import Grid from '@material-ui/core/Grid'; import { ApiBlueprint, - NavItemBlueprint, PageBlueprint, createFrontendPlugin, } from '@backstage/frontend-plugin-api'; @@ -39,14 +38,6 @@ import { EntityContentBlueprint, } from '@backstage/plugin-catalog-react/alpha'; -const apiDocsNavItem = NavItemBlueprint.make({ - params: { - title: 'APIs', - routeRef: rootRoute, - icon: () => , - }, -}); - const apiDocsConfigApi = ApiBlueprint.make({ name: 'config', params: defineParams => @@ -76,6 +67,8 @@ const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({ return originalFactory({ path: '/api-docs', routeRef: rootRoute, + title: 'APIs', + icon: , loader: () => import('./components/ApiExplorerPage/DefaultApiExplorerPage').then( m => ( @@ -222,7 +215,6 @@ export default createFrontendPlugin({ registerApi: registerComponentRouteRef, }, extensions: [ - apiDocsNavItem, apiDocsConfigApi, apiDocsExplorerPage, apiDocsHasApisEntityCard, diff --git a/plugins/app-visualizer/README.md b/plugins/app-visualizer/README.md index 865cd66746..cf15cdce71 100644 --- a/plugins/app-visualizer/README.md +++ b/plugins/app-visualizer/README.md @@ -4,7 +4,6 @@ A plugin to help explore the structure of your Backstage app. This plugin provides the following extensions: -| ID | Type | Description | Default Config | -| ------------------------- | --------- | ------------------------------------ | ------------------------- | -| `page:app-visualizer` | `Page` | The app visualizer page | `{ path: '/visualizer' }` | -| `nav-item:app-visualizer` | `NavItem` | Nav item for the app visualizer page | | +| ID | Type | Description | Default Config | +| --------------------- | ------ | ----------------------- | ------------------------- | +| `page:app-visualizer` | `Page` | The app visualizer page | `{ path: '/visualizer' }` | diff --git a/plugins/app-visualizer/report.api.md b/plugins/app-visualizer/report.api.md index f8636d8b4e..07a8fcef62 100644 --- a/plugins/app-visualizer/report.api.md +++ b/plugins/app-visualizer/report.api.md @@ -7,7 +7,6 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -19,31 +18,6 @@ const visualizerPlugin: OverridableFrontendPlugin< {}, {}, { - 'nav-item:app-visualizer': OverridableExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: { - title: string | undefined; - }; - configInput: { - title?: string | undefined; - }; - output: ExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }; - }>; 'page:app-visualizer': OverridableExtensionDefinition<{ kind: 'page'; name: undefined; diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx index e88f994627..d3c6bab057 100644 --- a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx @@ -19,11 +19,15 @@ import { ExtensionDataRef, coreExtensionData, ApiBlueprint, - NavItemBlueprint, useApi, routeResolutionApiRef, appTreeApiRef, + createExtensionDataRef, } from '@backstage/frontend-plugin-api'; + +const legacyNavItemTargetDataRef = createExtensionDataRef().with({ + id: 'core.nav-item.target', +}); import { Box, Flex, @@ -86,7 +90,7 @@ const getOutputColor = createOutputColorGenerator( [coreExtensionData.routePath.id]: '#ffeb3b', [coreExtensionData.routeRef.id]: '#9c27b0', [ApiBlueprint.dataRefs.factory.id]: '#2196f3', - [NavItemBlueprint.dataRefs.target.id]: '#ff9800', + [legacyNavItemTargetDataRef.id]: '#ff9800', }, ['#90caf9', '#ffcc80', '#a5d6a7', '#ef9a9a', '#fff59d', '#ce93d8', '#e6ee9c'], @@ -335,7 +339,7 @@ const legendMap = { 'Utility API': ApiBlueprint.dataRefs.factory, 'Route Path': coreExtensionData.routePath, 'Route Ref': coreExtensionData.routeRef, - 'Nav Target': NavItemBlueprint.dataRefs.target, + 'Nav Target': legacyNavItemTargetDataRef, }; function Legend() { diff --git a/plugins/app-visualizer/src/plugin.tsx b/plugins/app-visualizer/src/plugin.tsx index fab8479c54..7a33d7f32e 100644 --- a/plugins/app-visualizer/src/plugin.tsx +++ b/plugins/app-visualizer/src/plugin.tsx @@ -17,7 +17,6 @@ import { createFrontendPlugin, createRouteRef, - NavItemBlueprint, PageBlueprint, PluginHeaderActionBlueprint, SubPageBlueprint, @@ -31,6 +30,7 @@ const appVisualizerPage = PageBlueprint.make({ path: '/visualizer', routeRef: rootRouteRef, title: 'Visualizer', + icon: , }, }); @@ -82,14 +82,6 @@ const copyTreeAsJson = PluginHeaderActionBlueprint.make({ }, }); -export const appVisualizerNavItem = NavItemBlueprint.make({ - params: { - title: 'Visualizer', - icon: () => , - routeRef: rootRouteRef, - }, -}); - /** @public */ export const visualizerPlugin = createFrontendPlugin({ pluginId: 'app-visualizer', @@ -101,7 +93,6 @@ export const visualizerPlugin = createFrontendPlugin({ appVisualizerTreePage, appVisualizerDetailedPage, appVisualizerTextPage, - appVisualizerNavItem, copyTreeAsJson, ], }); diff --git a/plugins/app/src/extensions/AppNav.test.tsx b/plugins/app/src/extensions/AppNav.test.tsx index 387c75a87a..c1434d7bfc 100644 --- a/plugins/app/src/extensions/AppNav.test.tsx +++ b/plugins/app/src/extensions/AppNav.test.tsx @@ -18,38 +18,46 @@ import { screen, waitFor, within } from '@testing-library/react'; import { renderTestApp } from '@backstage/frontend-test-utils'; import { PageBlueprint, - NavItemBlueprint, + createExtension, createRouteRef, } from '@backstage/frontend-plugin-api'; +import { legacyNavItemTargetDataRef } from './legacyNavItem'; const DEFAULT_CONFIG = { app: { baseUrl: 'http://localhost:3000' }, backend: { baseUrl: 'http://localhost:7007' }, }; +const mockRouteRef = createRouteRef(); + const mockPage = PageBlueprint.make({ name: 'my-plugin', params: { title: 'My Plugin', icon: icon, path: '/my-plugin', - routeRef: createRouteRef(), + routeRef: mockRouteRef, }, }); -const mockNavItem = NavItemBlueprint.make({ +const mockLegacyNavItem = createExtension({ + kind: 'nav-item', name: 'my-plugin', - params: { - title: 'My Plugin', - icon: () => icon, - routeRef: createRouteRef(), - }, + attachTo: { id: 'app/nav', input: 'items' }, + output: [legacyNavItemTargetDataRef], + factory: () => [ + legacyNavItemTargetDataRef({ + title: 'Legacy Nav Title', + icon: () => legacy icon, + routeRef: mockRouteRef, + }), + ], }); describe('AppNav', () => { - it('should show a nav item for a page with an enabled nav-item extension', async () => { + it('should show a nav item for a page with title and icon', async () => { renderTestApp({ - extensions: [mockPage, mockNavItem], + extensions: [mockPage], config: DEFAULT_CONFIG, }); @@ -60,40 +68,24 @@ describe('AppNav', () => { }); }); - it('should hide a nav item when its nav-item extension is disabled via config', async () => { - renderTestApp({ - extensions: [mockPage, mockNavItem], - config: { - ...DEFAULT_CONFIG, - app: { - ...DEFAULT_CONFIG.app, - extensions: [{ 'nav-item:test/my-plugin': false }], - }, + it('should merge legacy nav item metadata when page has no explicit title', async () => { + const pageWithoutTitle = PageBlueprint.make({ + name: 'legacy-plugin', + params: { + path: '/legacy-plugin', + routeRef: mockRouteRef, + icon: page icon, }, }); + renderTestApp({ + extensions: [pageWithoutTitle, mockLegacyNavItem], + config: DEFAULT_CONFIG, + }); + await waitFor(() => { expect( - within(screen.getByRole('navigation')).queryByText('My Plugin'), - ).not.toBeInTheDocument(); - }); - }); - - it('should still show a nav item for a page without a nav-item extension', async () => { - renderTestApp({ - extensions: [mockPage], - config: { - ...DEFAULT_CONFIG, - app: { - ...DEFAULT_CONFIG.app, - extensions: [{ 'nav-item:test/my-plugin': false }], - }, - }, - }); - - await waitFor(() => { - expect( - within(screen.getByRole('navigation')).getByText('My Plugin'), + within(screen.getByRole('navigation')).getByText('Legacy Nav Title'), ).toBeInTheDocument(); }); }); diff --git a/plugins/app/src/extensions/AppNav.tsx b/plugins/app/src/extensions/AppNav.tsx index 09203303e5..4b5c456436 100644 --- a/plugins/app/src/extensions/AppNav.tsx +++ b/plugins/app/src/extensions/AppNav.tsx @@ -18,7 +18,6 @@ import { createExtension, coreExtensionData, createExtensionInput, - NavItemBlueprint, routeResolutionApiRef, appTreeApiRef, IconComponent, @@ -27,6 +26,7 @@ import { RouteResolutionApi, useApi, } from '@backstage/frontend-plugin-api'; +import { legacyNavItemTargetDataRef } from './legacyNavItem'; import { NavContentBlueprint, NavContentComponent, @@ -248,7 +248,7 @@ export const AppNav = createExtension({ name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, inputs: { - items: createExtensionInput([NavItemBlueprint.dataRefs.target]), + items: createExtensionInput([legacyNavItemTargetDataRef]), content: createExtensionInput([NavContentBlueprint.dataRefs.component], { singleton: true, optional: true, @@ -264,7 +264,7 @@ export const AppNav = createExtension({ yield coreExtensionData.reactElement( - item.get(NavItemBlueprint.dataRefs.target), + item.get(legacyNavItemTargetDataRef), )} Content={Content} />, diff --git a/plugins/catalog/src/alpha/navItems.tsx b/plugins/app/src/extensions/legacyNavItem.ts similarity index 53% rename from plugins/catalog/src/alpha/navItems.tsx rename to plugins/app/src/extensions/legacyNavItem.ts index e72c20ebf4..bda23dabe8 100644 --- a/plugins/catalog/src/alpha/navItems.tsx +++ b/plugins/app/src/extensions/legacyNavItem.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,20 @@ * limitations under the License. */ -import HomeIcon from '@material-ui/icons/Home'; -import { NavItemBlueprint } from '@backstage/frontend-plugin-api'; -import { rootRouteRef } from '../routes'; +import { + createExtensionDataRef, + IconComponent, + RouteRef, +} from '@backstage/frontend-plugin-api'; -export const catalogNavItem = NavItemBlueprint.make({ - params: { - routeRef: rootRouteRef, - title: 'Catalog', - icon: HomeIcon, - }, -}); - -export default [catalogNavItem]; +/** + * @internal + * + * Data ref for legacy nav-item extensions. Kept for backward compatibility with + * extensions created by older versions of the framework. + */ +export const legacyNavItemTargetDataRef = createExtensionDataRef<{ + title: string; + icon: IconComponent; + routeRef: RouteRef; +}>().with({ id: 'core.nav-item.target' }); diff --git a/plugins/catalog-unprocessed-entities/report-alpha.api.md b/plugins/catalog-unprocessed-entities/report-alpha.api.md index 23d7e7b931..ef72269e22 100644 --- a/plugins/catalog-unprocessed-entities/report-alpha.api.md +++ b/plugins/catalog-unprocessed-entities/report-alpha.api.md @@ -10,7 +10,6 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -40,31 +39,6 @@ const _default: OverridableFrontendPlugin< params: ApiFactory, ) => ExtensionBlueprintParams; }>; - 'nav-item:catalog-unprocessed-entities': OverridableExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: { - title: string | undefined; - }; - configInput: { - title?: string | undefined; - }; - output: ExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef_2; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef_2; - }; - }>; 'page:catalog-unprocessed-entities': OverridableExtensionDefinition<{ kind: 'page'; name: undefined; diff --git a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx index 1bf2bc2bc4..8b829511e0 100644 --- a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx +++ b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx @@ -20,7 +20,6 @@ import { fetchApiRef, ApiBlueprint, PageBlueprint, - NavItemBlueprint, SubPageBlueprint, } from '@backstage/frontend-plugin-api'; @@ -52,6 +51,8 @@ export const catalogUnprocessedEntitiesPage = PageBlueprint.make({ params: { path: '/catalog-unprocessed-entities', routeRef: rootRouteRef, + title: 'Unprocessed Entities', + icon: , loader: () => import('../components/UnprocessedEntities').then(m => ( @@ -59,15 +60,6 @@ export const catalogUnprocessedEntitiesPage = PageBlueprint.make({ }, }); -/** @alpha */ -export const catalogUnprocessedEntitiesNavItem = NavItemBlueprint.make({ - params: { - title: 'Unprocessed Entities', - routeRef: rootRouteRef, - icon: QueueIcon, - }, -}); - /** * DevTools content for catalog unprocessed entities. * @@ -99,7 +91,6 @@ export default createFrontendPlugin({ extensions: [ catalogUnprocessedEntitiesApi, catalogUnprocessedEntitiesPage, - catalogUnprocessedEntitiesNavItem, unprocessedEntitiesDevToolsContent, ], }); diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md index b3074b11bf..a682ddc9c6 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -21,7 +21,6 @@ import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FilterPredicate } from '@backstage/filter-predicates'; -import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconElement } from '@backstage/frontend-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; @@ -1027,31 +1026,6 @@ const _default: OverridableFrontendPlugin< filter?: FilterPredicate | ((entity: Entity) => boolean); }; }>; - 'nav-item:catalog': OverridableExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: { - title: string | undefined; - }; - configInput: { - title?: string | undefined; - }; - output: ExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef_2; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef_2; - }; - }>; 'page:catalog': OverridableExtensionDefinition<{ config: { pagination: diff --git a/plugins/catalog/src/alpha/plugin.tsx b/plugins/catalog/src/alpha/plugin.tsx index 243b93db9b..b2ea8d3eca 100644 --- a/plugins/catalog/src/alpha/plugin.tsx +++ b/plugins/catalog/src/alpha/plugin.tsx @@ -29,7 +29,6 @@ import { import apis from './apis'; import pages from './pages'; import filters from './filters'; -import navItems from './navItems'; import entityCards from './entityCards'; import entityContents from './entityContents'; import entityIconLinks from './entityIconLinks'; @@ -58,7 +57,6 @@ export default createFrontendPlugin({ ...apis, ...pages, ...filters, - ...navItems, ...entityCards, ...entityContents, ...entityIconLinks, diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md index b911a82b48..2c3006ea12 100644 --- a/plugins/devtools/report-alpha.api.md +++ b/plugins/devtools/report-alpha.api.md @@ -10,7 +10,6 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -40,31 +39,6 @@ const _default: OverridableFrontendPlugin< params: ApiFactory, ) => ExtensionBlueprintParams; }>; - 'nav-item:devtools': OverridableExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: { - title: string | undefined; - }; - configInput: { - title?: string | undefined; - }; - output: ExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef_2; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef_2; - }; - }>; 'page:devtools': OverridableExtensionDefinition<{ config: { path: string | undefined; diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 0144308069..292abd282f 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -21,7 +21,6 @@ import { fetchApiRef, ApiBlueprint, PageBlueprint, - NavItemBlueprint, SubPageBlueprint, } from '@backstage/frontend-plugin-api'; @@ -72,6 +71,7 @@ export const devToolsPage = PageBlueprint.makeWithOverrides({ path: '/devtools', routeRef: rootRouteRef, title: 'DevTools', + icon: , }, { inputs: { @@ -133,15 +133,6 @@ export const devToolsScheduledTasksPage = SubPageBlueprint.make({ }, }); -/** @alpha */ -export const devToolsNavItem = NavItemBlueprint.make({ - params: { - title: 'DevTools', - routeRef: rootRouteRef, - icon: BuildIcon, - }, -}); - /** @alpha */ export default createFrontendPlugin({ pluginId: 'devtools', @@ -157,6 +148,5 @@ export default createFrontendPlugin({ devToolsInfoPage, devToolsConfigPage, devToolsScheduledTasksPage, - devToolsNavItem, ], }); diff --git a/plugins/home/report-alpha.api.md b/plugins/home/report-alpha.api.md index 1014c626a2..179799cd24 100644 --- a/plugins/home/report-alpha.api.md +++ b/plugins/home/report-alpha.api.md @@ -13,7 +13,6 @@ import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { HomePageLayoutProps } from '@backstage/plugin-home-react/alpha'; import { HomePageWidgetBlueprintParams } from '@backstage/plugin-home-react/alpha'; import { HomePageWidgetData } from '@backstage/plugin-home-react/alpha'; -import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -81,31 +80,6 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: HomePageWidgetBlueprintParams; }>; - 'nav-item:home': OverridableExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: { - title: string | undefined; - }; - configInput: { - title?: string | undefined; - }; - output: ExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }; - }>; 'page:home': OverridableExtensionDefinition<{ config: { path: string | undefined; diff --git a/plugins/home/src/alpha.test.ts b/plugins/home/src/alpha.test.ts index caa7d45cf7..900d886539 100644 --- a/plugins/home/src/alpha.test.ts +++ b/plugins/home/src/alpha.test.ts @@ -38,10 +38,6 @@ describe('Home Plugin Alpha', () => { it('should export core home page extension', () => { expect(homePlugin.getExtension('page:home')).toBeDefined(); }); - - it('should export navigation item extension', () => { - expect(homePlugin.getExtension('nav-item:home')).toBeDefined(); - }); }); describe('Optional Extensions (Disabled by Default)', () => { @@ -99,7 +95,6 @@ describe('Home Plugin Alpha', () => { it('should include all extensions in the correct order', () => { // Core extensions (always enabled) expect(homePlugin.getExtension('page:home')).toBeDefined(); - expect(homePlugin.getExtension('nav-item:home')).toBeDefined(); // Optional extensions (disabled by default) expect(homePlugin.getExtension('api:home/visits')).toBeDefined(); diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index bdce683cee..2a9e86425f 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -28,7 +28,6 @@ import { lazy as reactLazy } from 'react'; import { createExtensionInput, PageBlueprint, - NavItemBlueprint, createFrontendPlugin, createRouteRef, AppRootElementBlueprint, @@ -65,6 +64,8 @@ const homePage = PageBlueprint.makeWithOverrides({ path: '/home', noHeader: true, routeRef: rootRouteRef, + title: 'Home', + icon: , loader: async () => { const LazyDefaultLayout = reactLazy(() => import('./alpha/DefaultHomePageLayout').then(m => ({ @@ -122,14 +123,6 @@ const visitsApi = ApiBlueprint.make({ }), }); -const homeNavItem = NavItemBlueprint.make({ - params: { - title: 'Home', - routeRef: rootRouteRef, - icon: HomeIcon, - }, -}); - const homePageToolkitWidget = HomePageWidgetBlueprint.make({ name: 'toolkit', params: { @@ -213,7 +206,6 @@ export default createFrontendPlugin({ info: { packageJson: () => import('../package.json') }, extensions: [ homePage, - homeNavItem, visitsApi, visitListenerAppRootElement, homePageToolkitWidget, diff --git a/plugins/org/README-alpha.md b/plugins/org/README-alpha.md index db8a84f35c..0f112b943a 100644 --- a/plugins/org/README-alpha.md +++ b/plugins/org/README-alpha.md @@ -305,67 +305,37 @@ For more information about where to place extension overrides, see the official ### My Groups Sidebar Item -As the [NavItem](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) extension type does not support conditional rendering, this plugin does not provide navigation items, so to use the `MyGroupsSidebarItem` component, we recommend overriding the [App/Nav](https://backstage.io/docs/frontend-system/building-apps/built-in-extensions#app-nav) extension and adding the item statically. - -> [!IMPORTANT] -> As you can see in the example below, we are using the same attachment point, inputs and outputs as the default App/Nav extension to avoid side effects on the NavItem and NavLogo extensions. +This plugin does not provide a page extension for the groups sidebar item, since it requires conditional rendering based on the logged-in user. To use the `MyGroupsSidebarItem` component, add it to your custom sidebar implementation using the `NavContentBlueprint` in `packages/app/src/modules/nav/Sidebar.tsx`: ```tsx -// ... import { MyGroupsSidebarItem } from '@backstage/plugin-org'; import GroupIcon from '@material-ui/icons/People'; +import { NavContentBlueprint } from '@backstage/plugin-app-react'; -export default createFrontendModule({ - pluginId: 'app', - extensions: [ - createExtension({ - // Name is necessary so the system knows that this extension will override the default app nav extension - name: 'nav', - // Keeping the same attachment point as in the default App/Nav extension - attachTo: { id: 'app/layout', input: 'nav' }, - // Keeping the same inputs as in the default App/Nav extension - inputs: { - items: createExtensionInput({ - target: createNavItemExtension.targetDataRef, - }), - logos: createExtensionInput( - { - elements: createNavLogoExtension.logoElementsDataRef, - }, - { - singleton: true, - optional: true, - }, - ), - }, - // Keeping the same output as in the default App/Nav extension - output: { - element: coreExtensionData.reactElement, - }, - factory({ inputs }) { - return { - element: ( - - {/* Code borrowed from the default extension implementation to render the logos and items inputs */} - - - {inputs.items.map((item, index) => ( - - ))} - {/* Here is where we actually modifies the default implementation by adding a static item to render a group of squad pages */} - }> - {/* The MyGroupsSidebarItem provides quick access to the group(s) the logged in user is a member of directly in the sidebar. */} - - - - ), - }; - }, - }), - ], +export const SidebarContent = NavContentBlueprint.make({ + params: { + component: ({ navItems }) => { + const nav = navItems.withComponent(item => ( + item.icon} to={item.href} text={item.title} /> + )); + + return ( + + + + }> + {nav.rest()} + + + + ); + }, + }, }); ``` + +For more details on customizing the sidebar, see the [app migration guide](https://backstage.io/docs/frontend-system/building-apps/migrating#app-root-sidebar). diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md index 471f460241..31cee88452 100644 --- a/plugins/scaffolder/report-alpha.api.md +++ b/plugins/scaffolder/report-alpha.api.md @@ -20,7 +20,6 @@ import { FormField } from '@backstage/plugin-scaffolder-react/alpha'; import { formFieldsApiRef } from '@backstage/plugin-scaffolder-react/alpha'; import type { FormProps as FormProps_2 } from '@rjsf/core'; import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react'; -import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconElement } from '@backstage/frontend-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; @@ -173,31 +172,6 @@ const _default: OverridableFrontendPlugin< filter?: FilterPredicate | ((entity: Entity) => boolean); }; }>; - 'nav-item:scaffolder': OverridableExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: { - title: string | undefined; - }; - configInput: { - title?: string | undefined; - }; - output: ExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef_2; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef_2; - }; - }>; 'page:scaffolder': OverridableExtensionDefinition<{ config: { path: string | undefined; diff --git a/plugins/scaffolder/src/alpha/extensions.tsx b/plugins/scaffolder/src/alpha/extensions.tsx index 1694114e00..8523fd9c13 100644 --- a/plugins/scaffolder/src/alpha/extensions.tsx +++ b/plugins/scaffolder/src/alpha/extensions.tsx @@ -20,7 +20,6 @@ import { discoveryApiRef, fetchApiRef, identityApiRef, - NavItemBlueprint, PageBlueprint, SubPageBlueprint, } from '@backstage/frontend-plugin-api'; @@ -52,7 +51,8 @@ export const scaffolderPage = PageBlueprint.makeWithOverrides({ return originalFactory({ routeRef: rootRouteRef, path: '/create', - title: 'Create', + title: 'Create...', + icon: , }); }, }); @@ -154,14 +154,6 @@ export const scaffolderTemplatingExtensionsSubPage = SubPageBlueprint.make({ }, }); -export const scaffolderNavItem = NavItemBlueprint.make({ - params: { - routeRef: rootRouteRef, - title: 'Create...', - icon: CreateComponentIcon, - }, -}); - export const repoUrlPickerFormField = FormFieldBlueprint.make({ name: 'repo-url-picker', params: { diff --git a/plugins/scaffolder/src/alpha/plugin.tsx b/plugins/scaffolder/src/alpha/plugin.tsx index af79ea416a..18298e16c3 100644 --- a/plugins/scaffolder/src/alpha/plugin.tsx +++ b/plugins/scaffolder/src/alpha/plugin.tsx @@ -39,7 +39,6 @@ import { repoOwnerPickerFormField, repoUrlPickerFormField, scaffolderApi, - scaffolderNavItem, scaffolderPage, scaffolderTemplatesSubPage, scaffolderTasksSubPage, @@ -89,7 +88,6 @@ export default createFrontendPlugin({ scaffolderActionsSubPage, scaffolderEditorSubPage, scaffolderTemplatingExtensionsSubPage, - scaffolderNavItem, scaffolderEntityIconLink, formDecoratorsApi, formFieldsApi, diff --git a/plugins/search/report-alpha.api.md b/plugins/search/report-alpha.api.md index 9639741350..b025b9498a 100644 --- a/plugins/search/report-alpha.api.md +++ b/plugins/search/report-alpha.api.md @@ -10,7 +10,6 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -44,31 +43,6 @@ const _default: OverridableFrontendPlugin< params: ApiFactory, ) => ExtensionBlueprintParams; }>; - 'nav-item:search': OverridableExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: { - title: string | undefined; - }; - configInput: { - title?: string | undefined; - }; - output: ExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }; - }>; 'page:search': OverridableExtensionDefinition<{ config: { noTrack: boolean; @@ -214,33 +188,6 @@ export const searchApi: OverridableExtensionDefinition<{ ) => ExtensionBlueprintParams; }>; -// @alpha (undocumented) -export const searchNavItem: OverridableExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: { - title: string | undefined; - }; - configInput: { - title?: string | undefined; - }; - output: ExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }; -}>; - // @alpha (undocumented) export const searchPage: OverridableExtensionDefinition<{ config: { diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index a981afa1de..5b93fa6e1e 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -37,7 +37,6 @@ import { ApiBlueprint, createExtensionInput, PageBlueprint, - NavItemBlueprint, configApiRef, } from '@backstage/frontend-plugin-api'; @@ -110,6 +109,8 @@ export const searchPage = PageBlueprint.makeWithOverrides({ return originalFactory({ path: '/search', routeRef: rootRouteRef, + title: 'Search', + icon: , loader: async () => { const getResultItemComponent = (result: SearchResult) => { const value = inputs.items.find(item => @@ -258,22 +259,13 @@ export const searchPage = PageBlueprint.makeWithOverrides({ }, }); -/** @alpha */ -export const searchNavItem = NavItemBlueprint.make({ - params: { - routeRef: rootRouteRef, - title: 'Search', - icon: SearchIcon, - }, -}); - /** @alpha */ export default createFrontendPlugin({ pluginId: 'search', title: 'Search', icon: , info: { packageJson: () => import('../package.json') }, - extensions: [searchApi, searchPage, searchNavItem], + extensions: [searchApi, searchPage], routes: { root: rootRouteRef, }, diff --git a/plugins/techdocs/report-alpha.api.md b/plugins/techdocs/report-alpha.api.md index 81d6b5a547..2c166067b3 100644 --- a/plugins/techdocs/report-alpha.api.md +++ b/plugins/techdocs/report-alpha.api.md @@ -13,7 +13,6 @@ import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FilterPredicate } from '@backstage/filter-predicates'; -import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconElement } from '@backstage/frontend-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; @@ -259,31 +258,6 @@ const _default: OverridableFrontendPlugin< filter?: FilterPredicate | ((entity: Entity) => boolean); }; }>; - 'nav-item:techdocs': OverridableExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: { - title: string | undefined; - }; - configInput: { - title?: string | undefined; - }; - output: ExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef_2; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef_2; - }; - }>; 'page:techdocs': OverridableExtensionDefinition<{ kind: 'page'; name: undefined; diff --git a/plugins/techdocs/src/alpha/index.tsx b/plugins/techdocs/src/alpha/index.tsx index 6f77d1731e..aeda900a7f 100644 --- a/plugins/techdocs/src/alpha/index.tsx +++ b/plugins/techdocs/src/alpha/index.tsx @@ -21,7 +21,6 @@ import { createFrontendPlugin, ApiBlueprint, PageBlueprint, - NavItemBlueprint, PluginHeaderActionBlueprint, createExtensionInput, coreExtensionData, @@ -139,6 +138,8 @@ const techDocsPage = PageBlueprint.make({ params: { path: '/docs', routeRef: rootRouteRef, + title: 'Docs', + icon: , loader: () => import('./components/TechDocsIndexPageContent').then(m => ( @@ -265,15 +266,6 @@ const techDocsEntityContentEmptyState = createExtension({ factory: () => [], }); -/** @alpha */ -const techDocsNavItem = NavItemBlueprint.make({ - params: { - icon: () => , - title: 'Docs', - routeRef: rootRouteRef, - }, -}); - const techDocsSupportAction = PluginHeaderActionBlueprint.make({ params: defineParams => defineParams({ @@ -293,7 +285,6 @@ export default createFrontendPlugin({ techDocsClientApi, techDocsStorageApi, TechDocsAddonsApiExtension, - techDocsNavItem, techDocsSupportAction, techDocsPage, techDocsReaderPage, diff --git a/plugins/user-settings/report-alpha.api.md b/plugins/user-settings/report-alpha.api.md index aa5b0e0237..de8b37a6d7 100644 --- a/plugins/user-settings/report-alpha.api.md +++ b/plugins/user-settings/report-alpha.api.md @@ -7,47 +7,21 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; -import { RouteRef as RouteRef_2 } from '@backstage/core-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< { - root: RouteRef_2; + root: RouteRef; }, {}, { - 'nav-item:user-settings': OverridableExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: { - title: string | undefined; - }; - configInput: { - title?: string | undefined; - }; - output: ExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }; - }>; 'page:user-settings': OverridableExtensionDefinition<{ kind: 'page'; name: undefined; @@ -62,7 +36,7 @@ const _default: OverridableFrontendPlugin< output: | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -88,7 +62,7 @@ const _default: OverridableFrontendPlugin< | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -120,7 +94,7 @@ const _default: OverridableFrontendPlugin< title?: string; icon?: IconElement; loader?: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; noHeader?: boolean; }; }>; @@ -136,7 +110,7 @@ const _default: OverridableFrontendPlugin< output: | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -168,7 +142,7 @@ const _default: OverridableFrontendPlugin< title: string; icon?: IconElement; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; 'sub-page:user-settings/feature-flags': OverridableExtensionDefinition<{ @@ -185,7 +159,7 @@ const _default: OverridableFrontendPlugin< output: | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -206,7 +180,7 @@ const _default: OverridableFrontendPlugin< title: string; icon?: IconElement; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; 'sub-page:user-settings/general': OverridableExtensionDefinition<{ @@ -223,7 +197,7 @@ const _default: OverridableFrontendPlugin< output: | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -244,40 +218,13 @@ const _default: OverridableFrontendPlugin< title: string; icon?: IconElement; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; } >; export default _default; -// @alpha (undocumented) -export const settingsNavItem: OverridableExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: { - title: string | undefined; - }; - configInput: { - title?: string | undefined; - }; - output: ExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }; -}>; - // @alpha @deprecated (undocumented) export const userSettingsTranslationRef: TranslationRef< 'user-settings', diff --git a/plugins/user-settings/src/alpha.tsx b/plugins/user-settings/src/alpha.tsx index 482a9711f5..86bc031056 100644 --- a/plugins/user-settings/src/alpha.tsx +++ b/plugins/user-settings/src/alpha.tsx @@ -18,7 +18,6 @@ import { createExtensionInput, createFrontendPlugin, PageBlueprint, - NavItemBlueprint, SubPageBlueprint, } from '@backstage/frontend-plugin-api'; import { Content } from '@backstage/core-components'; @@ -38,6 +37,7 @@ const userSettingsPage = PageBlueprint.make({ path: '/settings', routeRef: settingsRouteRef, title: 'Settings', + icon: , }, }); @@ -95,15 +95,6 @@ const featureFlagsSettingsPage = SubPageBlueprint.make({ }, }); -/** @alpha */ -export const settingsNavItem = NavItemBlueprint.make({ - params: { - routeRef: settingsRouteRef, - title: 'Settings', - icon: SettingsIcon, - }, -}); - /** * @alpha */ @@ -117,7 +108,6 @@ export default createFrontendPlugin({ generalSettingsPage, authProvidersSettingsPage, featureFlagsSettingsPage, - settingsNavItem, ], routes: { root: settingsRouteRef,