From 5dc00a5d8890d6504600e8556d1c6dd602fabaa3 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 5 Oct 2023 15:08:24 +0200 Subject: [PATCH] feat(catalog): create index page filters Signed-off-by: Camila Belo --- plugins/catalog/src/alpha.tsx | 202 ++++++++++++++++-- .../CatalogPage/DefaultCatalogPage.tsx | 22 +- 2 files changed, 197 insertions(+), 27 deletions(-) diff --git a/plugins/catalog/src/alpha.tsx b/plugins/catalog/src/alpha.tsx index e34830f21d..86ff0a1846 100644 --- a/plugins/catalog/src/alpha.tsx +++ b/plugins/catalog/src/alpha.tsx @@ -29,14 +29,24 @@ import { createPageExtension, createPlugin, createNavItemExtension, + createExtension, + coreExtensionData, + AnyExtensionInputMap, + PortableSchema, + ExtensionBoundary, + createExtensionInput, } from '@backstage/frontend-plugin-api'; import { + AsyncEntityProvider, catalogApiRef, + entityRouteRef, starredEntitiesApiRef, } from '@backstage/plugin-catalog-react'; import { createSearchResultListItemExtension } from '@backstage/plugin-search-react/alpha'; import { DefaultStarredEntitiesApi } from './apis'; import { rootRouteRef } from './routes'; +import { Progress } from '@backstage/core-components'; +import { useEntityFromUrl } from './components/CatalogEntityPage/useEntityFromUrl'; /** @alpha */ export const CatalogApi = createApiExtension({ @@ -72,30 +82,175 @@ export const CatalogSearchResultListItemExtension = }); /** @alpha */ -export const CatalogIndexPage = createPageExtension({ - id: 'catalog', - routeRef: rootRouteRef, +export function createCatalogFilterExtension< + TInputs extends AnyExtensionInputMap, + TConfig = never, +>(options: { + id: string; + inputs?: TInputs; + configSchema?: PortableSchema; + loader: (options: { config: TConfig }) => Promise; +}) { + return createExtension({ + id: `catalog.filter.${options.id}`, + attachTo: { id: 'catalog', input: 'filters' }, + inputs: options.inputs ?? {}, + configSchema: options.configSchema, + output: { + element: coreExtensionData.reactElement, + }, + factory({ bind, config, source }) { + const LazyComponent = React.lazy(() => + options + .loader({ config }) + .then(element => ({ default: () => element })), + ); + + bind({ + element: ( + + }> + + + + ), + }); + }, + }); +} + +/** @alpha */ +export const CatalogEntityTagFilter = createCatalogFilterExtension({ + id: 'entity.tag', + loader: async () => { + const { EntityTagPicker } = await import('@backstage/plugin-catalog-react'); + return ; + }, +}); + +/** @alpha */ +export const CatalogEntityKindFilter = createCatalogFilterExtension({ + id: 'entity.kind', configSchema: createSchemaFromZod(z => z.object({ - path: z.string().default('/catalog'), - initialKind: z.string().default('component'), - initiallySelectedFilter: z - .enum(['owned', 'starred', 'all']) - .default('owned'), - ownerPickerMode: z.enum(['owners-only', 'all']).optional(), + initialFilter: z.string().default('component'), }), ), loader: async ({ config }) => { - const { CatalogPage } = await import('./components/CatalogPage'); - const { ownerPickerMode, initialKind, initiallySelectedFilter } = config; - - return ( - + const { EntityKindPicker } = await import( + '@backstage/plugin-catalog-react' ); + return ; + }, +}); + +/** @alpha */ +export const CatalogEntityTypeFilter = createCatalogFilterExtension({ + id: 'entity.type', + loader: async () => { + const { EntityTypePicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, +}); + +/** @alpha */ +export const CatalogEntityOwnerFilter = createCatalogFilterExtension({ + id: 'entity.mode', + configSchema: createSchemaFromZod(z => + z.object({ + mode: z.enum(['owners-only', 'all']).optional(), + }), + ), + loader: async ({ config }) => { + const { EntityOwnerPicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, +}); + +/** @alpha */ +export const CatalogEntityNamespaceFilter = createCatalogFilterExtension({ + id: 'entity.namespace', + loader: async () => { + const { EntityNamespacePicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, +}); + +/** @alpha */ +export const CatalogEntityLifecycleFilter = createCatalogFilterExtension({ + id: 'entity.lifecycle', + loader: async () => { + const { EntityLifecyclePicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, +}); + +/** @alpha */ +export const CatalogEntityProcessingStatusFilter = createCatalogFilterExtension( + { + id: 'entity.processing.status', + loader: async () => { + const { EntityProcessingStatusPicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, + }, +); + +/** @alpha */ +export const CatalogUserListFilter = createCatalogFilterExtension({ + id: 'user.list', + configSchema: createSchemaFromZod(z => + z.object({ + initialFilter: z.enum(['owned', 'starred', 'all']).default('owned'), + }), + ), + loader: async ({ config }) => { + const { UserListPicker } = await import('@backstage/plugin-catalog-react'); + return ; + }, +}); + +/** @alpha */ +export const CatalogIndexPage = createPageExtension({ + id: 'catalog', + defaultPath: '/catalog', + routeRef: rootRouteRef, + inputs: { + filters: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + loader: async ({ inputs }) => { + const { DefaultCatalogPage } = await import('./components/CatalogPage'); + const filters = inputs.filters.map(filter => filter.element); + return ; + }, +}); + +/** @alpha */ +export const CatalogEntityPage = createPageExtension({ + id: 'catalog:entity', + defaultPath: '/catalog/:namespace/:kind/:name', + routeRef: entityRouteRef, + loader: async () => { + const Component = () => { + return ( + +
🚧 Work In Progress
+
+ ); + }; + return ; }, }); @@ -113,7 +268,16 @@ export default createPlugin({ CatalogApi, StarredEntitiesApi, CatalogSearchResultListItemExtension, - CatalogNavItem, + CatalogEntityKindFilter, + CatalogEntityTypeFilter, + CatalogUserListFilter, + CatalogEntityOwnerFilter, + CatalogEntityLifecycleFilter, + CatalogEntityTagFilter, + CatalogEntityProcessingStatusFilter, + CatalogEntityNamespaceFilter, CatalogIndexPage, + CatalogEntityPage, + CatalogNavItem, ], }); diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx index fb9b282d57..7d75ce4b1d 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx @@ -57,6 +57,7 @@ export interface DefaultCatalogPageProps { tableOptions?: TableProps['options']; emptyContent?: ReactNode; ownerPickerMode?: EntityOwnerPickerProps['mode']; + filters?: ReactNode; } export function DefaultCatalogPage(props: DefaultCatalogPageProps) { @@ -68,6 +69,7 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) { tableOptions = {}, emptyContent, ownerPickerMode, + filters, } = props; const orgName = useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; @@ -87,14 +89,18 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) { - - - - - - - - + {filters ?? ( + <> + + + + + + + + + + )}