@centreon/ui 25.7.1 → 25.7.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centreon/ui",
3
- "version": "25.7.1",
3
+ "version": "25.7.3",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "update:deps": "pnpx npm-check-updates -i --format group",
@@ -12,10 +12,10 @@
12
12
  "test": "jest",
13
13
  "test:storybook:local": "test-storybook --url http://127.0.0.1:9001",
14
14
  "test:ci": "jest --silent --reporter=jest-junit",
15
- "cypress:ui": "cypress open --component --browser=chrome",
16
- "cypress:run:updateSnapshots": "cypress run --component --browser=chrome --env updateSnapshots=true",
17
- "cypress:run:coverage": "cypress run --component --browser=chrome --env codeCoverageTasksRegistered=true",
18
- "cypress:run": "cypress run --component --browser=chrome",
15
+ "cypress:ui": "cypress open --component --browser=chromium",
16
+ "cypress:run:updateSnapshots": "cypress run --component --browser=chromium --env updateSnapshots=true",
17
+ "cypress:run:coverage": "cypress run --component --browser=chromium --env codeCoverageTasksRegistered=true",
18
+ "cypress:run": "cypress run --component --browser=chromium",
19
19
  "cypress:install": "cypress install",
20
20
  "cypress:cli": "./cypress/scripts/cypress-cli.sh",
21
21
  "tokens:transform": "TS_NODE_PROJECT=tsconfig.node.json ts-node style-dictionary.transform.ts",
@@ -30,10 +30,20 @@ const initialize = ({
30
30
  tooltip,
31
31
  axis,
32
32
  orientation,
33
- barStyle
33
+ barStyle,
34
+ min,
35
+ max
34
36
  }: Pick<
35
37
  BarChartProps,
36
- 'data' | 'legend' | 'axis' | 'barStyle' | 'orientation' | 'tooltip' | 'start'
38
+ | 'data'
39
+ | 'legend'
40
+ | 'axis'
41
+ | 'barStyle'
42
+ | 'orientation'
43
+ | 'tooltip'
44
+ | 'start'
45
+ | 'min'
46
+ | 'max'
37
47
  >): void => {
38
48
  cy.adjustViewport();
39
49
 
@@ -47,6 +57,8 @@ const initialize = ({
47
57
  legend={legend}
48
58
  orientation={orientation ?? 'horizontal'}
49
59
  tooltip={tooltip}
60
+ min={min}
61
+ max={max}
50
62
  {...defaultArgs}
51
63
  />
52
64
  </div>
@@ -138,8 +150,6 @@ describe('Bar chart', () => {
138
150
  cy.contains(':40 AM').should('be.visible');
139
151
 
140
152
  cy.findByTestId('stacked-bar-3-0-0.16196').should('be.visible');
141
-
142
- cy.makeSnapshot();
143
153
  });
144
154
 
145
155
  it(`displays bar chart ${orientation}ly with a mix of stacked and non-stacked data centered in zero`, () => {
@@ -289,4 +299,17 @@ describe('Bar chart', () => {
289
299
  cy.contains('05/31/2023').should('be.visible');
290
300
  cy.contains('06/07/2023').should('be.visible');
291
301
  });
302
+
303
+ it('displays the bar chart according to min and max boundaries', () => {
304
+ initialize({
305
+ data: dataLastWeek,
306
+ min: -0.05,
307
+ max: 1
308
+ });
309
+
310
+ cy.contains('05/31/2023').should('be.visible');
311
+ cy.contains('06/07/2023').should('be.visible');
312
+ cy.contains('1 s').should('be.visible');
313
+ cy.contains('1%').should('be.visible');
314
+ });
292
315
  });
@@ -250,3 +250,13 @@ export const mixedStackedVertical: Story = {
250
250
  },
251
251
  render: Template
252
252
  };
253
+
254
+ export const mixedStackedMinMax: Story = {
255
+ args: {
256
+ ...defaultArgs,
257
+ data: dataPingServiceMixedStacked,
258
+ min: 10,
259
+ max: 20
260
+ },
261
+ render: Template
262
+ };
@@ -10,11 +10,11 @@ import { Provider } from 'jotai';
10
10
 
11
11
  import { Box } from '@mui/material';
12
12
 
13
+ import Loading from '../../LoadingSkeleton';
13
14
  import LoadingSkeleton from '../Chart/LoadingSkeleton';
14
15
  import { LineChartProps } from '../Chart/models';
15
16
  import useChartData from '../Chart/useChartData';
16
17
  import { LineChartData, Thresholds } from '../common/models';
17
- import Loading from '../../LoadingSkeleton';
18
18
 
19
19
  import useResizeObserver from 'use-resize-observer';
20
20
  import ResponsiveBarChart from './ResponsiveBarChart';
@@ -26,7 +26,17 @@ dayjs.extend(timezonePlugin);
26
26
 
27
27
  export interface BarChartProps
28
28
  extends Partial<
29
- Pick<LineChartProps, 'tooltip' | 'legend' | 'height' | 'axis' | 'header'>
29
+ Pick<
30
+ LineChartProps,
31
+ | 'tooltip'
32
+ | 'legend'
33
+ | 'height'
34
+ | 'axis'
35
+ | 'header'
36
+ | 'min'
37
+ | 'max'
38
+ | 'boundariesUnit'
39
+ >
30
40
  > {
31
41
  barStyle?: BarStyle;
32
42
  data?: LineChartData;
@@ -58,7 +68,10 @@ const BarChart = ({
58
68
  opacity: 1,
59
69
  radius: 0.2
60
70
  },
61
- skipIntersectionObserver
71
+ skipIntersectionObserver,
72
+ min,
73
+ max,
74
+ boundariesUnit
62
75
  }: BarChartProps): JSX.Element => {
63
76
  const { adjustedData } = useChartData({ data, end, start });
64
77
  const { ref, width, height: responsiveHeight } = useResizeObserver();
@@ -93,6 +106,9 @@ const BarChart = ({
93
106
  tooltip={tooltip}
94
107
  width={width || 0}
95
108
  skipIntersectionObserver={skipIntersectionObserver}
109
+ min={min}
110
+ max={max}
111
+ boundariesUnit={boundariesUnit}
96
112
  />
97
113
  )}
98
114
  </Box>
@@ -40,6 +40,9 @@ interface Props
40
40
  thresholds?: ThresholdsModel;
41
41
  width: number;
42
42
  skipIntersectionObserver?: boolean;
43
+ min?: number;
44
+ max?: number;
45
+ boundariesUnit?: string;
43
46
  }
44
47
 
45
48
  const ResponsiveBarChart = ({
@@ -56,7 +59,10 @@ const ResponsiveBarChart = ({
56
59
  orientation,
57
60
  tooltip,
58
61
  barStyle,
59
- skipIntersectionObserver
62
+ skipIntersectionObserver,
63
+ min,
64
+ max,
65
+ boundariesUnit
60
66
  }: Props): JSX.Element => {
61
67
  const { title, timeSeries, baseAxis, lines } = graphData || {};
62
68
 
@@ -131,7 +137,11 @@ const ResponsiveBarChart = ({
131
137
  thresholdUnit,
132
138
  thresholds: (thresholds?.enabled && thresholdValues) || [],
133
139
  valueGraphHeight:
134
- (isHorizontal ? graphHeight : graphWidth) - margin.bottom
140
+ (isHorizontal ? graphHeight : graphWidth) - margin.bottom,
141
+ min,
142
+ max,
143
+ isBarChart: true,
144
+ boundariesUnit
135
145
  }),
136
146
  [
137
147
  displayedLines,
@@ -43,6 +43,8 @@ interface Props {
43
43
  xScale: ScaleTime<number, number>;
44
44
  yScale: ScaleLinear<number, number>;
45
45
  lineStyle: LineStyle | Array<LineStyle>;
46
+ hasSecondUnit?: boolean;
47
+ maxLeftAxisCharacters: number;
46
48
  }
47
49
 
48
50
  const StackLines = ({
@@ -51,7 +53,9 @@ const StackLines = ({
51
53
  yScale,
52
54
  xScale,
53
55
  displayAnchor,
54
- lineStyle
56
+ lineStyle,
57
+ hasSecondUnit,
58
+ maxLeftAxisCharacters
55
59
  }: Props): JSX.Element => {
56
60
  const curveType = getCurveFactory(
57
61
  (equals(type(lineStyle), 'Array')
@@ -101,6 +105,8 @@ const StackLines = ({
101
105
  transparency={transparency}
102
106
  xScale={xScale}
103
107
  yScale={yScale}
108
+ hasSecondUnit={hasSecondUnit}
109
+ maxLeftAxisCharacters={maxLeftAxisCharacters}
104
110
  />
105
111
  )}
106
112
  {style?.showPoints &&
@@ -40,6 +40,8 @@ interface Props extends GlobalAreaLines {
40
40
  xScale: ScaleLinear<number, number>;
41
41
  yScalesPerUnit: Record<string, ScaleLinear<number, number>>;
42
42
  lineStyle: LineStyle | Array<LineStyle>;
43
+ hasSecondUnit?: boolean;
44
+ maxLeftAxisCharacters: number;
43
45
  }
44
46
 
45
47
  const Lines = ({
@@ -56,7 +58,9 @@ const Lines = ({
56
58
  areaRegularLines,
57
59
  scale,
58
60
  scaleLogarithmicBase,
59
- lineStyle
61
+ lineStyle,
62
+ hasSecondUnit,
63
+ maxLeftAxisCharacters
60
64
  }: Props): JSX.Element => {
61
65
  const { stackedLinesData, invertedStackedLinesData } = useStackedLines({
62
66
  lines: displayedLines,
@@ -78,7 +82,9 @@ const Lines = ({
78
82
  graphHeight: height,
79
83
  graphSvgRef,
80
84
  graphWidth: width,
81
- xScale
85
+ xScale,
86
+ hasSecondUnit,
87
+ maxLeftAxisCharacters
82
88
  };
83
89
 
84
90
  return (
@@ -192,6 +198,8 @@ const Lines = ({
192
198
  transparency={transparency}
193
199
  xScale={xScale}
194
200
  yScale={yScale}
201
+ maxLeftAxisCharacters={maxLeftAxisCharacters}
202
+ hasSecondUnit={hasSecondUnit}
195
203
  />
196
204
  )}
197
205
  {style?.showPoints &&
@@ -22,7 +22,15 @@ import WrapperChart from '.';
22
22
  interface Props
23
23
  extends Pick<
24
24
  LineChartProps,
25
- 'legend' | 'tooltip' | 'axis' | 'lineStyle' | 'barStyle' | 'additionalLines'
25
+ | 'legend'
26
+ | 'tooltip'
27
+ | 'axis'
28
+ | 'lineStyle'
29
+ | 'barStyle'
30
+ | 'additionalLines'
31
+ | 'min'
32
+ | 'max'
33
+ | 'boundariesUnit'
26
34
  > {
27
35
  data?: LineChartData;
28
36
  }
@@ -69,7 +77,10 @@ const initialize = ({
69
77
  axis,
70
78
  lineStyle,
71
79
  barStyle,
72
- additionalLines
80
+ additionalLines,
81
+ min,
82
+ max,
83
+ boundariesUnit
73
84
  }: Props): void => {
74
85
  cy.adjustViewport();
75
86
 
@@ -93,6 +104,9 @@ const initialize = ({
93
104
  barStyle={barStyle}
94
105
  tooltip={tooltip}
95
106
  additionalLines={additionalLines}
107
+ min={min}
108
+ max={max}
109
+ boundariesUnit={boundariesUnit}
96
110
  />
97
111
  </Provider>
98
112
  )
@@ -165,8 +179,8 @@ describe('Line chart', () => {
165
179
 
166
180
  cy.contains('06/18/2023').should('be.visible');
167
181
 
168
- cy.contains('0.45 s').should('be.visible');
169
- cy.contains('73.65%').should('be.visible');
182
+ cy.contains('0.4 s').should('be.visible');
183
+ cy.contains('75.64%').should('be.visible');
170
184
 
171
185
  cy.makeSnapshot();
172
186
  });
@@ -188,12 +202,12 @@ describe('Line chart', () => {
188
202
  cy.get('[data-metric="connTime"]').should(
189
203
  'have.attr',
190
204
  'data-highlight',
191
- 'false'
205
+ 'true'
192
206
  );
193
207
  cy.get('[data-metric="hitratio"]').should(
194
208
  'have.attr',
195
209
  'data-highlight',
196
- 'true'
210
+ 'false'
197
211
  );
198
212
 
199
213
  cy.makeSnapshot();
@@ -437,7 +451,7 @@ describe('Line chart', () => {
437
451
 
438
452
  cy.contains(':00 AM').should('be.visible');
439
453
 
440
- cy.get('text[transform="rotate(-35, -2, 241.23251487506278)"]').should(
454
+ cy.get('text[transform="rotate(-35, -2, 198.7929601526369)"]').should(
441
455
  'be.visible'
442
456
  );
443
457
 
@@ -451,8 +465,8 @@ describe('Line chart', () => {
451
465
 
452
466
  cy.contains(':00 AM').should('be.visible');
453
467
 
454
- cy.contains('0.9').should('be.visible');
455
- cy.contains('-0.9').should('be.visible');
468
+ cy.contains('0.8').should('be.visible');
469
+ cy.contains('-0.8').should('be.visible');
456
470
 
457
471
  cy.makeSnapshot();
458
472
  });
@@ -519,7 +533,7 @@ describe('Line chart', () => {
519
533
  checkGraphWidth();
520
534
  cy.contains(':00 AM').should('be.visible');
521
535
  cy.get('circle[cx="250.83333333333334"]').should('be.visible');
522
- cy.get('circle[cy="52.90739222860398"]').should('be.visible');
536
+ cy.get('circle[cy="246.2421135204699"]').should('be.visible');
523
537
 
524
538
  cy.makeSnapshot();
525
539
  });
@@ -733,10 +747,10 @@ describe('Lines and bars', () => {
733
747
  checkGraphWidth();
734
748
 
735
749
  cy.get(
736
- 'path[d="M7.501377410468319,273.1948717814711 h56.51239669421488 h1v1 v100.80512821852892 a1,1 0 0 1 -1,1 h-56.51239669421488 a1,1 0 0 1 -1,-1 v-100.80512821852892 v-1h1z"]'
737
- ).should('be.visible');
750
+ 'path[d="M7.501377410468319,278.09035407759154 h56.51239669421488 h1v1 v95.90964592240846 a1,1 0 0 1 -1,1 h-56.51239669421488 a1,1 0 0 1 -1,-1 v-95.90964592240846 v-1h1z'
751
+ );
738
752
  cy.get(
739
- 'path[d="M24.05509641873278,218.2484747081302 h23.404958677685954 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,17.553719008264462 v19.83895905681195 v17.553719008264462h-17.553719008264462 h-23.404958677685954 h-17.553719008264462v-17.553719008264462 v-19.83895905681195 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,-17.553719008264462z"]'
753
+ 'path[d="M24.05509641873278,225.7604521029811 h23.404958677685954 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,17.553719008264462 v17.222463958081512 v17.553719008264462h-17.553719008264462 h-23.404958677685954 h-17.553719008264462v-17.553719008264462 v-17.222463958081512 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,-17.553719008264462z"]'
740
754
  ).should('be.visible');
741
755
 
742
756
  cy.makeSnapshot();
@@ -763,4 +777,43 @@ describe('Lines and bars', () => {
763
777
 
764
778
  cy.makeSnapshot();
765
779
  });
780
+
781
+ it('displays graph according to min and max boundaries', () => {
782
+ initialize({
783
+ data: dataPingServiceLines,
784
+ min: 0.01,
785
+ max: 0.1
786
+ });
787
+
788
+ checkGraphWidth();
789
+
790
+ cy.get('path[data-metric="1"]').should('be.visible');
791
+ cy.get('path[data-metric="3"]').should('be.visible');
792
+ cy.get('path[data-metric="3"]').should('be.visible');
793
+
794
+ cy.contains('0.1 ms').should('be.visible');
795
+ cy.contains('0.1%').should('be.visible');
796
+
797
+ cy.makeSnapshot();
798
+ });
799
+
800
+ it('displays graph according to min and max boundaries for a unit', () => {
801
+ initialize({
802
+ data: dataPingServiceLines,
803
+ min: 0.01,
804
+ max: 0.1,
805
+ boundariesUnit: 'ms'
806
+ });
807
+
808
+ checkGraphWidth();
809
+
810
+ cy.get('path[data-metric="1"]').should('be.visible');
811
+ cy.get('path[data-metric="3"]').should('be.visible');
812
+ cy.get('path[data-metric="3"]').should('be.visible');
813
+
814
+ cy.contains('0.1 ms').should('be.visible');
815
+ cy.contains('2%').should('be.visible');
816
+
817
+ cy.makeSnapshot();
818
+ });
766
819
  });
@@ -717,3 +717,44 @@ export const WithAdditionalLines: Story = {
717
717
  />
718
718
  )
719
719
  };
720
+
721
+ export const linesAndBarsMinMax: Story = {
722
+ argTypes,
723
+ args: {
724
+ ...argumentsData,
725
+ min: 10,
726
+ max: 30,
727
+ lineStyle: {
728
+ curve: 'natural',
729
+ lineWidth: 2,
730
+ showPoints: true
731
+ }
732
+ },
733
+ render: (args) => (
734
+ <WrapperChart
735
+ {...args}
736
+ data={dataPingServiceLinesBarsMixed as unknown as LineChartData}
737
+ />
738
+ )
739
+ };
740
+
741
+ export const linesAndBarsMinMaxForUnit: Story = {
742
+ argTypes,
743
+ args: {
744
+ ...argumentsData,
745
+ min: 10,
746
+ max: 30,
747
+ boundariesUnit: '%',
748
+ lineStyle: {
749
+ curve: 'natural',
750
+ lineWidth: 2,
751
+ showPoints: true
752
+ }
753
+ },
754
+ render: (args) => (
755
+ <WrapperChart
756
+ {...args}
757
+ data={dataPingServiceLinesBarsMixed as unknown as LineChartData}
758
+ />
759
+ )
760
+ };
@@ -112,7 +112,10 @@ const Chart = ({
112
112
  limitLegend,
113
113
  skipIntersectionObserver,
114
114
  transformMatrix,
115
- additionalLines
115
+ additionalLines,
116
+ min,
117
+ max,
118
+ boundariesUnit
116
119
  }: Props): JSX.Element => {
117
120
  const { classes } = useChartStyles();
118
121
 
@@ -191,7 +194,10 @@ const Chart = ({
191
194
  scaleLogarithmicBase: axis?.scaleLogarithmicBase,
192
195
  thresholdUnit,
193
196
  thresholds: (thresholds?.enabled && thresholdValues) || [],
194
- valueGraphHeight: graphHeight - margin.bottom
197
+ valueGraphHeight: graphHeight - margin.bottom,
198
+ min,
199
+ max,
200
+ boundariesUnit
195
201
  }),
196
202
  [
197
203
  linesGraph,
@@ -246,6 +252,8 @@ const Chart = ({
246
252
  [axis?.showGridLines]
247
253
  );
248
254
 
255
+ const hasSecondUnit = useMemo(() => Boolean(secondUnit), [secondUnit]);
256
+
249
257
  if ((!isInViewport && !skipIntersectionObserver) || !height) {
250
258
  return (
251
259
  <Skeleton
@@ -299,7 +307,7 @@ const Chart = ({
299
307
  timeSeries={timeSeries}
300
308
  xScale={xScale}
301
309
  maxAxisCharacters={maxLeftAxisCharacters}
302
- hasSecondUnit={Boolean(secondUnit)}
310
+ hasSecondUnit={hasSecondUnit}
303
311
  >
304
312
  <>
305
313
  {!isEmpty(linesDisplayedAsBar) && (
@@ -327,6 +335,8 @@ const Chart = ({
327
335
  width={graphWidth}
328
336
  xScale={xScale}
329
337
  yScalesPerUnit={yScalesPerUnit}
338
+ hasSecondUnit={hasSecondUnit}
339
+ maxLeftAxisCharacters={maxLeftAxisCharacters}
330
340
  {...shapeLines}
331
341
  />
332
342
  )}
@@ -355,6 +365,8 @@ const Chart = ({
355
365
  }}
356
366
  zoomData={{ ...zoomPreview }}
357
367
  transformMatrix={transformMatrix}
368
+ hasSecondUnit={hasSecondUnit}
369
+ maxLeftAxisCharacters={maxLeftAxisCharacters}
358
370
  />
359
371
  {thresholds?.enabled && (
360
372
  <Thresholds
@@ -15,6 +15,8 @@ interface Props {
15
15
  timeSeries: Array<TimeValue>;
16
16
  xScale: ScaleTime<number, number>;
17
17
  yScale: ScaleLinear<number, number>;
18
+ hasSecondUnit?: boolean;
19
+ maxLeftAxisCharacters: number;
18
20
  }
19
21
 
20
22
  export const getYAnchorPoint = ({
@@ -38,11 +40,15 @@ const RegularAnchorPoint = ({
38
40
  yScale,
39
41
  metric_id,
40
42
  timeSeries,
41
- lineColor
43
+ lineColor,
44
+ maxLeftAxisCharacters,
45
+ hasSecondUnit
42
46
  }: Props): JSX.Element | null => {
43
47
  const { tickAxisBottom: timeTick } = useTickGraph({
44
48
  timeSeries,
45
- xScale
49
+ xScale,
50
+ maxLeftAxisCharacters,
51
+ hasSecondUnit
46
52
  });
47
53
 
48
54
  if (isNil(timeTick)) {
@@ -15,7 +15,10 @@ interface Props {
15
15
  timeSeries: Array<TimeValue>;
16
16
  xScale: ScaleTime<number, number>;
17
17
  yScale: ScaleLinear<number, number>;
18
+ hasSecondUnit?: boolean;
19
+ maxLeftAxisCharacters: number;
18
20
  }
21
+
19
22
  interface GetYAnchorPoint {
20
23
  stackValues: Array<StackValue>;
21
24
  timeTick: Date | null;
@@ -51,11 +54,15 @@ const StackedAnchorPoint = ({
51
54
  yScale,
52
55
  stackValues,
53
56
  timeSeries,
54
- lineColor
57
+ lineColor,
58
+ hasSecondUnit,
59
+ maxLeftAxisCharacters
55
60
  }: Props): JSX.Element | null => {
56
61
  const { tickAxisBottom: timeTick } = useTickGraph({
57
62
  timeSeries,
58
- xScale
63
+ xScale,
64
+ hasSecondUnit,
65
+ maxLeftAxisCharacters
59
66
  });
60
67
 
61
68
  if (isNil(timeTick)) {
@@ -6,6 +6,10 @@ import { useAtomValue } from 'jotai';
6
6
  import useAxisY from '../../../common/Axes/useAxisY';
7
7
  import { getTimeValue } from '../../../common/timeSeries';
8
8
  import { Line, TimeValue } from '../../../common/timeSeries/models';
9
+ import {
10
+ computPixelsToShiftMouse,
11
+ computeGElementMarginLeft
12
+ } from '../../../common/utils';
9
13
  import { margin } from '../../common';
10
14
  import { mousePositionAtom } from '../interactionWithGraphAtoms';
11
15
 
@@ -25,6 +29,8 @@ interface Props {
25
29
  rightScale?: ScaleLinear<number, number>;
26
30
  timeSeries: Array<TimeValue>;
27
31
  xScale: ScaleLinear<number, number>;
32
+ hasSecondUnit?: boolean;
33
+ maxLeftAxisCharacters: number;
28
34
  }
29
35
 
30
36
  const useTickGraph = ({
@@ -33,7 +39,9 @@ const useTickGraph = ({
33
39
  leftScale,
34
40
  rightScale,
35
41
  lines = [],
36
- baseAxis = 1000
42
+ baseAxis = 1000,
43
+ hasSecondUnit,
44
+ maxLeftAxisCharacters
37
45
  }: Props): AnchorPointResult => {
38
46
  const guidingLinesRef = useRef<SVGGElement | null>(null);
39
47
  const [tickAxisBottom, setTickAxisBottom] = useState<Date | null>(null);
@@ -69,8 +77,17 @@ const useTickGraph = ({
69
77
 
70
78
  return;
71
79
  }
80
+ const pixelToShift = computPixelsToShiftMouse(xScale);
72
81
  const mousePositionTimeTick = mousePosition
73
- ? getTimeValue({ timeSeries, x: mousePosition[0], xScale })?.timeTick
82
+ ? getTimeValue({
83
+ timeSeries,
84
+ x: mousePosition[0] - pixelToShift,
85
+ xScale,
86
+ marginLeft: computeGElementMarginLeft({
87
+ maxCharacters: maxLeftAxisCharacters,
88
+ hasSecondUnit
89
+ })
90
+ })?.timeTick
74
91
  : 0;
75
92
  const timeTickValue = mousePosition
76
93
  ? new Date(mousePositionTimeTick || 0)
@@ -37,6 +37,10 @@ import type {
37
37
  InteractedZone as ZoomPreviewModel
38
38
  } from '../models';
39
39
 
40
+ import {
41
+ computPixelsToShiftMouse,
42
+ computeGElementMarginLeft
43
+ } from '../../common/utils';
40
44
  import Annotations from './Annotations';
41
45
  import type { TimelineEvent } from './Annotations/models';
42
46
  import Bar from './Bar';
@@ -80,6 +84,8 @@ interface Props {
80
84
  fx?: (pointX: number) => number;
81
85
  fy?: (pointY: number) => number;
82
86
  };
87
+ hasSecondUnit?: boolean;
88
+ maxLeftAxisCharacters: number;
83
89
  }
84
90
 
85
91
  const InteractionWithGraph = ({
@@ -87,7 +93,9 @@ const InteractionWithGraph = ({
87
93
  commonData,
88
94
  annotationData,
89
95
  timeShiftZonesData,
90
- transformMatrix
96
+ transformMatrix,
97
+ hasSecondUnit,
98
+ maxLeftAxisCharacters
91
99
  }: Props): JSX.Element => {
92
100
  const { classes } = useStyles();
93
101
 
@@ -151,10 +159,15 @@ const InteractionWithGraph = ({
151
159
 
152
160
  return;
153
161
  }
162
+ const pixelToShift = computPixelsToShiftMouse(xScale);
154
163
  const timeValue = getTimeValue({
155
164
  timeSeries,
156
- x: pointPosition[0],
157
- xScale
165
+ x: pointPosition[0] - pixelToShift,
166
+ xScale,
167
+ marginLeft: computeGElementMarginLeft({
168
+ maxCharacters: maxLeftAxisCharacters,
169
+ hasSecondUnit
170
+ })
158
171
  });
159
172
 
160
173
  if (isNil(timeValue)) {
@@ -70,6 +70,9 @@ const WrapperChart = ({
70
70
  getRef,
71
71
  transformMatrix,
72
72
  additionalLines,
73
+ min,
74
+ max,
75
+ boundariesUnit,
73
76
  ...rest
74
77
  }: Props): JSX.Element | null => {
75
78
  const { classes, cx } = useChartStyles();
@@ -125,6 +128,9 @@ const WrapperChart = ({
125
128
  skipIntersectionObserver={rest.skipIntersectionObserver}
126
129
  additionalLines={additionalLines}
127
130
  transformMatrix={transformMatrix}
131
+ min={min}
132
+ max={max}
133
+ boundariesUnit={boundariesUnit}
128
134
  />
129
135
  )}
130
136
  </div>
@@ -122,6 +122,9 @@ export interface LineChartProps {
122
122
  zoomPreview?: InteractedZone;
123
123
  skipIntersectionObserver?: boolean;
124
124
  additionalLines?: Array<AdditionalLineProps>;
125
+ min?: number;
126
+ max?: number;
127
+ boundariesUnit?: string;
125
128
  }
126
129
 
127
130
  export interface Area {
@@ -8,6 +8,7 @@ import { ChartAxis } from '../../Chart/models';
8
8
  import Axes from '../Axes';
9
9
  import Grids from '../Grids';
10
10
  import { Line, TimeValue } from '../timeSeries/models';
11
+ import { computeGElementMarginLeft } from '../utils';
11
12
 
12
13
  interface Props {
13
14
  allUnits: Array<string>;
@@ -58,10 +59,10 @@ const ChartSvgWrapper = ({
58
59
  width="100%"
59
60
  >
60
61
  <Group.Group
61
- left={
62
- maxAxisCharacters * 5 +
63
- (hasSecondUnit ? margin.top * 0.8 : margin.top * 0.6)
64
- }
62
+ left={computeGElementMarginLeft({
63
+ maxCharacters: maxAxisCharacters,
64
+ hasSecondUnit
65
+ })}
65
66
  top={margin.top}
66
67
  >
67
68
  {showGridLines && (
@@ -20,7 +20,6 @@ import {
20
20
  includes,
21
21
  isEmpty,
22
22
  isNil,
23
- isNotNil,
24
23
  keys,
25
24
  last,
26
25
  lt,
@@ -353,6 +352,19 @@ const getScaleType = (
353
352
  const hasOnlyZeroesHasValue = (graphValues: Array<number>): boolean =>
354
353
  graphValues.every((value) => equals(value, 0) || equals(value, null));
355
354
 
355
+ const getSanitizedValues = reject(
356
+ (
357
+ value:
358
+ | number
359
+ | boolean
360
+ | typeof Number.POSITIVE_INFINITY
361
+ | typeof Number.NEGATIVE_INFINITY
362
+ ) =>
363
+ equals(value, false) ||
364
+ equals(value, Number.POSITIVE_INFINITY) ||
365
+ equals(value, Number.NEGATIVE_INFINITY)
366
+ );
367
+
356
368
  const getScale = ({
357
369
  graphValues,
358
370
  height,
@@ -363,23 +375,39 @@ const getScale = ({
363
375
  scaleLogarithmicBase,
364
376
  isHorizontal,
365
377
  invert,
366
- hasDisplayAsBar
378
+ hasDisplayAsBar,
379
+ hasLineFilled,
380
+ min,
381
+ max
367
382
  }): ScaleLinear<number, number> => {
368
383
  const isLogScale = equals(scale, 'logarithmic');
369
- const minValue = Math.min(
370
- hasDisplayAsBar && 0,
371
- invert && graphValues.every(lt(0))
372
- ? negate(getMax(graphValues))
373
- : getMin(graphValues),
374
- getMin(stackedValues),
375
- Math.min(...thresholds)
376
- );
377
- const maxValue = Math.max(
378
- getMax(graphValues),
379
- getMax(stackedValues),
380
- hasOnlyZeroesHasValue(graphValues) ? 1 : 0,
381
- Math.max(...thresholds)
382
- );
384
+ const sanitizedValuesForMinimum = min
385
+ ? [min]
386
+ : getSanitizedValues([
387
+ invert && graphValues.every(lt(0))
388
+ ? negate(getMax(graphValues))
389
+ : getMin(graphValues),
390
+ !isEmpty(stackedValues) &&
391
+ !equals(stackedValues, [0]) &&
392
+ getMin(stackedValues),
393
+ Math.min(...thresholds)
394
+ ]);
395
+ const minValue = Math.min(...sanitizedValuesForMinimum);
396
+ const minValueWithMargin =
397
+ (hasDisplayAsBar || hasLineFilled) && minValue > 0 && !min
398
+ ? 0
399
+ : minValue - Math.abs(minValue) * 0.05;
400
+
401
+ const sanitizedValuesForMaximum = max
402
+ ? [max]
403
+ : getSanitizedValues([
404
+ getMax(graphValues),
405
+ getMax(stackedValues),
406
+ hasOnlyZeroesHasValue(graphValues) ? 1 : 0,
407
+ Math.max(...thresholds)
408
+ ]);
409
+ const maxValue = Math.max(...sanitizedValuesForMaximum);
410
+ const maxValueWithMargin = maxValue + Math.abs(maxValue) * 0.05;
383
411
 
384
412
  const scaleType = getScaleType(scale);
385
413
 
@@ -387,21 +415,26 @@ const getScale = ({
387
415
  const range = [height, upperRangeValue];
388
416
 
389
417
  if (isCenteredZero) {
390
- const greatestValue = Math.max(Math.abs(maxValue), Math.abs(minValue));
418
+ const greatestValue = Math.max(
419
+ Math.abs(maxValueWithMargin),
420
+ Math.abs(minValueWithMargin)
421
+ );
391
422
 
392
423
  return scaleType<number>({
393
424
  base: scaleLogarithmicBase || 2,
394
425
  domain: [-greatestValue, greatestValue],
395
- range: isHorizontal ? range : range.reverse()
426
+ range: isHorizontal ? range : range.reverse(),
427
+ clamp: min || max
396
428
  });
397
429
  }
398
430
 
399
- const domain = [isLogScale ? 0.001 : minValue, maxValue];
431
+ const domain = [isLogScale ? 0.001 : minValueWithMargin, maxValueWithMargin];
400
432
 
401
433
  return scaleType<number>({
402
434
  base: scaleLogarithmicBase || 2,
403
435
  domain,
404
- range: isHorizontal ? range : range.reverse()
436
+ range: isHorizontal ? range : range.reverse(),
437
+ clamp: min || max
405
438
  });
406
439
  };
407
440
 
@@ -437,11 +470,19 @@ const getYScaleUnit = ({
437
470
  scaleLogarithmicBase,
438
471
  isHorizontal = true,
439
472
  unit,
440
- invert
441
- }: AxeScale & { invert?: boolean | string | null; unit: string }): ScaleLinear<
442
- number,
443
- number
444
- > => {
473
+ invert,
474
+ min,
475
+ max,
476
+ isBarChart,
477
+ boundariesUnit
478
+ }: AxeScale & {
479
+ invert?: boolean | string | null;
480
+ unit: string;
481
+ max?: number;
482
+ min?: number;
483
+ boundariesUnit?: string;
484
+ isBarChart?: boolean;
485
+ }): ScaleLinear<number, number> => {
445
486
  const [firstUnit] = getUnits(dataLines);
446
487
  const shouldApplyThresholds =
447
488
  equals(unit, thresholdUnit) || (!thresholdUnit && equals(firstUnit, unit));
@@ -468,11 +509,14 @@ const getYScaleUnit = ({
468
509
 
469
510
  return getScale({
470
511
  graphValues,
471
- hasDisplayAsBar: dataLines.some(
472
- ({ displayAs, unit: lineUnit, stackOrder }) =>
473
- equals(unit, lineUnit) &&
474
- equals(displayAs, 'bar') &&
475
- isNotNil(stackOrder)
512
+ hasDisplayAsBar:
513
+ isBarChart ||
514
+ dataLines.some(
515
+ ({ displayAs, unit: lineUnit }) =>
516
+ equals(unit, lineUnit) && equals(displayAs, 'bar')
517
+ ),
518
+ hasLineFilled: dataLines.some(
519
+ ({ unit: lineUnit, filled }) => equals(unit, lineUnit) && filled
476
520
  ),
477
521
  height: valueGraphHeight,
478
522
  invert,
@@ -481,10 +525,24 @@ const getYScaleUnit = ({
481
525
  scale,
482
526
  scaleLogarithmicBase,
483
527
  stackedValues,
484
- thresholds: shouldApplyThresholds ? thresholds : []
528
+ thresholds: shouldApplyThresholds ? thresholds : [],
529
+ min: boundaryToApplyToUnit({ unit, boundariesUnit, boundary: min }),
530
+ max: boundaryToApplyToUnit({ unit, boundariesUnit, boundary: max })
485
531
  });
486
532
  };
487
533
 
534
+ const boundaryToApplyToUnit = ({
535
+ boundary,
536
+ boundariesUnit,
537
+ unit
538
+ }): number | undefined => {
539
+ if (!boundariesUnit) {
540
+ return boundary;
541
+ }
542
+
543
+ return equals(boundariesUnit, unit) ? boundary : undefined;
544
+ };
545
+
488
546
  const getYScalePerUnit = ({
489
547
  dataLines,
490
548
  dataTimeSeries,
@@ -494,8 +552,17 @@ const getYScalePerUnit = ({
494
552
  isCenteredZero,
495
553
  scale,
496
554
  scaleLogarithmicBase,
497
- isHorizontal = true
498
- }: AxeScale): Record<string, ScaleLinear<number, number>> => {
555
+ isHorizontal = true,
556
+ isBarChart,
557
+ min,
558
+ max,
559
+ boundariesUnit
560
+ }: AxeScale & {
561
+ min?: number;
562
+ max?: number;
563
+ isBarChart?: boolean;
564
+ boundariesUnit?: string;
565
+ }): Record<string, ScaleLinear<number, number>> => {
499
566
  const units = getUnits(dataLines);
500
567
 
501
568
  const scalePerUnit = units.reduce((acc, unit) => {
@@ -514,7 +581,11 @@ const getYScalePerUnit = ({
514
581
  thresholdUnit,
515
582
  thresholds,
516
583
  unit,
517
- valueGraphHeight
584
+ valueGraphHeight,
585
+ min,
586
+ max,
587
+ isBarChart,
588
+ boundariesUnit
518
589
  })
519
590
  };
520
591
  }, {});
@@ -20,7 +20,9 @@ import {
20
20
 
21
21
  import { Theme, darken, getLuminance, lighten } from '@mui/material';
22
22
 
23
+ import dayjs from 'dayjs';
23
24
  import { BarStyle } from '../BarChart/models';
25
+ import { margin } from '../Chart/common';
24
26
  import { LineStyle } from '../Chart/models';
25
27
  import { Threshold, Thresholds } from './models';
26
28
  import { formatMetricValue } from './timeSeries';
@@ -256,3 +258,20 @@ export const getFormattedAxisValues = ({
256
258
  .concat(formattedThresholdValues)
257
259
  .filter((v) => v) as Array<string>;
258
260
  };
261
+
262
+ interface ComputeGElementMarginLeftProps {
263
+ maxCharacters: number;
264
+ hasSecondUnit?: boolean;
265
+ }
266
+
267
+ export const computeGElementMarginLeft = ({
268
+ maxCharacters,
269
+ hasSecondUnit
270
+ }: ComputeGElementMarginLeftProps): number =>
271
+ maxCharacters * 5 + (hasSecondUnit ? margin.top * 0.8 : margin.top * 0.6);
272
+
273
+ export const computPixelsToShiftMouse = (xScale): number => {
274
+ const domain = xScale.domain();
275
+
276
+ return Math.round(8 / dayjs(domain[1]).diff(domain[0], 'h'));
277
+ };
@@ -0,0 +1,20 @@
1
+ import { SvgIconProps } from '@mui/material';
2
+ import { ReactElement } from 'react';
3
+ import BaseIcon from './BaseIcon';
4
+
5
+ const icon = (
6
+ <g transform="translate(2 2)">
7
+ <path
8
+ d="M19.468,8.107,17.2,6.8l2.264-1.307a.8.8,0,0,0-.8-1.386L16.4,5.414V2.8a.8.8,0,1,0-1.6,0V5.414L12.54,4.107a.8.8,0,1,0-.8,1.386L14,6.8,11.74,8.107a.8.8,0,1,0,.8,1.386L14.8,8.186V10.8a.8.8,0,1,0,1.6,0V8.186l2.264,1.307a.789.789,0,0,0,.4.107.8.8,0,0,0,.4-1.493"
9
+ transform="translate(-1.868)"
10
+ />
11
+ <path
12
+ d="M5.2,20.4a3.2,3.2,0,1,1,3.2-3.2,3.2,3.2,0,0,1-3.2,3.2m0-4.8a1.6,1.6,0,1,0,1.6,1.6,1.6,1.6,0,0,0-1.6-1.6"
13
+ transform="translate(0 -2.4)"
14
+ />
15
+ </g>
16
+ );
17
+
18
+ export const RegexIcon = (props: SvgIconProps): ReactElement => (
19
+ <BaseIcon {...props} Icon={icon} dataTestId="RegexIcon" />
20
+ );
package/src/Icon/index.ts CHANGED
@@ -8,3 +8,4 @@ export { HostGroupIcon } from './HostGroupIcon';
8
8
  export { ServiceGroupIcon } from './ServiceGroupIcon';
9
9
  export { MetaServiceIcon } from './MetaServiceIcon';
10
10
  export { ContainerIcon } from './ContainerIcon';
11
+ export { RegexIcon } from './RegexIcon';
@@ -17,7 +17,7 @@ import {
17
17
  import { CircularProgress, useTheme } from '@mui/material';
18
18
 
19
19
  import { Props as AutocompleteFieldProps } from '..';
20
- import { ListingModel, ListingMapModel, SelectEntry } from '../../../..';
20
+ import { ListingMapModel, ListingModel, SelectEntry } from '../../../..';
21
21
  import {
22
22
  ConditionsSearchParameter,
23
23
  SearchParameter
@@ -122,26 +122,31 @@ const ConnectedAutocompleteField = (
122
122
  }
123
123
  });
124
124
 
125
- const getOptionResult = useCallback((
126
- newOptions: ListingModel<TData> | ListingMapModel<TData>
127
- ): OptionResult<TData> => {
128
- if ('result' in newOptions) return {
129
- result: newOptions.result || [],
130
- total: newOptions.meta.total || 1,
131
- limit: newOptions.meta.limit || 1,
132
- };
133
- if ('content' in newOptions) return {
134
- result: newOptions.content || [],
135
- total: newOptions.totalElements || 1,
136
- limit: newOptions.size || 1,
137
- };
138
-
139
- return {
140
- result: [],
141
- total: 1,
142
- limit: 1,
143
- }
144
- }, []);
125
+ const getOptionResult = useCallback(
126
+ (
127
+ newOptions: ListingModel<TData> | ListingMapModel<TData>
128
+ ): OptionResult<TData> => {
129
+ if ('result' in newOptions)
130
+ return {
131
+ result: newOptions.result || [],
132
+ total: newOptions.meta.total || 1,
133
+ limit: newOptions.meta.limit || 1
134
+ };
135
+ if ('content' in newOptions)
136
+ return {
137
+ result: newOptions.content || [],
138
+ total: newOptions.totalElements || 1,
139
+ limit: newOptions.size || 1
140
+ };
141
+
142
+ return {
143
+ result: [],
144
+ total: 1,
145
+ limit: 1
146
+ };
147
+ },
148
+ []
149
+ );
145
150
 
146
151
  const lastOptionRef = useIntersectionObserver({
147
152
  action: () => setPage(page + 1),
@@ -297,7 +297,7 @@ export const lightPalette: PaletteOptions = {
297
297
  contrastText: '#000',
298
298
  main: '#FD9B27',
299
299
  light: '#FCC481',
300
- dark: '#FC7E00',
300
+ dark: '#FC7E00'
301
301
  }
302
302
  };
303
303
 
package/src/api/models.ts CHANGED
@@ -10,10 +10,10 @@ export interface Listing<TEntity> {
10
10
  }
11
11
 
12
12
  export interface ListingMap<TEntity> {
13
- content: Array<TEntity>;
14
- totalPages: number;
15
- totalElements: number;
16
- size: number;
17
- number: number;
18
- numberOfElements: number;
13
+ content: Array<TEntity>;
14
+ totalPages: number;
15
+ totalElements: number;
16
+ size: number;
17
+ number: number;
18
+ numberOfElements: number;
19
19
  }
@@ -19,6 +19,7 @@ export interface ConfirmationModalProps<TAtom> {
19
19
  onCancel?: (atomData: Awaited<TAtom> | null) => void;
20
20
  onClose?: (atomData: Awaited<TAtom> | null) => void;
21
21
  onConfirm?: (atomData: Awaited<TAtom> | null) => void;
22
+ size?: 'small' | 'medium' | 'large' | 'xlarge' | 'fullscreen';
22
23
  }
23
24
 
24
25
  interface GetLabelProps<TAtom> {
@@ -39,7 +40,8 @@ export const ConfirmationModal = <TAtom,>({
39
40
  onClose,
40
41
  hasCloseButton = true,
41
42
  isDanger,
42
- disabled
43
+ disabled,
44
+ size
43
45
  }: ConfirmationModalProps<TAtom>): JSX.Element => {
44
46
  const [atomData, setAtomData] = useAtom<TAtom | null>(atom);
45
47
 
@@ -72,6 +74,7 @@ export const ConfirmationModal = <TAtom,>({
72
74
  hasCloseButton={hasCloseButton}
73
75
  open={Boolean(atomData)}
74
76
  onClose={closeModal}
77
+ size={size}
75
78
  >
76
79
  <Modal.Header>{formattedLabels.title}</Modal.Header>
77
80
  <Modal.Body>{formattedLabels.description}</Modal.Body>