From 6651f2be06b6e32ed49c0b90cde7fa290da1acdb Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 10:48:24 +0200 Subject: [PATCH 01/17] app: remove nav item disable matching Signed-off-by: Patrik Oldsberg --- plugins/app/src/extensions/AppNav.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/plugins/app/src/extensions/AppNav.tsx b/plugins/app/src/extensions/AppNav.tsx index 0d6618fc8c..09203303e5 100644 --- a/plugins/app/src/extensions/AppNav.tsx +++ b/plugins/app/src/extensions/AppNav.tsx @@ -188,14 +188,6 @@ function NavContentRenderer(props: { return []; } - // TODO: Nav items are deprecated, and this code covers for a documented ability to use nav items still in config to disable pages in the sidebar. - // Remove this code once nav items are completely gone from the codebase. - const navItemNodeId = node.spec.id.replace(/^page:/, 'nav-item:'); - const navItemNode = tree.nodes.get(navItemNodeId); - if (navItemNode?.spec.disabled) { - return []; - } - const routeRef = node.instance.getData(coreExtensionData.routeRef); if (!routeRef) { return []; From 10e5d6f8aa7a04d86f723bdb3f09773ef734cf5f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:00:46 +0200 Subject: [PATCH 02/17] 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, From 77ceb88228c7e262e7f28d5c61fdcd4de561e40e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:01:05 +0200 Subject: [PATCH 03/17] Remove Nav Target legend from app visualizer Legacy nav-item output is no longer a first-class extension type worth highlighting in the detailed visualizer legend. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .../components/AppVisualizerPage/DetailedVisualizer.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx index d3c6bab057..2406d36a68 100644 --- a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx @@ -22,12 +22,7 @@ import { useApi, routeResolutionApiRef, appTreeApiRef, - createExtensionDataRef, } from '@backstage/frontend-plugin-api'; - -const legacyNavItemTargetDataRef = createExtensionDataRef().with({ - id: 'core.nav-item.target', -}); import { Box, Flex, @@ -90,7 +85,6 @@ const getOutputColor = createOutputColorGenerator( [coreExtensionData.routePath.id]: '#ffeb3b', [coreExtensionData.routeRef.id]: '#9c27b0', [ApiBlueprint.dataRefs.factory.id]: '#2196f3', - [legacyNavItemTargetDataRef.id]: '#ff9800', }, ['#90caf9', '#ffcc80', '#a5d6a7', '#ef9a9a', '#fff59d', '#ce93d8', '#e6ee9c'], @@ -339,7 +333,6 @@ const legendMap = { 'Utility API': ApiBlueprint.dataRefs.factory, 'Route Path': coreExtensionData.routePath, 'Route Ref': coreExtensionData.routeRef, - 'Nav Target': legacyNavItemTargetDataRef, }; function Legend() { From 44d77e9680117281a8743295696c2fea69469ec1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:01:58 +0200 Subject: [PATCH 04/17] Split nav item removal into separate changesets Use dedicated changesets for the breaking frontend-plugin-api removal, plugin migrations, and frontend-test-utils update. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .changeset/remove-nav-item-blueprint-api.md | 22 +++++++++++++++++++ ...d => remove-nav-item-blueprint-plugins.md} | 6 ++--- .../remove-nav-item-blueprint-test-utils.md | 5 +++++ 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 .changeset/remove-nav-item-blueprint-api.md rename .changeset/{remove-nav-item-blueprint.md => remove-nav-item-blueprint-plugins.md} (57%) create mode 100644 .changeset/remove-nav-item-blueprint-test-utils.md diff --git a/.changeset/remove-nav-item-blueprint-api.md b/.changeset/remove-nav-item-blueprint-api.md new file mode 100644 index 0000000000..93d27d1a39 --- /dev/null +++ b/.changeset/remove-nav-item-blueprint-api.md @@ -0,0 +1,22 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Removed the deprecated `NavItemBlueprint`. Navigation items are now discovered from `PageBlueprint` extensions based on their `title` and `icon` params. + +If you were still using `NavItemBlueprint`, migrate by moving `title` and `icon` to your `PageBlueprint` instead: + +```diff +-const navItem = NavItemBlueprint.make({ +- params: { title: 'Example', icon: ExampleIcon, routeRef }, +-}); + const page = PageBlueprint.make({ + params: { ++ title: 'Example', ++ icon: , + routeRef, + path: '/example', + loader: () => import('./Page').then(m => ), + }, + }); +``` diff --git a/.changeset/remove-nav-item-blueprint.md b/.changeset/remove-nav-item-blueprint-plugins.md similarity index 57% rename from .changeset/remove-nav-item-blueprint.md rename to .changeset/remove-nav-item-blueprint-plugins.md index b54e59f74c..3d096363ea 100644 --- a/.changeset/remove-nav-item-blueprint.md +++ b/.changeset/remove-nav-item-blueprint-plugins.md @@ -1,5 +1,4 @@ --- -'@backstage/frontend-plugin-api': minor '@backstage/plugin-app': patch '@backstage/plugin-catalog': patch '@backstage/plugin-search': patch @@ -11,7 +10,8 @@ '@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. +Removed separate nav item extensions. Sidebar entries are now provided via `title` and `icon` on each plugin's page extension. + +The app nav extension still accepts legacy `nav-item` extensions for backward compatibility with older plugin versions. diff --git a/.changeset/remove-nav-item-blueprint-test-utils.md b/.changeset/remove-nav-item-blueprint-test-utils.md new file mode 100644 index 0000000000..96e4554556 --- /dev/null +++ b/.changeset/remove-nav-item-blueprint-test-utils.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Updated `renderInTestApp` to render legacy nav-item extensions without relying on the removed `NavItemBlueprint` from `@backstage/frontend-plugin-api`. From 7089280c6e228d1b1c3b8594ab987cf9912229bc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:03:20 +0200 Subject: [PATCH 05/17] Add Remix icon migration notes to NavItemBlueprint changeset Document switching from MUI IconComponent nav items to Remix Icon elements on PageBlueprint. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .changeset/remove-nav-item-blueprint-api.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.changeset/remove-nav-item-blueprint-api.md b/.changeset/remove-nav-item-blueprint-api.md index 93d27d1a39..cff78131be 100644 --- a/.changeset/remove-nav-item-blueprint-api.md +++ b/.changeset/remove-nav-item-blueprint-api.md @@ -20,3 +20,23 @@ If you were still using `NavItemBlueprint`, migrate by moving `title` and `icon` }, }); ``` + +`PageBlueprint` expects an `IconElement` rather than a Material UI `IconComponent`, so this is also a good time to switch to [Remix Icon](https://remixicon.com/) if you were using Material UI icons only for the nav item: + +```diff +-import ExampleIcon from '@material-ui/icons/Extension'; ++import { RiPuzzleLine } from '@remixicon/react'; + +-const navItem = NavItemBlueprint.make({ +- params: { title: 'Example', icon: ExampleIcon, routeRef }, +-}); + const page = PageBlueprint.make({ + params: { ++ title: 'Example', ++ icon: , + routeRef, + path: '/example', + loader: () => import('./Page').then(m => ), + }, + }); +``` From 483297f8451f3ac331207fff73b69ac6ff0beb54 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:13:45 +0200 Subject: [PATCH 06/17] Trim Remix icon migration diff in changeset Show only import and icon param changes for the Remix Icon example. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .changeset/remove-nav-item-blueprint-api.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.changeset/remove-nav-item-blueprint-api.md b/.changeset/remove-nav-item-blueprint-api.md index cff78131be..c032a05840 100644 --- a/.changeset/remove-nav-item-blueprint-api.md +++ b/.changeset/remove-nav-item-blueprint-api.md @@ -26,17 +26,7 @@ If you were still using `NavItemBlueprint`, migrate by moving `title` and `icon` ```diff -import ExampleIcon from '@material-ui/icons/Extension'; +import { RiPuzzleLine } from '@remixicon/react'; - --const navItem = NavItemBlueprint.make({ -- params: { title: 'Example', icon: ExampleIcon, routeRef }, --}); - const page = PageBlueprint.make({ - params: { -+ title: 'Example', + ... +- icon: ExampleIcon, + icon: , - routeRef, - path: '/example', - loader: () => import('./Page').then(m => ), - }, - }); ``` From 269c4f9f0d019c94c900f7724ce614b40c4d2d29 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:15:31 +0200 Subject: [PATCH 07/17] Remove legacy nav stub from renderInTestApp Disable app/nav in the minimal test shell instead of rendering legacy nav-item extensions. Use renderTestApp when nav behavior matters. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .../remove-nav-item-blueprint-test-utils.md | 2 +- .../src/app/renderInTestApp.tsx | 59 +------------------ 2 files changed, 3 insertions(+), 58 deletions(-) diff --git a/.changeset/remove-nav-item-blueprint-test-utils.md b/.changeset/remove-nav-item-blueprint-test-utils.md index 96e4554556..ef553119b5 100644 --- a/.changeset/remove-nav-item-blueprint-test-utils.md +++ b/.changeset/remove-nav-item-blueprint-test-utils.md @@ -2,4 +2,4 @@ '@backstage/frontend-test-utils': patch --- -Updated `renderInTestApp` to render legacy nav-item extensions without relying on the removed `NavItemBlueprint` from `@backstage/frontend-plugin-api`. +Disabled the app nav extension in `renderInTestApp`, removing the legacy nav-item rendering stub. diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index 3d30ae3d6b..d599bdca01 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -15,19 +15,16 @@ */ import { Fragment } from 'react'; -import { Link, MemoryRouter } from 'react-router-dom'; +import { MemoryRouter } from 'react-router-dom'; import { prepareSpecializedApp } from '@backstage/frontend-app-api'; import { RenderResult, render } from '@testing-library/react'; import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { createExtension, - createExtensionDataRef, ExtensionDefinition, coreExtensionData, RouteRef, - useRouteRef, - IconComponent, createFrontendPlugin, FrontendFeature, createFrontendModule, @@ -49,13 +46,6 @@ 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 @@ -110,25 +100,6 @@ export type TestAppOptions = { apis?: readonly [...TestApiPairs]; }; -const NavItem = (props: { - routeRef: RouteRef; - title: string; - icon: IconComponent; -}) => { - const { routeRef, title, icon: Icon } = props; - const link = useRouteRef(routeRef); - if (!link) { - return null; - } - return ( -
  • - - {title} - -
  • - ); -}; - const appPluginOverride = appPlugin.withOverrides({ extensions: [ appPlugin.getExtension('sign-in-page:app').override({ @@ -141,33 +112,7 @@ const appPluginOverride = appPlugin.withOverrides({ disabled: true, }), appPlugin.getExtension('app/nav').override({ - output: [coreExtensionData.reactElement], - factory(_originalFactory, { inputs }) { - return [ - coreExtensionData.reactElement( - , - ), - ]; - }, + disabled: true, }), ], }); From fe72a38768bc21a5fc9eb8eb36eaec1dba2f8239 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:16:14 +0200 Subject: [PATCH 08/17] Mark renderInTestApp nav removal as breaking in changeset Bump frontend-test-utils as minor with user-facing impact guidance. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .changeset/remove-nav-item-blueprint-test-utils.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.changeset/remove-nav-item-blueprint-test-utils.md b/.changeset/remove-nav-item-blueprint-test-utils.md index ef553119b5..b480688b07 100644 --- a/.changeset/remove-nav-item-blueprint-test-utils.md +++ b/.changeset/remove-nav-item-blueprint-test-utils.md @@ -1,5 +1,9 @@ --- -'@backstage/frontend-test-utils': patch +'@backstage/frontend-test-utils': minor --- -Disabled the app nav extension in `renderInTestApp`, removing the legacy nav-item rendering stub. +**BREAKING**: `renderInTestApp` no longer renders a sidebar or legacy `nav-item` extensions. The app nav extension is now disabled in the minimal test app shell, along with the layout and routes extensions. + +If your tests passed `features` containing `nav-item` extensions and asserted on links or labels in that stub sidebar, switch to `renderTestApp` instead — it uses the real app shell and discovers nav items from page extensions. + +If you only use `renderInTestApp` to mount a component with APIs or route refs, there is no change. From 2ba8c1010674e144aa4c99be683d1a65135e037e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:20:28 +0200 Subject: [PATCH 09/17] Split plugin-app into its own changeset Keep legacy nav-item compatibility note on plugin-app only; other plugins get a changeset focused on page title/icon for sidebar entries. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .changeset/remove-nav-item-blueprint-plugin-app.md | 5 +++++ .changeset/remove-nav-item-blueprint-plugins.md | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 .changeset/remove-nav-item-blueprint-plugin-app.md diff --git a/.changeset/remove-nav-item-blueprint-plugin-app.md b/.changeset/remove-nav-item-blueprint-plugin-app.md new file mode 100644 index 0000000000..3dbfdfb355 --- /dev/null +++ b/.changeset/remove-nav-item-blueprint-plugin-app.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app': patch +--- + +The app nav extension still accepts legacy `nav-item` extensions for backward compatibility with older plugin versions. diff --git a/.changeset/remove-nav-item-blueprint-plugins.md b/.changeset/remove-nav-item-blueprint-plugins.md index 3d096363ea..d25a44a140 100644 --- a/.changeset/remove-nav-item-blueprint-plugins.md +++ b/.changeset/remove-nav-item-blueprint-plugins.md @@ -1,5 +1,4 @@ --- -'@backstage/plugin-app': patch '@backstage/plugin-catalog': patch '@backstage/plugin-search': patch '@backstage/plugin-home': patch @@ -13,5 +12,3 @@ --- Removed separate nav item extensions. Sidebar entries are now provided via `title` and `icon` on each plugin's page extension. - -The app nav extension still accepts legacy `nav-item` extensions for backward compatibility with older plugin versions. From 177a1c1af6e8edd09f44ea1d6c76c92b6cae6440 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:21:23 +0200 Subject: [PATCH 10/17] Clarify plugin-app changeset message in isolation Explain page-based nav discovery and legacy nav-item compatibility together. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .changeset/remove-nav-item-blueprint-plugin-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/remove-nav-item-blueprint-plugin-app.md b/.changeset/remove-nav-item-blueprint-plugin-app.md index 3dbfdfb355..34929e83bd 100644 --- a/.changeset/remove-nav-item-blueprint-plugin-app.md +++ b/.changeset/remove-nav-item-blueprint-plugin-app.md @@ -2,4 +2,4 @@ '@backstage/plugin-app': patch --- -The app nav extension still accepts legacy `nav-item` extensions for backward compatibility with older plugin versions. +Updated the built-in app nav to discover sidebar entries from page extensions, following the removal of `NavItemBlueprint` in `@backstage/frontend-plugin-api`. Legacy `nav-item` extensions are still accepted so older plugins keep working until they migrate. From e1b0499b7d014cf53015f07b12bb80ca79789610 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:22:05 +0200 Subject: [PATCH 11/17] docs: use Remix icon in plugin override example Update the architecture plugins doc to match page IconElement usage. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- docs/frontend-system/architecture/15-plugins.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/frontend-system/architecture/15-plugins.md b/docs/frontend-system/architecture/15-plugins.md index 2d16223b68..1e2f2d45c7 100644 --- a/docs/frontend-system/architecture/15-plugins.md +++ b/docs/frontend-system/architecture/15-plugins.md @@ -130,7 +130,7 @@ 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'; +import { RiLayoutGridLine } from '@remixicon/react'; export default plugin.withOverrides({ // These overrides are merged with the original extensions @@ -139,7 +139,7 @@ export default plugin.withOverrides({ plugin.getExtension('page:catalog').override({ factory: origFactory => origFactory({ - icon: , + icon: , loader: () => import('./CustomCatalogIndexPage').then(m => ), }), From 15afdd994df1871904c2ac445a1f73366b29c3e3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:22:38 +0200 Subject: [PATCH 12/17] Fix plugin-app changeset to describe legacy nav-item support Page-based nav discovery is unchanged; the app nav change is backward compatibility after NavItemBlueprint removal. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .changeset/remove-nav-item-blueprint-plugin-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/remove-nav-item-blueprint-plugin-app.md b/.changeset/remove-nav-item-blueprint-plugin-app.md index 34929e83bd..6e5cfd4e24 100644 --- a/.changeset/remove-nav-item-blueprint-plugin-app.md +++ b/.changeset/remove-nav-item-blueprint-plugin-app.md @@ -2,4 +2,4 @@ '@backstage/plugin-app': patch --- -Updated the built-in app nav to discover sidebar entries from page extensions, following the removal of `NavItemBlueprint` in `@backstage/frontend-plugin-api`. Legacy `nav-item` extensions are still accepted so older plugins keep working until they migrate. +Following the removal of `NavItemBlueprint` in `@backstage/frontend-plugin-api`, the built-in app nav was updated to keep accepting legacy `nav-item` extensions so older plugins continue to work until they migrate. From b5032acd835b0da1ef0284907cc2dc1f7f00bea8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:23:03 +0200 Subject: [PATCH 13/17] docs: remove legacy items input row from app nav table The items input is internal backward compatibility, not public API. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- docs/frontend-system/building-apps/03-built-in-extensions.md | 1 - 1 file changed, 1 deletion(-) 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 f61f3112bf..527ee4b343 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,6 @@ 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 | Legacy nav item target objects. | `core.nav-item.target` | true | - | Legacy extensions only; nav items are auto-discovered from page extensions. | ### App routes From 36998fea170e5c8c6e37a6a87fd50fe3049b1cd3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:28:12 +0200 Subject: [PATCH 14/17] scaffolder: restore page nav title to Create Use Create on the page extension rather than the former nav item label. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- plugins/scaffolder/src/alpha/extensions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/alpha/extensions.tsx b/plugins/scaffolder/src/alpha/extensions.tsx index 8523fd9c13..ddbc1df19b 100644 --- a/plugins/scaffolder/src/alpha/extensions.tsx +++ b/plugins/scaffolder/src/alpha/extensions.tsx @@ -51,7 +51,7 @@ export const scaffolderPage = PageBlueprint.makeWithOverrides({ return originalFactory({ routeRef: rootRouteRef, path: '/create', - title: 'Create...', + title: 'Create', icon: , }); }, From 59d62d30196a8cbdfa0f81afbddf9674d1c83291 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:29:12 +0200 Subject: [PATCH 15/17] docs: use Remix icon in building plugins tutorial Update the PageBlueprint example to use RiPuzzleLine and drop stale nav item references from later snippets. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- docs/frontend-system/building-plugins/01-index.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index 60f6f6ea4a..8e832d7a35 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -44,7 +44,7 @@ The plugin ID should be a lowercase dash-separated string, while the plugin inst ## Adding extensions -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. +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, with a title and icon that appear in the app sidebar. 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. @@ -63,7 +63,7 @@ import { createFrontendPlugin, PageBlueprint, } from '@backstage/frontend-plugin-api'; -import ExampleIcon from '@material-ui/icons/Extension'; +import { RiPuzzleLine } from '@remixicon/react'; import { rootRouteRef } from './routes'; // Note that these extensions aren't exported, only the plugin itself is. @@ -77,7 +77,7 @@ const examplePage = PageBlueprint.make({ // The title and icon are used to populate the app sidebar automatically title: 'Example', - icon: , + icon: , // Page extensions are always dynamically loaded using React.lazy(). // All of the functionality of this page is implemented in the @@ -101,7 +101,7 @@ export const examplePlugin = createFrontendPlugin({ }); ``` -What we've built here is a very common type of plugin. It's a top-level tool that provides a single page, along with a method for navigating to that page. The implementation of the page component, in this case the highlighted `ExamplePage`, can be arbitrarily complex. It can be anything from a single simple information page, to a full-blown application with multiple sub-pages. +What we've built here is a very common type of plugin. It's a top-level tool that provides a single page, which the app discovers and links to from the sidebar automatically. The implementation of the page component, in this case the highlighted `ExamplePage`, can be arbitrarily complex. It can be anything from a single simple information page, to a full-blown application with multiple sub-pages. We have also provided external access to our route reference by passing it to the plugin `routes` option. This makes it possible for app integrators to bind an external link from a different plugin to our plugin page. You can read more about how this works in the [External Route References](../architecture/36-routes.md#external-route-references) section. @@ -177,7 +177,7 @@ const exampleApi = ApiBlueprint.make({ }); // highlight-add-end -/* Omitted definitions for examplePage, exampleNavItem, and rootRouteRef. */ +/* Omitted definitions for examplePage and rootRouteRef. */ export const examplePlugin = createFrontendPlugin({ pluginId: 'example', @@ -185,7 +185,6 @@ export const examplePlugin = createFrontendPlugin({ // highlight-add-next-line exampleApi, examplePage, - exampleNavItem, ], routes: { root: rootRouteRef, @@ -222,7 +221,6 @@ export const examplePlugin = createFrontendPlugin({ exampleEntityContent, exampleApi, examplePage, - exampleNavItem, ], routes: { root: rootRouteRef, From 2ec4c54b5fe5f54e0c4796e3672679229c4da6ba Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 19 May 2026 11:29:29 +0200 Subject: [PATCH 16/17] Remove rolled-back nav item disable changeset The grumpy-experts-leave fix was superseded by removing nav-item config matching entirely. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .changeset/grumpy-experts-leave.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/grumpy-experts-leave.md diff --git a/.changeset/grumpy-experts-leave.md b/.changeset/grumpy-experts-leave.md deleted file mode 100644 index 7504fe6428..0000000000 --- a/.changeset/grumpy-experts-leave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-app': patch ---- - -Fixed a regression that caused disabled nav items to appear in the navigation bar. From fd2c369f7b6b05018ec9bf1985404431f256401e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 10:21:51 +0000 Subject: [PATCH 17/17] test(notifications-backend): increase router test timeout for DB teardown Agent-Logs-Url: https://github.com/backstage/backstage/sessions/7f504e1c-86b2-4d51-add3-6fb873dfe222 --- plugins/notifications-backend/src/service/router.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/notifications-backend/src/service/router.test.ts b/plugins/notifications-backend/src/service/router.test.ts index 4ea63a9554..aabf0b99af 100644 --- a/plugins/notifications-backend/src/service/router.test.ts +++ b/plugins/notifications-backend/src/service/router.test.ts @@ -35,6 +35,8 @@ import { DatabaseService } from '@backstage/backend-plugin-api'; import { randomUUID as uuid } from 'node:crypto'; import { DatabaseNotificationsStore, generateSettingsHash } from '../database'; +jest.setTimeout(60_000); + const databases = TestDatabases.create(); let store: DatabaseNotificationsStore;