@centreon/ui 25.10.5 → 25.10.7

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.10.5",
3
+ "version": "25.10.7",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "update:deps": "pnpx npm-check-updates -i --format group",
@@ -84,6 +84,61 @@ const checkWidth = (orientation): void => {
84
84
  };
85
85
 
86
86
  describe('Bar chart', () => {
87
+ it('displays a tooltip when a single bar is hovered', () => {
88
+ initialize({
89
+ orientation: 'horizontal'
90
+ });
91
+
92
+ checkWidth('horizontal');
93
+ cy.contains('0 ms').should('be.visible');
94
+ cy.contains('20').should('be.visible');
95
+ cy.contains(':40 AM').should('be.visible');
96
+
97
+ cy.findByTestId('stacked-bar-10-0-7650.368581547736').realHover();
98
+
99
+ cy.contains('06/19/2024').should('be.visible');
100
+ cy.contains('Centreon-Server: Round-Trip Maximum Time').should(
101
+ 'be.visible'
102
+ );
103
+ cy.contains('7.47 KB').should('be.visible');
104
+
105
+ cy.makeSnapshot();
106
+ });
107
+
108
+ it('displays a tooltip when a stacked bar is hovered', () => {
109
+ initialize({
110
+ data: dataPingServiceStacked,
111
+ orientation: 'horizontal',
112
+ tooltip: {
113
+ mode: 'all',
114
+ sortOrder: 'ascending'
115
+ }
116
+ });
117
+
118
+ checkWidth('horizontal');
119
+ cy.contains('0 ms').should('be.visible');
120
+ cy.contains('20').should('be.visible');
121
+ cy.contains(':40 AM').should('be.visible');
122
+
123
+ cy.findByTestId('stacked-bar-1-0-0.05296').realHover();
124
+
125
+ cy.contains('06/19/2024').should('be.visible');
126
+ cy.contains('Centreon-Server: Round-Trip Maximum Time').should(
127
+ 'be.visible'
128
+ );
129
+ cy.contains('Centreon-Server: Round-Trip Average Time').should(
130
+ 'be.visible'
131
+ );
132
+ cy.contains('Centreon-Server: Round-Trip Minimum Time').should(
133
+ 'be.visible'
134
+ );
135
+ cy.contains('0.05 ms').should('be.visible');
136
+ cy.contains('0.02 ms').should('be.visible');
137
+ cy.contains('0.11 ms').should('be.visible');
138
+
139
+ cy.findByTestId('stacked-bar-3-0-0.16196').should('be.visible');
140
+ });
141
+
87
142
  ['horizontal', 'vertical'].forEach((orientation) => {
88
143
  it(`displays the bar chart ${orientation}ly`, () => {
89
144
  initialize({ orientation });
@@ -183,27 +238,6 @@ describe('Bar chart', () => {
183
238
  });
184
239
  });
185
240
 
186
- it('displays a tooltip when a single bar is hovered', () => {
187
- initialize({
188
- orientation: 'horizontal'
189
- });
190
-
191
- checkWidth('horizontal');
192
- cy.contains('0 ms').should('be.visible');
193
- cy.contains('20').should('be.visible');
194
- cy.contains(':40 AM').should('be.visible');
195
-
196
- cy.findByTestId('stacked-bar-10-0-7650.368581547736').realHover();
197
-
198
- cy.contains('06/19/2024').should('be.visible');
199
- cy.contains('Centreon-Server: Round-Trip Maximum Time').should(
200
- 'be.visible'
201
- );
202
- cy.contains('7.47 KB').should('be.visible');
203
-
204
- cy.makeSnapshot();
205
- });
206
-
207
241
  it('does not display a tooltip when a bar is hovered and a props is set', () => {
208
242
  initialize({
209
243
  data: dataPingServiceStacked,
@@ -228,68 +262,6 @@ describe('Bar chart', () => {
228
262
  cy.makeSnapshot();
229
263
  });
230
264
 
231
- it('displays a tooltip when a stacked bar is hovered', () => {
232
- initialize({
233
- data: dataPingServiceStacked,
234
- orientation: 'horizontal',
235
- tooltip: {
236
- mode: 'all',
237
- sortOrder: 'ascending'
238
- }
239
- });
240
-
241
- checkWidth('horizontal');
242
- cy.contains('0 ms').should('be.visible');
243
- cy.contains('20').should('be.visible');
244
- cy.contains(':40 AM').should('be.visible');
245
-
246
- cy.findByTestId('stacked-bar-1-0-0.05296').realHover();
247
-
248
- cy.contains('06/19/2024').should('be.visible');
249
- cy.contains('Centreon-Server: Round-Trip Maximum Time').should(
250
- 'be.visible'
251
- );
252
- cy.contains('Centreon-Server: Round-Trip Average Time').should(
253
- 'be.visible'
254
- );
255
- cy.contains('Centreon-Server: Round-Trip Minimum Time').should(
256
- 'be.visible'
257
- );
258
- cy.contains('0.05 ms').should('be.visible');
259
- cy.contains('0.02 ms').should('be.visible');
260
- cy.contains('0.11 ms').should('be.visible');
261
-
262
- cy.findByTestId('stacked-bar-3-0-0.16196').should('be.visible');
263
- });
264
-
265
- it('displays a tooltip with a single metric when a stacked bar is hovered and a prop is set', () => {
266
- initialize({
267
- data: dataPingServiceStacked,
268
- orientation: 'horizontal',
269
- tooltip: {
270
- mode: 'single',
271
- sortOrder: 'descending'
272
- }
273
- });
274
-
275
- checkWidth('horizontal');
276
- cy.contains('0 ms').should('be.visible');
277
- cy.contains('20').should('be.visible');
278
- cy.contains(':40 AM').should('be.visible');
279
-
280
- cy.findByTestId('stacked-bar-1-0-0.05296').realHover();
281
-
282
- cy.contains('06/19/2024').should('be.visible');
283
- cy.contains('Centreon-Server: Round-Trip Average Time').should(
284
- 'be.visible'
285
- );
286
- cy.contains('0.05 ms').should('be.visible');
287
-
288
- cy.findByTestId('stacked-bar-3-0-0.16196').should('be.visible');
289
-
290
- cy.makeSnapshot();
291
- });
292
-
293
265
  it('displays the bottom axis correctly when data starts from several days ago', () => {
294
266
  initialize({
295
267
  data: dataLastWeek,
@@ -1,4 +1,4 @@
1
- import { memo } from 'react';
1
+ import { memo, ReactElement } from 'react';
2
2
 
3
3
  import { ScaleType, scaleBand } from '@visx/scale';
4
4
  import { BarRounded } from '@visx/shape';
@@ -22,6 +22,7 @@ interface Props extends Omit<UseBarStackProps, 'xScale'> {
22
22
  barWidth: number;
23
23
  isTooltipHidden: boolean;
24
24
  neutralValue: number;
25
+ isStacked?: boolean;
25
26
  }
26
27
 
27
28
  const getPadding = ({ padding, size, isNegativeValue }): number => {
@@ -83,8 +84,9 @@ const BarStack = ({
83
84
  barIndex,
84
85
  isTooltipHidden,
85
86
  barStyle = { opacity: 1, radius: 0.2 },
86
- neutralValue
87
- }: Props): JSX.Element => {
87
+ neutralValue,
88
+ isStacked
89
+ }: Props): ReactElement => {
88
90
  const {
89
91
  BarStackComponent,
90
92
  commonBarStackProps,
@@ -118,6 +120,15 @@ const BarStack = ({
118
120
  metricId: Number(bar.key)
119
121
  }) as BarStyle;
120
122
 
123
+ const barY =
124
+ isNegativeValue && isStacked && !shouldApplyRadiusOnBottom
125
+ ? getPadding({
126
+ isNegativeValue,
127
+ padding: bar.y,
128
+ size: bar.height
129
+ })
130
+ : bar.y;
131
+
121
132
  return (
122
133
  <BarRounded
123
134
  {...barRoundedProps}
@@ -128,10 +139,10 @@ const BarStack = ({
128
139
  barWidth,
129
140
  y: isHorizontal
130
141
  ? getPadding({
131
- isNegativeValue,
132
- padding: bar.y,
133
- size: bar.height
134
- })
142
+ isNegativeValue,
143
+ padding: bar.y,
144
+ size: bar.height
145
+ })
135
146
  : barPadding,
136
147
  isFirstBar: shouldApplyRadiusOnBottom,
137
148
  isHorizontal,
@@ -146,19 +157,19 @@ const BarStack = ({
146
157
  isHorizontal
147
158
  ? barPadding
148
159
  : getPadding({
149
- isNegativeValue,
150
- padding: bar.x,
151
- size: bar.width
152
- })
160
+ isNegativeValue,
161
+ padding: bar.x,
162
+ size: bar.width
163
+ })
153
164
  }
154
- y={isHorizontal ? bar.y : barPadding}
165
+ y={isHorizontal ? barY : barPadding}
155
166
  onMouseEnter={
156
167
  isTooltipHidden
157
168
  ? undefined
158
169
  : hoverBar({
159
- barIndex,
160
- highlightedMetric: Number(bar.key)
161
- })
170
+ barIndex,
171
+ highlightedMetric: Number(bar.key)
172
+ })
162
173
  }
163
174
  onMouseLeave={isTooltipHidden ? undefined : exitBar}
164
175
  />
@@ -61,18 +61,19 @@ const MemoizedGroup = ({
61
61
  const linesBar = isStackedBar
62
62
  ? stackedLinesTimeSeriesPerUnit[bar.key.replace('stacked-', '')].lines
63
63
  : (notStackedLines.find(({ metric_id }) =>
64
- equals(metric_id, Number(bar.key))
65
- ) as Line);
64
+ equals(metric_id, Number(bar.key))
65
+ ) as Line);
66
66
  const timeSeriesBar = isStackedBar
67
67
  ? stackedLinesTimeSeriesPerUnit[bar.key.replace('stacked-', '')]
68
68
  .timeSeries
69
69
  : notStackedTimeSeries.map((timeSerie) => ({
70
- timeTick: timeSerie.timeTick,
71
- [bar.key]: timeSerie[Number(bar.key)]
72
- }));
70
+ timeTick: timeSerie.timeTick,
71
+ [bar.key]: timeSerie[Number(bar.key)]
72
+ }));
73
73
 
74
74
  return isStackedBar ? (
75
75
  <BarStack
76
+ isStacked
76
77
  key={`bar-${barGroup.index}-${bar.width}-${bar.y}-${bar.height}-${bar.x}`}
77
78
  barIndex={barGroup.index}
78
79
  barPadding={isHorizontal ? bar.x : bar.y}
@@ -82,7 +83,7 @@ const MemoizedGroup = ({
82
83
  isTooltipHidden={isTooltipHidden}
83
84
  lines={linesBar as Array<Line>}
84
85
  timeSeries={timeSeriesBar}
85
- yScale={yScalesPerUnit[bar.key.replace('stacked-', '')]}
86
+ yScale={yScalesPerUnit[bar.key.replace('stacked-', '')] ?? undefined}
86
87
  neutralValue={neutralValue}
87
88
  />
88
89
  ) : (
@@ -96,7 +97,7 @@ const MemoizedGroup = ({
96
97
  isTooltipHidden={isTooltipHidden}
97
98
  lines={[linesBar as Line]}
98
99
  timeSeries={timeSeriesBar}
99
- yScale={yScalesPerUnit[(linesBar as Line).unit]}
100
+ yScale={yScalesPerUnit[(linesBar as Line).unit ?? undefined]}
100
101
  neutralValue={neutralValue}
101
102
  />
102
103
  );
@@ -27,6 +27,7 @@ import { StackValue } from '../../../InteractiveComponents/AnchorPoint/models';
27
27
  import { getCurveFactory, getFillColor } from '../../../common';
28
28
  import { LineStyle } from '../../../models';
29
29
  import Point from '../Point';
30
+ import { ReactElement } from 'react';
30
31
 
31
32
  interface Props {
32
33
  areaTransparency?: number;
@@ -56,7 +57,7 @@ const StackLines = ({
56
57
  lineStyle,
57
58
  hasSecondUnit,
58
59
  maxLeftAxisCharacters
59
- }: Props): JSX.Element => {
60
+ }: Props): ReactElement => {
60
61
  const curveType = getCurveFactory(
61
62
  (equals(type(lineStyle), 'Array')
62
63
  ? lineStyle?.[0].curve
@@ -134,9 +135,9 @@ const StackLines = ({
134
135
  equals(style?.showArea, false)
135
136
  ? 'transparent'
136
137
  : getFillColor({
137
- areaColor: areaColor || lineColor,
138
- transparency: formattedTransparency
139
- })
138
+ areaColor: areaColor || lineColor,
139
+ transparency: formattedTransparency
140
+ })
140
141
  }
141
142
  opacity={highlight === false ? 0.3 : 1}
142
143
  stroke={lineColor}
@@ -107,7 +107,7 @@ const Lines = ({
107
107
  key={`stacked-${unit}`}
108
108
  lines={lines}
109
109
  timeSeries={stackedTimeSeries}
110
- yScale={yScalesPerUnit[unit]}
110
+ yScale={yScalesPerUnit[unit] ?? undefined}
111
111
  {...commonStackedLinesProps}
112
112
  />
113
113
  )
@@ -123,7 +123,7 @@ const Lines = ({
123
123
  invert: '1',
124
124
  scale,
125
125
  scaleLogarithmicBase,
126
- unit,
126
+ unit: unit ?? undefined,
127
127
  yScalesPerUnit
128
128
  })}
129
129
  {...commonStackedLinesProps}
@@ -146,107 +146,107 @@ const Lines = ({
146
146
 
147
147
  {displayAreaRegularLines
148
148
  ? regularLines.map(
149
- ({
150
- areaColor,
151
- transparency,
152
- lineColor,
153
- filled,
149
+ ({
150
+ areaColor,
151
+ transparency,
152
+ lineColor,
153
+ filled,
154
+ unit,
155
+ highlight,
156
+ invert,
157
+ metric_id,
158
+ ...rest
159
+ }) => {
160
+ const yScale = getYScale({
161
+ invert,
162
+ scale,
163
+ scaleLogarithmicBase,
154
164
  unit,
155
- highlight,
165
+ yScalesPerUnit
166
+ });
167
+ const relatedTimeSeries = getTimeSeriesForLines({
156
168
  invert,
157
- metric_id,
158
- ...rest
159
- }) => {
160
- const yScale = getYScale({
161
- invert,
162
- scale,
163
- scaleLogarithmicBase,
164
- unit,
165
- yScalesPerUnit
166
- });
167
- const relatedTimeSeries = getTimeSeriesForLines({
168
- invert,
169
- lines: [
170
- {
171
- areaColor,
172
- filled,
173
- highlight,
174
- invert,
175
- lineColor,
176
- metric_id,
177
- transparency,
178
- unit,
179
- ...rest
180
- }
181
- ],
182
- timeSeries
183
- });
184
-
185
- const style = getStyle({
186
- style: lineStyle,
187
- metricId: metric_id
188
- }) as LineStyle;
189
-
190
- return (
191
- <g key={metric_id}>
192
- {displayGuidingLines && (
193
- <RegularAnchorPoint
194
- areaColor={areaColor || lineColor}
195
- lineColor={lineColor}
196
- metric_id={metric_id}
197
- timeSeries={relatedTimeSeries}
198
- transparency={transparency}
199
- xScale={xScale}
200
- yScale={yScale}
201
- maxLeftAxisCharacters={maxLeftAxisCharacters}
202
- hasSecondUnit={hasSecondUnit}
203
- />
204
- )}
205
- {style?.showPoints &&
206
- getDates(relatedTimeSeries).map((timeTick) => (
207
- <Point
208
- key={timeTick.toString()}
209
- lineColor={lineColor}
210
- metric_id={metric_id}
211
- radius={getPointRadius(style?.lineWidth)}
212
- timeSeries={relatedTimeSeries}
213
- timeTick={timeTick}
214
- xScale={xScale}
215
- yPoint={getYAnchorPoint({
216
- metric_id,
217
- timeSeries: relatedTimeSeries,
218
- timeTick,
219
- yScale
220
- })}
221
- yScale={yScale}
222
- />
223
- ))}
224
- <RegularLine
169
+ lines: [
170
+ {
171
+ areaColor,
172
+ filled,
173
+ highlight,
174
+ invert,
175
+ lineColor,
176
+ metric_id,
177
+ transparency,
178
+ unit,
179
+ ...rest
180
+ }
181
+ ],
182
+ timeSeries
183
+ });
184
+
185
+ const style = getStyle({
186
+ style: lineStyle,
187
+ metricId: metric_id
188
+ }) as LineStyle;
189
+
190
+ return (
191
+ <g key={metric_id}>
192
+ {displayGuidingLines && (
193
+ <RegularAnchorPoint
225
194
  areaColor={areaColor || lineColor}
226
- curve={style?.curve || 'linear'}
227
- dashLength={style?.dashLength}
228
- dashOffset={style?.dashOffset}
229
- dotOffset={style?.dotOffset}
230
- filled={isNil(style?.showArea) ? filled : style.showArea}
231
- graphHeight={height}
232
- highlight={highlight}
233
195
  lineColor={lineColor}
234
- lineWidth={style?.lineWidth || 2}
235
196
  metric_id={metric_id}
236
197
  timeSeries={relatedTimeSeries}
237
- transparency={
238
- isNil(style?.areaTransparency)
239
- ? transparency || 80
240
- : style.areaTransparency
241
- }
242
- unit={unit}
198
+ transparency={transparency}
243
199
  xScale={xScale}
244
200
  yScale={yScale}
201
+ maxLeftAxisCharacters={maxLeftAxisCharacters}
202
+ hasSecondUnit={hasSecondUnit}
245
203
  />
246
- </g>
247
- );
248
- }
249
- )
204
+ )}
205
+ {style?.showPoints &&
206
+ getDates(relatedTimeSeries).map((timeTick) => (
207
+ <Point
208
+ key={timeTick.toString()}
209
+ lineColor={lineColor}
210
+ metric_id={metric_id}
211
+ radius={getPointRadius(style?.lineWidth)}
212
+ timeSeries={relatedTimeSeries}
213
+ timeTick={timeTick}
214
+ xScale={xScale}
215
+ yPoint={getYAnchorPoint({
216
+ metric_id,
217
+ timeSeries: relatedTimeSeries,
218
+ timeTick,
219
+ yScale
220
+ })}
221
+ yScale={yScale}
222
+ />
223
+ ))}
224
+ <RegularLine
225
+ areaColor={areaColor || lineColor}
226
+ curve={style?.curve || 'linear'}
227
+ dashLength={style?.dashLength}
228
+ dashOffset={style?.dashOffset}
229
+ dotOffset={style?.dotOffset}
230
+ filled={isNil(style?.showArea) ? filled : style.showArea}
231
+ graphHeight={height}
232
+ highlight={highlight}
233
+ lineColor={lineColor}
234
+ lineWidth={style?.lineWidth || 2}
235
+ metric_id={metric_id}
236
+ timeSeries={relatedTimeSeries}
237
+ transparency={
238
+ isNil(style?.areaTransparency)
239
+ ? transparency || 80
240
+ : style.areaTransparency
241
+ }
242
+ unit={unit}
243
+ xScale={xScale}
244
+ yScale={yScale}
245
+ />
246
+ </g>
247
+ );
248
+ }
249
+ )
250
250
  : null}
251
251
  </g>
252
252
  );
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  type MutableRefObject,
3
+ ReactElement,
3
4
  useEffect,
4
5
  useMemo,
5
6
  useRef,
@@ -116,7 +117,7 @@ const Chart = ({
116
117
  min,
117
118
  max,
118
119
  boundariesUnit
119
- }: Props): JSX.Element => {
120
+ }: Props): ReactElement => {
120
121
  const { classes } = useChartStyles();
121
122
 
122
123
  const { title, timeSeries, baseAxis, lines } = graphData;
@@ -197,7 +198,8 @@ const Chart = ({
197
198
  valueGraphHeight: graphHeight - margin.bottom,
198
199
  min,
199
200
  max,
200
- boundariesUnit
201
+ boundariesUnit,
202
+ isFilled: lineStyle?.showArea
201
203
  }),
202
204
  [
203
205
  linesGraph,
@@ -32,7 +32,8 @@ import {
32
32
  reject,
33
33
  sortBy,
34
34
  split,
35
- uniq
35
+ uniq,
36
+ isNotNil
36
37
  } from 'ramda';
37
38
 
38
39
  import { margin } from '../../Chart/common';
@@ -158,8 +159,8 @@ const getMetrics = (timeValue: TimeValue): Array<string> =>
158
159
 
159
160
  const getValueForMetric =
160
161
  (timeValue: TimeValue) =>
161
- (metric_id: number): number =>
162
- prop(metric_id, timeValue) as number;
162
+ (metric_id: number): number =>
163
+ prop(metric_id, timeValue) as number;
163
164
 
164
165
  const getUnits = (lines: Array<Line>): Array<string> =>
165
166
  pipe(map(prop('unit')), uniq)(lines);
@@ -303,8 +304,8 @@ const getTimeSeriesForLines = ({
303
304
  ...acc,
304
305
  [metric_id]:
305
306
  invert &&
306
- metricsValue[metric_id] &&
307
- gt(metricsValue[metric_id], 0)
307
+ metricsValue[metric_id] &&
308
+ gt(metricsValue[metric_id], 0)
308
309
  ? negate(metricsValue[metric_id])
309
310
  : metricsValue[metric_id]
310
311
  };
@@ -337,10 +338,10 @@ const getYScale = ({
337
338
 
338
339
  return invert
339
340
  ? getScaleType(scale)({
340
- base: scaleLogarithmicBase,
341
- domain: yScale.domain().reverse(),
342
- range: yScale.range().reverse()
343
- })
341
+ base: scaleLogarithmicBase,
342
+ domain: yScale.domain().reverse(),
343
+ range: yScale.range().reverse()
344
+ })
344
345
  : yScale;
345
346
  };
346
347
 
@@ -377,6 +378,7 @@ const getScale = ({
377
378
  invert,
378
379
  hasDisplayAsBar,
379
380
  hasLineFilled,
381
+ hasStackedLines,
380
382
  min,
381
383
  max
382
384
  }): ScaleLinear<number, number> => {
@@ -384,30 +386,42 @@ const getScale = ({
384
386
  const sanitizedValuesForMinimum = min
385
387
  ? [min]
386
388
  : 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;
389
+ invert && graphValues.every(lt(0))
390
+ ? negate(getMax(graphValues))
391
+ : getMin(graphValues),
392
+ !isEmpty(stackedValues) &&
393
+ !equals(stackedValues, [0]) &&
394
+ getMin(stackedValues),
395
+ Math.min(...thresholds)
396
+ ]);
397
+ const minValue = Math.min(...sanitizedValuesForMinimum.filter(isNotNil));
400
398
 
401
399
  const sanitizedValuesForMaximum = max
402
400
  ? [max]
403
401
  : 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;
402
+ getMax(graphValues),
403
+ getMax(stackedValues),
404
+ hasOnlyZeroesHasValue(graphValues) ? 1 : null,
405
+ Math.max(...thresholds)
406
+ ]);
407
+ const maxValue = Math.max(...sanitizedValuesForMaximum.filter(isNotNil));
408
+
409
+ const minValueWithMargin =
410
+ (hasDisplayAsBar && minValue > 0) ||
411
+ (hasLineFilled &&
412
+ Math.max(maxValue, minValue) > minValue &&
413
+ minValue > 0) ||
414
+ (hasStackedLines && minValue > maxValue)
415
+ ? 0
416
+ : minValue - Math.abs(minValue) * 0.05;
417
+ const maxValueWithMargin =
418
+ (hasDisplayAsBar && maxValue < 0) ||
419
+ (hasLineFilled &&
420
+ Math.min(maxValue, minValue) < maxValue &&
421
+ maxValue < 0) ||
422
+ (hasStackedLines && minValue > maxValue)
423
+ ? 0
424
+ : maxValue + Math.abs(maxValue) * 0.05;
411
425
 
412
426
  const scaleType = getScaleType(scale);
413
427
 
@@ -474,7 +488,8 @@ const getYScaleUnit = ({
474
488
  min,
475
489
  max,
476
490
  isBarChart,
477
- boundariesUnit
491
+ boundariesUnit,
492
+ isFilled
478
493
  }: AxeScale & {
479
494
  invert?: boolean | string | null;
480
495
  unit: string;
@@ -482,6 +497,7 @@ const getYScaleUnit = ({
482
497
  min?: number;
483
498
  boundariesUnit?: string;
484
499
  isBarChart?: boolean;
500
+ isFilled?: boolean;
485
501
  }): ScaleLinear<number, number> => {
486
502
  const [firstUnit] = getUnits(dataLines);
487
503
  const shouldApplyThresholds =
@@ -500,12 +516,12 @@ const getYScaleUnit = ({
500
516
 
501
517
  const stackedValues = hasStackedLines
502
518
  ? getStackedMetricValues({
503
- lines: getSortedStackedLines(dataLines).filter(
504
- ({ unit: stackedUnit }) => equals(unit, stackedUnit)
505
- ),
506
- timeSeries: dataTimeSeries
507
- })
508
- : [0];
519
+ lines: getSortedStackedLines(dataLines).filter(
520
+ ({ unit: stackedUnit }) => equals(unit, stackedUnit)
521
+ ),
522
+ timeSeries: dataTimeSeries
523
+ })
524
+ : [];
509
525
 
510
526
  return getScale({
511
527
  graphValues,
@@ -515,8 +531,14 @@ const getYScaleUnit = ({
515
531
  ({ displayAs, unit: lineUnit }) =>
516
532
  equals(unit, lineUnit) && equals(displayAs, 'bar')
517
533
  ),
518
- hasLineFilled: dataLines.some(
519
- ({ unit: lineUnit, filled }) => equals(unit, lineUnit) && filled
534
+ hasLineFilled: isNil(isFilled)
535
+ ? dataLines.some(
536
+ ({ unit: lineUnit, filled }) => equals(unit, lineUnit) && filled
537
+ )
538
+ : isFilled,
539
+ hasStackedLines: dataLines.some(
540
+ ({ unit: lineUnit, stackKey, stackOrder }) =>
541
+ equals(unit, lineUnit) && (stackKey || stackOrder)
520
542
  ),
521
543
  height: valueGraphHeight,
522
544
  invert,
@@ -556,12 +578,14 @@ const getYScalePerUnit = ({
556
578
  isBarChart,
557
579
  min,
558
580
  max,
559
- boundariesUnit
581
+ boundariesUnit,
582
+ isFilled
560
583
  }: AxeScale & {
561
584
  min?: number;
562
585
  max?: number;
563
586
  isBarChart?: boolean;
564
587
  boundariesUnit?: string;
588
+ isFilled?: boolean;
565
589
  }): Record<string, ScaleLinear<number, number>> => {
566
590
  const units = getUnits(dataLines);
567
591
 
@@ -585,7 +609,8 @@ const getYScalePerUnit = ({
585
609
  min,
586
610
  max,
587
611
  isBarChart,
588
- boundariesUnit
612
+ boundariesUnit,
613
+ isFilled
589
614
  })
590
615
  };
591
616
  }, {});
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useState } from 'react';
1
+ import { ReactElement, useCallback, useEffect, useState } from 'react';
2
2
 
3
3
  import {
4
4
  equals,
@@ -44,7 +44,7 @@ export interface ConnectedAutoCompleteFieldProps<TData> {
44
44
  field: string;
45
45
  getEndpoint: ({ search, page }) => string;
46
46
  decoder?;
47
- getRenderedOptionText: (option: TData) => string;
47
+ getRenderedOptionText?: (option: TData) => ReactElement | string;
48
48
  getRequestHeaders?: HeadersInit;
49
49
  initialPage: number;
50
50
  labelKey?: string;
@@ -53,9 +53,9 @@ export interface ConnectedAutoCompleteFieldProps<TData> {
53
53
  }
54
54
 
55
55
  const ConnectedAutocompleteField = (
56
- AutocompleteField: (props) => JSX.Element,
56
+ AutocompleteField: (props) => ReactElement,
57
57
  multiple: boolean
58
- ): ((props) => JSX.Element) => {
58
+ ): ((props) => ReactElement) => {
59
59
  const InnerConnectedAutocompleteField = <TData extends { name: string }>({
60
60
  initialPage = 1,
61
61
  getEndpoint,
@@ -74,7 +74,7 @@ const ConnectedAutocompleteField = (
74
74
  changeIdValue,
75
75
  ...props
76
76
  }: ConnectedAutoCompleteFieldProps<TData> &
77
- Omit<AutocompleteFieldProps, 'options'>): JSX.Element => {
77
+ Omit<AutocompleteFieldProps, 'options'>): ReactElement => {
78
78
  const [options, setOptions] = useState<Array<TData>>([]);
79
79
  const [page, setPage] = useState(1);
80
80
  const [maxPage, setMaxPage] = useState(initialPage);
@@ -221,7 +221,7 @@ const ConnectedAutocompleteField = (
221
221
  debounce(event.target.value);
222
222
  };
223
223
 
224
- const renderOptions = (renderProps, option, { selected }): JSX.Element => {
224
+ const renderOptions = (renderProps, option, { selected }): ReactElement => {
225
225
  const { value } = props;
226
226
 
227
227
  const lastValue = Array.isArray(value) ? last(value) : value;
@@ -13,6 +13,7 @@ import { AutocompleteSlotsAndSlotProps } from '@mui/material/Autocomplete';
13
13
  import { TextFieldSlotsAndSlotProps } from '@mui/material/TextField';
14
14
  import { UseAutocompleteProps } from '@mui/material/useAutocomplete';
15
15
 
16
+ import type { AutocompleteRenderOptionState } from '@mui/material/Autocomplete';
16
17
  import { ForwardedRef, HTMLAttributes, ReactElement, forwardRef } from 'react';
17
18
  import { SelectEntry } from '..';
18
19
  import { getNormalizedId } from '../../../utils';
@@ -33,6 +34,11 @@ export type Props = {
33
34
  error?: string;
34
35
  getOptionItemLabel?: (option) => string;
35
36
  hideInput?: boolean;
37
+ renderOption?: (
38
+ renderProps: HTMLAttributes<HTMLLIElement>,
39
+ option: SelectEntry,
40
+ state: AutocompleteRenderOptionState
41
+ ) => ReactElement;
36
42
  label: string;
37
43
  loading?: boolean;
38
44
  onTextChange?;
@@ -52,7 +58,7 @@ export type Props = {
52
58
  > &
53
59
  UseAutocompleteProps<SelectEntry, Multiple, DisableClearable, FreeSolo>;
54
60
 
55
- const LoadingIndicator = (): JSX.Element => {
61
+ const LoadingIndicator = (): ReactElement => {
56
62
  const { classes } = useAutoCompleteStyles({});
57
63
 
58
64
  return (
@@ -90,10 +96,11 @@ const AutocompleteField = forwardRef(
90
96
  forceInputRenderValue = false,
91
97
  textFieldSlotsAndSlotProps,
92
98
  autocompleteSlotsAndSlotProps,
99
+ renderOption,
93
100
  ...autocompleteProps
94
101
  }: Props,
95
102
  ref?: ForwardedRef<HTMLDivElement>
96
- ): JSX.Element => {
103
+ ): ReactElement => {
97
104
  const { classes, cx } = useAutoCompleteStyles({ hideInput });
98
105
  const { t } = useTranslation();
99
106
  const theme = useTheme();
@@ -107,7 +114,24 @@ const AutocompleteField = forwardRef(
107
114
  );
108
115
  };
109
116
 
110
- const renderInput = (params): JSX.Element => {
117
+ const renderOptions = renderOption
118
+ ? renderOption
119
+ : (props, option): ReactElement => {
120
+ return (
121
+ <li
122
+ className={classes.options}
123
+ {...(props as HTMLAttributes<HTMLLIElement>)}
124
+ >
125
+ <Option
126
+ thumbnailUrl={displayOptionThumbnail ? option.url : undefined}
127
+ >
128
+ {getOptionItemLabel(option)}
129
+ </Option>
130
+ </li>
131
+ );
132
+ };
133
+
134
+ const renderInput = (params): ReactElement => {
111
135
  return (
112
136
  <TextField
113
137
  {...params}
@@ -202,20 +226,7 @@ const AutocompleteField = forwardRef(
202
226
  options={options}
203
227
  ref={ref}
204
228
  renderInput={renderInput}
205
- renderOption={(props, option): JSX.Element => {
206
- return (
207
- <li
208
- className={classes.options}
209
- {...(props as HTMLAttributes<HTMLLIElement>)}
210
- >
211
- <Option
212
- thumbnailUrl={displayOptionThumbnail ? option.url : undefined}
213
- >
214
- {getOptionItemLabel(option)}
215
- </Option>
216
- </li>
217
- );
218
- }}
229
+ renderOption={renderOptions}
219
230
  size="small"
220
231
  slotProps={{
221
232
  ...autocompleteSlotsAndSlotProps?.slotProps,
@@ -1,4 +1,4 @@
1
- import { RefObject, forwardRef } from 'react';
1
+ import { ReactElement, RefObject, forwardRef } from 'react';
2
2
 
3
3
  import { equals, isNil } from 'ramda';
4
4
  import { makeStyles } from 'tss-react/mui';
@@ -28,12 +28,12 @@ const useStyles = makeStyles()((theme) => ({
28
28
 
29
29
  interface Props {
30
30
  checkboxSelected?: boolean;
31
- children: string;
31
+ children: string | ReactElement;
32
32
  thumbnailUrl?: string;
33
33
  }
34
34
 
35
35
  const Option = forwardRef(
36
- ({ children, checkboxSelected, thumbnailUrl }: Props, ref): JSX.Element => {
36
+ ({ children, checkboxSelected, thumbnailUrl }: Props, ref): ReactElement => {
37
37
  const { classes } = useStyles();
38
38
 
39
39
  return (
@@ -8,6 +8,7 @@ import { SelectEntry, SingleConnectedAutocompleteField } from '../../../..';
8
8
  import RoleSelectField from '../common/RoleSelectField';
9
9
  import { Endpoints, Labels } from '../models';
10
10
 
11
+ import { ReactElement } from 'react';
11
12
  import ContactSwitch from './ContactSwitch';
12
13
  import { useShareInputStyles } from './ShareInput.styles';
13
14
  import useShareInput from './useShareInput';
@@ -18,18 +19,18 @@ interface Props {
18
19
  roles: Array<SelectEntry>;
19
20
  }
20
21
 
21
- const ShareInput = ({ labels, endpoints, roles }: Props): JSX.Element => {
22
+ const ShareInput = ({ labels, endpoints, roles }: Props): ReactElement => {
22
23
  const { t } = useTranslation();
23
24
  const { classes } = useShareInputStyles();
24
25
 
25
26
  const {
26
- renderOption,
27
27
  selectedContact,
28
28
  getOptionDisabled,
29
29
  getEndpoint,
30
30
  selectContact,
31
31
  isContactGroup,
32
32
  selectedRole,
33
+ getRenderedOptionText,
33
34
  setSelectedRole,
34
35
  add,
35
36
  changeIdValue
@@ -46,6 +47,7 @@ const ShareInput = ({ labels, endpoints, roles }: Props): JSX.Element => {
46
47
  disableClearable={false}
47
48
  field="name"
48
49
  getEndpoint={getEndpoint}
50
+ getRenderedOptionText={getRenderedOptionText}
49
51
  getOptionDisabled={getOptionDisabled}
50
52
  label={t(
51
53
  isContactGroup
@@ -53,7 +55,6 @@ const ShareInput = ({ labels, endpoints, roles }: Props): JSX.Element => {
53
55
  : t(labels.autocompleteContact)
54
56
  )}
55
57
  queryKey={isContactGroup ? labels.contactGroup : labels.contact}
56
- renderOption={renderOption}
57
58
  value={selectedContact}
58
59
  onChange={selectContact}
59
60
  />
@@ -1,10 +1,15 @@
1
- import { Dispatch, SetStateAction, useEffect, useState } from 'react';
1
+ import {
2
+ Dispatch,
3
+ ReactElement,
4
+ SetStateAction,
5
+ useEffect,
6
+ useState
7
+ } from 'react';
2
8
 
3
9
  import { useAtomValue, useSetAtom } from 'jotai';
4
10
  import { equals, includes, isNil } from 'ramda';
5
11
 
6
12
  import CheckCircleIcon from '@mui/icons-material/CheckCircle';
7
- import { ListItemText, MenuItem } from '@mui/material';
8
13
 
9
14
  import { SelectEntry, buildListingEndpoint } from '../../../..';
10
15
  import {
@@ -20,7 +25,7 @@ interface UseShareInputState {
20
25
  getEndpoint: (parameters) => string;
21
26
  getOptionDisabled: (option) => boolean;
22
27
  isContactGroup: boolean;
23
- renderOption: (attr, option) => JSX.Element;
28
+ getRenderedOptionText: (option: unknown) => ReactElement | string;
24
29
  selectContact: (_, entry) => void;
25
30
  selectedContact: AccessRightInitialValues | null;
26
31
  selectedRole: string;
@@ -40,7 +45,7 @@ const useShareInput = (endpoints: Endpoints): UseShareInputState => {
40
45
 
41
46
  const selectContact = (_, entry): void => {
42
47
  setSelectedContact(entry);
43
- if (equals('editor', entry.most_permissive_role)) {
48
+ if (equals('editor', entry?.most_permissive_role)) {
44
49
  return;
45
50
  }
46
51
  setSelectedRole('viewer');
@@ -71,14 +76,14 @@ const useShareInput = (endpoints: Endpoints): UseShareInputState => {
71
76
  }
72
77
  });
73
78
 
74
- const renderOption = (attr, option): JSX.Element => {
79
+ const getRenderedOptionText = (option): ReactElement => {
75
80
  return (
76
- <MenuItem {...attr}>
77
- <ListItemText>{option.name}</ListItemText>
78
- {includes(option.id, accessRightIds) && (
81
+ <>
82
+ {option?.name}
83
+ {includes(option?.id, accessRightIds) && (
79
84
  <CheckCircleIcon color="success" />
80
85
  )}
81
- </MenuItem>
86
+ </>
82
87
  );
83
88
  };
84
89
 
@@ -102,7 +107,7 @@ const useShareInput = (endpoints: Endpoints): UseShareInputState => {
102
107
  getEndpoint,
103
108
  getOptionDisabled,
104
109
  isContactGroup,
105
- renderOption,
110
+ getRenderedOptionText,
106
111
  selectContact,
107
112
  selectedContact,
108
113
  selectedRole,
@@ -5,13 +5,12 @@ import {
5
5
  Menu as MenuIcon
6
6
  } from '@mui/icons-material';
7
7
 
8
+ import { useStyles } from './MenuButton.styles';
8
9
  import { AriaLabelingAttributes } from '../../../@types/aria-attributes';
9
10
  import { DataTestAttributes } from '../../../@types/data-attributes';
10
11
  import { Button, ButtonProps } from '../../Button';
11
12
  import { useMenu } from '../useMenu';
12
13
 
13
- import { useStyles } from './MenuButton.styles';
14
-
15
14
  type MenuButtonProps = {
16
15
  ariaLabel?: string;
17
16
  children?: ReactNode;