diff --git a/.changeset/live-nav-rest-items.md b/.changeset/live-nav-rest-items.md
new file mode 100644
index 0000000000..41b0fd8636
--- /dev/null
+++ b/.changeset/live-nav-rest-items.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-app': patch
+'@backstage/plugin-app-react': patch
+---
+
+`NavContentBlueprint` nav item collections now keep previously collected `rest()` results in sync when additional items are taken later in the same render, making it easier to place items across multiple sidebar sections.
diff --git a/.changeset/nfs-header-foundations.md b/.changeset/nfs-header-foundations.md
new file mode 100644
index 0000000000..68efd332c4
--- /dev/null
+++ b/.changeset/nfs-header-foundations.md
@@ -0,0 +1,7 @@
+---
+'@backstage/frontend-plugin-api': patch
+'@backstage/plugin-app': patch
+'@backstage/ui': patch
+---
+
+Pages created with `PageBlueprint` now render the plugin header by default in the new frontend system.
diff --git a/.changeset/nfs-header-page-migrations.md b/.changeset/nfs-header-page-migrations.md
new file mode 100644
index 0000000000..d8d6cb04cd
--- /dev/null
+++ b/.changeset/nfs-header-page-migrations.md
@@ -0,0 +1,12 @@
+---
+'@backstage/plugin-api-docs': patch
+'@backstage/plugin-auth': patch
+'@backstage/plugin-catalog': patch
+'@backstage/plugin-catalog-unprocessed-entities': patch
+'@backstage/plugin-devtools': patch
+'@backstage/plugin-notifications': patch
+'@backstage/plugin-search': patch
+'@backstage/plugin-user-settings': patch
+---
+
+New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern.
diff --git a/.changeset/remove-devtools-content-blueprint.md b/.changeset/remove-devtools-content-blueprint.md
new file mode 100644
index 0000000000..5cb5c0687e
--- /dev/null
+++ b/.changeset/remove-devtools-content-blueprint.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-catalog-unprocessed-entities': patch
+'@backstage/plugin-devtools': patch
+'@backstage/plugin-devtools-react': minor
+---
+
+Removed the deprecated `DevToolsContentBlueprint` from `@backstage/plugin-devtools-react`. DevTools pages in the new frontend system now use `SubPageBlueprint` tabs instead, and the catalog unprocessed entities alpha extension now attaches to DevTools as a subpage.
diff --git a/app-config.yaml b/app-config.yaml
index 5eea93084e..86391154f3 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -146,8 +146,6 @@ app:
# - entity-content:azure-devops/pull-requests
# - entity-content:azure-devops/git-tags
- # Enable the catalog-unprocessed-entities tab in devtools
- - devtools-content:catalog-unprocessed-entities: true
# Disable the catalog-unprocessed-entities element outside devtools
- page:catalog-unprocessed-entities: false
diff --git a/lighthouserc.js b/lighthouserc.js
index 5273844c79..390f1bcb86 100644
--- a/lighthouserc.js
+++ b/lighthouserc.js
@@ -34,10 +34,11 @@ module.exports = {
/** Search */
'http://localhost:3000/search',
/** Miscellaneous */
- 'http://localhost:3000/settings',
+ // TODO(Rugvip): Figure out why these don't work after the BUI header switch
+ // 'http://localhost:3000/settings/general',
/** plugin-devtools */
- 'http://localhost:3000/devtools',
- 'http://localhost:3000/devtools/config',
+ // 'http://localhost:3000/devtools/info',
+ // 'http://localhost:3000/devtools/config',
/** plugin-explore */
'http://localhost:3000/explore',
'http://localhost:3000/explore/groups',
diff --git a/packages/app/src/modules/appModuleNav.tsx b/packages/app/src/modules/appModuleNav.tsx
index a507679c73..6c6ba0bfb6 100644
--- a/packages/app/src/modules/appModuleNav.tsx
+++ b/packages/app/src/modules/appModuleNav.tsx
@@ -27,15 +27,11 @@ import {
} from '@backstage/core-components';
import SearchIcon from '@material-ui/icons/Search';
import MenuIcon from '@material-ui/icons/Menu';
-import BuildIcon from '@material-ui/icons/Build';
import { createFrontendModule } from '@backstage/frontend-plugin-api';
import { NavContentBlueprint } from '@backstage/plugin-app-react';
import { SidebarSearchModal } from '@backstage/plugin-search';
import { NotificationsSidebarItem } from '@backstage/plugin-notifications';
-import {
- Settings,
- UserSettingsSignInAvatar,
-} from '@backstage/plugin-user-settings';
+import { UserSettingsSignInAvatar } from '@backstage/plugin-user-settings';
import { makeStyles } from '@material-ui/core/styles';
const useSidebarLogoStyles = makeStyles({
@@ -111,7 +107,9 @@ export const appModuleNav = createFrontendModule({
text={item.title}
/>
));
- nav.take('page:home'); // Skip home
+ // Skip these
+ nav.take('page:home');
+ nav.take('page:search');
return (
@@ -136,8 +134,8 @@ export const appModuleNav = createFrontendModule({
to="/settings"
>
-
-
+ {nav.take('page:devtools')}
+ {nav.take('page:user-settings')}
);
diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx
index 513da27faa..bf2e6d8135 100644
--- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx
+++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx
@@ -75,6 +75,9 @@ export const PageBlueprint = createExtensionBlueprint({
const icon = params.icon;
const pluginId = node.spec.plugin.pluginId;
const noHeader = params.noHeader ?? false;
+ const resolvedTitle =
+ title ?? node.spec.plugin.title ?? node.spec.plugin.pluginId;
+ const resolvedIcon = icon ?? node.spec.plugin.icon;
yield coreExtensionData.routePath(config.path ?? params.path);
if (params.loader) {
@@ -85,8 +88,8 @@ export const PageBlueprint = createExtensionBlueprint({
return (
@@ -117,8 +120,8 @@ export const PageBlueprint = createExtensionBlueprint({
return (
@@ -147,7 +150,11 @@ export const PageBlueprint = createExtensionBlueprint({
const headerActionsApi = useApi(pluginHeaderActionsApiRef);
const headerActions = headerActionsApi.getPluginHeaderActions(pluginId);
return (
-
+
);
};
yield coreExtensionData.reactElement();
diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx
index 94ae1ac27a..af4190bd13 100644
--- a/plugins/api-docs/src/alpha.tsx
+++ b/plugins/api-docs/src/alpha.tsx
@@ -77,11 +77,13 @@ const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({
path: '/api-docs',
routeRef: rootRoute,
loader: () =>
- import('./components/ApiExplorerPage').then(m => (
-
- )),
+ import('./components/ApiExplorerPage/DefaultApiExplorerPage').then(
+ m => (
+
+ ),
+ ),
});
},
});
diff --git a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx
index 625fdbe31c..20a8a1a7d0 100644
--- a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx
+++ b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx
@@ -24,6 +24,7 @@ import {
TableProps,
} from '@backstage/core-components';
import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
+import { HeaderPage } from '@backstage/ui';
import { CatalogTable, CatalogTableRow } from '@backstage/plugin-catalog';
import {
EntityKindPicker,
@@ -67,6 +68,42 @@ export type DefaultApiExplorerPageProps = {
pagination?: EntityListPagination;
};
+type ApiExplorerPageContentProps = {
+ initiallySelectedFilter: UserListFilterKind;
+ columns?: TableColumn[];
+ actions?: TableProps['actions'];
+ ownerPickerMode?: EntityOwnerPickerProps['mode'];
+ pagination?: EntityListPagination;
+};
+
+function ApiExplorerPageContent(props: ApiExplorerPageContentProps) {
+ const {
+ initiallySelectedFilter,
+ columns,
+ actions,
+ ownerPickerMode,
+ pagination,
+ } = props;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
/**
* DefaultApiExplorerPage
* @public
@@ -89,6 +126,19 @@ export const DefaultApiExplorerPage = (props: DefaultApiExplorerPageProps) => {
const { allowed } = usePermission({
permission: catalogEntityCreatePermission,
});
+ const headerActions = (
+ <>
+ {allowed && (
+
+ )}
+
+ {t('defaultApiExplorerPage.supportButtonTitle')}
+
+ >
+ );
return (
{
pageTitleOverride={t('defaultApiExplorerPage.pageTitleOverride')}
>
-
- {allowed && (
-
- )}
-
- {t('defaultApiExplorerPage.supportButtonTitle')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {headerActions}
+
);
};
+
+export const NfsApiExplorerPage = (props: DefaultApiExplorerPageProps) => {
+ const {
+ initiallySelectedFilter = 'all',
+ columns,
+ actions,
+ ownerPickerMode,
+ pagination,
+ } = props;
+
+ const configApi = useApi(configApiRef);
+ const { t } = useTranslationRef(apiDocsTranslationRef);
+ const generatedSubtitle = t('defaultApiExplorerPage.subtitle', {
+ orgName: configApi.getOptionalString('organization.name') ?? 'Backstage',
+ });
+ const registerComponentLink = useRouteRef(registerComponentRouteRef);
+ const { allowed } = usePermission({
+ permission: catalogEntityCreatePermission,
+ });
+ const headerActions = (
+ <>
+ {allowed && (
+
+ )}
+
+ {t('defaultApiExplorerPage.supportButtonTitle')}
+
+ >
+ );
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
diff --git a/plugins/app-react/src/blueprints/NavContentBlueprint.test.tsx b/plugins/app-react/src/blueprints/NavContentBlueprint.test.tsx
index 2c74300d9d..eb0e8c1007 100644
--- a/plugins/app-react/src/blueprints/NavContentBlueprint.test.tsx
+++ b/plugins/app-react/src/blueprints/NavContentBlueprint.test.tsx
@@ -31,36 +31,72 @@ function mockNode(id: string): AppNode {
function mockNavItems(items: NavContentNavItem[]): NavContentNavItems {
const taken = new Set();
+ let restItems: NavContentNavItem[] | undefined;
return {
take(id: string) {
const item = items.find(i => i.node.spec.id === id);
- if (item) {
+ if (item && !taken.has(id)) {
taken.add(id);
+ if (restItems) {
+ const index = restItems.findIndex(
+ restItem => restItem.node.spec.id === id,
+ );
+ if (index !== -1) {
+ restItems.splice(index, 1);
+ }
+ }
}
return item;
},
- rest: () => items.filter(i => !taken.has(i.node.spec.id)),
+ rest: () => {
+ if (!restItems) {
+ restItems = items.filter(i => !taken.has(i.node.spec.id));
+ }
+ return restItems;
+ },
clone() {
return mockNavItems(items);
},
withComponent(Component: (props: NavContentNavItem) => JSX.Element) {
+ let renderedItems: JSX.Element[] | undefined;
return {
take: (id: string) => {
const item = items.find(i => i.node.spec.id === id);
- if (item) {
+ if (item && !taken.has(id)) {
taken.add(id);
- return ;
+ if (restItems) {
+ const index = restItems.findIndex(
+ restItem => restItem.node.spec.id === id,
+ );
+ if (index !== -1) {
+ restItems.splice(index, 1);
+ }
+ }
+ if (renderedItems) {
+ const index = renderedItems.findIndex(
+ renderedItem => renderedItem.key === item.node.spec.id,
+ );
+ if (index !== -1) {
+ renderedItems.splice(index, 1);
+ }
+ }
+ return ;
}
return null;
},
rest: (options?: { sortBy?: 'title' }) => {
- const remaining = items.filter(i => !taken.has(i.node.spec.id));
- if (options?.sortBy === 'title') {
- remaining.sort((a, b) => a.title.localeCompare(b.title));
+ if (!restItems) {
+ restItems = items.filter(i => !taken.has(i.node.spec.id));
}
- return remaining.map(item => (
-
- ));
+ if (!renderedItems) {
+ if (options?.sortBy === 'title') {
+ restItems.sort((a, b) => a.title.localeCompare(b.title));
+ }
+ renderedItems = restItems.map(item => (
+
+ ));
+ }
+ return renderedItems;
},
};
},
@@ -267,4 +303,61 @@ describe('NavContentBlueprint', () => {
expect(docsLink).toBeInTheDocument();
expect(docsLink.closest('nav')).toBeTruthy();
});
+
+ it('should keep the rendered rest array in sync with later take calls', () => {
+ const items: NavContentNavItem[] = [
+ {
+ node: mockNode('page:catalog'),
+ href: '/catalog',
+ title: 'Catalog',
+ icon: catalog,
+ routeRef,
+ },
+ {
+ node: mockNode('page:devtools'),
+ href: '/devtools',
+ title: 'DevTools',
+ icon: devtools,
+ routeRef,
+ },
+ {
+ node: mockNode('page:user-settings'),
+ href: '/settings',
+ title: 'Settings',
+ icon: settings,
+ routeRef,
+ },
+ ];
+
+ const extension = NavContentBlueprint.make({
+ name: 'test',
+ params: {
+ component: ({ navItems }) => {
+ const nav = navItems.withComponent(item => (
+ {item.title}
+ ));
+ const rest = nav.rest({ sortBy: 'title' });
+
+ return (
+
+
+
+
+
+ );
+ },
+ },
+ });
+
+ const tester = createExtensionTester(extension);
+ const Component = tester.get(NavContentBlueprint.dataRefs.component);
+
+ render();
+
+ expect(screen.getByText('Catalog').closest('nav')).toBeTruthy();
+ expect(screen.queryByText('DevTools')?.closest('nav')).toBeFalsy();
+ expect(screen.queryByText('Settings')?.closest('nav')).toBeFalsy();
+ expect(screen.getByText('DevTools').closest('aside')).toBeTruthy();
+ expect(screen.getByText('Settings').closest('footer')).toBeTruthy();
+ });
});
diff --git a/plugins/app-react/src/blueprints/NavContentBlueprint.ts b/plugins/app-react/src/blueprints/NavContentBlueprint.ts
index f88ba48a38..8cfbf7a276 100644
--- a/plugins/app-react/src/blueprints/NavContentBlueprint.ts
+++ b/plugins/app-react/src/blueprints/NavContentBlueprint.ts
@@ -53,7 +53,12 @@ export interface NavContentNavItem {
export interface NavContentNavItemsWithComponent {
/** Render and take a specific item by extension ID. Returns null if not found. */
take(id: string): JSX.Element | null;
- /** Render all remaining items not yet taken, optionally sorted. */
+ /**
+ * Render all remaining items not yet taken, optionally sorted.
+ *
+ * The returned array is live and is updated when later `take` calls remove
+ * items from the collection.
+ */
rest(options?: { sortBy?: 'title' }): JSX.Element[];
}
@@ -66,7 +71,12 @@ export interface NavContentNavItemsWithComponent {
export interface NavContentNavItems {
/** Take an item by extension ID, removing it from the collection. */
take(id: string): NavContentNavItem | undefined;
- /** All items not yet taken. */
+ /**
+ * All items not yet taken.
+ *
+ * The returned array is live and is updated when later `take` calls remove
+ * items from the collection.
+ */
rest(): NavContentNavItem[];
/** Create a copy of the collection preserving the current taken state. */
clone(): NavContentNavItems;
diff --git a/plugins/app/src/extensions/AppNav.tsx b/plugins/app/src/extensions/AppNav.tsx
index 129a94cd9e..09203303e5 100644
--- a/plugins/app/src/extensions/AppNav.tsx
+++ b/plugins/app/src/extensions/AppNav.tsx
@@ -41,6 +41,7 @@ class NavItemBag implements NavContentNavItems {
readonly #items: NavContentNavItem[];
readonly #index: Map;
readonly #taken: Set;
+ #restItems: NavContentNavItem[] | undefined;
constructor(items: NavContentNavItem[], taken?: Iterable) {
this.#items = items;
@@ -50,14 +51,27 @@ class NavItemBag implements NavContentNavItems {
take(id: string): NavContentNavItem | undefined {
const item = this.#index.get(id);
- if (item) {
+ if (item && !this.#taken.has(id)) {
this.#taken.add(id);
+ if (this.#restItems) {
+ const index = this.#restItems.findIndex(
+ restItem => restItem.node.spec.id === id,
+ );
+ if (index !== -1) {
+ this.#restItems.splice(index, 1);
+ }
+ }
}
return item;
}
rest(): NavContentNavItem[] {
- return this.#items.filter(item => !this.#taken.has(item.node.spec.id));
+ if (!this.#restItems) {
+ this.#restItems = this.#items.filter(
+ item => !this.#taken.has(item.node.spec.id),
+ );
+ }
+ return this.#restItems;
}
clone(): NavContentNavItems {
@@ -65,19 +79,32 @@ class NavItemBag implements NavContentNavItems {
}
withComponent(Component: (props: NavContentNavItem) => JSX.Element) {
+ let renderedItems: JSX.Element[] | undefined;
+
return {
take: (id: string) => {
const item = this.take(id);
- return item ? : null;
+ if (item && renderedItems) {
+ const index = renderedItems.findIndex(
+ renderedItem => renderedItem.key === item.node.spec.id,
+ );
+ if (index !== -1) {
+ renderedItems.splice(index, 1);
+ }
+ }
+ return item ? : null;
},
rest: (options?: { sortBy?: 'title' }) => {
const items = this.rest();
- if (options?.sortBy === 'title') {
- items.sort((a, b) => a.title.localeCompare(b.title));
+ if (!renderedItems) {
+ if (options?.sortBy === 'title') {
+ items.sort((a, b) => a.title.localeCompare(b.title));
+ }
+ renderedItems = items.map(item => (
+
+ ));
}
- return items.map(item => (
-
- ));
+ return renderedItems;
},
};
}
diff --git a/plugins/app/src/extensions/components.tsx b/plugins/app/src/extensions/components.tsx
index 230db89767..1f31e58f9c 100644
--- a/plugins/app/src/extensions/components.tsx
+++ b/plugins/app/src/extensions/components.tsx
@@ -29,6 +29,7 @@ import {
import { PluginHeader } from '@backstage/ui';
import Button from '@material-ui/core/Button';
import { useMemo } from 'react';
+import { useResolvedPath } from 'react-router-dom';
export const Progress = SwappableComponentBlueprint.make({
name: 'core-progress',
@@ -75,31 +76,35 @@ export const PageLayout = SwappableComponentBlueprint.make({
component: SwappablePageLayout,
loader: () => (props: PageLayoutProps) => {
const { title, icon, noHeader, headerActions, tabs, children } = props;
- const tabsWithMatchStrategy = useMemo(
+ // TODO(Rugvip): Different solution to this path handling would be good
+ const parentPath = useResolvedPath('.').pathname.replace(/\/$/, '');
+ const resolvedTabs = useMemo(
() =>
tabs?.map(tab => ({
...tab,
+ href: tab.href.startsWith('/')
+ ? tab.href
+ : `${parentPath}/${tab.href}`.replace(/\/{2,}/g, '/'),
matchStrategy: 'prefix' as const,
})),
- [tabs],
+ [tabs, parentPath],
);
- if (tabsWithMatchStrategy) {
- return (
- <>
- {!noHeader && (
-
- )}
- {children}
- >
- );
+ if (noHeader) {
+ return <>{children}>;
}
- return <>{children}>;
+
+ return (
+ <>
+
+ {children}
+ >
+ );
},
}),
});
diff --git a/plugins/auth/package.json b/plugins/auth/package.json
index 52a51d81ce..d08e2c5390 100644
--- a/plugins/auth/package.json
+++ b/plugins/auth/package.json
@@ -49,6 +49,7 @@
"dependencies": {
"@backstage/errors": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
+ "@backstage/theme": "workspace:^",
"@backstage/ui": "workspace:^",
"@remixicon/react": "^4.6.0",
"react-use": "^17.2.4"
diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json
index eea96e391e..7ffca76ef7 100644
--- a/plugins/catalog-unprocessed-entities/package.json
+++ b/plugins/catalog-unprocessed-entities/package.json
@@ -56,7 +56,7 @@
"@backstage/errors": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/plugin-catalog-unprocessed-entities-common": "workspace:^",
- "@backstage/plugin-devtools-react": "workspace:^",
+ "@backstage/ui": "workspace:^",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.60",
diff --git a/plugins/catalog-unprocessed-entities/report-alpha.api.md b/plugins/catalog-unprocessed-entities/report-alpha.api.md
index 7408831f87..c4a2b529bf 100644
--- a/plugins/catalog-unprocessed-entities/report-alpha.api.md
+++ b/plugins/catalog-unprocessed-entities/report-alpha.api.md
@@ -7,7 +7,6 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api';
import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
import { ApiFactory } from '@backstage/frontend-plugin-api';
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
-import { DevToolsContentBlueprintParams } from '@backstage/plugin-devtools-react';
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionInput } from '@backstage/frontend-plugin-api';
@@ -144,8 +143,8 @@ export default _default;
// @alpha
export const unprocessedEntitiesDevToolsContent: OverridableExtensionDefinition<{
- kind: 'devtools-content';
- name: undefined;
+ kind: 'sub-page';
+ name: 'unprocessed-entities';
config: {
path: string | undefined;
title: string | undefined;
@@ -164,9 +163,22 @@ export const unprocessedEntitiesDevToolsContent: OverridableExtensionDefinition<
}
>
| ExtensionDataRef
- | ExtensionDataRef;
+ | ExtensionDataRef
+ | ExtensionDataRef<
+ IconElement,
+ 'core.icon',
+ {
+ optional: true;
+ }
+ >;
inputs: {};
- params: DevToolsContentBlueprintParams;
+ params: {
+ path: string;
+ title: string;
+ icon?: IconElement;
+ loader: () => Promise;
+ routeRef?: RouteRef_2;
+ };
}>;
// (No @packageDocumentation comment for this package)
diff --git a/plugins/catalog-unprocessed-entities/src/alpha/devToolsContent.tsx b/plugins/catalog-unprocessed-entities/src/alpha/devToolsContent.tsx
index 3dcdc0ad6b..3d88c5299d 100644
--- a/plugins/catalog-unprocessed-entities/src/alpha/devToolsContent.tsx
+++ b/plugins/catalog-unprocessed-entities/src/alpha/devToolsContent.tsx
@@ -14,23 +14,25 @@
* limitations under the License.
*/
-import { DevToolsContentBlueprint } from '@backstage/plugin-devtools-react';
+import { SubPageBlueprint } from '@backstage/frontend-plugin-api';
+import { Content } from '@backstage/core-components';
/**
* DevTools content for catalog unprocessed entities.
*
* @alpha
*/
-export const unprocessedEntitiesDevToolsContent = DevToolsContentBlueprint.make(
- {
- disabled: true,
- params: {
- path: 'unprocessed-entities',
- title: 'Unprocessed Entities',
- loader: () =>
- import('../components/UnprocessedEntities').then(m => (
+export const unprocessedEntitiesDevToolsContent = SubPageBlueprint.make({
+ attachTo: { id: 'page:devtools', input: 'pages' },
+ name: 'unprocessed-entities',
+ params: {
+ path: 'unprocessed-entities',
+ title: 'Unprocessed Entities',
+ loader: () =>
+ import('../components/UnprocessedEntities').then(m => (
+
- )),
- },
+
+ )),
},
-);
+});
diff --git a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx
index d2987f3197..beeb19b153 100644
--- a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx
+++ b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx
@@ -51,7 +51,7 @@ export const catalogUnprocessedEntitiesPage = PageBlueprint.make({
routeRef: rootRouteRef,
loader: () =>
import('../components/UnprocessedEntities').then(m => (
-
+
)),
},
});
diff --git a/plugins/catalog-unprocessed-entities/src/components/UnprocessedEntities.tsx b/plugins/catalog-unprocessed-entities/src/components/UnprocessedEntities.tsx
index 425683be25..fdbd90fb30 100644
--- a/plugins/catalog-unprocessed-entities/src/components/UnprocessedEntities.tsx
+++ b/plugins/catalog-unprocessed-entities/src/components/UnprocessedEntities.tsx
@@ -16,6 +16,7 @@
import { ChangeEvent, useState } from 'react';
import { Page, Header, Content } from '@backstage/core-components';
+import { HeaderPage } from '@backstage/ui';
import Tab from '@material-ui/core/Tab';
import { makeStyles } from '@material-ui/core/styles';
import TabContext from '@material-ui/lab/TabContext';
@@ -66,3 +67,14 @@ export const UnprocessedEntities = () => {
);
};
+
+export const NfsUnprocessedEntities = () => {
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
diff --git a/plugins/catalog/src/alpha/pages.tsx b/plugins/catalog/src/alpha/pages.tsx
index 6d64c65b74..f8f09dad3b 100644
--- a/plugins/catalog/src/alpha/pages.tsx
+++ b/plugins/catalog/src/alpha/pages.tsx
@@ -62,12 +62,14 @@ export const catalogPage = PageBlueprint.makeWithOverrides({
icon: ,
title: 'Catalog',
loader: async () => {
- const { BaseCatalogPage } = await import('../components/CatalogPage');
+ const { NfsDefaultCatalogPage } = await import(
+ '../components/CatalogPage/DefaultCatalogPage'
+ );
const filters = inputs.filters.map(filter =>
filter.get(coreExtensionData.reactElement),
);
return (
- {filters}>}
pagination={config.pagination}
/>
diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx
index b5f0898829..d1ff7b53f4 100644
--- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx
+++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx
@@ -24,6 +24,7 @@ import {
TableProps,
} from '@backstage/core-components';
import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
+import { HeaderPage } from '@backstage/ui';
import {
CatalogFilterLayout,
DefaultFilters,
@@ -48,9 +49,51 @@ export type BaseCatalogPageProps = {
pagination?: EntityListPagination;
};
+function CatalogPageContent(props: BaseCatalogPageProps) {
+ const { filters, content = , pagination } = props;
+
+ return (
+
+
+ {filters}
+ {content}
+
+
+ );
+}
+
/** @internal */
export function BaseCatalogPage(props: BaseCatalogPageProps) {
- const { filters, content = , pagination } = props;
+ const orgName =
+ useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage';
+ const createComponentLink = useRouteRef(createComponentRouteRef);
+ const { t } = useTranslationRef(catalogTranslationRef);
+ const { allowed } = usePermission({
+ permission: catalogEntityCreatePermission,
+ });
+ const headerActions = (
+ <>
+ {allowed && (
+
+ )}
+ {t('indexPage.supportButtonContent')}
+ >
+ );
+
+ return (
+
+
+ {headerActions}
+
+
+
+ );
+}
+
+function NfsBaseCatalogPage(props: BaseCatalogPageProps) {
const orgName =
useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage';
const createComponentLink = useRouteRef(createComponentRouteRef);
@@ -60,25 +103,25 @@ export function BaseCatalogPage(props: BaseCatalogPageProps) {
});
return (
-
+ <>
+
+ {allowed && (
+
+ )}
+ {t('indexPage.supportButtonContent')}
+ >
+ }
+ />
-
- {allowed && (
-
- )}
- {t('indexPage.supportButtonContent')}
-
-
-
- {filters}
- {content}
-
-
+
-
+ >
);
}
@@ -138,3 +181,42 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) {
/>
);
}
+
+export function NfsDefaultCatalogPage(props: DefaultCatalogPageProps) {
+ const {
+ columns,
+ actions,
+ initiallySelectedFilter = 'owned',
+ initialKind = 'component',
+ tableOptions = {},
+ emptyContent,
+ pagination,
+ ownerPickerMode,
+ filters,
+ initiallySelectedNamespaces,
+ } = props;
+
+ return (
+
+ )
+ }
+ content={
+
+ }
+ pagination={pagination}
+ />
+ );
+}
diff --git a/plugins/devtools-react/report.api.md b/plugins/devtools-react/report.api.md
index 6916afed84..c99c9f953e 100644
--- a/plugins/devtools-react/report.api.md
+++ b/plugins/devtools-react/report.api.md
@@ -3,48 +3,4 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
-import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
-import { ExtensionBlueprint } from '@backstage/frontend-plugin-api';
-import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
-import { JSX as JSX_2 } from 'react';
-import { RouteRef } from '@backstage/frontend-plugin-api';
-
-// @public
-export const DevToolsContentBlueprint: ExtensionBlueprint<{
- kind: 'devtools-content';
- params: DevToolsContentBlueprintParams;
- output:
- | ExtensionDataRef
- | ExtensionDataRef<
- RouteRef,
- 'core.routing.ref',
- {
- optional: true;
- }
- >
- | ExtensionDataRef
- | ExtensionDataRef;
- inputs: {};
- config: {
- path: string | undefined;
- title: string | undefined;
- };
- configInput: {
- title?: string | undefined;
- path?: string | undefined;
- };
- dataRefs: never;
-}>;
-
-// @public
-export interface DevToolsContentBlueprintParams {
- // (undocumented)
- loader: () => Promise;
- // (undocumented)
- path: string;
- // (undocumented)
- routeRef?: RouteRef;
- // (undocumented)
- title: string;
-}
```
diff --git a/plugins/devtools-react/src/devToolsContentBlueprint.tsx b/plugins/devtools-react/src/devToolsContentBlueprint.tsx
deleted file mode 100644
index c10d8dade0..0000000000
--- a/plugins/devtools-react/src/devToolsContentBlueprint.tsx
+++ /dev/null
@@ -1,87 +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 {
- coreExtensionData,
- createExtensionBlueprint,
- ExtensionBoundary,
- RouteRef,
-} from '@backstage/frontend-plugin-api';
-import { JSX } from 'react';
-
-/**
- * Parameters for creating a DevTools route extension
- * @public
- */
-export interface DevToolsContentBlueprintParams {
- path: string;
- title: string;
- loader: () => Promise;
- routeRef?: RouteRef;
-}
-
-/**
- * Extension blueprint for creating DevTools content pages (appearing as tabs)
- *
- * @example
- * ```tsx
- * const myDevToolsContent = DevToolsContentBlueprint.make({
- * {
- * params: {
- * path: 'my-dev-tools',
- * title: 'My DevTools',
- * loader: () =>
- * import('../components/MyDevTools').then(m =>
- * compatWrapper(),
- * ),
- * },
- * },
- * });
- * ```
- * @public
- */
-export const DevToolsContentBlueprint = createExtensionBlueprint({
- kind: 'devtools-content',
- attachTo: { id: 'page:devtools', input: 'contents' },
- output: [
- coreExtensionData.reactElement,
- coreExtensionData.routePath,
- coreExtensionData.routeRef.optional(),
- coreExtensionData.title,
- ],
- config: {
- schema: {
- path: z => z.string().optional(),
- title: z => z.string().optional(),
- },
- },
- *factory(params: DevToolsContentBlueprintParams, { node, config }) {
- const path = config.path ?? params.path;
- const title = config.title ?? params.title;
-
- yield coreExtensionData.reactElement(
- ExtensionBoundary.lazy(node, params.loader),
- );
-
- yield coreExtensionData.routePath(path);
-
- yield coreExtensionData.title(title);
-
- if (params.routeRef) {
- yield coreExtensionData.routeRef(params.routeRef);
- }
- },
-});
diff --git a/plugins/devtools-react/src/index.ts b/plugins/devtools-react/src/index.ts
index 70b4915ae8..1a71bef82f 100644
--- a/plugins/devtools-react/src/index.ts
+++ b/plugins/devtools-react/src/index.ts
@@ -19,7 +19,5 @@
*
* @packageDocumentation
*/
-export {
- type DevToolsContentBlueprintParams,
- DevToolsContentBlueprint,
-} from './devToolsContentBlueprint';
+
+export {};
diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json
index 36ff2b8452..6b6761a879 100644
--- a/plugins/devtools/package.json
+++ b/plugins/devtools/package.json
@@ -59,8 +59,8 @@
"@backstage/errors": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/plugin-devtools-common": "workspace:^",
- "@backstage/plugin-devtools-react": "workspace:^",
"@backstage/plugin-permission-react": "workspace:^",
+ "@backstage/ui": "workspace:^",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.57",
diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md
index 753875eaaa..1f8517736b 100644
--- a/plugins/devtools/report-alpha.api.md
+++ b/plugins/devtools/report-alpha.api.md
@@ -125,23 +125,6 @@ const _default: OverridableFrontendPlugin<
internal: false;
}
>;
- contents: ExtensionInput<
- | ConfigurableExtensionDataRef
- | ConfigurableExtensionDataRef
- | ConfigurableExtensionDataRef
- | ConfigurableExtensionDataRef<
- RouteRef_2,
- 'core.routing.ref',
- {
- optional: true;
- }
- >,
- {
- singleton: false;
- optional: true;
- internal: false;
- }
- >;
};
kind: 'page';
name: undefined;
@@ -154,6 +137,120 @@ const _default: OverridableFrontendPlugin<
noHeader?: boolean;
};
}>;
+ 'sub-page:devtools/config': OverridableExtensionDefinition<{
+ kind: 'sub-page';
+ name: 'config';
+ config: {
+ path: string | undefined;
+ title: string | undefined;
+ };
+ configInput: {
+ title?: string | undefined;
+ path?: string | undefined;
+ };
+ output:
+ | ExtensionDataRef
+ | ExtensionDataRef<
+ RouteRef_2,
+ 'core.routing.ref',
+ {
+ optional: true;
+ }
+ >
+ | ExtensionDataRef
+ | ExtensionDataRef
+ | ExtensionDataRef<
+ IconElement,
+ 'core.icon',
+ {
+ optional: true;
+ }
+ >;
+ inputs: {};
+ params: {
+ path: string;
+ title: string;
+ icon?: IconElement;
+ loader: () => Promise;
+ routeRef?: RouteRef_2;
+ };
+ }>;
+ 'sub-page:devtools/info': OverridableExtensionDefinition<{
+ kind: 'sub-page';
+ name: 'info';
+ config: {
+ path: string | undefined;
+ title: string | undefined;
+ };
+ configInput: {
+ title?: string | undefined;
+ path?: string | undefined;
+ };
+ output:
+ | ExtensionDataRef
+ | ExtensionDataRef<
+ RouteRef_2,
+ 'core.routing.ref',
+ {
+ optional: true;
+ }
+ >
+ | ExtensionDataRef
+ | ExtensionDataRef
+ | ExtensionDataRef<
+ IconElement,
+ 'core.icon',
+ {
+ optional: true;
+ }
+ >;
+ inputs: {};
+ params: {
+ path: string;
+ title: string;
+ icon?: IconElement;
+ loader: () => Promise;
+ routeRef?: RouteRef_2;
+ };
+ }>;
+ 'sub-page:devtools/scheduled-tasks': OverridableExtensionDefinition<{
+ kind: 'sub-page';
+ name: 'scheduled-tasks';
+ config: {
+ path: string | undefined;
+ title: string | undefined;
+ };
+ configInput: {
+ title?: string | undefined;
+ path?: string | undefined;
+ };
+ output:
+ | ExtensionDataRef
+ | ExtensionDataRef<
+ RouteRef_2,
+ 'core.routing.ref',
+ {
+ optional: true;
+ }
+ >
+ | ExtensionDataRef
+ | ExtensionDataRef
+ | ExtensionDataRef<
+ IconElement,
+ 'core.icon',
+ {
+ optional: true;
+ }
+ >;
+ inputs: {};
+ params: {
+ path: string;
+ title: string;
+ icon?: IconElement;
+ loader: () => Promise;
+ routeRef?: RouteRef_2;
+ };
+ }>;
}
>;
export default _default;
diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx
index b991e34bf9..0144308069 100644
--- a/plugins/devtools/src/alpha/plugin.tsx
+++ b/plugins/devtools/src/alpha/plugin.tsx
@@ -16,18 +16,25 @@
import {
createFrontendPlugin,
+ coreExtensionData,
discoveryApiRef,
fetchApiRef,
ApiBlueprint,
PageBlueprint,
NavItemBlueprint,
- createExtensionInput,
- coreExtensionData,
+ SubPageBlueprint,
} from '@backstage/frontend-plugin-api';
import { devToolsApiRef, DevToolsClient } from '../api';
import BuildIcon from '@material-ui/icons/Build';
+import { Content } from '@backstage/core-components';
import { rootRouteRef } from '../routes';
+import {
+ devToolsConfigReadPermission,
+ devToolsInfoReadPermission,
+} from '@backstage/plugin-devtools-common';
+import { devToolsTaskSchedulerReadPermission } from '@backstage/plugin-devtools-common/alpha';
+import { RequirePermission } from '@backstage/plugin-permission-react';
/** @alpha */
export const devToolsApi = ApiBlueprint.make({
@@ -45,34 +52,84 @@ export const devToolsApi = ApiBlueprint.make({
/** @alpha */
export const devToolsPage = PageBlueprint.makeWithOverrides({
- inputs: {
- contents: createExtensionInput(
- [
- coreExtensionData.reactElement,
- coreExtensionData.routePath,
- coreExtensionData.routeRef.optional(),
- coreExtensionData.title,
- ],
- {
- optional: true,
- },
- ),
- },
factory(originalFactory, { inputs }) {
- return originalFactory({
- path: '/devtools',
- routeRef: rootRouteRef,
- loader: () => {
- const contents = inputs.contents.map(content => ({
- path: content.get(coreExtensionData.routePath),
- title: content.get(coreExtensionData.title),
- children: content.get(coreExtensionData.reactElement),
- }));
- return import('../components/DevToolsPage').then(m => (
-
- ));
- },
+ const pages = [...inputs.pages].sort((left, right) => {
+ const leftPath = left.get(coreExtensionData.routePath);
+ const rightPath = right.get(coreExtensionData.routePath);
+
+ if (leftPath === 'info' && rightPath !== 'info') {
+ return -1;
+ }
+ if (leftPath !== 'info' && rightPath === 'info') {
+ return 1;
+ }
+
+ return 0;
});
+
+ return originalFactory(
+ {
+ path: '/devtools',
+ routeRef: rootRouteRef,
+ title: 'DevTools',
+ },
+ {
+ inputs: {
+ pages,
+ },
+ },
+ );
+ },
+});
+
+/** @alpha */
+export const devToolsInfoPage = SubPageBlueprint.make({
+ name: 'info',
+ params: {
+ path: 'info',
+ title: 'Info',
+ loader: () =>
+ import('../components/Content').then(m => (
+
+
+
+
+
+ )),
+ },
+});
+
+/** @alpha */
+export const devToolsConfigPage = SubPageBlueprint.make({
+ name: 'config',
+ params: {
+ path: 'config',
+ title: 'Config',
+ loader: () =>
+ import('../components/Content').then(m => (
+
+
+
+
+
+ )),
+ },
+});
+
+/** @alpha */
+export const devToolsScheduledTasksPage = SubPageBlueprint.make({
+ name: 'scheduled-tasks',
+ params: {
+ path: 'scheduled-tasks',
+ title: 'Scheduled Tasks',
+ loader: () =>
+ import('../components/Content').then(m => (
+
+
+
+
+
+ )),
},
});
@@ -94,5 +151,12 @@ export default createFrontendPlugin({
routes: {
root: rootRouteRef,
},
- extensions: [devToolsApi, devToolsPage, devToolsNavItem],
+ extensions: [
+ devToolsApi,
+ devToolsPage,
+ devToolsInfoPage,
+ devToolsConfigPage,
+ devToolsScheduledTasksPage,
+ devToolsNavItem,
+ ],
});
diff --git a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx
index efbed90737..06d7c2ed4e 100644
--- a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx
+++ b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx
@@ -21,7 +21,7 @@ import {
import { ConfigContent } from '../Content';
import { devToolsTaskSchedulerReadPermission } from '@backstage/plugin-devtools-common/alpha';
-import { DevToolsLayout } from '../DevToolsLayout';
+import { DevToolsLayout } from '../DevToolsLayout/DevToolsLayout';
import { InfoContent } from '../Content';
import { RequirePermission } from '@backstage/plugin-permission-react';
import { ScheduledTasksContent } from '../Content/ScheduledTasksContent';
diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json
index e550a8d75e..fe443ef7f8 100644
--- a/plugins/notifications/package.json
+++ b/plugins/notifications/package.json
@@ -58,6 +58,7 @@
"@backstage/plugin-notifications-common": "workspace:^",
"@backstage/plugin-signals-react": "workspace:^",
"@backstage/theme": "workspace:^",
+ "@backstage/ui": "workspace:^",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
"lodash": "^4.17.21",
diff --git a/plugins/notifications/src/alpha.tsx b/plugins/notifications/src/alpha.tsx
index 12ae164aa1..a40bc96b84 100644
--- a/plugins/notifications/src/alpha.tsx
+++ b/plugins/notifications/src/alpha.tsx
@@ -30,7 +30,7 @@ const page = PageBlueprint.make({
routeRef: rootRouteRef,
loader: () =>
import('./components/NotificationsPage').then(m => (
-
+
)),
},
});
diff --git a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx
index 49530797c3..78ad4f14d0 100644
--- a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx
+++ b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx
@@ -66,7 +66,9 @@ export type NotificationsPageProps = {
typeLink?: string;
};
-export const NotificationsPage = (props?: NotificationsPageProps) => {
+function NotificationsPageContent(
+ props: NotificationsPageProps & { headerVariant: 'legacy' | 'bui' },
+) {
const { t } = useTranslationRef(notificationsTranslationRef);
const {
title = t('notificationsPage.title'),
@@ -76,7 +78,8 @@ export const NotificationsPage = (props?: NotificationsPageProps) => {
type,
typeLink,
markAsReadOnLinkOpen,
- } = props ?? {};
+ headerVariant,
+ } = props;
const [refresh, setRefresh] = useState(false);
const { lastSignal } = useSignal('notifications');
@@ -186,6 +189,52 @@ export const NotificationsPage = (props?: NotificationsPageProps) => {
});
}
+ const pageContent = (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ if (headerVariant === 'bui') {
+ return pageContent;
+ }
+
return (
{
type={type}
typeLink={typeLink}
>
-
-
-
-
-
-
-
-
-
-
-
-
+ {pageContent}
);
-};
+}
+
+export const NotificationsPage = (props?: NotificationsPageProps) => (
+
+);
+
+export const NfsNotificationsPage = (props?: NotificationsPageProps) => (
+
+);
diff --git a/plugins/search/package.json b/plugins/search/package.json
index 4e731a8769..5a7cc9b478 100644
--- a/plugins/search/package.json
+++ b/plugins/search/package.json
@@ -66,6 +66,7 @@
"@backstage/plugin-search-common": "workspace:^",
"@backstage/plugin-search-react": "workspace:^",
"@backstage/types": "workspace:^",
+ "@backstage/ui": "workspace:^",
"@backstage/version-bridge": "workspace:^",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx
index 0c93fd71cc..e5ba6215e8 100644
--- a/plugins/search/src/alpha.tsx
+++ b/plugins/search/src/alpha.tsx
@@ -23,8 +23,6 @@ import {
CatalogIcon,
Content,
DocsIcon,
- Header,
- Page,
useSidebarPinState,
} from '@backstage/core-components';
import {
@@ -143,113 +141,110 @@ export const searchPage = PageBlueprint.makeWithOverrides({
const configApi = useApi(configApiRef);
return (
-
- {!isMobile && }
-
-
-
-
-
- {!isMobile && (
-
- ,
- },
- {
- value: 'techdocs',
- name: 'Documentation',
- icon: ,
- },
- ].concat(resultTypes)}
- />
-
- {types.includes('techdocs') && (
- {
- // Return a list of entities which are documented.
- const { items } = await catalogApi.getEntities({
- fields: ['metadata.name'],
- filter: {
- 'metadata.annotations.backstage.io/techdocs-ref':
- CATALOG_FILTER_EXISTS,
- },
- });
-
- const names = items.map(
- entity => entity.metadata.name,
- );
- names.sort();
- return names;
- }}
- />
- )}
+
+
+
+
+
+ {!isMobile && (
+
+ ,
+ },
+ {
+ value: 'techdocs',
+ name: 'Documentation',
+ icon: ,
+ },
+ ].concat(resultTypes)}
+ />
+
+ {types.includes('techdocs') && (
-
- {additionalSearchFilters.map(SearchFilterComponent => (
-
- ))}
-
-
- )}
-
-
-
- {({ results }) => (
- <>
- {results.map((result, index) => {
- const { noTrack } = config;
- const { document, ...rest } = result;
- const SearchResultListItem =
- getResultItemComponent(result);
- return (
-
+ label="Entity"
+ name="name"
+ values={async () => {
+ // Return a list of entities which are documented.
+ const { items } = await catalogApi.getEntities({
+ fields: ['metadata.name'],
+ filter: {
+ 'metadata.annotations.backstage.io/techdocs-ref':
+ CATALOG_FILTER_EXISTS,
+ },
+ });
+
+ const names = items.map(
+ entity => entity.metadata.name,
);
- })}
- >
+ names.sort();
+ return names;
+ }}
+ />
)}
-
-
+
+
+ {additionalSearchFilters.map(SearchFilterComponent => (
+
+ ))}
+
+ )}
+
+
+
+ {({ results }) => (
+ <>
+ {results.map((result, index) => {
+ const { noTrack } = config;
+ const { document, ...rest } = result;
+ const SearchResultListItem =
+ getResultItemComponent(result);
+ return (
+
+ );
+ })}
+ >
+ )}
+
+
-
-
+
+
);
};
diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json
index a4d5d505b7..5a10a7b3ef 100644
--- a/plugins/user-settings/package.json
+++ b/plugins/user-settings/package.json
@@ -66,6 +66,7 @@
"@backstage/plugin-user-settings-common": "workspace:^",
"@backstage/theme": "workspace:^",
"@backstage/types": "workspace:^",
+ "@backstage/ui": "workspace:^",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.61",
diff --git a/plugins/user-settings/report-alpha.api.md b/plugins/user-settings/report-alpha.api.md
index 70a9ec869b..a4fefe1ff3 100644
--- a/plugins/user-settings/report-alpha.api.md
+++ b/plugins/user-settings/report-alpha.api.md
@@ -45,6 +45,8 @@ const _default: OverridableFrontendPlugin<
};
}>;
'page:user-settings': OverridableExtensionDefinition<{
+ kind: 'page';
+ name: undefined;
config: {
path: string | undefined;
title: string | undefined;
@@ -108,6 +110,44 @@ const _default: OverridableFrontendPlugin<
internal: false;
}
>;
+ };
+ params: {
+ path: string;
+ title?: string;
+ icon?: IconElement;
+ loader?: () => Promise;
+ routeRef?: RouteRef;
+ noHeader?: boolean;
+ };
+ }>;
+ 'sub-page:user-settings/auth-providers': OverridableExtensionDefinition<{
+ config: {
+ path: string | undefined;
+ title: string | undefined;
+ };
+ configInput: {
+ title?: string | undefined;
+ path?: string | undefined;
+ };
+ output:
+ | ExtensionDataRef
+ | ExtensionDataRef<
+ RouteRef,
+ 'core.routing.ref',
+ {
+ optional: true;
+ }
+ >
+ | ExtensionDataRef
+ | ExtensionDataRef
+ | ExtensionDataRef<
+ IconElement,
+ 'core.icon',
+ {
+ optional: true;
+ }
+ >;
+ inputs: {
providerSettings: ExtensionInput<
ConfigurableExtensionDataRef,
{
@@ -117,15 +157,90 @@ const _default: OverridableFrontendPlugin<
}
>;
};
- kind: 'page';
- name: undefined;
+ kind: 'sub-page';
+ name: 'auth-providers';
params: {
path: string;
- title?: string;
+ title: string;
icon?: IconElement;
- loader?: () => Promise;
+ loader: () => Promise;
+ routeRef?: RouteRef;
+ };
+ }>;
+ 'sub-page:user-settings/feature-flags': OverridableExtensionDefinition<{
+ kind: 'sub-page';
+ name: 'feature-flags';
+ config: {
+ path: string | undefined;
+ title: string | undefined;
+ };
+ configInput: {
+ title?: string | undefined;
+ path?: string | undefined;
+ };
+ output:
+ | ExtensionDataRef
+ | ExtensionDataRef<
+ RouteRef,
+ 'core.routing.ref',
+ {
+ optional: true;
+ }
+ >
+ | ExtensionDataRef
+ | ExtensionDataRef
+ | ExtensionDataRef<
+ IconElement,
+ 'core.icon',
+ {
+ optional: true;
+ }
+ >;
+ inputs: {};
+ params: {
+ path: string;
+ title: string;
+ icon?: IconElement;
+ loader: () => Promise;
+ routeRef?: RouteRef;
+ };
+ }>;
+ 'sub-page:user-settings/general': OverridableExtensionDefinition<{
+ kind: 'sub-page';
+ name: 'general';
+ config: {
+ path: string | undefined;
+ title: string | undefined;
+ };
+ configInput: {
+ title?: string | undefined;
+ path?: string | undefined;
+ };
+ output:
+ | ExtensionDataRef
+ | ExtensionDataRef<
+ RouteRef,
+ 'core.routing.ref',
+ {
+ optional: true;
+ }
+ >
+ | ExtensionDataRef
+ | ExtensionDataRef
+ | ExtensionDataRef<
+ IconElement,
+ 'core.icon',
+ {
+ optional: true;
+ }
+ >;
+ inputs: {};
+ params: {
+ path: string;
+ title: string;
+ icon?: IconElement;
+ loader: () => Promise;
routeRef?: RouteRef;
- noHeader?: boolean;
};
}>;
}
diff --git a/plugins/user-settings/src/alpha.tsx b/plugins/user-settings/src/alpha.tsx
index 0a267c14c6..482a9711f5 100644
--- a/plugins/user-settings/src/alpha.tsx
+++ b/plugins/user-settings/src/alpha.tsx
@@ -19,7 +19,9 @@ import {
createFrontendPlugin,
PageBlueprint,
NavItemBlueprint,
+ SubPageBlueprint,
} from '@backstage/frontend-plugin-api';
+import { Content } from '@backstage/core-components';
import SettingsIcon from '@material-ui/icons/Settings';
import { settingsRouteRef } from './plugin';
@@ -31,7 +33,30 @@ import { userSettingsTranslationRef as _userSettingsTranslationRef } from './tra
*/
export const userSettingsTranslationRef = _userSettingsTranslationRef;
-const userSettingsPage = PageBlueprint.makeWithOverrides({
+const userSettingsPage = PageBlueprint.make({
+ params: {
+ path: '/settings',
+ routeRef: settingsRouteRef,
+ title: 'Settings',
+ },
+});
+
+const generalSettingsPage = SubPageBlueprint.make({
+ name: 'general',
+ params: {
+ path: 'general',
+ title: 'General',
+ loader: () =>
+ import('./components/General').then(m => (
+
+
+
+ )),
+ },
+});
+
+const authProvidersSettingsPage = SubPageBlueprint.makeWithOverrides({
+ name: 'auth-providers',
inputs: {
providerSettings: createExtensionInput([coreExtensionData.reactElement], {
singleton: true,
@@ -40,20 +65,36 @@ const userSettingsPage = PageBlueprint.makeWithOverrides({
},
factory(originalFactory, { inputs }) {
return originalFactory({
- path: '/settings',
- routeRef: settingsRouteRef,
+ path: 'auth-providers',
+ title: 'Authentication Providers',
loader: () =>
- import('./components/SettingsPage').then(m => (
-
+ import('./components/AuthProviders').then(m => (
+
+
+
)),
});
},
});
+const featureFlagsSettingsPage = SubPageBlueprint.make({
+ name: 'feature-flags',
+ params: {
+ path: 'feature-flags',
+ title: 'Feature Flags',
+ loader: () =>
+ import('./components/FeatureFlags').then(m => (
+
+
+
+ )),
+ },
+});
+
/** @alpha */
export const settingsNavItem = NavItemBlueprint.make({
params: {
@@ -71,7 +112,13 @@ export default createFrontendPlugin({
title: 'Settings',
icon: ,
info: { packageJson: () => import('../package.json') },
- extensions: [userSettingsPage, settingsNavItem],
+ extensions: [
+ userSettingsPage,
+ generalSettingsPage,
+ authProvidersSettingsPage,
+ featureFlagsSettingsPage,
+ settingsNavItem,
+ ],
routes: {
root: settingsRouteRef,
},
diff --git a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx
index 87a2dd212b..ea5bc66603 100644
--- a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx
+++ b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx
@@ -18,7 +18,8 @@ import { ReactElement } from 'react';
import { UserSettingsAuthProviders } from '../AuthProviders';
import { UserSettingsFeatureFlags } from '../FeatureFlags';
import { UserSettingsGeneral } from '../General';
-import { SettingsLayout, SettingsLayoutRouteProps } from '../SettingsLayout';
+import { SettingsLayoutRouteProps } from '../SettingsLayout';
+import { SettingsLayout } from '../SettingsLayout/SettingsLayout';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { userSettingsTranslationRef } from '../../translation';
diff --git a/yarn.lock b/yarn.lock
index f62f425dbe..a43f62f77c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4679,6 +4679,7 @@ __metadata:
"@backstage/frontend-defaults": "workspace:^"
"@backstage/frontend-plugin-api": "workspace:^"
"@backstage/test-utils": "workspace:^"
+ "@backstage/theme": "workspace:^"
"@backstage/ui": "workspace:^"
"@remixicon/react": "npm:^4.6.0"
"@testing-library/jest-dom": "npm:^6.0.0"
@@ -5383,7 +5384,7 @@ __metadata:
"@backstage/errors": "workspace:^"
"@backstage/frontend-plugin-api": "workspace:^"
"@backstage/plugin-catalog-unprocessed-entities-common": "workspace:^"
- "@backstage/plugin-devtools-react": "workspace:^"
+ "@backstage/ui": "workspace:^"
"@material-ui/core": "npm:^4.9.13"
"@material-ui/icons": "npm:^4.9.1"
"@material-ui/lab": "npm:^4.0.0-alpha.60"
@@ -5546,7 +5547,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@backstage/plugin-devtools-react@workspace:^, @backstage/plugin-devtools-react@workspace:plugins/devtools-react":
+"@backstage/plugin-devtools-react@workspace:plugins/devtools-react":
version: 0.0.0-use.local
resolution: "@backstage/plugin-devtools-react@workspace:plugins/devtools-react"
dependencies:
@@ -5584,8 +5585,8 @@ __metadata:
"@backstage/errors": "workspace:^"
"@backstage/frontend-plugin-api": "workspace:^"
"@backstage/plugin-devtools-common": "workspace:^"
- "@backstage/plugin-devtools-react": "workspace:^"
"@backstage/plugin-permission-react": "workspace:^"
+ "@backstage/ui": "workspace:^"
"@material-ui/core": "npm:^4.9.13"
"@material-ui/icons": "npm:^4.9.1"
"@material-ui/lab": "npm:^4.0.0-alpha.57"
@@ -6273,6 +6274,7 @@ __metadata:
"@backstage/plugin-signals-react": "workspace:^"
"@backstage/test-utils": "workspace:^"
"@backstage/theme": "workspace:^"
+ "@backstage/ui": "workspace:^"
"@material-ui/core": "npm:^4.9.13"
"@material-ui/icons": "npm:^4.9.1"
"@testing-library/jest-dom": "npm:^6.0.0"
@@ -7311,6 +7313,7 @@ __metadata:
"@backstage/plugin-search-react": "workspace:^"
"@backstage/test-utils": "workspace:^"
"@backstage/types": "workspace:^"
+ "@backstage/ui": "workspace:^"
"@backstage/version-bridge": "workspace:^"
"@material-ui/core": "npm:^4.12.2"
"@material-ui/icons": "npm:^4.9.1"
@@ -7737,6 +7740,7 @@ __metadata:
"@backstage/test-utils": "workspace:^"
"@backstage/theme": "workspace:^"
"@backstage/types": "workspace:^"
+ "@backstage/ui": "workspace:^"
"@material-ui/core": "npm:^4.12.2"
"@material-ui/icons": "npm:^4.9.1"
"@material-ui/lab": "npm:4.0.0-alpha.61"