Merge branch 'master' of github.com:spotify/backstage into shmidt-i/github-actions-frontend-list-builds

This commit is contained in:
Ivan Shmidt
2020-07-13 14:40:13 +02:00
56 changed files with 918 additions and 268 deletions
@@ -14,25 +14,23 @@
* limitations under the License.
*/
export enum BuildStatus {
Null,
Success,
Failure,
Pending,
Running,
}
import { createApiRef } from '@backstage/core';
import { Build, BuildDetails } from './types';
export type Build = {
commitId: string;
message: string;
branch: string;
status: BuildStatus;
uri: string;
};
export const githubActionsApiRef = createApiRef<GithubActionsApi>({
id: 'plugin.githubactions.service',
description: 'Used by the Github Actions plugin to make requests',
});
export type BuildDetails = {
build: Build;
author: string;
logUrl: string;
overviewUrl: string;
export type GithubActionsApi = {
listBuilds: ({
owner,
repo,
token,
}: {
owner: string;
repo: string;
token: string;
}) => Promise<Build[]>;
getBuild: (buildUri: string, token: Promise<string>) => Promise<BuildDetails>;
};
@@ -0,0 +1,131 @@
/*
* 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, WorkflowRun } from './types';
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({
owner,
repo,
token,
}: {
owner: string;
repo: string;
token: string;
}): Promise<Build[]> {
const url = `https://api.github.com/repos/${owner}/${repo}/actions/runs`;
const response = await fetch(url, {
headers: new Headers({
Authorization: `Bearer ${token}`,
}),
});
if (!response.ok) {
return [
{
commitId: 'Error',
message: 'Response status is not OK',
branch: 'Error',
status: BuildStatus.Failure,
uri: 'Error',
},
];
}
const data = await response.json();
const newData: WorkflowRun[] = data.workflow_runs;
const endData: Build[] = [];
newData.forEach((element, index) => {
const transData: Build = {
commitId: '',
message: '',
branch: '',
status: BuildStatus.Null,
uri: '',
};
transData.commitId = String(element.head_commit.id);
transData.branch = element.head_branch;
transData.status = conclusionToStatus(element.conclusion);
transData.message = element.head_commit.message;
transData.uri = element.url;
endData[index] = transData;
});
return endData;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getBuild(
buildUri: string,
token: Promise<string>,
): Promise<BuildDetails> {
const response = await fetch(buildUri, {
headers: new Headers({
Authorization: `Bearer ${await token}`,
}),
});
const buildBlank: Build = {
commitId: '',
message: '',
branch: '',
status: BuildStatus.Null,
uri: '',
};
const dataBlank: BuildDetails = {
build: buildBlank,
author: '',
logUrl: '',
overviewUrl: '',
};
if (!response.ok) {
return dataBlank;
}
const data = await response.json();
const newData: WorkflowRun = data;
dataBlank.author = newData.head_commit.author.name;
dataBlank.build.branch = newData.head_branch;
dataBlank.build.commitId = newData.head_commit.id;
dataBlank.build.message = newData.head_commit.message;
dataBlank.build.status = conclusionToStatus(newData.status);
dataBlank.build.uri = newData.url;
dataBlank.logUrl = newData.logs_url;
dataBlank.overviewUrl = newData.html_url;
return dataBlank;
}
}
@@ -14,5 +14,6 @@
* limitations under the License.
*/
export { BuildsClient } from './BuildsClient';
export * from './GithubActionsApi';
export * from './GithubActionsClient';
export * from './types';
+224
View File
@@ -0,0 +1,224 @@
/*
* 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 enum BuildStatus {
Null,
Success,
Failure,
Pending,
Running,
}
export type Build = {
commitId: string;
message: string;
branch: string;
status: BuildStatus;
uri: string;
};
export type BuildDetails = {
build: Build;
author: string;
logUrl: string;
overviewUrl: string;
};
export interface Author {
name: string;
email: string;
}
export interface Committer {
name: string;
email: string;
}
export interface HeadCommit {
id: string;
tree_id: string;
message: string;
timestamp: Date;
author: Author;
committer: Committer;
}
export interface Owner {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
}
export interface Repository {
id: number;
node_id: string;
name: string;
full_name: string;
private: boolean;
owner: Owner;
html_url: string;
description?: any;
fork: boolean;
url: string;
forks_url: string;
keys_url: string;
collaborators_url: string;
teams_url: string;
hooks_url: string;
issue_events_url: string;
events_url: string;
assignees_url: string;
branches_url: string;
tags_url: string;
blobs_url: string;
git_tags_url: string;
git_refs_url: string;
trees_url: string;
statuses_url: string;
languages_url: string;
stargazers_url: string;
contributors_url: string;
subscribers_url: string;
subscription_url: string;
commits_url: string;
git_commits_url: string;
comments_url: string;
issue_comment_url: string;
contents_url: string;
compare_url: string;
merges_url: string;
archive_url: string;
downloads_url: string;
issues_url: string;
pulls_url: string;
milestones_url: string;
notifications_url: string;
labels_url: string;
releases_url: string;
deployments_url: string;
}
export interface Owner2 {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
}
export interface HeadRepository {
id: number;
node_id: string;
name: string;
full_name: string;
private: boolean;
owner: Owner2;
html_url: string;
description?: any;
fork: boolean;
url: string;
forks_url: string;
keys_url: string;
collaborators_url: string;
teams_url: string;
hooks_url: string;
issue_events_url: string;
events_url: string;
assignees_url: string;
branches_url: string;
tags_url: string;
blobs_url: string;
git_tags_url: string;
git_refs_url: string;
trees_url: string;
statuses_url: string;
languages_url: string;
stargazers_url: string;
contributors_url: string;
subscribers_url: string;
subscription_url: string;
commits_url: string;
git_commits_url: string;
comments_url: string;
issue_comment_url: string;
contents_url: string;
compare_url: string;
merges_url: string;
archive_url: string;
downloads_url: string;
issues_url: string;
pulls_url: string;
milestones_url: string;
notifications_url: string;
labels_url: string;
releases_url: string;
deployments_url: string;
}
export interface WorkflowRun {
id: number;
node_id: string;
head_branch: string;
head_sha: string;
run_number: number;
event: string;
status: string;
conclusion: string;
workflow_id: number;
url: string;
html_url: string;
pull_requests: any[];
created_at: Date;
updated_at: Date;
jobs_url: string;
logs_url: string;
check_suite_url: string;
artifacts_url: string;
cancel_url: string;
rerun_url: string;
workflow_url: string;
head_commit: HeadCommit;
repository: Repository;
head_repository: HeadRepository;
}
@@ -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 { Build, BuildDetails, BuildStatus } from './types';
export class BuildsClient {
static create(): BuildsClient {
return new BuildsClient();
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async listBuilds(_entityUri: string): Promise<Build[]> {
return [];
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getBuild(_buildUri: string): Promise<BuildDetails> {
return {
build: {
commitId: 'TODO',
branch: 'TODO',
uri: 'TODO',
status: BuildStatus.Running,
message: 'TODO',
},
author: 'TODO',
logUrl: 'TODO',
overviewUrl: 'TODO',
};
}
}
@@ -14,7 +14,6 @@
* limitations under the License.
*/
import { Link } from '@backstage/core';
import {
Button,
ButtonGroup,
@@ -30,10 +29,11 @@ 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 { BuildsClient } from '../../apis/builds';
import { BuildStatusIndicator } from '../BuildStatusIndicator';
import { Link, useApi, githubAuthApiRef } from '@backstage/core';
import { githubActionsApiRef } from '../../api';
const useStyles = makeStyles<Theme>(theme => ({
root: {
@@ -48,12 +48,18 @@ const useStyles = makeStyles<Theme>(theme => ({
},
}));
const client = BuildsClient.create();
export const BuildDetailsPage = () => {
const api = useApi(githubActionsApiRef);
const githubApi = useApi(githubAuthApiRef);
const token = githubApi.getAccessToken('repo');
const classes = useStyles();
const { buildUri } = useParams();
const status = useAsync(() => client.getBuild(buildUri), [buildUri]);
const location = useLocation();
const status = useAsync(
() =>
api.getBuild(decodeURIComponent(location.search.split('uri=')[1]), token),
[location.search],
);
if (status.loading) {
return <LinearProgress />;
@@ -70,7 +76,7 @@ export const BuildDetailsPage = () => {
return (
<div className={classes.root}>
<Typography className={classes.title} variant="h3">
<Link to="/builds">
<Link to="/github-actions">
<Typography component="span" variant="h3" color="primary">
&lt;
</Typography>
@@ -124,12 +130,12 @@ export const BuildDetailsPage = () => {
>
{details?.overviewUrl && (
<Button>
<Link to={details.overviewUrl}>GitHub</Link>
<a href={details.overviewUrl}>GitHub</a>
</Button>
)}
{details?.logUrl && (
<Button>
<Link to={details.logUrl}>Logs</Link>
<a href={details.logUrl}>Logs</a>
</Button>
)}
</ButtonGroup>
@@ -14,7 +14,6 @@
* limitations under the License.
*/
import { Link } from '@backstage/core';
import {
LinearProgress,
makeStyles,
@@ -27,10 +26,9 @@ import {
} from '@material-ui/core';
import React from 'react';
import { useAsync } from 'react-use';
import { BuildsClient } from '../../apis/builds';
import { BuildStatusIndicator } from '../BuildStatusIndicator';
const client = BuildsClient.create();
import { githubActionsApiRef } from '../../api';
import { Link, useApi, githubAuthApiRef } from '@backstage/core';
const useStyles = makeStyles<Theme>(theme => ({
root: {
@@ -41,62 +39,69 @@ const useStyles = makeStyles<Theme>(theme => ({
},
}));
export const BuildInfoCard = () => {
const classes = useStyles();
const status = useAsync(() => client.listBuilds('entity:spotify:backstage'));
const BuildInfoCardContent = () => {
const api = useApi(githubActionsApiRef);
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 = <LinearProgress />;
return <LinearProgress />;
} else if (status.error) {
content = (
return (
<Typography variant="h2" color="error">
Failed to load builds, {status.error.message}
</Typography>
);
} else {
const [build] =
status.value?.filter(({ branch }) => branch === 'master') ?? [];
content = (
<Table>
<TableBody>
<TableRow>
<TableCell>
<Typography noWrap>Message</Typography>
</TableCell>
<TableCell>
<Link to={`builds/${encodeURIComponent(build?.uri || '')}`}>
<Typography color="primary">{build?.message}</Typography>
</Link>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Commit ID</Typography>
</TableCell>
<TableCell>{build?.commitId}</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Status</Typography>
</TableCell>
<TableCell>
<BuildStatusIndicator status={build?.status} />
</TableCell>
</TableRow>
</TableBody>
</Table>
);
}
const [build] =
status.value?.filter(({ branch }) => branch === 'master') ?? [];
return (
<Table>
<TableBody>
<TableRow>
<TableCell>
<Typography noWrap>Message</Typography>
</TableCell>
<TableCell>
<Link to={`builds/${encodeURIComponent(build?.uri || '')}`}>
<Typography color="primary">{build?.message}</Typography>
</Link>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Commit ID</Typography>
</TableCell>
<TableCell>{build?.commitId}</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Status</Typography>
</TableCell>
<TableCell>
<BuildStatusIndicator status={build?.status} />
</TableCell>
</TableRow>
</TableBody>
</Table>
);
};
export const BuildInfoCard = () => {
const classes = useStyles();
return (
<div className={classes.root}>
<Typography variant="h2" className={classes.title}>
Master Build
</Typography>
{content}
<BuildInfoCardContent />
</div>
);
};
@@ -21,7 +21,7 @@ import SuccessIcon from '@material-ui/icons/CheckCircle';
import FailureIcon from '@material-ui/icons/Error';
import UnknownIcon from '@material-ui/icons/Help';
import React from 'react';
import { BuildStatus } from '../../apis/builds';
import { BuildStatus } from '../../api/types';
type Props = {
status?: BuildStatus;
+1
View File
@@ -15,3 +15,4 @@
*/
export { plugin } from './plugin';
export * from './api';
+1 -1
View File
@@ -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',
});