diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index df17886216..e80f031bf3 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -59,7 +59,7 @@ import { scaffolderApiRef, ScaffolderApi } from '@backstage/plugin-scaffolder'; import { rollbarApiRef, RollbarClient } from '@backstage/plugin-rollbar'; import { - MockGithubActionsClient, + GithubActionsClient, githubActionsApiRef, } from '@backstage/plugin-github-actions'; @@ -79,7 +79,7 @@ export const apis = (config: ConfigApi) => { builder.add(storageApiRef, WebStorage.create({ errorApi })); builder.add(circleCIApiRef, new CircleCIApi()); - builder.add(githubActionsApiRef, new MockGithubActionsClient()); + builder.add(githubActionsApiRef, new GithubActionsClient()); builder.add(featureFlagsApiRef, new FeatureFlags()); builder.add(lighthouseApiRef, new LighthouseRestApi('http://localhost:3003')); diff --git a/plugins/github-actions/src/api/GithubActionsApi.ts b/plugins/github-actions/src/api/GithubActionsApi.ts index c47cff7eda..dd75f34bf7 100644 --- a/plugins/github-actions/src/api/GithubActionsApi.ts +++ b/plugins/github-actions/src/api/GithubActionsApi.ts @@ -16,8 +16,6 @@ import { createApiRef } from '@backstage/core'; import { Build, BuildDetails } from './types'; -import { Entity } from '@backstage/catalog-model'; - export const githubActionsApiRef = createApiRef({ id: 'plugin.githubactions.service', @@ -25,6 +23,14 @@ export const githubActionsApiRef = createApiRef({ }); export type GithubActionsApi = { - listBuilds: (entity: Entity, token: Promise) => Promise; + listBuilds: ({ + owner, + repo, + token, + }: { + owner: string; + repo: string; + token: string; + }) => Promise; getBuild: (buildUri: string, token: Promise) => Promise; }; diff --git a/plugins/github-actions/src/api/GithubActionsClient.ts b/plugins/github-actions/src/api/GithubActionsClient.ts index 0c9a98e57c..93ace7fa38 100644 --- a/plugins/github-actions/src/api/GithubActionsClient.ts +++ b/plugins/github-actions/src/api/GithubActionsClient.ts @@ -16,26 +16,34 @@ import { GithubActionsApi } from './GithubActionsApi'; import { Build, BuildDetails, BuildStatus, WorkflowRun } from './types'; -import { Entity } from '@backstage/catalog-model'; + +const statusToBuildStatus: { [status: string]: BuildStatus } = { + success: BuildStatus.Success, + failure: BuildStatus.Failure, + pending: BuildStatus.Pending, + running: BuildStatus.Running, + in_progress: BuildStatus.Running, + completed: BuildStatus.Success, +}; + +const conclusionToStatus = (conslusion: string): BuildStatus => + statusToBuildStatus[conslusion] ?? BuildStatus.Null; export class GithubActionsClient implements GithubActionsApi { - async listBuilds(entity: Entity, token: Promise): Promise { - // ### Feedback request ### - // I asumed the following: (maybe not the best. Ideally this should come from the link to the component.yaml file) - // entity.metadata.namespace => org name - // entity.metadata.name => repo name - // entityUri -> entity:spotify:backstage - - let url: string; - if (entity.metadata.name !== '') { - url = `https://api.github.com/repos/${entity.metadata.namespace}/${entity.metadata.name}/runs`; - } else { - url = 'https://api.github.com/repos/spotify/backstage/actions/runs'; - } + async listBuilds({ + owner, + repo, + token, + }: { + owner: string; + repo: string; + token: string; + }): Promise { + const url = `https://api.github.com/repos/${owner}/${repo}/actions/runs`; const response = await fetch(url, { headers: new Headers({ - Authorization: `Bearer ${await token}`, + Authorization: `Bearer ${token}`, }), }); @@ -67,24 +75,7 @@ export class GithubActionsClient implements GithubActionsApi { }; transData.commitId = String(element.head_commit.id); transData.branch = element.head_branch; - - // ### Feedback request ### - // TODO: I am not sure about this part. Looks ugly. Maybe there is a better way of doing this. - if (element.conclusion === 'success') { - transData.status = BuildStatus.Success; - } else if (element.conclusion === 'failure') { - transData.status = BuildStatus.Failure; - } else if (element.conclusion === 'pending') { - transData.status = BuildStatus.Pending; - } else if (element.conclusion === 'running') { - transData.status = BuildStatus.Running; - } else { - if (element.status === 'in_progress') { - transData.status = BuildStatus.Running; - } else { - transData.status = BuildStatus.Null; - } - } + transData.status = conclusionToStatus(element.conclusion); transData.message = element.head_commit.message; transData.uri = element.url; endData[index] = transData; @@ -130,21 +121,7 @@ export class GithubActionsClient implements GithubActionsApi { dataBlank.build.branch = newData.head_branch; dataBlank.build.commitId = newData.head_commit.id; dataBlank.build.message = newData.head_commit.message; - - // ### Feedback request ### - // TODO: I am not sure about this part. Look ugly. Maybe there is a better way of doing this. - if (newData.status === 'completed') { - dataBlank.build.status = BuildStatus.Success; - } else if (newData.status === 'in_progress') { - dataBlank.build.status = BuildStatus.Running; - } else if (newData.status === 'pending') { - dataBlank.build.status = BuildStatus.Pending; - } else if (newData.status === 'failure') { - dataBlank.build.status = BuildStatus.Failure; - } else { - dataBlank.build.status = BuildStatus.Null; - } - + dataBlank.build.status = conclusionToStatus(newData.status); dataBlank.build.uri = newData.url; dataBlank.logUrl = newData.logs_url; dataBlank.overviewUrl = newData.html_url; diff --git a/plugins/github-actions/src/api/MockGithubActionsClient.ts b/plugins/github-actions/src/api/MockGithubActionsClient.ts deleted file mode 100644 index 89ecad3931..0000000000 --- a/plugins/github-actions/src/api/MockGithubActionsClient.ts +++ /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 { GithubActionsApi } from './GithubActionsApi'; -import { Build, BuildDetails, BuildStatus } from './types'; - -export class MockGithubActionsClient implements GithubActionsApi { - async listBuilds(): Promise { - return []; - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async getBuild(): Promise { - return { - build: { - commitId: 'TODO', - branch: 'TODO', - uri: 'TODO', - status: BuildStatus.Running, - message: 'TODO', - }, - author: 'TODO', - logUrl: 'TODO', - overviewUrl: 'TODO', - }; - } -} diff --git a/plugins/github-actions/src/api/index.test.ts b/plugins/github-actions/src/api/index.test.ts deleted file mode 100644 index feff353115..0000000000 --- a/plugins/github-actions/src/api/index.test.ts +++ /dev/null @@ -1,44 +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 { GithubActionsClient } from './GithubActionsClient'; -import { BuildStatus } from './types'; - -describe('Github Actions API', () => { - let client: GithubActionsClient; - beforeEach(() => { - client = new GithubActionsClient(); - }); - describe('Mock client', () => { - it('gets a list of builds by a project id', async () => { - await expect(client.listBuilds()).resolves.toEqual([]); - }); - it('gets a build info by its id', async () => { - await expect(client.getBuild()).resolves.toEqual({ - build: { - commitId: 'TODO', - branch: 'TODO', - uri: 'TODO', - status: BuildStatus.Running, - message: 'TODO', - }, - author: 'TODO', - logUrl: 'TODO', - overviewUrl: 'TODO', - }); - }); - }); -}); diff --git a/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx b/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx index 84e9332837..707c670f40 100644 --- a/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx +++ b/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { Link } from '@backstage/core'; import { Button, ButtonGroup, @@ -30,10 +29,10 @@ import { Typography, } from '@material-ui/core'; import React from 'react'; -import { useParams } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { useAsync } from 'react-use'; import { BuildStatusIndicator } from '../BuildStatusIndicator'; -import { useApi, githubAuthApiRef } from '@backstage/core-api'; +import { Link, useApi, githubAuthApiRef } from '@backstage/core'; import { githubActionsApiRef } from '../../api'; const useStyles = makeStyles(theme => ({ @@ -55,8 +54,12 @@ export const BuildDetailsPage = () => { const token = githubApi.getAccessToken('repo'); const classes = useStyles(); - const { buildUri } = useParams(); - const status = useAsync(() => api.getBuild(buildUri, token), [buildUri]); + const location = useLocation(); + const status = useAsync( + () => + api.getBuild(decodeURIComponent(location.search.split('uri=')[1]), token), + [location.search], + ); if (status.loading) { return ; @@ -73,7 +76,7 @@ export const BuildDetailsPage = () => { return (
- + < @@ -127,12 +130,12 @@ export const BuildDetailsPage = () => { > {details?.overviewUrl && ( )} {details?.logUrl && ( )} diff --git a/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx b/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx index 2e4cbb03bd..cbbc1da339 100644 --- a/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx +++ b/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { Link } from '@backstage/core'; import { LinearProgress, makeStyles, @@ -29,7 +28,7 @@ import React from 'react'; import { useAsync } from 'react-use'; import { BuildStatusIndicator } from '../BuildStatusIndicator'; import { githubActionsApiRef } from '../../api'; -import { useApi } from '@backstage/core-api'; +import { Link, useApi, githubAuthApiRef } from '@backstage/core'; const useStyles = makeStyles(theme => ({ root: { @@ -40,63 +39,69 @@ const useStyles = makeStyles(theme => ({ }, })); -export const BuildInfoCard = () => { - const classes = useStyles(); +const BuildInfoCardContent = () => { const api = useApi(githubActionsApiRef); - const status = useAsync(() => api.listBuilds('entity:spotify:backstage')); + const githubApi = useApi(githubAuthApiRef); - let content: JSX.Element; + const status = useAsync(async () => { + const token = await githubApi.getAccessToken('repo'); + return api.listBuilds({ owner: 'spotify', repo: 'backstage', token }); + }); if (status.loading) { - content = ; + return ; } else if (status.error) { - content = ( + return ( 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 - - - - - - -
- ); } + const [build] = + status.value?.filter(({ branch }) => branch === 'master') ?? []; + + return ( + + + + + Message + + + + {build?.message} + + + + + + Commit ID + + {build?.commitId} + + + + Status + + + + + + +
+ ); +}; + +export const BuildInfoCard = () => { + const classes = useStyles(); + return (
Master Build - {content} +
); }; diff --git a/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx b/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx index 85568c8c07..236bc182ef 100644 --- a/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx +++ b/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Link } from '@backstage/core'; +import { Link, useApi, githubAuthApiRef } from '@backstage/core'; import { LinearProgress, makeStyles, @@ -29,13 +29,10 @@ import { Tooltip, Typography, } from '@material-ui/core'; -import React, { FC } from 'react'; +import React from 'react'; import { useAsync } from 'react-use'; import { BuildStatusIndicator } from '../BuildStatusIndicator'; -import { githubActionsApiRef } from '../../api'; -import { useApi, githubAuthApiRef } from '@backstage/core-api'; -import { Entity } from '@backstage/catalog-model'; - +import { githubActionsApiRef, Build } from '../../api'; const LongText = ({ text, max }: { text: string; max: number }) => { if (text.length < max) { @@ -57,14 +54,15 @@ const useStyles = makeStyles(theme => ({ }, })); -const PageContents: FC<{ entity: Entity }> = ({ entity }) => { +const PageContents = ({ owner, repo }: { owner: string; repo: string }) => { const api = useApi(githubActionsApiRef); const githubApi = useApi(githubAuthApiRef); - const token = githubApi.getAccessToken('repo'); - const { loading, error, value } = useAsync(() => - api.listBuilds(entity, token), - ); + const { loading, error, value } = useAsync(async () => { + const token = await githubApi.getAccessToken('repo'); + + return api.listBuilds({ owner, repo, token }); + }, [githubApi, owner, repo]); if (loading) { return ; @@ -90,7 +88,7 @@ const PageContents: FC<{ entity: Entity }> = ({ entity }) => { - {value!.map(build => ( + {value?.map((build: Build) => ( @@ -101,7 +99,7 @@ const PageContents: FC<{ entity: Entity }> = ({ entity }) => {
- + @@ -120,14 +118,15 @@ const PageContents: FC<{ entity: Entity }> = ({ entity }) => { ); }; -export const BuildListPage: FC<{ entity: Entity }> = ({ entity }) => { +export const BuildListPage = () => { const classes = useStyles(); + return (
CI/CD Builds - +
); }; diff --git a/plugins/github-actions/src/plugin.ts b/plugins/github-actions/src/plugin.ts index 638c8d2c8c..8897fe53db 100644 --- a/plugins/github-actions/src/plugin.ts +++ b/plugins/github-actions/src/plugin.ts @@ -24,7 +24,7 @@ export const rootRouteRef = createRouteRef({ title: 'GitHub Actions', }); export const buildRouteRef = createRouteRef({ - path: '/github-actions/builds/:buildUri', + path: '/github-actions/builds', title: 'GitHub Actions Build', });