feat: builds
This commit is contained in:
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
}),
|
||||
};
|
||||
@@ -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,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 });
|
||||
|
||||
Reference in New Issue
Block a user