@scality/core-ui 0.166.0 → 0.168.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/.storybook/preview.js +6 -4
- package/README.md +27 -80
- package/dist/components/barchart/BarChart.component.d.ts +5 -0
- package/dist/components/barchart/BarChart.component.d.ts.map +1 -1
- package/dist/components/barchart/BarChart.component.js +5 -0
- package/dist/components/barchartv2/Barchart.component.d.ts +5 -1
- package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
- package/dist/components/barchartv2/Barchart.component.js +6 -2
- package/dist/components/barchartv2/utils.js +2 -2
- package/dist/components/button/Button.component.d.ts +2 -1
- package/dist/components/button/Button.component.d.ts.map +1 -1
- package/dist/components/button/Button.component.js +2 -1
- package/dist/components/chartlegend/ChartLegend.d.ts.map +1 -1
- package/dist/components/chartlegend/ChartLegend.js +7 -13
- package/dist/components/chartlegend/ChartLegendWrapper.d.ts +1 -2
- package/dist/components/chartlegend/ChartLegendWrapper.d.ts.map +1 -1
- package/dist/components/chartlegend/ChartLegendWrapper.js +4 -9
- package/dist/components/date/FormattedDateTime.d.ts +6 -1
- package/dist/components/date/FormattedDateTime.d.ts.map +1 -1
- package/dist/components/date/FormattedDateTime.js +11 -0
- package/dist/components/icon/Icon.component.d.ts +9 -11
- package/dist/components/icon/Icon.component.d.ts.map +1 -1
- package/dist/components/icon/Icon.component.js +2 -0
- package/dist/components/linetemporalchart/ChartUtil.d.ts +3 -2
- package/dist/components/linetemporalchart/ChartUtil.d.ts.map +1 -1
- package/dist/components/linetemporalchart/ChartUtil.js +30 -20
- package/dist/components/linetemporalchart/LineTemporalChart.component.d.ts +4 -0
- package/dist/components/linetemporalchart/LineTemporalChart.component.d.ts.map +1 -1
- package/dist/components/linetemporalchart/LineTemporalChart.component.js +4 -0
- package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +8 -2
- package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -1
- package/dist/components/linetimeseriechart/linetimeseriechart.component.js +17 -11
- package/dist/index.d.ts +1 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -11
- package/dist/next.d.ts +1 -0
- package/dist/next.d.ts.map +1 -1
- package/jest.config.js +1 -0
- package/package.json +16 -37
- package/src/lib/components/barchart/BarChart.component.tsx +5 -0
- package/src/lib/components/barchartv2/Barchart.component.test.tsx +11 -7
- package/src/lib/components/barchartv2/Barchart.component.tsx +15 -3
- package/src/lib/components/barchartv2/utils.test.ts +2 -2
- package/src/lib/components/barchartv2/utils.ts +2 -2
- package/src/lib/components/button/Button.component.tsx +2 -1
- package/src/lib/components/chartlegend/ChartLegend.test.tsx +17 -0
- package/src/lib/components/chartlegend/ChartLegend.tsx +6 -12
- package/src/lib/components/chartlegend/ChartLegendWrapper.tsx +5 -13
- package/src/lib/components/date/FormattedDateTime.tsx +14 -1
- package/src/lib/components/icon/Icon.component.tsx +12 -1
- package/src/lib/components/linetemporalchart/ChartUtil.test.ts +23 -35
- package/src/lib/components/linetemporalchart/ChartUtil.ts +38 -26
- package/src/lib/components/linetemporalchart/LineTemporalChart.component.tsx +19 -15
- package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +36 -13
- package/src/lib/components/linetimeseriechart/linetimeseriechart.test.tsx +68 -0
- package/src/lib/components/selectv2/selectv2.test.tsx +1 -1
- package/src/lib/components/toast/useMutationsHandler.test.tsx +22 -32
- package/src/lib/index.ts +6 -11
- package/src/lib/next.ts +1 -0
- package/stories/BarChart/barchart.stories.tsx +108 -13
- package/stories/barchart.stories.tsx +1 -1
- package/stories/card.stories.tsx +7 -5
- package/stories/controls.ts +19 -20
- package/stories/linecharttemporal.stories.tsx +1 -1
- package/stories/linetimeseriechart.stories.tsx +146 -0
- package/stories/navbar.stories.tsx +103 -0
- package/stories/tabsv2.stories.tsx +51 -56
- package/dist/components/areachart/AreaChart.component.d.ts +0 -13
- package/dist/components/areachart/AreaChart.component.d.ts.map +0 -1
- package/dist/components/areachart/AreaChart.component.js +0 -27
- package/dist/components/chips/Chips.component.d.ts +0 -21
- package/dist/components/chips/Chips.component.d.ts.map +0 -1
- package/dist/components/chips/Chips.component.js +0 -105
- package/dist/components/cloudprogressbar/CloudProgressBar.component.d.ts +0 -10
- package/dist/components/cloudprogressbar/CloudProgressBar.component.d.ts.map +0 -1
- package/dist/components/cloudprogressbar/CloudProgressBar.component.js +0 -38
- package/dist/components/collapsiblepanel/CollapsiblePanel.component.d.ts +0 -9
- package/dist/components/collapsiblepanel/CollapsiblePanel.component.d.ts.map +0 -1
- package/dist/components/collapsiblepanel/CollapsiblePanel.component.js +0 -44
- package/dist/components/linechart/LineChart.component.d.ts +0 -21
- package/dist/components/linechart/LineChart.component.d.ts.map +0 -1
- package/dist/components/linechart/LineChart.component.js +0 -109
- package/dist/components/multiselect/MultiSelect.component.d.ts +0 -28
- package/dist/components/multiselect/MultiSelect.component.d.ts.map +0 -1
- package/dist/components/multiselect/MultiSelect.component.js +0 -73
- package/dist/components/select/Select.component.d.ts +0 -14
- package/dist/components/select/Select.component.d.ts.map +0 -1
- package/dist/components/select/Select.component.js +0 -71
- package/dist/components/spacedbox/SpacedBox.d.ts +0 -34
- package/dist/components/spacedbox/SpacedBox.d.ts.map +0 -1
- package/dist/components/spacedbox/SpacedBox.js +0 -64
- package/dist/components/sparkline/SparkLine.component.d.ts +0 -18
- package/dist/components/sparkline/SparkLine.component.d.ts.map +0 -1
- package/dist/components/sparkline/SparkLine.component.js +0 -148
- package/dist/components/vegachart/VegaChart.component.d.ts +0 -13
- package/dist/components/vegachart/VegaChart.component.d.ts.map +0 -1
- package/dist/components/vegachart/VegaChart.component.js +0 -120
- package/plopfile.js +0 -38
- package/src/lib/components/areachart/AreaChart.component.tsx +0 -49
- package/src/lib/components/chips/Chips.component.tsx +0 -169
- package/src/lib/components/cloudprogressbar/CloudProgressBar.component.tsx +0 -105
- package/src/lib/components/collapsiblepanel/CollapsiblePanel.component.tsx +0 -77
- package/src/lib/components/linechart/LineChart.component.tsx +0 -152
- package/src/lib/components/multiselect/MultiSelect.component.tsx +0 -158
- package/src/lib/components/select/Select.component.tsx +0 -98
- package/src/lib/components/spacedbox/SpacedBox.ts +0 -116
- package/src/lib/components/sparkline/SparkLine.component.tsx +0 -176
- package/src/lib/components/vegachart/VegaChart.component.tsx +0 -146
- package/stories/areachart.stories.tsx +0 -120
- package/stories/chips.stories.tsx +0 -107
- package/stories/cloudprogressbar.stories.tsx +0 -93
- package/stories/collapsiblepanel.stories.tsx +0 -57
- package/stories/data/areachart.ts +0 -122
- package/stories/data/sparklinechart.ts +0 -164
- package/stories/linechart.stories.tsx +0 -319
- package/stories/multiselect.stories.tsx +0 -126
- package/stories/select.stories.tsx +0 -52
- package/stories/sparkline.stories.tsx +0 -85
- package/stories/vegachart.stories.tsx +0 -98
|
@@ -242,8 +242,9 @@ const Anchor = ButtonStyled.withComponent('a');
|
|
|
242
242
|
|
|
243
243
|
/**
|
|
244
244
|
* @deprecated
|
|
245
|
+
* Used only in Dropdown.component.tsx, can be removed after refactoring
|
|
245
246
|
* You should use ButtonV2 with
|
|
246
|
-
* import { Button } from '@scality/core-ui/dist/next';
|
|
247
|
+
* @example import { Button } from '@scality/core-ui/dist/next';
|
|
247
248
|
*/
|
|
248
249
|
function Button({
|
|
249
250
|
text = '',
|
|
@@ -214,5 +214,22 @@ describe('ChartLegend', () => {
|
|
|
214
214
|
expect(screen.getByLabelText('Memory not selected')).toBeInTheDocument();
|
|
215
215
|
expect(screen.getByLabelText('Disk selected')).toBeInTheDocument();
|
|
216
216
|
});
|
|
217
|
+
it('should select one item when clicking on selected item and other items are selected', () => {
|
|
218
|
+
render(
|
|
219
|
+
<ChartLegendWrapper colorSet={colorSet}>
|
|
220
|
+
<ChartLegend shape="line" />
|
|
221
|
+
</ChartLegendWrapper>,
|
|
222
|
+
);
|
|
223
|
+
userEvent.click(screen.getByText('CPU'));
|
|
224
|
+
userEvent.click(screen.getByText('Memory'), { metaKey: true });
|
|
225
|
+
|
|
226
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
227
|
+
expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
|
|
228
|
+
expect(screen.getByLabelText('Disk not selected')).toBeInTheDocument();
|
|
229
|
+
userEvent.click(screen.getByText('CPU'));
|
|
230
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
231
|
+
expect(screen.getByLabelText('Memory not selected')).toBeInTheDocument();
|
|
232
|
+
expect(screen.getByLabelText('Disk not selected')).toBeInTheDocument();
|
|
233
|
+
});
|
|
217
234
|
});
|
|
218
235
|
});
|
|
@@ -73,9 +73,8 @@ export const ChartLegend = ({
|
|
|
73
73
|
addSelectedResource,
|
|
74
74
|
removeSelectedResource,
|
|
75
75
|
selectAllResources,
|
|
76
|
-
getAllResourcesCount,
|
|
77
|
-
getSelectedCount,
|
|
78
76
|
selectOnlyResource,
|
|
77
|
+
isOnlyOneSelected,
|
|
79
78
|
} = useChartLegend();
|
|
80
79
|
|
|
81
80
|
const resources = listResources();
|
|
@@ -89,7 +88,7 @@ export const ChartLegend = ({
|
|
|
89
88
|
|
|
90
89
|
if (isModifierClick) {
|
|
91
90
|
if (itemIsSelected) {
|
|
92
|
-
if (
|
|
91
|
+
if (isOnlyOneSelected()) {
|
|
93
92
|
selectAllResources();
|
|
94
93
|
} else {
|
|
95
94
|
removeSelectedResource(resource);
|
|
@@ -97,14 +96,10 @@ export const ChartLegend = ({
|
|
|
97
96
|
} else {
|
|
98
97
|
addSelectedResource(resource);
|
|
99
98
|
}
|
|
99
|
+
} else if (itemIsSelected && isOnlyOneSelected()) {
|
|
100
|
+
selectAllResources();
|
|
100
101
|
} else {
|
|
101
|
-
|
|
102
|
-
selectOnlyResource(resource);
|
|
103
|
-
} else if (itemIsSelected) {
|
|
104
|
-
selectAllResources();
|
|
105
|
-
} else {
|
|
106
|
-
selectOnlyResource(resource);
|
|
107
|
-
}
|
|
102
|
+
selectOnlyResource(resource);
|
|
108
103
|
}
|
|
109
104
|
},
|
|
110
105
|
[
|
|
@@ -114,8 +109,7 @@ export const ChartLegend = ({
|
|
|
114
109
|
removeSelectedResource,
|
|
115
110
|
selectAllResources,
|
|
116
111
|
selectOnlyResource,
|
|
117
|
-
|
|
118
|
-
getSelectedCount,
|
|
112
|
+
isOnlyOneSelected,
|
|
119
113
|
],
|
|
120
114
|
);
|
|
121
115
|
|
|
@@ -17,8 +17,7 @@ export type ChartLegendState = {
|
|
|
17
17
|
isSelected: (resource: string) => boolean;
|
|
18
18
|
getColor: (resource: string) => string | undefined;
|
|
19
19
|
listResources: () => string[];
|
|
20
|
-
|
|
21
|
-
getSelectedCount: () => number;
|
|
20
|
+
isOnlyOneSelected: () => boolean;
|
|
22
21
|
};
|
|
23
22
|
|
|
24
23
|
const ChartLegendContext = createContext<ChartLegendState | null>(null);
|
|
@@ -54,14 +53,9 @@ export const ChartLegendWrapper = ({
|
|
|
54
53
|
setSelectedResources([resource]);
|
|
55
54
|
}, []);
|
|
56
55
|
|
|
57
|
-
const
|
|
58
|
-
return
|
|
59
|
-
}, [allResources]);
|
|
60
|
-
|
|
61
|
-
const getSelectedCount = useCallback(() => {
|
|
62
|
-
return selectedResources.length;
|
|
56
|
+
const isOnlyOneSelected = useCallback(() => {
|
|
57
|
+
return selectedResources.length === 1;
|
|
63
58
|
}, [selectedResources]);
|
|
64
|
-
|
|
65
59
|
const isSelected = useCallback(
|
|
66
60
|
(resource: string) => {
|
|
67
61
|
return selectedResources.includes(resource);
|
|
@@ -97,8 +91,7 @@ export const ChartLegendWrapper = ({
|
|
|
97
91
|
isSelected,
|
|
98
92
|
getColor,
|
|
99
93
|
listResources,
|
|
100
|
-
|
|
101
|
-
getSelectedCount,
|
|
94
|
+
isOnlyOneSelected,
|
|
102
95
|
}),
|
|
103
96
|
[
|
|
104
97
|
selectedResources,
|
|
@@ -109,8 +102,7 @@ export const ChartLegendWrapper = ({
|
|
|
109
102
|
isSelected,
|
|
110
103
|
getColor,
|
|
111
104
|
listResources,
|
|
112
|
-
|
|
113
|
-
getSelectedCount,
|
|
105
|
+
isOnlyOneSelected,
|
|
114
106
|
],
|
|
115
107
|
);
|
|
116
108
|
|
|
@@ -54,6 +54,16 @@ export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE = Intl.DateTimeFormat('en-GB', {
|
|
|
54
54
|
hour12: false,
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* @description Year month day formatter, without time. Used for describing long term date.
|
|
59
|
+
* @example 2025-01-01
|
|
60
|
+
*/
|
|
61
|
+
export const YEAR_MONTH_DAY_FORMATTER = Intl.DateTimeFormat('en-CA', {
|
|
62
|
+
year: 'numeric',
|
|
63
|
+
month: '2-digit',
|
|
64
|
+
day: '2-digit',
|
|
65
|
+
});
|
|
66
|
+
|
|
57
67
|
type FormattedDateTimeProps = {
|
|
58
68
|
format:
|
|
59
69
|
| 'date'
|
|
@@ -65,7 +75,8 @@ type FormattedDateTimeProps = {
|
|
|
65
75
|
| 'day-month-abbreviated-hour-minute'
|
|
66
76
|
| 'day-month-abbreviated-hour-minute-second'
|
|
67
77
|
| 'long-date'
|
|
68
|
-
| 'chart-date'
|
|
78
|
+
| 'chart-date'
|
|
79
|
+
| 'year-month-day';
|
|
69
80
|
|
|
70
81
|
value: Date;
|
|
71
82
|
};
|
|
@@ -198,6 +209,8 @@ export const FormattedDateTime = ({
|
|
|
198
209
|
return <>{LONG_DATE_FORMATER.format(value)}</>;
|
|
199
210
|
case 'chart-date':
|
|
200
211
|
return <>{DAY_MONTH_FORMATER.format(value).replace(/[ ,]/g, '')}</>;
|
|
212
|
+
case 'year-month-day':
|
|
213
|
+
return <>{YEAR_MONTH_DAY_FORMATTER.format(value)}</>;
|
|
201
214
|
default:
|
|
202
215
|
return <></>;
|
|
203
216
|
}
|
|
@@ -142,7 +142,15 @@ export const iconTable = {
|
|
|
142
142
|
Mail: 'fas faEnvelope',
|
|
143
143
|
};
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
type IconProps = {
|
|
146
|
+
'aria-label'?: string;
|
|
147
|
+
color?: string;
|
|
148
|
+
size?: string;
|
|
149
|
+
icon?: string;
|
|
150
|
+
title?: string;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const customIcons: Record<string, ((props: IconProps) => JSX.Element) & { displayName?: string }> = {
|
|
146
154
|
'Remote-user': ({ 'aria-label': ariaLabel, color, size }) => (
|
|
147
155
|
<RemoteUser ariaLabel={ariaLabel} color={color} size={size} />
|
|
148
156
|
),
|
|
@@ -151,6 +159,9 @@ export const customIcons = {
|
|
|
151
159
|
),
|
|
152
160
|
};
|
|
153
161
|
|
|
162
|
+
customIcons['Remote-user'].displayName = 'RemoteUser';
|
|
163
|
+
customIcons['Remote-group'].displayName = 'RemoteGroup';
|
|
164
|
+
|
|
154
165
|
const IconStyled = styled(FontAwesomeIcon)`
|
|
155
166
|
${(props) => {
|
|
156
167
|
const theme = props.theme;
|
|
@@ -165,33 +165,33 @@ it('returns the unit label GiB/Sec', () => {
|
|
|
165
165
|
expect(valueBase).toEqual(1024 * 1024 * 1024);
|
|
166
166
|
});
|
|
167
167
|
// test for addMissingDataPoint function
|
|
168
|
-
const originalValue = [
|
|
169
|
-
[0, 0],
|
|
170
|
-
[1, 1],
|
|
171
|
-
[2, 2],
|
|
172
|
-
[3, 3],
|
|
173
|
-
[4, 4],
|
|
174
|
-
[5, 5],
|
|
175
|
-
[6, 6],
|
|
176
|
-
[8, 8],
|
|
177
|
-
[9, 9],
|
|
178
|
-
[10, 10],
|
|
168
|
+
const originalValue: [number, number | string | null][] = [
|
|
169
|
+
[0, '0'],
|
|
170
|
+
[1, '1'],
|
|
171
|
+
[2, '2'],
|
|
172
|
+
[3, '3'],
|
|
173
|
+
[4, '4'],
|
|
174
|
+
[5, '5'],
|
|
175
|
+
[6, '6'],
|
|
176
|
+
[8, '8'],
|
|
177
|
+
[9, '9'],
|
|
178
|
+
[10, '10'],
|
|
179
179
|
];
|
|
180
180
|
const startingTimeStamp = 0;
|
|
181
181
|
const sampleDuration = 10;
|
|
182
182
|
const sampleFrequency = 1;
|
|
183
183
|
const newValues = [
|
|
184
|
-
[0, 0],
|
|
185
|
-
[1, 1],
|
|
186
|
-
[2, 2],
|
|
187
|
-
[3, 3],
|
|
188
|
-
[4, 4],
|
|
189
|
-
[5, 5],
|
|
190
|
-
[6, 6],
|
|
184
|
+
[0, '0'],
|
|
185
|
+
[1, '1'],
|
|
186
|
+
[2, '2'],
|
|
187
|
+
[3, '3'],
|
|
188
|
+
[4, '4'],
|
|
189
|
+
[5, '5'],
|
|
190
|
+
[6, '6'],
|
|
191
191
|
[7, 'NAN'],
|
|
192
|
-
[8, 8],
|
|
193
|
-
[9, 9],
|
|
194
|
-
[10, 10],
|
|
192
|
+
[8, '8'],
|
|
193
|
+
[9, '9'],
|
|
194
|
+
[10, '10'],
|
|
195
195
|
];
|
|
196
196
|
it('should add missing data point with null', () => {
|
|
197
197
|
const result = addMissingDataPoint(
|
|
@@ -210,19 +210,7 @@ it('should return the array with string NAN when the original dataset is empty',
|
|
|
210
210
|
sampleDuration,
|
|
211
211
|
sampleFrequency,
|
|
212
212
|
);
|
|
213
|
-
expect(result).toEqual([
|
|
214
|
-
[0, 'NAN'],
|
|
215
|
-
[1, 'NAN'],
|
|
216
|
-
[2, 'NAN'],
|
|
217
|
-
[3, 'NAN'],
|
|
218
|
-
[4, 'NAN'],
|
|
219
|
-
[5, 'NAN'],
|
|
220
|
-
[6, 'NAN'],
|
|
221
|
-
[7, 'NAN'],
|
|
222
|
-
[8, 'NAN'],
|
|
223
|
-
[9, 'NAN'],
|
|
224
|
-
[10, 'NAN'],
|
|
225
|
-
]);
|
|
213
|
+
expect(result).toEqual([]);
|
|
226
214
|
});
|
|
227
215
|
it('should return an empty array when the starting timestamp is undefined', () => {
|
|
228
216
|
const result = addMissingDataPoint(
|
|
@@ -259,4 +247,4 @@ it('should return an empty array when sample frequency is undefined', () => {
|
|
|
259
247
|
undefined,
|
|
260
248
|
);
|
|
261
249
|
expect(result).toEqual([]);
|
|
262
|
-
});
|
|
250
|
+
});
|
|
@@ -72,6 +72,12 @@ export function getUnitLabel(
|
|
|
72
72
|
valueBase: number;
|
|
73
73
|
unitLabel: string;
|
|
74
74
|
} {
|
|
75
|
+
if (!unitRange || unitRange.length === 0) {
|
|
76
|
+
return {
|
|
77
|
+
valueBase: 1,
|
|
78
|
+
unitLabel: '',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
75
81
|
// first sort the unitRange
|
|
76
82
|
unitRange.sort(
|
|
77
83
|
(
|
|
@@ -109,55 +115,61 @@ export function getUnitLabel(
|
|
|
109
115
|
|
|
110
116
|
/**
|
|
111
117
|
* This function manually adds the missing data points with `null` value caused by downtime of the VMs
|
|
118
|
+
* Missing data points are only added when the gap between consecutive data points is bigger than 2 intervals
|
|
112
119
|
*
|
|
113
120
|
* @param {array} orginalValues - The array of the data points are already sorted according to the time series
|
|
114
121
|
* @param {number} startingTimeStamp - The starting timestamp in seconds
|
|
115
122
|
* @param {number} sampleDuration - The time span value in seconds
|
|
116
|
-
* @param {number}
|
|
123
|
+
* @param {number} sampleInterval - The time difference between two data points in seconds
|
|
117
124
|
*
|
|
118
125
|
*/
|
|
119
126
|
export function addMissingDataPoint(
|
|
120
|
-
orginalValues: [number, string | null][],
|
|
121
|
-
startingTimeStamp
|
|
122
|
-
sampleDuration
|
|
123
|
-
|
|
124
|
-
): [number, string | null][] {
|
|
127
|
+
orginalValues: [number, number | string | null][],
|
|
128
|
+
startingTimeStamp?: number,
|
|
129
|
+
sampleDuration?: number,
|
|
130
|
+
sampleInterval?: number,
|
|
131
|
+
): [number, number | string | null][] {
|
|
125
132
|
if (
|
|
126
133
|
!orginalValues ||
|
|
127
134
|
startingTimeStamp === undefined ||
|
|
128
135
|
!sampleDuration ||
|
|
129
|
-
!
|
|
136
|
+
!sampleInterval ||
|
|
130
137
|
startingTimeStamp < 0 ||
|
|
131
138
|
sampleDuration <= 0 ||
|
|
132
|
-
|
|
139
|
+
sampleInterval <= 0
|
|
133
140
|
) {
|
|
134
141
|
return [];
|
|
135
142
|
}
|
|
136
143
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// initialize the array with all "NAN" value, in order for the tooltip to display dash(-)
|
|
142
|
-
for (let i = 0; i < numberOfDataPoints; i++) {
|
|
143
|
-
newValues.push([samplingPointTime, NAN_STRING]);
|
|
144
|
-
samplingPointTime += sampleFrequency;
|
|
144
|
+
// If there are no original values, return empty array
|
|
145
|
+
if (orginalValues.length === 0) {
|
|
146
|
+
return [];
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
|
|
148
|
-
if (newValues.length === 0) return [];
|
|
149
|
-
let nextIndex = 0;
|
|
149
|
+
const newValues: [number, number | string | null][] = [];
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
151
|
+
// Process all but the last element
|
|
152
|
+
for (let i = 0; i < orginalValues.length - 1; i++) {
|
|
153
|
+
// Always add the current data point
|
|
154
|
+
newValues.push(orginalValues[i]);
|
|
155
|
+
|
|
156
|
+
const currentTimestamp = orginalValues[i][0];
|
|
157
|
+
const nextTimestamp = orginalValues[i + 1][0];
|
|
158
|
+
const gap = nextTimestamp - currentTimestamp;
|
|
159
|
+
|
|
160
|
+
// Calculate how many missing points to add
|
|
161
|
+
const missingIntervals = Math.floor(gap / sampleInterval) - 1;
|
|
162
|
+
|
|
163
|
+
// Add missing data points with NAN_STRING (only executes if missingIntervals > 0)
|
|
164
|
+
for (let j = 1; j <= missingIntervals; j++) {
|
|
165
|
+
const missingTimestamp = currentTimestamp + j * sampleInterval;
|
|
166
|
+
newValues.push([missingTimestamp, NAN_STRING]);
|
|
158
167
|
}
|
|
159
168
|
}
|
|
160
169
|
|
|
170
|
+
// Add the last element
|
|
171
|
+
newValues.push(orginalValues[orginalValues.length - 1]);
|
|
172
|
+
|
|
161
173
|
return newValues;
|
|
162
174
|
}
|
|
163
175
|
// get the value for the based value
|
|
@@ -165,6 +165,10 @@ const colorRange = [
|
|
|
165
165
|
];
|
|
166
166
|
|
|
167
167
|
// Note: we need to make sure the start time and end timefor the prometheus query between the series are the same.
|
|
168
|
+
/**
|
|
169
|
+
* @deprecated Use LineTimeSerieChart instead
|
|
170
|
+
* @example import { LineTimeSerieChart } from '@scality/core-ui/dist/next';
|
|
171
|
+
*/
|
|
168
172
|
function LineTemporalChart({
|
|
169
173
|
series,
|
|
170
174
|
title,
|
|
@@ -451,10 +455,10 @@ function LineTemporalChart({
|
|
|
451
455
|
],
|
|
452
456
|
}
|
|
453
457
|
: yAxisType === 'percentage'
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
+
? {
|
|
459
|
+
domain: [0, 100],
|
|
460
|
+
}
|
|
461
|
+
: undefined,
|
|
458
462
|
};
|
|
459
463
|
}, [yAxisTitle, yAxisType]);
|
|
460
464
|
const symmetricalColorRange =
|
|
@@ -652,15 +656,15 @@ function LineTemporalChart({
|
|
|
652
656
|
},
|
|
653
657
|
}
|
|
654
658
|
: yAxisType === 'symmetrical'
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
659
|
+
? {
|
|
660
|
+
// for symmetrical chart we manually draw the line from minValue to maxValue
|
|
661
|
+
...syncedVerticalRuler,
|
|
662
|
+
encoding: {
|
|
663
|
+
...syncedVerticalRuler.encoding,
|
|
664
|
+
...syncedVerticalRulerSymmetrical.encoding,
|
|
665
|
+
},
|
|
666
|
+
}
|
|
667
|
+
: syncedVerticalRuler,
|
|
664
668
|
],
|
|
665
669
|
},
|
|
666
670
|
tooltipConfig,
|
|
@@ -698,8 +702,8 @@ function LineTemporalChart({
|
|
|
698
702
|
const unitLabel = unitRange
|
|
699
703
|
? getUnitLabel(unitRange, maxValue).unitLabel
|
|
700
704
|
: yAxisType === 'percentage'
|
|
701
|
-
|
|
702
|
-
|
|
705
|
+
? '%'
|
|
706
|
+
: '';
|
|
703
707
|
return (
|
|
704
708
|
<LineTemporalChartWrapper>
|
|
705
709
|
<ChartHeader>
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
YAxis,
|
|
9
9
|
CartesianGrid,
|
|
10
10
|
} from 'recharts';
|
|
11
|
-
import { useMemo, useRef } from 'react';
|
|
11
|
+
import { useCallback, useMemo, useRef } from 'react';
|
|
12
12
|
import { useTheme } from 'styled-components';
|
|
13
13
|
import { addMissingDataPoint } from '../linetemporalchart/ChartUtil';
|
|
14
14
|
import styled from 'styled-components';
|
|
@@ -23,6 +23,7 @@ import { Tooltip as TooltipComponent } from '../tooltip/Tooltip.component';
|
|
|
23
23
|
import {
|
|
24
24
|
DAY_MONTH_ABBREVIATED_HOUR_MINUTE,
|
|
25
25
|
FormattedDateTime,
|
|
26
|
+
YEAR_MONTH_DAY_FORMATTER,
|
|
26
27
|
} from '../date/FormattedDateTime';
|
|
27
28
|
|
|
28
29
|
const LineTemporalChartWrapper = styled.div`
|
|
@@ -91,8 +92,8 @@ const TooltipInstanceValue = styled.div`
|
|
|
91
92
|
export type Serie = {
|
|
92
93
|
// the name of the resource
|
|
93
94
|
resource: string;
|
|
94
|
-
// the original data format from prometheus
|
|
95
|
-
data: [number, string | null][];
|
|
95
|
+
// the original data format from prometheus, extend the value to include number type.
|
|
96
|
+
data: [number, number | string | null][];
|
|
96
97
|
// it's mandatory to display tooltip label in the tooltip
|
|
97
98
|
getTooltipLabel: (metricPrefix?: string, resource?: string) => string;
|
|
98
99
|
// the name of the metric prefix with read, write, in, out
|
|
@@ -129,6 +130,12 @@ export type LineChartProps = (
|
|
|
129
130
|
label: string;
|
|
130
131
|
}[];
|
|
131
132
|
isLoading?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* The format of the x axis, default is 'date-time' which is like 01 Sep 16:00
|
|
135
|
+
* If you want to display the date only, you can set it to 'date' which is like 2025-09-01
|
|
136
|
+
* This will affect the format of the tooltip as well
|
|
137
|
+
*/
|
|
138
|
+
timeFormat?: 'date-time' | 'date';
|
|
132
139
|
yAxisTitle?: string;
|
|
133
140
|
helpText?: string;
|
|
134
141
|
};
|
|
@@ -138,6 +145,7 @@ const CustomTooltip = ({
|
|
|
138
145
|
payload,
|
|
139
146
|
label,
|
|
140
147
|
unitLabel,
|
|
148
|
+
timeFormat,
|
|
141
149
|
}: {
|
|
142
150
|
active?: boolean;
|
|
143
151
|
payload?: Array<{
|
|
@@ -148,6 +156,7 @@ const CustomTooltip = ({
|
|
|
148
156
|
}>;
|
|
149
157
|
label?: string;
|
|
150
158
|
unitLabel?: string;
|
|
159
|
+
timeFormat?: 'date-time' | 'date';
|
|
151
160
|
}) => {
|
|
152
161
|
if (!active || !payload || !payload.length || !label) return null;
|
|
153
162
|
// We can't use the default itemSorter method because it's a custom tooltip.
|
|
@@ -169,7 +178,11 @@ const CustomTooltip = ({
|
|
|
169
178
|
<TooltipContainer>
|
|
170
179
|
<TooltipTime>
|
|
171
180
|
<FormattedDateTime
|
|
172
|
-
format=
|
|
181
|
+
format={
|
|
182
|
+
timeFormat === 'date-time'
|
|
183
|
+
? 'day-month-abbreviated-hour-minute-second'
|
|
184
|
+
: 'long-date'
|
|
185
|
+
}
|
|
173
186
|
value={new Date(label)}
|
|
174
187
|
/>
|
|
175
188
|
</TooltipTime>
|
|
@@ -205,6 +218,7 @@ export function LineTimeSerieChart({
|
|
|
205
218
|
duration,
|
|
206
219
|
unitRange,
|
|
207
220
|
isLoading = false,
|
|
221
|
+
timeFormat = 'date-time',
|
|
208
222
|
yAxisType = 'default',
|
|
209
223
|
yAxisTitle,
|
|
210
224
|
helpText,
|
|
@@ -259,7 +273,7 @@ export function LineTimeSerieChart({
|
|
|
259
273
|
// Initialize an object to hold data points by timestamp
|
|
260
274
|
const dataPointsByTime: Record<
|
|
261
275
|
number,
|
|
262
|
-
{ timestamp: number } & Record<string,
|
|
276
|
+
{ timestamp: number } & Record<string, number | null>
|
|
263
277
|
> = {};
|
|
264
278
|
const seriesToProcess =
|
|
265
279
|
yAxisType === 'symmetrical' && isSymmetricalSeries(normalizedSeries)
|
|
@@ -285,8 +299,8 @@ export function LineTimeSerieChart({
|
|
|
285
299
|
// Convert object to array for Recharts
|
|
286
300
|
return Object.values(dataPointsByTime).sort(
|
|
287
301
|
(
|
|
288
|
-
a: { timestamp: number } & Record<string,
|
|
289
|
-
b: { timestamp: number } & Record<string,
|
|
302
|
+
a: { timestamp: number } & Record<string, number | null>,
|
|
303
|
+
b: { timestamp: number } & Record<string, number | null>,
|
|
290
304
|
) => (a.timestamp as number) - (b.timestamp as number),
|
|
291
305
|
);
|
|
292
306
|
}, [series, startingTimeStamp, duration, interval, yAxisType]);
|
|
@@ -393,12 +407,16 @@ export function LineTimeSerieChart({
|
|
|
393
407
|
}, [series, getColor]);
|
|
394
408
|
|
|
395
409
|
// Format time for display the tick in the x axis
|
|
396
|
-
const
|
|
397
|
-
(
|
|
410
|
+
const formatXAxisLabel = useCallback(
|
|
411
|
+
(timestamp: number) => {
|
|
398
412
|
const date = new Date(timestamp);
|
|
399
|
-
return
|
|
413
|
+
return timeFormat === 'date-time'
|
|
414
|
+
? DAY_MONTH_ABBREVIATED_HOUR_MINUTE.format(date).replace(',', '')
|
|
415
|
+
: timeFormat === 'date'
|
|
416
|
+
? YEAR_MONTH_DAY_FORMATTER.format(date)
|
|
417
|
+
: '';
|
|
400
418
|
},
|
|
401
|
-
[],
|
|
419
|
+
[timeFormat],
|
|
402
420
|
);
|
|
403
421
|
|
|
404
422
|
return (
|
|
@@ -438,7 +456,7 @@ export function LineTimeSerieChart({
|
|
|
438
456
|
type="number"
|
|
439
457
|
domain={['dataMin', 'dataMax']}
|
|
440
458
|
ticks={xAxisTicks}
|
|
441
|
-
tickFormatter={
|
|
459
|
+
tickFormatter={formatXAxisLabel}
|
|
442
460
|
tickCount={5}
|
|
443
461
|
tick={{
|
|
444
462
|
fill: theme.textSecondary,
|
|
@@ -473,7 +491,11 @@ export function LineTimeSerieChart({
|
|
|
473
491
|
}}
|
|
474
492
|
tickFormatter={(value) => Math.round(value).toString()}
|
|
475
493
|
/>
|
|
476
|
-
<Tooltip
|
|
494
|
+
<Tooltip
|
|
495
|
+
content={
|
|
496
|
+
<CustomTooltip unitLabel={unitLabel} timeFormat={timeFormat} />
|
|
497
|
+
}
|
|
498
|
+
/>
|
|
477
499
|
{/* Add horizontal line at y=0 for symmetrical charts */}
|
|
478
500
|
{yAxisType === 'symmetrical' && (
|
|
479
501
|
<ReferenceLine y={0} stroke={theme.border} />
|
|
@@ -493,6 +515,7 @@ export function LineTimeSerieChart({
|
|
|
493
515
|
dataKey={label}
|
|
494
516
|
stroke={colorMapping[resource]}
|
|
495
517
|
dot={false}
|
|
518
|
+
isAnimationActive={false}
|
|
496
519
|
/>
|
|
497
520
|
);
|
|
498
521
|
}),
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { render } from "@testing-library/react";
|
|
2
|
+
import { LineChartProps, LineTimeSerieChart } from "./linetimeseriechart.component";
|
|
3
|
+
import { ChartLegendWrapper } from "../chartlegend/ChartLegendWrapper";
|
|
4
|
+
import { ThemeProvider } from "styled-components";
|
|
5
|
+
import { coreUIAvailableThemes } from "../../style/theme";
|
|
6
|
+
|
|
7
|
+
const TestSeries = [
|
|
8
|
+
{
|
|
9
|
+
|
|
10
|
+
resource: "Series 1",
|
|
11
|
+
getTooltipLabel: () => `Series 1`,
|
|
12
|
+
data: [
|
|
13
|
+
[1622505600000, 10],
|
|
14
|
+
[1622509200000, 20],
|
|
15
|
+
[1622512800000, 30],
|
|
16
|
+
] as [number, number][],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
resource: "Series 2",
|
|
20
|
+
getTooltipLabel: () => `Series 2`,
|
|
21
|
+
data: [
|
|
22
|
+
[1622505600000, 15],
|
|
23
|
+
[1622509200000, 25],
|
|
24
|
+
[1622512800000, 35],
|
|
25
|
+
] as [number, number][],
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const ColorSet = {
|
|
30
|
+
"Series 1": "#FF0000",
|
|
31
|
+
"Series 2": "#00FF00",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const renderLineTimeSerieChart = (props: Partial<LineChartProps> = {}) => {
|
|
35
|
+
return render(
|
|
36
|
+
<ThemeProvider theme={coreUIAvailableThemes.artescaLight}>
|
|
37
|
+
<ChartLegendWrapper colorSet={ColorSet}>
|
|
38
|
+
<LineTimeSerieChart
|
|
39
|
+
{
|
|
40
|
+
...{
|
|
41
|
+
title: "Test Chart",
|
|
42
|
+
yAxisType: "default",
|
|
43
|
+
series: TestSeries,
|
|
44
|
+
height: 400,
|
|
45
|
+
startingTimeStamp: TestSeries[0].data[0][0],
|
|
46
|
+
interval: TestSeries[0].data[1][0] - TestSeries[0].data[0][0],
|
|
47
|
+
duration: TestSeries[0].data[TestSeries[0].data.length - 1][0] - TestSeries[0].data[0][0],
|
|
48
|
+
unitRange: [{ label: 'units', value: 1 }],
|
|
49
|
+
...props
|
|
50
|
+
} as LineChartProps
|
|
51
|
+
}
|
|
52
|
+
/>
|
|
53
|
+
</ChartLegendWrapper>
|
|
54
|
+
</ThemeProvider>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
describe('LineTimeSerieChart', () => {
|
|
59
|
+
it('should render when with basic parameters', async () => {
|
|
60
|
+
const { container } = renderLineTimeSerieChart();
|
|
61
|
+
expect(container).toBeInTheDocument();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should render when no unitRange is provided', async () => {
|
|
65
|
+
const { container } = renderLineTimeSerieChart({ unitRange: undefined });
|
|
66
|
+
expect(container).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -69,7 +69,7 @@ describe('SelectV2', () => {
|
|
|
69
69
|
it('should throw error if <Option/> is outside <Select/>', () => {
|
|
70
70
|
// mock console.error as this is the only way to silent expected error thrown by the component
|
|
71
71
|
const consoleErrorFn = jest.spyOn(console, 'error').mockImplementation(() => jest.fn());
|
|
72
|
-
expect(() => render(<Option value="Option 1" />)).
|
|
72
|
+
expect(() => render(<Option value="Option 1" />)).toThrow();
|
|
73
73
|
consoleErrorFn.mockRestore();
|
|
74
74
|
});
|
|
75
75
|
|