From f00a76dc968a912ab4624306256c7a895d631810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20DOREAU?= Date: Fri, 19 Jun 2020 00:46:11 +0200 Subject: [PATCH 01/19] feat(tech-radar): add unit tests --- .../RadarBubble/RadarBubble.test.tsx | 52 +++++++++++++++ .../components/RadarEntry/RadarEntry.test.tsx | 52 +++++++++++++++ .../RadarFooter/RadarFooter.test.tsx | 50 +++++++++++++++ .../components/RadarGrid/RadarGrid.test.tsx | 50 +++++++++++++++ .../RadarLegend/RadarLegend.test.tsx | 60 ++++++++++++++++++ .../components/RadarPlot/RadarPlot.test.tsx | 63 +++++++++++++++++++ 6 files changed, 327 insertions(+) create mode 100644 plugins/tech-radar/src/components/RadarBubble/RadarBubble.test.tsx create mode 100644 plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx create mode 100644 plugins/tech-radar/src/components/RadarFooter/RadarFooter.test.tsx create mode 100644 plugins/tech-radar/src/components/RadarGrid/RadarGrid.test.tsx create mode 100644 plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx create mode 100644 plugins/tech-radar/src/components/RadarPlot/RadarPlot.test.tsx diff --git a/plugins/tech-radar/src/components/RadarBubble/RadarBubble.test.tsx b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.test.tsx new file mode 100644 index 0000000000..688fd55f9f --- /dev/null +++ b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.test.tsx @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { render } from '@testing-library/react'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; +import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; + +import RadarBubble from './RadarBubble'; + +const minProps = { + visible: true, + text: 'RadarBubble', + x: 2, + y: 2, +}; + +describe('RadarBubble', () => { + beforeAll(() => { + GetBBoxPolyfill.create(0, 0, 1000, 500); + }); + + afterAll(() => { + GetBBoxPolyfill.remove(); + }); + + it('should render', () => { + const rendered = render( + + + + + , + ); + + expect(rendered).not.toBeNull(); + }); +}); diff --git a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx new file mode 100644 index 0000000000..20bc6fa7be --- /dev/null +++ b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { render } from '@testing-library/react'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; +import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; + +import RadarEntry from './RadarEntry'; + +const minProps = { + x: 2, + y: 2, + value: 2, + color: 'red', +}; + +describe('RadarEntry', () => { + beforeAll(() => { + GetBBoxPolyfill.create(0, 0, 1000, 500); + }); + + afterAll(() => { + GetBBoxPolyfill.remove(); + }); + + it('should render', () => { + const rendered = render( + + + + + , + ); + + expect(rendered).not.toBeNull(); + }); +}); diff --git a/plugins/tech-radar/src/components/RadarFooter/RadarFooter.test.tsx b/plugins/tech-radar/src/components/RadarFooter/RadarFooter.test.tsx new file mode 100644 index 0000000000..3e0ee7e407 --- /dev/null +++ b/plugins/tech-radar/src/components/RadarFooter/RadarFooter.test.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { render } from '@testing-library/react'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; +import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; + +import RadarFooter from './RadarFooter'; + +const minProps = { + x: 2, + y: 2, +}; + +describe('RadarFooter', () => { + beforeAll(() => { + GetBBoxPolyfill.create(0, 0, 1000, 500); + }); + + afterAll(() => { + GetBBoxPolyfill.remove(); + }); + + it('should render', () => { + const rendered = render( + + + + + , + ); + + expect(rendered).not.toBeNull(); + }); +}); diff --git a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.test.tsx b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.test.tsx new file mode 100644 index 0000000000..8a2873564d --- /dev/null +++ b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.test.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { render } from '@testing-library/react'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; +import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; + +import RadarGrid from './RadarGrid'; + +const minProps = { + radius: 5, + rings: [{ id: 'use', name: 'USE', color: '#93c47d' }], +}; + +describe('RadarGrid', () => { + beforeAll(() => { + GetBBoxPolyfill.create(0, 0, 1000, 500); + }); + + afterAll(() => { + GetBBoxPolyfill.remove(); + }); + + it('should render', () => { + const rendered = render( + + + + + , + ); + + expect(rendered).not.toBeNull(); + }); +}); diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx new file mode 100644 index 0000000000..8168688fdd --- /dev/null +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { render } from '@testing-library/react'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; +import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; + +import RadarLegend from './RadarLegend'; + +const minProps = { + quadrants: [{ id: 'languages', name: 'Languages' }], + rings: [{ id: 'use', name: 'USE', color: '#93c47d' }], + entries: [ + { + id: 'typescript', + title: 'TypeScript', + quadrant: { id: 'languages', name: 'Languages' }, + moved: 0, + ring: { id: 'use', name: 'USE', color: '#93c47d' }, + url: '#', + }, + ], +}; + +describe('RadarLegend', () => { + beforeAll(() => { + GetBBoxPolyfill.create(0, 0, 1000, 500); + }); + + afterAll(() => { + GetBBoxPolyfill.remove(); + }); + + it('should render', () => { + const rendered = render( + + + + + , + ); + + expect(rendered).not.toBeNull(); + }); +}); diff --git a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.test.tsx b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.test.tsx new file mode 100644 index 0000000000..31f450baeb --- /dev/null +++ b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.test.tsx @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { render } from '@testing-library/react'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; +import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; + +import RadarPlot from './RadarPlot'; + +const minProps = { + width: 500, + height: 200, + radius: 50, + quadrants: [{ id: 'languages', name: 'Languages' }], + rings: [{ id: 'use', name: 'USE', color: '#93c47d' }], + entries: [ + { + id: 'typescript', + title: 'TypeScript', + quadrant: { id: 'languages', name: 'Languages' }, + moved: 0, + ring: { id: 'use', name: 'USE', color: '#93c47d' }, + url: '#', + }, + ], +}; + +describe('RadarPlot', () => { + beforeAll(() => { + GetBBoxPolyfill.create(0, 0, 1000, 500); + }); + + afterAll(() => { + GetBBoxPolyfill.remove(); + }); + + it('should render', () => { + const rendered = render( + + + + + , + ); + + expect(rendered).not.toBeNull(); + }); +}); From de1fe3c0e84f0be71b9d9ea8cf5a693ac7048749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20DOREAU?= Date: Fri, 19 Jun 2020 00:47:25 +0200 Subject: [PATCH 02/19] refactor(tech-radar): style + code quality --- .../tech-radar/src/components/Radar/Radar.tsx | 6 +- .../tech-radar/src/components/Radar/utils.ts | 2 +- .../components/RadarBubble/RadarBubble.tsx | 4 +- .../src/components/RadarComponent.tsx | 28 ++--- .../src/components/RadarEntry/RadarEntry.tsx | 47 ++++---- .../components/RadarFooter/RadarFooter.tsx | 4 +- .../components/RadarLegend/RadarLegend.tsx | 103 +++++++++--------- .../tech-radar/src/components/RadarPage.tsx | 4 +- .../src/components/RadarPlot/RadarPlot.tsx | 6 +- plugins/tech-radar/src/utils/components.tsx | 35 ++++++ 10 files changed, 134 insertions(+), 105 deletions(-) create mode 100644 plugins/tech-radar/src/utils/components.tsx diff --git a/plugins/tech-radar/src/components/Radar/Radar.tsx b/plugins/tech-radar/src/components/Radar/Radar.tsx index 1db655730d..a1e4d2da78 100644 --- a/plugins/tech-radar/src/components/Radar/Radar.tsx +++ b/plugins/tech-radar/src/components/Radar/Radar.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC, useState, useRef } from 'react'; +import React, { useState, useRef } from 'react'; import RadarPlot from '../RadarPlot'; import { Ring, Quadrant, Entry } from '../../utils/types'; import { adjustQuadrants, adjustRings, adjustEntries } from './utils'; @@ -28,7 +28,7 @@ type Props = { svgProps?: object; }; -const Radar: FC = props => { +const Radar = (props: Props): JSX.Element => { const { width, height, quadrants, rings, entries } = props; const radius = Math.min(width, height) / 2; @@ -38,7 +38,7 @@ const Radar: FC = props => { // TODO(dflemstr): most of this can be heavily memoized if performance becomes a problem adjustQuadrants(quadrants, radius, width, height); adjustRings(rings, radius); - adjustEntries(entries, activeEntry, quadrants, rings, radius); + adjustEntries(entries, quadrants, rings, radius, activeEntry); return ( diff --git a/plugins/tech-radar/src/components/Radar/utils.ts b/plugins/tech-radar/src/components/Radar/utils.ts index a8896ddf31..65268613fe 100644 --- a/plugins/tech-radar/src/components/Radar/utils.ts +++ b/plugins/tech-radar/src/components/Radar/utils.ts @@ -98,10 +98,10 @@ export const adjustQuadrants = ( export const adjustEntries = ( entries: Entry[], - activeEntry: Entry | null | undefined, quadrants: Quadrant[], rings: Ring[], radius: number, + activeEntry?: Entry | null, ) => { let seed = 42; entries.forEach((entry, idx) => { diff --git a/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx index dcf9a37ed7..723a408885 100644 --- a/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx +++ b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC, useRef, useLayoutEffect } from 'react'; +import React, { useRef, useLayoutEffect } from 'react'; import { makeStyles, Theme } from '@material-ui/core'; type Props = { @@ -46,7 +46,7 @@ const useStyles = makeStyles(() => ({ }, })); -const RadarBubble: FC = props => { +const RadarBubble = (props: Props): JSX.Element => { const classes = useStyles(props); const { visible, text } = props; diff --git a/plugins/tech-radar/src/components/RadarComponent.tsx b/plugins/tech-radar/src/components/RadarComponent.tsx index 981d1f1369..8d56c6aa0f 100644 --- a/plugins/tech-radar/src/components/RadarComponent.tsx +++ b/plugins/tech-radar/src/components/RadarComponent.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { useEffect, useState, FC } from 'react'; +import React, { useEffect, useState } from 'react'; import { Progress, useApi, errorApiRef, ErrorApi } from '@backstage/core'; import Radar from '../components/Radar'; import { TechRadarComponentProps, TechRadarLoaderResponse } from '../api'; @@ -22,15 +22,9 @@ import getSampleData from '../sampleData'; const useTechRadarLoader = (props: TechRadarComponentProps) => { const errorApi = useApi(errorApiRef); - const [state, setState] = useState<{ - loading: boolean; - error?: Error; - data?: TechRadarLoaderResponse; - }>({ - loading: true, - error: undefined, - data: undefined, - }); + const [error, setError] = useState(); + const [loading, setLoading] = useState(true); + const [data, setData] = useState(); const { getData } = props; @@ -41,22 +35,20 @@ const useTechRadarLoader = (props: TechRadarComponentProps) => { getData() .then((payload: TechRadarLoaderResponse) => { - setState({ loading: false, error: undefined, data: payload }); + setLoading(false); + setData(payload); }) .catch((err: Error) => { errorApi.post(err); - setState({ - loading: false, - error: err, - data: undefined, - }); + setLoading(false); + setError(err); }); }, [getData, errorApi]); - return state; + return { data, loading, error }; }; -const RadarComponent: FC = props => { +const RadarComponent = (props: TechRadarComponentProps): JSX.Element => { const { loading, error, data } = useTechRadarLoader(props); return ( diff --git a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx index 6006979e67..ff499f4575 100644 --- a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx +++ b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx @@ -14,13 +14,14 @@ * limitations under the License. */ -import React, { FC } from 'react'; +import React from 'react'; import { makeStyles, Theme } from '@material-ui/core'; +import { WithLink } from '../../utils/components'; type Props = { x: number; y: number; - number: number; + value: number; color: string; url?: string; moved?: number; @@ -43,14 +44,27 @@ const useStyles = makeStyles(() => ({ }, })); -const RadarEntry: FC = props => { +const makeBlip = (color: string, moved?: number) => { + const style = { fill: color }; + + let blip = ; + if (moved && moved > 0) { + blip = ; // triangle pointing up + } else if (moved && moved < 0) { + blip = ; // triangle pointing down + } + + return blip; +}; + +const RadarEntry = (props: Props): JSX.Element => { const classes = useStyles(props); const { moved, color, url, - number, + value, x, y, onMouseEnter, @@ -58,24 +72,7 @@ const RadarEntry: FC = props => { onClick, } = props; - const style = { fill: color }; - - let blip; - if (moved && moved > 0) { - blip = ; // triangle pointing up - } else if (moved && moved < 0) { - blip = ; // triangle pointing down - } else { - blip = ; - } - - if (url) { - blip = ( - - {blip} - - ); - } + const blip = makeBlip(color, moved); return ( = props => { onMouseLeave={onMouseLeave} onClick={onClick} > - {blip} + + {blip} + - {number} + {value} ); diff --git a/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx b/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx index 7d60a63f59..b6284644eb 100644 --- a/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx +++ b/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC } from 'react'; +import React from 'react'; import { makeStyles, Theme } from '@material-ui/core'; type Props = { @@ -31,7 +31,7 @@ const useStyles = makeStyles(() => ({ }, })); -const RadarFooter: FC = props => { +const RadarFooter = (props: Props): JSX.Element => { const { x, y } = props; const classes = useStyles(props); diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx index 03146a8071..c2509e9cac 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { FC } from 'react'; +import React from 'react'; import { makeStyles, Theme } from '@material-ui/core'; import { Quadrant, Ring, Entry } from '../../utils/types'; +import { WithLink } from '../../utils/components'; type Segments = { [k: number]: { [k: number]: Entry[] }; @@ -29,20 +30,7 @@ type Props = { onEntryMouseLeave?: (entry: Entry) => void; }; -const useStyles = makeStyles(() => ({ - quadrant: { - height: '100%', - width: '100%', - overflow: 'hidden', - pointerEvents: 'none', - }, - quadrantHeading: { - pointerEvents: 'none', - userSelect: 'none', - marginTop: 0, - marginBottom: 'calc(18px * 0.375)', - fontSize: '18px', - }, +const ringStyles = { rings: { columns: 3, }, @@ -69,6 +57,25 @@ const useStyles = makeStyles(() => ({ '-webkit-font-feature-settings': 'pnum', 'font-feature-settings': 'pnum', }, +}; + +const quadrantStyles = { + quadrant: { + height: '100%', + width: '100%', + overflow: 'hidden', + pointerEvents: 'none', + }, + quadrantHeading: { + pointerEvents: 'none', + userSelect: 'none', + marginTop: 0, + marginBottom: 'calc(18px * 0.375)', + fontSize: '18px', + }, +}; + +const entryStyle = { entry: { pointerEvents: 'none', userSelect: 'none', @@ -77,12 +84,18 @@ const useStyles = makeStyles(() => ({ entryLink: { pointerEvents: 'none', }, +}; + +const useStyles = makeStyles(() => ({ + ringStyles, + quadrantStyles, + entryStyle, })); -const RadarLegend: FC = props => { +const RadarLegend = (props: Props): JSX.Element => { const classes = useStyles(props); - const _getSegment = ( + const getSegment = ( segmented: Segments, quadrant: Quadrant, ring: Ring, @@ -94,7 +107,7 @@ const RadarLegend: FC = props => { return ridx === undefined ? [] : segmentedData[ridx + ringOffset] || []; }; - const _renderRing = ( + const renderRing = ( ring: Ring, entries: Entry[], onEntryMouseEnter?: Props['onEntryMouseEnter'], @@ -107,39 +120,29 @@ const RadarLegend: FC = props => {

(empty)

) : (
    - {entries.map(entry => { - let node = {entry.title}; - - if (entry.url) { - node = ( - - {node} - - ); - } - - return ( -
  1. onEntryMouseEnter(entry)) - } - onMouseLeave={ - onEntryMouseLeave && (() => onEntryMouseLeave(entry)) - } - > - {node} -
  2. - ); - })} + {entries.map(entry => ( +
  3. onEntryMouseEnter(entry)) + } + onMouseLeave={ + onEntryMouseLeave && (() => onEntryMouseLeave(entry)) + } + > + + {entry.title} + +
  4. + ))}
)} ); }; - const _renderQuadrant = ( + const renderQuadrant = ( segments: Segments, quadrant: Quadrant, rings: Ring[], @@ -158,9 +161,9 @@ const RadarLegend: FC = props => {

{quadrant.name}

{rings.map(ring => - _renderRing( + renderRing( ring, - _getSegment(segments, quadrant, ring), + getSegment(segments, quadrant, ring), onEntryMouseEnter, onEntryMouseLeave, ), @@ -171,7 +174,7 @@ const RadarLegend: FC = props => { ); }; - const _setupSegments = (entries: Entry[]) => { + const setupSegments = (entries: Entry[]) => { const segments: Segments = {}; for (const entry of entries) { @@ -209,12 +212,12 @@ const RadarLegend: FC = props => { onEntryMouseLeave, } = props; - const segments: Segments = _setupSegments(entries); + const segments: Segments = setupSegments(entries); return ( {quadrants.map(quadrant => - _renderQuadrant( + renderQuadrant( segments, quadrant, rings, diff --git a/plugins/tech-radar/src/components/RadarPage.tsx b/plugins/tech-radar/src/components/RadarPage.tsx index 4c7236e01f..1aefb72eca 100644 --- a/plugins/tech-radar/src/components/RadarPage.tsx +++ b/plugins/tech-radar/src/components/RadarPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC } from 'react'; +import React from 'react'; import { Grid } from '@material-ui/core'; import { Content, @@ -29,7 +29,7 @@ import { import RadarComponent from '../components/RadarComponent'; import { techRadarApiRef, TechRadarApi } from '../api'; -const RadarPage: FC<{}> = () => { +const RadarPage = (): JSX.Element => { const techRadarApi = useApi(techRadarApiRef); return ( diff --git a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx index 48ad660dde..0315afde5f 100644 --- a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx +++ b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC } from 'react'; +import React from 'react'; import { Quadrant, Ring, Entry } from '../../utils/types'; import RadarGrid from '../RadarGrid'; @@ -36,7 +36,7 @@ type Props = { }; // A component that draws the radar circle. -const RadarPlot: FC = props => { +const RadarPlot = (props: Props): JSX.Element => { const { width, height, @@ -71,7 +71,7 @@ const RadarPlot: FC = props => { x={entry.x || 0} y={entry.y || 0} color={entry.color || ''} - number={((entry && entry.idx) || 0) + 1} + value={((entry && entry.idx) || 0) + 1} url={entry.url} moved={entry.moved} onMouseEnter={onEntryMouseEnter && (() => onEntryMouseEnter(entry))} diff --git a/plugins/tech-radar/src/utils/components.tsx b/plugins/tech-radar/src/utils/components.tsx new file mode 100644 index 0000000000..af5a7b05bd --- /dev/null +++ b/plugins/tech-radar/src/utils/components.tsx @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Spotify AB + * + * 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'; + +type WithLinkProps = { + url?: string; + className: string; + children: React.ReactNode; +}; + +export const WithLink = ({ + url, + className, + children, +}: WithLinkProps): JSX.Element => + url ? ( + + {children} + + ) : ( + <>{children} + ); From 3d318efeaedb55ab59c3d899daaaf153828b9dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20DOREAU?= Date: Fri, 19 Jun 2020 16:20:37 +0200 Subject: [PATCH 03/19] refactor(tech-radar): variable names + undefined check --- .../tech-radar/src/components/Radar/Radar.tsx | 6 ++-- .../tech-radar/src/components/Radar/utils.ts | 28 +++++++-------- .../src/components/RadarGrid/RadarGrid.tsx | 6 ++-- .../components/RadarLegend/RadarLegend.tsx | 35 ++++++++++--------- .../src/components/RadarPlot/RadarPlot.tsx | 2 +- plugins/tech-radar/src/utils/types.ts | 6 ++-- 6 files changed, 44 insertions(+), 39 deletions(-) diff --git a/plugins/tech-radar/src/components/Radar/Radar.tsx b/plugins/tech-radar/src/components/Radar/Radar.tsx index a1e4d2da78..8fe3c15fba 100644 --- a/plugins/tech-radar/src/components/Radar/Radar.tsx +++ b/plugins/tech-radar/src/components/Radar/Radar.tsx @@ -32,7 +32,7 @@ const Radar = (props: Props): JSX.Element => { const { width, height, quadrants, rings, entries } = props; const radius = Math.min(width, height) / 2; - const [activeEntry, setActiveEntry] = useState(); + const [activeEntry, setActiveEntry] = useState(); const node = useRef(null); // TODO(dflemstr): most of this can be heavily memoized if performance becomes a problem @@ -49,9 +49,9 @@ const Radar = (props: Props): JSX.Element => { entries={entries} quadrants={quadrants} rings={rings} - activeEntry={activeEntry || undefined} + activeEntry={activeEntry} onEntryMouseEnter={entry => setActiveEntry(entry)} - onEntryMouseLeave={() => setActiveEntry(null)} + onEntryMouseLeave={() => setActiveEntry(undefined)} /> ); diff --git a/plugins/tech-radar/src/components/Radar/utils.ts b/plugins/tech-radar/src/components/Radar/utils.ts index 65268613fe..25d9e7dac8 100644 --- a/plugins/tech-radar/src/components/Radar/utils.ts +++ b/plugins/tech-radar/src/components/Radar/utils.ts @@ -81,14 +81,14 @@ export const adjustQuadrants = ( }, ]; - quadrants.forEach((quadrant, idx) => { - const legendParam = legendParams[idx % 4]; + quadrants.forEach((quadrant, index) => { + const legendParam = legendParams[index % 4]; - quadrant.idx = idx; - quadrant.radialMin = (idx * Math.PI) / 2; - quadrant.radialMax = ((idx + 1) * Math.PI) / 2; - quadrant.offsetX = idx % 4 === 0 || idx % 4 === 3 ? 1 : -1; - quadrant.offsetY = idx % 4 === 0 || idx % 4 === 1 ? 1 : -1; + quadrant.index = index; + quadrant.radialMin = (index * Math.PI) / 2; + quadrant.radialMax = ((index + 1) * Math.PI) / 2; + quadrant.offsetX = index % 4 === 0 || index % 4 === 3 ? 1 : -1; + quadrant.offsetY = index % 4 === 0 || index % 4 === 1 ? 1 : -1; quadrant.legendX = legendParam.x; quadrant.legendY = legendParam.y; quadrant.legendWidth = legendParam.width; @@ -101,10 +101,10 @@ export const adjustEntries = ( quadrants: Quadrant[], rings: Ring[], radius: number, - activeEntry?: Entry | null, + activeEntry?: Entry, ) => { let seed = 42; - entries.forEach((entry, idx) => { + entries.forEach((entry, index) => { const quadrant = quadrants.find(q => { const match = typeof entry.quadrant === 'object' ? entry.quadrant.id : entry.quadrant; @@ -124,7 +124,7 @@ export const adjustEntries = ( throw new Error(`Unknown ring ${entry.ring} for entry ${entry.id}!`); } - entry.idx = idx; + entry.index = index; entry.quadrant = quadrant; entry.ring = ring; entry.segment = new Segment(quadrant, ring, radius, () => seed++); @@ -163,10 +163,10 @@ export const adjustEntries = ( }; export const adjustRings = (rings: Ring[], radius: number) => { - rings.forEach((ring, idx) => { - ring.idx = idx; - ring.outerRadius = ((idx + 2) / (rings.length + 1)) * radius; + rings.forEach((ring, index) => { + ring.index = index; + ring.outerRadius = ((index + 2) / (rings.length + 1)) * radius; ring.innerRadius = - ((idx === 0 ? 0 : idx + 1) / (rings.length + 1)) * radius; + ((index === 0 ? 0 : index + 1) / (rings.length + 1)) * radius; }); }; diff --git a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx index 514afbe958..68c84e2443 100644 --- a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx +++ b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx @@ -49,7 +49,7 @@ const RadarGrid = (props: Props) => { const { radius, rings } = props; const classes = useStyles(props); - const makeRingNode = (ringRadius: number | undefined, ringIndex: number) => [ + const makeRingNode = (ringIndex: number, ringRadius?: number) => [ { />, ]; - const ringNodes = rings.map(r => r.outerRadius).map(makeRingNode); + const ringNodes = rings + .map(r => r.outerRadius) + .map((ringRadius, ringIndex) => makeRingNode(ringIndex, ringRadius)); return <>{axisNodes.concat(...ringNodes)}; }; diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx index c2509e9cac..ad204f4beb 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx @@ -101,10 +101,13 @@ const RadarLegend = (props: Props): JSX.Element => { ring: Ring, ringOffset = 0, ) => { - const qidx = quadrant.idx; - const ridx = ring.idx; - const segmentedData = qidx === undefined ? {} : segmented[qidx] || {}; - return ridx === undefined ? [] : segmentedData[ridx + ringOffset] || []; + const quadrantIndex = quadrant.index; + const ringIndex = ring.index; + const segmentedData = + quadrantIndex === undefined ? {} : segmented[quadrantIndex] || {}; + return ringIndex === undefined + ? [] + : segmentedData[ringIndex + ringOffset] || []; }; const renderRing = ( @@ -116,14 +119,14 @@ const RadarLegend = (props: Props): JSX.Element => { return (

{ring.name}

- {entries.length === 0 ? ( + {!entries.length ? (

(empty)

) : (
    {entries.map(entry => (
  1. onEntryMouseEnter(entry)) } @@ -178,24 +181,24 @@ const RadarLegend = (props: Props): JSX.Element => { const segments: Segments = {}; for (const entry of entries) { - const qidx = entry.quadrant.idx; - const ridx = entry.ring.idx; + const quadrantIndex = entry.quadrant.index; + const ringIndex = entry.ring.index; let quadrantData: { [k: number]: Entry[] } = {}; - if (qidx !== undefined) { - if (segments[qidx] === undefined) { - segments[qidx] = {}; + if (quadrantIndex !== undefined) { + if (segments[quadrantIndex] === undefined) { + segments[quadrantIndex] = {}; } - quadrantData = segments[qidx]; + quadrantData = segments[quadrantIndex]; } let ringData = []; - if (ridx !== undefined) { - if (quadrantData[ridx] === undefined) { - quadrantData[ridx] = []; + if (ringIndex !== undefined) { + if (quadrantData[ringIndex] === undefined) { + quadrantData[ringIndex] = []; } - ringData = quadrantData[ridx]; + ringData = quadrantData[ringIndex]; } ringData.push(entry); diff --git a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx index 0315afde5f..7fd46b95b7 100644 --- a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx +++ b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx @@ -71,7 +71,7 @@ const RadarPlot = (props: Props): JSX.Element => { x={entry.x || 0} y={entry.y || 0} color={entry.color || ''} - value={((entry && entry.idx) || 0) + 1} + value={((entry && entry.index) || 0) + 1} url={entry.url} moved={entry.moved} onMouseEnter={onEntryMouseEnter && (() => onEntryMouseEnter(entry))} diff --git a/plugins/tech-radar/src/utils/types.ts b/plugins/tech-radar/src/utils/types.ts index 14a0554a1f..51f3cee7f8 100644 --- a/plugins/tech-radar/src/utils/types.ts +++ b/plugins/tech-radar/src/utils/types.ts @@ -17,7 +17,7 @@ // Parameters for a ring; its index in an array determines how close to the center this ring is. export type Ring = { id: string; - idx?: number; + index?: number; name: string; color: string; outerRadius?: number; @@ -27,7 +27,7 @@ export type Ring = { // Parameters for a quadrant (there should be exactly 4 of course) export type Quadrant = { id: string; - idx?: number; + index?: number; name: string; legendX?: number; legendY?: number; @@ -47,7 +47,7 @@ export type Segment = { export type Entry = { id: string; - idx?: number; + index?: number; x?: number; y?: number; color?: string; From 4cae750c72875087988e0818f8d830130eaf61d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20DOREAU?= Date: Fri, 19 Jun 2020 22:46:23 +0200 Subject: [PATCH 04/19] refactor(tech-radar): update moved prop type --- plugins/tech-radar/src/api.ts | 2 +- plugins/tech-radar/src/components/Radar/Radar.test.tsx | 4 ++-- plugins/tech-radar/src/components/Radar/Radar.tsx | 2 +- .../src/components/RadarBubble/RadarBubble.test.tsx | 4 ++-- plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx | 2 +- .../tech-radar/src/components/RadarEntry/RadarEntry.test.tsx | 4 ++-- plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx | 2 +- .../src/components/RadarFooter/RadarFooter.test.tsx | 4 ++-- plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx | 2 +- .../tech-radar/src/components/RadarGrid/RadarGrid.test.tsx | 4 ++-- plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx | 2 +- .../src/components/RadarLegend/RadarLegend.test.tsx | 4 ++-- plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx | 2 +- .../tech-radar/src/components/RadarPlot/RadarPlot.test.tsx | 4 ++-- plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx | 2 +- plugins/tech-radar/src/utils/types.ts | 2 +- 16 files changed, 23 insertions(+), 23 deletions(-) diff --git a/plugins/tech-radar/src/api.ts b/plugins/tech-radar/src/api.ts index 3dec964ddb..d7f5b57a43 100644 --- a/plugins/tech-radar/src/api.ts +++ b/plugins/tech-radar/src/api.ts @@ -34,7 +34,7 @@ export interface RadarQuadrant { export interface RadarEntry { key: string; // react key id: string; - moved: number; + moved: -1 | 0 | 1; quadrant: RadarQuadrant; ring: RadarRing; title: string; diff --git a/plugins/tech-radar/src/components/Radar/Radar.test.tsx b/plugins/tech-radar/src/components/Radar/Radar.test.tsx index 469f0d85cf..f0ad0d66ed 100644 --- a/plugins/tech-radar/src/components/Radar/Radar.test.tsx +++ b/plugins/tech-radar/src/components/Radar/Radar.test.tsx @@ -20,9 +20,9 @@ import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; -import Radar from './Radar'; +import Radar, { Props } from './Radar'; -const minProps = { +const minProps: Props = { width: 500, height: 200, quadrants: [{ id: 'languages', name: 'Languages' }], diff --git a/plugins/tech-radar/src/components/Radar/Radar.tsx b/plugins/tech-radar/src/components/Radar/Radar.tsx index 8fe3c15fba..2c7838e803 100644 --- a/plugins/tech-radar/src/components/Radar/Radar.tsx +++ b/plugins/tech-radar/src/components/Radar/Radar.tsx @@ -19,7 +19,7 @@ import RadarPlot from '../RadarPlot'; import { Ring, Quadrant, Entry } from '../../utils/types'; import { adjustQuadrants, adjustRings, adjustEntries } from './utils'; -type Props = { +export type Props = { width: number; height: number; quadrants: Quadrant[]; diff --git a/plugins/tech-radar/src/components/RadarBubble/RadarBubble.test.tsx b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.test.tsx index 688fd55f9f..9e402b9a91 100644 --- a/plugins/tech-radar/src/components/RadarBubble/RadarBubble.test.tsx +++ b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.test.tsx @@ -20,9 +20,9 @@ import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; -import RadarBubble from './RadarBubble'; +import RadarBubble, { Props } from './RadarBubble'; -const minProps = { +const minProps: Props = { visible: true, text: 'RadarBubble', x: 2, diff --git a/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx index 723a408885..be38231087 100644 --- a/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx +++ b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx @@ -17,7 +17,7 @@ import React, { useRef, useLayoutEffect } from 'react'; import { makeStyles, Theme } from '@material-ui/core'; -type Props = { +export type Props = { visible: boolean; text: string; x: number; diff --git a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx index 20bc6fa7be..14614e88e1 100644 --- a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx +++ b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx @@ -20,9 +20,9 @@ import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; -import RadarEntry from './RadarEntry'; +import RadarEntry, { Props } from './RadarEntry'; -const minProps = { +const minProps: Props = { x: 2, y: 2, value: 2, diff --git a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx index ff499f4575..98841dce87 100644 --- a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx +++ b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { makeStyles, Theme } from '@material-ui/core'; import { WithLink } from '../../utils/components'; -type Props = { +export type Props = { x: number; y: number; value: number; diff --git a/plugins/tech-radar/src/components/RadarFooter/RadarFooter.test.tsx b/plugins/tech-radar/src/components/RadarFooter/RadarFooter.test.tsx index 3e0ee7e407..321947a24c 100644 --- a/plugins/tech-radar/src/components/RadarFooter/RadarFooter.test.tsx +++ b/plugins/tech-radar/src/components/RadarFooter/RadarFooter.test.tsx @@ -20,9 +20,9 @@ import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; -import RadarFooter from './RadarFooter'; +import RadarFooter, { Props } from './RadarFooter'; -const minProps = { +const minProps: Props = { x: 2, y: 2, }; diff --git a/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx b/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx index b6284644eb..9ba0ed0405 100644 --- a/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx +++ b/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { makeStyles, Theme } from '@material-ui/core'; -type Props = { +export type Props = { x: number; y: number; }; diff --git a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.test.tsx b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.test.tsx index 8a2873564d..9ba170a0e7 100644 --- a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.test.tsx +++ b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.test.tsx @@ -20,9 +20,9 @@ import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; -import RadarGrid from './RadarGrid'; +import RadarGrid, { Props } from './RadarGrid'; -const minProps = { +const minProps: Props = { radius: 5, rings: [{ id: 'use', name: 'USE', color: '#93c47d' }], }; diff --git a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx index 68c84e2443..cd06bf6ef8 100644 --- a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx +++ b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { makeStyles, Theme } from '@material-ui/core'; import { Ring } from '../../utils/types'; -type Props = { +export type Props = { radius: number; rings: Ring[]; }; diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx index 8168688fdd..5900f5c1bc 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx @@ -20,9 +20,9 @@ import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; -import RadarLegend from './RadarLegend'; +import RadarLegend, { Props } from './RadarLegend'; -const minProps = { +const minProps: Props = { quadrants: [{ id: 'languages', name: 'Languages' }], rings: [{ id: 'use', name: 'USE', color: '#93c47d' }], entries: [ diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx index ad204f4beb..59bd2e2337 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx @@ -22,7 +22,7 @@ type Segments = { [k: number]: { [k: number]: Entry[] }; }; -type Props = { +export type Props = { quadrants: Quadrant[]; rings: Ring[]; entries: Entry[]; diff --git a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.test.tsx b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.test.tsx index 31f450baeb..876d3009e4 100644 --- a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.test.tsx +++ b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.test.tsx @@ -20,9 +20,9 @@ import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; -import RadarPlot from './RadarPlot'; +import RadarPlot, { Props } from './RadarPlot'; -const minProps = { +const minProps: Props = { width: 500, height: 200, radius: 50, diff --git a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx index 7fd46b95b7..c1ce70eecc 100644 --- a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx +++ b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx @@ -23,7 +23,7 @@ import RadarBubble from '../RadarBubble'; import RadarFooter from '../RadarFooter'; import RadarLegend from '../RadarLegend'; -type Props = { +export type Props = { width: number; height: number; radius: number; diff --git a/plugins/tech-radar/src/utils/types.ts b/plugins/tech-radar/src/utils/types.ts index 51f3cee7f8..5ee27c29c7 100644 --- a/plugins/tech-radar/src/utils/types.ts +++ b/plugins/tech-radar/src/utils/types.ts @@ -61,7 +61,7 @@ export type Entry = { // An URL to a longer description as to why this entry is where it is url?: string; // How this entry has recently moved; -1 for "down", +1 for "up", 0 for not moved - moved?: number; + moved?: -1 | 0 | 1; active?: boolean; }; From 984f777b677157b37603f226d8b081b55c4a240e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20DOREAU?= Date: Sat, 20 Jun 2020 17:45:12 +0200 Subject: [PATCH 05/19] refactor(tech-radar): update test + RadarLegend refacto --- .../RadarBubble/RadarBubble.test.tsx | 2 +- .../components/RadarBubble/RadarBubble.tsx | 1 + .../components/RadarEntry/RadarEntry.test.tsx | 6 +- .../src/components/RadarEntry/RadarEntry.tsx | 1 + .../RadarFooter/RadarFooter.test.tsx | 6 +- .../components/RadarFooter/RadarFooter.tsx | 6 +- .../components/RadarGrid/RadarGrid.test.tsx | 3 +- .../src/components/RadarGrid/RadarGrid.tsx | 2 + .../RadarLegend/RadarLegend.test.tsx | 4 +- .../components/RadarLegend/RadarLegend.tsx | 84 +++++++++++-------- .../components/RadarPlot/RadarPlot.test.tsx | 6 +- .../src/components/RadarPlot/RadarPlot.tsx | 2 +- 12 files changed, 81 insertions(+), 42 deletions(-) diff --git a/plugins/tech-radar/src/components/RadarBubble/RadarBubble.test.tsx b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.test.tsx index 9e402b9a91..d56ca743c2 100644 --- a/plugins/tech-radar/src/components/RadarBubble/RadarBubble.test.tsx +++ b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.test.tsx @@ -47,6 +47,6 @@ describe('RadarBubble', () => { , ); - expect(rendered).not.toBeNull(); + expect(rendered.getByText(minProps.text)).toBeInTheDocument(); }); }); diff --git a/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx index be38231087..abaed92253 100644 --- a/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx +++ b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.tsx @@ -98,6 +98,7 @@ const RadarBubble = (props: Props): JSX.Element => { x={0} y={0} className={visible ? classes.visibleBubble : classes.bubble} + data-testid="radar-bubble" > diff --git a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx index 14614e88e1..2d24f301d1 100644 --- a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx +++ b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx @@ -47,6 +47,10 @@ describe('RadarEntry', () => { , ); - expect(rendered).not.toBeNull(); + const radarEntry = rendered.getByTestId('radar-entry'); + const { x, y } = minProps; + expect(radarEntry).toBeInTheDocument(); + expect(radarEntry.getAttribute('transform')).toBe(`translate(${x}, ${y})`); + expect(rendered.getByText(String(minProps.value))).toBeInTheDocument(); }); }); diff --git a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx index 98841dce87..29d35e01e2 100644 --- a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx +++ b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx @@ -80,6 +80,7 @@ const RadarEntry = (props: Props): JSX.Element => { onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} onClick={onClick} + data-testid="radar-entry" > {blip} diff --git a/plugins/tech-radar/src/components/RadarFooter/RadarFooter.test.tsx b/plugins/tech-radar/src/components/RadarFooter/RadarFooter.test.tsx index 321947a24c..77f21b6e63 100644 --- a/plugins/tech-radar/src/components/RadarFooter/RadarFooter.test.tsx +++ b/plugins/tech-radar/src/components/RadarFooter/RadarFooter.test.tsx @@ -44,7 +44,9 @@ describe('RadarFooter', () => { , ); - - expect(rendered).not.toBeNull(); + const radarFooter = rendered.getByTestId('radar-footer'); + const { x, y } = minProps; + expect(radarFooter).toBeInTheDocument(); + expect(radarFooter.getAttribute('transform')).toBe(`translate(${x}, ${y})`); }); }); diff --git a/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx b/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx index 9ba0ed0405..ced1ab8054 100644 --- a/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx +++ b/plugins/tech-radar/src/components/RadarFooter/RadarFooter.tsx @@ -36,7 +36,11 @@ const RadarFooter = (props: Props): JSX.Element => { const classes = useStyles(props); return ( - + {'▲ moved up\u00a0\u00a0\u00a0\u00a0\u00a0▼ moved down'} ); diff --git a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.test.tsx b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.test.tsx index 9ba170a0e7..3027cc4adb 100644 --- a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.test.tsx +++ b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.test.tsx @@ -45,6 +45,7 @@ describe('RadarGrid', () => { , ); - expect(rendered).not.toBeNull(); + expect(rendered.getByTestId('radar-grid-x-line')).toBeInTheDocument(); + expect(rendered.getByTestId('radar-grid-y-line')).toBeInTheDocument(); }); }); diff --git a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx index cd06bf6ef8..63c164bc7d 100644 --- a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx +++ b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx @@ -76,6 +76,7 @@ const RadarGrid = (props: Props) => { x2={0} y2={radius} className={classes.axis} + data-testid="radar-grid-x-line" />, // Y axis { x2={radius} y2={0} className={classes.axis} + data-testid="radar-grid-y-line" />, ]; diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx index 5900f5c1bc..68bfe892ea 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx @@ -55,6 +55,8 @@ describe('RadarLegend', () => { , ); - expect(rendered).not.toBeNull(); + expect(rendered.getByTestId('radar-legend')).toBeInTheDocument(); + expect(rendered.getAllByTestId('radar-quadrant')).toHaveLength(1); + expect(rendered.getAllByTestId('radar-ring')).toHaveLength(1); }); }); diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx index 59bd2e2337..57a453205a 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; import { makeStyles, Theme } from '@material-ui/core'; -import { Quadrant, Ring, Entry } from '../../utils/types'; +import type { Quadrant, Ring, Entry } from '../../utils/types'; import { WithLink } from '../../utils/components'; type Segments = { @@ -110,14 +110,21 @@ const RadarLegend = (props: Props): JSX.Element => { : segmentedData[ringIndex + ringOffset] || []; }; - const renderRing = ( - ring: Ring, - entries: Entry[], - onEntryMouseEnter?: Props['onEntryMouseEnter'], - onEntryMouseLeave?: Props['onEntryMouseEnter'], - ) => { + type RadarLegendRingProps = { + ring: Ring; + entries: Entry[]; + onEntryMouseEnter?: Props['onEntryMouseEnter']; + onEntryMouseLeave?: Props['onEntryMouseEnter']; + }; + + const RadarLegendRing = ({ + ring, + entries, + onEntryMouseEnter, + onEntryMouseLeave, + }: RadarLegendRingProps) => { return ( -
    +

    {ring.name}

    {!entries.length ? (

    (empty)

    @@ -145,13 +152,21 @@ const RadarLegend = (props: Props): JSX.Element => { ); }; - const renderQuadrant = ( - segments: Segments, - quadrant: Quadrant, - rings: Ring[], - onEntryMouseEnter: Props['onEntryMouseEnter'], - onEntryMouseLeave: Props['onEntryMouseLeave'], - ) => { + type RadarLegendQuadrantProps = { + segments: Segments; + quadrant: Quadrant; + rings: Ring[]; + onEntryMouseEnter: Props['onEntryMouseEnter']; + onEntryMouseLeave: Props['onEntryMouseLeave']; + }; + + const RadarLegendQuadrant = ({ + segments, + quadrant, + rings, + onEntryMouseEnter, + onEntryMouseLeave, + }: RadarLegendQuadrantProps) => { return ( { y={quadrant.legendY} width={quadrant.legendWidth} height={quadrant.legendHeight} + data-testid="radar-quadrant" >

    {quadrant.name}

    - {rings.map(ring => - renderRing( - ring, - getSegment(segments, quadrant, ring), - onEntryMouseEnter, - onEntryMouseLeave, - ), - )} + {rings.map(ring => ( + + ))}
    @@ -218,16 +235,17 @@ const RadarLegend = (props: Props): JSX.Element => { const segments: Segments = setupSegments(entries); return ( - - {quadrants.map(quadrant => - renderQuadrant( - segments, - quadrant, - rings, - onEntryMouseEnter, - onEntryMouseLeave, - ), - )} + + {quadrants.map(quadrant => ( + + ))} ); }; diff --git a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.test.tsx b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.test.tsx index 876d3009e4..5b17578478 100644 --- a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.test.tsx +++ b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.test.tsx @@ -58,6 +58,10 @@ describe('RadarPlot', () => { , ); - expect(rendered).not.toBeNull(); + expect(rendered.getByTestId('radar-plot')).toBeInTheDocument(); + expect(rendered.getByTestId('radar-legend')).toBeInTheDocument(); + expect(rendered.getByTestId('radar-footer')).toBeInTheDocument(); + expect(rendered.getByTestId('radar-bubble')).toBeInTheDocument(); + expect(rendered.getAllByTestId('radar-entry')).toHaveLength(1); }); }); diff --git a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx index c1ce70eecc..f1b41373ca 100644 --- a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx +++ b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx @@ -50,7 +50,7 @@ const RadarPlot = (props: Props): JSX.Element => { } = props; return ( - + Date: Sat, 20 Jun 2020 18:41:12 +0200 Subject: [PATCH 06/19] fix(tech-radar): moved @types/react to devDependencies --- plugins/tech-radar/package.json | 2 +- plugins/tech-radar/src/components/Radar/Radar.tsx | 2 +- plugins/tech-radar/src/components/Radar/utils.ts | 2 +- plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx | 2 +- plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 16a36dba5d..ee9ce910ec 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -27,7 +27,6 @@ "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", - "@types/react": "^16.9", "color": "^3.1.2", "d3-force": "^2.0.1", "prop-types": "^15.7.2", @@ -43,6 +42,7 @@ "@testing-library/user-event": "^10.2.4", "@types/color": "^3.0.1", "@types/d3-force": "^1.2.1", + "@types/react": "^16.9", "@types/jest": "^25.2.2", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", diff --git a/plugins/tech-radar/src/components/Radar/Radar.tsx b/plugins/tech-radar/src/components/Radar/Radar.tsx index 2c7838e803..124e8bb68a 100644 --- a/plugins/tech-radar/src/components/Radar/Radar.tsx +++ b/plugins/tech-radar/src/components/Radar/Radar.tsx @@ -16,7 +16,7 @@ import React, { useState, useRef } from 'react'; import RadarPlot from '../RadarPlot'; -import { Ring, Quadrant, Entry } from '../../utils/types'; +import type { Ring, Quadrant, Entry } from '../../utils/types'; import { adjustQuadrants, adjustRings, adjustEntries } from './utils'; export type Props = { diff --git a/plugins/tech-radar/src/components/Radar/utils.ts b/plugins/tech-radar/src/components/Radar/utils.ts index 25d9e7dac8..d7a826543c 100644 --- a/plugins/tech-radar/src/components/Radar/utils.ts +++ b/plugins/tech-radar/src/components/Radar/utils.ts @@ -17,7 +17,7 @@ import color from 'color'; import { forceCollide, forceSimulation } from 'd3-force'; import Segment from '../../utils/segment'; -import { Ring, Quadrant, Entry } from '../../utils/types'; +import type { Ring, Quadrant, Entry } from '../../utils/types'; export const adjustQuadrants = ( quadrants: Quadrant[], diff --git a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx index 63c164bc7d..eae85d4e64 100644 --- a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx +++ b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { makeStyles, Theme } from '@material-ui/core'; -import { Ring } from '../../utils/types'; +import type { Ring } from '../../utils/types'; export type Props = { radius: number; diff --git a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx index f1b41373ca..1e28cbaf02 100644 --- a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx +++ b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { Quadrant, Ring, Entry } from '../../utils/types'; +import type { Quadrant, Ring, Entry } from '../../utils/types'; import RadarGrid from '../RadarGrid'; import RadarEntry from '../RadarEntry'; From fac4893555ec2f4bccb15a1beffd7b8fc5e27853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20DOREAU?= Date: Sat, 20 Jun 2020 18:52:15 +0200 Subject: [PATCH 07/19] fix(tech-radar): missing state setters --- plugins/tech-radar/src/components/RadarComponent.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/tech-radar/src/components/RadarComponent.tsx b/plugins/tech-radar/src/components/RadarComponent.tsx index 8d56c6aa0f..86ef10e07d 100644 --- a/plugins/tech-radar/src/components/RadarComponent.tsx +++ b/plugins/tech-radar/src/components/RadarComponent.tsx @@ -37,11 +37,13 @@ const useTechRadarLoader = (props: TechRadarComponentProps) => { .then((payload: TechRadarLoaderResponse) => { setLoading(false); setData(payload); + setError(undefined); }) .catch((err: Error) => { errorApi.post(err); setLoading(false); setError(err); + setData(undefined); }); }, [getData, errorApi]); From 542d209a6bf20be666f562abe01b4c5adefff9b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20DOREAU?= Date: Sat, 20 Jun 2020 23:45:25 +0200 Subject: [PATCH 08/19] refactor(tech-radar): requested changes --- .../src/components/RadarComponent.tsx | 38 +++++++--------- .../components/RadarLegend/RadarLegend.tsx | 44 +++++++------------ .../src/components/RadarPlot/RadarPlot.tsx | 8 ++-- 3 files changed, 36 insertions(+), 54 deletions(-) diff --git a/plugins/tech-radar/src/components/RadarComponent.tsx b/plugins/tech-radar/src/components/RadarComponent.tsx index 86ef10e07d..99190202b9 100644 --- a/plugins/tech-radar/src/components/RadarComponent.tsx +++ b/plugins/tech-radar/src/components/RadarComponent.tsx @@ -14,44 +14,38 @@ * limitations under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { Progress, useApi, errorApiRef, ErrorApi } from '@backstage/core'; +import { useAsync } from 'react-use'; import Radar from '../components/Radar'; import { TechRadarComponentProps, TechRadarLoaderResponse } from '../api'; import getSampleData from '../sampleData'; const useTechRadarLoader = (props: TechRadarComponentProps) => { const errorApi = useApi(errorApiRef); - const [error, setError] = useState(); - const [loading, setLoading] = useState(true); - const [data, setData] = useState(); const { getData } = props; - useEffect(() => { - if (!getData) { - return; + const state = useAsync(async () => { + if (getData) { + const response: TechRadarLoaderResponse = await getData(); + return response; } - - getData() - .then((payload: TechRadarLoaderResponse) => { - setLoading(false); - setData(payload); - setError(undefined); - }) - .catch((err: Error) => { - errorApi.post(err); - setLoading(false); - setError(err); - setData(undefined); - }); + return undefined; }, [getData, errorApi]); - return { data, loading, error }; + useEffect(() => { + const { error } = state; + if (error) { + errorApi.post(error); + } + }, [errorApi, state]); + + return state; }; const RadarComponent = (props: TechRadarComponentProps): JSX.Element => { - const { loading, error, data } = useTechRadarLoader(props); + const { loading, error, value: data } = useTechRadarLoader(props); return ( <> diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx index 57a453205a..e319846d78 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx @@ -30,7 +30,20 @@ export type Props = { onEntryMouseLeave?: (entry: Entry) => void; }; -const ringStyles = { +const useStyles = makeStyles(theme => ({ + quadrant: { + height: '100%', + width: '100%', + overflow: 'hidden', + pointerEvents: 'none', + }, + quadrantHeading: { + pointerEvents: 'none', + userSelect: 'none', + marginTop: 0, + marginBottom: theme.spacing(8 / (18 * 0.375)), + fontSize: '18px', + }, rings: { columns: 3, }, @@ -44,7 +57,7 @@ const ringStyles = { pointerEvents: 'none', userSelect: 'none', marginTop: 0, - marginBottom: 'calc(12px * 0.375)', + marginBottom: theme.spacing(8 / (12 * 0.375)), fontSize: '12px', fontWeight: 800, }, @@ -57,25 +70,6 @@ const ringStyles = { '-webkit-font-feature-settings': 'pnum', 'font-feature-settings': 'pnum', }, -}; - -const quadrantStyles = { - quadrant: { - height: '100%', - width: '100%', - overflow: 'hidden', - pointerEvents: 'none', - }, - quadrantHeading: { - pointerEvents: 'none', - userSelect: 'none', - marginTop: 0, - marginBottom: 'calc(18px * 0.375)', - fontSize: '18px', - }, -}; - -const entryStyle = { entry: { pointerEvents: 'none', userSelect: 'none', @@ -84,12 +78,6 @@ const entryStyle = { entryLink: { pointerEvents: 'none', }, -}; - -const useStyles = makeStyles(() => ({ - ringStyles, - quadrantStyles, - entryStyle, })); const RadarLegend = (props: Props): JSX.Element => { @@ -126,7 +114,7 @@ const RadarLegend = (props: Props): JSX.Element => { return (

    {ring.name}

    - {!entries.length ? ( + {entries.length === 0 ? (

    (empty)

    ) : (
      diff --git a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx index 1e28cbaf02..7f68ffe021 100644 --- a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx +++ b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx @@ -71,7 +71,7 @@ const RadarPlot = (props: Props): JSX.Element => { x={entry.x || 0} y={entry.y || 0} color={entry.color || ''} - value={((entry && entry.index) || 0) + 1} + value={(entry?.index || 0) + 1} url={entry.url} moved={entry.moved} onMouseEnter={onEntryMouseEnter && (() => onEntryMouseEnter(entry))} @@ -80,9 +80,9 @@ const RadarPlot = (props: Props): JSX.Element => { ))} From 1baa5309a17111633aea2e3e7f09a29df4b9ad5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20DOREAU?= Date: Wed, 24 Jun 2020 14:03:03 +0200 Subject: [PATCH 09/19] refactor(tech-radar): add moved type --- plugins/tech-radar/src/api.ts | 3 ++- plugins/tech-radar/src/utils/types.ts | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/tech-radar/src/api.ts b/plugins/tech-radar/src/api.ts index d7f5b57a43..856cf4ff3c 100644 --- a/plugins/tech-radar/src/api.ts +++ b/plugins/tech-radar/src/api.ts @@ -15,6 +15,7 @@ */ import { createApiRef } from '@backstage/core'; +import { MovedState } from './utils/types'; /** * Types related to the Radar's visualization. @@ -34,7 +35,7 @@ export interface RadarQuadrant { export interface RadarEntry { key: string; // react key id: string; - moved: -1 | 0 | 1; + moved: MovedState; quadrant: RadarQuadrant; ring: RadarRing; title: string; diff --git a/plugins/tech-radar/src/utils/types.ts b/plugins/tech-radar/src/utils/types.ts index 5ee27c29c7..f3933ee584 100644 --- a/plugins/tech-radar/src/utils/types.ts +++ b/plugins/tech-radar/src/utils/types.ts @@ -45,6 +45,12 @@ export type Segment = { random: Function; }; +export enum MovedState { + Down = -1, + NoChange = 0, + Up = 1, +} + export type Entry = { id: string; index?: number; @@ -61,7 +67,7 @@ export type Entry = { // An URL to a longer description as to why this entry is where it is url?: string; // How this entry has recently moved; -1 for "down", +1 for "up", 0 for not moved - moved?: -1 | 0 | 1; + moved?: MovedState; active?: boolean; }; From ec0230a6c896662e4e2580840eb95336da659831 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 29 Jun 2020 11:03:57 +0200 Subject: [PATCH 10/19] feat(techdocs): techdocs landing page --- packages/app/src/components/Root/Root.tsx | 2 + packages/core/src/layout/index.ts | 1 + plugins/techdocs/src/plugin.ts | 6 +++ .../techdocs/src/reader/components/Reader.tsx | 41 ++++++++++++++++--- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 4b92f8281e..a86b92ab43 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -22,6 +22,7 @@ import ExploreIcon from '@material-ui/icons/Explore'; import BuildIcon from '@material-ui/icons/BuildRounded'; import RuleIcon from '@material-ui/icons/AssignmentTurnedIn'; import MapIcon from '@material-ui/icons/MyLocation'; +import LibraryBooks from '@material-ui/icons/LibraryBooks'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; @@ -89,6 +90,7 @@ const Root: FC<{}> = ({ children }) => ( {/* Global nav, not org-specific */} + {/* End global nav */} diff --git a/packages/core/src/layout/index.ts b/packages/core/src/layout/index.ts index e2de159ec6..e8245119f3 100644 --- a/packages/core/src/layout/index.ts +++ b/packages/core/src/layout/index.ts @@ -26,3 +26,4 @@ export * from './Sidebar'; export * from './SignInPage'; export * from './TabbedCard'; export * from './HeaderTabs'; +export * from './ItemCard'; diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts index fec2b1cf74..89a5ddeb73 100644 --- a/plugins/techdocs/src/plugin.ts +++ b/plugins/techdocs/src/plugin.ts @@ -33,6 +33,11 @@ import { createPlugin, createRouteRef } from '@backstage/core'; import { Reader } from './reader/components/Reader'; export const rootRouteRef = createRouteRef({ + path: '/docs', + title: 'TechDocs Landing Page', +}); + +export const rootDocsRouteRef = createRouteRef({ path: '/docs/:componentId/*', title: 'Docs', }); @@ -41,5 +46,6 @@ export const plugin = createPlugin({ id: 'techdocs', register({ router }) { router.addRoute(rootRouteRef, Reader); + router.addRoute(rootDocsRouteRef, Reader); }, }); diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 0175462cc3..821d3f650a 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -23,7 +23,8 @@ import transformer, { addEventListener, } from '../transformers'; import { docStorageURL } from '../../config'; -import { Link } from '@backstage/core'; +import { Grid } from '@material-ui/core'; +import { Link, InfoCard, Header, Content, ItemCard } from '@backstage/core'; import { useLocation, useParams, useNavigate } from 'react-router-dom'; import URLParser from '../urlParser'; @@ -89,11 +90,39 @@ export const Reader = () => { return ( <> - -
      + {componentId ? ( +
      + ) : ( + <> +
      + + + + + navigate('/docs/mkdocs')} + tags={['Developer Tool']} + title="MkDocs" + label="Read Docs" + description="MkDocs is a fast, simple and downright gorgeous static site generator that's geared towards building project documentation. " + /> + + + navigate('/docs/backstage-microsite')} + tags={['Service']} + title="Backstage" + label="Read Docs" + description="Getting started guides, API Overview, documentation around how to Create a Plugin and more. " + /> + + + + + )} ); }; From 5066a79143e5a354ae08fabdba7024473bb304c6 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 29 Jun 2020 11:04:27 +0200 Subject: [PATCH 11/19] fix(docs): update readmeg --- plugins/techdocs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/techdocs/README.md b/plugins/techdocs/README.md index 10ddcf9411..36d00fc970 100644 --- a/plugins/techdocs/README.md +++ b/plugins/techdocs/README.md @@ -10,7 +10,7 @@ Welcome to the TechDocs plugin - Spotify's docs-like-code approach built directl ## Getting started -Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/techdocs](http://localhost:3000/techdocs). +Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/docs](http://localhost:3000/docs). You can also serve the plugin in isolation by running `yarn start` in the plugin directory. This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. From 2454dcbd79de7f04caee6624043ab6ae573c83d8 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 29 Jun 2020 11:05:04 +0200 Subject: [PATCH 12/19] feat(ItemCard): reusable card component --- .../src/layout/ItemCard/InfoCard.stories.tsx | 35 ++++++++ .../core/src/layout/ItemCard/ItemCard.tsx | 79 +++++++++++++++++++ packages/core/src/layout/ItemCard/index.ts | 17 ++++ 3 files changed, 131 insertions(+) create mode 100644 packages/core/src/layout/ItemCard/InfoCard.stories.tsx create mode 100644 packages/core/src/layout/ItemCard/ItemCard.tsx create mode 100644 packages/core/src/layout/ItemCard/index.ts diff --git a/packages/core/src/layout/ItemCard/InfoCard.stories.tsx b/packages/core/src/layout/ItemCard/InfoCard.stories.tsx new file mode 100644 index 0000000000..87c5b0684e --- /dev/null +++ b/packages/core/src/layout/ItemCard/InfoCard.stories.tsx @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Spotify AB + * + * 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, { FC } from 'react'; +import { ItemCard } from '.'; +import { Grid } from '@material-ui/core'; + +export default { + title: 'Item Card', + component: ItemCard, +}; + +const Wrapper: FC<{}> = ({ children }) => ( + + {children} + +); + +export const Default = () => ( + + + +); diff --git a/packages/core/src/layout/ItemCard/ItemCard.tsx b/packages/core/src/layout/ItemCard/ItemCard.tsx new file mode 100644 index 0000000000..86ad53e8dd --- /dev/null +++ b/packages/core/src/layout/ItemCard/ItemCard.tsx @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Spotify AB + * + * 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, { FC } from 'react'; +import { Button, Card, Chip, Typography, makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + header: { + color: theme.palette.common.white, + padding: theme.spacing(2, 2, 6), + backgroundImage: + 'linear-gradient(-137deg, rgb(25, 230, 140) 0%, rgb(29, 127, 110) 100%)', + }, + content: { + padding: theme.spacing(2), + }, + description: { + height: 175, + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + footer: { + display: 'flex', + flexDirection: 'row-reverse', + }, +})); + +type ItemCardProps = { + description: string; + tags?: string[]; + title: string; + type?: string; + label: string; + onClick?: () => void; +}; +export const ItemCard: FC = ({ + description, + tags, + title, + type, + label, + onClick, +}) => { + const classes = useStyles(); + + return ( + +
      + {type ?? {type}} + {title} +
      +
      + {tags?.map(tag => ( + + ))} + + {description} + +
      + +
      +
      +
      + ); +}; diff --git a/packages/core/src/layout/ItemCard/index.ts b/packages/core/src/layout/ItemCard/index.ts new file mode 100644 index 0000000000..b38dd2acc2 --- /dev/null +++ b/packages/core/src/layout/ItemCard/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +export { ItemCard } from './ItemCard'; From 695b1971ad8109802407bd976d040e0be363854f Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Mon, 29 Jun 2020 12:17:07 +0200 Subject: [PATCH 13/19] Fixed lint issue --- plugins/techdocs/src/reader/components/Reader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 821d3f650a..474457ac5c 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -24,7 +24,7 @@ import transformer, { } from '../transformers'; import { docStorageURL } from '../../config'; import { Grid } from '@material-ui/core'; -import { Link, InfoCard, Header, Content, ItemCard } from '@backstage/core'; +import { Header, Content, ItemCard } from '@backstage/core'; import { useLocation, useParams, useNavigate } from 'react-router-dom'; import URLParser from '../urlParser'; From 580569e40350ab7a796a9c323050f83ce2181d71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 29 Jun 2020 14:42:52 +0200 Subject: [PATCH 14/19] yarn.lock again --- yarn.lock | 63 ++++++++++++++++++++++++------------------------------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4ee33d5b1d..8a8fd29dcd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -868,14 +868,6 @@ "@babel/plugin-transform-react-jsx-self" "^7.9.0" "@babel/plugin-transform-react-jsx-source" "^7.9.0" -"@babel/runtime-corejs2@^7.4.4": - version "7.9.2" - resolved "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.9.2.tgz#f11d074ff99b9b4319b5ecf0501f12202bf2bf4d" - integrity sha512-ayjSOxuK2GaSDJFCtLgHnYjuMyIpViNujWrZo8GUpN60/n7juzJKK5yOo6RFVb0zdU9ACJFK+MsZrUnj3OmXMw== - dependencies: - core-js "^2.6.5" - regenerator-runtime "^0.13.4" - "@babel/runtime-corejs3@^7.10.2": version "7.10.3" resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.3.tgz#931ed6941d3954924a7aa967ee440e60c507b91a" @@ -6544,7 +6536,7 @@ core-js-pure@^3.0.0, core-js-pure@^3.0.1: resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a" integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw== -core-js@^2.4.0, core-js@^2.6.5: +core-js@^2.4.0: version "2.6.11" resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== @@ -6701,10 +6693,10 @@ crypto-random-string@^2.0.0: resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -css-box-model@^1.1.2: - version "1.2.0" - resolved "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.0.tgz#3a26377b4162b3200d2ede4b064ec5b6a75186d0" - integrity sha512-lri0br+jSNV0kkkiGEp9y9y3Njq2PmpqbeGWRFQJuZteZzY9iC9GZhQ8Y4WpPwM/2YocjHePxy14igJY7YKzkA== +css-box-model@^1.2.0: + version "1.2.1" + resolved "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1" + integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw== dependencies: tiny-invariant "^1.0.6" @@ -12427,10 +12419,10 @@ markdown-to-jsx@^6.11.4: prop-types "^15.6.2" unquote "^1.1.0" -material-table@^1.58.0: - version "1.58.2" - resolved "https://registry.npmjs.org/material-table/-/material-table-1.58.2.tgz#dc0d19652848e6bb92f747d122bd7d4681cca6dc" - integrity sha512-s/m6ebyXFXmg07zxv1Fl6qPySKaiQhASXaOB3ubRKUFA1DkryUy3PGSEVWTjUYnRyi63kGq+N6b5nsokLR6m5A== +material-table@1.62.x: + version "1.62.0" + resolved "https://registry.npmjs.org/material-table/-/material-table-1.62.0.tgz#117793ebf16ab0fccbb6f8a670d849a7be0b5995" + integrity sha512-+3tnk32lXtkXeKM7k/hZ82jpSzlXU5CsWXqJHq4Tl0Un7ycjK2Kef6EMPqeE3i58vKNqbIvbrFf/ESH0D/Qwig== dependencies: "@date-io/date-fns" "^1.1.0" "@material-ui/pickers" "^3.2.2" @@ -12440,7 +12432,7 @@ material-table@^1.58.0: fast-deep-equal "2.0.1" filefy "0.1.10" prop-types "^15.6.2" - react-beautiful-dnd "11.0.3" + react-beautiful-dnd "^13.0.0" react-double-scrollbar "0.0.15" md5.js@^1.3.4: @@ -12479,7 +12471,7 @@ media-typer@0.3.0: resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -memoize-one@^5.0.4: +memoize-one@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA== @@ -15128,7 +15120,7 @@ quick-lru@^1.0.0: resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g= -raf-schd@^4.0.0: +raf-schd@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.2.tgz#bd44c708188f2e84c810bf55fcea9231bcaed8a0" integrity sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ== @@ -15211,19 +15203,18 @@ rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-beautiful-dnd@11.0.3: - version "11.0.3" - resolved "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-11.0.3.tgz#5678bb3e725d8b56cb7cf57f56e952105fc4f2af" - integrity sha512-2FX2SnOlKMmfn90xUHCav7cxRWXwY7FeRa6TzdxWeX7DdP5JTvVQcsWgiOkdbJSj+J+1q1nA9QO4/HQ52D0DAA== +react-beautiful-dnd@^13.0.0: + version "13.0.0" + resolved "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#f70cc8ff82b84bc718f8af157c9f95757a6c3b40" + integrity sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg== dependencies: - "@babel/runtime-corejs2" "^7.4.4" - css-box-model "^1.1.2" - memoize-one "^5.0.4" - raf-schd "^4.0.0" - react-redux "^7.0.3" - redux "^4.0.1" - tiny-invariant "^1.0.4" - use-memo-one "^1.1.0" + "@babel/runtime" "^7.8.4" + css-box-model "^1.2.0" + memoize-one "^5.1.1" + raf-schd "^4.0.2" + react-redux "^7.1.1" + redux "^4.0.4" + use-memo-one "^1.1.1" react-clientside-effect@^1.2.2: version "1.2.2" @@ -15473,7 +15464,7 @@ react-popper@^1.3.6: typed-styles "^0.0.7" warning "^4.0.2" -react-redux@^7.0.3: +react-redux@^7.1.1: version "7.2.0" resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d" integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA== @@ -15849,7 +15840,7 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -redux@^4.0.1: +redux@^4.0.4: version "4.0.5" resolved "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== @@ -17948,7 +17939,7 @@ tiny-emitter@^2.0.0: resolved "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== -tiny-invariant@^1.0.2, tiny-invariant@^1.0.4, tiny-invariant@^1.0.6: +tiny-invariant@^1.0.2, tiny-invariant@^1.0.6: version "1.1.0" resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== @@ -18587,7 +18578,7 @@ use-callback-ref@^1.2.1: resolved "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.1.tgz#898759ccb9e14be6c7a860abafa3ffbd826c89bb" integrity sha512-C3nvxh0ZpaOxs9RCnWwAJ+7bJPwQI8LHF71LzbQ3BvzH5XkdtlkMadqElGevg5bYBDFip4sAnD4m06zAKebg1w== -use-memo-one@^1.1.0: +use-memo-one@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c" integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ== From fc7b0a0aa55d7a2acadbceeadf4d1243a13e6087 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 29 Jun 2020 15:23:09 +0200 Subject: [PATCH 15/19] fix(techdocs): move card component into techdocs plugin from backstage core --- .../src/layout/ItemCard/InfoCard.stories.tsx | 35 ------------------- packages/core/src/layout/ItemCard/index.ts | 17 --------- packages/core/src/layout/index.ts | 1 - .../src/reader/components/DocsCard.tsx | 4 +-- .../techdocs/src/reader/components/Reader.tsx | 13 ++++--- 5 files changed, 10 insertions(+), 60 deletions(-) delete mode 100644 packages/core/src/layout/ItemCard/InfoCard.stories.tsx delete mode 100644 packages/core/src/layout/ItemCard/index.ts rename packages/core/src/layout/ItemCard/ItemCard.tsx => plugins/techdocs/src/reader/components/DocsCard.tsx (96%) diff --git a/packages/core/src/layout/ItemCard/InfoCard.stories.tsx b/packages/core/src/layout/ItemCard/InfoCard.stories.tsx deleted file mode 100644 index 87c5b0684e..0000000000 --- a/packages/core/src/layout/ItemCard/InfoCard.stories.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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, { FC } from 'react'; -import { ItemCard } from '.'; -import { Grid } from '@material-ui/core'; - -export default { - title: 'Item Card', - component: ItemCard, -}; - -const Wrapper: FC<{}> = ({ children }) => ( - - {children} - -); - -export const Default = () => ( - - - -); diff --git a/packages/core/src/layout/ItemCard/index.ts b/packages/core/src/layout/ItemCard/index.ts deleted file mode 100644 index b38dd2acc2..0000000000 --- a/packages/core/src/layout/ItemCard/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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. - */ - -export { ItemCard } from './ItemCard'; diff --git a/packages/core/src/layout/index.ts b/packages/core/src/layout/index.ts index e8245119f3..e2de159ec6 100644 --- a/packages/core/src/layout/index.ts +++ b/packages/core/src/layout/index.ts @@ -26,4 +26,3 @@ export * from './Sidebar'; export * from './SignInPage'; export * from './TabbedCard'; export * from './HeaderTabs'; -export * from './ItemCard'; diff --git a/packages/core/src/layout/ItemCard/ItemCard.tsx b/plugins/techdocs/src/reader/components/DocsCard.tsx similarity index 96% rename from packages/core/src/layout/ItemCard/ItemCard.tsx rename to plugins/techdocs/src/reader/components/DocsCard.tsx index 86ad53e8dd..68f42bf84a 100644 --- a/packages/core/src/layout/ItemCard/ItemCard.tsx +++ b/plugins/techdocs/src/reader/components/DocsCard.tsx @@ -37,7 +37,7 @@ const useStyles = makeStyles(theme => ({ }, })); -type ItemCardProps = { +type DocsCardProps = { description: string; tags?: string[]; title: string; @@ -45,7 +45,7 @@ type ItemCardProps = { label: string; onClick?: () => void; }; -export const ItemCard: FC = ({ +export const DocsCard: FC = ({ description, tags, title, diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 474457ac5c..a4ff03af47 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -17,16 +17,19 @@ import React from 'react'; import { useShadowDom } from '..'; import { useAsync } from 'react-use'; +import { useLocation, useParams, useNavigate } from 'react-router-dom'; + +import { Grid } from '@material-ui/core'; +import { Header, Content } from '@backstage/core'; + import transformer, { addBaseUrl, rewriteDocLinks, addEventListener, } from '../transformers'; import { docStorageURL } from '../../config'; -import { Grid } from '@material-ui/core'; -import { Header, Content, ItemCard } from '@backstage/core'; -import { useLocation, useParams, useNavigate } from 'react-router-dom'; import URLParser from '../urlParser'; +import { DocsCard } from './DocsCard'; const useFetch = (url: string) => { const state = useAsync(async () => { @@ -102,7 +105,7 @@ export const Reader = () => { - navigate('/docs/mkdocs')} tags={['Developer Tool']} title="MkDocs" @@ -111,7 +114,7 @@ export const Reader = () => { /> - navigate('/docs/backstage-microsite')} tags={['Service']} title="Backstage" From 7ca78c23e78b6b95d72015225e14586807b9070d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 29 Jun 2020 16:16:00 +0200 Subject: [PATCH 16/19] nitpick: newlines between mock entity outputs --- plugins/catalog-backend/scripts/mock-data | 1 + plugins/scaffolder-backend/scripts/mock-data | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/catalog-backend/scripts/mock-data b/plugins/catalog-backend/scripts/mock-data index 01675db363..76f97ccf3a 100755 --- a/plugins/catalog-backend/scripts/mock-data +++ b/plugins/catalog-backend/scripts/mock-data @@ -15,4 +15,5 @@ for URL in \ --request POST 'localhost:7000/catalog/locations' \ --header 'Content-Type: application/json' \ --data-raw "{\"type\": \"github\", \"target\": \"https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/${URL}\"}" + echo done diff --git a/plugins/scaffolder-backend/scripts/mock-data b/plugins/scaffolder-backend/scripts/mock-data index 594bd76607..2424d15094 100755 --- a/plugins/scaffolder-backend/scripts/mock-data +++ b/plugins/scaffolder-backend/scripts/mock-data @@ -9,4 +9,5 @@ for URL in \ --request POST 'localhost:7000/catalog/locations' \ --header 'Content-Type: application/json' \ --data-raw "{\"type\": \"file\", \"target\": \"$(pwd)/sample-templates/${URL}/template.yaml\"}" + echo done From d5a0a5968e8ba2475548bdd3c3ae0f2fe2c2f639 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 29 Jun 2020 17:49:54 +0200 Subject: [PATCH 17/19] feat(scaffolder): making the API work by taking the entity that is pulled from the client --- .../src/scaffolder/jobs/logger.ts | 2 +- .../src/scaffolder/jobs/processor.test.ts | 6 ++- .../src/scaffolder/jobs/processor.ts | 6 +-- .../src/scaffolder/jobs/types.ts | 4 +- .../src/scaffolder/stages/store/github.ts | 14 ++--- .../stages/templater/cookiecutter.test.ts | 23 +++++---- .../src/scaffolder/stages/templater/types.ts | 3 +- .../scaffolder-backend/src/service/router.ts | 51 ++++++------------- 8 files changed, 48 insertions(+), 61 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts b/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts index d2eaa13c3b..2e88dd34fe 100644 --- a/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts @@ -17,7 +17,7 @@ import { PassThrough } from 'stream'; import winston from 'winston'; import { JsonValue } from '@backstage/config'; -export const useLogStream = (meta: Record) => { +export const makeLogStream = (meta: Record) => { const log: string[] = []; // Create an empty stream to collect all the log lines into diff --git a/plugins/scaffolder-backend/src/scaffolder/jobs/processor.test.ts b/plugins/scaffolder-backend/src/scaffolder/jobs/processor.test.ts index 501e72c078..a8cfe9f3ea 100644 --- a/plugins/scaffolder-backend/src/scaffolder/jobs/processor.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/jobs/processor.test.ts @@ -16,6 +16,7 @@ import { JobProcessor } from './processor'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { StageInput } from './types'; +import { RequiredTemplateValues } from '../stages/templater'; describe('JobProcessor', () => { const mockEntity: TemplateEntityV1alpha1 = { @@ -41,7 +42,10 @@ describe('JobProcessor', () => { }, }; - const mockValues = { component_id: 'bob' }; + const mockValues: RequiredTemplateValues = { + owner: 'blobby', + storePath: 'spotify/mock-repo', + }; describe('create', () => { it('creates should create a new job with a unique id', async () => { diff --git a/plugins/scaffolder-backend/src/scaffolder/jobs/processor.ts b/plugins/scaffolder-backend/src/scaffolder/jobs/processor.ts index 1c76ae252d..0a3b556941 100644 --- a/plugins/scaffolder-backend/src/scaffolder/jobs/processor.ts +++ b/plugins/scaffolder-backend/src/scaffolder/jobs/processor.ts @@ -20,7 +20,7 @@ import * as uuid from 'uuid'; import Docker from 'dockerode'; import { RequiredTemplateValues, TemplaterBase } from '../stages/templater'; import { PreparerBuilder } from '../stages/prepare'; -import { useLogStream } from './logger'; +import { makeLogStream } from './logger'; export type JobProcessorArguments = { preparers: PreparerBuilder; @@ -46,7 +46,7 @@ export class JobProcessor implements Processor { stages: StageInput[]; }): Job { const id = uuid.v4(); - const { logger, stream } = useLogStream({ id }); + const { logger, stream } = makeLogStream({ id }); const context: StageContext = { entity, @@ -87,7 +87,7 @@ export class JobProcessor implements Processor { for (const stage of job.stages) { // Create a logger for each stage so we can create seperate // Streams for each step. - const { logger, log, stream } = useLogStream({ + const { logger, log, stream } = makeLogStream({ id: job.id, stage: stage.name, }); diff --git a/plugins/scaffolder-backend/src/scaffolder/jobs/types.ts b/plugins/scaffolder-backend/src/scaffolder/jobs/types.ts index fd554e2033..73da47aed9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/jobs/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/jobs/types.ts @@ -31,7 +31,7 @@ export type StageContext = { export type ProcessorStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; -export interface Stage extends StageInput { +export interface StageResult extends StageInput { log: string[]; status: ProcessorStatus; startedAt?: number; @@ -47,7 +47,7 @@ export type Job = { id: string; context: StageContext; status: ProcessorStatus; - stages: Stage[]; + stages: StageResult[]; error?: Error; }; diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts index a59e6c90cd..046a6599fb 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts @@ -20,7 +20,7 @@ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/config'; import { RequiredTemplateValues } from '../templater'; import { Repository, Remote, Signature, Cred } from 'nodegit'; -import gitUrlParse from 'git-url-parse'; +import gitParse from 'git-url-parse'; export class GithubStorer implements Storer { private client: Octokit; @@ -34,15 +34,15 @@ export class GithubStorer implements Storer { entity: TemplateEntityV1alpha1; values: RequiredTemplateValues & Record; }) { + const [owner, name] = values.owner.split('/'); + const { data: { clone_url: cloneUrl }, } = await this.client.repos.createInOrg({ - name: values.component_id, - org: values.org as string, + name, + org: owner, }); - console.warn(cloneUrl); - return cloneUrl; } @@ -54,8 +54,8 @@ export class GithubStorer implements Storer { const oid = await index.writeTree(); await repo.createCommit( 'HEAD', - Signature.now('Foo bar', 'foo@bar.com'), - Signature.now('Foo bar', 'foo@bar.com'), + Signature.now('Scaffolder', 'scaffolder@backstage.io'), + Signature.now('Scaffolder', 'scaffolder@backstage.io'), 'initial commit', oid, [], diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts index 994307e8b7..55496a63d4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts @@ -45,7 +45,8 @@ describe('CookieCutter Templater', () => { const tempdir = await mkTemp(); const values = { - component_id: 'test', + owner: 'blobby', + storePath: 'spotify/end-repo', description: 'description', }; @@ -65,8 +66,8 @@ describe('CookieCutter Templater', () => { await fs.writeJSON(`${tempdir}/cookiecutter.json`, existingJson); const values = { - component_id: 'hello', - description: 'im something cool', + owner: 'blobby', + storePath: 'spotify/end-repo', }; await cookie.run({ directory: tempdir, values, dockerClient: mockDocker }); @@ -82,8 +83,8 @@ describe('CookieCutter Templater', () => { await fs.writeFile(`${tempdir}/cookiecutter.json`, "{'"); const values = { - component_id: 'hello', - description: 'im something cool', + owner: 'blobby', + storePath: 'spotify/end-repo', }; await expect( @@ -95,8 +96,8 @@ describe('CookieCutter Templater', () => { const tempdir = await mkTemp(); const values = { - component_id: 'test', - description: 'description', + owner: 'blobby', + storePath: 'spotify/end-repo', }; await cookie.run({ directory: tempdir, values, dockerClient: mockDocker }); @@ -122,8 +123,8 @@ describe('CookieCutter Templater', () => { const tempdir = await mkTemp(); const values = { - component_id: 'test', - description: 'description', + owner: 'blobby', + storePath: 'spotify/end-repo', }; const returnPath = await cookie.run({ @@ -141,8 +142,8 @@ describe('CookieCutter Templater', () => { const tempdir = await mkTemp(); const values = { - component_id: 'test', - description: 'description', + owner: 'blobby', + storePath: 'spotify/end-repo', }; await cookie.run({ diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts index 5788b6b657..cdefa7821e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts @@ -19,7 +19,8 @@ import Docker from 'dockerode'; import { JsonValue } from '@backstage/config'; export type RequiredTemplateValues = { - component_id: string; + owner: string; + storePath: string; }; export type TemplaterRunOptions = { diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index da7dce40f9..efa2f9588d 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -17,13 +17,19 @@ import { Logger } from 'winston'; import Router from 'express-promise-router'; import express from 'express'; -import { PreparerBuilder, TemplaterBase, JobProcessor } from '../scaffolder'; +import { + PreparerBuilder, + TemplaterBase, + JobProcessor, + RequiredTemplateValues, +} from '../scaffolder'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import Docker from 'dockerode'; import {} from '@backstage/backend-common'; import { StageContext } from '../scaffolder/jobs/types'; import { Octokit } from '@octokit/rest'; import { GithubStorer } from '../scaffolder/stages/store/github'; +import { JsonValue } from '@backstage/config'; export interface RouterOptions { preparers: PreparerBuilder; templater: TemplaterBase; @@ -51,7 +57,9 @@ export async function createRouter( return; } - res.send(job.stages[Number(params.index)].log.join('')); + const { log } = job.stages[Number(params.index)] ?? { log: [] }; + + res.send(log.join('')); }) .get('/v1/job/:jobId', ({ params }, res) => { const job = jobProcessor.get(params.jobId); @@ -76,41 +84,14 @@ export async function createRouter( error: job.error, }); }) - .post('/v1/jobs', async (_, res) => { - // TODO(blam): Create a unique job here and return the ID so that - // The end user can poll for updates on the current job - - // TODO(blam): Take this entity from the post body sent from the frontend - const mockEntity: TemplateEntityV1alpha1 = { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Template', - metadata: { - annotations: { - 'backstage.io/managed-by-location': - 'file:/Users/blam/dev/spotify/backstage/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml', - }, - name: 'graphql-starter', - title: 'GraphQL Service', - description: - 'A GraphQL starter template for backstage to get you up and running\nthe best pracices with GraphQL\n', - uid: '9cf16bad-16e0-4213-b314-c4eec773c50b', - etag: 'ZTkxMjUxMjUtYWY3Yi00MjU2LWFkYWMtZTZjNjU5ZjJhOWM2', - - generation: 1, - }, - spec: { - type: 'cookiecutter', - path: '.', - }, - }; + .post('/v1/jobs', async (req, res) => { + const template: TemplateEntityV1alpha1 = req.body.template; + const values: RequiredTemplateValues & Record = + req.body.values; const job = jobProcessor.create({ - entity: mockEntity, - values: { - component_id: `blob${Date.now()}`, - org: 'hojden', - description: 'test', - }, + entity: template, + values, stages: [ { name: 'Prepare the skeleton', From d7dbc15b11f0746158f4e0d810c6128ac2816786 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 29 Jun 2020 18:05:56 +0200 Subject: [PATCH 18/19] chore(scaffolder): fixing issues with typescript and code review comment: --- .../scaffolder-backend/src/scaffolder/stages/store/github.ts | 3 +-- .../src/scaffolder/stages/templater/cookiecutter.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts index 046a6599fb..8f5cc40ff9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/store/github.ts @@ -20,7 +20,6 @@ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/config'; import { RequiredTemplateValues } from '../templater'; import { Repository, Remote, Signature, Cred } from 'nodegit'; -import gitParse from 'git-url-parse'; export class GithubStorer implements Storer { private client: Octokit; @@ -34,7 +33,7 @@ export class GithubStorer implements Storer { entity: TemplateEntityV1alpha1; values: RequiredTemplateValues & Record; }) { - const [owner, name] = values.owner.split('/'); + const [owner, name] = values.storePath.split('/'); const { data: { clone_url: cloneUrl }, diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts index 5e875b6838..3e94d67c6e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts @@ -65,6 +65,6 @@ export class CookieCutter implements TemplaterBase { dockerClient: options.dockerClient, }); - return path.resolve(resultDir, options.values.component_id); + return path.resolve(resultDir, options.values.component_id as string); } } From 9a7352c52bebeaa2bc51d9e0f2a54ba690064f19 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 29 Jun 2020 21:29:21 +0200 Subject: [PATCH 19/19] chore(scaffolder): fixing broken tests --- .../src/scaffolder/stages/templater/cookiecutter.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts index 55496a63d4..27e4cd4b01 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.test.ts @@ -48,6 +48,7 @@ describe('CookieCutter Templater', () => { owner: 'blobby', storePath: 'spotify/end-repo', description: 'description', + component_id: 'newthing', }; await cookie.run({ directory: tempdir, values, dockerClient: mockDocker }); @@ -68,6 +69,7 @@ describe('CookieCutter Templater', () => { const values = { owner: 'blobby', storePath: 'spotify/end-repo', + component_id: 'something', }; await cookie.run({ directory: tempdir, values, dockerClient: mockDocker }); @@ -98,6 +100,7 @@ describe('CookieCutter Templater', () => { const values = { owner: 'blobby', storePath: 'spotify/end-repo', + component_id: 'newthing', }; await cookie.run({ directory: tempdir, values, dockerClient: mockDocker }); @@ -125,6 +128,7 @@ describe('CookieCutter Templater', () => { const values = { owner: 'blobby', storePath: 'spotify/end-repo', + component_id: 'newthing', }; const returnPath = await cookie.run({ @@ -144,6 +148,7 @@ describe('CookieCutter Templater', () => { const values = { owner: 'blobby', storePath: 'spotify/end-repo', + component_id: 'newthing', }; await cookie.run({