@centreon/ui 25.3.0 → 25.3.2

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.3.0",
3
+ "version": "25.3.2",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "update:deps": "pnpx npm-check-updates -i --format group",
@@ -83,7 +83,7 @@ describe('Bar chart', () => {
83
83
  cy.contains('20').should('be.visible');
84
84
  cy.contains(':40 AM').should('be.visible');
85
85
 
86
- cy.findByTestId('stacked-bar-3-0-0.08644').should('be.visible');
86
+ cy.findByTestId('stacked-bar-3-0-0.08084').should('be.visible');
87
87
 
88
88
  cy.makeSnapshot();
89
89
  });
@@ -96,7 +96,7 @@ describe('Bar chart', () => {
96
96
  cy.contains('20').should('be.visible');
97
97
  cy.contains(':40 AM').should('be.visible');
98
98
 
99
- cy.findByTestId('stacked-bar-3-0-0.08644').should('be.visible');
99
+ cy.findByTestId('stacked-bar-3-0-0.08084').should('be.visible');
100
100
 
101
101
  cy.makeSnapshot();
102
102
  });
@@ -195,6 +195,45 @@ export const customBarStyle: Story = {
195
195
  render: Template
196
196
  };
197
197
 
198
+ export const customBarStyleForABar: Story = {
199
+ args: {
200
+ ...defaultArgs,
201
+ barStyle: [
202
+ {
203
+ opacity: 0.5,
204
+ radius: 0.5,
205
+ metricId: 10
206
+ },
207
+ {
208
+ opacity: 0.2,
209
+ radius: 0.3,
210
+ metricId: 1
211
+ }
212
+ ]
213
+ },
214
+ render: Template
215
+ };
216
+
217
+ export const customBarStyleForABarStacked: Story = {
218
+ args: {
219
+ ...defaultArgs,
220
+ data: dataPingServiceStacked,
221
+ barStyle: [
222
+ {
223
+ opacity: 0.5,
224
+ radius: 0.5,
225
+ metricId: 10
226
+ },
227
+ {
228
+ opacity: 0.2,
229
+ radius: 0.3,
230
+ metricId: 1
231
+ }
232
+ ]
233
+ },
234
+ render: Template
235
+ };
236
+
198
237
  export const mixedStacked: Story = {
199
238
  args: {
200
239
  ...defaultArgs,
@@ -5,6 +5,7 @@ import { BarRounded } from '@visx/shape';
5
5
  import { dec, equals, gt, pick } from 'ramda';
6
6
 
7
7
  import { BarGroupBar, SeriesPoint, StackKey } from '@visx/shape/lib/types';
8
+ import { getStyle } from '../common/utils';
8
9
  import { BarStyle } from './models';
9
10
  import { UseBarStackProps, useBarStack } from './useBarStack';
10
11
 
@@ -112,6 +113,11 @@ const BarStack = ({
112
113
  [isHorizontal ? 'top' : 'right']: shouldApplyRadiusOnTop
113
114
  };
114
115
 
116
+ const style = getStyle({
117
+ style: barStyle,
118
+ metricId: Number(bar.key)
119
+ }) as BarStyle;
120
+
115
121
  return (
116
122
  <BarRounded
117
123
  {...barRoundedProps}
@@ -133,8 +139,8 @@ const BarStack = ({
133
139
  neutralValue
134
140
  })}
135
141
  key={`bar-stack-${barStack.index}-${bar.index}`}
136
- opacity={barStyle.opacity ?? 1}
137
- radius={barWidth * barStyle.radius}
142
+ opacity={style?.opacity || 1}
143
+ radius={style?.radius ? barWidth * style.radius : 0}
138
144
  width={isHorizontal ? barWidth : Math.abs(bar.width)}
139
145
  x={
140
146
  isHorizontal
@@ -87,14 +87,15 @@ const ResponsiveBarChart = ({
87
87
  secondUnit
88
88
  });
89
89
 
90
- const { legendRef, graphWidth, graphHeight } = useComputeBaseChartDimensions({
91
- hasSecondUnit: Boolean(secondUnit),
92
- height,
93
- legendDisplay: legend?.display,
94
- legendPlacement: legend?.placement,
95
- width,
96
- maxAxisCharacters: maxRightAxisCharacters || maxLeftAxisCharacters
97
- });
90
+ const { legendRef, graphWidth, graphHeight, titleRef } =
91
+ useComputeBaseChartDimensions({
92
+ hasSecondUnit: Boolean(secondUnit),
93
+ height,
94
+ legendDisplay: legend?.display,
95
+ legendPlacement: legend?.placement,
96
+ width,
97
+ maxAxisCharacters: maxRightAxisCharacters || maxLeftAxisCharacters
98
+ });
98
99
 
99
100
  const thresholdValues = flatten([
100
101
  pluck('value', thresholds?.warning || []),
@@ -192,6 +193,7 @@ const ResponsiveBarChart = ({
192
193
  lines={linesGraph}
193
194
  setLines={setLinesGraph}
194
195
  title={title}
196
+ titleRef={titleRef}
195
197
  >
196
198
  <Tooltip
197
199
  classes={{
@@ -1,15 +1,31 @@
1
1
  import { Shape } from '@visx/visx';
2
2
  import { ScaleLinear, ScaleTime } from 'd3-scale';
3
- import { path, all, equals, isNil, map, not, nth, pipe, prop } from 'ramda';
3
+ import {
4
+ path,
5
+ all,
6
+ equals,
7
+ isNil,
8
+ map,
9
+ not,
10
+ nth,
11
+ pipe,
12
+ prop,
13
+ type
14
+ } from 'ramda';
4
15
 
5
16
  import { getDates, getTime } from '../../../../common/timeSeries';
6
17
  import { Line, TimeValue } from '../../../../common/timeSeries/models';
7
- import { getPointRadius, getStrokeDashArray } from '../../../../common/utils';
18
+ import {
19
+ getPointRadius,
20
+ getStrokeDashArray,
21
+ getStyle
22
+ } from '../../../../common/utils';
8
23
  import StackedAnchorPoint, {
9
24
  getYAnchorPoint
10
25
  } from '../../../InteractiveComponents/AnchorPoint/StackedAnchorPoint';
11
26
  import { StackValue } from '../../../InteractiveComponents/AnchorPoint/models';
12
27
  import { getCurveFactory, getFillColor } from '../../../common';
28
+ import { LineStyle } from '../../../models';
13
29
  import Point from '../Point';
14
30
 
15
31
  interface Props {
@@ -26,6 +42,7 @@ interface Props {
26
42
  timeSeries: Array<TimeValue>;
27
43
  xScale: ScaleTime<number, number>;
28
44
  yScale: ScaleLinear<number, number>;
45
+ lineStyle: LineStyle | Array<LineStyle>;
29
46
  }
30
47
 
31
48
  const StackLines = ({
@@ -34,19 +51,13 @@ const StackLines = ({
34
51
  yScale,
35
52
  xScale,
36
53
  displayAnchor,
37
- curve,
38
- showPoints,
39
- showArea,
40
- areaTransparency,
41
- lineWidth,
42
- dashLength,
43
- dashOffset,
44
- dotOffset
54
+ lineStyle
45
55
  }: Props): JSX.Element => {
46
- const curveType = getCurveFactory(curve);
47
-
48
- const formattedLineWidth = lineWidth ?? 2;
49
-
56
+ const curveType = getCurveFactory(
57
+ (equals(type(lineStyle), 'Array')
58
+ ? lineStyle?.[0].curve
59
+ : lineStyle?.curve) || 'linear'
60
+ );
50
61
  return (
51
62
  <Shape.AreaStack
52
63
  curve={curveType}
@@ -69,15 +80,21 @@ const StackLines = ({
69
80
  const { areaColor, transparency, lineColor, highlight, metric_id } =
70
81
  nth(index, lines) as Line;
71
82
 
72
- const formattedTransparency = isNil(areaTransparency)
83
+ const style = getStyle({
84
+ style: lineStyle,
85
+ metricId: metric_id
86
+ }) as LineStyle;
87
+ const formattedLineWidth = style?.lineWidth ?? 2;
88
+
89
+ const formattedTransparency = isNil(style?.areaTransparency)
73
90
  ? transparency || 80
74
- : areaTransparency;
91
+ : style.areaTransparency;
75
92
 
76
93
  return (
77
94
  <g key={`stack-${prop('key', stack)}`}>
78
95
  {displayAnchor && (
79
96
  <StackedAnchorPoint
80
- areaColor={areaColor}
97
+ areaColor={style?.areaColor}
81
98
  lineColor={lineColor}
82
99
  stackValues={stack as unknown as Array<StackValue>}
83
100
  timeSeries={timeSeries}
@@ -86,13 +103,13 @@ const StackLines = ({
86
103
  yScale={yScale}
87
104
  />
88
105
  )}
89
- {showPoints &&
106
+ {style?.showPoints &&
90
107
  getDates(timeSeries).map((timeTick) => (
91
108
  <Point
92
109
  key={timeTick.toString()}
93
110
  lineColor={lineColor}
94
111
  metric_id={metric_id}
95
- radius={getPointRadius(lineWidth)}
112
+ radius={getPointRadius(style?.lineWidth)}
96
113
  timeSeries={timeSeries}
97
114
  timeTick={timeTick}
98
115
  xScale={xScale}
@@ -108,7 +125,7 @@ const StackLines = ({
108
125
  d={linePath(stack) || ''}
109
126
  data-metric={metric_id}
110
127
  fill={
111
- equals(showArea, false)
128
+ equals(style?.showArea, false)
112
129
  ? 'transparent'
113
130
  : getFillColor({
114
131
  areaColor: areaColor || lineColor,
@@ -118,10 +135,10 @@ const StackLines = ({
118
135
  opacity={highlight === false ? 0.3 : 1}
119
136
  stroke={lineColor}
120
137
  strokeDasharray={getStrokeDashArray({
121
- dashLength,
122
- dashOffset,
123
- dotOffset,
124
- lineWidth: formattedLineWidth
138
+ dashLength: style?.dashLength,
139
+ dashOffset: style?.dashOffset,
140
+ dotOffset: style?.dotOffset,
141
+ lineWidth: style?.lineWidth ?? 2
125
142
  })}
126
143
  strokeWidth={
127
144
  highlight
@@ -9,13 +9,13 @@ import {
9
9
  getYScale
10
10
  } from '../../../common/timeSeries';
11
11
  import type { Line, TimeValue } from '../../../common/timeSeries/models';
12
- import { getPointRadius } from '../../../common/utils';
12
+ import { getPointRadius, getStyle } from '../../../common/utils';
13
13
  import GuidingLines from '../../InteractiveComponents/AnchorPoint/GuidingLines';
14
14
  import RegularAnchorPoint, {
15
15
  getYAnchorPoint
16
16
  } from '../../InteractiveComponents/AnchorPoint/RegularAnchorPoint';
17
17
  import { displayArea } from '../../helpers/index';
18
- import type { DisplayAnchor, GlobalAreaLines } from '../../models';
18
+ import type { DisplayAnchor, GlobalAreaLines, LineStyle } from '../../models';
19
19
 
20
20
  import Point from './Point';
21
21
  import RegularLine from './RegularLines';
@@ -29,33 +29,24 @@ import {
29
29
  } from './Threshold/models';
30
30
 
31
31
  interface Props extends GlobalAreaLines {
32
- areaTransparency?: number;
33
- curve: 'linear' | 'step' | 'natural';
34
- dashLength?: number;
35
- dashOffset?: number;
36
32
  displayAnchor?: DisplayAnchor;
37
33
  displayedLines: Array<Line>;
38
- dotOffset?: number;
39
34
  graphSvgRef: MutableRefObject<SVGSVGElement | null>;
40
35
  height: number;
41
- lineWidth?: number;
42
36
  scale?: 'linear' | 'logarithmic';
43
37
  scaleLogarithmicBase?: number;
44
- showArea?: boolean;
45
- showPoints?: boolean;
46
38
  timeSeries: Array<TimeValue>;
47
39
  width: number;
48
40
  xScale: ScaleLinear<number, number>;
49
41
  yScalesPerUnit: Record<string, ScaleLinear<number, number>>;
42
+ lineStyle: LineStyle | Array<LineStyle>;
50
43
  }
51
44
 
52
45
  const Lines = ({
53
- areaTransparency,
54
46
  height,
55
47
  graphSvgRef,
56
48
  width,
57
49
  displayAnchor,
58
- curve,
59
50
  yScalesPerUnit,
60
51
  xScale,
61
52
  timeSeries,
@@ -63,14 +54,9 @@ const Lines = ({
63
54
  areaThresholdLines,
64
55
  areaStackedLines,
65
56
  areaRegularLines,
66
- showArea,
67
- showPoints,
68
- lineWidth,
69
- dotOffset,
70
- dashLength,
71
- dashOffset,
72
57
  scale,
73
- scaleLogarithmicBase
58
+ scaleLogarithmicBase,
59
+ lineStyle
74
60
  }: Props): JSX.Element => {
75
61
  const { stackedLinesData, invertedStackedLinesData } = useStackedLines({
76
62
  lines: displayedLines,
@@ -88,18 +74,10 @@ const Lines = ({
88
74
 
89
75
  const displayGuidingLines = displayAnchor?.displayGuidingLines ?? true;
90
76
  const commonStackedLinesProps = {
91
- areaTransparency,
92
- curve,
93
- dashLength,
94
- dashOffset,
95
77
  displayAnchor: displayGuidingLines,
96
- dotOffset,
97
78
  graphHeight: height,
98
79
  graphSvgRef,
99
80
  graphWidth: width,
100
- lineWidth,
101
- showArea,
102
- showPoints,
103
81
  xScale
104
82
  };
105
83
 
@@ -119,6 +97,7 @@ const Lines = ({
119
97
  {Object.entries(stackedLinesData).map(
120
98
  ([unit, { lines, timeSeries: stackedTimeSeries }]) => (
121
99
  <StackedLines
100
+ lineStyle={lineStyle}
122
101
  key={`stacked-${unit}`}
123
102
  lines={lines}
124
103
  timeSeries={stackedTimeSeries}
@@ -130,6 +109,7 @@ const Lines = ({
130
109
  {Object.entries(invertedStackedLinesData).map(
131
110
  ([unit, { lines, timeSeries: stackedTimeSeries }]) => (
132
111
  <StackedLines
112
+ lineStyle={lineStyle}
133
113
  key={`invert-stacked-${unit}`}
134
114
  lines={lines}
135
115
  timeSeries={stackedTimeSeries}
@@ -196,6 +176,11 @@ const Lines = ({
196
176
  timeSeries
197
177
  });
198
178
 
179
+ const style = getStyle({
180
+ style: lineStyle,
181
+ metricId: metric_id
182
+ }) as LineStyle;
183
+
199
184
  return (
200
185
  <g key={metric_id}>
201
186
  {displayGuidingLines && (
@@ -209,13 +194,13 @@ const Lines = ({
209
194
  yScale={yScale}
210
195
  />
211
196
  )}
212
- {showPoints &&
197
+ {style?.showPoints &&
213
198
  getDates(relatedTimeSeries).map((timeTick) => (
214
199
  <Point
215
200
  key={timeTick.toString()}
216
201
  lineColor={lineColor}
217
202
  metric_id={metric_id}
218
- radius={getPointRadius(lineWidth)}
203
+ radius={getPointRadius(style?.lineWidth)}
219
204
  timeSeries={relatedTimeSeries}
220
205
  timeTick={timeTick}
221
206
  xScale={xScale}
@@ -230,21 +215,21 @@ const Lines = ({
230
215
  ))}
231
216
  <RegularLine
232
217
  areaColor={areaColor || lineColor}
233
- curve={curve}
234
- dashLength={dashLength}
235
- dashOffset={dashOffset}
236
- dotOffset={dotOffset}
237
- filled={isNil(showArea) ? filled : showArea}
218
+ curve={style?.curve || 'linear'}
219
+ dashLength={style?.dashLength}
220
+ dashOffset={style?.dashOffset}
221
+ dotOffset={style?.dotOffset}
222
+ filled={isNil(style?.showArea) ? filled : style.showArea}
238
223
  graphHeight={height}
239
224
  highlight={highlight}
240
225
  lineColor={lineColor}
241
- lineWidth={lineWidth}
226
+ lineWidth={style?.lineWidth || 2}
242
227
  metric_id={metric_id}
243
228
  timeSeries={relatedTimeSeries}
244
229
  transparency={
245
- isNil(areaTransparency)
230
+ isNil(style?.areaTransparency)
246
231
  ? transparency || 80
247
- : areaTransparency
232
+ : style.areaTransparency
248
233
  }
249
234
  unit={unit}
250
235
  xScale={xScale}
@@ -9,6 +9,7 @@ import dataCurvesWithSameColor from '../mockedData/curvesWithSameColor.json';
9
9
  import dataLastDay from '../mockedData/lastDay.json';
10
10
  import dataLastDayWithIncompleteValues from '../mockedData/lastDayWithIncompleteValues.json';
11
11
  import dataLastDayWithNullValues from '../mockedData/lastDayWithNullValues.json';
12
+ import dataPingServiceLines from '../mockedData/pingService.json';
12
13
  import dataPingServiceLinesBars from '../mockedData/pingServiceLinesBars.json';
13
14
  import dataPingServiceLinesBarsMixed from '../mockedData/pingServiceLinesBarsMixed.json';
14
15
  import dataPingServiceLinesBarsStacked from '../mockedData/pingServiceLinesBarsStacked.json';
@@ -19,7 +20,10 @@ import { LineChartProps } from './models';
19
20
  import WrapperChart from '.';
20
21
 
21
22
  interface Props
22
- extends Pick<LineChartProps, 'legend' | 'tooltip' | 'axis' | 'lineStyle'> {
23
+ extends Pick<
24
+ LineChartProps,
25
+ 'legend' | 'tooltip' | 'axis' | 'lineStyle' | 'barStyle' | 'additionalLines'
26
+ > {
23
27
  data?: LineChartData;
24
28
  }
25
29
 
@@ -63,7 +67,9 @@ const initialize = ({
63
67
  tooltip,
64
68
  legend,
65
69
  axis,
66
- lineStyle
70
+ lineStyle,
71
+ barStyle,
72
+ additionalLines
67
73
  }: Props): void => {
68
74
  cy.adjustViewport();
69
75
 
@@ -84,7 +90,9 @@ const initialize = ({
84
90
  data={data as unknown as LineChartData}
85
91
  legend={legend}
86
92
  lineStyle={lineStyle}
93
+ barStyle={barStyle}
87
94
  tooltip={tooltip}
95
+ additionalLines={additionalLines}
88
96
  />
89
97
  </Provider>
90
98
  )
@@ -131,7 +139,7 @@ const initializeCustomUnits = ({
131
139
  const checkGraphWidth = (): void => {
132
140
  cy.findByTestId('graph-interaction-zone')
133
141
  .should('have.attr', 'height')
134
- .and('equal', '393');
142
+ .and('equal', '376.203125');
135
143
 
136
144
  cy.findByTestId('graph-interaction-zone').then((graph) => {
137
145
  expect(Number(graph[0].attributes.width.value)).to.be.greaterThan(1170);
@@ -170,7 +178,7 @@ describe('Line chart', () => {
170
178
 
171
179
  cy.contains('Min: 70.31').should('be.visible');
172
180
 
173
- cy.findByTestId('graph-interaction-zone').realMouseMove(452, 26);
181
+ cy.findByTestId('graph-interaction-zone').realMouseMove(230, 26);
174
182
 
175
183
  cy.get('[data-metric="querytime"]').should(
176
184
  'have.attr',
@@ -182,6 +190,11 @@ describe('Line chart', () => {
182
190
  'data-highlight',
183
191
  'false'
184
192
  );
193
+ cy.get('[data-metric="hitratio"]').should(
194
+ 'have.attr',
195
+ 'data-highlight',
196
+ 'true'
197
+ );
185
198
 
186
199
  cy.makeSnapshot();
187
200
  });
@@ -215,25 +228,6 @@ describe('Line chart', () => {
215
228
  cy.makeSnapshot();
216
229
  });
217
230
 
218
- it('displays the tooltip a single metric when the corresponding prop is set', () => {
219
- initialize({ tooltip: { mode: 'single', sortOrder: 'name' } });
220
-
221
- checkGraphWidth();
222
-
223
- cy.contains('Min: 70.31').should('be.visible');
224
-
225
- cy.findByTestId('graph-interaction-zone').realMouseMove(452, 26);
226
-
227
- cy.get('[data-metric="hitratio"]').should(
228
- 'have.attr',
229
- 'data-highlight',
230
- 'true'
231
- );
232
- cy.get('[data-metric="querytime"]').should('not.exist');
233
-
234
- cy.makeSnapshot();
235
- });
236
-
237
231
  it('does not display the tooltip when the corresponding prop is set', () => {
238
232
  initialize({ tooltip: { mode: 'hidden', sortOrder: 'name' } });
239
233
 
@@ -443,7 +437,7 @@ describe('Line chart', () => {
443
437
 
444
438
  cy.contains(':00 AM').should('be.visible');
445
439
 
446
- cy.get('text[transform="rotate(-35, -2, 312.508173777963)"]').should(
440
+ cy.get('text[transform="rotate(-35, -2, 145.04834208635688)"]').should(
447
441
  'be.visible'
448
442
  );
449
443
 
@@ -479,15 +473,14 @@ describe('Line chart', () => {
479
473
  });
480
474
 
481
475
  it('displays the curve in a step style when the prop is set', () => {
482
- initialize({ lineStyle: { curve: 'step' } });
476
+ initialize({ lineStyle: { curve: 'step' }, data: dataPingServiceLines });
483
477
 
484
478
  checkGraphWidth();
485
479
 
486
480
  cy.contains(':00 AM').should('be.visible');
487
- cy.get('[data-metric="13536"]').should('be.visible');
488
- cy.get('[data-metric="13534"]').should('be.visible');
489
- cy.get('[data-metric="13535"]').should('be.visible');
490
- checkLegendInformation();
481
+ cy.get('[data-metric="1"]').should('be.visible');
482
+ cy.get('[data-metric="2"]').should('be.visible');
483
+ cy.get('[data-metric="3"]').should('be.visible');
491
484
 
492
485
  cy.makeSnapshot();
493
486
  });
@@ -525,7 +518,8 @@ describe('Line chart', () => {
525
518
 
526
519
  checkGraphWidth();
527
520
  cy.contains(':00 AM').should('be.visible');
528
- cy.get('circle[cx="37.625"]').should('be.visible');
521
+ cy.get('circle[cx="250.83333333333334"]').should('be.visible');
522
+ cy.get('circle[cy="52.93597418085514"]').should('be.visible');
529
523
 
530
524
  cy.makeSnapshot();
531
525
  });
@@ -552,7 +546,7 @@ describe('Line chart', () => {
552
546
  .and('equals', '4 10');
553
547
  });
554
548
 
555
- it('displays lines with dots width when the prop is set', () => {
549
+ it('displays lines with dashes width when props are set', () => {
556
550
  initialize({ lineStyle: { dashLength: 5, dashOffset: 8 } });
557
551
 
558
552
  checkGraphWidth();
@@ -562,6 +556,31 @@ describe('Line chart', () => {
562
556
  .should('have.attr', 'stroke-dasharray')
563
557
  .and('equals', '5 8');
564
558
  });
559
+
560
+ it('displays only one line with custom style when props are set', () => {
561
+ initialize({
562
+ lineStyle: [
563
+ {
564
+ dashLength: 5,
565
+ dashOffset: 4,
566
+ lineWidth: 1,
567
+ showPoints: true,
568
+ showArea: true,
569
+ metricId: 13534
570
+ }
571
+ ]
572
+ });
573
+
574
+ checkGraphWidth();
575
+
576
+ cy.contains(':00 AM').should('be.visible');
577
+ cy.get('path.visx-area-closed')
578
+ .should('have.attr', 'stroke-dasharray')
579
+ .and('equals', '5 4');
580
+ cy.get('circle[cx="33.44444444444444"]').should('be.visible');
581
+
582
+ cy.makeSnapshot();
583
+ });
565
584
  });
566
585
  });
567
586
 
@@ -690,4 +709,58 @@ describe('Lines and bars', () => {
690
709
 
691
710
  cy.makeSnapshot();
692
711
  });
712
+
713
+ it('displays stacked lines and bars when a line and a bar are customized', () => {
714
+ initialize({
715
+ data: dataPingServiceLinesBarsStacked,
716
+ lineStyle: [
717
+ {
718
+ metricId: 1,
719
+ showArea: false,
720
+ dotOffset: 4,
721
+ lineWidth: 3
722
+ }
723
+ ],
724
+ barStyle: [
725
+ {
726
+ metricId: 10,
727
+ opacity: 0.5,
728
+ radius: 0.3
729
+ }
730
+ ]
731
+ });
732
+
733
+ checkGraphWidth();
734
+
735
+ cy.get(
736
+ 'path[d="M7.501377410468319,273.3424587717121 h56.51239669421488 h1v1 v100.86066622828793 a1,1 0 0 1 -1,1 h-56.51239669421488 a1,1 0 0 1 -1,-1 v-100.86066622828793 v-1h1z"]'
737
+ ).should('be.visible');
738
+ cy.get(
739
+ 'path[d="M24.05509641873278,218.3663782225586 h23.404958677685954 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,17.553719008264462 v19.86864253262454 v17.553719008264462h-17.553719008264462 h-23.404958677685954 h-17.553719008264462v-17.553719008264462 v-19.86864253262454 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,-17.553719008264462z"]'
740
+ ).should('be.visible');
741
+
742
+ cy.makeSnapshot();
743
+ });
744
+
745
+ it('displays additional lines when props are set', () => {
746
+ initialize({
747
+ data: dataPingServiceLines,
748
+ additionalLines: [
749
+ { color: 'pink', unit: '%', yValue: 3 },
750
+ { color: 'red', unit: 'ms', yValue: 0.15, text: 'some text' }
751
+ ]
752
+ });
753
+
754
+ checkGraphWidth();
755
+
756
+ cy.get('path[data-metric="1"]').should('be.visible');
757
+ cy.get('path[data-metric="3"]').should('be.visible');
758
+ cy.get('path[data-metric="3"]').should('be.visible');
759
+
760
+ cy.contains('some text').should('be.visible');
761
+ cy.findByTestId('pink-3').should('be.visible');
762
+ cy.findByTestId('red-0.15').should('be.visible');
763
+
764
+ cy.makeSnapshot();
765
+ });
693
766
  });
@@ -22,6 +22,7 @@ import dataLastDayThreshold from '../mockedData/lastDayThreshold.json';
22
22
  import dataLastDayWithLotOfUnits from '../mockedData/lastDayWithLotOfUnits.json';
23
23
  import dataLastMonth from '../mockedData/lastMonth.json';
24
24
  import dataLastWeek from '../mockedData/lastWeek.json';
25
+ import dataPingService from '../mockedData/pingService.json';
25
26
  import dataPingServiceLinesBars from '../mockedData/pingServiceLinesBars.json';
26
27
  import dataPingServiceLinesBarsMixed from '../mockedData/pingServiceLinesBarsMixed.json';
27
28
  import dataPingServiceLinesBarsStacked from '../mockedData/pingServiceLinesBarsStacked.json';
@@ -536,7 +537,7 @@ export const customLinesAndBars: Story = {
536
537
  render: (args) => (
537
538
  <WrapperChart
538
539
  {...args}
539
- data={dataPingServiceLinesBars as unknown as LineChartData}
540
+ data={dataPingService as unknown as LineChartData}
540
541
  />
541
542
  )
542
543
  };
@@ -565,7 +566,7 @@ export const linesAndBars: Story = {
565
566
  render: (args) => (
566
567
  <WrapperChart
567
568
  {...args}
568
- data={dataPingServiceLinesBars as unknown as LineChartData}
569
+ data={dataPingServiceLinesBarsMixed as unknown as LineChartData}
569
570
  />
570
571
  )
571
572
  };
@@ -695,3 +696,24 @@ export const customYUnits: Story = {
695
696
  args: argumentsData,
696
697
  render: (args) => <CustomYUnits {...args} />
697
698
  };
699
+
700
+ export const WithAdditionalLines: Story = {
701
+ argTypes,
702
+ args: {
703
+ ...argumentsData,
704
+ additionalLines: [
705
+ {
706
+ yValue: 3,
707
+ text: 'my text',
708
+ color: 'grey',
709
+ unit: '%'
710
+ }
711
+ ]
712
+ },
713
+ render: (args) => (
714
+ <WrapperChart
715
+ {...args}
716
+ data={dataPingService as unknown as LineChartData}
717
+ />
718
+ )
719
+ };
@@ -13,6 +13,7 @@ import { ClickAwayListener, Skeleton } from '@mui/material';
13
13
 
14
14
  import { useDeepCompare } from '../../utils';
15
15
  import BarGroup from '../BarChart/BarGroup';
16
+ import AdditionalLine from '../common/BaseChart/AdditionalLine';
16
17
  import BaseChart from '../common/BaseChart/BaseChart';
17
18
  import ChartSvgWrapper from '../common/BaseChart/ChartSvgWrapper';
18
19
  import { useComputeBaseChartDimensions } from '../common/BaseChart/useComputeBaseChartDimensions';
@@ -110,7 +111,8 @@ const Chart = ({
110
111
  thresholdUnit,
111
112
  limitLegend,
112
113
  skipIntersectionObserver,
113
- transformMatrix
114
+ transformMatrix,
115
+ additionalLines
114
116
  }: Props): JSX.Element => {
115
117
  const { classes } = useChartStyles();
116
118
 
@@ -150,15 +152,16 @@ const Chart = ({
150
152
  secondUnit
151
153
  });
152
154
 
153
- const { legendRef, graphWidth, graphHeight } = useComputeBaseChartDimensions({
154
- hasSecondUnit: Boolean(secondUnit),
155
- height,
156
- legendDisplay: legend?.display,
157
- legendHeight: legend?.height,
158
- legendPlacement: legend?.placement,
159
- width,
160
- maxAxisCharacters: maxRightAxisCharacters || maxLeftAxisCharacters
161
- });
155
+ const { legendRef, graphWidth, graphHeight, titleRef } =
156
+ useComputeBaseChartDimensions({
157
+ hasSecondUnit: Boolean(secondUnit),
158
+ height,
159
+ legendDisplay: legend?.display,
160
+ legendHeight: legend?.height,
161
+ legendPlacement: legend?.placement,
162
+ width,
163
+ maxAxisCharacters: maxRightAxisCharacters || maxLeftAxisCharacters
164
+ });
162
165
 
163
166
  const xScale = useMemo(
164
167
  () =>
@@ -273,6 +276,7 @@ const Chart = ({
273
276
  lines={linesGraph}
274
277
  setLines={setLinesGraph}
275
278
  title={title}
279
+ titleRef={titleRef}
276
280
  >
277
281
  <GraphValueTooltip
278
282
  baseAxis={baseAxis}
@@ -312,20 +316,13 @@ const Chart = ({
312
316
  )}
313
317
  {!isEmpty(linesDisplayedAsLine) && (
314
318
  <Lines
315
- areaTransparency={lineStyle?.areaTransparency}
316
- curve={lineStyle?.curve || 'linear'}
317
- dashLength={lineStyle?.dashLength}
318
- dashOffset={lineStyle?.dashOffset}
319
+ lineStyle={lineStyle}
319
320
  displayAnchor={displayAnchor}
320
321
  displayedLines={linesDisplayedAsLine}
321
- dotOffset={lineStyle?.dotOffset}
322
322
  graphSvgRef={graphSvgRef}
323
323
  height={graphHeight - margin.top}
324
- lineWidth={lineStyle?.lineWidth}
325
324
  scale={axis?.scale}
326
325
  scaleLogarithmicBase={axis?.scaleLogarithmicBase}
327
- showArea={lineStyle?.showArea}
328
- showPoints={lineStyle?.showPoints}
329
326
  timeSeries={timeSeries}
330
327
  width={graphWidth}
331
328
  xScale={xScale}
@@ -333,6 +330,14 @@ const Chart = ({
333
330
  {...shapeLines}
334
331
  />
335
332
  )}
333
+ {additionalLines?.map((additionalLine) => (
334
+ <AdditionalLine
335
+ key={additionalLine.yValue}
336
+ {...additionalLine}
337
+ graphWidth={graphWidth}
338
+ yScale={yScalesPerUnit[additionalLine.unit]}
339
+ />
340
+ ))}
336
341
  <InteractionWithGraph
337
342
  annotationData={{ ...annotationEvent }}
338
343
  commonData={{
@@ -70,6 +70,7 @@ const WrapperChart = ({
70
70
  limitLegend,
71
71
  getRef,
72
72
  transformMatrix,
73
+ additionalLines,
73
74
  ...rest
74
75
  }: Props): JSX.Element | null => {
75
76
  const { classes, cx } = useChartStyles();
@@ -126,6 +127,7 @@ const WrapperChart = ({
126
127
  width={width ?? responsiveWidth}
127
128
  zoomPreview={zoomPreview}
128
129
  skipIntersectionObserver={rest.skipIntersectionObserver}
130
+ additionalLines={additionalLines}
129
131
  transformMatrix={transformMatrix}
130
132
  />
131
133
  );
@@ -8,7 +8,7 @@ import type {
8
8
  Axis as AxisYLeft,
9
9
  AxisYRight
10
10
  } from '../common/Axes/models';
11
- import type { LineChartData } from '../common/models';
11
+ import type { AdditionalLineProps, LineChartData } from '../common/models';
12
12
  import type { Line, TimeValue } from '../common/timeSeries/models';
13
13
 
14
14
  import type { FactorsVariation } from './BasicComponents/Lines/Threshold/models';
@@ -110,17 +110,18 @@ export interface LineStyle {
110
110
  export interface LineChartProps {
111
111
  annotationEvent?: AnnotationEvent;
112
112
  axis?: ChartAxis;
113
- barStyle?: BarStyle;
113
+ barStyle?: BarStyle | Array<BarStyle & { metricId: number }>;
114
114
  displayAnchor?: DisplayAnchor;
115
115
  header?: LineChartHeader;
116
116
  height?: number | null;
117
117
  legend?: LegendModel;
118
- lineStyle?: LineStyle;
118
+ lineStyle?: LineStyle | Array<LineStyle & { metricId: number }>;
119
119
  timeShiftZones?: InteractedZone;
120
120
  tooltip?: Tooltip;
121
121
  width: number;
122
122
  zoomPreview?: InteractedZone;
123
123
  skipIntersectionObserver?: boolean;
124
+ additionalLines?: Array<AdditionalLineProps>;
124
125
  }
125
126
 
126
127
  export interface Area {
@@ -156,5 +156,8 @@ export const treeWithZoom: Story = {
156
156
  getStrokeWidth: ({ target }) => (target.status === 'ok' ? 1 : 2)
157
157
  }
158
158
  },
159
- render: TreeWithZoom
159
+ render: TreeWithZoom,
160
+ parameters: {
161
+ chromatic: { disableSnapshot: true }
162
+ }
160
163
  };
@@ -46,7 +46,7 @@ const Axes = ({
46
46
 
47
47
  const [, secondUnit] = getUnits(lines);
48
48
 
49
- const xTickCount = Math.min(Math.ceil(width / 82), 12);
49
+ const xTickCount = Math.floor(Math.min(width / 100, 12));
50
50
 
51
51
  const domain = xScale.domain();
52
52
 
@@ -0,0 +1,37 @@
1
+ import { useMemo } from 'react';
2
+ import { AdditionalLineProps } from '../models';
3
+
4
+ interface Props extends AdditionalLineProps {
5
+ graphWidth: number;
6
+ yScale;
7
+ }
8
+
9
+ const AdditionalLine = ({
10
+ yValue,
11
+ color,
12
+ text,
13
+ graphWidth,
14
+ yScale
15
+ }: Props): JSX.Element => {
16
+ const positionY = useMemo(() => yScale(yValue), [yValue, yScale]);
17
+
18
+ return (
19
+ <g>
20
+ {text && (
21
+ <text x={8} y={positionY - 8} fill={color} style={{ fontSize: '10px' }}>
22
+ {text}
23
+ </text>
24
+ )}
25
+ <line
26
+ x1={0}
27
+ x2={graphWidth}
28
+ y1={positionY}
29
+ y2={positionY}
30
+ stroke={color}
31
+ data-testid={`${color}-${yValue}`}
32
+ />
33
+ </g>
34
+ );
35
+ };
36
+
37
+ export default AdditionalLine;
@@ -23,6 +23,7 @@ interface Props {
23
23
  displayLegend: boolean;
24
24
  legendHeight?: number;
25
25
  };
26
+ titleRef: MutableRefObject<HTMLDivElement | null>;
26
27
  legendRef: MutableRefObject<HTMLDivElement | null>;
27
28
  limitLegend?: number | false;
28
29
  lines: Array<Line>;
@@ -42,6 +43,7 @@ const BaseChart = ({
42
43
  setLines,
43
44
  children,
44
45
  legendRef,
46
+ titleRef,
45
47
  title,
46
48
  header,
47
49
  isHorizontal = true
@@ -68,7 +70,9 @@ const BaseChart = ({
68
70
 
69
71
  return (
70
72
  <>
71
- <Header header={header} title={title} />
73
+ <div ref={titleRef}>
74
+ <Header header={header} title={title} ref={titleRef} />
75
+ </div>
72
76
  <div className={classes.container}>
73
77
  <Stack
74
78
  direction={equals(legend?.placement, 'left') ? 'row' : 'row-reverse'}
@@ -19,13 +19,11 @@ const Header = ({ title, header }: Props): JSX.Element => {
19
19
  Component: (
20
20
  <div className={classes.header}>
21
21
  <div />
22
- <div>
23
- {displayTitle && (
24
- <Typography align="center" variant="body1">
25
- {title}
26
- </Typography>
27
- )}
28
- </div>
22
+ {displayTitle && (
23
+ <Typography align="center" variant="body1" className={classes.title}>
24
+ {title}
25
+ </Typography>
26
+ )}
29
27
  {header?.extraComponent}
30
28
  </div>
31
29
  ),
@@ -3,7 +3,11 @@ import { makeStyles } from 'tss-react/mui';
3
3
  export const ussHeaderChartStyles = makeStyles()({
4
4
  header: {
5
5
  display: 'grid',
6
- gridTemplateColumns: '0.4fr 1fr 0.4fr',
6
+ gridTemplateColumns: 'auto 1fr auto',
7
7
  width: '100%'
8
+ },
9
+ title: {
10
+ whiteSpace: 'pre-wrap',
11
+ lineHeight: '1.2'
8
12
  }
9
13
  });
@@ -21,6 +21,7 @@ interface UseComputeBaseChartDimensionsState {
21
21
  graphHeight: number;
22
22
  graphWidth: number;
23
23
  legendRef: MutableRefObject<HTMLDivElement | null>;
24
+ titleRef: MutableRefObject<HTMLDivElement | null>;
24
25
  }
25
26
 
26
27
  export const useComputeBaseChartDimensions = ({
@@ -33,6 +34,7 @@ export const useComputeBaseChartDimensions = ({
33
34
  maxAxisCharacters
34
35
  }: UseComputeBaseChartDimensionsProps): UseComputeBaseChartDimensionsState => {
35
36
  const legendRef = useRef<HTMLDivElement | null>(null);
37
+ const titleRef = useRef<HTMLDivElement | null>(null);
36
38
 
37
39
  const currentLegendHeight =
38
40
  legendHeight ?? (legendRef.current?.getBoundingClientRect().height || 0);
@@ -57,12 +59,17 @@ export const useComputeBaseChartDimensions = ({
57
59
  : 0;
58
60
  const graphHeight =
59
61
  (height || 0) > 0
60
- ? (height || 0) - margin.top - 5 - legendBoundingHeight
62
+ ? (height || 0) -
63
+ margin.top -
64
+ legendBoundingHeight -
65
+ (titleRef.current?.getBoundingClientRect().height || 0) -
66
+ 5
61
67
  : 0;
62
68
 
63
69
  return {
64
70
  graphHeight,
65
71
  graphWidth,
66
- legendRef
72
+ legendRef,
73
+ titleRef
67
74
  };
68
75
  };
@@ -16,3 +16,10 @@ export interface Thresholds {
16
16
  enabled: boolean;
17
17
  warning: Array<Threshold>;
18
18
  }
19
+
20
+ export interface AdditionalLineProps {
21
+ yValue: number;
22
+ text?: string;
23
+ color: string;
24
+ unit: string;
25
+ }
@@ -581,7 +581,7 @@ const formatMetricValue = ({
581
581
 
582
582
  const formattedMetricValue = numeral(Math.abs(value))
583
583
  .format(`0.[00]${formatSuffix}`)
584
- .replace(/iB/g, unit);
584
+ .replace(/(iB|B)/g, unit);
585
585
 
586
586
  if (lt(value, 0)) {
587
587
  return `-${formattedMetricValue}`;
@@ -12,11 +12,14 @@ import {
12
12
  length,
13
13
  lt,
14
14
  lte,
15
- pluck
15
+ pluck,
16
+ type
16
17
  } from 'ramda';
17
18
 
18
19
  import { Theme, darken, getLuminance, lighten } from '@mui/material';
19
20
 
21
+ import { BarStyle } from '../BarChart/models';
22
+ import { LineStyle } from '../Chart/models';
20
23
  import { Threshold, Thresholds } from './models';
21
24
  import { formatMetricValue } from './timeSeries';
22
25
  import { Line, TimeValue } from './timeSeries/models';
@@ -182,6 +185,24 @@ export const commonTickLabelProps = {
182
185
  textAnchor: 'middle'
183
186
  };
184
187
 
188
+ interface GetStyleProps {
189
+ metricId?: number;
190
+ style:
191
+ | LineStyle
192
+ | BarStyle
193
+ | Array<LineStyle & { metricId: number }>
194
+ | Array<BarStyle & { metricId: number }>;
195
+ }
196
+
197
+ export const getStyle = ({
198
+ style,
199
+ metricId
200
+ }: GetStyleProps): BarStyle | LineStyle => {
201
+ return equals(type(style), 'Array')
202
+ ? style.find((metricStyle) => equals(metricId, metricStyle.metricId))
203
+ : style;
204
+ };
205
+
185
206
  interface GetFormattedAxisValuesProps {
186
207
  thresholdUnit?: string;
187
208
  axisUnit: string;
@@ -329,20 +329,7 @@
329
329
  0.32339333333,
330
330
  null
331
331
  ],
332
- "prints": [
333
- [
334
- "Last:0.32"
335
- ],
336
- [
337
- "Min:0.03"
338
- ],
339
- [
340
- "Max:0.97"
341
- ],
342
- [
343
- "Average:0.51"
344
- ]
345
- ],
332
+ "prints": [["Last:0.32"], ["Min:0.03"], ["Max:0.97"], ["Average:0.51"]],
346
333
  "last_value": 0.32,
347
334
  "minimum_value": 0.03,
348
335
  "maximum_value": 0.97,
@@ -667,18 +654,10 @@
667
654
  null
668
655
  ],
669
656
  "prints": [
670
- [
671
- "Last:87.27"
672
- ],
673
- [
674
- "Min:70.31"
675
- ],
676
- [
677
- "Max:88.03"
678
- ],
679
- [
680
- "Average:78.07"
681
- ]
657
+ ["Last:87.27"],
658
+ ["Min:70.31"],
659
+ ["Max:88.03"],
660
+ ["Average:78.07"]
682
661
  ],
683
662
  "last_value": 87.27,
684
663
  "minimum_value": 70.31,
@@ -1003,20 +982,7 @@
1003
982
  null,
1004
983
  null
1005
984
  ],
1006
- "prints": [
1007
- [
1008
- "Last:0.65"
1009
- ],
1010
- [
1011
- "Min:0.03"
1012
- ],
1013
- [
1014
- "Max:0.98"
1015
- ],
1016
- [
1017
- "Average:0.50"
1018
- ]
1019
- ],
985
+ "prints": [["Last:0.65"], ["Min:0.03"], ["Max:0.98"], ["Average:0.50"]],
1020
986
  "last_value": 0.65,
1021
987
  "minimum_value": 0.03,
1022
988
  "maximum_value": 0.98,
@@ -62,7 +62,7 @@
62
62
  "ds_filled": true,
63
63
  "ds_invert": false,
64
64
  "ds_legend": null,
65
- "ds_stack": false,
65
+ "ds_stack": true,
66
66
  "ds_order": 1,
67
67
  "ds_transparency": 80.0,
68
68
  "ds_color_line_mode": 0
@@ -99,7 +99,7 @@
99
99
  "ds_filled": true,
100
100
  "ds_invert": false,
101
101
  "ds_legend": null,
102
- "ds_stack": false,
102
+ "ds_stack": true,
103
103
  "ds_order": 1,
104
104
  "ds_transparency": 80.0,
105
105
  "ds_color_line_mode": 0
@@ -144,7 +144,7 @@
144
144
  "ds_filled": false,
145
145
  "ds_invert": false,
146
146
  "ds_legend": null,
147
- "ds_stack": false,
147
+ "ds_stack": true,
148
148
  "ds_order": 2,
149
149
  "ds_transparency": 80.0,
150
150
  "ds_color_line_mode": 0
@@ -130,7 +130,9 @@ const useGraphQuery = ({
130
130
  parameters: {
131
131
  search: {
132
132
  lists: resources.map((resource) => ({
133
- field: resourceTypeQueryParameter[resource.resourceType],
133
+ field: equals(resource.resourceType, 'hostgroup')
134
+ ? resourceTypeQueryParameter[WidgetResourceType.hostGroup]
135
+ : resourceTypeQueryParameter[resource.resourceType],
134
136
  values: equals(resource.resourceType, 'service')
135
137
  ? pluck('name', resource.resources)
136
138
  : pluck('id', resource.resources)