From f71fd0d2eaa3f75511117f0d519f880c5a7bda7c Mon Sep 17 00:00:00 2001 From: mufazalov Date: Fri, 2 Jun 2023 19:42:32 +0300 Subject: [PATCH] refactor: migrate node reducer to ts --- src/containers/Node/Node.tsx | 2 +- .../Node/NodeStructure/NodeStructure.tsx | 36 ++--- src/services/api.ts | 4 +- src/store/reducers/index.ts | 2 +- src/store/reducers/node.js | 141 ------------------ src/store/reducers/node/node.ts | 102 +++++++++++++ src/store/reducers/node/selectors.ts | 59 ++++++++ src/store/reducers/node/types.ts | 44 ++++++ 8 files changed, 228 insertions(+), 162 deletions(-) delete mode 100644 src/store/reducers/node.js create mode 100644 src/store/reducers/node/node.ts create mode 100644 src/store/reducers/node/selectors.ts create mode 100644 src/store/reducers/node/types.ts diff --git a/src/containers/Node/Node.tsx b/src/containers/Node/Node.tsx index 5fa4aa00a1..4db155687c 100644 --- a/src/containers/Node/Node.tsx +++ b/src/containers/Node/Node.tsx @@ -15,7 +15,7 @@ import NodeStructure from './NodeStructure/NodeStructure'; import {Loader} from '../../components/Loader'; import {BasicNodeViewer} from '../../components/BasicNodeViewer'; -import {getNodeInfo, resetNode} from '../../store/reducers/node'; +import {getNodeInfo, resetNode} from '../../store/reducers/node/node'; import routes, {CLUSTER_PAGES, createHref} from '../../routes'; import {setHeader} from '../../store/reducers/header'; import {AutoFetcher} from '../../utils/autofetcher'; diff --git a/src/containers/Node/NodeStructure/NodeStructure.tsx b/src/containers/Node/NodeStructure/NodeStructure.tsx index cd51db4505..03ea6c7c90 100644 --- a/src/containers/Node/NodeStructure/NodeStructure.tsx +++ b/src/containers/Node/NodeStructure/NodeStructure.tsx @@ -1,16 +1,19 @@ import {useEffect, useRef, useMemo} from 'react'; -import {useDispatch, useSelector} from 'react-redux'; +import {useDispatch} from 'react-redux'; import url from 'url'; import _ from 'lodash'; import cn from 'bem-cn-lite'; -import {PDisk} from './Pdisk'; import {Loader} from '../.././../components/Loader'; -import {getNodeStructure, selectNodeStructure} from '../../../store/reducers/node'; +import {getNodeStructure} from '../../../store/reducers/node/node'; +import {selectNodeStructure} from '../../../store/reducers/node/selectors'; import {AutoFetcher} from '../../../utils/autofetcher'; +import {useTypedSelector} from '../../../utils/hooks'; + +import {PDisk} from './Pdisk'; import './NodeStructure.scss'; @@ -32,20 +35,19 @@ interface NodeStructureProps { const autofetcher = new AutoFetcher(); -function NodeStructure(props: NodeStructureProps) { +function NodeStructure({nodeId, className, additionalNodesInfo}: NodeStructureProps) { const dispatch = useDispatch(); - const nodeStructure: any = useSelector(selectNodeStructure); + const nodeStructure = useTypedSelector(selectNodeStructure); - const loadingStructure = useSelector((state: any) => state.node.loadingStructure); - const wasLoadedStructure = useSelector((state: any) => state.node.wasLoadedStructure); - const nodeData = useSelector((state: any) => state.node?.data?.SystemStateInfo?.[0]); + const {loadingStructure, wasLoadedStructure} = useTypedSelector((state) => state.node); + const nodeData = useTypedSelector((state) => state.node?.data?.SystemStateInfo?.[0]); const nodeHref = useMemo(() => { - return props.additionalNodesInfo?.getNodeRef - ? props.additionalNodesInfo.getNodeRef(nodeData) + return additionalNodesInfo?.getNodeRef + ? additionalNodesInfo.getNodeRef(nodeData) : undefined; - }, [nodeData, props.additionalNodesInfo]); + }, [nodeData, additionalNodesInfo]); const {pdiskId: pdiskIdFromUrl, vdiskId: vdiskIdFromUrl} = url.parse( window.location.href, @@ -71,16 +73,16 @@ function NodeStructure(props: NodeStructureProps) { }, []); useEffect(() => { - dispatch(getNodeStructure(props.nodeId)); + dispatch(getNodeStructure(nodeId)); autofetcher.start(); - autofetcher.fetch(() => dispatch(getNodeStructure(props.nodeId))); + autofetcher.fetch(() => dispatch(getNodeStructure(nodeId))); return () => { scrolled.current = false; isReady.current = false; autofetcher.stop(); }; - }, [props.nodeId, dispatch]); + }, [nodeId, dispatch]); useEffect(() => { if (!_.isEmpty(nodeStructure) && scrollContainer) { @@ -98,9 +100,9 @@ function NodeStructure(props: NodeStructureProps) { if (vdiskIdFromUrl) { const vDisks = nodeStructure[pdiskIdFromUrl as string]?.vDisks; - const vDisk = vDisks?.find((el: any) => el.id === vdiskIdFromUrl); + const vDisk = vDisks?.find((el) => el.id === vdiskIdFromUrl); const dataTable = vDisk ? document.querySelector('.data-table') : undefined; - const order = vDisk?.order; + const order = vDisk?.order || 0; if (dataTable) { scrollToVdisk += (dataTable as HTMLElement).offsetTop + 40 * order; @@ -147,7 +149,7 @@ function NodeStructure(props: NodeStructureProps) { return (
-
{renderContent()}
+
{renderContent()}
); } diff --git a/src/services/api.ts b/src/services/api.ts index 5688bb0e88..de3ee45dcb 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -99,8 +99,8 @@ export class YdbEmbeddedAPI extends AxiosWrapper { filter, nodeId, }: { - tenant: string; - filter: string; + tenant?: string; + filter?: string; nodeId: string; }, {concurrentId}: AxiosOptions = {}, diff --git a/src/store/reducers/index.ts b/src/store/reducers/index.ts index 554925aff0..76ad4f4480 100644 --- a/src/store/reducers/index.ts +++ b/src/store/reducers/index.ts @@ -5,7 +5,7 @@ import cluster from './cluster/cluster'; import clusterNodes from './clusterNodes/clusterNodes'; import tenant from './tenant/tenant'; import storage from './storage'; -import node from './node'; +import node from './node/node'; import tooltip from './tooltip'; import tablets from './tablets'; import heatmap from './heatmap'; diff --git a/src/store/reducers/node.js b/src/store/reducers/node.js deleted file mode 100644 index d490af2c30..0000000000 --- a/src/store/reducers/node.js +++ /dev/null @@ -1,141 +0,0 @@ -import {createRequestActionTypes, createApiRequest} from '../utils'; -import '../../services/api'; -import {stringifyVdiskId} from '../../utils'; -import {createSelector} from 'reselect'; - -const FETCH_NODE = createRequestActionTypes('node', 'FETCH_NODE'); -const FETCH_NODE_STRUCTURE = createRequestActionTypes('node', 'FETCH_NODE_STRUCTURE'); -const RESET_NODE = 'node/RESET_NODE'; - -const node = ( - state = { - data: {}, - loading: true, - wasLoaded: false, - nodeStructure: {}, - loadingStructure: true, - wasLoadedStructure: false, - }, - action, -) => { - switch (action.type) { - case FETCH_NODE.REQUEST: { - return { - ...state, - loading: true, - }; - } - case FETCH_NODE.SUCCESS: { - return { - ...state, - data: action.data, - loading: false, - wasLoaded: true, - error: undefined, - }; - } - case FETCH_NODE.FAILURE: { - return { - ...state, - error: action.error, - loading: false, - }; - } - case FETCH_NODE_STRUCTURE.REQUEST: { - return { - ...state, - loadingStructure: true, - }; - } - case FETCH_NODE_STRUCTURE.SUCCESS: { - return { - ...state, - nodeStructure: action.data, - loadingStructure: false, - wasLoadedStructure: true, - errorStructure: undefined, - }; - } - case FETCH_NODE_STRUCTURE.FAILURE: { - return { - ...state, - errorStructure: action.error, - loadingStructure: false, - }; - } - case RESET_NODE: { - return { - ...state, - data: {}, - wasLoaded: false, - nodeStructure: {}, - wasLoadedStructure: false, - }; - } - default: - return state; - } -}; - -export const getNodeInfo = (id) => { - return createApiRequest({ - request: window.api.getNodeInfo(id), - actions: FETCH_NODE, - }); -}; - -export const getNodeStructure = (nodeId) => { - return createApiRequest({ - request: window.api.getStorageInfo({nodeId}, {concurrentId: 'getNodeStructure'}), - actions: FETCH_NODE_STRUCTURE, - }); -}; - -export function resetNode() { - return { - type: RESET_NODE, - }; -} - -const getNodeId = (state) => state.node?.data?.SystemStateInfo?.[0].NodeId; - -const getRawNodeStructure = (state) => state.node?.nodeStructure; - -export const selectNodeStructure = createSelector( - [getNodeId, getRawNodeStructure], - (nodeId, rawNodeStructure) => { - const pools = rawNodeStructure?.StoragePools; - const structure = {}; - pools?.forEach((pool) => { - const groups = pool.Groups; - groups?.forEach((group) => { - const vDisks = group.VDisks?.filter((el) => el.NodeId === nodeId); - vDisks?.forEach((vd) => { - const vDiskId = stringifyVdiskId(vd.VDiskId); - const pDiskId = vd.PDisk?.PDiskId; - if (!structure[String(pDiskId)]) { - structure[String(pDiskId)] = {vDisks: {}, ...vd.PDisk}; - } - structure[String(pDiskId)].vDisks[vDiskId] = { - ...vd, - // VDisk doesn't have its own StoragePoolName when located inside StoragePool data - StoragePoolName: pool.Name, - }; - }); - }); - }); - - const structureWithVdisksArray = Object.keys(structure).reduce((acc, el) => { - const vDisks = structure[el].vDisks; - const vDisksArray = Object.keys(vDisks).reduce((acc, key, index) => { - acc.push({...vDisks[key], id: key, order: index}); - return acc; - }, []); - acc[el] = {...structure[el], vDisks: vDisksArray}; - return acc; - }, {}); - return structureWithVdisksArray; - }, -); - -export default node; diff --git a/src/store/reducers/node/node.ts b/src/store/reducers/node/node.ts new file mode 100644 index 0000000000..5ddcc90d5b --- /dev/null +++ b/src/store/reducers/node/node.ts @@ -0,0 +1,102 @@ +import {Reducer} from 'redux'; + +import '../../../services/api'; +import {createRequestActionTypes, createApiRequest} from '../../utils'; + +import type {NodeAction, NodeState} from './types'; + +export const FETCH_NODE = createRequestActionTypes('node', 'FETCH_NODE'); +export const FETCH_NODE_STRUCTURE = createRequestActionTypes('node', 'FETCH_NODE_STRUCTURE'); + +const RESET_NODE = 'node/RESET_NODE'; + +const initialState = { + data: {}, + loading: true, + wasLoaded: false, + nodeStructure: {}, + loadingStructure: true, + wasLoadedStructure: false, +}; + +const node: Reducer = (state = initialState, action) => { + switch (action.type) { + case FETCH_NODE.REQUEST: { + return { + ...state, + loading: true, + }; + } + case FETCH_NODE.SUCCESS: { + return { + ...state, + data: action.data, + loading: false, + wasLoaded: true, + error: undefined, + }; + } + case FETCH_NODE.FAILURE: { + return { + ...state, + error: action.error, + loading: false, + }; + } + case FETCH_NODE_STRUCTURE.REQUEST: { + return { + ...state, + loadingStructure: true, + }; + } + case FETCH_NODE_STRUCTURE.SUCCESS: { + return { + ...state, + nodeStructure: action.data, + loadingStructure: false, + wasLoadedStructure: true, + errorStructure: undefined, + }; + } + case FETCH_NODE_STRUCTURE.FAILURE: { + return { + ...state, + errorStructure: action.error, + loadingStructure: false, + }; + } + case RESET_NODE: { + return { + ...state, + data: {}, + wasLoaded: false, + nodeStructure: {}, + wasLoadedStructure: false, + }; + } + default: + return state; + } +}; + +export const getNodeInfo = (id: string) => { + return createApiRequest({ + request: window.api.getNodeInfo(id), + actions: FETCH_NODE, + }); +}; + +export const getNodeStructure = (nodeId: string) => { + return createApiRequest({ + request: window.api.getStorageInfo({nodeId}, {concurrentId: 'getNodeStructure'}), + actions: FETCH_NODE_STRUCTURE, + }); +}; + +export function resetNode() { + return { + type: RESET_NODE, + } as const; +} + +export default node; diff --git a/src/store/reducers/node/selectors.ts b/src/store/reducers/node/selectors.ts new file mode 100644 index 0000000000..efc4e486d5 --- /dev/null +++ b/src/store/reducers/node/selectors.ts @@ -0,0 +1,59 @@ +import type {Selector} from 'reselect'; +import {createSelector} from 'reselect'; + +import {stringifyVdiskId} from '../../../utils'; + +import type { + NodeStateSlice, + PreparedNodeStructure, + PreparedStructureVDisk, + RawNodeStructure, +} from './types'; + +const selectNodeId = (state: NodeStateSlice) => state.node?.data?.SystemStateInfo?.[0].NodeId; + +const selectRawNodeStructure = (state: NodeStateSlice) => state.node?.nodeStructure; + +export const selectNodeStructure: Selector = createSelector( + [selectNodeId, selectRawNodeStructure], + (nodeId, storageInfo) => { + const pools = storageInfo?.StoragePools; + const structure: RawNodeStructure = {}; + + pools?.forEach((pool) => { + const groups = pool.Groups; + groups?.forEach((group) => { + const vDisks = group.VDisks?.filter((el) => el.NodeId === nodeId); + vDisks?.forEach((vd) => { + const vDiskId = stringifyVdiskId(vd.VDiskId); + const pDiskId = vd.PDisk?.PDiskId; + if (!structure[String(pDiskId)]) { + structure[String(pDiskId)] = {vDisks: {}, ...vd.PDisk}; + } + structure[String(pDiskId)].vDisks[vDiskId] = { + ...vd, + // VDisk doesn't have its own StoragePoolName when located inside StoragePool data + StoragePoolName: pool.Name, + }; + }); + }); + }); + + const structureWithVdisksArray = Object.keys(structure).reduce( + (preparedStructure, el) => { + const vDisks = structure[el].vDisks; + const vDisksArray = Object.keys(vDisks).reduce( + (acc, key, index) => { + acc.push({...vDisks[key], id: key, order: index}); + return acc; + }, + [], + ); + preparedStructure[el] = {...structure[el], vDisks: vDisksArray}; + return preparedStructure; + }, + {}, + ); + return structureWithVdisksArray; + }, +); diff --git a/src/store/reducers/node/types.ts b/src/store/reducers/node/types.ts new file mode 100644 index 0000000000..dd13612eda --- /dev/null +++ b/src/store/reducers/node/types.ts @@ -0,0 +1,44 @@ +import type {IResponseError} from '../../../types/api/error'; +import type {TPDiskStateInfo} from '../../../types/api/pdisk'; +import type {TStorageInfo} from '../../../types/api/storage'; +import type {TEvSystemStateResponse} from '../../../types/api/systemState'; +import type {TVDiskStateInfo} from '../../../types/api/vdisk'; +import type {ApiRequestAction} from '../../utils'; + +import {FETCH_NODE, FETCH_NODE_STRUCTURE, resetNode} from './node'; + +interface RawStructurePDisk extends TPDiskStateInfo { + vDisks: Record; +} + +export type RawNodeStructure = Record; + +export interface PreparedStructureVDisk extends TVDiskStateInfo { + id: string; + order: number; +} + +export interface PreparedStructurePDisk extends TPDiskStateInfo { + vDisks: PreparedStructureVDisk[]; +} + +export type PreparedNodeStructure = Record; + +export interface NodeState { + data: TEvSystemStateResponse; + loading: boolean; + wasLoaded: boolean; + + nodeStructure: TStorageInfo; + loadingStructure: boolean; + wasLoadedStructure: boolean; +} + +export type NodeAction = + | ApiRequestAction + | ApiRequestAction + | ReturnType; + +export interface NodeStateSlice { + node: NodeState; +}