@centreon/ui 25.10.20 → 25.10.22

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 (34) hide show
  1. package/package.json +1 -1
  2. package/src/Form/Inputs/Grid.tsx +3 -2
  3. package/src/Form/Section/navigateToSection.ts +6 -6
  4. package/src/Graph/BarChart/BarChart.cypress.spec.tsx +19 -0
  5. package/src/Graph/BarChart/BarChart.stories.tsx +51 -1
  6. package/src/Graph/BarChart/BarChart.tsx +1 -1
  7. package/src/Graph/BarChart/BarGroup.tsx +22 -32
  8. package/src/Graph/BarChart/MemoizedGroup.tsx +8 -11
  9. package/src/Graph/BarChart/ResponsiveBarChart.tsx +2 -1
  10. package/src/Graph/Chart/BasicComponents/Lines/StackedLines/useStackedLines.ts +18 -45
  11. package/src/Graph/Chart/BasicComponents/Lines/index.tsx +32 -26
  12. package/src/Graph/Chart/Chart.cypress.spec.tsx +24 -5
  13. package/src/Graph/Chart/Chart.stories.tsx +43 -1
  14. package/src/Graph/Chart/Chart.tsx +3 -2
  15. package/src/Graph/Chart/Legend/index.tsx +26 -2
  16. package/src/Graph/Chart/models.ts +6 -1
  17. package/src/Graph/Chart/useChartData.ts +1 -1
  18. package/src/Graph/common/BaseChart/BaseChart.tsx +6 -1
  19. package/src/Graph/common/timeSeries/index.test.ts +20 -0
  20. package/src/Graph/common/timeSeries/index.ts +121 -11
  21. package/src/Graph/common/timeSeries/models.ts +4 -2
  22. package/src/Graph/common/utils.ts +10 -4
  23. package/src/Graph/mockedData/dataWithMissingPoint.json +74 -0
  24. package/src/Graph/mockedData/pingServiceWithStackedKeys.json +205 -0
  25. package/src/InputField/Select/index.tsx +1 -2
  26. package/src/Module/index.tsx +8 -2
  27. package/src/ThemeProvider/index.tsx +30 -21
  28. package/src/ThemeProvider/tailwindcss.css +10 -10
  29. package/src/components/Layout/PageLayout/PageLayout.tsx +9 -3
  30. package/src/components/Layout/PageLayout/PageLayoutActions.tsx +5 -3
  31. package/src/components/Layout/PageLayout/PageLayoutBody.tsx +5 -3
  32. package/src/components/Layout/PageLayout/PageLayoutHeader.tsx +5 -3
  33. package/src/components/Modal/Modal.styles.ts +1 -2
  34. package/src/components/Modal/ModalHeader.tsx +5 -1
@@ -175,6 +175,11 @@ export interface LegendModel {
175
175
  mode: 'grid' | 'list';
176
176
  placement: 'bottom' | 'left' | 'right';
177
177
  renderExtraComponent?: ReactNode;
178
+ secondaryClick?: (props: {
179
+ element: EventTarget | null;
180
+ metricId: number | string;
181
+ position: [number, number];
182
+ }) => void;
178
183
  }
179
184
 
180
185
  export interface GetDate {
@@ -193,4 +198,4 @@ export interface GraphTooltipData {
193
198
  unit: string;
194
199
  value: number;
195
200
  }>;
196
- }
201
+ }
@@ -40,7 +40,7 @@ const useGraphData = ({ data, end, start }: Props): GraphDataResult => {
40
40
  return data;
41
41
  }
42
42
 
43
- if (isEmpty(data.metrics) || isEmpty(data.times)) {
43
+ if (isEmpty(data.metrics)) {
44
44
  return undefined;
45
45
  }
46
46
 
@@ -19,7 +19,10 @@ interface Props {
19
19
  header?: LineChartHeader;
20
20
  height: number | null;
21
21
  isHorizontal?: boolean;
22
- legend: Pick<LegendModel, 'renderExtraComponent' | 'placement' | 'mode'> & {
22
+ legend: Pick<
23
+ LegendModel,
24
+ 'renderExtraComponent' | 'placement' | 'mode' | 'secondaryClick'
25
+ > & {
23
26
  displayLegend: boolean;
24
27
  legendHeight?: number;
25
28
  };
@@ -101,6 +104,7 @@ const BaseChart = ({
101
104
  shouldDisplayLegendInCompactMode={
102
105
  shouldDisplayLegendInCompactMode
103
106
  }
107
+ secondaryClick={legend?.secondaryClick}
104
108
  />
105
109
  </div>
106
110
  )}
@@ -122,6 +126,7 @@ const BaseChart = ({
122
126
  renderExtraComponent={legend.renderExtraComponent}
123
127
  setLinesGraph={setLines}
124
128
  shouldDisplayLegendInCompactMode={shouldDisplayLegendInCompactMode}
129
+ secondaryClick={legend?.secondaryClick}
125
130
  />
126
131
  </div>
127
132
  )}
@@ -185,6 +185,7 @@ describe('timeSeries', () => {
185
185
  average_value: 1,
186
186
  color: 'black',
187
187
  display: true,
188
+ displayAs: undefined,
188
189
  filled: false,
189
190
  highlight: undefined,
190
191
  invert: null,
@@ -195,6 +196,7 @@ describe('timeSeries', () => {
195
196
  metric_id: 1,
196
197
  minimum_value: 0.5,
197
198
  name: 'Round-Trip-Time Average (ms)',
199
+ stackKey: null,
198
200
  stackOrder: null,
199
201
  transparency: 80,
200
202
  unit: 'ms'
@@ -204,6 +206,7 @@ describe('timeSeries', () => {
204
206
  average_value: 1,
205
207
  color: 'blue',
206
208
  display: true,
209
+ displayAs: undefined,
207
210
  filled: true,
208
211
  highlight: undefined,
209
212
  invert: null,
@@ -214,6 +217,7 @@ describe('timeSeries', () => {
214
217
  metric_id: 2,
215
218
  minimum_value: 0.5,
216
219
  name: 'Time (ms)',
220
+ stackKey: null,
217
221
  stackOrder: null,
218
222
  transparency: 80,
219
223
  unit: 'ms'
@@ -223,6 +227,7 @@ describe('timeSeries', () => {
223
227
  average_value: 1,
224
228
  color: 'red',
225
229
  display: true,
230
+ displayAs: undefined,
226
231
  filled: true,
227
232
  highlight: undefined,
228
233
  invert: null,
@@ -233,6 +238,7 @@ describe('timeSeries', () => {
233
238
  metric_id: 3,
234
239
  minimum_value: 0.5,
235
240
  name: 'Average duration (ms)',
241
+ stackKey: null,
236
242
  stackOrder: 2,
237
243
  transparency: 80,
238
244
  unit: 'ms'
@@ -242,6 +248,7 @@ describe('timeSeries', () => {
242
248
  average_value: 1,
243
249
  color: 'yellow',
244
250
  display: true,
251
+ displayAs: undefined,
245
252
  filled: true,
246
253
  highlight: undefined,
247
254
  invert: '1',
@@ -252,6 +259,7 @@ describe('timeSeries', () => {
252
259
  metric_id: 4,
253
260
  minimum_value: 0.5,
254
261
  name: 'Duration (ms)',
262
+ stackKey: null,
255
263
  stackOrder: 1,
256
264
  transparency: 80,
257
265
  unit: 'ms'
@@ -261,6 +269,7 @@ describe('timeSeries', () => {
261
269
  average_value: 1,
262
270
  color: 'yellow',
263
271
  display: true,
272
+ displayAs: undefined,
264
273
  filled: true,
265
274
  highlight: undefined,
266
275
  invert: null,
@@ -271,6 +280,7 @@ describe('timeSeries', () => {
271
280
  metric_id: 5,
272
281
  minimum_value: 0.5,
273
282
  name: 'Packet Loss (%)',
283
+ stackKey: null,
274
284
  stackOrder: null,
275
285
  transparency: 80,
276
286
  unit: '%'
@@ -331,6 +341,7 @@ describe('timeSeries', () => {
331
341
  average_value: 1,
332
342
  color: 'black',
333
343
  display: true,
344
+ displayAs: undefined,
334
345
  filled: false,
335
346
  highlight: undefined,
336
347
  invert: null,
@@ -341,6 +352,7 @@ describe('timeSeries', () => {
341
352
  metric_id: 1,
342
353
  minimum_value: 0.5,
343
354
  name: 'Round-Trip-Time Average (ms)',
355
+ stackKey: null,
344
356
  stackOrder: null,
345
357
  transparency: 80,
346
358
  unit: 'ms'
@@ -389,6 +401,7 @@ describe('timeSeries', () => {
389
401
  average_value: 1,
390
402
  color: 'yellow',
391
403
  display: true,
404
+ displayAs: undefined,
392
405
  filled: true,
393
406
  highlight: undefined,
394
407
  invert: '1',
@@ -399,6 +412,7 @@ describe('timeSeries', () => {
399
412
  metric_id: 4,
400
413
  minimum_value: 0.5,
401
414
  name: 'Duration (ms)',
415
+ stackKey: null,
402
416
  stackOrder: 1,
403
417
  transparency: 80,
404
418
  unit: 'ms'
@@ -408,6 +422,7 @@ describe('timeSeries', () => {
408
422
  average_value: 1,
409
423
  color: 'red',
410
424
  display: true,
425
+ displayAs: undefined,
411
426
  filled: true,
412
427
  highlight: undefined,
413
428
  invert: null,
@@ -418,6 +433,7 @@ describe('timeSeries', () => {
418
433
  metric_id: 3,
419
434
  minimum_value: 0.5,
420
435
  name: 'Average duration (ms)',
436
+ stackKey: null,
421
437
  stackOrder: 2,
422
438
  transparency: 80,
423
439
  unit: 'ms'
@@ -475,6 +491,7 @@ describe('timeSeries', () => {
475
491
  average_value: 1,
476
492
  color: 'yellow',
477
493
  display: true,
494
+ displayAs: undefined,
478
495
  filled: true,
479
496
  highlight: undefined,
480
497
  invert: '1',
@@ -485,6 +502,7 @@ describe('timeSeries', () => {
485
502
  metric_id: 4,
486
503
  minimum_value: 0.5,
487
504
  name: 'Duration (ms)',
505
+ stackKey: null,
488
506
  stackOrder: 1,
489
507
  transparency: 80,
490
508
  unit: 'ms'
@@ -503,6 +521,7 @@ describe('timeSeries', () => {
503
521
  average_value: 1,
504
522
  color: 'red',
505
523
  display: true,
524
+ displayAs: undefined,
506
525
  filled: true,
507
526
  highlight: undefined,
508
527
  invert: null,
@@ -513,6 +532,7 @@ describe('timeSeries', () => {
513
532
  metric_id: 3,
514
533
  minimum_value: 0.5,
515
534
  name: 'Average duration (ms)',
535
+ stackKey: null,
516
536
  stackOrder: 2,
517
537
  transparency: 80,
518
538
  unit: 'ms'
@@ -26,6 +26,7 @@ import {
26
26
  map,
27
27
  negate,
28
28
  pipe,
29
+ pluck,
29
30
  prop,
30
31
  propEq,
31
32
  reduce,
@@ -72,7 +73,7 @@ const toTimeTickValue = (
72
73
  const getMetricsForIndex = (): Omit<TimeValue, 'timeTick'> => {
73
74
  const addMetricForTimeIndex = (acc, { metric_id, data }): TimeValue => ({
74
75
  ...acc,
75
- [metric_id]: data[timeIndex]
76
+ [metric_id]: data[timeIndex] === undefined ? null : data[timeIndex]
76
77
  });
77
78
 
78
79
  return reduce(addMetricForTimeIndex, {} as TimeValue, metrics);
@@ -139,6 +140,7 @@ const toLine = ({
139
140
  equals(ds_data.ds_stack, '1') || equals(ds_data.ds_stack, true)
140
141
  ? Number.parseInt(ds_data.ds_order || '0', 10)
141
142
  : null,
143
+ stackKey: ds_data.ds_stack_key || null,
142
144
  transparency: ds_data.ds_transparency,
143
145
  unit
144
146
  });
@@ -240,7 +242,10 @@ const getStackedMetricValues = ({
240
242
  timeSeries
241
243
  }: LinesTimeSeries): Array<number> => {
242
244
  const getTimeSeriesValuesForMetric = (metric_id): Array<number> =>
243
- map((timeValue) => getValueForMetric(timeValue)(metric_id), timeSeries);
245
+ map(
246
+ (timeValue) => getValueForMetric(timeValue)(metric_id) || 0,
247
+ timeSeries
248
+ );
244
249
 
245
250
  const metricsValues = pipe(
246
251
  map(prop('metric_id')) as (metric) => Array<number>,
@@ -593,15 +598,15 @@ const getYScalePerUnit = ({
593
598
  return scalePerUnit;
594
599
  };
595
600
 
596
- const formatTime = (value: number): string => {
597
- return `${numeral(value).format('0.[00]a')} ms`;
601
+ const formatTime = ({ value, unit }): string => {
602
+ return `${numeral(value).format('0.[00]a')} ${unit}`;
598
603
  };
599
604
 
600
605
  const registerMsUnitToNumeral = (): null => {
601
606
  try {
602
607
  numeral.register('format', 'milliseconds', {
603
608
  format: (value) => {
604
- return formatTime(value);
609
+ return formatTime({ value, unit: 'ms' });
605
610
  },
606
611
  regexps: {
607
612
  format: /(ms)/,
@@ -618,6 +623,27 @@ const registerMsUnitToNumeral = (): null => {
618
623
 
619
624
  registerMsUnitToNumeral();
620
625
 
626
+ const registerSecondsUnitToNumeral = (): null => {
627
+ try {
628
+ numeral.register('format', 'seconds', {
629
+ format: (value) => {
630
+ return formatTime({ value, unit: 's' });
631
+ },
632
+ regexps: {
633
+ format: /(s)/,
634
+ unformat: /(s)/
635
+ },
636
+ unformat: () => ''
637
+ });
638
+
639
+ return null;
640
+ } catch (_) {
641
+ return null;
642
+ }
643
+ };
644
+
645
+ registerSecondsUnitToNumeral();
646
+
621
647
  const getBase1024 = ({ unit, base }): boolean => {
622
648
  const base2Units = [
623
649
  'B',
@@ -647,6 +673,7 @@ const formatMetricValue = ({
647
673
 
648
674
  const formatSuffix = cond([
649
675
  [equals('ms'), always(' ms')],
676
+ [equals('s'), always(' s')],
650
677
  [T, always(base1024 ? ' ib' : 'a')]
651
678
  ])(unit);
652
679
 
@@ -671,8 +698,6 @@ const formatMetricValueWithUnit = ({
671
698
  return null;
672
699
  }
673
700
 
674
- const base1024 = getBase1024({ base, unit });
675
-
676
701
  if (isRaw) {
677
702
  const unitText = equals('%', unit) ? unit : ` ${unit}`;
678
703
 
@@ -685,9 +710,7 @@ const formatMetricValueWithUnit = ({
685
710
 
686
711
  const formattedMetricValue = formatMetricValue({ base, unit, value });
687
712
 
688
- return base1024 || !unit || equals(unit, 'ms')
689
- ? formattedMetricValue
690
- : `${formattedMetricValue} ${unit}`;
713
+ return formattedMetricValue;
691
714
  };
692
715
 
693
716
  const bisectDate = bisector(identity).center;
@@ -737,6 +760,93 @@ export const formatMetricName = ({
737
760
  return metricName;
738
761
  };
739
762
 
763
+ export const getStackedLinesTimeSeriesPerStackAndUnit = ({
764
+ stackedLines,
765
+ timeSeries,
766
+ invert
767
+ }: {
768
+ stackedLines: Array<Line>;
769
+ timeSeries: Array<TimeValue>;
770
+ invert?: boolean;
771
+ }): {
772
+ stackedLinesTimeSeriesPerStackKeyAndUnit: Record<
773
+ string,
774
+ { lines: Array<Line>; timeSeries: Array<TimeValue> }
775
+ >;
776
+ stackedKeys: Record<string, null>;
777
+ } => {
778
+ const stackedKeys = stackedLines.reduce(
779
+ (acc, { unit, stackKey }) => ({
780
+ ...acc,
781
+ [`stacked-${unit || ''}-${stackKey ? stackKey : ''}`]: null
782
+ }),
783
+ {}
784
+ );
785
+ const stackedKeysWithOnlyStackKey = Object.keys(stackedKeys).filter(
786
+ (stackKey: string) => stackKey.split('-')[2]
787
+ );
788
+ const stackedKeysWithOnlyUnit = Object.keys(stackedKeys).filter(
789
+ (stackKey: string) => !stackKey.split('-')[2]
790
+ );
791
+
792
+ const stackedLinesTimeSeriesPerStackKey = stackedKeysWithOnlyStackKey.reduce(
793
+ (acc, stackedKey: string) => {
794
+ const [, stackUnit, stackKey] = stackedKey.split('-');
795
+ const relatedLines = stackedLines.filter(({ unit, stackKey: key }) => {
796
+ return stackUnit === (unit || '') && stackKey === key;
797
+ });
798
+
799
+ return {
800
+ ...acc,
801
+ [stackedKey]: {
802
+ lines: relatedLines,
803
+ timeSeries: getTimeSeriesForLines({
804
+ invert,
805
+ lines: relatedLines,
806
+ timeSeries
807
+ })
808
+ }
809
+ };
810
+ },
811
+ {}
812
+ );
813
+ const affectedLinesPerStackKey = flatten(
814
+ pluck('lines', Object.values(stackedLinesTimeSeriesPerStackKey))
815
+ );
816
+ const stackedLinesTimeSeriesPerUnit = stackedKeysWithOnlyUnit.reduce(
817
+ (acc, stackedKey: string) => {
818
+ const [, stackUnit] = stackedKey.split('-');
819
+ const relatedLines = stackedLines.filter(
820
+ (line) =>
821
+ !affectedLinesPerStackKey.some(
822
+ (affectedLine) => line.metric_id === affectedLine.metric_id
823
+ ) && stackUnit === (line.unit || '')
824
+ );
825
+
826
+ return {
827
+ ...acc,
828
+ [stackedKey]: {
829
+ lines: relatedLines,
830
+ timeSeries: getTimeSeriesForLines({
831
+ lines: relatedLines,
832
+ timeSeries,
833
+ invert
834
+ })
835
+ }
836
+ };
837
+ },
838
+ {}
839
+ );
840
+
841
+ return {
842
+ stackedLinesTimeSeriesPerStackKeyAndUnit: {
843
+ ...stackedLinesTimeSeriesPerStackKey,
844
+ ...stackedLinesTimeSeriesPerUnit
845
+ },
846
+ stackedKeys
847
+ };
848
+ };
849
+
740
850
  export {
741
851
  getTimeSeries,
742
852
  getLineData,
@@ -766,4 +876,4 @@ export {
766
876
  formatMetricValueWithUnit,
767
877
  getYScaleUnit,
768
878
  getYScalePerUnit
769
- };
879
+ };
@@ -9,7 +9,8 @@ interface DsData {
9
9
  ds_invert: string | null;
10
10
  ds_legend: string | null;
11
11
  ds_order: string | null;
12
- ds_stack: string | null;
12
+ ds_stack: string | boolean | null;
13
+ ds_stack_key?: string | null;
13
14
  ds_transparency: number;
14
15
  }
15
16
 
@@ -20,7 +21,7 @@ export interface Metric {
20
21
  critical_low_threshold: number | null;
21
22
  data: Array<number | null>;
22
23
  displayAs?: 'line' | 'bar';
23
- ds_data?: DsData;
24
+ ds_data: DsData;
24
25
  legend: string;
25
26
  maximum_value: number | null;
26
27
  metric: string;
@@ -56,6 +57,7 @@ export interface Line {
56
57
  minimum_value: number | null;
57
58
  name: string;
58
59
  stackOrder: number | null;
60
+ stackKey: string | null;
59
61
  transparency: number;
60
62
  unit: string;
61
63
  }
@@ -25,7 +25,7 @@ import { BarStyle } from '../BarChart/models';
25
25
  import { margin } from '../Chart/common';
26
26
  import { LineStyle } from '../Chart/models';
27
27
  import { Threshold, Thresholds } from './models';
28
- import { formatMetricValue } from './timeSeries';
28
+ import { formatMetricValueWithUnit } from './timeSeries';
29
29
  import { Line, TimeValue } from './timeSeries/models';
30
30
 
31
31
  interface GetColorFromDataAndThresholdsProps {
@@ -234,7 +234,7 @@ export const getFormattedAxisValues = ({
234
234
 
235
235
  const formattedData = metricIds.map((metricId) =>
236
236
  timeSeries.map((data) =>
237
- formatMetricValue({
237
+ formatMetricValueWithUnit({
238
238
  value: data[metricId],
239
239
  unit: axisUnit,
240
240
  base
@@ -246,7 +246,7 @@ export const getFormattedAxisValues = ({
246
246
 
247
247
  const formattedThresholdValues = equals(thresholdUnit, axisUnit)
248
248
  ? threshold.map(({ value }) =>
249
- formatMetricValue({
249
+ formatMetricValueWithUnit({
250
250
  value,
251
251
  unit: axisUnit,
252
252
  base
@@ -273,5 +273,11 @@ export const computeGElementMarginLeft = ({
273
273
  export const computPixelsToShiftMouse = (xScale): number => {
274
274
  const domain = xScale.domain();
275
275
 
276
- return Math.round(8 / dayjs(domain[1]).diff(domain[0], 'h'));
276
+ const hoursDiffInGraph = dayjs(domain[1]).diff(domain[0], 'h');
277
+
278
+ if (!hoursDiffInGraph) {
279
+ return 0;
280
+ }
281
+
282
+ return Math.round(8 / hoursDiffInGraph);
277
283
  };
@@ -0,0 +1,74 @@
1
+ {
2
+ "global": {},
3
+ "metrics": [
4
+ {
5
+ "metric": "count",
6
+ "metric_id": 1,
7
+ "legend": "count",
8
+ "displayAs": "bar",
9
+ "ds_data": {
10
+ "ds_stack": true,
11
+ "ds_transparency": 0,
12
+ "ds_filled": false,
13
+ "ds_color_area": "#4269d0",
14
+ "ds_color_line": "#4269d0"
15
+ },
16
+ "datasourceOptions": {
17
+ "field": null,
18
+ "group_by": null,
19
+ "op": "count-doc",
20
+ "query": "severity_text:\"Fatal\" OR severity_text:\"Error\"",
21
+ "period": 3600
22
+ },
23
+ "data": [
24
+ 217, 270, 293, 300, 303, 295, 298, 283, 299, 299, 297, 285, 270, 248,
25
+ 274, 292, 284, 47
26
+ ]
27
+ },
28
+ {
29
+ "metric": "count",
30
+ "metric_id": 2,
31
+ "legend": "count",
32
+ "displayAs": "bar",
33
+ "ds_data": {
34
+ "ds_stack": true,
35
+ "ds_transparency": 0,
36
+ "ds_filled": false,
37
+ "ds_color_area": "#efb118",
38
+ "ds_color_line": "#efb118"
39
+ },
40
+ "datasourceOptions": {
41
+ "field": null,
42
+ "group_by": null,
43
+ "op": "count-doc",
44
+ "query": "",
45
+ "period": 3600
46
+ },
47
+ "data": [
48
+ 32, 825, 939, 922, 918, 939, 943, 947, 946, 909, 931, 939, 883, 907,
49
+ 928, 904, 923, 893, 139
50
+ ]
51
+ }
52
+ ],
53
+ "times": [
54
+ "2025-10-04T16:19:30.000Z",
55
+ "2025-10-04T16:20:00.000Z",
56
+ "2025-10-04T16:20:30.000Z",
57
+ "2025-10-04T16:21:00.000Z",
58
+ "2025-10-04T16:21:30.000Z",
59
+ "2025-10-04T16:22:00.000Z",
60
+ "2025-10-04T16:22:30.000Z",
61
+ "2025-10-04T16:23:00.000Z",
62
+ "2025-10-04T16:23:30.000Z",
63
+ "2025-10-04T16:24:00.000Z",
64
+ "2025-10-04T16:24:30.000Z",
65
+ "2025-10-04T16:25:00.000Z",
66
+ "2025-10-04T16:25:30.000Z",
67
+ "2025-10-04T16:26:00.000Z",
68
+ "2025-10-04T16:26:30.000Z",
69
+ "2025-10-04T16:27:00.000Z",
70
+ "2025-10-04T16:27:30.000Z",
71
+ "2025-10-04T16:28:00.000Z",
72
+ "2025-10-04T16:28:30.000Z"
73
+ ]
74
+ }