From feea2211231674e5a31e99a66cef7914a1e96273 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 29 Jan 2024 11:54:04 +0100 Subject: [PATCH 01/10] feat: configure alpha subpath exports Signed-off-by: Camila Belo --- plugins/catalog-graph/package.json | 19 ++++++++++++++++--- plugins/catalog-graph/src/alpha.ts | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 plugins/catalog-graph/src/alpha.ts diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 1dba15d840..4aad12b5bc 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -5,9 +5,22 @@ "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" + }, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.tsx", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.tsx" + ], + "package.json": [ + "package.json" + ] + } }, "backstage": { "role": "frontend-plugin" diff --git a/plugins/catalog-graph/src/alpha.ts b/plugins/catalog-graph/src/alpha.ts new file mode 100644 index 0000000000..94adcafa53 --- /dev/null +++ b/plugins/catalog-graph/src/alpha.ts @@ -0,0 +1,15 @@ +/* + * 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. + */ From a36b357b5af88e430f467e77407579dccd8f2a60 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 29 Jan 2024 11:55:36 +0100 Subject: [PATCH 02/10] feat: migrate catalog-graph plugin Signed-off-by: Camila Belo --- plugins/catalog-graph/package.json | 2 ++ plugins/catalog-graph/src/alpha.ts | 14 ++++++++++++++ yarn.lock | 2 ++ 3 files changed, 18 insertions(+) diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 4aad12b5bc..b92cf201e4 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -44,8 +44,10 @@ "dependencies": { "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", + "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/types": "workspace:^", "@material-ui/core": "^4.12.2", diff --git a/plugins/catalog-graph/src/alpha.ts b/plugins/catalog-graph/src/alpha.ts index 94adcafa53..345a632dea 100644 --- a/plugins/catalog-graph/src/alpha.ts +++ b/plugins/catalog-graph/src/alpha.ts @@ -13,3 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { createPlugin } from '@backstage/frontend-plugin-api'; +import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { catalogGraphRouteRef, catalogEntityRouteRef } from './routes'; + +export default createPlugin({ + id: 'catalog-graph', + routes: { + catalogGraph: convertLegacyRouteRef(catalogGraphRouteRef), + }, + externalRoutes: { + catalogEntity: convertLegacyRouteRef(catalogEntityRouteRef), + }, + extensions: [], +}); diff --git a/yarn.lock b/yarn.lock index 5f84cbbc51..5cd5a0cbf5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5783,9 +5783,11 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" + "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/test-utils": "workspace:^" From 1b06d313d89ce94f51f0947387ca37f4b529899f Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 30 Jan 2024 10:08:56 +0100 Subject: [PATCH 03/10] feat: migrate catalog graph page Signed-off-by: Camila Belo --- plugins/catalog-graph/src/alpha.ts | 29 -------------- plugins/catalog-graph/src/alpha.tsx | 62 +++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 29 deletions(-) delete mode 100644 plugins/catalog-graph/src/alpha.ts create mode 100644 plugins/catalog-graph/src/alpha.tsx diff --git a/plugins/catalog-graph/src/alpha.ts b/plugins/catalog-graph/src/alpha.ts deleted file mode 100644 index 345a632dea..0000000000 --- a/plugins/catalog-graph/src/alpha.ts +++ /dev/null @@ -1,29 +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 { createPlugin } from '@backstage/frontend-plugin-api'; -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; -import { catalogGraphRouteRef, catalogEntityRouteRef } from './routes'; - -export default createPlugin({ - id: 'catalog-graph', - routes: { - catalogGraph: convertLegacyRouteRef(catalogGraphRouteRef), - }, - externalRoutes: { - catalogEntity: convertLegacyRouteRef(catalogEntityRouteRef), - }, - extensions: [], -}); diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx new file mode 100644 index 0000000000..bf2b0ffce8 --- /dev/null +++ b/plugins/catalog-graph/src/alpha.tsx @@ -0,0 +1,62 @@ +/* + * 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 React from 'react'; +import { + createPageExtension, + createPlugin, + createSchemaFromZod, +} from '@backstage/frontend-plugin-api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; +import { catalogGraphRouteRef, catalogEntityRouteRef } from './routes'; +import { Direction } from './components'; + +const CatalogGraphPage = createPageExtension({ + defaultPath: '/catalog-graph', + routeRef: convertLegacyRouteRef(catalogGraphRouteRef), + configSchema: createSchemaFromZod(z => + z.object({ + path: z.string().default('/catalog-graph'), + selectedKinds: z.array(z.string()).optional(), + selectedRelations: z.array(z.string()).optional(), + rootEntityRefs: z.array(z.string()).optional(), + maxDepth: z.number().optional(), + unidirectional: z.boolean().optional(), + mergeRelations: z.boolean().optional(), + direction: z.nativeEnum(Direction).optional(), + showFilters: z.boolean().optional(), + curve: z.enum(['curveStepBefore', 'curveMonotoneX']).optional(), + }), + ), + loader: ({ config: { path, ...initialState } }) => + import('./components/CatalogGraphPage').then(m => + compatWrapper(), + ), +}); + +export default createPlugin({ + id: 'catalog-graph', + routes: { + catalogGraph: convertLegacyRouteRef(catalogGraphRouteRef), + }, + externalRoutes: { + catalogEntity: convertLegacyRouteRef(catalogEntityRouteRef), + }, + extensions: [CatalogGraphPage], +}); From 0e7340cb25e9b713579386a7e665c54be9a954fa Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 30 Jan 2024 14:03:33 +0100 Subject: [PATCH 04/10] feat(catalog-react): accepts entity cards config schema Signed-off-by: Camila Belo --- plugins/catalog-react/src/alpha.tsx | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-react/src/alpha.tsx b/plugins/catalog-react/src/alpha.tsx index 330cd573cd..f5f4dfdb74 100644 --- a/plugins/catalog-react/src/alpha.tsx +++ b/plugins/catalog-react/src/alpha.tsx @@ -17,6 +17,7 @@ import { AnyExtensionInputMap, ExtensionBoundary, + PortableSchema, ResolvedExtensionInputs, RouteRef, coreExtensionData, @@ -48,6 +49,7 @@ export const catalogExtensionData = { // TODO: Figure out how to merge with provided config schema /** @alpha */ export function createEntityCardExtension< + TConfig extends { filter?: string }, TInputs extends AnyExtensionInputMap, >(options: { namespace?: string; @@ -55,13 +57,23 @@ export function createEntityCardExtension< attachTo?: { id: string; input: string }; disabled?: boolean; inputs?: TInputs; + configSchema?: PortableSchema; filter?: | typeof catalogExtensionData.entityFilterFunction.T | typeof catalogExtensionData.entityFilterExpression.T; loader: (options: { + config: TConfig; inputs: Expand>; }) => Promise; }) { + const configSchema = + 'configSchema' in options + ? options.configSchema + : (createSchemaFromZod(z => + z.object({ + filter: z.string().optional(), + }), + ) as PortableSchema); return createExtension({ kind: 'entity-card', namespace: options.namespace, @@ -77,15 +89,11 @@ export function createEntityCardExtension< filterExpression: catalogExtensionData.entityFilterExpression.optional(), }, inputs: options.inputs, - configSchema: createSchemaFromZod(z => - z.object({ - filter: z.string().optional(), - }), - ), + configSchema, factory({ config, inputs, node }) { const ExtensionComponent = lazy(() => options - .loader({ inputs }) + .loader({ inputs, config }) .then(element => ({ default: () => element })), ); From c888887b65308af6e16d6b63a6d13a84a7a853ec Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 30 Jan 2024 14:06:30 +0100 Subject: [PATCH 05/10] feat(catalog-graph): create entity card extension Signed-off-by: Camila Belo --- plugins/catalog-graph/src/alpha.tsx | 80 ++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx index bf2b0ffce8..00be9e8e69 100644 --- a/plugins/catalog-graph/src/alpha.tsx +++ b/plugins/catalog-graph/src/alpha.tsx @@ -24,29 +24,85 @@ import { compatWrapper, convertLegacyRouteRef, } from '@backstage/core-compat-api'; +import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; import { catalogGraphRouteRef, catalogEntityRouteRef } from './routes'; import { Direction } from './components'; +type Zod = Parameters[0]>[0]; + +function getCompoundEntityRefConfigSchema(z: Zod) { + return z.object({ + kind: z.string(), + namespace: z.string(), + name: z.string(), + }); +} + +function getEntityGraphRelationsConfigSchema(z: Zod) { + // Mapping EntityRelationsGraphProps to config + // TODO: Define how className and render functions will be configured + return z.object({ + rootEntityRef: getCompoundEntityRefConfigSchema(z) + .or(z.array(getCompoundEntityRefConfigSchema(z))) + .optional(), + kinds: z.array(z.string()).optional(), + relations: z.array(z.string()).optional(), + maxDepth: z.number().optional(), + unidirectional: z.boolean().optional(), + mergeRelations: z.boolean().optional(), + direction: z.nativeEnum(Direction).optional(), + relationPairs: z.array(z.tuple([z.string(), z.string()])).optional(), + zoom: z.enum(['enabled', 'disabled', 'enable-on-click']).optional(), + curve: z.enum(['curveStepBefore', 'curveMonotoneX']).optional(), + }); +} + +const CatalogGraphEntityCard = createEntityCardExtension({ + name: 'catalog-graph', + configSchema: createSchemaFromZod(z => + z + .object({ + // Filter is a config required to all entity cards + filter: z.string().optional(), + title: z.string().optional(), + height: z.number().optional(), + variant: z.enum(['flex', 'fullHeight', 'gridItem']).optional(), + }) + .merge(getEntityGraphRelationsConfigSchema(z)), + ), + loader: async ({ config: { filter, ...props } }) => + import('./components/CatalogGraphCard').then(m => + compatWrapper(), + ), +}); + const CatalogGraphPage = createPageExtension({ defaultPath: '/catalog-graph', routeRef: convertLegacyRouteRef(catalogGraphRouteRef), configSchema: createSchemaFromZod(z => z.object({ + // Path is a default config required to all pages path: z.string().default('/catalog-graph'), - selectedKinds: z.array(z.string()).optional(), - selectedRelations: z.array(z.string()).optional(), - rootEntityRefs: z.array(z.string()).optional(), - maxDepth: z.number().optional(), - unidirectional: z.boolean().optional(), - mergeRelations: z.boolean().optional(), - direction: z.nativeEnum(Direction).optional(), - showFilters: z.boolean().optional(), - curve: z.enum(['curveStepBefore', 'curveMonotoneX']).optional(), + // Mapping intialState prop to config + initialState: z + .object({ + selectedKinds: z.array(z.string()).optional(), + selectedRelations: z.array(z.string()).optional(), + rootEntityRefs: z.array(z.string()).optional(), + maxDepth: z.number().optional(), + unidirectional: z.boolean().optional(), + mergeRelations: z.boolean().optional(), + direction: z.nativeEnum(Direction).optional(), + showFilters: z.boolean().optional(), + curve: z.enum(['curveStepBefore', 'curveMonotoneX']).optional(), + }) + .merge(getEntityGraphRelationsConfigSchema(z)) + .optional(), }), ), - loader: ({ config: { path, ...initialState } }) => + loader: ({ config: { path, ...props } }) => import('./components/CatalogGraphPage').then(m => - compatWrapper(), + compatWrapper(), ), }); @@ -58,5 +114,5 @@ export default createPlugin({ externalRoutes: { catalogEntity: convertLegacyRouteRef(catalogEntityRouteRef), }, - extensions: [CatalogGraphPage], + extensions: [CatalogGraphPage, CatalogGraphEntityCard], }); From d73d7dd5b1824eb5c22cb94ac4ad5582c040c89f Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 31 Jan 2024 10:21:06 +0100 Subject: [PATCH 06/10] feat(app-next): install catalog in app-next Signed-off-by: Camila Belo --- packages/app-next/app-config.yaml | 1 + plugins/catalog-graph/src/alpha.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index a79bb866db..7402493179 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -16,6 +16,7 @@ app: config: filter: kind:component has:links - entity-card:linguist/languages + - entity-card:catalog-graph/entity-relations # Entity page content - entity-content:techdocs diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx index 00be9e8e69..6d0c9d2403 100644 --- a/plugins/catalog-graph/src/alpha.tsx +++ b/plugins/catalog-graph/src/alpha.tsx @@ -58,7 +58,7 @@ function getEntityGraphRelationsConfigSchema(z: Zod) { } const CatalogGraphEntityCard = createEntityCardExtension({ - name: 'catalog-graph', + name: 'entity-relations', configSchema: createSchemaFromZod(z => z .object({ From 4d27f6ae69a7c98effc00e6aa621c5f8970fc3dd Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 1 Feb 2024 09:55:51 +0100 Subject: [PATCH 07/10] docs: start drafting the card documentation Signed-off-by: Camila Belo --- plugins/catalog-graph/EXPERIMENTAL.md | 224 ++++++++++++++++++++++++++ plugins/catalog-graph/README.md | 3 + plugins/catalog-graph/src/alpha.tsx | 28 ++-- 3 files changed, 246 insertions(+), 9 deletions(-) create mode 100644 plugins/catalog-graph/EXPERIMENTAL.md diff --git a/plugins/catalog-graph/EXPERIMENTAL.md b/plugins/catalog-graph/EXPERIMENTAL.md new file mode 100644 index 0000000000..5870b30e4b --- /dev/null +++ b/plugins/catalog-graph/EXPERIMENTAL.md @@ -0,0 +1,224 @@ +# catalog-graph + +> **Disclaimer:** +> This documentation is made for those using the experimental new Frontend system. +> If you are not using the new Backstage frontend system, please go [here](./README.md). + +Welcome to the catalog graph plugin! The catalog graph visualizes the relations +between entities, like ownership, grouping or API relationships. + +The plugin comes with these features: + +- Catalog entity relations graph card: + A card that displays the directly related entities to the current entity. + This card is for use on the entity page. + The card can be customized, for example filtering for specific relations. +