Add non working settings implemented with hooks

This commit is contained in:
Nikita Nek Dudnik
2020-05-13 19:08:41 +02:00
parent ba2b19d4c0
commit 7f56215da6
6 changed files with 173 additions and 175 deletions
@@ -15,10 +15,11 @@
*/
import React, { FC, useReducer, Dispatch } from 'react';
import { circleCIApiRef } from '../../api';
import { BuildSummary } from 'circleci-api';
export const SettingsContext = React.createContext<
[RootState, Dispatch<Action>]
>([] as any);
export const AppContext = React.createContext<[AppState, Dispatch<Action>]>(
[] as any,
);
type SettingsState = {
owner: string;
@@ -26,10 +27,22 @@ type SettingsState = {
token: string;
};
export enum PollingState {
Polling,
Idle,
}
export type BuildsState = {
builds: BuildSummary[];
pollingIntervalId: number | null;
pollingState: PollingState;
};
export const STORAGE_KEY = `${circleCIApiRef.id}.settings`;
type RootState = {
export type AppState = {
settings: SettingsState;
builds: BuildsState;
};
const initialState = {
@@ -38,9 +51,14 @@ const initialState = {
repo: '',
token: '',
},
builds: {
builds: [],
pollingIntervalId: null,
pollingState: PollingState.Idle,
},
};
type Action = {
type SettingsAction = {
type: 'setCredentials';
payload: {
repo: string;
@@ -49,12 +67,39 @@ type Action = {
};
};
const reducer = (state: RootState, action: Action): RootState => {
type BuildsAction =
| {
type: 'setBuilds';
payload: BuildSummary[];
}
| {
type: 'setPollingIntervalId';
payload: number | null;
};
type Action = SettingsAction | BuildsAction;
const reducer = (state: AppState, action: Action): AppState => {
switch (action.type) {
case 'setCredentials':
return {
...state,
settings: { ...state.settings, ...action.payload },
settings: { ...state.settings, ...(action.payload as {}) },
};
case 'setBuilds':
return {
...state,
builds: { ...state.builds, builds: action.payload },
};
case 'setPollingIntervalId':
return {
...state,
builds: {
...state.builds,
pollingIntervalId: action.payload,
pollingState:
action.payload === null ? PollingState.Idle : PollingState.Polling,
},
};
default:
return state;
@@ -64,9 +109,9 @@ const reducer = (state: RootState, action: Action): RootState => {
export const Store: FC = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<SettingsContext.Provider value={[state, dispatch]}>
<AppContext.Provider value={[state, dispatch]}>
<div>{children}</div>
</SettingsContext.Provider>
</AppContext.Provider>
);
};
@@ -0,0 +1,108 @@
/*
* 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 { GitType } from 'circleci-api';
import { useContext, useEffect } from 'react';
import { CircleCIApi } from '../../api';
import { circleCIApiRef } from '../../api/index';
import { AppContext, BuildsState } from '../../components/Store';
export type BuildsDispatch = {
startPolling: (api: CircleCIApi) => void;
stopPolling: () => void;
restartBuild: (buildId: number) => Promise<void>;
};
const INTERVAL_AMOUNT = 3000;
export function useBuilds(): [BuildsState, BuildsDispatch] {
const [{ builds, settings }, dispatch] = useContext(AppContext);
const api = useApi(circleCIApiRef);
const errorApi = useApi(errorApiRef);
const getBuilds = async () => {
console.log(settings);
if (settings.owner === '' || settings.repo === '') return;
try {
const newBuilds = await api.getBuilds({
token: settings.token,
vcs: {
owner: settings.owner,
repo: settings.repo,
type: GitType.GITHUB,
},
});
dispatch({
type: 'setBuilds',
payload: newBuilds,
});
} catch (e) {
errorApi.post(e);
}
};
const restartBuild = async (buildId: number) => {
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 = () => {
if (builds.pollingIntervalId) return;
const intervalId = (setInterval(
() => getBuilds(),
INTERVAL_AMOUNT,
) as any) as number;
dispatch({
type: 'setPollingIntervalId',
payload: intervalId,
});
};
const stopPolling = () => {
const currentIntervalId = builds.pollingIntervalId;
if (currentIntervalId) clearInterval(currentIntervalId);
dispatch({
type: 'setPollingIntervalId',
payload: null,
});
};
useEffect(() => {
startPolling();
return () => {
stopPolling();
};
}, []);
return [
builds,
{
restartBuild,
startPolling,
stopPolling,
},
];
}
@@ -13,12 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { FC, useEffect } from 'react';
import { useApi } from '@backstage/core';
import { CITable, CITableBuildInfo } from '../CITable';
import { useSelector, useDispatch } from 'react-redux';
import { iRootState, Dispatch } from '../../../../state/store';
import { circleCIApiRef, BuildSummary } from '../../../../api';
import React, { FC } from 'react';
import { CITableBuildInfo, CITable } from '../CITable';
import { BuildSummary } from '../../../../api';
import { useBuilds } from '../../builds';
import { useSettings } from '../../../SettingsPage/settings';
const makeReadableStatus = (status: string | undefined) => {
if (!status) return '';
@@ -41,8 +40,7 @@ const makeReadableStatus = (status: string | undefined) => {
const transform = (
buildsData: BuildSummary[],
dispatch: Dispatch,
api: typeof circleCIApiRef.T,
restartBuild: { (buildId: number): Promise<void> },
): CITableBuildInfo[] => {
return buildsData.map((buildData) => {
const tableBuildInfo: CITableBuildInfo = {
@@ -52,7 +50,8 @@ const transform = (
(buildData.retry_of ? ` (retry of #${buildData.retry_of})` : '')
: '',
onRetryClick: () =>
dispatch.builds.restartBuild({ buildId: buildData.build_num, api }),
typeof buildData.build_num !== 'undefined' &&
restartBuild(buildData.build_num),
source: {
branchName: String(buildData.branch),
commit: {
@@ -68,19 +67,9 @@ const transform = (
};
export const Builds: FC<{}> = () => {
const dispatch: Dispatch = useDispatch();
const api = useApi(circleCIApiRef);
useEffect(() => {
dispatch.builds.startPolling(api);
return () => {
dispatch.builds.stopPolling();
};
}, []);
const { builds } = useSelector((state: iRootState) => state.builds);
const { repo, owner } = useSelector((state: iRootState) => state.settings);
const transformedBuilds = transform(builds, dispatch, api);
const [{ builds }, { restartBuild }] = useBuilds();
const [{ repo, owner }] = useSettings();
const transformedBuilds = transform(builds, restartBuild);
return (
<CITable builds={transformedBuilds} projectName={`${owner}/${repo}`} />
@@ -1,72 +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 { useContext, useEffect } from 'react';
import { SettingsContext, STORAGE_KEY } from '../../components/Store';
import { useApi, errorApiRef } from '@backstage/core';
export type SettingsDispatch = {
saveSettings: (settings: SettingsState) => void;
};
export type SettingsState = {
token: string;
owner: string;
repo: string;
};
export function useSettings(): [SettingsState, SettingsDispatch] {
const [{ settings }, dispatch] = useContext(SettingsContext);
const errorApi = useApi(errorApiRef);
const rehydrate = () => {
try {
const stateFromStorage = JSON.parse(sessionStorage.getItem(STORAGE_KEY)!);
if (
stateFromStorage &&
Object.keys(stateFromStorage).some(
(k) => (settings as any)[k] !== stateFromStorage[k],
)
)
dispatch({
type: 'setCredentials',
payload: stateFromStorage,
});
} catch (error) {
errorApi.post(error);
}
};
useEffect(() => {
rehydrate();
}, []);
const persist = (state: SettingsState) => {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));
};
return [
settings,
{
saveSettings: (state: SettingsState) => {
persist(state);
dispatch({
type: 'setCredentials',
payload: state,
});
},
},
];
}
@@ -13,19 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { settings } from './settings';
import { builds } from './builds';
import { buildWithSteps } from './buildWithSteps';
// no need to extend from Models
export interface RootModel {
settings: typeof settings;
builds: typeof builds;
buildWithSteps: typeof buildWithSteps;
}
export const models = {
settings,
builds,
buildWithSteps,
};
@@ -1,69 +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 { circleCIApiRef } from '../../api';
const STORAGE_KEY = `${circleCIApiRef.id}.settings`;
export const settings = {
state: {
token: '',
owner: '',
repo: '',
}, // initial state
reducers: {
setCredentials(
state: SettingsState,
{ doPersist, ...credentials }: SettingsState & { doPersist: boolean },
) {
return { ...state, ...credentials };
},
},
effects: (dispatch: Dispatch) => ({
setCredentials({
doPersist = true,
...credentials
}: SettingsState & { doPersist: boolean }) {
if (doPersist) dispatch.settings.persist(credentials);
},
setRehydrateError(state: SettingsState, payload: Error | null) {
return { ...state, rehydrateError: payload };
},
persist(credentials: SettingsState) {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(credentials));
},
rehydrate(_: any, state: iRootState) {
try {
dispatch.settings.setRehydrateError(null);
const stateFromStorage = JSON.parse(
sessionStorage.getItem(STORAGE_KEY)!,
);
if (
stateFromStorage &&
Object.keys(stateFromStorage).some(
(k) => (state as any).settings[k] !== stateFromStorage[k],
)
)
dispatch.settings.setCredentials({
...stateFromStorage,
doPersist: false,
});
} catch (e) {
dispatch.settings.setRehydrateError(e);
}
},
}),
};