refactor: detailedViewPage moved to hooks
Co-authored-by: Nikita Dudnik <nikdudnik@gmail.com>
This commit is contained in:
@@ -24,10 +24,11 @@ import {
|
||||
BuildWithSteps,
|
||||
BuildStepAction,
|
||||
BuildSummary,
|
||||
GitType,
|
||||
} from 'circleci-api';
|
||||
import { ApiRef } from '@backstage/core';
|
||||
|
||||
export { BuildWithSteps, BuildStepAction, BuildSummary };
|
||||
export { BuildWithSteps, BuildStepAction, BuildSummary, GitType };
|
||||
|
||||
export const circleCIApiRef = new ApiRef<CircleCIApi>({
|
||||
id: 'plugin.circleci.service',
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import React, { FC, useReducer, Dispatch } from 'react';
|
||||
import { circleCIApiRef } from '../../api';
|
||||
import { BuildSummary } from 'circleci-api';
|
||||
import { BuildSummary, BuildWithSteps } from 'circleci-api';
|
||||
|
||||
export const AppContext = React.createContext<[AppState, Dispatch<Action>]>(
|
||||
[] as any,
|
||||
@@ -43,9 +43,10 @@ export const STORAGE_KEY = `${circleCIApiRef.id}.settings`;
|
||||
export type AppState = {
|
||||
settings: SettingsState;
|
||||
builds: BuildsState;
|
||||
buildsWithSteps: BuildsWithStepsState;
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
const initialState: AppState = {
|
||||
settings: {
|
||||
owner: '',
|
||||
repo: '',
|
||||
@@ -56,6 +57,12 @@ const initialState = {
|
||||
pollingIntervalId: null,
|
||||
pollingState: PollingState.Idle,
|
||||
},
|
||||
buildsWithSteps: {
|
||||
builds: {},
|
||||
pollingIntervalId: null,
|
||||
pollingState: PollingState.Idle,
|
||||
getBuildError: null,
|
||||
},
|
||||
};
|
||||
|
||||
type SettingsAction = {
|
||||
@@ -77,7 +84,24 @@ type BuildsAction =
|
||||
payload: number | null;
|
||||
};
|
||||
|
||||
type Action = SettingsAction | BuildsAction;
|
||||
type BuildsWithStepsAction =
|
||||
| {
|
||||
type: 'setBuildWithSteps';
|
||||
payload: BuildWithSteps;
|
||||
}
|
||||
| {
|
||||
type: 'setPollingIntervalIdForBuildsWithSteps';
|
||||
payload: number | null;
|
||||
};
|
||||
|
||||
export type BuildsWithStepsState = {
|
||||
builds: Record<number, BuildWithSteps>;
|
||||
pollingIntervalId: number | null;
|
||||
pollingState: PollingState;
|
||||
getBuildError: Error | null;
|
||||
};
|
||||
|
||||
type Action = SettingsAction | BuildsAction | BuildsWithStepsAction;
|
||||
|
||||
const reducer = (state: AppState, action: Action): AppState => {
|
||||
switch (action.type) {
|
||||
@@ -91,11 +115,37 @@ const reducer = (state: AppState, action: Action): AppState => {
|
||||
...state,
|
||||
builds: { ...state.builds, builds: action.payload },
|
||||
};
|
||||
case 'setBuildWithSteps': {
|
||||
if (state.buildsWithSteps.pollingState !== PollingState.Polling) {
|
||||
return state;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
buildsWithSteps: {
|
||||
...state.buildsWithSteps,
|
||||
builds: {
|
||||
...state.buildsWithSteps.builds,
|
||||
[action.payload.build_num!]: action.payload,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'setPollingIntervalId':
|
||||
return {
|
||||
...state,
|
||||
builds: {
|
||||
...state.builds,
|
||||
pollingIntervalId: action.payload,
|
||||
pollingState:
|
||||
action.payload === null ? PollingState.Idle : PollingState.Polling,
|
||||
},
|
||||
};
|
||||
case 'setPollingIntervalIdForBuildsWithSteps':
|
||||
return {
|
||||
...state,
|
||||
buildsWithSteps: {
|
||||
...state.buildsWithSteps,
|
||||
|
||||
pollingIntervalId: action.payload,
|
||||
pollingState:
|
||||
action.payload === null ? PollingState.Idle : PollingState.Polling,
|
||||
|
||||
@@ -14,17 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { FC } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Content, InfoCard, useApi } from '@backstage/core';
|
||||
import { circleCIApiRef, BuildWithSteps, BuildStepAction } from '../../api';
|
||||
import { Content, InfoCard } from '@backstage/core';
|
||||
import { BuildWithSteps, BuildStepAction } from '../../api';
|
||||
import { Grid, Box } from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { PluginHeader } from '../../components/PluginHeader';
|
||||
import { ActionOutput } from './lib/ActionOutput/ActionOutput';
|
||||
import { Layout } from '../../components/Layout';
|
||||
import { Dispatch, iRootState } from '../../state/store';
|
||||
import { withStore } from '../../components/Store';
|
||||
import { useBuildWithSteps } from './hooks';
|
||||
|
||||
const BuildName: FC<{ build: BuildWithSteps | null }> = ({ build }) => (
|
||||
<>
|
||||
@@ -91,17 +90,8 @@ const pickClassName = (
|
||||
const DetailedViewPage: FC<{}> = () => {
|
||||
const { buildId = '' } = useParams();
|
||||
const classes = useStyles();
|
||||
const dispatch: Dispatch = useDispatch();
|
||||
const api = useApi(circleCIApiRef);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch.buildWithSteps.startPolling({ api, buildId: Number(buildId) });
|
||||
return () => {
|
||||
dispatch.buildWithSteps.stopPolling();
|
||||
};
|
||||
}, []);
|
||||
const { builds } = useSelector((state: iRootState) => state.buildWithSteps);
|
||||
const build = builds[parseInt(buildId, 10)];
|
||||
const [build] = useBuildWithSteps(parseInt(buildId, 10));
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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 { errorApiRef, useApi } from '@backstage/core';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { circleCIApiRef, GitType } from '../../api/index';
|
||||
import { AppContext } from '../../components/Store';
|
||||
import { useSettings } from '../SettingsPage/settings';
|
||||
|
||||
const INTERVAL_AMOUNT = 3000;
|
||||
|
||||
export function useBuildWithSteps(buildId: number) {
|
||||
const [settings] = useSettings();
|
||||
const [{ buildsWithSteps }, dispatch] = useContext(AppContext);
|
||||
const api = useApi(circleCIApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
|
||||
const getBuildWithSteps = async () => {
|
||||
try {
|
||||
const options = {
|
||||
token: settings.token,
|
||||
vcs: {
|
||||
owner: settings.owner,
|
||||
repo: settings.repo,
|
||||
type: GitType.GITHUB,
|
||||
},
|
||||
};
|
||||
const build = await api.getBuild(buildId, options);
|
||||
dispatch({ type: 'setBuildWithSteps', payload: build });
|
||||
} catch (e) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
};
|
||||
|
||||
const restartBuild = async () => {
|
||||
try {
|
||||
await api.retry(buildId, {
|
||||
token: settings.token,
|
||||
vcs: {
|
||||
owner: settings.owner,
|
||||
repo: settings.repo,
|
||||
type: GitType.GITHUB,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
};
|
||||
|
||||
const startPolling = () => {
|
||||
stopPolling();
|
||||
const intervalId = (setInterval(
|
||||
() => getBuildWithSteps(),
|
||||
INTERVAL_AMOUNT,
|
||||
) as any) as number;
|
||||
dispatch({
|
||||
type: 'setPollingIntervalIdForBuildsWithSteps',
|
||||
payload: intervalId,
|
||||
});
|
||||
};
|
||||
|
||||
const stopPolling = () => {
|
||||
const currentIntervalId = buildsWithSteps.pollingIntervalId;
|
||||
if (currentIntervalId) clearInterval(currentIntervalId);
|
||||
dispatch({
|
||||
type: 'setPollingIntervalIdForBuildsWithSteps',
|
||||
payload: null,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
startPolling();
|
||||
return () => {
|
||||
stopPolling();
|
||||
};
|
||||
}, [buildId, settings]);
|
||||
|
||||
const build = buildsWithSteps.builds[buildId];
|
||||
|
||||
return [
|
||||
build,
|
||||
{
|
||||
restartBuild,
|
||||
startPolling,
|
||||
stopPolling,
|
||||
getBuildWithSteps,
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* 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 { Dispatch, iRootState } from '../store';
|
||||
import { GitType, BuildWithSteps } from 'circleci-api';
|
||||
import { CircleCIApi } from '../../api';
|
||||
|
||||
export type BuildState = {
|
||||
builds: Record<number, BuildWithSteps>;
|
||||
pollingIntervalId: number | null;
|
||||
pollingState: PollingState;
|
||||
getBuildError: Error | null;
|
||||
};
|
||||
|
||||
const INTERVAL_AMOUNT = 1500;
|
||||
|
||||
export enum PollingState {
|
||||
Polling,
|
||||
Idle,
|
||||
}
|
||||
export const buildWithSteps = {
|
||||
state: {
|
||||
builds: {},
|
||||
pollingIntervalId: null,
|
||||
pollingState: PollingState.Idle,
|
||||
getBuildError: null,
|
||||
} as BuildState,
|
||||
reducers: {
|
||||
setBuild(state: BuildState, payload: BuildWithSteps) {
|
||||
if (state.pollingState !== PollingState.Polling) {
|
||||
return state;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
builds: { ...state.builds, [payload.build_num!]: payload },
|
||||
};
|
||||
},
|
||||
setBuildError(state: BuildState, payload: Error | null) {
|
||||
return { ...state, getBuildError: payload };
|
||||
},
|
||||
setPollingIntervalId(state: BuildState, payload: number | null) {
|
||||
return {
|
||||
...state,
|
||||
pollingIntervalId: payload,
|
||||
pollingState:
|
||||
payload === null ? PollingState.Idle : PollingState.Polling,
|
||||
};
|
||||
},
|
||||
},
|
||||
effects: (dispatch: Dispatch) => ({
|
||||
async getBuild(
|
||||
{ api, buildId }: { api: CircleCIApi; buildId: number },
|
||||
state: iRootState,
|
||||
) {
|
||||
try {
|
||||
dispatch.buildWithSteps.setBuildError(null);
|
||||
const options = {
|
||||
token: state.settings.token,
|
||||
vcs: {
|
||||
owner: state.settings.owner,
|
||||
repo: state.settings.repo,
|
||||
type: GitType.GITHUB,
|
||||
},
|
||||
};
|
||||
const build = await api.getBuild(buildId, options);
|
||||
dispatch.buildWithSteps.setBuild(build);
|
||||
} catch (e) {
|
||||
dispatch.buildWithSteps.setBuildError(e);
|
||||
}
|
||||
},
|
||||
startPolling(
|
||||
{ api, buildId }: { api: CircleCIApi; buildId: number },
|
||||
state: iRootState,
|
||||
) {
|
||||
if (state.buildWithSteps.pollingIntervalId) return;
|
||||
|
||||
const intervalId = (setInterval(
|
||||
() => dispatch.buildWithSteps.getBuild({ buildId, api }),
|
||||
INTERVAL_AMOUNT,
|
||||
) as any) as number;
|
||||
dispatch.buildWithSteps.setPollingIntervalId(intervalId);
|
||||
},
|
||||
stopPolling(_: any, state: iRootState) {
|
||||
const currentIntervalId = state.buildWithSteps.pollingIntervalId;
|
||||
if (currentIntervalId) clearInterval(currentIntervalId);
|
||||
dispatch.buildWithSteps.setPollingIntervalId(null);
|
||||
},
|
||||
}),
|
||||
};
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* 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 { Dispatch, iRootState } from '../store';
|
||||
import { BuildSummary, GitType } from 'circleci-api';
|
||||
import { CircleCIApi } from '../../api';
|
||||
|
||||
export type BuildsState = {
|
||||
builds: BuildSummary[];
|
||||
pollingIntervalId: number | null;
|
||||
pollingState: PollingState;
|
||||
restartBuildError: Error | null;
|
||||
getBuildsError: Error | null;
|
||||
};
|
||||
|
||||
const INTERVAL_AMOUNT = 1500;
|
||||
|
||||
export enum PollingState {
|
||||
Polling,
|
||||
Idle,
|
||||
}
|
||||
export const builds = {
|
||||
state: {
|
||||
builds: [] as BuildSummary[],
|
||||
pollingIntervalId: null,
|
||||
pollingState: PollingState.Idle,
|
||||
restartBuildError: null,
|
||||
getBuildsError: null,
|
||||
},
|
||||
reducers: {
|
||||
setBuilds(state: BuildsState, payload: BuildSummary[]) {
|
||||
if (state.pollingState !== PollingState.Polling) {
|
||||
return state;
|
||||
}
|
||||
return { ...state, builds: payload };
|
||||
},
|
||||
setPollingIntervalId(state: BuildsState, payload: number | null) {
|
||||
return {
|
||||
...state,
|
||||
pollingIntervalId: payload,
|
||||
pollingState:
|
||||
payload === null ? PollingState.Idle : PollingState.Polling,
|
||||
};
|
||||
},
|
||||
setRestartBuildError(state: BuildsState, payload: Error | null) {
|
||||
return { ...state, restartBuildError: payload };
|
||||
},
|
||||
setGetBuildsError(state: BuildsState, payload: Error | null) {
|
||||
return { ...state, getBuildsError: payload };
|
||||
},
|
||||
},
|
||||
effects: (dispatch: Dispatch) => ({
|
||||
async getBuilds(api: CircleCIApi, state: iRootState) {
|
||||
try {
|
||||
dispatch.builds.setGetBuildsError(null);
|
||||
const newBuilds = await api.getBuilds({
|
||||
token: state.settings.token,
|
||||
vcs: {
|
||||
owner: state.settings.owner,
|
||||
repo: state.settings.repo,
|
||||
type: GitType.GITHUB,
|
||||
},
|
||||
});
|
||||
dispatch.builds.setBuilds(newBuilds);
|
||||
} catch (e) {
|
||||
dispatch.builds.setGetBuildsError(null);
|
||||
}
|
||||
},
|
||||
async restartBuild(
|
||||
{ api, buildId }: { api: CircleCIApi; buildId: number },
|
||||
state: iRootState,
|
||||
) {
|
||||
try {
|
||||
dispatch.builds.setRestartBuildError(null);
|
||||
await api.retry(buildId, {
|
||||
token: state.settings.token,
|
||||
vcs: {
|
||||
owner: state.settings.owner,
|
||||
repo: state.settings.repo,
|
||||
type: GitType.GITHUB,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
dispatch.builds.setRestartBuildError(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,28 +0,0 @@
|
||||
/*
|
||||
* 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 { builds } from './builds';
|
||||
import { buildWithSteps } from './buildWithSteps';
|
||||
|
||||
// no need to extend from Models
|
||||
export interface RootModel {
|
||||
builds: typeof builds;
|
||||
buildWithSteps: typeof buildWithSteps;
|
||||
}
|
||||
|
||||
export const models = {
|
||||
builds,
|
||||
buildWithSteps,
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* 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 { init, RematchRootState, RematchDispatch } from '@rematch/core';
|
||||
import { models, RootModel } from './models';
|
||||
|
||||
export type Dispatch = RematchDispatch<RootModel>;
|
||||
export type iRootState = RematchRootState<RootModel>;
|
||||
export default init({ models });
|
||||
Reference in New Issue
Block a user