From 5cf3f854d36b82fb4183025b3dfb9dad39af672e Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Sun, 4 Oct 2020 17:55:43 +0530 Subject: [PATCH 001/104] feat: implement customPageTheme which includes core material-ui theming features & backstage pages theming features --- packages/theme/src/baseTheme.ts | 14 +++++++ packages/theme/src/pageTheme.ts | 66 +++++++++++++++++++++++++++++++++ packages/theme/src/themes.ts | 8 +++- packages/theme/src/types.ts | 21 +++++++++++ 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 packages/theme/src/pageTheme.ts diff --git a/packages/theme/src/baseTheme.ts b/packages/theme/src/baseTheme.ts index 00a485bb41..a3fded7e04 100644 --- a/packages/theme/src/baseTheme.ts +++ b/packages/theme/src/baseTheme.ts @@ -19,6 +19,8 @@ import { darken, lighten } from '@material-ui/core/styles/colorManipulator'; import { Overrides } from '@material-ui/core/styles/overrides'; import { + BackstagePageTheme, + BackstagePageThemeOptions, BackstageTheme, BackstageThemeOptions, SimpleThemeOptions, @@ -242,3 +244,15 @@ export function createTheme(options: SimpleThemeOptions): BackstageTheme { const theme = { ...baseTheme, overrides }; return theme; } + +export function createPageTheme( + options: BackstagePageThemeOptions, +): BackstagePageTheme { + const baseTheme = createTheme(options); + const pageTheme = options.pageTheme; + + return { + pageTheme, + baseTheme, + }; +} diff --git a/packages/theme/src/pageTheme.ts b/packages/theme/src/pageTheme.ts new file mode 100644 index 0000000000..f86f3a6449 --- /dev/null +++ b/packages/theme/src/pageTheme.ts @@ -0,0 +1,66 @@ +/* + * 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 { PageTheme } from './types'; + +/* + # How to add a shape + 1. Get the svg shape from figma, should be ~1400 wide, ~400 high + and only the white->transparent mask, no colors. + 2. Run it through https://jakearchibald.github.io/svgomg/ + 3. Run that through https://github.com/tigt/mini-svg-data-uri + with something like https://npm.runkit.com/mini-svg-data-uri + 4. Wrap the output in `url("")` + 5. Give it a name and paste it into the `shapes` object below. +*/ +export const shapes: Record = { + wave: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368' height='400' fill='none'%3e%3cmask id='a' width='1368' height='401' x='0' y='0' maskUnits='userSpaceOnUse'%3e%3cpath fill='url(%23paint0_linear)' d='M437 116C223 116 112 0 112 0h1256v400c-82 0-225-21-282-109-112-175-436-175-649-175z'/%3e%3cpath fill='url(%23paint1_linear)' d='M1368 400V282C891-29 788 40 711 161 608 324 121 372 0 361v39h1368z'/%3e%3cpath fill='url(%23paint2_linear)' d='M1368 244v156H0V94c92-24 198-46 375 0l135 41c176 51 195 109 858 109z'/%3e%3cpath fill='url(%23paint3_linear)' d='M1252 400h116c-14-7-35-14-116-16-663-14-837-128-1013-258l-85-61C98 28 46 8 0 0v400h1252z'/%3e%3c/mask%3e%3cg mask='url(%23a)'%3e%3cpath fill='white' d='M-172-98h1671v601H-172z'/%3e%3c/g%3e%3cdefs%3e%3clinearGradient id='paint0_linear' x1='602' x2='1093.5' y1='-960.5' y2='272' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint1_linear' x1='482' x2='480' y1='1058.5' y2='70.5' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint2_linear' x1='424' x2='446.1' y1='-587.5' y2='274.6' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint3_linear' x1='587' x2='349' y1='-1120.5' y2='341' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e")`, + wave2: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368' height='400' fill='none'%3e%3cmask id='a' width='1764' height='479' x='-229' y='-6' maskUnits='userSpaceOnUse'%3e%3cpath fill='url(%23paint0_linear)' d='M0 400h1350C1321 336 525 33 179-2c-345-34-395 236-408 402H0z'/%3e%3cpath fill='url(%23paint1_linear)' d='M1378 177v223H0V217s219 75 327 52C436 246 717-35 965 45s254 144 413 132z'/%3e%3cpath fill='url(%23paint2_linear)' d='M26 400l-78-16c-170 205-44-6-137-30l-4-1 4 1 137 30c37-45 89-110 159-201 399-514-45 238 1176-50 275-65 354-39 91 267H26z'/%3e%3c/mask%3e%3cg mask='url(%23a)'%3e%3cpath fill='white' d='M0 0h1368v400H0z'/%3e%3c/g%3e%3cdefs%3e%3clinearGradient id='paint0_linear' x1='431' x2='397.3' y1='-599' y2='372.8' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint1_linear' x1='236.5' x2='446.6' y1='-586' y2='381.5' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint2_linear' x1='851.8' x2='640.4' y1='-867.2' y2='363.7' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e")`, + round: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368' height='400' fill='none'%3e%3cmask id='a' width='2269' height='1408' x='-610' y='-509' maskUnits='userSpaceOnUse'%3e%3ccircle cx='1212.8' cy='74.8' r='317.5' fill='url(%23paint0_linear)' transform='rotate(-52 1213 75)'/%3e%3ccircle cx='737.8' cy='445.8' r='317.5' fill='url(%23paint1_linear)' transform='rotate(-116 738 446)'/%3e%3ccircle cx='601.8' cy='52.8' r='418.6' fill='url(%23paint2_linear)' transform='rotate(-117 602 53)'/%3e%3ccircle cx='999.8' cy='364' r='389.1' fill='url(%23paint3_linear)' transform='rotate(31 1000 364)'/%3e%3cellipse cx='-109.2' cy='263.5' fill='url(%23paint4_linear)' rx='429.2' ry='465.8' transform='rotate(-85 -109 264)'/%3e%3c/mask%3e%3cg mask='url(%23a)'%3e%3cpath fill='white' d='M0 0h1368v400H0z'/%3e%3c/g%3e%3cdefs%3e%3clinearGradient id='paint0_linear' x1='1301.2' x2='161.4' y1='-1879.7' y2='-969.6' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint1_linear' x1='826.2' x2='-313.6' y1='-1508.7' y2='-598.6' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint2_linear' x1='718.4' x2='-784.3' y1='-2524' y2='-1324.2' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint3_linear' x1='1108.2' x2='-288.6' y1='-2031.1' y2='-915.9' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint4_linear' x1='10.4' x2='-1626.5' y1='-2603.8' y2='-1399.5' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e")`, +}; + +export const colorVariants: Record = { + darkGrey: ['#171717', '#383838'], + marineBlue: ['#006D8F', '#0049A1'], + veryBlue: ['#0027AF', '#270094'], + rubyRed: ['#98002B', '#8D1134'], + toastyOrange: ['#BE2200', '#A41D00'], + purpleSky: ['#8912CA', '#3E00EA'], + eveningSea: ['#00FFF2', '#035355'], + teal: ['#005B4B'], + pinkSea: ['#C8077A', '#C2297D'], +}; + +// As the background shapes and colors are decorative, we place them onto +// the page as a css background-image instead of an html element of its own. +// Utility to not have to write colors and shapes twice. +export function genPageTheme(colors: string[], shape: string): PageTheme { + const gradientColors = colors.length === 1 ? [colors[0], colors[0]] : colors; + const gradient = `linear-gradient(90deg, ${gradientColors.join(', ')})`; + const backgroundImage = `${shape}, ${gradient}`; + + return { colors, shape, backgroundImage }; +} + +export const pageTheme: Record = { + home: genPageTheme(colorVariants.teal, shapes.wave), + documentation: genPageTheme(colorVariants.pinkSea, shapes.wave2), + tool: genPageTheme(colorVariants.purpleSky, shapes.round), + service: genPageTheme(colorVariants.marineBlue, shapes.wave), + website: genPageTheme(colorVariants.veryBlue, shapes.wave), + library: genPageTheme(colorVariants.rubyRed, shapes.wave), + other: genPageTheme(colorVariants.darkGrey, shapes.wave), + app: genPageTheme(colorVariants.toastyOrange, shapes.wave), +}; diff --git a/packages/theme/src/themes.ts b/packages/theme/src/themes.ts index 0ec3304ac5..78a68c8f13 100644 --- a/packages/theme/src/themes.ts +++ b/packages/theme/src/themes.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { createTheme } from './baseTheme'; +import { createTheme, createPageTheme } from './baseTheme'; +import { pageTheme } from './pageTheme'; import { yellow } from '@material-ui/core/colors'; export const lightTheme = createTheme({ @@ -132,3 +133,8 @@ export const darkTheme = createTheme({ }, }, }); + +export const customPageTheme = createPageTheme({ + pageTheme, + palette: lightTheme.palette, +}); diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts index 1d244f8cd4..3d3943392a 100644 --- a/packages/theme/src/types.ts +++ b/packages/theme/src/types.ts @@ -87,3 +87,24 @@ export type SimpleThemeOptions = { palette: BackstagePaletteOptions; fontFamily?: string; }; + +export type PageTheme = { + colors: string[]; + shape: string; + backgroundImage: string; +}; + +export type BackstagePage = Record; + +export interface BackstagePageThemeOptions extends SimpleThemeOptions { + pageTheme: BackstagePage; +} + +export interface BackstagePageTheme { + pageTheme: BackstagePage; + baseTheme: BackstageTheme; +} + +export interface BackstagePageThemeConsumer extends BackstageTheme { + pageTheme: PageTheme; +} From 26cfd3e2ca28b200a6f0eddcb567db8a73e4d48a Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Sun, 4 Oct 2020 17:58:09 +0530 Subject: [PATCH 002/104] feat: remove old PageTheme provider & add new implemented functionality to provide theme[using ThemeProvider] --- packages/core/src/layout/Page/Page.stories.tsx | 6 +++--- packages/core/src/layout/Page/Page.tsx | 17 +++++++++-------- packages/core/src/layout/Page/index.ts | 2 -- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/core/src/layout/Page/Page.stories.tsx b/packages/core/src/layout/Page/Page.stories.tsx index 8fd779196d..427dc644d9 100644 --- a/packages/core/src/layout/Page/Page.stories.tsx +++ b/packages/core/src/layout/Page/Page.stories.tsx @@ -15,13 +15,13 @@ */ import React, { useState } from 'react'; +import { customPageTheme } from '@backstage/theme'; import { Header, Page, HeaderLabel, ContentHeader, Content, - pageTheme, InfoCard, HeaderTabs, } from '../'; @@ -196,7 +196,7 @@ export const PluginWithData = () => { const [selectedTab, setSelectedTab] = useState(2); return (
- + { export const PluginWithTable = () => { return (
- + diff --git a/packages/core/src/layout/Page/Page.tsx b/packages/core/src/layout/Page/Page.tsx index a5f5e4fd2b..f4dc4888ed 100644 --- a/packages/core/src/layout/Page/Page.tsx +++ b/packages/core/src/layout/Page/Page.tsx @@ -15,10 +15,8 @@ */ import React, { FC } from 'react'; -import { PageTheme, pageTheme } from './PageThemeProvider'; -import { makeStyles } from '@material-ui/core'; - -export const PageThemeContext = React.createContext(pageTheme.home); +import { customPageTheme, PageTheme } from '@backstage/theme'; +import { makeStyles, ThemeProvider } from '@material-ui/core'; const useStyles = makeStyles(() => ({ root: { @@ -32,14 +30,17 @@ const useStyles = makeStyles(() => ({ })); type Props = { - theme?: PageTheme; + pageTheme?: PageTheme; }; -export const Page: FC = ({ theme = pageTheme.home, children }) => { +export const Page: FC = ({ + pageTheme = customPageTheme.pageTheme.home, + children, +}) => { const classes = useStyles(); return ( - +
{children}
-
+ ); }; diff --git a/packages/core/src/layout/Page/index.ts b/packages/core/src/layout/Page/index.ts index 000a565c83..987aee5cdb 100644 --- a/packages/core/src/layout/Page/index.ts +++ b/packages/core/src/layout/Page/index.ts @@ -15,5 +15,3 @@ */ export { Page } from './Page'; -export { pageTheme } from './PageThemeProvider'; -export type { PageTheme } from './PageThemeProvider'; From 317bb8b6543cbc3b66dc68587d3cb636647d2f88 Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Sun, 4 Oct 2020 17:59:21 +0530 Subject: [PATCH 003/104] chore: remove old Page theme Provider file --- .../core/src/layout/Page/PageThemeProvider.ts | 72 ------------------- 1 file changed, 72 deletions(-) delete mode 100644 packages/core/src/layout/Page/PageThemeProvider.ts diff --git a/packages/core/src/layout/Page/PageThemeProvider.ts b/packages/core/src/layout/Page/PageThemeProvider.ts deleted file mode 100644 index 57411c8786..0000000000 --- a/packages/core/src/layout/Page/PageThemeProvider.ts +++ /dev/null @@ -1,72 +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 type PageTheme = { - colors: string[]; - shape: string; - backgroundImage: string; -}; - -/* - # How to add a shape - 1. Get the svg shape from figma, should be ~1400 wide, ~400 high - and only the white->transparent mask, no colors. - 2. Run it through https://jakearchibald.github.io/svgomg/ - 3. Run that through https://github.com/tigt/mini-svg-data-uri - with something like https://npm.runkit.com/mini-svg-data-uri - 4. Wrap the output in `url("")` - 5. Give it a name and paste it into the `shapes` object below. - -*/ -export const shapes: Record = { - wave: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368' height='400' fill='none'%3e%3cmask id='a' width='1368' height='401' x='0' y='0' maskUnits='userSpaceOnUse'%3e%3cpath fill='url(%23paint0_linear)' d='M437 116C223 116 112 0 112 0h1256v400c-82 0-225-21-282-109-112-175-436-175-649-175z'/%3e%3cpath fill='url(%23paint1_linear)' d='M1368 400V282C891-29 788 40 711 161 608 324 121 372 0 361v39h1368z'/%3e%3cpath fill='url(%23paint2_linear)' d='M1368 244v156H0V94c92-24 198-46 375 0l135 41c176 51 195 109 858 109z'/%3e%3cpath fill='url(%23paint3_linear)' d='M1252 400h116c-14-7-35-14-116-16-663-14-837-128-1013-258l-85-61C98 28 46 8 0 0v400h1252z'/%3e%3c/mask%3e%3cg mask='url(%23a)'%3e%3cpath fill='white' d='M-172-98h1671v601H-172z'/%3e%3c/g%3e%3cdefs%3e%3clinearGradient id='paint0_linear' x1='602' x2='1093.5' y1='-960.5' y2='272' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint1_linear' x1='482' x2='480' y1='1058.5' y2='70.5' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint2_linear' x1='424' x2='446.1' y1='-587.5' y2='274.6' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint3_linear' x1='587' x2='349' y1='-1120.5' y2='341' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e")`, - wave2: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368' height='400' fill='none'%3e%3cmask id='a' width='1764' height='479' x='-229' y='-6' maskUnits='userSpaceOnUse'%3e%3cpath fill='url(%23paint0_linear)' d='M0 400h1350C1321 336 525 33 179-2c-345-34-395 236-408 402H0z'/%3e%3cpath fill='url(%23paint1_linear)' d='M1378 177v223H0V217s219 75 327 52C436 246 717-35 965 45s254 144 413 132z'/%3e%3cpath fill='url(%23paint2_linear)' d='M26 400l-78-16c-170 205-44-6-137-30l-4-1 4 1 137 30c37-45 89-110 159-201 399-514-45 238 1176-50 275-65 354-39 91 267H26z'/%3e%3c/mask%3e%3cg mask='url(%23a)'%3e%3cpath fill='white' d='M0 0h1368v400H0z'/%3e%3c/g%3e%3cdefs%3e%3clinearGradient id='paint0_linear' x1='431' x2='397.3' y1='-599' y2='372.8' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint1_linear' x1='236.5' x2='446.6' y1='-586' y2='381.5' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint2_linear' x1='851.8' x2='640.4' y1='-867.2' y2='363.7' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e")`, - round: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368' height='400' fill='none'%3e%3cmask id='a' width='2269' height='1408' x='-610' y='-509' maskUnits='userSpaceOnUse'%3e%3ccircle cx='1212.8' cy='74.8' r='317.5' fill='url(%23paint0_linear)' transform='rotate(-52 1213 75)'/%3e%3ccircle cx='737.8' cy='445.8' r='317.5' fill='url(%23paint1_linear)' transform='rotate(-116 738 446)'/%3e%3ccircle cx='601.8' cy='52.8' r='418.6' fill='url(%23paint2_linear)' transform='rotate(-117 602 53)'/%3e%3ccircle cx='999.8' cy='364' r='389.1' fill='url(%23paint3_linear)' transform='rotate(31 1000 364)'/%3e%3cellipse cx='-109.2' cy='263.5' fill='url(%23paint4_linear)' rx='429.2' ry='465.8' transform='rotate(-85 -109 264)'/%3e%3c/mask%3e%3cg mask='url(%23a)'%3e%3cpath fill='white' d='M0 0h1368v400H0z'/%3e%3c/g%3e%3cdefs%3e%3clinearGradient id='paint0_linear' x1='1301.2' x2='161.4' y1='-1879.7' y2='-969.6' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint1_linear' x1='826.2' x2='-313.6' y1='-1508.7' y2='-598.6' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint2_linear' x1='718.4' x2='-784.3' y1='-2524' y2='-1324.2' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint3_linear' x1='1108.2' x2='-288.6' y1='-2031.1' y2='-915.9' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint4_linear' x1='10.4' x2='-1626.5' y1='-2603.8' y2='-1399.5' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e")`, -}; - -export const colorVariants: Record = { - darkGrey: ['#171717', '#383838'], - marineBlue: ['#006D8F', '#0049A1'], - veryBlue: ['#0027AF', '#270094'], - rubyRed: ['#98002B', '#8D1134'], - toastyOrange: ['#BE2200', '#A41D00'], - purpleSky: ['#8912CA', '#3E00EA'], - eveningSea: ['#00FFF2', '#035355'], - teal: ['#005B4B'], - pinkSea: ['#C8077A', '#C2297D'], -}; - -export const pageTheme: Record = { - home: genTheme(colorVariants.teal, shapes.wave), - documentation: genTheme(colorVariants.pinkSea, shapes.wave2), - tool: genTheme(colorVariants.purpleSky, shapes.round), - service: genTheme(colorVariants.marineBlue, shapes.wave), - website: genTheme(colorVariants.veryBlue, shapes.wave), - library: genTheme(colorVariants.rubyRed, shapes.wave), - other: genTheme(colorVariants.darkGrey, shapes.wave), - app: genTheme(colorVariants.toastyOrange, shapes.wave), -}; - -// As the background shapes and colors are decorative, we place them onto -// the page as a css background-image instead of an html element of its own. -// Utility to not have to write colors and shapes twice. -function genTheme(colors: string[], shape: string) { - const gradientColors = colors.length === 1 ? [colors[0], colors[0]] : colors; - const gradient = `linear-gradient(90deg, ${gradientColors.join(', ')})`; - const backgroundImage = `${shape}, ${gradient}`; - - return { colors, shape, backgroundImage }; -} From 3a82aaef7ab693bc02f1bc5911c4d955713f51f9 Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Sun, 4 Oct 2020 18:02:00 +0530 Subject: [PATCH 004/104] feat: Add new theme consumer in Header [use useTheme consumer provided in material-ui] --- .../core/src/layout/Header/Header.stories.tsx | 19 ++++++++++--------- packages/core/src/layout/Header/Header.tsx | 14 +++++++------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/core/src/layout/Header/Header.stories.tsx b/packages/core/src/layout/Header/Header.stories.tsx index 39b9133c0a..cc0276224a 100644 --- a/packages/core/src/layout/Header/Header.stories.tsx +++ b/packages/core/src/layout/Header/Header.stories.tsx @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { customPageTheme } from '@backstage/theme'; import React from 'react'; import { Header } from '.'; import { HeaderLabel } from '../HeaderLabel'; -import { Page, pageTheme } from '../Page'; +import { Page } from '../Page'; export default { title: 'Header', @@ -32,7 +33,7 @@ const labels = ( ); export const Home = () => ( - +
{labels}
@@ -46,7 +47,7 @@ export const HomeWithSubtitle = () => ( ); export const Tool = () => ( - +
{labels}
@@ -54,7 +55,7 @@ export const Tool = () => ( ); export const Service = () => ( - +
{labels}
@@ -62,7 +63,7 @@ export const Service = () => ( ); export const Website = () => ( - +
{labels}
@@ -70,7 +71,7 @@ export const Website = () => ( ); export const Library = () => ( - +
{labels}
@@ -78,7 +79,7 @@ export const Library = () => ( ); export const App = () => ( - +
{labels}
@@ -86,7 +87,7 @@ export const App = () => ( ); export const Documentation = () => ( - +
{labels}
@@ -94,7 +95,7 @@ export const Documentation = () => ( ); export const Other = () => ( - +
{labels}
diff --git a/packages/core/src/layout/Header/Header.tsx b/packages/core/src/layout/Header/Header.tsx index 6b9ea05245..dc71fe333b 100644 --- a/packages/core/src/layout/Header/Header.tsx +++ b/packages/core/src/layout/Header/Header.tsx @@ -14,12 +14,10 @@ * limitations under the License. */ -import React, { ReactNode, CSSProperties, FC, useContext } from 'react'; +import React, { ReactNode, CSSProperties, FC } from 'react'; import { Helmet } from 'react-helmet'; -import { Typography, Tooltip, makeStyles } from '@material-ui/core'; -import { BackstageTheme } from '@backstage/theme'; - -import { PageThemeContext } from '../Page/Page'; +import { Typography, Tooltip, makeStyles, useTheme } from '@material-ui/core'; +import { BackstageTheme, BackstagePageThemeConsumer } from '@backstage/theme'; const useStyles = makeStyles( theme => ({ @@ -163,8 +161,10 @@ export const Header: FC = ({ type, typeLink, }) => { - const theme = useContext(PageThemeContext); - const classes = useStyles({ backgroundImage: theme.backgroundImage }); + const theme = useTheme(); + const classes = useStyles({ + backgroundImage: theme.pageTheme.backgroundImage, + }); const documentTitle = pageTitleOverride || title; const pageTitle = title || pageTitleOverride; const titleTemplate = `${documentTitle} | %s | Backstage`; From e2564645e5751d684b3b528e90011518eee6c376 Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Sun, 4 Oct 2020 18:03:50 +0530 Subject: [PATCH 005/104] feat: Replace old page theme props with new ones --- .../components/ApiEntityPage/ApiEntityPage.tsx | 8 ++++---- .../ApiExplorerPage/ApiExplorerLayout.tsx | 5 +++-- .../src/components/CatalogPage/CatalogLayout.tsx | 4 ++-- .../EntityPageLayout/EntityPageLayout.tsx | 15 ++++----------- .../AlertInstructionsLayout.tsx | 5 +++-- .../CostInsightsLayout/CostInsightsLayout.tsx | 6 +++--- .../explore/src/components/ExplorePluginPage.tsx | 5 ++--- .../components/NewProjectPage/NewProjectPage.tsx | 4 ++-- .../ProjectDetailsPage/ProjectDetailsPage.tsx | 5 ++--- .../ProjectListPage/ProjectListPage.tsx | 5 ++--- .../src/components/ClusterList/ClusterList.tsx | 4 ++-- .../src/components/ClusterPage/ClusterPage.tsx | 5 ++--- .../components/ProfileCatalog/ProfileCatalog.tsx | 4 ++-- .../src/components/GraphiQLPage/GraphiQLPage.tsx | 5 ++--- .../KubernetesContent/KubernetesContent.tsx | 4 ++-- .../lighthouse/src/components/AuditList/index.tsx | 5 ++--- .../lighthouse/src/components/AuditView/index.tsx | 4 ++-- .../src/components/CreateAudit/index.tsx | 4 ++-- .../NewRelicComponent/NewRelicComponent.tsx | 4 ++-- .../RegisterComponentPage.tsx | 4 ++-- .../src/components/RollbarHome/RollbarHome.tsx | 5 +++-- .../RollbarProjectPage/RollbarProjectPage.tsx | 5 +++-- .../components/ScaffolderPage/ScaffolderPage.tsx | 4 ++-- .../src/components/TemplateCard/TemplateCard.tsx | 6 ++++-- .../src/components/TemplatePage/TemplatePage.tsx | 4 ++-- .../SentryPluginPage/SentryPluginPage.tsx | 4 ++-- plugins/tech-radar/src/components/RadarPage.tsx | 4 ++-- .../src/reader/components/TechDocsHome.tsx | 8 ++++---- .../src/reader/components/TechDocsPage.tsx | 5 +++-- .../src/components/WelcomePage/WelcomePage.tsx | 4 ++-- 30 files changed, 74 insertions(+), 80 deletions(-) diff --git a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx index b7e9996cc0..edfc3ab08b 100644 --- a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx +++ b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx @@ -20,11 +20,10 @@ import { errorApiRef, Header, Page, - pageTheme, - PageTheme, Progress, useApi, } from '@backstage/core'; +import { customPageTheme, PageTheme } from '@backstage/theme'; import { catalogApiRef } from '@backstage/plugin-catalog'; import { Box } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; @@ -54,9 +53,10 @@ function headerProps( }; } +// REUSABLE IN plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx export const getPageTheme = (entity?: Entity): PageTheme => { const themeKey = entity?.spec?.type?.toString() ?? 'home'; - return pageTheme[themeKey] ?? pageTheme.home; + return customPageTheme.pageTheme[themeKey] ?? customPageTheme.pageTheme.home; }; type EntityPageTitleProps = { @@ -107,7 +107,7 @@ export const ApiEntityPage = () => { ); return ( - +
} pageTitleOverride={headerTitle} diff --git a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx index a111007278..53b50f1826 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx @@ -14,7 +14,8 @@ * limitations under the License. */ -import { Header, Page, pageTheme } from '@backstage/core'; +import { Header, Page } from '@backstage/core'; +import { customPageTheme } from '@backstage/theme'; import React from 'react'; type Props = { @@ -23,7 +24,7 @@ type Props = { export const ApiExplorerLayout = ({ children }: Props) => { return ( - +
{ const userId = useApi(identityApiRef).getUserId(); return ( - +
{ const themeKey = entity?.spec?.type?.toString() ?? 'home'; - return pageTheme[themeKey] ?? pageTheme.home; + return customPageTheme.pageTheme[themeKey] ?? customPageTheme.pageTheme.home; }; const EntityPageTitle = ({ @@ -100,7 +93,7 @@ export const EntityPageLayout = ({ const showRemovalDialog = () => setConfirmationDialogOpen(true); return ( - +
} pageTitleOverride={headerTitle} diff --git a/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx b/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx index 0a422c335a..c0777fc844 100644 --- a/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx +++ b/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx @@ -14,10 +14,11 @@ * limitations under the License. */ +import { customPageTheme } from '@backstage/theme'; import React, { ReactNode } from 'react'; import { Box, Button, Container, makeStyles } from '@material-ui/core'; import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; -import { Header, Page, pageTheme } from '@backstage/core'; +import { Header, Page } from '@backstage/core'; import { CostInsightsThemeProvider } from '../CostInsightsPage/CostInsightsThemeProvider'; const useStyles = makeStyles(theme => ({ @@ -39,7 +40,7 @@ const AlertInstructionsLayout = ({ const classes = useStyles(); return ( - +
diff --git a/plugins/cost-insights/src/components/CostInsightsLayout/CostInsightsLayout.tsx b/plugins/cost-insights/src/components/CostInsightsLayout/CostInsightsLayout.tsx index 61bf7ac22f..c95f624bbe 100644 --- a/plugins/cost-insights/src/components/CostInsightsLayout/CostInsightsLayout.tsx +++ b/plugins/cost-insights/src/components/CostInsightsLayout/CostInsightsLayout.tsx @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { customPageTheme } from '@backstage/theme'; import React from 'react'; import { makeStyles } from '@material-ui/core'; -import { Header, Page, pageTheme } from '@backstage/core'; +import { Header, Page } from '@backstage/core'; import { Group } from '../../types'; import CostInsightsTabs from '../CostInsightsTabs'; @@ -41,7 +41,7 @@ type CostInsightsLayoutProps = { const CostInsightsLayout = ({ groups, children }: CostInsightsLayoutProps) => { const classes = useStyles(); return ( - +
(theme => ({ container: { @@ -117,7 +116,7 @@ const toolsCards = [ export const ExplorePluginPage = () => { const classes = useStyles(); return ( - +
{ return ( - +
{labels}
diff --git a/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx b/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx index 3dfb4e886b..e310c27c68 100644 --- a/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx +++ b/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { customPageTheme } from '@backstage/theme'; import { Content, ContentHeader, Header, HeaderLabel, Page, - pageTheme, SupportButton, useApi, WarningPanel, @@ -149,7 +148,7 @@ const labels = ( export const ProjectDetailsPage = () => { return ( - +
{labels}
diff --git a/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx b/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx index 542d03d5fd..5a404dbb01 100644 --- a/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx +++ b/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx @@ -15,7 +15,7 @@ */ // NEEDS WORK - +import { customPageTheme } from '@backstage/theme'; import { Content, ContentHeader, @@ -23,7 +23,6 @@ import { HeaderLabel, Link, Page, - pageTheme, SupportButton, useApi, WarningPanel, @@ -136,7 +135,7 @@ const PageContents = () => { export const ProjectListPage = () => { return ( - +
{labels}
diff --git a/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx b/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx index baab018415..8926ed3260 100644 --- a/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx +++ b/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { customPageTheme } from '@backstage/theme'; import React, { FC, useState } from 'react'; import { Content, @@ -21,7 +22,6 @@ import { Header, SupportButton, Page, - pageTheme, Progress, HeaderLabel, useApi, @@ -91,7 +91,7 @@ const ClusterList: FC<{}> = () => { } return ( - +
diff --git a/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx b/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx index b2d62e8650..d51fb7656e 100644 --- a/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx +++ b/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { customPageTheme } from '@backstage/theme'; import React, { FC, useEffect, useState } from 'react'; import { Content, Header, Page, - pageTheme, Table, Progress, HeaderLabel, @@ -85,7 +84,7 @@ const ClusterPage: FC<{}> = () => { }, [pollingLog, api, params, githubAuth, githubAccessToken, githubUsername]); return ( - +
diff --git a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx index a5125fb006..078e1b7601 100644 --- a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx +++ b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx @@ -14,11 +14,11 @@ * limitations under the License. */ +import { customPageTheme } from '@backstage/theme'; import React, { FC, useEffect, useState } from 'react'; import { Header, Page, - pageTheme, Content, ContentHeader, HeaderLabel, @@ -259,7 +259,7 @@ const ProfileCatalog: FC<{}> = () => { ]; return ( - +
{ } return ( - +
diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index 6119884c43..381b942f3d 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -14,13 +14,13 @@ * limitations under the License. */ +import { customPageTheme } from '@backstage/theme'; import React, { ReactElement, useEffect, useState } from 'react'; import { Grid, TabProps } from '@material-ui/core'; import { CardTab, Content, Page, - pageTheme, Progress, TabbedCard, useApi, @@ -120,7 +120,7 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { kubernetesObjects?.items.filter(r => r.errors.length > 0) ?? []; return ( - + {kubernetesObjects === undefined && error === undefined && ( diff --git a/plugins/lighthouse/src/components/AuditList/index.tsx b/plugins/lighthouse/src/components/AuditList/index.tsx index a75bb25a0d..9c38e27fe3 100644 --- a/plugins/lighthouse/src/components/AuditList/index.tsx +++ b/plugins/lighthouse/src/components/AuditList/index.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { customPageTheme } from '@backstage/theme'; import React, { useState, useMemo, FC, ReactNode } from 'react'; import { useLocalStorage, useAsync } from 'react-use'; import { useNavigate } from 'react-router-dom'; @@ -28,7 +28,6 @@ import { ContentHeader, HeaderLabel, Progress, - pageTheme, useApi, } from '@backstage/core'; @@ -95,7 +94,7 @@ const AuditList: FC<{}> = () => { } return ( - +
= () => { } return ( - +
= () => { ]); return ( - +
= () => ( - +
diff --git a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx index 933b67060a..5e4da3fc35 100644 --- a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx +++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx @@ -14,12 +14,12 @@ * limitations under the License. */ +import { customPageTheme } from '@backstage/theme'; import React, { useState } from 'react'; import { Grid, makeStyles } from '@material-ui/core'; import { InfoCard, Page, - pageTheme, Content, useApi, errorApiRef, @@ -113,7 +113,7 @@ export const RegisterComponentPage = ({ }; return ( - +
diff --git a/plugins/rollbar/src/components/RollbarHome/RollbarHome.tsx b/plugins/rollbar/src/components/RollbarHome/RollbarHome.tsx index d09c2054b1..9f0deca967 100644 --- a/plugins/rollbar/src/components/RollbarHome/RollbarHome.tsx +++ b/plugins/rollbar/src/components/RollbarHome/RollbarHome.tsx @@ -14,8 +14,9 @@ * limitations under the License. */ +import { customPageTheme } from '@backstage/theme'; import React from 'react'; -import { Content, Header, Page, pageTheme } from '@backstage/core'; +import { Content, Header, Page } from '@backstage/core'; import { RollbarProjectTable } from '../RollbarProjectTable/RollbarProjectTable'; import { useRollbarEntities } from '../../hooks/useRollbarEntities'; @@ -23,7 +24,7 @@ export const RollbarHome = () => { const { entities, loading, error } = useRollbarEntities(); return ( - +
{ const { entity } = useCatalogEntity(); return ( - +
diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index b5bdaaef4a..1bd43ded44 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { customPageTheme } from '@backstage/theme'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { Content, @@ -22,7 +23,6 @@ import { Header, Lifecycle, Page, - pageTheme, Progress, SupportButton, useApi, @@ -66,7 +66,7 @@ export const ScaffolderPage = () => { }, [error, errorApi]); return ( - +
{ - const theme = pageTheme[type] ?? pageTheme.other; + const theme = + customPageTheme.pageTheme[type] ?? customPageTheme.pageTheme.other; const classes = useStyles({ backgroundImage: theme.backgroundImage }); const href = generatePath(templateRoute.path, { templateName: name }); diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index e68c795bd6..5067f7963d 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { customPageTheme } from '@backstage/theme'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { Content, @@ -22,7 +23,6 @@ import { Lifecycle, Page, useApi, - pageTheme, } from '@backstage/core'; import { catalogApiRef } from '@backstage/plugin-catalog'; import { LinearProgress } from '@material-ui/core'; @@ -145,7 +145,7 @@ export const TemplatePage = () => { } return ( - +
= () => { const sentryProjectId = 'sample-sentry-project-id'; return ( - +
diff --git a/plugins/tech-radar/src/components/RadarPage.tsx b/plugins/tech-radar/src/components/RadarPage.tsx index 519c9afeee..58dcaec9af 100644 --- a/plugins/tech-radar/src/components/RadarPage.tsx +++ b/plugins/tech-radar/src/components/RadarPage.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { customPageTheme } from '@backstage/theme'; import React from 'react'; import { Grid } from '@material-ui/core'; import { @@ -23,7 +24,6 @@ import { Header, HeaderLabel, SupportButton, - pageTheme, } from '@backstage/core'; import RadarComponent from '../components/RadarComponent'; import { TechRadarComponentProps } from '../api'; @@ -40,7 +40,7 @@ export const RadarPage = ({ pageTitle, ...props }: TechRadarPageProps): JSX.Element => ( - +
diff --git a/plugins/techdocs/src/reader/components/TechDocsHome.tsx b/plugins/techdocs/src/reader/components/TechDocsHome.tsx index c478a52127..11211e713c 100644 --- a/plugins/techdocs/src/reader/components/TechDocsHome.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsHome.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { customPageTheme } from '@backstage/theme'; import React from 'react'; import { useAsync } from 'react-use'; import { useNavigate, generatePath } from 'react-router-dom'; @@ -24,7 +25,6 @@ import { useApi, Content, Page, - pageTheme, Header, } from '@backstage/core'; import { catalogApiRef } from '@backstage/plugin-catalog'; @@ -43,7 +43,7 @@ export const TechDocsHome = () => { if (loading) { return ( - +
{ if (error) { return ( - +
{ } return ( - +
{ }; return ( - + { const profile = { givenName: '' }; return ( - +
Date: Sun, 4 Oct 2020 18:05:16 +0530 Subject: [PATCH 006/104] chore: update new consumer variables in example md file & update in handlebars template --- .../docs/tutorials/quickstart-app-plugin/ExampleComponent.md | 3 ++- .../src/components/ExampleComponent/ExampleComponent.tsx.hbs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md b/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md index 855608c8b1..ed4bfc8012 100644 --- a/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md +++ b/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md @@ -3,6 +3,7 @@ ExampleComponent.tsx reference ```tsx +import { customPageTheme } from '@backstage/theme'; import React, { FC } from 'react'; import { Typography, Grid } from '@material-ui/core'; import { @@ -25,7 +26,7 @@ const ExampleComponent: FC<{}> = () => { const profile = identityApi.getProfile(); return ( - +
= () => ( - +
From 773e521301bcbeb1958a15ef9da2f226e461372f Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Sun, 4 Oct 2020 20:53:18 +0530 Subject: [PATCH 007/104] fix: e2e, storybook & unit tests Header component required ThemeProvider as wrapper to work properly --- .../ExampleComponent/ExampleComponent.tsx.hbs | 1 - .../core/src/layout/Header/Header.stories.tsx | 8 +- .../core/src/layout/Header/Header.test.tsx | 86 +++++++++++++------ 3 files changed, 67 insertions(+), 28 deletions(-) diff --git a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs index 5b15ab0c70..a050ccd2ec 100644 --- a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs +++ b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs @@ -5,7 +5,6 @@ import { InfoCard, Header, Page, - pageTheme, Content, ContentHeader, HeaderLabel, diff --git a/packages/core/src/layout/Header/Header.stories.tsx b/packages/core/src/layout/Header/Header.stories.tsx index cc0276224a..b902a709cf 100644 --- a/packages/core/src/layout/Header/Header.stories.tsx +++ b/packages/core/src/layout/Header/Header.stories.tsx @@ -41,9 +41,11 @@ export const Home = () => ( ); export const HomeWithSubtitle = () => ( -
- {labels} -
+ +
+ {labels} +
+
); export const Tool = () => ( diff --git a/packages/core/src/layout/Header/Header.test.tsx b/packages/core/src/layout/Header/Header.test.tsx index 5d28c6633f..00c5bfa2bd 100644 --- a/packages/core/src/layout/Header/Header.test.tsx +++ b/packages/core/src/layout/Header/Header.test.tsx @@ -15,8 +15,10 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; +import { act, render, RenderResult } from '@testing-library/react'; import { wrapInTestApp } from '@backstage/test-utils'; +import { ThemeProvider } from '@material-ui/core'; +import { customPageTheme } from '@backstage/theme'; import { Header } from './Header'; jest.mock('react-helmet', () => { @@ -25,37 +27,73 @@ jest.mock('react-helmet', () => { }; }); +const ThemeProviderWrapper: React.FC = ({ children }) => ( + + {children} + +); + describe('
', () => { - it('should render with title', () => { - const rendered = render(wrapInTestApp(
)); - rendered.getByText('Title'); + it('should render with title', async () => { + let rendered: RenderResult | undefined; + await act(async () => { + rendered = render(wrapInTestApp(
), { + wrapper: ThemeProviderWrapper, + }); + }); + rendered?.getByText('Title'); }); - it('should set document title', () => { - const rendered = render(wrapInTestApp(
)); - rendered.getByText('Title1'); - rendered.getByText('defaultTitle: Title1 | Backstage'); + it('should set document title', async () => { + let rendered: RenderResult | undefined; + await act(async () => { + rendered = render(wrapInTestApp(
), { + wrapper: ThemeProviderWrapper, + }); + }); + rendered?.getByText('Title1'); + rendered?.getByText('defaultTitle: Title1 | Backstage'); }); - it('should override document title', () => { - const rendered = render( - wrapInTestApp(
), - ); - rendered.getByText('Title1'); - rendered.getByText('defaultTitle: Title2 | Backstage'); + it('should override document title', async () => { + let rendered: RenderResult | undefined; + await act(async () => { + rendered = render( + wrapInTestApp(
), + { + wrapper: ThemeProviderWrapper, + }, + ); + }); + rendered?.getByText('Title1'); + rendered?.getByText('defaultTitle: Title2 | Backstage'); }); - it('should have subtitle', () => { - const rendered = render( - wrapInTestApp(
), - ); - rendered.getByText('Subtitle'); + it('should have subtitle', async () => { + let rendered: RenderResult | undefined; + await act(async () => { + rendered = render( + wrapInTestApp(
), + { + wrapper: ThemeProviderWrapper, + }, + ); + }); + rendered?.getByText('Subtitle'); }); - it('should have type rendered', () => { - const rendered = render( - wrapInTestApp(
), - ); - rendered.getByText('tool'); + it('should have type rendered', async () => { + let rendered: RenderResult | undefined; + await act(async () => { + rendered = render(wrapInTestApp(
), { + wrapper: ThemeProviderWrapper, + }); + }); + rendered?.getByText('tool'); }); }); From fd842a09482439e3db3e33bc92fbecc73b77e8da Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Sun, 4 Oct 2020 22:00:05 +0530 Subject: [PATCH 008/104] chore: use home as default pageTheme to reduce complexity --- .../core/src/layout/Header/Header.stories.tsx | 8 +- .../core/src/layout/Header/Header.test.tsx | 76 +++++-------------- packages/core/src/layout/Header/Header.tsx | 10 ++- 3 files changed, 30 insertions(+), 64 deletions(-) diff --git a/packages/core/src/layout/Header/Header.stories.tsx b/packages/core/src/layout/Header/Header.stories.tsx index b902a709cf..cc0276224a 100644 --- a/packages/core/src/layout/Header/Header.stories.tsx +++ b/packages/core/src/layout/Header/Header.stories.tsx @@ -41,11 +41,9 @@ export const Home = () => ( ); export const HomeWithSubtitle = () => ( - -
- {labels} -
-
+
+ {labels} +
); export const Tool = () => ( diff --git a/packages/core/src/layout/Header/Header.test.tsx b/packages/core/src/layout/Header/Header.test.tsx index 00c5bfa2bd..e5dc7fec4d 100644 --- a/packages/core/src/layout/Header/Header.test.tsx +++ b/packages/core/src/layout/Header/Header.test.tsx @@ -15,10 +15,8 @@ */ import React from 'react'; -import { act, render, RenderResult } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { wrapInTestApp } from '@backstage/test-utils'; -import { ThemeProvider } from '@material-ui/core'; -import { customPageTheme } from '@backstage/theme'; import { Header } from './Header'; jest.mock('react-helmet', () => { @@ -27,73 +25,37 @@ jest.mock('react-helmet', () => { }; }); -const ThemeProviderWrapper: React.FC = ({ children }) => ( - - {children} - -); - describe('
', () => { it('should render with title', async () => { - let rendered: RenderResult | undefined; - await act(async () => { - rendered = render(wrapInTestApp(
), { - wrapper: ThemeProviderWrapper, - }); - }); - rendered?.getByText('Title'); + const rendered = render(wrapInTestApp(
)); + rendered.getByText('Title'); }); it('should set document title', async () => { - let rendered: RenderResult | undefined; - await act(async () => { - rendered = render(wrapInTestApp(
), { - wrapper: ThemeProviderWrapper, - }); - }); - rendered?.getByText('Title1'); - rendered?.getByText('defaultTitle: Title1 | Backstage'); + const rendered = render(wrapInTestApp(
)); + rendered.getByText('Title1'); + rendered.getByText('defaultTitle: Title1 | Backstage'); }); it('should override document title', async () => { - let rendered: RenderResult | undefined; - await act(async () => { - rendered = render( - wrapInTestApp(
), - { - wrapper: ThemeProviderWrapper, - }, - ); - }); - rendered?.getByText('Title1'); - rendered?.getByText('defaultTitle: Title2 | Backstage'); + const rendered = render( + wrapInTestApp(
), + ); + rendered.getByText('Title1'); + rendered.getByText('defaultTitle: Title2 | Backstage'); }); it('should have subtitle', async () => { - let rendered: RenderResult | undefined; - await act(async () => { - rendered = render( - wrapInTestApp(
), - { - wrapper: ThemeProviderWrapper, - }, - ); - }); - rendered?.getByText('Subtitle'); + const rendered = render( + wrapInTestApp(
), + ); + rendered.getByText('Subtitle'); }); it('should have type rendered', async () => { - let rendered: RenderResult | undefined; - await act(async () => { - rendered = render(wrapInTestApp(
), { - wrapper: ThemeProviderWrapper, - }); - }); - rendered?.getByText('tool'); + const rendered = render( + wrapInTestApp(
), + ); + rendered.getByText('tool'); }); }); diff --git a/packages/core/src/layout/Header/Header.tsx b/packages/core/src/layout/Header/Header.tsx index dc71fe333b..41840b6ff0 100644 --- a/packages/core/src/layout/Header/Header.tsx +++ b/packages/core/src/layout/Header/Header.tsx @@ -17,7 +17,11 @@ import React, { ReactNode, CSSProperties, FC } from 'react'; import { Helmet } from 'react-helmet'; import { Typography, Tooltip, makeStyles, useTheme } from '@material-ui/core'; -import { BackstageTheme, BackstagePageThemeConsumer } from '@backstage/theme'; +import { + BackstageTheme, + BackstagePageThemeConsumer, + customPageTheme, +} from '@backstage/theme'; const useStyles = makeStyles( theme => ({ @@ -163,7 +167,9 @@ export const Header: FC = ({ }) => { const theme = useTheme(); const classes = useStyles({ - backgroundImage: theme.pageTheme.backgroundImage, + backgroundImage: + theme.pageTheme?.backgroundImage ?? + customPageTheme.pageTheme.home.backgroundImage, }); const documentTitle = pageTitleOverride || title; const pageTitle = title || pageTitleOverride; From 837ae7719729c2efe89ea9cc58a816054ba37d62 Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Sun, 11 Oct 2020 19:10:41 +0530 Subject: [PATCH 009/104] feat: strategy to add page them in top level theme provider --- packages/theme/src/baseTheme.ts | 29 ++++++++++++++--------------- packages/theme/src/index.ts | 1 + packages/theme/src/themes.ts | 10 +++------- packages/theme/src/types.ts | 24 +++++++++--------------- 4 files changed, 27 insertions(+), 37 deletions(-) diff --git a/packages/theme/src/baseTheme.ts b/packages/theme/src/baseTheme.ts index a3fded7e04..1f0e01586e 100644 --- a/packages/theme/src/baseTheme.ts +++ b/packages/theme/src/baseTheme.ts @@ -19,20 +19,28 @@ import { darken, lighten } from '@material-ui/core/styles/colorManipulator'; import { Overrides } from '@material-ui/core/styles/overrides'; import { - BackstagePageTheme, - BackstagePageThemeOptions, BackstageTheme, BackstageThemeOptions, SimpleThemeOptions, } from './types'; +import { pageTheme } from './pageTheme'; + const DEFAULT_FONT_FAMILY = '"Helvetica Neue", Helvetica, Roboto, Arial, sans-serif'; export function createThemeOptions( options: SimpleThemeOptions, ): BackstageThemeOptions { - const { palette, fontFamily = DEFAULT_FONT_FAMILY } = options; + const { + palette, + fontFamily = DEFAULT_FONT_FAMILY, + defaultPageTheme, + } = options; + + if (!pageTheme[defaultPageTheme]) { + throw new Error(`${defaultPageTheme} is not defined in pageTheme.`); + } return { palette, @@ -70,6 +78,9 @@ export function createThemeOptions( marginBottom: 10, }, }, + page: pageTheme[defaultPageTheme], + getPageTheme: ({ themeId }) => + pageTheme[themeId] ?? pageTheme[defaultPageTheme], }; } @@ -244,15 +255,3 @@ export function createTheme(options: SimpleThemeOptions): BackstageTheme { const theme = { ...baseTheme, overrides }; return theme; } - -export function createPageTheme( - options: BackstagePageThemeOptions, -): BackstagePageTheme { - const baseTheme = createTheme(options); - const pageTheme = options.pageTheme; - - return { - pageTheme, - baseTheme, - }; -} diff --git a/packages/theme/src/index.ts b/packages/theme/src/index.ts index 862f9b7755..cafad3fc7c 100644 --- a/packages/theme/src/index.ts +++ b/packages/theme/src/index.ts @@ -17,3 +17,4 @@ export * from './themes'; export * from './baseTheme'; export * from './types'; +export * from './pageTheme'; diff --git a/packages/theme/src/themes.ts b/packages/theme/src/themes.ts index 78a68c8f13..3838ed01cc 100644 --- a/packages/theme/src/themes.ts +++ b/packages/theme/src/themes.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { createTheme, createPageTheme } from './baseTheme'; -import { pageTheme } from './pageTheme'; +import { createTheme } from './baseTheme'; import { yellow } from '@material-ui/core/colors'; export const lightTheme = createTheme({ @@ -74,6 +73,7 @@ export const lightTheme = createTheme({ indicator: '#9BF0E1', }, }, + defaultPageTheme: 'home', }); export const darkTheme = createTheme({ @@ -132,9 +132,5 @@ export const darkTheme = createTheme({ indicator: '#9BF0E1', }, }, -}); - -export const customPageTheme = createPageTheme({ - pageTheme, - palette: lightTheme.palette, + defaultPageTheme: 'home', }); diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts index 3d3943392a..b5a07e78c5 100644 --- a/packages/theme/src/types.ts +++ b/packages/theme/src/types.ts @@ -72,12 +72,20 @@ type PaletteAdditions = { export type BackstagePalette = Palette & PaletteAdditions; export type BackstagePaletteOptions = PaletteOptions & PaletteAdditions; +export type pageThemeOptions = { + themeId: string; +}; + export interface BackstageTheme extends Theme { palette: BackstagePalette; + page: PageTheme; + getPageTheme: ({ themeId }: pageThemeOptions) => PageTheme; } export interface BackstageThemeOptions extends ThemeOptions { palette: BackstagePaletteOptions; + page: PageTheme; + getPageTheme: ({ themeId }: pageThemeOptions) => PageTheme; } /** @@ -85,6 +93,7 @@ export interface BackstageThemeOptions extends ThemeOptions { */ export type SimpleThemeOptions = { palette: BackstagePaletteOptions; + defaultPageTheme: string; fontFamily?: string; }; @@ -93,18 +102,3 @@ export type PageTheme = { shape: string; backgroundImage: string; }; - -export type BackstagePage = Record; - -export interface BackstagePageThemeOptions extends SimpleThemeOptions { - pageTheme: BackstagePage; -} - -export interface BackstagePageTheme { - pageTheme: BackstagePage; - baseTheme: BackstageTheme; -} - -export interface BackstagePageThemeConsumer extends BackstageTheme { - pageTheme: PageTheme; -} From 886bff9d44a373778ea4b92fd20887d211d63488 Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Sun, 11 Oct 2020 19:12:48 +0530 Subject: [PATCH 010/104] feat: update theme provider in Page component, to override page theme in children --- packages/core/src/layout/Page/Page.stories.tsx | 6 +++--- packages/core/src/layout/Page/Page.tsx | 11 ++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/core/src/layout/Page/Page.stories.tsx b/packages/core/src/layout/Page/Page.stories.tsx index 427dc644d9..998f799b56 100644 --- a/packages/core/src/layout/Page/Page.stories.tsx +++ b/packages/core/src/layout/Page/Page.stories.tsx @@ -15,7 +15,7 @@ */ import React, { useState } from 'react'; -import { customPageTheme } from '@backstage/theme'; +import { pageTheme } from '@backstage/theme'; import { Header, Page, @@ -196,7 +196,7 @@ export const PluginWithData = () => { const [selectedTab, setSelectedTab] = useState(2); return (
- + { export const PluginWithTable = () => { return (
- + diff --git a/packages/core/src/layout/Page/Page.tsx b/packages/core/src/layout/Page/Page.tsx index f4dc4888ed..4bc1917659 100644 --- a/packages/core/src/layout/Page/Page.tsx +++ b/packages/core/src/layout/Page/Page.tsx @@ -15,7 +15,7 @@ */ import React, { FC } from 'react'; -import { customPageTheme, PageTheme } from '@backstage/theme'; +import { PageTheme } from '@backstage/theme'; import { makeStyles, ThemeProvider } from '@material-ui/core'; const useStyles = makeStyles(() => ({ @@ -30,16 +30,13 @@ const useStyles = makeStyles(() => ({ })); type Props = { - pageTheme?: PageTheme; + theme: PageTheme; }; -export const Page: FC = ({ - pageTheme = customPageTheme.pageTheme.home, - children, -}) => { +export const Page: FC = ({ theme, children }) => { const classes = useStyles(); return ( - + ({ ...baseTheme, page: theme })}>
{children}
); From 6d68f6fec17429764cd55d0b84fcd9f1bbde9140 Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Sun, 11 Oct 2020 19:14:23 +0530 Subject: [PATCH 011/104] feat: consume theme provided by Page theme provider --- .../core/src/layout/Header/Header.stories.tsx | 18 +++---- .../core/src/layout/Header/Header.test.tsx | 10 ++-- packages/core/src/layout/Header/Header.tsx | 12 ++--- .../core/src/layout/SignInPage/SignInPage.tsx | 6 ++- .../ApiEntityPage/ApiEntityPage.test.tsx | 33 +----------- .../ApiEntityPage/ApiEntityPage.tsx | 17 +++--- .../ApiExplorerPage/ApiExplorerLayout.tsx | 11 +++- .../components/CatalogPage/CatalogLayout.tsx | 6 ++- .../EntityPageLayout/EntityPageLayout.tsx | 19 ++++--- .../AlertInstructionsLayout.tsx | 17 ++++-- .../CostInsightsLayout/CostInsightsLayout.tsx | 11 ++-- .../src/components/ExplorePluginPage.tsx | 12 +++-- .../NewProjectPage/NewProjectPage.tsx | 12 +++-- .../ProjectDetailsPage/ProjectDetailsPage.tsx | 10 +++- .../ProjectListPage/ProjectListPage.tsx | 11 +++- .../components/ClusterList/ClusterList.tsx | 11 ++-- .../components/ClusterPage/ClusterPage.tsx | 11 ++-- .../ProfileCatalog/ProfileCatalog.tsx | 12 +++-- .../components/GraphiQLPage/GraphiQLPage.tsx | 11 ++-- .../KubernetesContent/KubernetesContent.tsx | 11 ++-- .../src/components/AuditList/index.tsx | 11 ++-- .../src/components/AuditView/index.tsx | 10 +++- .../src/components/CreateAudit/index.tsx | 10 +++- .../NewRelicComponent/NewRelicComponent.tsx | 48 +++++++++-------- .../RegisterComponentPage.tsx | 11 ++-- .../components/RollbarHome/RollbarHome.tsx | 10 +++- .../RollbarProjectPage/RollbarProjectPage.tsx | 10 +++- .../ScaffolderPage/ScaffolderPage.tsx | 7 +-- .../components/TemplateCard/TemplateCard.tsx | 16 ++++-- .../components/TemplatePage/TemplatePage.tsx | 7 +-- .../SentryPluginPage/SentryPluginPage.tsx | 11 ++-- .../tech-radar/src/components/RadarPage.tsx | 54 ++++++++++--------- .../src/reader/components/TechDocsHome.tsx | 23 ++++++-- .../src/reader/components/TechDocsPage.tsx | 10 +++- .../components/WelcomePage/WelcomePage.tsx | 10 +++- 35 files changed, 320 insertions(+), 189 deletions(-) diff --git a/packages/core/src/layout/Header/Header.stories.tsx b/packages/core/src/layout/Header/Header.stories.tsx index cc0276224a..a99e9e235f 100644 --- a/packages/core/src/layout/Header/Header.stories.tsx +++ b/packages/core/src/layout/Header/Header.stories.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { pageTheme } from '@backstage/theme'; import React from 'react'; import { Header } from '.'; import { HeaderLabel } from '../HeaderLabel'; @@ -33,7 +33,7 @@ const labels = ( ); export const Home = () => ( - +
{labels}
@@ -47,7 +47,7 @@ export const HomeWithSubtitle = () => ( ); export const Tool = () => ( - +
{labels}
@@ -55,7 +55,7 @@ export const Tool = () => ( ); export const Service = () => ( - +
{labels}
@@ -63,7 +63,7 @@ export const Service = () => ( ); export const Website = () => ( - +
{labels}
@@ -71,7 +71,7 @@ export const Website = () => ( ); export const Library = () => ( - +
{labels}
@@ -79,7 +79,7 @@ export const Library = () => ( ); export const App = () => ( - +
{labels}
@@ -87,7 +87,7 @@ export const App = () => ( ); export const Documentation = () => ( - +
{labels}
@@ -95,7 +95,7 @@ export const Documentation = () => ( ); export const Other = () => ( - +
{labels}
diff --git a/packages/core/src/layout/Header/Header.test.tsx b/packages/core/src/layout/Header/Header.test.tsx index e5dc7fec4d..5d28c6633f 100644 --- a/packages/core/src/layout/Header/Header.test.tsx +++ b/packages/core/src/layout/Header/Header.test.tsx @@ -26,18 +26,18 @@ jest.mock('react-helmet', () => { }); describe('
', () => { - it('should render with title', async () => { + it('should render with title', () => { const rendered = render(wrapInTestApp(
)); rendered.getByText('Title'); }); - it('should set document title', async () => { + it('should set document title', () => { const rendered = render(wrapInTestApp(
)); rendered.getByText('Title1'); rendered.getByText('defaultTitle: Title1 | Backstage'); }); - it('should override document title', async () => { + it('should override document title', () => { const rendered = render( wrapInTestApp(
), ); @@ -45,14 +45,14 @@ describe('
', () => { rendered.getByText('defaultTitle: Title2 | Backstage'); }); - it('should have subtitle', async () => { + it('should have subtitle', () => { const rendered = render( wrapInTestApp(
), ); rendered.getByText('Subtitle'); }); - it('should have type rendered', async () => { + it('should have type rendered', () => { const rendered = render( wrapInTestApp(
), ); diff --git a/packages/core/src/layout/Header/Header.tsx b/packages/core/src/layout/Header/Header.tsx index 41840b6ff0..6fadd0c834 100644 --- a/packages/core/src/layout/Header/Header.tsx +++ b/packages/core/src/layout/Header/Header.tsx @@ -17,11 +17,7 @@ import React, { ReactNode, CSSProperties, FC } from 'react'; import { Helmet } from 'react-helmet'; import { Typography, Tooltip, makeStyles, useTheme } from '@material-ui/core'; -import { - BackstageTheme, - BackstagePageThemeConsumer, - customPageTheme, -} from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; const useStyles = makeStyles( theme => ({ @@ -165,11 +161,9 @@ export const Header: FC = ({ type, typeLink, }) => { - const theme = useTheme(); + const backstageTheme = useTheme(); const classes = useStyles({ - backgroundImage: - theme.pageTheme?.backgroundImage ?? - customPageTheme.pageTheme.home.backgroundImage, + backgroundImage: backstageTheme.page.backgroundImage, }); const documentTitle = pageTitleOverride || title; const pageTitle = title || pageTitleOverride; diff --git a/packages/core/src/layout/SignInPage/SignInPage.tsx b/packages/core/src/layout/SignInPage/SignInPage.tsx index 8a1ff68250..b2b049d161 100644 --- a/packages/core/src/layout/SignInPage/SignInPage.tsx +++ b/packages/core/src/layout/SignInPage/SignInPage.tsx @@ -19,7 +19,8 @@ import { Page } from '../Page'; import { Header } from '../Header'; import { Content } from '../Content/Content'; import { ContentHeader } from '../ContentHeader/ContentHeader'; -import { Grid } from '@material-ui/core'; +import { BackstageTheme } from '@backstage/theme'; +import { Grid, useTheme } from '@material-ui/core'; import { SignInPageProps, useApi, configApiRef } from '@backstage/core-api'; import { useSignInProviders, getSignInProviders } from './providers'; import { IdentityProviders } from './types'; @@ -38,6 +39,7 @@ export const SignInPage: FC = ({ title, align = 'left', }) => { + const backstageTheme = useTheme(); const configApi = useApi(configApiRef); const classes = useStyles(); @@ -52,7 +54,7 @@ export const SignInPage: FC = ({ } return ( - +
{title && } diff --git a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.test.tsx b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.test.tsx index af5213e3f1..9a2bc0cfb1 100644 --- a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.test.tsx +++ b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.test.tsx @@ -13,14 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { Entity } from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog'; import { wrapInTestApp } from '@backstage/test-utils'; import { render, waitFor } from '@testing-library/react'; import * as React from 'react'; -import { ApiEntityPage, getPageTheme } from './ApiEntityPage'; +import { ApiEntityPage } from './ApiEntityPage'; jest.mock('react-router-dom', () => { const actual = jest.requireActual('react-router-dom'); @@ -71,32 +69,3 @@ describe('ApiEntityPage', () => { ); }); }); - -describe('getPageTheme', () => { - const defaultPageTheme = getPageTheme(); - it.each(['service', 'app', 'library', 'tool', 'documentation', 'website'])( - 'should select right theme for predefined type: %p ̰ ', - type => { - const theme = getPageTheme(({ - spec: { - type, - }, - } as any) as Entity); - expect(theme).toBeDefined(); - expect(theme).not.toBe(defaultPageTheme); - }, - ); - - it('should select default theme for unknown/unspecified types', () => { - const theme1 = getPageTheme(({ - spec: { - type: 'unknown-type', - }, - } as any) as Entity); - const theme2 = getPageTheme(({ - spec: {}, - } as any) as Entity); - expect(theme1).toBe(defaultPageTheme); - expect(theme2).toBe(defaultPageTheme); - }); -}); diff --git a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx index edfc3ab08b..85250a8569 100644 --- a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx +++ b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx @@ -23,9 +23,9 @@ import { Progress, useApi, } from '@backstage/core'; -import { customPageTheme, PageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import { catalogApiRef } from '@backstage/plugin-catalog'; -import { Box } from '@material-ui/core'; +import { Box, useTheme } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import React, { useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; @@ -53,12 +53,6 @@ function headerProps( }; } -// REUSABLE IN plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx -export const getPageTheme = (entity?: Entity): PageTheme => { - const themeKey = entity?.spec?.type?.toString() ?? 'home'; - return customPageTheme.pageTheme[themeKey] ?? customPageTheme.pageTheme.home; -}; - type EntityPageTitleProps = { title: string; entity: Entity | undefined; @@ -71,6 +65,7 @@ const EntityPageTitle = ({ title }: EntityPageTitleProps) => ( ); export const ApiEntityPage = () => { + const backstageTheme = useTheme(); const { optionalNamespaceAndName } = useParams() as { optionalNamespaceAndName: string; }; @@ -107,7 +102,11 @@ export const ApiEntityPage = () => { ); return ( - +
} pageTitleOverride={headerTitle} diff --git a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx index 53b50f1826..410432d715 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx @@ -15,7 +15,8 @@ */ import { Header, Page } from '@backstage/core'; -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; +import { useTheme } from '@material-ui/core'; import React from 'react'; type Props = { @@ -23,8 +24,14 @@ type Props = { }; export const ApiExplorerLayout = ({ children }: Props) => { + const backstageTheme = useTheme(); + return ( - +
{ + const backstageTheme = useTheme(); const greeting = getTimeBasedGreeting(); const profile = useApi(identityApiRef).getProfile(); const userId = useApi(identityApiRef).getUserId(); return ( - +
{ - const themeKey = entity?.spec?.type?.toString() ?? 'home'; - return customPageTheme.pageTheme[themeKey] ?? customPageTheme.pageTheme.home; -}; +// const getPageTheme = (entity?: Entity): PageTheme => { +// const themeKey = entity?.spec?.type?.toString() ?? 'home'; +// return customPageTheme.pageTheme[themeKey] ?? customPageTheme.pageTheme.home; +// }; const EntityPageTitle = ({ entity, @@ -69,6 +69,7 @@ export const EntityPageLayout = ({ }: { children?: React.ReactNode; }) => { + const backstageTheme = useTheme(); const { optionalNamespaceAndName, kind } = useParams() as { optionalNamespaceAndName: string; kind: string; @@ -93,7 +94,11 @@ export const EntityPageLayout = ({ const showRemovalDialog = () => setConfirmationDialogOpen(true); return ( - +
} pageTitleOverride={headerTitle} diff --git a/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx b/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx index c0777fc844..d09b130b70 100644 --- a/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx +++ b/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx @@ -14,9 +14,15 @@ * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import React, { ReactNode } from 'react'; -import { Box, Button, Container, makeStyles } from '@material-ui/core'; +import { + Box, + Button, + Container, + makeStyles, + useTheme, +} from '@material-ui/core'; import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; import { Header, Page } from '@backstage/core'; import { CostInsightsThemeProvider } from '../CostInsightsPage/CostInsightsThemeProvider'; @@ -37,10 +43,15 @@ const AlertInstructionsLayout = ({ title, children, }: AlertInstructionsLayoutProps) => { + const backstageTheme = useTheme(); const classes = useStyles(); return ( - +
diff --git a/plugins/cost-insights/src/components/CostInsightsLayout/CostInsightsLayout.tsx b/plugins/cost-insights/src/components/CostInsightsLayout/CostInsightsLayout.tsx index c95f624bbe..068174cd68 100644 --- a/plugins/cost-insights/src/components/CostInsightsLayout/CostInsightsLayout.tsx +++ b/plugins/cost-insights/src/components/CostInsightsLayout/CostInsightsLayout.tsx @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import React from 'react'; -import { makeStyles } from '@material-ui/core'; +import { makeStyles, useTheme } from '@material-ui/core'; import { Header, Page } from '@backstage/core'; import { Group } from '../../types'; import CostInsightsTabs from '../CostInsightsTabs'; @@ -39,9 +39,14 @@ type CostInsightsLayoutProps = { }; const CostInsightsLayout = ({ groups, children }: CostInsightsLayoutProps) => { + const backstageTheme = useTheme(); const classes = useStyles(); return ( - +
(theme => ({ container: { @@ -115,8 +115,14 @@ const toolsCards = [ export const ExplorePluginPage = () => { const classes = useStyles(); + const backstageTheme = useTheme(); + return ( - +
{ @@ -112,8 +112,14 @@ const labels = ( ); export const NewProjectPage = () => { + const backstageTheme = useTheme(); + return ( - +
{labels}
diff --git a/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx b/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx index e310c27c68..eb86b6fd03 100644 --- a/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx +++ b/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import { Content, ContentHeader, @@ -36,6 +36,7 @@ import { TableRow, Theme, Typography, + useTheme, } from '@material-ui/core'; import React from 'react'; import { useAsync } from 'react-use'; @@ -147,8 +148,13 @@ const labels = ( ); export const ProjectDetailsPage = () => { + const backstageTheme = useTheme(); return ( - +
{labels}
diff --git a/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx b/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx index 5a404dbb01..33b3813c2a 100644 --- a/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx +++ b/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx @@ -15,7 +15,7 @@ */ // NEEDS WORK -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import { Content, ContentHeader, @@ -38,6 +38,7 @@ import { TableRow, Tooltip, Typography, + useTheme, } from '@material-ui/core'; import React from 'react'; import { useAsync } from 'react-use'; @@ -134,8 +135,14 @@ const PageContents = () => { }; export const ProjectListPage = () => { + const backstageTheme = useTheme(); + return ( - +
{labels}
diff --git a/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx b/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx index 8926ed3260..3b33320abe 100644 --- a/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx +++ b/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import React, { FC, useState } from 'react'; import { Content, @@ -29,12 +29,13 @@ import { } from '@backstage/core'; import ClusterTable from '../ClusterTable/ClusterTable'; -import { Button } from '@material-ui/core'; +import { Button, useTheme } from '@material-ui/core'; import { useAsync } from 'react-use'; import { gitOpsApiRef } from '../../api'; import { Alert } from '@material-ui/lab'; const ClusterList: FC<{}> = () => { + const backstageTheme = useTheme(); const api = useApi(gitOpsApiRef); const githubAuth = useApi(githubAuthApiRef); const [githubUsername, setGithubUsername] = useState(String); @@ -91,7 +92,11 @@ const ClusterList: FC<{}> = () => { } return ( - +
diff --git a/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx b/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx index d51fb7656e..f7205c3326 100644 --- a/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx +++ b/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import React, { FC, useEffect, useState } from 'react'; import { Content, @@ -26,12 +26,13 @@ import { githubAuthApiRef, } from '@backstage/core'; -import { Link } from '@material-ui/core'; +import { Link, useTheme } from '@material-ui/core'; import { useParams } from 'react-router-dom'; import { gitOpsApiRef, Status } from '../../api'; import { transformRunStatus } from '../ProfileCatalog'; const ClusterPage: FC<{}> = () => { + const backstageTheme = useTheme(); const params = useParams() as { owner: string; repo: string }; const [pollingLog, setPollingLog] = useState(true); @@ -84,7 +85,11 @@ const ClusterPage: FC<{}> = () => { }, [pollingLog, api, params, githubAuth, githubAccessToken, githubUsername]); return ( - +
diff --git a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx index 078e1b7601..fe1e97235a 100644 --- a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx +++ b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import React, { FC, useEffect, useState } from 'react'; import { Header, @@ -37,7 +37,7 @@ import { useApi, githubAuthApiRef, } from '@backstage/core'; -import { TextField, List, ListItem, Link } from '@material-ui/core'; +import { TextField, List, ListItem, Link, useTheme } from '@material-ui/core'; import ClusterTemplateCardList from '../ClusterTemplateCardList'; import ProfileCardList from '../ProfileCardList'; @@ -81,6 +81,8 @@ export const transformRunStatus = (x: Status[]) => { }; const ProfileCatalog: FC<{}> = () => { + const backstageTheme = useTheme(); + // TODO: get data from REST API const [clusterTemplates] = React.useState([ { @@ -259,7 +261,11 @@ const ProfileCatalog: FC<{}> = () => { ]; return ( - +
{ + const backstageTheme = useTheme(); const graphQlBrowseApi = useApi(graphQlBrowseApiRef); const endpoints = useAsync(() => graphQlBrowseApi.getEndpoints()); @@ -59,7 +60,11 @@ export const GraphiQLPage = () => { } return ( - +
diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index 381b942f3d..70edc167f3 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -14,9 +14,9 @@ * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import React, { ReactElement, useEffect, useState } from 'react'; -import { Grid, TabProps } from '@material-ui/core'; +import { Grid, TabProps, useTheme } from '@material-ui/core'; import { CardTab, Content, @@ -98,6 +98,7 @@ const groupResponses = (fetchResponse: FetchResponse[]) => { type KubernetesContentProps = { entity: Entity; children?: React.ReactNode }; export const KubernetesContent = ({ entity }: KubernetesContentProps) => { + const backstageTheme = useTheme(); const kubernetesApi = useApi(kubernetesApiRef); const [kubernetesObjects, setKubernetesObjects] = useState< @@ -120,7 +121,11 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { kubernetesObjects?.items.filter(r => r.errors.length > 0) ?? []; return ( - + {kubernetesObjects === undefined && error === undefined && ( diff --git a/plugins/lighthouse/src/components/AuditList/index.tsx b/plugins/lighthouse/src/components/AuditList/index.tsx index 9c38e27fe3..cae1761ecb 100644 --- a/plugins/lighthouse/src/components/AuditList/index.tsx +++ b/plugins/lighthouse/src/components/AuditList/index.tsx @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import React, { useState, useMemo, FC, ReactNode } from 'react'; import { useLocalStorage, useAsync } from 'react-use'; import { useNavigate } from 'react-router-dom'; -import { Grid, Button } from '@material-ui/core'; +import { Grid, Button, useTheme } from '@material-ui/core'; import Alert from '@material-ui/lab/Alert'; import Pagination from '@material-ui/lab/Pagination'; import { @@ -41,6 +41,7 @@ import { createAuditRouteRef } from '../../plugin'; export const LIMIT = 10; const AuditList: FC<{}> = () => { + const backstageTheme = useTheme(); const [dismissedStored] = useLocalStorage(LIGHTHOUSE_INTRO_LOCAL_STORAGE); const [dismissed, setDismissed] = useState(dismissedStored); @@ -94,7 +95,11 @@ const AuditList: FC<{}> = () => { } return ( - +
= ({ audit }: { audit?: Audit }) => { }; const ConnectedAuditView: FC<{}> = () => { + const backstageTheme = useTheme(); const lighthouseApi = useApi(lighthouseApiRef); const params = useParams() as { id: string }; const classes = useStyles(); @@ -173,7 +175,11 @@ const ConnectedAuditView: FC<{}> = () => { } return ( - +
({ })); const CreateAudit: FC<{}> = () => { + const backstageTheme = useTheme(); const errorApi = useApi(errorApiRef); const lighthouseApi = useApi(lighthouseApiRef); const classes = useStyles(); @@ -94,7 +96,11 @@ const CreateAudit: FC<{}> = () => { ]); return ( - +
= () => ( - -
- -
- - - - New Relic Application Performance Monitoring - - - - - +const NewRelicComponent: FC<{}> = () => { + const backstageTheme = useTheme(); + return ( + +
+ +
+ + + + New Relic Application Performance Monitoring + + + + + + -
-
-
-); - + + + ); +}; export default NewRelicComponent; diff --git a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx index 5e4da3fc35..722e2e1964 100644 --- a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx +++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx @@ -14,9 +14,9 @@ * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import React, { useState } from 'react'; -import { Grid, makeStyles } from '@material-ui/core'; +import { Grid, makeStyles, useTheme } from '@material-ui/core'; import { InfoCard, Page, @@ -61,6 +61,7 @@ export const RegisterComponentPage = ({ }: { catalogRouteRef: RouteRef; }) => { + const backstageTheme = useTheme(); const classes = useStyles(); const catalogApi = useApi(catalogApiRef); const [formState, setFormState] = useState>( @@ -113,7 +114,11 @@ export const RegisterComponentPage = ({ }; return ( - +
diff --git a/plugins/rollbar/src/components/RollbarHome/RollbarHome.tsx b/plugins/rollbar/src/components/RollbarHome/RollbarHome.tsx index 9f0deca967..f219be9615 100644 --- a/plugins/rollbar/src/components/RollbarHome/RollbarHome.tsx +++ b/plugins/rollbar/src/components/RollbarHome/RollbarHome.tsx @@ -14,17 +14,23 @@ * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; +import { useTheme } from '@material-ui/core'; import React from 'react'; import { Content, Header, Page } from '@backstage/core'; import { RollbarProjectTable } from '../RollbarProjectTable/RollbarProjectTable'; import { useRollbarEntities } from '../../hooks/useRollbarEntities'; export const RollbarHome = () => { + const backstageTheme = useTheme(); const { entities, loading, error } = useRollbarEntities(); return ( - +
{ + const backstageTheme = useTheme(); const { entity } = useCatalogEntity(); return ( - +
diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 1bd43ded44..bd70fd9565 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { Content, @@ -29,7 +29,7 @@ import { WarningPanel, } from '@backstage/core'; import { catalogApiRef } from '@backstage/plugin-catalog'; -import { Button, Grid, Link, Typography } from '@material-ui/core'; +import { Button, Grid, Link, Typography, useTheme } from '@material-ui/core'; import React, { useEffect } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import useStaleWhileRevalidate from 'swr'; @@ -49,6 +49,7 @@ const getTemplateCardProps = ( }; export const ScaffolderPage = () => { + const backstageTheme = useTheme(); const catalogApi = useApi(catalogApiRef); const errorApi = useApi(errorApiRef); @@ -66,7 +67,7 @@ export const ScaffolderPage = () => { }, [error, errorApi]); return ( - +
{ - const theme = - customPageTheme.pageTheme[type] ?? customPageTheme.pageTheme.other; + const backstageTheme = useTheme(); + + const themeId = pageTheme[type] ? type : 'other'; + const theme = backstageTheme.getPageTheme({ themeId }); const classes = useStyles({ backgroundImage: theme.backgroundImage }); const href = generatePath(templateRoute.path, { templateName: name }); diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 5067f7963d..7bfc1b78a2 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { Content, @@ -25,7 +25,7 @@ import { useApi, } from '@backstage/core'; import { catalogApiRef } from '@backstage/plugin-catalog'; -import { LinearProgress } from '@material-ui/core'; +import { LinearProgress, useTheme } from '@material-ui/core'; import { IChangeEvent } from '@rjsf/core'; import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; @@ -80,6 +80,7 @@ const REPO_FORMAT = { }; export const TemplatePage = () => { + const backstageTheme = useTheme(); const errorApi = useApi(errorApiRef); const catalogApi = useApi(catalogApiRef); const scaffolderApi = useApi(scaffolderApiRef); @@ -145,7 +146,7 @@ export const TemplatePage = () => { } return ( - +
= () => { + const backstageTheme = useTheme(); const [statsFor, setStatsFor] = useState<'12h' | '24h'>('12h'); const toggleStatsFor = () => setStatsFor(statsFor === '12h' ? '12h' : '24h'); const sentryProjectId = 'sample-sentry-project-id'; return ( - +
diff --git a/plugins/tech-radar/src/components/RadarPage.tsx b/plugins/tech-radar/src/components/RadarPage.tsx index 58dcaec9af..d23061b094 100644 --- a/plugins/tech-radar/src/components/RadarPage.tsx +++ b/plugins/tech-radar/src/components/RadarPage.tsx @@ -14,9 +14,9 @@ * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import React from 'react'; -import { Grid } from '@material-ui/core'; +import { Grid, useTheme } from '@material-ui/core'; import { Content, ContentHeader, @@ -39,29 +39,35 @@ export const RadarPage = ({ subtitle, pageTitle, ...props -}: TechRadarPageProps): JSX.Element => ( - -
- - -
- - - - This is used for visualizing the official guidelines of different - areas of software development such as languages, frameworks, - infrastructure and processes. - - - - - +}: TechRadarPageProps): JSX.Element => { + const backstageTheme = useTheme(); + return ( + +
+ + +
+ + + + This is used for visualizing the official guidelines of different + areas of software development such as languages, frameworks, + infrastructure and processes. + + + + + + -
-
-
-); - +
+ + ); +}; RadarPage.defaultProps = { title: 'Tech Radar', subtitle: 'Pick the recommended technologies for your projects', diff --git a/plugins/techdocs/src/reader/components/TechDocsHome.tsx b/plugins/techdocs/src/reader/components/TechDocsHome.tsx index 11211e713c..149117ec00 100644 --- a/plugins/techdocs/src/reader/components/TechDocsHome.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsHome.tsx @@ -14,11 +14,11 @@ * limitations under the License. */ -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import React from 'react'; import { useAsync } from 'react-use'; import { useNavigate, generatePath } from 'react-router-dom'; -import { Grid } from '@material-ui/core'; +import { Grid, useTheme } from '@material-ui/core'; import { ItemCard, Progress, @@ -31,6 +31,7 @@ import { catalogApiRef } from '@backstage/plugin-catalog'; import { rootDocsRouteRef } from '../../plugin'; export const TechDocsHome = () => { + const backstageTheme = useTheme(); const catalogApi = useApi(catalogApiRef); const navigate = useNavigate(); @@ -43,7 +44,11 @@ export const TechDocsHome = () => { if (loading) { return ( - +
{ if (error) { return ( - +
{ } return ( - +
{ + const backstageTheme = useTheme(); const [documentReady, setDocumentReady] = useState(false); const { entityId } = useParams(); const [kind, namespace, name] = entityId.split(':'); @@ -47,7 +49,11 @@ export const TechDocsPage = () => { }; return ( - + { + const backstageTheme = useTheme(); const appTitle = useApi(configApiRef).getOptionalString('app.title') ?? 'Backstage'; const profile = { givenName: '' }; return ( - +
Date: Sun, 11 Oct 2020 19:27:59 +0530 Subject: [PATCH 012/104] fix: Implement new strategy consumer --- plugins/user-settings/src/components/SettingsPage.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/user-settings/src/components/SettingsPage.tsx b/plugins/user-settings/src/components/SettingsPage.tsx index 86096e9a1f..e8e5cbdbfd 100644 --- a/plugins/user-settings/src/components/SettingsPage.tsx +++ b/plugins/user-settings/src/components/SettingsPage.tsx @@ -15,7 +15,9 @@ */ import React, { useState } from 'react'; -import { Content, Header, HeaderTabs, Page, pageTheme } from '@backstage/core'; +import { Content, Header, HeaderTabs, Page } from '@backstage/core'; +import { BackstageTheme } from '@backstage/theme'; +import { useTheme } from '@material-ui/core'; import { General } from './General'; import { AuthProviders } from './AuthProviders'; import { FeatureFlags } from './FeatureFlags'; @@ -25,6 +27,7 @@ type Props = { }; export const SettingsPage = ({ providerSettings }: Props) => { + const backstageTheme = useTheme(); const [activeTab, setActiveTab] = useState(0); const onTabChange = (index: number) => { setActiveTab(index); @@ -43,7 +46,11 @@ export const SettingsPage = ({ providerSettings }: Props) => { ]; return ( - +
{content[activeTab]} From cd66aed3d1b37c1a1cba2a56ed61cb3069f08c03 Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Sun, 11 Oct 2020 19:34:16 +0530 Subject: [PATCH 013/104] chore: remove comments --- .../src/components/EntityPageLayout/EntityPageLayout.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx b/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx index 68e871009a..4958a6f850 100644 --- a/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx +++ b/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx @@ -27,11 +27,6 @@ import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEnti import { Alert } from '@material-ui/lab'; import { Tabbed } from './Tabbed'; -// const getPageTheme = (entity?: Entity): PageTheme => { -// const themeKey = entity?.spec?.type?.toString() ?? 'home'; -// return customPageTheme.pageTheme[themeKey] ?? customPageTheme.pageTheme.home; -// }; - const EntityPageTitle = ({ entity, title, From a4aa277e9f4447e253bf9c58b0aabb0eeb73bea5 Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Sun, 11 Oct 2020 19:35:01 +0530 Subject: [PATCH 014/104] fix: Replace new theme strategy consumer in handlebar template & Example md file --- .../tutorials/quickstart-app-plugin/ExampleComponent.md | 7 ++++--- .../components/ExampleComponent/ExampleComponent.tsx.hbs | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md b/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md index ed4bfc8012..83b18bf4c2 100644 --- a/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md +++ b/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md @@ -3,9 +3,9 @@ ExampleComponent.tsx reference ```tsx -import { customPageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import React, { FC } from 'react'; -import { Typography, Grid } from '@material-ui/core'; +import { Typography, Grid, useTheme } from '@material-ui/core'; import { InfoCard, Header, @@ -21,12 +21,13 @@ import { useApi } from '@backstage/core-api'; import ExampleFetchComponent from '../ExampleFetchComponent'; const ExampleComponent: FC<{}> = () => { + const backstageTheme = useTheme(); const identityApi = useApi(identityApiRef); const userId = identityApi.getUserId(); const profile = identityApi.getProfile(); return ( - +
= () => ( - +
From fae67f2be1aa19f2caad112df92e5b93e68de009 Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Sun, 11 Oct 2020 20:08:08 +0530 Subject: [PATCH 015/104] chore: update example component --- .../ExampleComponent/ExampleComponent.tsx.hbs | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs index 9cdd22c0ca..d4be6d87ac 100644 --- a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs +++ b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs @@ -1,6 +1,6 @@ -import { BackstageTheme } from '@backstage/theme'; +import { pageTheme } from '@backstage/theme'; import React, { FC } from 'react'; -import { Typography, Grid, useTheme } from '@material-ui/core'; +import { Typography, Grid } from '@material-ui/core'; import { InfoCard, Header, @@ -12,30 +12,31 @@ import { } from '@backstage/core'; import ExampleFetchComponent from '../ExampleFetchComponent'; -const ExampleComponent: FC<{}> = () => ( - -
- - -
- - - A description of your plugin goes here. - - - - - - All content should be wrapped in a card like this. - - +const ExampleComponent: FC<{}> = () => { + return ( + +
+ + +
+ + + A description of your plugin goes here. + + + + + + All content should be wrapped in a card like this. + + + + + + - - - -
-
-
-); - + + + ); +} export default ExampleComponent; From 689b2f50e528d45c9d2e9b31da374b8d0b580f01 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Mon, 12 Oct 2020 14:27:59 +0200 Subject: [PATCH 016/104] Run vale linter only if vale is installed --- package.json | 3 +- scripts/check-docs-quality.js | 86 ++++++++++++++++++++--------------- yarn.lock | 5 ++ 3 files changed, 57 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index 371646074c..96e02e33f9 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@changesets/cli": "^2.11.0", "@spotify/eslint-config-oss": "^1.0.1", "@spotify/prettier-config": "^8.0.0", + "command-exists": "^1.2.9", "concurrently": "^5.2.0", "fs-extra": "^9.0.0", "husky": "^4.2.3", @@ -65,7 +66,7 @@ "prettier --write" ], "*.md": [ - "vale" + "node ./scripts/check-docs-quality" ] }, "jest": { diff --git a/scripts/check-docs-quality.js b/scripts/check-docs-quality.js index 61968a2ac0..4f5f689105 100755 --- a/scripts/check-docs-quality.js +++ b/scripts/check-docs-quality.js @@ -16,6 +16,8 @@ */ const { execSync, spawnSync } = require('child_process'); const path = require('path'); +// eslint-disable-next-line import/no-extraneous-dependencies +const commandExists = require('command-exists'); const listFilesTrackedByGit = 'git ls-files'; @@ -23,46 +25,58 @@ const inheritStdIo = { stdio: 'inherit', }; +const LINT_SKIPPED_MESSAGE = + 'Skipping documentation quality check (vale not found). Install vale linter (https://docs.errata.ai/vale/install) to enable.\n'; const ERROR_MESSAGE = - 'Please install vale linter(https://docs.errata.ai/vale/install). Ignore this message if already installed.\n'; + 'Language linter (vale) generated errors. Please check the errors and review any markdown files that you changed.\n'; -// xargs is not supported by shx. -if (process.platform === 'win32') { - const validMDFilesCommand = `${listFilesTrackedByGit} | .\\node_modules\\.bin\\shx grep ".md"`; - try { - // get list of all md files except in directories of gitignore. - let filesToLint = execSync(validMDFilesCommand, { - stdio: ['ignore', 'pipe', 'inherit'], - }); +// Proceed with the script only if Vale linter is installed. Limit the friction and surprises caused by the script. +commandExists('vale') + .catch(() => { + console.log(LINT_SKIPPED_MESSAGE); + process.exit(0); + }) + .then(() => { + // xargs is not supported by shx. + if (process.platform === 'win32') { + const validMDFilesCommand = `${listFilesTrackedByGit} | .\\node_modules\\.bin\\shx grep ".md"`; + try { + // get list of all md files except in directories of gitignore. + let filesToLint = execSync(validMDFilesCommand, { + stdio: ['ignore', 'pipe', 'inherit'], + }); - // set all file(s) path as absolute path - filesToLint = filesToLint - .toString() - .split('\n') - .map(filepath => (filepath ? path.join(process.cwd(), filepath) : null)) - .filter(Boolean); + // set all file(s) path as absolute path + filesToLint = filesToLint + .toString() + .split('\n') + .map(filepath => + filepath ? path.join(process.cwd(), filepath) : null, + ) + .filter(Boolean); - const output = spawnSync('vale', filesToLint, inheritStdIo); + const output = spawnSync('vale', filesToLint, inheritStdIo); - // if the command does not succeed - if (output.status !== 0) { - // if it contains system level error. [in this case vale does not exist] - if (output.error) { - console.error(ERROR_MESSAGE); + // if the command does not succeed + if (output.status !== 0) { + // if it contains system level error. [in this case vale does not exist] + if (output.error) { + console.error(ERROR_MESSAGE); + } + process.exit(1); + } + } catch (e) { + console.error(e.message); + process.exit(1); + } + } else { + const validMDFilesCommand = `${listFilesTrackedByGit} | ./node_modules/.bin/shx grep ".md"`; + // use xargs + try { + execSync(`${validMDFilesCommand} | xargs vale`, inheritStdIo); + } catch (e) { + console.error(ERROR_MESSAGE); + process.exit(1); } - process.exit(1); } - } catch (e) { - console.error(e.message); - process.exit(1); - } -} else { - const validMDFilesCommand = `${listFilesTrackedByGit} | ./node_modules/.bin/shx grep ".md"`; - // use xargs - try { - execSync(`${validMDFilesCommand} | xargs vale`, inheritStdIo); - } catch (e) { - console.error(ERROR_MESSAGE); - process.exit(1); - } -} + }); diff --git a/yarn.lock b/yarn.lock index 7dbc7f964a..859c26c6c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8537,6 +8537,11 @@ command-exists-promise@^2.0.2: resolved "https://registry.npmjs.org/command-exists-promise/-/command-exists-promise-2.0.2.tgz#7beecc4b218299f3c61fa69a4047aa0b36a64a99" integrity sha512-T6PB6vdFrwnHXg/I0kivM3DqaCGZLjjYSOe0a5WgFKcz1sOnmOeIjnhQPXVXX3QjVbLyTJ85lJkX6lUpukTzaA== +command-exists@^1.2.9: + version "1.2.9" + resolved "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" + integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== + commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@~2.20.3: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" From 1268eadd193f7e93ba41f9781d63bdcb29582baa Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Tue, 13 Oct 2020 16:01:50 -0400 Subject: [PATCH 017/104] add metric comparison to top panel --- app-config.yaml | 7 +- .../ExampleCostInsightsClient.ts | 44 ++-- .../cost-insights/src/api/CostInsightsApi.ts | 29 +-- .../CostInsightsPage/CostInsightsPage.tsx | 41 ++-- .../CostOverviewCard/CostOverviewCard.tsx | 94 ++++++--- .../CostOverviewCard/CostOverviewChart.tsx | 188 ++++++++++++++++++ .../CostOverviewTooltip.tsx | 21 +- .../CostOverviewChart.test.tsx | 50 ----- .../CostOverviewChart/CostOverviewChart.tsx | 120 ----------- .../src/components/CostOverviewChart/index.ts | 17 -- .../CostOverviewChartLegend.test.tsx | 40 ---- .../CostOverviewChartLegend.tsx | 48 ----- plugins/cost-insights/src/hooks/useConfig.tsx | 29 ++- .../cost-insights/src/hooks/useFilters.tsx | 5 +- .../src/types/ChangeStatistic.ts | 13 ++ .../index.ts => types/ChartData.tsx} | 7 +- plugins/cost-insights/src/types/Cost.ts | 4 + plugins/cost-insights/src/types/Filters.ts | 2 +- plugins/cost-insights/src/types/Metric.ts | 3 +- plugins/cost-insights/src/types/index.ts | 1 + plugins/cost-insights/src/utils/charts.ts | 39 ++++ plugins/cost-insights/src/utils/graphs.tsx | 7 +- 22 files changed, 417 insertions(+), 392 deletions(-) create mode 100644 plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx rename plugins/cost-insights/src/components/{CostOverviewChart => CostOverviewCard}/CostOverviewTooltip.tsx (72%) delete mode 100644 plugins/cost-insights/src/components/CostOverviewChart/CostOverviewChart.test.tsx delete mode 100644 plugins/cost-insights/src/components/CostOverviewChart/CostOverviewChart.tsx delete mode 100644 plugins/cost-insights/src/components/CostOverviewChart/index.ts delete mode 100644 plugins/cost-insights/src/components/CostOverviewChartLegend/CostOverviewChartLegend.test.tsx delete mode 100644 plugins/cost-insights/src/components/CostOverviewChartLegend/CostOverviewChartLegend.tsx rename plugins/cost-insights/src/{components/CostOverviewChartLegend/index.ts => types/ChartData.tsx} (84%) create mode 100644 plugins/cost-insights/src/utils/charts.ts diff --git a/app-config.yaml b/app-config.yaml index 228eafc588..aad474ea33 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -237,9 +237,12 @@ costInsights: icon: search metrics: dailyCost: - name: Your Company's Daily Cost + name: Pied Piper's Daily Cost + compare: DAU DAU: - name: Cost Per DAU + name: Daily Active Users + MSC: + name: Monthly Active Subscribers homepage: clocks: - label: UTC diff --git a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts index aa795e9eee..fc2249fadc 100644 --- a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts +++ b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts @@ -28,6 +28,7 @@ import { Group, inclusiveStartDateOf, Maybe, + MetricData, ProductCost, Project, ProjectGrowthAlert, @@ -116,19 +117,33 @@ export class ExampleCostInsightsClient implements CostInsightsApi { return projects; } - async getGroupDailyCost( - group: string, + async getMetricData( metric: string | null, intervals: string, - ): Promise { + ): Promise { const aggregation = aggregationFor( durationOf(intervals), - metric ? 0.3 : 8_000, - ); - const groupDailyCost: Cost = await this.request( - { group, metric, intervals }, + 100_000, + ).map(entry => ({ ...entry, amount: Math.round(entry.amount) })); + + const cost: MetricData = await this.request( + { metric, intervals }, + { + format: 'number', + aggregation: aggregation, + change: changeOf(aggregation), + trendline: trendlineOf(aggregation), + }, + ); + + return cost; + } + + async getGroupDailyCost(group: string, intervals: string): Promise { + const aggregation = aggregationFor(durationOf(intervals), 8_000); + const groupDailyCost: Cost = await this.request( + { group, intervals }, { - id: metric, // costs with null ids will appear as "All Projects" in Cost Overview panel aggregation: aggregation, change: changeOf(aggregation), trendline: trendlineOf(aggregation), @@ -138,17 +153,10 @@ export class ExampleCostInsightsClient implements CostInsightsApi { return groupDailyCost; } - async getProjectDailyCost( - project: string, - metric: string | null, - intervals: string, - ): Promise { - const aggregation = aggregationFor( - durationOf(intervals), - metric ? 0.1 : 1_500, - ); + async getProjectDailyCost(project: string, intervals: string): Promise { + const aggregation = aggregationFor(durationOf(intervals), 1_500); const projectDailyCost: Cost = await this.request( - { project, metric, intervals }, + { project, intervals }, { id: 'project-a', aggregation: aggregation, diff --git a/plugins/cost-insights/src/api/CostInsightsApi.ts b/plugins/cost-insights/src/api/CostInsightsApi.ts index bdf673b7d1..52b3f335d9 100644 --- a/plugins/cost-insights/src/api/CostInsightsApi.ts +++ b/plugins/cost-insights/src/api/CostInsightsApi.ts @@ -23,6 +23,7 @@ import { Project, ProductCost, Maybe, + MetricData, } from '../types'; export type CostInsightsApi = { @@ -54,16 +55,10 @@ export type CostInsightsApi = { * reduction) and compare it to metrics important to the business. * * @param group The group id from getUserGroups or query parameters - * @param metric A metric from the cost-insights configuration in app-config.yaml. The backend - * should divide the actual daily cost by the corresponding metric for the same date. * @param intervals An ISO 8601 repeating interval string, such as R2/P1M/2020-09-01 * https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals */ - getGroupDailyCost( - group: string, - metric: string | null, - intervals: string, - ): Promise; + getGroupDailyCost(group: string, intervals: string): Promise; /** * Get daily cost aggregations for a given billing entity (project in GCP, AWS has a similar @@ -78,16 +73,21 @@ export type CostInsightsApi = { * (or reduction) and compare it to metrics important to the business. * * @param project The project id from getGroupProjects or query parameters - * @param metric A metric from the cost-insights configuration in app-config.yaml. The backend - * should divide the actual daily cost by the corresponding metric for the same date. * @param intervals An ISO 8601 repeating interval string, such as R2/P1M/2020-09-01 * https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals */ - getProjectDailyCost( - project: string, - metric: string | null, - intervals: string, - ): Promise; + getProjectDailyCost(project: string, intervals: string): Promise; + + /** + * Get aggregations for a particular metric and interval timeframe. Teams + * can see metrics important to their business in comparison to the growth + * (or reduction) of a project or group's daily costs. + * + * @param metric A metric from the cost-insights configuration in app-config.yaml. + * @param intervals An ISO 8601 repeating interval string, such as R2/P1M/2020-09-01 + * https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals + */ + getMetricData(metric: string | null, intervals: string): Promise; /** * Get cost aggregations for a particular cloud product and interval timeframe. This includes @@ -107,6 +107,7 @@ export type CostInsightsApi = { * of how the durations are interpreted in Cost Insights. * @param project (optional) The project id from getGroupProjects or query parameters */ + getProductInsights( product: string, group: string, diff --git a/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx b/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx index 8922ac31e0..9bc396537b 100644 --- a/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx +++ b/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx @@ -39,7 +39,14 @@ import { useCurrency, useConfig, } from '../../hooks'; -import { Alert, Cost, intervalsOf, Maybe, Project } from '../../types'; +import { + Alert, + Cost, + intervalsOf, + Maybe, + MetricData, + Project, +} from '../../types'; import { mapLoadingToProps } from './selector'; import ProjectSelect from '../ProjectSelect'; @@ -48,15 +55,17 @@ const CostInsightsPage = () => { // There is not currently a UI to set feature flags // flags.set('cost-insights-currencies', FeatureFlagState.On); const client = useApi(costInsightsApiRef); - const { currencies } = useConfig(); + const config = useConfig(); const groups = useGroups(); const [currency, setCurrency] = useCurrency(); const [projects, setProjects] = useState>(null); const [dailyCost, setDailyCost] = useState>(null); + const [metricData, setMetricData] = useState>(null); const [alerts, setAlerts] = useState>(null); const [error, setError] = useState>(null); const { pageFilters, setPageFilters } = useFilters(p => p); + const { loadingActions, loadingGroups, @@ -94,26 +103,32 @@ const CostInsightsPage = () => { dispatchLoadingInsights(true); const [ fetchedProjects, - fetchedCosts, fetchedAlerts, + fetchedMetricData, + fetchedCosts, ] = await Promise.all([ client.getGroupProjects(pageFilters.group), + client.getAlerts(pageFilters.group), + pageFilters.metric + ? client.getMetricData( + pageFilters.metric, + intervalsOf(pageFilters.duration), + ) + : null, pageFilters.project ? client.getProjectDailyCost( pageFilters.project, - pageFilters.metric, intervalsOf(pageFilters.duration), ) : client.getGroupDailyCost( pageFilters.group, - pageFilters.metric, intervalsOf(pageFilters.duration), ), - client.getAlerts(pageFilters.group), ]); setProjects(fetchedProjects); - setDailyCost(fetchedCosts); setAlerts(fetchedAlerts); + setMetricData(fetchedMetricData); + setDailyCost(fetchedCosts); } else { dispatchLoadingNone(loadingActions); } @@ -133,11 +148,11 @@ const CostInsightsPage = () => { }, [ client, pageFilters, + loadingActions, loadingGroups, dispatchLoadingInsights, dispatchLoadingInitial, dispatchLoadingNone, - loadingActions, ]); if (loadingInitial) { @@ -166,7 +181,6 @@ const CostInsightsPage = () => { ); } - // These should be defined, alerts can be an empty array but that's truthy if (!dailyCost || !alerts) { return ( @@ -195,7 +209,7 @@ const CostInsightsPage = () => { @@ -253,12 +267,7 @@ const CostInsightsPage = () => { {!!dailyCost.aggregation.length && ( - + )} diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx index b8d1be5d01..7f270e2374 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -15,42 +15,41 @@ */ import React from 'react'; -import { Box, Card, CardContent, Divider } from '@material-ui/core'; -import CostOverviewChart from '../CostOverviewChart'; -import CostOverviewChartLegend from '../CostOverviewChartLegend'; +import { Box, Card, CardContent, Divider, useTheme } from '@material-ui/core'; +import CostGrowth from '../CostGrowth'; +import CostOverviewChart from './CostOverviewChart'; import CostOverviewHeader from './CostOverviewHeader'; +import LegendItem from '../LegendItem'; import MetricSelect from '../MetricSelect'; import PeriodSelect from '../PeriodSelect'; import { useScroll, useFilters, useConfig } from '../../hooks'; import { mapFiltersToProps } from './selector'; import { DefaultNavigation } from '../../utils/navigation'; +import { formatPercent } from '../../utils/formatters'; import { - ChangeStatistic, - DateAggregation, - Project, - Trendline, + Cost, + CostInsightsTheme, + MetricData, findAlways, + getComparedChange, } from '../../types'; -type CostOverviewCardProps = { - change: ChangeStatistic; - aggregation: Array; - trendline: Trendline; - projects: Array; +export type CostOverviewCardProps = { + data: [Cost, MetricData | null]; }; -const CostOverviewCard = ({ - change, - aggregation, - trendline, -}: CostOverviewCardProps) => { - const { metrics } = useConfig(); +const CostOverviewCard = ({ data }: CostOverviewCardProps) => { + const theme = useTheme(); + const config = useConfig(); const { ScrollAnchor } = useScroll(DefaultNavigation.CostOverviewCard); - const { setDuration, setProject, metric, setMetric, ...filters } = useFilters( + const { setDuration, setProject, setMetric, ...filters } = useFilters( mapFiltersToProps, ); - const { name } = findAlways(metrics, m => m.kind === metric); + // There should always be a daily cost metric but a comparison metric is optional. + const dailyCost = findAlways(config.metrics, m => m.kind === null); + const metric = config.metrics.find(m => m.kind === filters.metric); + const comparedChange = data[1] ? getComparedChange(data[0], data[1]) : null; return ( @@ -60,22 +59,53 @@ const CostOverviewCard = ({ - - + + + + + {formatPercent(data[0].change.ratio)} + + + {metric && metric.kind && data[1] && comparedChange && ( + <> + + + {formatPercent(data[1].change.ratio)} + + + + + + + )} + - + {config.metrics.length > 1 && ( + + )} diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx new file mode 100644 index 0000000000..40cefbb589 --- /dev/null +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx @@ -0,0 +1,188 @@ +/* + * 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 { useTheme } from '@material-ui/core'; +import { + AxisDomain, + ComposedChart, + XAxis, + YAxis, + Tooltip, + CartesianGrid, + Area, + Line, + ResponsiveContainer, + TooltipPayload, +} from 'recharts'; +import { + ChartData, + Cost, + Metric, + MetricData, + CostInsightsTheme, +} from '../../types'; +import { + overviewGraphTickFormatter, + formatGraphValue, +} from '../../utils/graphs'; +import CostOverviewTooltip from './CostOverviewTooltip'; +import { TooltipItemProps } from '../Tooltip'; +import { NULL_METRIC } from '../../hooks/useConfig'; +import { useCostOverviewStyles as useStyles } from '../../utils/styles'; +import { groupByDate, toDataMax, trendFrom } from '../../utils/charts'; +import { aggregationSort } from '../../utils/sort'; + +type CostOverviewChartProps = { + data: [Cost, MetricData | null]; + name: string; + compare?: Metric; + responsive?: boolean; +}; + +const CostOverviewChart = ({ + data, + name, + compare, + responsive = true, +}: CostOverviewChartProps) => { + const theme = useTheme(); + const styles = useStyles(theme); + + const { dailyCost, metric } = { + dailyCost: { + id: NULL_METRIC, + name: name, + format: 'number', + data: data[0], + }, + metric: { + id: compare?.kind ?? 'Unknown', + name: compare?.name ?? 'Unknown', + format: data[1]?.format ?? 'number', + data: data[1], + }, + }; + + const metricsByDate = metric.data + ? metric.data.aggregation.reduce(groupByDate, {}) + : {}; + + const chartData: ChartData[] = dailyCost + .data!.aggregation.slice() + .sort(aggregationSort) + .map(entry => ({ + date: Date.parse(entry.date), + trend: trendFrom(dailyCost.data!.trendline, Date.parse(entry.date)), + dailyCost: entry.amount, + ...(metric && metric.data + ? { [metric.id]: metricsByDate[`${entry.date}`] } + : {}), + })); + + const metricDataMax: AxisDomain = metric + ? toDataMax(metric.id, chartData) + : 'dataMax'; + + function tooltipFormatter(payload: TooltipPayload): TooltipItemProps { + return { + label: payload.dataKey === dailyCost.id ? dailyCost.name : metric.name, + value: + payload.dataKey === dailyCost.id + ? formatGraphValue(payload.value as number, dailyCost.format) + : formatGraphValue(payload.value as number, metric.format), + fill: + payload.dataKey === dailyCost.id + ? theme.palette.blue + : theme.palette.magenta, + }; + } + + return ( + + + + + 0, 'dataMax']} + tick={{ fill: styles.axis.fill }} + tickFormatter={formatGraphValue} + width={styles.yAxis.width} + yAxisId={dailyCost.id} + /> + {metric && ( + 0, metricDataMax]} + width={styles.yAxis.width} + yAxisId={metric.id} + /> + )} + + + {metric && ( + + )} + + } + animationDuration={100} + /> + + + ); +}; + +export default CostOverviewChart; diff --git a/plugins/cost-insights/src/components/CostOverviewChart/CostOverviewTooltip.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewTooltip.tsx similarity index 72% rename from plugins/cost-insights/src/components/CostOverviewChart/CostOverviewTooltip.tsx rename to plugins/cost-insights/src/components/CostOverviewCard/CostOverviewTooltip.tsx index 17d466441d..a30f370166 100644 --- a/plugins/cost-insights/src/components/CostOverviewChart/CostOverviewTooltip.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewTooltip.tsx @@ -16,29 +16,24 @@ import React from 'react'; import moment from 'moment'; import { TooltipPayload, TooltipProps } from 'recharts'; -import Tooltip from '../../components/Tooltip'; +import Tooltip, { TooltipItemProps } from '../../components/Tooltip'; import { DEFAULT_DATE_FORMAT } from '../../types'; -import { formatGraphValue } from '../../utils/graphs'; -type CostOverviewTooltipProps = TooltipProps & { - metric: string; - name: string; +export type CostOverviewTooltipProps = TooltipProps & { + dataKeys: Array; + format: (payload: TooltipPayload) => TooltipItemProps; }; const CostOverviewTooltip = ({ label, payload, - metric, - name, + dataKeys, + format, }: CostOverviewTooltipProps) => { const tooltipLabel = moment(label).format(DEFAULT_DATE_FORMAT); const items = payload - ?.filter(data => data.name === metric) - .map((data: TooltipPayload) => ({ - label: name, - value: formatGraphValue(data.value as number), - fill: data.fill as string, - })); + ?.filter((p: TooltipPayload) => dataKeys.includes(p.dataKey as string)) + .map(p => format(p)); return ; }; diff --git a/plugins/cost-insights/src/components/CostOverviewChart/CostOverviewChart.test.tsx b/plugins/cost-insights/src/components/CostOverviewChart/CostOverviewChart.test.tsx deleted file mode 100644 index e6311d147a..0000000000 --- a/plugins/cost-insights/src/components/CostOverviewChart/CostOverviewChart.test.tsx +++ /dev/null @@ -1,50 +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 CostOverviewChart from './CostOverviewChart'; -import { renderInTestApp } from '@backstage/test-utils'; -import React from 'react'; -import { DateAggregation, Trendline } from '../../types'; -import { CostInsightsThemeProvider } from '../CostInsightsPage/CostInsightsThemeProvider'; - -const mockAggregation = [ - { date: '2020-04-01', amount: 100 }, - { date: '2020-04-02', amount: 101 }, - { date: '2020-04-03', amount: 102 }, - { date: '2020-04-04', amount: 103 }, -] as Array; - -const mockTrendline = { slope: 0.3, intercept: 101.5 } as Trendline; -const mockMetric = 'mock-metric'; - -describe('', () => { - it('Renders without exploding', async () => { - const rendered = await renderInTestApp( - - - , - ); - expect( - rendered.container.querySelector('.cost-overview-chart'), - ).toBeInTheDocument(); - }); -}); diff --git a/plugins/cost-insights/src/components/CostOverviewChart/CostOverviewChart.tsx b/plugins/cost-insights/src/components/CostOverviewChart/CostOverviewChart.tsx deleted file mode 100644 index 2b40668409..0000000000 --- a/plugins/cost-insights/src/components/CostOverviewChart/CostOverviewChart.tsx +++ /dev/null @@ -1,120 +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 from 'react'; -import { - ComposedChart, - XAxis, - YAxis, - Tooltip, - CartesianGrid, - Area, - Line, - ResponsiveContainer, -} from 'recharts'; -import { - Maybe, - DateAggregation, - Trendline, - CostInsightsTheme, -} from '../../types'; -import { - overviewGraphTickFormatter, - formatGraphValue, -} from '../../utils/graphs'; -import CostOverviewTooltip from './CostOverviewTooltip'; -import { useTheme } from '@material-ui/core'; -import { useCostOverviewStyles as useStyles } from '../../utils/styles'; -import { NULL_METRIC } from '../../hooks/useConfig'; - -type CostOverviewChartProps = { - responsive: boolean; - aggregation: Array; - trendline?: Maybe; - metric: string | null; - tooltip: string; -}; - -const CostOverviewChart = ({ - responsive = true, - aggregation, - trendline, - metric, - tooltip, -}: CostOverviewChartProps) => { - const theme = useTheme(); - const styles = useStyles(theme); - - const id = metric ? metric : NULL_METRIC; - - const dailyCostData = aggregation.map((entry: DateAggregation) => ({ - date: Date.parse(entry.date), - [id]: entry.amount, - trend: trendline - ? trendline.slope * (Date.parse(entry.date) / 1000) + trendline.intercept - : null, - })); - - return ( - - - - - 0, 'dataMax']} - tick={{ fill: styles.axis.fill }} - tickFormatter={formatGraphValue} - width={styles.yAxis.width} - yAxisId={id} - /> - - - } - animationDuration={100} - /> - - - ); -}; - -export default CostOverviewChart; diff --git a/plugins/cost-insights/src/components/CostOverviewChart/index.ts b/plugins/cost-insights/src/components/CostOverviewChart/index.ts deleted file mode 100644 index 21f345a282..0000000000 --- a/plugins/cost-insights/src/components/CostOverviewChart/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 { default } from './CostOverviewChart'; diff --git a/plugins/cost-insights/src/components/CostOverviewChartLegend/CostOverviewChartLegend.test.tsx b/plugins/cost-insights/src/components/CostOverviewChartLegend/CostOverviewChartLegend.test.tsx deleted file mode 100644 index a40ff58ec1..0000000000 --- a/plugins/cost-insights/src/components/CostOverviewChartLegend/CostOverviewChartLegend.test.tsx +++ /dev/null @@ -1,40 +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 { renderInTestApp } from '@backstage/test-utils'; -import CostOverviewChartLegend from './CostOverviewChartLegend'; -import React from 'react'; -import { ChangeStatistic } from '../../types'; - -describe('', () => { - it('Correctly displays text if change is not supplied', async () => { - const rendered = await renderInTestApp( - , - ); - expect(rendered.queryByText('Unclear')).toBeInTheDocument(); - }); - it('Correctly displays formatted change percentage', async () => { - const change = { - ratio: 0.3456, - amount: 40000, - } as ChangeStatistic; - const rendered = await renderInTestApp( - , - ); - expect(rendered.queryByText('Unclear')).not.toBeInTheDocument(); - expect(rendered.queryByText('35%')).toBeInTheDocument(); - }); -}); diff --git a/plugins/cost-insights/src/components/CostOverviewChartLegend/CostOverviewChartLegend.tsx b/plugins/cost-insights/src/components/CostOverviewChartLegend/CostOverviewChartLegend.tsx deleted file mode 100644 index 538cd2a63a..0000000000 --- a/plugins/cost-insights/src/components/CostOverviewChartLegend/CostOverviewChartLegend.tsx +++ /dev/null @@ -1,48 +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 from 'react'; -import { Box, useTheme } from '@material-ui/core'; -import LegendItem from '../LegendItem'; -import { formatPercent } from '../../utils/formatters'; -import { ChangeStatistic, CostInsightsTheme } from '../../types'; - -type CostOverviewChartLegendProps = { - change?: ChangeStatistic; - title: string; - tooltip?: string; -}; - -const CostOverviewChartLegend = ({ - change, - title, - tooltip, -}: CostOverviewChartLegendProps) => { - const theme = useTheme(); - - return ( - - - {change ? formatPercent(change.ratio) : 'Unclear'} - - - ); -}; - -export default CostOverviewChartLegend; diff --git a/plugins/cost-insights/src/hooks/useConfig.tsx b/plugins/cost-insights/src/hooks/useConfig.tsx index aee6567056..a73a7bf301 100644 --- a/plugins/cost-insights/src/hooks/useConfig.tsx +++ b/plugins/cost-insights/src/hooks/useConfig.tsx @@ -42,6 +42,9 @@ export const NULL_METRIC_NAME = 'Daily Cost'; * name: Product B * icon: data * metrics: + * dailyCost: + * name: Daily Cost + * compare: metricA * metricA: * name: Metric A * metricB: @@ -89,6 +92,7 @@ export const ConfigProvider = ({ children }: { children: ReactNode }) => { return metrics.keys().map(key => ({ kind: key === NULL_METRIC ? null : key, name: metrics.getString(`${key}.name`), + compare: metrics.getOptionalString(`${key}.compare`), })); } @@ -115,23 +119,14 @@ export const ConfigProvider = ({ children }: { children: ReactNode }) => { const engineerCost = getEngineerCost(); const icons = getIcons(); - if (metrics.find((m: Metric) => m.kind === null)) { - setConfig(prevState => ({ - ...prevState, - metrics, - products, - engineerCost, - icons, - })); - } else { - setConfig(prevState => ({ - ...prevState, - metrics: [...prevState.metrics, ...metrics], - products, - engineerCost, - icons, - })); - } + setConfig(prevState => ({ + ...prevState, + metrics, + products, + engineerCost, + icons, + })); + setLoading(false); } diff --git a/plugins/cost-insights/src/hooks/useFilters.tsx b/plugins/cost-insights/src/hooks/useFilters.tsx index c2053d6b0f..0ad4c24267 100644 --- a/plugins/cost-insights/src/hooks/useFilters.tsx +++ b/plugins/cost-insights/src/hooks/useFilters.tsx @@ -69,6 +69,7 @@ export const FilterContext = React.createContext< >(undefined); export const FilterProvider = ({ children }: FilterProviderProps) => { + const config = useConfig(); const navigate = useNavigate(); const location = useLocation(); const queryParams = useQueryParams(); @@ -101,7 +102,9 @@ export const FilterProvider = ({ children }: FilterProviderProps) => { // TODO: Figure out why pageFilters doesn't get updated by the above when groups are loaded. useEffect(() => { - setPageFilters(getInitialPageState(groups, queryParams.pageFilters)); + const initialState = getInitialPageState(groups, queryParams.pageFilters); + const compared = config.metrics.find(m => m.kind === null)?.compare; + setPageFilters({ ...initialState, metric: compared || null }); }, [groups]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { diff --git a/plugins/cost-insights/src/types/ChangeStatistic.ts b/plugins/cost-insights/src/types/ChangeStatistic.ts index 538692bb1d..cf7310ef97 100644 --- a/plugins/cost-insights/src/types/ChangeStatistic.ts +++ b/plugins/cost-insights/src/types/ChangeStatistic.ts @@ -14,6 +14,9 @@ * limitations under the License. */ +import { Cost } from './Cost'; +import { aggregationSort } from '../utils/sort'; + export interface ChangeStatistic { // The ratio of change from one duration to another, expressed as: (newSum - oldSum) / oldSum ratio: number; @@ -46,3 +49,13 @@ export function growthOf(amount: number, ratio: number) { return Growth.Negligible; } + +// Used by for displaying engineer totals +export function getComparedChange(a: Cost, b: Cost): ChangeStatistic { + const ratio = a.change.ratio - b.change.ratio; + const amount = a.aggregation.slice().sort(aggregationSort)[0].amount; + return { + ratio: ratio, + amount: amount * ratio, + }; +} diff --git a/plugins/cost-insights/src/components/CostOverviewChartLegend/index.ts b/plugins/cost-insights/src/types/ChartData.tsx similarity index 84% rename from plugins/cost-insights/src/components/CostOverviewChartLegend/index.ts rename to plugins/cost-insights/src/types/ChartData.tsx index 1b6c1fe975..d99e9d9bc3 100644 --- a/plugins/cost-insights/src/components/CostOverviewChartLegend/index.ts +++ b/plugins/cost-insights/src/types/ChartData.tsx @@ -14,4 +14,9 @@ * limitations under the License. */ -export { default } from './CostOverviewChartLegend'; +export type ChartData = { + date: number; + trend: number; + dailyCost: number; + [key: string]: number; +}; diff --git a/plugins/cost-insights/src/types/Cost.ts b/plugins/cost-insights/src/types/Cost.ts index 84a8bfae51..8a9ce3c69b 100644 --- a/plugins/cost-insights/src/types/Cost.ts +++ b/plugins/cost-insights/src/types/Cost.ts @@ -24,3 +24,7 @@ export interface Cost { change: ChangeStatistic; trendline: Trendline; } + +export interface MetricData extends Cost { + format: 'number' | 'currency'; +} diff --git a/plugins/cost-insights/src/types/Filters.ts b/plugins/cost-insights/src/types/Filters.ts index def1d74d1a..f90b9b4c78 100644 --- a/plugins/cost-insights/src/types/Filters.ts +++ b/plugins/cost-insights/src/types/Filters.ts @@ -22,7 +22,7 @@ export interface PageFilters { group: Maybe; project: Maybe; duration: Duration; - metric: Maybe; + metric: string | null; } export type ProductFilters = Array; diff --git a/plugins/cost-insights/src/types/Metric.ts b/plugins/cost-insights/src/types/Metric.ts index 5a440a8d1f..4fa9cd189b 100644 --- a/plugins/cost-insights/src/types/Metric.ts +++ b/plugins/cost-insights/src/types/Metric.ts @@ -14,9 +14,10 @@ * limitations under the License. */ -import { Maybe } from './Maybe'; +import { Maybe } from '../types'; export type Metric = { kind: Maybe; name: string; + compare?: string; }; diff --git a/plugins/cost-insights/src/types/index.ts b/plugins/cost-insights/src/types/index.ts index c3d9a87886..8523c7db7b 100644 --- a/plugins/cost-insights/src/types/index.ts +++ b/plugins/cost-insights/src/types/index.ts @@ -16,6 +16,7 @@ export * from './Alert'; export * from './ChangeStatistic'; +export * from './ChartData'; export * from './Cost'; export * from './DateAggregation'; export * from './Duration'; diff --git a/plugins/cost-insights/src/utils/charts.ts b/plugins/cost-insights/src/utils/charts.ts new file mode 100644 index 0000000000..60a8e85816 --- /dev/null +++ b/plugins/cost-insights/src/utils/charts.ts @@ -0,0 +1,39 @@ +/* + * 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 { DateAggregation, Trendline, ChartData } from '../types'; + +export function trendFrom(trendline: Trendline, date: number): number { + return trendline.slope * (date / 1000) + trendline.intercept; +} + +export function groupByDate( + acc: Record, + entry: DateAggregation, +): Record { + return { ...acc, [entry.date]: entry.amount }; +} + +export function toMaxCost(acc: ChartData, entry: ChartData): ChartData { + return acc.dailyCost > entry.dailyCost ? acc : entry; +} + +export function toDataMax(metric: string, data: ChartData[]): number { + return ( + (data.reduce(toMaxCost).dailyCost / Math.abs(data[0].trend)) * + data[0][metric] + ); +} diff --git a/plugins/cost-insights/src/utils/graphs.tsx b/plugins/cost-insights/src/utils/graphs.tsx index 5200b14b1f..167132cb36 100644 --- a/plugins/cost-insights/src/utils/graphs.tsx +++ b/plugins/cost-insights/src/utils/graphs.tsx @@ -20,10 +20,15 @@ import { lengthyCurrencyFormatter, } from './formatters'; -export function formatGraphValue(value: number) { +export function formatGraphValue(value: number, format?: string) { + if (format === 'number') { + return value.toLocaleString(); + } + if (value < 1) { return lengthyCurrencyFormatter.format(value); } + return currencyFormatter.format(value); } From 94b248e6826bfb9301b2312601e8743e578c94b2 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Tue, 13 Oct 2020 16:25:24 -0400 Subject: [PATCH 018/104] set default null metric --- plugins/cost-insights/src/hooks/useConfig.tsx | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/plugins/cost-insights/src/hooks/useConfig.tsx b/plugins/cost-insights/src/hooks/useConfig.tsx index a73a7bf301..830cafc4f1 100644 --- a/plugins/cost-insights/src/hooks/useConfig.tsx +++ b/plugins/cost-insights/src/hooks/useConfig.tsx @@ -119,13 +119,23 @@ export const ConfigProvider = ({ children }: { children: ReactNode }) => { const engineerCost = getEngineerCost(); const icons = getIcons(); - setConfig(prevState => ({ - ...prevState, - metrics, - products, - engineerCost, - icons, - })); + if (metrics.find((m: Metric) => m.kind === null)) { + setConfig(prevState => ({ + ...prevState, + metrics, + products, + engineerCost, + icons, + })); + } else { + setConfig(prevState => ({ + ...prevState, + metrics: [...prevState.metrics, ...metrics], + products, + engineerCost, + icons, + })); + } setLoading(false); } From 54504deb135ce71395ad8a5b5505575dac7e220f Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Tue, 13 Oct 2020 16:43:45 -0400 Subject: [PATCH 019/104] tweak custom metric verbiage --- app-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-config.yaml b/app-config.yaml index aad474ea33..d1414582ed 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -242,7 +242,7 @@ costInsights: DAU: name: Daily Active Users MSC: - name: Monthly Active Subscribers + name: Monthly Subscribers homepage: clocks: - label: UTC From 3b17fa02f87eef8c3ea0a7a5ccf18ba43737e4ce Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 14 Oct 2020 11:07:56 +0200 Subject: [PATCH 020/104] Vale: Use files provided by lint staged to check --- scripts/check-docs-quality.js | 63 ++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/scripts/check-docs-quality.js b/scripts/check-docs-quality.js index 4f5f689105..cd41fa25c2 100755 --- a/scripts/check-docs-quality.js +++ b/scripts/check-docs-quality.js @@ -1,4 +1,3 @@ -#!/usr/bin/env node /* * Copyright 2020 Spotify AB * @@ -15,67 +14,71 @@ * limitations under the License. */ const { execSync, spawnSync } = require('child_process'); -const path = require('path'); // eslint-disable-next-line import/no-extraneous-dependencies const commandExists = require('command-exists'); -const listFilesTrackedByGit = 'git ls-files'; - const inheritStdIo = { stdio: 'inherit', }; const LINT_SKIPPED_MESSAGE = 'Skipping documentation quality check (vale not found). Install vale linter (https://docs.errata.ai/vale/install) to enable.\n'; -const ERROR_MESSAGE = - 'Language linter (vale) generated errors. Please check the errors and review any markdown files that you changed.\n'; +const ERROR_MESSAGE = `Language linter (vale) generated errors. Please check the errors and review any markdown files that you changed. + Possibly update .github/styles/vocab.txt to add new valid words.\n`; + +// Note: Make sure the script is run as `node check-docs-quality.js [FILES]` instead of `./check-docs-quality.js [FILES]` +// If the script receives arguments (file paths), the script is run exclusively on them. (e.g. when run via pre-commit hook) +const getFilesToLint = () => { + // Files have been provided as arguments + if (process.argv.length > 2) { + return process.argv.slice(2); + } + + let command = `git ls-files | ./node_modules/.bin/shx grep ".md"`; + if (process.platform === 'win32') { + command = `git ls-files | .\\node_modules\\.bin\\shx grep ".md"`; + } + + return execSync(command, { + stdio: ['ignore', 'pipe', 'inherit'], + }) + .toString() + .split('\n'); +}; // Proceed with the script only if Vale linter is installed. Limit the friction and surprises caused by the script. commandExists('vale') .catch(() => { console.log(LINT_SKIPPED_MESSAGE); - process.exit(0); + // process.exit(0); + process.exit(1); }) .then(() => { + const filesToLint = getFilesToLint(); + console.log('files to lint'); + console.log(filesToLint); + // xargs is not supported by shx. if (process.platform === 'win32') { - const validMDFilesCommand = `${listFilesTrackedByGit} | .\\node_modules\\.bin\\shx grep ".md"`; try { - // get list of all md files except in directories of gitignore. - let filesToLint = execSync(validMDFilesCommand, { - stdio: ['ignore', 'pipe', 'inherit'], - }); - - // set all file(s) path as absolute path - filesToLint = filesToLint - .toString() - .split('\n') - .map(filepath => - filepath ? path.join(process.cwd(), filepath) : null, - ) - .filter(Boolean); - const output = spawnSync('vale', filesToLint, inheritStdIo); // if the command does not succeed if (output.status !== 0) { // if it contains system level error. [in this case vale does not exist] if (output.error) { - console.error(ERROR_MESSAGE); + console.log(ERROR_MESSAGE); } process.exit(1); } } catch (e) { - console.error(e.message); + console.log(e.message); process.exit(1); } } else { - const validMDFilesCommand = `${listFilesTrackedByGit} | ./node_modules/.bin/shx grep ".md"`; - // use xargs - try { - execSync(`${validMDFilesCommand} | xargs vale`, inheritStdIo); - } catch (e) { - console.error(ERROR_MESSAGE); + const output = spawnSync('vale', filesToLint, inheritStdIo); + if (output.status !== 0) { + console.log(ERROR_MESSAGE); process.exit(1); } } From c1d6bc03f6ac7722987fcde48a8e89359149260f Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 14 Oct 2020 11:16:23 +0200 Subject: [PATCH 021/104] Vale: Enforce vale to be run on CI --- scripts/check-docs-quality.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/scripts/check-docs-quality.js b/scripts/check-docs-quality.js index cd41fa25c2..f29463939b 100755 --- a/scripts/check-docs-quality.js +++ b/scripts/check-docs-quality.js @@ -23,8 +23,9 @@ const inheritStdIo = { const LINT_SKIPPED_MESSAGE = 'Skipping documentation quality check (vale not found). Install vale linter (https://docs.errata.ai/vale/install) to enable.\n'; -const ERROR_MESSAGE = `Language linter (vale) generated errors. Please check the errors and review any markdown files that you changed. +const LINT_ERROR_MESSAGE = `Language linter (vale) generated errors. Please check the errors and review any markdown files that you changed. Possibly update .github/styles/vocab.txt to add new valid words.\n`; +const VALE_NOT_FOUND_MESSAGE = `Language linter (vale) was not found. Please install vale linter (https://docs.errata.ai/vale/install).\n`; // Note: Make sure the script is run as `node check-docs-quality.js [FILES]` instead of `./check-docs-quality.js [FILES]` // If the script receives arguments (file paths), the script is run exclusively on them. (e.g. when run via pre-commit hook) @@ -47,27 +48,29 @@ const getFilesToLint = () => { }; // Proceed with the script only if Vale linter is installed. Limit the friction and surprises caused by the script. +// On CI, we want to ensure vale linter is run. commandExists('vale') .catch(() => { + if (process.env.CI) { + console.log(VALE_NOT_FOUND_MESSAGE); + process.exit(1); + } console.log(LINT_SKIPPED_MESSAGE); - // process.exit(0); - process.exit(1); + process.exit(0); }) .then(() => { const filesToLint = getFilesToLint(); - console.log('files to lint'); - console.log(filesToLint); - // xargs is not supported by shx. if (process.platform === 'win32') { + // Windows try { const output = spawnSync('vale', filesToLint, inheritStdIo); - // if the command does not succeed + // If the command does not succeed if (output.status !== 0) { - // if it contains system level error. [in this case vale does not exist] + // If it contains system level error. In this case vale does not exist. if (output.error) { - console.log(ERROR_MESSAGE); + console.log(LINT_ERROR_MESSAGE); } process.exit(1); } @@ -76,9 +79,10 @@ commandExists('vale') process.exit(1); } } else { + // Unix const output = spawnSync('vale', filesToLint, inheritStdIo); if (output.status !== 0) { - console.log(ERROR_MESSAGE); + console.log(LINT_ERROR_MESSAGE); process.exit(1); } } From cb867d885445cf204a59e1758b447f0a857647d1 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 14 Oct 2020 13:36:12 +0200 Subject: [PATCH 022/104] Friendlier error when NR APM API Key isn't configured. --- .../NewRelicFetchComponent/NewRelicFetchComponent.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/newrelic/src/components/NewRelicFetchComponent/NewRelicFetchComponent.tsx b/plugins/newrelic/src/components/NewRelicFetchComponent/NewRelicFetchComponent.tsx index 9b3791e062..4322bba10b 100644 --- a/plugins/newrelic/src/components/NewRelicFetchComponent/NewRelicFetchComponent.tsx +++ b/plugins/newrelic/src/components/NewRelicFetchComponent/NewRelicFetchComponent.tsx @@ -107,10 +107,15 @@ const NewRelicFetchComponent: FC<{}> = () => { const configApi = useApi(configApiRef); const apiBaseUrl = configApi.getString('newrelic.api.baseUrl'); const apiKey = configApi.getString('newrelic.api.key'); + const apiKeyIsSet = apiKey && apiKey !== 'NEW_RELIC_REST_API_KEY'; const { value, loading, error } = useAsync(async (): Promise< NewRelicApplication[] > => { + if (!apiKeyIsSet) { + return []; + } + const response = await fetch(`${apiBaseUrl}/applications.json`, { headers: { 'X-Api-Key': apiKey, @@ -126,6 +131,12 @@ const NewRelicFetchComponent: FC<{}> = () => { return ; } else if (error) { return {error.message}; + } else if (!apiKeyIsSet) { + return ( + + No REST API Key configured. Check your app-config.yaml. + + ); } return ; From 8e0016f7ad3d80681e95c7afcbce57d0a1485046 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 14 Oct 2020 17:49:41 +0200 Subject: [PATCH 023/104] Introduce a New Relic API class for future expansion. --- plugins/newrelic/src/api/index.ts | 96 +++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 plugins/newrelic/src/api/index.ts diff --git a/plugins/newrelic/src/api/index.ts b/plugins/newrelic/src/api/index.ts new file mode 100644 index 0000000000..692cb19e6f --- /dev/null +++ b/plugins/newrelic/src/api/index.ts @@ -0,0 +1,96 @@ +/* + * 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 { createApiRef, DiscoveryApi } from '@backstage/core'; + +export type NewRelicApplication = { + id: number; + application_summary: NewRelicApplicationSummary; + name: string; + language: string; + health_status: string; + reporting: boolean; + settings: NewRelicApplicationSettings; + links?: NewRelicApplicationLinks; +}; + +export type NewRelicApplicationSummary = { + apdex_score: number; + error_rate: number; + host_count: number; + instance_count: number; + response_time: number; + throughput: number; +}; + +export type NewRelicApplicationSettings = { + app_apdex_threshold: number; + end_user_apdex_threshold: number; + enable_real_user_monitoring: boolean; + use_server_side_config: boolean; +}; + +export type NewRelicApplicationLinks = { + application_instances: Array; + servers: Array; + application_hosts: Array; +}; + +export type NewRelicApplications = { + applications: NewRelicApplication[]; +}; + +export const newRelicApiRef = createApiRef({ + id: 'plugin.newrelic.service', + description: 'Used by the NewRelic plugin to make requests', +}); + +const DEFAULT_PROXY_PATH_BASE = '/newrelic'; + +type Options = { + discoveryApi: DiscoveryApi; + /** + * Path to use for requests via the proxy, defaults to /newrelic + */ + proxyPathBase?: string; +}; + +export class NewRelicApi { + private readonly discoveryApi: DiscoveryApi; + private readonly proxyPathBase: string; + + constructor(options: Options) { + this.discoveryApi = options.discoveryApi; + this.proxyPathBase = options.proxyPathBase ?? DEFAULT_PROXY_PATH_BASE; + } + + async getApplications(): Promise { + const url = await this.getApiUrl('apm', 'applications.json'); + const response = await fetch(url); + const responseJson = await response.json(); + + if (response.status !== 200) { + throw new Error(responseJson?.error?.title || response.statusText); + } + + return responseJson; + } + + private async getApiUrl(product: string, path: string) { + const proxyUrl = await this.discoveryApi.getBaseUrl('proxy'); + return `${proxyUrl}${this.proxyPathBase}/${product}/api/${path}`; + } +} From 2865fcf9cb017a363eec9a8d715965c5a1d78c71 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 14 Oct 2020 17:51:47 +0200 Subject: [PATCH 024/104] Make the plugin aware of the NR API. --- plugins/newrelic/src/plugin.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/plugins/newrelic/src/plugin.ts b/plugins/newrelic/src/plugin.ts index 21aac69e8f..f247158fbd 100644 --- a/plugins/newrelic/src/plugin.ts +++ b/plugins/newrelic/src/plugin.ts @@ -14,7 +14,13 @@ * limitations under the License. */ -import { createPlugin, createRouteRef } from '@backstage/core'; +import { + createApiFactory, + createPlugin, + createRouteRef, + discoveryApiRef, +} from '@backstage/core'; +import { NewRelicApi, newRelicApiRef } from './api'; import NewRelicComponent from './components/NewRelicComponent'; export const rootRouteRef = createRouteRef({ @@ -24,6 +30,13 @@ export const rootRouteRef = createRouteRef({ export const plugin = createPlugin({ id: 'newrelic', + apis: [ + createApiFactory({ + api: newRelicApiRef, + deps: { discoveryApi: discoveryApiRef }, + factory: ({ discoveryApi }) => new NewRelicApi({ discoveryApi }), + }), + ], register({ router }) { router.addRoute(rootRouteRef, NewRelicComponent); }, From 25645e25dbc8516d0cf89b3181fdca47b513d857 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 14 Oct 2020 17:52:59 +0200 Subject: [PATCH 025/104] Refactor config/type usage out of component, use new API instead. --- .../NewRelicFetchComponent.tsx | 76 ++----------------- 1 file changed, 7 insertions(+), 69 deletions(-) diff --git a/plugins/newrelic/src/components/NewRelicFetchComponent/NewRelicFetchComponent.tsx b/plugins/newrelic/src/components/NewRelicFetchComponent/NewRelicFetchComponent.tsx index 4322bba10b..62f52e7875 100644 --- a/plugins/newrelic/src/components/NewRelicFetchComponent/NewRelicFetchComponent.tsx +++ b/plugins/newrelic/src/components/NewRelicFetchComponent/NewRelicFetchComponent.tsx @@ -15,52 +15,10 @@ */ import React, { FC } from 'react'; -import { - configApiRef, - Progress, - Table, - TableColumn, - useApi, -} from '@backstage/core'; +import { Progress, Table, TableColumn, useApi } from '@backstage/core'; import Alert from '@material-ui/lab/Alert'; import { useAsync } from 'react-use'; - -type NewRelicApplication = { - id: number; - application_summary: NewRelicApplicationSummary; - name: string; - language: string; - health_status: string; - reporting: boolean; - settings: NewRelicApplicationSettings; - links?: NewRelicApplicationLinks; -}; - -type NewRelicApplicationSummary = { - apdex_score: number; - error_rate: number; - host_count: number; - instance_count: number; - response_time: number; - throughput: number; -}; - -type NewRelicApplicationSettings = { - app_apdex_threshold: number; - end_user_apdex_threshold: number; - enable_real_user_monitoring: boolean; - use_server_side_config: boolean; -}; - -type NewRelicApplicationLinks = { - application_instances: Array; - servers: Array; - application_hosts: Array; -}; - -type NewRelicApplications = { - applications: NewRelicApplication[]; -}; +import { newRelicApiRef, NewRelicApplications } from '../../api'; export const NewRelicAPMTable: FC = ({ applications, @@ -73,7 +31,7 @@ export const NewRelicAPMTable: FC = ({ { title: 'Instance Count', field: 'instanceCount' }, { title: 'Apdex', field: 'apdexScore' }, ]; - const data = applications.map((app: NewRelicApplication) => { + const data = applications.map(app => { const { name, application_summary: applicationSummary } = app; const { response_time: responseTime, @@ -104,25 +62,11 @@ export const NewRelicAPMTable: FC = ({ }; const NewRelicFetchComponent: FC<{}> = () => { - const configApi = useApi(configApiRef); - const apiBaseUrl = configApi.getString('newrelic.api.baseUrl'); - const apiKey = configApi.getString('newrelic.api.key'); - const apiKeyIsSet = apiKey && apiKey !== 'NEW_RELIC_REST_API_KEY'; + const api = useApi(newRelicApiRef); - const { value, loading, error } = useAsync(async (): Promise< - NewRelicApplication[] - > => { - if (!apiKeyIsSet) { - return []; - } - - const response = await fetch(`${apiBaseUrl}/applications.json`, { - headers: { - 'X-Api-Key': apiKey, - }, - }); - const data: NewRelicApplications = await response.json(); - return data.applications.filter((application: NewRelicApplication) => { + const { value, loading, error } = useAsync(async () => { + const data = await api.getApplications(); + return data.applications.filter(application => { return application.hasOwnProperty('application_summary'); }); }, []); @@ -131,12 +75,6 @@ const NewRelicFetchComponent: FC<{}> = () => { return ; } else if (error) { return {error.message}; - } else if (!apiKeyIsSet) { - return ( - - No REST API Key configured. Check your app-config.yaml. - - ); } return ; From c64a0a47ea0898eebc6f99b1eadd5db0ae806c84 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 14 Oct 2020 17:54:15 +0200 Subject: [PATCH 026/104] Use new expected proxy configs to hit New Relic API. --- app-config.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 228eafc588..7dee71534f 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -34,6 +34,12 @@ proxy: $env: TRAVISCI_AUTH_TOKEN travis-api-version: 3 + '/newrelic/apm/api': + target: https://api.newrelic.com/v2 + headers: + X-Api-Key: + $env: NEW_RELIC_REST_API_KEY + organization: name: Spotify @@ -51,11 +57,6 @@ rollbar: accountToken: $env: ROLLBAR_ACCOUNT_TOKEN -newrelic: - api: - baseUrl: 'https://api.newrelic.com/v2' - key: NEW_RELIC_REST_API_KEY - lighthouse: baseUrl: http://localhost:3003 From 78fea5041efef33abdeeaff53142e914eb5ef93c Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 14 Oct 2020 17:54:48 +0200 Subject: [PATCH 027/104] Update README with new instructions on how to configure the plugin. --- plugins/newrelic/README.md | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/plugins/newrelic/README.md b/plugins/newrelic/README.md index 19bd24950c..e787d9c91f 100644 --- a/plugins/newrelic/README.md +++ b/plugins/newrelic/README.md @@ -7,16 +7,38 @@ Website: [https://newrelic.com](https://newrelic.com) ## Getting Started -Add New Relic REST API Key to `app-config.yaml` +This plugin uses the Backstage proxy to securely communicate with New Relic's +APIs. Add the following to your `app-config.yaml` to enable this configuration: ```yaml -newrelic: - api: - baseUrl: 'https://api.newrelic.com/v2' - key: +proxy: + '/newrelic/apm/api': + target: https://api.newrelic.com/v2 + headers: + X-Api-Key: + $env: NEW_RELIC_REST_API_KEY ``` -New Relic Plugin Path: [/newrelic](http://localhost:3000/newrelic) +In your production deployment of Backstage, you would also need to ensure that +you've set the `NEW_RELIC_REST_API_KEY` environment variable before starting +the backend. + +While working locally, you may wish to hard-code your API key in your +`app-config.local.yaml` like this: + +```yaml +# app-config.local.yaml +proxy: + 'newrelic/apm/api': + headers: + X-Api-Key: NRRA-YourActualApiKey +``` + +Read more about how to find or generate this key in +[New Relic's Documentation](https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys#rest-api-key). + +See if it's working by visiting the New Relic Plugin Path: +[/newrelic](http://localhost:3000/newrelic) ## Features From 4512b9967889ec6299742cae5c66494c3b7b5806 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 14 Oct 2020 18:10:45 +0200 Subject: [PATCH 028/104] Marked minor, although it is major-ish... --- .changeset/old-falcons-jump.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .changeset/old-falcons-jump.md diff --git a/.changeset/old-falcons-jump.md b/.changeset/old-falcons-jump.md new file mode 100644 index 0000000000..107d75ff88 --- /dev/null +++ b/.changeset/old-falcons-jump.md @@ -0,0 +1,25 @@ +--- +'@backstage/plugin-newrelic': minor +--- + +The New Relic plugin now uses the Backstage proxy to communicate with New Relic's API. + +Please update your `app-config.yaml` as follows: + +```yaml +# Old Config +newrelic: + api: + baseUrl: 'https://api.newrelic.com/v2' + key: NEW_RELIC_REST_API_KEY +``` + +```yaml +# New Config +proxy: + '/newrelic/apm/api': + target: https://api.newrelic.com/v2 + headers: + X-Api-Key: + $env: NEW_RELIC_REST_API_KEY +``` From 22e1195419d3c39923fb46aebf85fb900a5db636 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Wed, 14 Oct 2020 14:18:34 -0400 Subject: [PATCH 029/104] cost -> cost trend --- .../src/components/CostOverviewCard/CostOverviewCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx index 7f270e2374..e7d2bbf5be 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -63,7 +63,7 @@ const CostOverviewCard = ({ data }: CostOverviewCardProps) => { {formatPercent(data[0].change.ratio)} From e142a2767f72cc0c1308c07a1b1b193a4501687b Mon Sep 17 00:00:00 2001 From: Mahmood Hosseini Date: Wed, 14 Oct 2020 14:21:09 -0400 Subject: [PATCH 030/104] bug(passport-strategy-helper): Better presentation of auth errors --- .changeset/spotty-apples-visit.md | 5 +++++ .../passport/PassportStrategyHelper.test.ts | 9 +++++++-- .../lib/passport/PassportStrategyHelper.ts | 19 +++++++++++++++++-- 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 .changeset/spotty-apples-visit.md diff --git a/.changeset/spotty-apples-visit.md b/.changeset/spotty-apples-visit.md new file mode 100644 index 0000000000..ba481fa052 --- /dev/null +++ b/.changeset/spotty-apples-visit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Better presentation of authentication errors diff --git a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.test.ts b/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.test.ts index d8f6d25d0f..f7a22e9ad9 100644 --- a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.test.ts +++ b/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.test.ts @@ -16,6 +16,7 @@ import express from 'express'; import passport from 'passport'; +import { InternalOAuthError } from 'passport-oauth2'; import { executeRedirectStrategy, executeFrameHandlerStrategy, @@ -58,7 +59,11 @@ describe('PassportStrategyHelper', () => { } class MyCustomAuthErrorStrategy extends passport.Strategy { authenticate() { - this.error(new Error('MyCustomAuth error')); + this.error( + new InternalOAuthError('MyCustomAuth error', { + data: '{ "message": "Custom message" }', + }), + ); } } class MyCustomAuthRedirectStrategy extends passport.Strategy { @@ -97,7 +102,7 @@ describe('PassportStrategyHelper', () => { ); expect(spyAuthenticate).toBeCalledTimes(1); await expect(frameHandlerStrategyPromise).rejects.toThrow( - 'Authentication failed, Error: MyCustomAuth error', + 'Authentication failed, MyCustomAuth error - Custom message', ); }); diff --git a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts b/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts index 7bc34186f4..3264fd8faa 100644 --- a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts +++ b/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts @@ -18,6 +18,7 @@ import express from 'express'; import passport from 'passport'; import jwtDecoder from 'jwt-decode'; import { ProfileInfo, RedirectInfo } from '../../providers/types'; +import { InternalOAuthError } from 'passport-oauth2'; export type PassportDoneCallback = ( err?: Error, @@ -95,8 +96,22 @@ export const executeFrameHandlerStrategy = async ( ) => { reject(new Error(`Authentication rejected, ${info.message ?? ''}`)); }; - strategy.error = (error: Error) => { - reject(new Error(`Authentication failed, ${error}`)); + strategy.error = (error: InternalOAuthError) => { + let message = `Authentication failed, ${error.message}`; + + if (error.oauthError?.data) { + try { + const errorData = JSON.parse(error.oauthError.data); + + if (errorData.message) { + message += ` - ${errorData.message}`; + } + } catch (parseError) { + message += ` - ${error.oauthError}`; + } + } + + reject(new Error(message)); }; strategy.redirect = () => { reject(new Error('Unexpected redirect')); From 921d705d510f00b6dc4c90516d478dbadb8fbaf5 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Wed, 14 Oct 2020 14:21:33 -0400 Subject: [PATCH 031/104] use ratio for engineer cost display --- .../src/components/CostOverviewCard/CostOverviewCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx index e7d2bbf5be..daffe335c5 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -81,7 +81,7 @@ const CostOverviewCard = ({ data }: CostOverviewCardProps) => { Date: Thu, 15 Oct 2020 09:14:15 +0530 Subject: [PATCH 032/104] chore: update naming convention for page theme options --- packages/theme/src/types.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts index b5a07e78c5..e551208f3f 100644 --- a/packages/theme/src/types.ts +++ b/packages/theme/src/types.ts @@ -72,20 +72,20 @@ type PaletteAdditions = { export type BackstagePalette = Palette & PaletteAdditions; export type BackstagePaletteOptions = PaletteOptions & PaletteAdditions; -export type pageThemeOptions = { +export type PageThemeSelector = { themeId: string; }; export interface BackstageTheme extends Theme { palette: BackstagePalette; page: PageTheme; - getPageTheme: ({ themeId }: pageThemeOptions) => PageTheme; + getPageTheme: ({ themeId }: PageThemeSelector) => PageTheme; } export interface BackstageThemeOptions extends ThemeOptions { palette: BackstagePaletteOptions; page: PageTheme; - getPageTheme: ({ themeId }: pageThemeOptions) => PageTheme; + getPageTheme: ({ themeId }: PageThemeSelector) => PageTheme; } /** From 8f0608a5ed24ffc1a27cfecc9bc0f370c98966d8 Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Thu, 15 Oct 2020 09:16:39 +0530 Subject: [PATCH 033/104] chore: refactor --- .../core/src/layout/Header/Header.stories.tsx | 17 +++++++------- .../core/src/layout/Page/Page.stories.tsx | 5 ++--- packages/core/src/layout/Page/Page.tsx | 13 +++++++---- .../core/src/layout/SignInPage/SignInPage.tsx | 6 ++--- .../ApiEntityPage/ApiEntityPage.tsx | 10 ++------- .../ApiExplorerPage/ApiExplorerLayout.tsx | 10 +-------- .../components/CatalogPage/CatalogLayout.tsx | 5 +---- .../EntityPageLayout/EntityPageLayout.tsx | 10 ++------- .../AlertInstructionsLayout.tsx | 12 ++-------- .../CostInsightsLayout/CostInsightsLayout.tsx | 10 ++------- .../src/components/ExplorePluginPage.tsx | 9 ++------ .../NewProjectPage/NewProjectPage.tsx | 11 ++-------- .../ProjectDetailsPage/ProjectDetailsPage.tsx | 9 +------- .../ProjectListPage/ProjectListPage.tsx | 10 +-------- .../components/ClusterList/ClusterList.tsx | 10 ++------- .../components/ClusterPage/ClusterPage.tsx | 10 ++------- .../ProfileCatalog/ProfileCatalog.tsx | 11 ++-------- .../components/GraphiQLPage/GraphiQLPage.tsx | 10 ++------- .../KubernetesContent/KubernetesContent.tsx | 10 ++------- .../src/components/AuditList/index.tsx | 10 ++------- .../src/components/AuditView/index.tsx | 9 +------- .../src/components/CreateAudit/index.tsx | 9 +------- .../NewRelicComponent/NewRelicComponent.tsx | 10 ++------- .../RegisterComponentPage.tsx | 10 ++------- .../components/RollbarHome/RollbarHome.tsx | 9 +------- .../RollbarProjectPage/RollbarProjectPage.tsx | 9 +------- .../ScaffolderPage/ScaffolderPage.tsx | 6 ++--- .../components/TemplatePage/TemplatePage.tsx | 6 ++--- .../SentryPluginPage/SentryPluginPage.tsx | 10 ++------- .../tech-radar/src/components/RadarPage.tsx | 10 ++------- .../src/reader/components/TechDocsHome.tsx | 22 ++++--------------- .../src/reader/components/TechDocsPage.tsx | 9 +------- .../src/components/SettingsPage.tsx | 9 +------- .../components/WelcomePage/WelcomePage.tsx | 9 +------- 34 files changed, 72 insertions(+), 263 deletions(-) diff --git a/packages/core/src/layout/Header/Header.stories.tsx b/packages/core/src/layout/Header/Header.stories.tsx index f4ca74d3f8..62e6e3e52c 100644 --- a/packages/core/src/layout/Header/Header.stories.tsx +++ b/packages/core/src/layout/Header/Header.stories.tsx @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { pageTheme } from '@backstage/theme'; import React from 'react'; import { Header } from '.'; import { HeaderLabel } from '../HeaderLabel'; @@ -33,7 +32,7 @@ const labels = ( ); export const Home = () => ( - +
{labels}
@@ -47,7 +46,7 @@ export const HomeWithSubtitle = () => ( ); export const Tool = () => ( - +
{labels}
@@ -55,7 +54,7 @@ export const Tool = () => ( ); export const Service = () => ( - +
{labels}
@@ -63,7 +62,7 @@ export const Service = () => ( ); export const Website = () => ( - +
{labels}
@@ -71,7 +70,7 @@ export const Website = () => ( ); export const Library = () => ( - +
{labels}
@@ -79,7 +78,7 @@ export const Library = () => ( ); export const App = () => ( - +
{labels}
@@ -87,7 +86,7 @@ export const App = () => ( ); export const Documentation = () => ( - +
{labels}
@@ -95,7 +94,7 @@ export const Documentation = () => ( ); export const Other = () => ( - +
{labels}
diff --git a/packages/core/src/layout/Page/Page.stories.tsx b/packages/core/src/layout/Page/Page.stories.tsx index 882b989bae..b3a75c19e9 100644 --- a/packages/core/src/layout/Page/Page.stories.tsx +++ b/packages/core/src/layout/Page/Page.stories.tsx @@ -15,7 +15,6 @@ */ import React, { useState } from 'react'; -import { pageTheme } from '@backstage/theme'; import { Header, Page, @@ -196,7 +195,7 @@ export const PluginWithData = () => { const [selectedTab, setSelectedTab] = useState(2); return (
- + { export const PluginWithTable = () => { return (
- + diff --git a/packages/core/src/layout/Page/Page.tsx b/packages/core/src/layout/Page/Page.tsx index 4bc1917659..a14f772334 100644 --- a/packages/core/src/layout/Page/Page.tsx +++ b/packages/core/src/layout/Page/Page.tsx @@ -15,7 +15,7 @@ */ import React, { FC } from 'react'; -import { PageTheme } from '@backstage/theme'; +import { BackstageTheme } from '@backstage/theme'; import { makeStyles, ThemeProvider } from '@material-ui/core'; const useStyles = makeStyles(() => ({ @@ -30,13 +30,18 @@ const useStyles = makeStyles(() => ({ })); type Props = { - theme: PageTheme; + themeId: string; }; -export const Page: FC = ({ theme, children }) => { +export const Page: FC = ({ themeId, children }) => { const classes = useStyles(); return ( - ({ ...baseTheme, page: theme })}> + ({ + ...baseTheme, + page: baseTheme.getPageTheme({ themeId }), + })} + >
{children}
); diff --git a/packages/core/src/layout/SignInPage/SignInPage.tsx b/packages/core/src/layout/SignInPage/SignInPage.tsx index b2b049d161..ebc9bf278f 100644 --- a/packages/core/src/layout/SignInPage/SignInPage.tsx +++ b/packages/core/src/layout/SignInPage/SignInPage.tsx @@ -19,8 +19,7 @@ import { Page } from '../Page'; import { Header } from '../Header'; import { Content } from '../Content/Content'; import { ContentHeader } from '../ContentHeader/ContentHeader'; -import { BackstageTheme } from '@backstage/theme'; -import { Grid, useTheme } from '@material-ui/core'; +import { Grid } from '@material-ui/core'; import { SignInPageProps, useApi, configApiRef } from '@backstage/core-api'; import { useSignInProviders, getSignInProviders } from './providers'; import { IdentityProviders } from './types'; @@ -39,7 +38,6 @@ export const SignInPage: FC = ({ title, align = 'left', }) => { - const backstageTheme = useTheme(); const configApi = useApi(configApiRef); const classes = useStyles(); @@ -54,7 +52,7 @@ export const SignInPage: FC = ({ } return ( - +
{title && } diff --git a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx index 85250a8569..f755705ac7 100644 --- a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx +++ b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx @@ -23,9 +23,8 @@ import { Progress, useApi, } from '@backstage/core'; -import { BackstageTheme } from '@backstage/theme'; import { catalogApiRef } from '@backstage/plugin-catalog'; -import { Box, useTheme } from '@material-ui/core'; +import { Box } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import React, { useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; @@ -65,7 +64,6 @@ const EntityPageTitle = ({ title }: EntityPageTitleProps) => ( ); export const ApiEntityPage = () => { - const backstageTheme = useTheme(); const { optionalNamespaceAndName } = useParams() as { optionalNamespaceAndName: string; }; @@ -102,11 +100,7 @@ export const ApiEntityPage = () => { ); return ( - +
} pageTitleOverride={headerTitle} diff --git a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx index 410432d715..b534489487 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx @@ -15,8 +15,6 @@ */ import { Header, Page } from '@backstage/core'; -import { BackstageTheme } from '@backstage/theme'; -import { useTheme } from '@material-ui/core'; import React from 'react'; type Props = { @@ -24,14 +22,8 @@ type Props = { }; export const ApiExplorerLayout = ({ children }: Props) => { - const backstageTheme = useTheme(); - return ( - +
{ - const backstageTheme = useTheme(); const greeting = getTimeBasedGreeting(); const profile = useApi(identityApiRef).getProfile(); const userId = useApi(identityApiRef).getUserId(); return ( - +
{ - const backstageTheme = useTheme(); const { optionalNamespaceAndName, kind } = useParams() as { optionalNamespaceAndName: string; kind: string; @@ -89,11 +87,7 @@ export const EntityPageLayout = ({ const showRemovalDialog = () => setConfirmationDialogOpen(true); return ( - +
} pageTitleOverride={headerTitle} diff --git a/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx b/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx index f4baf227db..82954baa86 100644 --- a/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx +++ b/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx @@ -14,15 +14,8 @@ * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; import React, { ReactNode } from 'react'; -import { - Box, - Button, - Container, - makeStyles, - useTheme, -} from '@material-ui/core'; +import { Box, Button, Container, makeStyles } from '@material-ui/core'; import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; import { Header, Page } from '@backstage/core'; import { CostInsightsThemeProvider } from '../CostInsightsPage/CostInsightsThemeProvider'; @@ -44,13 +37,12 @@ const AlertInstructionsLayout = ({ title, children, }: AlertInstructionsLayoutProps) => { - const backstageTheme = useTheme(); const classes = useStyles(); return ( - +
{ - const backstageTheme = useTheme(); const classes = useStyles(); return ( - +
{ const classes = useStyles(); - const backstageTheme = useTheme(); return ( - +
{ @@ -112,14 +111,8 @@ const labels = ( ); export const NewProjectPage = () => { - const backstageTheme = useTheme(); - return ( - +
{labels}
diff --git a/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx b/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx index eb86b6fd03..7e44618441 100644 --- a/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx +++ b/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; import { Content, ContentHeader, @@ -36,7 +35,6 @@ import { TableRow, Theme, Typography, - useTheme, } from '@material-ui/core'; import React from 'react'; import { useAsync } from 'react-use'; @@ -148,13 +146,8 @@ const labels = ( ); export const ProjectDetailsPage = () => { - const backstageTheme = useTheme(); return ( - +
{labels}
diff --git a/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx b/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx index 33b3813c2a..b2148bcfc8 100644 --- a/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx +++ b/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx @@ -15,7 +15,6 @@ */ // NEEDS WORK -import { BackstageTheme } from '@backstage/theme'; import { Content, ContentHeader, @@ -38,7 +37,6 @@ import { TableRow, Tooltip, Typography, - useTheme, } from '@material-ui/core'; import React from 'react'; import { useAsync } from 'react-use'; @@ -135,14 +133,8 @@ const PageContents = () => { }; export const ProjectListPage = () => { - const backstageTheme = useTheme(); - return ( - +
{labels}
diff --git a/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx b/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx index 3b33320abe..70bf4c8c3f 100644 --- a/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx +++ b/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; import React, { FC, useState } from 'react'; import { Content, @@ -29,13 +28,12 @@ import { } from '@backstage/core'; import ClusterTable from '../ClusterTable/ClusterTable'; -import { Button, useTheme } from '@material-ui/core'; +import { Button } from '@material-ui/core'; import { useAsync } from 'react-use'; import { gitOpsApiRef } from '../../api'; import { Alert } from '@material-ui/lab'; const ClusterList: FC<{}> = () => { - const backstageTheme = useTheme(); const api = useApi(gitOpsApiRef); const githubAuth = useApi(githubAuthApiRef); const [githubUsername, setGithubUsername] = useState(String); @@ -92,11 +90,7 @@ const ClusterList: FC<{}> = () => { } return ( - +
diff --git a/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx b/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx index f7205c3326..9b6e5bdb08 100644 --- a/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx +++ b/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; import React, { FC, useEffect, useState } from 'react'; import { Content, @@ -26,13 +25,12 @@ import { githubAuthApiRef, } from '@backstage/core'; -import { Link, useTheme } from '@material-ui/core'; +import { Link } from '@material-ui/core'; import { useParams } from 'react-router-dom'; import { gitOpsApiRef, Status } from '../../api'; import { transformRunStatus } from '../ProfileCatalog'; const ClusterPage: FC<{}> = () => { - const backstageTheme = useTheme(); const params = useParams() as { owner: string; repo: string }; const [pollingLog, setPollingLog] = useState(true); @@ -85,11 +83,7 @@ const ClusterPage: FC<{}> = () => { }, [pollingLog, api, params, githubAuth, githubAccessToken, githubUsername]); return ( - +
diff --git a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx index fe1e97235a..a70cf97342 100644 --- a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx +++ b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; import React, { FC, useEffect, useState } from 'react'; import { Header, @@ -37,7 +36,7 @@ import { useApi, githubAuthApiRef, } from '@backstage/core'; -import { TextField, List, ListItem, Link, useTheme } from '@material-ui/core'; +import { TextField, List, ListItem, Link } from '@material-ui/core'; import ClusterTemplateCardList from '../ClusterTemplateCardList'; import ProfileCardList from '../ProfileCardList'; @@ -81,8 +80,6 @@ export const transformRunStatus = (x: Status[]) => { }; const ProfileCatalog: FC<{}> = () => { - const backstageTheme = useTheme(); - // TODO: get data from REST API const [clusterTemplates] = React.useState([ { @@ -261,11 +258,7 @@ const ProfileCatalog: FC<{}> = () => { ]; return ( - +
{ - const backstageTheme = useTheme(); const graphQlBrowseApi = useApi(graphQlBrowseApiRef); const endpoints = useAsync(() => graphQlBrowseApi.getEndpoints()); @@ -60,11 +58,7 @@ export const GraphiQLPage = () => { } return ( - +
diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index 451383df58..a3e82e79a4 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -14,9 +14,8 @@ * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; import React, { ReactElement, useEffect, useState } from 'react'; -import { Grid, TabProps, useTheme } from '@material-ui/core'; +import { Grid, TabProps } from '@material-ui/core'; import { Config } from '@backstage/config'; import { CardTab, @@ -102,7 +101,6 @@ const groupResponses = (fetchResponse: FetchResponse[]) => { type KubernetesContentProps = { entity: Entity; children?: React.ReactNode }; export const KubernetesContent = ({ entity }: KubernetesContentProps) => { - const backstageTheme = useTheme(); const kubernetesApi = useApi(kubernetesApiRef); const [kubernetesObjects, setKubernetesObjects] = useState< @@ -149,11 +147,7 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { kubernetesObjects?.items.filter(r => r.errors.length > 0) ?? []; return ( - + {kubernetesObjects === undefined && error === undefined && ( diff --git a/plugins/lighthouse/src/components/AuditList/index.tsx b/plugins/lighthouse/src/components/AuditList/index.tsx index cae1761ecb..117a40a901 100644 --- a/plugins/lighthouse/src/components/AuditList/index.tsx +++ b/plugins/lighthouse/src/components/AuditList/index.tsx @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; import React, { useState, useMemo, FC, ReactNode } from 'react'; import { useLocalStorage, useAsync } from 'react-use'; import { useNavigate } from 'react-router-dom'; -import { Grid, Button, useTheme } from '@material-ui/core'; +import { Grid, Button } from '@material-ui/core'; import Alert from '@material-ui/lab/Alert'; import Pagination from '@material-ui/lab/Pagination'; import { @@ -41,7 +40,6 @@ import { createAuditRouteRef } from '../../plugin'; export const LIMIT = 10; const AuditList: FC<{}> = () => { - const backstageTheme = useTheme(); const [dismissedStored] = useLocalStorage(LIGHTHOUSE_INTRO_LOCAL_STORAGE); const [dismissed, setDismissed] = useState(dismissedStored); @@ -95,11 +93,7 @@ const AuditList: FC<{}> = () => { } return ( - +
= () => { }; const ConnectedAuditView = () => { - const backstageTheme = useTheme(); return ( - +
= () => { }; const CreateAudit = () => { - const backstageTheme = useTheme(); return ( - +
= () => { - const backstageTheme = useTheme(); return ( - +
diff --git a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx index 722e2e1964..cd3eb1adbb 100644 --- a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx +++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx @@ -14,9 +14,8 @@ * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; import React, { useState } from 'react'; -import { Grid, makeStyles, useTheme } from '@material-ui/core'; +import { Grid, makeStyles } from '@material-ui/core'; import { InfoCard, Page, @@ -61,7 +60,6 @@ export const RegisterComponentPage = ({ }: { catalogRouteRef: RouteRef; }) => { - const backstageTheme = useTheme(); const classes = useStyles(); const catalogApi = useApi(catalogApiRef); const [formState, setFormState] = useState>( @@ -114,11 +112,7 @@ export const RegisterComponentPage = ({ }; return ( - +
diff --git a/plugins/rollbar/src/components/RollbarHome/RollbarHome.tsx b/plugins/rollbar/src/components/RollbarHome/RollbarHome.tsx index f219be9615..fe99e70575 100644 --- a/plugins/rollbar/src/components/RollbarHome/RollbarHome.tsx +++ b/plugins/rollbar/src/components/RollbarHome/RollbarHome.tsx @@ -14,23 +14,16 @@ * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; -import { useTheme } from '@material-ui/core'; import React from 'react'; import { Content, Header, Page } from '@backstage/core'; import { RollbarProjectTable } from '../RollbarProjectTable/RollbarProjectTable'; import { useRollbarEntities } from '../../hooks/useRollbarEntities'; export const RollbarHome = () => { - const backstageTheme = useTheme(); const { entities, loading, error } = useRollbarEntities(); return ( - +
{ - const backstageTheme = useTheme(); const { entity } = useCatalogEntity(); return ( - +
diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index bd70fd9565..d0cb25f254 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { Content, @@ -29,7 +28,7 @@ import { WarningPanel, } from '@backstage/core'; import { catalogApiRef } from '@backstage/plugin-catalog'; -import { Button, Grid, Link, Typography, useTheme } from '@material-ui/core'; +import { Button, Grid, Link, Typography } from '@material-ui/core'; import React, { useEffect } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import useStaleWhileRevalidate from 'swr'; @@ -49,7 +48,6 @@ const getTemplateCardProps = ( }; export const ScaffolderPage = () => { - const backstageTheme = useTheme(); const catalogApi = useApi(catalogApiRef); const errorApi = useApi(errorApiRef); @@ -67,7 +65,7 @@ export const ScaffolderPage = () => { }, [error, errorApi]); return ( - +
{ - const backstageTheme = useTheme(); const errorApi = useApi(errorApiRef); const catalogApi = useApi(catalogApiRef); const scaffolderApi = useApi(scaffolderApiRef); @@ -146,7 +144,7 @@ export const TemplatePage = () => { } return ( - +
= () => { - const backstageTheme = useTheme(); const [statsFor, setStatsFor] = useState<'12h' | '24h'>('12h'); const toggleStatsFor = () => setStatsFor(statsFor === '12h' ? '12h' : '24h'); const sentryProjectId = 'sample-sentry-project-id'; return ( - +
diff --git a/plugins/tech-radar/src/components/RadarPage.tsx b/plugins/tech-radar/src/components/RadarPage.tsx index d23061b094..d5c4355b0e 100644 --- a/plugins/tech-radar/src/components/RadarPage.tsx +++ b/plugins/tech-radar/src/components/RadarPage.tsx @@ -14,9 +14,8 @@ * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; import React from 'react'; -import { Grid, useTheme } from '@material-ui/core'; +import { Grid } from '@material-ui/core'; import { Content, ContentHeader, @@ -40,13 +39,8 @@ export const RadarPage = ({ pageTitle, ...props }: TechRadarPageProps): JSX.Element => { - const backstageTheme = useTheme(); return ( - +
diff --git a/plugins/techdocs/src/reader/components/TechDocsHome.tsx b/plugins/techdocs/src/reader/components/TechDocsHome.tsx index 016f6df4f0..0408a540ca 100644 --- a/plugins/techdocs/src/reader/components/TechDocsHome.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsHome.tsx @@ -14,11 +14,10 @@ * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; import React from 'react'; import { useAsync } from 'react-use'; import { useNavigate, generatePath } from 'react-router-dom'; -import { Grid, useTheme } from '@material-ui/core'; +import { Grid } from '@material-ui/core'; import { ItemCard, Progress, @@ -31,7 +30,6 @@ import { catalogApiRef } from '@backstage/plugin-catalog'; import { rootDocsRouteRef } from '../../plugin'; export const TechDocsHome = () => { - const backstageTheme = useTheme(); const catalogApi = useApi(catalogApiRef); const navigate = useNavigate(); @@ -44,11 +42,7 @@ export const TechDocsHome = () => { if (loading) { return ( - +
{ if (error) { return ( - +
{ } return ( - +
{ - const backstageTheme = useTheme(); const [documentReady, setDocumentReady] = useState(false); const { namespace, kind, name } = useParams(); @@ -48,11 +45,7 @@ export const TechDocsPage = () => { }; return ( - + { - const backstageTheme = useTheme(); const [activeTab, setActiveTab] = useState(0); const onTabChange = (index: number) => { setActiveTab(index); @@ -46,11 +43,7 @@ export const SettingsPage = ({ providerSettings }: Props) => { ]; return ( - +
{content[activeTab]} diff --git a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx b/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx index 7c68ab96bf..7b94024948 100644 --- a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx +++ b/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; import React from 'react'; import { Link as RouterLink } from 'react-router-dom'; import { @@ -24,7 +23,6 @@ import { ListItem, ListItemText, Link, - useTheme, } from '@material-ui/core'; import { Content, @@ -40,17 +38,12 @@ import { } from '@backstage/core'; const WelcomePage = () => { - const backstageTheme = useTheme(); const appTitle = useApi(configApiRef).getOptionalString('app.title') ?? 'Backstage'; const profile = { givenName: '' }; return ( - +
Date: Thu, 15 Oct 2020 09:37:13 +0530 Subject: [PATCH 034/104] chore: update example component --- .../tutorials/quickstart-app-plugin/ExampleComponent.md | 7 ++----- .../components/ExampleComponent/ExampleComponent.tsx.hbs | 3 +-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md b/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md index 83b18bf4c2..5d633544e3 100644 --- a/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md +++ b/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md @@ -3,14 +3,12 @@ ExampleComponent.tsx reference ```tsx -import { BackstageTheme } from '@backstage/theme'; import React, { FC } from 'react'; -import { Typography, Grid, useTheme } from '@material-ui/core'; +import { Typography, Grid } from '@material-ui/core'; import { InfoCard, Header, Page, - pageTheme, Content, ContentHeader, HeaderLabel, @@ -21,13 +19,12 @@ import { useApi } from '@backstage/core-api'; import ExampleFetchComponent from '../ExampleFetchComponent'; const ExampleComponent: FC<{}> = () => { - const backstageTheme = useTheme(); const identityApi = useApi(identityApiRef); const userId = identityApi.getUserId(); const profile = identityApi.getProfile(); return ( - +
= () => { return ( - +
From c861f4415fec4c9081bd1f6f5e91aa3642918261 Mon Sep 17 00:00:00 2001 From: "althaf.hameez" Date: Thu, 15 Oct 2020 14:24:21 +0800 Subject: [PATCH 035/104] Use organization name as the prefix for Service Catalog --- plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx b/plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx index 92f51ace06..9d658d7122 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx @@ -15,6 +15,7 @@ */ import { + configApiRef, Header, HomepageTimer, identityApiRef, @@ -33,12 +34,13 @@ const CatalogLayout = ({ children }: Props) => { const greeting = getTimeBasedGreeting(); const profile = useApi(identityApiRef).getProfile(); const userId = useApi(identityApiRef).getUserId(); + const orgName = useApi(configApiRef).getOptionalString('organization.name'); return (
From 9c779c0b09e69602926f8f8aaa3f9700d31637f1 Mon Sep 17 00:00:00 2001 From: "althaf.hameez" Date: Thu, 15 Oct 2020 14:59:30 +0800 Subject: [PATCH 036/104] Fix e2e tests --- .../default-app/packages/app/cypress/integration/app.js | 2 +- packages/e2e-test/src/commands/run.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js b/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js index dde26b7b39..2d63193fbf 100644 --- a/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js +++ b/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js @@ -1,6 +1,6 @@ describe('App', () => { it('should render the catalog', () => { cy.visit('/'); - cy.contains('Backstage Service Catalog'); + cy.contains('Spotify Service Catalog'); }); }); diff --git a/packages/e2e-test/src/commands/run.ts b/packages/e2e-test/src/commands/run.ts index cfe0ad9685..7775fb29b2 100644 --- a/packages/e2e-test/src/commands/run.ts +++ b/packages/e2e-test/src/commands/run.ts @@ -301,7 +301,7 @@ async function testAppServe(pluginName: string, appDir: string) { try { const browser = new Browser(); - await waitForPageWithText(browser, '/', 'Backstage Service Catalog'); + await waitForPageWithText(browser, '/', 'Spotify Service Catalog'); await waitForPageWithText( browser, `/${pluginName}`, From fe4926cb7591ab26e0fe7e930b0d421a7138bab1 Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Thu, 15 Oct 2020 13:52:18 +0530 Subject: [PATCH 037/104] chore: remove explicit return --- .../ExampleComponent/ExampleComponent.tsx.hbs | 51 ++++--- packages/core/src/layout/Header/Header.tsx | 134 +++++++++--------- .../ApiExplorerPage/ApiExplorerLayout.tsx | 22 ++- .../NewProjectPage/NewProjectPage.tsx | 32 ++--- .../ProjectDetailsPage/ProjectDetailsPage.tsx | 28 ++-- .../ProjectListPage/ProjectListPage.tsx | 34 +++-- .../src/components/AuditView/index.tsx | 28 ++-- .../src/components/CreateAudit/index.tsx | 27 ++-- .../NewRelicComponent/NewRelicComponent.tsx | 39 +++-- .../tech-radar/src/components/RadarPage.tsx | 45 +++--- 10 files changed, 207 insertions(+), 233 deletions(-) diff --git a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs index 616f2cd2fc..e08f1650d5 100644 --- a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs +++ b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs @@ -11,31 +11,30 @@ import { } from '@backstage/core'; import ExampleFetchComponent from '../ExampleFetchComponent'; -const ExampleComponent: FC<{}> = () => { - return ( - -
- - -
- - - A description of your plugin goes here. - - - - - - All content should be wrapped in a card like this. - - - - - - +const ExampleComponent: FC<{}> = () => ( + +
+ + +
+ + + A description of your plugin goes here. + + + + + + All content should be wrapped in a card like this. + + - -
- ); -} + + + +
+
+
+); + export default ExampleComponent; diff --git a/packages/core/src/layout/Header/Header.tsx b/packages/core/src/layout/Header/Header.tsx index 25afb5c9d1..ec4a3db6e2 100644 --- a/packages/core/src/layout/Header/Header.tsx +++ b/packages/core/src/layout/Header/Header.tsx @@ -22,76 +22,73 @@ import { Tooltip, makeStyles, Breadcrumbs, - useTheme, } from '@material-ui/core'; import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import { BackstageTheme } from '@backstage/theme'; -const useStyles = makeStyles( - theme => ({ - header: { - gridArea: 'pageHeader', - padding: theme.spacing(3), - minHeight: 118, - width: '100%', - boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)', - position: 'relative', - zIndex: 100, - display: 'flex', - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'flex-end', - alignItems: 'center', - backgroundImage: props => props.backgroundImage, - backgroundPosition: 'center', - backgroundSize: 'cover', - }, - leftItemsBox: { - flex: '1 1 auto', - }, - rightItemsBox: { - flex: '0 1 auto', - display: 'flex', - flexDirection: 'row', - flexWrap: 'wrap', - alignItems: 'center', - marginRight: theme.spacing(1), - }, - title: { - color: theme.palette.bursts.fontColor, - lineHeight: '1.0em', - wordBreak: 'break-all', - fontSize: 'calc(24px + 6 * ((100vw - 320px) / 680))', - marginBottom: theme.spacing(1), - }, - subtitle: { - color: 'rgba(255, 255, 255, 0.8)', - lineHeight: '1.0em', - }, - type: { - textTransform: 'uppercase', - fontSize: 11, - opacity: 0.8, - marginBottom: theme.spacing(1), - color: theme.palette.bursts.fontColor, - }, - breadcrumb: { - fontSize: 'calc(15px + 1 * ((100vw - 320px) / 680))', - color: theme.palette.bursts.fontColor, - }, - breadcrumbType: { - fontSize: 'inherit', - opacity: 0.7, - marginRight: -theme.spacing(0.3), - marginBottom: theme.spacing(0.3), - }, - breadcrumbTitle: { - fontSize: 'inherit', - marginLeft: -theme.spacing(0.3), - marginBottom: theme.spacing(0.3), - }, - }), -); +const useStyles = makeStyles(theme => ({ + header: { + gridArea: 'pageHeader', + padding: theme.spacing(3), + minHeight: 118, + width: '100%', + boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)', + position: 'relative', + zIndex: 100, + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'flex-end', + alignItems: 'center', + backgroundImage: theme.page.backgroundImage, + backgroundPosition: 'center', + backgroundSize: 'cover', + }, + leftItemsBox: { + flex: '1 1 auto', + }, + rightItemsBox: { + flex: '0 1 auto', + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', + alignItems: 'center', + marginRight: theme.spacing(1), + }, + title: { + color: theme.palette.bursts.fontColor, + lineHeight: '1.0em', + wordBreak: 'break-all', + fontSize: 'calc(24px + 6 * ((100vw - 320px) / 680))', + marginBottom: theme.spacing(1), + }, + subtitle: { + color: 'rgba(255, 255, 255, 0.8)', + lineHeight: '1.0em', + }, + type: { + textTransform: 'uppercase', + fontSize: 11, + opacity: 0.8, + marginBottom: theme.spacing(1), + color: theme.palette.bursts.fontColor, + }, + breadcrumb: { + fontSize: 'calc(15px + 1 * ((100vw - 320px) / 680))', + color: theme.palette.bursts.fontColor, + }, + breadcrumbType: { + fontSize: 'inherit', + opacity: 0.7, + marginRight: -theme.spacing(0.3), + marginBottom: theme.spacing(0.3), + }, + breadcrumbTitle: { + fontSize: 'inherit', + marginLeft: -theme.spacing(0.3), + marginBottom: theme.spacing(0.3), + }, +})); type HeaderStyles = ReturnType; @@ -200,10 +197,7 @@ export const Header: FC = ({ type, typeLink, }) => { - const backstageTheme = useTheme(); - const classes = useStyles({ - backgroundImage: backstageTheme.page.backgroundImage, - }); + const classes = useStyles(); const documentTitle = pageTitleOverride || title; const pageTitle = title || pageTitleOverride; const titleTemplate = `${documentTitle} | %s | Backstage`; diff --git a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx index b534489487..0720ecf45b 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx @@ -21,15 +21,13 @@ type Props = { children?: React.ReactNode; }; -export const ApiExplorerLayout = ({ children }: Props) => { - return ( - -
- {children} - - ); -}; +export const ApiExplorerLayout = ({ children }: Props) => ( + +
+ {children} + +); diff --git a/plugins/gcp-projects/src/components/NewProjectPage/NewProjectPage.tsx b/plugins/gcp-projects/src/components/NewProjectPage/NewProjectPage.tsx index 94cf6ce920..462aa17099 100644 --- a/plugins/gcp-projects/src/components/NewProjectPage/NewProjectPage.tsx +++ b/plugins/gcp-projects/src/components/NewProjectPage/NewProjectPage.tsx @@ -110,20 +110,18 @@ const labels = ( ); -export const NewProjectPage = () => { - return ( - -
- {labels} -
- - - - This plugin allows you to view and interact with your gcp projects. - - - - -
- ); -}; +export const NewProjectPage = () => ( + +
+ {labels} +
+ + + + This plugin allows you to view and interact with your gcp projects. + + + + +
+); diff --git a/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx b/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx index 7e44618441..0c6350b849 100644 --- a/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx +++ b/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx @@ -145,18 +145,16 @@ const labels = ( ); -export const ProjectDetailsPage = () => { - return ( - -
- {labels} -
- - - Support Button - - - -
- ); -}; +export const ProjectDetailsPage = () => ( + +
+ {labels} +
+ + + Support Button + + + +
+); diff --git a/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx b/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx index b2148bcfc8..b911040535 100644 --- a/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx +++ b/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx @@ -132,21 +132,19 @@ const PageContents = () => { ); }; -export const ProjectListPage = () => { - return ( - -
- {labels} -
- - - - All your software catalog entities - - - -
- ); -}; +export const ProjectListPage = () => ( + +
+ {labels} +
+ + + + All your software catalog entities + + + +
+); diff --git a/plugins/lighthouse/src/components/AuditView/index.tsx b/plugins/lighthouse/src/components/AuditView/index.tsx index 4813be8bff..ac38a329f1 100644 --- a/plugins/lighthouse/src/components/AuditView/index.tsx +++ b/plugins/lighthouse/src/components/AuditView/index.tsx @@ -191,20 +191,16 @@ export const AuditViewContent: FC<{}> = () => { ); }; -const ConnectedAuditView = () => { - return ( - -
- - -
- - - -
- ); -}; +const ConnectedAuditView = () => ( + +
+ + +
+ + + +
+); + export default ConnectedAuditView; diff --git a/plugins/lighthouse/src/components/CreateAudit/index.tsx b/plugins/lighthouse/src/components/CreateAudit/index.tsx index ac4d2a2e3c..372992a718 100644 --- a/plugins/lighthouse/src/components/CreateAudit/index.tsx +++ b/plugins/lighthouse/src/components/CreateAudit/index.tsx @@ -168,21 +168,16 @@ export const CreateAuditContent: FC<{}> = () => { ); }; -const CreateAudit = () => { - return ( - -
- - -
- - - -
- ); -}; +const CreateAudit = () => ( + +
+ + +
+ + + +
+); export default CreateAudit; diff --git a/plugins/newrelic/src/components/NewRelicComponent/NewRelicComponent.tsx b/plugins/newrelic/src/components/NewRelicComponent/NewRelicComponent.tsx index 5dea8b7ee6..81e28a8c80 100644 --- a/plugins/newrelic/src/components/NewRelicComponent/NewRelicComponent.tsx +++ b/plugins/newrelic/src/components/NewRelicComponent/NewRelicComponent.tsx @@ -26,25 +26,24 @@ import { } from '@backstage/core'; import NewRelicFetchComponent from '../NewRelicFetchComponent'; -const NewRelicComponent: FC<{}> = () => { - return ( - -
- -
- - - - New Relic Application Performance Monitoring - - - - - - +const NewRelicComponent: FC<{}> = () => ( + +
+ +
+ + + + New Relic Application Performance Monitoring + + + + + - -
- ); -}; +
+
+
+); + export default NewRelicComponent; diff --git a/plugins/tech-radar/src/components/RadarPage.tsx b/plugins/tech-radar/src/components/RadarPage.tsx index d5c4355b0e..84cfef3213 100644 --- a/plugins/tech-radar/src/components/RadarPage.tsx +++ b/plugins/tech-radar/src/components/RadarPage.tsx @@ -38,30 +38,29 @@ export const RadarPage = ({ subtitle, pageTitle, ...props -}: TechRadarPageProps): JSX.Element => { - return ( - -
- - -
- - - - This is used for visualizing the official guidelines of different - areas of software development such as languages, frameworks, - infrastructure and processes. - - - - - - +}: TechRadarPageProps): JSX.Element => ( + +
+ + +
+ + + + This is used for visualizing the official guidelines of different + areas of software development such as languages, frameworks, + infrastructure and processes. + + + + + - -
- ); -}; +
+
+
+); + RadarPage.defaultProps = { title: 'Tech Radar', subtitle: 'Pick the recommended technologies for your projects', From 1f4f856cd4ae8d62d45d374b82a04f18fccf8d34 Mon Sep 17 00:00:00 2001 From: "althaf.hameez" Date: Thu, 15 Oct 2020 16:25:15 +0800 Subject: [PATCH 038/104] Revert --- .../default-app/packages/app/cypress/integration/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js b/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js index 2d63193fbf..dde26b7b39 100644 --- a/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js +++ b/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js @@ -1,6 +1,6 @@ describe('App', () => { it('should render the catalog', () => { cy.visit('/'); - cy.contains('Spotify Service Catalog'); + cy.contains('Backstage Service Catalog'); }); }); From 53104074ee594c02c083c10f3b17c38759157c35 Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Thu, 15 Oct 2020 14:04:20 +0530 Subject: [PATCH 039/104] feat: allow passing pageTheme in createThemeOptions --- packages/theme/src/baseTheme.ts | 3 +-- packages/theme/src/themes.ts | 3 +++ packages/theme/src/types.ts | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/theme/src/baseTheme.ts b/packages/theme/src/baseTheme.ts index 1f0e01586e..640476c487 100644 --- a/packages/theme/src/baseTheme.ts +++ b/packages/theme/src/baseTheme.ts @@ -24,8 +24,6 @@ import { SimpleThemeOptions, } from './types'; -import { pageTheme } from './pageTheme'; - const DEFAULT_FONT_FAMILY = '"Helvetica Neue", Helvetica, Roboto, Arial, sans-serif'; @@ -36,6 +34,7 @@ export function createThemeOptions( palette, fontFamily = DEFAULT_FONT_FAMILY, defaultPageTheme, + pageTheme, } = options; if (!pageTheme[defaultPageTheme]) { diff --git a/packages/theme/src/themes.ts b/packages/theme/src/themes.ts index 3838ed01cc..2c06f387b8 100644 --- a/packages/theme/src/themes.ts +++ b/packages/theme/src/themes.ts @@ -15,6 +15,7 @@ */ import { createTheme } from './baseTheme'; +import { pageTheme } from './pageTheme'; import { yellow } from '@material-ui/core/colors'; export const lightTheme = createTheme({ @@ -74,6 +75,7 @@ export const lightTheme = createTheme({ }, }, defaultPageTheme: 'home', + pageTheme, }); export const darkTheme = createTheme({ @@ -133,4 +135,5 @@ export const darkTheme = createTheme({ }, }, defaultPageTheme: 'home', + pageTheme, }); diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts index e551208f3f..696ccf586f 100644 --- a/packages/theme/src/types.ts +++ b/packages/theme/src/types.ts @@ -94,6 +94,7 @@ export interface BackstageThemeOptions extends ThemeOptions { export type SimpleThemeOptions = { palette: BackstagePaletteOptions; defaultPageTheme: string; + pageTheme: Record; fontFamily?: string; }; From 0d67d11eb349f810a0069588a2496a47a04a1718 Mon Sep 17 00:00:00 2001 From: "althaf.hameez" Date: Thu, 15 Oct 2020 16:54:36 +0800 Subject: [PATCH 040/104] fix --- .../default-app/packages/app/cypress/integration/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js b/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js index dde26b7b39..2d63193fbf 100644 --- a/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js +++ b/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js @@ -1,6 +1,6 @@ describe('App', () => { it('should render the catalog', () => { cy.visit('/'); - cy.contains('Backstage Service Catalog'); + cy.contains('Spotify Service Catalog'); }); }); From a6c0d9803ecde293b82a0464375a7dad6669ee52 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 15 Oct 2020 11:08:16 +0200 Subject: [PATCH 041/104] Type with interface instead of class. --- plugins/newrelic/src/api/index.ts | 6 +++++- plugins/newrelic/src/plugin.ts | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/newrelic/src/api/index.ts b/plugins/newrelic/src/api/index.ts index 692cb19e6f..eb94acdbd1 100644 --- a/plugins/newrelic/src/api/index.ts +++ b/plugins/newrelic/src/api/index.ts @@ -68,7 +68,11 @@ type Options = { proxyPathBase?: string; }; -export class NewRelicApi { +export interface NewRelicApi { + getApplications(): Promise; +} + +export class NewRelicClient implements NewRelicApi { private readonly discoveryApi: DiscoveryApi; private readonly proxyPathBase: string; diff --git a/plugins/newrelic/src/plugin.ts b/plugins/newrelic/src/plugin.ts index f247158fbd..5f5ba88617 100644 --- a/plugins/newrelic/src/plugin.ts +++ b/plugins/newrelic/src/plugin.ts @@ -20,7 +20,7 @@ import { createRouteRef, discoveryApiRef, } from '@backstage/core'; -import { NewRelicApi, newRelicApiRef } from './api'; +import { NewRelicClient, newRelicApiRef } from './api'; import NewRelicComponent from './components/NewRelicComponent'; export const rootRouteRef = createRouteRef({ @@ -34,7 +34,7 @@ export const plugin = createPlugin({ createApiFactory({ api: newRelicApiRef, deps: { discoveryApi: discoveryApiRef }, - factory: ({ discoveryApi }) => new NewRelicApi({ discoveryApi }), + factory: ({ discoveryApi }) => new NewRelicClient({ discoveryApi }), }), ], register({ router }) { From 94e8a38953397f3b69bef54880d6d20375c05e01 Mon Sep 17 00:00:00 2001 From: "althaf.hameez" Date: Thu, 15 Oct 2020 18:08:22 +0800 Subject: [PATCH 042/104] Acme corporation fix --- .../default-app/packages/app/cypress/integration/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js b/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js index 2d63193fbf..227e218fe6 100644 --- a/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js +++ b/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js @@ -1,6 +1,6 @@ describe('App', () => { it('should render the catalog', () => { cy.visit('/'); - cy.contains('Spotify Service Catalog'); + cy.contains('Acme Corporation Service Catalog'); }); }); From 37015caeb9679197dbdb4aa887168ab8e9399fef Mon Sep 17 00:00:00 2001 From: "althaf.hameez" Date: Thu, 15 Oct 2020 18:25:48 +0800 Subject: [PATCH 043/104] Revert to backstage --- packages/e2e-test/src/commands/run.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/e2e-test/src/commands/run.ts b/packages/e2e-test/src/commands/run.ts index 7775fb29b2..cfe0ad9685 100644 --- a/packages/e2e-test/src/commands/run.ts +++ b/packages/e2e-test/src/commands/run.ts @@ -301,7 +301,7 @@ async function testAppServe(pluginName: string, appDir: string) { try { const browser = new Browser(); - await waitForPageWithText(browser, '/', 'Spotify Service Catalog'); + await waitForPageWithText(browser, '/', 'Backstage Service Catalog'); await waitForPageWithText( browser, `/${pluginName}`, From 1af3dbdb5ebaa8c283d4b2cb6eecd04aeb1949ba Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 15 Oct 2020 12:37:50 +0200 Subject: [PATCH 044/104] Provide more context. Handle more edge cases. --- plugins/newrelic/src/api/index.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/plugins/newrelic/src/api/index.ts b/plugins/newrelic/src/api/index.ts index eb94acdbd1..d76a87875c 100644 --- a/plugins/newrelic/src/api/index.ts +++ b/plugins/newrelic/src/api/index.ts @@ -84,10 +84,20 @@ export class NewRelicClient implements NewRelicApi { async getApplications(): Promise { const url = await this.getApiUrl('apm', 'applications.json'); const response = await fetch(url); - const responseJson = await response.json(); + let responseJson; + + try { + responseJson = await response.json(); + } catch (e) { + responseJson = { applications: [] }; + } if (response.status !== 200) { - throw new Error(responseJson?.error?.title || response.statusText); + throw new Error( + `Error communicating with New Relic: ${ + responseJson?.error?.title || response.statusText + }`, + ); } return responseJson; From a6c47b3d180e12dbf05d58c3097c025954165d95 Mon Sep 17 00:00:00 2001 From: "althaf.hameez" Date: Thu, 15 Oct 2020 18:42:26 +0800 Subject: [PATCH 045/104] Acme --- packages/e2e-test/src/commands/run.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/e2e-test/src/commands/run.ts b/packages/e2e-test/src/commands/run.ts index cfe0ad9685..e7e23f4825 100644 --- a/packages/e2e-test/src/commands/run.ts +++ b/packages/e2e-test/src/commands/run.ts @@ -301,7 +301,7 @@ async function testAppServe(pluginName: string, appDir: string) { try { const browser = new Browser(); - await waitForPageWithText(browser, '/', 'Backstage Service Catalog'); + await waitForPageWithText(browser, '/', 'Acme Corporation Service Catalog'); await waitForPageWithText( browser, `/${pluginName}`, From 4e5d8b28ac54b0bdba1dc443ffeeb57c911f02ff Mon Sep 17 00:00:00 2001 From: "althaf.hameez" Date: Thu, 15 Oct 2020 18:53:46 +0800 Subject: [PATCH 046/104] Fix prettier --- packages/e2e-test/src/commands/run.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/e2e-test/src/commands/run.ts b/packages/e2e-test/src/commands/run.ts index e7e23f4825..c05aed0b83 100644 --- a/packages/e2e-test/src/commands/run.ts +++ b/packages/e2e-test/src/commands/run.ts @@ -301,7 +301,11 @@ async function testAppServe(pluginName: string, appDir: string) { try { const browser = new Browser(); - await waitForPageWithText(browser, '/', 'Acme Corporation Service Catalog'); + await waitForPageWithText( + browser, + '/', + 'Acme Corporation Service Catalog', + ); await waitForPageWithText( browser, `/${pluginName}`, From 6943ead4c644958245974304306fec8918e7a995 Mon Sep 17 00:00:00 2001 From: Markus Heinemann Date: Thu, 15 Oct 2020 13:04:12 +0000 Subject: [PATCH 047/104] feat: add base url config property to github scaffholders --- app-config.yaml | 1 + packages/backend/src/plugins/scaffolder.ts | 6 +++++- .../default-app/packages/backend/src/plugins/scaffolder.ts | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 8ec43c6007..b527069c15 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -147,6 +147,7 @@ catalog: scaffolder: github: + baseUrl: https://github.com token: $env: GITHUB_TOKEN visibility: public # or 'internal' or 'private' diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts index 90667bfb16..7ba90ed8b5 100644 --- a/packages/backend/src/plugins/scaffolder.ts +++ b/packages/backend/src/plugins/scaffolder.ts @@ -68,7 +68,11 @@ export default async function createPlugin({ ) as RepoVisibilityOptions; const githubToken = githubConfig.getString('token'); - const githubClient = new Octokit({ auth: githubToken }); + const githubBaseUrl = githubConfig.getString('baseUrl'); + const githubClient = new Octokit({ + auth: githubToken, + baseUrl: githubBaseUrl, + }); const githubPublisher = new GithubPublisher({ client: githubClient, token: githubToken, diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts index ed16d766f1..9f59412036 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts @@ -48,7 +48,8 @@ export default async function createPlugin({ ) as RepoVisibilityOptions; const githubToken = githubConfig.getString('token'); - const githubClient = new Octokit({ auth: githubToken }); + const githubBaseUrl = githubConfig.getString('baseUrl'); + const githubClient = new Octokit({ auth: githubToken, baseUrl: githubBaseUrl }); const githubPublisher = new GithubPublisher({ client: githubClient, token: githubToken, From 1c8c437562e7945b6fe887e4e3a9f35301d30389 Mon Sep 17 00:00:00 2001 From: Markus Heinemann Date: Thu, 15 Oct 2020 13:18:14 +0000 Subject: [PATCH 048/104] Add changeset --- .changeset/selfish-bats-perform.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/selfish-bats-perform.md diff --git a/.changeset/selfish-bats-perform.md b/.changeset/selfish-bats-perform.md new file mode 100644 index 0000000000..e4d05c088c --- /dev/null +++ b/.changeset/selfish-bats-perform.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-scaffolder-backend': patch +--- + +The new `scaffolder.github.baseUrl` config property allows to specify a custom base url for GitHub enterprise instances From 1797d4924c198ec37dabb92edcc61c8f4b9cb886 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 15 Oct 2020 15:38:34 +0200 Subject: [PATCH 049/104] Update create-app to use expected techdocs api origin url. (#2911) --- packages/create-app/templates/default-app/app-config.yaml.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index 7175ea0e4f..347bbaa29c 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -42,7 +42,7 @@ proxy: techdocs: storageUrl: http://localhost:7000/api/techdocs/static/docs - requestUrl: http://localhost:7000/api/techdocs/docs + requestUrl: http://localhost:7000/api/techdocs generators: techdocs: 'docker' From a5cb46bac95b4e181e6a86a14608c7d47b83a29b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 15 Oct 2020 15:48:03 +0200 Subject: [PATCH 050/104] chore(catalog-backend): rename locationprocessor to catalogprocessor --- .changeset/raghu-is-great.md | 10 +++++ .../src/ingestion/LocationReaders.ts | 38 +++++++++---------- .../AnnotateLocationEntityProcessor.ts | 4 +- .../processors/AzureApiReaderProcessor.ts | 10 ++--- .../processors/BitbucketApiReaderProcessor.ts | 10 ++--- .../processors/CodeOwnersProcessor.ts | 9 ++--- .../processors/EntityPolicyProcessor.ts | 4 +- .../processors/FileReaderProcessor.ts | 6 +-- .../processors/GithubOrgReaderProcessor.ts | 6 +-- .../processors/GithubReaderProcessor.ts | 6 +-- .../processors/GitlabApiReaderProcessor.ts | 10 ++--- .../processors/GitlabReaderProcessor.ts | 6 +-- .../processors/LdapOrgReaderProcessor.ts | 6 +-- .../processors/LocationEntityProcessor.ts | 6 +-- .../processors/PlaceholderProcessor.ts | 4 +- .../processors/StaticLocationProcessor.ts | 6 +-- .../processors/UrlReaderProcessor.test.ts | 24 ++++++------ .../processors/UrlReaderProcessor.ts | 8 ++-- .../processors/YamlProcessor.test.ts | 18 ++++----- .../src/ingestion/processors/YamlProcessor.ts | 6 +-- .../src/ingestion/processors/results.ts | 14 +++---- .../src/ingestion/processors/types.ts | 32 ++++++++-------- .../src/service/CatalogBuilder.test.ts | 8 ++-- .../src/service/CatalogBuilder.ts | 36 +++++++++--------- 24 files changed, 147 insertions(+), 140 deletions(-) create mode 100644 .changeset/raghu-is-great.md diff --git a/.changeset/raghu-is-great.md b/.changeset/raghu-is-great.md new file mode 100644 index 0000000000..805e49f3e8 --- /dev/null +++ b/.changeset/raghu-is-great.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Renamed the `LocationProcessor` class to `CatalogProcessor`. + +Likewise, renamed `LocationProcessorResult`, `LocationProcessorLocationResult`, +`LocationProcessorDataResult`, `LocationProcessorEntityResult`, +`LocationProcessorErrorResult`, and `LocationProcessorEmit` to their `Catalog*` +counterparts. diff --git a/plugins/catalog-backend/src/ingestion/LocationReaders.ts b/plugins/catalog-backend/src/ingestion/LocationReaders.ts index 0f6ad18f97..c36b3d2b05 100644 --- a/plugins/catalog-backend/src/ingestion/LocationReaders.ts +++ b/plugins/catalog-backend/src/ingestion/LocationReaders.ts @@ -25,13 +25,13 @@ import { Logger } from 'winston'; import { CatalogRulesEnforcer } from './CatalogRules'; import * as result from './processors/results'; import { - LocationProcessor, - LocationProcessorDataResult, - LocationProcessorEmit, - LocationProcessorEntityResult, - LocationProcessorErrorResult, - LocationProcessorLocationResult, - LocationProcessorResult, + CatalogProcessor, + CatalogProcessorDataResult, + CatalogProcessorEmit, + CatalogProcessorEntityResult, + CatalogProcessorErrorResult, + CatalogProcessorLocationResult, + CatalogProcessorResult, } from './processors/types'; import { LocationReader, ReadLocationResult } from './types'; @@ -42,7 +42,7 @@ type Options = { reader: UrlReader; logger: Logger; config: Config; - processors: LocationProcessor[]; + processors: CatalogProcessor[]; rulesEnforcer: CatalogRulesEnforcer; }; @@ -60,11 +60,11 @@ export class LocationReaders implements LocationReader { const { rulesEnforcer, logger } = this.options; const output: ReadLocationResult = { entities: [], errors: [] }; - let items: LocationProcessorResult[] = [result.location(location, false)]; + let items: CatalogProcessorResult[] = [result.location(location, false)]; for (let depth = 0; depth < MAX_DEPTH; ++depth) { - const newItems: LocationProcessorResult[] = []; - const emit: LocationProcessorEmit = i => newItems.push(i); + const newItems: CatalogProcessorResult[] = []; + const emit: CatalogProcessorEmit = i => newItems.push(i); for (const item of items) { if (item.type === 'location') { @@ -109,8 +109,8 @@ export class LocationReaders implements LocationReader { } private async handleLocation( - item: LocationProcessorLocationResult, - emit: LocationProcessorEmit, + item: CatalogProcessorLocationResult, + emit: CatalogProcessorEmit, ) { const { processors, logger } = this.options; @@ -136,8 +136,8 @@ export class LocationReaders implements LocationReader { } private async handleData( - item: LocationProcessorDataResult, - emit: LocationProcessorEmit, + item: CatalogProcessorDataResult, + emit: CatalogProcessorEmit, ) { const { processors, logger } = this.options; @@ -160,8 +160,8 @@ export class LocationReaders implements LocationReader { } private async handleEntity( - item: LocationProcessorEntityResult, - emit: LocationProcessorEmit, + item: CatalogProcessorEntityResult, + emit: CatalogProcessorEmit, ): Promise { const { processors, logger } = this.options; @@ -189,8 +189,8 @@ export class LocationReaders implements LocationReader { } private async handleError( - item: LocationProcessorErrorResult, - emit: LocationProcessorEmit, + item: CatalogProcessorErrorResult, + emit: CatalogProcessorEmit, ) { const { processors, logger } = this.options; diff --git a/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts index ec7e59281e..fea70b6c5e 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts @@ -16,9 +16,9 @@ import { Entity, LocationSpec } from '@backstage/catalog-model'; import lodash from 'lodash'; -import { LocationProcessor } from './types'; +import { CatalogProcessor } from './types'; -export class AnnotateLocationEntityProcessor implements LocationProcessor { +export class AnnotateLocationEntityProcessor implements CatalogProcessor { async processEntity(entity: Entity, location: LocationSpec): Promise { return lodash.merge( { diff --git a/plugins/catalog-backend/src/ingestion/processors/AzureApiReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/AzureApiReaderProcessor.ts index 2a15c18c84..b6e65a7d7a 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AzureApiReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AzureApiReaderProcessor.ts @@ -15,17 +15,17 @@ */ import { LocationSpec } from '@backstage/catalog-model'; -import fetch, { RequestInit, HeadersInit } from 'node-fetch'; -import * as result from './results'; -import { LocationProcessor, LocationProcessorEmit } from './types'; import { Config } from '@backstage/config'; +import fetch, { HeadersInit, RequestInit } from 'node-fetch'; +import * as result from './results'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; // *********************************************************************** // * NOTE: This has been replaced by packages/backend-common/src/reading * // * Don't implement new functionality here as this file will be removed * // *********************************************************************** -export class AzureApiReaderProcessor implements LocationProcessor { +export class AzureApiReaderProcessor implements CatalogProcessor { private privateToken: string; constructor(config: Config) { @@ -54,7 +54,7 @@ export class AzureApiReaderProcessor implements LocationProcessor { async readLocation( location: LocationSpec, optional: boolean, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise { if (location.type !== 'azure/api') { return false; diff --git a/plugins/catalog-backend/src/ingestion/processors/BitbucketApiReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BitbucketApiReaderProcessor.ts index b3720a5b76..4efcec2884 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BitbucketApiReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BitbucketApiReaderProcessor.ts @@ -15,17 +15,17 @@ */ import { LocationSpec } from '@backstage/catalog-model'; -import fetch, { RequestInit, HeadersInit } from 'node-fetch'; -import * as result from './results'; -import { LocationProcessor, LocationProcessorEmit } from './types'; import { Config } from '@backstage/config'; +import fetch, { HeadersInit, RequestInit } from 'node-fetch'; +import * as result from './results'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; // *********************************************************************** // * NOTE: This has been replaced by packages/backend-common/src/reading * // * Don't implement new functionality here as this file will be removed * // *********************************************************************** -export class BitbucketApiReaderProcessor implements LocationProcessor { +export class BitbucketApiReaderProcessor implements CatalogProcessor { private username: string; private password: string; @@ -58,7 +58,7 @@ export class BitbucketApiReaderProcessor implements LocationProcessor { async readLocation( location: LocationSpec, optional: boolean, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise { if (location.type !== 'bitbucket/api') { return false; diff --git a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts index e624af91ee..1802e01d99 100644 --- a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts @@ -16,14 +16,13 @@ import { UrlReader } from '@backstage/backend-common'; import { Entity, LocationSpec } from '@backstage/catalog-model'; -import { LocationProcessor } from './types'; import * as codeowners from 'codeowners-utils'; import { CodeOwnersEntry } from 'codeowners-utils'; -import parseGitUri from 'git-url-parse'; -import { filter, head, get, pipe, reverse } from 'lodash/fp'; - // NOTE: This can be removed when ES2021 is implemented import 'core-js/features/promise'; +import parseGitUri from 'git-url-parse'; +import { filter, get, head, pipe, reverse } from 'lodash/fp'; +import { CatalogProcessor } from './types'; const ALLOWED_LOCATION_TYPES = [ 'azure/api', @@ -38,7 +37,7 @@ type Options = { reader: UrlReader; }; -export class CodeOwnersProcessor implements LocationProcessor { +export class CodeOwnersProcessor implements CatalogProcessor { constructor(private readonly options: Options) {} async processEntity(entity: Entity, location: LocationSpec): Promise { diff --git a/plugins/catalog-backend/src/ingestion/processors/EntityPolicyProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/EntityPolicyProcessor.ts index 67da9a6705..87543d91f4 100644 --- a/plugins/catalog-backend/src/ingestion/processors/EntityPolicyProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/EntityPolicyProcessor.ts @@ -15,9 +15,9 @@ */ import { Entity, EntityPolicy } from '@backstage/catalog-model'; -import { LocationProcessor } from './types'; +import { CatalogProcessor } from './types'; -export class EntityPolicyProcessor implements LocationProcessor { +export class EntityPolicyProcessor implements CatalogProcessor { private readonly policy: EntityPolicy; constructor(policy: EntityPolicy) { diff --git a/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.ts index b95e2ddb58..83fc01185b 100644 --- a/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.ts @@ -17,13 +17,13 @@ import { LocationSpec } from '@backstage/catalog-model'; import fs from 'fs-extra'; import * as result from './results'; -import { LocationProcessor, LocationProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; -export class FileReaderProcessor implements LocationProcessor { +export class FileReaderProcessor implements CatalogProcessor { async readLocation( location: LocationSpec, optional: boolean, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise { if (location.type !== 'file') { return false; diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts index b8a6f6c43f..bef57a6e5c 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts @@ -19,14 +19,14 @@ import { Config } from '@backstage/config'; import { graphql } from '@octokit/graphql'; import { Logger } from 'winston'; import * as results from './results'; -import { LocationProcessor, LocationProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; import { getOrganizationTeams, getOrganizationUsers } from './util/github'; import { buildOrgHierarchy } from './util/org'; /** * Extracts teams and users out of a GitHub org. */ -export class GithubOrgReaderProcessor implements LocationProcessor { +export class GithubOrgReaderProcessor implements CatalogProcessor { private readonly providers: ProviderConfig[]; private readonly logger: Logger; @@ -45,7 +45,7 @@ export class GithubOrgReaderProcessor implements LocationProcessor { async readLocation( location: LocationSpec, _optional: boolean, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise { if (location.type !== 'github-org') { return false; diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubReaderProcessor.ts index 89d1f69860..2f03900cdb 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubReaderProcessor.ts @@ -20,7 +20,7 @@ import parseGitUri from 'git-url-parse'; import fetch, { HeadersInit, RequestInit } from 'node-fetch'; import { Logger } from 'winston'; import * as result from './results'; -import { LocationProcessor, LocationProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; // *********************************************************************** // * NOTE: This has been replaced by packages/backend-common/src/reading * @@ -210,7 +210,7 @@ export function readConfig(config: Config, logger: Logger): ProviderConfig[] { * A processor that adds the ability to read files from GitHub v3 APIs, such as * the one exposed by GitHub itself. */ -export class GithubReaderProcessor implements LocationProcessor { +export class GithubReaderProcessor implements CatalogProcessor { private providers: ProviderConfig[]; static fromConfig(config: Config, logger: Logger) { @@ -224,7 +224,7 @@ export class GithubReaderProcessor implements LocationProcessor { async readLocation( location: LocationSpec, optional: boolean, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise { // The github/api type is for backward compatibility if (location.type !== 'github' && location.type !== 'github/api') { diff --git a/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts index aeba7a7ebe..e47b7d1aad 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts @@ -15,17 +15,17 @@ */ import { LocationSpec } from '@backstage/catalog-model'; -import fetch, { RequestInit, HeadersInit } from 'node-fetch'; -import * as result from './results'; -import { LocationProcessor, LocationProcessorEmit } from './types'; import { Config } from '@backstage/config'; +import fetch, { HeadersInit, RequestInit } from 'node-fetch'; +import * as result from './results'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; // *********************************************************************** // * NOTE: This has been replaced by packages/backend-common/src/reading * // * Don't implement new functionality here as this file will be removed * // *********************************************************************** -export class GitlabApiReaderProcessor implements LocationProcessor { +export class GitlabApiReaderProcessor implements CatalogProcessor { private privateToken: string; constructor(config: Config) { @@ -50,7 +50,7 @@ export class GitlabApiReaderProcessor implements LocationProcessor { async readLocation( location: LocationSpec, optional: boolean, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise { if (location.type !== 'gitlab/api') { return false; diff --git a/plugins/catalog-backend/src/ingestion/processors/GitlabReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GitlabReaderProcessor.ts index b51bf85a8e..2c58a6d43e 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GitlabReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GitlabReaderProcessor.ts @@ -17,18 +17,18 @@ import { LocationSpec } from '@backstage/catalog-model'; import fetch from 'node-fetch'; import * as result from './results'; -import { LocationProcessor, LocationProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; // *********************************************************************** // * NOTE: This has been replaced by packages/backend-common/src/reading * // * Don't implement new functionality here as this file will be removed * // *********************************************************************** -export class GitlabReaderProcessor implements LocationProcessor { +export class GitlabReaderProcessor implements CatalogProcessor { async readLocation( location: LocationSpec, optional: boolean, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise { if (location.type !== 'gitlab') { return false; diff --git a/plugins/catalog-backend/src/ingestion/processors/LdapOrgReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/LdapOrgReaderProcessor.ts index a32c96480f..d863142169 100644 --- a/plugins/catalog-backend/src/ingestion/processors/LdapOrgReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/LdapOrgReaderProcessor.ts @@ -24,12 +24,12 @@ import { readLdapOrg, } from './ldap'; import * as results from './results'; -import { LocationProcessor, LocationProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; /** * Extracts teams and users out of an LDAP server. */ -export class LdapOrgReaderProcessor implements LocationProcessor { +export class LdapOrgReaderProcessor implements CatalogProcessor { private readonly providers: LdapProviderConfig[]; private readonly logger: Logger; @@ -49,7 +49,7 @@ export class LdapOrgReaderProcessor implements LocationProcessor { async readLocation( location: LocationSpec, _optional: boolean, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise { if (location.type !== 'ldap-org') { return false; diff --git a/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.ts index a5c9718574..1917f58fbd 100644 --- a/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.ts @@ -16,13 +16,13 @@ import { Entity, LocationEntity, LocationSpec } from '@backstage/catalog-model'; import * as result from './results'; -import { LocationProcessor, LocationProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; -export class LocationRefProcessor implements LocationProcessor { +export class LocationRefProcessor implements CatalogProcessor { async processEntity( entity: Entity, _location: LocationSpec, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise { if (entity.kind === 'Location') { const location = entity as LocationEntity; diff --git a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts index 630c78d600..3941a2bedc 100644 --- a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts @@ -18,7 +18,7 @@ import { UrlReader } from '@backstage/backend-common'; import { Entity, LocationSpec } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/config'; import yaml from 'yaml'; -import { LocationProcessor } from './types'; +import { CatalogProcessor } from './types'; export type ResolverRead = (url: string) => Promise; @@ -42,7 +42,7 @@ type Options = { * Traverses raw entity JSON looking for occurrences of $-prefixed placeholders * that it then fills in with actual data. */ -export class PlaceholderProcessor implements LocationProcessor { +export class PlaceholderProcessor implements CatalogProcessor { constructor(private readonly options: Options) {} async processEntity(entity: Entity, location: LocationSpec): Promise { diff --git a/plugins/catalog-backend/src/ingestion/processors/StaticLocationProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/StaticLocationProcessor.ts index 6a2d1096cc..f34b672183 100644 --- a/plugins/catalog-backend/src/ingestion/processors/StaticLocationProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/StaticLocationProcessor.ts @@ -15,9 +15,9 @@ */ import { LocationSpec } from '@backstage/catalog-model'; -import * as result from './results'; import { Config } from '@backstage/config'; -import { LocationProcessorEmit } from './types'; +import * as result from './results'; +import { CatalogProcessorEmit } from './types'; export class StaticLocationProcessor implements StaticLocationProcessor { static fromConfig(config: Config): StaticLocationProcessor { @@ -38,7 +38,7 @@ export class StaticLocationProcessor implements StaticLocationProcessor { async readLocation( location: LocationSpec, _optional: boolean, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise { if (location.type !== 'bootstrap') { return false; diff --git a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.test.ts index 009872cda3..a2b9678ba0 100644 --- a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.test.ts @@ -14,16 +14,16 @@ * limitations under the License. */ -import { UrlReaderProcessor } from './UrlReaderProcessor'; -import { - LocationProcessorDataResult, - LocationProcessorResult, - LocationProcessorErrorResult, -} from './types'; -import { setupServer } from 'msw/node'; -import { rest } from 'msw'; import { getVoidLogger, UrlReaders } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { + CatalogProcessorDataResult, + CatalogProcessorErrorResult, + CatalogProcessorResult, +} from './types'; +import { UrlReaderProcessor } from './UrlReaderProcessor'; describe('UrlReaderProcessor', () => { const mockApiOrigin = 'http://localhost:23000'; @@ -48,9 +48,9 @@ describe('UrlReaderProcessor', () => { ), ); - const generated = (await new Promise(emit => + const generated = (await new Promise(emit => processor.readLocation(spec, false, emit), - )) as LocationProcessorDataResult; + )) as CatalogProcessorDataResult; expect(generated.type).toBe('data'); expect(generated.location).toBe(spec); @@ -72,9 +72,9 @@ describe('UrlReaderProcessor', () => { }), ); - const generated = (await new Promise(emit => + const generated = (await new Promise(emit => processor.readLocation(spec, false, emit), - )) as LocationProcessorErrorResult; + )) as CatalogProcessorErrorResult; expect(generated.type).toBe('error'); expect(generated.location).toBe(spec); diff --git a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts index 2bc05bf1cb..0169abbbe3 100644 --- a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { Logger } from 'winston'; import { UrlReader } from '@backstage/backend-common'; import { LocationSpec } from '@backstage/catalog-model'; +import { Logger } from 'winston'; import * as result from './results'; -import { LocationProcessor, LocationProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; // TODO(Rugvip): Added for backwards compatibility when moving to UrlReader, this // can be removed in a bit @@ -35,13 +35,13 @@ type Options = { logger: Logger; }; -export class UrlReaderProcessor implements LocationProcessor { +export class UrlReaderProcessor implements CatalogProcessor { constructor(private readonly options: Options) {} async readLocation( location: LocationSpec, optional: boolean, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise { if (deprecatedTypes.includes(location.type)) { // TODO(Rugvip): Let's not enable this warning yet, as we want to move over the example YAMLs diff --git a/plugins/catalog-backend/src/ingestion/processors/YamlProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/YamlProcessor.test.ts index 9f1ede3f9e..f5d2dfea38 100644 --- a/plugins/catalog-backend/src/ingestion/processors/YamlProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/YamlProcessor.test.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { YamlProcessor } from './YamlProcessor'; import { Entity } from '@backstage/catalog-model'; -import yaml from 'yaml'; import { TextEncoder } from 'util'; +import yaml from 'yaml'; import { - LocationProcessorEntityResult, - LocationProcessorErrorResult, + CatalogProcessorEntityResult, + CatalogProcessorErrorResult, } from './types'; +import { YamlProcessor } from './YamlProcessor'; describe('YamlProcessor', () => { const processor = new YamlProcessor(); @@ -82,7 +82,7 @@ describe('YamlProcessor', () => { expect(await processor.parseData(buffer, locationSpec, emit)).toBe(true); - const e = emit.mock.calls[0][0] as LocationProcessorEntityResult; + const e = emit.mock.calls[0][0] as CatalogProcessorEntityResult; expect(e.type).toBe('entity'); expect(e.location).toBe(locationSpec); expect(e.entity).toEqual(entity); @@ -114,12 +114,12 @@ describe('YamlProcessor', () => { expect(await processor.parseData(buffer, locationSpec, emit)).toBe(true); - const eComponent = emit.mock.calls[0][0] as LocationProcessorEntityResult; + const eComponent = emit.mock.calls[0][0] as CatalogProcessorEntityResult; expect(eComponent.type).toBe('entity'); expect(eComponent.location).toBe(locationSpec); expect(eComponent.entity).toEqual(entityComponent); - const eApi = emit.mock.calls[1][0] as LocationProcessorEntityResult; + const eApi = emit.mock.calls[1][0] as CatalogProcessorEntityResult; expect(eApi.type).toBe('entity'); expect(eApi.location).toBe(locationSpec); expect(eApi.entity).toEqual(entityApi); @@ -131,7 +131,7 @@ describe('YamlProcessor', () => { expect(await processor.parseData(buffer, locationSpec, emit)).toBe(true); - const e = emit.mock.calls[0][0] as LocationProcessorErrorResult; + const e = emit.mock.calls[0][0] as CatalogProcessorErrorResult; expect(e.error.message).toMatch(/^YAML error, /); expect(e.type).toBe('error'); expect(e.location).toBe(locationSpec); @@ -143,7 +143,7 @@ describe('YamlProcessor', () => { expect(await processor.parseData(buffer, locationSpec, emit)).toBe(true); - const e = emit.mock.calls[0][0] as LocationProcessorErrorResult; + const e = emit.mock.calls[0][0] as CatalogProcessorErrorResult; expect(e.error.message).toMatch(/^Expected object at root, got /); expect(e.type).toBe('error'); expect(e.location).toBe(locationSpec); diff --git a/plugins/catalog-backend/src/ingestion/processors/YamlProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/YamlProcessor.ts index 860f9ff752..46df4e6a2b 100644 --- a/plugins/catalog-backend/src/ingestion/processors/YamlProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/YamlProcessor.ts @@ -18,18 +18,18 @@ import { Entity, LocationSpec } from '@backstage/catalog-model'; import lodash from 'lodash'; import yaml from 'yaml'; import * as result from './results'; -import { LocationProcessor, LocationProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; /** * Handles incoming raw data buffers, and if they have a yaml extension, * attempts to parse them into structured data and emitting them as un- * validated entities. */ -export class YamlProcessor implements LocationProcessor { +export class YamlProcessor implements CatalogProcessor { async parseData( data: Buffer, location: LocationSpec, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise { if (!location.target.match(/\.ya?ml/)) { return false; diff --git a/plugins/catalog-backend/src/ingestion/processors/results.ts b/plugins/catalog-backend/src/ingestion/processors/results.ts index 9c6a7fc2b3..2718d871dd 100644 --- a/plugins/catalog-backend/src/ingestion/processors/results.ts +++ b/plugins/catalog-backend/src/ingestion/processors/results.ts @@ -16,12 +16,12 @@ import { InputError, NotFoundError } from '@backstage/backend-common'; import { Entity, LocationSpec } from '@backstage/catalog-model'; -import { LocationProcessorResult } from './types'; +import { CatalogProcessorResult } from './types'; export function notFoundError( atLocation: LocationSpec, message: string, -): LocationProcessorResult { +): CatalogProcessorResult { return { type: 'error', location: atLocation, @@ -32,7 +32,7 @@ export function notFoundError( export function inputError( atLocation: LocationSpec, message: string, -): LocationProcessorResult { +): CatalogProcessorResult { return { type: 'error', location: atLocation, @@ -43,27 +43,27 @@ export function inputError( export function generalError( atLocation: LocationSpec, message: string, -): LocationProcessorResult { +): CatalogProcessorResult { return { type: 'error', location: atLocation, error: new Error(message) }; } export function data( atLocation: LocationSpec, newData: Buffer, -): LocationProcessorResult { +): CatalogProcessorResult { return { type: 'data', location: atLocation, data: newData }; } export function location( newLocation: LocationSpec, optional: boolean, -): LocationProcessorResult { +): CatalogProcessorResult { return { type: 'location', location: newLocation, optional }; } export function entity( atLocation: LocationSpec, newEntity: Entity, -): LocationProcessorResult { +): CatalogProcessorResult { return { type: 'entity', location: atLocation, entity: newEntity }; } diff --git a/plugins/catalog-backend/src/ingestion/processors/types.ts b/plugins/catalog-backend/src/ingestion/processors/types.ts index cd2c423423..0e6b1491a6 100644 --- a/plugins/catalog-backend/src/ingestion/processors/types.ts +++ b/plugins/catalog-backend/src/ingestion/processors/types.ts @@ -16,7 +16,7 @@ import { Entity, LocationSpec } from '@backstage/catalog-model'; -export type LocationProcessor = { +export type CatalogProcessor = { /** * Reads the contents of a location. * @@ -28,7 +28,7 @@ export type LocationProcessor = { readLocation?( location: LocationSpec, optional: boolean, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise; /** @@ -42,7 +42,7 @@ export type LocationProcessor = { parseData?( data: Buffer, location: LocationSpec, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise; /** @@ -57,7 +57,7 @@ export type LocationProcessor = { processEntity?( entity: Entity, location: LocationSpec, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise; /** @@ -71,40 +71,38 @@ export type LocationProcessor = { handleError?( error: Error, location: LocationSpec, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ): Promise; }; -export type LocationProcessorEmit = ( - generated: LocationProcessorResult, -) => void; +export type CatalogProcessorEmit = (generated: CatalogProcessorResult) => void; -export type LocationProcessorLocationResult = { +export type CatalogProcessorLocationResult = { type: 'location'; location: LocationSpec; optional: boolean; }; -export type LocationProcessorDataResult = { +export type CatalogProcessorDataResult = { type: 'data'; data: Buffer; location: LocationSpec; }; -export type LocationProcessorEntityResult = { +export type CatalogProcessorEntityResult = { type: 'entity'; entity: Entity; location: LocationSpec; }; -export type LocationProcessorErrorResult = { +export type CatalogProcessorErrorResult = { type: 'error'; error: Error; location: LocationSpec; }; -export type LocationProcessorResult = - | LocationProcessorLocationResult - | LocationProcessorDataResult - | LocationProcessorEntityResult - | LocationProcessorErrorResult; +export type CatalogProcessorResult = + | CatalogProcessorLocationResult + | CatalogProcessorDataResult + | CatalogProcessorEntityResult + | CatalogProcessorErrorResult; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.test.ts b/plugins/catalog-backend/src/service/CatalogBuilder.test.ts index e2467654d8..66e7dba682 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.test.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.test.ts @@ -18,9 +18,9 @@ import { getVoidLogger, UrlReader } from '@backstage/backend-common'; import { Entity, LocationSpec } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { DatabaseManager } from '../database'; -import { LocationProcessorEmit } from '../ingestion'; -import { CatalogBuilder, CatalogEnvironment } from './CatalogBuilder'; +import { CatalogProcessorEmit } from '../ingestion'; import * as result from '../ingestion/processors/results'; +import { CatalogBuilder, CatalogEnvironment } from './CatalogBuilder'; describe('CatalogBuilder', () => { const db = DatabaseManager.createTestDatabaseConnection(); @@ -54,7 +54,7 @@ describe('CatalogBuilder', () => { async readLocation( location: LocationSpec, _optional: boolean, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ) { expect(location.type).toBe('test'); emit(result.data(location, await reader.read('ignored'))); @@ -67,7 +67,7 @@ describe('CatalogBuilder', () => { async parseData( data: Buffer, location: LocationSpec, - emit: LocationProcessorEmit, + emit: CatalogProcessorEmit, ) { expect(data.toString()).toEqual('junk'); emit( diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index e1d76bc3cd..aa4f97258d 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -46,6 +46,7 @@ import { AnnotateLocationEntityProcessor, AzureApiReaderProcessor, BitbucketApiReaderProcessor, + CatalogProcessor, CodeOwnersProcessor, EntityPolicyProcessor, FileReaderProcessor, @@ -55,7 +56,6 @@ import { GitlabReaderProcessor, HigherOrderOperation, HigherOrderOperations, - LocationProcessor, LocationReaders, LocationRefProcessor, PlaceholderProcessor, @@ -121,13 +121,13 @@ export class CatalogBuilder { private entityPoliciesReplace: boolean; private entityKinds: EntityPolicy[]; private entityKindsReplace: boolean; - private readerProcessors: LocationProcessor[]; + private readerProcessors: CatalogProcessor[]; private readerProcessorsReplace: boolean; - private parserProcessors: LocationProcessor[]; + private parserProcessors: CatalogProcessor[]; private parserProcessorsReplace: boolean; - private preProcessors: LocationProcessor[]; + private preProcessors: CatalogProcessor[]; private preProcessorsReplace: boolean; - private postProcessors: LocationProcessor[]; + private postProcessors: CatalogProcessor[]; private postProcessorsReplace: boolean; private placeholderResolvers: Record; private fieldFormatValidators: Partial; @@ -219,7 +219,7 @@ export class CatalogBuilder { * * @param processors One or more processors */ - addReaderProcessor(...processors: LocationProcessor[]): CatalogBuilder { + addReaderProcessor(...processors: CatalogProcessor[]): CatalogBuilder { this.readerProcessors.push(...processors); return this; } @@ -234,7 +234,7 @@ export class CatalogBuilder { * * @param processors One or more processors */ - replaceReaderProcessors(processors: LocationProcessor[]): CatalogBuilder { + replaceReaderProcessors(processors: CatalogProcessor[]): CatalogBuilder { this.readerProcessors = [...processors]; this.readerProcessorsReplace = true; return this; @@ -247,7 +247,7 @@ export class CatalogBuilder { * * @param processors One or more processors */ - addParserProcessor(...processors: LocationProcessor[]): CatalogBuilder { + addParserProcessor(...processors: CatalogProcessor[]): CatalogBuilder { this.parserProcessors.push(...processors); return this; } @@ -262,7 +262,7 @@ export class CatalogBuilder { * * @param processors One or more processors */ - replaceParserProcessors(processors: LocationProcessor[]): CatalogBuilder { + replaceParserProcessors(processors: CatalogProcessor[]): CatalogBuilder { this.parserProcessors = [...processors]; this.parserProcessorsReplace = true; return this; @@ -274,7 +274,7 @@ export class CatalogBuilder { * * @param processors One or more processors */ - addPreProcessor(...processors: LocationProcessor[]): CatalogBuilder { + addPreProcessor(...processors: CatalogProcessor[]): CatalogBuilder { this.preProcessors.push(...processors); return this; } @@ -288,7 +288,7 @@ export class CatalogBuilder { * * @param processors One or more processors */ - replacePreProcessors(processors: LocationProcessor[]): CatalogBuilder { + replacePreProcessors(processors: CatalogProcessor[]): CatalogBuilder { this.preProcessors = [...processors]; this.preProcessorsReplace = true; return this; @@ -300,7 +300,7 @@ export class CatalogBuilder { * * @param processors One or more processors */ - addPostProcessor(...processors: LocationProcessor[]): CatalogBuilder { + addPostProcessor(...processors: CatalogProcessor[]): CatalogBuilder { this.postProcessors.push(...processors); return this; } @@ -314,7 +314,7 @@ export class CatalogBuilder { * * @param processors One or more processors */ - replacePostProcessors(processors: LocationProcessor[]): CatalogBuilder { + replacePostProcessors(processors: CatalogProcessor[]): CatalogBuilder { this.postProcessors = [...processors]; this.postProcessorsReplace = true; return this; @@ -418,7 +418,7 @@ export class CatalogBuilder { ]); } - private buildProcessors(entityPolicy: EntityPolicy): LocationProcessor[] { + private buildProcessors(entityPolicy: EntityPolicy): CatalogProcessor[] { const { config, reader } = this.env; const placeholderResolvers = lodash.merge( @@ -441,7 +441,7 @@ export class CatalogBuilder { ]; } - private buildReaderProcessors(): LocationProcessor[] { + private buildReaderProcessors(): CatalogProcessor[] { const { config, logger, reader } = this.env; if (this.readerProcessorsReplace) { @@ -491,7 +491,7 @@ export class CatalogBuilder { ]; } - private buildParserProcessors(): LocationProcessor[] { + private buildParserProcessors(): CatalogProcessor[] { if (this.parserProcessorsReplace) { return this.parserProcessors; } @@ -499,7 +499,7 @@ export class CatalogBuilder { return [new YamlProcessor(), ...this.parserProcessors]; } - private buildPreProcessors(): LocationProcessor[] { + private buildPreProcessors(): CatalogProcessor[] { const { reader } = this.env; if (this.preProcessorsReplace) { @@ -509,7 +509,7 @@ export class CatalogBuilder { return [new CodeOwnersProcessor({ reader }), ...this.preProcessors]; } - private buildPostProcessors(): LocationProcessor[] { + private buildPostProcessors(): CatalogProcessor[] { if (this.postProcessorsReplace) { return this.postProcessors; } From 973500dd8ddfa2c3f638f9cfbf54ab87426138b6 Mon Sep 17 00:00:00 2001 From: Marvin9 Date: Thu, 15 Oct 2020 19:34:57 +0530 Subject: [PATCH 051/104] chore: make pageTheme optional & provide default value --- packages/theme/src/baseTheme.ts | 3 ++- packages/theme/src/types.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/theme/src/baseTheme.ts b/packages/theme/src/baseTheme.ts index 640476c487..b0797c6506 100644 --- a/packages/theme/src/baseTheme.ts +++ b/packages/theme/src/baseTheme.ts @@ -23,6 +23,7 @@ import { BackstageThemeOptions, SimpleThemeOptions, } from './types'; +import { pageTheme as defaultPageThemes } from './pageTheme'; const DEFAULT_FONT_FAMILY = '"Helvetica Neue", Helvetica, Roboto, Arial, sans-serif'; @@ -34,7 +35,7 @@ export function createThemeOptions( palette, fontFamily = DEFAULT_FONT_FAMILY, defaultPageTheme, - pageTheme, + pageTheme = defaultPageThemes, } = options; if (!pageTheme[defaultPageTheme]) { diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts index 696ccf586f..1c6ccc0754 100644 --- a/packages/theme/src/types.ts +++ b/packages/theme/src/types.ts @@ -94,7 +94,7 @@ export interface BackstageThemeOptions extends ThemeOptions { export type SimpleThemeOptions = { palette: BackstagePaletteOptions; defaultPageTheme: string; - pageTheme: Record; + pageTheme?: Record; fontFamily?: string; }; From 9af34d30f17027d0471551c8f86dad587c72e670 Mon Sep 17 00:00:00 2001 From: David Tuite Date: Thu, 15 Oct 2020 15:53:29 +0100 Subject: [PATCH 052/104] Upgrade GitHub Insights plugin to 0.2.6 Fixes issue with entities backed to private repos not rendering when the GitHub insights plugin is installed. --- packages/app/package.json | 2 +- yarn.lock | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index baa273fd08..65ba6140b2 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -34,8 +34,8 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@octokit/rest": "^18.0.0", + "@roadiehq/backstage-plugin-github-insights": "^0.2.6", "@roadiehq/backstage-plugin-github-pull-requests": "^0.5.1", - "@roadiehq/backstage-plugin-github-insights": "^0.2.2", "@roadiehq/backstage-plugin-travis-ci": "^0.2.3", "dayjs": "^1.9.1", "history": "^5.0.0", diff --git a/yarn.lock b/yarn.lock index c47977b4f4..574c0f65a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3650,10 +3650,10 @@ resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-2.3.0.tgz#a051eb4db2ad778e39933a31ba806f415dd3ba90" integrity sha512-v/xZ4Xk18ZgBARcCe99IDqdO97GU0UC784gG8PhGGFDdy5Rbdy7ixTjHkGYttkr43PB7SX6ttohNhkKSW4nATQ== -"@roadiehq/backstage-plugin-github-insights@^0.2.2": - version "0.2.2" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-0.2.2.tgz#1647aec22f1f8bc40268f075ae307abf660ae75c" - integrity sha512-m4j1kUmoj+ZCRyaisXVBkLW9kaI/OGMq8xzthm4adOb12mS0s2p2P9AMa4T+bRyLgHpQ0vB75qhw4p6sHJAvWA== +"@roadiehq/backstage-plugin-github-insights@^0.2.6": + version "0.2.6" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-0.2.6.tgz#7c854cc6b107ff30b0f03b1a5e1ca5e04723dd99" + integrity sha512-d3CmA2B0iOB2jKFK3z4q1ehl9nCfh2yrCb6byOvsIyV2xlm3cnt3Uq5hLlQekTYLWRvBWw2ULQVC8+wk2Uq6gg== dependencies: "@backstage/catalog-model" "^0.1.1-alpha.24" "@backstage/core" "^0.1.1-alpha.24" @@ -3661,6 +3661,8 @@ "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" + "@octokit/rest" "^18.0.0" + "@octokit/types" "^5.0.1" history "^5.0.0" react "^16.13.1" react-dom "^16.13.1" From 5c46f0d337cfb937dc4e47286a427cc88b96ffcd Mon Sep 17 00:00:00 2001 From: Markus Heinemann Date: Thu, 15 Oct 2020 20:35:43 +0000 Subject: [PATCH 053/104] Rename config property for custom github scaffolder host --- app-config.yaml | 2 +- packages/backend/src/plugins/scaffolder.ts | 5 +++-- .../default-app/packages/backend/src/plugins/scaffolder.ts | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index b527069c15..768e60a346 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -147,7 +147,7 @@ catalog: scaffolder: github: - baseUrl: https://github.com + host: https://github.com token: $env: GITHUB_TOKEN visibility: public # or 'internal' or 'private' diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts index 7ba90ed8b5..24bd4e942f 100644 --- a/packages/backend/src/plugins/scaffolder.ts +++ b/packages/backend/src/plugins/scaffolder.ts @@ -68,10 +68,11 @@ export default async function createPlugin({ ) as RepoVisibilityOptions; const githubToken = githubConfig.getString('token'); - const githubBaseUrl = githubConfig.getString('baseUrl'); + const githubHost = + githubConfig.getOptionalString('host') ?? 'https://github.com'; const githubClient = new Octokit({ auth: githubToken, - baseUrl: githubBaseUrl, + baseUrl: githubHost, }); const githubPublisher = new GithubPublisher({ client: githubClient, diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts index 9f59412036..240309fbd1 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts @@ -48,8 +48,8 @@ export default async function createPlugin({ ) as RepoVisibilityOptions; const githubToken = githubConfig.getString('token'); - const githubBaseUrl = githubConfig.getString('baseUrl'); - const githubClient = new Octokit({ auth: githubToken, baseUrl: githubBaseUrl }); + const githubHost = githubConfig.getOptionalString('host'); + const githubClient = new Octokit({ auth: githubToken, baseUrl: githubHost }); const githubPublisher = new GithubPublisher({ client: githubClient, token: githubToken, From ee88f6d21023f9949248daf565057f22bb3e21ee Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 15 Oct 2020 22:44:15 +0200 Subject: [PATCH 054/104] Fix example in README. --- plugins/newrelic/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/newrelic/README.md b/plugins/newrelic/README.md index e787d9c91f..e14acedef2 100644 --- a/plugins/newrelic/README.md +++ b/plugins/newrelic/README.md @@ -29,7 +29,7 @@ While working locally, you may wish to hard-code your API key in your ```yaml # app-config.local.yaml proxy: - 'newrelic/apm/api': + '/newrelic/apm/api': headers: X-Api-Key: NRRA-YourActualApiKey ``` From d553996bf27dba5fb7fb85728a92acb04442f628 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2020 04:28:40 +0000 Subject: [PATCH 055/104] chore(deps): bump dayjs from 1.9.1 to 1.9.3 Bumps [dayjs](https://github.com/iamkun/dayjs) from 1.9.1 to 1.9.3. - [Release notes](https://github.com/iamkun/dayjs/releases) - [Changelog](https://github.com/iamkun/dayjs/blob/dev/CHANGELOG.md) - [Commits](https://github.com/iamkun/dayjs/compare/v1.9.1...v1.9.3) Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index adc0e7a8f1..4d28072207 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9594,9 +9594,9 @@ dateformat@^3.0.0: integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== dayjs@^1.9.1: - version "1.9.1" - resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.9.1.tgz#201a755f7db5103ed6de63ba93a984141c754541" - integrity sha512-01NCTBg8cuMJG1OQc6PR7T66+AFYiPwgDvdJmvJBn29NGzIG+DIFxPLNjHzwz3cpFIvG+NcwIjP9hSaPVoOaDg== + version "1.9.3" + resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.9.3.tgz#b7f94b22ad2a136a4ca02a01ab68ae893fe1a268" + integrity sha512-V+1SyIvkS+HmNbN1G7A9+ERbFTV9KTXu6Oor98v2xHmzzpp52OIJhQuJSTywWuBY5pyAEmlwbCi1Me87n/SLOw== de-indent@^1.0.2: version "1.0.2" From 275b7f001ab2d47c1c0c485a2166d765451686db Mon Sep 17 00:00:00 2001 From: Althaf Hameez Date: Fri, 16 Oct 2020 14:49:25 +0800 Subject: [PATCH 056/104] Change default organization to be set to My Company instead of Spotify / Acme Organization (#2918) --- app-config.yaml | 6 +++--- .../create-app/templates/default-app/app-config.yaml.hbs | 2 +- .../default-app/packages/app/cypress/integration/app.js | 2 +- packages/e2e-test/src/commands/run.ts | 6 +----- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 3de43ad7e9..542110f774 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -41,7 +41,7 @@ proxy: $env: NEW_RELIC_REST_API_KEY organization: - name: Spotify + name: My Company techdocs: storageUrl: http://localhost:7000/api/techdocs/static/docs @@ -50,10 +50,10 @@ techdocs: techdocs: 'docker' sentry: - organization: spotify + organization: my-company rollbar: - organization: spotify + organization: my-company accountToken: $env: ROLLBAR_ACCOUNT_TOKEN diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index 347bbaa29c..b92cdf22bd 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -3,7 +3,7 @@ app: baseUrl: http://localhost:7000 organization: - name: Acme Corporation + name: My Company backend: baseUrl: http://localhost:7000 diff --git a/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js b/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js index 227e218fe6..efcd5b8d93 100644 --- a/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js +++ b/packages/create-app/templates/default-app/packages/app/cypress/integration/app.js @@ -1,6 +1,6 @@ describe('App', () => { it('should render the catalog', () => { cy.visit('/'); - cy.contains('Acme Corporation Service Catalog'); + cy.contains('My Company Service Catalog'); }); }); diff --git a/packages/e2e-test/src/commands/run.ts b/packages/e2e-test/src/commands/run.ts index c05aed0b83..5caed14227 100644 --- a/packages/e2e-test/src/commands/run.ts +++ b/packages/e2e-test/src/commands/run.ts @@ -301,11 +301,7 @@ async function testAppServe(pluginName: string, appDir: string) { try { const browser = new Browser(); - await waitForPageWithText( - browser, - '/', - 'Acme Corporation Service Catalog', - ); + await waitForPageWithText(browser, '/', 'My Company Service Catalog'); await waitForPageWithText( browser, `/${pluginName}`, From c91054ac17c4dbe8dcd25ae6b312c5c6b3406024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 16 Oct 2020 13:29:45 +0200 Subject: [PATCH 057/104] chore(deps): bump yup --- packages/catalog-model/package.json | 4 +- .../policies/SchemaValidEntityPolicy.ts | 38 ++++++++++--------- .../src/kinds/ComponentEntityV1alpha1.test.ts | 20 ++++++++++ .../src/kinds/ComponentEntityV1alpha1.ts | 2 +- .../src/kinds/GroupEntityV1alpha1.test.ts | 27 ++++++++++--- .../src/kinds/GroupEntityV1alpha1.ts | 14 ++++--- .../src/kinds/LocationEntityV1alpha1.test.ts | 5 +++ .../src/kinds/LocationEntityV1alpha1.ts | 2 +- .../src/kinds/UserEntityV1alpha1.ts | 6 ++- .../catalog-model/src/location/validation.ts | 6 ++- packages/config-loader/package.json | 4 +- packages/config-loader/src/lib/secrets.ts | 2 +- plugins/catalog-backend/package.json | 4 +- plugins/proxy-backend/package.json | 4 +- yarn.lock | 10 ++--- 15 files changed, 98 insertions(+), 50 deletions(-) diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 8fc4484d33..5ba2b6efdf 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -22,11 +22,11 @@ "dependencies": { "@backstage/config": "^0.1.1-alpha.25", "@types/json-schema": "^7.0.5", - "@types/yup": "^0.28.2", + "@types/yup": "^0.29.8", "json-schema": "^0.2.5", "lodash": "^4.17.15", "uuid": "^8.0.0", - "yup": "^0.29.1" + "yup": "^0.29.3" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.25", diff --git a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts index a6d376b4ac..4ff73e9a49 100644 --- a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts +++ b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts @@ -18,24 +18,26 @@ import * as yup from 'yup'; import { EntityPolicy } from '../../types'; import { Entity } from '../Entity'; -const DEFAULT_ENTITY_SCHEMA = yup.object({ - apiVersion: yup.string().required(), - kind: yup.string().required(), - metadata: yup - .object({ - uid: yup.string().notRequired().min(1), - etag: yup.string().notRequired().min(1), - generation: yup.number().notRequired().integer().min(1), - name: yup.string().required(), - namespace: yup.string().notRequired(), - description: yup.string().notRequired(), - labels: yup.object>().notRequired(), - annotations: yup.object>().notRequired(), - tags: yup.array().notRequired(), - }) - .required(), - spec: yup.object({}).notRequired(), -}); +const DEFAULT_ENTITY_SCHEMA = yup + .object({ + apiVersion: yup.string().required(), + kind: yup.string().required(), + metadata: yup + .object({ + uid: yup.string().notRequired().min(1), + etag: yup.string().notRequired().min(1), + generation: yup.number().notRequired().integer().min(1), + name: yup.string().required(), + namespace: yup.string().notRequired(), + description: yup.string().notRequired(), + labels: yup.object>().notRequired(), + annotations: yup.object>().notRequired(), + tags: yup.array().notRequired(), + }) + .required(), + spec: yup.object({}).notRequired(), + }) + .required(); /** * Ensures that the entity spec is valid according to a schema. diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts index 57d6d47388..11434937cd 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts @@ -101,4 +101,24 @@ describe('ComponentV1alpha1Policy', () => { (entity as any).spec.owner = ''; await expect(policy.enforce(entity)).rejects.toThrow(/owner/); }); + + it('accepts missing implementsApis', async () => { + delete (entity as any).spec.implementsApis; + await expect(policy.enforce(entity)).resolves.toBe(entity); + }); + + it('rejects empty implementsApis', async () => { + (entity as any).spec.implementsApis = ['']; + await expect(policy.enforce(entity)).rejects.toThrow(/implementsApis/); + }); + + it('rejects undefined implementsApis', async () => { + (entity as any).spec.implementsApis = [undefined]; + await expect(policy.enforce(entity)).rejects.toThrow(/implementsApis/); + }); + + it('accepts no implementsApis', async () => { + (entity as any).spec.implementsApis = []; + await expect(policy.enforce(entity)).resolves.toBe(entity); + }); }); diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts index 9efc491ca9..662ce28b52 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -29,7 +29,7 @@ const schema = yup.object>({ type: yup.string().required().min(1), lifecycle: yup.string().required().min(1), owner: yup.string().required().min(1), - implementsApis: yup.array(yup.string()).notRequired(), + implementsApis: yup.array(yup.string().required()).notRequired(), }) .required(), }); diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts index dc0ecd3fa3..9137727c6e 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts @@ -90,9 +90,14 @@ describe('GroupV1alpha1Policy', () => { await expect(policy.enforce(entity)).rejects.toThrow(/ancestor/); }); - it('accepts empty ancestors', async () => { + it('rejects empty ancestors', async () => { (entity as any).spec.ancestors = ['']; - await expect(policy.enforce(entity)).resolves.toBe(entity); + await expect(policy.enforce(entity)).rejects.toThrow(/ancestor/); + }); + + it('rejects undefined ancestors', async () => { + (entity as any).spec.ancestors = [undefined]; + await expect(policy.enforce(entity)).rejects.toThrow(/ancestor/); }); it('accepts no ancestors', async () => { @@ -105,9 +110,14 @@ describe('GroupV1alpha1Policy', () => { await expect(policy.enforce(entity)).rejects.toThrow(/children/); }); - it('accepts empty children', async () => { + it('rejects empty children', async () => { (entity as any).spec.children = ['']; - await expect(policy.enforce(entity)).resolves.toBe(entity); + await expect(policy.enforce(entity)).rejects.toThrow(/children/); + }); + + it('rejects undefined children', async () => { + (entity as any).spec.children = [undefined]; + await expect(policy.enforce(entity)).rejects.toThrow(/children/); }); it('accepts no children', async () => { @@ -120,9 +130,14 @@ describe('GroupV1alpha1Policy', () => { await expect(policy.enforce(entity)).rejects.toThrow(/descendants/); }); - it('accepts empty descendants', async () => { + it('rejects empty descendants', async () => { (entity as any).spec.descendants = ['']; - await expect(policy.enforce(entity)).resolves.toBe(entity); + await expect(policy.enforce(entity)).rejects.toThrow(/descendants/); + }); + + it('rejects undefined descendants', async () => { + (entity as any).spec.descendants = [undefined]; + await expect(policy.enforce(entity)).rejects.toThrow(/descendants/); }); it('accepts no descendants', async () => { diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts index 073332d707..f0e91ddce8 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts @@ -30,21 +30,23 @@ const schema = yup.object>({ parent: yup.string().notRequired().min(1), // Use these manual tests because yup .required() requires at least // one element and there is no simple workaround -_- - ancestors: yup.array(yup.string()).test({ + // the cast is there to convince typescript that the array itself is + // required without using .required() + ancestors: yup.array(yup.string().required()).test({ name: 'isDefined', message: 'ancestors must be defined', test: v => Boolean(v), - }), - children: yup.array(yup.string()).test({ + }) as yup.ArraySchema, + children: yup.array(yup.string().required()).test({ name: 'isDefined', message: 'children must be defined', test: v => Boolean(v), - }), - descendants: yup.array(yup.string()).test({ + }) as yup.ArraySchema, + descendants: yup.array(yup.string().required()).test({ name: 'isDefined', message: 'descendants must be defined', test: v => Boolean(v), - }), + }) as yup.ArraySchema, }) .required(), }); diff --git a/packages/catalog-model/src/kinds/LocationEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.test.ts index a259eaeca8..865ceda53e 100644 --- a/packages/catalog-model/src/kinds/LocationEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.test.ts @@ -93,6 +93,11 @@ describe('LocationV1alpha1Policy', () => { await expect(policy.enforce(entity)).resolves.toBe(entity); }); + it('accepts empty targets', async () => { + (entity as any).spec.targets = []; + await expect(policy.enforce(entity)).resolves.toBe(entity); + }); + it('rejects wrong targets', async () => { (entity as any).spec.targets = 7; await expect(policy.enforce(entity)).rejects.toThrow(/targets/); diff --git a/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts index 6d1f1f5515..dc2485f11b 100644 --- a/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts @@ -28,7 +28,7 @@ const schema = yup.object>({ .object({ type: yup.string().required().min(1), target: yup.string().notRequired().min(1), - targets: yup.array(yup.string()).notRequired(), + targets: yup.array(yup.string().required()).notRequired(), }) .required(), }); diff --git a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts index ff4edcc8a2..6086b3776e 100644 --- a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts @@ -35,11 +35,13 @@ const schema = yup.object>({ .notRequired(), // Use this manual test because yup .required() requires at least one // element and there is no simple workaround -_- - memberOf: yup.array(yup.string()).test({ + // the cast is there to convince typescript that the array itself is + // required without using .required() + memberOf: yup.array(yup.string().required()).test({ name: 'isDefined', message: 'memberOf must be defined', test: v => Boolean(v), - }), + }) as yup.ArraySchema, }) .required(), }); diff --git a/packages/catalog-model/src/location/validation.ts b/packages/catalog-model/src/location/validation.ts index 5fad47bdd0..b1b91edd83 100644 --- a/packages/catalog-model/src/location/validation.ts +++ b/packages/catalog-model/src/location/validation.ts @@ -22,7 +22,8 @@ export const locationSpecSchema = yup type: yup.string().required(), target: yup.string().required(), }) - .noUnknown(); + .noUnknown() + .required(); export const locationSchema = yup .object({ @@ -30,4 +31,5 @@ export const locationSchema = yup type: yup.string().required(), target: yup.string().required(), }) - .noUnknown(); + .noUnknown() + .required(); diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 0a95dc8c86..eead016c8f 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -33,13 +33,13 @@ "@backstage/config": "^0.1.1-alpha.25", "fs-extra": "^9.0.0", "yaml": "^1.9.2", - "yup": "^0.29.1" + "yup": "^0.29.3" }, "devDependencies": { "@types/jest": "^26.0.7", "@types/mock-fs": "^4.10.0", "@types/node": "^12.0.0", - "@types/yup": "^0.28.2", + "@types/yup": "^0.29.8", "mock-fs": "^4.13.0" }, "files": [ diff --git a/packages/config-loader/src/lib/secrets.ts b/packages/config-loader/src/lib/secrets.ts index 4c3812d527..97f0de2940 100644 --- a/packages/config-loader/src/lib/secrets.ts +++ b/packages/config-loader/src/lib/secrets.ts @@ -58,7 +58,7 @@ const secretLoaderSchemas = { }; // The top-level secret schema, which figures out what type of secret it is. -const secretSchema = yup.lazy(value => { +const secretSchema = yup.lazy(value => { if (typeof value !== 'object' || value === null) { return yup.object().required().label('secret'); } diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 718bb2964c..34f9c7b360 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -43,7 +43,7 @@ "winston": "^3.2.1", "yaml": "^1.9.2", "yn": "^4.0.0", - "yup": "^0.29.1" + "yup": "^0.29.3" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.25", @@ -53,7 +53,7 @@ "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", - "@types/yup": "^0.28.2", + "@types/yup": "^0.29.8", "jest-fetch-mock": "^3.0.3", "msw": "^0.20.5", "supertest": "^4.0.2" diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index e96faaec0f..c7125d8b18 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -31,7 +31,7 @@ "winston": "^3.2.1", "yaml": "^1.9.2", "yn": "^4.0.0", - "yup": "^0.29.1" + "yup": "^0.29.3" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.25", @@ -39,7 +39,7 @@ "@types/node-fetch": "^2.5.7", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", - "@types/yup": "^0.28.2", + "@types/yup": "^0.29.8", "jest-fetch-mock": "^3.0.3", "supertest": "^4.0.2" }, diff --git a/yarn.lock b/yarn.lock index 4d28072207..5007bd0d58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5820,10 +5820,10 @@ dependencies: "@types/yargs-parser" "*" -"@types/yup@^0.28.2": - version "0.28.3" - resolved "https://registry.npmjs.org/@types/yup/-/yup-0.28.3.tgz#387c35f9a6a36b8d3561f6601eb4e72518b92899" - integrity sha512-0Sir2LxOmupF8HBUvpJoZghLmOqKfZsBk1GYlMwSIccLDDUoN04LHvo0KzDp9qxt1IKf9Fudpj35SrJ8VqetkQ== +"@types/yup@^0.29.8": + version "0.29.8" + resolved "https://registry.npmjs.org/@types/yup/-/yup-0.29.8.tgz#83db15735987db9fe5a38772a0fb9500e3c5bf39" + integrity sha512-MBSp62AjB1KrSOI3gX9GekddXU5YYQAVA93+aSl78biBqoSzxg876aQY2KJK5Gnfbpqq7O2cadVX5kPAtBqIXw== "@types/zen-observable@^0.8.0": version "0.8.0" @@ -24123,7 +24123,7 @@ yn@^4.0.0: resolved "https://registry.npmjs.org/yn/-/yn-4.0.0.tgz#611480051ea43b510da1dfdbe177ed159f00a979" integrity sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg== -yup@^0.29.1: +yup@^0.29.3: version "0.29.3" resolved "https://registry.npmjs.org/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea" integrity sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ== From 02c60b5f8b9b49511a543dfa80c4003068e1f093 Mon Sep 17 00:00:00 2001 From: Abhishek Jakhar Date: Fri, 16 Oct 2020 09:21:52 +0530 Subject: [PATCH 058/104] fix(tech-radar): add overflowX scroll to the company radar section --- .changeset/slow-experts-peel.md | 5 ++ .../tech-radar/src/components/RadarPage.tsx | 53 +++++++++++-------- 2 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 .changeset/slow-experts-peel.md diff --git a/.changeset/slow-experts-peel.md b/.changeset/slow-experts-peel.md new file mode 100644 index 0000000000..ed9c7af21e --- /dev/null +++ b/.changeset/slow-experts-peel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-radar': patch +--- + +fix the horizontal scrolling issue in the RadarPage component diff --git a/plugins/tech-radar/src/components/RadarPage.tsx b/plugins/tech-radar/src/components/RadarPage.tsx index 84cfef3213..779d1fa088 100644 --- a/plugins/tech-radar/src/components/RadarPage.tsx +++ b/plugins/tech-radar/src/components/RadarPage.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { Grid } from '@material-ui/core'; +import { Grid, makeStyles } from '@material-ui/core'; import { Content, ContentHeader, @@ -27,6 +27,12 @@ import { import RadarComponent from '../components/RadarComponent'; import { TechRadarComponentProps } from '../api'; +const useStyles = makeStyles(() => ({ + overflowXScroll: { + overflowX: 'scroll', + }, +})); + export type TechRadarPageProps = TechRadarComponentProps & { title?: string; subtitle?: string; @@ -38,28 +44,31 @@ export const RadarPage = ({ subtitle, pageTitle, ...props -}: TechRadarPageProps): JSX.Element => ( - -
- - -
- - - - This is used for visualizing the official guidelines of different - areas of software development such as languages, frameworks, - infrastructure and processes. - - - - - +}: TechRadarPageProps): JSX.Element => { + const classes = useStyles(); + return ( + +
+ + +
+ + + + This is used for visualizing the official guidelines of different + areas of software development such as languages, frameworks, + infrastructure and processes. + + + + + + -
-
-
-); + + + ); +}; RadarPage.defaultProps = { title: 'Tech Radar', From 0d53c752ace139be551a42a2b14728243e456a96 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Oct 2020 13:59:00 +0200 Subject: [PATCH 059/104] cli: cleanup and fix removePlugin tests --- .../remove-plugin/removePlugin.test.ts | 33 +++++++++++-------- .../commands/remove-plugin/removePlugin.ts | 1 - 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/commands/remove-plugin/removePlugin.test.ts b/packages/cli/src/commands/remove-plugin/removePlugin.test.ts index 3ffa8d63cf..a5e2af5974 100644 --- a/packages/cli/src/commands/remove-plugin/removePlugin.test.ts +++ b/packages/cli/src/commands/remove-plugin/removePlugin.test.ts @@ -32,7 +32,6 @@ import { removePluginFromCodeOwners, } from './removePlugin'; -// Some constant variables const BACKSTAGE = `@backstage`; const testPluginName = 'yarn-test-package'; const testPluginPackage = `${BACKSTAGE}/plugin-${testPluginName}`; @@ -68,8 +67,8 @@ const createTestPluginFile = async ( .split('-') .map(name => capitalize(name)) .join(''); - const exportStatement = `export { default as ${pluginNameCapitalized}} from @backstage/plugin-${testPluginName}`; - addExportStatement(testFilePath, exportStatement); + const exportStatement = `export { plugin as ${pluginNameCapitalized}} from @backstage/plugin-${testPluginName}`; + await addExportStatement(testFilePath, exportStatement); }; const mkTestPluginDir = (testDirPath: string) => { @@ -78,15 +77,21 @@ const mkTestPluginDir = (testDirPath: string) => { fse.createFileSync(path.join(testDirPath, `testFile${i}.ts`)); }; -beforeAll(() => { - // Create temporary directory for all tests - createTemporaryPluginFolder(tempDir); -}); - describe('removePlugin', () => { + beforeAll(() => { + // Create temporary directory for all tests + createTemporaryPluginFolder(tempDir); + }); + + afterAll(() => { + // Remove temporary directory + fse.removeSync(tempDir); + }); + describe('Remove Plugin Dependencies', () => { const appPath = paths.resolveTargetRoot('packages', 'app'); const githubDir = paths.resolveTargetRoot('.github'); + it('removes plugin references from /packages/app/package.json', async () => { // Set up test const packageFilePath = path.join(appPath, 'package.json'); @@ -105,7 +110,8 @@ describe('removePlugin', () => { fse.removeSync(testFilePath); } }); - it('removes plugin exports from /packages/app/src/packacge.json', async () => { + + it('removes plugin exports from /packages/app/src/package.json', async () => { const testFilePath = path.join(tempDir, 'test.ts'); const pluginsFilePaths = path.join(appPath, 'src', 'plugins.ts'); createTestPluginFile(testFilePath, pluginsFilePaths); @@ -122,6 +128,7 @@ describe('removePlugin', () => { fse.removeSync(testFilePath); } }); + it('removes codeOwners references', async () => { const testFilePath = path.join(tempDir, 'test'); const codeownersPath = path.join(githubDir, 'CODEOWNERS'); @@ -144,12 +151,14 @@ describe('removePlugin', () => { } }); }); + describe('Remove files', () => { const testDirPath = path.join( paths.resolveTargetRoot(), 'plugins', testPluginName, ); + describe('Removes Plugin Directory', () => { it('removes plugin directory from /plugins', async () => { try { @@ -162,6 +171,7 @@ describe('removePlugin', () => { } }); }); + describe('Removes System Link', () => { it('removes system link from @backstage', async () => { const scopedDir = paths.resolveTargetRoot('node_modules', '@backstage'); @@ -183,8 +193,3 @@ describe('removePlugin', () => { }); }); }); - -afterAll(() => { - // Remove temporary directory - fse.removeSync(tempDir); -}); diff --git a/packages/cli/src/commands/remove-plugin/removePlugin.ts b/packages/cli/src/commands/remove-plugin/removePlugin.ts index 1395384022..643bd599d7 100644 --- a/packages/cli/src/commands/remove-plugin/removePlugin.ts +++ b/packages/cli/src/commands/remove-plugin/removePlugin.ts @@ -20,7 +20,6 @@ import inquirer, { Answers, Question } from 'inquirer'; import { getCodeownersFilePath } from '../../lib/codeowners'; import { paths } from '../../lib/paths'; import { Task } from '../../lib/tasks'; -// import os from 'os'; const BACKSTAGE = '@backstage'; From d47e2287b50a781014c5577b90ea38ad245ca110 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Fri, 16 Oct 2020 09:54:28 -0400 Subject: [PATCH 060/104] compare -> default; deprecate dailyCost as metric; cleanup --- app-config.yaml | 4 +- .../ExampleCostInsightsClient.ts | 5 +- plugins/cost-insights/README.md | 7 +- .../cost-insights/src/api/CostInsightsApi.ts | 5 +- .../CostInsightsNavigation.test.tsx | 1 + .../CostInsightsPage/CostInsightsPage.tsx | 25 +++--- .../CostOverviewCard/CostOverviewCard.tsx | 37 +++++---- .../CostOverviewCard/CostOverviewChart.tsx | 83 +++++++++---------- .../MetricSelect/MetricSelect.test.tsx | 26 ++---- .../components/MetricSelect/MetricSelect.tsx | 54 ++++++------ plugins/cost-insights/src/hooks/useConfig.tsx | 49 ++++------- .../cost-insights/src/hooks/useFilters.tsx | 7 +- .../src/types/ChangeStatistic.ts | 10 ++- plugins/cost-insights/src/types/Cost.ts | 4 - plugins/cost-insights/src/types/Metric.ts | 6 +- plugins/cost-insights/src/types/MetricData.ts | 25 ++++++ plugins/cost-insights/src/types/index.ts | 1 + plugins/cost-insights/src/utils/config.ts | 32 +++++++ 18 files changed, 196 insertions(+), 185 deletions(-) create mode 100644 plugins/cost-insights/src/types/MetricData.ts create mode 100644 plugins/cost-insights/src/utils/config.ts diff --git a/app-config.yaml b/app-config.yaml index d1414582ed..966f067ff5 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -236,11 +236,9 @@ costInsights: name: Big Query icon: search metrics: - dailyCost: - name: Pied Piper's Daily Cost - compare: DAU DAU: name: Daily Active Users + default: true MSC: name: Monthly Subscribers homepage: diff --git a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts index fc2249fadc..0b275baf74 100644 --- a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts +++ b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts @@ -117,10 +117,7 @@ export class ExampleCostInsightsClient implements CostInsightsApi { return projects; } - async getMetricData( - metric: string | null, - intervals: string, - ): Promise { + async getMetricData(metric: string, intervals: string): Promise { const aggregation = aggregationFor( durationOf(intervals), 100_000, diff --git a/plugins/cost-insights/README.md b/plugins/cost-insights/README.md index 26f9d9371a..3028d3a09c 100644 --- a/plugins/cost-insights/README.md +++ b/plugins/cost-insights/README.md @@ -79,9 +79,9 @@ costInsights: ### Metrics (Optional) -In the `Cost Overview` panel, users can choose from a dropdown of business metrics to see costs as they relate to a metric, such as daily active users. Metrics must be defined as keys on the `metrics` field. A user-friendly name is **required**. Metrics will be provided to the `getDailyCost` and `getProjectCosts` API methods via the `metric` parameter. +In the `Cost Overview` panel, users can choose from a dropdown of business metrics to see costs as they relate to a metric, such as daily active users. Metrics must be defined as keys on the `metrics` field. A user-friendly name is **required**. Metrics will be provided to the `getMetricData` API method via the `metric` parameter. -**Note:** Cost Insights displays daily cost without a metric by default. The dropdown text for this default can be overridden by assigning it a value on the `dailyCost` field. +An optional `default` field can be set to `true` to set the default comparison metric to daily cost in the Cost Overview panel. ```yaml ## ./app-config.yaml @@ -95,10 +95,9 @@ costInsights: name: Some Other Cloud Product icon: data metrics: - dailyCost: - name: Earth Rotation metricA: name: Metric A ## required + default: true metricB: name: Metric B metricC: diff --git a/plugins/cost-insights/src/api/CostInsightsApi.ts b/plugins/cost-insights/src/api/CostInsightsApi.ts index 52b3f335d9..3136fb4c27 100644 --- a/plugins/cost-insights/src/api/CostInsightsApi.ts +++ b/plugins/cost-insights/src/api/CostInsightsApi.ts @@ -87,7 +87,7 @@ export type CostInsightsApi = { * @param intervals An ISO 8601 repeating interval string, such as R2/P1M/2020-09-01 * https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals */ - getMetricData(metric: string | null, intervals: string): Promise; + getMetricData(metric: string, intervals: string): Promise; /** * Get cost aggregations for a particular cloud product and interval timeframe. This includes @@ -104,10 +104,9 @@ export type CostInsightsApi = { * @param product The product from the cost-insights configuration in app-config.yaml * @param group * @param duration A time duration, such as P1M. See the Duration type for a detailed explanation - * of how the durations are interpreted in Cost Insights. + * of how the durations are interpreted in Cost Insights. * @param project (optional) The project id from getGroupProjects or query parameters */ - getProductInsights( product: string, group: string, diff --git a/plugins/cost-insights/src/components/CostInsightsNavigation/CostInsightsNavigation.test.tsx b/plugins/cost-insights/src/components/CostInsightsNavigation/CostInsightsNavigation.test.tsx index 3ec2828029..7ca4958c8e 100644 --- a/plugins/cost-insights/src/components/CostInsightsNavigation/CostInsightsNavigation.test.tsx +++ b/plugins/cost-insights/src/components/CostInsightsNavigation/CostInsightsNavigation.test.tsx @@ -40,6 +40,7 @@ const mockMetrics: Metric[] = [ { kind: 'some-metric', name: 'Some Metric', + default: false, }, ]; diff --git a/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx b/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx index 9bc396537b..20f965f1c7 100644 --- a/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx +++ b/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx @@ -101,34 +101,26 @@ const CostInsightsPage = () => { try { if (pageFilters.group) { dispatchLoadingInsights(true); + const intervals = intervalsOf(pageFilters.duration); const [ fetchedProjects, fetchedAlerts, fetchedMetricData, - fetchedCosts, + fetchedDailyCost, ] = await Promise.all([ client.getGroupProjects(pageFilters.group), client.getAlerts(pageFilters.group), pageFilters.metric - ? client.getMetricData( - pageFilters.metric, - intervalsOf(pageFilters.duration), - ) + ? client.getMetricData(pageFilters.metric, intervals) : null, pageFilters.project - ? client.getProjectDailyCost( - pageFilters.project, - intervalsOf(pageFilters.duration), - ) - : client.getGroupDailyCost( - pageFilters.group, - intervalsOf(pageFilters.duration), - ), + ? client.getProjectDailyCost(pageFilters.project, intervals) + : client.getGroupDailyCost(pageFilters.group, intervals), ]); setProjects(fetchedProjects); setAlerts(fetchedAlerts); setMetricData(fetchedMetricData); - setDailyCost(fetchedCosts); + setDailyCost(fetchedDailyCost); } else { dispatchLoadingNone(loadingActions); } @@ -267,7 +259,10 @@ const CostInsightsPage = () => { {!!dailyCost.aggregation.length && ( - + )} diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx index daffe335c5..a08e7fc789 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -35,10 +35,14 @@ import { } from '../../types'; export type CostOverviewCardProps = { - data: [Cost, MetricData | null]; + dailyCostData: Cost; + metricData: MetricData | null; }; -const CostOverviewCard = ({ data }: CostOverviewCardProps) => { +const CostOverviewCard = ({ + dailyCostData, + metricData, +}: CostOverviewCardProps) => { const theme = useTheme(); const config = useConfig(); const { ScrollAnchor } = useScroll(DefaultNavigation.CostOverviewCard); @@ -46,10 +50,12 @@ const CostOverviewCard = ({ data }: CostOverviewCardProps) => { mapFiltersToProps, ); - // There should always be a daily cost metric but a comparison metric is optional. - const dailyCost = findAlways(config.metrics, m => m.kind === null); - const metric = config.metrics.find(m => m.kind === filters.metric); - const comparedChange = data[1] ? getComparedChange(data[0], data[1]) : null; + const metric = filters.metric + ? findAlways(config.metrics, m => m.kind === filters.metric) + : null; + const comparedChange = metricData + ? getComparedChange(dailyCostData, metricData) + : null; return ( @@ -62,21 +68,18 @@ const CostOverviewCard = ({ data }: CostOverviewCardProps) => { - - {formatPercent(data[0].change.ratio)} + + {formatPercent(dailyCostData.change.ratio)} - {metric && metric.kind && data[1] && comparedChange && ( + {metric && metricData && comparedChange && ( <> - {formatPercent(data[1].change.ratio)} + {formatPercent(metricData.change.ratio)} { )} diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx index 40cefbb589..9612589542 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx @@ -16,7 +16,6 @@ import React from 'react'; import { useTheme } from '@material-ui/core'; import { - AxisDomain, ComposedChart, XAxis, YAxis, @@ -30,6 +29,7 @@ import { import { ChartData, Cost, + Maybe, Metric, MetricData, CostInsightsTheme, @@ -40,71 +40,69 @@ import { } from '../../utils/graphs'; import CostOverviewTooltip from './CostOverviewTooltip'; import { TooltipItemProps } from '../Tooltip'; -import { NULL_METRIC } from '../../hooks/useConfig'; import { useCostOverviewStyles as useStyles } from '../../utils/styles'; import { groupByDate, toDataMax, trendFrom } from '../../utils/charts'; import { aggregationSort } from '../../utils/sort'; type CostOverviewChartProps = { - data: [Cost, MetricData | null]; - name: string; - compare?: Metric; + metric: Maybe; + metricData: Maybe; + dailyCostData: Cost; responsive?: boolean; }; const CostOverviewChart = ({ - data, - name, - compare, + dailyCostData, + metric, + metricData, responsive = true, }: CostOverviewChartProps) => { const theme = useTheme(); const styles = useStyles(theme); - const { dailyCost, metric } = { + const data = { dailyCost: { - id: NULL_METRIC, - name: name, - format: 'number', - data: data[0], + dataKey: 'dailyCost', + name: `Daily Cost`, + format: 'currency', + data: dailyCostData, }, metric: { - id: compare?.kind ?? 'Unknown', - name: compare?.name ?? 'Unknown', - format: data[1]?.format ?? 'number', - data: data[1], + dataKey: metric?.kind ?? 'Unknown', + name: metric?.name ?? 'Unknown', + format: metricData?.format ?? 'number', + data: metricData, }, }; - const metricsByDate = metric.data - ? metric.data.aggregation.reduce(groupByDate, {}) + const metricsByDate = data.metric.data + ? data.metric.data.aggregation.reduce(groupByDate, {}) : {}; - const chartData: ChartData[] = dailyCost - .data!.aggregation.slice() + const chartData: ChartData[] = data.dailyCost.data.aggregation + .slice() .sort(aggregationSort) .map(entry => ({ date: Date.parse(entry.date), - trend: trendFrom(dailyCost.data!.trendline, Date.parse(entry.date)), + trend: trendFrom(data.dailyCost.data.trendline, Date.parse(entry.date)), dailyCost: entry.amount, - ...(metric && metric.data - ? { [metric.id]: metricsByDate[`${entry.date}`] } + ...(metric && data.metric.data + ? { [data.metric.dataKey]: metricsByDate[`${entry.date}`] } : {}), })); - const metricDataMax: AxisDomain = metric - ? toDataMax(metric.id, chartData) - : 'dataMax'; - function tooltipFormatter(payload: TooltipPayload): TooltipItemProps { return { - label: payload.dataKey === dailyCost.id ? dailyCost.name : metric.name, + label: + payload.dataKey === data.dailyCost.dataKey + ? data.dailyCost.name + : data.metric.name, value: - payload.dataKey === dailyCost.id - ? formatGraphValue(payload.value as number, dailyCost.format) - : formatGraphValue(payload.value as number, metric.format), + payload.dataKey === data.dailyCost.dataKey + ? formatGraphValue(payload.value as number, data.dailyCost.format) + : formatGraphValue(payload.value as number, data.metric.format), fill: - payload.dataKey === dailyCost.id + payload.dataKey === data.dailyCost.dataKey ? theme.palette.blue : theme.palette.magenta, }; @@ -131,23 +129,23 @@ const CostOverviewChart = ({ tick={{ fill: styles.axis.fill }} tickFormatter={formatGraphValue} width={styles.yAxis.width} - yAxisId={dailyCost.id} + yAxisId={data.dailyCost.dataKey} /> {metric && ( 0, metricDataMax]} + domain={[() => 0, toDataMax(data.metric.dataKey, chartData)]} width={styles.yAxis.width} - yAxisId={metric.id} + yAxisId={data.metric.dataKey} /> )} {metric && ( )} } diff --git a/plugins/cost-insights/src/components/MetricSelect/MetricSelect.test.tsx b/plugins/cost-insights/src/components/MetricSelect/MetricSelect.test.tsx index 67d1ec3ad3..e78fd77b1a 100644 --- a/plugins/cost-insights/src/components/MetricSelect/MetricSelect.test.tsx +++ b/plugins/cost-insights/src/components/MetricSelect/MetricSelect.test.tsx @@ -23,7 +23,7 @@ describe('', () => { it('should display a metric', async () => { const mockProps: MetricSelectProps = { metric: 'test', - metrics: [{ kind: 'test', name: 'some-name' }], + metrics: [{ kind: 'test', name: 'some-name', default: false }], onSelect: jest.fn(), }; const { getByText } = await renderInTestApp( @@ -32,25 +32,12 @@ describe('', () => { expect(getByText(/some-name/)).toBeInTheDocument(); }); - it('should display a null metric', async () => { - const mockProps: MetricSelectProps = { - metric: null, - metrics: [{ kind: null, name: 'billie-nullish' }], - onSelect: jest.fn(), - }; - const { getByText } = await renderInTestApp( - , - ); - expect(getByText(/billie-nullish/)).toBeInTheDocument(); - }); - it('should display all metrics', async () => { const mockProps: MetricSelectProps = { metric: null, metrics: [ - { kind: null, name: 'billie-nullish' }, - { kind: 'MAU1M', name: 'Cost Per Million MAU' }, - { kind: 'my-cool-metric', name: 'metric-mcmetric-face' }, + { kind: 'DAU', name: 'Daily Active Users', default: true }, + { kind: 'MSC', name: 'Monthly Subscribers', default: false }, ], onSelect: jest.fn(), }; @@ -61,11 +48,10 @@ describe('', () => { UserEvent.click(button); - await waitFor(() => getAllByText(/billie-nullish/)); + await waitFor(() => getAllByText(/None/)); // The active metric should display in the popver list and in the input - expect(getAllByText(/billie-nullish/).length).toBe(2); - expect(getByText(/Cost Per Million MAU/)).toBeInTheDocument(); - expect(getByText(/metric-mcmetric-face/)).toBeInTheDocument(); + expect(getByText(/Daily Active Users/)).toBeInTheDocument(); + expect(getByText(/Monthly Subscribers/)).toBeInTheDocument(); }); }); diff --git a/plugins/cost-insights/src/components/MetricSelect/MetricSelect.tsx b/plugins/cost-insights/src/components/MetricSelect/MetricSelect.tsx index 1d3af911d4..474d8622ea 100644 --- a/plugins/cost-insights/src/components/MetricSelect/MetricSelect.tsx +++ b/plugins/cost-insights/src/components/MetricSelect/MetricSelect.tsx @@ -15,10 +15,9 @@ */ import React from 'react'; -import { Select, MenuItem } from '@material-ui/core'; -import { Maybe, Metric, findAlways } from '../../types'; +import { InputLabel, FormControl, Select, MenuItem } from '@material-ui/core'; +import { Maybe, Metric } from '../../types'; import { useSelectStyles as useStyles } from '../../utils/styles'; -import { NULL_METRIC } from '../../hooks/useConfig'; export type MetricSelectProps = { metric: Maybe; @@ -29,38 +28,37 @@ export type MetricSelectProps = { const MetricSelect = ({ metric, metrics, onSelect }: MetricSelectProps) => { const classes = useStyles(); - const handleOnChange = (e: React.ChangeEvent<{ value: unknown }>) => { - if (e.target.value === NULL_METRIC) { + function onChange(e: React.ChangeEvent<{ value: unknown }>) { + if (e.target.value === 'none') { onSelect(null); } else { onSelect(e.target.value as string); } - }; - - const renderValue = (value: unknown) => { - const kind = (value === NULL_METRIC ? null : value) as Maybe; - const { name } = findAlways(metrics, m => m.kind === kind); - return {name}; - }; + } return ( - + + None - ))} - + {metrics.map((m: Metric) => ( + + {m.name} + + ))} + + ); }; diff --git a/plugins/cost-insights/src/hooks/useConfig.tsx b/plugins/cost-insights/src/hooks/useConfig.tsx index 830cafc4f1..6cbdd1a2ef 100644 --- a/plugins/cost-insights/src/hooks/useConfig.tsx +++ b/plugins/cost-insights/src/hooks/useConfig.tsx @@ -25,12 +25,10 @@ import { useApi, configApiRef } from '@backstage/core'; import { Config as BackstageConfig } from '@backstage/config'; import { Currency, defaultCurrencies, Product, Icon, Metric } from '../types'; import { getIcon } from '../utils/navigation'; - -export const NULL_METRIC = 'dailyCost'; -export const NULL_METRIC_NAME = 'Daily Cost'; +import { validateMetrics } from '../utils/config'; /* - * Config schema 2020-09-28 + * Config schema 2020-10-15 * * costInsights: * engineerCost: 200000 @@ -42,11 +40,9 @@ export const NULL_METRIC_NAME = 'Daily Cost'; * name: Product B * icon: data * metrics: - * dailyCost: - * name: Daily Cost - * compare: metricA * metricA: * name: Metric A + * default: true * metricB: * name: Metric B */ @@ -64,7 +60,7 @@ export const ConfigContext = createContext( ); const defaultState: ConfigContextProps = { - metrics: [{ kind: null, name: NULL_METRIC_NAME }], + metrics: [], products: [], icons: [], engineerCost: 0, @@ -90,9 +86,9 @@ export const ConfigProvider = ({ children }: { children: ReactNode }) => { const metrics = c.getOptionalConfig('costInsights.metrics'); if (metrics) { return metrics.keys().map(key => ({ - kind: key === NULL_METRIC ? null : key, + kind: key, name: metrics.getString(`${key}.name`), - compare: metrics.getOptionalString(`${key}.compare`), + default: metrics.getOptionalBoolean(`${key}.default`) ?? false, })); } @@ -119,23 +115,15 @@ export const ConfigProvider = ({ children }: { children: ReactNode }) => { const engineerCost = getEngineerCost(); const icons = getIcons(); - if (metrics.find((m: Metric) => m.kind === null)) { - setConfig(prevState => ({ - ...prevState, - metrics, - products, - engineerCost, - icons, - })); - } else { - setConfig(prevState => ({ - ...prevState, - metrics: [...prevState.metrics, ...metrics], - products, - engineerCost, - icons, - })); - } + validateMetrics(metrics); + + setConfig(prevState => ({ + ...prevState, + metrics, + products, + engineerCost, + icons, + })); setLoading(false); } @@ -154,12 +142,7 @@ export const ConfigProvider = ({ children }: { children: ReactNode }) => { export function useConfig(): ConfigContextProps { const config = useContext(ConfigContext); - - if (!config) { - assertNever(); - } - - return config; + return config ? config : assertNever(); } function assertNever(): never { diff --git a/plugins/cost-insights/src/hooks/useFilters.tsx b/plugins/cost-insights/src/hooks/useFilters.tsx index 0ad4c24267..7dcf8ddb26 100644 --- a/plugins/cost-insights/src/hooks/useFilters.tsx +++ b/plugins/cost-insights/src/hooks/useFilters.tsx @@ -75,9 +75,8 @@ export const FilterProvider = ({ children }: FilterProviderProps) => { const queryParams = useQueryParams(); const qsRef = useRef(''); const groups = useGroups(); - const { products } = useConfig(); - const defaultProductFilters = products.map(product => ({ + const defaultProductFilters = config.products.map(product => ({ productType: product.kind, duration: Duration.P1M, })); @@ -103,8 +102,8 @@ export const FilterProvider = ({ children }: FilterProviderProps) => { // TODO: Figure out why pageFilters doesn't get updated by the above when groups are loaded. useEffect(() => { const initialState = getInitialPageState(groups, queryParams.pageFilters); - const compared = config.metrics.find(m => m.kind === null)?.compare; - setPageFilters({ ...initialState, metric: compared || null }); + const defaultMetric = config.metrics.find(m => m.default); + setPageFilters({ ...initialState, metric: defaultMetric?.kind ?? null }); }, [groups]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { diff --git a/plugins/cost-insights/src/types/ChangeStatistic.ts b/plugins/cost-insights/src/types/ChangeStatistic.ts index cf7310ef97..cdd2e02da6 100644 --- a/plugins/cost-insights/src/types/ChangeStatistic.ts +++ b/plugins/cost-insights/src/types/ChangeStatistic.ts @@ -15,6 +15,7 @@ */ import { Cost } from './Cost'; +import { MetricData } from './MetricData'; import { aggregationSort } from '../utils/sort'; export interface ChangeStatistic { @@ -51,9 +52,12 @@ export function growthOf(amount: number, ratio: number) { } // Used by for displaying engineer totals -export function getComparedChange(a: Cost, b: Cost): ChangeStatistic { - const ratio = a.change.ratio - b.change.ratio; - const amount = a.aggregation.slice().sort(aggregationSort)[0].amount; +export function getComparedChange( + dailyCost: Cost, + metricData: MetricData, +): ChangeStatistic { + const ratio = dailyCost.change.ratio - metricData.change.ratio; + const amount = dailyCost.aggregation.slice().sort(aggregationSort)[0].amount; return { ratio: ratio, amount: amount * ratio, diff --git a/plugins/cost-insights/src/types/Cost.ts b/plugins/cost-insights/src/types/Cost.ts index 8a9ce3c69b..84a8bfae51 100644 --- a/plugins/cost-insights/src/types/Cost.ts +++ b/plugins/cost-insights/src/types/Cost.ts @@ -24,7 +24,3 @@ export interface Cost { change: ChangeStatistic; trendline: Trendline; } - -export interface MetricData extends Cost { - format: 'number' | 'currency'; -} diff --git a/plugins/cost-insights/src/types/Metric.ts b/plugins/cost-insights/src/types/Metric.ts index 4fa9cd189b..b0a0c9cc76 100644 --- a/plugins/cost-insights/src/types/Metric.ts +++ b/plugins/cost-insights/src/types/Metric.ts @@ -14,10 +14,8 @@ * limitations under the License. */ -import { Maybe } from '../types'; - export type Metric = { - kind: Maybe; + kind: string; name: string; - compare?: string; + default: boolean; }; diff --git a/plugins/cost-insights/src/types/MetricData.ts b/plugins/cost-insights/src/types/MetricData.ts new file mode 100644 index 0000000000..bd3bdde735 --- /dev/null +++ b/plugins/cost-insights/src/types/MetricData.ts @@ -0,0 +1,25 @@ +/* + * 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 { DateAggregation } from './DateAggregation'; +import { ChangeStatistic } from './ChangeStatistic'; + +export interface MetricData { + id: string; + format: 'number' | 'currency'; + aggregation: DateAggregation[]; + change: ChangeStatistic; +} diff --git a/plugins/cost-insights/src/types/index.ts b/plugins/cost-insights/src/types/index.ts index 8523c7db7b..398110ec80 100644 --- a/plugins/cost-insights/src/types/index.ts +++ b/plugins/cost-insights/src/types/index.ts @@ -27,6 +27,7 @@ export * from './Filters'; export * from './Group'; export * from './Loading'; export * from './Maybe'; +export * from './MetricData'; export * from './Metric'; export * from './Product'; export * from './Project'; diff --git a/plugins/cost-insights/src/utils/config.ts b/plugins/cost-insights/src/utils/config.ts new file mode 100644 index 0000000000..c2c2c066d9 --- /dev/null +++ b/plugins/cost-insights/src/utils/config.ts @@ -0,0 +1,32 @@ +/* + * 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 { Metric } from '../types'; + +export function validateMetrics(metrics: Metric[]) { + let defaultMetric = null; + for (const metric of metrics) { + if (metric.default) { + if (defaultMetric) { + throw new Error( + `Cannot set default for multiple metrics: Received: ${defaultMetric.kind} and ${metric.kind}`, + ); + } else { + defaultMetric = metric; + } + } + } +} From 71de0d8aee494c81c8cfe4ab623fd6fcf12a5f74 Mon Sep 17 00:00:00 2001 From: David Tuite Date: Fri, 16 Oct 2020 13:39:15 +0100 Subject: [PATCH 061/104] Add Google Analytics Specity a Google Analytics Tracking ID in the `app-config.yaml` to enable Google Analytics tracking. ``` app: title: baseUrl: googleAnalyticsTrackingId: UA-000000-0 ``` If the `googleAnalyticsTrackingId` is not supplied, no tracking script is added to the application and no tracking can occur. Includes Helm chart changes to allow overriding the GA Tracking ID. --- app-config.yaml | 1 + .../templates/backstage-app-config.yaml | 1 + contrib/chart/backstage/values.yaml | 1 + packages/app/public/index.html | 17 +++++++++++++++++ packages/cli/src/lib/bundler/config.ts | 3 +++ 5 files changed, 23 insertions(+) diff --git a/app-config.yaml b/app-config.yaml index 12fe211a4b..c48265df9e 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -1,6 +1,7 @@ app: title: Backstage Example App baseUrl: http://localhost:7000 + googleAnalyticsTrackingId: # UA-000000-0 backend: baseUrl: http://localhost:7000 diff --git a/contrib/chart/backstage/templates/backstage-app-config.yaml b/contrib/chart/backstage/templates/backstage-app-config.yaml index 061fc3285b..f06e47feab 100644 --- a/contrib/chart/backstage/templates/backstage-app-config.yaml +++ b/contrib/chart/backstage/templates/backstage-app-config.yaml @@ -13,6 +13,7 @@ metadata: data: APP_CONFIG_app_baseUrl: {{ .Values.appConfig.app.baseUrl | quote | quote }} APP_CONFIG_app_title: {{ .Values.appConfig.app.title | quote | quote }} + APP_CONFIG_app_googleAnalyticsTrackingId: {{ .Values.appConfig.app.googleAnalyticsTrackingId | quote | quote }} APP_CONFIG_backend_baseUrl: {{ .Values.appConfig.backend.baseUrl | quote | quote }} APP_CONFIG_backend_cors_origin: {{ .Values.appConfig.backend.cors.origin | quote | quote }} APP_CONFIG_techdocs_storageUrl: {{ .Values.appConfig.techdocs.storageUrl | quote | quote }} diff --git a/contrib/chart/backstage/values.yaml b/contrib/chart/backstage/values.yaml index d51f1016d9..f92e8aad47 100644 --- a/contrib/chart/backstage/values.yaml +++ b/contrib/chart/backstage/values.yaml @@ -86,6 +86,7 @@ appConfig: app: baseUrl: https://demo.example.com title: Backstage + googleAnalyticsTrackingId: backend: baseUrl: https://demo.example.com listen: diff --git a/packages/app/public/index.html b/packages/app/public/index.html index ea9208ca57..77e5c01e19 100644 --- a/packages/app/public/index.html +++ b/packages/app/public/index.html @@ -48,6 +48,23 @@ } <%= app.title %> + + <% if (app.googleAnalyticsTrackingId && typeof app.googleAnalyticsTrackingId + === 'string') { %> + + + <% } %> diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 00a555eebe..34b9f498ad 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -111,6 +111,9 @@ export async function createConfig( app: { title: options.config.getString('app.title'), baseUrl: validBaseUrl.href, + googleAnalyticsTrackingId: options.config.getOptionalString( + 'app.googleAnalyticsTrackingId', + ), }, }, }), From 1e278d09ad60aad36c4ced7e8cdb6aab43c837ba Mon Sep 17 00:00:00 2001 From: David Tuite Date: Fri, 16 Oct 2020 14:05:59 +0100 Subject: [PATCH 062/104] Add dist-types to .prettierignore --- .prettierignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierignore b/.prettierignore index 4b1acbb594..c4e675a5d7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,3 +6,4 @@ coverage templates plugins/scaffolder-backend/sample-templates .vscode +dist-types From 2e1a0b8c99afa8557078afc8ab7102430513e6f7 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Fri, 16 Oct 2020 16:02:08 +0200 Subject: [PATCH 063/104] feat(api-docs): show a custom error page when no apis are implemented by a component (#2933) --- .../MissingImplementsApisEmptyState.tsx | 82 +++++++++++++++++++ .../MissingImplementsApisEmptyState/index.ts | 17 ++++ plugins/api-docs/src/catalog/Router.tsx | 7 +- 3 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx create mode 100644 plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/index.ts diff --git a/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx new file mode 100644 index 0000000000..9160aca514 --- /dev/null +++ b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx @@ -0,0 +1,82 @@ +/* + * 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 { Button, makeStyles, Typography } from '@material-ui/core'; +import { BackstageTheme } from '@backstage/theme'; +import { CodeSnippet, EmptyState } from '@backstage/core'; + +const COMPONENT_YAML = `# Example +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: example +spec: + type: service + lifecycle: production + owner: guest + implementsApis: + - example-api +`; + +const useStyles = makeStyles(theme => ({ + code: { + borderRadius: 6, + margin: `${theme.spacing(2)}px 0px`, + background: theme.palette.type === 'dark' ? '#444' : '#fff', + }, +})); + +export const MissingImplementsApisEmptyState = () => { + const classes = useStyles(); + return ( + + Components can implement APIs that are displayed on this page. You + need to fill the implementsApis field to enable this + tool. + + } + action={ + <> + + Link an API to your component as shown in the highlighted example + below: + +
+ +
+ + + } + /> + ); +}; diff --git a/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/index.ts b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/index.ts new file mode 100644 index 0000000000..1b7d35c0a2 --- /dev/null +++ b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/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 { MissingImplementsApisEmptyState } from './MissingImplementsApisEmptyState'; diff --git a/plugins/api-docs/src/catalog/Router.tsx b/plugins/api-docs/src/catalog/Router.tsx index 71640954b3..d3fe7f7553 100644 --- a/plugins/api-docs/src/catalog/Router.tsx +++ b/plugins/api-docs/src/catalog/Router.tsx @@ -17,20 +17,17 @@ import React from 'react'; import { Entity } from '@backstage/catalog-model'; import { Route, Routes } from 'react-router'; -import { WarningPanel } from '@backstage/core'; import { catalogRoute } from '../routes'; import { EntityPageApi } from './EntityPageApi'; +import { MissingImplementsApisEmptyState } from './MissingImplementsApisEmptyState'; const isPluginApplicableToEntity = (entity: Entity) => { return ((entity.spec?.implementsApis as string[]) || []).length > 0; }; export const Router = ({ entity }: { entity: Entity }) => - // TODO(shmidt-i): move warning to a separate standardized component !isPluginApplicableToEntity(entity) ? ( - - The entity doesn't implement any APIs. - + ) : ( Date: Fri, 16 Oct 2020 16:09:05 +0200 Subject: [PATCH 064/104] chore(cloudbuild): publish the cloudbuild plugin --- plugins/cloudbuild/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 6c3ea2ff9a..d36b661664 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -4,7 +4,6 @@ "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", - "private": true, "publishConfig": { "access": "public", "main": "dist/index.esm.js", From a508287b9c36ac0d6664d7837b7aeaa94d42418e Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Fri, 16 Oct 2020 10:30:48 -0400 Subject: [PATCH 065/104] throw errors for multiple defaults --- plugins/cost-insights/src/utils/config.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/plugins/cost-insights/src/utils/config.ts b/plugins/cost-insights/src/utils/config.ts index c2c2c066d9..4af7140d9c 100644 --- a/plugins/cost-insights/src/utils/config.ts +++ b/plugins/cost-insights/src/utils/config.ts @@ -17,16 +17,10 @@ import { Metric } from '../types'; export function validateMetrics(metrics: Metric[]) { - let defaultMetric = null; - for (const metric of metrics) { - if (metric.default) { - if (defaultMetric) { - throw new Error( - `Cannot set default for multiple metrics: Received: ${defaultMetric.kind} and ${metric.kind}`, - ); - } else { - defaultMetric = metric; - } - } + const defaults = metrics.filter(metric => metric.default); + if (defaults.length > 1) { + throw new Error( + `Only one default metric can be set at a time. Found ${defaults.length}`, + ); } } From e62956f96f3fbd4ec13f1829752fd009b807d1e4 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Fri, 16 Oct 2020 11:02:11 -0400 Subject: [PATCH 066/104] getMetricData -> getDailyMetricData --- .../src/plugins/cost-insights/ExampleCostInsightsClient.ts | 5 ++++- plugins/cost-insights/src/api/CostInsightsApi.ts | 2 +- .../src/components/CostInsightsPage/CostInsightsPage.tsx | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts index 0b275baf74..a6ef15f3b9 100644 --- a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts +++ b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts @@ -117,7 +117,10 @@ export class ExampleCostInsightsClient implements CostInsightsApi { return projects; } - async getMetricData(metric: string, intervals: string): Promise { + async getDailyMetricData( + metric: string, + intervals: string, + ): Promise { const aggregation = aggregationFor( durationOf(intervals), 100_000, diff --git a/plugins/cost-insights/src/api/CostInsightsApi.ts b/plugins/cost-insights/src/api/CostInsightsApi.ts index 3136fb4c27..028aead853 100644 --- a/plugins/cost-insights/src/api/CostInsightsApi.ts +++ b/plugins/cost-insights/src/api/CostInsightsApi.ts @@ -87,7 +87,7 @@ export type CostInsightsApi = { * @param intervals An ISO 8601 repeating interval string, such as R2/P1M/2020-09-01 * https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals */ - getMetricData(metric: string, intervals: string): Promise; + getDailyMetricData(metric: string, intervals: string): Promise; /** * Get cost aggregations for a particular cloud product and interval timeframe. This includes diff --git a/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx b/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx index 20f965f1c7..77d9f5a05e 100644 --- a/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx +++ b/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx @@ -111,7 +111,7 @@ const CostInsightsPage = () => { client.getGroupProjects(pageFilters.group), client.getAlerts(pageFilters.group), pageFilters.metric - ? client.getMetricData(pageFilters.metric, intervals) + ? client.getDailyMetricData(pageFilters.metric, intervals) : null, pageFilters.project ? client.getProjectDailyCost(pageFilters.project, intervals) From 6dba5f6b60ace9113abef852ae2cf24ddfbee368 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Fri, 16 Oct 2020 11:22:39 -0400 Subject: [PATCH 067/104] update README --- plugins/cost-insights/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cost-insights/README.md b/plugins/cost-insights/README.md index 3028d3a09c..8c2fd11761 100644 --- a/plugins/cost-insights/README.md +++ b/plugins/cost-insights/README.md @@ -79,7 +79,7 @@ costInsights: ### Metrics (Optional) -In the `Cost Overview` panel, users can choose from a dropdown of business metrics to see costs as they relate to a metric, such as daily active users. Metrics must be defined as keys on the `metrics` field. A user-friendly name is **required**. Metrics will be provided to the `getMetricData` API method via the `metric` parameter. +In the `Cost Overview` panel, users can choose from a dropdown of business metrics to see costs as they relate to a metric, such as daily active users. Metrics must be defined as keys on the `metrics` field. A user-friendly name is **required**. Metrics will be provided to the `getDailyMetricData` API method via the `metric` parameter. An optional `default` field can be set to `true` to set the default comparison metric to daily cost in the Cost Overview panel. From e7d4ac7ce3137a01336c15a12eab46ee66db5f3e Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Fri, 16 Oct 2020 11:23:04 -0400 Subject: [PATCH 068/104] add changeset --- .changeset/red-games-float.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/red-games-float.md diff --git a/.changeset/red-games-float.md b/.changeset/red-games-float.md new file mode 100644 index 0000000000..6eabba26a4 --- /dev/null +++ b/.changeset/red-games-float.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-cost-insights': minor +--- + +- getProjectDailyCost and getGroupDailyCost no longer accept a metric as a parameter +- getDailyMetricData added to API for fetching daily metric data for given interval +- dailyCost removed as configurable metric +- default field added to metric configuration for displaying comparison metric data in top panel +- Metric.kind can no longer be null +- MetricData type added From fc1705050ef8d2312f49c675b7c20aea31f95629 Mon Sep 17 00:00:00 2001 From: Abhishek Jakhar Date: Sat, 17 Oct 2020 08:15:31 +0530 Subject: [PATCH 069/104] give aria-label attribute to Status Ok, Warning and Error --- .../core/src/components/Status/Status.tsx | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/Status/Status.tsx b/packages/core/src/components/Status/Status.tsx index 9a5ce5b218..6a2e753f1f 100644 --- a/packages/core/src/components/Status/Status.tsx +++ b/packages/core/src/components/Status/Status.tsx @@ -65,20 +65,34 @@ const useStyles = makeStyles(theme => ({ export const StatusOK: FC<{}> = props => { const classes = useStyles(props); - return ; + return ( + + ); }; export const StatusWarning: FC<{}> = props => { const classes = useStyles(props); return ( - + ); }; export const StatusError: FC<{}> = props => { const classes = useStyles(props); return ( - + ); }; From 754e31db5e0438038105d3d0636afe2fc3e89cbd Mon Sep 17 00:00:00 2001 From: Abhishek Jakhar Date: Sat, 17 Oct 2020 08:20:35 +0530 Subject: [PATCH 070/104] add changeset --- .changeset/swift-peas-argue.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/swift-peas-argue.md diff --git a/.changeset/swift-peas-argue.md b/.changeset/swift-peas-argue.md new file mode 100644 index 0000000000..d78d844d45 --- /dev/null +++ b/.changeset/swift-peas-argue.md @@ -0,0 +1,5 @@ +--- +'@backstage/core': patch +--- + +give aria-label attribute to Status Ok, Warning and Error From bb243dca25cf3057297dbfb6a3cbe52289f215c0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 13 Oct 2020 11:46:21 +0200 Subject: [PATCH 071/104] cli: add support for ts4 --- packages/cli/package.json | 4 ++-- packages/docgen/package.json | 2 +- yarn.lock | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 084254d7ff..ace0639cce 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -84,12 +84,12 @@ "rollup-pluginutils": "^2.8.2", "start-server-webpack-plugin": "^2.2.5", "style-loader": "^1.2.1", - "sucrase": "^3.14.1", + "sucrase": "^3.16.0", "tar": "^6.0.1", "terser-webpack-plugin": "^1.4.3", "ts-jest": "^26.0.0", "ts-loader": "^7.0.4", - "typescript": "^3.9.3", + "typescript": "^4.0.3", "url-loader": "^4.1.0", "webpack": "^4.41.6", "webpack-dev-server": "^3.11.0", diff --git a/packages/docgen/package.json b/packages/docgen/package.json index 61efc0bbd4..7c0bf846bd 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -31,7 +31,7 @@ "fs-extra": "^9.0.0", "github-slugger": "^1.3.0", "ts-node": "^8.6.2", - "typescript": "^3.9.3" + "typescript": "^4.0.3" }, "devDependencies": { "@types/fs-extra": "^9.0.1", diff --git a/yarn.lock b/yarn.lock index 4d28072207..9a5bd93ffb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21732,10 +21732,10 @@ subscriptions-transport-ws@^0.9.11, subscriptions-transport-ws@^0.9.16: symbol-observable "^1.0.4" ws "^5.2.0" -sucrase@^3.14.1: - version "3.15.0" - resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.15.0.tgz#78596a78be7264a65b52ed8d873883413ef0220c" - integrity sha512-05TJOUfMgckH7wKqfk/1p4G6q16nIeW/GHQwD44vkT0mQMqqzgfHCwkX3whNmwyOo7nVF0jDLwVu/qOBTtsscw== +sucrase@^3.16.0: + version "3.16.0" + resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.16.0.tgz#19b5b886ccca270dd5ca12ff060eeaf0b599735f" + integrity sha512-ovVuswxV5TayCPXfTk8bgBgk6uNRvsinIkEpq0J6zS1xXCx5N/LLGcbsKdRhqn/ToZylMX6+yXaR1LSn1I42Pg== dependencies: commander "^4.0.0" glob "7.1.6" @@ -22702,10 +22702,10 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.9.3: - version "3.9.7" - resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" - integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== +typescript@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5" + integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg== ua-parser-js@^0.7.18: version "0.7.21" From 9f3ddfacdeed708755b98a0964c936eed6f23b09 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 17 Oct 2020 14:44:02 +0200 Subject: [PATCH 072/104] cli: split and bump eslint dependencies + fix issues --- .../src/database/config.test.ts | 2 +- .../src/database/connection.test.ts | 2 +- .../src/database/postgres.test.ts | 10 +- .../src/database/sqlite3.test.ts | 4 +- packages/backend-common/src/paths.ts | 2 - packages/cli/package.json | 11 +- packages/cli/src/lib/parallel.test.ts | 4 +- packages/techdocs-cli/package.json | 6 +- .../processors/CodeOwnersProcessor.test.ts | 18 +- .../src/components/BarChart/BarChart.test.tsx | 14 +- .../ProjectSelect/ProjectSelect.test.tsx | 7 +- .../src/components/AuditView/index.test.tsx | 4 +- .../src/hooks/useWebsiteForEntity.test.tsx | 2 +- .../RegisterComponentForm.test.tsx | 2 +- yarn.lock | 190 ++++++++++-------- 15 files changed, 157 insertions(+), 121 deletions(-) diff --git a/packages/backend-common/src/database/config.test.ts b/packages/backend-common/src/database/config.test.ts index c9a4107686..eb26013b4b 100644 --- a/packages/backend-common/src/database/config.test.ts +++ b/packages/backend-common/src/database/config.test.ts @@ -17,7 +17,7 @@ import { mergeDatabaseConfig } from './config'; describe('config', () => { - describe(mergeDatabaseConfig, () => { + describe('mergeDatabaseConfig', () => { it('does not mutate the input object', () => { const input = { original: 'key', diff --git a/packages/backend-common/src/database/connection.test.ts b/packages/backend-common/src/database/connection.test.ts index 2cc3a54e06..de0b0e40dc 100644 --- a/packages/backend-common/src/database/connection.test.ts +++ b/packages/backend-common/src/database/connection.test.ts @@ -26,7 +26,7 @@ describe('database connection', () => { }, ]); - describe(createDatabaseClient, () => { + describe('createDatabaseClient', () => { it('returns a postgres connection', () => { expect( createDatabaseClient( diff --git a/packages/backend-common/src/database/postgres.test.ts b/packages/backend-common/src/database/postgres.test.ts index 82161dc53c..8d139e4481 100644 --- a/packages/backend-common/src/database/postgres.test.ts +++ b/packages/backend-common/src/database/postgres.test.ts @@ -44,7 +44,7 @@ describe('postgres', () => { }, ]); - describe(buildPgDatabaseConfig, () => { + describe('buildPgDatabaseConfig', () => { it('builds a postgres config', () => { const mockConnection = createMockConnection(); @@ -125,7 +125,7 @@ describe('postgres', () => { }); }); - describe(getPgConnectionConfig, () => { + describe('getPgConnectionConfig', () => { it('returns the connection object back', () => { const mockConnection = createMockConnection(); const config = createConfig(mockConnection); @@ -163,7 +163,7 @@ describe('postgres', () => { }); }); - describe(createPgDatabaseClient, () => { + describe('createPgDatabaseClient', () => { it('creates a postgres knex instance', () => { expect( createPgDatabaseClient( @@ -188,8 +188,8 @@ describe('postgres', () => { }); }); - describe(parsePgConnectionString, () => { - it('parses a connection string uri ', () => { + describe('parsePgConnectionString', () => { + it('parses a connection string uri', () => { expect( parsePgConnectionString( 'postgresql://postgres:pass@foobar:5432/dbname?ssl=true', diff --git a/packages/backend-common/src/database/sqlite3.test.ts b/packages/backend-common/src/database/sqlite3.test.ts index dbff5ee354..a3ab331c2d 100644 --- a/packages/backend-common/src/database/sqlite3.test.ts +++ b/packages/backend-common/src/database/sqlite3.test.ts @@ -32,7 +32,7 @@ describe('sqlite3', () => { }, ]); - describe(buildSqliteDatabaseConfig, () => { + describe('buildSqliteDatabaseConfig', () => { it('buidls a string connection', () => { expect(buildSqliteDatabaseConfig(createConfig(':memory:'))).toEqual({ client: 'sqlite3', @@ -72,7 +72,7 @@ describe('sqlite3', () => { }); }); - describe(createSqliteDatabaseClient, () => { + describe('createSqliteDatabaseClient', () => { it('creates an in memory knex instance', () => { expect( createSqliteDatabaseClient( diff --git a/packages/backend-common/src/paths.ts b/packages/backend-common/src/paths.ts index 402c0e2252..262be366f6 100644 --- a/packages/backend-common/src/paths.ts +++ b/packages/backend-common/src/paths.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -/* eslint-disable @typescript-eslint/camelcase */ - import { resolve as resolvePath } from 'path'; /** diff --git a/packages/cli/package.json b/packages/cli/package.json index ace0639cce..74cbbfff6a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -38,7 +38,9 @@ "@rollup/plugin-json": "^4.0.2", "@rollup/plugin-node-resolve": "^8.1.0", "@rollup/plugin-yaml": "^2.1.1", - "@spotify/eslint-config": "^7.0.1", + "@spotify/eslint-config-base": "^8.0.0", + "@spotify/eslint-config-react": "^8.0.0", + "@spotify/eslint-config-typescript": "^8.0.0", "@sucrase/webpack-loader": "^2.0.0", "@svgr/plugin-jsx": "5.4.x", "@svgr/plugin-svgo": "5.4.x", @@ -47,6 +49,8 @@ "@types/start-server-webpack-plugin": "^2.2.0", "@types/webpack-env": "^1.15.2", "@types/webpack-node-externals": "^2.5.0", + "@typescript-eslint/eslint-plugin": "^v3.10.1", + "@typescript-eslint/parser": "^v3.10.1", "bfj": "^7.0.2", "chalk": "^4.0.0", "chokidar": "^3.3.1", @@ -56,9 +60,14 @@ "diff": "^4.0.2", "esbuild": "^0.7.7", "eslint": "^7.1.0", + "eslint-config-prettier": "^6.0.0", "eslint-formatter-friendly": "^7.0.0", "eslint-plugin-import": "^2.20.2", + "eslint-plugin-jest": "^24.1.0", + "eslint-plugin-jsx-a11y": "^6.2.1", "eslint-plugin-monorepo": "^0.2.1", + "eslint-plugin-react": "^7.12.4", + "eslint-plugin-react-hooks": "^4.0.0", "fork-ts-checker-webpack-plugin": "^4.0.5", "fs-extra": "^9.0.0", "handlebars": "^4.7.3", diff --git a/packages/cli/src/lib/parallel.test.ts b/packages/cli/src/lib/parallel.test.ts index c8173603d9..8047774a6e 100644 --- a/packages/cli/src/lib/parallel.test.ts +++ b/packages/cli/src/lib/parallel.test.ts @@ -17,7 +17,7 @@ import { isParallelDefault, parseParallel } from './parallel'; describe('parallel', () => { - describe(parseParallel, () => { + describe('parseParallel', () => { it('coerces "false" string to boolean', () => { expect(parseParallel('false')).toBeFalsy(); }); @@ -44,7 +44,7 @@ describe('parallel', () => { }); }); - describe(isParallelDefault, () => { + describe('isParallelDefault', () => { it('returns true if default value', () => { expect(isParallelDefault(undefined)).toBeTruthy(); expect(isParallelDefault(true)).toBeTruthy(); diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index adce3dd101..30ca6e4538 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -28,11 +28,7 @@ "techdocs-cli": "bin/techdocs-cli" }, "devDependencies": { - "@spotify/eslint-config": "^7.0.0", - "@spotify/prettier-config": "^7.0.0", - "@types/serve-handler": "^6.1.0", - "eslint": "^7.1.0", - "eslint-plugin-import": "^2.22.0" + "@types/serve-handler": "^6.1.0" }, "files": [ "bin", diff --git a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts index c1c83b7c6c..e1bd5bcb46 100644 --- a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts @@ -27,7 +27,7 @@ import { resolveCodeOwner, } from './CodeOwnersProcessor'; -describe(CodeOwnersProcessor, () => { +describe('CodeOwnersProcessor', () => { const mockUrl = ({ basePath = '' } = {}): string => `https://github.com/spotify/backstage/blob/master/${basePath}catalog-info.yaml`; const mockLocation = ({ @@ -79,7 +79,7 @@ describe(CodeOwnersProcessor, () => { return data; }; - describe(buildUrl, () => { + describe('buildUrl', () => { it.each([['azure.com'], ['dev.azure.com']])( 'should throw not implemented error', source => { @@ -99,7 +99,7 @@ describe(CodeOwnersProcessor, () => { }); }); - describe(buildCodeOwnerUrl, () => { + describe('buildCodeOwnerUrl', () => { it('should build a location spec to the codeowners', () => { expect(buildCodeOwnerUrl(mockUrl(), '/docs/CODEOWNERS')).toEqual( 'https://github.com/spotify/backstage/blob/master/docs/CODEOWNERS', @@ -116,13 +116,13 @@ describe(CodeOwnersProcessor, () => { }); }); - describe(parseCodeOwners, () => { + describe('parseCodeOwners', () => { it('should parse the codeowners file', () => { expect(parseCodeOwners(mockCodeOwnersText())).toEqual(mockCodeOwners()); }); }); - describe(normalizeCodeOwner, () => { + describe('normalizeCodeOwner', () => { it('should remove org from org/team format', () => { expect(normalizeCodeOwner('@acme/foo')).toBe('foo'); }); @@ -139,13 +139,13 @@ describe(CodeOwnersProcessor, () => { ); }); - describe(findPrimaryCodeOwner, () => { + describe('findPrimaryCodeOwner', () => { it('should return the primary owner', () => { expect(findPrimaryCodeOwner(mockCodeOwners())).toBe('backstage-core'); }); }); - describe(findRawCodeOwners, () => { + describe('findRawCodeOwners', () => { it('should return found codeowner', async () => { const ownersText = mockCodeOwnersText(); const read = jest @@ -184,7 +184,7 @@ describe(CodeOwnersProcessor, () => { }); }); - describe(resolveCodeOwner, () => { + describe('resolveCodeOwner', () => { it('should return found codeowner', async () => { const read = jest .fn() @@ -207,7 +207,7 @@ describe(CodeOwnersProcessor, () => { }); }); - describe(CodeOwnersProcessor, () => { + describe('CodeOwnersProcessor', () => { const setupTest = ({ kind = 'Component', spec = {} } = {}) => { const entity = { kind, spec }; const read = jest diff --git a/plugins/cost-insights/src/components/BarChart/BarChart.test.tsx b/plugins/cost-insights/src/components/BarChart/BarChart.test.tsx index a39c5bafc2..737b86018c 100644 --- a/plugins/cost-insights/src/components/BarChart/BarChart.test.tsx +++ b/plugins/cost-insights/src/components/BarChart/BarChart.test.tsx @@ -79,12 +79,14 @@ describe('', () => { it('Should display only 6 resources by default, sorted by cost', async () => { const rendered = await renderWithProps({} as BarChartProps); - MockResources.sort(resourceSort).forEach((resource, index) => { - if (index < 6) { - expect(rendered.getByText(resource.name!)).toBeInTheDocument(); - } else { - expect(rendered.queryByText(resource.name!)).not.toBeInTheDocument(); - } + const sorted = MockResources.sort(resourceSort); + + expect(sorted.length).toBe(10); + sorted.slice(0, 6).forEach(resource => { + expect(rendered.getByText(resource.name!)).toBeInTheDocument(); + }); + sorted.slice(6).forEach(resource => { + expect(rendered.queryByText(resource.name!)).not.toBeInTheDocument(); }); }); diff --git a/plugins/cost-insights/src/components/ProjectSelect/ProjectSelect.test.tsx b/plugins/cost-insights/src/components/ProjectSelect/ProjectSelect.test.tsx index 92de819fe0..a74c79f3b8 100644 --- a/plugins/cost-insights/src/components/ProjectSelect/ProjectSelect.test.tsx +++ b/plugins/cost-insights/src/components/ProjectSelect/ProjectSelect.test.tsx @@ -59,10 +59,9 @@ describe('', () => { const button = getByRole(projectSelectContainer, 'button'); UserEvent.click(button); await waitFor(() => rendered.getByTestId('option-all')); - mockProjects.forEach( - project => - project.id && - expect(rendered.getByText(project.id)).toBeInTheDocument(), + + mockProjects.forEach(project => + expect(rendered.getByText(project.id)).toBeInTheDocument(), ); }); }); diff --git a/plugins/lighthouse/src/components/AuditView/index.test.tsx b/plugins/lighthouse/src/components/AuditView/index.test.tsx index 742be67fed..ca292a133d 100644 --- a/plugins/lighthouse/src/components/AuditView/index.test.tsx +++ b/plugins/lighthouse/src/components/AuditView/index.test.tsx @@ -163,7 +163,7 @@ describe('AuditView', () => { }); describe('when the request for the website by id is pending', () => { - it('it shows the loading', async () => { + it('shows the loading', async () => { mockFetch.mockImplementationOnce(() => new Promise(() => {})); const rendered = render( wrapInTestApp( @@ -177,7 +177,7 @@ describe('AuditView', () => { }); describe('when the request for the website by id fails', () => { - it('it shows an error', async () => { + it('shows an error', async () => { mockFetch.mockRejectOnce(new Error('failed to fetch')); const rendered = render( wrapInTestApp( diff --git a/plugins/lighthouse/src/hooks/useWebsiteForEntity.test.tsx b/plugins/lighthouse/src/hooks/useWebsiteForEntity.test.tsx index 868e26f04c..580162f182 100644 --- a/plugins/lighthouse/src/hooks/useWebsiteForEntity.test.tsx +++ b/plugins/lighthouse/src/hooks/useWebsiteForEntity.test.tsx @@ -75,7 +75,7 @@ describe('useWebsiteForEntity', () => { (mockLighthouseApi.getWebsiteByUrl as jest.Mock).mockResolvedValue(website); }); - it('returns the lighthouse information for the website url in annotations ', async () => { + it('returns the lighthouse information for the website url in annotations', async () => { const { result, waitForNextUpdate } = subject(); await waitForNextUpdate(); expect(result.current?.value).toBe(website); diff --git a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx index 10c3d9fcd1..875b1f8660 100644 --- a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx +++ b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx @@ -30,7 +30,7 @@ describe('RegisterComponentForm', () => { expect(screen.getByText('Submit').closest('button')).toBeDisabled(); }); - it('should enable a submit button when the target url is set ', async () => { + it('should enable a submit button when the target url is set', async () => { render(); await act(async () => { diff --git a/yarn.lock b/yarn.lock index 9a5bd93ffb..168da27a6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3802,10 +3802,10 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@spotify/eslint-config-base@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@spotify/eslint-config-base/-/eslint-config-base-7.0.0.tgz#36804ae09ec938f1aa5f9464ea993f3f151cfaa8" - integrity sha512-XRTrTRyRRYBxPYINKNHw4B8QWlA3p4I+id/Po2sMdejtXH3LZgDgIjdschUZSdQ6J6dVYDdaVykdizRM9I+G6Q== +"@spotify/eslint-config-base@^8.0.0": + version "8.0.0" + resolved "https://registry.npmjs.org/@spotify/eslint-config-base/-/eslint-config-base-8.0.0.tgz#c3d10d8a05ad9129d579952cb80063e4c4fa216e" + integrity sha512-07Fo+KoTMl4AaZJPD6deFnYmG634wzgeqfFcWdFlIrOLAasvWSH8Lp6ZE9cJBKuffOAy8kuEE1SwFo34+5X8bQ== "@spotify/eslint-config-oss@^1.0.1": version "1.0.2" @@ -3814,51 +3814,21 @@ dependencies: eslint-plugin-notice "^0.9.10" -"@spotify/eslint-config-react@^7.0.1": - version "7.0.1" - resolved "https://registry.npmjs.org/@spotify/eslint-config-react/-/eslint-config-react-7.0.1.tgz#2e70de9d7911ea9aeaa55a83a332220a28f6c431" - integrity sha512-HNDHvm19EaBXiDgsg52mjMgKWZTeA0hO2Q75ACNwb8UtjIKkANqyyuzyDGo8jiGMbWIm6wJjShlRtT95emNlqQ== +"@spotify/eslint-config-react@^8.0.0": + version "8.0.2" + resolved "https://registry.npmjs.org/@spotify/eslint-config-react/-/eslint-config-react-8.0.2.tgz#91af3c58b22d49a96795336ed66927e722cda430" + integrity sha512-V0gzO/jWmjhODLINAfeunHj+OBdj/cQeVDw+92ZUlkonAg6BZLjX6Vy8jeIUFjfgmVOtI9eKbcApxaurj1jN6g== -"@spotify/eslint-config-typescript@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@spotify/eslint-config-typescript/-/eslint-config-typescript-7.0.0.tgz#fc5227e3344f74b41ac3a530df24a95ac13254e4" - integrity sha512-28I/SAf68NKbWZ5IY0WYMa0D18PxWdC9DP9gRbOTlZufmsS8jEgqf3zBUWmP6XOf1nihpKWcqvbFUG5H7/JYXA== - -"@spotify/eslint-config@^7.0.0", "@spotify/eslint-config@^7.0.1": - version "7.0.1" - resolved "https://registry.npmjs.org/@spotify/eslint-config/-/eslint-config-7.0.1.tgz#07a21cfd7fce89cfc2c6dd5ea5d747e741201b66" - integrity sha512-8GI/TZGUhS4pr7oipT2MjrZFRgXcKzk9YImEusUdD2f5vlCniRFIBQNrvTMkyjfdQqvIVqJPLcdVPXeAgprsMw== - dependencies: - "@spotify/eslint-config-base" "^7.0.0" - "@spotify/eslint-config-react" "^7.0.1" - "@spotify/eslint-config-typescript" "^7.0.0" - "@spotify/web-scripts-utils" "^7.0.0" - "@typescript-eslint/eslint-plugin" "^2.14.0" - "@typescript-eslint/parser" "^2.14.0" - eslint-config-prettier "^6.0.0" - eslint-plugin-jest "^23.6.0" - eslint-plugin-jsx-a11y "^6.2.1" - eslint-plugin-react "^7.12.4" - eslint-plugin-react-hooks "^4.0.0" - -"@spotify/prettier-config@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-7.0.0.tgz#47750979d1282197295108b6958360660a955c16" - integrity sha512-lIMcx/2oDqTtW84iHKkRJe+8U6HK6GPwWH5sJp9UEHcDpdXomOQYvwcGXy2I2zwPQQ14gYYE6nEJuSnnYqsYRw== +"@spotify/eslint-config-typescript@^8.0.0": + version "8.1.1" + resolved "https://registry.npmjs.org/@spotify/eslint-config-typescript/-/eslint-config-typescript-8.1.1.tgz#a8c6f74ef9445fe740008ae3a26003a2eef78f93" + integrity sha512-0NkE6ZiSRf9IBWwCxzn/l7UdwcL/KZsbzjjOSweafyYueajEn+eIpKjOgDGUL/ebxSrOtjEp+YeFc4lSPkP0qg== "@spotify/prettier-config@^8.0.0": version "8.0.0" resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-8.0.0.tgz#8b6c2bd579ddc54887155a0721fe04e96c89f7f2" integrity sha512-so8w32ZV42CHWxOEXcBtbNO/hLXFrQNXVmhfzhUI6dVB9cq2xjRaiqu8GjFj8LvKbWpPj+S+KwTIS4aDVWqrFQ== -"@spotify/web-scripts-utils@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@spotify/web-scripts-utils/-/web-scripts-utils-7.0.0.tgz#8c6b8039fc645a36ac48629eb9ba06600f4d828a" - integrity sha512-McMy0j60lxOHjgDjegthZqEWN/PabphiM30A/mI/Y7xh9+JFnYWBTSm/wgn6EW2BsPpV631xSYYkk6B1Ph14Fw== - dependencies: - glob "^7.1.4" - read-pkg-up "^7.0.1" - "@storybook/addon-actions@^6.0.21": version "6.0.21" resolved "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.0.21.tgz#0de1d109d4b1eb99f644bbe84e74c25cfd2b1b6b" @@ -5830,49 +5800,113 @@ resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^2.14.0": - version "2.24.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.24.0.tgz#a86cf618c965a462cddf3601f594544b134d6d68" - integrity sha512-wJRBeaMeT7RLQ27UQkDFOu25MqFOBus8PtOa9KaT5ZuxC1kAsd7JEHqWt4YXuY9eancX0GK9C68i5OROnlIzBA== +"@typescript-eslint/eslint-plugin@^v3.10.1": + version "3.10.1" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz#7e061338a1383f59edc204c605899f93dc2e2c8f" + integrity sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ== dependencies: - "@typescript-eslint/experimental-utils" "2.24.0" - eslint-utils "^1.4.3" + "@typescript-eslint/experimental-utils" "3.10.1" + debug "^4.1.1" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" + semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.24.0", "@typescript-eslint/experimental-utils@^2.5.0": - version "2.24.0" - resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.24.0.tgz#a5cb2ed89fedf8b59638dc83484eb0c8c35e1143" - integrity sha512-DXrwuXTdVh3ycNCMYmWhUzn/gfqu9N0VzNnahjiDJvcyhfBy4gb59ncVZVxdp5XzBC77dCncu0daQgOkbvPwBw== +"@typescript-eslint/experimental-utils@3.10.1": + version "3.10.1" + resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686" + integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.24.0" + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/typescript-estree" "3.10.1" eslint-scope "^5.0.0" + eslint-utils "^2.0.0" -"@typescript-eslint/parser@^2.14.0": - version "2.24.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.24.0.tgz#2cf0eae6e6dd44d162486ad949c126b887f11eb8" - integrity sha512-H2Y7uacwSSg8IbVxdYExSI3T7uM1DzmOn2COGtCahCC3g8YtM1xYAPi2MAHyfPs61VKxP/J/UiSctcRgw4G8aw== +"@typescript-eslint/experimental-utils@^4.0.1": + version "4.4.1" + resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.4.1.tgz#40613b9757fa0170de3e0043254dbb077cafac0c" + integrity sha512-Nt4EVlb1mqExW9cWhpV6pd1a3DkUbX9DeyYsdoeziKOpIJ04S2KMVDO+SEidsXRH/XHDpbzXykKcMTLdTXH6cQ== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/scope-manager" "4.4.1" + "@typescript-eslint/types" "4.4.1" + "@typescript-eslint/typescript-estree" "4.4.1" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@^v3.10.1": + version "3.10.1" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467" + integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.24.0" - "@typescript-eslint/typescript-estree" "2.24.0" + "@typescript-eslint/experimental-utils" "3.10.1" + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/typescript-estree" "3.10.1" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@2.24.0": - version "2.24.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.24.0.tgz#38bbc8bb479790d2f324797ffbcdb346d897c62a" - integrity sha512-RJ0yMe5owMSix55qX7Mi9V6z2FDuuDpN6eR5fzRJrp+8in9UF41IGNQHbg5aMK4/PjVaEQksLvz0IA8n+Mr/FA== +"@typescript-eslint/scope-manager@4.4.1": + version "4.4.1" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.4.1.tgz#d19447e60db2ce9c425898d62fa03b2cce8ea3f9" + integrity sha512-2oD/ZqD4Gj41UdFeWZxegH3cVEEH/Z6Bhr/XvwTtGv66737XkR4C9IqEkebCuqArqBJQSj4AgNHHiN1okzD/wQ== dependencies: + "@typescript-eslint/types" "4.4.1" + "@typescript-eslint/visitor-keys" "4.4.1" + +"@typescript-eslint/types@3.10.1": + version "3.10.1" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" + integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== + +"@typescript-eslint/types@4.4.1": + version "4.4.1" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.4.1.tgz#c507b35cf523bc7ba00aae5f75ee9b810cdabbc1" + integrity sha512-KNDfH2bCyax5db+KKIZT4rfA8rEk5N0EJ8P0T5AJjo5xrV26UAzaiqoJCxeaibqc0c/IvZxp7v2g3difn2Pn3w== + +"@typescript-eslint/typescript-estree@3.10.1": + version "3.10.1" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853" + integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w== + dependencies: + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/visitor-keys" "3.10.1" debug "^4.1.1" - eslint-visitor-keys "^1.1.0" glob "^7.1.6" is-glob "^4.0.1" lodash "^4.17.15" - semver "^6.3.0" + semver "^7.3.2" tsutils "^3.17.1" +"@typescript-eslint/typescript-estree@4.4.1": + version "4.4.1" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.4.1.tgz#598f6de488106c2587d47ca2462c60f6e2797cb8" + integrity sha512-wP/V7ScKzgSdtcY1a0pZYBoCxrCstLrgRQ2O9MmCUZDtmgxCO/TCqOTGRVwpP4/2hVfqMz/Vw1ZYrG8cVxvN3g== + dependencies: + "@typescript-eslint/types" "4.4.1" + "@typescript-eslint/visitor-keys" "4.4.1" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/visitor-keys@3.10.1": + version "3.10.1" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931" + integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ== + dependencies: + eslint-visitor-keys "^1.1.0" + +"@typescript-eslint/visitor-keys@4.4.1": + version "4.4.1" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.4.1.tgz#1769dc7a9e2d7d2cfd3318b77ed8249187aed5c3" + integrity sha512-H2JMWhLaJNeaylSnMSQFEhT/S/FsJbebQALmoJxMPMxLtlVAMy2uJP/Z543n9IizhjRayLSqoInehCeNW9rWcw== + dependencies: + "@typescript-eslint/types" "4.4.1" + eslint-visitor-keys "^2.0.0" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -10677,7 +10711,7 @@ eslint-plugin-graphql@^4.0.0: lodash.flatten "^4.4.0" lodash.without "^4.4.0" -eslint-plugin-import@^2.20.2, eslint-plugin-import@^2.22.0: +eslint-plugin-import@^2.20.2: version "2.22.0" resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg== @@ -10696,12 +10730,12 @@ eslint-plugin-import@^2.20.2, eslint-plugin-import@^2.22.0: resolve "^1.17.0" tsconfig-paths "^3.9.0" -eslint-plugin-jest@^23.6.0: - version "23.8.2" - resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.8.2.tgz#6f28b41c67ef635f803ebd9e168f6b73858eb8d4" - integrity sha512-xwbnvOsotSV27MtAe7s8uGWOori0nUsrXh2f1EnpmXua8sDfY6VZhHAhHg2sqK7HBNycRQExF074XSZ7DvfoFg== +eslint-plugin-jest@^24.1.0: + version "24.1.0" + resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.1.0.tgz#6708037d7602e5288ce877fd0103f329dc978361" + integrity sha512-827YJ+E8B9PvXu/0eiVSNFfxxndbKv+qE/3GSMhdorCaeaOehtqHGX2YDW9B85TEOre9n/zscledkFW/KbnyGg== dependencies: - "@typescript-eslint/experimental-utils" "^2.5.0" + "@typescript-eslint/experimental-utils" "^4.0.1" eslint-plugin-jsx-a11y@^6.2.1: version "6.2.3" @@ -10779,13 +10813,6 @@ eslint-scope@^5.0.0, eslint-scope@^5.1.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^1.4.3: - version "1.4.3" - resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" - integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== - dependencies: - eslint-visitor-keys "^1.1.0" - eslint-utils@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" @@ -10798,6 +10825,11 @@ eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.2.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa" integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ== +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + eslint@^7.1.0: version "7.4.0" resolved "https://registry.npmjs.org/eslint/-/eslint-7.4.0.tgz#4e35a2697e6c1972f9d6ef2b690ad319f80f206f" @@ -12189,7 +12221,7 @@ globalthis@^1.0.0: dependencies: define-properties "^1.1.3" -globby@11.0.1, globby@^11.0.0: +globby@11.0.1, globby@^11.0.0, globby@^11.0.1: version "11.0.1" resolved "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== From e4d0aa4609a9bd3ce62044a28efec9707499f840 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 17 Oct 2020 15:11:28 +0200 Subject: [PATCH 073/104] docgen: fix duplicate help text --- packages/docgen/src/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/docgen/src/index.ts b/packages/docgen/src/index.ts index f41629e7fd..4b69fdbe01 100644 --- a/packages/docgen/src/index.ts +++ b/packages/docgen/src/index.ts @@ -49,10 +49,6 @@ const main = (argv: string[]) => { process.exit(1); }); - if (!process.argv.slice(2).length) { - program.outputHelp(chalk.yellow); - } - program.parse(argv); }; From 547cd78456053837c173f654aad222a41a6168a1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 13 Oct 2020 11:47:50 +0200 Subject: [PATCH 074/104] contrib/kubernetes: added plain single pod deployment example --- .github/styles/vocab.txt | 1 + .../plain_single_backend_deplyoment/README.md | 42 +++++++ .../deployment.yaml | 108 ++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 contrib/kubernetes/plain_single_backend_deplyoment/README.md create mode 100644 contrib/kubernetes/plain_single_backend_deplyoment/deployment.yaml diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 6a2eecdee0..40346fc0e8 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -105,6 +105,7 @@ Monorepo monorepos msw namespace +namespaces Namespaces neuro newrelic diff --git a/contrib/kubernetes/plain_single_backend_deplyoment/README.md b/contrib/kubernetes/plain_single_backend_deplyoment/README.md new file mode 100644 index 0000000000..685bed92a5 --- /dev/null +++ b/contrib/kubernetes/plain_single_backend_deplyoment/README.md @@ -0,0 +1,42 @@ +# Plain Kubernetes Deployment + +This directory contains an example of a simple Kubernetes deployment of Backstage. It is not intended to serve as a complete production deployment, but as a starting point for setting one up. + +## Usage + +You can try the deployment out as is. The easiest way is to use [Docker Desktop](https://www.docker.com/products/docker-desktop) with [Kubernetes](https://docs.docker.com/get-started/kube-deploy/). + +From a fresh clone of this repo, run the following in the root: + +```bash +yarn install + +yarn docker-build + +kubectl apply -f contrib/kubernetes/plain_single_backend_deplyoment/deployment.yaml +``` + +You can use the following commands to monitor the deployment: + +```bash +# List all resources in the backstage namespace +kubectl -n backstage get all + +# Inspect the status of the deployment resource +kubectl -n backstage describe deployment backstage-backend + +# Inspect the status of the pod running the backstage backend +kubectl -n backstage describe pod -l app=backstage,component=backend +``` + +Once the deployment is up and running, you can use the following to set up a proxy to reach the backend locally: + +```bash +kubectl proxy +``` + +With the proxy up and running, you should be able to navigate to [http://localhost:8001/api/v1/namespaces/backstage/services/backstage-backend:http/proxy](http://localhost:8001/api/v1/namespaces/backstage/services/backstage-backend:http/proxy) and see Backstage. Note that you'll end up on a 404 page, but hitting the home icon in the sidebar should take you to the catalog page where you can see a few example services. + +## Caveats + +This deployment is for demonstration purposes only, for a production deployment you will need to set up at least a persistent database and some form of ingress. If your organization doesn't already have established patterns for these, you could look at options of managed PostgreSQL instances from cloud providers, or something like Zalando's [postgres-operator](https://github.com/zalando/postgres-operator). For ingress there are also [plenty of options](https://ramitsurana.gitbook.io/awesome-kubernetes/docs/projects/projects#load-balancing), where `nginx` is a popular choice to get started. diff --git a/contrib/kubernetes/plain_single_backend_deplyoment/deployment.yaml b/contrib/kubernetes/plain_single_backend_deplyoment/deployment.yaml new file mode 100644 index 0000000000..87dee94895 --- /dev/null +++ b/contrib/kubernetes/plain_single_backend_deplyoment/deployment.yaml @@ -0,0 +1,108 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: backstage +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backstage-backend + namespace: backstage +spec: + replicas: 1 + selector: + matchLabels: + app: backstage + component: backend + template: + metadata: + labels: + app: backstage + component: backend + spec: + containers: + - name: backend + # This image is built with `yarn docker-build` in the repo root. + # Replace this with your own image to deploy your own Backstage app. + image: example-backend:latest + imagePullPolicy: Never + + env: + # We set this to development to make the backend start with incomplete configuration. In a production + # deployment you will want to make sure that you have a full configuration, and remove any plugins that + # you are not using. + - name: NODE_ENV + value: development + + # This makes us load in `app-config.production.yaml` if there is one. + - name: APP_ENV + value: production + + # This makes it possible for the app to reach the backend when serving through `kubectl proxy` + # If you expose the service using for example an ingress controller, you should + # switch this out or remove it. + # + # Note that we're not setting app.baseUrl here, as setting the base path is not working at the moment. + # Further work is needed around the routing in the frontend or react-router before we can support that. + - name: APP_CONFIG_backend_baseUrl + value: http://localhost:8001/api/v1/namespaces/backstage/services/backstage-backend:http/proxy + + ports: + - name: http + containerPort: 7000 + + volumeMounts: + - name: config-volume + mountPath: /usr/src/app/app-config.local.yaml + subPath: app-config.local.yaml + + resources: + limits: + cpu: 1 + memory: 0.5Gi + + readinessProbe: + httpGet: + port: 7000 + path: /healthcheck + livenessProbe: + httpGet: + port: 7000 + path: /healthcheck + + volumes: + - name: config-volume + configMap: + name: backstage-config + items: + - key: app-config + path: app-config.local.yaml +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: backstage-config + namespace: backstage +data: + # Note that the config here is only applied to the backend. The frontend config is applied at build time. + # To override frontend config in this deployment, use `APP_CONFIG_` env vars. + app-config: | + app: + baseUrl: http://localhost:8001/api/v1/namespaces/backstage/services/backstage-backend:http/proxy + backend: + baseUrl: http://localhost:8001/api/v1/namespaces/backstage/services/backstage-backend:http/proxy +--- +apiVersion: v1 +kind: Service +metadata: + name: backstage-backend + namespace: backstage +spec: + selector: + app: backstage + component: backend + ports: + - name: http + port: 80 + targetPort: http From 2d15cfb1a4837a9efbc244b0c90ef275bc4254af Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 17 Oct 2020 12:55:35 +0200 Subject: [PATCH 075/104] docs/getting-started: added some minimal docs for k8s deployment, referencing contrib example --- docs/getting-started/deployment-k8s.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/getting-started/deployment-k8s.md b/docs/getting-started/deployment-k8s.md index ddcbd290c6..69aa59a600 100644 --- a/docs/getting-started/deployment-k8s.md +++ b/docs/getting-started/deployment-k8s.md @@ -4,4 +4,10 @@ title: Kubernetes description: Documentation on Kubernetes and K8s Deployment --- -Coming soon! +Backstage itself provides tooling up to the point of building docker images. +Beyond that point we do not have an opinionated way to deploy Backstage within +Kubernetes, as each cluster has its own unique set of tooling and patterns. + +We do provide examples to help you get started though. Check out +[this example](https://github.com/spotify/backstage/tree/master/contrib/kubernetes/plain_single_backend_deplyoment/) +for a basic single-deployment setup. From bbf5faec6fd7663b61bead43a24436c52649468d Mon Sep 17 00:00:00 2001 From: amitkhurana92 <41064453+amitkhurana92@users.noreply.github.com> Date: Sat, 17 Oct 2020 19:22:20 +0530 Subject: [PATCH 076/104] Jenkins Plugin: Readme.md file updated (#2943) * Readme.md file updated The documentation was not concise and does not include the case if I don't want to use env variables. * Prettier invoked. Executed prettier command. Co-authored-by: Amit Khuraba --- plugins/jenkins/README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/plugins/jenkins/README.md b/plugins/jenkins/README.md index 565906ca85..1d3e7f60e5 100644 --- a/plugins/jenkins/README.md +++ b/plugins/jenkins/README.md @@ -33,7 +33,7 @@ proxy: $env: JENKINS_BASIC_AUTH_HEADER ``` -4. Add an environment variable which contains the Jenkins credentials, (note: use an API token not your password) +4. Add an environment variable which contains the Jenkins credentials, (note: use an API token not your password). Here user is the name of the user created in Jenkins. ```shell HEADER=$(echo -n user:api-token | base64) @@ -61,6 +61,21 @@ spec: 8. Click the component in the catalog you should now see Jenkins builds, and a last build result for your master build. +Note: + +If you are not using environment variable then you can directly type API token in app-config.yaml + +```yaml +proxy: + '/jenkins/api': + target: 'http://localhost:8080' # your Jenkins URL + changeOrigin: true + headers: + Authorization: Basic YWRtaW46MTFlYzI1NmU0Mzg1MDFjM2Y1Yzc2Yjc1MWE3ZTQ3YWY4Mw== +``` + +YWRtaW46MTFlYzI1NmU0Mzg1MDFjM2Y1Yzc2Yjc1MWE3ZTQ3YWY4Mw== is the base64 of user and it's API token e.g. admin:11ec256e438501c3f5c76b751a7e47af83 + ## Features - View all runs inside a folder From 406015b0d8b9801a9e6882d8f524cc6963bd941c Mon Sep 17 00:00:00 2001 From: Braxton Christensen Date: Sat, 17 Oct 2020 11:38:20 -0600 Subject: [PATCH 077/104] Fix/1577/item card color contrast (#2921) * fix(layout): adjust ItemCard header colors - adjust header colors to pass color contrast standards * chore(changeset): add changeset -add change set --- .changeset/heavy-berries-hear.md | 5 +++++ packages/core/src/layout/ItemCard/ItemCard.tsx | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .changeset/heavy-berries-hear.md diff --git a/.changeset/heavy-berries-hear.md b/.changeset/heavy-berries-hear.md new file mode 100644 index 0000000000..66aa6d93e1 --- /dev/null +++ b/.changeset/heavy-berries-hear.md @@ -0,0 +1,5 @@ +--- +'@backstage/core': patch +--- + +Update ItemCard headers to pass color contrast standards. diff --git a/packages/core/src/layout/ItemCard/ItemCard.tsx b/packages/core/src/layout/ItemCard/ItemCard.tsx index db73939fe7..2d425026a5 100644 --- a/packages/core/src/layout/ItemCard/ItemCard.tsx +++ b/packages/core/src/layout/ItemCard/ItemCard.tsx @@ -20,8 +20,7 @@ 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%)', + backgroundImage: 'linear-gradient(-137deg, #4BB8A5 0%, #187656 100%)', }, content: { padding: theme.spacing(2), From ebca83d4804d930f99e50194273601986184086c Mon Sep 17 00:00:00 2001 From: Abhishek Jakhar Date: Sat, 17 Oct 2020 21:56:42 +0530 Subject: [PATCH 078/104] add test cases for Status components --- .changeset/olive-bikes-jog.md | 5 ++ .../src/components/Status/Status.test.jsx | 69 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 .changeset/olive-bikes-jog.md create mode 100644 packages/core/src/components/Status/Status.test.jsx diff --git a/.changeset/olive-bikes-jog.md b/.changeset/olive-bikes-jog.md new file mode 100644 index 0000000000..fdbf1e0d9f --- /dev/null +++ b/.changeset/olive-bikes-jog.md @@ -0,0 +1,5 @@ +--- +'@backstage/core': patch +--- + +add test cases for Status components diff --git a/packages/core/src/components/Status/Status.test.jsx b/packages/core/src/components/Status/Status.test.jsx new file mode 100644 index 0000000000..9a8daecd33 --- /dev/null +++ b/packages/core/src/components/Status/Status.test.jsx @@ -0,0 +1,69 @@ +/* + * 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 { renderInTestApp } from '@backstage/test-utils'; + +import { + StatusOK, + StatusWarning, + StatusError, + StatusPending, + StatusRunning, + StatusAborted, +} from './Status'; + +describe('', () => { + it('renders without exploding', async () => { + const { getByLabelText } = await renderInTestApp(); + expect(getByLabelText('Status ok')).toBeInTheDocument(); + }); +}); + +describe('', () => { + it('renders without exploding', async () => { + const { getByLabelText } = await renderInTestApp(); + expect(getByLabelText('Status warning')).toBeInTheDocument(); + }); +}); + +describe('', () => { + it('renders without exploding', async () => { + const { getByLabelText } = await renderInTestApp(); + expect(getByLabelText('Status error')).toBeInTheDocument(); + }); +}); + +describe('', () => { + it('renders without exploding', async () => { + const { getByLabelText } = await renderInTestApp(); + expect(getByLabelText('Status pending')).toBeInTheDocument(); + }); +}); + +describe('', () => { + it('renders without exploding', async () => { + const { getByLabelText } = await renderInTestApp(); + expect(getByLabelText('Status running')).toBeInTheDocument(); + }); +}); + +describe('', () => { + it('renders without exploding', async () => { + const { getByLabelText } = await renderInTestApp(); + expect(getByLabelText('Status aborted')).toBeInTheDocument(); + }); +}); From c6ff101a99194be011225283c07088136543a25c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Sat, 17 Oct 2020 20:38:40 +0200 Subject: [PATCH 079/104] UX improvements to Settings --- .../components/AuthProviders/AuthProviders.tsx | 2 +- .../AuthProviders/ProviderSettingsItem.tsx | 16 +++++++--------- .../src/components/FeatureFlags/FeatureFlags.tsx | 2 +- .../components/FeatureFlags/FeatureFlagsItem.tsx | 15 ++++++--------- .../src/components/General/General.tsx | 8 ++++---- 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/plugins/user-settings/src/components/AuthProviders/AuthProviders.tsx b/plugins/user-settings/src/components/AuthProviders/AuthProviders.tsx index 563bf44150..bc2a68578d 100644 --- a/plugins/user-settings/src/components/AuthProviders/AuthProviders.tsx +++ b/plugins/user-settings/src/components/AuthProviders/AuthProviders.tsx @@ -37,7 +37,7 @@ export const AuthProviders = ({ providerSettings }: Props) => { } return ( - + {providers} ); diff --git a/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx b/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx index 7213ee2b83..4bae3dbfbb 100644 --- a/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx +++ b/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx @@ -22,14 +22,13 @@ import { SessionState, } from '@backstage/core'; import { + Button, ListItem, ListItemIcon, ListItemSecondaryAction, ListItemText, Tooltip, } from '@material-ui/core'; -import PowerButton from '@material-ui/icons/PowerSettingsNew'; -import { ToggleButton } from '@material-ui/lab'; type Props = { title: string; @@ -84,14 +83,13 @@ export const ProviderSettingsItem = ({ arrow title={signedIn ? `Sign out from ${title}` : `Sign in to ${title}`} > - (signedIn ? api.signOut() : api.signIn())} +
diff --git a/plugins/user-settings/src/components/FeatureFlags/FeatureFlags.tsx b/plugins/user-settings/src/components/FeatureFlags/FeatureFlags.tsx index 71f59c52b8..e0f4d6f40e 100644 --- a/plugins/user-settings/src/components/FeatureFlags/FeatureFlags.tsx +++ b/plugins/user-settings/src/components/FeatureFlags/FeatureFlags.tsx @@ -62,7 +62,7 @@ export const FeatureFlags = () => { } return ( - + {featureFlags.map(featureFlag => { const enabled = Boolean(state[featureFlag.name]); diff --git a/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx b/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx index 92065f2ed6..460a15f56b 100644 --- a/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx +++ b/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx @@ -19,10 +19,9 @@ import { ListItem, ListItemSecondaryAction, ListItemText, + Switch, Tooltip, } from '@material-ui/core'; -import CheckIcon from '@material-ui/icons/CheckCircle'; -import { ToggleButton } from '@material-ui/lab'; import { FeatureFlagsRegistryItem } from '@backstage/core'; type Props = { @@ -39,14 +38,12 @@ export const FlagItem = ({ flag, enabled, toggleHandler }: Props) => ( /> - toggleHandler(flag.name)} - > - - + name={flag.name} + /> diff --git a/plugins/user-settings/src/components/General/General.tsx b/plugins/user-settings/src/components/General/General.tsx index 00ac17dfe1..424f352502 100644 --- a/plugins/user-settings/src/components/General/General.tsx +++ b/plugins/user-settings/src/components/General/General.tsx @@ -21,12 +21,12 @@ import { Profile } from './Profile'; import { ThemeToggle } from './ThemeToggle'; export const General = () => ( - - + + - - + + From 3af02a945700c908515ecdb8df703d4bb9de5506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Sat, 17 Oct 2020 20:42:18 +0200 Subject: [PATCH 080/104] Trendlines noPadding in tables (#2945) --- packages/core/src/components/TrendLine/TrendLine.stories.tsx | 2 +- plugins/lighthouse/src/components/AuditList/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/TrendLine/TrendLine.stories.tsx b/packages/core/src/components/TrendLine/TrendLine.stories.tsx index 4eb74b2f17..5d2972095a 100644 --- a/packages/core/src/components/TrendLine/TrendLine.stories.tsx +++ b/packages/core/src/components/TrendLine/TrendLine.stories.tsx @@ -67,7 +67,7 @@ const columns = [ export const Default = () => (
- + = () => { - {content} + {content} From 4df45e0083d6c94b7a8586ee22574630e266e0ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Sat, 17 Oct 2020 20:54:47 +0200 Subject: [PATCH 081/104] (docs) Add Helm charts, cleanup (#2944) * (docs) Add Helm charts, cleanup * (docs) Add Helm charts, cleanup --- .github/styles/vocab.txt | 1 + DEPLOYMENT.md | 27 ------------- docs/FAQ.md | 10 +---- docs/getting-started/deployment-helm.md | 15 +++----- docs/getting-started/deployment-other.md | 30 +++++++++++++++ docs/getting-started/installation.md | 7 ---- docs/overview/roadmap.md | 49 +++++++++++++----------- microsite/sidebars.json | 1 + mkdocs.yml | 2 +- 9 files changed, 67 insertions(+), 75 deletions(-) delete mode 100644 DEPLOYMENT.md delete mode 100644 docs/getting-started/installation.md diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 40346fc0e8..0cbd0ffa05 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -68,6 +68,7 @@ graphviz Hackathons haproxy heroku +Heroku horizontalpodautoscalers Hostname http diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md deleted file mode 100644 index 1aebbeb0eb..0000000000 --- a/DEPLOYMENT.md +++ /dev/null @@ -1,27 +0,0 @@ -# Deploying Backstage - -## Heroku - -Deploying to heroku is relatively easy following these steps. - -First, make sure you have the [heroku CLI installed](https://devcenter.heroku.com/articles/heroku-cli) and log into it as well as login into Heroku's [container registry](https://devcenter.heroku.com/articles/container-registry-and-runtime). - -```bash -$ heroku login -$ heroku container:login -``` - -You _might_ also need to set your Heroku app's stack to `container` - -```bash -$ heroku stack:set container -a -``` - -We can now build/push the Docker image to Heroku's container registry and release it to the `web` worker. - -```bash -$ heroku container:push web -a -$ heroku container:release web -a -``` - -With that, you should have Backstage up and running! diff --git a/docs/FAQ.md b/docs/FAQ.md index 355cf97880..7163d955c7 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -178,8 +178,7 @@ Spotify will maintain the open source core, but we envision different parts of the project being maintained by various companies and contributors. We also envision a large, diverse ecosystem of open source plugins, which would be maintained by their original authors/contributors or by the community. When it -comes to -[deployment](https://github.com/spotify/backstage/blob/master/DEPLOYMENT.md), +comes to [deployment](https://backstage.io/docs/getting-started/deployment-k8s), the system integrator (typically, the infrastructure team in your organization) maintains Backstage in your own environment. @@ -187,7 +186,7 @@ maintains Backstage in your own environment. No, this is not a service offering. We build the piece of software, and someone in your infrastructure team is responsible for -[deploying](https://github.com/spotify/backstage/blob/master/DEPLOYMENT.md) and +[deploying](https://backstage.io/docs/getting-started/deployment-k8s) and maintaining it. ### How secure is Backstage? @@ -230,8 +229,3 @@ write an open source plugin for Backstage, like this See all the ways you can [contribute here](https://github.com/spotify/backstage/blob/master/CONTRIBUTING.md). We'd love to have you as part of the community. - -### Can I join the Backstage team? - -If you're interested in being part of the Backstage team, reach out to -[fossopportunities@spotify.com](mailto:fossopportunities@spotify.com) diff --git a/docs/getting-started/deployment-helm.md b/docs/getting-started/deployment-helm.md index bc239d9306..e674d28742 100644 --- a/docs/getting-started/deployment-helm.md +++ b/docs/getting-started/deployment-helm.md @@ -2,17 +2,18 @@ id: deployment-helm title: Helm description: Documentation on Kubernetes and Helm Deployment +sidebar_label: Kubernetes and Helm --- # Helm charts -An example Backstage app can be deployed in Kubernetes using the [Backstage Helm -charts][backstage-helm-charts] +An example Backstage app can be deployed in Kubernetes using the +[Backstage Helm charts](https://github.com/spotify/backstage/tree/master/contrib/chart/backstage) First, choose a DNS name where backstage will be hosted create a yaml file for your custom configuration. -``` +```yaml appConfig: app: baseUrl: https://backstage.mydomain.com @@ -26,7 +27,6 @@ appConfig: techdocs: storageUrl: https://backstage.mydomain.com/api/techdocs/static/docs requestUrl: https://backstage.mydomain.com/api/techdocs - ``` Then use it to run: @@ -61,9 +61,4 @@ backstage-ingress * 123.1.2.3 80 17m > **NOTE**: this is not a production ready deployment. For more information on how to customize the deployment check the -[README][charts-readme]. - -[backstage-helm-charts]: - (https://github.com/spotify/backstage/tree/master/contrib/chart/backstage) -[charts-readme]: - (https://github.com/spotify/backstage/tree/master/contrib/chart/backstage/README.md) +[README](https://github.com/spotify/backstage/tree/master/contrib/chart/backstage/README.md). diff --git a/docs/getting-started/deployment-other.md b/docs/getting-started/deployment-other.md index 509bbdc978..3e06e17c77 100644 --- a/docs/getting-started/deployment-other.md +++ b/docs/getting-started/deployment-other.md @@ -17,3 +17,33 @@ $ docker run --rm -it -p 7000:7000 -e APP_ENV=production -e NODE_ENV=development ``` Then open http://localhost/ on your browser. + +## Heroku + +Deploying to Heroku is relatively easy following these steps. + +First, make sure you have the +[heroku CLI installed](https://devcenter.heroku.com/articles/heroku-cli) and log +into it as well as login into Heroku's +[container registry](https://devcenter.heroku.com/articles/container-registry-and-runtime). + +```bash +$ heroku login +$ heroku container:login +``` + +You _might_ also need to set your Heroku app's stack to `container` + +```bash +$ heroku stack:set container -a +``` + +We can now build/push the Docker image to Heroku's container registry and +release it to the `web` worker. + +```bash +$ heroku container:push web -a +$ heroku container:release web -a +``` + +With that, you should have Backstage up and running! diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md deleted file mode 100644 index 3b11d2b8e1..0000000000 --- a/docs/getting-started/installation.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -id: installation -title: Installation -description: Documentation on Installation ---- - -Coming soon! diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index 59505aa915..51f09347ce 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -47,40 +47,29 @@ guidelines to get started. ### Ongoing work 🚧 -- **[Plugins for managing micro services end-2-end](https://github.com/spotify/backstage/milestone/14)** - +- **[Plugins for managing micro services end-2-end](https://github.com/spotify/backstage/milestone/14)** Out of the box Backstage will ship with a set of plugins (Overview, CI, API and Docs) that will demonstrate how a user can manage a micro service and follow a change all the way out in production. Completing this work will make it much easier to see how a plugin can be built that integrates with the Backstage Service Catalog. -- **[Kubernetes support](https://github.com/spotify/backstage/milestone/20)** - - Native support for Kubernetes, making it easier for developers to see and - manage their services running in k8s. - -- **[Helm charts](https://github.com/spotify/backstage/issues/2540)** - Provide - Helm charts for easy deployments of Backstage and its subsystems on - Kubernetes. +- **[Users and teams](https://github.com/spotify/backstage/issues/1807)** + Ownership is a central concept in Backstage. It should be easy to import your + existing organizational data, such as users and groups/teams, into Backstage. + A user the logs into Backstage should see software components owned by the + team(s) they are in. - **[Backstage platform is stable](https://github.com/spotify/backstage/milestone/19)** - The platform APIs and features are stable and can be depended on for production use. After this plugins will require little to no maintenance. -- Further improvements to platform documentation +* **[Improved Kubernetes plugin](https://github.com/spotify/backstage/issues/2857)** - + Native support for Kubernetes, making it easier for developers to see and + manage their services running in k8s. -### Plugins - -Building and maintaining [plugins](https://backstage.io/plugins) is the work of -the entire Backstage community. - -A list of plugins that are in development is -[available here](https://github.com/spotify/backstage/issues?q=is%3Aissue+is%3Aopen+label%3Aplugin+sort%3Areactions-%2B1-desc). -We strongly recommend to upvote 👍 plugins you are interested in. This helps us -and the community prioritize what plugins to build. - -Are you missing a plugin for your favorite tool? Please -[suggest a new one](https://github.com/spotify/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME). -Chances are that someone will jump in and help build it. +- Further improvements to platform documentation. Examples include a Golden Path + for plugin development. ### Future work 🔮 @@ -107,8 +96,24 @@ Chances are that someone will jump in and help build it. query. Plugins can easily query this API as well as extend the model where needed. +### Plugins + +Building and maintaining [plugins](https://backstage.io/plugins) is the work of +the entire Backstage community. + +A list of plugins that are in development is +[available here](https://github.com/spotify/backstage/issues?q=is%3Aissue+is%3Aopen+label%3Aplugin+sort%3Areactions-%2B1-desc). +We strongly recommend to upvote 👍 plugins you are interested in. This helps us +and the community prioritize what plugins to build. + +Are you missing a plugin for your favorite tool? Please +[suggest a new one](https://github.com/spotify/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME). +Chances are that someone will jump in and help build it. + ### Completed milestones ✅ +- [Kubernetes plugin - v1](https://github.com/spotify/backstage/tree/master/plugins/kubernetes) +- [Helm charts](https://github.com/spotify/backstage/tree/master/contrib/chart/backstage) - [Backstage Design System 💅](https://backstage.io/blog/2020/09/30/backstage-design-system) - [Cost Insights plugin 💸](https://engineering.atspotify.com/2020/09/29/managing-clouds-from-the-ground-up-cost-engineering-at-spotify/) - [Donate Backstage to the CNCF 🎉](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox) diff --git a/microsite/sidebars.json b/microsite/sidebars.json index ad48fb1e7c..64fdd2f420 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -28,6 +28,7 @@ "label": "Deployment", "ids": [ "getting-started/deployment-k8s", + "getting-started/deployment-helm", "getting-started/deployment-other" ] } diff --git a/mkdocs.yml b/mkdocs.yml index b4606131a7..8a4a78ddf1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,7 +13,6 @@ nav: - Getting started: - Getting Started: 'getting-started/index.md' - Running Backstage locally: 'getting-started/running-backstage-locally.md' - - Installation: 'getting-started/installation.md' - Local development: 'getting-started/development-environment.md' - Demo deployment: https://backstage-demo.roadie.io - Production deployments: @@ -23,6 +22,7 @@ nav: - Customize the look-and-feel of your App: 'getting-started/app-custom-theme.md' - Deployment scenarios: - Kubernetes: 'getting-started/deployment-k8s.md' + - Kubernetes and Helm: 'getting-started/deployment-helm.md' - Other: 'getting-started/deployment-other.md' - Features: - Software Catalog: From 7a2f8b04cd1e2bae5db4071f62cfa6172155f3f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Sat, 17 Oct 2020 21:19:53 +0200 Subject: [PATCH 082/104] Review comments --- .../src/components/General/General.tsx | 4 +-- .../src/components/General/PinButton.tsx | 25 ++++++------------- .../src/components/General/ThemeToggle.tsx | 12 +++++++-- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/plugins/user-settings/src/components/General/General.tsx b/plugins/user-settings/src/components/General/General.tsx index 424f352502..b6d30d0103 100644 --- a/plugins/user-settings/src/components/General/General.tsx +++ b/plugins/user-settings/src/components/General/General.tsx @@ -22,10 +22,10 @@ import { ThemeToggle } from './ThemeToggle'; export const General = () => ( - + - + diff --git a/plugins/user-settings/src/components/General/PinButton.tsx b/plugins/user-settings/src/components/General/PinButton.tsx index e36fc365f8..181574fccf 100644 --- a/plugins/user-settings/src/components/General/PinButton.tsx +++ b/plugins/user-settings/src/components/General/PinButton.tsx @@ -19,18 +19,11 @@ import { ListItem, ListItemSecondaryAction, ListItemText, + Switch, Tooltip, } from '@material-ui/core'; -import LockIcon from '@material-ui/icons/Lock'; -import LockOpenIcon from '@material-ui/icons/LockOpen'; -import { ToggleButton } from '@material-ui/lab'; import { SidebarPinStateContext } from '@backstage/core'; -type PinIconProps = { isPinned: boolean }; - -const PinIcon = ({ isPinned }: PinIconProps) => - isPinned ? : ; - export const PinButton = () => { const { isPinned, toggleSidebarPinState } = useContext( SidebarPinStateContext, @@ -48,16 +41,12 @@ export const PinButton = () => { arrow title={`${isPinned ? 'Unpin' : 'Pin'} Sidebar`} > - { - toggleSidebarPinState(); - }} - > - - + toggleSidebarPinState()} + name="pin" + /> diff --git a/plugins/user-settings/src/components/General/ThemeToggle.tsx b/plugins/user-settings/src/components/General/ThemeToggle.tsx index 111bd838a8..dc24a61f8f 100644 --- a/plugins/user-settings/src/components/General/ThemeToggle.tsx +++ b/plugins/user-settings/src/components/General/ThemeToggle.tsx @@ -102,12 +102,20 @@ export const ThemeToggle = () => { title={`Select ${theme.title}`} value={theme.variant} > - + <> + {theme.variant}  + + ); })} - + + Auto  From 014bc250b379ae94fa263a27f86c544583d027ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gomez?= Date: Sat, 10 Oct 2020 15:12:49 +0200 Subject: [PATCH 083/104] Fix API references parsing Allows API entities to use a different namespace than the default one. --- .../src/components/useComponentApiEntities.ts | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/plugins/api-docs/src/components/useComponentApiEntities.ts b/plugins/api-docs/src/components/useComponentApiEntities.ts index 11b5de988f..9e5cbd968e 100644 --- a/plugins/api-docs/src/components/useComponentApiEntities.ts +++ b/plugins/api-docs/src/components/useComponentApiEntities.ts @@ -16,7 +16,11 @@ import { useAsyncRetry } from 'react-use'; import { errorApiRef, useApi } from '@backstage/core'; -import { ApiEntity, ComponentEntity } from '@backstage/catalog-model'; +import { + ApiEntity, + ComponentEntity, + parseEntityName, +} from '@backstage/catalog-model'; import { catalogApiRef } from '@backstage/plugin-catalog'; import { useComponentApiNames } from './useComponentApiNames'; @@ -43,10 +47,20 @@ export function useComponentApiEntities({ await Promise.all( apiNames.map(async name => { try { - const api = (await catalogApi.getEntityByName({ - kind: 'API', - name, - })) as ApiEntity | undefined; + const apiEntityName = parseEntityName(name, { + defaultNamespace: entity.metadata.namespace, + defaultKind: 'API', + }); + + if (apiEntityName.kind !== 'API') { + throw new Error( + `Referenced entity of kind "${apiEntityName.kind}" as an API`, + ); + } + + const api = (await catalogApi.getEntityByName(apiEntityName)) as + | ApiEntity + | undefined; if (api) { resultMap.set(api.metadata.name, api); From d90a8109c276423c386fde4e35182042d4e8e043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Sun, 18 Oct 2020 07:53:22 +0200 Subject: [PATCH 084/104] Fix test? --- .../user-settings/src/components/General/PinButton.test.tsx | 3 +-- plugins/user-settings/src/components/General/PinButton.tsx | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/user-settings/src/components/General/PinButton.test.tsx b/plugins/user-settings/src/components/General/PinButton.test.tsx index 76af8ce8d4..88f636d918 100644 --- a/plugins/user-settings/src/components/General/PinButton.test.tsx +++ b/plugins/user-settings/src/components/General/PinButton.test.tsx @@ -32,10 +32,9 @@ describe('', () => { , ), ); - expect(rendered.getByText('Pin Sidebar')).toBeInTheDocument(); - const pinButton = rendered.getByTitle('Pin Sidebar'); + const pinButton = rendered.getByTestId('pin'); fireEvent.click(pinButton); expect(mockToggleFn).toHaveBeenCalled(); }); diff --git a/plugins/user-settings/src/components/General/PinButton.tsx b/plugins/user-settings/src/components/General/PinButton.tsx index 181574fccf..bfcdb0c7ff 100644 --- a/plugins/user-settings/src/components/General/PinButton.tsx +++ b/plugins/user-settings/src/components/General/PinButton.tsx @@ -46,6 +46,7 @@ export const PinButton = () => { checked={isPinned} onChange={() => toggleSidebarPinState()} name="pin" + data-test-id="pin" /> From c3113d1d39ddc6dd0b3a44ecad5e56d235232a40 Mon Sep 17 00:00:00 2001 From: Eduardo Apolinario Date: Sat, 17 Oct 2020 23:13:06 -0700 Subject: [PATCH 085/104] Fix Core Features configuration id (#2948) The current configuration id is pointing to an inexistent place. This PR fixes the name of the configuration node in sidebars.json. --- docs/features/software-catalog/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-catalog/configuration.md b/docs/features/software-catalog/configuration.md index 46de2d3d67..dd19d4cd51 100644 --- a/docs/features/software-catalog/configuration.md +++ b/docs/features/software-catalog/configuration.md @@ -1,5 +1,5 @@ --- -id: software-catalog-configuration +id: configuration title: Catalog Configuration description: Documentation on Software Catalog Configuration --- From 763ec1cd2f9c62fd4a7fb62fe0f41c1dfaaac832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Sun, 18 Oct 2020 08:20:06 +0200 Subject: [PATCH 086/104] Another try --- plugins/user-settings/src/components/General/PinButton.test.tsx | 2 +- plugins/user-settings/src/components/General/PinButton.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/user-settings/src/components/General/PinButton.test.tsx b/plugins/user-settings/src/components/General/PinButton.test.tsx index 88f636d918..d85fa3ddd9 100644 --- a/plugins/user-settings/src/components/General/PinButton.test.tsx +++ b/plugins/user-settings/src/components/General/PinButton.test.tsx @@ -35,7 +35,7 @@ describe('', () => { expect(rendered.getByText('Pin Sidebar')).toBeInTheDocument(); const pinButton = rendered.getByTestId('pin'); - fireEvent.click(pinButton); + fireEvent.change(pinButton); expect(mockToggleFn).toHaveBeenCalled(); }); }); diff --git a/plugins/user-settings/src/components/General/PinButton.tsx b/plugins/user-settings/src/components/General/PinButton.tsx index bfcdb0c7ff..a812079133 100644 --- a/plugins/user-settings/src/components/General/PinButton.tsx +++ b/plugins/user-settings/src/components/General/PinButton.tsx @@ -47,6 +47,7 @@ export const PinButton = () => { onChange={() => toggleSidebarPinState()} name="pin" data-test-id="pin" + inputProps={{ 'aria-label': 'Pin Sidebar Switch' }} /> From edeb5e45a304bb4c1fe5a2322bead66ec101aab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matic=20Ples=CC=8Cec?= Date: Sun, 18 Oct 2020 18:19:09 +0200 Subject: [PATCH 087/104] Update docs regarding npm config ignore-scripts flag --- docs/getting-started/create-an-app.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md index 9be0e24452..670c7fbf98 100644 --- a/docs/getting-started/create-an-app.md +++ b/docs/getting-started/create-an-app.md @@ -124,3 +124,21 @@ the root directory: ```bash yarn workspace backend start ``` + +### Troubleshooting + +#### Cannot find module + +You may encounter an error similar to below: + +``` +internal/modules/cjs/loader.js:968 + throw err; + ^ + +Error: Cannot find module '../build/Debug/nodegit.node' +``` + +This can occur if an npm dependency is not completely installed. Because some +dependencies run a post-install script, ensure that both your npm and yarn +configs have the `ignore-scripts` flag set to `false`. From f2819b2a9f1d8f25ff4061eb53b9daefaa384d76 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Oct 2020 01:32:35 +0200 Subject: [PATCH 088/104] core-api: move utility api system implementation into apis/system --- .../core-api/src/apis/definitions/AlertApi.ts | 2 +- .../src/apis/definitions/AppThemeApi.ts | 2 +- .../src/apis/definitions/ConfigApi.ts | 2 +- .../src/apis/definitions/DiscoveryApi.ts | 2 +- .../core-api/src/apis/definitions/ErrorApi.ts | 2 +- .../src/apis/definitions/FeatureFlagsApi.ts | 2 +- .../src/apis/definitions/IdentityApi.ts | 2 +- .../src/apis/definitions/OAuthRequestApi.ts | 2 +- .../src/apis/definitions/StorageApi.ts | 2 +- .../core-api/src/apis/definitions/auth.ts | 4 ++-- packages/core-api/src/apis/index.ts | 6 +---- .../apis/{ => system}/ApiAggregator.test.ts | 0 .../src/apis/{ => system}/ApiAggregator.ts | 3 +-- .../{ => system}/ApiFactoryRegistry.test.ts | 0 .../apis/{ => system}/ApiFactoryRegistry.ts | 2 +- .../apis/{ => system}/ApiProvider.test.tsx | 0 .../src/apis/{ => system}/ApiProvider.tsx | 3 +-- .../src/apis/{ => system}/ApiRef.test.ts | 0 .../core-api/src/apis/{ => system}/ApiRef.ts | 8 ++----- .../src/apis/{ => system}/ApiRegistry.test.ts | 0 .../src/apis/{ => system}/ApiRegistry.ts | 3 +-- .../src/apis/{ => system}/ApiResolver.test.ts | 0 .../src/apis/{ => system}/ApiResolver.ts | 2 +- .../core-api/src/apis/{ => system}/helpers.ts | 3 +-- packages/core-api/src/apis/system/index.ts | 23 +++++++++++++++++++ .../core-api/src/apis/{ => system}/types.ts | 6 ++++- packages/core-api/src/app/App.tsx | 3 +-- packages/core-api/src/plugin/types.ts | 2 +- 28 files changed, 50 insertions(+), 36 deletions(-) rename packages/core-api/src/apis/{ => system}/ApiAggregator.test.ts (100%) rename packages/core-api/src/apis/{ => system}/ApiAggregator.ts (93%) rename packages/core-api/src/apis/{ => system}/ApiFactoryRegistry.test.ts (100%) rename packages/core-api/src/apis/{ => system}/ApiFactoryRegistry.ts (98%) rename packages/core-api/src/apis/{ => system}/ApiProvider.test.tsx (100%) rename packages/core-api/src/apis/{ => system}/ApiProvider.tsx (96%) rename packages/core-api/src/apis/{ => system}/ApiRef.test.ts (100%) rename packages/core-api/src/apis/{ => system}/ApiRef.ts (95%) rename packages/core-api/src/apis/{ => system}/ApiRegistry.test.ts (100%) rename packages/core-api/src/apis/{ => system}/ApiRegistry.ts (96%) rename packages/core-api/src/apis/{ => system}/ApiResolver.test.ts (100%) rename packages/core-api/src/apis/{ => system}/ApiResolver.ts (98%) rename packages/core-api/src/apis/{ => system}/helpers.ts (93%) create mode 100644 packages/core-api/src/apis/system/index.ts rename packages/core-api/src/apis/{ => system}/types.ts (94%) diff --git a/packages/core-api/src/apis/definitions/AlertApi.ts b/packages/core-api/src/apis/definitions/AlertApi.ts index 123cd47651..d149940103 100644 --- a/packages/core-api/src/apis/definitions/AlertApi.ts +++ b/packages/core-api/src/apis/definitions/AlertApi.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createApiRef } from '../ApiRef'; +import { createApiRef } from '../system'; import { Observable } from '../../types'; export type AlertMessage = { diff --git a/packages/core-api/src/apis/definitions/AppThemeApi.ts b/packages/core-api/src/apis/definitions/AppThemeApi.ts index 3f580450f0..019aa2f496 100644 --- a/packages/core-api/src/apis/definitions/AppThemeApi.ts +++ b/packages/core-api/src/apis/definitions/AppThemeApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '../ApiRef'; +import { createApiRef } from '../system'; import { BackstageTheme } from '@backstage/theme'; import { Observable } from '../../types'; import { SvgIconProps } from '@material-ui/core'; diff --git a/packages/core-api/src/apis/definitions/ConfigApi.ts b/packages/core-api/src/apis/definitions/ConfigApi.ts index 1fa6e70d1f..6d34055f05 100644 --- a/packages/core-api/src/apis/definitions/ConfigApi.ts +++ b/packages/core-api/src/apis/definitions/ConfigApi.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createApiRef } from '../ApiRef'; +import { createApiRef } from '../system'; import { Config } from '@backstage/config'; // Using interface to make the ConfigApi name show up in docs diff --git a/packages/core-api/src/apis/definitions/DiscoveryApi.ts b/packages/core-api/src/apis/definitions/DiscoveryApi.ts index b0773086c7..13ead7b929 100644 --- a/packages/core-api/src/apis/definitions/DiscoveryApi.ts +++ b/packages/core-api/src/apis/definitions/DiscoveryApi.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createApiRef } from '../ApiRef'; +import { createApiRef } from '../system'; /** * The discovery API is used to provide a mechanism for plugins to diff --git a/packages/core-api/src/apis/definitions/ErrorApi.ts b/packages/core-api/src/apis/definitions/ErrorApi.ts index 7f9676ea5a..365f59f4a3 100644 --- a/packages/core-api/src/apis/definitions/ErrorApi.ts +++ b/packages/core-api/src/apis/definitions/ErrorApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '../ApiRef'; +import { createApiRef } from '../system'; import { Observable } from '../../types'; /** diff --git a/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts index dd9657d7d8..0b7cddfe15 100644 --- a/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '../ApiRef'; +import { createApiRef } from '../system'; import { UserFlags, FeatureFlagsRegistry } from '../../app/FeatureFlags'; import { FeatureFlagName } from '../../plugin'; diff --git a/packages/core-api/src/apis/definitions/IdentityApi.ts b/packages/core-api/src/apis/definitions/IdentityApi.ts index 2684422b1e..87dc4ca989 100644 --- a/packages/core-api/src/apis/definitions/IdentityApi.ts +++ b/packages/core-api/src/apis/definitions/IdentityApi.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createApiRef } from '../ApiRef'; +import { createApiRef } from '../system'; import { ProfileInfo } from './auth'; /** diff --git a/packages/core-api/src/apis/definitions/OAuthRequestApi.ts b/packages/core-api/src/apis/definitions/OAuthRequestApi.ts index 6fefbd1b9c..7bbebf90c1 100644 --- a/packages/core-api/src/apis/definitions/OAuthRequestApi.ts +++ b/packages/core-api/src/apis/definitions/OAuthRequestApi.ts @@ -16,7 +16,7 @@ import { IconComponent } from '../../icons'; import { Observable } from '../../types'; -import { createApiRef } from '../ApiRef'; +import { createApiRef } from '../system'; /** * Information about the auth provider that we're requesting a login towards. diff --git a/packages/core-api/src/apis/definitions/StorageApi.ts b/packages/core-api/src/apis/definitions/StorageApi.ts index 920ee56811..923633318b 100644 --- a/packages/core-api/src/apis/definitions/StorageApi.ts +++ b/packages/core-api/src/apis/definitions/StorageApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '../ApiRef'; +import { createApiRef } from '../system'; import { Observable } from '../../types'; import { ErrorApi } from './ErrorApi'; diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts index 109f15b26e..5ffe312591 100644 --- a/packages/core-api/src/apis/definitions/auth.ts +++ b/packages/core-api/src/apis/definitions/auth.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { createApiRef } from '../ApiRef'; -import { Observable } from '../..'; +import { createApiRef } from '../system'; +import { Observable } from '../../types'; /** * This file contains declarations for common interfaces of auth-related APIs. diff --git a/packages/core-api/src/apis/index.ts b/packages/core-api/src/apis/index.ts index c856a13668..03569f9570 100644 --- a/packages/core-api/src/apis/index.ts +++ b/packages/core-api/src/apis/index.ts @@ -14,10 +14,6 @@ * limitations under the License. */ -export { ApiProvider, useApi, useApiHolder } from './ApiProvider'; -export { ApiRegistry } from './ApiRegistry'; -export * from './ApiRef'; -export * from './types'; -export * from './helpers'; +export * from './system'; export * from './definitions'; export * from './implementations'; diff --git a/packages/core-api/src/apis/ApiAggregator.test.ts b/packages/core-api/src/apis/system/ApiAggregator.test.ts similarity index 100% rename from packages/core-api/src/apis/ApiAggregator.test.ts rename to packages/core-api/src/apis/system/ApiAggregator.test.ts diff --git a/packages/core-api/src/apis/ApiAggregator.ts b/packages/core-api/src/apis/system/ApiAggregator.ts similarity index 93% rename from packages/core-api/src/apis/ApiAggregator.ts rename to packages/core-api/src/apis/system/ApiAggregator.ts index da49ee6488..1587a1d10b 100644 --- a/packages/core-api/src/apis/ApiAggregator.ts +++ b/packages/core-api/src/apis/system/ApiAggregator.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { ApiRef } from './ApiRef'; -import { ApiHolder } from './types'; +import { ApiRef, ApiHolder } from './types'; /** * An ApiHolder that queries multiple other holders from for diff --git a/packages/core-api/src/apis/ApiFactoryRegistry.test.ts b/packages/core-api/src/apis/system/ApiFactoryRegistry.test.ts similarity index 100% rename from packages/core-api/src/apis/ApiFactoryRegistry.test.ts rename to packages/core-api/src/apis/system/ApiFactoryRegistry.test.ts diff --git a/packages/core-api/src/apis/ApiFactoryRegistry.ts b/packages/core-api/src/apis/system/ApiFactoryRegistry.ts similarity index 98% rename from packages/core-api/src/apis/ApiFactoryRegistry.ts rename to packages/core-api/src/apis/system/ApiFactoryRegistry.ts index 0d36e6c550..556f63589c 100644 --- a/packages/core-api/src/apis/ApiFactoryRegistry.ts +++ b/packages/core-api/src/apis/system/ApiFactoryRegistry.ts @@ -15,12 +15,12 @@ */ import { + ApiRef, ApiFactoryHolder, ApiFactory, AnyApiRef, AnyApiFactory, } from './types'; -import { ApiRef } from './ApiRef'; type ApiFactoryScope = | 'default' // Default factories registered by core and plugins diff --git a/packages/core-api/src/apis/ApiProvider.test.tsx b/packages/core-api/src/apis/system/ApiProvider.test.tsx similarity index 100% rename from packages/core-api/src/apis/ApiProvider.test.tsx rename to packages/core-api/src/apis/system/ApiProvider.test.tsx diff --git a/packages/core-api/src/apis/ApiProvider.tsx b/packages/core-api/src/apis/system/ApiProvider.tsx similarity index 96% rename from packages/core-api/src/apis/ApiProvider.tsx rename to packages/core-api/src/apis/system/ApiProvider.tsx index 24610a1372..f2aa70244e 100644 --- a/packages/core-api/src/apis/ApiProvider.tsx +++ b/packages/core-api/src/apis/system/ApiProvider.tsx @@ -16,8 +16,7 @@ import React, { FC, createContext, useContext, ReactNode } from 'react'; import PropTypes from 'prop-types'; -import { ApiRef } from './ApiRef'; -import { ApiHolder, TypesToApiRefs } from './types'; +import { ApiRef, ApiHolder, TypesToApiRefs } from './types'; import { ApiAggregator } from './ApiAggregator'; type ApiProviderProps = { diff --git a/packages/core-api/src/apis/ApiRef.test.ts b/packages/core-api/src/apis/system/ApiRef.test.ts similarity index 100% rename from packages/core-api/src/apis/ApiRef.test.ts rename to packages/core-api/src/apis/system/ApiRef.test.ts diff --git a/packages/core-api/src/apis/ApiRef.ts b/packages/core-api/src/apis/system/ApiRef.ts similarity index 95% rename from packages/core-api/src/apis/ApiRef.ts rename to packages/core-api/src/apis/system/ApiRef.ts index 793c1e5386..0e1eb177c8 100644 --- a/packages/core-api/src/apis/ApiRef.ts +++ b/packages/core-api/src/apis/system/ApiRef.ts @@ -14,17 +14,13 @@ * limitations under the License. */ +import type { ApiRef } from './types'; + export type ApiRefConfig = { id: string; description: string; }; -export type ApiRef = { - id: string; - description: string; - T: T; -}; - class ApiRefImpl implements ApiRef { constructor(private readonly config: ApiRefConfig) { const valid = config.id diff --git a/packages/core-api/src/apis/ApiRegistry.test.ts b/packages/core-api/src/apis/system/ApiRegistry.test.ts similarity index 100% rename from packages/core-api/src/apis/ApiRegistry.test.ts rename to packages/core-api/src/apis/system/ApiRegistry.test.ts diff --git a/packages/core-api/src/apis/ApiRegistry.ts b/packages/core-api/src/apis/system/ApiRegistry.ts similarity index 96% rename from packages/core-api/src/apis/ApiRegistry.ts rename to packages/core-api/src/apis/system/ApiRegistry.ts index 64d97ba234..14f58268b4 100644 --- a/packages/core-api/src/apis/ApiRegistry.ts +++ b/packages/core-api/src/apis/system/ApiRegistry.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { ApiRef } from './ApiRef'; -import { ApiHolder } from './types'; +import { ApiRef, ApiHolder } from './types'; type ApiImpl = readonly [ApiRef, T]; diff --git a/packages/core-api/src/apis/ApiResolver.test.ts b/packages/core-api/src/apis/system/ApiResolver.test.ts similarity index 100% rename from packages/core-api/src/apis/ApiResolver.test.ts rename to packages/core-api/src/apis/system/ApiResolver.test.ts diff --git a/packages/core-api/src/apis/ApiResolver.ts b/packages/core-api/src/apis/system/ApiResolver.ts similarity index 98% rename from packages/core-api/src/apis/ApiResolver.ts rename to packages/core-api/src/apis/system/ApiResolver.ts index cb65186a37..0dfeaeedd0 100644 --- a/packages/core-api/src/apis/ApiResolver.ts +++ b/packages/core-api/src/apis/system/ApiResolver.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { ApiRef } from './ApiRef'; import { + ApiRef, ApiHolder, ApiFactoryHolder, AnyApiRef, diff --git a/packages/core-api/src/apis/helpers.ts b/packages/core-api/src/apis/system/helpers.ts similarity index 93% rename from packages/core-api/src/apis/helpers.ts rename to packages/core-api/src/apis/system/helpers.ts index 7d616acd01..0ccd0cdb80 100644 --- a/packages/core-api/src/apis/helpers.ts +++ b/packages/core-api/src/apis/system/helpers.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { ApiFactory, TypesToApiRefs } from './types'; -import { ApiRef } from './ApiRef'; +import { ApiRef, ApiFactory, TypesToApiRefs } from './types'; /** * Used to infer types for a standalone ApiFactory that isn't immediately passed diff --git a/packages/core-api/src/apis/system/index.ts b/packages/core-api/src/apis/system/index.ts new file mode 100644 index 0000000000..10b2e0f084 --- /dev/null +++ b/packages/core-api/src/apis/system/index.ts @@ -0,0 +1,23 @@ +/* + * 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 { ApiProvider, useApi, useApiHolder } from './ApiProvider'; +export { ApiRegistry } from './ApiRegistry'; +export { ApiResolver } from './ApiResolver'; +export { ApiFactoryRegistry } from './ApiFactoryRegistry'; +export { createApiRef } from './ApiRef'; +export * from './types'; +export * from './helpers'; diff --git a/packages/core-api/src/apis/types.ts b/packages/core-api/src/apis/system/types.ts similarity index 94% rename from packages/core-api/src/apis/types.ts rename to packages/core-api/src/apis/system/types.ts index 61c229b18e..b0c19e551c 100644 --- a/packages/core-api/src/apis/types.ts +++ b/packages/core-api/src/apis/system/types.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { ApiRef } from './ApiRef'; +export type ApiRef = { + id: string; + description: string; + T: T; +}; export type AnyApiRef = ApiRef; diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index c222c3c420..0588cd819c 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -54,8 +54,7 @@ import { } from '../apis'; import { useAsync } from 'react-use'; import { AppIdentity } from './AppIdentity'; -import { ApiFactoryRegistry } from '../apis/ApiFactoryRegistry'; -import { ApiResolver } from '../apis/ApiResolver'; +import { ApiResolver, ApiFactoryRegistry } from '../apis/system'; type FullAppOptions = { apis: Iterable; diff --git a/packages/core-api/src/plugin/types.ts b/packages/core-api/src/plugin/types.ts index 12992f3620..dacb3668bc 100644 --- a/packages/core-api/src/plugin/types.ts +++ b/packages/core-api/src/plugin/types.ts @@ -16,7 +16,7 @@ import { ComponentType } from 'react'; import { RouteRef } from '../routing'; -import { AnyApiFactory } from '../apis'; +import { AnyApiFactory } from '../apis/system'; export type RouteOptions = { // Whether the route path must match exactly, defaults to true. From 1bf3ff96a7f52e05510d461dccad7175a74a3b04 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Oct 2020 01:37:00 +0200 Subject: [PATCH 089/104] core-api: work around issue with ApiRef export const declarations --- .../core-api/src/apis/definitions/AlertApi.ts | 4 +-- .../src/apis/definitions/AppThemeApi.ts | 4 +-- .../src/apis/definitions/ConfigApi.ts | 4 +-- .../src/apis/definitions/DiscoveryApi.ts | 4 +-- .../core-api/src/apis/definitions/ErrorApi.ts | 4 +-- .../src/apis/definitions/FeatureFlagsApi.ts | 4 +-- .../src/apis/definitions/IdentityApi.ts | 4 +-- .../src/apis/definitions/OAuthRequestApi.ts | 4 +-- .../src/apis/definitions/StorageApi.ts | 4 +-- .../core-api/src/apis/definitions/auth.ts | 34 +++++++++---------- 10 files changed, 35 insertions(+), 35 deletions(-) diff --git a/packages/core-api/src/apis/definitions/AlertApi.ts b/packages/core-api/src/apis/definitions/AlertApi.ts index d149940103..91641ac35b 100644 --- a/packages/core-api/src/apis/definitions/AlertApi.ts +++ b/packages/core-api/src/apis/definitions/AlertApi.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createApiRef } from '../system'; +import { createApiRef, ApiRef } from '../system'; import { Observable } from '../../types'; export type AlertMessage = { @@ -38,7 +38,7 @@ export type AlertApi = { alert$(): Observable; }; -export const alertApiRef = createApiRef({ +export const alertApiRef: ApiRef = createApiRef({ id: 'core.alert', description: 'Used to report alerts and forward them to the app', }); diff --git a/packages/core-api/src/apis/definitions/AppThemeApi.ts b/packages/core-api/src/apis/definitions/AppThemeApi.ts index 019aa2f496..515e8df082 100644 --- a/packages/core-api/src/apis/definitions/AppThemeApi.ts +++ b/packages/core-api/src/apis/definitions/AppThemeApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '../system'; +import { ApiRef, createApiRef } from '../system'; import { BackstageTheme } from '@backstage/theme'; import { Observable } from '../../types'; import { SvgIconProps } from '@material-ui/core'; @@ -77,7 +77,7 @@ export type AppThemeApi = { setActiveThemeId(themeId?: string): void; }; -export const appThemeApiRef = createApiRef({ +export const appThemeApiRef: ApiRef = createApiRef({ id: 'core.apptheme', description: 'API Used to configure the app theme, and enumerate options', }); diff --git a/packages/core-api/src/apis/definitions/ConfigApi.ts b/packages/core-api/src/apis/definitions/ConfigApi.ts index 6d34055f05..2ce972af0e 100644 --- a/packages/core-api/src/apis/definitions/ConfigApi.ts +++ b/packages/core-api/src/apis/definitions/ConfigApi.ts @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createApiRef } from '../system'; +import { ApiRef, createApiRef } from '../system'; import { Config } from '@backstage/config'; // Using interface to make the ConfigApi name show up in docs export type ConfigApi = Config; -export const configApiRef = createApiRef({ +export const configApiRef: ApiRef = createApiRef({ id: 'core.config', description: 'Used to access runtime configuration', }); diff --git a/packages/core-api/src/apis/definitions/DiscoveryApi.ts b/packages/core-api/src/apis/definitions/DiscoveryApi.ts index 13ead7b929..9777835ec8 100644 --- a/packages/core-api/src/apis/definitions/DiscoveryApi.ts +++ b/packages/core-api/src/apis/definitions/DiscoveryApi.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createApiRef } from '../system'; +import { ApiRef, createApiRef } from '../system'; /** * The discovery API is used to provide a mechanism for plugins to @@ -41,7 +41,7 @@ export type DiscoveryApi = { getBaseUrl(pluginId: string): Promise; }; -export const discoveryApiRef = createApiRef({ +export const discoveryApiRef: ApiRef = createApiRef({ id: 'core.discovery', description: 'Provides service discovery of backend plugins', }); diff --git a/packages/core-api/src/apis/definitions/ErrorApi.ts b/packages/core-api/src/apis/definitions/ErrorApi.ts index 365f59f4a3..edd6966968 100644 --- a/packages/core-api/src/apis/definitions/ErrorApi.ts +++ b/packages/core-api/src/apis/definitions/ErrorApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '../system'; +import { ApiRef, createApiRef } from '../system'; import { Observable } from '../../types'; /** @@ -62,7 +62,7 @@ export type ErrorApi = { error$(): Observable<{ error: Error; context?: ErrorContext }>; }; -export const errorApiRef = createApiRef({ +export const errorApiRef: ApiRef = createApiRef({ id: 'core.error', description: 'Used to report errors and forward them to the app', }); diff --git a/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts index 0b7cddfe15..6e8c8cd8fc 100644 --- a/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '../system'; +import { ApiRef, createApiRef } from '../system'; import { UserFlags, FeatureFlagsRegistry } from '../../app/FeatureFlags'; import { FeatureFlagName } from '../../plugin'; @@ -57,7 +57,7 @@ export interface FeatureFlagsRegistryItem { name: FeatureFlagName; } -export const featureFlagsApiRef = createApiRef({ +export const featureFlagsApiRef: ApiRef = createApiRef({ id: 'core.featureflags', description: 'Used to toggle functionality in features across Backstage', }); diff --git a/packages/core-api/src/apis/definitions/IdentityApi.ts b/packages/core-api/src/apis/definitions/IdentityApi.ts index 87dc4ca989..5cfc4723e0 100644 --- a/packages/core-api/src/apis/definitions/IdentityApi.ts +++ b/packages/core-api/src/apis/definitions/IdentityApi.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createApiRef } from '../system'; +import { ApiRef, createApiRef } from '../system'; import { ProfileInfo } from './auth'; /** @@ -51,7 +51,7 @@ export type IdentityApi = { signOut(): Promise; }; -export const identityApiRef = createApiRef({ +export const identityApiRef: ApiRef = createApiRef({ id: 'core.identity', description: 'Provides access to the identity of the signed in user', }); diff --git a/packages/core-api/src/apis/definitions/OAuthRequestApi.ts b/packages/core-api/src/apis/definitions/OAuthRequestApi.ts index 7bbebf90c1..7514a88dc1 100644 --- a/packages/core-api/src/apis/definitions/OAuthRequestApi.ts +++ b/packages/core-api/src/apis/definitions/OAuthRequestApi.ts @@ -16,7 +16,7 @@ import { IconComponent } from '../../icons'; import { Observable } from '../../types'; -import { createApiRef } from '../system'; +import { ApiRef, createApiRef } from '../system'; /** * Information about the auth provider that we're requesting a login towards. @@ -127,7 +127,7 @@ export type OAuthRequestApi = { authRequest$(): Observable; }; -export const oauthRequestApiRef = createApiRef({ +export const oauthRequestApiRef: ApiRef = createApiRef({ id: 'core.oauthrequest', description: 'An API for implementing unified OAuth flows in Backstage', }); diff --git a/packages/core-api/src/apis/definitions/StorageApi.ts b/packages/core-api/src/apis/definitions/StorageApi.ts index 923633318b..d0f71ea3a4 100644 --- a/packages/core-api/src/apis/definitions/StorageApi.ts +++ b/packages/core-api/src/apis/definitions/StorageApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '../system'; +import { ApiRef, createApiRef } from '../system'; import { Observable } from '../../types'; import { ErrorApi } from './ErrorApi'; @@ -65,7 +65,7 @@ export interface StorageApi { observe$(key: string): Observable>; } -export const storageApiRef = createApiRef({ +export const storageApiRef: ApiRef = createApiRef({ id: 'core.storage', description: 'Provides the ability to store data which is unique to the user', }); diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts index 5ffe312591..946baeabd9 100644 --- a/packages/core-api/src/apis/definitions/auth.ts +++ b/packages/core-api/src/apis/definitions/auth.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '../system'; +import { ApiRef, createApiRef } from '../system'; import { Observable } from '../../types'; /** @@ -212,13 +212,13 @@ export type SessionApi = { * Note that the ID token payload is only guaranteed to contain the user's numerical Google ID, * email and expiration information. Do not rely on any other fields, as they might not be present. */ -export const googleAuthApiRef = createApiRef< +export const googleAuthApiRef: ApiRef< OAuthApi & OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->({ +> = createApiRef({ id: 'core.auth.google', description: 'Provides authentication towards Google APIs and identities', }); @@ -229,9 +229,9 @@ export const googleAuthApiRef = createApiRef< * See https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/ * for a full list of supported scopes. */ -export const githubAuthApiRef = createApiRef< +export const githubAuthApiRef: ApiRef< OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->({ +> = createApiRef({ id: 'core.auth.github', description: 'Provides authentication towards GitHub APIs', }); @@ -242,13 +242,13 @@ export const githubAuthApiRef = createApiRef< * See https://developer.okta.com/docs/guides/implement-oauth-for-okta/scopes/ * for a full list of supported scopes. */ -export const oktaAuthApiRef = createApiRef< +export const oktaAuthApiRef: ApiRef< OAuthApi & OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->({ +> = createApiRef({ id: 'core.auth.okta', description: 'Provides authentication towards Okta APIs', }); @@ -259,9 +259,9 @@ export const oktaAuthApiRef = createApiRef< * See https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#limiting-scopes-of-a-personal-access-token * for a full list of supported scopes. */ -export const gitlabAuthApiRef = createApiRef< +export const gitlabAuthApiRef: ApiRef< OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->({ +> = createApiRef({ id: 'core.auth.gitlab', description: 'Provides authentication towards GitLab APIs', }); @@ -272,9 +272,9 @@ export const gitlabAuthApiRef = createApiRef< * See https://auth0.com/docs/scopes/current/oidc-scopes * for a full list of supported scopes. */ -export const auth0AuthApiRef = createApiRef< +export const auth0AuthApiRef: ApiRef< OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->({ +> = createApiRef({ id: 'core.auth.auth0', description: 'Provides authentication towards Auth0 APIs', }); @@ -286,13 +286,13 @@ export const auth0AuthApiRef = createApiRef< * - https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent * - https://docs.microsoft.com/en-us/graph/permissions-reference */ -export const microsoftAuthApiRef = createApiRef< +export const microsoftAuthApiRef: ApiRef< OAuthApi & OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->({ +> = createApiRef({ id: 'core.auth.microsoft', description: 'Provides authentication towards Microsoft APIs and identities', }); @@ -300,13 +300,13 @@ export const microsoftAuthApiRef = createApiRef< /** * Provides authentication for custom identity providers. */ -export const oauth2ApiRef = createApiRef< +export const oauth2ApiRef: ApiRef< OAuthApi & OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->({ +> = createApiRef({ id: 'core.auth.oauth2', description: 'Example of how to use oauth2 custom provider', }); @@ -314,9 +314,9 @@ export const oauth2ApiRef = createApiRef< /** * Provides authentication for saml based identity providers */ -export const samlAuthApiRef = createApiRef< +export const samlAuthApiRef: ApiRef< ProfileInfoApi & BackstageIdentityApi & SessionApi ->({ +> = createApiRef({ id: 'core.auth.saml', description: 'Example of how to use SAML custom provider', }); From feeceb96dc906ae12626e11c233039a92f64ddf8 Mon Sep 17 00:00:00 2001 From: Abhishek Jakhar Date: Mon, 19 Oct 2020 07:05:34 +0530 Subject: [PATCH 090/104] handle the case where no entities are available to show --- .../UnregisterEntityDialog/UnregisterEntityDialog.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/catalog/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx b/plugins/catalog/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx index e4eec3c21f..258cc38d15 100644 --- a/plugins/catalog/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx +++ b/plugins/catalog/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx @@ -85,7 +85,7 @@ export const UnregisterEntityDialog: FC = ({ {error.toString()} ) : null} - {entities ? ( + {entities?.length ? ( <> This action will unregister the following entities: @@ -107,11 +107,11 @@ export const UnregisterEntityDialog: FC = ({ - - To undo, just re-register the entity in Backstage. - ) : null} + + To undo, just re-register the entity in Backstage. +