From c2066cf6c652aa24a83160fdff3178a1c8560984 Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Mon, 22 Apr 2024 09:33:07 +0000 Subject: [PATCH] feat(healthcheck): use rtk query --- .../Healthcheck/HealthcheckDetails.tsx | 8 +- .../Healthcheck/HealthcheckPreview.tsx | 10 +- .../MetricsCards/MetricsCards.tsx | 5 +- .../TenantOverview/TenantOverview.tsx | 10 +- .../TenantOverview/useHealthcheck.ts | 50 +++---- src/services/api.ts | 4 +- .../healthcheckInfo/healthcheckInfo.ts | 129 ++++++------------ src/store/reducers/healthcheckInfo/types.ts | 27 +--- src/store/reducers/index.ts | 2 - 9 files changed, 76 insertions(+), 169 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckDetails.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckDetails.tsx index bd40b3662a..55fbbed220 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckDetails.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckDetails.tsx @@ -3,7 +3,6 @@ import React from 'react'; import {ResponseError} from '../../../../../components/Errors/ResponseError'; import {Loader} from '../../../../../components/Loader'; import type {IssuesTree} from '../../../../../store/reducers/healthcheckInfo/types'; -import type {IResponseError} from '../../../../../types/api/error'; import {cn} from '../../../../../utils/cn'; import IssueTree from './IssuesViewer/IssueTree'; @@ -16,19 +15,18 @@ const b = cn('healthcheck'); interface HealthcheckDetailsProps { issueTrees?: IssuesTree[]; loading?: boolean; - wasLoaded?: boolean; - error?: IResponseError; + error?: unknown; } export function HealthcheckDetails(props: HealthcheckDetailsProps) { - const {issueTrees, loading, wasLoaded, error} = props; + const {issueTrees, loading, error} = props; const renderContent = () => { if (error) { return ; } - if (loading && !wasLoaded) { + if (loading) { return ; } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx index f4ccaa0cd9..1f117115b9 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx @@ -7,7 +7,6 @@ import {EntityStatus} from '../../../../../components/EntityStatus/EntityStatus' import {ResponseError} from '../../../../../components/Errors/ResponseError'; import {Loader} from '../../../../../components/Loader'; import {hcStatusToColorFlag} from '../../../../../store/reducers/healthcheckInfo/utils'; -import type {IResponseError} from '../../../../../types/api/error'; import type {SelfCheckResult, StatusFlag} from '../../../../../types/api/healthcheck'; import {cn} from '../../../../../utils/cn'; @@ -23,19 +22,18 @@ interface HealthcheckPreviewProps { selfCheckResult: SelfCheckResult; issuesStatistics?: [StatusFlag, number][]; loading?: boolean; - wasLoaded?: boolean; onUpdate: VoidFunction; - error?: IResponseError; + error?: unknown; active?: boolean; } export function HealthcheckPreview(props: HealthcheckPreviewProps) { - const {selfCheckResult, issuesStatistics, loading, wasLoaded, onUpdate, error, active} = props; + const {selfCheckResult, issuesStatistics, loading, onUpdate, error, active} = props; const renderHeader = () => { const modifier = selfCheckResult.toLowerCase(); - if (loading && !wasLoaded) { + if (loading) { return null; } @@ -59,7 +57,7 @@ export function HealthcheckPreview(props: HealthcheckPreviewProps) { return ; } - if (loading && !wasLoaded) { + if (loading) { return ; } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.tsx index e4b479bdff..18ff295f4e 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.tsx @@ -24,7 +24,6 @@ import { memoryUsageToStatus, storageUsageToStatus, } from '../../../../../store/reducers/tenants/utils'; -import type {IResponseError} from '../../../../../types/api/error'; import type {SelfCheckResult, StatusFlag} from '../../../../../types/api/healthcheck'; import {cn} from '../../../../../utils/cn'; import {formatStorageValues} from '../../../../../utils/dataFormatters/dataFormatters'; @@ -60,7 +59,7 @@ interface MetricsCardsProps { fetchHealthcheck: VoidFunction; healthcheckLoading?: boolean; healthCheckWasLoaded?: boolean; - healthcheckError?: IResponseError; + healthcheckError?: unknown; } export function MetricsCards({ @@ -72,7 +71,6 @@ export function MetricsCards({ selfCheckResult, fetchHealthcheck, healthcheckLoading, - healthCheckWasLoaded, healthcheckError, }: MetricsCardsProps) { const location = useLocation(); @@ -136,7 +134,6 @@ export function MetricsCards({ issuesStatistics={issuesStatistics} onUpdate={fetchHealthcheck} loading={healthcheckLoading} - wasLoaded={healthCheckWasLoaded} error={healthcheckError} active={metricsTab === TENANT_METRICS_TABS_IDS.healthcheck} /> diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index 5484683cf1..49215a9fdb 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -47,11 +47,10 @@ export function TenantOverview({ issueTrees, issuesStatistics, selfCheckResult, - fetchHealthcheck, loading: healthcheckLoading, - wasLoaded: healthCheckWasLoaded, error: healthcheckError, - } = useHealthcheck(tenantName); + refetch: fetchHealthcheck, + } = useHealthcheck(tenantName, {autorefresh}); const fetchTenant = React.useCallback( (isBackground = true) => { @@ -66,9 +65,8 @@ export function TenantOverview({ useAutofetcher( (isBackground) => { fetchTenant(isBackground); - fetchHealthcheck(isBackground); }, - [fetchTenant, fetchHealthcheck], + [fetchTenant], autorefresh, ); @@ -125,7 +123,6 @@ export function TenantOverview({ ); @@ -161,7 +158,6 @@ export function TenantOverview({ selfCheckResult={selfCheckResult} fetchHealthcheck={fetchHealthcheck} healthcheckLoading={healthcheckLoading} - healthCheckWasLoaded={healthCheckWasLoaded} healthcheckError={healthcheckError} /> diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/useHealthcheck.ts b/src/containers/Tenant/Diagnostics/TenantOverview/useHealthcheck.ts index be54290c06..43331c703e 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/useHealthcheck.ts +++ b/src/containers/Tenant/Diagnostics/TenantOverview/useHealthcheck.ts @@ -1,53 +1,45 @@ -import React from 'react'; - +import {DEFAULT_POLLING_INTERVAL} from '../../../../lib'; import { - getHealthcheckInfo, + healthcheckApi, selectIssuesStatistics, selectIssuesTrees, - setDataWasNotLoaded, } from '../../../../store/reducers/healthcheckInfo/healthcheckInfo'; import type {IssuesTree} from '../../../../store/reducers/healthcheckInfo/types'; -import type {IResponseError} from '../../../../types/api/error'; import {SelfCheckResult} from '../../../../types/api/healthcheck'; import type {StatusFlag} from '../../../../types/api/healthcheck'; -import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks'; +import {useTypedSelector} from '../../../../utils/hooks'; interface HealthcheckParams { issueTrees: IssuesTree[]; issuesStatistics: [StatusFlag, number][]; - fetchHealthcheck: (isBackground?: boolean) => void; loading: boolean; - wasLoaded: boolean; - error?: IResponseError; + error?: unknown; + refetch: () => void; selfCheckResult: SelfCheckResult; } -export const useHealthcheck = (tenantName: string): HealthcheckParams => { - const dispatch = useTypedDispatch(); - - const {data, loading, wasLoaded, error} = useTypedSelector((state) => state.healthcheckInfo); +export const useHealthcheck = ( + tenantName: string, + {autorefresh}: {autorefresh?: boolean} = {}, +): HealthcheckParams => { + const { + currentData: data, + isFetching, + error, + refetch, + } = healthcheckApi.useGetHealthcheckInfoQuery(tenantName, { + pollingInterval: autorefresh ? DEFAULT_POLLING_INTERVAL : 0, + }); const selfCheckResult = data?.self_check_result || SelfCheckResult.UNSPECIFIED; - const issuesStatistics = useTypedSelector(selectIssuesStatistics); - const issueTrees = useTypedSelector(selectIssuesTrees); - - const fetchHealthcheck = React.useCallback( - (isBackground = true) => { - if (!isBackground) { - dispatch(setDataWasNotLoaded()); - } - - dispatch(getHealthcheckInfo(tenantName)); - }, - [dispatch, tenantName], - ); + const issuesStatistics = useTypedSelector((state) => selectIssuesStatistics(state, tenantName)); + const issueTrees = useTypedSelector((state) => selectIssuesTrees(state, tenantName)); return { issueTrees, issuesStatistics, - fetchHealthcheck, - loading, - wasLoaded, + loading: data === undefined && isFetching, error, + refetch, selfCheckResult, }; }; diff --git a/src/services/api.ts b/src/services/api.ts index 4c1930ca21..0fd42a3e9f 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -385,11 +385,11 @@ export class YdbEmbeddedAPI extends AxiosWrapper { {concurrentId: concurrentId || 'getHotKeys'}, ); } - getHealthcheckInfo(database: string, {concurrentId}: AxiosOptions = {}) { + getHealthcheckInfo(database: string, {concurrentId, signal}: AxiosOptions = {}) { return this.get( this.getPath('/viewer/json/healthcheck?merge_records=true'), {tenant: database}, - {concurrentId}, + {concurrentId, requestConfig: {signal}}, ); } evictVDisk({ diff --git a/src/store/reducers/healthcheckInfo/healthcheckInfo.ts b/src/store/reducers/healthcheckInfo/healthcheckInfo.ts index a9c8ab396d..d982730cf9 100644 --- a/src/store/reducers/healthcheckInfo/healthcheckInfo.ts +++ b/src/store/reducers/healthcheckInfo/healthcheckInfo.ts @@ -1,68 +1,26 @@ import {createSelector} from '@reduxjs/toolkit'; -import type {Reducer, Selector} from '@reduxjs/toolkit'; import type {IssueLog, StatusFlag} from '../../../types/api/healthcheck'; -import {createApiRequest, createRequestActionTypes} from '../../utils'; - -import type { - HealthCheckInfoAction, - HealthcheckInfoRootStateSlice, - HealthcheckInfoState, - IssuesTree, -} from './types'; - -export const FETCH_HEALTHCHECK = createRequestActionTypes('cluster', 'FETCH_HEALTHCHECK'); - -const SET_DATA_WAS_NOT_LOADED = 'healthcheckInfo/SET_DATA_WAS_NOT_LOADED'; - -const initialState = {loading: false, wasLoaded: false}; - -const healthcheckInfo: Reducer = function ( - state = initialState, - action, -) { - switch (action.type) { - case FETCH_HEALTHCHECK.REQUEST: { - return { - ...state, - loading: true, - }; - } - case FETCH_HEALTHCHECK.SUCCESS: { - const {data} = action; - - return { - ...state, - data, - wasLoaded: true, - loading: false, - error: undefined, - }; - } - case FETCH_HEALTHCHECK.FAILURE: { - if (action.error?.isCancelled) { - return state; - } - - return { - ...state, - error: action.error, - loading: false, - wasLoaded: true, - data: undefined, - }; - } - - case SET_DATA_WAS_NOT_LOADED: { - return { - ...state, - wasLoaded: false, - }; - } - default: - return state; - } -}; +import type {RootState} from '../../defaultStore'; +import {api} from '../api'; + +import type {IssuesTree} from './types'; + +export const healthcheckApi = api.injectEndpoints({ + endpoints: (builder) => ({ + getHealthcheckInfo: builder.query({ + queryFn: async (database: string, {signal}) => { + try { + const data = await window.api.getHealthcheckInfo(database, {signal}); + return {data}; + } catch (error) { + return {error}; + } + }, + providesTags: ['All'], + }), + }), +}); const mapStatusToPriority: Partial> = { RED: 0, @@ -133,33 +91,28 @@ const getIssuesStatistics = (data: IssueLog[]): [StatusFlag, number][] => { }); }; -const getIssuesLog = (state: HealthcheckInfoRootStateSlice) => - state.healthcheckInfo.data?.issue_log; +const createGetHealthcheckInfoSelector = createSelector( + (database: string) => database, + (database) => healthcheckApi.endpoints.getHealthcheckInfo.select(database), +); -export const selectIssuesTreesRoots: Selector = - createSelector(getIssuesLog, (issues = []) => getRoots(issues)); +const getIssuesLog = createSelector( + (state: RootState) => state, + (_state: RootState, database: string) => createGetHealthcheckInfoSelector(database), + (state: RootState, selectGetPost) => selectGetPost(state).data?.issue_log || [], +); -export const selectIssuesTrees: Selector = - createSelector([getIssuesLog, selectIssuesTreesRoots], (data = [], roots = []) => { - return getInvertedConsequencesTree({data, roots}); - }); +export const selectIssuesTreesRoots = createSelector(getIssuesLog, (issues = []) => + getRoots(issues), +); -export const selectIssuesStatistics: Selector< - HealthcheckInfoRootStateSlice, - [StatusFlag, number][] -> = createSelector(getIssuesLog, (issues = []) => getIssuesStatistics(issues)); - -export function getHealthcheckInfo(database: string) { - return createApiRequest({ - request: window.api.getHealthcheckInfo(database, {concurrentId: 'getHealthcheckInfo'}), - actions: FETCH_HEALTHCHECK, - }); -} - -export const setDataWasNotLoaded = () => { - return { - type: SET_DATA_WAS_NOT_LOADED, - } as const; -}; +export const selectIssuesTrees = createSelector( + [getIssuesLog, selectIssuesTreesRoots], + (data = [], roots = []) => { + return getInvertedConsequencesTree({data, roots}); + }, +); -export default healthcheckInfo; +export const selectIssuesStatistics = createSelector(getIssuesLog, (issues = []) => + getIssuesStatistics(issues), +); diff --git a/src/store/reducers/healthcheckInfo/types.ts b/src/store/reducers/healthcheckInfo/types.ts index 538b48ae1b..ad0db05dd3 100644 --- a/src/store/reducers/healthcheckInfo/types.ts +++ b/src/store/reducers/healthcheckInfo/types.ts @@ -1,30 +1,5 @@ -import type {IResponseError} from '../../../types/api/error'; -import type {HealthCheckAPIResponse, IssueLog} from '../../../types/api/healthcheck'; -import type {ApiRequestAction} from '../../utils'; - -import type {FETCH_HEALTHCHECK, setDataWasNotLoaded} from './healthcheckInfo'; +import type {IssueLog} from '../../../types/api/healthcheck'; export interface IssuesTree extends IssueLog { reasonsItems?: IssuesTree[]; } - -export interface HealthcheckInfoState { - loading: boolean; - wasLoaded: boolean; - data?: HealthCheckAPIResponse; - error?: IResponseError; -} - -type HealthCheckApiRequestAction = ApiRequestAction< - typeof FETCH_HEALTHCHECK, - HealthCheckAPIResponse, - IResponseError ->; - -export type HealthCheckInfoAction = - | HealthCheckApiRequestAction - | ReturnType; - -export interface HealthcheckInfoRootStateSlice { - healthcheckInfo: HealthcheckInfoState; -} diff --git a/src/store/reducers/index.ts b/src/store/reducers/index.ts index 926c0c5053..44b235d132 100644 --- a/src/store/reducers/index.ts +++ b/src/store/reducers/index.ts @@ -10,7 +10,6 @@ import executeTopQueries from './executeTopQueries/executeTopQueries'; import explainQuery from './explainQuery'; import fullscreen from './fullscreen'; import header from './header/header'; -import healthcheckInfo from './healthcheckInfo/healthcheckInfo'; import heatmap from './heatmap'; import host from './host'; import hotKeys from './hotKeys/hotKeys'; @@ -82,7 +81,6 @@ export const rootReducer = { executeTopQueries, executeTopTables, tenantOverviewTopQueries, - healthcheckInfo, shardsWorkload, tenantOverviewTopShards, hotKeys,