From 68ee1b2ba5008e186e1db9b272c85c4f23d47c0a Mon Sep 17 00:00:00 2001 From: mufazalov Date: Mon, 29 Jan 2024 18:44:07 +0300 Subject: [PATCH 1/7] refactor(MetricChart): process error only in catch block --- src/components/MetricChart/MetricChart.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/MetricChart/MetricChart.tsx b/src/components/MetricChart/MetricChart.tsx index a2927336b1..985efc8658 100644 --- a/src/components/MetricChart/MetricChart.tsx +++ b/src/components/MetricChart/MetricChart.tsx @@ -146,7 +146,9 @@ export const MetricChart = ({ }); // Hack to prevent setting value to state, if component unmounted - if (!mounted.current) return; + if (!mounted.current) { + return; + } // In some cases error could be in response with 200 status code // It happens when request is OK, but chart data cannot be returned due to some reason @@ -155,10 +157,14 @@ export const MetricChart = ({ const preparedData = convertResponse(response, metrics); dispatch(setChartData(preparedData)); } else { - dispatch(setChartError({statusText: response.error})); + const err = {statusText: response.error}; + + throw err; } } catch (err) { - if (!mounted.current) return; + if (!mounted.current) { + return; + } dispatch(setChartError(err as IResponseError)); } From aa6043394d764bdb2bb07530f2b7d1fcfeff2b40 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Mon, 29 Jan 2024 18:52:05 +0300 Subject: [PATCH 2/7] refactor(TenantOverview): separate dashboards configs --- .../Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx | 5 +++-- .../TenantCpu/{CpuDashboard.tsx => cpuDashboardConfig.ts} | 8 ++------ .../TenantOverview/TenantMemory/TenantMemory.tsx | 5 +++-- .../{MemoryDashboard.tsx => memoryDashboardConfig.ts} | 8 ++------ .../Tenant/Diagnostics/TenantOverview/TenantOverview.tsx | 5 +++-- .../TenantOverview/TenantStorage/TenantStorage.tsx | 6 ++++-- .../{StorageDashboard.tsx => storageDashboardConfig.ts} | 8 ++------ .../{DefaultDashboard.tsx => defaultDashboardConfig.ts} | 8 ++------ 8 files changed, 21 insertions(+), 32 deletions(-) rename src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/{CpuDashboard.tsx => cpuDashboardConfig.ts} (51%) rename src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/{MemoryDashboard.tsx => memoryDashboardConfig.ts} (56%) rename src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/{StorageDashboard.tsx => storageDashboardConfig.ts} (56%) rename src/containers/Tenant/Diagnostics/TenantOverview/{DefaultDashboard.tsx => defaultDashboardConfig.ts} (83%) diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx index 5fb16b71be..0a02939b37 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx @@ -1,5 +1,6 @@ import type {AdditionalNodesProps} from '../../../../../types/additionalProps'; -import {CpuDashboard} from './CpuDashboard'; +import {TenantDashboard} from '../TenantDashboard/TenantDashboard'; +import {cpuDashboardConfig} from './cpuDashboardConfig'; import {TopNodesByLoad} from './TopNodesByLoad'; import {TopNodesByCpu} from './TopNodesByCpu'; import {TopShards} from './TopShards'; @@ -13,7 +14,7 @@ interface TenantCpuProps { export function TenantCpu({path, additionalNodesProps}: TenantCpuProps) { return ( <> - + diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/CpuDashboard.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/cpuDashboardConfig.ts similarity index 51% rename from src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/CpuDashboard.tsx rename to src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/cpuDashboardConfig.ts index dfb220f9fb..eea62f2d68 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/CpuDashboard.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/cpuDashboardConfig.ts @@ -1,7 +1,7 @@ -import {type ChartConfig, TenantDashboard} from '../TenantDashboard/TenantDashboard'; +import type {ChartConfig} from '../TenantDashboard/TenantDashboard'; import i18n from '../i18n'; -const cpuDashboardConfig: ChartConfig[] = [ +export const cpuDashboardConfig: ChartConfig[] = [ { title: i18n('charts.cpu-usage'), metrics: [ @@ -12,7 +12,3 @@ const cpuDashboardConfig: ChartConfig[] = [ ], }, ]; - -export const CpuDashboard = () => { - return ; -}; diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx index ddc51a3d05..974e9a1852 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx @@ -1,4 +1,5 @@ -import {MemoryDashboard} from './MemoryDashboard'; +import {TenantDashboard} from '../TenantDashboard/TenantDashboard'; +import {memoryDashboardConfig} from './memoryDashboardConfig'; import {TopNodesByMemory} from './TopNodesByMemory'; interface TenantMemoryProps { @@ -8,7 +9,7 @@ interface TenantMemoryProps { export function TenantMemory({path}: TenantMemoryProps) { return ( <> - + ); diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/MemoryDashboard.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/memoryDashboardConfig.ts similarity index 56% rename from src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/MemoryDashboard.tsx rename to src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/memoryDashboardConfig.ts index 0c600c3ea9..e5ac1295f7 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/MemoryDashboard.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/memoryDashboardConfig.ts @@ -1,7 +1,7 @@ -import {type ChartConfig, TenantDashboard} from '../TenantDashboard/TenantDashboard'; +import type {ChartConfig} from '../TenantDashboard/TenantDashboard'; import i18n from '../i18n'; -const memoryDashboardConfig: ChartConfig[] = [ +export const memoryDashboardConfig: ChartConfig[] = [ { title: i18n('charts.memory-usage'), metrics: [ @@ -15,7 +15,3 @@ const memoryDashboardConfig: ChartConfig[] = [ }, }, ]; - -export const MemoryDashboard = () => { - return ; -}; diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index 0e971735d8..895677f9f8 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -17,7 +17,8 @@ import {HealthcheckDetails} from './Healthcheck/HealthcheckDetails'; import {MetricsCards, type TenantMetrics} from './MetricsCards/MetricsCards'; import {TenantStorage} from './TenantStorage/TenantStorage'; import {TenantMemory} from './TenantMemory/TenantMemory'; -import {DefaultDashboard} from './DefaultDashboard'; +import {TenantDashboard} from './TenantDashboard/TenantDashboard'; +import {defaultDashboardConfig} from './defaultDashboardConfig'; import {useHealthcheck} from './useHealthcheck'; import './TenantOverview.scss'; @@ -141,7 +142,7 @@ export function TenantOverview({ ); } default: { - return ; + return ; } } }; diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx index 290fcb89e4..0e619c8f4c 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx @@ -5,9 +5,11 @@ import {ProgressViewer} from '../../../../../components/ProgressViewer/ProgressV import {formatStorageValues} from '../../../../../utils/dataFormatters/dataFormatters'; import {getSizeWithSignificantDigits} from '../../../../../utils/bytesParsers'; +import {TenantDashboard} from '../TenantDashboard/TenantDashboard'; + import '../TenantOverview.scss'; -import {StorageDashboard} from './StorageDashboard'; +import {storageDashboardConfig} from './storageDashboardConfig'; import {TopTables} from './TopTables'; import {TopGroups} from './TopGroups'; @@ -63,7 +65,7 @@ export function TenantStorage({tenantName, metrics}: TenantStorageProps) { ]; return ( <> - + diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/StorageDashboard.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/storageDashboardConfig.ts similarity index 56% rename from src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/StorageDashboard.tsx rename to src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/storageDashboardConfig.ts index 1780946206..9add0b73ce 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/StorageDashboard.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/storageDashboardConfig.ts @@ -1,7 +1,7 @@ -import {type ChartConfig, TenantDashboard} from '../TenantDashboard/TenantDashboard'; +import type {ChartConfig} from '../TenantDashboard/TenantDashboard'; import i18n from '../i18n'; -const storageDashboardConfig: ChartConfig[] = [ +export const storageDashboardConfig: ChartConfig[] = [ { title: i18n('charts.storage-usage'), metrics: [ @@ -15,7 +15,3 @@ const storageDashboardConfig: ChartConfig[] = [ }, }, ]; - -export const StorageDashboard = () => { - return ; -}; diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/DefaultDashboard.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/defaultDashboardConfig.ts similarity index 83% rename from src/containers/Tenant/Diagnostics/TenantOverview/DefaultDashboard.tsx rename to src/containers/Tenant/Diagnostics/TenantOverview/defaultDashboardConfig.ts index 71d8d6dc6b..a6e2bbed8f 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/DefaultDashboard.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/defaultDashboardConfig.ts @@ -1,7 +1,7 @@ -import {type ChartConfig, TenantDashboard} from './TenantDashboard/TenantDashboard'; +import type {ChartConfig} from './TenantDashboard/TenantDashboard'; import i18n from './i18n'; -const defaultDashboardConfig: ChartConfig[] = [ +export const defaultDashboardConfig: ChartConfig[] = [ { title: i18n('charts.queries-per-second'), metrics: [ @@ -44,7 +44,3 @@ const defaultDashboardConfig: ChartConfig[] = [ }, }, ]; - -export const DefaultDashboard = () => { - return ; -}; From ca3a411df342144b00bce112132390cfc5b6facb Mon Sep 17 00:00:00 2001 From: mufazalov Date: Tue, 30 Jan 2024 16:05:59 +0300 Subject: [PATCH 3/7] fix(TenantDashboard): hide if charts not enabled --- src/components/Loader/Loader.tsx | 5 +- .../LoadingContainer/LoadingContainer.scss | 10 ++++ .../LoadingContainer/LoadingContainer.tsx | 31 +++++++++++ src/components/MetricChart/MetricChart.tsx | 23 +++++++- src/components/MetricChart/index.ts | 2 +- src/components/MetricChart/types.ts | 3 ++ .../DefaultOverviewContent.tsx | 22 ++++++++ .../defaultDashboardConfig.ts | 4 +- .../TenantOverview/TenantCpu/TenantCpu.tsx | 15 ++++-- .../TenantDashboard/TenantDashboard.tsx | 53 ++++++++++++++++--- .../TenantMemory/TenantMemory.tsx | 14 +++-- .../TenantOverview/TenantOverview.scss | 4 ++ .../TenantOverview/TenantOverview.tsx | 5 +- .../TenantStorage/TenantStorage.tsx | 18 ++++--- .../Diagnostics/TenantOverview/utils.ts | 3 ++ src/containers/UserSettings/i18n/en.json | 5 +- src/containers/UserSettings/i18n/ru.json | 5 +- src/containers/UserSettings/settings.ts | 13 +---- src/services/settings.ts | 2 - src/utils/constants.ts | 2 - 20 files changed, 187 insertions(+), 52 deletions(-) create mode 100644 src/components/LoadingContainer/LoadingContainer.scss create mode 100644 src/components/LoadingContainer/LoadingContainer.tsx create mode 100644 src/containers/Tenant/Diagnostics/TenantOverview/DefaultOverviewContent/DefaultOverviewContent.tsx rename src/containers/Tenant/Diagnostics/TenantOverview/{ => DefaultOverviewContent}/defaultDashboardConfig.ts (92%) create mode 100644 src/containers/Tenant/Diagnostics/TenantOverview/utils.ts diff --git a/src/components/Loader/Loader.tsx b/src/components/Loader/Loader.tsx index 1defea4a08..f33adac68a 100644 --- a/src/components/Loader/Loader.tsx +++ b/src/components/Loader/Loader.tsx @@ -7,11 +7,12 @@ const b = cn('ydb-loader'); interface LoaderProps { size?: LoaderSize; + className?: string; } -export const Loader = ({size = 'm'}: LoaderProps) => { +export const Loader = ({size = 'm', className}: LoaderProps) => { return ( -
+
); diff --git a/src/components/LoadingContainer/LoadingContainer.scss b/src/components/LoadingContainer/LoadingContainer.scss new file mode 100644 index 0000000000..44da2f35a1 --- /dev/null +++ b/src/components/LoadingContainer/LoadingContainer.scss @@ -0,0 +1,10 @@ +.ydb-loading-container { + position: relative; + + &__loader { + position: absolute; + left: 50%; + + height: max-content; + } +} diff --git a/src/components/LoadingContainer/LoadingContainer.tsx b/src/components/LoadingContainer/LoadingContainer.tsx new file mode 100644 index 0000000000..9ff44c8ef0 --- /dev/null +++ b/src/components/LoadingContainer/LoadingContainer.tsx @@ -0,0 +1,31 @@ +import {type ReactNode} from 'react'; +import {cn} from '../../utils/cn'; +import {Loader} from '../Loader'; + +import './LoadingContainer.scss'; + +const block = cn('ydb-loading-container'); + +interface LoadingContainerProps { + loader?: ReactNode; + children?: ReactNode; + loading?: boolean; + className?: string; +} + +// Use visibility:hidden to preserve loading content size +// Also it helps to properly render charts and virtualized elements +// that didn't render properly with display:none since they need parent container sizes +export const LoadingContainer = ({ + loader = , + children, + loading, + className, +}: LoadingContainerProps) => { + return ( +
+ {loading && loader} +
{children}
+
+ ); +}; diff --git a/src/components/MetricChart/MetricChart.tsx b/src/components/MetricChart/MetricChart.tsx index 985efc8658..5f68ff4666 100644 --- a/src/components/MetricChart/MetricChart.tsx +++ b/src/components/MetricChart/MetricChart.tsx @@ -1,4 +1,4 @@ -import {useCallback, useEffect, useReducer, useRef} from 'react'; +import {useCallback, useEffect, useReducer, useRef, useState} from 'react'; import {RawSerieData, YagrPlugin, YagrWidgetData} from '@gravity-ui/chartkit/yagr'; import ChartKit, {settings} from '@gravity-ui/chartkit'; @@ -12,7 +12,13 @@ import {cn} from '../../utils/cn'; import {Loader} from '../Loader'; import {ResponseError} from '../Errors/ResponseError'; -import type {ChartOptions, MetricDescription, PreparedMetricsData} from './types'; +import type { + ChartOptions, + ChartDataStatus, + MetricDescription, + OnChartDataStatusChange, + PreparedMetricsData, +} from './types'; import {convertResponse} from './convertReponse'; import {getDefaultDataFormatter} from './getDefaultDataFormatter'; import {getChartData} from './getChartData'; @@ -102,6 +108,8 @@ interface DiagnosticsChartProps { width?: number; chartOptions?: ChartOptions; + + onChartDataStatusChange?: OnChartDataStatusChange; } export const MetricChart = ({ @@ -112,6 +120,7 @@ export const MetricChart = ({ width = 400, height = width / 1.5, chartOptions, + onChartDataStatusChange, }: DiagnosticsChartProps) => { const mounted = useRef(false); @@ -127,9 +136,17 @@ export const MetricChart = ({ initialChartState, ); + const [chartDataStatus, setChartDataStatus] = useState('loading'); + + // Update status in useEffect instead of fetchChartData, so data won't be fetched on callback update + useEffect(() => { + onChartDataStatusChange?.(chartDataStatus); + }, [onChartDataStatusChange, chartDataStatus]); + const fetchChartData = useCallback( async (isBackground: boolean) => { dispatch(setChartDataLoading()); + setChartDataStatus('loading'); if (!isBackground) { dispatch(setChartDataWasNotLoaded()); @@ -156,6 +173,7 @@ export const MetricChart = ({ if (Array.isArray(response)) { const preparedData = convertResponse(response, metrics); dispatch(setChartData(preparedData)); + setChartDataStatus('success'); } else { const err = {statusText: response.error}; @@ -167,6 +185,7 @@ export const MetricChart = ({ } dispatch(setChartError(err as IResponseError)); + setChartDataStatus('error'); } }, [metrics, timeFrame, width], diff --git a/src/components/MetricChart/index.ts b/src/components/MetricChart/index.ts index 0d3adfec55..01faeed70e 100644 --- a/src/components/MetricChart/index.ts +++ b/src/components/MetricChart/index.ts @@ -1,2 +1,2 @@ -export type {MetricDescription, Metric, ChartOptions} from './types'; +export * from './types'; export {MetricChart} from './MetricChart'; diff --git a/src/components/MetricChart/types.ts b/src/components/MetricChart/types.ts index 94cce8e71a..c43a0fd06c 100644 --- a/src/components/MetricChart/types.ts +++ b/src/components/MetricChart/types.ts @@ -30,3 +30,6 @@ export type ChartDataType = 'ms' | 'size'; export interface ChartOptions { dataType?: ChartDataType; } + +export type ChartDataStatus = 'loading' | 'success' | 'error'; +export type OnChartDataStatusChange = (newStatus: ChartDataStatus) => void; diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/DefaultOverviewContent/DefaultOverviewContent.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/DefaultOverviewContent/DefaultOverviewContent.tsx new file mode 100644 index 0000000000..2bec2b6fc1 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/TenantOverview/DefaultOverviewContent/DefaultOverviewContent.tsx @@ -0,0 +1,22 @@ +import {useState} from 'react'; +import {LoadingContainer} from '../../../../../components/LoadingContainer/LoadingContainer'; +import {TenantDashboard} from '../TenantDashboard/TenantDashboard'; +import {defaultDashboardConfig} from './defaultDashboardConfig'; +import {b} from '../utils'; + +export const DefaultOverviewContent = () => { + const [dashboardLoading, setDashboardLoading] = useState(true); + + return ( + + setDashboardLoading(false)} + /> + + ); +}; diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/defaultDashboardConfig.ts b/src/containers/Tenant/Diagnostics/TenantOverview/DefaultOverviewContent/defaultDashboardConfig.ts similarity index 92% rename from src/containers/Tenant/Diagnostics/TenantOverview/defaultDashboardConfig.ts rename to src/containers/Tenant/Diagnostics/TenantOverview/DefaultOverviewContent/defaultDashboardConfig.ts index a6e2bbed8f..b4dcbd255f 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/defaultDashboardConfig.ts +++ b/src/containers/Tenant/Diagnostics/TenantOverview/DefaultOverviewContent/defaultDashboardConfig.ts @@ -1,5 +1,5 @@ -import type {ChartConfig} from './TenantDashboard/TenantDashboard'; -import i18n from './i18n'; +import type {ChartConfig} from '../TenantDashboard/TenantDashboard'; +import i18n from '../i18n'; export const defaultDashboardConfig: ChartConfig[] = [ { diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx index 0a02939b37..98d2028c3b 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx @@ -1,10 +1,14 @@ +import {useState} from 'react'; + import type {AdditionalNodesProps} from '../../../../../types/additionalProps'; +import {LoadingContainer} from '../../../../../components/LoadingContainer/LoadingContainer'; import {TenantDashboard} from '../TenantDashboard/TenantDashboard'; import {cpuDashboardConfig} from './cpuDashboardConfig'; import {TopNodesByLoad} from './TopNodesByLoad'; import {TopNodesByCpu} from './TopNodesByCpu'; import {TopShards} from './TopShards'; import {TopQueries} from './TopQueries'; +import {b} from '../utils'; interface TenantCpuProps { path: string; @@ -12,13 +16,18 @@ interface TenantCpuProps { } export function TenantCpu({path, additionalNodesProps}: TenantCpuProps) { + const [dashboardLoading, setDashboardLoading] = useState(true); + return ( - <> - + + setDashboardLoading(false)} + /> - + ); } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantDashboard/TenantDashboard.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantDashboard/TenantDashboard.tsx index 467552ebc7..5e2ceca8d1 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantDashboard/TenantDashboard.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantDashboard/TenantDashboard.tsx @@ -1,14 +1,15 @@ +import {useRef, useState} from 'react'; import {StringParam, useQueryParam} from 'use-query-params'; import {cn} from '../../../../../utils/cn'; import type {TimeFrame} from '../../../../../utils/timeframes'; -import {useSetting, useTypedSelector} from '../../../../../utils/hooks'; -import {DISPLAY_CHARTS_IN_DB_DIAGNOSTICS_KEY} from '../../../../../utils/constants'; +import {useTypedSelector} from '../../../../../utils/hooks'; import {TimeFrameSelector} from '../../../../../components/TimeFrameSelector/TimeFrameSelector'; import { type ChartOptions, MetricChart, type MetricDescription, + type ChartDataStatus, } from '../../../../../components/MetricChart'; import './TenantDashboard.scss'; @@ -26,16 +27,52 @@ export interface ChartConfig { interface TenantDashboardProps { charts: ChartConfig[]; + onDashboardLoad?: VoidFunction; } -export const TenantDashboard = ({charts}: TenantDashboardProps) => { +type TenantDashboardStatus = 'success' | 'error' | undefined; + +type DashboardsChartsStatuses = Record; + +export const TenantDashboard = ({charts, onDashboardLoad}: TenantDashboardProps) => { + const [dashboardStatus, setDashboardStatus] = useState(); + const chartsStatuses = useRef({}); + const [timeFrame = '1h', setTimeframe] = useQueryParam('timeframe', StringParam); const {autorefresh} = useTypedSelector((state) => state.schema); - const [chartsEnabled] = useSetting(DISPLAY_CHARTS_IN_DB_DIAGNOSTICS_KEY); + /** + * Charts should be hidden, if they are not enabled: + * 1. GraphShard is not enabled + * 2. ydb version does not have /viewer/json/render endpoint (400, 404, CORS error, etc.) + * @link https://github.com/ydb-platform/ydb-embedded-ui/issues/659 + * @todo disable only for specific errors ('GraphShard is not enabled') after ydb-stable-24 is generally used + */ + const handleChartDataStatusChange = (chartId: string, chartStatus: ChartDataStatus) => { + // If status for chart or dashboard is set, doesn't update it + // Dashboard should be consistently hidden or shown + if (dashboardStatus || chartsStatuses.current[chartId] || chartStatus === 'loading') { + return; + } + chartsStatuses.current[chartId] = chartStatus; + + // Show dashboard, if at least one chart is successfully loaded + if (chartStatus === 'success') { + setDashboardStatus('success'); + onDashboardLoad?.(); + return; + } + + // If there is no charts with successfull data load, dashboard shouldn't be shown + if (Object.keys(chartsStatuses.current).length === charts.length) { + setDashboardStatus('error'); + onDashboardLoad?.(); + } + }; - if (!chartsEnabled) { + // Do not continue render charts if dashboard not valid + if (dashboardStatus === 'error') { return null; } @@ -45,9 +82,10 @@ export const TenantDashboard = ({charts}: TenantDashboardProps) => { const renderContent = () => { return charts.map((chartConfig) => { + const chartId = chartConfig.metrics.map(({target}) => target).join('&'); return ( target).join('&')} + key={chartId} title={chartConfig.title} metrics={chartConfig.metrics} timeFrame={timeFrame as TimeFrame} @@ -55,6 +93,9 @@ export const TenantDashboard = ({charts}: TenantDashboardProps) => { autorefresh={autorefresh} width={chartWidth} height={chartHeight} + onChartDataStatusChange={(status) => + handleChartDataStatusChange(chartId, status) + } /> ); }); diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx index 974e9a1852..853c0d74a5 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx @@ -1,16 +1,24 @@ +import {useState} from 'react'; +import {LoadingContainer} from '../../../../../components/LoadingContainer/LoadingContainer'; import {TenantDashboard} from '../TenantDashboard/TenantDashboard'; import {memoryDashboardConfig} from './memoryDashboardConfig'; import {TopNodesByMemory} from './TopNodesByMemory'; +import {b} from '../utils'; interface TenantMemoryProps { path: string; } export function TenantMemory({path}: TenantMemoryProps) { + const [dashboardLoading, setDashboardLoading] = useState(true); + return ( - <> - + + setDashboardLoading(false)} + /> - + ); } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss index c695093931..d9e03a3912 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss @@ -71,4 +71,8 @@ &__storage-info { margin-bottom: 36px; } + + &__loading-container { + width: var(--diagnostics-section-table-width); + } } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index 895677f9f8..e4d737fb8b 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -17,8 +17,7 @@ import {HealthcheckDetails} from './Healthcheck/HealthcheckDetails'; import {MetricsCards, type TenantMetrics} from './MetricsCards/MetricsCards'; import {TenantStorage} from './TenantStorage/TenantStorage'; import {TenantMemory} from './TenantMemory/TenantMemory'; -import {TenantDashboard} from './TenantDashboard/TenantDashboard'; -import {defaultDashboardConfig} from './defaultDashboardConfig'; +import {DefaultOverviewContent} from './DefaultOverviewContent/DefaultOverviewContent'; import {useHealthcheck} from './useHealthcheck'; import './TenantOverview.scss'; @@ -142,7 +141,7 @@ export function TenantOverview({ ); } default: { - return ; + return ; } } }; diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx index 0e619c8f4c..10927446a3 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx @@ -1,7 +1,8 @@ -import cn from 'bem-cn-lite'; +import {useState} from 'react'; import InfoViewer from '../../../../../components/InfoViewer/InfoViewer'; import {ProgressViewer} from '../../../../../components/ProgressViewer/ProgressViewer'; +import {LoadingContainer} from '../../../../../components/LoadingContainer/LoadingContainer'; import {formatStorageValues} from '../../../../../utils/dataFormatters/dataFormatters'; import {getSizeWithSignificantDigits} from '../../../../../utils/bytesParsers'; @@ -12,8 +13,7 @@ import '../TenantOverview.scss'; import {storageDashboardConfig} from './storageDashboardConfig'; import {TopTables} from './TopTables'; import {TopGroups} from './TopGroups'; - -const b = cn('tenant-overview'); +import {b} from '../utils'; export interface TenantStorageMetrics { blobStorageUsed?: number; @@ -28,6 +28,8 @@ interface TenantStorageProps { } export function TenantStorage({tenantName, metrics}: TenantStorageProps) { + const [dashboardLoading, setDashboardLoading] = useState(true); + const {blobStorageUsed, tableStorageUsed, blobStorageLimit, tableStorageLimit} = metrics; const formatValues = (value?: number, total?: number) => { const size = getSizeWithSignificantDigits(Number(blobStorageLimit || blobStorageUsed), 0); @@ -63,12 +65,16 @@ export function TenantStorage({tenantName, metrics}: TenantStorageProps) { ), }, ]; + return ( - <> - + + setDashboardLoading(false)} + /> - + ); } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/utils.ts b/src/containers/Tenant/Diagnostics/TenantOverview/utils.ts new file mode 100644 index 0000000000..1da74e5223 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/TenantOverview/utils.ts @@ -0,0 +1,3 @@ +import {cn} from '../../../../utils/cn'; + +export const b = cn('tenant-overview'); diff --git a/src/containers/UserSettings/i18n/en.json b/src/containers/UserSettings/i18n/en.json index a0e2e7ea81..c4b534e68a 100644 --- a/src/containers/UserSettings/i18n/en.json +++ b/src/containers/UserSettings/i18n/en.json @@ -23,8 +23,5 @@ "settings.useVirtualTables.popover": "It will increase performance, but could work unstable", "settings.queryUseMultiSchema.title": "Allow queries with multiple result sets", - "settings.queryUseMultiSchema.popover": "Use 'multi' schema for queries that enables queries with multiple result sets. Returns nothing on versions 23-3 and older", - - "settings.displayChartsInDbDiagnostics.title": "Display charts in database diagnostics", - "settings.displayChartsInDbDiagnostics.popover": "Incorrect data may be displayed (shows data only for the database related to the current node), does not work well on static nodes" + "settings.queryUseMultiSchema.popover": "Use 'multi' schema for queries that enables queries with multiple result sets. Returns nothing on versions 23-3 and older" } diff --git a/src/containers/UserSettings/i18n/ru.json b/src/containers/UserSettings/i18n/ru.json index 4caa9e43d3..1e5666d953 100644 --- a/src/containers/UserSettings/i18n/ru.json +++ b/src/containers/UserSettings/i18n/ru.json @@ -23,8 +23,5 @@ "settings.useVirtualTables.popover": "Это улучшит производительность, но может работать нестабильно", "settings.queryUseMultiSchema.title": "Разрешить запросы с несколькими результатами", - "settings.queryUseMultiSchema.popover": "Использовать для запросов схему 'multi', которая позволяет выполнять запросы с несколькими результатами. На версиях 23-3 и старше результат не возвращается вообще", - - "settings.displayChartsInDbDiagnostics.title": "Показывать графики в диагностике базы данных", - "settings.displayChartsInDbDiagnostics.popover": "Могут отображаться неправильные данные (показывает данные только для базы, относящейся к текущей ноде), плохо работает на статических нодах" + "settings.queryUseMultiSchema.popover": "Использовать для запросов схему 'multi', которая позволяет выполнять запросы с несколькими результатами. На версиях 23-3 и старше результат не возвращается вообще" } diff --git a/src/containers/UserSettings/settings.ts b/src/containers/UserSettings/settings.ts index 866a558688..aeddfbf5c6 100644 --- a/src/containers/UserSettings/settings.ts +++ b/src/containers/UserSettings/settings.ts @@ -10,7 +10,6 @@ import { USE_BACKEND_PARAMS_FOR_TABLES_KEY, USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY, QUERY_USE_MULTI_SCHEMA_KEY, - DISPLAY_CHARTS_IN_DB_DIAGNOSTICS_KEY, } from '../../utils/constants'; import {Lang, defaultLang} from '../../utils/i18n'; @@ -95,11 +94,6 @@ export const queryUseMultiSchemaSetting: SettingProps = { title: i18n('settings.queryUseMultiSchema.title'), helpPopoverContent: i18n('settings.queryUseMultiSchema.popover'), }; -export const displayChartsInDbDiagnosticsSetting: SettingProps = { - settingKey: DISPLAY_CHARTS_IN_DB_DIAGNOSTICS_KEY, - title: i18n('settings.displayChartsInDbDiagnostics.title'), - helpPopoverContent: i18n('settings.displayChartsInDbDiagnostics.popover'), -}; export const appearanceSection: SettingsSection = { id: 'appearanceSection', @@ -109,12 +103,7 @@ export const appearanceSection: SettingsSection = { export const experimentsSection: SettingsSection = { id: 'experimentsSection', title: i18n('section.experiments'), - settings: [ - useNodesEndpointSetting, - useVirtualTables, - queryUseMultiSchemaSetting, - displayChartsInDbDiagnosticsSetting, - ], + settings: [useNodesEndpointSetting, useVirtualTables, queryUseMultiSchemaSetting], }; export const generalPage: SettingsPage = { diff --git a/src/services/settings.ts b/src/services/settings.ts index 54dea57c28..ce3e02e50b 100644 --- a/src/services/settings.ts +++ b/src/services/settings.ts @@ -3,7 +3,6 @@ import {TENANT_PAGES_IDS} from '../store/reducers/tenant/constants'; import { ASIDE_HEADER_COMPACT_KEY, CLUSTER_INFO_HIDDEN_KEY, - DISPLAY_CHARTS_IN_DB_DIAGNOSTICS_KEY, INVERTED_DISKS_KEY, LANGUAGE_KEY, LAST_USED_QUERY_ACTION_KEY, @@ -38,7 +37,6 @@ export const DEFAULT_USER_SETTINGS: SettingsObject = { [PARTITIONS_HIDDEN_COLUMNS_KEY]: [], [CLUSTER_INFO_HIDDEN_KEY]: true, [USE_BACKEND_PARAMS_FOR_TABLES_KEY]: false, - [DISPLAY_CHARTS_IN_DB_DIAGNOSTICS_KEY]: false, }; class SettingsManager { diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 9fdd8f59e2..b57176acd6 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -127,5 +127,3 @@ export const USE_BACKEND_PARAMS_FOR_TABLES_KEY = 'useBackendParamsForTables'; // Enable schema that supports multiple resultsets export const QUERY_USE_MULTI_SCHEMA_KEY = 'queryUseMultiSchema'; - -export const DISPLAY_CHARTS_IN_DB_DIAGNOSTICS_KEY = 'displayChartsInDbDiagnostics'; From 2a9d644213a99d1d83d63bc265ffd4bb40bc7c47 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Mon, 5 Feb 2024 16:26:44 +0300 Subject: [PATCH 4/7] refactor(MetricChart): use reducer state for chart status --- src/components/MetricChart/MetricChart.tsx | 23 ++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/MetricChart/MetricChart.tsx b/src/components/MetricChart/MetricChart.tsx index 5f68ff4666..f1bfd02555 100644 --- a/src/components/MetricChart/MetricChart.tsx +++ b/src/components/MetricChart/MetricChart.tsx @@ -1,4 +1,4 @@ -import {useCallback, useEffect, useReducer, useRef, useState} from 'react'; +import {useCallback, useEffect, useReducer, useRef} from 'react'; import {RawSerieData, YagrPlugin, YagrWidgetData} from '@gravity-ui/chartkit/yagr'; import ChartKit, {settings} from '@gravity-ui/chartkit'; @@ -14,7 +14,6 @@ import {ResponseError} from '../Errors/ResponseError'; import type { ChartOptions, - ChartDataStatus, MetricDescription, OnChartDataStatusChange, PreparedMetricsData, @@ -136,17 +135,23 @@ export const MetricChart = ({ initialChartState, ); - const [chartDataStatus, setChartDataStatus] = useState('loading'); - - // Update status in useEffect instead of fetchChartData, so data won't be fetched on callback update useEffect(() => { - onChartDataStatusChange?.(chartDataStatus); - }, [onChartDataStatusChange, chartDataStatus]); + 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 = useCallback( async (isBackground: boolean) => { dispatch(setChartDataLoading()); - setChartDataStatus('loading'); if (!isBackground) { dispatch(setChartDataWasNotLoaded()); @@ -173,7 +178,6 @@ export const MetricChart = ({ if (Array.isArray(response)) { const preparedData = convertResponse(response, metrics); dispatch(setChartData(preparedData)); - setChartDataStatus('success'); } else { const err = {statusText: response.error}; @@ -185,7 +189,6 @@ export const MetricChart = ({ } dispatch(setChartError(err as IResponseError)); - setChartDataStatus('error'); } }, [metrics, timeFrame, width], From e06808bb4e9c48daa19848680404c852390e8519 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Tue, 6 Feb 2024 11:59:47 +0300 Subject: [PATCH 5/7] fix(TenantDashboard): update hide logic --- .../LoadingContainer/LoadingContainer.scss | 10 ---- .../LoadingContainer/LoadingContainer.tsx | 31 ------------ src/components/MetricChart/MetricChart.tsx | 14 +++++- .../DefaultOverviewContent.tsx | 18 +------ .../TenantOverview/TenantCpu/TenantCpu.tsx | 15 ++---- .../TenantDashboard/TenantDashboard.tsx | 49 +++++-------------- .../TenantMemory/TenantMemory.tsx | 14 ++---- .../TenantOverview/TenantOverview.scss | 4 -- .../TenantStorage/TenantStorage.tsx | 14 ++---- src/utils/hooks/index.ts | 1 + src/utils/hooks/useIsElementVisible.ts | 38 ++++++++++++++ 11 files changed, 74 insertions(+), 134 deletions(-) delete mode 100644 src/components/LoadingContainer/LoadingContainer.scss delete mode 100644 src/components/LoadingContainer/LoadingContainer.tsx create mode 100644 src/utils/hooks/useIsElementVisible.ts diff --git a/src/components/LoadingContainer/LoadingContainer.scss b/src/components/LoadingContainer/LoadingContainer.scss deleted file mode 100644 index 44da2f35a1..0000000000 --- a/src/components/LoadingContainer/LoadingContainer.scss +++ /dev/null @@ -1,10 +0,0 @@ -.ydb-loading-container { - position: relative; - - &__loader { - position: absolute; - left: 50%; - - height: max-content; - } -} diff --git a/src/components/LoadingContainer/LoadingContainer.tsx b/src/components/LoadingContainer/LoadingContainer.tsx deleted file mode 100644 index 9ff44c8ef0..0000000000 --- a/src/components/LoadingContainer/LoadingContainer.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import {type ReactNode} from 'react'; -import {cn} from '../../utils/cn'; -import {Loader} from '../Loader'; - -import './LoadingContainer.scss'; - -const block = cn('ydb-loading-container'); - -interface LoadingContainerProps { - loader?: ReactNode; - children?: ReactNode; - loading?: boolean; - className?: string; -} - -// Use visibility:hidden to preserve loading content size -// Also it helps to properly render charts and virtualized elements -// that didn't render properly with display:none since they need parent container sizes -export const LoadingContainer = ({ - loader = , - children, - loading, - className, -}: LoadingContainerProps) => { - return ( -
- {loading && loader} -
{children}
-
- ); -}; diff --git a/src/components/MetricChart/MetricChart.tsx b/src/components/MetricChart/MetricChart.tsx index f1bfd02555..fe9dbf5ece 100644 --- a/src/components/MetricChart/MetricChart.tsx +++ b/src/components/MetricChart/MetricChart.tsx @@ -5,7 +5,8 @@ import ChartKit, {settings} from '@gravity-ui/chartkit'; import type {IResponseError} from '../../types/api/error'; import type {TimeFrame} from '../../utils/timeframes'; -import {useAutofetcher} from '../../utils/hooks'; +import {useAutofetcher, useIsElementVisible} from '../../utils/hooks'; + import {COLORS} from '../../utils/versions'; import {cn} from '../../utils/cn'; @@ -121,6 +122,12 @@ export const MetricChart = ({ chartOptions, onChartDataStatusChange, }: DiagnosticsChartProps) => { + // YAGR charts use uplot inside + // uplot has the issue, that it doesn't render correctly inside not visible elements + // so if chart is used inside component with display:none, it will be empty, when visibility change + const containerRef = useRef(null); + const isChartVisible = useIsElementVisible(containerRef); + const mounted = useRef(false); useEffect(() => { @@ -203,6 +210,10 @@ export const MetricChart = ({ return ; } + if (!isChartVisible) { + return null; + } + return (
@@ -213,6 +224,7 @@ export const MetricChart = ({ return (
{ - const [dashboardLoading, setDashboardLoading] = useState(true); - - return ( - - setDashboardLoading(false)} - /> - - ); + return ; }; diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx index 98d2028c3b..0a02939b37 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx @@ -1,14 +1,10 @@ -import {useState} from 'react'; - import type {AdditionalNodesProps} from '../../../../../types/additionalProps'; -import {LoadingContainer} from '../../../../../components/LoadingContainer/LoadingContainer'; import {TenantDashboard} from '../TenantDashboard/TenantDashboard'; import {cpuDashboardConfig} from './cpuDashboardConfig'; import {TopNodesByLoad} from './TopNodesByLoad'; import {TopNodesByCpu} from './TopNodesByCpu'; import {TopShards} from './TopShards'; import {TopQueries} from './TopQueries'; -import {b} from '../utils'; interface TenantCpuProps { path: string; @@ -16,18 +12,13 @@ interface TenantCpuProps { } export function TenantCpu({path, additionalNodesProps}: TenantCpuProps) { - const [dashboardLoading, setDashboardLoading] = useState(true); - return ( - - setDashboardLoading(false)} - /> + <> + - + ); } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantDashboard/TenantDashboard.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantDashboard/TenantDashboard.tsx index 5e2ceca8d1..8112681ef2 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantDashboard/TenantDashboard.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantDashboard/TenantDashboard.tsx @@ -1,4 +1,4 @@ -import {useRef, useState} from 'react'; +import {useState} from 'react'; import {StringParam, useQueryParam} from 'use-query-params'; import {cn} from '../../../../../utils/cn'; @@ -27,55 +27,32 @@ export interface ChartConfig { interface TenantDashboardProps { charts: ChartConfig[]; - onDashboardLoad?: VoidFunction; } -type TenantDashboardStatus = 'success' | 'error' | undefined; - -type DashboardsChartsStatuses = Record; - -export const TenantDashboard = ({charts, onDashboardLoad}: TenantDashboardProps) => { - const [dashboardStatus, setDashboardStatus] = useState(); - const chartsStatuses = useRef({}); +export const TenantDashboard = ({charts}: TenantDashboardProps) => { + const [isDashboardHidden, setIsDashboardHidden] = useState(true); const [timeFrame = '1h', setTimeframe] = useQueryParam('timeframe', StringParam); const {autorefresh} = useTypedSelector((state) => state.schema); + // Refetch data only if dashboard successfully loaded + const shouldRefresh = autorefresh && !isDashboardHidden; + /** * Charts should be hidden, if they are not enabled: * 1. GraphShard is not enabled * 2. ydb version does not have /viewer/json/render endpoint (400, 404, CORS error, etc.) + * If at least one chart successfully loaded, dashboard should be shown * @link https://github.com/ydb-platform/ydb-embedded-ui/issues/659 * @todo disable only for specific errors ('GraphShard is not enabled') after ydb-stable-24 is generally used */ - const handleChartDataStatusChange = (chartId: string, chartStatus: ChartDataStatus) => { - // If status for chart or dashboard is set, doesn't update it - // Dashboard should be consistently hidden or shown - if (dashboardStatus || chartsStatuses.current[chartId] || chartStatus === 'loading') { - return; - } - chartsStatuses.current[chartId] = chartStatus; - - // Show dashboard, if at least one chart is successfully loaded + const handleChartDataStatusChange = (chartStatus: ChartDataStatus) => { if (chartStatus === 'success') { - setDashboardStatus('success'); - onDashboardLoad?.(); - return; - } - - // If there is no charts with successfull data load, dashboard shouldn't be shown - if (Object.keys(chartsStatuses.current).length === charts.length) { - setDashboardStatus('error'); - onDashboardLoad?.(); + setIsDashboardHidden(false); } }; - // Do not continue render charts if dashboard not valid - if (dashboardStatus === 'error') { - return null; - } - // If there is only one chart, display it with full width const chartWidth = charts.length === 1 ? CHART_WIDTH_FULL : CHART_WIDTH; const chartHeight = CHART_WIDTH / 1.5; @@ -90,19 +67,17 @@ export const TenantDashboard = ({charts, onDashboardLoad}: TenantDashboardProps) metrics={chartConfig.metrics} timeFrame={timeFrame as TimeFrame} chartOptions={chartConfig.options} - autorefresh={autorefresh} + autorefresh={shouldRefresh} width={chartWidth} height={chartHeight} - onChartDataStatusChange={(status) => - handleChartDataStatusChange(chartId, status) - } + onChartDataStatusChange={handleChartDataStatusChange} /> ); }); }; return ( -
+
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx index 853c0d74a5..974e9a1852 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx @@ -1,24 +1,16 @@ -import {useState} from 'react'; -import {LoadingContainer} from '../../../../../components/LoadingContainer/LoadingContainer'; import {TenantDashboard} from '../TenantDashboard/TenantDashboard'; import {memoryDashboardConfig} from './memoryDashboardConfig'; import {TopNodesByMemory} from './TopNodesByMemory'; -import {b} from '../utils'; interface TenantMemoryProps { path: string; } export function TenantMemory({path}: TenantMemoryProps) { - const [dashboardLoading, setDashboardLoading] = useState(true); - return ( - - setDashboardLoading(false)} - /> + <> + - + ); } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss index d9e03a3912..c695093931 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss @@ -71,8 +71,4 @@ &__storage-info { margin-bottom: 36px; } - - &__loading-container { - width: var(--diagnostics-section-table-width); - } } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx index 10927446a3..3349c45b30 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx @@ -1,8 +1,5 @@ -import {useState} from 'react'; - import InfoViewer from '../../../../../components/InfoViewer/InfoViewer'; import {ProgressViewer} from '../../../../../components/ProgressViewer/ProgressViewer'; -import {LoadingContainer} from '../../../../../components/LoadingContainer/LoadingContainer'; import {formatStorageValues} from '../../../../../utils/dataFormatters/dataFormatters'; import {getSizeWithSignificantDigits} from '../../../../../utils/bytesParsers'; @@ -28,8 +25,6 @@ interface TenantStorageProps { } export function TenantStorage({tenantName, metrics}: TenantStorageProps) { - const [dashboardLoading, setDashboardLoading] = useState(true); - const {blobStorageUsed, tableStorageUsed, blobStorageLimit, tableStorageLimit} = metrics; const formatValues = (value?: number, total?: number) => { const size = getSizeWithSignificantDigits(Number(blobStorageLimit || blobStorageUsed), 0); @@ -67,14 +62,11 @@ export function TenantStorage({tenantName, metrics}: TenantStorageProps) { ]; return ( - - setDashboardLoading(false)} - /> + <> + - + ); } diff --git a/src/utils/hooks/index.ts b/src/utils/hooks/index.ts index 42ebaaa93b..6c7679ea2a 100644 --- a/src/utils/hooks/index.ts +++ b/src/utils/hooks/index.ts @@ -4,6 +4,7 @@ export * from './useSetting'; export * from './useQueryModes'; export * from './useTableSort'; export * from './useSearchQuery'; +export * from './useIsElementVisible'; export * from './useNodesRequestParams'; export * from './useStorageRequestParams'; diff --git a/src/utils/hooks/useIsElementVisible.ts b/src/utils/hooks/useIsElementVisible.ts new file mode 100644 index 0000000000..1cba18a40d --- /dev/null +++ b/src/utils/hooks/useIsElementVisible.ts @@ -0,0 +1,38 @@ +import {type RefObject, useEffect, useRef, useState} from 'react'; + +/** Determines, whether element is currently displayd by boundingClientRect */ +export const useIsElementVisible = (ref: RefObject) => { + const observer = useRef(); + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const callback = (entries: ResizeObserverEntry[]) => { + entries.forEach((entry) => { + // Assuming this observer has only one entry + const rect = entry.target.getBoundingClientRect(); + setIsVisible(rect.height > 0 && rect.width > 0); + }); + }; + + observer.current = new ResizeObserver(callback); + + return () => { + observer.current?.disconnect(); + observer.current = undefined; + }; + }, []); + + useEffect(() => { + const el = ref.current; + if (el) { + observer.current?.observe(el); + } + return () => { + if (el) { + observer.current?.unobserve(el); + } + }; + }, [ref]); + + return isVisible; +}; From 123cebcc98670d72b312833c3219df43e3bba507 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Tue, 6 Feb 2024 12:10:54 +0300 Subject: [PATCH 6/7] refactor(TenantOverview): use block cn from utils --- .../Tenant/Diagnostics/TenantOverview/TenantOverview.tsx | 4 +--- .../Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index e4d737fb8b..d542e65460 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -1,4 +1,3 @@ -import cn from 'bem-cn-lite'; import {useCallback} from 'react'; import {useDispatch} from 'react-redux'; @@ -21,8 +20,7 @@ import {DefaultOverviewContent} from './DefaultOverviewContent/DefaultOverviewCo import {useHealthcheck} from './useHealthcheck'; import './TenantOverview.scss'; - -const b = cn('tenant-overview'); +import {b} from './utils'; interface TenantOverviewProps { tenantName: string; diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx index 77612c4ef0..9b7473397c 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx @@ -1,5 +1,4 @@ import type {ReactNode} from 'react'; -import cn from 'bem-cn-lite'; import DataTable from '@gravity-ui/react-data-table'; import type {DataTableProps} from '@gravity-ui/react-data-table'; @@ -11,8 +10,7 @@ import { import type {IResponseError} from '../../../../types/api/error'; import {TableSkeleton} from '../../../../components/TableSkeleton/TableSkeleton'; import {ResponseError} from '../../../../components/Errors/ResponseError'; - -const b = cn('tenant-overview'); +import {b} from './utils'; interface TenantOverviewTableLayoutProps extends Omit, 'theme'> { title: ReactNode; From ead59017a4bc95659fdd9a165b3d92457d1b3de9 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Wed, 7 Feb 2024 17:03:34 +0300 Subject: [PATCH 7/7] fix(MetricChart): pass visibility as prop --- src/components/MetricChart/MetricChart.tsx | 17 +++++---- .../TenantDashboard/TenantDashboard.tsx | 2 + src/utils/hooks/index.ts | 1 - src/utils/hooks/useIsElementVisible.ts | 38 ------------------- 4 files changed, 11 insertions(+), 47 deletions(-) delete mode 100644 src/utils/hooks/useIsElementVisible.ts diff --git a/src/components/MetricChart/MetricChart.tsx b/src/components/MetricChart/MetricChart.tsx index fe9dbf5ece..c3d82e640c 100644 --- a/src/components/MetricChart/MetricChart.tsx +++ b/src/components/MetricChart/MetricChart.tsx @@ -5,7 +5,7 @@ import ChartKit, {settings} from '@gravity-ui/chartkit'; import type {IResponseError} from '../../types/api/error'; import type {TimeFrame} from '../../utils/timeframes'; -import {useAutofetcher, useIsElementVisible} from '../../utils/hooks'; +import {useAutofetcher} from '../../utils/hooks'; import {COLORS} from '../../utils/versions'; import {cn} from '../../utils/cn'; @@ -110,6 +110,13 @@ interface DiagnosticsChartProps { chartOptions?: ChartOptions; onChartDataStatusChange?: OnChartDataStatusChange; + + /** + * YAGR charts don't render correctly inside not visible elements\ + * So if chart is used inside component with 'display:none', it will be empty, when visibility change\ + * Pass isChartVisible prop to ensure proper chart render + */ + isChartVisible?: boolean; } export const MetricChart = ({ @@ -121,13 +128,8 @@ export const MetricChart = ({ height = width / 1.5, chartOptions, onChartDataStatusChange, + isChartVisible, }: DiagnosticsChartProps) => { - // YAGR charts use uplot inside - // uplot has the issue, that it doesn't render correctly inside not visible elements - // so if chart is used inside component with display:none, it will be empty, when visibility change - const containerRef = useRef(null); - const isChartVisible = useIsElementVisible(containerRef); - const mounted = useRef(false); useEffect(() => { @@ -224,7 +226,6 @@ export const MetricChart = ({ return (
{ * Charts should be hidden, if they are not enabled: * 1. GraphShard is not enabled * 2. ydb version does not have /viewer/json/render endpoint (400, 404, CORS error, etc.) + * * If at least one chart successfully loaded, dashboard should be shown * @link https://github.com/ydb-platform/ydb-embedded-ui/issues/659 * @todo disable only for specific errors ('GraphShard is not enabled') after ydb-stable-24 is generally used @@ -71,6 +72,7 @@ export const TenantDashboard = ({charts}: TenantDashboardProps) => { width={chartWidth} height={chartHeight} onChartDataStatusChange={handleChartDataStatusChange} + isChartVisible={!isDashboardHidden} /> ); }); diff --git a/src/utils/hooks/index.ts b/src/utils/hooks/index.ts index 6c7679ea2a..42ebaaa93b 100644 --- a/src/utils/hooks/index.ts +++ b/src/utils/hooks/index.ts @@ -4,7 +4,6 @@ export * from './useSetting'; export * from './useQueryModes'; export * from './useTableSort'; export * from './useSearchQuery'; -export * from './useIsElementVisible'; export * from './useNodesRequestParams'; export * from './useStorageRequestParams'; diff --git a/src/utils/hooks/useIsElementVisible.ts b/src/utils/hooks/useIsElementVisible.ts deleted file mode 100644 index 1cba18a40d..0000000000 --- a/src/utils/hooks/useIsElementVisible.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {type RefObject, useEffect, useRef, useState} from 'react'; - -/** Determines, whether element is currently displayd by boundingClientRect */ -export const useIsElementVisible = (ref: RefObject) => { - const observer = useRef(); - const [isVisible, setIsVisible] = useState(false); - - useEffect(() => { - const callback = (entries: ResizeObserverEntry[]) => { - entries.forEach((entry) => { - // Assuming this observer has only one entry - const rect = entry.target.getBoundingClientRect(); - setIsVisible(rect.height > 0 && rect.width > 0); - }); - }; - - observer.current = new ResizeObserver(callback); - - return () => { - observer.current?.disconnect(); - observer.current = undefined; - }; - }, []); - - useEffect(() => { - const el = ref.current; - if (el) { - observer.current?.observe(el); - } - return () => { - if (el) { - observer.current?.unobserve(el); - } - }; - }, [ref]); - - return isVisible; -};