Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

frontend: clusterActionSlice: Refactor clusterActions into a slice #1446

Merged
merged 1 commit into from
Nov 13, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
350 changes: 203 additions & 147 deletions frontend/package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -73,7 +73,6 @@
"react-scripts": "5.0.0",
"react-window": "^1.8.7",
"recharts": "^2.1.4",
"redux-saga": "^1.1.3",
"semver": "^7.3.5",
"spacetime": "^7.4.0",
"stream-browserify": "^3.0.0",
@@ -191,6 +190,8 @@
"@storybook/preset-create-react-app": "^4.1.1",
"@storybook/react": "6.4.13",
"@testing-library/user-event": "^14.5.1",
"@types/redux-mock-store": "^1.0.4",
"fetch-mock": "^9.11.0",
"http-proxy-middleware": "^2.0.1",
"husky": "^4.3.8",
"i18next-parser": "^4.7.0",
@@ -199,6 +200,7 @@
"msw": "^0.47.4",
"msw-storybook-addon": "^1.6.3",
"prettier": "^2.4.1",
"redux-mock-store": "^1.5.4",
"resize-observer-polyfill": "^1.5.1",
"typedoc": "0.22.10",
"typedoc-hugo-theme": "1.0.0",
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Meta, Story } from '@storybook/react/types-6-0';
import React from 'react';
import store from '../../../redux/stores/store';
import { TestContext } from '../../../test';
import { PluginSettingsPure, PluginSettingsPureProps } from './PluginSettings';

@@ -10,7 +8,7 @@ export default {
} as Meta;

const Template: Story<PluginSettingsPureProps> = args => (
<TestContext store={store}>
<TestContext>
<PluginSettingsPure {...args} />
</TestContext>
);
1 change: 0 additions & 1 deletion frontend/src/components/common/ActionsNotifier.stories.tsx
Original file line number Diff line number Diff line change
@@ -29,7 +29,6 @@ Some.args = {
'1': {
id: '1',
url: '/tmp',
key: '1',
dismissSnackbar: '1',
message: 'Some message',
snackbarProps: {},
19 changes: 9 additions & 10 deletions frontend/src/components/common/ActionsNotifier.tsx
Original file line number Diff line number Diff line change
@@ -4,8 +4,7 @@ import { useSnackbar } from 'notistack';
import React from 'react';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { CLUSTER_ACTION_GRACE_PERIOD } from '../../lib/util';
import { ClusterAction } from '../../redux/actions/actions';
import { CLUSTER_ACTION_GRACE_PERIOD, ClusterAction } from '../../redux/clusterActionSlice';
import { useTypedSelector } from '../../redux/reducers/reducers';

export interface PureActionsNotifierProps {
@@ -50,14 +49,14 @@ function PureActionsNotifier({ dispatch, clusterActions }: PureActionsNotifierPr
closeSnackbar(clusterAction.dismissSnackbar);
}

const { key, message, snackbarProps } = clusterAction;
enqueueSnackbar(message, {
key,
preventDuplicate: true,
autoHideDuration: CLUSTER_ACTION_GRACE_PERIOD,
action,
...snackbarProps,
});
if (clusterAction.message) {
enqueueSnackbar(clusterAction.message, {
key: clusterAction.key,
autoHideDuration: clusterAction.autoHideDuration || CLUSTER_ACTION_GRACE_PERIOD,
action,
...clusterAction.snackbarProps,
});
}
}

React.useEffect(
2 changes: 1 addition & 1 deletion frontend/src/components/common/Resource/CreateButton.tsx
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { apply } from '../../../lib/k8s/apiProxy';
import { KubeObjectInterface } from '../../../lib/k8s/cluster';
import { clusterAction } from '../../../redux/actions/actions';
import { clusterAction } from '../../../redux/clusterActionSlice';
import ActionButton from '../ActionButton';
import EditorDialog from './EditorDialog';

2 changes: 1 addition & 1 deletion frontend/src/components/common/Resource/DeleteButton.tsx
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { KubeObject } from '../../../lib/k8s/cluster';
import { CallbackActionOptions, clusterAction } from '../../../redux/actions/actions';
import { CallbackActionOptions, clusterAction } from '../../../redux/clusterActionSlice';
import ActionButton from '../ActionButton';
import { ConfirmDialog } from '../Dialog';
import AuthVisible from './AuthVisible';
2 changes: 1 addition & 1 deletion frontend/src/components/common/Resource/EditButton.tsx
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { KubeObject, KubeObjectInterface } from '../../../lib/k8s/cluster';
import { CallbackActionOptions, clusterAction } from '../../../redux/actions/actions';
import { CallbackActionOptions, clusterAction } from '../../../redux/clusterActionSlice';
import ActionButton from '../ActionButton';
import AuthVisible from './AuthVisible';
import EditorDialog from './EditorDialog';
2 changes: 1 addition & 1 deletion frontend/src/components/common/Resource/RestartButton.tsx
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { apply } from '../../../lib/k8s/apiProxy';
import { KubeObject } from '../../../lib/k8s/cluster';
import { clusterAction } from '../../../redux/actions/actions';
import { clusterAction } from '../../../redux/clusterActionSlice';
import AuthVisible from './AuthVisible';

interface RestartButtonProps {
2 changes: 1 addition & 1 deletion frontend/src/components/common/Resource/ScaleButton.tsx
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { KubeObject } from '../../../lib/k8s/cluster';
import { CallbackActionOptions, clusterAction } from '../../../redux/actions/actions';
import { CallbackActionOptions, clusterAction } from '../../../redux/clusterActionSlice';
import { LightTooltip } from '../Tooltip';
import AuthVisible from './AuthVisible';

3 changes: 1 addition & 2 deletions frontend/src/components/common/SimpleTable.stories.tsx
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ import { Meta, Story } from '@storybook/react/types-6-0';
import { useLocation } from 'react-router-dom';
import { KubeObjectInterface } from '../../lib/k8s/cluster';
import { useFilterFunc } from '../../lib/util';
import store from '../../redux/stores/store';
import { TestContext, TestContextProps } from '../../test';
import SectionFilterHeader from './SectionFilterHeader';
import SimpleTable, { SimpleTableProps } from './SimpleTable';
@@ -33,7 +32,7 @@ function TestSimpleTable(props: SimpleTableProps) {
}

const Template: Story<SimpleTableProps> = args => (
<TestContext store={store}>
<TestContext>
<TestSimpleTable {...args} />
</TestContext>
);
2 changes: 1 addition & 1 deletion frontend/src/components/cronjob/Details.tsx
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ import { apply } from '../../lib/k8s/apiProxy';
import { KubeObjectInterface } from '../../lib/k8s/cluster';
import CronJob from '../../lib/k8s/cronJob';
import Job from '../../lib/k8s/job';
import { clusterAction } from '../../redux/actions/actions';
import { clusterAction } from '../../redux/clusterActionSlice';
import { ActionButton } from '../common';
import { DetailsGrid } from '../common/Resource';
import AuthVisible from '../common/Resource/AuthVisible';
2 changes: 1 addition & 1 deletion frontend/src/components/node/Details.tsx
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ import { apply, drainNode, drainNodeStatus } from '../../lib/k8s/apiProxy';
import { KubeMetrics } from '../../lib/k8s/cluster';
import Node from '../../lib/k8s/node';
import { getCluster, timeAgo } from '../../lib/util';
import { clusterAction } from '../../redux/actions/actions';
import { clusterAction } from '../../redux/clusterActionSlice';
import { CpuCircularChart, MemoryCircularChart } from '../cluster/Charts';
import { ActionButton, ObjectEventList, StatusLabelProps } from '../common';
import { HeaderLabel, StatusLabel, ValueLabel } from '../common/Label';
50 changes: 0 additions & 50 deletions frontend/src/redux/actions/actions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { OptionsObject as SnackbarProps } from 'notistack';
import { AppLogoType } from '../../components/App/AppLogo';
import { ClusterChooserType } from '../../components/cluster/ClusterChooser';
import { ResourceTableProps } from '../../components/common/Resource/ResourceTable';
@@ -8,9 +7,6 @@ import { Notification } from '../../lib/notification';
import { Route } from '../../lib/router';
import { UIState } from '../reducers/ui';

export const CLUSTER_ACTION = 'CLUSTER_ACTION';
export const CLUSTER_ACTION_UPDATE = 'CLUSTER_ACTION_UPDATE';
export const CLUSTER_ACTION_CANCEL = 'CLUSTER_ACTION_CANCEL';
export const UI_SIDEBAR_SET_SELECTED = 'UI_SIDEBAR_SET_SELECTED';
export const UI_SIDEBAR_SET_VISIBLE = 'UI_SIDEBAR_SET_VISIBLE';
export const UI_SIDEBAR_SET_ITEM = 'UI_SIDEBAR_SET_ITEM';
@@ -38,41 +34,6 @@ export interface BrandingProps {
export const UI_SET_NOTIFICATIONS = 'UI_SET_NOTIFICATIONS';
export const UI_UPDATE_NOTIFICATION = 'UI_UPDATE_NOTIFICATION';

export interface ClusterActionButton {
label: string;
actionToDispatch: string;
}

export interface ClusterAction {
id: string;
key?: string;
message?: string;
url?: string;
buttons?: ClusterActionButton[];
dismissSnackbar?: string;
snackbarProps?: SnackbarProps;
}

export interface CallbackAction extends CallbackActionOptions {
callback: (...args: any[]) => void;
}

export interface CallbackActionOptions {
startUrl?: string;
cancelUrl?: string;
errorUrl?: string;
successUrl?: string;
startMessage?: string;
cancelledMessage?: string;
errorMessage?: string;
successMessage?: string;
startOptions?: SnackbarProps;
cancelledOptions?: SnackbarProps;
successOptions?: SnackbarProps;
errorOptions?: SnackbarProps;
cancelCallback?: (...args: any[]) => void;
}

export interface Action {
type: string;
[propName: string]: any;
@@ -95,17 +56,6 @@ export type TableColumnsProcessor = {
}) => ResourceTableProps['columns'];
};

export function clusterAction(
callback: CallbackAction['callback'],
actionOptions: CallbackActionOptions = {}
) {
return { type: CLUSTER_ACTION, callback, ...actionOptions };
}

export function updateClusterAction(actionOptions: ClusterAction) {
return { type: CLUSTER_ACTION_UPDATE, ...actionOptions };
}

export function setSidebarSelected(selected: string | null, sidebar: string | null = '') {
return { type: UI_SIDEBAR_SET_SELECTED, selected: { item: selected, sidebar } };
}
174 changes: 174 additions & 0 deletions frontend/src/redux/clusterActionSlice.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { AnyAction } from 'redux';
import configureStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import clusterActionSliceReducer, {
CallbackAction,
cancelClusterAction,
CLUSTER_ACTION_GRACE_PERIOD,
executeClusterAction,
updateClusterAction,
} from './clusterActionSlice';
import { RootState } from './stores/store';

const middlewares = [thunk];

type DispatchType = ThunkDispatch<RootState, undefined, AnyAction>;
const mockStore = configureStore<RootState, DispatchType>(middlewares);

jest.setTimeout(10000);

describe('clusterActionSlice', () => {
let store: ReturnType<typeof mockStore>;

beforeEach(() => {
// Reset the store after each test
store = mockStore();
});

describe('executeClusterAction', () => {
it('should execute cluster action', async () => {
const callback = jest.fn(() => Promise.resolve());
const action: CallbackAction = {
callback,
startMessage: 'Starting',
successMessage: 'Success',
errorMessage: 'Error',
cancelledMessage: 'Cancelled',
startOptions: {},
successOptions: { variant: 'success' },
errorOptions: { variant: 'error' },
cancelledOptions: {},
};

jest.useFakeTimers();
const dispatchedAction = store.dispatch(executeClusterAction(action));
jest.advanceTimersByTime(CLUSTER_ACTION_GRACE_PERIOD);
await dispatchedAction;
const actions = store.getActions();
expect(actions[0].type).toBe('clusterAction/execute/pending');
expect(callback).toHaveBeenCalledTimes(1);

// sucess action is done
expect(actions).toContainEqual(
expect.objectContaining({
type: updateClusterAction.type,
payload: expect.objectContaining({
message: 'Success',
}),
})
);
jest.advanceTimersByTime(CLUSTER_ACTION_GRACE_PERIOD);
});

it('should dispatch a cancelled action if is cancelled within grace period', async () => {
const callback = jest.fn(() => Promise.resolve());
const cancelCallback = jest.fn(() => Promise.resolve());

const action: CallbackAction = {
callback,
startMessage: 'Starting',
successMessage: 'Success',
errorMessage: 'Error',
cancelledMessage: 'Cancelled',
startOptions: {},
successOptions: { variant: 'success' },
errorOptions: { variant: 'error' },
cancelledOptions: {},
cancelCallback: cancelCallback,
};

jest.useFakeTimers();
const dispatchedAction = store.dispatch(executeClusterAction(action));

jest.advanceTimersByTime(CLUSTER_ACTION_GRACE_PERIOD / 2);

const actionKey = store.getActions().find(action => action.payload?.id !== undefined)
?.payload?.id;

clusterActionSliceReducer(undefined, cancelClusterAction(actionKey));
await dispatchedAction;

// cancelled action is done
const actions = store.getActions();
expect(actions).toContainEqual(
expect.objectContaining({
type: updateClusterAction.type,
payload: expect.objectContaining({
message: 'Cancelled',
}),
})
);

expect(callback).not.toHaveBeenCalled();
expect(cancelCallback).toHaveBeenCalled();
});

it('should dispatch an error action if the callback throws an error', async () => {
const callback = jest.fn(() => {
throw new Error('Something went wrong');
});

const action: CallbackAction = {
callback,
startMessage: 'Starting',
successMessage: 'Success',
errorMessage: 'Error',
cancelledMessage: 'Cancelled',
startOptions: {},
successOptions: { variant: 'success' },
errorOptions: { variant: 'error' },
cancelledOptions: {},
};

jest.useFakeTimers();
const dispatchedAction = store.dispatch(executeClusterAction(action));
jest.advanceTimersByTime(CLUSTER_ACTION_GRACE_PERIOD);
await dispatchedAction;

// error action is done
const actions = store.getActions();
expect(actions).toContainEqual(
expect.objectContaining({
type: updateClusterAction.type,
payload: expect.objectContaining({
message: 'Error',
}),
})
);

expect(callback).toHaveBeenCalled();
});
});

describe('updateClusterAction', () => {
it('should remove an action from the state if only id is provided', () => {
const actionKey = 'actionKey';
// first add an action with something other than id, then remove it.
const action = updateClusterAction({ id: actionKey, message: 'test' });
const state1 = clusterActionSliceReducer(undefined, action);
expect(state1[actionKey]).toBeDefined();

// now remove it by only passing in the id.
const action2 = updateClusterAction({ id: actionKey });
const state = clusterActionSliceReducer(undefined, action2);
expect(state[actionKey]).toBeUndefined();
});
});

describe('cancelClusterAction', () => {
it('should cancel an action', () => {
const actionKey = 'actionKey';
const action = updateClusterAction({ id: actionKey, message: 'test' });
const state1 = clusterActionSliceReducer(undefined, action);
expect(state1[actionKey]).toBeDefined();

const action2 = cancelClusterAction(actionKey);
const state = clusterActionSliceReducer(undefined, action2);
expect(state[actionKey]).toBeUndefined();
});
});

afterEach(() => {
jest.useRealTimers();
});
});
336 changes: 336 additions & 0 deletions frontend/src/redux/clusterActionSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,336 @@
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import i18next from 'i18next';
import { OptionsObject as SnackbarProps } from 'notistack';

/**
* See components/common/ActionsNotifier.tsx for a user of cluster actions.
*/

/**
* A button to display on the action.
*/
export interface ClusterActionButton {
/**
* The label to display on the button.
*/
label: string;
/**
* The action to dispatch when the button is clicked.
*/
actionToDispatch: string;
}

export interface ClusterAction {
/**
* A unique id for the action.
*/
id: string;
/**
* A unique id for the action.
*/
key?: string;
/**
* The amount of time to display the snackbar.
*/
autoHideDuration?: number;
/**
* The message to display on the action.
*/
message?: string;
/**
* The url to navigate to when the action is complete.
*/
url?: string;
/**
* The buttons to display on the action.
*/
buttons?: ClusterActionButton[];
/**
* The id of the snackbar to dismiss.
*/
dismissSnackbar?: string;
/**
* The props to pass to the snackbar. Could be { variant: 'success' }.
*/
snackbarProps?: SnackbarProps;
}

export interface CallbackAction extends CallbackActionOptions {
callback: (...args: any[]) => void;
}

export interface CallbackActionOptions {
/**
* The url to navigate to when the action has started.
*/
startUrl?: string;
/**
* The url to navigate to when it is cancelled.
*/
cancelUrl?: string;
/**
* The url to navigate to when there is an error.
*/
errorUrl?: string;
/**
* The url to navigate to when it is successful.
*/
successUrl?: string;
/**
* The message to display when the action has started.
*/
startMessage?: string;
/**
* The message to display when the action is cancelled.
*/
cancelledMessage?: string;
/**
* The message to display when there is an error.
*/
errorMessage?: string;
/**
* The message to display when it is successful.
*/
successMessage?: string;
/**
* The props to pass to the snackbar when the action has started.
*/
startOptions?: SnackbarProps;
/**
* The props to pass to the snackbar when the action is cancelled.
*/
cancelledOptions?: SnackbarProps;
/**
* The props to pass to the snackbar when there is an error.
*/
successOptions?: SnackbarProps;
/**
* The props to pass to the snackbar when it is successful.
*/
errorOptions?: SnackbarProps;
/**
* A callback to execute when the action is cancelled.
*/
cancelCallback?: (...args: any[]) => void;
}

/**
* A unique key for each action.
*/
export interface ClusterState {
[id: string]: ClusterAction;
}

export const initialState: ClusterState = {};

const controllers = new Map<string, AbortController>();

/** The amount of time to wait before allowing the action to be cancelled. */
export const CLUSTER_ACTION_GRACE_PERIOD = 5000;

/**
* Uses the callback to execute an action and dispatches actions
* to update the UI based on the result.
*
* Gives the user 5 seconds to cancel the action before executing it.
*/
export const executeClusterAction = createAsyncThunk(
'clusterAction/execute',
async (action: CallbackAction, { dispatch, rejectWithValue }) => {
const actionKey = (new Date().getTime() + Math.random()).toString();

/**
* See the handler for clusterAction/cancel/ in extraReducers below.
*/
const uniqueCancelActionType = 'clusterAction/cancel/' + actionKey;

const controller = new AbortController();
controllers.set(actionKey, controller);

const {
callback,
startUrl,
cancelUrl,
successUrl,
startMessage,
cancelledMessage,
errorMessage,
errorUrl,
successMessage,
cancelCallback,
startOptions = {},
cancelledOptions = {},
successOptions = { variant: 'success' },
errorOptions = { variant: 'error' },
} = action;

// Dispatch actions for all the states.

function dispatchStart() {
dispatch(
updateClusterAction({
id: actionKey,
key: actionKey,
message: startMessage,
url: startUrl,
buttons: [
{
label: i18next.t('frequent|Cancel'),
actionToDispatch: uniqueCancelActionType,
},
],
snackbarProps: startOptions,
})
);
}
function dispatchSuccess() {
dispatch(
updateClusterAction({
buttons: undefined,
dismissSnackbar: actionKey,
id: actionKey,
message: successMessage,
snackbarProps: successOptions,
url: successUrl,
})
);
}
function dispatchCancelled() {
dispatch(
updateClusterAction({
buttons: undefined,
id: actionKey,
message: cancelledMessage,
dismissSnackbar: actionKey,
url: cancelUrl,
snackbarProps: cancelledOptions,
autoHideDuration: 3000,
})
);
}
function dispatchError() {
dispatch(
updateClusterAction({
buttons: undefined,
dismissSnackbar: actionKey,
id: actionKey,
message: errorMessage,
snackbarProps: errorOptions,
url: errorUrl,
})
);
}
function dispatchClose() {
dispatch(
updateClusterAction({
id: actionKey,
})
);
}

async function cancellableActionLogic() {
dispatchStart();
try {
await new Promise((resolve, reject) => {
const timeoutId = setTimeout(resolve, CLUSTER_ACTION_GRACE_PERIOD);
controller.signal.addEventListener('abort', () => {
clearTimeout(timeoutId);
reject('Action cancelled');
});
});

if (controller.signal.aborted) {
return rejectWithValue('Action cancelled');
}
callback();
dispatchSuccess();
} catch (err) {
if ((err as Error).message === 'Action cancelled' || controller.signal.aborted) {
dispatchCancelled();

if (cancelCallback) {
try {
cancelCallback();
} catch (err) {
console.error(err);
}
}
} else {
dispatchError();
}
} finally {
controllers.delete(actionKey);
setTimeout(dispatchClose, 3000);
}
}

await cancellableActionLogic();
return actionKey;
}
);

const clusterActionSlice = createSlice({
name: 'clusterAction',
initialState,
reducers: {
/**
* Updates the state of the action.
*
* If only id is provided, the action is removed from the state.
*/
updateClusterAction: (
state,
action: PayloadAction<Partial<ClusterAction> & { id: string }>
) => {
const { id, ...actionOptions } = action.payload;
if (Object.keys(actionOptions).length === 0) {
delete state[id];
} else {
const { snackbarProps, ...otherActionOptions } = actionOptions;
state[id] = { ...state[id], ...otherActionOptions, id };
state[id].snackbarProps = snackbarProps as any; // any because snackbarProps is problematic to ts
}
},

/**
* Cancels the action with the given key.
*/
cancelClusterAction: (state, action: PayloadAction<string>) => {
const actionKey = action.payload;
const controller = controllers.get(actionKey);

if (controller) {
controller.abort();
controllers.delete(actionKey);
}
delete state[actionKey];
},
},

extraReducers: builder => {
builder.addMatcher(
action => action.type.startsWith('clusterAction/cancel/'),
(state, action) => {
const actionKey = action.type.split('clusterAction/cancel/')[1];
clusterActionSlice.caseReducers.cancelClusterAction(state, {
type: 'clusterAction/cancelClusterAction',
payload: actionKey,
});
}
);
},
});

export const { updateClusterAction, cancelClusterAction } = clusterActionSlice.actions;

/**
* Executes the callback action with the given options.
*/
export function clusterAction(
callback: CallbackAction['callback'],
actionOptions: CallbackActionOptions = {}
) {
return executeClusterAction({ callback, ...actionOptions });
}

export default clusterActionSlice.reducer;
31 changes: 0 additions & 31 deletions frontend/src/redux/reducers/clusterAction.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion frontend/src/redux/reducers/reducers.tsx
Original file line number Diff line number Diff line change
@@ -2,10 +2,10 @@ import { TypedUseSelectorHook, useSelector } from 'react-redux';
import { combineReducers } from 'redux';
import pluginsReducer from '../../plugin/pluginsSlice';
import actionButtons from '../actionButtonsSlice';
import clusterAction from '../clusterActionSlice';
import configReducer from '../configSlice';
import detailsViewSectionsSlice from '../detailsViewSectionsSlice';
import filterReducer from '../filterSlice';
import clusterAction from './clusterAction';
import uiReducer from './ui';

const reducers = combineReducers({
139 changes: 0 additions & 139 deletions frontend/src/redux/sagas/sagas.tsx

This file was deleted.

24 changes: 9 additions & 15 deletions frontend/src/redux/stores/store.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
import { configureStore } from '@reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import { initialState as CLUSTER_ACTIONS_INITIAL_STATE } from '../clusterActionSlice';
import { initialState as CONFIG_INITIAL_STATE } from '../configSlice';
import { initialState as FILTER_INITIAL_STATE } from '../filterSlice';
import reducers from '../reducers/reducers';
import { INITIAL_STATE as UI_INITIAL_STATE } from '../reducers/ui';
import rootSaga from '../sagas/sagas';

const initialState = {
filter: FILTER_INITIAL_STATE,
ui: UI_INITIAL_STATE,
config: CONFIG_INITIAL_STATE,
};

const sagaMiddleware = createSagaMiddleware();

const store = configureStore({
reducer: reducers,
preloadedState: initialState,
preloadedState: {
filter: FILTER_INITIAL_STATE,
ui: UI_INITIAL_STATE,
config: CONFIG_INITIAL_STATE,
clusterAction: CLUSTER_ACTIONS_INITIAL_STATE,
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
serializableCheck: false,
thunk: false,
}).concat(sagaMiddleware),
thunk: true,
}),
});

export default store;

sagaMiddleware.run(rootSaga);

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
322 changes: 171 additions & 151 deletions plugins/headlamp-plugin/package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion plugins/headlamp-plugin/package.json
Original file line number Diff line number Diff line change
@@ -55,6 +55,7 @@
"@types/react-redux": "^7.1.19",
"@types/react-router-dom": "^5.3.1",
"@types/react-window": "^1.8.5",
"@types/redux-mock-store": "^1.0.4",
"@types/semver": "^7.3.8",
"@types/webpack-env": "^1.16.2",
"@typescript-eslint/eslint-plugin": "^4.33.0",
@@ -76,6 +77,7 @@
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-unused-imports": "^1.1.5",
"fetch-mock": "^9.11.0",
"file-loader": "^6.2.0",
"filemanager-webpack-plugin": "^7.0.0",
"fs-extra": "^9.1.0",
@@ -118,7 +120,7 @@
"react-scripts": "5.0.0",
"react-window": "^1.8.7",
"recharts": "^2.1.4",
"redux-saga": "^1.1.3",
"redux-mock-store": "^1.5.4",
"resize-observer-polyfill": "^1.5.1",
"semver": "^7.3.5",
"shx": "^0.3.4",