From ea581a2c62fa5b805cab7985318f6b7195ee3ab6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Feb 2020 12:50:32 +0100 Subject: [PATCH 1/6] front/plugins/github-actions/BuildsListPage: tweak styles --- .../src/components/BuildListPage/BuildListPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx b/frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx index 9096cc8f2e..47dfb2e8d9 100644 --- a/frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx +++ b/frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx @@ -35,7 +35,7 @@ const useStyles = makeStyles(theme => ({ padding: theme.spacing(2), }, title: { - paddingBottom: theme.spacing(2), + padding: theme.spacing(1, 0, 2, 0), }, })); @@ -49,7 +49,7 @@ const BuildListPage: FC<{}> = () => { content = ; } else if (status.error) { content = ( - + Failed to load builds, {status.error.message} ); @@ -101,7 +101,7 @@ const BuildListPage: FC<{}> = () => { return (
- + CI/CD Builds {content} From ea35a14f68d74c87946aaed052a9974c87444b6b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Feb 2020 13:11:56 +0100 Subject: [PATCH 2/6] front/core: add DefaultWidgetView and pass params for widgets --- frontend/packages/app/src/entities/index.ts | 4 +-- .../src/api/widgetView/WidgetViewBuilder.tsx | 33 ++++++------------- .../packages/core/src/api/widgetView/types.ts | 10 ++++++ .../DefaultWidgetView/DefaultWidgetView.tsx | 30 +++++++++++++++++ .../src/components/DefaultWidgetView/index.ts | 1 + 5 files changed, 53 insertions(+), 25 deletions(-) create mode 100644 frontend/packages/core/src/api/widgetView/types.ts create mode 100644 frontend/packages/core/src/components/DefaultWidgetView/DefaultWidgetView.tsx create mode 100644 frontend/packages/core/src/components/DefaultWidgetView/index.ts diff --git a/frontend/packages/app/src/entities/index.ts b/frontend/packages/app/src/entities/index.ts index 01f4105902..d0839118f5 100644 --- a/frontend/packages/app/src/entities/index.ts +++ b/frontend/packages/app/src/entities/index.ts @@ -12,8 +12,8 @@ import GithubActionsPlugin from '@backstage/plugin-github-actions'; /* SERVICE */ const serviceOverviewPage = createWidgetView() - .addComponent(MockEntityCard) - .addComponent(MockEntityCard); + .add({ size: 4, component: MockEntityCard }) + .add({ size: 4, component: MockEntityCard }); const serviceView = createEntityPage() .addPage('Overview', WebIcon, '/overview', serviceOverviewPage) diff --git a/frontend/packages/core/src/api/widgetView/WidgetViewBuilder.tsx b/frontend/packages/core/src/api/widgetView/WidgetViewBuilder.tsx index 3ab13c2f8c..164ceaf485 100644 --- a/frontend/packages/core/src/api/widgetView/WidgetViewBuilder.tsx +++ b/frontend/packages/core/src/api/widgetView/WidgetViewBuilder.tsx @@ -1,50 +1,37 @@ -import React, { ComponentType, FC } from 'react'; +import React, { ComponentType } from 'react'; import { App, AppComponentBuilder } from '../app/types'; - -type Props = { - app: App; - cards: ComponentType[]; -}; - -const WidgetViewComponent: FC = ({ cards }) => { - return ( -
- {cards.map((CardComponent, index) => ( - - ))} -
- ); -}; +import { Widget } from './types'; +import DefaultWidgetView from '../../components/DefaultWidgetView'; type WidgetViewRegistration = { type: 'component'; - component: ComponentType; + widget: Widget; }; export default class WidgetViewBuilder extends AppComponentBuilder { private readonly registrations = new Array(); private output?: ComponentType; - addComponent(component: ComponentType): WidgetViewBuilder { - this.registrations.push({ type: 'component', component }); + add(widget: Widget): WidgetViewBuilder { + this.registrations.push({ type: 'component', widget }); return this; } - build(app: App): ComponentType { + build(_app: App): ComponentType { if (this.output) { return this.output; } - const cards = this.registrations.map(reg => { + const widgets = this.registrations.map(reg => { switch (reg.type) { case 'component': - return reg.component; + return reg.widget; default: throw new Error(`Unknown WidgetViewBuilder registration`); } }); - this.output = () => ; + this.output = () => ; return this.output; } } diff --git a/frontend/packages/core/src/api/widgetView/types.ts b/frontend/packages/core/src/api/widgetView/types.ts new file mode 100644 index 0000000000..c7c0cf865b --- /dev/null +++ b/frontend/packages/core/src/api/widgetView/types.ts @@ -0,0 +1,10 @@ +import { ComponentType } from 'react'; + +export type Widget = { + size: 4 | 6 | 8 | 12; + component: ComponentType; +}; + +export type WidgetViewProps = { + widgets: Widget[]; +}; diff --git a/frontend/packages/core/src/components/DefaultWidgetView/DefaultWidgetView.tsx b/frontend/packages/core/src/components/DefaultWidgetView/DefaultWidgetView.tsx new file mode 100644 index 0000000000..c7ec02b4cf --- /dev/null +++ b/frontend/packages/core/src/components/DefaultWidgetView/DefaultWidgetView.tsx @@ -0,0 +1,30 @@ +import React, { FC } from 'react'; +import { Grid, Paper, makeStyles, Theme } from '@material-ui/core'; +import { WidgetViewProps } from '../../api/widgetView/types'; + +const useStyles = makeStyles(theme => ({ + root: { + padding: theme.spacing(2), + }, + widgetWrapper: { + padding: theme.spacing(2), + }, +})); + +const WidgetViewComponent: FC = ({ widgets }) => { + const classes = useStyles(); + + return ( + + {widgets.map(({ size, component: WidgetComponent }, index) => ( + + + + + + ))} + + ); +}; + +export default WidgetViewComponent; diff --git a/frontend/packages/core/src/components/DefaultWidgetView/index.ts b/frontend/packages/core/src/components/DefaultWidgetView/index.ts new file mode 100644 index 0000000000..37e32e9faf --- /dev/null +++ b/frontend/packages/core/src/components/DefaultWidgetView/index.ts @@ -0,0 +1 @@ +export { default } from './DefaultWidgetView'; From bb450005af9a852de37d5b6ec22c476c7dda7203 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Feb 2020 13:27:43 +0100 Subject: [PATCH 3/6] front/core: add plugin api hooks and output for widgets --- .../packages/core/src/api/plugin/Plugin.tsx | 11 +++++ .../packages/core/src/api/plugin/types.ts | 7 ++++ .../src/api/widgetView/WidgetViewBuilder.tsx | 42 +++++++++++++++---- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/frontend/packages/core/src/api/plugin/Plugin.tsx b/frontend/packages/core/src/api/plugin/Plugin.tsx index 772776e288..624a9042e6 100644 --- a/frontend/packages/core/src/api/plugin/Plugin.tsx +++ b/frontend/packages/core/src/api/plugin/Plugin.tsx @@ -1,6 +1,7 @@ import { ComponentType } from 'react'; import { PluginOutput, RoutePath, RouteOptions } from './types'; import { IconComponent } from '../types'; +import { Widget } from '../widgetView/types'; export type PluginConfig = { id: string; @@ -10,6 +11,7 @@ export type PluginConfig = { export type PluginHooks = { router: RouterHooks; entityPage: EntityPageHooks; + widgets: WidgetHooks; }; export type RouterHooks = { @@ -41,6 +43,10 @@ export type EntityPageHooks = { ): void; }; +export type WidgetHooks = { + add(widget: Widget): void; +}; + export const registerSymbol = Symbol('plugin-register'); export const outputSymbol = Symbol('plugin-output'); @@ -98,6 +104,11 @@ export default class Plugin { }); }, }, + widgets: { + add(widget: Widget) { + outputs.push({ type: 'widget', widget }); + }, + }, }); this.storedOutput = outputs; diff --git a/frontend/packages/core/src/api/plugin/types.ts b/frontend/packages/core/src/api/plugin/types.ts index f6efee230e..9b0eb73639 100644 --- a/frontend/packages/core/src/api/plugin/types.ts +++ b/frontend/packages/core/src/api/plugin/types.ts @@ -1,5 +1,6 @@ import { ComponentType } from 'react'; import { IconComponent } from '../types'; +import { Widget } from '../widgetView/types'; export type RouteOptions = { // Whether the route path must match exactly, defaults to true. @@ -22,6 +23,11 @@ export type RedirectRouteOutput = { options?: RouteOptions; }; +export type WidgetOutput = { + type: 'widget'; + widget: Widget; +}; + export type EntityPageViewRouteOutput = { type: 'entity-page-view-route'; path: RoutePath; @@ -39,5 +45,6 @@ export type EntityPageNavItemOutput = { export type PluginOutput = | RouteOutput | RedirectRouteOutput + | WidgetOutput | EntityPageViewRouteOutput | EntityPageNavItemOutput; diff --git a/frontend/packages/core/src/api/widgetView/WidgetViewBuilder.tsx b/frontend/packages/core/src/api/widgetView/WidgetViewBuilder.tsx index 164ceaf485..84e231651f 100644 --- a/frontend/packages/core/src/api/widgetView/WidgetViewBuilder.tsx +++ b/frontend/packages/core/src/api/widgetView/WidgetViewBuilder.tsx @@ -1,12 +1,18 @@ import React, { ComponentType } from 'react'; import { App, AppComponentBuilder } from '../app/types'; import { Widget } from './types'; +import BackstagePlugin from '../plugin/Plugin'; import DefaultWidgetView from '../../components/DefaultWidgetView'; -type WidgetViewRegistration = { - type: 'component'; - widget: Widget; -}; +type WidgetViewRegistration = + | { + type: 'component'; + widget: Widget; + } + | { + type: 'plugin'; + plugin: BackstagePlugin; + }; export default class WidgetViewBuilder extends AppComponentBuilder { private readonly registrations = new Array(); @@ -17,19 +23,41 @@ export default class WidgetViewBuilder extends AppComponentBuilder { return this; } + register(plugin: BackstagePlugin): WidgetViewBuilder { + this.registrations.push({ type: 'plugin', plugin }); + return this; + } + build(_app: App): ComponentType { if (this.output) { return this.output; } - const widgets = this.registrations.map(reg => { + const widgets = new Array(); + + for (const reg of this.registrations) { switch (reg.type) { case 'component': - return reg.widget; + widgets.push(reg.widget); + break; + case 'plugin': + let added = false; + for (const output of reg.plugin.output()) { + if (output.type === 'widget') { + widgets.push(output.widget); + added = true; + } + } + if (!added) { + throw new Error( + `Plugin ${reg.plugin} was registered as widget provider, but did not provide any widgets`, + ); + } + break; default: throw new Error(`Unknown WidgetViewBuilder registration`); } - }); + } this.output = () => ; return this.output; From 42e24486eb6d1eceb1a51073c8588c15ee1e4aef Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Feb 2020 13:40:02 +0100 Subject: [PATCH 4/6] front/core/DefaultWidgetView: tweak padding --- .../DefaultWidgetView/DefaultWidgetView.tsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/frontend/packages/core/src/components/DefaultWidgetView/DefaultWidgetView.tsx b/frontend/packages/core/src/components/DefaultWidgetView/DefaultWidgetView.tsx index c7ec02b4cf..1b3291a01a 100644 --- a/frontend/packages/core/src/components/DefaultWidgetView/DefaultWidgetView.tsx +++ b/frontend/packages/core/src/components/DefaultWidgetView/DefaultWidgetView.tsx @@ -15,15 +15,17 @@ const WidgetViewComponent: FC = ({ widgets }) => { const classes = useStyles(); return ( - - {widgets.map(({ size, component: WidgetComponent }, index) => ( - - - - - - ))} - +
+ + {widgets.map(({ size, component: WidgetComponent }, index) => ( + + + + + + ))} + +
); }; From bb15807004d3bc51f1612a2f3fa4e0757e677e4c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Feb 2020 13:40:21 +0100 Subject: [PATCH 5/6] front/plugins/github-actions: register build info card and popuplate card --- .../BuildInfoCard/BuildInfoCard.tsx | 83 ++++++++++++++++++- .../plugins/github-actions/src/plugin.ts | 4 +- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/frontend/packages/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx b/frontend/packages/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx index 38fc2266db..823b43a21a 100644 --- a/frontend/packages/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx +++ b/frontend/packages/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx @@ -1,8 +1,87 @@ import React, { FC } from 'react'; -import { InfoCard } from '@backstage/core'; +import { RelativeEntityLink } from '@backstage/core'; +import { BuildsClient } from '../../apis/builds'; +import { useAsync } from 'react-use'; +import { + Typography, + Table, + TableBody, + TableRow, + TableCell, + LinearProgress, + makeStyles, + Theme, +} from '@material-ui/core'; + +const client = BuildsClient.create('http://localhost:8080'); + +const useStyles = makeStyles(theme => ({ + root: { + // height: 400, + }, + title: { + paddingBottom: theme.spacing(1), + }, +})); const BuildInfoCard: FC<{}> = () => { - return Last build was acb67fa3b5472w; + const classes = useStyles(); + const status = useAsync(() => client.listBuilds('entity:spotify:backstage')); + + let content: JSX.Element; + + if (status.loading) { + content = ; + } else if (status.error) { + content = ( + + Failed to load builds, {status.error.message} + + ); + } else { + const [build] = + status.value?.filter(({ branch }) => branch === 'master') ?? []; + + content = ( + + + + + Message + + + + {build?.message} + + + + + + Commit ID + + {build?.commitId} + + + + Status + + {build?.status} + + +
+ ); + } + + return ( +
+ + Master Build + + {content} +
+ ); }; export default BuildInfoCard; diff --git a/frontend/packages/plugins/github-actions/src/plugin.ts b/frontend/packages/plugins/github-actions/src/plugin.ts index d95d5b5bdc..0f5a7f9607 100644 --- a/frontend/packages/plugins/github-actions/src/plugin.ts +++ b/frontend/packages/plugins/github-actions/src/plugin.ts @@ -2,6 +2,7 @@ import { createPlugin } from '@backstage/core'; import BuildDetailsPage from './components/BuildDetailsPage'; import BuildListPage from './components/BuildListPage'; import BuildIcon from '@material-ui/icons/Build'; +import BuildInfoCard from './components/BuildInfoCard'; // export const buildListRoute = createEntityRoute<[]>('/builds') // export const buildDetailsRoute = createEntityRoute<[number]>('/builds/:buildId') @@ -9,9 +10,10 @@ import BuildIcon from '@material-ui/icons/Build'; export default createPlugin({ id: 'github-actions', - register({ entityPage }) { + register({ entityPage, widgets }) { entityPage.navItem({ title: 'CI/CD', icon: BuildIcon, target: '/builds' }); entityPage.route('/builds', BuildListPage); entityPage.route('/builds/:buildUri', BuildDetailsPage); + widgets.add({ size: 8, component: BuildInfoCard }); }, }); From c21263d64cf95f2d62af7b76745ed96a5f333d86 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Feb 2020 13:40:35 +0100 Subject: [PATCH 6/6] front/app/entities: add build into card to service entities --- frontend/packages/app/src/entities/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/packages/app/src/entities/index.ts b/frontend/packages/app/src/entities/index.ts index d0839118f5..9823b7fa38 100644 --- a/frontend/packages/app/src/entities/index.ts +++ b/frontend/packages/app/src/entities/index.ts @@ -13,7 +13,7 @@ import GithubActionsPlugin from '@backstage/plugin-github-actions'; /* SERVICE */ const serviceOverviewPage = createWidgetView() .add({ size: 4, component: MockEntityCard }) - .add({ size: 4, component: MockEntityCard }); + .register(GithubActionsPlugin); const serviceView = createEntityPage() .addPage('Overview', WebIcon, '/overview', serviceOverviewPage)