Removed Mock implementation and tried to add the real one
This commit is contained in:
@@ -21,7 +21,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.13",
|
||||
"@backstage/core": "^0.1.1-alpha.13",
|
||||
"@backstage/core-api": "^0.1.1-alpha.13",
|
||||
"@backstage/theme": "^0.1.1-alpha.13",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 { createApiRef } from '@backstage/core';
|
||||
import { Build, BuildDetails } from './types';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
export const githubActionsApiRef = createApiRef<GithubActionsApi>({
|
||||
id: 'plugin.githubactions.service',
|
||||
description: 'Used by the Github Actions plugin to make requests',
|
||||
});
|
||||
|
||||
export type GithubActionsApi = {
|
||||
listBuilds: (entity: Entity, token: Promise<string>) => Promise<Build[]>;
|
||||
getBuild: (buildUri: string, token: Promise<string>) => Promise<BuildDetails>;
|
||||
};
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
export class GithubActionsClient implements GithubActionsApi {
|
||||
async listBuilds(entity: Entity, token: Promise<string>): Promise<Build[]> {
|
||||
// ### 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';
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: new Headers({
|
||||
Authorization: `Bearer ${await token}`,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.status > 200) {
|
||||
return [
|
||||
{
|
||||
commitId: 'Error',
|
||||
message: 'ResponseCode > 200',
|
||||
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;
|
||||
|
||||
// ### 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.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.status > 200) {
|
||||
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;
|
||||
|
||||
// ### 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.uri = newData.url;
|
||||
dataBlank.logUrl = newData.logs_url;
|
||||
dataBlank.overviewUrl = newData.html_url;
|
||||
|
||||
return dataBlank;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 * from './GithubActionsApi';
|
||||
export * from './GithubActionsClient';
|
||||
export * from './types';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -32,8 +32,9 @@ import {
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useAsync } from 'react-use';
|
||||
import { BuildsClient } from '../../apis/builds';
|
||||
import { BuildStatusIndicator } from '../BuildStatusIndicator';
|
||||
import { useApi, githubAuthApiRef } from '@backstage/core-api';
|
||||
import { githubActionsApiRef } from '../../api';
|
||||
|
||||
const useStyles = makeStyles<Theme>(theme => ({
|
||||
root: {
|
||||
@@ -48,12 +49,14 @@ 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 status = useAsync(() => api.getBuild(buildUri, token), [buildUri]);
|
||||
|
||||
if (status.loading) {
|
||||
return <LinearProgress />;
|
||||
|
||||
@@ -27,10 +27,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 { useApi } from '@backstage/core-api';
|
||||
|
||||
const useStyles = makeStyles<Theme>(theme => ({
|
||||
root: {
|
||||
@@ -43,7 +42,8 @@ const useStyles = makeStyles<Theme>(theme => ({
|
||||
|
||||
export const BuildInfoCard = () => {
|
||||
const classes = useStyles();
|
||||
const status = useAsync(() => client.listBuilds('entity:spotify:backstage'));
|
||||
const api = useApi(githubActionsApiRef);
|
||||
const status = useAsync(() => api.listBuilds('entity:spotify:backstage'));
|
||||
|
||||
let content: JSX.Element;
|
||||
|
||||
|
||||
@@ -29,12 +29,12 @@ import {
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import React, { FC } 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 { useApi, githubAuthApiRef } from '@backstage/core-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
const LongText = ({ text, max }: { text: string; max: number }) => {
|
||||
if (text.length < max) {
|
||||
@@ -56,9 +56,13 @@ const useStyles = makeStyles<Theme>(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const PageContents = () => {
|
||||
const PageContents: FC<{ entity: Entity }> = ({ entity }) => {
|
||||
const api = useApi(githubActionsApiRef);
|
||||
const githubApi = useApi(githubAuthApiRef);
|
||||
const token = githubApi.getAccessToken('repo');
|
||||
|
||||
const { loading, error, value } = useAsync(() =>
|
||||
client.listBuilds('entity:spotify:backstage'),
|
||||
api.listBuilds(entity, token),
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
@@ -115,14 +119,14 @@ const PageContents = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const BuildListPage = () => {
|
||||
export const BuildListPage: FC<{ entity: Entity }> = ({ entity }) => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Typography variant="h3" className={classes.title}>
|
||||
CI/CD Builds
|
||||
</Typography>
|
||||
<PageContents />
|
||||
<PageContents entity={entity} />
|
||||
</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;
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
*/
|
||||
|
||||
export { plugin } from './plugin';
|
||||
export * from './api';
|
||||
|
||||
Reference in New Issue
Block a user