Skip to content

Commit 5c05c4a

Browse files
Added: [DI-28776] - Add humanization support for selected units in CloudPulse metrics yAxis graph, legend and tooltip (#13220)
* Added: [DI-28776] - Add humanization support for selected units in CloudPulse metrics yAxis graph, legend and tooltip * Added: [DI-28776] - Changeset
1 parent 930bc9f commit 5c05c4a

File tree

7 files changed

+135
-48
lines changed

7 files changed

+135
-48
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Added
3+
---
4+
5+
Add humanization support for selected units in CloudPulse metrics graphs'1 yAxis, legend and tooltip ([#13220](https://github.com/linode/manager/pull/13220))

packages/manager/src/components/AreaChart/AreaChart.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ export interface AreaChartProps {
134134
*/
135135
timezone: string;
136136

137+
/**
138+
* formatter for the tooltip value
139+
*/
140+
tooltipCustomValueFormatter?: (value: number, unit: string) => string;
141+
137142
/**
138143
* unit to be displayed with data
139144
*/
@@ -189,6 +194,7 @@ export const AreaChart = (props: AreaChartProps) => {
189194
xAxis,
190195
xAxisTickCount,
191196
yAxisProps,
197+
tooltipCustomValueFormatter,
192198
} = props;
193199

194200
const theme = useTheme();
@@ -227,7 +233,9 @@ export const AreaChart = (props: AreaChartProps) => {
227233
{item.dataKey}
228234
</Typography>
229235
<Typography marginLeft={2} sx={{ font: theme.font.bold }}>
230-
{tooltipValueFormatter(item.value, unit)}
236+
{tooltipCustomValueFormatter
237+
? tooltipCustomValueFormatter(item.value, unit)
238+
: tooltipValueFormatter(item.value, unit)}
231239
</Typography>
232240
</Box>
233241
))}

packages/manager/src/featureFlags.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ interface AclpFlag {
9999
*/
100100
enabled: boolean;
101101

102+
/**
103+
* This property indicates for which unit, we need to humanize the values e.g., count, iops etc.,
104+
*/
105+
humanizableUnits?: string[];
106+
102107
/**
103108
* This property indicates whether to show widget dimension filters or not
104109
*/

packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.test.ts

Lines changed: 81 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { formatPercentage } from '@linode/utilities';
22

3+
import * as utilities from 'src/components/AreaChart/utils';
34
import { widgetFactory } from 'src/factories';
45

56
import {
@@ -100,52 +101,90 @@ describe('getLabelName method', () => {
100101
});
101102
});
102103

103-
it('test generateGraphData with metrics data', () => {
104-
const mockMetricsResponse: CloudPulseMetricsResponse = {
105-
data: {
106-
result: [
107-
{
108-
metric: { entity_id: '1' },
109-
values: [[1234567890, '50']],
110-
},
111-
],
112-
result_type: 'matrix',
113-
},
114-
isPartial: false,
115-
stats: {
116-
series_fetched: 1,
117-
},
118-
status: 'success',
119-
};
104+
describe('generateGraphData method', () => {
105+
it('test generateGraphData with metrics data', () => {
106+
const mockMetricsResponse: CloudPulseMetricsResponse = {
107+
data: {
108+
result: [
109+
{
110+
metric: { entity_id: '1' },
111+
values: [[1234567890, '50']],
112+
},
113+
],
114+
result_type: 'matrix',
115+
},
116+
isPartial: false,
117+
stats: {
118+
series_fetched: 1,
119+
},
120+
status: 'success',
121+
};
120122

121-
const result = generateGraphData({
122-
label: 'Graph',
123-
metricsList: mockMetricsResponse,
124-
resources: [{ id: '1', label: 'linode-1' }],
125-
status: 'success',
126-
unit: '%',
127-
serviceType: 'linode',
128-
groupBy: ['entity_id'],
123+
const result = generateGraphData({
124+
label: 'Graph',
125+
metricsList: mockMetricsResponse,
126+
resources: [{ id: '1', label: 'linode-1' }],
127+
status: 'success',
128+
unit: '%',
129+
serviceType: 'linode',
130+
groupBy: ['entity_id'],
131+
humanizableUnits: [],
132+
});
133+
134+
expect(result.areas[0].dataKey).toBe('linode-1');
135+
expect(result.dimensions).toEqual([
136+
{
137+
'linode-1': 50,
138+
timestamp: 1234567890000,
139+
},
140+
]);
141+
142+
expect(result.legendRowsData[0].data).toEqual({
143+
average: 50,
144+
last: 50,
145+
length: 1,
146+
max: 50,
147+
total: 50,
148+
});
149+
expect(result.legendRowsData[0].format).toBeDefined();
150+
expect(result.legendRowsData[0].legendTitle).toBe('linode-1');
151+
expect(result.unit).toBe('%');
129152
});
130153

131-
expect(result.areas[0].dataKey).toBe('linode-1');
132-
expect(result.dimensions).toEqual([
133-
{
134-
'linode-1': 50,
135-
timestamp: 1234567890000,
136-
},
137-
]);
138-
139-
expect(result.legendRowsData[0].data).toEqual({
140-
average: 50,
141-
last: 50,
142-
length: 1,
143-
max: 50,
144-
total: 50,
154+
it('test makes legend rows humanizable when unit is in humanizableUnits', () => {
155+
const spy = vi.spyOn(utilities, 'humanizeLargeData');
156+
const mockMetricsResponse: CloudPulseMetricsResponse = {
157+
data: {
158+
result: [
159+
{
160+
metric: { entity_id: '1' },
161+
values: [[1234567890, '50000']],
162+
},
163+
],
164+
result_type: 'matrix',
165+
},
166+
isPartial: false,
167+
stats: {
168+
series_fetched: 1,
169+
},
170+
status: 'success',
171+
};
172+
173+
const result = generateGraphData({
174+
label: 'Graph',
175+
metricsList: mockMetricsResponse,
176+
resources: [{ id: '1', label: 'linode-1' }],
177+
status: 'success',
178+
unit: 'Count',
179+
serviceType: 'linode',
180+
groupBy: ['entity_id'],
181+
humanizableUnits: ['Count'],
182+
});
183+
184+
expect(result.legendRowsData[0].format).toBeDefined();
185+
result.legendRowsData[0].format(50000);
186+
expect(spy).toHaveBeenCalledWith(50000);
145187
});
146-
expect(result.legendRowsData[0].format).toBeDefined();
147-
expect(result.legendRowsData[0].legendTitle).toBe('linode-1');
148-
expect(result.unit).toBe('%');
149188
});
150189

151190
describe('getDimensionName method', () => {

packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Alias } from '@linode/design-language-system';
22
import { DateTimeRangePicker } from '@linode/ui';
33
import { getMetrics } from '@linode/utilities';
44

5+
import { humanizeLargeData } from 'src/components/AreaChart/utils';
6+
57
import { DIMENSION_TRANSFORM_CONFIG } from '../shared/DimensionTransform';
68
import {
79
convertValueToUnit,
@@ -75,6 +77,10 @@ interface GraphDataOptionsProps {
7577
* array of group by fields
7678
*/
7779
groupBy?: string[];
80+
/**
81+
* The units for which to apply humanization
82+
*/
83+
humanizableUnits?: string[];
7884
/**
7985
* label for the graph title
8086
*/
@@ -215,11 +221,15 @@ export const generateGraphData = (props: GraphDataOptionsProps): GraphData => {
215221
unit,
216222
groupBy,
217223
metricLabel,
224+
humanizableUnits: humanizedUnits,
218225
} = props;
219226
const legendRowsData: MetricsDisplayRow[] = [];
220227
const dimension: { [timestamp: number]: { [label: string]: number } } = {};
221228
const areas: AreaProps[] = [];
222229
const colors = Object.values(Alias.Chart.Categorical);
230+
const isHumanizableUnit = humanizedUnits?.some(
231+
(unitElement) => unitElement.toLowerCase() === unit.toLowerCase()
232+
);
223233

224234
// check whether to hide metric name or not based on the number of unique metric names
225235
const hideMetricName =
@@ -277,7 +287,9 @@ export const generateGraphData = (props: GraphDataOptionsProps): GraphData => {
277287
// construct a legend row with the dimension
278288
const legendRow: MetricsDisplayRow = {
279289
data: getMetrics(data as number[][]),
280-
format: (value: number) => formatToolTip(value, unit),
290+
format: isHumanizableUnit
291+
? (value: number) => `${humanizeLargeData(value)} ${unit}` // we need to humanize count values in legend
292+
: (value: number) => formatToolTip(value, unit),
281293
legendColor: color,
282294
legendTitle: labelName,
283295
};

packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => {
446446
serviceType,
447447
groupBy: [...globalFilterGroupBy, ...(groupBy ?? [])],
448448
metricLabel: availableMetrics?.label,
449+
humanizableUnits: flags.aclp?.humanizableUnits ?? [],
449450
});
450451

451452
data = generatedData.dimensions;

packages/manager/src/features/CloudPulse/Widget/components/CloudPulseLineGraph.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { Box, useMediaQuery, useTheme } from '@mui/material';
44
import * as React from 'react';
55

66
import { AreaChart } from 'src/components/AreaChart/AreaChart';
7+
import { humanizeLargeData } from 'src/components/AreaChart/utils';
8+
import { useFlags } from 'src/hooks/useFlags';
79

810
import type { AreaChartProps } from 'src/components/AreaChart/AreaChart';
911

@@ -13,7 +15,8 @@ export interface CloudPulseLineGraph extends AreaChartProps {
1315
}
1416

1517
export const CloudPulseLineGraph = React.memo((props: CloudPulseLineGraph) => {
16-
const { error, loading, ...rest } = props;
18+
const { error, loading, unit, ...rest } = props;
19+
const flags = useFlags();
1720

1821
const theme = useTheme();
1922

@@ -29,6 +32,10 @@ export const CloudPulseLineGraph = React.memo((props: CloudPulseLineGraph) => {
2932
}
3033

3134
const noDataMessage = 'No data to display';
35+
const isHumanizableUnit =
36+
flags.aclp?.humanizableUnits?.some(
37+
(unitElement) => unitElement.toLowerCase() === unit.toLowerCase()
38+
) ?? false;
3239
return (
3340
<Box
3441
sx={{
@@ -51,12 +58,22 @@ export const CloudPulseLineGraph = React.memo((props: CloudPulseLineGraph) => {
5158
right: 30,
5259
top: 2,
5360
}}
61+
tooltipCustomValueFormatter={
62+
isHumanizableUnit
63+
? (value, unit) => `${humanizeLargeData(value)} ${unit}`
64+
: undefined
65+
}
66+
unit={unit}
5467
xAxisTickCount={
5568
isSmallScreen ? undefined : Math.min(rest.data.length, 7)
5669
}
57-
yAxisProps={{
58-
tickFormat: (value: number) => `${roundTo(value, 3)}`,
59-
}}
70+
yAxisProps={
71+
isHumanizableUnit
72+
? undefined
73+
: {
74+
tickFormat: (value: number) => `${roundTo(value, 3)}`,
75+
}
76+
}
6077
/>
6178
)}
6279
{rest.data.length === 0 && (

0 commit comments

Comments
 (0)