Removed Mock implementation and tried to add the real one

This commit is contained in:
ebarrios
2020-07-10 15:48:03 +02:00
parent eed37376fc
commit 5e93d376b5
11 changed files with 497 additions and 17 deletions
+2
View File
@@ -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',
});
});
});
});
+19
View File
@@ -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';
+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;
}
@@ -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;
+1
View File
@@ -15,3 +15,4 @@
*/
export { plugin } from './plugin';
export * from './api';