feat: builds

This commit is contained in:
Ivan Shmidt
2020-05-07 17:02:27 +02:00
parent fa05620b45
commit 85e5e1f70e
8 changed files with 132 additions and 65 deletions
+10 -3
View File
@@ -45,14 +45,21 @@ export class CircleCIApi {
}
async getBuilds(options: CircleCIOptions) {
return getBuildSummaries(options.token, { vcs: {}, ...options });
return getBuildSummaries(options.token, {
vcs: {},
circleHost: this.apiUrl,
...options,
});
}
async getUser(options: CircleCIOptions) {
return getMe(options.token, options);
return getMe(options.token, { circleHost: this.apiUrl, ...options });
}
async getBuild(buildNumber: number, options: CircleCIOptions) {
return getFullBuild(options.token, buildNumber, options);
return getFullBuild(options.token, buildNumber, {
circleHost: this.apiUrl,
...options,
});
}
}
+21 -7
View File
@@ -1,17 +1,31 @@
import { Route } from 'react-router';
import { Route, Switch } from 'react-router';
import React from 'react';
// import { BuildsPage } from 'pages/BuildsPage';
import { BuildsPage } from 'pages/BuildsPage';
// import { DetailedViewPage } from 'pages/DetailedViewPage';
import { SettingsPage } from 'pages/SettingsPage';
import { Provider } from 'react-redux';
import store from 'state/store';
import { Provider, useDispatch } from 'react-redux';
import store, { Dispatch } from 'state/store';
const RehydrateSettings = () => {
const dispatch: Dispatch = useDispatch();
React.useEffect(() => {
dispatch.settings.rehydrate();
}, []);
return null;
};
export const App = () => {
return (
<Provider store={store}>
{/* <Route path="/" component={BuildsPage} />
<Route path="/build/:buildId" component={DetailedViewPage} /> */}
<Route path="*" component={SettingsPage} />
<>
<RehydrateSettings />
<Switch>
<Route path="/circleci" component={BuildsPage} exact />
<Route path="/circleci/settings" component={SettingsPage} />
</Switch>
</>
{/* <Route path="/build/:buildId" component={DetailedViewPage} /> */}
</Provider>
);
};
@@ -22,9 +22,10 @@ import React, { FC } from 'react';
import { BuildSummary } from 'circleci-api';
import { CITable, CITableBuildInfo } from '../CITable';
import { circleCIApiRef } from 'api';
import { useSelector, useDispatch } from 'react-redux';
import { iRootState, Dispatch } from 'state/store';
import { useApi } from '@backstage/core';
import { circleCIApiRef } from 'api';
// "lifecycle" : "finished", // :queued, :scheduled, :not_run, :not_running, :running or :finished
// "outcome" : "failed", // :canceled, :infrastructure_fail, :timedout, :failed, :no_tests or :success
@@ -47,10 +48,7 @@ const makeReadableStatus = (status: string | undefined) => {
} as Record<string, string>)[status];
};
const transform = (
buildsData: BuildSummary[],
api: typeof circleCIApiRef.T,
): CITableBuildInfo[] => {
const transform = (buildsData: BuildSummary[]): CITableBuildInfo[] => {
return buildsData.map((buildData) => {
const tableBuildInfo: CITableBuildInfo = {
id: String(buildData.build_num),
@@ -58,7 +56,7 @@ const transform = (
? buildData.subject +
(buildData.retry_of ? ` (retry of #${buildData.retry_of})` : '')
: '',
onRetryClick: () => api.retry(String(buildData.build_num)),
onRetryClick: () => {}, //api.retry(String(buildData.build_num)),
source: {
branchName: String(buildData.branch),
commit: {
@@ -81,35 +79,17 @@ const transform = (
};
export const CircleCIFetch: FC<{}> = () => {
const [authed, setAuthed] = React.useState(false);
const [builds, setBuilds] = React.useState<BuildSummary[]>([]);
const dispatch: Dispatch = useDispatch();
const api = useApi(circleCIApiRef);
React.useEffect(() => {
const intervalId = setInterval(async () => {
if (!authed) {
await api.restorePersistedSettings();
await api
.validateToken()
.then(() => {
setAuthed(true);
})
.catch(() => setAuthed(false));
}
api.getBuilds().then(setBuilds);
}, 1500);
return () => clearInterval(intervalId);
}, [authed]);
dispatch.builds.startPolling(api);
return () => {
dispatch.builds.stopPolling();
};
}, []);
const { builds } = useSelector((state: iRootState) => state.builds);
const transformedBuilds = transform(builds);
if (!authed) return <div>Not authenticated</div>;
const transformedBuilds = transform(builds || [], api);
return (
<>
{!api.authed ? (
<div>Not authenticated</div>
) : (
<CITable builds={transformedBuilds} />
)}
</>
);
return <CITable builds={transformedBuilds} />;
};
@@ -20,20 +20,20 @@ export const SettingsPage = () => {
token: tokenFromStore,
owner: ownerFromStore,
repo: repoFromStore,
} = useSelector((state: iRootState): SettingsState => state.settings);
} = useSelector(
(state: iRootState): SettingsState =>
(console.log({ state }) as any) || state.settings,
);
const dispatch: Dispatch = useDispatch();
// const api = useApi(circleCIApiRef);
// const apiGitInfo = api.options.vcs;
// const [authed] = React.useState(false);
const [token, setToken] = React.useState('');
const [owner, setOwner] = React.useState('');
const [repo, setRepo] = React.useState('');
const [token, setToken] = React.useState(() => tokenFromStore);
const [owner, setOwner] = React.useState(() => ownerFromStore);
const [repo, setRepo] = React.useState(() => repoFromStore);
React.useEffect(() => {
dispatch.settings.rehydrate();
}, []);
const dispatch: Dispatch = useDispatch();
React.useEffect(() => () => console.log('Settings unmounterd'), []);
React.useEffect(() => {
if (tokenFromStore !== token) {
setToken(tokenFromStore);
@@ -71,7 +71,7 @@ export const SettingsPage = () => {
name="circleci-token"
label="Token"
value={token}
onChange={e => setToken(e.target.value)}
onChange={(e) => setToken(e.target.value)}
/>
</ListItem>
<ListItem>
@@ -79,7 +79,7 @@ export const SettingsPage = () => {
name="circleci-owner"
label="Owner"
value={owner}
onChange={e => setOwner(e.target.value)}
onChange={(e) => setOwner(e.target.value)}
/>
</ListItem>
<ListItem>
@@ -87,7 +87,7 @@ export const SettingsPage = () => {
name="circleci-repo"
label="Repo"
value={repo}
onChange={e => setRepo(e.target.value)}
onChange={(e) => setRepo(e.target.value)}
/>
</ListItem>
<ListItem>
+1 -1
View File
@@ -19,6 +19,6 @@ import { App } from './components/App';
export const plugin = createPlugin({
id: 'circleci',
register({ router }) {
router.registerRoute('/circleci', App);
router.registerRoute('/circleci', App, { exact: false });
},
});
@@ -0,0 +1,67 @@
import { Dispatch, iRootState } from '../store';
import { BuildSummary, GitType } from 'circleci-api';
import { CircleCIApi } from 'api';
export type BuildsState = {
builds: BuildSummary[];
pollingIntervalId: number | null;
pollingState: PollingState;
};
const INTERVAL_AMOUNT = 1500;
export enum PollingState {
Polling,
Idle,
}
export const builds = {
state: {
builds: [] as BuildSummary[],
pollingIntervalId: null,
pollingState: PollingState.Idle,
},
reducers: {
setBuilds(state: BuildsState, payload: BuildSummary[]) {
return { ...state, builds: payload };
},
setPollingIntervalId(state: BuildsState, payload: number | null) {
return {
...state,
pollingIntervalId: payload,
pollingState:
payload === null ? PollingState.Idle : PollingState.Polling,
};
},
},
effects: (dispatch: Dispatch) => ({
async getBuilds(api: CircleCIApi, state: iRootState) {
try {
const builds = await api.getBuilds({
token: state.settings.token,
vcs: {
owner: state.settings.owner,
repo: state.settings.repo,
type: GitType.GITHUB,
},
});
dispatch.builds.setBuilds(builds);
} catch (e) {
console.log(e);
}
},
startPolling(api: CircleCIApi, state: iRootState) {
if (state.builds.pollingIntervalId) return;
const intervalId = (setInterval(
() => dispatch.builds.getBuilds(api),
INTERVAL_AMOUNT,
) as any) as number;
dispatch.builds.setPollingIntervalId(intervalId);
},
stopPolling(_: any, state: iRootState) {
const currentIntervalId = state.builds.pollingIntervalId;
if (currentIntervalId) clearInterval(currentIntervalId);
dispatch.builds.setPollingIntervalId(null);
},
}),
};
+6 -1
View File
@@ -1,8 +1,13 @@
import { settings } from './settings';
import { builds } from './builds';
// no need to extend from Models
export interface RootModel {
settings: typeof settings;
builds: typeof builds;
}
export const models: RootModel = { settings };
export const models = {
settings,
builds,
};
+1 -7
View File
@@ -1,12 +1,6 @@
import { init, RematchRootState, RematchDispatch } from '@rematch/core';
import { models, RootModel } from './models';
const store = init({
models,
});
export type Store = typeof store;
export type Dispatch = RematchDispatch<RootModel>;
export type iRootState = RematchRootState<RootModel>;
export default store;
export default init({ models });