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 <poldsberg@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Patrik Oldsberg
2026-05-19 11:00:46 +02:00
parent 6651f2be06
commit 10e5d6f8aa
45 changed files with 165 additions and 905 deletions
+17
View File
@@ -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.
-7
View File
@@ -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:
@@ -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
@@ -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 `<Icon fontSize="inherit" />`.
- **Timeline** (`@material-ui/lab`): No BUI equivalent exists.
For these cases, keep using MUI components.
@@ -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'
```
@@ -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: <CustomCatalogIcon fontSize="inherit" />,
loader: () =>
import('./CustomCatalogIndexPage').then(m => <m.Page />),
}),
],
}),
// Override the catalog index page with a completely custom implementation
PageBlueprint.make({
params: {
path: '/catalog',
routeRef: plugin.routes.catalogIndex,
loader: () => import('./CustomCatalogIndexPage').then(m => <m.Page />),
},
}),
],
});
@@ -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
@@ -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 `<SidebarItem {...item}
You might also notice that when you're rendering additional fixed icons for plugins (e.g. Search in a dedicated group) these might become duplicated, since that page is also included in `nav.rest()`. To exclude an item from the remaining list, call `nav.take('page:search')` before calling `nav.rest()` — you can discard the return value. Items that have been taken will not appear in `rest()`.
You can also use the old `NavItemBlueprint`-based nav item extensions to disable items from the nav bar, these can be disabled in config without affecting the page itself:
```yaml title="in app-config.yaml"
app:
extensions:
- nav-item:search: false
- nav-item:catalog: false
```
To hide a page from the sidebar without disabling the page itself, use `nav.take('page:...')` in your custom sidebar implementation before calling `nav.rest()`, or disable the page extension in config with `page:<plugin-id>: false`.
#### App Root Routes
@@ -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: <ExampleIcon fontSize="inherit" />,
// 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: {
@@ -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.
@@ -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:
@@ -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<undefined>;
};
output: ExtensionDataRef_2<
{
title: string;
icon: IconComponent;
routeRef: RouteRef<undefined>;
},
'core.nav-item.target',
{}
>;
inputs: {};
config: {
title: string | undefined;
};
configInput: {
title?: string | undefined;
};
dataRefs: {
target: ConfigurableExtensionDataRef_2<
{
title: string;
icon: IconComponent;
routeRef: RouteRef<undefined>;
},
'core.nav-item.target',
{}
>;
};
}>;
// @public (undocumented)
export const NotFoundErrorPage: {
(props: NotFoundErrorPageProps): JSX.Element | 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,
});
});
});
@@ -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<undefined>;
}>().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<undefined>;
},
{ config },
) => [
targetDataRef({
title: config.title ?? title,
icon,
routeRef,
}),
],
configSchema: {
title: z.string().optional(),
},
});
@@ -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';
@@ -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<undefined>;
}>().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 (
-92
View File
@@ -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:
# <extension-kind>/<plugin-namespace>:<extension-name>
# 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:
# <extension-kind>/<plugin-namespace>:<extension-name>
- 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:
# <extension-kind>/<plugin-namespace>:<extension-name>
- 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
-26
View File
@@ -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<undefined>;
},
'core.nav-item.target',
{}
>;
inputs: {};
params: {
title: string;
icon: IconComponent;
routeRef: RouteRef_2<undefined>;
};
}>;
'page:api-docs': OverridableExtensionDefinition<{
config: {
initiallySelectedFilter: 'all' | 'owned' | 'starred' | undefined;
+2 -10
View File
@@ -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: () => <AppIcon fontSize="inherit" id="kind:api" />,
},
});
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: <AppIcon fontSize="inherit" id="kind:api" />,
loader: () =>
import('./components/ApiExplorerPage/DefaultApiExplorerPage').then(
m => (
@@ -222,7 +215,6 @@ export default createFrontendPlugin({
registerApi: registerComponentRouteRef,
},
extensions: [
apiDocsNavItem,
apiDocsConfigApi,
apiDocsExplorerPage,
apiDocsHasApisEntityCard,
+3 -4
View File
@@ -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' }` |
-26
View File
@@ -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<undefined>;
},
'core.nav-item.target',
{}
>;
inputs: {};
params: {
title: string;
icon: IconComponent;
routeRef: RouteRef<undefined>;
};
}>;
'page:app-visualizer': OverridableExtensionDefinition<{
kind: 'page';
name: undefined;
@@ -19,11 +19,15 @@ import {
ExtensionDataRef,
coreExtensionData,
ApiBlueprint,
NavItemBlueprint,
useApi,
routeResolutionApiRef,
appTreeApiRef,
createExtensionDataRef,
} from '@backstage/frontend-plugin-api';
const legacyNavItemTargetDataRef = createExtensionDataRef<unknown>().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() {
+1 -10
View File
@@ -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: <RiEyeLine />,
},
});
@@ -82,14 +82,6 @@ const copyTreeAsJson = PluginHeaderActionBlueprint.make({
},
});
export const appVisualizerNavItem = NavItemBlueprint.make({
params: {
title: 'Visualizer',
icon: () => <RiEyeLine />,
routeRef: rootRouteRef,
},
});
/** @public */
export const visualizerPlugin = createFrontendPlugin({
pluginId: 'app-visualizer',
@@ -101,7 +93,6 @@ export const visualizerPlugin = createFrontendPlugin({
appVisualizerTreePage,
appVisualizerDetailedPage,
appVisualizerTextPage,
appVisualizerNavItem,
copyTreeAsJson,
],
});
+31 -39
View File
@@ -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: <span>icon</span>,
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: () => <span>icon</span>,
routeRef: createRouteRef(),
},
attachTo: { id: 'app/nav', input: 'items' },
output: [legacyNavItemTargetDataRef],
factory: () => [
legacyNavItemTargetDataRef({
title: 'Legacy Nav Title',
icon: () => <span>legacy icon</span>,
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: <span>page icon</span>,
},
});
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();
});
});
+3 -3
View File
@@ -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(
<NavContentRenderer
legacyNavItems={inputs.items.map(item =>
item.get(NavItemBlueprint.dataRefs.target),
item.get(legacyNavItemTargetDataRef),
)}
Content={Content}
/>,
@@ -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<undefined>;
}>().with({ id: 'core.nav-item.target' });
@@ -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<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
'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<undefined>;
},
'core.nav-item.target',
{}
>;
inputs: {};
params: {
title: string;
icon: IconComponent;
routeRef: RouteRef_2<undefined>;
};
}>;
'page:catalog-unprocessed-entities': OverridableExtensionDefinition<{
kind: 'page';
name: undefined;
@@ -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: <QueueIcon fontSize="inherit" />,
loader: () =>
import('../components/UnprocessedEntities').then(m => (
<m.NfsUnprocessedEntities />
@@ -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,
],
});
-26
View File
@@ -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<undefined>;
},
'core.nav-item.target',
{}
>;
inputs: {};
params: {
title: string;
icon: IconComponent;
routeRef: RouteRef_2<undefined>;
};
}>;
'page:catalog': OverridableExtensionDefinition<{
config: {
pagination:
-2
View File
@@ -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,
-26
View File
@@ -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<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
'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<undefined>;
},
'core.nav-item.target',
{}
>;
inputs: {};
params: {
title: string;
icon: IconComponent;
routeRef: RouteRef_2<undefined>;
};
}>;
'page:devtools': OverridableExtensionDefinition<{
config: {
path: string | undefined;
+1 -11
View File
@@ -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: <BuildIcon fontSize="inherit" />,
},
{
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,
],
});
-26
View File
@@ -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<undefined>;
},
'core.nav-item.target',
{}
>;
inputs: {};
params: {
title: string;
icon: IconComponent;
routeRef: RouteRef<undefined>;
};
}>;
'page:home': OverridableExtensionDefinition<{
config: {
path: string | undefined;
-5
View File
@@ -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();
+2 -10
View File
@@ -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: <HomeIcon fontSize="inherit" />,
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,
+27 -57
View File
@@ -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: (
<Sidebar>
{/* Code borrowed from the default extension implementation to render the logos and items inputs */}
<SidebarLogo {...inputs.logos?.output.elements} />
<SidebarDivider />
{inputs.items.map((item, index) => (
<SidebarNavItem {...item.output.target} key={index} />
))}
{/* Here is where we actually modifies the default implementation by adding a static item to render a group of squad pages */}
<SidebarGroup label="Menu" icon={<MenuIcon />}>
{/* The MyGroupsSidebarItem provides quick access to the group(s) the logged in user is a member of directly in the sidebar. */}
<MyGroupsSidebarItem
singularTitle="My Squad"
pluralTitle="My Squads"
icon={GroupIcon}
/>
</SidebarGroup>
</Sidebar>
),
};
},
}),
],
export const SidebarContent = NavContentBlueprint.make({
params: {
component: ({ navItems }) => {
const nav = navItems.withComponent(item => (
<SidebarItem icon={() => item.icon} to={item.href} text={item.title} />
));
return (
<Sidebar>
<SidebarLogo />
<SidebarDivider />
<SidebarGroup label="Menu" icon={<MenuIcon />}>
{nav.rest()}
<MyGroupsSidebarItem
singularTitle="My Squad"
pluralTitle="My Squads"
icon={GroupIcon}
/>
</SidebarGroup>
</Sidebar>
);
},
},
});
```
For more details on customizing the sidebar, see the [app migration guide](https://backstage.io/docs/frontend-system/building-apps/migrating#app-root-sidebar).
-26
View File
@@ -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<undefined>;
},
'core.nav-item.target',
{}
>;
inputs: {};
params: {
title: string;
icon: IconComponent;
routeRef: RouteRef_2<undefined>;
};
}>;
'page:scaffolder': OverridableExtensionDefinition<{
config: {
path: string | undefined;
+2 -10
View File
@@ -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: <CreateComponentIcon fontSize="inherit" />,
});
},
});
@@ -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: {
-2
View File
@@ -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,
-53
View File
@@ -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<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
'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<undefined>;
},
'core.nav-item.target',
{}
>;
inputs: {};
params: {
title: string;
icon: IconComponent;
routeRef: RouteRef<undefined>;
};
}>;
'page:search': OverridableExtensionDefinition<{
config: {
noTrack: boolean;
@@ -214,33 +188,6 @@ export const searchApi: OverridableExtensionDefinition<{
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
// @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<undefined>;
},
'core.nav-item.target',
{}
>;
inputs: {};
params: {
title: string;
icon: IconComponent;
routeRef: RouteRef<undefined>;
};
}>;
// @alpha (undocumented)
export const searchPage: OverridableExtensionDefinition<{
config: {
+3 -11
View File
@@ -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: <SearchIcon fontSize="inherit" />,
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: <SearchIcon fontSize="inherit" />,
info: { packageJson: () => import('../package.json') },
extensions: [searchApi, searchPage, searchNavItem],
extensions: [searchApi, searchPage],
routes: {
root: rootRouteRef,
},
-26
View File
@@ -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<undefined>;
},
'core.nav-item.target',
{}
>;
inputs: {};
params: {
title: string;
icon: IconComponent;
routeRef: RouteRef_2<undefined>;
};
}>;
'page:techdocs': OverridableExtensionDefinition<{
kind: 'page';
name: undefined;
+2 -11
View File
@@ -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: <RiArticleLine />,
loader: () =>
import('./components/TechDocsIndexPageContent').then(m => (
<m.TechDocsIndexPageContent />
@@ -265,15 +266,6 @@ const techDocsEntityContentEmptyState = createExtension({
factory: () => [],
});
/** @alpha */
const techDocsNavItem = NavItemBlueprint.make({
params: {
icon: () => <RiArticleLine />,
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,
+12 -65
View File
@@ -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<undefined>;
root: RouteRef<undefined>;
},
{},
{
'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<undefined>;
},
'core.nav-item.target',
{}
>;
inputs: {};
params: {
title: string;
icon: IconComponent;
routeRef: RouteRef<undefined>;
};
}>;
'page:user-settings': OverridableExtensionDefinition<{
kind: 'page';
name: undefined;
@@ -62,7 +36,7 @@ const _default: OverridableFrontendPlugin<
output:
| ExtensionDataRef<string, 'core.routing.path', {}>
| ExtensionDataRef<
RouteRef<AnyRouteRefParams>,
RouteRef_2<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
@@ -88,7 +62,7 @@ const _default: OverridableFrontendPlugin<
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
RouteRef_2<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
@@ -120,7 +94,7 @@ const _default: OverridableFrontendPlugin<
title?: string;
icon?: IconElement;
loader?: () => Promise<JSX_2.Element>;
routeRef?: RouteRef;
routeRef?: RouteRef_2;
noHeader?: boolean;
};
}>;
@@ -136,7 +110,7 @@ const _default: OverridableFrontendPlugin<
output:
| ExtensionDataRef<string, 'core.routing.path', {}>
| ExtensionDataRef<
RouteRef<AnyRouteRefParams>,
RouteRef_2<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
@@ -168,7 +142,7 @@ const _default: OverridableFrontendPlugin<
title: string;
icon?: IconElement;
loader: () => Promise<JSX.Element>;
routeRef?: RouteRef;
routeRef?: RouteRef_2;
};
}>;
'sub-page:user-settings/feature-flags': OverridableExtensionDefinition<{
@@ -185,7 +159,7 @@ const _default: OverridableFrontendPlugin<
output:
| ExtensionDataRef<string, 'core.routing.path', {}>
| ExtensionDataRef<
RouteRef<AnyRouteRefParams>,
RouteRef_2<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
@@ -206,7 +180,7 @@ const _default: OverridableFrontendPlugin<
title: string;
icon?: IconElement;
loader: () => Promise<JSX.Element>;
routeRef?: RouteRef;
routeRef?: RouteRef_2;
};
}>;
'sub-page:user-settings/general': OverridableExtensionDefinition<{
@@ -223,7 +197,7 @@ const _default: OverridableFrontendPlugin<
output:
| ExtensionDataRef<string, 'core.routing.path', {}>
| ExtensionDataRef<
RouteRef<AnyRouteRefParams>,
RouteRef_2<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
@@ -244,40 +218,13 @@ const _default: OverridableFrontendPlugin<
title: string;
icon?: IconElement;
loader: () => Promise<JSX.Element>;
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<undefined>;
},
'core.nav-item.target',
{}
>;
inputs: {};
params: {
title: string;
icon: IconComponent;
routeRef: RouteRef<undefined>;
};
}>;
// @alpha @deprecated (undocumented)
export const userSettingsTranslationRef: TranslationRef<
'user-settings',
+1 -11
View File
@@ -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: <SettingsIcon fontSize="inherit" />,
},
});
@@ -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,