diff --git a/src/.eslintrc b/src/.eslintrc
index e861c8caa6..75c762f2bb 100644
--- a/src/.eslintrc
+++ b/src/.eslintrc
@@ -3,12 +3,6 @@
"rules": {
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
- "react-hooks/exhaustive-deps": [
- "warn",
- {
- "additionalHooks": "(useAutofetcher)",
- },
- ],
"valid-jsdoc": "off",
"react/jsx-fragments": ["error", "element"],
"no-restricted-syntax": [
diff --git a/src/components/MetricChart/MetricChart.tsx b/src/components/MetricChart/MetricChart.tsx
index 1d6d22ef6e..2846b7b4e9 100644
--- a/src/components/MetricChart/MetricChart.tsx
+++ b/src/components/MetricChart/MetricChart.tsx
@@ -4,26 +4,14 @@ import ChartKit, {settings} from '@gravity-ui/chartkit';
import type {YagrSeriesData, YagrWidgetData} from '@gravity-ui/chartkit/yagr';
import {YagrPlugin} from '@gravity-ui/chartkit/yagr';
-import type {IResponseError} from '../../types/api/error';
import {cn} from '../../utils/cn';
-import {useAutofetcher} from '../../utils/hooks';
import type {TimeFrame} from '../../utils/timeframes';
import {ResponseError} from '../Errors/ResponseError';
import {Loader} from '../Loader';
import {colorToRGBA, colors} from './colors';
-import {convertResponse} from './convertResponse';
-import {getChartData} from './getChartData';
import {getDefaultDataFormatter} from './getDefaultDataFormatter';
-import i18n from './i18n';
-import {
- chartReducer,
- initialChartState,
- setChartData,
- setChartDataLoading,
- setChartDataWasNotLoaded,
- setChartError,
-} from './reducer';
+import {chartApi} from './reducer';
import type {
ChartOptions,
MetricDescription,
@@ -107,6 +95,8 @@ const prepareWidgetData = (
};
};
+const emptyChartData: PreparedMetricsData = {timeline: [], metrics: []};
+
interface DiagnosticsChartProps {
database: string;
@@ -114,7 +104,7 @@ interface DiagnosticsChartProps {
metrics: MetricDescription[];
timeFrame?: TimeFrame;
- autorefresh?: boolean;
+ autorefresh?: number;
height?: number;
width?: number;
@@ -143,90 +133,29 @@ export const MetricChart = ({
onChartDataStatusChange,
isChartVisible,
}: DiagnosticsChartProps) => {
- const mounted = React.useRef(false);
-
- React.useEffect(() => {
- mounted.current = true;
- return () => {
- mounted.current = false;
- };
- }, []);
-
- const [{loading, wasLoaded, data, error}, dispatch] = React.useReducer(
- chartReducer,
- initialChartState,
- );
-
- React.useEffect(() => {
- if (error) {
- return onChartDataStatusChange?.('error');
- }
- if (loading && !wasLoaded) {
- return onChartDataStatusChange?.('loading');
- }
- if (!loading && wasLoaded) {
- return onChartDataStatusChange?.('success');
- }
-
- return undefined;
- }, [loading, wasLoaded, error, onChartDataStatusChange]);
-
- const fetchChartData = React.useCallback(
- async (isBackground: boolean) => {
- dispatch(setChartDataLoading());
-
- if (!isBackground) {
- dispatch(setChartDataWasNotLoaded());
- }
-
- try {
- // maxDataPoints param is calculated based on width
- // should be width > maxDataPoints to prevent points that cannot be selected
- // more px per dataPoint - easier to select, less - chart is smoother
- const response = await getChartData({
- database,
- metrics,
- timeFrame,
- maxDataPoints: width / 2,
- });
-
- // Hack to prevent setting value to state, if component unmounted
- if (!mounted.current) {
- return;
- }
-
- // Response could be a plain html for ydb versions without charts support
- // Or there could be an error in response with 200 status code
- // It happens when request is OK, but chart data cannot be returned due to some reason
- // Example: charts are not enabled in the DB ('GraphShard is not enabled' error)
- if (Array.isArray(response)) {
- const preparedData = convertResponse(response, metrics);
- dispatch(setChartData(preparedData));
- } else {
- const err = {
- statusText:
- typeof response === 'string' ? i18n('not-supported') : response.error,
- };
-
- throw err;
- }
- } catch (err) {
- if (!mounted.current) {
- return;
- }
-
- dispatch(setChartError(err as IResponseError));
- }
+ const {currentData, error, isFetching, status} = chartApi.useGetChartDataQuery(
+ // maxDataPoints param is calculated based on width
+ // should be width > maxDataPoints to prevent points that cannot be selected
+ // more px per dataPoint - easier to select, less - chart is smoother
+ {
+ database,
+ metrics,
+ timeFrame,
+ maxDataPoints: width / 2,
},
- [database, metrics, timeFrame, width],
+ {pollingInterval: autorefresh},
);
- useAutofetcher(fetchChartData, [fetchChartData], autorefresh);
+ const loading = isFetching && !currentData;
+
+ React.useEffect(() => {
+ return onChartDataStatusChange?.(status === 'fulfilled' ? 'success' : 'loading');
+ }, [status, onChartDataStatusChange]);
- const convertedData = prepareWidgetData(data, chartOptions);
+ const convertedData = prepareWidgetData(currentData || emptyChartData, chartOptions);
const renderContent = () => {
- if (loading && !wasLoaded) {
+ if (loading) {
return ;
}
@@ -237,7 +166,7 @@ export const MetricChart = ({
return (
- {error && }
+ {error ? : null}
);
};
diff --git a/src/components/MetricChart/getChartData.ts b/src/components/MetricChart/getChartData.ts
index 95117d3471..5bfc45557c 100644
--- a/src/components/MetricChart/getChartData.ts
+++ b/src/components/MetricChart/getChartData.ts
@@ -3,19 +3,17 @@ import type {TimeFrame} from '../../utils/timeframes';
import type {MetricDescription} from './types';
-interface GetChartDataParams {
+export interface GetChartDataParams {
database: string;
metrics: MetricDescription[];
timeFrame: TimeFrame;
maxDataPoints: number;
}
-export const getChartData = async ({
- database,
- metrics,
- timeFrame,
- maxDataPoints,
-}: GetChartDataParams) => {
+export const getChartData = async (
+ {database, metrics, timeFrame, maxDataPoints}: GetChartDataParams,
+ {signal}: {signal?: AbortSignal} = {},
+) => {
const targetString = metrics.map((metric) => `target=${metric.target}`).join('&');
const until = Math.round(Date.now() / 1000);
@@ -23,6 +21,6 @@ export const getChartData = async ({
return window.api.getChartData(
{target: targetString, from, until, maxDataPoints, database},
- {concurrentId: `getChartData|${targetString}`},
+ {signal},
);
};
diff --git a/src/components/MetricChart/reducer.ts b/src/components/MetricChart/reducer.ts
index 014d76b40d..04d814d262 100644
--- a/src/components/MetricChart/reducer.ts
+++ b/src/components/MetricChart/reducer.ts
@@ -1,86 +1,38 @@
-import {createRequestActionTypes} from '../../store/utils';
-import type {IResponseError} from '../../types/api/error';
-
-import type {PreparedMetricsData} from './types';
-
-const FETCH_CHART_DATA = createRequestActionTypes('chart', 'FETCH_CHART_DATA');
-const SET_CHART_DATA_WAS_NOT_LOADED = 'chart/SET_DATA_WAS_NOT_LOADED';
-
-export const setChartDataLoading = () => {
- return {
- type: FETCH_CHART_DATA.REQUEST,
- } as const;
-};
-
-export const setChartData = (data: PreparedMetricsData) => {
- return {
- data,
- type: FETCH_CHART_DATA.SUCCESS,
- } as const;
-};
-
-export const setChartError = (error: IResponseError) => {
- return {
- error,
- type: FETCH_CHART_DATA.FAILURE,
- } as const;
-};
-
-export const setChartDataWasNotLoaded = () => {
- return {
- type: SET_CHART_DATA_WAS_NOT_LOADED,
- } as const;
-};
-
-type ChartAction =
- | ReturnType
- | ReturnType
- | ReturnType
- | ReturnType;
-
-interface ChartState {
- loading: boolean;
- wasLoaded: boolean;
- data: PreparedMetricsData;
- error: IResponseError | undefined;
-}
-
-export const initialChartState: ChartState = {
- // Set chart initial state as loading, in order not to mount and unmount component in between requests
- // as it leads to memory leak errors in console (not proper useEffect cleanups in chart component itself)
- // TODO: possible fix (check needed): chart component is always present, but display: none for chart while loading
- loading: true,
- wasLoaded: false,
- data: {timeline: [], metrics: []},
- error: undefined,
-};
-
-export const chartReducer = (state: ChartState, action: ChartAction) => {
- switch (action.type) {
- case FETCH_CHART_DATA.REQUEST: {
- return {...state, loading: true};
- }
- case FETCH_CHART_DATA.SUCCESS: {
- return {...state, loading: false, wasLoaded: true, error: undefined, data: action.data};
- }
- case FETCH_CHART_DATA.FAILURE: {
- if (action.error?.isCancelled) {
- return state;
- }
-
- return {
- ...state,
- error: action.error,
- // Clear data, so error will be displayed with empty chart
- data: {timeline: [], metrics: []},
- loading: false,
- wasLoaded: true,
- };
- }
- case SET_CHART_DATA_WAS_NOT_LOADED: {
- return {...state, wasLoaded: false};
- }
- default:
- return state;
- }
-};
+import {api} from '../../store/reducers/api';
+
+import {convertResponse} from './convertResponse';
+import type {GetChartDataParams} from './getChartData';
+import {getChartData} from './getChartData';
+import i18n from './i18n';
+
+export const chartApi = api.injectEndpoints({
+ endpoints: (builder) => ({
+ getChartData: builder.query({
+ queryFn: async (params: GetChartDataParams, {signal}) => {
+ try {
+ const response = await getChartData(params, {signal});
+
+ // Response could be a plain html for ydb versions without charts support
+ // Or there could be an error in response with 200 status code
+ // It happens when request is OK, but chart data cannot be returned due to some reason
+ // Example: charts are not enabled in the DB ('GraphShard is not enabled' error)
+ if (Array.isArray(response)) {
+ const preparedData = convertResponse(response, params.metrics);
+ return {data: preparedData};
+ }
+
+ return {
+ error: new Error(
+ typeof response === 'string' ? i18n('not-supported') : response.error,
+ ),
+ };
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ keepUnusedDataFor: 0,
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/containers/App/Content.tsx b/src/containers/App/Content.tsx
index 6382561a22..687eb45483 100644
--- a/src/containers/App/Content.tsx
+++ b/src/containers/App/Content.tsx
@@ -10,7 +10,7 @@ import type {SlotComponent} from '../../components/slots/types';
import routes from '../../routes';
import type {RootState} from '../../store';
import {getUser} from '../../store/reducers/authentication/authentication';
-import {getNodesList} from '../../store/reducers/nodesList';
+import {nodesListApi} from '../../store/reducers/nodesList';
import {cn} from '../../utils/cn';
import {useTypedDispatch, useTypedSelector} from '../../utils/hooks';
import Authentication from '../Authentication/Authentication';
@@ -178,12 +178,7 @@ function GetUser() {
}
function GetNodesList() {
- const dispatch = useTypedDispatch();
-
- React.useEffect(() => {
- dispatch(getNodesList());
- }, [dispatch]);
-
+ nodesListApi.useGetNodesListQuery(undefined);
return null;
}
diff --git a/src/containers/Heatmap/Heatmap.tsx b/src/containers/Heatmap/Heatmap.tsx
index e338dfc025..671ffed8d2 100644
--- a/src/containers/Heatmap/Heatmap.tsx
+++ b/src/containers/Heatmap/Heatmap.tsx
@@ -4,12 +4,12 @@ import {Checkbox, Select} from '@gravity-ui/uikit';
import {ResponseError} from '../../components/Errors/ResponseError';
import {Loader} from '../../components/Loader';
-import {getTabletsInfo, setHeatmapOptions} from '../../store/reducers/heatmap';
+import {heatmapApi, setHeatmapOptions} from '../../store/reducers/heatmap';
import {hideTooltip, showTooltip} from '../../store/reducers/tooltip';
import type {IHeatmapMetricValue} from '../../types/store/heatmap';
import {cn} from '../../utils/cn';
import {formatNumber} from '../../utils/dataFormatters/dataFormatters';
-import {useAutofetcher, useTypedDispatch, useTypedSelector} from '../../utils/hooks';
+import {useTypedDispatch, useTypedSelector} from '../../utils/hooks';
import {HeatmapCanvas} from './HeatmapCanvas/HeatmapCanvas';
import {Histogram} from './Histogram/Histogram';
@@ -30,43 +30,16 @@ export const Heatmap = ({path}: HeatmapProps) => {
const itemsContainer = React.createRef();
const {autorefresh} = useTypedSelector((state) => state.schema);
- const {
- loading,
- wasLoaded,
- error,
- sort,
- heatmap,
- metrics,
- currentMetric,
- data: tablets = [],
- } = useTypedSelector((state) => state.heatmap);
-
- const [selectedMetric, setSelectedMetric] = React.useState(['']);
-
- React.useEffect(() => {
- if (!currentMetric && metrics && metrics.length) {
- dispatch(
- setHeatmapOptions({
- currentMetric: metrics[0].value,
- }),
- );
- }
- if (currentMetric) {
- setSelectedMetric([currentMetric]);
- }
- }, [currentMetric, metrics, dispatch]);
-
- const fetchData = React.useCallback(
- (isBackground: boolean) => {
- if (!isBackground) {
- dispatch(setHeatmapOptions({wasLoaded: false}));
- }
- dispatch(getTabletsInfo({path}));
- },
- [path, dispatch],
+
+ const {currentData, isFetching, error} = heatmapApi.useGetHeatmapTabletsInfoQuery(
+ {path},
+ {pollingInterval: autorefresh},
);
- useAutofetcher(fetchData, [fetchData], autorefresh);
+ const loading = isFetching && currentData === undefined;
+
+ const {tablets = [], metrics} = currentData || {};
+ const {sort, heatmap, currentMetric} = useTypedSelector((state) => state.heatmap);
const onShowTooltip = (...args: Parameters) => {
dispatch(showTooltip(...args));
@@ -137,7 +110,6 @@ export const Heatmap = ({path}: HeatmapProps) => {
parentRef={itemsContainer}
showTooltip={onShowTooltip}
hideTooltip={onHideTooltip}
- currentMetric={currentMetric}
/>
);
@@ -151,7 +123,7 @@ export const Heatmap = ({path}: HeatmapProps) => {
diff --git a/src/containers/Tablet/TabletControls/TabletControls.tsx b/src/containers/Tablet/TabletControls/TabletControls.tsx
index c9f7a9b514..ab700b89e3 100644
--- a/src/containers/Tablet/TabletControls/TabletControls.tsx
+++ b/src/containers/Tablet/TabletControls/TabletControls.tsx
@@ -3,13 +3,12 @@ import React from 'react';
import {ButtonWithConfirmDialog} from '../../../components/ButtonWithConfirmDialog/ButtonWithConfirmDialog';
import {ETabletState} from '../../../types/api/tablet';
import type {TTabletStateInfo} from '../../../types/api/tablet';
-import type {ITabletHandledResponse} from '../../../types/store/tablet';
import {b} from '../Tablet';
import i18n from '../i18n';
interface TabletControlsProps {
tablet: TTabletStateInfo;
- fetchData: () => Promise
;
+ fetchData: () => Promise;
}
export const TabletControls = ({tablet, fetchData}: TabletControlsProps) => {
diff --git a/src/containers/Tablets/Tablets.tsx b/src/containers/Tablets/Tablets.tsx
index e41307035d..ef28c0dd56 100644
--- a/src/containers/Tablets/Tablets.tsx
+++ b/src/containers/Tablets/Tablets.tsx
@@ -1,20 +1,18 @@
import React from 'react';
import {Select} from '@gravity-ui/uikit';
+import {skipToken} from '@reduxjs/toolkit/query';
import ReactList from 'react-list';
+import {ResponseError} from '../../components/Errors/ResponseError';
import {Loader} from '../../components/Loader';
import {Tablet} from '../../components/Tablet';
import TabletsOverall from '../../components/TabletsOverall/TabletsOverall';
-import {
- clearWasLoadingFlag,
- getTabletsInfo,
- setStateFilter,
- setTypeFilter,
-} from '../../store/reducers/tablets';
-import type {ETabletState, EType, TTabletStateInfo} from '../../types/api/tablet';
+import {setStateFilter, setTypeFilter, tabletsApi} from '../../store/reducers/tablets';
+import type {ETabletState, EType} from '../../types/api/tablet';
+import type {TabletsApiRequestParams} from '../../types/store/tablets';
import {cn} from '../../utils/cn';
-import {useAutofetcher, useTypedDispatch, useTypedSelector} from '../../utils/hooks';
+import {useTypedDispatch, useTypedSelector} from '../../utils/hooks';
import i18n from './i18n';
@@ -31,32 +29,23 @@ interface TabletsProps {
export const Tablets = ({path, nodeId, className}: TabletsProps) => {
const dispatch = useTypedDispatch();
- const {data, wasLoaded, loading, error, stateFilter, typeFilter} = useTypedSelector(
- (state) => state.tablets,
- );
+ const {stateFilter, typeFilter} = useTypedSelector((state) => state.tablets);
const {autorefresh} = useTypedSelector((state) => state.schema);
- const tablets = React.useMemo(() => data?.TabletStateInfo || [], [data]);
-
- const fetchData = React.useCallback(
- (isBackground: boolean) => {
- if (!isBackground) {
- dispatch(clearWasLoadingFlag());
- }
- if (nodeId) {
- dispatch(getTabletsInfo({nodes: [String(nodeId)]}));
- } else if (path) {
- dispatch(getTabletsInfo({path}));
- }
- },
- [path, nodeId, dispatch],
- );
-
- useAutofetcher(fetchData, [fetchData], autorefresh);
+ let params: TabletsApiRequestParams | typeof skipToken = skipToken;
+ if (nodeId) {
+ params = {nodes: [String(nodeId)]};
+ } else if (path) {
+ params = {path};
+ }
+ const {currentData, isFetching, error} = tabletsApi.useGetTabletsInfoQuery(params, {
+ pollingInterval: autorefresh,
+ });
- const [tabletsToRender, setTabletsToRender] = React.useState([]);
+ const loading = isFetching && currentData === undefined;
+ const tablets = React.useMemo(() => currentData?.TabletStateInfo || [], [currentData]);
- React.useEffect(() => {
+ const tabletsToRender = React.useMemo(() => {
let filteredTablets = tablets;
if (typeFilter.length > 0) {
@@ -69,7 +58,7 @@ export const Tablets = ({path, nodeId, className}: TabletsProps) => {
stateFilter.some((filter) => tablet.State === filter),
);
}
- setTabletsToRender(filteredTablets);
+ return filteredTablets;
}, [tablets, stateFilter, typeFilter]);
const handleStateFilterChange = (value: string[]) => {
@@ -133,10 +122,10 @@ export const Tablets = ({path, nodeId, className}: TabletsProps) => {
);
};
- if (loading && !wasLoaded) {
+ if (loading) {
return ;
} else if (error) {
- return {error.statusText}
;
+ return ;
} else {
return tablets.length > 0 ? (
renderContent()
diff --git a/src/containers/Tenant/Diagnostics/Autorefresh/AutorefreshControl.scss b/src/containers/Tenant/Diagnostics/Autorefresh/AutorefreshControl.scss
new file mode 100644
index 0000000000..5d8376978b
--- /dev/null
+++ b/src/containers/Tenant/Diagnostics/Autorefresh/AutorefreshControl.scss
@@ -0,0 +1,5 @@
+.autorefresh-control {
+ display: flex;
+ align-items: center;
+ gap: var(--g-spacing-1);
+}
diff --git a/src/containers/Tenant/Diagnostics/Autorefresh/AutorefreshControl.tsx b/src/containers/Tenant/Diagnostics/Autorefresh/AutorefreshControl.tsx
new file mode 100644
index 0000000000..c46a40a899
--- /dev/null
+++ b/src/containers/Tenant/Diagnostics/Autorefresh/AutorefreshControl.tsx
@@ -0,0 +1,50 @@
+import {ArrowsRotateLeft} from '@gravity-ui/icons';
+import {Button, Select} from '@gravity-ui/uikit';
+
+import {api} from '../../../../store/reducers/api';
+import {setAutorefreshInterval} from '../../../../store/reducers/schema/schema';
+import {cn} from '../../../../utils/cn';
+import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
+
+import i18n from './i18n';
+
+import './AutorefreshControl.scss';
+
+const b = cn('autorefresh-control');
+
+interface AutorefreshControlProps {
+ className?: string;
+}
+
+export function AutorefreshControl({className}: AutorefreshControlProps) {
+ const dispatch = useTypedDispatch();
+ const autorefresh = useTypedSelector((state) => state.schema.autorefresh);
+ return (
+
+
+
+
+ );
+}
diff --git a/src/containers/Tenant/Diagnostics/Autorefresh/i18n/en.json b/src/containers/Tenant/Diagnostics/Autorefresh/i18n/en.json
new file mode 100644
index 0000000000..03a64ebd3e
--- /dev/null
+++ b/src/containers/Tenant/Diagnostics/Autorefresh/i18n/en.json
@@ -0,0 +1,8 @@
+{
+ "None": "None",
+ "15 sec": "15 sec",
+ "1 min": "1 min",
+ "2 min": "2 min",
+ "5 min": "5 min",
+ "Refresh": "Refresh"
+}
diff --git a/src/containers/Tenant/Diagnostics/Autorefresh/i18n/index.ts b/src/containers/Tenant/Diagnostics/Autorefresh/i18n/index.ts
new file mode 100644
index 0000000000..be83c33511
--- /dev/null
+++ b/src/containers/Tenant/Diagnostics/Autorefresh/i18n/index.ts
@@ -0,0 +1,7 @@
+import {registerKeysets} from '../../../../../utils/i18n';
+
+import en from './en.json';
+
+const COMPONENT = 'ydb-diagnostics-autorefresh-control';
+
+export default registerKeysets(COMPONENT, {en});
diff --git a/src/containers/Tenant/Diagnostics/Consumers/Consumers.tsx b/src/containers/Tenant/Diagnostics/Consumers/Consumers.tsx
index b9d6eca593..ffc9bea812 100644
--- a/src/containers/Tenant/Diagnostics/Consumers/Consumers.tsx
+++ b/src/containers/Tenant/Diagnostics/Consumers/Consumers.tsx
@@ -7,15 +7,14 @@ import {ResponseError} from '../../../../components/Errors/ResponseError';
import {Loader} from '../../../../components/Loader';
import {Search} from '../../../../components/Search';
import {
- getTopic,
selectPreparedConsumersData,
selectPreparedTopicStats,
- setDataWasNotLoaded,
+ topicApi,
} from '../../../../store/reducers/topic';
import type {EPathType} from '../../../../types/api/schema';
import {cn} from '../../../../utils/cn';
import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants';
-import {useAutofetcher, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
+import {useTypedSelector} from '../../../../utils/hooks';
import {isCdcStreamEntityType} from '../../utils/schema';
import {ConsumersTopicStats} from './TopicStats';
@@ -34,28 +33,16 @@ interface ConsumersProps {
export const Consumers = ({path, type}: ConsumersProps) => {
const isCdcStream = isCdcStreamEntityType(type);
- const dispatch = useTypedDispatch();
-
const [searchValue, setSearchValue] = React.useState('');
const {autorefresh} = useTypedSelector((state) => state.schema);
- const {loading, wasLoaded, error} = useTypedSelector((state) => state.topic);
-
- const consumers = useTypedSelector((state) => selectPreparedConsumersData(state));
- const topic = useTypedSelector((state) => selectPreparedTopicStats(state));
-
- const fetchData = React.useCallback(
- (isBackground: boolean) => {
- if (!isBackground) {
- dispatch(setDataWasNotLoaded);
- }
-
- dispatch(getTopic(path));
- },
- [dispatch, path],
+ const {currentData, isFetching, error} = topicApi.useGetTopicQuery(
+ {path},
+ {pollingInterval: autorefresh},
);
-
- useAutofetcher(fetchData, [fetchData], autorefresh);
+ const loading = isFetching && currentData === undefined;
+ const consumers = useTypedSelector((state) => selectPreparedConsumersData(state, path));
+ const topic = useTypedSelector((state) => selectPreparedTopicStats(state, path));
const dataToRender = React.useMemo(() => {
if (!consumers) {
@@ -73,7 +60,7 @@ export const Consumers = ({path, type}: ConsumersProps) => {
setSearchValue(value);
};
- if (loading && !wasLoaded) {
+ if (loading) {
return ;
}
diff --git a/src/containers/Tenant/Diagnostics/Describe/Describe.tsx b/src/containers/Tenant/Diagnostics/Describe/Describe.tsx
index cc55b51ec3..630cf8962d 100644
--- a/src/containers/Tenant/Diagnostics/Describe/Describe.tsx
+++ b/src/containers/Tenant/Diagnostics/Describe/Describe.tsx
@@ -1,20 +1,14 @@
-import React from 'react';
-
+import {skipToken} from '@reduxjs/toolkit/query';
import JSONTree from 'react-json-inspector';
import {shallowEqual} from 'react-redux';
import {ResponseError} from '../../../../components/Errors/ResponseError';
import {Loader} from '../../../../components/Loader';
-import {
- getDescribe,
- getDescribeBatched,
- setCurrentDescribePath,
- setDataWasNotLoaded,
-} from '../../../../store/reducers/describe';
+import {describeApi} from '../../../../store/reducers/describe';
import {selectSchemaMergedChildrenPaths} from '../../../../store/reducers/schema/schema';
import type {EPathType} from '../../../../types/api/schema';
import {cn} from '../../../../utils/cn';
-import {useAutofetcher, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
+import {useTypedSelector} from '../../../../utils/hooks';
import {isEntityWithMergedImplementation} from '../../utils/schema';
import './Describe.scss';
@@ -30,26 +24,6 @@ interface IDescribeProps {
}
const Describe = ({tenant, type}: IDescribeProps) => {
- const dispatch = useTypedDispatch();
-
- const {currentDescribe, error, loading, wasLoaded} = useTypedSelector(
- (state) => state.describe,
- );
-
- const [preparedDescribeData, setPreparedDescribeData] = React.useState
);
diff --git a/src/containers/Tenant/Diagnostics/Network/Network.js b/src/containers/Tenant/Diagnostics/Network/Network.js
deleted file mode 100644
index fd5b2bd27f..0000000000
--- a/src/containers/Tenant/Diagnostics/Network/Network.js
+++ /dev/null
@@ -1,377 +0,0 @@
-import React from 'react';
-
-import {Checkbox, Loader} from '@gravity-ui/uikit';
-import reduce from 'lodash/reduce';
-import PropTypes from 'prop-types';
-import {connect} from 'react-redux';
-import {Link} from 'react-router-dom';
-
-import {Icon} from '../../../../components/Icon';
-import {Illustration} from '../../../../components/Illustration';
-import {ProblemFilter} from '../../../../components/ProblemFilter';
-import {getNetworkInfo, setDataWasNotLoaded} from '../../../../store/reducers/network/network';
-import {ProblemFilterValues, changeFilter} from '../../../../store/reducers/settings/settings';
-import {hideTooltip, showTooltip} from '../../../../store/reducers/tooltip';
-import {AutoFetcher} from '../../../../utils/autofetcher';
-import {cn} from '../../../../utils/cn';
-import {getDefaultNodePath} from '../../../Node/NodePages';
-
-import NodeNetwork from './NodeNetwork/NodeNetwork';
-import {getConnectedNodesCount} from './utils';
-
-import './Network.scss';
-
-const b = cn('network');
-
-class Network extends React.Component {
- static propTypes = {
- getNetworkInfo: PropTypes.func,
- setDataWasNotLoaded: PropTypes.func,
- netWorkInfo: PropTypes.object,
- hideTooltip: PropTypes.func,
- showTooltip: PropTypes.func,
- error: PropTypes.object,
- wasLoaded: PropTypes.bool,
- loading: PropTypes.bool,
- autorefresh: PropTypes.bool,
- path: PropTypes.string,
- filter: PropTypes.string,
- changeFilter: PropTypes.func,
- };
-
- static defaultProps = {};
-
- static renderLoader() {
- return (
-
-
-
- );
- }
-
- state = {
- howNodeSeeOtherNodesSortType: '',
- howOthersSeeNodeSortType: '',
- howNodeSeeOtherSearch: '',
- howOtherSeeNodeSearch: '',
- hoveredNode: undefined,
- clickedNode: undefined,
- highlightedNodes: [],
- showId: false,
- showRacks: false,
- };
-
- autofetcher;
-
- componentDidMount() {
- const {path, autorefresh, getNetworkInfo} = this.props;
- this.autofetcher = new AutoFetcher();
-
- if (autorefresh) {
- this.autofetcher.start();
- this.autofetcher.fetch(() => getNetworkInfo(path));
- }
-
- getNetworkInfo(path);
- }
-
- componentDidUpdate(prevProps) {
- const {autorefresh, path, setDataWasNotLoaded, getNetworkInfo} = this.props;
-
- const restartAutorefresh = () => {
- this.autofetcher.stop();
- this.autofetcher.start();
- this.autofetcher.fetch(() => getNetworkInfo(path));
- };
-
- if (autorefresh && !prevProps.autorefresh) {
- getNetworkInfo(path);
- restartAutorefresh();
- }
- if (!autorefresh && prevProps.autorefresh) {
- this.autofetcher.stop();
- }
-
- if (path !== prevProps.path) {
- setDataWasNotLoaded();
- getNetworkInfo(path);
- restartAutorefresh();
- }
- }
-
- componentWillUnmount() {
- this.autofetcher.stop();
- }
-
- onChange = (field, num) => {
- this.setState({[field]: num});
- };
-
- handleSortChange = (sortItem) => {
- this.setState({sort: sortItem});
- };
-
- handleNodeClickWrap = (nodeInfo) => {
- return () => {
- const {clickedNode} = this.state;
- const {NodeId} = nodeInfo;
- if (!clickedNode) {
- this.setState({
- clickedNode: nodeInfo,
- rightNodes: this.groupNodesByField(nodeInfo.Peers, 'NodeType'),
- });
- } else if (NodeId === clickedNode.nodeId) {
- this.setState({clickedNode: undefined});
- } else {
- this.setState({
- clickedNode: nodeInfo,
- rightNodes: this.groupNodesByField(nodeInfo.Peers, 'NodeType'),
- });
- }
- };
- };
-
- groupNodesByField = (nodes, field) => {
- return reduce(
- nodes,
- (acc, node) => {
- if (!acc[node[field]]) {
- acc[node[field]] = [node];
- } else {
- acc[node[field]].push(node);
- }
- return acc;
- },
- {},
- );
- };
-
- renderNodes = (nodes, isRight) => {
- const {showId, showRacks, clickedNode} = this.state;
- let problemNodesCount = 0;
- const {showTooltip, hideTooltip, filter} = this.props;
- const result = Object.keys(nodes).map((key, j) => {
- const nodesGroupedByRack = this.groupNodesByField(nodes[key], 'Rack');
- return (
-
-
{key} nodes
-
- {showRacks
- ? Object.keys(nodesGroupedByRack).map((key, i) => (
-
-
- {key === 'undefined' ? '?' : key}
-
- {/* eslint-disable-next-line array-callback-return */}
- {nodesGroupedByRack[key].map((nodeInfo, index) => {
- let capacity, connected;
- if (!isRight && nodeInfo?.Peers) {
- capacity = Object.keys(nodeInfo?.Peers).length;
- connected = getConnectedNodesCount(nodeInfo?.Peers);
- }
-
- if (
- (filter === ProblemFilterValues.PROBLEMS &&
- capacity !== connected) ||
- filter === ProblemFilterValues.ALL ||
- isRight
- ) {
- problemNodesCount++;
- return (
-
- );
- }
- })}
-
- ))
- : // eslint-disable-next-line array-callback-return
- nodes[key].map((nodeInfo, index) => {
- let capacity, connected;
- if (!isRight) {
- capacity = nodeInfo?.Peers?.length;
- connected = getConnectedNodesCount(nodeInfo?.Peers);
- }
-
- if (
- (filter === ProblemFilterValues.PROBLEMS &&
- capacity !== connected) ||
- filter === ProblemFilterValues.ALL ||
- isRight
- ) {
- problemNodesCount++;
- return (
-
- );
- }
- })}
-
-
- );
- });
-
- if (filter === ProblemFilterValues.PROBLEMS && problemNodesCount === 0) {
- return ;
- } else {
- return result;
- }
- };
-
- renderContent = () => {
- const {netWorkInfo, filter, changeFilter} = this.props;
-
- const {showId, showRacks, rightNodes} = this.state;
- const {clickedNode} = this.state;
-
- const nodes = netWorkInfo.Tenants && netWorkInfo.Tenants[0].Nodes;
- const nodesGroupedByType = this.groupNodesByField(nodes, 'NodeType');
-
- if (!nodes?.length) {
- return no nodes data
;
- }
-
- return (
-
-
-
-
-
-
-
-
- this.onChange('showId', !showId)}
- checked={showId}
- >
- ID
-
-
-
- this.onChange('showRacks', !showRacks)}
- checked={showRacks}
- >
- Racks
-
-
-
-
- {this.renderNodes(nodesGroupedByType)}
-
-
-
- {clickedNode ? (
-
-
- Connectivity of node{' '}
-
- {clickedNode.NodeId}
- {' '}
- to other nodes
-
-
- {this.renderNodes(rightNodes, true)}
-
-
- ) : (
-
-
-
-
-
-
- Select node to see its connectivity to other nodes
-
-
- )}
-
-
-
-
- );
- };
-
- render() {
- const {loading, wasLoaded, error} = this.props;
- if (loading && !wasLoaded) {
- return Network.renderLoader();
- } else if (error) {
- return {error.statusText}
;
- } else {
- return this.renderContent();
- }
- }
-}
-
-const mapStateToProps = (state) => {
- const {wasLoaded, loading, data: netWorkInfo = {}} = state.network;
- const {autorefresh} = state.schema;
- return {
- netWorkInfo,
- wasLoaded,
- loading,
- filter: state.settings.problemFilter,
- autorefresh,
- };
-};
-
-const mapDispatchToProps = {
- getNetworkInfo,
- hideTooltip,
- showTooltip,
- changeFilter,
- setDataWasNotLoaded,
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(Network);
diff --git a/src/containers/Tenant/Diagnostics/Network/Network.tsx b/src/containers/Tenant/Diagnostics/Network/Network.tsx
new file mode 100644
index 0000000000..0390d8766f
--- /dev/null
+++ b/src/containers/Tenant/Diagnostics/Network/Network.tsx
@@ -0,0 +1,324 @@
+import React from 'react';
+
+import {Checkbox, Loader} from '@gravity-ui/uikit';
+import {Link} from 'react-router-dom';
+
+import {ResponseError} from '../../../../components/Errors/ResponseError';
+import {Icon} from '../../../../components/Icon';
+import {Illustration} from '../../../../components/Illustration';
+import {ProblemFilter} from '../../../../components/ProblemFilter';
+import {networkApi} from '../../../../store/reducers/network/network';
+import {
+ ProblemFilterValues,
+ changeFilter,
+ selectProblemFilter,
+} from '../../../../store/reducers/settings/settings';
+import {hideTooltip, showTooltip} from '../../../../store/reducers/tooltip';
+import type {TNetNodeInfo, TNetNodePeerInfo} from '../../../../types/api/netInfo';
+import {cn} from '../../../../utils/cn';
+import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
+import {getDefaultNodePath} from '../../../Node/NodePages';
+
+import {NodeNetwork} from './NodeNetwork/NodeNetwork';
+import {getConnectedNodesCount} from './utils';
+
+import './Network.scss';
+
+const b = cn('network');
+
+interface NetworkProps {
+ path: string;
+}
+export function Network({path}: NetworkProps) {
+ const {autorefresh} = useTypedSelector((state) => state.schema);
+ const filter = useTypedSelector(selectProblemFilter);
+ const dispatch = useTypedDispatch();
+
+ const [clickedNode, setClickedNode] = React.useState();
+ const [showId, setShowId] = React.useState(false);
+ const [showRacks, setShowRacks] = React.useState(false);
+
+ const {currentData, isFetching, error} = networkApi.useGetNetworkInfoQuery(path, {
+ pollingInterval: autorefresh,
+ });
+ const loading = isFetching && currentData === undefined;
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error) {
+ return ;
+ }
+
+ const netWorkInfo = currentData;
+ const nodes = (netWorkInfo?.Tenants && netWorkInfo.Tenants[0].Nodes) ?? [];
+ if (nodes.length === 0) {
+ return no nodes data
;
+ }
+
+ const nodesGroupedByType = groupNodesByField(nodes, 'NodeType');
+ const rightNodes = clickedNode ? groupNodesByField(clickedNode.Peers ?? [], 'NodeType') : {};
+
+ return (
+
+
+
+
+
+
+
{
+ dispatch(changeFilter(v));
+ }}
+ className={b('problem-filter')}
+ />
+
+ {
+ setShowId(!showId);
+ }}
+ checked={showId}
+ >
+ ID
+
+
+
+ {
+ setShowRacks(!showRacks);
+ }}
+ checked={showRacks}
+ >
+ Racks
+
+
+
+
+
+
+
+
+ {clickedNode ? (
+
+
+ Connectivity of node{' '}
+
+ {clickedNode.NodeId}
+ {' '}
+ to other nodes
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+ Select node to see its connectivity to other nodes
+
+
+ )}
+
+
+
+
+ );
+}
+
+interface NodesProps {
+ nodes: Record;
+ isRight?: boolean;
+ showId?: boolean;
+ showRacks?: boolean;
+ clickedNode?: TNetNodeInfo;
+ onClickNode: (node: TNetNodeInfo | undefined) => void;
+}
+function Nodes({nodes, isRight, showId, showRacks, clickedNode, onClickNode}: NodesProps) {
+ const filter = useTypedSelector(selectProblemFilter);
+ const dispatch = useTypedDispatch();
+
+ let problemNodesCount = 0;
+ const result = Object.keys(nodes).map((key, j) => {
+ const nodesGroupedByRack = groupNodesByField(nodes[key], 'Rack');
+ return (
+
+
{key} nodes
+
+ {showRacks
+ ? Object.keys(nodesGroupedByRack).map((key, i) => (
+
+
+ {key === 'undefined' ? '?' : key}
+
+ {nodesGroupedByRack[key].map((nodeInfo, index) => {
+ let capacity, connected;
+ if (!isRight && 'Peers' in nodeInfo && nodeInfo.Peers) {
+ capacity = Object.keys(nodeInfo.Peers).length;
+ connected = getConnectedNodesCount(nodeInfo.Peers);
+ }
+
+ if (
+ (filter === ProblemFilterValues.PROBLEMS &&
+ capacity !== connected) ||
+ filter === ProblemFilterValues.ALL ||
+ isRight
+ ) {
+ problemNodesCount++;
+ return (
+
{
+ dispatch(showTooltip(...params));
+ }}
+ onMouseLeave={() => {
+ dispatch(hideTooltip());
+ }}
+ onClick={
+ isRight
+ ? undefined
+ : () => {
+ onClickNode(
+ clickedNode &&
+ nodeInfo.NodeId ===
+ clickedNode.NodeId
+ ? undefined
+ : (nodeInfo as TNetNodeInfo),
+ );
+ }
+ }
+ isBlurred={
+ !isRight &&
+ clickedNode &&
+ clickedNode.NodeId !== nodeInfo.NodeId
+ }
+ />
+ );
+ }
+ return null;
+ })}
+
+ ))
+ : nodes[key].map((nodeInfo, index) => {
+ let capacity, connected;
+ const peers =
+ nodeInfo && 'Peers' in nodeInfo ? nodeInfo.Peers : undefined;
+ if (!isRight && 'Peers' in nodeInfo && nodeInfo.Peers) {
+ capacity = nodeInfo.Peers.length;
+ connected = getConnectedNodesCount(peers);
+ }
+
+ if (
+ (filter === ProblemFilterValues.PROBLEMS &&
+ capacity !== connected) ||
+ filter === ProblemFilterValues.ALL ||
+ isRight
+ ) {
+ problemNodesCount++;
+ return (
+
{
+ dispatch(showTooltip(...params));
+ }}
+ onMouseLeave={() => {
+ dispatch(hideTooltip());
+ }}
+ onClick={
+ isRight
+ ? undefined
+ : () => {
+ onClickNode(
+ clickedNode &&
+ nodeInfo.NodeId ===
+ clickedNode.NodeId
+ ? undefined
+ : (nodeInfo as TNetNodeInfo),
+ );
+ }
+ }
+ isBlurred={
+ !isRight &&
+ clickedNode &&
+ clickedNode.NodeId !== nodeInfo.NodeId
+ }
+ />
+ );
+ }
+ return null;
+ })}
+
+
+ );
+ });
+
+ if (filter === ProblemFilterValues.PROBLEMS && problemNodesCount === 0) {
+ return ;
+ } else {
+ return result;
+ }
+}
+
+function groupNodesByField>(
+ nodes: T[],
+ field: 'NodeType' | 'Rack',
+) {
+ return nodes.reduce>((acc, node) => {
+ if (acc[node[field]]) {
+ acc[node[field]].push(node);
+ } else {
+ acc[node[field]] = [node];
+ }
+ return acc;
+ }, {});
+}
diff --git a/src/containers/Tenant/Diagnostics/Network/NodeNetwork/NodeNetwork.js b/src/containers/Tenant/Diagnostics/Network/NodeNetwork/NodeNetwork.js
deleted file mode 100644
index 5cfa07a961..0000000000
--- a/src/containers/Tenant/Diagnostics/Network/NodeNetwork/NodeNetwork.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-
-import {EFlag} from '../../../../../types/api/enums';
-import {cn} from '../../../../../utils/cn';
-
-import './NodeNetwork.scss';
-
-const b = cn('node-network');
-
-const getNodeModifier = (connected, capacity) => {
- const percents = Math.floor((connected / capacity) * 100);
- if (percents === 100) {
- return EFlag.Green;
- } else if (percents >= 70) {
- return EFlag.Yellow;
- } else if (percents >= 1) {
- return EFlag.Red;
- } else {
- return EFlag.Grey;
- }
-};
-
-export class NodeNetwork extends React.Component {
- static propTypes = {
- onMouseEnter: PropTypes.func,
- onMouseLeave: PropTypes.func,
- nodeId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
- connected: PropTypes.number,
- capacity: PropTypes.number,
- rack: PropTypes.string,
- status: PropTypes.string,
- onClick: PropTypes.func,
- showID: PropTypes.bool,
- isBlurred: PropTypes.bool,
- };
-
- static defaultProps = {
- onMouseEnter: () => {},
- onMouseLeave: () => {},
- onClick: () => {},
- };
-
- node = React.createRef();
-
- _onNodeHover = () => {
- const {onMouseEnter, nodeId, connected, capacity, rack} = this.props;
- const popupData = {nodeId, connected, capacity, rack};
- onMouseEnter(this.node.current, popupData, 'node');
- };
- _onNodeLeave = () => {
- this.props.onMouseLeave();
- };
-
- render() {
- const {nodeId, connected, capacity, status, onClick, showID, isBlurred} = this.props;
-
- const color = status || getNodeModifier(connected, capacity);
-
- return (
- onClick(nodeId)}
- >
- {showID && nodeId}
-
- );
- }
-}
-
-export default NodeNetwork;
diff --git a/src/containers/Tenant/Diagnostics/Network/NodeNetwork/NodeNetwork.tsx b/src/containers/Tenant/Diagnostics/Network/NodeNetwork/NodeNetwork.tsx
new file mode 100644
index 0000000000..fbc3a77b0f
--- /dev/null
+++ b/src/containers/Tenant/Diagnostics/Network/NodeNetwork/NodeNetwork.tsx
@@ -0,0 +1,73 @@
+import React from 'react';
+
+import {EFlag} from '../../../../../types/api/enums';
+import type {ITooltipTemplateType} from '../../../../../types/store/tooltip';
+import {cn} from '../../../../../utils/cn';
+
+import './NodeNetwork.scss';
+
+const b = cn('node-network');
+
+function getNodeModifier(connected = 0, capacity = 0) {
+ const percents = Math.floor((connected / capacity) * 100);
+ if (percents === 100) {
+ return EFlag.Green;
+ } else if (percents >= 70) {
+ return EFlag.Yellow;
+ } else if (percents >= 1) {
+ return EFlag.Red;
+ } else {
+ return EFlag.Grey;
+ }
+}
+
+function noop() {}
+
+interface NodeNetworkProps {
+ onMouseEnter?: (node: HTMLDivElement, data: any, type: ITooltipTemplateType) => void;
+ onMouseLeave?: () => void;
+ nodeId: number | string;
+ connected?: number;
+ capacity?: number;
+ rack: string;
+ status?: EFlag;
+ onClick?: (nodeId: number | string) => void;
+ showID?: boolean;
+ isBlurred?: boolean;
+}
+
+export function NodeNetwork({
+ nodeId,
+ connected,
+ capacity,
+ rack,
+ status,
+ onClick = noop,
+ onMouseEnter = noop,
+ onMouseLeave = noop,
+ showID,
+ isBlurred,
+}: NodeNetworkProps) {
+ const ref = React.useRef(null);
+ const color = status || getNodeModifier(connected, capacity);
+
+ return (
+ {
+ onMouseEnter(ref.current!, {nodeId, connected, capacity, rack}, 'node');
+ }}
+ onMouseLeave={() => {
+ onMouseLeave();
+ }}
+ onClick={() => onClick(nodeId)}
+ >
+ {showID ? nodeId : null}
+
+ );
+}
diff --git a/src/containers/Tenant/Diagnostics/Overview/Overview.tsx b/src/containers/Tenant/Diagnostics/Overview/Overview.tsx
index b8a8aabbd2..b56a72c65e 100644
--- a/src/containers/Tenant/Diagnostics/Overview/Overview.tsx
+++ b/src/containers/Tenant/Diagnostics/Overview/Overview.tsx
@@ -1,30 +1,21 @@
import React from 'react';
+import {skipToken} from '@reduxjs/toolkit/query';
import {shallowEqual} from 'react-redux';
import {ResponseError} from '../../../../components/Errors/ResponseError';
import {TableIndexInfo} from '../../../../components/InfoViewer/schemaInfo';
import {Loader} from '../../../../components/Loader';
-import {
- getOlapStats,
- resetLoadingState as resetOlapLoadingState,
-} from '../../../../store/reducers/olapStats';
-import {
- getOverview,
- getOverviewBatched,
- setCurrentOverviewPath,
- setDataWasNotLoaded,
-} from '../../../../store/reducers/overview/overview';
+import {olapApi} from '../../../../store/reducers/olapStats';
+import {overviewApi} from '../../../../store/reducers/overview/overview';
import {selectSchemaMergedChildrenPaths} from '../../../../store/reducers/schema/schema';
-import {getTopic} from '../../../../store/reducers/topic';
import {EPathType} from '../../../../types/api/schema';
-import {useAutofetcher, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
+import {useTypedSelector} from '../../../../utils/hooks';
import {ExternalDataSourceInfo} from '../../Info/ExternalDataSource/ExternalDataSource';
import {ExternalTableInfo} from '../../Info/ExternalTable/ExternalTable';
import {
isColumnEntityType,
isEntityWithMergedImplementation,
- isPathTypeWithTopic,
isTableType,
} from '../../utils/schema';
@@ -38,21 +29,17 @@ interface OverviewProps {
}
function Overview({type, tenantName}: OverviewProps) {
- const dispatch = useTypedDispatch();
-
const {autorefresh, currentSchemaPath} = useTypedSelector((state) => state.schema);
- const {
- data: rawData,
- additionalData,
- loading: overviewLoading,
- wasLoaded: overviewWasLoaded,
- error: overviewError,
- } = useTypedSelector((state) => state.overview);
- const {
- data: {result: olapStats} = {result: undefined},
- loading: olapStatsLoading,
- wasLoaded: olapStatsWasLoaded,
- } = useTypedSelector((state) => state.olapStats);
+
+ const schemaPath = currentSchemaPath || tenantName;
+ const olapParams =
+ isTableType(type) && isColumnEntityType(type) ? {path: schemaPath} : skipToken;
+ const {currentData: olapData, isFetching: olapIsFetching} = olapApi.useGetOlapStatsQuery(
+ olapParams,
+ {pollingInterval: autorefresh},
+ );
+ const olapStatsLoading = olapIsFetching && olapData === undefined;
+ const {result: olapStats} = olapData || {result: undefined};
const isEntityWithMergedImpl = isEntityWithMergedImplementation(type);
@@ -62,52 +49,27 @@ function Overview({type, tenantName}: OverviewProps) {
shallowEqual,
);
- const entityLoading =
- (overviewLoading && !overviewWasLoaded) || (olapStatsLoading && !olapStatsWasLoaded);
- const entityNotReady = isEntityWithMergedImpl && !mergedChildrenPaths;
+ let paths: string[] | typeof skipToken = skipToken;
+ if (schemaPath) {
+ if (!isEntityWithMergedImpl) {
+ paths = [schemaPath];
+ } else if (mergedChildrenPaths) {
+ paths = [schemaPath, ...mergedChildrenPaths];
+ }
+ }
- const fetchData = React.useCallback(
- (isBackground: boolean) => {
- const schemaPath = currentSchemaPath || tenantName;
-
- if (!schemaPath) {
- return;
- }
-
- dispatch(setCurrentOverviewPath(schemaPath));
-
- if (!isBackground) {
- dispatch(setDataWasNotLoaded());
- }
-
- if (!isEntityWithMergedImpl) {
- dispatch(getOverview({path: schemaPath}));
- } else if (mergedChildrenPaths) {
- dispatch(getOverviewBatched([schemaPath, ...mergedChildrenPaths]));
- }
-
- if (isTableType(type) && isColumnEntityType(type)) {
- if (!isBackground) {
- dispatch(resetOlapLoadingState());
- }
- dispatch(getOlapStats({path: schemaPath}));
- }
-
- if (isPathTypeWithTopic(type)) {
- dispatch(getTopic(currentSchemaPath));
- }
- },
- [
- tenantName,
- currentSchemaPath,
- type,
- isEntityWithMergedImpl,
- mergedChildrenPaths,
- dispatch,
- ],
- );
+ const {
+ currentData,
+ isFetching,
+ error: overviewError,
+ } = overviewApi.useGetOverviewQuery(paths, {
+ pollingInterval: autorefresh,
+ });
+ const overviewLoading = isFetching && currentData === undefined;
+ const {data: rawData, additionalData} = currentData || {};
- useAutofetcher(fetchData, [fetchData], autorefresh);
+ const entityLoading = overviewLoading || olapStatsLoading;
+ const entityNotReady = isEntityWithMergedImpl && !mergedChildrenPaths;
const renderContent = () => {
const data = rawData ?? undefined;
diff --git a/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx b/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx
index 4e3aa1c903..f496c44f59 100644
--- a/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx
+++ b/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx
@@ -5,7 +5,7 @@ import {LabelWithPopover} from '../../../../../components/LabelWithPopover';
import {LagPopoverContent} from '../../../../../components/LagPopoverContent';
import {Loader} from '../../../../../components/Loader';
import {SpeedMultiMeter} from '../../../../../components/SpeedMultiMeter';
-import {selectPreparedTopicStats} from '../../../../../store/reducers/topic';
+import {selectPreparedTopicStats, topicApi} from '../../../../../store/reducers/topic';
import type {IPreparedTopicStats} from '../../../../../types/store/topic';
import {cn} from '../../../../../utils/cn';
import {formatBps, formatBytes} from '../../../../../utils/dataFormatters/dataFormatters';
@@ -70,11 +70,15 @@ const prepareBytesWrittenInfo = (data: IPreparedTopicStats): Array {
- const {error, loading, wasLoaded} = useTypedSelector((state) => state.topic);
-
- const data = useTypedSelector(selectPreparedTopicStats);
+ const {autorefresh, currentSchemaPath} = useTypedSelector((state) => state.schema);
+ const {currentData, isFetching, error} = topicApi.useGetTopicQuery(
+ {path: currentSchemaPath},
+ {pollingInterval: autorefresh},
+ );
+ const loading = isFetching && currentData === undefined;
+ const data = useTypedSelector((state) => selectPreparedTopicStats(state, currentSchemaPath));
- if (loading && !wasLoaded) {
+ if (loading) {
return (
diff --git a/src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx b/src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx
index 0e914a9a7a..34a8bd659b 100644
--- a/src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx
+++ b/src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx
@@ -1,29 +1,16 @@
import React from 'react';
import DataTable from '@gravity-ui/react-data-table';
+import {skipToken} from '@reduxjs/toolkit/query';
import {ResponseError} from '../../../../components/Errors/ResponseError';
import {TableSkeleton} from '../../../../components/TableSkeleton/TableSkeleton';
-import {selectNodesMap} from '../../../../store/reducers/nodesList';
-import {
- getPartitions,
- setDataWasNotLoaded as setPartitionsDataWasNotLoaded,
- setSelectedConsumer,
-} from '../../../../store/reducers/partitions/partitions';
-import {
- cleanTopicData,
- getTopic,
- selectConsumersNames,
- setDataWasNotLoaded as setTopicDataWasNotLoaded,
-} from '../../../../store/reducers/topic';
+import {nodesListApi, selectNodesMap} from '../../../../store/reducers/nodesList';
+import {partitionsApi, setSelectedConsumer} from '../../../../store/reducers/partitions/partitions';
+import {selectConsumersNames, topicApi} from '../../../../store/reducers/topic';
import {cn} from '../../../../utils/cn';
import {DEFAULT_TABLE_SETTINGS, PARTITIONS_HIDDEN_COLUMNS_KEY} from '../../../../utils/constants';
-import {
- useAutofetcher,
- useSetting,
- useTypedDispatch,
- useTypedSelector,
-} from '../../../../utils/hooks';
+import {useSetting, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
import {PartitionsControls} from './PartitionsControls/PartitionsControls';
import i18n from './i18n';
@@ -50,69 +37,58 @@ export const Partitions = ({path}: PartitionsProps) => {
PreparedPartitionDataWithHosts[]
>([]);
- const consumers = useTypedSelector(selectConsumersNames);
- const nodesMap = useTypedSelector(selectNodesMap);
+ const consumers = useTypedSelector((state) => selectConsumersNames(state, path));
const {autorefresh} = useTypedSelector((state) => state.schema);
+ const {selectedConsumer} = useTypedSelector((state) => state.partitions);
const {
- loading: partitionsLoading,
- wasLoaded: partitionsWasLoaded,
- error: partitionsError,
- partitions: rawPartitions,
- selectedConsumer,
- } = useTypedSelector((state) => state.partitions);
- const {
- loading: topicLoading,
- wasLoaded: topicWasLoaded,
+ currentData: topicData,
+ isFetching: topicIsFetching,
error: topicError,
- } = useTypedSelector((state) => state.topic);
+ } = topicApi.useGetTopicQuery({path});
+ const topicLoading = topicIsFetching && topicData === undefined;
const {
- loading: nodesLoading,
- wasLoaded: nodesWasLoaded,
+ currentData: nodesData,
+ isFetching: nodesIsFetching,
error: nodesError,
- } = useTypedSelector((state) => state.nodesList);
+ } = nodesListApi.useGetNodesListQuery(undefined);
+ const nodesLoading = nodesIsFetching && nodesData === undefined;
+ const nodesMap = useTypedSelector(selectNodesMap);
const [hiddenColumns, setHiddenColumns] = useSetting
(PARTITIONS_HIDDEN_COLUMNS_KEY);
const [columns, columnsIdsForSelector] = useGetPartitionsColumns(selectedConsumer);
React.useEffect(() => {
- dispatch(cleanTopicData());
- dispatch(setTopicDataWasNotLoaded());
-
- dispatch(getTopic(path));
-
setComponentCurrentPath(path);
}, [dispatch, path]);
+ const params =
+ !topicLoading && componentCurrentPath
+ ? {path: componentCurrentPath, consumerName: selectedConsumer}
+ : skipToken;
+ const {
+ currentData: partitionsData,
+ isFetching: partitionsIsFetching,
+ error: partitionsError,
+ } = partitionsApi.useGetPartitionsQuery(params, {pollingInterval: autorefresh});
+ const partitionsLoading = partitionsIsFetching && partitionsData === undefined;
+ const rawPartitions = partitionsData;
+
const partitionsWithHosts = React.useMemo(() => {
return addHostToPartitions(rawPartitions, nodesMap);
}, [rawPartitions, nodesMap]);
- const fetchData = React.useCallback(
- (isBackground: boolean) => {
- if (!isBackground) {
- dispatch(setPartitionsDataWasNotLoaded());
- }
- if (topicWasLoaded && componentCurrentPath) {
- dispatch(getPartitions(componentCurrentPath, selectedConsumer));
- }
- },
- [dispatch, selectedConsumer, componentCurrentPath, topicWasLoaded],
- );
-
- useAutofetcher(fetchData, [fetchData], autorefresh);
-
// Wrong consumer could be passed in search query
// Reset consumer if it doesn't exist for current topic
React.useEffect(() => {
- const isTopicWithoutConsumers = topicWasLoaded && !consumers;
+ const isTopicWithoutConsumers = !topicLoading && !consumers;
const wrongSelectedConsumer =
selectedConsumer && consumers && !consumers.includes(selectedConsumer);
if (isTopicWithoutConsumers || wrongSelectedConsumer) {
dispatch(setSelectedConsumer(''));
}
- }, [dispatch, topicWasLoaded, selectedConsumer, consumers]);
+ }, [dispatch, topicLoading, selectedConsumer, consumers]);
const columnsToShow = React.useMemo(() => {
return columns.filter((column) => !hiddenColumns.includes(column.name));
@@ -126,10 +102,7 @@ export const Partitions = ({path}: PartitionsProps) => {
dispatch(setSelectedConsumer(value));
};
- const loading =
- (topicLoading && !topicWasLoaded) ||
- (nodesLoading && !nodesWasLoaded) ||
- (partitionsLoading && !partitionsWasLoaded);
+ const loading = topicLoading || nodesLoading || partitionsLoading;
const error = nodesError || topicError || partitionsError;
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx
index 1044edb864..ea639d90b7 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx
@@ -1,7 +1,6 @@
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
import {topNodesApi} from '../../../../../store/reducers/tenantOverview/topNodes/topNodes';
import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
-import {DEFAULT_POLLING_INTERVAL} from '../../../../../utils/constants';
import {useSearchQuery, useTypedSelector} from '../../../../../utils/hooks';
import {getTopNodesByCpuColumns} from '../../../../Nodes/getNodesColumns';
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
@@ -22,7 +21,7 @@ export function TopNodesByCpu({path, additionalNodesProps}: TopNodesByCpuProps)
const {currentData, isFetching, error} = topNodesApi.useGetTopNodesQuery(
{tenant: path, sortValue: 'CPU'},
- {pollingInterval: autorefresh ? DEFAULT_POLLING_INTERVAL : 0},
+ {pollingInterval: autorefresh},
);
const loading = isFetching && currentData === undefined;
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx
index 5da25aa5b2..90380ca6a8 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx
@@ -1,7 +1,6 @@
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
import {topNodesApi} from '../../../../../store/reducers/tenantOverview/topNodes/topNodes';
import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
-import {DEFAULT_POLLING_INTERVAL} from '../../../../../utils/constants';
import {useSearchQuery, useTypedSelector} from '../../../../../utils/hooks';
import {getTopNodesByLoadColumns} from '../../../../Nodes/getNodesColumns';
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
@@ -22,7 +21,7 @@ export function TopNodesByLoad({path, additionalNodesProps}: TopNodesByLoadProps
const {currentData, isFetching, error} = topNodesApi.useGetTopNodesQuery(
{tenant: path, sortValue: 'LoadAverage'},
- {pollingInterval: autorefresh ? DEFAULT_POLLING_INTERVAL : 0},
+ {pollingInterval: autorefresh},
);
const loading = isFetching && currentData === undefined;
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx
index 0eeb7e14a2..16acbf42d1 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx
@@ -11,7 +11,6 @@ import {
TENANT_QUERY_TABS_ID,
} from '../../../../../store/reducers/tenant/constants';
import {topQueriesApi} from '../../../../../store/reducers/tenantOverview/topQueries/tenantOverviewTopQueries';
-import {DEFAULT_POLLING_INTERVAL} from '../../../../../utils/constants';
import {useTypedDispatch, useTypedSelector} from '../../../../../utils/hooks';
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
import {getTenantOverviewTopQueriesColumns} from '../../TopQueries/getTopQueriesColumns';
@@ -33,9 +32,9 @@ export function TopQueries({path}: TopQueriesProps) {
const {autorefresh} = useTypedSelector((state) => state.schema);
const columns = getTenantOverviewTopQueriesColumns();
- const {currentData, isFetching, error} = topQueriesApi.useGetTopQueriesQuery(
+ const {currentData, isFetching, error} = topQueriesApi.useGetOverviewTopQueriesQuery(
{database: path},
- {pollingInterval: autorefresh ? DEFAULT_POLLING_INTERVAL : 0},
+ {pollingInterval: autorefresh},
);
const loading = isFetching && currentData === undefined;
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx
index 8a6548e534..c9c50dda62 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx
@@ -3,7 +3,6 @@ import {useLocation} from 'react-router';
import {parseQuery} from '../../../../../routes';
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
import {topShardsApi} from '../../../../../store/reducers/tenantOverview/topShards/tenantOverviewTopShards';
-import {DEFAULT_POLLING_INTERVAL} from '../../../../../utils/constants';
import {useTypedSelector} from '../../../../../utils/hooks';
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
import {getTopShardsColumns} from '../../TopShards/getTopShardsColumns';
@@ -24,7 +23,7 @@ export const TopShards = ({path}: TopShardsProps) => {
const {currentData, isFetching, error} = topShardsApi.useGetTopShardsQuery(
{database: path, path: currentSchemaPath},
- {pollingInterval: autorefresh ? DEFAULT_POLLING_INTERVAL : 0},
+ {pollingInterval: autorefresh},
);
const loading = isFetching && currentData === undefined;
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantDashboard/TenantDashboard.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantDashboard/TenantDashboard.tsx
index 8a5fbbb53b..2831523e44 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantDashboard/TenantDashboard.tsx
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantDashboard/TenantDashboard.tsx
@@ -39,7 +39,7 @@ export const TenantDashboard = ({database, charts}: TenantDashboardProps) => {
const {autorefresh} = useTypedSelector((state) => state.schema);
// Refetch data only if dashboard successfully loaded
- const shouldRefresh = autorefresh && !isDashboardHidden;
+ const shouldRefresh = isDashboardHidden ? 0 : autorefresh;
/**
* Charts should be hidden, if they are not enabled:
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx
index 3e4634855b..0233aea8ef 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx
@@ -1,7 +1,6 @@
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
import {topNodesApi} from '../../../../../store/reducers/tenantOverview/topNodes/topNodes';
import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
-import {DEFAULT_POLLING_INTERVAL} from '../../../../../utils/constants';
import {useSearchQuery, useTypedSelector} from '../../../../../utils/hooks';
import {getTopNodesByMemoryColumns} from '../../../../Nodes/getNodesColumns';
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
@@ -24,7 +23,7 @@ export function TopNodesByMemory({path, additionalNodesProps}: TopNodesByMemoryP
const {currentData, isFetching, error} = topNodesApi.useGetTopNodesQuery(
{tenant: path, sortValue: 'Memory'},
- {pollingInterval: autorefresh ? DEFAULT_POLLING_INTERVAL : 0},
+ {pollingInterval: autorefresh},
);
const loading = isFetching && currentData === undefined;
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx
index 443cc1d11f..aadfcff788 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx
@@ -5,7 +5,7 @@ import {TENANT_METRICS_TABS_IDS} from '../../../../store/reducers/tenant/constan
import {tenantApi} from '../../../../store/reducers/tenant/tenant';
import {calculateTenantMetrics} from '../../../../store/reducers/tenants/utils';
import type {AdditionalNodesProps, AdditionalTenantsProps} from '../../../../types/additionalProps';
-import {DEFAULT_POLLING_INTERVAL, TENANT_DEFAULT_TITLE} from '../../../../utils/constants';
+import {TENANT_DEFAULT_TITLE} from '../../../../utils/constants';
import {useTypedSelector} from '../../../../utils/hooks';
import {mapDatabaseTypeToDBName} from '../../utils/schema';
@@ -46,7 +46,7 @@ export function TenantOverview({
const {currentData: tenant, isFetching} = tenantApi.useGetTenantInfoQuery(
{path: tenantName},
{
- pollingInterval: autorefresh ? DEFAULT_POLLING_INTERVAL : 0,
+ pollingInterval: autorefresh,
},
);
const tenantLoading = isFetching && tenant === undefined;
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx
index 7ece61bae6..d23786f055 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx
@@ -1,6 +1,5 @@
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
import {topStorageGroupsApi} from '../../../../../store/reducers/tenantOverview/topStorageGroups/topStorageGroups';
-import {DEFAULT_POLLING_INTERVAL} from '../../../../../utils/constants';
import {useSearchQuery, useTypedSelector} from '../../../../../utils/hooks';
import {getStorageTopGroupsColumns} from '../../../../Storage/StorageGroups/getStorageGroupsColumns';
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
@@ -21,7 +20,7 @@ export function TopGroups({tenant}: TopGroupsProps) {
const {currentData, isFetching, error} = topStorageGroupsApi.useGetTopStorageGroupsQuery(
{tenant},
- {pollingInterval: autorefresh ? DEFAULT_POLLING_INTERVAL : 0},
+ {pollingInterval: autorefresh},
);
const loading = isFetching && currentData === undefined;
const topGroups = currentData;
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx
index f0123e9040..b6050998e3 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx
@@ -7,7 +7,6 @@ import {LinkToSchemaObject} from '../../../../../components/LinkToSchemaObject/L
import {topTablesApi} from '../../../../../store/reducers/tenantOverview/executeTopTables/executeTopTables';
import type {KeyValueRow} from '../../../../../types/api/query';
import {formatBytes, getSizeWithSignificantDigits} from '../../../../../utils/bytesParsers';
-import {DEFAULT_POLLING_INTERVAL} from '../../../../../utils/constants';
import {useTypedSelector} from '../../../../../utils/hooks';
import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
import {getSectionTitle} from '../getSectionTitle';
@@ -26,7 +25,7 @@ export function TopTables({path}: TopTablesProps) {
const {currentData, error, isFetching} = topTablesApi.useGetTopTablesQuery(
{path},
- {pollingInterval: autorefresh ? DEFAULT_POLLING_INTERVAL : 0},
+ {pollingInterval: autorefresh},
);
const loading = isFetching && currentData === undefined;
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/useHealthcheck.ts b/src/containers/Tenant/Diagnostics/TenantOverview/useHealthcheck.ts
index 4c88f16439..72cc767126 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/useHealthcheck.ts
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/useHealthcheck.ts
@@ -6,7 +6,6 @@ import {
import type {IssuesTree} from '../../../../store/reducers/healthcheckInfo/types';
import {SelfCheckResult} from '../../../../types/api/healthcheck';
import type {StatusFlag} from '../../../../types/api/healthcheck';
-import {DEFAULT_POLLING_INTERVAL} from '../../../../utils/constants';
import {useTypedSelector} from '../../../../utils/hooks';
interface HealthcheckParams {
@@ -20,7 +19,7 @@ interface HealthcheckParams {
export const useHealthcheck = (
tenantName: string,
- {autorefresh}: {autorefresh?: boolean} = {},
+ {autorefresh}: {autorefresh?: number} = {},
): HealthcheckParams => {
const {
currentData: data,
@@ -28,7 +27,7 @@ export const useHealthcheck = (
error,
refetch,
} = healthcheckApi.useGetHealthcheckInfoQuery(tenantName, {
- pollingInterval: autorefresh ? DEFAULT_POLLING_INTERVAL : 0,
+ pollingInterval: autorefresh,
});
const selfCheckResult = data?.self_check_result || SelfCheckResult.UNSPECIFIED;
const issuesStatistics = useTypedSelector((state) => selectIssuesStatistics(state, tenantName));
diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx
index b676faf801..ddce120f39 100644
--- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx
+++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx
@@ -10,22 +10,18 @@ import {Search} from '../../../../components/Search';
import {parseQuery} from '../../../../routes';
import {changeUserInput} from '../../../../store/reducers/executeQuery';
import {
- fetchTopQueries,
setTopQueriesFilters,
- setTopQueriesState,
+ topQueriesApi,
} from '../../../../store/reducers/executeTopQueries/executeTopQueries';
-import type {ITopQueriesFilters} from '../../../../store/reducers/executeTopQueries/types';
import {
TENANT_PAGE,
TENANT_PAGES_IDS,
TENANT_QUERY_TABS_ID,
} from '../../../../store/reducers/tenant/constants';
import type {EPathType} from '../../../../types/api/schema';
-import type {IQueryResult} from '../../../../types/store/query';
import {cn} from '../../../../utils/cn';
-import {HOUR_IN_SECONDS} from '../../../../utils/constants';
import {isSortableTopQueriesProperty} from '../../../../utils/diagnostics';
-import {useAutofetcher, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
+import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
import {prepareQueryError} from '../../../../utils/query';
import {TenantTabsGroups, getTenantPath} from '../../TenantPages';
import {QUERY_TABLE_SETTINGS} from '../../utils/constants';
@@ -50,79 +46,23 @@ export const TopQueries = ({path, type}: TopQueriesProps) => {
const {autorefresh} = useTypedSelector((state) => state.schema);
- const {
- loading,
- wasLoaded,
- error,
- data: {result: data = undefined} = {},
- filters: storeFilters,
- } = useTypedSelector((state) => state.executeTopQueries);
- const rawColumns = getTopQueriesColumns();
-
- const preventFetch = React.useRef(false);
-
- // some filters sync between redux state and URL
- // component state is for default values,
- // default values are determined from the query response, and should not propagate to URL
- const [filters, setFilters] = React.useState(storeFilters);
-
- React.useEffect(() => {
- dispatch(setTopQueriesFilters(filters));
- }, [dispatch, filters]);
+ const filters = useTypedSelector((state) => state.executeTopQueries);
+ const {currentData, isFetching, error} = topQueriesApi.useGetTopQueriesQuery(
+ {
+ database: path,
+ filters,
+ },
+ {pollingInterval: autorefresh},
+ );
+ const loading = isFetching && currentData === undefined;
+ const {result: data} = currentData || {};
+ const rawColumns = getTopQueriesColumns();
const columns = rawColumns.map((column) => ({
...column,
sortable: isSortableTopQueriesProperty(column.name),
}));
- const setDefaultFiltersFromResponse = (responseData?: IQueryResult) => {
- const intervalEnd = responseData?.result?.[0]?.IntervalEnd;
-
- if (intervalEnd) {
- const to = new Date(intervalEnd).getTime();
- const from = new Date(to - HOUR_IN_SECONDS * 1000).getTime();
-
- setFilters((currentFilters) => {
- // request without filters returns the latest interval with data
- // only in this case should update filters in ui
- // also don't update if user already interacted with controls
- const shouldUpdateFilters = !currentFilters.from && !currentFilters.to;
-
- if (!shouldUpdateFilters) {
- return currentFilters;
- }
-
- preventFetch.current = true;
-
- return {...currentFilters, from, to};
- });
- }
- };
-
- useAutofetcher(
- (isBackground) => {
- if (preventFetch.current) {
- preventFetch.current = false;
- return;
- }
-
- if (!isBackground) {
- dispatch(
- setTopQueriesState({
- wasLoaded: false,
- data: undefined,
- }),
- );
- }
-
- dispatch(fetchTopQueries({database: path, filters})).then(
- setDefaultFiltersFromResponse,
- );
- },
- [dispatch, filters, path],
- autorefresh,
- );
-
const handleRowClick = React.useCallback(
(row: any) => {
const {QueryText: input} = row;
@@ -143,11 +83,11 @@ export const TopQueries = ({path, type}: TopQueriesProps) => {
);
const handleTextSearchUpdate = (text: string) => {
- setFilters((currentFilters) => ({...currentFilters, text}));
+ dispatch(setTopQueriesFilters({text}));
};
const handleDateRangeChange = (value: DateRangeValues) => {
- setFilters((currentFilters) => ({...currentFilters, ...value}));
+ dispatch(setTopQueriesFilters(value));
};
const renderLoader = () => {
@@ -159,11 +99,11 @@ export const TopQueries = ({path, type}: TopQueriesProps) => {
};
const renderContent = () => {
- if (loading && !wasLoaded) {
+ if (loading) {
return renderLoader();
}
- if (error && !error.isCancelled) {
+ if (error && typeof error === 'object' && !(error as any).isCancelled) {
return {prepareQueryError(error)}
;
}
diff --git a/src/containers/Tenant/Diagnostics/TopShards/Filters/Filters.tsx b/src/containers/Tenant/Diagnostics/TopShards/Filters/Filters.tsx
index c24fb3e096..c0b25a2ac2 100644
--- a/src/containers/Tenant/Diagnostics/TopShards/Filters/Filters.tsx
+++ b/src/containers/Tenant/Diagnostics/TopShards/Filters/Filters.tsx
@@ -3,7 +3,7 @@ import {RadioButton} from '@gravity-ui/uikit';
import type {DateRangeValues} from '../../../../../components/DateRange';
import {DateRange} from '../../../../../components/DateRange';
import {EShardsWorkloadMode} from '../../../../../store/reducers/shardsWorkload/types';
-import type {IShardsWorkloadFilters} from '../../../../../store/reducers/shardsWorkload/types';
+import type {ShardsWorkloadFilters} from '../../../../../store/reducers/shardsWorkload/types';
import {isEnumMember} from '../../../../../utils/typecheckers';
import {b} from '../TopShards';
import i18n from '../i18n';
@@ -11,8 +11,8 @@ import i18n from '../i18n';
import './Filters.scss';
interface FiltersProps {
- value: IShardsWorkloadFilters;
- onChange: (value: Partial) => void;
+ value: ShardsWorkloadFilters;
+ onChange: (value: Partial) => void;
className?: string;
}
diff --git a/src/containers/Tenant/Diagnostics/TopShards/TopShards.tsx b/src/containers/Tenant/Diagnostics/TopShards/TopShards.tsx
index d7be4c0fa6..3561b63857 100644
--- a/src/containers/Tenant/Diagnostics/TopShards/TopShards.tsx
+++ b/src/containers/Tenant/Diagnostics/TopShards/TopShards.tsx
@@ -6,19 +6,18 @@ import {Loader} from '@gravity-ui/uikit';
import {useLocation} from 'react-router';
import {
- sendShardQuery,
setShardsQueryFilters,
- setShardsState,
+ shardApi,
} from '../../../../store/reducers/shardsWorkload/shardsWorkload';
import {EShardsWorkloadMode} from '../../../../store/reducers/shardsWorkload/types';
-import type {IShardsWorkloadFilters} from '../../../../store/reducers/shardsWorkload/types';
+import type {ShardsWorkloadFilters} from '../../../../store/reducers/shardsWorkload/types';
import type {CellValue, KeyValueRow} from '../../../../types/api/query';
import type {EPathType} from '../../../../types/api/schema';
import {cn} from '../../../../utils/cn';
import {DEFAULT_TABLE_SETTINGS, HOUR_IN_SECONDS} from '../../../../utils/constants';
import {formatDateTime} from '../../../../utils/dataFormatters/dataFormatters';
import {isSortableTopShardsProperty} from '../../../../utils/diagnostics';
-import {useAutofetcher, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
+import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
import {prepareQueryError} from '../../../../utils/query';
import {isColumnEntityType} from '../../utils/schema';
@@ -79,7 +78,7 @@ function dataTableToStringSortOrder(value: SortOrder | SortOrder[] = []) {
return sortOrders.map(({columnId}) => columnId).join(',');
}
-function fillDateRangeFor(value: IShardsWorkloadFilters) {
+function fillDateRangeFor(value: ShardsWorkloadFilters) {
value.to = Date.now();
value.from = value.to - HOUR_IN_SECONDS * 1000;
return value;
@@ -96,17 +95,11 @@ export const TopShards = ({tenantPath, type}: TopShardsProps) => {
const {autorefresh, currentSchemaPath} = useTypedSelector((state) => state.schema);
- const {
- loading,
- data: {result: data = undefined} = {},
- filters: storeFilters,
- error,
- wasLoaded,
- } = useTypedSelector((state) => state.shardsWorkload);
+ const storeFilters = useTypedSelector((state) => state.shardsWorkload);
// default filters shouldn't propagate into URL until user interacts with the control
// redux initial value can't be used, as it synchronizes with URL
- const [filters, setFilters] = React.useState(() => {
+ const [filters, setFilters] = React.useState(() => {
const defaultValue = {...storeFilters};
if (!defaultValue.mode) {
@@ -121,31 +114,21 @@ export const TopShards = ({tenantPath, type}: TopShardsProps) => {
});
const [sortOrder, setSortOrder] = React.useState(tableColumnsNames.CPUCores);
-
- useAutofetcher(
- () => {
- dispatch(
- sendShardQuery({
- database: tenantPath,
- path: currentSchemaPath,
- sortOrder: stringToQuerySortOrder(sortOrder),
- filters,
- }),
- );
+ const {
+ data: result,
+ isFetching,
+ error,
+ } = shardApi.useSendShardQueryQuery(
+ {
+ database: tenantPath,
+ path: currentSchemaPath,
+ sortOrder: stringToQuerySortOrder(sortOrder),
+ filters,
},
- [dispatch, tenantPath, currentSchemaPath, sortOrder, filters],
- autorefresh,
+ {pollingInterval: autorefresh},
);
-
- // don't show loader for requests triggered by table sort, only for path change
- React.useEffect(() => {
- dispatch(
- setShardsState({
- wasLoaded: false,
- data: undefined,
- }),
- );
- }, [dispatch, currentSchemaPath, tenantPath, filters]);
+ const loading = isFetching && result === undefined;
+ const {result: data} = result ?? {};
const onSort = (newSortOrder?: SortOrder | SortOrder[]) => {
// omit information about sort order to disable ASC order, only DESC makes sense for top shards
@@ -154,7 +137,7 @@ export const TopShards = ({tenantPath, type}: TopShardsProps) => {
setSortOrder(dataTableToStringSortOrder(newSortOrder));
};
- const handleFiltersChange = (value: Partial) => {
+ const handleFiltersChange = (value: Partial) => {
const newStateValue = {...value};
const isDateRangePristine =
!storeFilters.from && !storeFilters.to && !value.from && !value.to;
@@ -212,11 +195,11 @@ export const TopShards = ({tenantPath, type}: TopShardsProps) => {
};
const renderContent = () => {
- if (loading && !wasLoaded) {
+ if (loading) {
return renderLoader();
}
- if (error && !error.isCancelled) {
+ if (error && typeof error === 'object' && !(error as any).isCancelled) {
return {prepareQueryError(error)}
;
}
diff --git a/src/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx b/src/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx
index 2067dc33a5..e3a00af7bc 100644
--- a/src/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx
+++ b/src/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx
@@ -16,7 +16,7 @@ import './ObjectGeneral.scss';
const b = cn('object-general');
interface ObjectGeneralProps {
- type: EPathType;
+ type?: EPathType;
tenantName: string;
additionalTenantProps?: AdditionalTenantsProps;
additionalNodesProps?: AdditionalNodesProps;
diff --git a/src/containers/Tenant/Query/Preview/Preview.tsx b/src/containers/Tenant/Query/Preview/Preview.tsx
index 949d208ed8..678efd1ed2 100644
--- a/src/containers/Tenant/Query/Preview/Preview.tsx
+++ b/src/containers/Tenant/Query/Preview/Preview.tsx
@@ -1,16 +1,14 @@
-import React from 'react';
-
import {Button, Loader} from '@gravity-ui/uikit';
import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
import {Icon} from '../../../../components/Icon';
import {QueryResultTable} from '../../../../components/QueryResultTable';
-import {sendQuery, setQueryOptions} from '../../../../store/reducers/preview';
+import {previewApi} from '../../../../store/reducers/preview';
import {setShowPreview} from '../../../../store/reducers/schema/schema';
import type {EPathType} from '../../../../types/api/schema';
import {cn} from '../../../../utils/cn';
-import {useAutofetcher, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
+import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
import {prepareQueryError} from '../../../../utils/query';
import {isExternalTable, isTableType} from '../../utils/schema';
import i18n from '../i18n';
@@ -27,39 +25,16 @@ interface PreviewProps {
export const Preview = ({database, type}: PreviewProps) => {
const dispatch = useTypedDispatch();
- const {data = {}, loading, error, wasLoaded} = useTypedSelector((state) => state.preview);
const {autorefresh, currentSchemaPath} = useTypedSelector((state) => state.schema);
const isFullscreen = useTypedSelector((state) => state.fullscreen);
- const sendQueryForPreview = React.useCallback(
- (isBackground: boolean) => {
- if (!isTableType(type)) {
- return;
- }
-
- if (!isBackground) {
- dispatch(
- setQueryOptions({
- wasLoaded: false,
- data: undefined,
- }),
- );
- }
-
- const query = `--!syntax_v1\nselect * from \`${currentSchemaPath}\` limit 32`;
-
- dispatch(
- sendQuery({
- query,
- database,
- action: isExternalTable(type) ? 'execute-query' : 'execute-scan',
- }),
- );
- },
- [dispatch, database, currentSchemaPath, type],
+ const query = `--!syntax_v1\nselect * from \`${currentSchemaPath}\` limit 32`;
+ const {currentData, isFetching, error} = previewApi.useSendQueryQuery(
+ {database, query, action: isExternalTable(type) ? 'execute-query' : 'execute-scan'},
+ {pollingInterval: autorefresh},
);
-
- useAutofetcher(sendQueryForPreview, [sendQueryForPreview], autorefresh);
+ const loading = isFetching && currentData === undefined;
+ const data = currentData ?? {};
const handleClosePreview = () => {
dispatch(setShowPreview(false));
@@ -86,7 +61,7 @@ export const Preview = ({database, type}: PreviewProps) => {
);
};
- if (loading && !wasLoaded) {
+ if (loading) {
return (
diff --git a/src/containers/Tenant/Query/Query.tsx b/src/containers/Tenant/Query/Query.tsx
index 6d819309b9..828081701b 100644
--- a/src/containers/Tenant/Query/Query.tsx
+++ b/src/containers/Tenant/Query/Query.tsx
@@ -22,7 +22,7 @@ const b = cn('ydb-query');
interface QueryProps {
theme: string;
path: string;
- type: EPathType;
+ type?: EPathType;
}
export const Query = (props: QueryProps) => {
diff --git a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx
index e65e0116f7..f7fb431eb3 100644
--- a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx
+++ b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx
@@ -77,7 +77,7 @@ interface QueryEditorProps {
executeQuery: ExecuteQueryState;
explainQuery: ExplainQueryState;
theme: string;
- type: EPathType;
+ type?: EPathType;
showPreview: boolean;
setShowPreview: (...args: Parameters
) => void;
saveQueryToHistory: (...args: Parameters) => void;
@@ -470,7 +470,7 @@ interface ResultProps {
resultVisibilityState: InitialPaneState;
onExpandResultHandler: () => void;
onCollapseResultHandler: () => void;
- type: EPathType;
+ type?: EPathType;
handleAstQuery: () => void;
theme: string;
resultType: ValueOf | undefined;
diff --git a/src/containers/Tenant/Tenant.tsx b/src/containers/Tenant/Tenant.tsx
index 34331db07a..e499ba508e 100644
--- a/src/containers/Tenant/Tenant.tsx
+++ b/src/containers/Tenant/Tenant.tsx
@@ -7,7 +7,7 @@ import {useLocation} from 'react-router';
import {AccessDenied} from '../../components/Errors/403';
import SplitPane from '../../components/SplitPane';
import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
-import {disableAutorefresh, getSchema} from '../../store/reducers/schema/schema';
+import {getSchema} from '../../store/reducers/schema/schema';
import type {AdditionalNodesProps, AdditionalTenantsProps} from '../../types/additionalProps';
import type {TEvDescribeSchemeResult} from '../../types/api/schema';
import {cn} from '../../utils/cn';
@@ -99,10 +99,6 @@ function Tenant(props: TenantProps) {
}
}, [currentSchemaPath, dispatch]);
- React.useEffect(() => {
- dispatch(disableAutorefresh());
- }, [currentSchemaPath, tenantName, dispatch]);
-
React.useEffect(() => {
if (tenantName) {
dispatch(setHeaderBreadcrumbs('tenant', {tenantName}));
@@ -148,7 +144,6 @@ function Tenant(props: TenantProps) {
isCollapsed={summaryVisibilityState.collapsed}
/>
{
const dispatch = useTypedDispatch();
- const {error, loading, wasLoaded, tenants} = useTypedSelector((state) => state.tenants);
+ const {currentData, isFetching, error} = tenantsApi.useGetTenantsInfoQuery(
+ {clusterName},
+ {pollingInterval: DEFAULT_POLLING_INTERVAL},
+ );
+ const loading = isFetching && currentData === undefined;
+ const tenants = currentData ?? [];
+
const searchValue = useTypedSelector(selectTenantsSearchValue);
- const filteredTenants = useTypedSelector(selectFilteredTenants);
+ const filteredTenants = useTypedSelector((state) => selectFilteredTenants(state, clusterName));
const problemFilter = useTypedSelector(selectProblemFilter);
- useAutofetcher(
- () => {
- dispatch(getTenantsInfo(clusterName));
- },
- [dispatch],
- true,
- );
-
const handleProblemFilterChange = (value: ProblemFilterValue) => {
dispatch(changeFilter(value));
};
@@ -83,7 +81,7 @@ export const Tenants = ({additionalTenantsProps}: TenantsProps) => {
total={tenants.length}
current={filteredTenants?.length || 0}
label={'Databases'}
- loading={loading && !wasLoaded}
+ loading={loading}
/>
);
@@ -167,7 +165,7 @@ export const Tenants = ({additionalTenantsProps}: TenantsProps) => {
width: 80,
render: ({row}) => {
// Don't show values below 0.01 when formatted
- if (row.cpu > 10_000) {
+ if (row.cpu && row.cpu > 10_000) {
return formatCPU(row.cpu);
}
return '—';
@@ -261,7 +259,7 @@ export const Tenants = ({additionalTenantsProps}: TenantsProps) => {
return (
{renderControls()}
-
+
{renderTable()}
diff --git a/src/containers/VDiskPage/VDiskPage.tsx b/src/containers/VDiskPage/VDiskPage.tsx
index 5e9d0a719e..c136bd29c1 100644
--- a/src/containers/VDiskPage/VDiskPage.tsx
+++ b/src/containers/VDiskPage/VDiskPage.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import {Icon} from '@gravity-ui/uikit';
+import {skipToken} from '@reduxjs/toolkit/query';
import {Helmet} from 'react-helmet-async';
import {StringParam, useQueryParams} from 'use-query-params';
@@ -13,12 +14,13 @@ import {VDiskWithDonorsStack} from '../../components/VDisk/VDiskWithDonorsStack'
import {VDiskInfo} from '../../components/VDiskInfo/VDiskInfo';
import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
import {selectNodesMap} from '../../store/reducers/nodesList';
-import {getVDiskData, setVDiskDataWasNotLoaded} from '../../store/reducers/vdisk/vdisk';
+import {vDiskApi} from '../../store/reducers/vdisk/vdisk';
import {valueIsDefined} from '../../utils';
import {cn} from '../../utils/cn';
+import {DEFAULT_POLLING_INTERVAL} from '../../utils/constants';
import {stringifyVdiskId} from '../../utils/dataFormatters/dataFormatters';
import {getSeverityColor} from '../../utils/disks/helpers';
-import {useAutofetcher, useTypedDispatch, useTypedSelector} from '../../utils/hooks';
+import {useTypedDispatch, useTypedSelector} from '../../utils/hooks';
import {vDiskPageKeyset} from './i18n';
@@ -32,8 +34,6 @@ export function VDiskPage() {
const dispatch = useTypedDispatch();
const nodesMap = useTypedSelector(selectNodesMap);
- const {vDiskData, groupData, loading, wasLoaded} = useTypedSelector((state) => state.vDisk);
- const {NodeHost, NodeId, NodeType, NodeDC, PDiskId, PDiskType, Severity, VDiskId} = vDiskData;
const [{nodeId, pDiskId, vDiskSlotId}] = useQueryParams({
nodeId: StringParam,
@@ -45,20 +45,16 @@ export function VDiskPage() {
dispatch(setHeaderBreadcrumbs('vDisk', {nodeId, pDiskId, vDiskSlotId}));
}, [dispatch, nodeId, pDiskId, vDiskSlotId]);
- const fetchData = React.useCallback(
- async (isBackground?: boolean) => {
- if (!isBackground) {
- dispatch(setVDiskDataWasNotLoaded());
- }
- if (valueIsDefined(nodeId) && valueIsDefined(pDiskId) && valueIsDefined(vDiskSlotId)) {
- return dispatch(getVDiskData({nodeId, pDiskId, vDiskSlotId}));
- }
- return undefined;
- },
- [dispatch, nodeId, pDiskId, vDiskSlotId],
- );
-
- useAutofetcher(fetchData, [fetchData], true);
+ const params =
+ valueIsDefined(nodeId) && valueIsDefined(pDiskId) && valueIsDefined(vDiskSlotId)
+ ? {nodeId, pDiskId, vDiskSlotId}
+ : skipToken;
+ const {currentData, isFetching, refetch} = vDiskApi.useGetVDiskDataQuery(params, {
+ pollingInterval: DEFAULT_POLLING_INTERVAL,
+ });
+ const loading = isFetching && currentData === undefined;
+ const {vDiskData = {}, groupData} = currentData || {};
+ const {NodeHost, NodeId, NodeType, NodeDC, PDiskId, PDiskType, Severity, VDiskId} = vDiskData;
const handleEvictVDisk = async () => {
const {GroupID, GroupGeneration, Ring, Domain, VDisk} = VDiskId || {};
@@ -83,7 +79,7 @@ export function VDiskPage() {
};
const handleAfterEvictVDisk = async () => {
- return fetchData(true);
+ return refetch();
};
const renderHelmet = () => {
@@ -112,7 +108,7 @@ export function VDiskPage() {
return (
);
@@ -175,7 +171,7 @@ export function VDiskPage() {
};
const renderContent = () => {
- if (loading && !wasLoaded) {
+ if (loading) {
return ;
}
diff --git a/src/services/api.ts b/src/services/api.ts
index f5e283bc79..5d792120b6 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -64,24 +64,32 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
{concurrentId: concurrentId || `getClusterInfo`, requestConfig: {signal}},
);
}
- getClusterNodes({concurrentId}: AxiosOptions = {}) {
+ getClusterNodes({concurrentId, signal}: AxiosOptions = {}) {
return this.get(
this.getPath('/viewer/json/sysinfo'),
{},
- {concurrentId: concurrentId || `getClusterNodes`},
+ {concurrentId: concurrentId || `getClusterNodes`, requestConfig: {signal}},
);
}
- getNodeInfo(id?: string | number) {
- return this.get(this.getPath('/viewer/json/sysinfo?enums=true'), {
- node_id: id,
- });
+ getNodeInfo(id?: string | number, {concurrentId, signal}: AxiosOptions = {}) {
+ return this.get(
+ this.getPath('/viewer/json/sysinfo?enums=true'),
+ {
+ node_id: id,
+ },
+ {concurrentId, requestConfig: {signal}},
+ );
}
- getTenants(clusterName?: string) {
- return this.get(this.getPath('/viewer/json/tenantinfo'), {
- tablets: 1,
- storage: 1,
- cluster_name: clusterName,
- });
+ getTenants(clusterName?: string, {concurrentId, signal}: AxiosOptions = {}) {
+ return this.get(
+ this.getPath('/viewer/json/tenantinfo'),
+ {
+ tablets: 1,
+ storage: 1,
+ cluster_name: clusterName,
+ },
+ {concurrentId, requestConfig: {signal}},
+ );
}
getTenantInfo({path}: {path: string}, {concurrentId, signal}: AxiosOptions = {}) {
return this.get(
@@ -116,14 +124,14 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
/** @deprecated use getNodes instead */
getCompute(
{sortOrder, sortValue, ...params}: ComputeApiRequestParams,
- {concurrentId}: AxiosOptions = {},
+ {concurrentId, signal}: AxiosOptions = {},
) {
const sort = prepareSortValue(sortValue, sortOrder);
return this.get(
this.getPath('/viewer/json/compute?enums=true'),
{sort, ...params},
- {concurrentId},
+ {concurrentId, requestConfig: {signal}},
);
}
getStorageInfo(
@@ -155,45 +163,70 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
{concurrentId, requestConfig: {signal}},
);
}
- getPdiskInfo(nodeId: string | number, pdiskId: string | number) {
- return this.get(this.getPath('/viewer/json/pdiskinfo?enums=true'), {
- filter: `(NodeId=${nodeId}${pdiskId ? `;PDiskId=${pdiskId}` : ''})`,
- });
+ getPDiskInfo(
+ {nodeId, pDiskId}: {nodeId: string | number; pDiskId: string | number},
+ {concurrentId, signal}: AxiosOptions = {},
+ ) {
+ return this.get(
+ this.getPath('/viewer/json/pdiskinfo?enums=true'),
+ {
+ filter: `(NodeId=${nodeId}${pDiskId ? `;PDiskId=${pDiskId}` : ''})`,
+ },
+ {concurrentId, requestConfig: {signal}},
+ );
}
- getVdiskInfo({
- vDiskSlotId,
- pDiskId,
- nodeId,
- }: {
- vDiskSlotId: string | number;
- pDiskId: string | number;
- nodeId: string | number;
- }) {
- return this.get(this.getPath('/viewer/json/vdiskinfo?enums=true'), {
- node_id: nodeId,
- filter: `(PDiskId=${pDiskId};VDiskSlotId=${vDiskSlotId})`,
- });
+ getVDiskInfo(
+ {
+ vDiskSlotId,
+ pDiskId,
+ nodeId,
+ }: {
+ vDiskSlotId: string | number;
+ pDiskId: string | number;
+ nodeId: string | number;
+ },
+ {concurrentId, signal}: AxiosOptions = {},
+ ) {
+ return this.get(
+ this.getPath('/viewer/json/vdiskinfo?enums=true'),
+ {
+ node_id: nodeId,
+ filter: `(PDiskId=${pDiskId};VDiskSlotId=${vDiskSlotId})`,
+ },
+ {concurrentId, requestConfig: {signal}},
+ );
}
- getGroupInfo(groupId: string | number) {
- return this.get(this.getPath('/viewer/json/storage?enums=true'), {
- group_id: groupId,
- });
+ getGroupInfo(groupId: string | number, {concurrentId, signal}: AxiosOptions = {}) {
+ return this.get(
+ this.getPath('/viewer/json/storage?enums=true'),
+ {
+ group_id: groupId,
+ },
+ {concurrentId, requestConfig: {signal}},
+ );
}
- getHostInfo() {
+ getHostInfo({concurrentId, signal}: AxiosOptions = {}) {
return this.get(
this.getPath('/viewer/json/sysinfo?node_id=.&enums=true'),
- {},
+ {concurrentId, requestConfig: {signal}},
);
}
- getTabletsInfo({nodes = [], path}: {nodes?: string[]; path?: string}) {
+ getTabletsInfo(
+ {nodes = [], path}: {nodes?: string[]; path?: string},
+ {concurrentId, signal}: AxiosOptions = {},
+ ) {
const filter = nodes.length > 0 && `(NodeId=[${nodes.join(',')}])`;
- return this.get(this.getPath('/viewer/json/tabletinfo'), {
- filter,
- path,
- enums: true,
- });
+ return this.get(
+ this.getPath('/viewer/json/tabletinfo'),
+ {
+ filter,
+ path,
+ enums: true,
+ },
+ {concurrentId, requestConfig: {signal}},
+ );
}
- getSchema({path}: {path: string}, {concurrentId}: AxiosOptions = {}) {
+ getSchema({path}: {path: string}, {concurrentId, signal}: AxiosOptions = {}) {
return this.get>(
this.getPath('/viewer/json/describe'),
{
@@ -206,10 +239,10 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
partitioning_info: true,
subs: 1,
},
- {concurrentId: concurrentId || `getSchema|${path}`},
+ {concurrentId: concurrentId || `getSchema|${path}`, requestConfig: {signal}},
);
}
- getDescribe({path}: {path: string}, {concurrentId}: AxiosOptions = {}) {
+ getDescribe({path}: {path: string}, {concurrentId, signal}: AxiosOptions = {}) {
return this.get>(
this.getPath('/viewer/json/describe'),
{
@@ -218,35 +251,43 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
partition_stats: true,
subs: 0,
},
- {concurrentId: concurrentId || `getDescribe|${path}`},
+ {concurrentId: concurrentId || `getDescribe|${path}`, requestConfig: {signal}},
);
}
- getSchemaAcl({path}: {path: string}) {
+ getSchemaAcl({path}: {path: string}, {concurrentId, signal}: AxiosOptions = {}) {
return this.get(
this.getPath('/viewer/json/acl'),
{
path,
},
- {concurrentId: `getSchemaAcl`},
+ {concurrentId: concurrentId || `getSchemaAcl`, requestConfig: {signal}},
);
}
- getHeatmapData({path}: {path: string}) {
- return this.get>(this.getPath('/viewer/json/describe'), {
- path,
- enums: true,
- backup: false,
- children: false,
- partition_config: false,
- partition_stats: true,
- });
+ getHeatmapData({path}: {path: string}, {concurrentId, signal}: AxiosOptions = {}) {
+ return this.get>(
+ this.getPath('/viewer/json/describe'),
+ {
+ path,
+ enums: true,
+ backup: false,
+ children: false,
+ partition_config: false,
+ partition_stats: true,
+ },
+ {concurrentId, requestConfig: {signal}},
+ );
}
- getNetwork(path: string) {
- return this.get(this.getPath('/viewer/json/netinfo'), {
- enums: true,
- path,
- });
+ getNetwork(path: string, {concurrentId, signal}: AxiosOptions = {}) {
+ return this.get(
+ this.getPath('/viewer/json/netinfo'),
+ {
+ enums: true,
+ path,
+ },
+ {concurrentId, requestConfig: {signal}},
+ );
}
- getTopic({path}: {path?: string}, {concurrentId}: AxiosOptions = {}) {
+ getTopic({path}: {path?: string}, {concurrentId, signal}: AxiosOptions = {}) {
return this.get(
this.getPath('/viewer/json/describe_topic'),
{
@@ -254,12 +295,12 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
include_stats: true,
path,
},
- {concurrentId: concurrentId || 'getTopic'},
+ {concurrentId, requestConfig: {signal}},
);
}
getConsumer(
{path, consumer}: {path: string; consumer: string},
- {concurrentId}: AxiosOptions = {},
+ {concurrentId, signal}: AxiosOptions = {},
) {
return this.get(
this.getPath('/viewer/json/describe_consumer'),
@@ -269,40 +310,68 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
path,
consumer,
},
- {concurrentId: concurrentId || 'getConsumer'},
+ {concurrentId: concurrentId || 'getConsumer', requestConfig: {signal}},
);
}
- getPoolInfo(poolName: string) {
- return this.get(this.getPath('/viewer/json/storage'), {
- pool: poolName,
- enums: true,
- });
+ getPoolInfo(poolName: string, {concurrentId, signal}: AxiosOptions = {}) {
+ return this.get(
+ this.getPath('/viewer/json/storage'),
+ {
+ pool: poolName,
+ enums: true,
+ },
+ {concurrentId, requestConfig: {signal}},
+ );
}
- getTablet({id}: {id?: string}) {
+ getTablet({id}: {id?: string}, {concurrentId, signal}: AxiosOptions = {}) {
return this.get(
this.getPath(`/viewer/json/tabletinfo?filter=(TabletId=${id})`),
{
enums: true,
},
+ {
+ concurrentId,
+ requestConfig: {signal},
+ },
);
}
- getTabletHistory({id}: {id?: string}) {
+ getTabletHistory({id}: {id?: string}, {concurrentId, signal}: AxiosOptions = {}) {
return this.get(
this.getPath(`/viewer/json/tabletinfo?filter=(TabletId=${id})`),
{
enums: true,
merge: false,
},
+ {
+ concurrentId,
+ requestConfig: {signal},
+ },
);
}
- getNodesList() {
- return this.get(this.getPath('/viewer/json/nodelist'), {enums: true});
+ getNodesList({concurrentId, signal}: AxiosOptions = {}) {
+ return this.get(
+ this.getPath('/viewer/json/nodelist'),
+ {
+ enums: true,
+ },
+ {
+ concurrentId,
+ requestConfig: {signal},
+ },
+ );
}
- getTenantsList() {
- return this.get(this.getPath('/viewer/json/tenants'), {
- enums: true,
- state: 0,
- });
+ getTenantsList({concurrentId, signal}: AxiosOptions = {}) {
+ return this.get(
+ this.getPath('/viewer/json/tenants'),
+ {
+ enums: true,
+ state: 0,
+ },
+ {
+ concurrentId,
+ requestConfig: {signal},
+ },
+ );
}
sendQuery(
{
@@ -376,14 +445,14 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
{},
);
}
- getHotKeys(path: string, enableSampling: boolean, {concurrentId}: AxiosOptions = {}) {
+ getHotKeys(path: string, enableSampling: boolean, {concurrentId, signal}: AxiosOptions = {}) {
return this.get(
this.getPath('/viewer/json/hotkeys'),
{
path,
enable_sampling: enableSampling,
},
- {concurrentId: concurrentId || 'getHotKeys'},
+ {concurrentId: concurrentId || 'getHotKeys', requestConfig: {signal}},
);
}
getHealthcheckInfo(database: string, {concurrentId, signal}: AxiosOptions = {}) {
@@ -465,15 +534,19 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
{},
);
}
- getTabletDescribe(tenantId: TDomainKey) {
- return this.get>(this.getPath('/viewer/json/describe'), {
- schemeshard_id: tenantId?.SchemeShard,
- path_id: tenantId?.PathId,
- });
+ getTabletDescribe(tenantId: TDomainKey, {concurrentId, signal}: AxiosOptions = {}) {
+ return this.get>(
+ this.getPath('/viewer/json/describe'),
+ {
+ schemeshard_id: tenantId?.SchemeShard,
+ path_id: tenantId?.PathId,
+ },
+ {concurrentId, requestConfig: {signal}},
+ );
}
getChartData(
{target, from, until, maxDataPoints, database}: JsonRenderRequestParams,
- {concurrentId}: AxiosOptions = {},
+ {concurrentId, signal}: AxiosOptions = {},
) {
const requestString = `${target}&from=${from}&until=${until}&maxDataPoints=${maxDataPoints}&format=json`;
@@ -486,6 +559,7 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
+ requestConfig: {signal},
},
);
}
@@ -533,10 +607,14 @@ export class YdbWebVersionAPI extends YdbEmbeddedAPI {
).then(parseMetaCluster);
}
- getTenants(clusterName: string) {
- return this.get(`${META_BACKEND || ''}/meta/cp_databases`, {
- cluster_name: clusterName,
- }).then(parseMetaTenants);
+ getTenants(clusterName: string, {concurrentId, signal}: AxiosOptions = {}) {
+ return this.get(
+ `${META_BACKEND || ''}/meta/cp_databases`,
+ {
+ cluster_name: clusterName,
+ },
+ {concurrentId, requestConfig: {signal}},
+ ).then(parseMetaTenants);
}
}
diff --git a/src/store/configureStore.ts b/src/store/configureStore.ts
index 7185ebd7f3..c198aac16a 100644
--- a/src/store/configureStore.ts
+++ b/src/store/configureStore.ts
@@ -27,10 +27,12 @@ function _configureStore<
preloadedState,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
- immutableCheck: {ignoredPaths: ['tooltip.currentHoveredRef']},
+ immutableCheck: {
+ ignoredPaths: ['tooltip.currentHoveredRef'],
+ },
serializableCheck: {
ignoredPaths: ['tooltip.currentHoveredRef', 'api'],
- ignoredActions: [UPDATE_REF],
+ ignoredActions: [UPDATE_REF, 'api/executeQuery/rejected'],
},
}).concat(locationMiddleware, ...middleware),
});
diff --git a/src/store/reducers/api.ts b/src/store/reducers/api.ts
index d29da43430..ebba359785 100644
--- a/src/store/reducers/api.ts
+++ b/src/store/reducers/api.ts
@@ -9,6 +9,7 @@ export const api = createApi({
*/
endpoints: () => ({}),
refetchOnMountOrArgChange: true,
+ invalidationBehavior: 'immediately',
tagTypes: ['All'],
});
diff --git a/src/store/reducers/cluster/cluster.ts b/src/store/reducers/cluster/cluster.ts
index 058c906177..0ab88dd5d3 100644
--- a/src/store/reducers/cluster/cluster.ts
+++ b/src/store/reducers/cluster/cluster.ts
@@ -91,4 +91,5 @@ export const clusterApi = api.injectEndpoints({
providesTags: ['All'],
}),
}),
+ overrideExisting: 'throw',
});
diff --git a/src/store/reducers/clusterNodes/clusterNodes.tsx b/src/store/reducers/clusterNodes/clusterNodes.tsx
index f11b960cfd..3d3f42281e 100644
--- a/src/store/reducers/clusterNodes/clusterNodes.tsx
+++ b/src/store/reducers/clusterNodes/clusterNodes.tsx
@@ -27,4 +27,5 @@ export const clusterNodesApi = api.injectEndpoints({
providesTags: ['All'],
}),
}),
+ overrideExisting: 'throw',
});
diff --git a/src/store/reducers/clusters/clusters.ts b/src/store/reducers/clusters/clusters.ts
index 9444ac279a..7e3df18e95 100644
--- a/src/store/reducers/clusters/clusters.ts
+++ b/src/store/reducers/clusters/clusters.ts
@@ -41,4 +41,5 @@ export const clustersApi = api.injectEndpoints({
providesTags: ['All'],
}),
}),
+ overrideExisting: 'throw',
});
diff --git a/src/store/reducers/describe.ts b/src/store/reducers/describe.ts
index b1c80fee13..d821736b5a 100644
--- a/src/store/reducers/describe.ts
+++ b/src/store/reducers/describe.ts
@@ -1,144 +1,28 @@
-import type {Reducer} from '@reduxjs/toolkit';
-
-import type {
- IDescribeAction,
- IDescribeData,
- IDescribeHandledResponse,
- IDescribeState,
-} from '../../types/store/describe';
-import {createApiRequest, createRequestActionTypes} from '../utils';
-
-export const FETCH_DESCRIBE = createRequestActionTypes('describe', 'FETCH_DESCRIBE');
-const SET_CURRENT_DESCRIBE_PATH = 'describe/SET_CURRENT_DESCRIBE_PATH';
-const SET_DATA_WAS_NOT_LOADED = 'describe/SET_DATA_WAS_NOT_LOADED';
-
-const initialState = {
- loading: false,
- wasLoaded: false,
- data: {},
- currentDescribe: undefined,
- currentDescribePath: undefined,
-};
-
-const describe: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_DESCRIBE.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_DESCRIBE.SUCCESS: {
- const isCurrentDescribePath = action.data.path === state.currentDescribePath;
- const newData = {...state.data, ...action.data.data};
-
- if (!isCurrentDescribePath) {
- return {
- ...state,
- data: newData,
- };
- }
-
- return {
- ...state,
- data: newData,
- currentDescribe: action.data.currentDescribe,
- loading: false,
- wasLoaded: true,
- error: undefined,
- };
- }
-
- case FETCH_DESCRIBE.FAILURE: {
- if (action.error?.isCancelled) {
- return state;
- }
-
- return {
- ...state,
- error: action.error,
- loading: false,
- };
- }
- case SET_CURRENT_DESCRIBE_PATH: {
- return {
- ...state,
- currentDescribePath: action.data,
- };
- }
- case SET_DATA_WAS_NOT_LOADED: {
- return {
- ...state,
- wasLoaded: false,
- };
- }
- default:
- return state;
- }
-};
-
-export const setCurrentDescribePath = (path: string) => {
- return {
- type: SET_CURRENT_DESCRIBE_PATH,
- data: path,
- } as const;
-};
-
-export const setDataWasNotLoaded = () => {
- return {
- type: SET_DATA_WAS_NOT_LOADED,
- } as const;
-};
-
-export function getDescribe({path}: {path: string}) {
- const request = window.api.getDescribe({path});
- return createApiRequest({
- request,
- actions: FETCH_DESCRIBE,
- dataHandler: (data): IDescribeHandledResponse => {
- const dataPath = data?.Path;
- const currentDescribe: IDescribeData = {};
- const newData: IDescribeData = {};
-
- if (dataPath) {
- currentDescribe[dataPath] = data;
- newData[dataPath] = data;
- }
-
- return {
- path: dataPath,
- currentDescribe,
- data: newData,
- };
- },
- });
-}
-
-export function getDescribeBatched(paths: string[]) {
- const requestsArray = paths.map((p) => window.api.getDescribe({path: p}));
-
- const request = Promise.all(requestsArray);
- return createApiRequest({
- request,
- actions: FETCH_DESCRIBE,
- dataHandler: (data): IDescribeHandledResponse => {
- const currentDescribe: IDescribeData = {};
- const newData: IDescribeData = {};
-
- data.forEach((dataItem) => {
- if (dataItem?.Path) {
- newData[dataItem.Path] = dataItem;
- currentDescribe[dataItem.Path] = dataItem;
+import type {IDescribeData} from '../../types/store/describe';
+
+import {api} from './api';
+
+export const describeApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getDescribe: build.query({
+ queryFn: async (paths: string[], {signal}) => {
+ try {
+ const response = await Promise.all(
+ paths.map((p) => window.api.getDescribe({path: p}, {signal})),
+ );
+ const data = response.reduce((acc, item) => {
+ if (item?.Path) {
+ acc[item.Path] = item;
+ }
+ return acc;
+ }, {});
+ return {data};
+ } catch (error) {
+ return {error};
}
- });
-
- return {
- path: data[0]?.Path,
- currentDescribe,
- data: newData,
- };
- },
- });
-}
-
-export default describe;
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/store/reducers/executeTopQueries/executeTopQueries.ts b/src/store/reducers/executeTopQueries/executeTopQueries.ts
index b8021ce1b9..4351d33fbf 100644
--- a/src/store/reducers/executeTopQueries/executeTopQueries.ts
+++ b/src/store/reducers/executeTopQueries/executeTopQueries.ts
@@ -1,24 +1,29 @@
-import type {AnyAction, Reducer, ThunkAction} from '@reduxjs/toolkit';
+import {createSlice} from '@reduxjs/toolkit';
+import type {PayloadAction} from '@reduxjs/toolkit';
-import type {RootState} from '../..';
-import type {IQueryResult} from '../../../types/store/query';
+import {HOUR_IN_SECONDS} from '../../../utils/constants';
import {parseQueryAPIExecuteResponse} from '../../../utils/query';
-import {createApiRequest, createRequestActionTypes} from '../../utils';
+import {api} from '../api';
-import type {ITopQueriesAction, ITopQueriesFilters, ITopQueriesState} from './types';
+import type {TopQueriesFilters} from './types';
import {getFiltersConditions} from './utils';
-export const FETCH_TOP_QUERIES = createRequestActionTypes('top-queries', 'FETCH_TOP_QUERIES');
-const SET_TOP_QUERIES_STATE = 'top-queries/SET_TOP_QUERIES_STATE';
-const SET_TOP_QUERIES_FILTERS = 'top-queries/SET_TOP_QUERIES_FILTERS';
+const initialState: TopQueriesFilters = {};
-const initialState = {
- loading: false,
- wasLoaded: false,
- filters: {},
-};
+const slice = createSlice({
+ name: 'executeTopQueries',
+ initialState,
+ reducers: {
+ setTopQueriesFilters: (state, action: PayloadAction) => {
+ return {...state, ...action.payload};
+ },
+ },
+});
+
+export const {setTopQueriesFilters} = slice.actions;
+export default slice.reducer;
-const getQueryText = (path: string, filters?: ITopQueriesFilters) => {
+const getQueryText = (path: string, filters?: TopQueriesFilters) => {
const filterConditions = getFiltersConditions(path, filters);
return `
SELECT
@@ -35,87 +40,39 @@ WHERE ${filterConditions || 'true'}
`;
};
-const executeTopQueries: Reducer = (
- state = initialState,
- action,
-) => {
- switch (action.type) {
- case FETCH_TOP_QUERIES.REQUEST: {
- return {
- ...state,
- loading: true,
- error: undefined,
- };
- }
- case FETCH_TOP_QUERIES.SUCCESS: {
- return {
- ...state,
- data: action.data,
- loading: false,
- error: undefined,
- wasLoaded: true,
- };
- }
- // 401 Unauthorized error is handled by GenericAPI
- case FETCH_TOP_QUERIES.FAILURE: {
- return {
- ...state,
- error: action.error || 'Unauthorized',
- loading: false,
- };
- }
- case SET_TOP_QUERIES_STATE:
- return {
- ...state,
- ...action.data,
- };
- case SET_TOP_QUERIES_FILTERS:
- return {
- ...state,
- filters: {
- ...state.filters,
- ...action.filters,
- },
- };
- default:
- return state;
- }
-};
-
-type FetchTopQueries = (params: {
- database: string;
- filters?: ITopQueriesFilters;
-}) => ThunkAction, RootState, unknown, AnyAction>;
-
-export const fetchTopQueries: FetchTopQueries = ({database, filters}) =>
- createApiRequest({
- request: window.api.sendQuery(
- {
- schema: 'modern',
- query: getQueryText(database, filters),
- database,
- action: 'execute-scan',
+export const topQueriesApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getTopQueries: build.query({
+ queryFn: async (
+ {database, filters}: {database: string; filters?: TopQueriesFilters},
+ {signal, dispatch},
+ ) => {
+ try {
+ const response = await window.api.sendQuery(
+ {
+ schema: 'modern',
+ query: getQueryText(database, filters),
+ database,
+ action: 'execute-scan',
+ },
+ {signal},
+ );
+ const data = parseQueryAPIExecuteResponse(response);
+ // FIXME: do we really need this?
+ if (!filters?.from && !filters?.to) {
+ const intervalEnd = data?.result?.[0]?.IntervalEnd;
+ if (intervalEnd) {
+ const to = new Date(intervalEnd).getTime();
+ const from = new Date(to - HOUR_IN_SECONDS * 1000).getTime();
+ dispatch(setTopQueriesFilters({from, to}));
+ }
+ }
+ return {data};
+ } catch (error) {
+ return {error};
+ }
},
- {
- concurrentId: 'executeTopQueries',
- },
- ),
- actions: FETCH_TOP_QUERIES,
- dataHandler: parseQueryAPIExecuteResponse,
- });
-
-export function setTopQueriesState(state: Partial) {
- return {
- type: SET_TOP_QUERIES_STATE,
- data: state,
- } as const;
-}
-
-export function setTopQueriesFilters(filters: Partial) {
- return {
- type: SET_TOP_QUERIES_FILTERS,
- filters,
- } as const;
-}
-
-export default executeTopQueries;
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/store/reducers/executeTopQueries/types.ts b/src/store/reducers/executeTopQueries/types.ts
index f0a106b258..c43fac2093 100644
--- a/src/store/reducers/executeTopQueries/types.ts
+++ b/src/store/reducers/executeTopQueries/types.ts
@@ -1,13 +1,4 @@
-import type {IQueryResult, QueryErrorResponse} from '../../../types/store/query';
-import type {ApiRequestAction} from '../../utils';
-
-import type {
- FETCH_TOP_QUERIES,
- setTopQueriesFilters,
- setTopQueriesState,
-} from './executeTopQueries';
-
-export interface ITopQueriesFilters {
+export interface TopQueriesFilters {
/** ms from epoch */
from?: number;
/** ms from epoch */
@@ -15,19 +6,6 @@ export interface ITopQueriesFilters {
text?: string;
}
-export interface ITopQueriesState {
- loading: boolean;
- wasLoaded: boolean;
- data?: IQueryResult;
- error?: QueryErrorResponse;
- filters: ITopQueriesFilters;
-}
-
-export type ITopQueriesAction =
- | ApiRequestAction
- | ReturnType
- | ReturnType;
-
-export interface ITopQueriesRootStateSlice {
- executeTopQueries: ITopQueriesState;
+export interface TopQueriesRootStateSlice {
+ executeTopQueries: TopQueriesFilters;
}
diff --git a/src/store/reducers/executeTopQueries/utils.ts b/src/store/reducers/executeTopQueries/utils.ts
index e9b72e4605..147f402092 100644
--- a/src/store/reducers/executeTopQueries/utils.ts
+++ b/src/store/reducers/executeTopQueries/utils.ts
@@ -1,4 +1,4 @@
-import type {ITopQueriesFilters} from './types';
+import type {TopQueriesFilters} from './types';
const endTimeColumn = 'EndTime';
const intervalEndColumn = 'IntervalEnd';
@@ -9,7 +9,7 @@ const getMaxIntervalSubquery = (path: string) => `(
FROM \`${path}/.sys/top_queries_by_cpu_time_one_hour\`
)`;
-export function getFiltersConditions(path: string, filters?: ITopQueriesFilters) {
+export function getFiltersConditions(path: string, filters?: TopQueriesFilters) {
const conditions: string[] = [];
if (filters?.from && filters?.to && filters.from > filters.to) {
diff --git a/src/store/reducers/healthcheckInfo/healthcheckInfo.ts b/src/store/reducers/healthcheckInfo/healthcheckInfo.ts
index d982730cf9..aacf026293 100644
--- a/src/store/reducers/healthcheckInfo/healthcheckInfo.ts
+++ b/src/store/reducers/healthcheckInfo/healthcheckInfo.ts
@@ -20,6 +20,7 @@ export const healthcheckApi = api.injectEndpoints({
providesTags: ['All'],
}),
}),
+ overrideExisting: 'throw',
});
const mapStatusToPriority: Partial> = {
diff --git a/src/store/reducers/heatmap.ts b/src/store/reducers/heatmap.ts
index 3b99654067..ab5021b6e0 100644
--- a/src/store/reducers/heatmap.ts
+++ b/src/store/reducers/heatmap.ts
@@ -1,118 +1,117 @@
-import type {Reducer} from '@reduxjs/toolkit';
+import {createSlice} from '@reduxjs/toolkit';
+import type {PayloadAction} from '@reduxjs/toolkit';
+import type {TEvDescribeSchemeResult} from '../../types/api/schema';
+import type {TEvTabletStateResponse} from '../../types/api/tablet';
import type {
- IHeatmapAction,
IHeatmapApiRequestParams,
+ IHeatmapMetricValue,
IHeatmapState,
IHeatmapTabletData,
} from '../../types/store/heatmap';
-import {createApiRequest, createRequestActionTypes} from '../utils';
+import type {Nullable} from '../../utils/typecheckers';
+import type {RootState} from '../defaultStore';
-export const FETCH_HEATMAP = createRequestActionTypes('heatmap', 'FETCH_HEATMAP');
+import {api} from './api';
-const SET_HEATMAP_OPTIONS = 'heatmap/SET_HEATMAP_OPTIONS';
-
-export const initialState = {
- loading: false,
- wasLoaded: false,
+export const initialState: IHeatmapState = {
currentMetric: undefined,
sort: false,
heatmap: false,
};
-const heatmap: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_HEATMAP.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_HEATMAP.SUCCESS: {
- return {
- ...state,
- ...action.data,
- loading: false,
- wasLoaded: true,
- error: undefined,
- };
- }
- case FETCH_HEATMAP.FAILURE: {
- return {
- ...state,
- error: action.error,
- loading: false,
- wasLoaded: false,
- };
- }
- case SET_HEATMAP_OPTIONS:
+const slice = createSlice({
+ name: 'heatmap',
+ initialState,
+ reducers: {
+ setHeatmapOptions: (state, action: PayloadAction>) => {
return {
...state,
- ...action.data,
+ ...action.payload,
};
- default:
- return state;
- }
-};
+ },
+ },
+});
-export function getTabletsInfo({nodes, path}: IHeatmapApiRequestParams) {
- return createApiRequest({
- request: Promise.all([
- window.api.getTabletsInfo({nodes, path}),
- window.api.getHeatmapData({path}),
- ]),
- actions: FETCH_HEATMAP,
- dataHandler: ([tabletsData = {}, describe]) => {
- const {TabletStateInfo: tablets = []} = tabletsData;
- const TabletsMap: Map = new Map();
- const {PathDescription = {}} = describe ?? {};
- const {
- TablePartitions = [],
- TablePartitionStats = [],
- TablePartitionMetrics = [],
- } = PathDescription;
+export default slice.reducer;
- tablets.forEach((item) => {
- if (item.TabletId) {
- TabletsMap.set(item.TabletId, item);
- }
- });
+export const {setHeatmapOptions} = slice.actions;
+
+export const heatmapApi = api.injectEndpoints({
+ endpoints: (builder) => ({
+ getHeatmapTabletsInfo: builder.query({
+ queryFn: async (
+ {nodes, path}: IHeatmapApiRequestParams,
+ {signal, getState, dispatch},
+ ) => {
+ try {
+ const response = await Promise.all([
+ window.api.getTabletsInfo({nodes, path}, {signal}),
+ window.api.getHeatmapData({path}, {signal}),
+ ]);
+ const data = transformResponse(response);
- TablePartitions.forEach((item, index) => {
- const metrics = Object.assign(
- {},
- TablePartitionStats[index],
- TablePartitionMetrics[index],
- );
- if (item.DatashardId) {
- TabletsMap.set(item.DatashardId, {
- ...TabletsMap.get(item.DatashardId),
- metrics,
- });
+ if (data.metrics?.length) {
+ const state = getState() as RootState;
+ const currentMetric = state.heatmap.currentMetric;
+ if (
+ !currentMetric ||
+ !data.metrics.find((item) => item.value === currentMetric)
+ ) {
+ dispatch(setHeatmapOptions({currentMetric: data.metrics[0].value}));
+ }
+ }
+
+ return {data};
+ } catch (error) {
+ return {error};
}
- });
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
- const preparedTablets = Array.from(TabletsMap.values());
- const selectMetrics =
- preparedTablets[0] &&
- preparedTablets[0].metrics &&
- Object.keys(preparedTablets[0].metrics).map((item) => {
- return {
- value: item,
- content: item,
- };
- });
+function transformResponse([tabletsData, describe]: [
+ TEvTabletStateResponse,
+ Nullable,
+]) {
+ const {TabletStateInfo: tablets = []} = tabletsData;
+ const TabletsMap: Map = new Map();
+ const {PathDescription = {}} = describe ?? {};
+ const {
+ TablePartitions = [],
+ TablePartitionStats = [],
+ TablePartitionMetrics = [],
+ } = PathDescription;
- return {data: preparedTablets, metrics: selectMetrics};
- },
+ tablets.forEach((item) => {
+ if (item.TabletId) {
+ TabletsMap.set(item.TabletId, item);
+ }
});
-}
-export function setHeatmapOptions(options: Partial) {
- return {
- type: SET_HEATMAP_OPTIONS,
- data: options,
- } as const;
-}
+ TablePartitions.forEach((item, index) => {
+ const metrics = Object.assign({}, TablePartitionStats[index], TablePartitionMetrics[index]);
+ if (item.DatashardId) {
+ TabletsMap.set(item.DatashardId, {
+ ...TabletsMap.get(item.DatashardId),
+ metrics,
+ });
+ }
+ });
-export default heatmap;
+ const preparedTablets = Array.from(TabletsMap.values());
+ const selectMetrics =
+ preparedTablets[0] &&
+ preparedTablets[0].metrics &&
+ (Object.keys(preparedTablets[0].metrics).map((item) => {
+ return {
+ value: item,
+ content: item,
+ };
+ }) as {value: IHeatmapMetricValue; content: IHeatmapMetricValue}[]);
+
+ return {tablets: preparedTablets, metrics: selectMetrics};
+}
diff --git a/src/store/reducers/index.ts b/src/store/reducers/index.ts
index f256d23ed3..cbff8b9adb 100644
--- a/src/store/reducers/index.ts
+++ b/src/store/reducers/index.ts
@@ -4,7 +4,6 @@ import {api} from './api';
import authentication from './authentication/authentication';
import cluster from './cluster/cluster';
import clusters from './clusters/clusters';
-import describe from './describe';
import executeQuery from './executeQuery';
import executeTopQueries from './executeTopQueries/executeTopQueries';
import explainQuery from './explainQuery';
@@ -13,15 +12,8 @@ import header from './header/header';
import heatmap from './heatmap';
import host from './host';
import hotKeys from './hotKeys/hotKeys';
-import network from './network/network';
-import node from './node/node';
import nodes from './nodes/nodes';
-import nodesList from './nodesList';
-import olapStats from './olapStats';
-import overview from './overview/overview';
import partitions from './partitions/partitions';
-import pDisk from './pdisk/pdisk';
-import preview from './preview';
import saveQuery from './saveQuery';
import schema from './schema/schema';
import schemaAcl from './schemaAcl/schemaAcl';
@@ -29,14 +21,11 @@ import settings from './settings/settings';
import shardsWorkload from './shardsWorkload/shardsWorkload';
import singleClusterMode from './singleClusterMode';
import storage from './storage/storage';
-import tablet from './tablet';
import tablets from './tablets';
import tabletsFilters from './tabletsFilters';
import tenant from './tenant/tenant';
import tenants from './tenants/tenants';
import tooltip from './tooltip';
-import topic from './topic';
-import vDisk from './vdisk/vdisk';
export const rootReducer = {
[api.reducerPath]: api.reducer,
@@ -45,27 +34,17 @@ export const rootReducer = {
cluster,
tenant,
storage,
- node,
tooltip,
tablets,
schema,
- overview,
- olapStats,
host,
- network,
tenants,
- tablet,
- topic,
partitions,
- pDisk,
executeQuery,
explainQuery,
tabletsFilters,
heatmap,
settings,
- preview,
- nodesList,
- describe,
schemaAcl,
executeTopQueries,
shardsWorkload,
@@ -75,7 +54,6 @@ export const rootReducer = {
saveQuery,
fullscreen,
clusters,
- vDisk,
};
const combinedReducer = combineReducers({
diff --git a/src/store/reducers/network/network.ts b/src/store/reducers/network/network.ts
index dc1af970c1..d5ce8ee987 100644
--- a/src/store/reducers/network/network.ts
+++ b/src/store/reducers/network/network.ts
@@ -1,68 +1,18 @@
-import type {Reducer} from '@reduxjs/toolkit';
-
-import {createApiRequest, createRequestActionTypes} from '../../utils';
-
-import type {NetworkAction, NetworkState} from './types';
-
-export const FETCH_ALL_NODES_NETWORK = createRequestActionTypes(
- 'network',
- 'FETCH_ALL_NODES_NETWORK',
-);
-
-const SET_DATA_WAS_NOT_LOADED = 'network/SET_DATA_WAS_NOT_LOADED';
-
-const initialState = {
- loading: false,
- wasLoaded: false,
-};
-
-const network: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_ALL_NODES_NETWORK.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_ALL_NODES_NETWORK.SUCCESS: {
- return {
- ...state,
- data: action.data,
- loading: false,
- wasLoaded: true,
- error: undefined,
- };
- }
- case FETCH_ALL_NODES_NETWORK.FAILURE: {
- return {
- ...state,
- error: action.error,
- loading: false,
- };
- }
-
- case SET_DATA_WAS_NOT_LOADED: {
- return {
- ...state,
- wasLoaded: false,
- };
- }
- default:
- return state;
- }
-};
-
-export const setDataWasNotLoaded = () => {
- return {
- type: SET_DATA_WAS_NOT_LOADED,
- } as const;
-};
-
-export const getNetworkInfo = (tenant: string) => {
- return createApiRequest({
- request: window.api.getNetwork(tenant),
- actions: FETCH_ALL_NODES_NETWORK,
- });
-};
-
-export default network;
+import {api} from '../api';
+
+export const networkApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getNetworkInfo: build.query({
+ queryFn: async (tenant: string, {signal}) => {
+ try {
+ const data = await window.api.getNetwork(tenant, {signal});
+ return {data};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/store/reducers/network/types.ts b/src/store/reducers/network/types.ts
deleted file mode 100644
index fd2f39a3b7..0000000000
--- a/src/store/reducers/network/types.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import type {IResponseError} from '../../../types/api/error';
-import type {TNetInfo} from '../../../types/api/netInfo';
-import type {ApiRequestAction} from '../../utils';
-
-import type {FETCH_ALL_NODES_NETWORK, setDataWasNotLoaded} from './network';
-
-export interface NetworkState {
- loading: boolean;
- wasLoaded: boolean;
- data?: TNetInfo;
- error?: IResponseError;
-}
-
-export type NetworkAction =
- | ApiRequestAction
- | ReturnType;
diff --git a/src/store/reducers/node/node.ts b/src/store/reducers/node/node.ts
index a3ed6383e6..fe596452d1 100644
--- a/src/store/reducers/node/node.ts
+++ b/src/store/reducers/node/node.ts
@@ -1,103 +1,31 @@
-import type {Reducer} from '@reduxjs/toolkit';
+import {api} from '../api';
-import {createApiRequest, createRequestActionTypes} from '../../utils';
-
-import type {NodeAction, NodeState} from './types';
import {prepareNodeData} from './utils';
-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,
- dataHandler: prepareNodeData,
- });
-};
-
-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;
+export const nodeApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getNodeInfo: build.query({
+ queryFn: async ({nodeId}: {nodeId: string}, {signal}) => {
+ try {
+ const data = await window.api.getNodeInfo(nodeId, {signal});
+ return {data: prepareNodeData(data)};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ getNodeStructure: build.query({
+ queryFn: async ({nodeId}: {nodeId: string}, {signal}) => {
+ try {
+ const data = await window.api.getStorageInfo({nodeId}, {signal});
+ return {data};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/store/reducers/node/selectors.ts b/src/store/reducers/node/selectors.ts
index 84cfd3e9dc..b4b10ec722 100644
--- a/src/store/reducers/node/selectors.ts
+++ b/src/store/reducers/node/selectors.ts
@@ -1,22 +1,26 @@
-import type {Selector} from '@reduxjs/toolkit';
import {createSelector} from '@reduxjs/toolkit';
import {stringifyVdiskId} from '../../../utils/dataFormatters/dataFormatters';
import {preparePDiskData} from '../../../utils/disks/prepareDisks';
+import type {RootState} from '../../defaultStore';
-import type {
- NodeStateSlice,
- PreparedNodeStructure,
- PreparedStructureVDisk,
- RawNodeStructure,
-} from './types';
+import {nodeApi} from './node';
+import type {PreparedNodeStructure, PreparedStructureVDisk, RawNodeStructure} from './types';
-const selectNodeId = (state: NodeStateSlice) => state.node?.data?.NodeId;
+const getNodeStructureSelector = createSelector(
+ (nodeId: string) => nodeId,
+ (nodeId) => nodeApi.endpoints.getNodeStructure.select({nodeId}),
+);
-const selectRawNodeStructure = (state: NodeStateSlice) => state.node?.nodeStructure;
+const selectGetNodeStructureData = createSelector(
+ (state: RootState) => state,
+ (_state: RootState, nodeId: string) => getNodeStructureSelector(nodeId),
+ (state, selectGetNodeStructure) => selectGetNodeStructure(state).data,
+);
-export const selectNodeStructure: Selector = createSelector(
- [selectNodeId, selectRawNodeStructure],
+export const selectNodeStructure = createSelector(
+ (_state: RootState, nodeId: string) => Number(nodeId),
+ (state: RootState, nodeId: string) => selectGetNodeStructureData(state, nodeId),
(nodeId, storageInfo) => {
const pools = storageInfo?.StoragePools;
const structure: RawNodeStructure = {};
@@ -43,7 +47,7 @@ export const selectNodeStructure: Selector(
+ const structureWithVDisksArray = Object.keys(structure).reduce(
(preparedStructure, el) => {
const vDisks = structure[el].vDisks;
const vDisksArray = Object.keys(vDisks).reduce(
@@ -58,6 +62,6 @@ export const selectNodeStructure: Selector;
@@ -25,24 +20,3 @@ export interface PreparedStructurePDisk extends PreparedPDisk {
export type PreparedNodeStructure = Record;
export interface PreparedNode extends Partial {}
-
-export interface NodeState {
- data: PreparedNode;
- loading: boolean;
- wasLoaded: boolean;
- error?: IResponseError;
-
- nodeStructure: TStorageInfo;
- loadingStructure: boolean;
- wasLoadedStructure: boolean;
- errorStructure?: IResponseError;
-}
-
-export type NodeAction =
- | ApiRequestAction
- | ApiRequestAction
- | ReturnType;
-
-export interface NodeStateSlice {
- node: NodeState;
-}
diff --git a/src/store/reducers/nodes/nodes.ts b/src/store/reducers/nodes/nodes.ts
index c6083a8671..58104ceb27 100644
--- a/src/store/reducers/nodes/nodes.ts
+++ b/src/store/reducers/nodes/nodes.ts
@@ -80,4 +80,5 @@ export const nodesApi = api.injectEndpoints({
providesTags: ['All'],
}),
}),
+ overrideExisting: 'throw',
});
diff --git a/src/store/reducers/nodesList.ts b/src/store/reducers/nodesList.ts
index e721aa630b..a316f187a7 100644
--- a/src/store/reducers/nodesList.ts
+++ b/src/store/reducers/nodesList.ts
@@ -1,57 +1,30 @@
import {createSelector} from '@reduxjs/toolkit';
-import type {Reducer} from '@reduxjs/toolkit';
-import type {
- NodesListAction,
- NodesListRootStateSlice,
- NodesListState,
-} from '../../types/store/nodesList';
import {prepareNodesMap} from '../../utils/nodes';
-import {createApiRequest, createRequestActionTypes} from '../utils';
+import type {RootState} from '../defaultStore';
-export const FETCH_NODES_LIST = createRequestActionTypes('nodesList', 'FETCH_NODES_LIST');
+import {api} from './api';
-const initialState = {loading: true, wasLoaded: false, data: []};
+export const nodesListApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getNodesList: build.query({
+ queryFn: async (_, {signal}) => {
+ try {
+ const data = await window.api.getNodesList({signal});
+ return {data};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
-const nodesList: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_NODES_LIST.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_NODES_LIST.SUCCESS: {
- return {
- ...state,
- data: action.data,
- loading: false,
- wasLoaded: true,
- error: undefined,
- };
- }
- case FETCH_NODES_LIST.FAILURE: {
- return {
- ...state,
- error: action.error,
- loading: false,
- };
- }
- default:
- return state;
- }
-};
-
-export function getNodesList() {
- return createApiRequest({
- request: window.api.getNodesList(),
- actions: FETCH_NODES_LIST,
- });
-}
+const selectNodesList = nodesListApi.endpoints.getNodesList.select(undefined);
export const selectNodesMap = createSelector(
- (state: NodesListRootStateSlice) => state.nodesList.data,
- (nodes) => prepareNodesMap(nodes),
+ (state: RootState) => selectNodesList(state).data,
+ (data) => prepareNodesMap(data),
);
-
-export default nodesList;
diff --git a/src/store/reducers/olapStats.ts b/src/store/reducers/olapStats.ts
index 6ace287b8a..600b5b4bbe 100644
--- a/src/store/reducers/olapStats.ts
+++ b/src/store/reducers/olapStats.ts
@@ -1,16 +1,6 @@
-import type {Reducer} from '@reduxjs/toolkit';
-
-import type {OlapStatsAction, OlapStatsState} from '../../types/store/olapStats';
import {parseQueryAPIExecuteResponse} from '../../utils/query';
-import {createApiRequest, createRequestActionTypes} from '../utils';
-
-export const FETCH_OLAP_STATS = createRequestActionTypes('query', 'SEND_OLAP_STATS_QUERY');
-const RESET_LOADING_STATE = 'olapStats/RESET_LOADING_STATE';
-const initialState = {
- loading: false,
- wasLoaded: false,
-};
+import {api} from './api';
function createOlatStatsQuery(path: string) {
return `SELECT * FROM \`${path}/.sys/primary_index_stats\``;
@@ -18,59 +8,27 @@ function createOlatStatsQuery(path: string) {
const queryAction = 'execute-scan';
-const olapStats: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_OLAP_STATS.REQUEST: {
- return {
- ...state,
- loading: true,
- error: undefined,
- };
- }
- case FETCH_OLAP_STATS.SUCCESS: {
- return {
- ...state,
- data: action.data,
- loading: false,
- error: undefined,
- wasLoaded: true,
- };
- }
- case FETCH_OLAP_STATS.FAILURE: {
- return {
- ...state,
- error: action.error || 'Unauthorized',
- loading: false,
- };
- }
- case RESET_LOADING_STATE: {
- return {
- ...state,
- wasLoaded: initialState.wasLoaded,
- };
- }
- default:
- return state;
- }
-};
-
-export const getOlapStats = ({path = ''}) => {
- return createApiRequest({
- request: window.api.sendQuery({
- schema: 'modern',
- query: createOlatStatsQuery(path),
- database: path,
- action: queryAction,
+export const olapApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getOlapStats: build.query({
+ queryFn: async ({path = ''}: {path?: string} = {}, {signal}) => {
+ try {
+ const response = await window.api.sendQuery(
+ {
+ schema: 'modern',
+ query: createOlatStatsQuery(path),
+ database: path,
+ action: queryAction,
+ },
+ {signal},
+ );
+ return {data: parseQueryAPIExecuteResponse(response)};
+ } catch (error) {
+ return {error: error || new Error('Unauthorized')};
+ }
+ },
+ providesTags: ['All'],
}),
- actions: FETCH_OLAP_STATS,
- dataHandler: parseQueryAPIExecuteResponse,
- });
-};
-
-export function resetLoadingState() {
- return {
- type: RESET_LOADING_STATE,
- } as const;
-}
-
-export default olapStats;
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/store/reducers/overview/overview.ts b/src/store/reducers/overview/overview.ts
index b04152f5c7..b6bd392059 100644
--- a/src/store/reducers/overview/overview.ts
+++ b/src/store/reducers/overview/overview.ts
@@ -1,108 +1,18 @@
-import type {Reducer} from '@reduxjs/toolkit';
-
-import {createApiRequest, createRequestActionTypes} from '../../utils';
-
-import type {OverviewAction, OverviewHandledResponse, OverviewState} from './types';
-
-export const FETCH_OVERVIEW = createRequestActionTypes('overview', 'FETCH_OVERVIEW');
-const SET_CURRENT_OVERVIEW_PATH = 'overview/SET_CURRENT_OVERVIEW_PATH';
-const SET_DATA_WAS_NOT_LOADED = 'overview/SET_DATA_WAS_NOT_LOADED';
-
-export const initialState = {
- loading: true,
- wasLoaded: false,
-};
-
-const schema: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_OVERVIEW.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_OVERVIEW.SUCCESS: {
- if (action.data.data?.Path !== state.currentOverviewPath) {
- return state;
- }
-
- return {
- ...state,
- error: undefined,
- data: action.data.data,
- additionalData: action.data.additionalData,
- loading: false,
- wasLoaded: true,
- };
- }
- case FETCH_OVERVIEW.FAILURE: {
- if (action.error?.isCancelled) {
- return state;
- }
-
- return {
- ...state,
- error: action.error,
- loading: false,
- };
- }
- case SET_CURRENT_OVERVIEW_PATH: {
- return {
- ...state,
- currentOverviewPath: action.data,
- };
- }
- case SET_DATA_WAS_NOT_LOADED: {
- return {
- ...state,
- wasLoaded: false,
- };
- }
- default:
- return state;
- }
-};
-
-export function getOverview({path}: {path: string}) {
- const request = window.api.getDescribe({path}, {concurrentId: 'getOverview'});
- return createApiRequest({
- request,
- actions: FETCH_OVERVIEW,
- dataHandler: (data): OverviewHandledResponse => {
- return {data};
- },
- });
-}
-
-export function getOverviewBatched(paths: string[]) {
- const requestArray = paths.map((p) =>
- window.api.getDescribe({path: p}, {concurrentId: `getOverviewBatched|${p}`}),
- );
- const request = Promise.all(requestArray);
-
- return createApiRequest({
- request,
- actions: FETCH_OVERVIEW,
- dataHandler: ([item, ...rest]): OverviewHandledResponse => {
- return {
- data: item,
- additionalData: rest,
- };
- },
- });
-}
-
-export function setDataWasNotLoaded() {
- return {
- type: SET_DATA_WAS_NOT_LOADED,
- } as const;
-}
-
-export const setCurrentOverviewPath = (path?: string) => {
- return {
- type: SET_CURRENT_OVERVIEW_PATH,
- data: path,
- } as const;
-};
-
-export default schema;
+import {api} from '../api';
+
+export const overviewApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getOverview: build.query({
+ queryFn: async (paths: string[], {signal}) => {
+ try {
+ const [data, ...additionalData] = await Promise.all(
+ paths.map((p) => window.api.getDescribe({path: p}, {signal})),
+ );
+ return {data: {data, additionalData}};
+ } catch (error) {
+ return {error};
+ }
+ },
+ }),
+ }),
+});
diff --git a/src/store/reducers/overview/types.ts b/src/store/reducers/overview/types.ts
deleted file mode 100644
index 5744583013..0000000000
--- a/src/store/reducers/overview/types.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import type {IResponseError} from '../../../types/api/error';
-import type {TEvDescribeSchemeResult} from '../../../types/api/schema';
-import type {Nullable} from '../../../utils/typecheckers';
-import type {ApiRequestAction} from '../../utils';
-
-import type {FETCH_OVERVIEW, setCurrentOverviewPath, setDataWasNotLoaded} from './overview';
-
-export interface OverviewState {
- loading: boolean;
- wasLoaded: boolean;
- currentOverviewPath?: string;
- data?: Nullable;
- additionalData?: Nullable[];
- error?: IResponseError;
-}
-
-export interface OverviewHandledResponse {
- data: Nullable;
- additionalData?: Nullable[];
-}
-
-export type OverviewAction =
- | ApiRequestAction
- | ReturnType
- | ReturnType;
diff --git a/src/store/reducers/partitions/partitions.ts b/src/store/reducers/partitions/partitions.ts
index a771495615..fb3ec22a5e 100644
--- a/src/store/reducers/partitions/partitions.ts
+++ b/src/store/reducers/partitions/partitions.ts
@@ -1,102 +1,56 @@
-import type {Reducer} from '@reduxjs/toolkit';
+import {createSlice} from '@reduxjs/toolkit';
+import type {PayloadAction} from '@reduxjs/toolkit';
-import {createApiRequest, createRequestActionTypes} from '../../utils';
+import {api} from '../api';
-import type {PartitionsAction, PartitionsState} from './types';
+import type {PartitionsState} from './types';
import {prepareConsumerPartitions, prepareTopicPartitions} from './utils';
-export const FETCH_PARTITIONS = createRequestActionTypes('partitions', 'FETCH_PARTITIONS');
-
-const SET_SELECTED_CONSUMER = 'partitions/SET_SELECTED_CONSUMER';
-const SET_DATA_WAS_NOT_LOADED = 'partitions/SET_DATA_WAS_NOT_LOADED';
-
-const initialState = {
- loading: false,
- wasLoaded: false,
+const initialState: PartitionsState = {
selectedConsumer: '',
};
-const partitions: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_PARTITIONS.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_PARTITIONS.SUCCESS: {
- return {
- ...state,
- partitions: action.data,
- loading: false,
- wasLoaded: true,
- error: undefined,
- };
- }
- case FETCH_PARTITIONS.FAILURE: {
- if (action.error?.isCancelled) {
- return state;
- }
-
- return {
- ...state,
- error: action.error,
- loading: false,
- };
- }
- case SET_SELECTED_CONSUMER: {
- return {
- ...state,
- selectedConsumer: action.data,
- };
- }
- case SET_DATA_WAS_NOT_LOADED: {
- return {
- ...state,
- wasLoaded: false,
- };
- }
- default:
- return state;
- }
-};
-
-export const setSelectedConsumer = (value: string) => {
- return {
- type: SET_SELECTED_CONSUMER,
- data: value,
- } as const;
-};
-
-export const setDataWasNotLoaded = () => {
- return {
- type: SET_DATA_WAS_NOT_LOADED,
- } as const;
-};
-
-export function getPartitions(path: string, consumerName?: string) {
- if (consumerName) {
- return createApiRequest({
- request: window.api.getConsumer(
- {path, consumer: consumerName},
- {concurrentId: 'getPartitions'},
- ),
- actions: FETCH_PARTITIONS,
- dataHandler: (data) => {
- const rawPartitions = data.partitions;
- return prepareConsumerPartitions(rawPartitions);
- },
- });
- }
-
- return createApiRequest({
- request: window.api.getTopic({path}, {concurrentId: 'getPartitions'}),
- actions: FETCH_PARTITIONS,
- dataHandler: (data) => {
- const rawPartitions = data.partitions;
- return prepareTopicPartitions(rawPartitions);
+const slice = createSlice({
+ name: 'partitions',
+ initialState,
+ reducers: {
+ setSelectedConsumer: (state, action: PayloadAction) => {
+ state.selectedConsumer = action.payload;
},
- });
-}
-
-export default partitions;
+ },
+});
+
+export const {setSelectedConsumer} = slice.actions;
+export default slice.reducer;
+
+export const partitionsApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getPartitions: build.query({
+ queryFn: async (
+ {path, consumerName}: {path: string; consumerName?: string},
+ {signal},
+ ) => {
+ try {
+ if (consumerName) {
+ const response = await window.api.getConsumer(
+ {path, consumer: consumerName},
+ {signal},
+ );
+ const rawPartitions = response.partitions;
+ const data = prepareConsumerPartitions(rawPartitions);
+ return {data};
+ } else {
+ const response = await window.api.getTopic({path}, {signal});
+ const rawPartitions = response.partitions;
+ const data = prepareTopicPartitions(rawPartitions);
+ return {data};
+ }
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/store/reducers/partitions/types.ts b/src/store/reducers/partitions/types.ts
index 432e6c3eee..e821c0abe2 100644
--- a/src/store/reducers/partitions/types.ts
+++ b/src/store/reducers/partitions/types.ts
@@ -1,8 +1,4 @@
-import type {IResponseError} from '../../../types/api/error';
import type {ProcessSpeedStats} from '../../../utils/bytesParsers';
-import type {ApiRequestAction} from '../../utils';
-
-import type {FETCH_PARTITIONS, setDataWasNotLoaded, setSelectedConsumer} from './partitions';
// Fields that could be undefined corresponds to partitions without consumers
export interface PreparedPartitionData {
@@ -34,14 +30,5 @@ export interface PreparedPartitionData {
}
export interface PartitionsState {
- loading: boolean;
- wasLoaded: boolean;
selectedConsumer: string;
- partitions?: PreparedPartitionData[];
- error?: IResponseError;
}
-
-export type PartitionsAction =
- | ApiRequestAction
- | ReturnType
- | ReturnType;
diff --git a/src/store/reducers/pdisk/pdisk.ts b/src/store/reducers/pdisk/pdisk.ts
index 4efb5a5be7..2ab33a0e20 100644
--- a/src/store/reducers/pdisk/pdisk.ts
+++ b/src/store/reducers/pdisk/pdisk.ts
@@ -1,117 +1,45 @@
-import type {Reducer} from '@reduxjs/toolkit';
-
import {EVersion} from '../../../types/api/storage';
-import {createApiRequest, createRequestActionTypes} from '../../utils';
-
-import type {PDiskAction, PDiskState} from './types';
-import {preparePDiksDataResponse, preparePDiskStorageResponse} from './utils';
-
-export const FETCH_PDISK = createRequestActionTypes('pdisk', 'FETCH_PDISK');
-export const FETCH_PDISK_GROUPS = createRequestActionTypes('pdisk', 'FETCH_PDISK_GROUPS');
-const SET_PDISK_DATA_WAS_NOT_LOADED = 'pdisk/SET_PDISK_DATA_WAS_NOT_LOADED';
-
-const initialState = {
- pDiskLoading: false,
- pDiskWasLoaded: false,
- pDiskData: {},
- groupsLoading: false,
- groupsWasLoaded: false,
- groupsData: [],
-};
-
-const pdisk: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_PDISK.REQUEST: {
- return {
- ...state,
- pDiskLoading: true,
- };
- }
- case FETCH_PDISK.SUCCESS: {
- return {
- ...state,
- pDiskData: action.data,
- pDiskLoading: false,
- pDiskWasLoaded: true,
- pDiskError: undefined,
- };
- }
- case FETCH_PDISK.FAILURE: {
- return {
- ...state,
- pDiskError: action.error,
- pDiskLoading: false,
- };
- }
- case FETCH_PDISK_GROUPS.REQUEST: {
- return {
- ...state,
- groupsLoading: true,
- };
- }
- case FETCH_PDISK_GROUPS.SUCCESS: {
- return {
- ...state,
- groupsData: action.data,
- groupsLoading: false,
- groupsWasLoaded: true,
- groupsError: undefined,
- };
- }
- case FETCH_PDISK_GROUPS.FAILURE: {
- return {
- ...state,
- groupsError: action.error,
- groupsLoading: false,
- };
- }
- case SET_PDISK_DATA_WAS_NOT_LOADED: {
- return {
- ...state,
- pDiskWasLoaded: false,
- groupsWasLoaded: false,
- };
- }
- default:
- return state;
- }
-};
+import {api} from '../api';
-export const setPDiskDataWasNotLoaded = () => {
- return {
- type: SET_PDISK_DATA_WAS_NOT_LOADED,
- } as const;
-};
+import {preparePDiskDataResponse, preparePDiskStorageResponse} from './utils';
-export const getPDiskData = ({
- nodeId,
- pDiskId,
-}: {
+interface PDiskParams {
nodeId: number | string;
pDiskId: number | string;
-}) => {
- return createApiRequest({
- request: Promise.all([
- window.api.getPdiskInfo(nodeId, pDiskId),
- window.api.getNodeInfo(nodeId),
- ]),
- actions: FETCH_PDISK,
- dataHandler: preparePDiksDataResponse,
- });
-};
-
-export const getPDiskStorage = ({
- nodeId,
- pDiskId,
-}: {
- nodeId: number | string;
- pDiskId: number | string;
-}) => {
- return createApiRequest({
- request: window.api.getStorageInfo({nodeId, version: EVersion.v1}),
- actions: FETCH_PDISK_GROUPS,
- dataHandler: (data) => preparePDiskStorageResponse(data, pDiskId, nodeId),
- });
-};
-
-export default pdisk;
+}
+
+export const pDiskApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getPdiskInfo: build.query({
+ queryFn: async ({nodeId, pDiskId}: PDiskParams, {signal}) => {
+ try {
+ const response = await Promise.all([
+ window.api.getPDiskInfo({nodeId, pDiskId}, {signal}),
+ window.api.getNodeInfo(nodeId, {signal}),
+ ]);
+ const data = preparePDiskDataResponse(response);
+ return {data};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ getStorageInfo: build.query({
+ queryFn: async ({nodeId, pDiskId}: PDiskParams, {signal}) => {
+ try {
+ const response = await window.api.getStorageInfo(
+ {nodeId, version: EVersion.v1},
+ {signal},
+ );
+ const data = preparePDiskStorageResponse(response, pDiskId, nodeId);
+ return {data};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/store/reducers/pdisk/types.ts b/src/store/reducers/pdisk/types.ts
index 8fcd50d335..8faa8d36e2 100644
--- a/src/store/reducers/pdisk/types.ts
+++ b/src/store/reducers/pdisk/types.ts
@@ -1,30 +1,8 @@
-import type {IResponseError} from '../../../types/api/error';
import type {PreparedPDisk} from '../../../utils/disks/types';
-import type {ApiRequestAction} from '../../utils';
-import type {PreparedStorageGroup} from '../storage/types';
-import type {FETCH_PDISK, FETCH_PDISK_GROUPS, setPDiskDataWasNotLoaded} from './pdisk';
-
-interface PDiskData extends PreparedPDisk {
+export interface PDiskData extends PreparedPDisk {
NodeId?: number;
NodeHost?: string;
NodeType?: string;
NodeDC?: string;
}
-
-export interface PDiskState {
- pDiskLoading: boolean;
- pDiskWasLoaded: boolean;
- pDiskData: PDiskData;
- pDiskError?: IResponseError;
-
- groupsLoading: boolean;
- groupsWasLoaded: boolean;
- groupsData: PreparedStorageGroup[];
- groupsError?: IResponseError;
-}
-
-export type PDiskAction =
- | ApiRequestAction
- | ApiRequestAction
- | ReturnType;
diff --git a/src/store/reducers/pdisk/utils.ts b/src/store/reducers/pdisk/utils.ts
index 38794e0964..1697a78bc7 100644
--- a/src/store/reducers/pdisk/utils.ts
+++ b/src/store/reducers/pdisk/utils.ts
@@ -6,10 +6,12 @@ import {prepareNodeSystemState} from '../../../utils/nodes';
import type {PreparedStorageGroup} from '../storage/types';
import {prepareStorageGroupData} from '../storage/utils';
-export function preparePDiksDataResponse([pdiskResponse, nodeResponse]: [
+import type {PDiskData} from './types';
+
+export function preparePDiskDataResponse([pdiskResponse, nodeResponse]: [
TEvPDiskStateResponse,
TEvSystemStateResponse,
-]) {
+]): PDiskData {
const rawPDisk = pdiskResponse.PDiskStateInfo?.[0];
const preparedPDisk = preparePDiskData(rawPDisk);
diff --git a/src/store/reducers/preview.ts b/src/store/reducers/preview.ts
index 3a8f0418f3..554718c765 100644
--- a/src/store/reducers/preview.ts
+++ b/src/store/reducers/preview.ts
@@ -1,57 +1,7 @@
import type {ExecuteActions} from '../../types/api/query';
-import type {IQueryResult, QueryErrorResponse} from '../../types/store/query';
import {parseQueryAPIExecuteResponse} from '../../utils/query';
-import type {ApiRequestAction} from '../utils';
-import {createApiRequest, createRequestActionTypes} from '../utils';
-const SEND_QUERY = createRequestActionTypes('preview', 'SEND_QUERY');
-const SET_QUERY_OPTIONS = 'preview/SET_QUERY_OPTIONS';
-
-const initialState = {
- loading: false,
- wasLoaded: false,
-};
-
-const preview = (
- state = initialState,
- action:
- | ApiRequestAction
- | ReturnType,
-) => {
- switch (action.type) {
- case SEND_QUERY.REQUEST: {
- return {
- ...state,
- loading: true,
- error: undefined,
- };
- }
- case SEND_QUERY.SUCCESS: {
- return {
- ...state,
- data: action.data,
- loading: false,
- error: undefined,
- wasLoaded: true,
- };
- }
- // 401 Unauthorized error is handled by GenericAPI
- case SEND_QUERY.FAILURE: {
- return {
- ...state,
- error: action.error || 'Unauthorized',
- loading: false,
- };
- }
- case SET_QUERY_OPTIONS:
- return {
- ...state,
- ...action.data,
- };
- default:
- return state;
- }
-};
+import {api} from './api';
interface SendQueryParams {
query?: string;
@@ -59,19 +9,22 @@ interface SendQueryParams {
action?: ExecuteActions;
}
-export const sendQuery = ({query, database, action}: SendQueryParams) => {
- return createApiRequest({
- request: window.api.sendQuery({schema: 'modern', query, database, action}),
- actions: SEND_QUERY,
- dataHandler: parseQueryAPIExecuteResponse,
- });
-};
-
-export function setQueryOptions(options: any) {
- return {
- type: SET_QUERY_OPTIONS,
- data: options,
- } as const;
-}
-
-export default preview;
+export const previewApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ sendQuery: build.query({
+ queryFn: async ({query, database, action}: SendQueryParams, {signal}) => {
+ try {
+ const response = await window.api.sendQuery(
+ {schema: 'modern', query, database, action},
+ {signal},
+ );
+ return {data: parseQueryAPIExecuteResponse(response)};
+ } catch (error) {
+ return {error: error || new Error('Unauthorized')};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/store/reducers/schema/schema.ts b/src/store/reducers/schema/schema.ts
index a8fa666e7a..3074845de5 100644
--- a/src/store/reducers/schema/schema.ts
+++ b/src/store/reducers/schema/schema.ts
@@ -1,8 +1,10 @@
-import type {Reducer, Selector} from '@reduxjs/toolkit';
+import type {Dispatch, Reducer, Selector} from '@reduxjs/toolkit';
import {createSelector} from '@reduxjs/toolkit';
import {isEntityWithMergedImplementation} from '../../../containers/Tenant/utils/schema';
+import {settingsManager} from '../../../services/settings';
import type {EPathType} from '../../../types/api/schema';
+import {AUTO_REFRESH_INTERVAL} from '../../../utils/constants';
import {createApiRequest, createRequestActionTypes} from '../../utils';
import type {
@@ -17,16 +19,17 @@ export const FETCH_SCHEMA = createRequestActionTypes('schema', 'FETCH_SCHEMA');
const PRELOAD_SCHEMAS = 'schema/PRELOAD_SCHEMAS';
const SET_SCHEMA = 'schema/SET_SCHEMA';
const SET_SHOW_PREVIEW = 'schema/SET_SHOW_PREVIEW';
-const ENABLE_AUTOREFRESH = 'schema/ENABLE_AUTOREFRESH';
-const DISABLE_AUTOREFRESH = 'schema/DISABLE_AUTOREFRESH';
+export const SET_AUTOREFRESH_INTERVAL = 'schema/SET_AUTOREFRESH_INTERVAL';
const RESET_LOADING_STATE = 'schema/RESET_LOADING_STATE';
+const autoRefreshLS = Number(settingsManager.readUserSettingsValue(AUTO_REFRESH_INTERVAL, 0));
+
export const initialState = {
loading: true,
wasLoaded: false,
data: {},
currentSchemaPath: undefined,
- autorefresh: false,
+ autorefresh: isNaN(autoRefreshLS) ? 0 : autoRefreshLS,
showPreview: false,
};
@@ -88,16 +91,10 @@ const schema: Reducer = (state = initialState, action
currentSchemaPath: action.data,
};
}
- case ENABLE_AUTOREFRESH: {
- return {
- ...state,
- autorefresh: true,
- };
- }
- case DISABLE_AUTOREFRESH: {
+ case SET_AUTOREFRESH_INTERVAL: {
return {
...state,
- autorefresh: false,
+ autorefresh: action.data,
};
}
case SET_SHOW_PREVIEW: {
@@ -142,15 +139,14 @@ export function setCurrentSchemaPath(currentSchemaPath: string) {
data: currentSchemaPath,
} as const;
}
-export function enableAutorefresh() {
- return {
- type: ENABLE_AUTOREFRESH,
- } as const;
-}
-export function disableAutorefresh() {
- return {
- type: DISABLE_AUTOREFRESH,
- } as const;
+export function setAutorefreshInterval(interval: number) {
+ return (dispatch: Dispatch) => {
+ settingsManager.setUserSettingsValue(AUTO_REFRESH_INTERVAL, interval);
+ dispatch({
+ type: SET_AUTOREFRESH_INTERVAL,
+ data: interval,
+ } as const);
+ };
}
export function setShowPreview(value: boolean) {
return {
diff --git a/src/store/reducers/schema/types.ts b/src/store/reducers/schema/types.ts
index 4439813eed..162f4c5cf2 100644
--- a/src/store/reducers/schema/types.ts
+++ b/src/store/reducers/schema/types.ts
@@ -4,8 +4,7 @@ import type {ApiRequestAction} from '../../utils';
import type {
FETCH_SCHEMA,
- disableAutorefresh,
- enableAutorefresh,
+ SET_AUTOREFRESH_INTERVAL,
preloadSchemas,
resetLoadingState,
setCurrentSchemaPath,
@@ -20,7 +19,7 @@ export interface SchemaState {
data: SchemaData;
currentSchema?: TEvDescribeSchemeResult;
currentSchemaPath?: string;
- autorefresh: boolean;
+ autorefresh: number;
showPreview: boolean;
error?: IResponseError;
}
@@ -41,8 +40,7 @@ export type SchemaAction =
| SchemaApiRequestAction
| (
| ReturnType
- | ReturnType
- | ReturnType
+ | {type: typeof SET_AUTOREFRESH_INTERVAL; data: number}
| ReturnType
| ReturnType
| ReturnType
diff --git a/src/store/reducers/shardsWorkload/shardsWorkload.ts b/src/store/reducers/shardsWorkload/shardsWorkload.ts
index ce0f44c655..02a498a2bf 100644
--- a/src/store/reducers/shardsWorkload/shardsWorkload.ts
+++ b/src/store/reducers/shardsWorkload/shardsWorkload.ts
@@ -1,20 +1,13 @@
-import type {Reducer} from '@reduxjs/toolkit';
+import {createSlice} from '@reduxjs/toolkit';
+import type {PayloadAction} from '@reduxjs/toolkit';
import {parseQueryAPIExecuteResponse} from '../../../utils/query';
-import {createApiRequest, createRequestActionTypes} from '../../utils';
+import {api} from '../api';
-import type {IShardsWorkloadAction, IShardsWorkloadFilters, IShardsWorkloadState} from './types';
+import type {ShardsWorkloadFilters} from './types';
import {EShardsWorkloadMode} from './types';
-export const SEND_SHARD_QUERY = createRequestActionTypes('query', 'SEND_SHARD_QUERY');
-const SET_SHARD_STATE = 'query/SET_SHARD_STATE';
-const SET_SHARD_QUERY_FILTERS = 'shardsWorkload/SET_SHARD_QUERY_FILTERS';
-
-const initialState = {
- loading: false,
- wasLoaded: false,
- filters: {},
-};
+const initialState: ShardsWorkloadFilters = {};
export interface SortOrder {
columnId: string;
@@ -25,7 +18,7 @@ function formatSortOrder({columnId, order}: SortOrder) {
return `${columnId} ${order}`;
}
-function getFiltersConditions(filters?: IShardsWorkloadFilters) {
+function getFiltersConditions(filters?: ShardsWorkloadFilters) {
const conditions: string[] = [];
if (filters?.from && filters?.to && filters.from > filters.to) {
@@ -48,7 +41,7 @@ function getFiltersConditions(filters?: IShardsWorkloadFilters) {
function createShardQueryHistorical(
path: string,
- filters?: IShardsWorkloadFilters,
+ filters?: ShardsWorkloadFilters,
sortOrder?: SortOrder[],
tenantName?: string,
) {
@@ -104,104 +97,64 @@ LIMIT 20`;
const queryAction = 'execute-scan';
-const shardsWorkload: Reducer = (
- state = initialState,
- action,
-) => {
- switch (action.type) {
- case SEND_SHARD_QUERY.REQUEST: {
+const slice = createSlice({
+ name: 'shardsWorkload',
+ initialState,
+ reducers: {
+ setShardsQueryFilters: (state, action: PayloadAction) => {
return {
...state,
- loading: true,
- error: undefined,
+ ...action.payload,
};
- }
- case SEND_SHARD_QUERY.SUCCESS: {
- return {
- ...state,
- data: action.data,
- loading: false,
- error: undefined,
- wasLoaded: true,
- };
- }
- // 401 Unauthorized error is handled by GenericAPI
- case SEND_SHARD_QUERY.FAILURE: {
- if (action.error?.isCancelled) {
- return state;
- }
+ },
+ },
+});
- return {
- ...state,
- error: action.error || 'Unauthorized',
- loading: false,
- };
- }
- case SET_SHARD_STATE:
- return {
- ...state,
- ...action.data,
- };
- case SET_SHARD_QUERY_FILTERS:
- return {
- ...state,
- filters: {
- ...state.filters,
- ...action.filters,
- },
- };
- default:
- return state;
- }
-};
+export const {setShardsQueryFilters} = slice.actions;
+export default slice.reducer;
interface SendShardQueryParams {
database?: string;
path?: string;
sortOrder?: SortOrder[];
- filters?: IShardsWorkloadFilters;
-}
-
-export const sendShardQuery = ({database, path = '', sortOrder, filters}: SendShardQueryParams) => {
- try {
- return createApiRequest({
- request: window.api.sendQuery(
- {
- schema: 'modern',
- query:
- filters?.mode === EShardsWorkloadMode.Immediate
- ? createShardQueryImmediate(path, sortOrder, database)
- : createShardQueryHistorical(path, filters, sortOrder, database),
- database,
- action: queryAction,
- },
- {
- concurrentId: 'shardsWorkload',
- },
- ),
- actions: SEND_SHARD_QUERY,
- dataHandler: parseQueryAPIExecuteResponse,
- });
- } catch (error) {
- return {
- type: SEND_SHARD_QUERY.FAILURE,
- error,
- };
- }
-};
-
-export function setShardsState(options: Partial) {
- return {
- type: SET_SHARD_STATE,
- data: options,
- } as const;
-}
-
-export function setShardsQueryFilters(filters: Partial) {
- return {
- type: SET_SHARD_QUERY_FILTERS,
- filters,
- } as const;
+ filters?: ShardsWorkloadFilters;
}
-export default shardsWorkload;
+export const shardApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ sendShardQuery: build.query({
+ queryFn: async (
+ {database, path = '', sortOrder, filters}: SendShardQueryParams,
+ {signal},
+ ) => {
+ try {
+ const response = await window.api.sendQuery(
+ {
+ schema: 'modern',
+ query:
+ filters?.mode === EShardsWorkloadMode.Immediate
+ ? createShardQueryImmediate(path, sortOrder, database)
+ : createShardQueryHistorical(
+ path,
+ filters,
+ sortOrder,
+ database,
+ ),
+ database,
+ action: queryAction,
+ },
+ {
+ signal,
+ },
+ );
+ const data = parseQueryAPIExecuteResponse(response);
+ return {data};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/store/reducers/shardsWorkload/types.ts b/src/store/reducers/shardsWorkload/types.ts
index 2254e70545..f2e6e5a571 100644
--- a/src/store/reducers/shardsWorkload/types.ts
+++ b/src/store/reducers/shardsWorkload/types.ts
@@ -1,14 +1,9 @@
-import type {IQueryResult, QueryErrorResponse} from '../../../types/store/query';
-import type {ApiRequestAction} from '../../utils';
-
-import type {SEND_SHARD_QUERY, setShardsQueryFilters, setShardsState} from './shardsWorkload';
-
export enum EShardsWorkloadMode {
Immediate = 'immediate',
History = 'history',
}
-export interface IShardsWorkloadFilters {
+export interface ShardsWorkloadFilters {
/** ms from epoch */
from?: number;
/** ms from epoch */
@@ -16,19 +11,6 @@ export interface IShardsWorkloadFilters {
mode?: EShardsWorkloadMode;
}
-export interface IShardsWorkloadState {
- loading: boolean;
- wasLoaded: boolean;
- data?: IQueryResult;
- error?: QueryErrorResponse;
- filters: IShardsWorkloadFilters;
-}
-
-export type IShardsWorkloadAction =
- | ApiRequestAction
- | ReturnType
- | ReturnType;
-
-export interface IShardsWorkloadRootStateSlice {
- shardsWorkload: IShardsWorkloadState;
+export interface ShardsWorkloadRootStateSlice {
+ shardsWorkload: ShardsWorkloadFilters;
}
diff --git a/src/store/reducers/storage/storage.ts b/src/store/reducers/storage/storage.ts
index c2eb6bbb33..6c336c7fa6 100644
--- a/src/store/reducers/storage/storage.ts
+++ b/src/store/reducers/storage/storage.ts
@@ -101,4 +101,5 @@ export const storageApi = api.injectEndpoints({
providesTags: ['All'],
}),
}),
+ overrideExisting: 'throw',
});
diff --git a/src/store/reducers/tablet.ts b/src/store/reducers/tablet.ts
index e3ea18584f..046768fc66 100644
--- a/src/store/reducers/tablet.ts
+++ b/src/store/reducers/tablet.ts
@@ -1,140 +1,72 @@
-import type {Reducer} from '@reduxjs/toolkit';
-
import type {TDomainKey} from '../../types/api/tablet';
-import type {
- ITabletAction,
- ITabletDescribeHandledResponse,
- ITabletHandledResponse,
- ITabletPreparedHistoryItem,
- ITabletState,
-} from '../../types/store/tablet';
+import type {ITabletPreparedHistoryItem} from '../../types/store/tablet';
import {prepareNodesMap} from '../../utils/nodes';
-import {createApiRequest, createRequestActionTypes} from '../utils';
-
-export const FETCH_TABLET = createRequestActionTypes('TABLET', 'FETCH_TABLET');
-export const FETCH_TABLET_DESCRIBE = createRequestActionTypes('TABLET', 'FETCH_TABLET_DESCRIBE');
-
-const CLEAR_TABLET_DATA = 'tablet/CLEAR_TABLET_DATA';
-
-const initialState = {
- loading: false,
- tenantPath: undefined,
-};
-
-const tablet: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_TABLET.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_TABLET.SUCCESS: {
- const {tabletData, historyData} = action.data;
- const {TabletId: id} = tabletData;
- return {
- ...state,
- id,
- data: tabletData,
- history: historyData,
- loading: false,
- error: undefined,
- };
- }
- case FETCH_TABLET.FAILURE: {
- return {
- ...state,
- error: action.error,
- loading: false,
- };
- }
- case FETCH_TABLET_DESCRIBE.SUCCESS: {
- const {tenantPath} = action.data;
-
- return {
- ...state,
- tenantPath,
- error: undefined,
- };
- }
- case CLEAR_TABLET_DATA: {
- return {
- ...state,
- id: undefined,
- tenantPath: undefined,
- data: undefined,
- history: undefined,
- };
- }
- default:
- return state;
- }
-};
-
-export const getTablet = (id: string) => {
- return createApiRequest({
- request: Promise.all([
- window.api.getTablet({id}),
- window.api.getTabletHistory({id}),
- window.api.getNodesList(),
- ]),
- actions: FETCH_TABLET,
- dataHandler: ([
- tabletResponseData,
- historyResponseData,
- nodesList,
- ]): ITabletHandledResponse => {
- const nodesMap = prepareNodesMap(nodesList);
-
- const historyData = Object.keys(historyResponseData).reduce<
- ITabletPreparedHistoryItem[]
- >((list, nodeId) => {
- const tabletInfo = historyResponseData[nodeId]?.TabletStateInfo;
- if (tabletInfo && tabletInfo.length) {
- const leaderTablet = tabletInfo.find((t) => t.Leader) || tabletInfo[0];
-
- const {ChangeTime, Generation, State, Leader, FollowerId} = leaderTablet;
- const fqdn = nodesMap && nodeId ? nodesMap.get(Number(nodeId)) : undefined;
-
- list.push({
- nodeId,
- generation: Generation,
- changeTime: ChangeTime,
- state: State,
- leader: Leader,
- followerId: FollowerId,
- fqdn,
- });
+import {api} from './api';
+
+export const tabletApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getTablet: build.query({
+ queryFn: async ({id}: {id: string}, {signal}) => {
+ try {
+ const [tabletResponseData, historyResponseData, nodesList] = await Promise.all([
+ window.api.getTablet({id}, {signal}),
+ window.api.getTabletHistory({id}, {signal}),
+ window.api.getNodesList({signal}),
+ ]);
+ const nodesMap = prepareNodesMap(nodesList);
+
+ const historyData = Object.keys(historyResponseData).reduce<
+ ITabletPreparedHistoryItem[]
+ >((list, nodeId) => {
+ const tabletInfo = historyResponseData[nodeId]?.TabletStateInfo;
+ if (tabletInfo && tabletInfo.length) {
+ const leaderTablet = tabletInfo.find((t) => t.Leader) || tabletInfo[0];
+
+ const {ChangeTime, Generation, State, Leader, FollowerId} =
+ leaderTablet;
+
+ const fqdn =
+ nodesMap && nodeId ? nodesMap.get(Number(nodeId)) : undefined;
+
+ list.push({
+ nodeId,
+ generation: Generation,
+ changeTime: ChangeTime,
+ state: State,
+ leader: Leader,
+ followerId: FollowerId,
+ fqdn,
+ });
+ }
+ return list;
+ }, []);
+
+ const {TabletStateInfo = []} = tabletResponseData;
+ const [tabletData = {}] = TabletStateInfo;
+ const {TabletId} = tabletData;
+
+ return {data: {id: TabletId, data: tabletData, history: historyData}};
+ } catch (error) {
+ return {error};
}
- return list;
- }, []);
-
- const {TabletStateInfo = []} = tabletResponseData;
- const [tabletData = {}] = TabletStateInfo;
-
- return {tabletData, historyData};
- },
- });
-};
-
-export const getTabletDescribe = (tenantId: TDomainKey = {}) => {
- return createApiRequest({
- request: window.api.getTabletDescribe(tenantId),
- actions: FETCH_TABLET_DESCRIBE,
- dataHandler: (tabletDescribe): ITabletDescribeHandledResponse => {
- const {SchemeShard, PathId} = tenantId;
- const tenantPath = tabletDescribe?.Path || `${SchemeShard}:${PathId}`;
-
- return {tenantPath};
- },
- });
-};
-
-export const clearTabletData = () => {
- return {
- type: CLEAR_TABLET_DATA,
- } as const;
-};
-
-export default tablet;
+ },
+ providesTags: ['All'],
+ }),
+ getTabletDescribe: build.query({
+ queryFn: async ({tenantId}: {tenantId: TDomainKey}, {signal}) => {
+ try {
+ const tabletDescribe = await window.api.getTabletDescribe(tenantId, {signal});
+ const {SchemeShard, PathId} = tenantId;
+ const tenantPath = tabletDescribe?.Path || `${SchemeShard}:${PathId}`;
+
+ return {data: tenantPath};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/store/reducers/tablets.ts b/src/store/reducers/tablets.ts
index 00cb758eb8..f77e1e07f6 100644
--- a/src/store/reducers/tablets.ts
+++ b/src/store/reducers/tablets.ts
@@ -1,98 +1,45 @@
-import type {Reducer} from '@reduxjs/toolkit';
+import {createSlice} from '@reduxjs/toolkit';
+import type {PayloadAction} from '@reduxjs/toolkit';
import type {ETabletState, EType} from '../../types/api/tablet';
-import type {
- ITabletsAction,
- ITabletsApiRequestParams,
- ITabletsState,
-} from '../../types/store/tablets';
-import {createApiRequest, createRequestActionTypes} from '../utils';
+import type {TabletsApiRequestParams, TabletsState} from '../../types/store/tablets';
-export const FETCH_TABLETS = createRequestActionTypes('tablets', 'FETCH_TABLETS');
+import {api} from './api';
-const CLEAR_WAS_LOADING_TABLETS = 'tablets/CLEAR_WAS_LOADING_TABLETS';
-const SET_STATE_FILTER = 'tablets/SET_STATE_FILTER';
-const SET_TYPE_FILTER = 'tablets/SET_TYPE_FILTER';
-
-const initialState = {
- loading: true,
- wasLoaded: false,
+const initialState: TabletsState = {
stateFilter: [],
typeFilter: [],
};
-const tablets: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_TABLETS.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_TABLETS.SUCCESS: {
- return {
- ...state,
- data: action.data,
- loading: false,
- error: undefined,
- wasLoaded: true,
- };
- }
- case FETCH_TABLETS.FAILURE: {
- return {
- ...state,
- error: action.error,
- loading: false,
- };
- }
- case CLEAR_WAS_LOADING_TABLETS: {
- return {
- ...state,
- wasLoaded: false,
- loading: true,
- };
- }
- case SET_STATE_FILTER: {
- return {
- ...state,
- stateFilter: action.data,
- };
- }
- case SET_TYPE_FILTER: {
- return {
- ...state,
- typeFilter: action.data,
- };
- }
- default:
- return state;
- }
-};
-
-export const setStateFilter = (stateFilter: ETabletState[]) => {
- return {
- type: SET_STATE_FILTER,
- data: stateFilter,
- } as const;
-};
-
-export const setTypeFilter = (typeFilter: EType[]) => {
- return {
- type: SET_TYPE_FILTER,
- data: typeFilter,
- } as const;
-};
-
-export const clearWasLoadingFlag = () =>
- ({
- type: CLEAR_WAS_LOADING_TABLETS,
- }) as const;
-
-export function getTabletsInfo(data: ITabletsApiRequestParams) {
- return createApiRequest({
- request: window.api.getTabletsInfo(data),
- actions: FETCH_TABLETS,
- });
-}
-
-export default tablets;
+const slice = createSlice({
+ name: 'tablets',
+ initialState,
+ reducers: {
+ setStateFilter: (state, action: PayloadAction) => {
+ state.stateFilter = action.payload;
+ },
+ setTypeFilter: (state, action: PayloadAction) => {
+ state.typeFilter = action.payload;
+ },
+ },
+});
+
+export const {setStateFilter, setTypeFilter} = slice.actions;
+export default slice.reducer;
+
+export const tabletsApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getTabletsInfo: build.query({
+ queryFn: async (params: TabletsApiRequestParams, {signal}) => {
+ try {
+ const data = await window.api.getTabletsInfo(params, {signal});
+ return {data};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/store/reducers/tenant/tenant.ts b/src/store/reducers/tenant/tenant.ts
index 5d465e818d..494ed9a97d 100644
--- a/src/store/reducers/tenant/tenant.ts
+++ b/src/store/reducers/tenant/tenant.ts
@@ -52,4 +52,5 @@ export const tenantApi = api.injectEndpoints({
providesTags: ['All'],
}),
}),
+ overrideExisting: 'throw',
});
diff --git a/src/store/reducers/tenantOverview/executeTopTables/executeTopTables.ts b/src/store/reducers/tenantOverview/executeTopTables/executeTopTables.ts
index 8624970bec..efad5982ac 100644
--- a/src/store/reducers/tenantOverview/executeTopTables/executeTopTables.ts
+++ b/src/store/reducers/tenantOverview/executeTopTables/executeTopTables.ts
@@ -35,4 +35,5 @@ export const topTablesApi = api.injectEndpoints({
providesTags: ['All'],
}),
}),
+ overrideExisting: 'throw',
});
diff --git a/src/store/reducers/tenantOverview/topNodes/topNodes.ts b/src/store/reducers/tenantOverview/topNodes/topNodes.ts
index 036327f6ca..73c099ca7e 100644
--- a/src/store/reducers/tenantOverview/topNodes/topNodes.ts
+++ b/src/store/reducers/tenantOverview/topNodes/topNodes.ts
@@ -26,4 +26,5 @@ export const topNodesApi = api.injectEndpoints({
providesTags: ['All'],
}),
}),
+ overrideExisting: 'throw',
});
diff --git a/src/store/reducers/tenantOverview/topQueries/tenantOverviewTopQueries.ts b/src/store/reducers/tenantOverview/topQueries/tenantOverviewTopQueries.ts
index faa029d03f..6dade227cb 100644
--- a/src/store/reducers/tenantOverview/topQueries/tenantOverviewTopQueries.ts
+++ b/src/store/reducers/tenantOverview/topQueries/tenantOverviewTopQueries.ts
@@ -15,7 +15,7 @@ LIMIT ${TENANT_OVERVIEW_TABLES_LIMIT}
export const topQueriesApi = api.injectEndpoints({
endpoints: (builder) => ({
- getTopQueries: builder.query({
+ getOverviewTopQueries: builder.query({
queryFn: async ({database}: {database: string}, {signal}) => {
try {
const data = await window.api.sendQuery(
@@ -35,4 +35,5 @@ export const topQueriesApi = api.injectEndpoints({
providesTags: ['All'],
}),
}),
+ overrideExisting: 'throw',
});
diff --git a/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts b/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts
index 78ce8240ea..5569c6f168 100644
--- a/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts
+++ b/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts
@@ -43,4 +43,5 @@ export const topShardsApi = api.injectEndpoints({
providesTags: ['All'],
}),
}),
+ overrideExisting: 'throw',
});
diff --git a/src/store/reducers/tenantOverview/topStorageGroups/topStorageGroups.ts b/src/store/reducers/tenantOverview/topStorageGroups/topStorageGroups.ts
index 2205cc6ad4..10d7d9b5cd 100644
--- a/src/store/reducers/tenantOverview/topStorageGroups/topStorageGroups.ts
+++ b/src/store/reducers/tenantOverview/topStorageGroups/topStorageGroups.ts
@@ -29,4 +29,5 @@ export const topStorageGroupsApi = api.injectEndpoints({
providesTags: ['All'],
}),
}),
+ overrideExisting: 'throw',
});
diff --git a/src/store/reducers/tenants/selectors.ts b/src/store/reducers/tenants/selectors.ts
index a1e2c60917..ecd09d1bd3 100644
--- a/src/store/reducers/tenants/selectors.ts
+++ b/src/store/reducers/tenants/selectors.ts
@@ -1,4 +1,3 @@
-import type {Selector} from '@reduxjs/toolkit';
import {createSelector} from '@reduxjs/toolkit';
import escapeRegExp from 'lodash/escapeRegExp';
@@ -7,6 +6,7 @@ import {EFlag} from '../../../types/api/enums';
import {ProblemFilterValues, selectProblemFilter} from '../settings/settings';
import type {ProblemFilterValue} from '../settings/types';
+import {tenantsApi} from './tenants';
import type {PreparedTenant, TenantsStateSlice} from './types';
// ==== Filters ====
@@ -29,13 +29,22 @@ const filteredTenantsBySearch = (tenants: PreparedTenant[], searchQuery: string)
};
// ==== Simple selectors ====
+const createGetTenantsInfoSelector = createSelector(
+ (clusterName: string | undefined) => clusterName,
+ (clusterName) => tenantsApi.endpoints.getTenantsInfo.select({clusterName}),
+);
-export const selectTenants = (state: TenantsStateSlice) => state.tenants.tenants;
+export const selectTenants = createSelector(
+ (state: RootState) => state,
+ (_state: RootState, clusterName: string | undefined) =>
+ createGetTenantsInfoSelector(clusterName),
+ (state: RootState, selectTenantsInfo) => selectTenantsInfo(state).data ?? [],
+);
export const selectTenantsSearchValue = (state: TenantsStateSlice) => state.tenants.searchValue;
// ==== Complex selectors ====
-export const selectFilteredTenants: Selector = createSelector(
+export const selectFilteredTenants = createSelector(
[selectTenants, selectProblemFilter, selectTenantsSearchValue],
(tenants, problemFilter, searchQuery) => {
let result = filterTenantsByProblems(tenants, problemFilter);
diff --git a/src/store/reducers/tenants/tenants.ts b/src/store/reducers/tenants/tenants.ts
index cc017dae45..9cb363a148 100644
--- a/src/store/reducers/tenants/tenants.ts
+++ b/src/store/reducers/tenants/tenants.ts
@@ -1,72 +1,47 @@
-import type {Reducer} from '@reduxjs/toolkit';
+import {createSlice} from '@reduxjs/toolkit';
+import type {PayloadAction} from '@reduxjs/toolkit';
-import {createApiRequest, createRequestActionTypes} from '../../utils';
+import type {RootState} from '../../defaultStore';
+import {api} from '../api';
-import type {TenantsAction, TenantsState} from './types';
+import type {PreparedTenant, TenantsState} from './types';
import {prepareTenants} from './utils';
-export const FETCH_TENANTS = createRequestActionTypes('tenants', 'FETCH_TENANTS');
+const initialState: TenantsState = {searchValue: ''};
-const SET_SEARCH_VALUE = 'tenants/SET_SEARCH_VALUE';
-
-const initialState = {loading: true, wasLoaded: false, searchValue: '', tenants: []};
-
-const tenants: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_TENANTS.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_TENANTS.SUCCESS: {
- return {
- ...state,
- tenants: action.data,
- loading: false,
- wasLoaded: true,
- error: undefined,
- };
- }
- case FETCH_TENANTS.FAILURE: {
- return {
- ...state,
- error: action.error,
- loading: false,
- };
- }
- case SET_SEARCH_VALUE: {
- return {
- ...state,
- searchValue: action.data,
- };
- }
- default:
- return state;
- }
-};
-
-export function getTenantsInfo(clusterName?: string) {
- return createApiRequest({
- request: window.api.getTenants(clusterName),
- actions: FETCH_TENANTS,
- dataHandler: (response, getState) => {
- const {singleClusterMode} = getState();
-
- if (!response.TenantInfo) {
- return [];
- }
-
- return prepareTenants(response.TenantInfo, singleClusterMode);
+const slice = createSlice({
+ name: 'tenants',
+ initialState,
+ reducers: {
+ setSearchValue: (state, action: PayloadAction) => {
+ state.searchValue = action.payload;
},
- });
-}
-
-export const setSearchValue = (value: string) => {
- return {
- type: SET_SEARCH_VALUE,
- data: value,
- } as const;
-};
-
-export default tenants;
+ },
+});
+
+export const {setSearchValue} = slice.actions;
+export default slice.reducer;
+
+export const tenantsApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getTenantsInfo: build.query({
+ queryFn: async ({clusterName}: {clusterName?: string}, {signal, getState}) => {
+ try {
+ const response = await window.api.getTenants(clusterName, {signal});
+ let data: PreparedTenant[];
+ if (Array.isArray(response.TenantInfo)) {
+ const {singleClusterMode} = getState() as RootState;
+ data = prepareTenants(response.TenantInfo, singleClusterMode);
+ } else {
+ data = [];
+ }
+ return {data};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/store/reducers/tenants/types.ts b/src/store/reducers/tenants/types.ts
index 36d885ebb8..81d665d1a3 100644
--- a/src/store/reducers/tenants/types.ts
+++ b/src/store/reducers/tenants/types.ts
@@ -1,34 +1,23 @@
-import type {IResponseError} from '../../../types/api/error';
import type {TTenant} from '../../../types/api/tenant';
import type {ValueOf} from '../../../types/common';
-import type {ApiRequestAction} from '../../utils';
import type {METRIC_STATUS} from './contants';
-import type {FETCH_TENANTS, setSearchValue} from './tenants';
export interface PreparedTenant extends TTenant {
backend: string | undefined;
sharedTenantName: string | undefined;
controlPlaneName: string;
- cpu: number;
- memory: number;
- storage: number;
+ cpu: number | undefined;
+ memory: number | undefined;
+ storage: number | undefined;
nodesCount: number;
groupsCount: number;
}
export interface TenantsState {
- loading: boolean;
- wasLoaded: boolean;
searchValue: string;
- tenants: PreparedTenant[];
- error?: IResponseError;
}
-export type TenantsAction =
- | ApiRequestAction
- | ReturnType;
-
export interface TenantsStateSlice {
tenants: TenantsState;
}
diff --git a/src/store/reducers/tenants/utils.ts b/src/store/reducers/tenants/utils.ts
index 7990f5881f..9a91770655 100644
--- a/src/store/reducers/tenants/utils.ts
+++ b/src/store/reducers/tenants/utils.ts
@@ -6,6 +6,7 @@ import {formatCPUWithLabel} from '../../../utils/dataFormatters/dataFormatters';
import {isNumeric} from '../../../utils/utils';
import {METRIC_STATUS} from './contants';
+import type {PreparedTenant} from './types';
const getControlPlaneValue = (tenant: TTenant) => {
const parts = tenant.Name?.split('/');
@@ -174,7 +175,7 @@ const calculateTenantEntities = (tenant: TTenant) => {
return {nodesCount, groupsCount};
};
-export const prepareTenants = (tenants: TTenant[], useNodeAsBackend: boolean) => {
+export const prepareTenants = (tenants: TTenant[], useNodeAsBackend: boolean): PreparedTenant[] => {
return tenants.map((tenant) => {
const backend = useNodeAsBackend ? getTenantBackend(tenant) : undefined;
const sharedTenantName = tenants.find((item) => item.Id === tenant.ResourceId)?.Name;
diff --git a/src/store/reducers/topic.ts b/src/store/reducers/topic.ts
index ef9eacbd29..9e8ea48976 100644
--- a/src/store/reducers/topic.ts
+++ b/src/store/reducers/topic.ts
@@ -1,112 +1,56 @@
/* eslint-disable camelcase */
-import type {Reducer, Selector} from '@reduxjs/toolkit';
import {createSelector} from '@reduxjs/toolkit';
-import type {
- IPreparedConsumerData,
- IPreparedTopicStats,
- ITopicAction,
- ITopicRootStateSlice,
- ITopicState,
-} from '../../types/store/topic';
import {convertBytesObjectToSpeed} from '../../utils/bytesParsers';
import {parseLag, parseTimestampToIdleTime} from '../../utils/timeParsers';
-import {createApiRequest, createRequestActionTypes} from '../utils';
-
-export const FETCH_TOPIC = createRequestActionTypes('topic', 'FETCH_TOPIC');
-
-const SET_DATA_WAS_NOT_LOADED = 'topic/SET_DATA_WAS_NOT_LOADED';
-const CLEAN_TOPIC_DATA = 'topic/CLEAN_TOPIC_DATA';
-
-const initialState = {
- loading: true,
- wasLoaded: false,
- data: undefined,
-};
-
-const topic: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_TOPIC.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_TOPIC.SUCCESS: {
- // On older version it can return HTML page of Developer UI with an error
- if (typeof action.data !== 'object') {
- return {...state, loading: false, error: {}};
- }
-
- return {
- ...state,
- data: action.data,
- loading: false,
- wasLoaded: true,
- error: undefined,
- };
- }
- case FETCH_TOPIC.FAILURE: {
- if (action.error?.isCancelled) {
- return state;
- }
-
- return {
- ...state,
- error: action.error,
- loading: false,
- };
- }
- case SET_DATA_WAS_NOT_LOADED: {
- return {
- ...state,
- wasLoaded: false,
- };
- }
- case CLEAN_TOPIC_DATA: {
- return {
- ...state,
- data: undefined,
- };
- }
- default:
- return state;
- }
-};
-
-export const setDataWasNotLoaded = () => {
- return {
- type: SET_DATA_WAS_NOT_LOADED,
- } as const;
-};
-
-export const cleanTopicData = () => {
- return {
- type: CLEAN_TOPIC_DATA,
- } as const;
-};
-
-export function getTopic(path?: string) {
- return createApiRequest({
- request: window.api.getTopic({path}),
- actions: FETCH_TOPIC,
- });
-}
-
-const selectTopicStats = (state: ITopicRootStateSlice) => state.topic.data?.topic_stats;
-const selectConsumers = (state: ITopicRootStateSlice) => state.topic.data?.consumers;
+import type {RootState} from '../defaultStore';
+
+import {api} from './api';
+
+export const topicApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getTopic: build.query({
+ queryFn: async (params: {path?: string}) => {
+ try {
+ const data = await window.api.getTopic(params);
+ // On older version it can return HTML page of Developer UI with an error
+ if (typeof data !== 'object') {
+ return {error: {}};
+ }
+ return {data};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
-export const selectConsumersNames: Selector =
- createSelector([selectConsumers], (consumers) => {
- return consumers
- ?.map((consumer) => consumer?.name)
- .filter((consumer): consumer is string => consumer !== undefined);
- });
+const createGetTopicSelector = createSelector(
+ (path?: string) => path,
+ (path) => topicApi.endpoints.getTopic.select({path}),
+);
+
+const selectTopicStats = createSelector(
+ (state: RootState) => state,
+ (_state: RootState, path?: string) => createGetTopicSelector(path),
+ (state, selectGetTopic) => selectGetTopic(state).data?.topic_stats,
+);
+const selectConsumers = createSelector(
+ (state: RootState) => state,
+ (_state: RootState, path?: string) => createGetTopicSelector(path),
+ (state, selectGetTopic) => selectGetTopic(state).data?.consumers,
+);
+
+export const selectConsumersNames = createSelector(selectConsumers, (consumers) => {
+ return consumers
+ ?.map((consumer) => consumer?.name)
+ .filter((consumer): consumer is string => consumer !== undefined);
+});
-export const selectPreparedTopicStats: Selector<
- ITopicRootStateSlice,
- IPreparedTopicStats | undefined
-> = createSelector([selectTopicStats], (rawTopicStats) => {
+export const selectPreparedTopicStats = createSelector(selectTopicStats, (rawTopicStats) => {
if (!rawTopicStats) {
return undefined;
}
@@ -126,10 +70,7 @@ export const selectPreparedTopicStats: Selector<
};
});
-export const selectPreparedConsumersData: Selector<
- ITopicRootStateSlice,
- IPreparedConsumerData[] | undefined
-> = createSelector([selectConsumers], (consumers) => {
+export const selectPreparedConsumersData = createSelector(selectConsumers, (consumers) => {
return consumers?.map((consumer) => {
const {name, consumer_stats} = consumer || {};
@@ -146,5 +87,3 @@ export const selectPreparedConsumersData: Selector<
};
});
});
-
-export default topic;
diff --git a/src/store/reducers/vdisk/types.ts b/src/store/reducers/vdisk/types.ts
index 729fb5a610..91829954f3 100644
--- a/src/store/reducers/vdisk/types.ts
+++ b/src/store/reducers/vdisk/types.ts
@@ -1,10 +1,6 @@
-import type {IResponseError} from '../../../types/api/error';
import type {PreparedVDisk} from '../../../utils/disks/types';
-import type {ApiRequestAction} from '../../utils';
import type {PreparedStorageGroup} from '../storage/types';
-import type {FETCH_VDISK, setVDiskDataWasNotLoaded} from './vdisk';
-
export type VDiskGroup = Partial;
export interface VDiskData extends PreparedVDisk {
@@ -16,20 +12,3 @@ export interface VDiskData extends PreparedVDisk {
PDiskId?: number;
PDiskType?: string;
}
-
-export interface VDiskState {
- loading: boolean;
- wasLoaded: boolean;
- error?: IResponseError;
-
- vDiskData: VDiskData;
- groupData?: VDiskGroup;
-}
-
-export type VDiskAction =
- | ApiRequestAction<
- typeof FETCH_VDISK,
- {vDiskData: VDiskData; groupData?: VDiskGroup},
- IResponseError
- >
- | ReturnType;
diff --git a/src/store/reducers/vdisk/vdisk.ts b/src/store/reducers/vdisk/vdisk.ts
index 4ca9bd0caa..159bf3ea36 100644
--- a/src/store/reducers/vdisk/vdisk.ts
+++ b/src/store/reducers/vdisk/vdisk.ts
@@ -1,76 +1,45 @@
-import type {Reducer} from '@reduxjs/toolkit';
-
import {EVersion} from '../../../types/api/storage';
import {valueIsDefined} from '../../../utils';
-import {createApiRequest, createRequestActionTypes} from '../../utils';
+import {api} from '../api';
-import type {VDiskAction, VDiskGroup, VDiskState} from './types';
+import type {VDiskGroup} from './types';
import {prepareVDiskDataResponse, prepareVDiskGroupResponse} from './utils';
-export const FETCH_VDISK = createRequestActionTypes('vdisk', 'FETCH_VDISK');
-const SET_VDISK_DATA_WAS_NOT_LOADED = 'vdisk/SET_VDISK_DATA_WAS_NOT_LOADED';
-
-const initialState = {
- loading: false,
- wasLoaded: false,
- vDiskData: {},
-};
-
-const vdisk: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_VDISK.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_VDISK.SUCCESS: {
- const {vDiskData, groupData} = action.data;
-
- return {
- ...state,
- vDiskData,
- groupData,
- loading: false,
- wasLoaded: true,
- error: undefined,
- };
- }
- case FETCH_VDISK.FAILURE: {
- return {
- ...state,
- error: action.error,
- loading: false,
- };
- }
- case SET_VDISK_DATA_WAS_NOT_LOADED: {
- return {
- ...state,
- wasLoaded: false,
- };
- }
- default:
- return state;
- }
-};
-
-export const setVDiskDataWasNotLoaded = () => {
- return {
- type: SET_VDISK_DATA_WAS_NOT_LOADED,
- } as const;
-};
-
interface VDiskDataRequestParams {
nodeId: number | string;
pDiskId: number | string;
vDiskSlotId: number | string;
}
-const requestVDiskData = async ({nodeId, pDiskId, vDiskSlotId}: VDiskDataRequestParams) => {
+export const vDiskApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getVDiskData: build.query({
+ queryFn: async ({nodeId, pDiskId, vDiskSlotId}) => {
+ try {
+ const {vDiskData, groupData} = await requestVDiskData({
+ nodeId,
+ pDiskId,
+ vDiskSlotId,
+ });
+ return {data: {vDiskData, groupData}};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
+
+async function requestVDiskData(
+ {nodeId, pDiskId, vDiskSlotId}: VDiskDataRequestParams,
+ {signal}: {signal?: AbortSignal} = {},
+) {
const vDiskDataResponse = await Promise.all([
- window.api.getVdiskInfo({nodeId, pDiskId, vDiskSlotId}),
- window.api.getPdiskInfo(nodeId, pDiskId),
- window.api.getNodeInfo(nodeId),
+ window.api.getVDiskInfo({nodeId, pDiskId, vDiskSlotId}, {signal}),
+ window.api.getPDiskInfo({nodeId, pDiskId}, {signal}),
+ window.api.getNodeInfo(nodeId, {signal}),
]);
const vDiskData = prepareVDiskDataResponse(vDiskDataResponse);
@@ -81,23 +50,17 @@ const requestVDiskData = async ({nodeId, pDiskId, vDiskSlotId}: VDiskDataRequest
let groupData: VDiskGroup | undefined;
if (valueIsDefined(StoragePoolName) && valueIsDefined(GroupID)) {
- const groupResponse = await window.api.getStorageInfo({
- nodeId,
- poolName: StoragePoolName,
- groupId: GroupID,
- version: EVersion.v1,
- });
+ const groupResponse = await window.api.getStorageInfo(
+ {
+ nodeId,
+ poolName: StoragePoolName,
+ groupId: GroupID,
+ version: EVersion.v1,
+ },
+ {signal},
+ );
groupData = prepareVDiskGroupResponse(groupResponse, StoragePoolName, GroupID);
}
return {vDiskData, groupData};
-};
-
-export const getVDiskData = (params: VDiskDataRequestParams) => {
- return createApiRequest({
- request: requestVDiskData(params),
- actions: FETCH_VDISK,
- });
-};
-
-export default vdisk;
+}
diff --git a/src/store/state-url-mapping.ts b/src/store/state-url-mapping.ts
index 7ff5564002..45834d5a09 100644
--- a/src/store/state-url-mapping.ts
+++ b/src/store/state-url-mapping.ts
@@ -63,22 +63,22 @@ const paramSetup: ParamSetup = {
stateKey: 'tenant.metricsTab',
},
shardsMode: {
- stateKey: 'shardsWorkload.filters.mode',
+ stateKey: 'shardsWorkload.mode',
},
shardsDateFrom: {
- stateKey: 'shardsWorkload.filters.from',
+ stateKey: 'shardsWorkload.from',
type: 'number',
},
shardsDateTo: {
- stateKey: 'shardsWorkload.filters.to',
+ stateKey: 'shardsWorkload.to',
type: 'number',
},
topQueriesDateFrom: {
- stateKey: 'executeTopQueries.filters.from',
+ stateKey: 'executeTopQueries.from',
type: 'number',
},
topQueriesDateTo: {
- stateKey: 'executeTopQueries.filters.to',
+ stateKey: 'executeTopQueries.to',
type: 'number',
},
selectedConsumer: {
@@ -107,6 +107,11 @@ const paramSetup: ParamSetup = {
stateKey: 'nodes.searchValue',
},
},
+ '/cluster/tenants': {
+ search: {
+ stateKey: 'tenants.searchValue',
+ },
+ },
};
function mergeLocationToState(state: S, location: Pick): S {
diff --git a/src/types/api/netInfo.ts b/src/types/api/netInfo.ts
index 342c3bab00..46585164ac 100644
--- a/src/types/api/netInfo.ts
+++ b/src/types/api/netInfo.ts
@@ -16,7 +16,7 @@ interface TNetTenantInfo {
Nodes?: TNetNodeInfo[];
}
-interface TNetNodeInfo {
+export interface TNetNodeInfo {
NodeId: number;
Overall: EFlag;
Peers?: TNetNodePeerInfo[];
diff --git a/src/types/store/describe.ts b/src/types/store/describe.ts
index 49c8a32d83..3415ee2cd5 100644
--- a/src/types/store/describe.ts
+++ b/src/types/store/describe.ts
@@ -1,40 +1,3 @@
-import type {
- FETCH_DESCRIBE,
- setCurrentDescribePath,
- setDataWasNotLoaded,
-} from '../../store/reducers/describe';
-import type {ApiRequestAction} from '../../store/utils';
-import type {IResponseError} from '../api/error';
import type {TEvDescribeSchemeResult} from '../api/schema';
export type IDescribeData = Record;
-
-export interface IDescribeState {
- loading: boolean;
- wasLoaded: boolean;
- data: IDescribeData;
- currentDescribe?: IDescribeData;
- currentDescribePath?: string;
- error?: IResponseError;
-}
-
-export interface IDescribeHandledResponse {
- path: string | undefined;
- data: IDescribeData | undefined;
- currentDescribe: IDescribeData | undefined;
-}
-
-type IDescribeApiRequestAction = ApiRequestAction<
- typeof FETCH_DESCRIBE,
- IDescribeHandledResponse,
- IResponseError
->;
-
-export type IDescribeAction =
- | IDescribeApiRequestAction
- | ReturnType
- | ReturnType;
-
-export interface IDescribeRootStateSlice {
- describe: IDescribeState;
-}
diff --git a/src/types/store/heatmap.ts b/src/types/store/heatmap.ts
index 289b11475c..71342b4910 100644
--- a/src/types/store/heatmap.ts
+++ b/src/types/store/heatmap.ts
@@ -1,6 +1,3 @@
-import type {FETCH_HEATMAP, setHeatmapOptions} from '../../store/reducers/heatmap';
-import type {ApiRequestAction} from '../../store/utils';
-import type {IResponseError} from '../api/error';
import type {TTableStats} from '../api/schema';
import type {TTabletStateInfo} from '../api/tablet';
import type {TMetrics} from '../api/tenant';
@@ -11,40 +8,13 @@ export interface IHeatmapTabletData extends TTabletStateInfo {
export type IHeatmapMetricValue = keyof TTableStats | keyof TMetrics;
-interface IHeatmapMetric {
- value: IHeatmapMetricValue;
- content: IHeatmapMetricValue;
-}
-
export interface IHeatmapState {
- loading: boolean;
- wasLoaded: boolean;
currentMetric?: IHeatmapMetricValue;
sort: boolean;
heatmap: boolean;
- data?: IHeatmapTabletData[];
- metrics?: IHeatmapMetric[];
- error?: IResponseError;
}
export interface IHeatmapApiRequestParams {
nodes?: string[];
path: string;
}
-
-interface IHeatmapHandledResponse {
- data: IHeatmapTabletData[];
- metrics?: IHeatmapMetric[];
-}
-
-type IHeatmapApiRequestAction = ApiRequestAction<
- typeof FETCH_HEATMAP,
- IHeatmapHandledResponse,
- IResponseError
->;
-
-export type IHeatmapAction = IHeatmapApiRequestAction | ReturnType;
-
-export interface IHeatmapRootStateSlice {
- heatmap: IHeatmapState;
-}
diff --git a/src/types/store/nodesList.ts b/src/types/store/nodesList.ts
index ae0ad15a2d..ce28a71fce 100644
--- a/src/types/store/nodesList.ts
+++ b/src/types/store/nodesList.ts
@@ -1,23 +1 @@
-import type {FETCH_NODES_LIST} from '../../store/reducers/nodesList';
-import type {ApiRequestAction} from '../../store/utils';
-import type {IResponseError} from '../api/error';
-import type {TEvNodesInfo} from '../api/nodesList';
-
-export interface NodesListState {
- loading: boolean;
- wasLoaded: boolean;
- data?: TEvNodesInfo;
- error?: IResponseError;
-}
-
-export type NodesListAction = ApiRequestAction<
- typeof FETCH_NODES_LIST,
- TEvNodesInfo,
- IResponseError
->;
-
export type NodesMap = Map;
-
-export interface NodesListRootStateSlice {
- nodesList: NodesListState;
-}
diff --git a/src/types/store/olapStats.ts b/src/types/store/olapStats.ts
deleted file mode 100644
index acc9768f18..0000000000
--- a/src/types/store/olapStats.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type {FETCH_OLAP_STATS, resetLoadingState} from '../../store/reducers/olapStats';
-import type {ApiRequestAction} from '../../store/utils';
-
-import type {IQueryResult} from './query';
-
-export interface OlapStatsState {
- loading: boolean;
- wasLoaded: boolean;
- data?: IQueryResult;
- error?: unknown;
-}
-
-export type OlapStatsAction =
- | ApiRequestAction
- | ReturnType;
diff --git a/src/types/store/tablet.ts b/src/types/store/tablet.ts
index 51854bcd59..a9ac2a26c7 100644
--- a/src/types/store/tablet.ts
+++ b/src/types/store/tablet.ts
@@ -1,10 +1,3 @@
-import type {
- FETCH_TABLET,
- FETCH_TABLET_DESCRIBE,
- clearTabletData,
-} from '../../store/reducers/tablet';
-import type {ApiRequestAction} from '../../store/utils';
-import type {IResponseError} from '../api/error';
import type {ETabletState, TTabletStateInfo} from '../api/tablet';
export interface ITabletPreparedHistoryItem {
@@ -17,15 +10,6 @@ export interface ITabletPreparedHistoryItem {
fqdn: string | undefined;
}
-export interface ITabletState {
- loading: boolean;
- tenantPath?: string;
- error?: IResponseError;
- id?: string;
- history?: ITabletPreparedHistoryItem[];
- data?: TTabletStateInfo;
-}
-
export interface ITabletHandledResponse {
tabletData: TTabletStateInfo;
historyData: ITabletPreparedHistoryItem[];
@@ -34,24 +18,3 @@ export interface ITabletHandledResponse {
export interface ITabletDescribeHandledResponse {
tenantPath: string;
}
-
-type ITabletApiRequestAction = ApiRequestAction<
- typeof FETCH_TABLET,
- ITabletHandledResponse,
- IResponseError
->;
-
-type ITabletDescribeApiRequestAction = ApiRequestAction<
- typeof FETCH_TABLET_DESCRIBE,
- ITabletDescribeHandledResponse,
- IResponseError
->;
-
-export type ITabletAction =
- | ITabletApiRequestAction
- | ITabletDescribeApiRequestAction
- | ReturnType;
-
-export interface ITabletRootStateSlice {
- tablet: ITabletState;
-}
diff --git a/src/types/store/tablets.ts b/src/types/store/tablets.ts
index 30fb0e1cfb..fd74ef908e 100644
--- a/src/types/store/tablets.ts
+++ b/src/types/store/tablets.ts
@@ -1,41 +1,15 @@
-import type {
- FETCH_TABLETS,
- clearWasLoadingFlag,
- setStateFilter,
- setTypeFilter,
-} from '../../store/reducers/tablets';
-import type {ApiRequestAction} from '../../store/utils';
-import type {IResponseError} from '../api/error';
-import type {ETabletState, EType, TEvTabletStateResponse} from '../api/tablet';
+import type {ETabletState, EType} from '../api/tablet';
-export interface ITabletsState {
- loading: boolean;
- wasLoaded: boolean;
+export interface TabletsState {
stateFilter: ETabletState[];
typeFilter: EType[];
- data?: TEvTabletStateResponse;
- error?: IResponseError;
}
-export interface ITabletsApiRequestParams {
+export interface TabletsApiRequestParams {
nodes?: string[];
path?: string;
}
-type ITabletsApiRequestAction = ApiRequestAction<
- typeof FETCH_TABLETS,
- TEvTabletStateResponse,
- IResponseError
->;
-
-export type ITabletsAction =
- | ITabletsApiRequestAction
- | (
- | ReturnType
- | ReturnType
- | ReturnType
- );
-
-export interface ITabletsRootStateSlice {
- tablets: ITabletsState;
+export interface TabletsRootStateSlice {
+ tablets: TabletsState;
}
diff --git a/src/types/store/topic.ts b/src/types/store/topic.ts
index 5706b432c9..6b31711959 100644
--- a/src/types/store/topic.ts
+++ b/src/types/store/topic.ts
@@ -1,8 +1,4 @@
-import type {FETCH_TOPIC, cleanTopicData, setDataWasNotLoaded} from '../../store/reducers/topic';
-import type {ApiRequestAction} from '../../store/utils';
import type {ProcessSpeedStats} from '../../utils/bytesParsers';
-import type {IResponseError} from '../api/error';
-import type {DescribeTopicResult} from '../api/topic';
export interface IPreparedConsumerData {
name: string | undefined;
@@ -21,19 +17,3 @@ export interface IPreparedTopicStats {
writeSpeed: ProcessSpeedStats;
}
-
-export interface ITopicState {
- loading: boolean;
- wasLoaded: boolean;
- data?: DescribeTopicResult;
- error?: IResponseError;
-}
-
-export type ITopicAction =
- | ApiRequestAction
- | ReturnType
- | ReturnType;
-
-export interface ITopicRootStateSlice {
- topic: ITopicState;
-}
diff --git a/src/utils/autofetcher.ts b/src/utils/autofetcher.ts
deleted file mode 100644
index 64b9c449a4..0000000000
--- a/src/utils/autofetcher.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-export class AutoFetcher {
- static DEFAULT_TIMEOUT = 30000;
- static MIN_TIMEOUT = 30000;
- timeout: number;
- active: boolean;
- timer: undefined | ReturnType;
- launchCounter: number;
-
- constructor() {
- this.timeout = AutoFetcher.DEFAULT_TIMEOUT;
- this.active = true;
- this.timer = undefined;
- this.launchCounter = 0;
- }
-
- wait(ms: number) {
- return new Promise((resolve) => {
- this.timer = setTimeout(resolve, ms);
- return;
- });
- }
- async fetch(request: Function) {
- if (!this.active) {
- return;
- }
-
- const currentLaunch = this.launchCounter;
-
- await this.wait(this.timeout);
-
- if (this.active) {
- const startTs = Date.now();
- await request();
- const finishTs = Date.now();
-
- if (currentLaunch !== this.launchCounter) {
- // autofetcher was restarted while request was in progress
- // stop further fetches, we are in deprecated thread
- return;
- }
-
- const responseTime = finishTs - startTs;
- const nextTimeout =
- responseTime > AutoFetcher.MIN_TIMEOUT ? responseTime : AutoFetcher.MIN_TIMEOUT;
- this.timeout = nextTimeout;
-
- this.fetch(request);
- } else {
- return;
- }
- }
- stop() {
- if (this.timer) {
- clearTimeout(this.timer);
- }
- this.active = false;
- }
- start() {
- this.launchCounter++;
- this.active = true;
- }
-}
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 7be1ab6899..68b652bfb4 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -89,6 +89,7 @@ export const ASIDE_HEADER_COMPACT_KEY = 'asideHeaderCompact';
export const QUERIES_HISTORY_KEY = 'queries_history';
export const DATA_QA_TUNE_COLUMNS_POPUP = 'tune-columns-popup';
export const BINARY_DATA_IN_PLAIN_TEXT_DISPLAY = 'binaryDataInPlainTextDisplay';
+export const AUTO_REFRESH_INTERVAL = 'auto-refresh-interval';
export const DEFAULT_SIZE_RESULT_PANE_KEY = 'default-size-result-pane';
export const DEFAULT_SIZE_TENANT_SUMMARY_KEY = 'default-size-tenant-summary-pane';
diff --git a/src/utils/hooks/index.ts b/src/utils/hooks/index.ts
index 7fe29f9291..658ed38467 100644
--- a/src/utils/hooks/index.ts
+++ b/src/utils/hooks/index.ts
@@ -1,4 +1,3 @@
-export * from './useAutofetcher';
export * from './useTypedSelector';
export * from './useTypedDispatch';
export * from './useSetting';
diff --git a/src/utils/hooks/useAutofetcher.ts b/src/utils/hooks/useAutofetcher.ts
deleted file mode 100644
index 16272f75da..0000000000
--- a/src/utils/hooks/useAutofetcher.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react';
-
-import {AutoFetcher} from '../autofetcher';
-
-export const useAutofetcher = (
- fetchData: (isBackground: boolean) => void,
- deps: React.DependencyList,
- enabled = true,
-) => {
- const ref = React.useRef(null);
-
- if (ref.current === null) {
- ref.current = new AutoFetcher();
- }
-
- const autofetcher = ref.current;
-
- // initial fetch
- React.useEffect(() => {
- fetchData(false);
- }, deps); // eslint-disable-line react-hooks/exhaustive-deps
-
- React.useEffect(() => {
- autofetcher.stop();
-
- if (enabled) {
- autofetcher.start();
- autofetcher.fetch(() => fetchData(true));
- }
-
- return () => {
- autofetcher.stop();
- };
- }, [enabled, ...deps]); // eslint-disable-line react-hooks/exhaustive-deps
-};
diff --git a/src/utils/tooltip.js b/src/utils/tooltip.js
index c022876b45..0b88129595 100644
--- a/src/utils/tooltip.js
+++ b/src/utils/tooltip.js
@@ -25,14 +25,14 @@ const NodeTooltip = (props) => {
| Rack |
{data.rack || '?'} |
- {data.connected && data.capacity && (
+ {data.connected && data.capacity ? (
| Net |
{`${data.connected} / ${data.capacity}`}
|
- )}
+ ) : null}