From dc81a30ff44cf096c11f413aba0cb0ccc6c39196 Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Tue, 2 Jun 2020 14:56:22 +0200 Subject: [PATCH 1/9] Initial github link --- plugins/catalog/src/api/CatalogClient.ts | 20 +++++++ plugins/catalog/src/api/types.ts | 1 + .../components/CatalogPage/CatalogPage.tsx | 56 +++++++++++++++++-- .../components/CatalogTable/CatalogTable.tsx | 5 +- plugins/catalog/src/data/component.ts | 1 + plugins/catalog/src/data/utils.ts | 6 +- 6 files changed, 83 insertions(+), 6 deletions(-) diff --git a/plugins/catalog/src/api/CatalogClient.ts b/plugins/catalog/src/api/CatalogClient.ts index 42ef1c4da5..d6d717a085 100644 --- a/plugins/catalog/src/api/CatalogClient.ts +++ b/plugins/catalog/src/api/CatalogClient.ts @@ -16,6 +16,7 @@ import { CatalogApi } from './types'; import { DescriptorEnvelope } from '../types'; +import { Entity } from '@backstage/catalog-model'; export class CatalogClient implements CatalogApi { private apiOrigin: string; @@ -42,4 +43,23 @@ export class CatalogClient implements CatalogApi { if (entity) return entity; throw new Error(`'Entity not found: ${name}`); } + async getLocationByEntity(entity: Entity): Promise { + const findLocationIdInEntity = (e: Entity) => { + return e.metadata.annotations + ? e.metadata.annotations['backstage.io/managed-by-location'] + : null; + }; + + const locationId = findLocationIdInEntity(entity); + if (!locationId) return null; + + const response = await fetch( + `${this.apiOrigin}${this.basePath}/locations/${locationId}`, + ); + if (response.ok) { + const location = await response.json(); + if (location) return location; + } + throw new Error(`'Location not found: ${locationId}`); + } } diff --git a/plugins/catalog/src/api/types.ts b/plugins/catalog/src/api/types.ts index eb2cdca562..845dbe8e83 100644 --- a/plugins/catalog/src/api/types.ts +++ b/plugins/catalog/src/api/types.ts @@ -25,4 +25,5 @@ export const catalogApiRef = createApiRef({ export interface CatalogApi { getEntities(): Promise; getEntityByName(name: string): Promise; + getLocationByEntity(entity: Entity): Promise; } diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 8a2b448d97..4242f89fe1 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC } from 'react'; +import React, { FC, useCallback, useState, useEffect } from 'react'; import { Content, ContentHeader, @@ -34,6 +34,8 @@ import { } from '../CatalogFilter/CatalogFilter'; import { Button, makeStyles, Typography, Link } from '@material-ui/core'; import { filterGroups, defaultFilter } from '../../data/filters'; +import GitHub from '@material-ui/icons/GitHub'; +import { Entity } from '@backstage/catalog-model'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -50,16 +52,52 @@ import { envelopeToComponent } from '../../data/utils'; const CatalogPage: FC<{}> = () => { const catalogApi = useApi(catalogApiRef); const { value, error, loading } = useAsync(() => catalogApi.getEntities()); - const [selectedFilter, setSelectedFilter] = React.useState( + const [locations, setLocations] = useState([]); + const [selectedFilter, setSelectedFilter] = useState( defaultFilter, ); - const onFilterSelected = React.useCallback( + const onFilterSelected = useCallback( selected => setSelectedFilter(selected), [], ); const styles = useStyles(); + useEffect(() => { + const getLocationDataForEntities = async (entities: Entity[]) => { + return Promise.all( + entities.map(entity => catalogApi.getLocationByEntity(entity)), + ); + }; + + if (value) { + getLocationDataForEntities(value) + .then(location => location.map(l => l.data)) + .then(setLocations); + } + }, [value, catalogApi]); + + const actions = [ + (rowData: any) => ({ + icon: GitHub, + tooltop: 'View on GitHub', + onClick: () => { + if (!rowData || !rowData.location) return; + window.open(rowData.location.target, '_blank'); + }, + isHidden: + rowData && rowData.location + ? rowData.location.type === 'github' + : false, + }), + ]; + + const findLocationForEntity = (entity: Entity, l: any) => { + const entityLocationId = + entity.metadata.annotations?.['backstage.io/managed-by-location']; + return l.find((location: any) => location.id === entityLocationId); + }; + return (
@@ -98,9 +136,19 @@ const CatalogPage: FC<{}> = () => { + envelopeToComponent( + val, + findLocationForEntity(val, locations), + ), + )) || + [] + } loading={loading} error={error} + actions={actions} /> diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index f5fa45dc7e..59359fab16 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -42,12 +42,14 @@ type CatalogTableProps = { titlePreamble: string; loading: boolean; error?: any; + actions: any; }; const CatalogTable: FC = ({ components, loading, error, titlePreamble, + actions, }) => { if (loading) { return ; @@ -64,9 +66,10 @@ const CatalogTable: FC = ({ return ( ); }; diff --git a/plugins/catalog/src/data/component.ts b/plugins/catalog/src/data/component.ts index 57cba03a9a..5da962911e 100644 --- a/plugins/catalog/src/data/component.ts +++ b/plugins/catalog/src/data/component.ts @@ -17,4 +17,5 @@ export type Component = { name: string; kind: string; description: string; + location: any; }; diff --git a/plugins/catalog/src/data/utils.ts b/plugins/catalog/src/data/utils.ts index 05fb2fee46..e54b39c7c0 100644 --- a/plugins/catalog/src/data/utils.ts +++ b/plugins/catalog/src/data/utils.ts @@ -16,10 +16,14 @@ import { Component } from './component'; import { Entity } from '@backstage/catalog-model'; -export function envelopeToComponent(envelope: Entity): Component { +export function envelopeToComponent( + envelope: Entity, + location: any, +): Component { return { name: envelope.metadata?.name ?? '', kind: envelope.kind ?? 'unknown', description: envelope.metadata?.annotations?.description ?? 'placeholder', + location, }; } From 8e6df5f575d0399fe934d4c8445224267544db5a Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Tue, 2 Jun 2020 15:03:11 +0200 Subject: [PATCH 2/9] quickfix failing tests --- .../catalog/src/components/CatalogPage/CatalogPage.test.tsx | 5 ++++- plugins/catalog/src/components/CatalogTable/CatalogTable.tsx | 2 +- plugins/catalog/src/data/component.ts | 2 +- plugins/catalog/src/data/utils.ts | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index ec26ce5b28..9ebeb7f546 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -22,7 +22,10 @@ import { wrapInTestApp } from '@backstage/test-utils'; import { catalogApiRef } from '../..'; const errorApi = { post: () => {} }; -const catalogApi = { getEntities: () => Promise.resolve([{ kind: '' }]) }; +const catalogApi = { + getEntities: () => Promise.resolve([{ kind: '', metadata: {} }]), + getLocationByEntity: () => Promise.resolve({ data: {} }), +}; describe('CatalogPage', () => { // this test right now causes some red lines in the log output when running tests diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 59359fab16..015916c3f4 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -42,7 +42,7 @@ type CatalogTableProps = { titlePreamble: string; loading: boolean; error?: any; - actions: any; + actions?: any; }; const CatalogTable: FC = ({ components, diff --git a/plugins/catalog/src/data/component.ts b/plugins/catalog/src/data/component.ts index 5da962911e..d53a6387fc 100644 --- a/plugins/catalog/src/data/component.ts +++ b/plugins/catalog/src/data/component.ts @@ -17,5 +17,5 @@ export type Component = { name: string; kind: string; description: string; - location: any; + location?: any; }; diff --git a/plugins/catalog/src/data/utils.ts b/plugins/catalog/src/data/utils.ts index e54b39c7c0..645d6b845d 100644 --- a/plugins/catalog/src/data/utils.ts +++ b/plugins/catalog/src/data/utils.ts @@ -18,7 +18,7 @@ import { Entity } from '@backstage/catalog-model'; export function envelopeToComponent( envelope: Entity, - location: any, + location?: any, ): Component { return { name: envelope.metadata?.name ?? '', From 254d9089be2cb49ad3d202da4835eca6c3a6791b Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Tue, 2 Jun 2020 15:36:31 +0200 Subject: [PATCH 3/9] Improved types slightly --- plugins/catalog/src/api/CatalogClient.ts | 15 ++++++--------- plugins/catalog/src/api/types.ts | 4 ++-- .../src/components/CatalogPage/CatalogPage.tsx | 14 ++++++++------ 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/plugins/catalog/src/api/CatalogClient.ts b/plugins/catalog/src/api/CatalogClient.ts index d6d717a085..20720d34bd 100644 --- a/plugins/catalog/src/api/CatalogClient.ts +++ b/plugins/catalog/src/api/CatalogClient.ts @@ -16,7 +16,7 @@ import { CatalogApi } from './types'; import { DescriptorEnvelope } from '../types'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, Location } from '@backstage/catalog-model'; export class CatalogClient implements CatalogApi { private apiOrigin: string; @@ -43,22 +43,19 @@ export class CatalogClient implements CatalogApi { if (entity) return entity; throw new Error(`'Entity not found: ${name}`); } - async getLocationByEntity(entity: Entity): Promise { - const findLocationIdInEntity = (e: Entity) => { - return e.metadata.annotations - ? e.metadata.annotations['backstage.io/managed-by-location'] - : null; - }; + async getLocationByEntity(entity: Entity): Promise { + const findLocationIdInEntity = (e: Entity) => + e.metadata.annotations?.['backstage.io/managed-by-location']; const locationId = findLocationIdInEntity(entity); - if (!locationId) return null; + if (!locationId) return undefined; const response = await fetch( `${this.apiOrigin}${this.basePath}/locations/${locationId}`, ); if (response.ok) { const location = await response.json(); - if (location) return location; + if (location) return location.data; } throw new Error(`'Location not found: ${locationId}`); } diff --git a/plugins/catalog/src/api/types.ts b/plugins/catalog/src/api/types.ts index 845dbe8e83..9e8dc5fbac 100644 --- a/plugins/catalog/src/api/types.ts +++ b/plugins/catalog/src/api/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { createApiRef } from '@backstage/core'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, Location } from '@backstage/catalog-model'; export const catalogApiRef = createApiRef({ id: 'plugin.catalog.service', @@ -25,5 +25,5 @@ export const catalogApiRef = createApiRef({ export interface CatalogApi { getEntities(): Promise; getEntityByName(name: string): Promise; - getLocationByEntity(entity: Entity): Promise; + getLocationByEntity(entity: Entity): Promise; } diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 4242f89fe1..479b5fee42 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -35,7 +35,7 @@ import { import { Button, makeStyles, Typography, Link } from '@material-ui/core'; import { filterGroups, defaultFilter } from '../../data/filters'; import GitHub from '@material-ui/icons/GitHub'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, Location } from '@backstage/catalog-model'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -48,6 +48,7 @@ const useStyles = makeStyles(theme => ({ import { catalogApiRef } from '../..'; import { envelopeToComponent } from '../../data/utils'; +import { Component } from '../../data/component'; const CatalogPage: FC<{}> = () => { const catalogApi = useApi(catalogApiRef); @@ -71,14 +72,12 @@ const CatalogPage: FC<{}> = () => { }; if (value) { - getLocationDataForEntities(value) - .then(location => location.map(l => l.data)) - .then(setLocations); + getLocationDataForEntities(value).then(setLocations); } }, [value, catalogApi]); const actions = [ - (rowData: any) => ({ + (rowData: Component) => ({ icon: GitHub, tooltop: 'View on GitHub', onClick: () => { @@ -92,7 +91,10 @@ const CatalogPage: FC<{}> = () => { }), ]; - const findLocationForEntity = (entity: Entity, l: any) => { + const findLocationForEntity = ( + entity: Entity, + l: Location[], + ): Location | undefined => { const entityLocationId = entity.metadata.annotations?.['backstage.io/managed-by-location']; return l.find((location: any) => location.id === entityLocationId); From d620646890a293509b1cd2cbd44bf6a5596887c5 Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Tue, 2 Jun 2020 15:45:38 +0200 Subject: [PATCH 4/9] Updated types --- plugins/catalog/src/api/CatalogClient.ts | 3 ++- plugins/catalog/src/components/CatalogPage/CatalogPage.tsx | 4 +++- plugins/catalog/src/data/component.ts | 4 +++- plugins/catalog/src/data/utils.ts | 4 ++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/catalog/src/api/CatalogClient.ts b/plugins/catalog/src/api/CatalogClient.ts index 20720d34bd..bf308cb012 100644 --- a/plugins/catalog/src/api/CatalogClient.ts +++ b/plugins/catalog/src/api/CatalogClient.ts @@ -57,6 +57,7 @@ export class CatalogClient implements CatalogApi { const location = await response.json(); if (location) return location.data; } - throw new Error(`'Location not found: ${locationId}`); + + return undefined; } } diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 479b5fee42..fe21c7de31 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -72,7 +72,9 @@ const CatalogPage: FC<{}> = () => { }; if (value) { - getLocationDataForEntities(value).then(setLocations); + getLocationDataForEntities(value) + .then(location => location.filter(l => !!l)) + .then(setLocations); } }, [value, catalogApi]); diff --git a/plugins/catalog/src/data/component.ts b/plugins/catalog/src/data/component.ts index d53a6387fc..1a7935a196 100644 --- a/plugins/catalog/src/data/component.ts +++ b/plugins/catalog/src/data/component.ts @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { Location } from '@backstage/catalog-model'; + export type Component = { name: string; kind: string; description: string; - location?: any; + location?: Location; }; diff --git a/plugins/catalog/src/data/utils.ts b/plugins/catalog/src/data/utils.ts index 645d6b845d..fe2ec62493 100644 --- a/plugins/catalog/src/data/utils.ts +++ b/plugins/catalog/src/data/utils.ts @@ -14,11 +14,11 @@ * limitations under the License. */ import { Component } from './component'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, Location } from '@backstage/catalog-model'; export function envelopeToComponent( envelope: Entity, - location?: any, + location?: Location, ): Component { return { name: envelope.metadata?.name ?? '', From a2a3d7ea2b74c49975a77a0cd743339fbab9cc79 Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Wed, 3 Jun 2020 10:17:37 +0200 Subject: [PATCH 5/9] Fixed typo and added a type --- plugins/catalog/src/api/CatalogClient.ts | 2 +- plugins/catalog/src/components/CatalogPage/CatalogPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog/src/api/CatalogClient.ts b/plugins/catalog/src/api/CatalogClient.ts index bf308cb012..abba8a7523 100644 --- a/plugins/catalog/src/api/CatalogClient.ts +++ b/plugins/catalog/src/api/CatalogClient.ts @@ -44,7 +44,7 @@ export class CatalogClient implements CatalogApi { throw new Error(`'Entity not found: ${name}`); } async getLocationByEntity(entity: Entity): Promise { - const findLocationIdInEntity = (e: Entity) => + const findLocationIdInEntity = (e: Entity): string | undefined => e.metadata.annotations?.['backstage.io/managed-by-location']; const locationId = findLocationIdInEntity(entity); diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index fe21c7de31..24337337c9 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -81,7 +81,7 @@ const CatalogPage: FC<{}> = () => { const actions = [ (rowData: Component) => ({ icon: GitHub, - tooltop: 'View on GitHub', + tooltip: 'View on GitHub', onClick: () => { if (!rowData || !rowData.location) return; window.open(rowData.location.target, '_blank'); From f7d232c1a8aa4c0b0083be9e4f1d085d4e578b36 Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Wed, 3 Jun 2020 10:34:53 +0200 Subject: [PATCH 6/9] Fixed hiding of github link actions when location isn't github --- plugins/catalog/src/components/CatalogPage/CatalogPage.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 24337337c9..09688bffb5 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -86,10 +86,8 @@ const CatalogPage: FC<{}> = () => { if (!rowData || !rowData.location) return; window.open(rowData.location.target, '_blank'); }, - isHidden: - rowData && rowData.location - ? rowData.location.type === 'github' - : false, + hidden: + rowData && rowData.location ? rowData.location.type !== 'github' : true, }), ]; From a47ebf902fb7f4d7663869cff03720239604f58a Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Wed, 3 Jun 2020 10:49:58 +0200 Subject: [PATCH 7/9] Removed a few more any types --- plugins/catalog/src/components/CatalogPage/CatalogPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 09688bffb5..725d266fbf 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -53,7 +53,7 @@ import { Component } from '../../data/component'; const CatalogPage: FC<{}> = () => { const catalogApi = useApi(catalogApiRef); const { value, error, loading } = useAsync(() => catalogApi.getEntities()); - const [locations, setLocations] = useState([]); + const [locations, setLocations] = useState([]); const [selectedFilter, setSelectedFilter] = useState( defaultFilter, ); @@ -97,7 +97,7 @@ const CatalogPage: FC<{}> = () => { ): Location | undefined => { const entityLocationId = entity.metadata.annotations?.['backstage.io/managed-by-location']; - return l.find((location: any) => location.id === entityLocationId); + return l.find(location => location.id === entityLocationId); }; return ( From 241a5a89440f607d82a434197be102cebbbf6b07 Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Wed, 3 Jun 2020 11:10:51 +0200 Subject: [PATCH 8/9] Convert Array to Array --- plugins/catalog/src/components/CatalogPage/CatalogPage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 725d266fbf..5951365b6e 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -73,7 +73,10 @@ const CatalogPage: FC<{}> = () => { if (value) { getLocationDataForEntities(value) - .then(location => location.filter(l => !!l)) + .then( + (location): Location[] => + location.filter(l => !!l) as Array, + ) .then(setLocations); } }, [value, catalogApi]); From 5d3a972e4ee1aaa44b774e7a53be02831b02ac02 Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Thu, 4 Jun 2020 11:44:01 +0200 Subject: [PATCH 9/9] Only set locations if component is still mounted --- .../catalog/src/components/CatalogPage/CatalogPage.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 5951365b6e..25d2618a9a 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -26,7 +26,7 @@ import { pageTheme, useApi, } from '@backstage/core'; -import { useAsync } from 'react-use'; +import { useAsync, useMountedState } from 'react-use'; import CatalogTable from '../CatalogTable/CatalogTable'; import { CatalogFilter, @@ -57,6 +57,7 @@ const CatalogPage: FC<{}> = () => { const [selectedFilter, setSelectedFilter] = useState( defaultFilter, ); + const isMounted = useMountedState(); const onFilterSelected = useCallback( selected => setSelectedFilter(selected), @@ -77,9 +78,11 @@ const CatalogPage: FC<{}> = () => { (location): Location[] => location.filter(l => !!l) as Array, ) - .then(setLocations); + .then(location => { + if (isMounted()) setLocations(location); + }); } - }, [value, catalogApi]); + }, [value, catalogApi, isMounted]); const actions = [ (rowData: Component) => ({