diff --git a/plugins/circleci/src/api/index.ts b/plugins/circleci/src/api/index.ts index b132b6a208..f7645ab760 100644 --- a/plugins/circleci/src/api/index.ts +++ b/plugins/circleci/src/api/index.ts @@ -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, + }); } } diff --git a/plugins/circleci/src/components/App.tsx b/plugins/circleci/src/components/App.tsx index 2dc92ea574..799016130f 100644 --- a/plugins/circleci/src/components/App.tsx +++ b/plugins/circleci/src/components/App.tsx @@ -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 ( - {/* - */} - + <> + + + + + + + {/* */} ); }; diff --git a/plugins/circleci/src/components/CircleCIFetch/CirleCIFetch.tsx b/plugins/circleci/src/components/CircleCIFetch/CirleCIFetch.tsx index abd4b2c363..68358dccd6 100644 --- a/plugins/circleci/src/components/CircleCIFetch/CirleCIFetch.tsx +++ b/plugins/circleci/src/components/CircleCIFetch/CirleCIFetch.tsx @@ -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)[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([]); + 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
Not authenticated
; - const transformedBuilds = transform(builds || [], api); - return ( - <> - {!api.authed ? ( -
Not authenticated
- ) : ( - - )} - - ); + return ; }; diff --git a/plugins/circleci/src/pages/SettingsPage/SettingsPage.tsx b/plugins/circleci/src/pages/SettingsPage/SettingsPage.tsx index 3d9e14f6c6..cd68577d3b 100644 --- a/plugins/circleci/src/pages/SettingsPage/SettingsPage.tsx +++ b/plugins/circleci/src/pages/SettingsPage/SettingsPage.tsx @@ -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)} /> @@ -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)} /> @@ -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)} /> diff --git a/plugins/circleci/src/plugin.ts b/plugins/circleci/src/plugin.ts index 064cce93fb..77a223a162 100644 --- a/plugins/circleci/src/plugin.ts +++ b/plugins/circleci/src/plugin.ts @@ -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 }); }, }); diff --git a/plugins/circleci/src/state/models/builds.ts b/plugins/circleci/src/state/models/builds.ts new file mode 100644 index 0000000000..71b5c4f4f2 --- /dev/null +++ b/plugins/circleci/src/state/models/builds.ts @@ -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); + }, + }), +}; diff --git a/plugins/circleci/src/state/models/index.ts b/plugins/circleci/src/state/models/index.ts index c02f31a6e1..39623b96f9 100644 --- a/plugins/circleci/src/state/models/index.ts +++ b/plugins/circleci/src/state/models/index.ts @@ -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, +}; diff --git a/plugins/circleci/src/state/store.ts b/plugins/circleci/src/state/store.ts index c06c44d8e6..43b447bf9c 100644 --- a/plugins/circleci/src/state/store.ts +++ b/plugins/circleci/src/state/store.ts @@ -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; export type iRootState = RematchRootState; - -export default store; +export default init({ models });