From b894a0be0c747edbbf4c11e5901bf788abce9cfe Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 6 Feb 2020 09:01:55 +0100 Subject: [PATCH] frontend: added github-actions plugin skeleton + plugin output experiments --- frontend/packages/app/package.json | 1 + frontend/packages/app/src/entities/index.ts | 4 +- .../api/entityView/EntityViewPageBuilder.tsx | 23 +++++++++- .../packages/core/src/api/plugin/Plugin.tsx | 15 +++++- .../core/src/api/plugin/PluginOutputHook.ts | 11 +++++ .../packages/core/src/api/plugin/outputs.ts | 8 ++++ .../src/api/widgetView/WidgetViewBuilder.tsx | 4 +- .../src/components/EntityLink/EntityLink.tsx | 2 +- .../EntityLink/RelativeEntityLink.tsx | 16 +++++++ .../core/src/components/EntityLink/index.ts | 1 + frontend/packages/core/src/index.ts | 5 +- .../packages/plugins/github-actions/README.md | 1 + .../plugins/github-actions/jest.config.js | 4 ++ .../plugins/github-actions/jest.setup.ts | 1 + .../plugins/github-actions/package.json | 28 +++++++++++ .../BuildDetailsPage/BuildDetailsPage.tsx | 20 ++++++++ .../src/components/BuildDetailsPage/index.ts | 1 + .../BuildInfoCard/BuildInfoCard.tsx | 8 ++++ .../src/components/BuildInfoCard/index.ts | 1 + .../BuildListPage/BuildListPage.tsx | 46 +++++++++++++++++++ .../src/components/BuildListPage/index.ts | 1 + .../src/components/BuildPage/BuildPage.tsx | 22 +++++++++ .../src/components/BuildPage/index.ts | 1 + .../plugins/github-actions/src/index.ts | 1 + .../plugins/github-actions/src/plugin.test.ts | 7 +++ .../plugins/github-actions/src/plugin.ts | 18 ++++++++ 26 files changed, 243 insertions(+), 7 deletions(-) create mode 100644 frontend/packages/core/src/api/plugin/PluginOutputHook.ts create mode 100644 frontend/packages/core/src/api/plugin/outputs.ts create mode 100644 frontend/packages/core/src/components/EntityLink/RelativeEntityLink.tsx create mode 100644 frontend/packages/plugins/github-actions/README.md create mode 100644 frontend/packages/plugins/github-actions/jest.config.js create mode 100644 frontend/packages/plugins/github-actions/jest.setup.ts create mode 100644 frontend/packages/plugins/github-actions/package.json create mode 100644 frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx create mode 100644 frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/index.ts create mode 100644 frontend/packages/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx create mode 100644 frontend/packages/plugins/github-actions/src/components/BuildInfoCard/index.ts create mode 100644 frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx create mode 100644 frontend/packages/plugins/github-actions/src/components/BuildListPage/index.ts create mode 100644 frontend/packages/plugins/github-actions/src/components/BuildPage/BuildPage.tsx create mode 100644 frontend/packages/plugins/github-actions/src/components/BuildPage/index.ts create mode 100644 frontend/packages/plugins/github-actions/src/index.ts create mode 100644 frontend/packages/plugins/github-actions/src/plugin.test.ts create mode 100644 frontend/packages/plugins/github-actions/src/plugin.ts diff --git a/frontend/packages/app/package.json b/frontend/packages/app/package.json index 130e7972ad..6738d591d7 100644 --- a/frontend/packages/app/package.json +++ b/frontend/packages/app/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@backstage/core": "0.0.0", + "@backstage/plugin-github-actions": "0.0.0", "@backstage/plugin-hello-world": "0.0.0", "@backstage/plugin-home-page": "0.0.0", "@backstage/plugin-login": "0.0.0", diff --git a/frontend/packages/app/src/entities/index.ts b/frontend/packages/app/src/entities/index.ts index f32f99ab06..8b42342e5d 100644 --- a/frontend/packages/app/src/entities/index.ts +++ b/frontend/packages/app/src/entities/index.ts @@ -6,6 +6,7 @@ import { import ComputerIcon from '@material-ui/icons/Computer'; import MockEntityPage from './MockEntityPage'; import MockEntityCard from './MockEntityCard'; +import GithubActionsPlugin from '@backstage/plugin-github-actions'; /* SERVICE */ const serviceOverviewPage = createWidgetView() @@ -14,7 +15,8 @@ const serviceOverviewPage = createWidgetView() const serviceView = createEntityView() .addPage('Overview', 'overview', serviceOverviewPage) - .addComponent('CI/CD', 'ci-cd', MockEntityPage); + .register(GithubActionsPlugin) + .addComponent('Deployment', 'deployment', MockEntityPage); const serviceEntity = createEntityKind({ kind: 'service', diff --git a/frontend/packages/core/src/api/entityView/EntityViewPageBuilder.tsx b/frontend/packages/core/src/api/entityView/EntityViewPageBuilder.tsx index 10985325fc..e4089e16d7 100644 --- a/frontend/packages/core/src/api/entityView/EntityViewPageBuilder.tsx +++ b/frontend/packages/core/src/api/entityView/EntityViewPageBuilder.tsx @@ -5,6 +5,8 @@ import ListItem from '@material-ui/core/ListItem'; import { AppComponentBuilder, App } from '../app/types'; import { useEntity, useEntityUri, useEntityConfig } from './EntityContext'; import EntityLink from '../../components/EntityLink/EntityLink'; +import BackstagePlugin, { outputSymbol } from '../plugin/Plugin'; +import { entityViewPage } from '../plugin/outputs'; const EntityLayout: FC<{}> = ({ children }) => { const config = useEntityConfig(); @@ -57,7 +59,7 @@ const EntityViewComponent: FC = ({ pages }) => { {pages.map(({ path, component }) => ( @@ -75,6 +77,10 @@ type EntityViewRegistration = path: string; page: AppComponentBuilder; } + | { + type: 'plugin'; + plugin: BackstagePlugin; + } | { type: 'component'; title: string; @@ -103,6 +109,11 @@ export default class EntityViewBuilder extends AppComponentBuilder { return this; } + register(plugin: BackstagePlugin): EntityViewBuilder { + this.registrations.push({ type: 'plugin', plugin }); + return this; + } + build(app: App): ComponentType { const pages = this.registrations.map(registration => { switch (registration.type) { @@ -114,6 +125,16 @@ export default class EntityViewBuilder extends AppComponentBuilder { const { title, path, component } = registration; return { title, path, component }; } + case 'plugin': { + const { plugin } = registration; + const output = plugin[outputSymbol](entityViewPage); + if (!output) { + throw new Error( + `Plugin ${plugin} was registered as entity view, but did not have any output`, + ); + } + return output; + } default: throw new Error(`Unknown EntityViewBuilder registration`); } diff --git a/frontend/packages/core/src/api/plugin/Plugin.tsx b/frontend/packages/core/src/api/plugin/Plugin.tsx index 13caa40475..a5714fb009 100644 --- a/frontend/packages/core/src/api/plugin/Plugin.tsx +++ b/frontend/packages/core/src/api/plugin/Plugin.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { Route, Redirect } from 'react-router-dom'; +import PluginOutputHook from './PluginOutputHook'; export type PluginConfig = { id: string; @@ -8,6 +9,7 @@ export type PluginConfig = { export type PluginHooks = { router: Router; + provide(ref: PluginOutputHook, value: T): void; }; export type RouteOptions = { @@ -35,9 +37,11 @@ export type Router = { export type PluginRegistrationResult = { routes?: JSX.Element[]; + outputs?: Map, any>; }; export const registerSymbol = Symbol('plugin-register'); +export const outputSymbol = Symbol('plugin-output'); export default class Plugin { private result?: PluginRegistrationResult; @@ -55,6 +59,7 @@ export default class Plugin { const { id } = this.config; const routes = new Array(); + const outputs = new Map, any>(); this.config.register({ router: { @@ -86,12 +91,20 @@ export default class Plugin { ); }, }, + provide(hook, value) { + outputs.set(hook, value); + }, }); - this.result = { routes }; + this.result = { routes, outputs }; return this.result; } + [outputSymbol](outputHook: PluginOutputHook): T | undefined { + const { outputs } = this[registerSymbol](); + return outputs?.get(outputHook) as T; + } + toString() { return `plugin{${this.config.id}}`; } diff --git a/frontend/packages/core/src/api/plugin/PluginOutputHook.ts b/frontend/packages/core/src/api/plugin/PluginOutputHook.ts new file mode 100644 index 0000000000..2780117d24 --- /dev/null +++ b/frontend/packages/core/src/api/plugin/PluginOutputHook.ts @@ -0,0 +1,11 @@ +export default class PluginOutputHook { + constructor(private readonly name: string) {} + + get T(): T { + throw new Error('use typeof instead'); + } + + toString() { + return `pluginOutput{${this.name}}`; + } +} diff --git a/frontend/packages/core/src/api/plugin/outputs.ts b/frontend/packages/core/src/api/plugin/outputs.ts new file mode 100644 index 0000000000..59c4555cbf --- /dev/null +++ b/frontend/packages/core/src/api/plugin/outputs.ts @@ -0,0 +1,8 @@ +import PluginOutputHook from './PluginOutputHook'; +import { ComponentType } from 'react'; + +export const entityViewPage = new PluginOutputHook<{ + title: string; + path: string; + component: ComponentType; +}>('entity-view-page'); diff --git a/frontend/packages/core/src/api/widgetView/WidgetViewBuilder.tsx b/frontend/packages/core/src/api/widgetView/WidgetViewBuilder.tsx index eb9f5d1ed0..3ab13c2f8c 100644 --- a/frontend/packages/core/src/api/widgetView/WidgetViewBuilder.tsx +++ b/frontend/packages/core/src/api/widgetView/WidgetViewBuilder.tsx @@ -9,8 +9,8 @@ type Props = { const WidgetViewComponent: FC = ({ cards }) => { return (
- {cards.map(CardComponent => ( - + {cards.map((CardComponent, index) => ( + ))}
); diff --git a/frontend/packages/core/src/components/EntityLink/EntityLink.tsx b/frontend/packages/core/src/components/EntityLink/EntityLink.tsx index c26972c467..a060cb1719 100644 --- a/frontend/packages/core/src/components/EntityLink/EntityLink.tsx +++ b/frontend/packages/core/src/components/EntityLink/EntityLink.tsx @@ -13,7 +13,7 @@ type Props = { } ); -function buildPath(kind: string, id?: string, subPath?: string) { +export function buildPath(kind: string, id?: string, subPath?: string) { if (id) { if (subPath) { return `/entity/${kind}/${id}/${subPath}`; diff --git a/frontend/packages/core/src/components/EntityLink/RelativeEntityLink.tsx b/frontend/packages/core/src/components/EntityLink/RelativeEntityLink.tsx new file mode 100644 index 0000000000..aa61abcc01 --- /dev/null +++ b/frontend/packages/core/src/components/EntityLink/RelativeEntityLink.tsx @@ -0,0 +1,16 @@ +import React, { FC } from 'react'; +import { Link } from 'react-router-dom'; +import { useEntity } from '../../api'; +import { buildPath } from './EntityLink'; + +type Props = { + view: string; +}; + +const RelativeEntityLink: FC = ({ view, children }) => { + const entity = useEntity(); + + return {children}; +}; + +export default RelativeEntityLink; diff --git a/frontend/packages/core/src/components/EntityLink/index.ts b/frontend/packages/core/src/components/EntityLink/index.ts index d35b5bee26..f067ebf3e2 100644 --- a/frontend/packages/core/src/components/EntityLink/index.ts +++ b/frontend/packages/core/src/components/EntityLink/index.ts @@ -1 +1,2 @@ export { default } from './EntityLink'; +export { default as RelativeEntityLink } from './RelativeEntityLink'; diff --git a/frontend/packages/core/src/index.ts b/frontend/packages/core/src/index.ts index 3dc9c80d41..ccca30231a 100644 --- a/frontend/packages/core/src/index.ts +++ b/frontend/packages/core/src/index.ts @@ -1,5 +1,8 @@ export * from './api'; -export { default as EntityLink } from './components/EntityLink'; +export { + default as EntityLink, + RelativeEntityLink, +} from './components/EntityLink'; export { default as Page } from '../src/layout/Page'; export { gradients, theme } from '../src/layout/Page'; export { default as Header } from '../src/layout/Header/Header'; diff --git a/frontend/packages/plugins/github-actions/README.md b/frontend/packages/plugins/github-actions/README.md new file mode 100644 index 0000000000..d55e47c5b1 --- /dev/null +++ b/frontend/packages/plugins/github-actions/README.md @@ -0,0 +1 @@ +Welcome to your github-actions plugin! diff --git a/frontend/packages/plugins/github-actions/jest.config.js b/frontend/packages/plugins/github-actions/jest.config.js new file mode 100644 index 0000000000..6b28dacb3d --- /dev/null +++ b/frontend/packages/plugins/github-actions/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + ...require('@spotify/web-scripts/config/jest.config.js'), + setupFilesAfterEnv: ['../jest.setup.ts'], +}; diff --git a/frontend/packages/plugins/github-actions/jest.setup.ts b/frontend/packages/plugins/github-actions/jest.setup.ts new file mode 100644 index 0000000000..666127af39 --- /dev/null +++ b/frontend/packages/plugins/github-actions/jest.setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom/extend-expect'; diff --git a/frontend/packages/plugins/github-actions/package.json b/frontend/packages/plugins/github-actions/package.json new file mode 100644 index 0000000000..cfe203d986 --- /dev/null +++ b/frontend/packages/plugins/github-actions/package.json @@ -0,0 +1,28 @@ +{ + "name": "@backstage/plugin-github-actions", + "version": "0.0.0", + "main": "src/index.ts", + "main:src": "src/index.ts", + "devDependencies": { + "@backstage/core": "0.0.0", + "@spotify/web-scripts": "^6.0.0", + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", + "@testing-library/user-event": "^7.1.2", + "@types/jest": "^24.0.0", + "@types/node": "^12.0.0", + "@types/react": "^16.9.0", + "@types/react-dom": "^16.9.0", + "@types/react-router-dom": "^5.1.3", + "react": "^16.12.0", + "react-dom": "^16.12.0", + "react-router-dom": "^5.1.2", + "@material-ui/core": "^4.9.1", + "@material-ui/icons": "^4.9.1" + }, + "scripts": { + "lint": "web-scripts lint", + "test": "web-scripts test" + }, + "license": "Apache-2.0" +} diff --git a/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx b/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx new file mode 100644 index 0000000000..5cd5e21bf7 --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx @@ -0,0 +1,20 @@ +import React, { FC } from 'react'; +import Button from '@material-ui/core/Button'; + +type Props = { + buildId: string; +}; + +const BuildDetailsPage: FC = ({ buildId }) => { + return ( + + ); +}; + +export default BuildDetailsPage; diff --git a/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/index.ts b/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/index.ts new file mode 100644 index 0000000000..a2b6def3b0 --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/index.ts @@ -0,0 +1 @@ +export { default } from './BuildDetailsPage'; diff --git a/frontend/packages/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx b/frontend/packages/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx new file mode 100644 index 0000000000..38fc2266db --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx @@ -0,0 +1,8 @@ +import React, { FC } from 'react'; +import { InfoCard } from '@backstage/core'; + +const BuildInfoCard: FC<{}> = () => { + return Last build was acb67fa3b5472w; +}; + +export default BuildInfoCard; diff --git a/frontend/packages/plugins/github-actions/src/components/BuildInfoCard/index.ts b/frontend/packages/plugins/github-actions/src/components/BuildInfoCard/index.ts new file mode 100644 index 0000000000..21c0b99099 --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/components/BuildInfoCard/index.ts @@ -0,0 +1 @@ +export { default } from './BuildInfoCard'; diff --git a/frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx b/frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx new file mode 100644 index 0000000000..697d9edfa6 --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx @@ -0,0 +1,46 @@ +import React, { FC } from 'react'; +import { + TableContainer, + Table, + TableHead, + TableRow, + TableCell, + Paper, + TableBody, +} from '@material-ui/core'; +import { RelativeEntityLink } from '@backstage/core'; + +const BuildListPage: FC<{}> = () => { + const rows = [ + { message: 'Fixed a Bar', commit: 'fb46ca3dfbd7af5bc43da', id: 165 }, + { message: 'Fixed a Foo', commit: 'd7af5bc43dafb46ca3dfb', id: 164 }, + ]; + return ( + + + + + Message + Commit + Build ID + + + + {rows.map(row => ( + + {row.message} + {row.commit} + + + {row.commit} + + + + ))} + +
+
+ ); +}; + +export default BuildListPage; diff --git a/frontend/packages/plugins/github-actions/src/components/BuildListPage/index.ts b/frontend/packages/plugins/github-actions/src/components/BuildListPage/index.ts new file mode 100644 index 0000000000..497fb05ab5 --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/components/BuildListPage/index.ts @@ -0,0 +1 @@ +export { default } from './BuildListPage'; diff --git a/frontend/packages/plugins/github-actions/src/components/BuildPage/BuildPage.tsx b/frontend/packages/plugins/github-actions/src/components/BuildPage/BuildPage.tsx new file mode 100644 index 0000000000..6fff20640a --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/components/BuildPage/BuildPage.tsx @@ -0,0 +1,22 @@ +import React, { FC } from 'react'; +import { Switch, Route } from 'react-router-dom'; +import BuildListPage from '../BuildListPage'; +import BuildDetailsPage from '../BuildDetailsPage'; +import { useRouteMatch } from 'react-router-dom'; + +const BuildPage: FC<{}> = () => { + const match = useRouteMatch(); + return ( + + ( + + )} + /> + + + ); +}; + +export default BuildPage; diff --git a/frontend/packages/plugins/github-actions/src/components/BuildPage/index.ts b/frontend/packages/plugins/github-actions/src/components/BuildPage/index.ts new file mode 100644 index 0000000000..092d2e639d --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/components/BuildPage/index.ts @@ -0,0 +1 @@ +export { default } from './BuildPage'; diff --git a/frontend/packages/plugins/github-actions/src/index.ts b/frontend/packages/plugins/github-actions/src/index.ts new file mode 100644 index 0000000000..b68aea57f9 --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/frontend/packages/plugins/github-actions/src/plugin.test.ts b/frontend/packages/plugins/github-actions/src/plugin.test.ts new file mode 100644 index 0000000000..c202f56231 --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/plugin.test.ts @@ -0,0 +1,7 @@ +import plugin from './plugin'; + +describe('github-actions', () => { + it('should export plugin', () => { + expect(plugin).toBeDefined(); + }); +}); diff --git a/frontend/packages/plugins/github-actions/src/plugin.ts b/frontend/packages/plugins/github-actions/src/plugin.ts new file mode 100644 index 0000000000..359ba2e7f9 --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/plugin.ts @@ -0,0 +1,18 @@ +import { createPlugin } from '@backstage/core'; +import { entityViewPage } from '@backstage/core/src/api/plugin/outputs'; +import BuildPage from './components/BuildPage'; + +// export const buildListRoute = createEntityRoute<[]>('/builds') +// export const buildDetailsRoute = createEntityRoute<[number]>('/builds/:buildId') + +export default createPlugin({ + id: 'github-actions', + + register({ provide }) { + provide(entityViewPage, { + title: 'CI/CD', + path: 'builds', + component: BuildPage, + }); + }, +});