@scality/core-ui 0.161.0 → 0.163.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 (173) hide show
  1. package/README.md +15 -15
  2. package/dist/components/accordion/Accordion.component.d.ts +0 -1
  3. package/dist/components/accordion/Accordion.component.d.ts.map +1 -1
  4. package/dist/components/barchartv2/Barchart.component.d.ts +53 -0
  5. package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -0
  6. package/dist/components/barchartv2/Barchart.component.js +86 -0
  7. package/dist/components/barchartv2/utils.d.ts +118 -0
  8. package/dist/components/barchartv2/utils.d.ts.map +1 -0
  9. package/dist/components/barchartv2/utils.js +337 -0
  10. package/dist/components/buttonv2/Buttonv2.component.d.ts +1 -1
  11. package/dist/components/buttonv2/Buttonv2.component.d.ts.map +1 -1
  12. package/dist/components/chartlegend/ChartLegend.d.ts +8 -0
  13. package/dist/components/chartlegend/ChartLegend.d.ts.map +1 -0
  14. package/dist/components/chartlegend/ChartLegend.js +65 -0
  15. package/dist/components/chartlegend/ChartLegendWrapper.d.ts +17 -0
  16. package/dist/components/chartlegend/ChartLegendWrapper.d.ts.map +1 -0
  17. package/dist/components/chartlegend/ChartLegendWrapper.js +50 -0
  18. package/dist/components/constrainedtext/Constrainedtext.component.d.ts +2 -1
  19. package/dist/components/constrainedtext/Constrainedtext.component.d.ts.map +1 -1
  20. package/dist/components/constrainedtext/Constrainedtext.component.js +5 -4
  21. package/dist/components/coreuithemeprovider/CoreUiThemeProvider.d.ts +0 -1
  22. package/dist/components/coreuithemeprovider/CoreUiThemeProvider.d.ts.map +1 -1
  23. package/dist/components/date/FormattedDateTime.d.ts +4 -1
  24. package/dist/components/date/FormattedDateTime.d.ts.map +1 -1
  25. package/dist/components/date/FormattedDateTime.js +24 -1
  26. package/dist/components/date/FormattedDateTime.spec.js +12 -0
  27. package/dist/components/emptytable/Emptytable.component.d.ts +0 -1
  28. package/dist/components/emptytable/Emptytable.component.d.ts.map +1 -1
  29. package/dist/components/emptytable/Emptytable.component.js +1 -0
  30. package/dist/components/error-pages/ErrorPage401.component.d.ts +0 -1
  31. package/dist/components/error-pages/ErrorPage401.component.d.ts.map +1 -1
  32. package/dist/components/error-pages/ErrorPage404.component.d.ts +0 -1
  33. package/dist/components/error-pages/ErrorPage404.component.d.ts.map +1 -1
  34. package/dist/components/error-pages/ErrorPage500.component.d.ts +0 -1
  35. package/dist/components/error-pages/ErrorPage500.component.d.ts.map +1 -1
  36. package/dist/components/error-pages/ErrorPageAuth.component.d.ts.map +1 -1
  37. package/dist/components/form/Form.component.d.ts +2 -2
  38. package/dist/components/form/Form.component.d.ts.map +1 -1
  39. package/dist/components/icon/Icon.component.d.ts +5 -5
  40. package/dist/components/icon/Icon.component.d.ts.map +1 -1
  41. package/dist/components/icon/Icon.component.js +33 -31
  42. package/dist/components/infomessage/InfoMessage.component.d.ts +0 -1
  43. package/dist/components/infomessage/InfoMessage.component.d.ts.map +1 -1
  44. package/dist/components/lateralnavbarlayout/LateralNavbarLayout.component.d.ts.map +1 -1
  45. package/dist/components/layout/Layout.component.d.ts.map +1 -1
  46. package/dist/components/layout/v2/panels.d.ts.map +1 -1
  47. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +33 -0
  48. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -0
  49. package/dist/components/linetimeseriechart/linetimeseriechart.component.js +249 -0
  50. package/dist/components/modal/Modal.component.js +2 -2
  51. package/dist/components/navbar/Navbar.component.js +2 -2
  52. package/dist/components/scrollbarwrapper/ScrollbarWrapper.component.d.ts +0 -1
  53. package/dist/components/scrollbarwrapper/ScrollbarWrapper.component.d.ts.map +1 -1
  54. package/dist/components/searchinput/SearchInput.component.d.ts +1 -2
  55. package/dist/components/searchinput/SearchInput.component.d.ts.map +1 -1
  56. package/dist/components/selectv2/Selectv2.component.d.ts +5 -5
  57. package/dist/components/selectv2/Selectv2.component.d.ts.map +1 -1
  58. package/dist/components/selectv2/Selectv2.component.js +11 -6
  59. package/dist/components/statuswrapper/Statuswrapper.component.d.ts +0 -1
  60. package/dist/components/statuswrapper/Statuswrapper.component.d.ts.map +1 -1
  61. package/dist/components/steppers/Stepper.component.d.ts.map +1 -1
  62. package/dist/components/steppers/Stepper.component.js +9 -8
  63. package/dist/components/tablev2/Search.js +2 -2
  64. package/dist/components/tablev2/SingleSelectableContent.d.ts +1 -2
  65. package/dist/components/tablev2/SingleSelectableContent.d.ts.map +1 -1
  66. package/dist/components/tablev2/TableCommon.d.ts +2 -2
  67. package/dist/components/tablev2/TableCommon.d.ts.map +1 -1
  68. package/dist/components/tablev2/TableSync.d.ts +8 -0
  69. package/dist/components/tablev2/TableSync.d.ts.map +1 -0
  70. package/dist/components/tablev2/TableSync.js +11 -0
  71. package/dist/components/tablev2/Tablev2.component.d.ts +2 -1
  72. package/dist/components/tablev2/Tablev2.component.d.ts.map +1 -1
  73. package/dist/components/tablev2/Tablev2.component.js +10 -9
  74. package/dist/components/tabsv2/ScrollButton.d.ts +1 -2
  75. package/dist/components/tabsv2/ScrollButton.d.ts.map +1 -1
  76. package/dist/components/tabsv2/ScrollButton.js +2 -2
  77. package/dist/components/tabsv2/Tabsv2.component.d.ts +2 -2
  78. package/dist/components/tabsv2/Tabsv2.component.d.ts.map +1 -1
  79. package/dist/components/tabsv2/Tabsv2.component.js +2 -2
  80. package/dist/components/text/Text.component.d.ts +0 -1
  81. package/dist/components/text/Text.component.d.ts.map +1 -1
  82. package/dist/components/textarea/TextArea.component.d.ts +3 -3
  83. package/dist/components/textarea/TextArea.component.d.ts.map +1 -1
  84. package/dist/components/textbadge/TextBadge.component.d.ts +0 -1
  85. package/dist/components/textbadge/TextBadge.component.d.ts.map +1 -1
  86. package/dist/components/toast/Toast.component.d.ts +1 -1
  87. package/dist/components/toast/Toast.component.d.ts.map +1 -1
  88. package/dist/components/toast/ToastProvider.d.ts.map +1 -1
  89. package/dist/components/toast/ToastProvider.js +4 -5
  90. package/dist/components/vegachartv2/SyncedCursorCharts.d.ts +1 -2
  91. package/dist/components/vegachartv2/SyncedCursorCharts.d.ts.map +1 -1
  92. package/dist/components/vegachartv2/SyncedCursorCharts.js +3 -5
  93. package/dist/components/vegachartv2/VegaChartV2.component.d.ts +1 -2
  94. package/dist/components/vegachartv2/VegaChartV2.component.d.ts.map +1 -1
  95. package/dist/components/vegachartv2/VegaChartV2.component.js +2 -2
  96. package/dist/icons/branding.d.ts.map +1 -1
  97. package/dist/icons/scality-loading.d.ts.map +1 -1
  98. package/dist/index.d.ts +1 -0
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +1 -0
  101. package/dist/next.d.ts +2 -0
  102. package/dist/next.d.ts.map +1 -1
  103. package/dist/next.js +2 -0
  104. package/dist/style/theme.d.ts +20 -0
  105. package/dist/style/theme.d.ts.map +1 -1
  106. package/dist/style/theme.js +46 -1
  107. package/package.json +7 -4
  108. package/setupTests.js +6 -0
  109. package/src/lib/components/accordion/Accordion.component.tsx +1 -1
  110. package/src/lib/components/accordion/Accordion.test.tsx +7 -15
  111. package/src/lib/components/barchartv2/Barchart.component.test.tsx +364 -0
  112. package/src/lib/components/barchartv2/Barchart.component.tsx +321 -0
  113. package/src/lib/components/barchartv2/utils.test.ts +899 -0
  114. package/src/lib/components/barchartv2/utils.ts +534 -0
  115. package/src/lib/components/buttonv2/Buttonv2.component.tsx +1 -1
  116. package/src/lib/components/chartlegend/ChartLegend.tsx +113 -0
  117. package/src/lib/components/chartlegend/ChartLegendWrapper.tsx +85 -0
  118. package/src/lib/components/constrainedtext/Constrainedtext.component.tsx +22 -3
  119. package/src/lib/components/coreuithemeprovider/CoreUiThemeProvider.tsx +0 -1
  120. package/src/lib/components/date/FormattedDateTime.spec.tsx +24 -0
  121. package/src/lib/components/date/FormattedDateTime.tsx +42 -2
  122. package/src/lib/components/emptytable/Emptytable.component.tsx +1 -1
  123. package/src/lib/components/error-pages/ErrorPage401.component.tsx +0 -1
  124. package/src/lib/components/error-pages/ErrorPage404.component.tsx +0 -1
  125. package/src/lib/components/error-pages/ErrorPage500.component.tsx +0 -1
  126. package/src/lib/components/error-pages/ErrorPageAuth.component.tsx +0 -1
  127. package/src/lib/components/form/Form.component.tsx +1 -1
  128. package/src/lib/components/healthselectorv2/HealthSelector.component.test.tsx +3 -3
  129. package/src/lib/components/icon/Icon.component.tsx +48 -60
  130. package/src/lib/components/infomessage/InfoMessage.component.tsx +0 -1
  131. package/src/lib/components/inlineinput/InlineInput.test.tsx +22 -19
  132. package/src/lib/components/inputlist/InputList.test.tsx +21 -19
  133. package/src/lib/components/lateralnavbarlayout/LateralNavbarLayout.component.tsx +0 -1
  134. package/src/lib/components/layout/Layout.component.tsx +0 -1
  135. package/src/lib/components/layout/v2/panels.tsx +1 -1
  136. package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +502 -0
  137. package/src/lib/components/modal/Modal.component.tsx +2 -2
  138. package/src/lib/components/navbar/Navbar.component.tsx +2 -2
  139. package/src/lib/components/scrollbarwrapper/ScrollbarWrapper.component.tsx +0 -1
  140. package/src/lib/components/searchinput/SearchInput.component.tsx +0 -1
  141. package/src/lib/components/searchinput/SearchInput.test.tsx +3 -7
  142. package/src/lib/components/selectv2/Selectv2.component.tsx +24 -14
  143. package/src/lib/components/selectv2/selectv2.test.tsx +62 -57
  144. package/src/lib/components/sidebar/Sidebar.component.tsx +1 -1
  145. package/src/lib/components/statuswrapper/Statuswrapper.component.tsx +0 -1
  146. package/src/lib/components/steppers/Stepper.component.tsx +10 -8
  147. package/src/lib/components/tablev2/Search.tsx +2 -2
  148. package/src/lib/components/tablev2/SingleSelectableContent.tsx +2 -2
  149. package/src/lib/components/tablev2/TableCommon.tsx +1 -1
  150. package/src/lib/components/tablev2/TableSync.test.tsx +28 -0
  151. package/src/lib/components/tablev2/TableSync.tsx +36 -0
  152. package/src/lib/components/tablev2/Tablev2.component.tsx +11 -9
  153. package/src/lib/components/tablev2/Tablev2.test.tsx +36 -37
  154. package/src/lib/components/tabsv2/ScrollButton.tsx +2 -2
  155. package/src/lib/components/tabsv2/Tabsv2.component.tsx +6 -6
  156. package/src/lib/components/text/Text.component.tsx +4 -5
  157. package/src/lib/components/textarea/TextArea.component.tsx +3 -2
  158. package/src/lib/components/textbadge/TextBadge.component.tsx +0 -1
  159. package/src/lib/components/toast/Toast.component.tsx +1 -1
  160. package/src/lib/components/toast/ToastProvider.tsx +17 -7
  161. package/src/lib/components/vegachartv2/SyncedCursorCharts.tsx +5 -7
  162. package/src/lib/components/vegachartv2/VegaChartV2.component.tsx +2 -2
  163. package/src/lib/icons/branding.tsx +0 -2
  164. package/src/lib/icons/scality-loading.tsx +0 -2
  165. package/src/lib/index.ts +1 -0
  166. package/src/lib/next.ts +6 -0
  167. package/src/lib/style/theme.ts +53 -1
  168. package/stories/BarChart/barchart.stories.tsx +822 -0
  169. package/stories/areachart.stories.tsx +0 -1
  170. package/stories/format.mdx +4 -2
  171. package/stories/linetimeseriechart.stories.tsx +485 -0
  172. package/stories/tablev2.stories.tsx +41 -0
  173. package/tsconfig.json +5 -2
@@ -0,0 +1,899 @@
1
+ import { coreUIAvailableThemes } from '../../style/theme';
2
+ import {
3
+ applySortingToData,
4
+ computeUnitLabelAndRoundReferenceValue,
5
+ filterChartDataAndBarsByLegendSelection,
6
+ formatPrometheusDataToRechartsDataAndBars,
7
+ getMaxBarValue,
8
+ getRoundReferenceValue,
9
+ renderTooltipContent,
10
+ sortStackedBars,
11
+ transformCategoryData,
12
+ transformTimeData,
13
+ UnitRange,
14
+ } from './utils';
15
+
16
+ // Mock theme object for tests
17
+ const mockTheme = {
18
+ statusHealthy: '#00D100',
19
+ statusHealthyRGB: '0, 209, 0',
20
+ statusCritical: '#D10000',
21
+ statusCriticalRGB: '209, 0, 0',
22
+ statusWarning: '#FFA500',
23
+ statusWarningRGB: '255, 165, 0',
24
+ selectedActive: '#337FBD',
25
+ highlight: '#337FBD',
26
+ border: '#C2C8CC',
27
+ buttonPrimary: '#337FBD',
28
+ buttonSecondary: '#68737D',
29
+ buttonDelete: '#EF3340',
30
+ infoPrimary: '#337FBD',
31
+ infoSecondary: '#68737D',
32
+ backgroundLevel1: '#ffffff',
33
+ backgroundLevel2: '#F9FAFB',
34
+ backgroundLevel3: '#E9EBED',
35
+ backgroundLevel4: '#D8DCDE',
36
+ textPrimary: '#2F3941',
37
+ textSecondary: '#68737D',
38
+ textTertiary: '#87929D',
39
+ textReverse: '#ffffff',
40
+ textLink: '#337FBD',
41
+ } as const;
42
+
43
+ describe('transformTimeData', () => {
44
+ it('should transform time data with daily intervals', () => {
45
+ const bars = [
46
+ {
47
+ label: 'Success',
48
+ data: [
49
+ [new Date('2024-07-05T00:00:00'), 10],
50
+ [new Date('2024-07-06T00:00:00'), 20],
51
+ ] as [Date, number][],
52
+ },
53
+ ];
54
+
55
+ const type = {
56
+ type: 'time' as const,
57
+ timeRange: {
58
+ startDate: new Date('2024-07-05T00:00:00'),
59
+ endDate: new Date('2024-07-06T00:00:00'),
60
+ interval: 24 * 60 * 60 * 1000, // 1 day
61
+ },
62
+ };
63
+
64
+ const barDataKeys = ['Success'];
65
+
66
+ const result = transformTimeData(bars, type, barDataKeys);
67
+
68
+ expect(result).toEqual([
69
+ { category: 'Fri05Jul', Success: 10 },
70
+ { category: 'Sat06Jul', Success: 20 },
71
+ ]);
72
+ });
73
+
74
+ it('should create empty time slots when data is missing', () => {
75
+ const bars = [
76
+ {
77
+ label: 'Success',
78
+ data: [
79
+ [new Date('2024-07-05T00:00:00'), 10],
80
+ // Missing July 6th
81
+ [new Date('2024-07-07T00:00:00'), 30],
82
+ ] as [Date, number][],
83
+ },
84
+ ];
85
+
86
+ const type = {
87
+ type: 'time' as const,
88
+ timeRange: {
89
+ startDate: new Date('2024-07-05T00:00:00'),
90
+ endDate: new Date('2024-07-07T00:00:00'),
91
+ interval: 24 * 60 * 60 * 1000, // 1 day
92
+ },
93
+ };
94
+
95
+ const barDataKeys = ['Success'];
96
+
97
+ const result = transformTimeData(bars, type, barDataKeys);
98
+
99
+ expect(result).toEqual([
100
+ { category: 'Fri05Jul', Success: 10 },
101
+ { category: 'Sat06Jul', Success: 0 }, // Missing data filled with 0
102
+ { category: 'Sun07Jul', Success: 30 },
103
+ ]);
104
+ });
105
+
106
+ it('should handle hourly intervals correctly', () => {
107
+ const bars = [
108
+ {
109
+ label: 'Success',
110
+ data: [
111
+ [new Date('2024-07-05T10:00:00'), 10],
112
+ [new Date('2024-07-05T11:00:00'), 20],
113
+ ] as [Date, number][],
114
+ },
115
+ ];
116
+
117
+ const type = {
118
+ type: 'time' as const,
119
+ timeRange: {
120
+ startDate: new Date('2024-07-05T10:00:00'),
121
+ endDate: new Date('2024-07-05T11:00:00'),
122
+ interval: 60 * 60 * 1000, // 1 hour
123
+ },
124
+ };
125
+
126
+ const barDataKeys = ['Success'];
127
+
128
+ const result = transformTimeData(bars, type, barDataKeys);
129
+
130
+ expect(result).toEqual([
131
+ { category: '10:00', Success: 10 },
132
+ { category: '11:00', Success: 20 },
133
+ ]);
134
+ });
135
+
136
+ it('should use latest value when multiple events occur in same time period', () => {
137
+ const bars = [
138
+ {
139
+ label: 'Success',
140
+ data: [
141
+ [new Date('2024-07-05T08:30:00'), 10], // 8:30 AM
142
+ [new Date('2024-07-05T14:45:00'), 25], // 2:45 PM (should overwrite 8:30 AM)
143
+ [new Date('2024-07-06T09:15:00'), 15], // Next day
144
+ ] as [Date, number][],
145
+ },
146
+ ];
147
+
148
+ const type = {
149
+ type: 'time' as const,
150
+ timeRange: {
151
+ startDate: new Date('2024-07-05T00:00:00'),
152
+ endDate: new Date('2024-07-06T00:00:00'),
153
+ interval: 24 * 60 * 60 * 1000, // 1 day
154
+ },
155
+ };
156
+
157
+ const barDataKeys = ['Success'];
158
+
159
+ const result = transformTimeData(bars, type, barDataKeys);
160
+
161
+ expect(result).toEqual([
162
+ { category: 'Fri05Jul', Success: 25 }, // Last value for July 5th
163
+ { category: 'Sat06Jul', Success: 15 }, // July 6th value
164
+ ]);
165
+ });
166
+
167
+ it('should preserve chronological order regardless of input order', () => {
168
+ const bars = [
169
+ {
170
+ label: 'Success',
171
+ data: [
172
+ [new Date('2024-07-07T10:00:00'), 30], // July 7th (latest)
173
+ [new Date('2024-07-05T08:00:00'), 10], // July 5th (earliest)
174
+ [new Date('2024-07-06T14:00:00'), 20], // July 6th (middle)
175
+ ] as [Date, number][],
176
+ },
177
+ ];
178
+
179
+ const type = {
180
+ type: 'time' as const,
181
+ timeRange: {
182
+ startDate: new Date('2024-07-05T00:00:00'),
183
+ endDate: new Date('2024-07-07T00:00:00'),
184
+ interval: 24 * 60 * 60 * 1000, // 1 day
185
+ },
186
+ };
187
+
188
+ const barDataKeys = ['Success'];
189
+
190
+ const result = transformTimeData(bars, type, barDataKeys);
191
+
192
+ // Should be in chronological order regardless of input order
193
+ expect(result).toEqual([
194
+ { category: 'Fri05Jul', Success: 10 },
195
+ { category: 'Sat06Jul', Success: 20 },
196
+ { category: 'Sun07Jul', Success: 30 },
197
+ ]);
198
+ });
199
+
200
+ it('should display multiple metrics with different time coverage', () => {
201
+ const bars = [
202
+ {
203
+ label: 'Success',
204
+ data: [[new Date('2024-07-05T00:00:00'), 10]] as [Date, number][],
205
+ },
206
+ {
207
+ label: 'Failed',
208
+ data: [[new Date('2024-07-06T00:00:00'), 5]] as [Date, number][],
209
+ },
210
+ ];
211
+
212
+ const type = {
213
+ type: 'time' as const,
214
+ timeRange: {
215
+ startDate: new Date('2024-07-05T00:00:00'),
216
+ endDate: new Date('2024-07-06T00:00:00'),
217
+ interval: 24 * 60 * 60 * 1000, // 1 day
218
+ },
219
+ };
220
+
221
+ const barDataKeys = ['Success', 'Failed'];
222
+
223
+ const result = transformTimeData(bars, type, barDataKeys);
224
+
225
+ expect(result).toEqual([
226
+ { category: 'Fri05Jul', Success: 10, Failed: 0 },
227
+ { category: 'Sat06Jul', Success: 0, Failed: 5 },
228
+ ]);
229
+ });
230
+ });
231
+
232
+ describe('transformCategoryData', () => {
233
+ it('should transform basic category data correctly', () => {
234
+ const bars = [
235
+ {
236
+ label: 'Success',
237
+ data: [
238
+ ['category1', 10],
239
+ ['category2', 20],
240
+ ] as [string, number][],
241
+ },
242
+ {
243
+ label: 'Failed',
244
+ data: [
245
+ ['category1', 5],
246
+ ['category2', 15],
247
+ ] as [string, number][],
248
+ },
249
+ ];
250
+
251
+ const barDataKeys = ['Success', 'Failed'];
252
+
253
+ const result = transformCategoryData(bars, barDataKeys);
254
+
255
+ expect(result).toEqual([
256
+ { category: 'category1', Success: 10, Failed: 5 },
257
+ { category: 'category2', Success: 20, Failed: 15 },
258
+ ]);
259
+ });
260
+
261
+ it('should show zero for categories missing in specific metrics', () => {
262
+ const bars = [
263
+ {
264
+ label: 'Success',
265
+ data: [
266
+ ['category1', 10],
267
+ ['category3', 30],
268
+ ] as [string, number][],
269
+ },
270
+ {
271
+ label: 'Failed',
272
+ data: [
273
+ ['category2', 20],
274
+ ['category3', 40],
275
+ ] as [string, number][],
276
+ },
277
+ ];
278
+
279
+ const barDataKeys = ['Success', 'Failed'];
280
+
281
+ const result = transformCategoryData(bars, barDataKeys);
282
+
283
+ expect(result).toEqual([
284
+ { category: 'category1', Success: 10, Failed: 0 }, // Failed missing, filled with 0
285
+ { category: 'category3', Success: 30, Failed: 40 }, // Appears in both bars
286
+ { category: 'category2', Success: 0, Failed: 20 }, // Success missing, filled with 0
287
+ ]);
288
+ });
289
+
290
+ it('should handle single bar data', () => {
291
+ const bars = [
292
+ {
293
+ label: 'Metric',
294
+ data: [
295
+ ['A', 100],
296
+ ['B', 200],
297
+ ['C', 300],
298
+ ] as [string, number][],
299
+ },
300
+ ];
301
+
302
+ const barDataKeys = ['Metric'];
303
+
304
+ const result = transformCategoryData(bars, barDataKeys);
305
+
306
+ expect(result).toEqual([
307
+ { category: 'A', Metric: 100 },
308
+ { category: 'B', Metric: 200 },
309
+ { category: 'C', Metric: 300 },
310
+ ]);
311
+ });
312
+
313
+ it('should convert category keys to strings', () => {
314
+ const bars = [
315
+ {
316
+ label: 'Count',
317
+ data: [
318
+ [123, 10], // Number key
319
+ ['text', 20], // String key
320
+ [null, 30], // Null key
321
+ ] as [any, number][],
322
+ },
323
+ ];
324
+
325
+ const barDataKeys = ['Count'];
326
+
327
+ const result = transformCategoryData(bars, barDataKeys);
328
+
329
+ expect(result).toEqual([
330
+ { category: '123', Count: 10 },
331
+ { category: 'text', Count: 20 },
332
+ { category: 'null', Count: 30 },
333
+ ]);
334
+ });
335
+
336
+ it('should handle empty data gracefully', () => {
337
+ const bars = [
338
+ {
339
+ label: 'Empty',
340
+ data: [] as [string, number][],
341
+ },
342
+ ];
343
+
344
+ const barDataKeys = ['Empty'];
345
+
346
+ const result = transformCategoryData(bars, barDataKeys);
347
+
348
+ expect(result).toEqual([]);
349
+ });
350
+ });
351
+
352
+ describe('applySortingToData', () => {
353
+ it('should apply custom sorting function to data', () => {
354
+ const data = [
355
+ { category: 'category1', Success: 50 },
356
+ { category: 'category2', Success: 20 },
357
+ { category: 'category3', Success: 30 },
358
+ { category: 'category4', Success: 40 },
359
+ ];
360
+
361
+ const barDataKeys = ['Success'];
362
+
363
+ // Sort by Success value ascending
364
+ const defaultSort = (pointA: any, pointB: any) => {
365
+ return pointA.Success - pointB.Success > 0 ? 1 : -1;
366
+ };
367
+
368
+ const result = applySortingToData(data, barDataKeys, defaultSort);
369
+
370
+ expect(result).toEqual([
371
+ { category: 'category2', Success: 20 },
372
+ { category: 'category3', Success: 30 },
373
+ { category: 'category4', Success: 40 },
374
+ { category: 'category1', Success: 50 },
375
+ ]);
376
+ });
377
+
378
+ it('should sort categories based on combined metric values', () => {
379
+ const data = [
380
+ { category: 'A', Bar1: 10, Bar2: 50 },
381
+ { category: 'B', Bar1: 30, Bar2: 20 },
382
+ { category: 'C', Bar1: 20, Bar2: 40 },
383
+ ];
384
+
385
+ const barDataKeys = ['Bar1', 'Bar2'];
386
+
387
+ // Sort by sum of Bar1 and Bar2
388
+ const defaultSort = (pointA: any, pointB: any) => {
389
+ const sumA = pointA.Bar1 + pointA.Bar2;
390
+ const sumB = pointB.Bar1 + pointB.Bar2;
391
+ return sumA < sumB ? -1 : sumA > sumB ? 1 : 0;
392
+ };
393
+
394
+ const result = applySortingToData(data, barDataKeys, defaultSort);
395
+
396
+ expect(result).toEqual([
397
+ { category: 'B', Bar1: 30, Bar2: 20 }, // Sum: 50
398
+ { category: 'A', Bar1: 10, Bar2: 50 }, // Sum: 60
399
+ { category: 'C', Bar1: 20, Bar2: 40 }, // Sum: 60
400
+ ]);
401
+ });
402
+
403
+ it('should sort text numbers as numeric values', () => {
404
+ const data = [
405
+ { category: 'A', Value: '100' },
406
+ { category: 'B', Value: '50' },
407
+ { category: 'C', Value: '75' },
408
+ ];
409
+
410
+ const barDataKeys = ['Value'];
411
+
412
+ // Sort by numeric value descending
413
+ const defaultSort = (pointA: any, pointB: any) => {
414
+ return pointB.Value > pointA.Value
415
+ ? 1
416
+ : pointB.Value < pointA.Value
417
+ ? -1
418
+ : 0;
419
+ };
420
+
421
+ const result = applySortingToData(data, barDataKeys, defaultSort);
422
+
423
+ expect(result).toEqual([
424
+ { category: 'A', Value: 100 }, // Converted to number and sorted
425
+ { category: 'C', Value: 75 },
426
+ { category: 'B', Value: 50 },
427
+ ]);
428
+ });
429
+
430
+ it('should treat invalid data as zero when sorting', () => {
431
+ const data = [
432
+ { category: 'A', Value: 30 },
433
+ { category: 'B', Value: 0 }, // Test with 0 value
434
+ { category: 'C', Value: 'NaN' }, // String that will become NaN
435
+ { category: 'D', Value: 10 },
436
+ ];
437
+
438
+ const barDataKeys = ['Value'];
439
+
440
+ // Sort by value ascending
441
+ const defaultSort = (pointA: any, pointB: any) => {
442
+ return pointA.Value < pointB.Value
443
+ ? -1
444
+ : pointA.Value > pointB.Value
445
+ ? 1
446
+ : 0;
447
+ };
448
+
449
+ const result = applySortingToData(data, barDataKeys, defaultSort);
450
+
451
+ expect(result).toEqual([
452
+ { category: 'B', Value: 0 }, // 0 value
453
+ { category: 'C', Value: 0 }, // 'NaN' converted to 0 (NaN becomes 0)
454
+ { category: 'D', Value: 10 },
455
+ { category: 'A', Value: 30 },
456
+ ]);
457
+ });
458
+
459
+ it('should preserve category data during sorting', () => {
460
+ const data = [
461
+ { category: 'Category Z', Metric: 1 },
462
+ { category: 'Category A', Metric: 3 },
463
+ { category: 'Category M', Metric: 2 },
464
+ ];
465
+
466
+ const barDataKeys = ['Metric'];
467
+
468
+ // Sort by category name alphabetically
469
+ const defaultSort = (pointA: any, pointB: any) => {
470
+ return pointA.category.localeCompare(pointB.category);
471
+ };
472
+
473
+ const result = applySortingToData(data, barDataKeys, defaultSort);
474
+
475
+ expect(result).toEqual([
476
+ { category: 'Category A', Metric: 3 },
477
+ { category: 'Category M', Metric: 2 },
478
+ { category: 'Category Z', Metric: 1 },
479
+ ]);
480
+ });
481
+ });
482
+
483
+ describe('getRoundReferenceValue', () => {
484
+ it('should return appropriate rounded values', () => {
485
+ expect(getRoundReferenceValue(1)).toBe(1);
486
+ expect(getRoundReferenceValue(2)).toBe(2.5);
487
+ expect(getRoundReferenceValue(3)).toBe(5);
488
+ expect(getRoundReferenceValue(7)).toBe(10);
489
+ expect(getRoundReferenceValue(15)).toBe(25);
490
+ expect(getRoundReferenceValue(35)).toBe(50);
491
+ expect(getRoundReferenceValue(75)).toBe(100);
492
+ expect(getRoundReferenceValue(150)).toBe(250);
493
+ expect(getRoundReferenceValue(350)).toBe(500);
494
+ expect(getRoundReferenceValue(750)).toBe(1000);
495
+ expect(getRoundReferenceValue(1500)).toBe(2500);
496
+ expect(getRoundReferenceValue(3500)).toBe(5000);
497
+ expect(getRoundReferenceValue(7500)).toBe(10000);
498
+ expect(getRoundReferenceValue(15000)).toBe(25000);
499
+ });
500
+ });
501
+
502
+ describe('getMaxBarValue', () => {
503
+ it('should return the maximum value from chart data', () => {
504
+ const data = [
505
+ { category: 'A', value1: 10, value2: 5 },
506
+ { category: 'B', value1: 20, value2: 15 },
507
+ { category: 'C', value1: 8, value2: 25 },
508
+ ];
509
+ expect(getMaxBarValue(data)).toBe(25);
510
+ });
511
+
512
+ it('should handle single value data', () => {
513
+ const data = [{ category: 'A', value: 42 }];
514
+ expect(getMaxBarValue(data)).toBe(42);
515
+ });
516
+
517
+ it('should return the maximum value from stacked data', () => {
518
+ const data = [
519
+ { category: 'A', value1: 10, value2: 5 },
520
+ { category: 'B', value1: 20, value2: 15 },
521
+ { category: 'C', value1: 8, value2: 25 },
522
+ ];
523
+ expect(getMaxBarValue(data, true)).toBe(35);
524
+ });
525
+ });
526
+
527
+ describe('formatPrometheusDataToRechartsDataAndBars', () => {
528
+ it('should format category data correctly', () => {
529
+ const bars = [
530
+ {
531
+ label: 'Success',
532
+ data: [
533
+ ['A', 10],
534
+ ['B', 20],
535
+ ] as [string, number][],
536
+ color: 'green',
537
+ },
538
+ {
539
+ label: 'Failed',
540
+ data: [
541
+ ['A', 5],
542
+ ['B', 15],
543
+ ] as [string, number][],
544
+ color: 'red',
545
+ },
546
+ ];
547
+
548
+ const result = formatPrometheusDataToRechartsDataAndBars(
549
+ bars,
550
+ 'category',
551
+ {
552
+ Success: coreUIAvailableThemes.darkRebrand.statusHealthy,
553
+ Failed: coreUIAvailableThemes.darkRebrand.statusCritical,
554
+ },
555
+ true, // stacked
556
+ );
557
+
558
+ // Should integrate: data transformation + color formatting + stacked sorting
559
+ expect(result.data).toEqual([
560
+ { category: 'A', Success: 10, Failed: 5 },
561
+ { category: 'B', Success: 20, Failed: 15 },
562
+ ]);
563
+ expect(result.rechartsBars).toEqual([
564
+ { dataKey: 'Success', fill: '#0AADA6', stackId: 'stacked' }, // statusHealthy = '#0AADA6'
565
+ { dataKey: 'Failed', fill: '#E84855', stackId: 'stacked' }, // statusCritical = '#E84855'
566
+ ]);
567
+ });
568
+
569
+ it('should format time data correctly', () => {
570
+ const bars = [
571
+ {
572
+ label: 'Success Count',
573
+ data: [[new Date('2024-07-05T00:00:00'), 10]] as [Date, number][],
574
+ },
575
+ {
576
+ label: 'Failed Count',
577
+ data: [[new Date('2024-07-05T00:00:00'), 5]] as [Date, number][],
578
+ },
579
+ ];
580
+
581
+ const result = formatPrometheusDataToRechartsDataAndBars(
582
+ bars,
583
+ {
584
+ type: 'time',
585
+ timeRange: {
586
+ startDate: new Date('2024-07-05T00:00:00'),
587
+ endDate: new Date('2024-07-05T00:00:00'),
588
+ interval: 24 * 60 * 60 * 1000,
589
+ },
590
+ },
591
+ {
592
+ 'Success Count': 'lineColor3',
593
+ 'Failed Count': 'blue',
594
+ },
595
+ false,
596
+ undefined,
597
+ );
598
+
599
+ // Should integrate: time transformation + status color assignment
600
+ expect(result.data).toEqual([
601
+ { category: 'Fri05Jul', 'Success Count': 10, 'Failed Count': 5 },
602
+ ]);
603
+ expect(result.rechartsBars).toEqual([
604
+ { dataKey: 'Success Count', fill: '#4BE4E2' }, // lineColor3
605
+ { dataKey: 'Failed Count', fill: 'blue' }, // blue
606
+ ]);
607
+ });
608
+
609
+ it('should integrate custom sorting with category data', () => {
610
+ const bars = [
611
+ {
612
+ label: 'Value',
613
+ data: [
614
+ ['C', 30],
615
+ ['A', 10],
616
+ ['B', 20],
617
+ ] as [string, number][],
618
+ },
619
+ ];
620
+
621
+ const result = formatPrometheusDataToRechartsDataAndBars(
622
+ bars,
623
+ 'category',
624
+ mockTheme,
625
+ false,
626
+ (pointA, pointB) => {
627
+ // Sort alphabetically by category
628
+ return String(pointA.category).localeCompare(
629
+ String(pointB.category),
630
+ ) as -1 | 0 | 1;
631
+ },
632
+ );
633
+
634
+ // Should integrate: category transformation + custom sorting
635
+ expect(result.data.map((item) => item.category)).toEqual(['A', 'B', 'C']);
636
+ });
637
+ });
638
+
639
+ describe('computeUnitLabelAndRoundReferenceValue', () => {
640
+ it('should compute the unit label and round reference value correctly when reaching threshold', () => {
641
+ const data = [
642
+ {
643
+ category: 'category1',
644
+ success: 1680,
645
+ },
646
+ ];
647
+ const maxValue = 1680;
648
+ const unitRange: UnitRange = [
649
+ {
650
+ threshold: 1000,
651
+ label: 'kB',
652
+ },
653
+ ];
654
+ const result = computeUnitLabelAndRoundReferenceValue(
655
+ data,
656
+ maxValue,
657
+ unitRange,
658
+ );
659
+
660
+ expect(result.unitLabel).toBe('kB');
661
+ expect(result.roundReferenceValue).toBe(10);
662
+ expect(result.rechartsData).toEqual([
663
+ {
664
+ category: 'category1',
665
+ success: 1.68,
666
+ },
667
+ ]);
668
+ });
669
+ it('should compute the unit label and round reference value correctly when threshold is 0', () => {
670
+ const data = [
671
+ {
672
+ category: 'category1',
673
+ success: 680,
674
+ },
675
+ ];
676
+ const maxValue = 680;
677
+ const unitRange: UnitRange = [
678
+ {
679
+ threshold: 0,
680
+ label: 'B',
681
+ },
682
+ {
683
+ threshold: 1000,
684
+ label: 'kB',
685
+ },
686
+ ];
687
+ const result = computeUnitLabelAndRoundReferenceValue(
688
+ data,
689
+ maxValue,
690
+ unitRange,
691
+ );
692
+
693
+ expect(result.unitLabel).toBe('B');
694
+ expect(result.roundReferenceValue).toBe(1000);
695
+ expect(result.rechartsData).toEqual([
696
+ { category: 'category1', success: 680 },
697
+ ]);
698
+ });
699
+ });
700
+
701
+ describe('sortStackedBars', () => {
702
+ const bars = [
703
+ { dataKey: 'bar1', fill: 'blue' },
704
+ { dataKey: 'bar2', fill: 'red' },
705
+ { dataKey: 'bar3', fill: 'green' },
706
+ ];
707
+ const data = [
708
+ { bar1: 10, bar2: 20, bar3: 30 },
709
+ { bar1: 40, bar2: 50, bar3: 60 },
710
+ { bar1: 70, bar2: 80, bar3: 90 },
711
+ ];
712
+ it('should sort bars by average values in descending order when stacked is true', () => {
713
+ const result = sortStackedBars(bars, data, true);
714
+ expect(result).toEqual([
715
+ { dataKey: 'bar3', fill: 'green' },
716
+ { dataKey: 'bar2', fill: 'red' },
717
+ { dataKey: 'bar1', fill: 'blue' },
718
+ ]);
719
+ });
720
+ it('should not sort bars when stacked is false', () => {
721
+ const result = sortStackedBars(bars, data, false);
722
+ expect(result).toEqual([
723
+ { dataKey: 'bar1', fill: 'blue' },
724
+ { dataKey: 'bar2', fill: 'red' },
725
+ { dataKey: 'bar3', fill: 'green' },
726
+ ]);
727
+ });
728
+ it('should not sort bars when stacked is undefined', () => {
729
+ const result = sortStackedBars(bars, data, undefined);
730
+ expect(result).toEqual([
731
+ { dataKey: 'bar1', fill: 'blue' },
732
+ { dataKey: 'bar2', fill: 'red' },
733
+ { dataKey: 'bar3', fill: 'green' },
734
+ ]);
735
+ });
736
+ });
737
+
738
+ describe('renderTooltipContent', () => {
739
+ it('should return null when active is false', () => {
740
+ const props = {
741
+ active: false,
742
+ payload: [],
743
+ label: 'test',
744
+ coordinate: { x: 0, y: 0 },
745
+ accessibilityLayer: false,
746
+ };
747
+ const result = renderTooltipContent(props, undefined, undefined);
748
+ expect(result).toBeNull();
749
+ });
750
+
751
+ it('should return null when tooltip is undefined', () => {
752
+ const props = {
753
+ active: true,
754
+ payload: [{ name: 'test', value: 10 }],
755
+ label: 'test',
756
+ coordinate: { x: 0, y: 0 },
757
+ accessibilityLayer: false,
758
+ };
759
+ const result = renderTooltipContent(props, undefined, 'test');
760
+ expect(result).toBeNull();
761
+ });
762
+ it('should call tooltip with the correct props', () => {
763
+ const tooltip = jest.fn();
764
+ const props = {
765
+ active: true,
766
+ payload: [
767
+ { name: 'Success', value: 10 },
768
+ { name: 'Failed', value: 20 },
769
+ ],
770
+ label: 'Test',
771
+ coordinate: { x: 0, y: 0 },
772
+ accessibilityLayer: false,
773
+ };
774
+ renderTooltipContent(props, tooltip, 'Success');
775
+ expect(tooltip).toHaveBeenCalledWith({
776
+ category: 'Test',
777
+ values: [
778
+ { label: 'Success', value: 10, isHovered: true },
779
+ { label: 'Failed', value: 20, isHovered: false },
780
+ ],
781
+ });
782
+ });
783
+ });
784
+
785
+ describe('filterChartDataAndBarsByLegendSelection', () => {
786
+ const mockChartData = [
787
+ { category: 'Jan', Success: 10, Failed: 5, Warning: 3, Pending: 2 },
788
+ { category: 'Feb', Success: 20, Failed: 8, Warning: 6, Pending: 4 },
789
+ { category: 'Mar', Success: 15, Failed: 12, Warning: 9, Pending: 7 },
790
+ ];
791
+
792
+ const mockRechartsBars = [
793
+ { dataKey: 'Success', fill: '#00D100', stackId: undefined },
794
+ { dataKey: 'Failed', fill: '#D10000', stackId: undefined },
795
+ { dataKey: 'Warning', fill: '#FFA500', stackId: 'stacked' },
796
+ { dataKey: 'Pending', fill: '#337FBD', stackId: 'stacked' },
797
+ ];
798
+
799
+ it('should return all data and bars when no resources are selected (empty array)', () => {
800
+ const result = filterChartDataAndBarsByLegendSelection(
801
+ mockChartData,
802
+ mockRechartsBars,
803
+ [],
804
+ );
805
+
806
+ expect(result.filteredData).toEqual(mockChartData);
807
+ expect(result.filteredRechartsBars).toEqual(mockRechartsBars);
808
+ expect(result.filteredData).toHaveLength(3);
809
+ expect(result.filteredRechartsBars).toHaveLength(4);
810
+ // Verify all properties are preserved
811
+ expect(Object.keys(result.filteredData[0])).toEqual([
812
+ 'category',
813
+ 'Success',
814
+ 'Failed',
815
+ 'Warning',
816
+ 'Pending',
817
+ ]);
818
+ });
819
+
820
+ it('should return only selected resources in both data and bars when resources are selected', () => {
821
+ const selectedResources = ['Success', 'Warning'];
822
+ const result = filterChartDataAndBarsByLegendSelection(
823
+ mockChartData,
824
+ mockRechartsBars,
825
+ selectedResources,
826
+ );
827
+
828
+ expect(result.filteredData).toHaveLength(3);
829
+ expect(result.filteredData).toEqual([
830
+ { category: 'Jan', Success: 10, Warning: 3 },
831
+ { category: 'Feb', Success: 20, Warning: 6 },
832
+ { category: 'Mar', Success: 15, Warning: 9 },
833
+ ]);
834
+
835
+ expect(result.filteredRechartsBars).toHaveLength(2);
836
+ expect(result.filteredRechartsBars).toEqual([
837
+ { dataKey: 'Success', fill: '#00D100', stackId: undefined },
838
+ { dataKey: 'Warning', fill: '#FFA500', stackId: 'stacked' },
839
+ ]);
840
+ });
841
+
842
+ it('should return single resource when only one resource is selected', () => {
843
+ const selectedResources = ['Failed'];
844
+ const result = filterChartDataAndBarsByLegendSelection(
845
+ mockChartData,
846
+ mockRechartsBars,
847
+ selectedResources,
848
+ );
849
+
850
+ expect(result.filteredData).toHaveLength(3);
851
+ expect(result.filteredData).toEqual([
852
+ { category: 'Jan', Failed: 5 },
853
+ { category: 'Feb', Failed: 8 },
854
+ { category: 'Mar', Failed: 12 },
855
+ ]);
856
+
857
+ expect(result.filteredRechartsBars).toHaveLength(1);
858
+ expect(result.filteredRechartsBars).toEqual([
859
+ { dataKey: 'Failed', fill: '#D10000', stackId: undefined },
860
+ ]);
861
+ });
862
+
863
+ it('should handle empty data array', () => {
864
+ const result = filterChartDataAndBarsByLegendSelection(
865
+ [],
866
+ mockRechartsBars,
867
+ ['Success'],
868
+ );
869
+
870
+ expect(result.filteredData).toEqual([]);
871
+ expect(result.filteredRechartsBars).toHaveLength(1);
872
+ expect(result.filteredRechartsBars).toEqual([
873
+ { dataKey: 'Success', fill: '#00D100', stackId: undefined },
874
+ ]);
875
+ });
876
+
877
+ it('should preserve order of selected resources based on data object keys', () => {
878
+ const selectedResources = ['Pending', 'Success', 'Failed']; // Different order
879
+ const result = filterChartDataAndBarsByLegendSelection(
880
+ mockChartData,
881
+ mockRechartsBars,
882
+ selectedResources,
883
+ );
884
+
885
+ // Should maintain the order they appear in selectedResources
886
+ expect(Object.keys(result.filteredData[0])).toEqual([
887
+ 'category',
888
+ 'Pending',
889
+ 'Success',
890
+ 'Failed',
891
+ ]);
892
+
893
+ // Should maintain original bar order regardless of selection order
894
+ expect(result.filteredRechartsBars).toHaveLength(3);
895
+ expect(result.filteredRechartsBars[0].dataKey).toBe('Success');
896
+ expect(result.filteredRechartsBars[1].dataKey).toBe('Failed');
897
+ expect(result.filteredRechartsBars[2].dataKey).toBe('Pending');
898
+ });
899
+ });