From 187180db5cece73dadb61927f517840c55ed9a0d Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Wed, 26 May 2021 20:45:45 -0600 Subject: [PATCH] Improve catalog-customization doc Signed-off-by: Tim Hansen --- .../software-catalog/catalog-customization.md | 130 +++++++++++------- 1 file changed, 83 insertions(+), 47 deletions(-) diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md index 6bd1643473..e0228bf7d0 100644 --- a/docs/features/software-catalog/catalog-customization.md +++ b/docs/features/software-catalog/catalog-customization.md @@ -20,13 +20,14 @@ the catalog - you can replace the routing in `App.tsx` to point to your own > periodically. For example, suppose that I want to allow filtering by a custom annotation added -to entities, `company.com/itgc-enabled`. To start, I'll copy the code for the -default catalog page, either in a new plugin or just in the `app` package: +to entities, `company.com/security-tier`. To start, I'll copy the code for the +default catalog page and create a component in a +[new plugin](../../plugins/create-a-plugin.md): ```tsx // imports, etc omitted for brevity. for full source see: // https://github.com/backstage/backstage/blob/master/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx -export const CustomCatalogIndexPage = () => { +export const CustomCatalogPage = () => { return ( @@ -52,20 +53,20 @@ export const CustomCatalogIndexPage = () => { ``` The `EntityListProvider` shown here provides a list of entities from the -`catalog-backend`, and a way to hook in filters. `EntityListProvider` has a -[generic](https://www.typescriptlang.org/docs/handbook/2/generics.html) argument -that can be extended to provide your own filters. +`catalog-backend`, and a way to hook in filters. Now we're ready to create a new filter that implements the `EntityFilter` interface: ```ts -import { EntityFilter } from '@backstage/catalog-react'; +import { EntityFilter } from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; -class ItgcEntityFilter implements EntityFilter { +class EntitySecurityTierFilter implements EntityFilter { + constructor(readonly values: string[]) {} filterEntity(entity: Entity): boolean { - return entity.metadata.annotations?.['company.com/itgc-enabled'] === true; + const tier = entity.metadata.annotations?.['company.com/security-tier']; + return tier !== undefined && this.values.includes(tier); } } ``` @@ -74,58 +75,69 @@ The `EntityFilter` interface permits backend filters, which are passed along to the `catalog-backend` - or frontend filters, which are applied after entities are loaded from the backend. -Let's create the custom filter shape extending the default and update the -`EntityListProvider` in the custom index page to use it: +We'll use this filter to extend the default filters in a type-safe way. Let's +create the custom filter shape extending the default somewhere alongside this +filter: -```diff -+export type CustomFilters = DefaultEntityFilters & { -+ itgc: ItgcEntityFilter; -+}; - -export const CustomCatalogIndexPage = () => { -... -
-- -+ > -
-... +```ts +export type CustomFilters = DefaultEntityFilters & { + securityTiers?: EntitySecurityTierFilter; +}; ``` -To control this filter, we can create a React component that shows a checkbox. -This component will make use of the `useEntityListProvider` hook, which also -accepts the same generic so we can use the added filter field: +To control this filter, we can create a React component that shows checkboxes +for the security tiers. This component will make use of the +`useEntityListProvider` hook, which accepts this extended filter type as a +[generic](https://www.typescriptlang.org/docs/handbook/2/generics.html) +parameter: ```tsx -export const ItgcPicker = () => { +export const EntitySecurityTierPicker = () => { + // The securityTiers key is recognized due to the CustomFilter generic const { - filters: { itgc: currentItgcFilter }, + filters: { securityTiers }, updateFilters, } = useEntityListProvider(); - const [enabled, setEnabled] = useState(false); - useEffect(() => { - updateFilters({ itgc: enabled ? new ItgcEntityFilter() : undefined }); - }, [enabled, updateFilters]); + // Toggles the value, depending on whether it's already selected + function onChange(value: string) { + const newTiers = securityTiers?.values.includes(value) + ? securityTiers.values.filter(tier => tier !== value) + : [...(securityTiers?.values ?? []), value]; + updateFilters({ + securityTiers: newTiers.length + ? new EntitySecurityTierFilter(newTiers) + : undefined, + }); + } + const tierOptions = ['1', '2', '3']; return ( - setEnabled(isEnabled => !isEnabled)} - name="itgc-picker" - /> - } - label="ITGC Required" - /> + + Security Tier + + {tierOptions.map(tier => ( + onChange(tier)} + /> + } + label={`Tier ${tier}`} + /> + ))} + + ); }; ``` -Now I can add the component to `CustomCatalogIndexPage`: +Now we can add the component to `CustomCatalogPage`: ```diff -export const CustomCatalogIndexPage = () => { +export const CustomCatalogPage = () => { return ( ... @@ -133,7 +145,7 @@ export const CustomCatalogIndexPage = () => {
@@ -142,6 +154,30 @@ export const CustomCatalogIndexPage = () => { }; ``` +This page itself can be exported as a routable extension in the plugin: + +```ts +export const CustomCatalogIndexPage = myPlugin.provide( + createRoutableExtension({ + component: () => + import('./components/CustomCatalogPage').then(m => m.CustomCatalogPage), + mountPoint: catalogRouteRef, + }), +); +``` + +Finally, we can replace the catalog route in the Backstage application with our +new `CustomCatalogIndexPage`. + +```diff +# packages/app/src/App.tsx +const routes = ( + + +- } /> ++ } /> +``` + The same method can be used to customize the _default_ filters with a different -interface - for such usage, the generic argument won't be needed since the -filter shape remains the same as the default. +interface - for such usage, the generic argument isn't needed since the filter +shape remains the same as the default.