From 63b1193cb819e9a9aa494d57a9ea2cf02db1cdd1 Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Fri, 5 Jun 2020 12:04:07 +0200 Subject: [PATCH 1/3] /catalog/:namespace?/:kind/:name/ --- plugins/catalog/src/api/CatalogClient.ts | 15 +++++++++++++-- plugins/catalog/src/api/types.ts | 6 +++++- .../src/components/CatalogTable/CatalogTable.tsx | 5 ++++- .../ComponentPage/ComponentPage.test.tsx | 7 ++++--- .../components/ComponentPage/ComponentPage.tsx | 8 +++++--- plugins/catalog/src/routes.ts | 2 +- .../RegisterComponentPage.test.tsx | 2 +- .../RegisterComponentResultDialog.tsx | 2 ++ 8 files changed, 35 insertions(+), 12 deletions(-) diff --git a/plugins/catalog/src/api/CatalogClient.ts b/plugins/catalog/src/api/CatalogClient.ts index 4ae4676703..c2f4497429 100644 --- a/plugins/catalog/src/api/CatalogClient.ts +++ b/plugins/catalog/src/api/CatalogClient.ts @@ -35,9 +35,20 @@ export class CatalogClient implements CatalogApi { const response = await fetch(`${this.apiOrigin}${this.basePath}/entities`); return await response.json(); } - async getEntityByName(name: string): Promise { + + async getEntity({ + name, + namespace, + kind, + }: { + name: string; + namespace?: string; + kind: string; + }): Promise { const response = await fetch( - `${this.apiOrigin}${this.basePath}/entities/by-name/Component/default/${name}`, + `${this.apiOrigin}${this.basePath}/entities/by-name/${kind}/${ + namespace ?? 'default' + }/${name}`, ); const entity = await response.json(); if (entity) return entity; diff --git a/plugins/catalog/src/api/types.ts b/plugins/catalog/src/api/types.ts index 5bb6a6ca83..b45994b861 100644 --- a/plugins/catalog/src/api/types.ts +++ b/plugins/catalog/src/api/types.ts @@ -24,7 +24,11 @@ export const catalogApiRef = createApiRef({ export interface CatalogApi { getEntities(): Promise; - getEntityByName(name: string): Promise; + getEntity(params: { + name: string; + namespace?: string; + kind: string; + }): Promise; addLocation(type: string, target: string): Promise; getLocationByEntity(entity: Entity): Promise; } diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 7ff521ee67..094dfc5352 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -28,7 +28,10 @@ const columns: TableColumn[] = [ render: (componentData: any) => ( {componentData.name} diff --git a/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx b/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx index f580a84d8a..d523b45622 100644 --- a/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx +++ b/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx @@ -20,11 +20,12 @@ import { wrapInTestApp } from '@backstage/test-utils'; import { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core'; import { catalogApiRef, CatalogApi } from '../../api/types'; -const getTestProps = (componentName: string) => { +const getTestProps = () => { return { match: { params: { - name: componentName, + name: 'componentName', + kind: 'Component', }, }, history: { @@ -37,7 +38,7 @@ const errorApi = { post: () => {} }; describe('ComponentPage', () => { it('should redirect to component table page when name is not provided', async () => { - const props = getTestProps(''); + const props = getTestProps(); await render( wrapInTestApp( = ({ match, history }) => { const [removingPending, setRemovingPending] = useState(false); const showRemovalDialog = () => setConfirmationDialogOpen(true); const hideRemovalDialog = () => setConfirmationDialogOpen(false); - const componentName = match.params.name; + const { name, namespace, kind } = match.params; const errorApi = useApi(errorApiRef); const catalogApi = useApi(catalogApiRef); const catalogRequest = useAsync(() => - catalogApi.getEntityByName(match.params.name), + catalogApi.getEntity({ name, namespace, kind }), ); useEffect(() => { @@ -67,7 +69,7 @@ const ComponentPage: FC = ({ match, history }) => { } }, [catalogRequest.error, errorApi, history]); - if (componentName === '') { + if (name === '') { history.push('/catalog'); return null; } diff --git a/plugins/catalog/src/routes.ts b/plugins/catalog/src/routes.ts index 6498d412b8..f5bb319a88 100644 --- a/plugins/catalog/src/routes.ts +++ b/plugins/catalog/src/routes.ts @@ -25,6 +25,6 @@ export const rootRoute = createRouteRef({ }); export const entityRoute = createRouteRef({ icon: NoIcon, - path: '/catalog/:name/', + path: '/catalog/:namespace?/:kind/:name/', title: 'Entity', }); diff --git a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx index ad47c3a41b..1d424858ad 100644 --- a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx +++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx @@ -28,7 +28,7 @@ const catalogApi: jest.Mocked = { /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ addLocation: jest.fn((_a, _b) => new Promise(() => {})), getEntities: jest.fn(), - getEntityByName: jest.fn(), + getEntity: jest.fn(), getLocationByEntity: jest.fn(), }; diff --git a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx index 4d158526a0..7c6e335f6e 100644 --- a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx +++ b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx @@ -69,10 +69,12 @@ export const RegisterComponentResultDialog: FC = ({ component={RouterLink} to={generatePath(entityRoute.path, { name: entity.metadata.name, + kind: entity.kind, })} > {generatePath(entityRoute.path, { name: entity.metadata.name, + kind: entity.kind, })} ), From 2ba33db74d8f76be9251ff06f3602ea8d33e920f Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Fri, 5 Jun 2020 12:22:07 +0200 Subject: [PATCH 2/3] Fix tests --- .../src/components/ComponentPage/ComponentPage.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx b/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx index d523b45622..510792a902 100644 --- a/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx +++ b/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx @@ -20,11 +20,11 @@ import { wrapInTestApp } from '@backstage/test-utils'; import { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core'; import { catalogApiRef, CatalogApi } from '../../api/types'; -const getTestProps = () => { +const getTestProps = (name: string) => { return { match: { params: { - name: 'componentName', + name: name, kind: 'Component', }, }, @@ -38,7 +38,7 @@ const errorApi = { post: () => {} }; describe('ComponentPage', () => { it('should redirect to component table page when name is not provided', async () => { - const props = getTestProps(); + const props = getTestProps(''); await render( wrapInTestApp( { [ catalogApiRef, ({ - async getEntityByName() {}, + async getEntity() {}, } as unknown) as CatalogApi, ], ])} From 2b5b3903f78a4ac4d189d181dd8a3ceb6c18b5d1 Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Tue, 9 Jun 2020 18:32:41 +0200 Subject: [PATCH 3/3] Optional namespace and name as one part of URL --- .../components/CatalogTable/CatalogTable.tsx | 7 ++- .../ComponentPage/ComponentPage.test.tsx | 2 +- .../ComponentPage/ComponentPage.tsx | 6 +- plugins/catalog/src/data/component.ts | 1 + plugins/catalog/src/data/utils.ts | 1 + plugins/catalog/src/routes.ts | 2 +- .../RegisterComponentResultDialog.tsx | 63 ++++++++++--------- 7 files changed, 46 insertions(+), 36 deletions(-) diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index f04add7cc1..bea9caf273 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -31,7 +31,12 @@ const columns: TableColumn[] = [ diff --git a/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx b/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx index ee5e7348b1..98fa6b4408 100644 --- a/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx +++ b/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx @@ -24,7 +24,7 @@ const getTestProps = (name: string) => { return { match: { params: { - name: name, + optionalNamespaceAndName: name, kind: 'Component', }, }, diff --git a/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx b/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx index a410be2bf4..72aef7fe16 100644 --- a/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx +++ b/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx @@ -40,8 +40,7 @@ const REDIRECT_DELAY = 1000; type ComponentPageProps = { match: { params: { - name: string; - namespace?: string; + optionalNamespaceAndName: string; kind: string; }; }; @@ -55,7 +54,8 @@ const ComponentPage: FC = ({ match, history }) => { const [removingPending, setRemovingPending] = useState(false); const showRemovalDialog = () => setConfirmationDialogOpen(true); const hideRemovalDialog = () => setConfirmationDialogOpen(false); - const { name, namespace, kind } = match.params; + const { optionalNamespaceAndName, kind } = match.params; + const [name, namespace] = optionalNamespaceAndName.split(':').reverse(); const errorApi = useApi(errorApiRef); const catalogApi = useApi(catalogApiRef); diff --git a/plugins/catalog/src/data/component.ts b/plugins/catalog/src/data/component.ts index 86749c6faa..68be7588f0 100644 --- a/plugins/catalog/src/data/component.ts +++ b/plugins/catalog/src/data/component.ts @@ -18,6 +18,7 @@ import { ReactNode } from 'react'; export type Component = { name: string; + namespace?: string; kind: string; metadata: EntityMeta; description: ReactNode; diff --git a/plugins/catalog/src/data/utils.ts b/plugins/catalog/src/data/utils.ts index b731268c41..f84bfee73f 100644 --- a/plugins/catalog/src/data/utils.ts +++ b/plugins/catalog/src/data/utils.ts @@ -24,6 +24,7 @@ import { Component } from './component'; export function entityToComponent(envelope: Entity): Component { return { name: envelope.metadata.name, + namespace: envelope.metadata.namespace, kind: envelope.kind, metadata: envelope.metadata, description: envelope.metadata.annotations?.description ?? 'placeholder', diff --git a/plugins/catalog/src/routes.ts b/plugins/catalog/src/routes.ts index f5bb319a88..69c4e651b4 100644 --- a/plugins/catalog/src/routes.ts +++ b/plugins/catalog/src/routes.ts @@ -25,6 +25,6 @@ export const rootRoute = createRouteRef({ }); export const entityRoute = createRouteRef({ icon: NoIcon, - path: '/catalog/:namespace?/:kind/:name/', + path: '/catalog/:kind/:optionalNamespaceAndName/', title: 'Entity', }); diff --git a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx index 7c6e335f6e..68b06d1751 100644 --- a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx +++ b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx @@ -54,36 +54,39 @@ export const RegisterComponentResultDialog: FC = ({ The following components have been succefully created: - {entities.map((entity: any, index: number) => ( - - - - {generatePath(entityRoute.path, { - name: entity.metadata.name, - kind: entity.kind, - })} - - ), - }} - /> - - {index < entities.length - 1 && } - - ))} + {entities.map((entity: any, index: number) => { + const entityPath = generatePath(entityRoute.path, { + optionalNamespaceAndName: [ + entity.metadata.namespace, + entity.metadata.name, + ] + .filter(Boolean) + .join(':'), + kind: entity.kind, + }); + + return ( + + + + {entityPath} + + ), + }} + /> + + {index < entities.length - 1 && } + + ); + })}