Add non working settings implemented with hooks
This commit is contained in:
@@ -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);
|
||||
}
|
||||
},
|
||||
}),
|
||||
};
|
||||
Reference in New Issue
Block a user