@scality/core-ui 0.163.0 → 0.165.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/barchartv2/Barchart.component.d.ts +10 -2
- package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
- package/dist/components/barchartv2/Barchart.component.js +20 -11
- package/dist/components/barchartv2/utils.d.ts +10 -3
- package/dist/components/barchartv2/utils.d.ts.map +1 -1
- package/dist/components/barchartv2/utils.js +45 -22
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/next.d.ts +2 -0
- package/dist/next.d.ts.map +1 -1
- package/dist/next.js +2 -0
- package/package.json +1 -1
- package/src/lib/components/accordion/Accordion.test.tsx +1 -1
- package/src/lib/components/barchartv2/Barchart.component.test.tsx +41 -18
- package/src/lib/components/barchartv2/Barchart.component.tsx +56 -14
- package/src/lib/components/barchartv2/utils.test.ts +82 -47
- package/src/lib/components/barchartv2/utils.ts +53 -16
- package/src/lib/components/healthselectorv2/HealthSelector.component.test.tsx +7 -7
- package/src/lib/components/infomessage/InfoMessageUtils.test.tsx +0 -1
- package/src/lib/components/inlineinput/InlineInput.test.tsx +10 -7
- package/src/lib/components/inputlist/InputList.test.tsx +1 -2
- package/src/lib/components/linetemporalchart/ChartUtil.test.ts +5 -4
- package/src/lib/components/selectv2/selectv2.test.tsx +8 -4
- package/src/lib/components/tablev2/TableSync.test.tsx +2 -3
- package/src/lib/components/tablev2/TableUtils.test.ts +6 -3
- package/src/lib/components/tablev2/Tablev2.test.tsx +2 -3
- package/src/lib/components/toggle/Toggle.test.tsx +1 -1
- package/src/lib/index.ts +0 -1
- package/src/lib/next.ts +2 -0
- package/stories/BarChart/barchart.stories.tsx +96 -5
- package/tsconfig.json +0 -1
|
@@ -18,13 +18,18 @@ import { ConstrainedText } from '../constrainedtext/Constrainedtext.component';
|
|
|
18
18
|
import { IconHelp } from '../iconhelper/IconHelper';
|
|
19
19
|
import { Loader } from '../loader/Loader.component';
|
|
20
20
|
import { Text } from '../text/Text.component';
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
formatDate,
|
|
23
|
+
renderTooltipContent,
|
|
24
|
+
UnitRange,
|
|
25
|
+
useChartData,
|
|
26
|
+
} from './utils';
|
|
22
27
|
import { useChartLegend } from '../chartlegend/ChartLegendWrapper';
|
|
23
28
|
|
|
24
29
|
const CHART_CONSTANTS = {
|
|
25
30
|
TICK_WIDTH_OFFSET: 5,
|
|
26
31
|
BAR_SIZE: 12,
|
|
27
|
-
MIN_POINT_SIZE:
|
|
32
|
+
MIN_POINT_SIZE: 3,
|
|
28
33
|
DEFAULT_HEIGHT: 200,
|
|
29
34
|
CHART_MARGIN: {
|
|
30
35
|
left: 0,
|
|
@@ -70,17 +75,25 @@ export type BarchartSortFn<T extends BarchartBars> = (
|
|
|
70
75
|
|
|
71
76
|
export type BarchartProps<T extends BarchartBars> = {
|
|
72
77
|
type: 'category' | TimeType;
|
|
73
|
-
bars
|
|
78
|
+
bars?: T;
|
|
74
79
|
tooltip?: BarchartTooltipFn<T>;
|
|
75
80
|
defaultSort?: BarchartSortFn<T>;
|
|
76
81
|
unitRange?: UnitRange;
|
|
77
|
-
helpTooltip?:
|
|
82
|
+
helpTooltip?: React.ReactNode;
|
|
78
83
|
stacked?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Sort the bars by default or by legend order
|
|
86
|
+
* legend will sort the bars by the order of the colorSet property of the ChartLegendWrapper component
|
|
87
|
+
* default will sort the bars by average values in descending order (biggest values will be at bottom)
|
|
88
|
+
* @default 'default'
|
|
89
|
+
*/
|
|
90
|
+
stackedBarSort?: 'default' | 'legend';
|
|
79
91
|
title?: string;
|
|
80
92
|
secondaryTitle?: string;
|
|
81
93
|
rightTitle?: React.ReactNode;
|
|
82
94
|
height?: number;
|
|
83
95
|
isLoading?: boolean;
|
|
96
|
+
isError?: boolean;
|
|
84
97
|
};
|
|
85
98
|
|
|
86
99
|
interface CustomTickProps {
|
|
@@ -91,6 +104,7 @@ interface CustomTickProps {
|
|
|
91
104
|
};
|
|
92
105
|
visibleTicksCount: number;
|
|
93
106
|
width: number;
|
|
107
|
+
type: TimeType;
|
|
94
108
|
}
|
|
95
109
|
|
|
96
110
|
/* ---------------------------------- COMPONENTS ---------------------------------- */
|
|
@@ -101,6 +115,7 @@ const CustomTick = ({
|
|
|
101
115
|
payload,
|
|
102
116
|
visibleTicksCount,
|
|
103
117
|
width,
|
|
118
|
+
type,
|
|
104
119
|
}: CustomTickProps) => {
|
|
105
120
|
const theme = useTheme();
|
|
106
121
|
const tickWidth =
|
|
@@ -118,7 +133,9 @@ const CustomTick = ({
|
|
|
118
133
|
<ConstrainedText
|
|
119
134
|
text={
|
|
120
135
|
<Text variant="Smaller" color="textSecondary">
|
|
121
|
-
{
|
|
136
|
+
{type.type === 'time'
|
|
137
|
+
? formatDate(new Date(payload.value), type.timeRange.interval)
|
|
138
|
+
: String(payload.value)}
|
|
122
139
|
</Text>
|
|
123
140
|
}
|
|
124
141
|
centered
|
|
@@ -149,16 +166,14 @@ const ChartHeader = ({
|
|
|
149
166
|
}: {
|
|
150
167
|
title?: string;
|
|
151
168
|
secondaryTitle?: string;
|
|
152
|
-
helpTooltip?:
|
|
169
|
+
helpTooltip?: React.ReactNode;
|
|
153
170
|
rightTitle?: React.ReactNode;
|
|
154
171
|
}) => {
|
|
155
172
|
return (
|
|
156
173
|
<Wrap>
|
|
157
174
|
<Stack gap="r4">
|
|
158
175
|
<Text variant="ChartTitle">{title}</Text>
|
|
159
|
-
{helpTooltip &&
|
|
160
|
-
<IconHelp tooltipMessage={helpTooltip} title={helpTooltip} />
|
|
161
|
-
)}
|
|
176
|
+
{helpTooltip && <IconHelp tooltipMessage={helpTooltip} />}
|
|
162
177
|
|
|
163
178
|
{secondaryTitle && (
|
|
164
179
|
<Text
|
|
@@ -177,6 +192,21 @@ const ChartHeader = ({
|
|
|
177
192
|
);
|
|
178
193
|
};
|
|
179
194
|
|
|
195
|
+
const Error = ({ height }: { height: number }) => {
|
|
196
|
+
return (
|
|
197
|
+
<Box
|
|
198
|
+
height={height}
|
|
199
|
+
style={{
|
|
200
|
+
alignItems: 'center',
|
|
201
|
+
justifyContent: 'center',
|
|
202
|
+
display: 'flex',
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
205
|
+
<Text>Chart data is not available</Text>
|
|
206
|
+
</Box>
|
|
207
|
+
);
|
|
208
|
+
};
|
|
209
|
+
|
|
180
210
|
const Loading = ({ height }: { height: number }) => {
|
|
181
211
|
return (
|
|
182
212
|
<Box
|
|
@@ -205,6 +235,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
205
235
|
type = 'category',
|
|
206
236
|
unitRange,
|
|
207
237
|
stacked,
|
|
238
|
+
stackedBarSort = 'default',
|
|
208
239
|
defaultSort,
|
|
209
240
|
tooltip,
|
|
210
241
|
title,
|
|
@@ -212,10 +243,11 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
212
243
|
helpTooltip,
|
|
213
244
|
rightTitle,
|
|
214
245
|
isLoading,
|
|
246
|
+
isError,
|
|
215
247
|
} = props;
|
|
216
248
|
|
|
217
249
|
// Create colorSet from ChartLegendWrapper
|
|
218
|
-
const colorSet = bars
|
|
250
|
+
const colorSet = bars?.reduce(
|
|
219
251
|
(acc, bar) => {
|
|
220
252
|
const color = getColor(bar.label);
|
|
221
253
|
if (color) {
|
|
@@ -227,7 +259,15 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
227
259
|
);
|
|
228
260
|
|
|
229
261
|
const { rechartsBars, unitLabel, roundReferenceValue, rechartsData } =
|
|
230
|
-
useChartData(
|
|
262
|
+
useChartData(
|
|
263
|
+
bars || [],
|
|
264
|
+
type,
|
|
265
|
+
colorSet || {},
|
|
266
|
+
stacked,
|
|
267
|
+
defaultSort,
|
|
268
|
+
unitRange,
|
|
269
|
+
stackedBarSort,
|
|
270
|
+
);
|
|
231
271
|
|
|
232
272
|
return (
|
|
233
273
|
<Stack direction="vertical" gap="r8">
|
|
@@ -237,7 +277,9 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
237
277
|
helpTooltip={helpTooltip}
|
|
238
278
|
rightTitle={rightTitle}
|
|
239
279
|
/>
|
|
240
|
-
{isLoading ? (
|
|
280
|
+
{isError || (!bars && !isLoading) ? (
|
|
281
|
+
<Error height={height} />
|
|
282
|
+
) : isLoading ? (
|
|
241
283
|
<Loading height={height} />
|
|
242
284
|
) : (
|
|
243
285
|
<StyledResponsiveContainer width="100%" height={height}>
|
|
@@ -260,7 +302,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
260
302
|
key={dataKey}
|
|
261
303
|
dataKey={dataKey}
|
|
262
304
|
fill={chartColors[fill] || fill}
|
|
263
|
-
minPointSize={CHART_CONSTANTS.MIN_POINT_SIZE}
|
|
305
|
+
minPointSize={stacked ? 0 : CHART_CONSTANTS.MIN_POINT_SIZE}
|
|
264
306
|
stackId={stackId}
|
|
265
307
|
onMouseOver={() => setHoveredValue(dataKey)}
|
|
266
308
|
onMouseLeave={() => setHoveredValue(undefined)}
|
|
@@ -295,7 +337,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
295
337
|
/>
|
|
296
338
|
<XAxis
|
|
297
339
|
dataKey="category"
|
|
298
|
-
tick={(props) => <CustomTick {...props} />}
|
|
340
|
+
tick={(props) => <CustomTick {...props} type={type} />}
|
|
299
341
|
type="category"
|
|
300
342
|
interval={0}
|
|
301
343
|
allowDataOverflow={true}
|
|
@@ -13,6 +13,21 @@ import {
|
|
|
13
13
|
UnitRange,
|
|
14
14
|
} from './utils';
|
|
15
15
|
|
|
16
|
+
// Test date constants to avoid repetition
|
|
17
|
+
const TEST_DATES = {
|
|
18
|
+
JULY_5_2024: new Date('2024-07-05T00:00:00'),
|
|
19
|
+
JULY_6_2024: new Date('2024-07-06T00:00:00'),
|
|
20
|
+
JULY_7_2024: new Date('2024-07-07T00:00:00'),
|
|
21
|
+
JULY_5_10AM: new Date('2024-07-05T10:00:00'),
|
|
22
|
+
JULY_5_11AM: new Date('2024-07-05T11:00:00'),
|
|
23
|
+
JULY_5_8_30AM: new Date('2024-07-05T08:30:00'),
|
|
24
|
+
JULY_5_2_45PM: new Date('2024-07-05T14:45:00'),
|
|
25
|
+
JULY_6_9_15AM: new Date('2024-07-06T09:15:00'),
|
|
26
|
+
JULY_5_8AM: new Date('2024-07-05T08:00:00'),
|
|
27
|
+
JULY_6_2PM: new Date('2024-07-06T14:00:00'),
|
|
28
|
+
JULY_7_10AM: new Date('2024-07-07T10:00:00'),
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
16
31
|
// Mock theme object for tests
|
|
17
32
|
const mockTheme = {
|
|
18
33
|
statusHealthy: '#00D100',
|
|
@@ -46,8 +61,8 @@ describe('transformTimeData', () => {
|
|
|
46
61
|
{
|
|
47
62
|
label: 'Success',
|
|
48
63
|
data: [
|
|
49
|
-
[
|
|
50
|
-
[
|
|
64
|
+
[TEST_DATES.JULY_5_2024, 10],
|
|
65
|
+
[TEST_DATES.JULY_6_2024, 20],
|
|
51
66
|
] as [Date, number][],
|
|
52
67
|
},
|
|
53
68
|
];
|
|
@@ -55,8 +70,8 @@ describe('transformTimeData', () => {
|
|
|
55
70
|
const type = {
|
|
56
71
|
type: 'time' as const,
|
|
57
72
|
timeRange: {
|
|
58
|
-
startDate:
|
|
59
|
-
endDate:
|
|
73
|
+
startDate: TEST_DATES.JULY_5_2024,
|
|
74
|
+
endDate: TEST_DATES.JULY_6_2024,
|
|
60
75
|
interval: 24 * 60 * 60 * 1000, // 1 day
|
|
61
76
|
},
|
|
62
77
|
};
|
|
@@ -66,8 +81,8 @@ describe('transformTimeData', () => {
|
|
|
66
81
|
const result = transformTimeData(bars, type, barDataKeys);
|
|
67
82
|
|
|
68
83
|
expect(result).toEqual([
|
|
69
|
-
{ category:
|
|
70
|
-
{ category:
|
|
84
|
+
{ category: TEST_DATES.JULY_5_2024.valueOf(), Success: 10 },
|
|
85
|
+
{ category: TEST_DATES.JULY_6_2024.valueOf(), Success: 20 },
|
|
71
86
|
]);
|
|
72
87
|
});
|
|
73
88
|
|
|
@@ -76,9 +91,9 @@ describe('transformTimeData', () => {
|
|
|
76
91
|
{
|
|
77
92
|
label: 'Success',
|
|
78
93
|
data: [
|
|
79
|
-
[
|
|
94
|
+
[TEST_DATES.JULY_5_2024, 10],
|
|
80
95
|
// Missing July 6th
|
|
81
|
-
[
|
|
96
|
+
[TEST_DATES.JULY_7_2024, 30],
|
|
82
97
|
] as [Date, number][],
|
|
83
98
|
},
|
|
84
99
|
];
|
|
@@ -86,8 +101,8 @@ describe('transformTimeData', () => {
|
|
|
86
101
|
const type = {
|
|
87
102
|
type: 'time' as const,
|
|
88
103
|
timeRange: {
|
|
89
|
-
startDate:
|
|
90
|
-
endDate:
|
|
104
|
+
startDate: TEST_DATES.JULY_5_2024,
|
|
105
|
+
endDate: TEST_DATES.JULY_7_2024,
|
|
91
106
|
interval: 24 * 60 * 60 * 1000, // 1 day
|
|
92
107
|
},
|
|
93
108
|
};
|
|
@@ -97,9 +112,9 @@ describe('transformTimeData', () => {
|
|
|
97
112
|
const result = transformTimeData(bars, type, barDataKeys);
|
|
98
113
|
|
|
99
114
|
expect(result).toEqual([
|
|
100
|
-
{ category:
|
|
101
|
-
{ category:
|
|
102
|
-
{ category:
|
|
115
|
+
{ category: TEST_DATES.JULY_5_2024.valueOf(), Success: 10 },
|
|
116
|
+
{ category: TEST_DATES.JULY_6_2024.valueOf(), Success: 0 }, // Missing data filled with 0
|
|
117
|
+
{ category: TEST_DATES.JULY_7_2024.valueOf(), Success: 30 },
|
|
103
118
|
]);
|
|
104
119
|
});
|
|
105
120
|
|
|
@@ -108,8 +123,8 @@ describe('transformTimeData', () => {
|
|
|
108
123
|
{
|
|
109
124
|
label: 'Success',
|
|
110
125
|
data: [
|
|
111
|
-
[
|
|
112
|
-
[
|
|
126
|
+
[TEST_DATES.JULY_5_10AM, 10],
|
|
127
|
+
[TEST_DATES.JULY_5_11AM, 20],
|
|
113
128
|
] as [Date, number][],
|
|
114
129
|
},
|
|
115
130
|
];
|
|
@@ -117,8 +132,8 @@ describe('transformTimeData', () => {
|
|
|
117
132
|
const type = {
|
|
118
133
|
type: 'time' as const,
|
|
119
134
|
timeRange: {
|
|
120
|
-
startDate:
|
|
121
|
-
endDate:
|
|
135
|
+
startDate: TEST_DATES.JULY_5_10AM,
|
|
136
|
+
endDate: TEST_DATES.JULY_5_11AM,
|
|
122
137
|
interval: 60 * 60 * 1000, // 1 hour
|
|
123
138
|
},
|
|
124
139
|
};
|
|
@@ -128,8 +143,8 @@ describe('transformTimeData', () => {
|
|
|
128
143
|
const result = transformTimeData(bars, type, barDataKeys);
|
|
129
144
|
|
|
130
145
|
expect(result).toEqual([
|
|
131
|
-
{ category:
|
|
132
|
-
{ category:
|
|
146
|
+
{ category: TEST_DATES.JULY_5_10AM.valueOf(), Success: 10 },
|
|
147
|
+
{ category: TEST_DATES.JULY_5_11AM.valueOf(), Success: 20 },
|
|
133
148
|
]);
|
|
134
149
|
});
|
|
135
150
|
|
|
@@ -138,9 +153,9 @@ describe('transformTimeData', () => {
|
|
|
138
153
|
{
|
|
139
154
|
label: 'Success',
|
|
140
155
|
data: [
|
|
141
|
-
[
|
|
142
|
-
[
|
|
143
|
-
[
|
|
156
|
+
[TEST_DATES.JULY_5_8_30AM, 10], // 8:30 AM
|
|
157
|
+
[TEST_DATES.JULY_5_2_45PM, 25], // 2:45 PM (should overwrite 8:30 AM)
|
|
158
|
+
[TEST_DATES.JULY_6_9_15AM, 15], // Next day
|
|
144
159
|
] as [Date, number][],
|
|
145
160
|
},
|
|
146
161
|
];
|
|
@@ -148,8 +163,8 @@ describe('transformTimeData', () => {
|
|
|
148
163
|
const type = {
|
|
149
164
|
type: 'time' as const,
|
|
150
165
|
timeRange: {
|
|
151
|
-
startDate:
|
|
152
|
-
endDate:
|
|
166
|
+
startDate: TEST_DATES.JULY_5_2024,
|
|
167
|
+
endDate: TEST_DATES.JULY_6_2024,
|
|
153
168
|
interval: 24 * 60 * 60 * 1000, // 1 day
|
|
154
169
|
},
|
|
155
170
|
};
|
|
@@ -159,8 +174,8 @@ describe('transformTimeData', () => {
|
|
|
159
174
|
const result = transformTimeData(bars, type, barDataKeys);
|
|
160
175
|
|
|
161
176
|
expect(result).toEqual([
|
|
162
|
-
{ category:
|
|
163
|
-
{ category:
|
|
177
|
+
{ category: TEST_DATES.JULY_5_2024.valueOf(), Success: 25 }, // Last value for July 5th
|
|
178
|
+
{ category: TEST_DATES.JULY_6_2024.valueOf(), Success: 15 }, // July 6th value
|
|
164
179
|
]);
|
|
165
180
|
});
|
|
166
181
|
|
|
@@ -169,9 +184,9 @@ describe('transformTimeData', () => {
|
|
|
169
184
|
{
|
|
170
185
|
label: 'Success',
|
|
171
186
|
data: [
|
|
172
|
-
[
|
|
173
|
-
[
|
|
174
|
-
[
|
|
187
|
+
[TEST_DATES.JULY_7_10AM, 30], // July 7th (latest)
|
|
188
|
+
[TEST_DATES.JULY_5_8AM, 10], // July 5th (earliest)
|
|
189
|
+
[TEST_DATES.JULY_6_2PM, 20], // July 6th (middle)
|
|
175
190
|
] as [Date, number][],
|
|
176
191
|
},
|
|
177
192
|
];
|
|
@@ -179,8 +194,8 @@ describe('transformTimeData', () => {
|
|
|
179
194
|
const type = {
|
|
180
195
|
type: 'time' as const,
|
|
181
196
|
timeRange: {
|
|
182
|
-
startDate:
|
|
183
|
-
endDate:
|
|
197
|
+
startDate: TEST_DATES.JULY_5_2024,
|
|
198
|
+
endDate: TEST_DATES.JULY_7_2024,
|
|
184
199
|
interval: 24 * 60 * 60 * 1000, // 1 day
|
|
185
200
|
},
|
|
186
201
|
};
|
|
@@ -191,9 +206,9 @@ describe('transformTimeData', () => {
|
|
|
191
206
|
|
|
192
207
|
// Should be in chronological order regardless of input order
|
|
193
208
|
expect(result).toEqual([
|
|
194
|
-
{ category:
|
|
195
|
-
{ category:
|
|
196
|
-
{ category:
|
|
209
|
+
{ category: TEST_DATES.JULY_5_2024.valueOf(), Success: 10 },
|
|
210
|
+
{ category: TEST_DATES.JULY_6_2024.valueOf(), Success: 20 },
|
|
211
|
+
{ category: TEST_DATES.JULY_7_2024.valueOf(), Success: 30 },
|
|
197
212
|
]);
|
|
198
213
|
});
|
|
199
214
|
|
|
@@ -201,19 +216,19 @@ describe('transformTimeData', () => {
|
|
|
201
216
|
const bars = [
|
|
202
217
|
{
|
|
203
218
|
label: 'Success',
|
|
204
|
-
data: [[
|
|
219
|
+
data: [[TEST_DATES.JULY_5_2024, 10]] as [Date, number][],
|
|
205
220
|
},
|
|
206
221
|
{
|
|
207
222
|
label: 'Failed',
|
|
208
|
-
data: [[
|
|
223
|
+
data: [[TEST_DATES.JULY_6_2024, 5]] as [Date, number][],
|
|
209
224
|
},
|
|
210
225
|
];
|
|
211
226
|
|
|
212
227
|
const type = {
|
|
213
228
|
type: 'time' as const,
|
|
214
229
|
timeRange: {
|
|
215
|
-
startDate:
|
|
216
|
-
endDate:
|
|
230
|
+
startDate: TEST_DATES.JULY_5_2024,
|
|
231
|
+
endDate: TEST_DATES.JULY_6_2024,
|
|
217
232
|
interval: 24 * 60 * 60 * 1000, // 1 day
|
|
218
233
|
},
|
|
219
234
|
};
|
|
@@ -223,8 +238,16 @@ describe('transformTimeData', () => {
|
|
|
223
238
|
const result = transformTimeData(bars, type, barDataKeys);
|
|
224
239
|
|
|
225
240
|
expect(result).toEqual([
|
|
226
|
-
{
|
|
227
|
-
|
|
241
|
+
{
|
|
242
|
+
category: TEST_DATES.JULY_5_2024.valueOf(),
|
|
243
|
+
Success: 10,
|
|
244
|
+
Failed: 0,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
category: TEST_DATES.JULY_6_2024.valueOf(),
|
|
248
|
+
Success: 0,
|
|
249
|
+
Failed: 5,
|
|
250
|
+
},
|
|
228
251
|
]);
|
|
229
252
|
});
|
|
230
253
|
});
|
|
@@ -482,8 +505,8 @@ describe('applySortingToData', () => {
|
|
|
482
505
|
|
|
483
506
|
describe('getRoundReferenceValue', () => {
|
|
484
507
|
it('should return appropriate rounded values', () => {
|
|
485
|
-
expect(getRoundReferenceValue(1)).toBe(
|
|
486
|
-
expect(getRoundReferenceValue(2)).toBe(
|
|
508
|
+
expect(getRoundReferenceValue(1)).toBe(5);
|
|
509
|
+
expect(getRoundReferenceValue(2)).toBe(5);
|
|
487
510
|
expect(getRoundReferenceValue(3)).toBe(5);
|
|
488
511
|
expect(getRoundReferenceValue(7)).toBe(10);
|
|
489
512
|
expect(getRoundReferenceValue(15)).toBe(25);
|
|
@@ -570,11 +593,11 @@ describe('formatPrometheusDataToRechartsDataAndBars', () => {
|
|
|
570
593
|
const bars = [
|
|
571
594
|
{
|
|
572
595
|
label: 'Success Count',
|
|
573
|
-
data: [[
|
|
596
|
+
data: [[TEST_DATES.JULY_5_2024, 10]] as [Date, number][],
|
|
574
597
|
},
|
|
575
598
|
{
|
|
576
599
|
label: 'Failed Count',
|
|
577
|
-
data: [[
|
|
600
|
+
data: [[TEST_DATES.JULY_5_2024, 5]] as [Date, number][],
|
|
578
601
|
},
|
|
579
602
|
];
|
|
580
603
|
|
|
@@ -583,8 +606,8 @@ describe('formatPrometheusDataToRechartsDataAndBars', () => {
|
|
|
583
606
|
{
|
|
584
607
|
type: 'time',
|
|
585
608
|
timeRange: {
|
|
586
|
-
startDate:
|
|
587
|
-
endDate:
|
|
609
|
+
startDate: TEST_DATES.JULY_5_2024,
|
|
610
|
+
endDate: TEST_DATES.JULY_5_2024,
|
|
588
611
|
interval: 24 * 60 * 60 * 1000,
|
|
589
612
|
},
|
|
590
613
|
},
|
|
@@ -598,7 +621,11 @@ describe('formatPrometheusDataToRechartsDataAndBars', () => {
|
|
|
598
621
|
|
|
599
622
|
// Should integrate: time transformation + status color assignment
|
|
600
623
|
expect(result.data).toEqual([
|
|
601
|
-
{
|
|
624
|
+
{
|
|
625
|
+
category: TEST_DATES.JULY_5_2024.valueOf(),
|
|
626
|
+
'Success Count': 10,
|
|
627
|
+
'Failed Count': 5,
|
|
628
|
+
},
|
|
602
629
|
]);
|
|
603
630
|
expect(result.rechartsBars).toEqual([
|
|
604
631
|
{ dataKey: 'Success Count', fill: '#4BE4E2' }, // lineColor3
|
|
@@ -717,6 +744,14 @@ describe('sortStackedBars', () => {
|
|
|
717
744
|
{ dataKey: 'bar1', fill: 'blue' },
|
|
718
745
|
]);
|
|
719
746
|
});
|
|
747
|
+
it('should sort bars by legend order when stacked is true and legendOrder is provided', () => {
|
|
748
|
+
const result = sortStackedBars(bars, data, true, ['bar3', 'bar2', 'bar1']);
|
|
749
|
+
expect(result).toEqual([
|
|
750
|
+
{ dataKey: 'bar3', fill: 'green' },
|
|
751
|
+
{ dataKey: 'bar2', fill: 'red' },
|
|
752
|
+
{ dataKey: 'bar1', fill: 'blue' },
|
|
753
|
+
]);
|
|
754
|
+
});
|
|
720
755
|
it('should not sort bars when stacked is false', () => {
|
|
721
756
|
const result = sortStackedBars(bars, data, false);
|
|
722
757
|
expect(result).toEqual([
|
|
@@ -18,10 +18,14 @@ export const getRoundReferenceValue = (value: number): number => {
|
|
|
18
18
|
const normalized = value / magnitude;
|
|
19
19
|
|
|
20
20
|
// Round to nice numbers based on normalized value
|
|
21
|
-
|
|
22
|
-
if (normalized <=
|
|
23
|
-
if (normalized <= 5)
|
|
24
|
-
|
|
21
|
+
let result: number;
|
|
22
|
+
if (normalized <= 1) result = magnitude;
|
|
23
|
+
else if (normalized <= 2.5) result = 2.5 * magnitude;
|
|
24
|
+
else if (normalized <= 5) result = 5 * magnitude;
|
|
25
|
+
else result = 10 * magnitude;
|
|
26
|
+
|
|
27
|
+
// Ensure minimum value of 5 for better chart appearance
|
|
28
|
+
return Math.max(result, 5);
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
export const getMaxBarValue = (
|
|
@@ -46,9 +50,9 @@ export const getMaxBarValue = (
|
|
|
46
50
|
.filter((key) => key !== 'category')
|
|
47
51
|
.map((key) => Number(item[key]));
|
|
48
52
|
// Get the max value among the values in the object (corresponding to one bar)
|
|
49
|
-
return Math.max(...numberValues);
|
|
53
|
+
return Math.max(...numberValues, 0); // Ensure we don't get -Infinity
|
|
50
54
|
});
|
|
51
|
-
return Math.max(...values);
|
|
55
|
+
return Math.max(...values, 0);
|
|
52
56
|
};
|
|
53
57
|
|
|
54
58
|
/**
|
|
@@ -64,9 +68,6 @@ const generateTimeRanges = (
|
|
|
64
68
|
interval: number,
|
|
65
69
|
): { start: Date; end: Date }[] => {
|
|
66
70
|
const ranges: { start: Date; end: Date }[] = [];
|
|
67
|
-
if (!startDate || !endDate || !interval) {
|
|
68
|
-
return ranges;
|
|
69
|
-
}
|
|
70
71
|
|
|
71
72
|
let currentDate = new Date(startDate.getTime());
|
|
72
73
|
while (currentDate.getTime() <= endDate.getTime()) {
|
|
@@ -89,7 +90,7 @@ const generateTimeRanges = (
|
|
|
89
90
|
* @param interval - Interval in milliseconds
|
|
90
91
|
* @returns Formatted string
|
|
91
92
|
*/
|
|
92
|
-
const formatDate = (date: Date, interval: number): string => {
|
|
93
|
+
export const formatDate = (date: Date, interval: number): string => {
|
|
93
94
|
if (interval > 24 * 60 * 60 * 1000) {
|
|
94
95
|
return (
|
|
95
96
|
DAY_MONTH_FORMATER.format(date).replace(/[ ,]/g, '') +
|
|
@@ -155,7 +156,8 @@ export const transformTimeData = <T extends BarchartBars>(
|
|
|
155
156
|
|
|
156
157
|
// Initialize all ranges with zeros
|
|
157
158
|
timeRanges.forEach((range) => {
|
|
158
|
-
const categoryDisplay = formatDate(range.start, type.timeRange.interval);
|
|
159
|
+
// const categoryDisplay = formatDate(range.start, type.timeRange.interval);
|
|
160
|
+
const categoryDisplay = range.start.getTime();
|
|
159
161
|
const initialData: { [key: string]: string | number } = {
|
|
160
162
|
category: categoryDisplay,
|
|
161
163
|
};
|
|
@@ -288,6 +290,7 @@ export const formatPrometheusDataToRechartsDataAndBars = <
|
|
|
288
290
|
colorSet: Record<string, ChartColors | string>,
|
|
289
291
|
stacked?: boolean,
|
|
290
292
|
defaultSort?: BarchartProps<T>['defaultSort'],
|
|
293
|
+
legendOrder?: string[],
|
|
291
294
|
): {
|
|
292
295
|
data: { [key: string]: string | number }[];
|
|
293
296
|
rechartsBars: { dataKey: string; fill: string; stackId?: string }[];
|
|
@@ -307,7 +310,12 @@ export const formatPrometheusDataToRechartsDataAndBars = <
|
|
|
307
310
|
data = applySortingToData(data, barDataKeys, defaultSort);
|
|
308
311
|
}
|
|
309
312
|
|
|
310
|
-
const sortedRechartsBars = sortStackedBars(
|
|
313
|
+
const sortedRechartsBars = sortStackedBars(
|
|
314
|
+
rechartsBars,
|
|
315
|
+
data,
|
|
316
|
+
stacked,
|
|
317
|
+
legendOrder,
|
|
318
|
+
);
|
|
311
319
|
|
|
312
320
|
return {
|
|
313
321
|
rechartsBars: sortedRechartsBars,
|
|
@@ -330,7 +338,7 @@ export const computeUnitLabelAndRoundReferenceValue = (
|
|
|
330
338
|
return { unitLabel: '', roundReferenceValue, rechartsData: data };
|
|
331
339
|
}
|
|
332
340
|
|
|
333
|
-
const { valueBase, unitLabel } = getUnitLabel(unitRange
|
|
341
|
+
const { valueBase, unitLabel } = getUnitLabel(unitRange, maxValue);
|
|
334
342
|
const topValue = Math.ceil(maxValue / valueBase / 10) * 10;
|
|
335
343
|
const roundReferenceValue = getRoundReferenceValue(topValue);
|
|
336
344
|
const rechartsData = data.map((dataPoint) => {
|
|
@@ -397,8 +405,8 @@ export function getUnitLabel(
|
|
|
397
405
|
};
|
|
398
406
|
}
|
|
399
407
|
|
|
400
|
-
// Sort stacked bars by their average values in descending order
|
|
401
|
-
// This ensures the largest bars appear at the bottom of the stack
|
|
408
|
+
// Sort stacked bars by their average values in descending order or by legend order
|
|
409
|
+
// This ensures the largest bars appear at the bottom of the stack (default) or follow legend order
|
|
402
410
|
export const sortStackedBars = (
|
|
403
411
|
rechartsBars: {
|
|
404
412
|
dataKey: string;
|
|
@@ -409,10 +417,33 @@ export const sortStackedBars = (
|
|
|
409
417
|
[key: string]: string | number;
|
|
410
418
|
}[],
|
|
411
419
|
stacked?: boolean,
|
|
420
|
+
legendOrder?: string[],
|
|
412
421
|
) => {
|
|
413
422
|
if (!stacked) {
|
|
414
423
|
return rechartsBars;
|
|
415
424
|
}
|
|
425
|
+
|
|
426
|
+
// If legend order is provided, sort by legend order
|
|
427
|
+
if (legendOrder && legendOrder.length > 0) {
|
|
428
|
+
return [...rechartsBars].sort((a, b) => {
|
|
429
|
+
const indexA = legendOrder.indexOf(a.dataKey);
|
|
430
|
+
const indexB = legendOrder.indexOf(b.dataKey);
|
|
431
|
+
|
|
432
|
+
// If both items are in legend order, sort by their position
|
|
433
|
+
if (indexA !== -1 && indexB !== -1) {
|
|
434
|
+
return indexA - indexB;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// If only one item is in legend order, prioritize it
|
|
438
|
+
if (indexA !== -1) return -1;
|
|
439
|
+
if (indexB !== -1) return 1;
|
|
440
|
+
|
|
441
|
+
// If neither is in legend order, maintain original order
|
|
442
|
+
return 0;
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Default behavior: sort by average values
|
|
416
447
|
const barAverages = rechartsBars.map((bar) => {
|
|
417
448
|
const values = data
|
|
418
449
|
.map((item) => Number(item[bar.dataKey]) || 0)
|
|
@@ -502,14 +533,20 @@ export const useChartData = <T extends BarchartBars>(
|
|
|
502
533
|
stacked?: boolean,
|
|
503
534
|
defaultSort?: BarchartProps<T>['defaultSort'],
|
|
504
535
|
unitRange?: UnitRange,
|
|
536
|
+
stackedBarSort?: 'default' | 'legend',
|
|
505
537
|
) => {
|
|
506
|
-
const { selectedResources } = useChartLegend();
|
|
538
|
+
const { selectedResources, listResources } = useChartLegend();
|
|
539
|
+
|
|
540
|
+
// Get legend order when stackedBarSort is 'legend'
|
|
541
|
+
const legendOrder = stackedBarSort === 'legend' ? listResources() : undefined;
|
|
542
|
+
|
|
507
543
|
const { data, rechartsBars } = formatPrometheusDataToRechartsDataAndBars(
|
|
508
544
|
bars,
|
|
509
545
|
type,
|
|
510
546
|
colorSet,
|
|
511
547
|
stacked,
|
|
512
548
|
defaultSort,
|
|
549
|
+
legendOrder,
|
|
513
550
|
);
|
|
514
551
|
|
|
515
552
|
// Filter both data and bars to only include selected resources for accurate maxValue calculation
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
HealthSelector,
|
|
3
3
|
optionsDefaultConfiguration,
|
|
4
4
|
} from './HealthSelector.component';
|
|
5
|
-
import
|
|
5
|
+
import { act } from 'react';
|
|
6
6
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
7
7
|
import userEvent from '@testing-library/user-event';
|
|
8
8
|
import { getWrapper } from '../../testUtils';
|
|
@@ -18,11 +18,11 @@ describe('HealthSelector', () => {
|
|
|
18
18
|
const input = screen.getByRole('textbox');
|
|
19
19
|
|
|
20
20
|
// open the menu
|
|
21
|
-
userEvent.click(input);
|
|
21
|
+
await act(() => userEvent.click(input));
|
|
22
22
|
const healthyOption = getByText(/healthy/i);
|
|
23
23
|
expect(healthyOption).toBeInTheDocument();
|
|
24
24
|
});
|
|
25
|
-
it('should call the onChange function when it change', () => {
|
|
25
|
+
it('should call the onChange function when it change', async () => {
|
|
26
26
|
const { Wrapper } = getWrapper();
|
|
27
27
|
const onChange = jest.fn();
|
|
28
28
|
const { getByText } = render(
|
|
@@ -31,12 +31,12 @@ describe('HealthSelector', () => {
|
|
|
31
31
|
</Wrapper>,
|
|
32
32
|
);
|
|
33
33
|
const input = screen.getByRole('textbox');
|
|
34
|
-
userEvent.click(input);
|
|
34
|
+
await act(() => userEvent.click(input));
|
|
35
35
|
const warningOption = getByText(/warning/i);
|
|
36
|
-
userEvent.click(warningOption);
|
|
36
|
+
await act(() => userEvent.click(warningOption));
|
|
37
37
|
expect(onChange).toHaveBeenCalledWith('warning');
|
|
38
38
|
});
|
|
39
|
-
it('should not display hidden options', () => {
|
|
39
|
+
it('should not display hidden options', async () => {
|
|
40
40
|
const { Wrapper } = getWrapper();
|
|
41
41
|
const { queryByText } = render(
|
|
42
42
|
<Wrapper>
|
|
@@ -55,7 +55,7 @@ describe('HealthSelector', () => {
|
|
|
55
55
|
|
|
56
56
|
// open the menu
|
|
57
57
|
const input = screen.getByRole('textbox');
|
|
58
|
-
userEvent.click(input);
|
|
58
|
+
await act(() => userEvent.click(input));
|
|
59
59
|
const healthyOption = queryByText(/healthy/i);
|
|
60
60
|
expect(healthyOption).not.toBeInTheDocument();
|
|
61
61
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import '@testing-library/jest-dom';
|
|
2
2
|
import { render } from '@testing-library/react';
|
|
3
|
-
import React from 'react';
|
|
4
3
|
import { coreUIAvailableThemes } from '../../style/theme';
|
|
5
4
|
import { CoreUiThemeProvider } from '../coreuithemeprovider/CoreUiThemeProvider';
|
|
6
5
|
import { useComputeBackgroundColor } from './InfoMessageUtils';
|