From 9b4b4050958b822d8647190d0a38a101fd8f84b2 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 2 Jun 2021 17:35:35 +0200 Subject: [PATCH 1/7] feat: reworking the techRadar plugin to support providing your own TechRadarApi to override how the data get's into your plugin Signed-off-by: blam --- plugins/tech-radar/README.md | 1 - plugins/tech-radar/src/api.ts | 22 +++++------ .../src/components/RadarComponent.tsx | 37 ++++++++----------- plugins/tech-radar/src/plugin.ts | 5 +++ .../src/{sampleData.ts => sample.ts} | 21 +++++++---- 5 files changed, 44 insertions(+), 42 deletions(-) rename plugins/tech-radar/src/{sampleData.ts => sample.ts} (94%) diff --git a/plugins/tech-radar/README.md b/plugins/tech-radar/README.md index 814ab6cba5..149ab54840 100644 --- a/plugins/tech-radar/README.md +++ b/plugins/tech-radar/README.md @@ -60,7 +60,6 @@ export type TechRadarPageProps = TechRadarComponentProps & { export interface TechRadarPageProps { width: number; height: number; - getData?: () => Promise; svgProps?: object; } ``` diff --git a/plugins/tech-radar/src/api.ts b/plugins/tech-radar/src/api.ts index eca80420bf..6f2a1a5cb1 100644 --- a/plugins/tech-radar/src/api.ts +++ b/plugins/tech-radar/src/api.ts @@ -15,6 +15,17 @@ */ import { MovedState } from './utils/types'; +import { createApiRef } from '@backstage/core'; + +export const techRadarApiRef = createApiRef({ + id: 'plugin.techradar.service', + description: 'Used to populate data in the TechRadar plugin', +}); + +export interface TechRadarApi { + // Loads the TechRadar response data to pass through to the TechRadar component + load: () => Promise; +} /** * Types related to the Radar's visualization. @@ -65,16 +76,5 @@ export interface TechRadarLoaderResponse { export interface TechRadarComponentProps { width: number; height: number; - getData?: () => Promise; svgProps?: object; } - -/** - * Set up the Radar as a Backstage plugin. - */ - -export interface TechRadarApi extends TechRadarComponentProps { - title?: string; - subtitle?: string; - pageTitle?: string; -} diff --git a/plugins/tech-radar/src/components/RadarComponent.tsx b/plugins/tech-radar/src/components/RadarComponent.tsx index a6e033a08e..39746e48fe 100644 --- a/plugins/tech-radar/src/components/RadarComponent.tsx +++ b/plugins/tech-radar/src/components/RadarComponent.tsx @@ -15,38 +15,35 @@ */ import React, { useEffect } from 'react'; -import { Progress, useApi, errorApiRef, ErrorApi } from '@backstage/core'; +import { Progress, useApi, errorApiRef } from '@backstage/core'; import { useAsync } from 'react-use'; import Radar from '../components/Radar'; -import { TechRadarComponentProps, TechRadarLoaderResponse } from '../api'; -import getSampleData from '../sampleData'; +import { + techRadarApiRef, + TechRadarComponentProps, + TechRadarLoaderResponse, +} from '../api'; import { Entry } from '../utils/types'; -const useTechRadarLoader = (props: TechRadarComponentProps) => { - const errorApi = useApi(errorApiRef); +const useTechRadarLoader = () => { + const errorApi = useApi(errorApiRef); + const techRadarApi = useApi(techRadarApiRef); - const { getData } = props; - - const state = useAsync(async () => { - if (getData) { - const response: TechRadarLoaderResponse = await getData(); - return response; - } - return undefined; - }, [getData, errorApi]); + const { error, value, loading } = useAsync(async () => techRadarApi.load(), [ + techRadarApi, + ]); useEffect(() => { - const { error } = state; if (error) { errorApi.post(error); } - }, [errorApi, state]); + }, [error, errorApi]); - return state; + return { loading, value, error }; }; const RadarComponent = (props: TechRadarComponentProps): JSX.Element => { - const { loading, error, value: data } = useTechRadarLoader(props); + const { loading, error, value: data } = useTechRadarLoader(); const mapToEntries = ( loaderResponse: TechRadarLoaderResponse | undefined, @@ -89,8 +86,4 @@ const RadarComponent = (props: TechRadarComponentProps): JSX.Element => { ); }; -RadarComponent.defaultProps = { - getData: getSampleData, -}; - export default RadarComponent; diff --git a/plugins/tech-radar/src/plugin.ts b/plugins/tech-radar/src/plugin.ts index 63af1f390f..e128600094 100644 --- a/plugins/tech-radar/src/plugin.ts +++ b/plugins/tech-radar/src/plugin.ts @@ -18,8 +18,12 @@ import { createPlugin, createRouteRef, createRoutableExtension, + createApiFactory, } from '@backstage/core'; +import { techRadarApiRef } from './api'; +import { SampleTechRadarApi } from './sample'; + const rootRouteRef = createRouteRef({ title: 'Tech Radar', }); @@ -29,6 +33,7 @@ export const techRadarPlugin = createPlugin({ routes: { root: rootRouteRef, }, + apis: [createApiFactory(techRadarApiRef, new SampleTechRadarApi())], }); export const TechRadarPage = techRadarPlugin.provide( diff --git a/plugins/tech-radar/src/sampleData.ts b/plugins/tech-radar/src/sample.ts similarity index 94% rename from plugins/tech-radar/src/sampleData.ts rename to plugins/tech-radar/src/sample.ts index 70b9303f39..ff895bcdb7 100644 --- a/plugins/tech-radar/src/sampleData.ts +++ b/plugins/tech-radar/src/sample.ts @@ -19,6 +19,7 @@ import { RadarQuadrant, RadarEntry, TechRadarLoaderResponse, + TechRadarApi, } from './api'; const rings = new Array(); @@ -164,15 +165,19 @@ entries.push({ ], url: '#', key: 'github-actions', - id: 'github-actions', - title: 'GitHub Actions', + id: 'github-actiosns', + title: 'GitHub Acssstions', quadrant: 'infrastructure', }); -export default function getSampleData(): Promise { - return Promise.resolve({ - rings, - quadrants, - entries, - }); +export const mock: TechRadarLoaderResponse = { + entries, + quadrants, + rings, +}; + +export class SampleTechRadarApi implements TechRadarApi { + async load() { + return mock; + } } From 313071b974ca70056e149684a6e531cbbd8394b3 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 3 Jun 2021 10:54:06 +0200 Subject: [PATCH 2/7] chore: adding documentation for loading data Signed-off-by: blam --- packages/app/src/apis.ts | 9 ++++++ plugins/tech-radar/README.md | 53 ++++++++++++++++---------------- plugins/tech-radar/src/sample.ts | 4 +-- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index ccc576e727..1b303a8aa3 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -34,6 +34,13 @@ import { GraphQLEndpoints, } from '@backstage/plugin-graphiql'; +import { techRadarApiRef, TechRadarApi } from '@backstage/plugin-tech-radar'; + +class MyOwnClient implements TechRadarApi { + async load() { + throw new Error('blah'); + } +} export const apis: AnyApiFactory[] = [ createApiFactory({ api: scmIntegrationsApiRef, @@ -61,4 +68,6 @@ export const apis: AnyApiFactory[] = [ }), createApiFactory(costInsightsApiRef, new ExampleCostInsightsClient()), + + createApiFactory(), ]; diff --git a/plugins/tech-radar/README.md b/plugins/tech-radar/README.md index 149ab54840..2781acabd9 100644 --- a/plugins/tech-radar/README.md +++ b/plugins/tech-radar/README.md @@ -72,36 +72,35 @@ export interface TechRadarPageProps { ### How do I load in my own data? -It's simple, you can pass through a `getData` prop which expects a `Promise` signature. +The `TechRadar` plugin uses the `TechRadarApiRef` to get a client which implements the `TechRadarApi` interface. The default sample one is located here: https://github.com/backstage/backstage/blob/master/plugins/tech-radar/src/sample.ts. To load your own data, you'll need to provide a class that implements the `TechRadarApi` and override the `TechRadarApiRef` in the `app/src/apis.ts`. -Here's an example: +```ts +// app/src/lib/MyClient.ts +import { + TechRadarApi, + TechRadarLoaderResponse, +} from '@backstage/plugin-tech-radar'; -```tsx -const getHardCodedData = () => - Promise.resolve({ - quadrants: [{ id: 'infrastructure', name: 'Infrastructure' }], - rings: [{ id: 'use', name: 'USE', color: '#93c47d' }], - entries: [ - { - url: '#', - key: 'github-actions', - id: 'github-actions', - title: 'GitHub Actions', - quadrant: 'infrastructure', - timeline: [ - { - moved: 0, - ringId: 'use', - date: new Date('2020-08-06'), - description: - 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat', - }, - ], - }, - ], - }); +class MyOwnClient implements TechRadarApi { + async load(): Promise { + const data = await fetch('https://mydata.json').then(res => res.json()); -; + // maybe you'll need to do some data transformation here to make it look like TechRadarLoaderResponse + + return data; + } +} + +// app/src/apis.ts +import { MyOwnClient } from './lib/MyClient'; +import { techRadarApiRef } from '@backstage/plugin-tech-radar'; + +export const apis: AnyApiFactory[] = [ + /* + ... + */ + createApiFactory(techRadarApiRef, new MyOwnClient()), +]; ``` ### How do I write tests? diff --git a/plugins/tech-radar/src/sample.ts b/plugins/tech-radar/src/sample.ts index ff895bcdb7..1e924d85d0 100644 --- a/plugins/tech-radar/src/sample.ts +++ b/plugins/tech-radar/src/sample.ts @@ -165,8 +165,8 @@ entries.push({ ], url: '#', key: 'github-actions', - id: 'github-actiosns', - title: 'GitHub Acssstions', + id: 'github-actions', + title: 'GitHub Actions', quadrant: 'infrastructure', }); From 89e2944e1f8578f221e87908184b5daf0eea7b9b Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 3 Jun 2021 11:03:20 +0200 Subject: [PATCH 3/7] chore: fixing tests and making the thing nice Signed-off-by: blam --- .../src/components/RadarComponent.test.tsx | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/plugins/tech-radar/src/components/RadarComponent.test.tsx b/plugins/tech-radar/src/components/RadarComponent.test.tsx index 59cababbc9..4b740597b5 100644 --- a/plugins/tech-radar/src/components/RadarComponent.test.tsx +++ b/plugins/tech-radar/src/components/RadarComponent.test.tsx @@ -24,6 +24,7 @@ import { withLogCollector } from '@backstage/test-utils'; import GetBBoxPolyfill from '../utils/polyfills/getBBox'; import RadarComponent from './RadarComponent'; +import { TechRadarLoaderResponse, techRadarApiRef, TechRadarApi } from '../api'; describe('RadarComponent', () => { beforeAll(() => { @@ -34,13 +35,30 @@ describe('RadarComponent', () => { GetBBoxPolyfill.remove(); }); + class MockClient implements TechRadarApi { + async load(): Promise { + return { + entries: [], + quadrants: [], + rings: [], + }; + } + } + + const mockClient = new MockClient(); + it('should render a progress bar', async () => { jest.useFakeTimers(); const errorApi = { post: () => {} }; const { getByTestId, queryByTestId } = render( - + { it('should call the errorApi if load fails', async () => { const errorApi = { post: jest.fn() }; - const techRadarLoadFail = () => - Promise.reject(new Error('404 Page Not Found')); + jest + .spyOn(mockClient, 'load') + .mockRejectedValue(new Error('404 Page Not Found')); const { queryByTestId } = render( - + From 90a505a77f3da68f0522d6623c3b847052968d4e Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 3 Jun 2021 11:09:15 +0200 Subject: [PATCH 4/7] chore: added changeset Signed-off-by: blam --- .changeset/proud-bottles-dream.md | 5 +++++ packages/app/src/apis.ts | 9 --------- 2 files changed, 5 insertions(+), 9 deletions(-) create mode 100644 .changeset/proud-bottles-dream.md diff --git a/.changeset/proud-bottles-dream.md b/.changeset/proud-bottles-dream.md new file mode 100644 index 0000000000..42cff9db46 --- /dev/null +++ b/.changeset/proud-bottles-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-radar': minor +--- + +Migrating the Tech Radar to support using `ApiRefs` to go fetch data diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index 1b303a8aa3..ccc576e727 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -34,13 +34,6 @@ import { GraphQLEndpoints, } from '@backstage/plugin-graphiql'; -import { techRadarApiRef, TechRadarApi } from '@backstage/plugin-tech-radar'; - -class MyOwnClient implements TechRadarApi { - async load() { - throw new Error('blah'); - } -} export const apis: AnyApiFactory[] = [ createApiFactory({ api: scmIntegrationsApiRef, @@ -68,6 +61,4 @@ export const apis: AnyApiFactory[] = [ }), createApiFactory(costInsightsApiRef, new ExampleCostInsightsClient()), - - createApiFactory(), ]; From 1ff7d88c57c271864cbcdb590d97fb54314481bb Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 3 Jun 2021 11:10:04 +0200 Subject: [PATCH 5/7] chore: reword the changeset Signed-off-by: blam --- .changeset/proud-bottles-dream.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/.changeset/proud-bottles-dream.md b/.changeset/proud-bottles-dream.md index 42cff9db46..d9c7259758 100644 --- a/.changeset/proud-bottles-dream.md +++ b/.changeset/proud-bottles-dream.md @@ -2,4 +2,31 @@ '@backstage/plugin-tech-radar': minor --- -Migrating the Tech Radar to support using `ApiRefs` to go fetch data +Migrating the Tech Radar to support using `ApiRefs` to load custom data. + +If you had a `getData` function, you'll now need to encapsulate that logic in a class that can override the `techRadarApiRef`. + +```ts +// app/src/lib/MyClient.ts +import { + TechRadarApi, + TechRadarLoaderResponse, +} from '@backstage/plugin-tech-radar'; + +class MyOwnClient implements TechRadarApi { + async load(): Promise { + // here's where you would put you logic to load the response that was previously passed into getData + } +} + +// app/src/apis.ts +import { MyOwnClient } from './lib/MyClient'; +import { techRadarApiRef } from '@backstage/plugin-tech-radar'; + +export const apis: AnyApiFactory[] = [ + /* + ... + */ + createApiFactory(techRadarApiRef, new MyOwnClient()), +]; +``` From 5312c627f87b14d7cc5ceec2b1079d718747b714 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 3 Jun 2021 11:34:13 +0200 Subject: [PATCH 6/7] chore: missed updating some of those tests Signed-off-by: blam --- .../src/components/RadarPage.test.tsx | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/plugins/tech-radar/src/components/RadarPage.test.tsx b/plugins/tech-radar/src/components/RadarPage.test.tsx index 918015001b..b5169fadd9 100644 --- a/plugins/tech-radar/src/components/RadarPage.test.tsx +++ b/plugins/tech-radar/src/components/RadarPage.test.tsx @@ -27,6 +27,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import GetBBoxPolyfill from '../utils/polyfills/getBBox'; import { RadarPage } from './RadarPage'; +import { TechRadarLoaderResponse, techRadarApiRef, TechRadarApi } from '../api'; describe('RadarPage', () => { beforeAll(() => { @@ -36,6 +37,17 @@ describe('RadarPage', () => { afterAll(() => { GetBBoxPolyfill.remove(); }); + class MockClient implements TechRadarApi { + async load(): Promise { + return { + entries: [], + quadrants: [], + rings: [], + }; + } + } + + const mockClient = new MockClient(); it('should render a progress bar', async () => { jest.useFakeTimers(); @@ -49,7 +61,9 @@ describe('RadarPage', () => { const { getByTestId, queryByTestId } = render( wrapInTestApp( - + + + , ), ); @@ -72,7 +86,9 @@ describe('RadarPage', () => { const { getByText, getByTestId } = await renderInTestApp( - + + + , ); @@ -86,18 +102,25 @@ describe('RadarPage', () => { it('should call the errorApi if load fails', async () => { const errorApi = new MockErrorApi({ collect: true }); - const techRadarLoadFail = () => - Promise.reject(new Error('404 Page Not Found')); + + jest + .spyOn(mockClient, 'load') + .mockRejectedValue(new Error('404 Page Not Found')); + const techRadarProps = { width: 1200, height: 800, - getData: techRadarLoadFail, svgProps: { 'data-testid': 'tech-radar-svg' }, }; const { queryByTestId } = await renderInTestApp( - + , From eca9b7e8915c0d1511ce8203d3201f77f07d78e6 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Thu, 3 Jun 2021 11:35:52 +0200 Subject: [PATCH 7/7] Update plugins/tech-radar/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: blam --- plugins/tech-radar/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/tech-radar/README.md b/plugins/tech-radar/README.md index 2781acabd9..35c510ddd3 100644 --- a/plugins/tech-radar/README.md +++ b/plugins/tech-radar/README.md @@ -72,7 +72,7 @@ export interface TechRadarPageProps { ### How do I load in my own data? -The `TechRadar` plugin uses the `TechRadarApiRef` to get a client which implements the `TechRadarApi` interface. The default sample one is located here: https://github.com/backstage/backstage/blob/master/plugins/tech-radar/src/sample.ts. To load your own data, you'll need to provide a class that implements the `TechRadarApi` and override the `TechRadarApiRef` in the `app/src/apis.ts`. +The `TechRadar` plugin uses the `techRadarApiRef` to get a client which implements the `TechRadarApi` interface. The default sample one is located [here](https://github.com/backstage/backstage/blob/master/plugins/tech-radar/src/sample.ts). To load your own data, you'll need to provide a class that implements the `TechRadarApi` and override the `techRadarApiRef` in the `app/src/apis.ts`. ```ts // app/src/lib/MyClient.ts