Skip to content

Commit fe0cad4

Browse files
fix(TenantDashboard): hide if charts not enabled (#675)
1 parent 2271535 commit fe0cad4

File tree

21 files changed

+116
-83
lines changed

21 files changed

+116
-83
lines changed

src/components/Loader/Loader.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ const b = cn('ydb-loader');
77

88
interface LoaderProps {
99
size?: LoaderSize;
10+
className?: string;
1011
}
1112

12-
export const Loader = ({size = 'm'}: LoaderProps) => {
13+
export const Loader = ({size = 'm', className}: LoaderProps) => {
1314
return (
14-
<div className={b()}>
15+
<div className={b(null, className)}>
1516
<KitLoader size={size} />
1617
</div>
1718
);

src/components/MetricChart/MetricChart.tsx

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@ import ChartKit, {settings} from '@gravity-ui/chartkit';
66
import type {IResponseError} from '../../types/api/error';
77
import type {TimeFrame} from '../../utils/timeframes';
88
import {useAutofetcher} from '../../utils/hooks';
9+
910
import {COLORS} from '../../utils/versions';
1011
import {cn} from '../../utils/cn';
1112

1213
import {Loader} from '../Loader';
1314
import {ResponseError} from '../Errors/ResponseError';
1415

15-
import type {ChartOptions, MetricDescription, PreparedMetricsData} from './types';
16+
import type {
17+
ChartOptions,
18+
MetricDescription,
19+
OnChartDataStatusChange,
20+
PreparedMetricsData,
21+
} from './types';
1622
import {convertResponse} from './convertReponse';
1723
import {getDefaultDataFormatter} from './getDefaultDataFormatter';
1824
import {getChartData} from './getChartData';
@@ -102,6 +108,15 @@ interface DiagnosticsChartProps {
102108
width?: number;
103109

104110
chartOptions?: ChartOptions;
111+
112+
onChartDataStatusChange?: OnChartDataStatusChange;
113+
114+
/**
115+
* YAGR charts don't render correctly inside not visible elements\
116+
* So if chart is used inside component with 'display:none', it will be empty, when visibility change\
117+
* Pass isChartVisible prop to ensure proper chart render
118+
*/
119+
isChartVisible?: boolean;
105120
}
106121

107122
export const MetricChart = ({
@@ -112,6 +127,8 @@ export const MetricChart = ({
112127
width = 400,
113128
height = width / 1.5,
114129
chartOptions,
130+
onChartDataStatusChange,
131+
isChartVisible,
115132
}: DiagnosticsChartProps) => {
116133
const mounted = useRef(false);
117134

@@ -127,6 +144,20 @@ export const MetricChart = ({
127144
initialChartState,
128145
);
129146

147+
useEffect(() => {
148+
if (error) {
149+
return onChartDataStatusChange?.('error');
150+
}
151+
if (loading && !wasLoaded) {
152+
return onChartDataStatusChange?.('loading');
153+
}
154+
if (!loading && wasLoaded) {
155+
return onChartDataStatusChange?.('success');
156+
}
157+
158+
return undefined;
159+
}, [loading, wasLoaded, error, onChartDataStatusChange]);
160+
130161
const fetchChartData = useCallback(
131162
async (isBackground: boolean) => {
132163
dispatch(setChartDataLoading());
@@ -146,7 +177,9 @@ export const MetricChart = ({
146177
});
147178

148179
// Hack to prevent setting value to state, if component unmounted
149-
if (!mounted.current) return;
180+
if (!mounted.current) {
181+
return;
182+
}
150183

151184
// In some cases error could be in response with 200 status code
152185
// It happens when request is OK, but chart data cannot be returned due to some reason
@@ -155,10 +188,14 @@ export const MetricChart = ({
155188
const preparedData = convertResponse(response, metrics);
156189
dispatch(setChartData(preparedData));
157190
} else {
158-
dispatch(setChartError({statusText: response.error}));
191+
const err = {statusText: response.error};
192+
193+
throw err;
159194
}
160195
} catch (err) {
161-
if (!mounted.current) return;
196+
if (!mounted.current) {
197+
return;
198+
}
162199

163200
dispatch(setChartError(err as IResponseError));
164201
}
@@ -175,6 +212,10 @@ export const MetricChart = ({
175212
return <Loader />;
176213
}
177214

215+
if (!isChartVisible) {
216+
return null;
217+
}
218+
178219
return (
179220
<div className={b('chart')}>
180221
<ChartKit type="yagr" data={convertedData} />
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export type {MetricDescription, Metric, ChartOptions} from './types';
1+
export * from './types';
22
export {MetricChart} from './MetricChart';

src/components/MetricChart/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ export type ChartDataType = 'ms' | 'size';
3030
export interface ChartOptions {
3131
dataType?: ChartDataType;
3232
}
33+
34+
export type ChartDataStatus = 'loading' | 'success' | 'error';
35+
export type OnChartDataStatusChange = (newStatus: ChartDataStatus) => void;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import {TenantDashboard} from '../TenantDashboard/TenantDashboard';
2+
import {defaultDashboardConfig} from './defaultDashboardConfig';
3+
4+
export const DefaultOverviewContent = () => {
5+
return <TenantDashboard charts={defaultDashboardConfig} />;
6+
};

src/containers/Tenant/Diagnostics/TenantOverview/DefaultDashboard.tsx renamed to src/containers/Tenant/Diagnostics/TenantOverview/DefaultOverviewContent/defaultDashboardConfig.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import {type ChartConfig, TenantDashboard} from './TenantDashboard/TenantDashboard';
2-
import i18n from './i18n';
1+
import type {ChartConfig} from '../TenantDashboard/TenantDashboard';
2+
import i18n from '../i18n';
33

4-
const defaultDashboardConfig: ChartConfig[] = [
4+
export const defaultDashboardConfig: ChartConfig[] = [
55
{
66
title: i18n('charts.queries-per-second'),
77
metrics: [
@@ -44,7 +44,3 @@ const defaultDashboardConfig: ChartConfig[] = [
4444
},
4545
},
4646
];
47-
48-
export const DefaultDashboard = () => {
49-
return <TenantDashboard charts={defaultDashboardConfig} />;
50-
};

src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
2-
import {CpuDashboard} from './CpuDashboard';
2+
import {TenantDashboard} from '../TenantDashboard/TenantDashboard';
3+
import {cpuDashboardConfig} from './cpuDashboardConfig';
34
import {TopNodesByLoad} from './TopNodesByLoad';
45
import {TopNodesByCpu} from './TopNodesByCpu';
56
import {TopShards} from './TopShards';
@@ -13,7 +14,7 @@ interface TenantCpuProps {
1314
export function TenantCpu({path, additionalNodesProps}: TenantCpuProps) {
1415
return (
1516
<>
16-
<CpuDashboard />
17+
<TenantDashboard charts={cpuDashboardConfig} />
1718
<TopNodesByLoad path={path} additionalNodesProps={additionalNodesProps} />
1819
<TopNodesByCpu path={path} additionalNodesProps={additionalNodesProps} />
1920
<TopShards path={path} />
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import {type ChartConfig, TenantDashboard} from '../TenantDashboard/TenantDashboard';
1+
import type {ChartConfig} from '../TenantDashboard/TenantDashboard';
22
import i18n from '../i18n';
33

4-
const cpuDashboardConfig: ChartConfig[] = [
4+
export const cpuDashboardConfig: ChartConfig[] = [
55
{
66
title: i18n('charts.cpu-usage'),
77
metrics: [
@@ -12,7 +12,3 @@ const cpuDashboardConfig: ChartConfig[] = [
1212
],
1313
},
1414
];
15-
16-
export const CpuDashboard = () => {
17-
return <TenantDashboard charts={cpuDashboardConfig} />;
18-
};

src/containers/Tenant/Diagnostics/TenantOverview/TenantDashboard/TenantDashboard.tsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
import {useState} from 'react';
12
import {StringParam, useQueryParam} from 'use-query-params';
23

34
import {cn} from '../../../../../utils/cn';
45
import type {TimeFrame} from '../../../../../utils/timeframes';
5-
import {useSetting, useTypedSelector} from '../../../../../utils/hooks';
6-
import {DISPLAY_CHARTS_IN_DB_DIAGNOSTICS_KEY} from '../../../../../utils/constants';
6+
import {useTypedSelector} from '../../../../../utils/hooks';
77
import {TimeFrameSelector} from '../../../../../components/TimeFrameSelector/TimeFrameSelector';
88
import {
99
type ChartOptions,
1010
MetricChart,
1111
type MetricDescription,
12+
type ChartDataStatus,
1213
} from '../../../../../components/MetricChart';
1314

1415
import './TenantDashboard.scss';
@@ -29,39 +30,56 @@ interface TenantDashboardProps {
2930
}
3031

3132
export const TenantDashboard = ({charts}: TenantDashboardProps) => {
33+
const [isDashboardHidden, setIsDashboardHidden] = useState<boolean>(true);
34+
3235
const [timeFrame = '1h', setTimeframe] = useQueryParam('timeframe', StringParam);
3336

3437
const {autorefresh} = useTypedSelector((state) => state.schema);
3538

36-
const [chartsEnabled] = useSetting(DISPLAY_CHARTS_IN_DB_DIAGNOSTICS_KEY);
39+
// Refetch data only if dashboard successfully loaded
40+
const shouldRefresh = autorefresh && !isDashboardHidden;
3741

38-
if (!chartsEnabled) {
39-
return null;
40-
}
42+
/**
43+
* Charts should be hidden, if they are not enabled:
44+
* 1. GraphShard is not enabled
45+
* 2. ydb version does not have /viewer/json/render endpoint (400, 404, CORS error, etc.)
46+
*
47+
* If at least one chart successfully loaded, dashboard should be shown
48+
* @link https://github.com/ydb-platform/ydb-embedded-ui/issues/659
49+
* @todo disable only for specific errors ('GraphShard is not enabled') after ydb-stable-24 is generally used
50+
*/
51+
const handleChartDataStatusChange = (chartStatus: ChartDataStatus) => {
52+
if (chartStatus === 'success') {
53+
setIsDashboardHidden(false);
54+
}
55+
};
4156

4257
// If there is only one chart, display it with full width
4358
const chartWidth = charts.length === 1 ? CHART_WIDTH_FULL : CHART_WIDTH;
4459
const chartHeight = CHART_WIDTH / 1.5;
4560

4661
const renderContent = () => {
4762
return charts.map((chartConfig) => {
63+
const chartId = chartConfig.metrics.map(({target}) => target).join('&');
4864
return (
4965
<MetricChart
50-
key={chartConfig.metrics.map(({target}) => target).join('&')}
66+
key={chartId}
5167
title={chartConfig.title}
5268
metrics={chartConfig.metrics}
5369
timeFrame={timeFrame as TimeFrame}
5470
chartOptions={chartConfig.options}
55-
autorefresh={autorefresh}
71+
autorefresh={shouldRefresh}
5672
width={chartWidth}
5773
height={chartHeight}
74+
onChartDataStatusChange={handleChartDataStatusChange}
75+
isChartVisible={!isDashboardHidden}
5876
/>
5977
);
6078
});
6179
};
6280

6381
return (
64-
<div className={b(null)}>
82+
<div className={b(null)} style={{display: isDashboardHidden ? 'none' : undefined}}>
6583
<div className={b('controls')}>
6684
<TimeFrameSelector value={timeFrame as TimeFrame} onChange={setTimeframe} />
6785
</div>

src/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import {MemoryDashboard} from './MemoryDashboard';
1+
import {TenantDashboard} from '../TenantDashboard/TenantDashboard';
2+
import {memoryDashboardConfig} from './memoryDashboardConfig';
23
import {TopNodesByMemory} from './TopNodesByMemory';
34

45
interface TenantMemoryProps {
@@ -8,7 +9,7 @@ interface TenantMemoryProps {
89
export function TenantMemory({path}: TenantMemoryProps) {
910
return (
1011
<>
11-
<MemoryDashboard />
12+
<TenantDashboard charts={memoryDashboardConfig} />
1213
<TopNodesByMemory path={path} />
1314
</>
1415
);

0 commit comments

Comments
 (0)