diff --git a/frontend/packages/plugins/github-actions/package.json b/frontend/packages/plugins/github-actions/package.json index cfe203d986..f1ad8c30e2 100644 --- a/frontend/packages/plugins/github-actions/package.json +++ b/frontend/packages/plugins/github-actions/package.json @@ -5,6 +5,7 @@ "main:src": "src/index.ts", "devDependencies": { "@backstage/core": "0.0.0", + "@backstage/protobuf-definitions": "0.0.0", "@spotify/web-scripts": "^6.0.0", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", diff --git a/frontend/packages/plugins/github-actions/src/apis/builds/BuildsClient.ts b/frontend/packages/plugins/github-actions/src/apis/builds/BuildsClient.ts new file mode 100644 index 0000000000..d435bffbad --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/apis/builds/BuildsClient.ts @@ -0,0 +1,59 @@ +import { buildsV1 } from '@backstage/protobuf-definitions'; +import { BuildStatus, Build, BuildDetails } from './types'; + +const statusTable = { + [buildsV1.BuildStatus.NULL]: BuildStatus.Null, + [buildsV1.BuildStatus.SUCCESS]: BuildStatus.Success, + [buildsV1.BuildStatus.FAILURE]: BuildStatus.Failure, + [buildsV1.BuildStatus.PENDING]: BuildStatus.Pending, + [buildsV1.BuildStatus.RUNNING]: BuildStatus.Running, +}; + +export default class BuildsClient { + static create(grpcAddress: string): BuildsClient { + return new BuildsClient(new buildsV1.Client(grpcAddress)); + } + + constructor(private readonly client: buildsV1.Client) {} + + async listBuilds(entityUri: string): Promise { + const req = new buildsV1.ListBuildsRequest(); + req.setEntityUri(entityUri); + + const res = await this.client.listBuilds(req); + + return res.getBuildsList().map(this.transformBuild); + } + + async getBuild(buildUri: string): Promise { + const req = new buildsV1.GetBuildRequest(); + req.setBuildUri(buildUri); + + const res = await this.client.getBuild(req); + + const build = res.getBuild(); + if (!build) { + throw new Error('No build in GetBuild response'); + } + const details = res.getDetails(); + if (!details) { + throw new Error('No details in GetBuild response'); + } + + return { + build: this.transformBuild(build), + author: details.getAuthor(), + logUrl: details.getLogUrl(), + overviewUrl: details.getOverviewUrl(), + }; + } + + private transformBuild = (build: buildsV1.Build): Build => { + return { + commitId: build.getCommitId(), + message: build.getMessage(), + status: statusTable[build.getStatus()] || BuildStatus.Null, + uri: build.getUri(), + }; + }; +} diff --git a/frontend/packages/plugins/github-actions/src/apis/builds/index.ts b/frontend/packages/plugins/github-actions/src/apis/builds/index.ts new file mode 100644 index 0000000000..8a294efdf5 --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/apis/builds/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export { default as BuildsClient } from './BuildsClient'; diff --git a/frontend/packages/plugins/github-actions/src/apis/builds/types.ts b/frontend/packages/plugins/github-actions/src/apis/builds/types.ts new file mode 100644 index 0000000000..3f4b205047 --- /dev/null +++ b/frontend/packages/plugins/github-actions/src/apis/builds/types.ts @@ -0,0 +1,21 @@ +export enum BuildStatus { + Null, + Success, + Failure, + Pending, + Running, +} + +export type Build = { + commitId: string; + message: string; + status: BuildStatus; + uri: string; +}; + +export type BuildDetails = { + build: Build; + author: string; + logUrl: string; + overviewUrl: string; +}; diff --git a/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx b/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx index 5cd5e21bf7..a9ba638302 100644 --- a/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx +++ b/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx @@ -1,19 +1,101 @@ import React, { FC } from 'react'; -import Button from '@material-ui/core/Button'; +import { BuildsClient } from '../../apis/builds'; +import { useAsync } from 'react-use'; +import { useRouteMatch } from 'react-router-dom'; +import { + LinearProgress, + Typography, + TableContainer, + Paper, + Table, + TableBody, + TableRow, + TableCell, + Link, + makeStyles, + ButtonGroup, + Button, +} from '@material-ui/core'; -type Props = { - buildId: string; -}; +const useStyles = makeStyles({ + root: { + maxWidth: 720, + }, +}); + +type Props = {}; + +const client = BuildsClient.create('http://localhost:8080'); + +const BuildDetailsPage: FC = () => { + const classes = useStyles(); + const match = useRouteMatch<{ buildUri: string }>(); + const buildUri = decodeURIComponent(match.params.buildUri); + const status = useAsync(() => client.getBuild(buildUri), [buildUri]); + + if (status.loading) { + return ; + } + if (status.error) { + return ( + + Failed to load build, {status.error} + + ); + } + + const details = status.value; -const BuildDetailsPage: FC = ({ buildId }) => { return ( - + + + + + + Message + + {details?.build.message} + + + + Commit ID + + {details?.build.commitId} + + + + Status + + {details?.build.status} + + + + Author + + {details?.author} + + + + Links + + + + + + + + + +
+
); }; 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 697d9edfa6..009d1df4ef 100644 --- a/frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx +++ b/frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx @@ -7,39 +7,79 @@ import { TableCell, Paper, TableBody, + LinearProgress, + Typography, + Tooltip, } from '@material-ui/core'; import { RelativeEntityLink } from '@backstage/core'; +import { BuildsClient } from '../../apis/builds'; +import { useAsync } from 'react-use'; + +const client = BuildsClient.create('http://localhost:8080'); + +const LongText: FC<{ text: string; max: number }> = ({ text, max }) => { + if (text.length < max) { + return {text}; + } + return ( + + {text.slice(0, max)}... + + ); +}; const BuildListPage: FC<{}> = () => { - const rows = [ - { message: 'Fixed a Bar', commit: 'fb46ca3dfbd7af5bc43da', id: 165 }, - { message: 'Fixed a Foo', commit: 'd7af5bc43dafb46ca3dfb', id: 164 }, - ]; + const status = useAsync(() => client.listBuilds('entity:spotify:backstage')); + + if (status.loading) { + return ; + } + if (status.error) { + return ( + + Failed to load builds, {status.error} + + ); + } + return ( - - - - - Message - Commit - Build ID - - - - {rows.map(row => ( - - {row.message} - {row.commit} - - - {row.commit} - - + <> + CI/CD Builds + +
+ + + Message + Commit + Status - ))} - -
-
+ + + {status.value!.map(build => ( + + + + + + + + + + + + {build.commitId.slice(0, 10)} + + + + {build.status} + + ))} + + + + ); }; diff --git a/frontend/packages/plugins/github-actions/src/plugin.ts b/frontend/packages/plugins/github-actions/src/plugin.ts index cecf142ca6..6972d8d128 100644 --- a/frontend/packages/plugins/github-actions/src/plugin.ts +++ b/frontend/packages/plugins/github-actions/src/plugin.ts @@ -11,6 +11,6 @@ export default createPlugin({ register({ entityPage }) { entityPage.navItem({ title: 'CI/CD', target: '/builds' }); entityPage.route('/builds', BuildListPage); - entityPage.route('/builds/:buildId', BuildDetailsPage); + entityPage.route('/builds/:buildUri', BuildDetailsPage); }, });