feat: proper api flow implementation

This commit is contained in:
Ivan Shmidt
2020-04-30 16:19:00 +02:00
parent a0c9155272
commit 6082897382
7 changed files with 137 additions and 54 deletions
+3 -1
View File
@@ -36,6 +36,8 @@ import {
loadSampleData,
} from '@backstage/plugin-tech-radar';
import { CircleCIApi, circleCIApiRef } from '@backstage/plugin-circleci';
const builder = ApiRegistry.builder();
export const alertApiForwarder = new AlertApiForwarder();
@@ -43,7 +45,7 @@ builder.add(alertApiRef, alertApiForwarder);
export const errorApiForwarder = new ErrorApiForwarder(alertApiForwarder);
builder.add(errorApiRef, errorApiForwarder);
builder.add(circleCIApiRef, new CircleCIApi());
builder.add(featureFlagsApiRef, new FeatureFlags());
builder.add(lighthouseApiRef, new LighthouseRestApi('http://localhost:3003'));
+61
View File
@@ -0,0 +1,61 @@
/*
* 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 { CircleCI, GitType, CircleCIOptions } from 'circleci-api';
import { ApiRef } from '@backstage/core';
const options: Partial<CircleCIOptions> = {
// Required for all requests
// token: CIRCLECI_TOKEN, // Set your CircleCi API token
// Optional
// Anything set here can be overriden when making the request
// Git information is required for project/build/etc endpoints
vcs: {
type: GitType.GITHUB, // default: github
owner: 'CircleCITest3',
repo: 'circleci-test',
},
};
export class CircleCIApi {
api: null | CircleCI = null;
constuctor() {}
async authenticate(token: string) {
try {
if (token === '') return Promise.reject();
this.api = new CircleCI({ ...options, token });
// await this.api.me();
return Promise.resolve();
} catch (e) {
this.api = null;
return this.cantAuth();
}
}
async cantAuth() {
return Promise.reject("Can't auth");
}
async getBuilds() {
if (!this.api) return this.cantAuth();
return this.api.builds();
}
}
export const circleCIApiRef = new ApiRef<CircleCIApi>({
id: 'plugin.circleci.service',
description: 'Used by the CircleCI plugin to make requests',
});
@@ -14,40 +14,22 @@
* limitations under the License.
*/
import React, { FC, useRef, useEffect } from 'react';
import React, { FC } from 'react';
// import Alert from '@material-ui/lab/Alert';
import { useAsync } from 'react-use';
// import { Progress } from '@backstage/core';
import { CircleCI, GitType, CircleCIOptions, BuildSummary } from 'circleci-api';
import { BuildSummary } from 'circleci-api';
import { CITable, CITableBuildInfo } from '../CITable';
const options: Partial<CircleCIOptions> = {
// Required for all requests
// token: CIRCLECI_TOKEN, // Set your CircleCi API token
// Optional
// Anything set here can be overriden when making the request
// Git information is required for project/build/etc endpoints
vcs: {
type: GitType.GITHUB, // default: github
owner: 'CircleCITest3',
repo: 'circleci-test',
},
// Optional query params for requests
// options: {
// branch: "master", // default: master
// }
};
import { circleCIApiRef } from 'api';
import { useApi } from '@backstage/core';
// "lifecycle" : "finished", // :queued, :scheduled, :not_run, :not_running, :running or :finished
// "outcome" : "failed", // :canceled, :infrastructure_fail, :timedout, :failed, :no_tests or :success
const makeReadableStatus = (status: string | undefined) => {
if (typeof status === 'undefined') return ""
if (typeof status === 'undefined') return '';
return ({
retried: 'Retried',
canceled: 'Canceled',
@@ -62,7 +44,8 @@ const makeReadableStatus = (status: string | undefined) => {
no_tests: 'No tests',
fixed: 'Fixed',
success: 'Success',
} as Record<string, string>)[status]};
} as Record<string, string>)[status];
};
const transform = (buildsData: BuildSummary[]): CITableBuildInfo[] => {
return buildsData.map(buildData => {
@@ -90,20 +73,19 @@ const transform = (buildsData: BuildSummary[]): CITableBuildInfo[] => {
});
};
export const CircleCIFetch: FC<{ token: string }> = ({ token }) => {
const api = useRef<CircleCI | null>(null);
useEffect(() => {
if (token !== '') api.current = new CircleCI({ ...options, token });
}, [token]);
export const CircleCIFetch: FC<{}> = () => {
const [builds, setBuilds] = React.useState<BuildSummary[]>([]);
const api = useApi(circleCIApiRef);
const { value, loading, error } = useAsync(() => {
if (api.current) return api.current.builds();
return Promise.reject('Api token not provided');
}, [token]);
React.useEffect(() => {
const intervalId = setInterval(() => {
if (!api.api) return;
api.getBuilds().then(setBuilds);
}, 1500);
return () => clearInterval(intervalId);
}, []);
if (loading) return <div>loading</div>;
if (error) return <div>{JSON.stringify(error, null, 2)}</div>;
const builds = transform(value || []);
return <CITable builds={builds} />;
if (!api.api) return <div>Not authenticated</div>;
const transformedBuilds = transform(builds || []);
return <CITable builds={transformedBuilds} />;
};
@@ -14,8 +14,8 @@
* limitations under the License.
*/
import React, { FC, useState } from 'react';
import { Typography, Grid, Input } from '@material-ui/core';
import React, { FC } from 'react';
import { Grid } from '@material-ui/core';
import {
InfoCard,
Header,
@@ -27,10 +27,9 @@ import {
SupportButton,
} from '@backstage/core';
import { CircleCIFetch } from '../CircleCIFetch';
import { LoginCard } from '../LoginCard';
export const CircleCIPage: FC<{}> = () => {
const [token, setToken] = useState<string>('');
return (
<Page theme={pageTheme.tool}>
<Header title="Welcome to circleci!" subtitle="Optional subtitle">
@@ -43,20 +42,11 @@ export const CircleCIPage: FC<{}> = () => {
</ContentHeader>
<Grid container spacing={3} direction="column">
<Grid item>
<InfoCard title="Information card">
<Typography variant="body1">
Please paste your CircleCI token here
<Input
onChange={e => setToken(e.target.value)}
type="password"
value={token}
></Input>
</Typography>
</InfoCard>
<LoginCard />
</Grid>
<Grid item>
<InfoCard title="CI/CD">
<CircleCIFetch token={token} />
<CircleCIFetch />
</InfoCard>
</Grid>
</Grid>
@@ -0,0 +1,46 @@
import React from 'react';
import {
Typography,
Button,
TextField,
List,
ListItem,
} from '@material-ui/core';
import { Person as PersonIcon } from '@material-ui/icons';
import { InfoCard, useApi } from '@backstage/core';
import { circleCIApiRef } from 'api';
export const LoginCard = () => {
const [token, setToken] = React.useState('');
const api = useApi(circleCIApiRef);
return (
<InfoCard>
<Typography variant="h6">
<PersonIcon /> CircleCI Auth
</Typography>
<List>
<ListItem>
<TextField
name="circleci-token"
type="password"
label="Token"
value={token}
onChange={e => setToken(e.target.value)}
/>
</ListItem>
<ListItem>
<Button
data-testid="github-auth-button"
variant="outlined"
color="primary"
onClick={() => api.authenticate(token)}
>
Authenticate
</Button>
</ListItem>
</List>
</InfoCard>
);
};
@@ -0,0 +1 @@
export * from './LoginCard';
+1
View File
@@ -15,3 +15,4 @@
*/
export { plugin } from './plugin';
export * from './api';