@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.
Files changed (119) hide show
  1. package/.storybook/preview.js +6 -4
  2. package/README.md +27 -80
  3. package/dist/components/barchart/BarChart.component.d.ts +5 -0
  4. package/dist/components/barchart/BarChart.component.d.ts.map +1 -1
  5. package/dist/components/barchart/BarChart.component.js +5 -0
  6. package/dist/components/barchartv2/Barchart.component.d.ts +5 -1
  7. package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
  8. package/dist/components/barchartv2/Barchart.component.js +6 -2
  9. package/dist/components/barchartv2/utils.js +2 -2
  10. package/dist/components/button/Button.component.d.ts +2 -1
  11. package/dist/components/button/Button.component.d.ts.map +1 -1
  12. package/dist/components/button/Button.component.js +2 -1
  13. package/dist/components/chartlegend/ChartLegend.d.ts.map +1 -1
  14. package/dist/components/chartlegend/ChartLegend.js +7 -13
  15. package/dist/components/chartlegend/ChartLegendWrapper.d.ts +1 -2
  16. package/dist/components/chartlegend/ChartLegendWrapper.d.ts.map +1 -1
  17. package/dist/components/chartlegend/ChartLegendWrapper.js +4 -9
  18. package/dist/components/date/FormattedDateTime.d.ts +6 -1
  19. package/dist/components/date/FormattedDateTime.d.ts.map +1 -1
  20. package/dist/components/date/FormattedDateTime.js +11 -0
  21. package/dist/components/icon/Icon.component.d.ts +9 -11
  22. package/dist/components/icon/Icon.component.d.ts.map +1 -1
  23. package/dist/components/icon/Icon.component.js +2 -0
  24. package/dist/components/linetemporalchart/ChartUtil.d.ts +3 -2
  25. package/dist/components/linetemporalchart/ChartUtil.d.ts.map +1 -1
  26. package/dist/components/linetemporalchart/ChartUtil.js +30 -20
  27. package/dist/components/linetemporalchart/LineTemporalChart.component.d.ts +4 -0
  28. package/dist/components/linetemporalchart/LineTemporalChart.component.d.ts.map +1 -1
  29. package/dist/components/linetemporalchart/LineTemporalChart.component.js +4 -0
  30. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +8 -2
  31. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -1
  32. package/dist/components/linetimeseriechart/linetimeseriechart.component.js +17 -11
  33. package/dist/index.d.ts +1 -11
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +0 -11
  36. package/dist/next.d.ts +1 -0
  37. package/dist/next.d.ts.map +1 -1
  38. package/jest.config.js +1 -0
  39. package/package.json +16 -37
  40. package/src/lib/components/barchart/BarChart.component.tsx +5 -0
  41. package/src/lib/components/barchartv2/Barchart.component.test.tsx +11 -7
  42. package/src/lib/components/barchartv2/Barchart.component.tsx +15 -3
  43. package/src/lib/components/barchartv2/utils.test.ts +2 -2
  44. package/src/lib/components/barchartv2/utils.ts +2 -2
  45. package/src/lib/components/button/Button.component.tsx +2 -1
  46. package/src/lib/components/chartlegend/ChartLegend.test.tsx +17 -0
  47. package/src/lib/components/chartlegend/ChartLegend.tsx +6 -12
  48. package/src/lib/components/chartlegend/ChartLegendWrapper.tsx +5 -13
  49. package/src/lib/components/date/FormattedDateTime.tsx +14 -1
  50. package/src/lib/components/icon/Icon.component.tsx +12 -1
  51. package/src/lib/components/linetemporalchart/ChartUtil.test.ts +23 -35
  52. package/src/lib/components/linetemporalchart/ChartUtil.ts +38 -26
  53. package/src/lib/components/linetemporalchart/LineTemporalChart.component.tsx +19 -15
  54. package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +36 -13
  55. package/src/lib/components/linetimeseriechart/linetimeseriechart.test.tsx +68 -0
  56. package/src/lib/components/selectv2/selectv2.test.tsx +1 -1
  57. package/src/lib/components/toast/useMutationsHandler.test.tsx +22 -32
  58. package/src/lib/index.ts +6 -11
  59. package/src/lib/next.ts +1 -0
  60. package/stories/BarChart/barchart.stories.tsx +108 -13
  61. package/stories/barchart.stories.tsx +1 -1
  62. package/stories/card.stories.tsx +7 -5
  63. package/stories/controls.ts +19 -20
  64. package/stories/linecharttemporal.stories.tsx +1 -1
  65. package/stories/linetimeseriechart.stories.tsx +146 -0
  66. package/stories/navbar.stories.tsx +103 -0
  67. package/stories/tabsv2.stories.tsx +51 -56
  68. package/dist/components/areachart/AreaChart.component.d.ts +0 -13
  69. package/dist/components/areachart/AreaChart.component.d.ts.map +0 -1
  70. package/dist/components/areachart/AreaChart.component.js +0 -27
  71. package/dist/components/chips/Chips.component.d.ts +0 -21
  72. package/dist/components/chips/Chips.component.d.ts.map +0 -1
  73. package/dist/components/chips/Chips.component.js +0 -105
  74. package/dist/components/cloudprogressbar/CloudProgressBar.component.d.ts +0 -10
  75. package/dist/components/cloudprogressbar/CloudProgressBar.component.d.ts.map +0 -1
  76. package/dist/components/cloudprogressbar/CloudProgressBar.component.js +0 -38
  77. package/dist/components/collapsiblepanel/CollapsiblePanel.component.d.ts +0 -9
  78. package/dist/components/collapsiblepanel/CollapsiblePanel.component.d.ts.map +0 -1
  79. package/dist/components/collapsiblepanel/CollapsiblePanel.component.js +0 -44
  80. package/dist/components/linechart/LineChart.component.d.ts +0 -21
  81. package/dist/components/linechart/LineChart.component.d.ts.map +0 -1
  82. package/dist/components/linechart/LineChart.component.js +0 -109
  83. package/dist/components/multiselect/MultiSelect.component.d.ts +0 -28
  84. package/dist/components/multiselect/MultiSelect.component.d.ts.map +0 -1
  85. package/dist/components/multiselect/MultiSelect.component.js +0 -73
  86. package/dist/components/select/Select.component.d.ts +0 -14
  87. package/dist/components/select/Select.component.d.ts.map +0 -1
  88. package/dist/components/select/Select.component.js +0 -71
  89. package/dist/components/spacedbox/SpacedBox.d.ts +0 -34
  90. package/dist/components/spacedbox/SpacedBox.d.ts.map +0 -1
  91. package/dist/components/spacedbox/SpacedBox.js +0 -64
  92. package/dist/components/sparkline/SparkLine.component.d.ts +0 -18
  93. package/dist/components/sparkline/SparkLine.component.d.ts.map +0 -1
  94. package/dist/components/sparkline/SparkLine.component.js +0 -148
  95. package/dist/components/vegachart/VegaChart.component.d.ts +0 -13
  96. package/dist/components/vegachart/VegaChart.component.d.ts.map +0 -1
  97. package/dist/components/vegachart/VegaChart.component.js +0 -120
  98. package/plopfile.js +0 -38
  99. package/src/lib/components/areachart/AreaChart.component.tsx +0 -49
  100. package/src/lib/components/chips/Chips.component.tsx +0 -169
  101. package/src/lib/components/cloudprogressbar/CloudProgressBar.component.tsx +0 -105
  102. package/src/lib/components/collapsiblepanel/CollapsiblePanel.component.tsx +0 -77
  103. package/src/lib/components/linechart/LineChart.component.tsx +0 -152
  104. package/src/lib/components/multiselect/MultiSelect.component.tsx +0 -158
  105. package/src/lib/components/select/Select.component.tsx +0 -98
  106. package/src/lib/components/spacedbox/SpacedBox.ts +0 -116
  107. package/src/lib/components/sparkline/SparkLine.component.tsx +0 -176
  108. package/src/lib/components/vegachart/VegaChart.component.tsx +0 -146
  109. package/stories/areachart.stories.tsx +0 -120
  110. package/stories/chips.stories.tsx +0 -107
  111. package/stories/cloudprogressbar.stories.tsx +0 -93
  112. package/stories/collapsiblepanel.stories.tsx +0 -57
  113. package/stories/data/areachart.ts +0 -122
  114. package/stories/data/sparklinechart.ts +0 -164
  115. package/stories/linechart.stories.tsx +0 -319
  116. package/stories/multiselect.stories.tsx +0 -126
  117. package/stories/select.stories.tsx +0 -52
  118. package/stories/sparkline.stories.tsx +0 -85
  119. 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 (getSelectedCount() === 1) {
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
- if (getSelectedCount() === getAllResourcesCount()) {
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
- getAllResourcesCount,
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
- getAllResourcesCount: () => number;
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 getAllResourcesCount = useCallback(() => {
58
- return allResources.length;
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
- getAllResourcesCount,
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
- getAllResourcesCount,
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
- export const customIcons = {
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} sampleFrequency - The time difference between two data points in seconds
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: number,
122
- sampleDuration: number,
123
- sampleFrequency: number,
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
- !sampleFrequency ||
136
+ !sampleInterval ||
130
137
  startingTimeStamp < 0 ||
131
138
  sampleDuration <= 0 ||
132
- sampleFrequency <= 0
139
+ sampleInterval <= 0
133
140
  ) {
134
141
  return [];
135
142
  }
136
143
 
137
- const newValues = [];
138
- const numberOfDataPoints = sampleDuration / sampleFrequency + 1;
139
- let samplingPointTime = startingTimeStamp;
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
- // copy the existing data points from `orginalValue` array to `newValues`
148
- if (newValues.length === 0) return [];
149
- let nextIndex = 0;
149
+ const newValues: [number, number | string | null][] = [];
150
150
 
151
- for (let i = 0; i < newValues.length; i++) {
152
- if (
153
- orginalValues[nextIndex] &&
154
- newValues[i][0] === orginalValues[nextIndex][0]
155
- ) {
156
- newValues[i][1] = orginalValues[nextIndex][1];
157
- nextIndex++;
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
- domain: [0, 100],
456
- }
457
- : undefined,
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
- // for symmetrical chart we manually draw the line from minValue to maxValue
657
- ...syncedVerticalRuler,
658
- encoding: {
659
- ...syncedVerticalRuler.encoding,
660
- ...syncedVerticalRulerSymmetrical.encoding,
661
- },
662
- }
663
- : syncedVerticalRuler,
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="day-month-abbreviated-hour-minute-second"
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, string | number | null>
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, string | number | null>,
289
- b: { timestamp: number } & Record<string, string | number | null>,
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 formatTime = useMemo(
397
- () => (timestamp: number) => {
410
+ const formatXAxisLabel = useCallback(
411
+ (timestamp: number) => {
398
412
  const date = new Date(timestamp);
399
- return DAY_MONTH_ABBREVIATED_HOUR_MINUTE.format(date).replace(',', '');
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={formatTime}
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 content={<CustomTooltip unitLabel={unitLabel} />} />
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" />)).toThrowError();
72
+ expect(() => render(<Option value="Option 1" />)).toThrow();
73
73
  consoleErrorFn.mockRestore();
74
74
  });
75
75