@centreon/ui 25.11.3 → 25.11.5
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 +4 -3
- package/public/mockServiceWorker.js +8 -31
- package/src/Graph/BarChart/BarChart.cypress.spec.tsx +21 -0
- package/src/Graph/BarChart/BarChart.stories.tsx +18 -0
- package/src/Graph/BarChart/BarChart.tsx +10 -1
- package/src/Graph/BarChart/ResponsiveBarChart.tsx +3 -1
- package/src/Graph/BarChart/useBarStack.ts +9 -9
- package/src/Graph/Chart/Chart.cypress.spec.tsx +37 -17
- package/src/Graph/Chart/Chart.stories.tsx +21 -0
- package/src/Graph/Chart/Chart.tsx +3 -0
- package/src/Graph/Chart/Legend/Legend.styles.ts +1 -30
- package/src/Graph/Chart/Legend/LegendContent.tsx +1 -1
- package/src/Graph/Chart/Legend/LegendHeader.tsx +7 -23
- package/src/Graph/Chart/Legend/index.tsx +82 -64
- package/src/Graph/Chart/index.tsx +6 -1
- package/src/Graph/Chart/models.ts +5 -0
- package/src/Graph/common/BaseChart/BaseChart.tsx +16 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centreon/ui",
|
|
3
|
-
"version": "25.11.
|
|
3
|
+
"version": "25.11.5",
|
|
4
4
|
"description": "Centreon UI Components",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"update:deps": "pnpx npm-check-updates -i --format group",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"@storybook/test": "^8.6.3",
|
|
76
76
|
"@storybook/test-runner": "^0.22.0",
|
|
77
77
|
"@storybook/theming": "^8.6.3",
|
|
78
|
-
"@tailwindcss/cli": "^4.1.
|
|
78
|
+
"@tailwindcss/cli": "^4.1.17",
|
|
79
79
|
"@tailwindcss/postcss": "^4.1.7",
|
|
80
80
|
"@tailwindcss/vite": "^4.1.7",
|
|
81
81
|
"@testing-library/cypress": "^10.0.3",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"storybook-dark-mode": "^4.0.2",
|
|
107
107
|
"storybook-react-rsbuild": "^1.0.1",
|
|
108
108
|
"style-dictionary": "^4.3.3",
|
|
109
|
-
"tailwindcss": "^4.1.
|
|
109
|
+
"tailwindcss": "^4.1.17",
|
|
110
110
|
"ts-node": "^10.9.2",
|
|
111
111
|
"use-resize-observer": "^9.1.0",
|
|
112
112
|
"vite": "^6.3.5",
|
|
@@ -142,6 +142,7 @@
|
|
|
142
142
|
"@visx/visx": "3.12.0",
|
|
143
143
|
"@visx/zoom": "^3.12.0",
|
|
144
144
|
"anylogger": "^1.0.11",
|
|
145
|
+
"chromatic": "^13.3.3",
|
|
145
146
|
"d3-array": "3.2.4",
|
|
146
147
|
"dayjs": "^1.11.13",
|
|
147
148
|
"highlight.js": "^11.11.1",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* - Please do NOT serve this file on production.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
const PACKAGE_VERSION = '2.
|
|
12
|
-
const INTEGRITY_CHECKSUM = '
|
|
11
|
+
const PACKAGE_VERSION = '2.2.14'
|
|
12
|
+
const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423'
|
|
13
13
|
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
|
|
14
14
|
const activeClientIds = new Set()
|
|
15
15
|
|
|
@@ -62,12 +62,7 @@ self.addEventListener('message', async function (event) {
|
|
|
62
62
|
|
|
63
63
|
sendToClient(client, {
|
|
64
64
|
type: 'MOCKING_ENABLED',
|
|
65
|
-
payload:
|
|
66
|
-
client: {
|
|
67
|
-
id: client.id,
|
|
68
|
-
frameType: client.frameType,
|
|
69
|
-
},
|
|
70
|
-
},
|
|
65
|
+
payload: true,
|
|
71
66
|
})
|
|
72
67
|
break
|
|
73
68
|
}
|
|
@@ -160,10 +155,6 @@ async function handleRequest(event, requestId) {
|
|
|
160
155
|
async function resolveMainClient(event) {
|
|
161
156
|
const client = await self.clients.get(event.clientId)
|
|
162
157
|
|
|
163
|
-
if (activeClientIds.has(event.clientId)) {
|
|
164
|
-
return client
|
|
165
|
-
}
|
|
166
|
-
|
|
167
158
|
if (client?.frameType === 'top-level') {
|
|
168
159
|
return client
|
|
169
160
|
}
|
|
@@ -192,26 +183,12 @@ async function getResponse(event, client, requestId) {
|
|
|
192
183
|
const requestClone = request.clone()
|
|
193
184
|
|
|
194
185
|
function passthrough() {
|
|
195
|
-
|
|
196
|
-
// so the headers can be manipulated with.
|
|
197
|
-
const headers = new Headers(requestClone.headers)
|
|
198
|
-
|
|
199
|
-
// Remove the "accept" header value that marked this request as passthrough.
|
|
200
|
-
// This prevents request alteration and also keeps it compliant with the
|
|
201
|
-
// user-defined CORS policies.
|
|
202
|
-
const acceptHeader = headers.get('accept')
|
|
203
|
-
if (acceptHeader) {
|
|
204
|
-
const values = acceptHeader.split(',').map((value) => value.trim())
|
|
205
|
-
const filteredValues = values.filter(
|
|
206
|
-
(value) => value !== 'msw/passthrough',
|
|
207
|
-
)
|
|
186
|
+
const headers = Object.fromEntries(requestClone.headers.entries())
|
|
208
187
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
}
|
|
188
|
+
// Remove internal MSW request header so the passthrough request
|
|
189
|
+
// complies with any potential CORS preflight checks on the server.
|
|
190
|
+
// Some servers forbid unknown request headers.
|
|
191
|
+
delete headers['x-msw-intention']
|
|
215
192
|
|
|
216
193
|
return fetch(requestClone, { headers })
|
|
217
194
|
}
|
|
@@ -11,6 +11,7 @@ import dataPingServiceMixedStacked from '../mockedData/pingServiceMixedStacked.j
|
|
|
11
11
|
import dataPingServiceStacked from '../mockedData/pingServiceStacked.json';
|
|
12
12
|
import dataPingServiceLinesStackKeys from '../mockedData/pingServiceWithStackedKeys.json';
|
|
13
13
|
|
|
14
|
+
import { labelAvg, labelMax, labelMin } from '../Chart/translatedLabels';
|
|
14
15
|
import BarChart, { BarChartProps } from './BarChart';
|
|
15
16
|
|
|
16
17
|
const defaultStart = new Date(
|
|
@@ -331,4 +332,24 @@ describe('Bar chart', () => {
|
|
|
331
332
|
|
|
332
333
|
cy.makeSnapshot();
|
|
333
334
|
});
|
|
335
|
+
|
|
336
|
+
it('does not displays corresponding calculations when props are set', () => {
|
|
337
|
+
initialize({
|
|
338
|
+
data: dataLastWeek,
|
|
339
|
+
orientation: 'horizontal',
|
|
340
|
+
legend: {
|
|
341
|
+
placement: 'bottom',
|
|
342
|
+
mode: 'grid',
|
|
343
|
+
showCalculations: {
|
|
344
|
+
min: true,
|
|
345
|
+
max: false,
|
|
346
|
+
avg: false
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
cy.contains(labelMin).should('be.visible');
|
|
352
|
+
cy.contains(labelMax).should('not.exist');
|
|
353
|
+
cy.contains(labelAvg).should('not.exist');
|
|
354
|
+
});
|
|
334
355
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import dayjs from 'dayjs';
|
|
3
|
+
import '../../ThemeProvider/tailwindcss.css';
|
|
3
4
|
|
|
4
5
|
import { LineChartData } from '../common/models';
|
|
5
6
|
import dataPingService from '../mockedData/pingService.json';
|
|
@@ -45,6 +46,11 @@ export const withCenteredZero: Story = {
|
|
|
45
46
|
...defaultArgs,
|
|
46
47
|
axis: {
|
|
47
48
|
isCenteredZero: true
|
|
49
|
+
},
|
|
50
|
+
legend: {
|
|
51
|
+
showCalculations: { avg: true, max: false, min: false },
|
|
52
|
+
mode: 'grid',
|
|
53
|
+
placement: 'bottom'
|
|
48
54
|
}
|
|
49
55
|
},
|
|
50
56
|
render: Template
|
|
@@ -310,3 +316,15 @@ export const stackKey: Story = {
|
|
|
310
316
|
},
|
|
311
317
|
render: Template
|
|
312
318
|
};
|
|
319
|
+
|
|
320
|
+
export const withControlledCalculations: Story = {
|
|
321
|
+
args: {
|
|
322
|
+
...defaultArgs,
|
|
323
|
+
legend: {
|
|
324
|
+
showCalculations: { avg: true, max: false, min: false },
|
|
325
|
+
mode: 'grid',
|
|
326
|
+
placement: 'bottom'
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
render: Template
|
|
330
|
+
};
|
|
@@ -61,7 +61,16 @@ const BarChart = ({
|
|
|
61
61
|
height = 500,
|
|
62
62
|
tooltip,
|
|
63
63
|
axis,
|
|
64
|
-
legend
|
|
64
|
+
legend = {
|
|
65
|
+
display: true,
|
|
66
|
+
mode: 'grid',
|
|
67
|
+
placement: 'bottom',
|
|
68
|
+
showCalculations: {
|
|
69
|
+
min: true,
|
|
70
|
+
max: true,
|
|
71
|
+
avg: true
|
|
72
|
+
}
|
|
73
|
+
},
|
|
65
74
|
loading,
|
|
66
75
|
limitLegend,
|
|
67
76
|
thresholdUnit,
|
|
@@ -235,7 +235,8 @@ const ResponsiveBarChart = ({
|
|
|
235
235
|
mode: legend?.mode,
|
|
236
236
|
placement: legend?.placement,
|
|
237
237
|
renderExtraComponent: legend?.renderExtraComponent,
|
|
238
|
-
secondaryClick: legend?.secondaryClick
|
|
238
|
+
secondaryClick: legend?.secondaryClick,
|
|
239
|
+
showCalculations: legend?.showCalculations
|
|
239
240
|
}}
|
|
240
241
|
legendRef={legendRef}
|
|
241
242
|
limitLegend={limitLegend}
|
|
@@ -243,6 +244,7 @@ const ResponsiveBarChart = ({
|
|
|
243
244
|
setLines={setLinesGraph}
|
|
244
245
|
title={title}
|
|
245
246
|
titleRef={titleRef}
|
|
247
|
+
graphHeight={graphHeight}
|
|
246
248
|
>
|
|
247
249
|
<Tooltip
|
|
248
250
|
classes={{
|
|
@@ -72,15 +72,15 @@ export const useBarStack = ({
|
|
|
72
72
|
|
|
73
73
|
const commonBarStackProps = isHorizontal
|
|
74
74
|
? {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
x: (d) => d.timeTick,
|
|
76
|
+
xScale,
|
|
77
|
+
yScale
|
|
78
|
+
}
|
|
79
79
|
: {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
xScale: yScale,
|
|
81
|
+
y: (d) => d.timeTick,
|
|
82
|
+
yScale: xScale
|
|
83
|
+
};
|
|
84
84
|
|
|
85
85
|
const hoverBar = useCallback(
|
|
86
86
|
({ highlightedMetric, barIndex }: HoverBarProps) =>
|
|
@@ -96,7 +96,7 @@ export const useBarStack = ({
|
|
|
96
96
|
index: barIndex
|
|
97
97
|
});
|
|
98
98
|
},
|
|
99
|
-
[]
|
|
99
|
+
[lines, timeSeries]
|
|
100
100
|
);
|
|
101
101
|
|
|
102
102
|
const exitBar = useCallback((): void => {
|
|
@@ -18,6 +18,7 @@ import { args as argumentsData } from './helpers/doc';
|
|
|
18
18
|
import { LineChartProps } from './models';
|
|
19
19
|
|
|
20
20
|
import WrapperChart from '.';
|
|
21
|
+
import { labelAvg, labelMin } from './translatedLabels';
|
|
21
22
|
|
|
22
23
|
interface Props
|
|
23
24
|
extends Pick<
|
|
@@ -153,7 +154,7 @@ const initializeCustomUnits = ({
|
|
|
153
154
|
const checkGraphWidth = (): void => {
|
|
154
155
|
cy.findByTestId('graph-interaction-zone')
|
|
155
156
|
.should('have.attr', 'height')
|
|
156
|
-
.and('equal', '
|
|
157
|
+
.and('equal', '387');
|
|
157
158
|
|
|
158
159
|
cy.findByTestId('graph-interaction-zone').then((graph) => {
|
|
159
160
|
expect(Number(graph[0].attributes.width.value)).to.be.greaterThan(1170);
|
|
@@ -295,23 +296,23 @@ describe('Line chart', () => {
|
|
|
295
296
|
.should('have.attr', 'width')
|
|
296
297
|
.and('equal', '1200');
|
|
297
298
|
|
|
298
|
-
cy.
|
|
299
|
-
.
|
|
299
|
+
cy.get('[data-icon="true"]')
|
|
300
|
+
.eq(0)
|
|
300
301
|
.should('have.css', 'background-color', 'rgb(41, 175, 238)');
|
|
301
|
-
cy.
|
|
302
|
-
.
|
|
302
|
+
cy.get('[data-icon="true"]')
|
|
303
|
+
.eq(1)
|
|
303
304
|
.should('have.css', 'background-color', 'rgb(83, 191, 241)');
|
|
304
|
-
cy.
|
|
305
|
-
.
|
|
305
|
+
cy.get('[data-icon="true"]')
|
|
306
|
+
.eq(2)
|
|
306
307
|
.should('have.css', 'background-color', 'rgb(8, 34, 47)');
|
|
307
|
-
cy.
|
|
308
|
-
.
|
|
308
|
+
cy.get('[data-icon="true"]')
|
|
309
|
+
.eq(3)
|
|
309
310
|
.should('have.css', 'background-color', 'rgb(16, 70, 95)');
|
|
310
|
-
cy.
|
|
311
|
-
.
|
|
311
|
+
cy.get('[data-icon="true"]')
|
|
312
|
+
.eq(4)
|
|
312
313
|
.should('have.css', 'background-color', 'rgb(24, 105, 142)');
|
|
313
|
-
cy.
|
|
314
|
-
.
|
|
314
|
+
cy.get('[data-icon="true"]')
|
|
315
|
+
.eq(5)
|
|
315
316
|
.should('have.css', 'background-color', 'rgb(32, 140, 190)');
|
|
316
317
|
|
|
317
318
|
cy.get('[data-metric="1"]').should(
|
|
@@ -451,7 +452,7 @@ describe('Line chart', () => {
|
|
|
451
452
|
|
|
452
453
|
cy.contains(':00 AM').should('be.visible');
|
|
453
454
|
|
|
454
|
-
cy.get('text[transform="rotate(-35, -2,
|
|
455
|
+
cy.get('text[transform="rotate(-35, -2, 204.60871164646406)"]').should(
|
|
455
456
|
'be.visible'
|
|
456
457
|
);
|
|
457
458
|
|
|
@@ -533,7 +534,7 @@ describe('Line chart', () => {
|
|
|
533
534
|
checkGraphWidth();
|
|
534
535
|
cy.contains(':00 AM').should('be.visible');
|
|
535
536
|
cy.get('circle[cx="248.33333333333334"]').should('be.visible');
|
|
536
|
-
cy.get('circle[cy="
|
|
537
|
+
cy.get('circle[cy="225.07536552649066"]').should('be.visible');
|
|
537
538
|
|
|
538
539
|
cy.makeSnapshot();
|
|
539
540
|
});
|
|
@@ -747,10 +748,10 @@ describe('Lines and bars', () => {
|
|
|
747
748
|
checkGraphWidth();
|
|
748
749
|
|
|
749
750
|
cy.get(
|
|
750
|
-
'path[d="M7.501377410468319,
|
|
751
|
+
'path[d="M7.501377410468319,286.2259761383722 h56.51239669421488 h1v1 v98.77402386162782 a1,1 0 0 1 -1,1 h-56.51239669421488 a1,1 0 0 1 -1,-1 v-98.77402386162782 v-1h1z"]'
|
|
751
752
|
).should('be.visible');
|
|
752
753
|
cy.get(
|
|
753
|
-
'path[d="M24.05509641873278,
|
|
754
|
+
'path[d="M24.05509641873278,232.36514618046195 h23.404958677685954 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,17.553719008264462 v18.753391941381302 v17.553719008264462h-17.553719008264462 h-23.404958677685954 h-17.553719008264462v-17.553719008264462 v-18.753391941381302 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,-17.553719008264462z"]'
|
|
754
755
|
).should('be.visible');
|
|
755
756
|
|
|
756
757
|
cy.makeSnapshot();
|
|
@@ -833,4 +834,23 @@ describe('Lines and bars', () => {
|
|
|
833
834
|
cy.contains('Packet Loss').rightclick();
|
|
834
835
|
cy.get('@secondaryClick').should('have.been.called');
|
|
835
836
|
});
|
|
837
|
+
|
|
838
|
+
it('does not displays corresponding calculations when props are set', () => {
|
|
839
|
+
initialize({
|
|
840
|
+
data: dataPingServiceLines,
|
|
841
|
+
legend: {
|
|
842
|
+
placement: 'bottom',
|
|
843
|
+
mode: 'grid',
|
|
844
|
+
showCalculations: {
|
|
845
|
+
min: true,
|
|
846
|
+
max: false,
|
|
847
|
+
avg: false
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
cy.contains(labelMin).should('be.visible');
|
|
853
|
+
cy.contains(/^Max$/).should('not.exist');
|
|
854
|
+
cy.contains(labelAvg).should('not.exist');
|
|
855
|
+
});
|
|
836
856
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
import '../../ThemeProvider/tailwindcss.css';
|
|
4
5
|
|
|
5
6
|
import { Button, Menu } from '@mui/material';
|
|
6
7
|
import ButtonGroup from '@mui/material/ButtonGroup';
|
|
@@ -800,3 +801,23 @@ export const stackedKey: Story = {
|
|
|
800
801
|
data: dataPingServiceLinesStackKeys
|
|
801
802
|
}
|
|
802
803
|
};
|
|
804
|
+
|
|
805
|
+
export const WithControlledCalculations: Story = {
|
|
806
|
+
...Template,
|
|
807
|
+
argTypes,
|
|
808
|
+
args: {
|
|
809
|
+
...argumentsData,
|
|
810
|
+
lineStyle: {
|
|
811
|
+
curve: 'step'
|
|
812
|
+
},
|
|
813
|
+
legend: {
|
|
814
|
+
mode: 'grid',
|
|
815
|
+
placement: 'bottom',
|
|
816
|
+
showCalculations: {
|
|
817
|
+
avg: true,
|
|
818
|
+
max: true,
|
|
819
|
+
min: false
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
};
|
|
@@ -273,11 +273,13 @@ const Chart = ({
|
|
|
273
273
|
header={header}
|
|
274
274
|
height={height}
|
|
275
275
|
legend={{
|
|
276
|
+
...legend,
|
|
276
277
|
displayLegend,
|
|
277
278
|
legendHeight: legend?.height,
|
|
278
279
|
mode: legend?.mode,
|
|
279
280
|
placement: legend?.placement,
|
|
280
281
|
renderExtraComponent: legend?.renderExtraComponent,
|
|
282
|
+
showCalculations: legend?.showCalculations,
|
|
281
283
|
secondaryClick: legend?.secondaryClick
|
|
282
284
|
}}
|
|
283
285
|
legendRef={legendRef}
|
|
@@ -286,6 +288,7 @@ const Chart = ({
|
|
|
286
288
|
setLines={setLinesGraph}
|
|
287
289
|
title={title}
|
|
288
290
|
titleRef={titleRef}
|
|
291
|
+
graphHeight={graphHeight}
|
|
289
292
|
>
|
|
290
293
|
<GraphValueTooltip
|
|
291
294
|
baseAxis={baseAxis}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { makeStyles } from 'tss-react/mui';
|
|
2
2
|
|
|
3
|
-
import { equals, lt } from 'ramda';
|
|
4
|
-
import { margin } from '../common';
|
|
5
3
|
import type { LegendModel } from '../models';
|
|
6
4
|
|
|
7
5
|
interface MakeStylesProps {
|
|
@@ -14,20 +12,8 @@ export const legendWidth = 21;
|
|
|
14
12
|
const legendItemHeight = 5.25;
|
|
15
13
|
const legendItemHeightCompact = 2;
|
|
16
14
|
|
|
17
|
-
const getLegendMaxHeight = ({ placement, height }) => {
|
|
18
|
-
if (!equals(placement, 'bottom')) {
|
|
19
|
-
return height || 0;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (lt(height || 0, 220)) {
|
|
23
|
-
return 40;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return 90;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
15
|
export const useStyles = makeStyles<MakeStylesProps>()(
|
|
30
|
-
(theme, { limitLegendRows
|
|
16
|
+
(theme, { limitLegendRows }) => ({
|
|
31
17
|
highlight: {
|
|
32
18
|
color: theme.typography.body1.color
|
|
33
19
|
},
|
|
@@ -54,21 +40,6 @@ export const useStyles = makeStyles<MakeStylesProps>()(
|
|
|
54
40
|
rowGap: theme.spacing(1),
|
|
55
41
|
width: '100%'
|
|
56
42
|
},
|
|
57
|
-
legend: {
|
|
58
|
-
'&[data-display-side="false"]': {
|
|
59
|
-
marginLeft: margin.left,
|
|
60
|
-
marginRight: margin.right
|
|
61
|
-
},
|
|
62
|
-
'&[data-display-side="true"]': {
|
|
63
|
-
height: '100%',
|
|
64
|
-
marginTop: `${margin.top / 2}px`
|
|
65
|
-
},
|
|
66
|
-
maxHeight: limitLegendRows
|
|
67
|
-
? theme.spacing(legendItemHeight * 2 + 1)
|
|
68
|
-
: getLegendMaxHeight({ placement, height }),
|
|
69
|
-
overflowY: 'auto',
|
|
70
|
-
overflowX: 'hidden'
|
|
71
|
-
},
|
|
72
43
|
minMaxAvgContainer: {
|
|
73
44
|
columnGap: theme.spacing(0.5),
|
|
74
45
|
display: 'grid',
|
|
@@ -15,7 +15,7 @@ const LegendContent = ({ data, label }: Props): JSX.Element => {
|
|
|
15
15
|
const { t } = useTranslation();
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
|
-
<div className=
|
|
18
|
+
<div className="leading-[1.2]" data-testid={label}>
|
|
19
19
|
<Typography className={classes.text} component="span" variant="caption">
|
|
20
20
|
{t(label)}:{' '}
|
|
21
21
|
<Typography
|
|
@@ -8,13 +8,11 @@ import {
|
|
|
8
8
|
import { Tooltip } from '../../../components';
|
|
9
9
|
import { Line } from '../../common/timeSeries/models';
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { ReactElement } from 'react';
|
|
12
12
|
import LegendContent from './LegendContent';
|
|
13
13
|
import { LegendDisplayMode } from './models';
|
|
14
14
|
|
|
15
15
|
interface Props {
|
|
16
|
-
color: string;
|
|
17
|
-
disabled?: boolean;
|
|
18
16
|
isDisplayedOnSide: boolean;
|
|
19
17
|
isListMode: boolean;
|
|
20
18
|
line: Line;
|
|
@@ -25,16 +23,12 @@ interface Props {
|
|
|
25
23
|
|
|
26
24
|
const LegendHeader = ({
|
|
27
25
|
line,
|
|
28
|
-
color,
|
|
29
|
-
disabled,
|
|
30
26
|
value,
|
|
31
27
|
minMaxAvg,
|
|
32
28
|
isListMode,
|
|
33
29
|
isDisplayedOnSide,
|
|
34
30
|
unit
|
|
35
|
-
}: Props):
|
|
36
|
-
const { classes, cx } = useLegendHeaderStyles({ color });
|
|
37
|
-
|
|
31
|
+
}: Props): ReactElement => {
|
|
38
32
|
const { name, legend } = line;
|
|
39
33
|
|
|
40
34
|
const metricName = formatMetricName({ legend, name });
|
|
@@ -42,16 +36,14 @@ const LegendHeader = ({
|
|
|
42
36
|
const legendName = legend || name;
|
|
43
37
|
|
|
44
38
|
return (
|
|
45
|
-
<div
|
|
46
|
-
className={cx(!isListMode ? classes.container : classes.containerList)}
|
|
47
|
-
>
|
|
39
|
+
<div className={isListMode ? 'w-fit' : 'w-full'}>
|
|
48
40
|
<Tooltip
|
|
49
41
|
followCursor={false}
|
|
50
42
|
label={
|
|
51
43
|
minMaxAvg ? (
|
|
52
44
|
<div>
|
|
53
45
|
<Typography>{legendName}</Typography>
|
|
54
|
-
<div className=
|
|
46
|
+
<div className="flex flex-wrap gap-1 whitespace-nowrap">
|
|
55
47
|
{minMaxAvg.map(({ label, value: subValue }) => (
|
|
56
48
|
<LegendContent
|
|
57
49
|
data={formatMetricValue({
|
|
@@ -70,18 +62,10 @@ const LegendHeader = ({
|
|
|
70
62
|
}
|
|
71
63
|
placement={isListMode ? 'right' : 'top'}
|
|
72
64
|
>
|
|
73
|
-
<div className=
|
|
74
|
-
<div
|
|
75
|
-
data-icon
|
|
76
|
-
className={cx(classes.icon, { [classes.disabled]: disabled })}
|
|
77
|
-
/>
|
|
65
|
+
<div className="flex items-center gap-1">
|
|
78
66
|
<EllipsisTypography
|
|
79
|
-
className=
|
|
80
|
-
containerClassname={
|
|
81
|
-
!isListMode && classes.legendName,
|
|
82
|
-
isListMode && !isDisplayedOnSide && classes.textListBottom,
|
|
83
|
-
isListMode && isDisplayedOnSide && classes.legendName
|
|
84
|
-
)}
|
|
67
|
+
className="text-xs leading-[1.2] font-medium"
|
|
68
|
+
containerClassname={`w-auto ${(!isListMode || (isListMode && isDisplayedOnSide)) && 'max-w-[166px]'}`}
|
|
85
69
|
data-mode={
|
|
86
70
|
value ? LegendDisplayMode.Compact : LegendDisplayMode.Normal
|
|
87
71
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Dispatch,
|
|
3
|
+
KeyboardEvent,
|
|
4
|
+
MouseEvent,
|
|
5
|
+
ReactElement,
|
|
6
|
+
ReactNode,
|
|
7
|
+
SetStateAction,
|
|
8
|
+
useMemo
|
|
9
|
+
} from 'react';
|
|
2
10
|
|
|
3
11
|
import { equals, prop, slice, sortBy } from 'ramda';
|
|
4
12
|
|
|
5
|
-
import {
|
|
13
|
+
import { alpha, useTheme } from '@mui/material';
|
|
6
14
|
|
|
7
15
|
import { useMemoComponent } from '@centreon/ui';
|
|
8
16
|
|
|
@@ -10,14 +18,13 @@ import { formatMetricValue } from '../../common/timeSeries';
|
|
|
10
18
|
import { Line } from '../../common/timeSeries/models';
|
|
11
19
|
import { LegendModel } from '../models';
|
|
12
20
|
import { labelAvg, labelMax, labelMin } from '../translatedLabels';
|
|
13
|
-
|
|
14
|
-
import { useStyles } from './Legend.styles';
|
|
15
21
|
import LegendContent from './LegendContent';
|
|
16
22
|
import LegendHeader from './LegendHeader';
|
|
17
23
|
import { GetMetricValueProps, LegendDisplayMode } from './models';
|
|
18
24
|
import useLegend from './useLegend';
|
|
19
25
|
|
|
20
|
-
interface Props
|
|
26
|
+
interface Props
|
|
27
|
+
extends Pick<LegendModel, 'placement' | 'mode' | 'showCalculations'> {
|
|
21
28
|
base: number;
|
|
22
29
|
height: number | null;
|
|
23
30
|
limitLegend?: false | number;
|
|
@@ -31,6 +38,7 @@ interface Props extends Pick<LegendModel, 'placement' | 'mode'> {
|
|
|
31
38
|
metricId: number | string;
|
|
32
39
|
position: [number, number];
|
|
33
40
|
}) => void;
|
|
41
|
+
graphHeight: number;
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
const MainLegend = ({
|
|
@@ -42,15 +50,15 @@ const MainLegend = ({
|
|
|
42
50
|
setLinesGraph,
|
|
43
51
|
shouldDisplayLegendInCompactMode,
|
|
44
52
|
placement,
|
|
45
|
-
height,
|
|
46
53
|
mode,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
showCalculations = {
|
|
55
|
+
min: true,
|
|
56
|
+
max: true,
|
|
57
|
+
avg: true
|
|
58
|
+
},
|
|
59
|
+
secondaryClick,
|
|
60
|
+
graphHeight
|
|
61
|
+
}: Props): ReactElement => {
|
|
54
62
|
const theme = useTheme();
|
|
55
63
|
|
|
56
64
|
const { selectMetricLine, clearHighlight, highlightLine, toggleMetricLine } =
|
|
@@ -73,22 +81,25 @@ const MainLegend = ({
|
|
|
73
81
|
|
|
74
82
|
const contextMenuClick =
|
|
75
83
|
(metricId: number) =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
(event: MouseEvent): void => {
|
|
85
|
+
if (!secondaryClick) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
secondaryClick({
|
|
90
|
+
element: event.target,
|
|
91
|
+
metricId,
|
|
92
|
+
position: [event.pageX, event.pageY]
|
|
93
|
+
});
|
|
94
|
+
};
|
|
87
95
|
|
|
88
96
|
const selectMetric = ({
|
|
89
97
|
event,
|
|
90
98
|
metric_id
|
|
91
|
-
}: {
|
|
99
|
+
}: {
|
|
100
|
+
event: MouseEvent<HTMLLIElement> | KeyboardEvent<HTMLLIElement>;
|
|
101
|
+
metric_id: number;
|
|
102
|
+
}): void => {
|
|
92
103
|
if (!toggable) {
|
|
93
104
|
return;
|
|
94
105
|
}
|
|
@@ -109,83 +120,90 @@ const MainLegend = ({
|
|
|
109
120
|
|
|
110
121
|
return (
|
|
111
122
|
<div
|
|
112
|
-
className={
|
|
123
|
+
className={`overflow-x-hidden overflow-y-auto ${!equals(placement, 'bottom') ? 'h-full mt-[15px]' : 'ml-[50px] mr-[40px]'} legend`}
|
|
113
124
|
data-display-side={!equals(placement, 'bottom')}
|
|
114
125
|
>
|
|
115
|
-
<
|
|
116
|
-
className={
|
|
126
|
+
<ul
|
|
127
|
+
className={`list-none flex gap-3 w-full ${!isListMode && equals(placement, 'bottom') && 'flex-wrap'} ${isListMode || !equals(placement, 'bottom') ? 'flex-col h-full w-fit' : ''} ${equals(placement, 'bottom') ? 'max-h-17' : 'max-h-0'}`}
|
|
128
|
+
style={{
|
|
129
|
+
height: equals(placement, 'bottom') ? 'auto' : `${graphHeight}px`
|
|
130
|
+
}}
|
|
117
131
|
data-as-list={isListMode || !equals(placement, 'bottom')}
|
|
118
132
|
data-mode={itemMode}
|
|
119
133
|
>
|
|
120
134
|
{displayedLines.map((line) => {
|
|
121
|
-
const { color, display,
|
|
135
|
+
const { color, display, metric_id, unit } = line;
|
|
122
136
|
|
|
123
137
|
const markerColor = display
|
|
124
138
|
? color
|
|
125
139
|
: alpha(theme.palette.text.disabled, 0.2);
|
|
126
140
|
|
|
127
141
|
const minMaxAvg = [
|
|
128
|
-
{
|
|
142
|
+
showCalculations.min && {
|
|
129
143
|
label: labelMin,
|
|
130
144
|
value: line.minimum_value
|
|
131
145
|
},
|
|
132
|
-
{
|
|
146
|
+
showCalculations.max && {
|
|
133
147
|
label: labelMax,
|
|
134
148
|
value: line.maximum_value
|
|
135
149
|
},
|
|
136
|
-
{
|
|
150
|
+
showCalculations.avg && {
|
|
137
151
|
label: labelAvg,
|
|
138
152
|
value: line.average_value
|
|
139
153
|
}
|
|
140
|
-
];
|
|
154
|
+
].filter(Boolean);
|
|
141
155
|
|
|
142
156
|
return (
|
|
143
|
-
<
|
|
144
|
-
className={
|
|
145
|
-
classes.item,
|
|
146
|
-
highlight ? classes.highlight : classes.normal,
|
|
147
|
-
toggable && classes.toggable
|
|
148
|
-
)}
|
|
157
|
+
<li
|
|
158
|
+
className={`${!display ? 'text-text-disabled' : 'text-text-primary'} flex gap-1 ${toggable && 'cursor-pointer'}`}
|
|
149
159
|
key={metric_id}
|
|
150
160
|
onClick={(event): void => selectMetric({ event, metric_id })}
|
|
161
|
+
onKeyUp={(event) =>
|
|
162
|
+
event.key === 'Enter' && selectMetric({ event, metric_id })
|
|
163
|
+
}
|
|
151
164
|
onMouseEnter={(): void => highlightLine(metric_id)}
|
|
152
165
|
onMouseLeave={(): void => clearHighlight()}
|
|
153
166
|
onContextMenu={contextMenuClick(metric_id)}
|
|
154
167
|
>
|
|
155
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
isListMode={isListMode}
|
|
160
|
-
line={line}
|
|
161
|
-
minMaxAvg={
|
|
162
|
-
shouldDisplayLegendInCompactMode ? minMaxAvg : undefined
|
|
163
|
-
}
|
|
164
|
-
unit={unit}
|
|
168
|
+
<div
|
|
169
|
+
className="h-full rounded-sm w-1 min-h-5"
|
|
170
|
+
style={{ backgroundColor: markerColor }}
|
|
171
|
+
data-icon
|
|
165
172
|
/>
|
|
166
|
-
|
|
167
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
<div>
|
|
174
|
+
<LegendHeader
|
|
175
|
+
isDisplayedOnSide={!equals(placement, 'bottom')}
|
|
176
|
+
isListMode={isListMode}
|
|
177
|
+
line={line}
|
|
178
|
+
minMaxAvg={
|
|
179
|
+
shouldDisplayLegendInCompactMode ? minMaxAvg : undefined
|
|
180
|
+
}
|
|
181
|
+
unit={unit}
|
|
182
|
+
/>
|
|
183
|
+
{!shouldDisplayLegendInCompactMode && !isListMode && (
|
|
184
|
+
<div>
|
|
185
|
+
<div className="flex flex-wrap gap-1 whitespace-nowrap">
|
|
186
|
+
{minMaxAvg.map(({ label, value }) => (
|
|
187
|
+
<LegendContent
|
|
188
|
+
data={getMetricValue({ unit: line.unit, value })}
|
|
189
|
+
key={label}
|
|
190
|
+
label={label}
|
|
191
|
+
/>
|
|
192
|
+
))}
|
|
193
|
+
</div>
|
|
176
194
|
</div>
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
</
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
</li>
|
|
180
198
|
);
|
|
181
199
|
})}
|
|
182
|
-
</
|
|
200
|
+
</ul>
|
|
183
201
|
{renderExtraComponent}
|
|
184
202
|
</div>
|
|
185
203
|
);
|
|
186
204
|
};
|
|
187
205
|
|
|
188
|
-
const Legend = (props: Props):
|
|
206
|
+
const Legend = (props: Props): ReactElement => {
|
|
189
207
|
const {
|
|
190
208
|
toggable,
|
|
191
209
|
limitLegend,
|
|
@@ -175,6 +175,11 @@ export interface LegendModel {
|
|
|
175
175
|
mode: 'grid' | 'list';
|
|
176
176
|
placement: 'bottom' | 'left' | 'right';
|
|
177
177
|
renderExtraComponent?: ReactNode;
|
|
178
|
+
showCalculations?: {
|
|
179
|
+
min: boolean;
|
|
180
|
+
max: boolean;
|
|
181
|
+
avg: boolean;
|
|
182
|
+
};
|
|
178
183
|
secondaryClick?: (props: {
|
|
179
184
|
element: EventTarget | null;
|
|
180
185
|
metricId: number | string;
|
|
@@ -21,7 +21,11 @@ interface Props {
|
|
|
21
21
|
isHorizontal?: boolean;
|
|
22
22
|
legend: Pick<
|
|
23
23
|
LegendModel,
|
|
24
|
-
|
|
24
|
+
| 'renderExtraComponent'
|
|
25
|
+
| 'placement'
|
|
26
|
+
| 'mode'
|
|
27
|
+
| 'secondaryClick'
|
|
28
|
+
| 'showCalculations'
|
|
25
29
|
> & {
|
|
26
30
|
displayLegend: boolean;
|
|
27
31
|
legendHeight?: number;
|
|
@@ -31,9 +35,10 @@ interface Props {
|
|
|
31
35
|
limitLegend?: number | false;
|
|
32
36
|
lines: Array<Line>;
|
|
33
37
|
setLines:
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
| Dispatch<SetStateAction<Array<Line> | null>>
|
|
39
|
+
| Dispatch<SetStateAction<Array<Line>>>;
|
|
36
40
|
title: string;
|
|
41
|
+
graphHeight: number;
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
const BaseChart = ({
|
|
@@ -49,7 +54,8 @@ const BaseChart = ({
|
|
|
49
54
|
titleRef,
|
|
50
55
|
title,
|
|
51
56
|
header,
|
|
52
|
-
isHorizontal = true
|
|
57
|
+
isHorizontal = true,
|
|
58
|
+
graphHeight
|
|
53
59
|
}: Props): JSX.Element => {
|
|
54
60
|
const { classes, cx } = useBaseChartStyles();
|
|
55
61
|
|
|
@@ -87,8 +93,8 @@ const BaseChart = ({
|
|
|
87
93
|
className={cx(
|
|
88
94
|
classes.legendContainer,
|
|
89
95
|
equals(legend?.placement, 'right') &&
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
!isHorizontal &&
|
|
97
|
+
classes.legendContainerVerticalSide
|
|
92
98
|
)}
|
|
93
99
|
ref={legendRef}
|
|
94
100
|
>
|
|
@@ -104,7 +110,9 @@ const BaseChart = ({
|
|
|
104
110
|
shouldDisplayLegendInCompactMode={
|
|
105
111
|
shouldDisplayLegendInCompactMode
|
|
106
112
|
}
|
|
113
|
+
showCalculations={legend?.showCalculations}
|
|
107
114
|
secondaryClick={legend?.secondaryClick}
|
|
115
|
+
graphHeight={graphHeight}
|
|
108
116
|
/>
|
|
109
117
|
</div>
|
|
110
118
|
)}
|
|
@@ -127,6 +135,8 @@ const BaseChart = ({
|
|
|
127
135
|
setLinesGraph={setLines}
|
|
128
136
|
shouldDisplayLegendInCompactMode={shouldDisplayLegendInCompactMode}
|
|
129
137
|
secondaryClick={legend?.secondaryClick}
|
|
138
|
+
showCalculations={legend?.showCalculations}
|
|
139
|
+
graphHeight={graphHeight}
|
|
130
140
|
/>
|
|
131
141
|
</div>
|
|
132
142
|
)}
|