@centreon/ui 24.4.45-develop.0 → 24.4.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/package.json +5 -1
  2. package/src/Graph/BarStack/BarStack.cypress.spec.tsx +154 -0
  3. package/src/Graph/BarStack/BarStack.stories.tsx +123 -0
  4. package/src/Graph/BarStack/BarStack.styles.ts +36 -0
  5. package/src/Graph/BarStack/BarStack.tsx +14 -0
  6. package/src/Graph/BarStack/ResponsiveBarStack.tsx +208 -0
  7. package/src/Graph/BarStack/index.ts +1 -0
  8. package/src/Graph/BarStack/models.ts +19 -0
  9. package/src/Graph/BarStack/useResponsiveBarStack.ts +139 -0
  10. package/src/Graph/Gauge/Gauge.cypress.spec.tsx +102 -0
  11. package/src/Graph/Gauge/Gauge.tsx +1 -1
  12. package/src/Graph/HeatMap/HeatMap.cypress.spec.tsx +145 -0
  13. package/src/Graph/HeatMap/HeatMap.stories.tsx +0 -25
  14. package/src/Graph/HeatMap/ResponsiveHeatMap.tsx +8 -2
  15. package/src/Graph/Legend/Legend.tsx +21 -0
  16. package/src/Graph/Legend/index.ts +1 -0
  17. package/src/Graph/Legend/models.ts +11 -0
  18. package/src/Graph/LineChart/Legend/Legend.styles.ts +1 -1
  19. package/src/Graph/LineChart/Legend/LegendHeader.tsx +1 -1
  20. package/src/Graph/LineChart/Legend/useInteractiveValues.ts +2 -2
  21. package/src/Graph/LineChart/index.tsx +1 -1
  22. package/src/Graph/PieChart/PieChart.cypress.spec.tsx +169 -0
  23. package/src/Graph/PieChart/PieChart.stories.tsx +194 -0
  24. package/src/Graph/PieChart/PieChart.styles.ts +39 -0
  25. package/src/Graph/PieChart/PieChart.tsx +14 -0
  26. package/src/Graph/PieChart/ResponsivePie.tsx +251 -0
  27. package/src/Graph/PieChart/index.ts +1 -0
  28. package/src/Graph/PieChart/models.ts +19 -0
  29. package/src/Graph/PieChart/useResponsivePie.ts +86 -0
  30. package/src/Graph/SingleBar/SingleBar.cypress.spec.tsx +121 -0
  31. package/src/Graph/Text/Text.cypress.spec.tsx +101 -0
  32. package/src/Graph/Text/Text.tsx +1 -1
  33. package/src/Graph/common/testUtils.ts +71 -0
  34. package/src/Graph/common/timeSeries/index.ts +19 -11
  35. package/src/Graph/common/utils.ts +19 -0
  36. package/src/Graph/index.ts +3 -0
  37. package/src/Graph/translatedLabels.ts +1 -0
  38. package/src/Listing/ActionBar/index.tsx +9 -8
  39. package/src/Listing/Cell/DataCell.styles.ts +3 -0
  40. package/src/Listing/Cell/DataCell.tsx +8 -4
  41. package/src/Listing/Listing.cypress.spec.tsx +80 -4
  42. package/src/Listing/Listing.styles.ts +3 -5
  43. package/src/Listing/index.stories.tsx +25 -2
  44. package/src/Listing/index.test.tsx +1 -1
  45. package/src/Listing/index.tsx +3 -1
  46. package/src/Listing/models.ts +1 -0
  47. package/src/api/useMutationQuery/index.test.ts +4 -4
  48. package/src/api/useMutationQuery/index.ts +24 -13
  49. package/src/components/Form/AccessRights/ShareInput/ContactSwitch.tsx +3 -3
  50. package/src/components/Form/AccessRights/ShareInput/ShareInput.tsx +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centreon/ui",
3
- "version": "24.4.45-develop.0",
3
+ "version": "24.4.45",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "eslint": "eslint ./src --ext .js,.jsx,.ts,.tsx --max-warnings 0",
@@ -28,6 +28,7 @@
28
28
  "author": {
29
29
  "name": "centreon@centreon.com"
30
30
  },
31
+ "baseCodeCoveragePercentage": 60,
31
32
  "license": "GPL-2.0",
32
33
  "bugs": {
33
34
  "url": "https://github.com/centreon/centreon/issues"
@@ -116,9 +117,11 @@
116
117
  "@react-spring/web": "^9.7.3",
117
118
  "@visx/curve": "^2.1.0",
118
119
  "@visx/group": "^3.3.0",
120
+ "@visx/legend": "^3.5.0",
119
121
  "@visx/pattern": "^3.0.0",
120
122
  "@visx/scale": "^3.0.0",
121
123
  "@visx/shape": "^2.12.2",
124
+ "@visx/text": "^3.3.0",
122
125
  "@visx/threshold": "^2.12.2",
123
126
  "@visx/visx": "2.16.0",
124
127
  "anylogger": "^1.0.11",
@@ -126,6 +129,7 @@
126
129
  "humanize-duration": "^3.27.3",
127
130
  "lexical": "^0.12.2",
128
131
  "notistack": "^3.0.1",
132
+ "numeral": "^2.0.6",
129
133
  "ramda": "0.29.1",
130
134
  "react-grid-layout": "^1.3.4",
131
135
  "react-html-parser": "^2.0.2",
@@ -0,0 +1,154 @@
1
+ import numeral from 'numeral';
2
+
3
+ import BarStack from './BarStack';
4
+ import { BarType, BarStackProps } from './models';
5
+
6
+ const defaultData = [
7
+ { color: '#88B922', label: 'Ok', value: 148 },
8
+ { color: '#999999', label: 'Unknown', value: 13 },
9
+ { color: '#F7931A', label: 'Warning', value: 16 },
10
+ { color: '#FF6666', label: 'Down', value: 62 }
11
+ ];
12
+
13
+ const dataWithNullValues = [
14
+ { color: '#88B922', label: 'Ok', value: 0 },
15
+ { color: '#999999', label: 'Unknown', value: 0 },
16
+ { color: '#F7931A', label: 'Warning', value: 0 },
17
+ { color: '#FF6666', label: 'Down', value: 0 }
18
+ ];
19
+
20
+ const total = Math.floor(
21
+ defaultData.reduce((acc, { value }) => acc + value, 0)
22
+ );
23
+
24
+ const TooltipContent = ({ label, color, value }: BarType): JSX.Element => {
25
+ return (
26
+ <div data-testid={`tooltip-${label}`} style={{ color }}>
27
+ {label} : {value}
28
+ </div>
29
+ );
30
+ };
31
+
32
+ const initialize = ({
33
+ width = '400px',
34
+ height = '400px',
35
+ data = defaultData,
36
+ ...args
37
+ }: Omit<BarStackProps, 'data'> & {
38
+ data?;
39
+ height?: string;
40
+ width?: string;
41
+ }): void => {
42
+ cy.mount({
43
+ Component: (
44
+ <div style={{ height, width }}>
45
+ <BarStack {...args} data={data} />
46
+ </div>
47
+ )
48
+ });
49
+ };
50
+
51
+ describe('Bar stack', () => {
52
+ it('renders Bar stack correctly with provided data', () => {
53
+ initialize({});
54
+
55
+ defaultData.forEach(({ label }) => {
56
+ cy.findByTestId(label).should('be.visible');
57
+ });
58
+
59
+ cy.makeSnapshot();
60
+ });
61
+
62
+ it('adjusts size based on the provided width and height', () => {
63
+ initialize({ displayLegend: false, height: '300px', width: '300px' });
64
+
65
+ cy.findByTestId('barStack').should('have.css', 'height', '300px');
66
+
67
+ cy.makeSnapshot();
68
+ });
69
+
70
+ it('renders as a horizontal bar when variant is set to "horizontal"', () => {
71
+ initialize({ variant: 'horizontal' });
72
+ cy.get('[data-variant="horizontal"]').should('exist');
73
+
74
+ cy.makeSnapshot();
75
+ });
76
+
77
+ it('renders as a vertical bar when variant is set to "vertical"', () => {
78
+ initialize({ variant: 'vertical' });
79
+ cy.get('[data-variant="vertical"]').should('exist');
80
+
81
+ cy.makeSnapshot();
82
+ });
83
+
84
+ it('displays tooltip with correct information on hover', () => {
85
+ initialize({ TooltipContent });
86
+
87
+ defaultData.forEach(({ label, value }) => {
88
+ cy.findByTestId(label).trigger('mouseover', { force: true });
89
+
90
+ cy.findByTestId(`tooltip-${label}`)
91
+ .should('contain', label)
92
+ .and('contain', numeral(value).format('0a').toUpperCase());
93
+ });
94
+
95
+ cy.makeSnapshot();
96
+ });
97
+
98
+ it('conditionally displays values on rects based on displayValues prop', () => {
99
+ initialize({ displayValues: true });
100
+ defaultData.forEach(({ value }, index) => {
101
+ cy.findAllByTestId('value')
102
+ .eq(index)
103
+ .children()
104
+ .eq(0)
105
+ .should('have.text', value);
106
+ });
107
+
108
+ initialize({ displayValues: false });
109
+ cy.findAllByTestId('value').should('not.exist');
110
+
111
+ cy.makeSnapshot();
112
+ });
113
+
114
+ it('displays values on rects in percentage unit when displayValues is set to true and unit to percentage', () => {
115
+ initialize({ displayValues: true, unit: 'percentage' });
116
+ defaultData.forEach(({ value }, index) => {
117
+ cy.findAllByTestId('value')
118
+ .eq(index)
119
+ .children()
120
+ .eq(0)
121
+ .should('have.text', `${((value * 100) / total).toFixed(1)}%`);
122
+ });
123
+
124
+ cy.makeSnapshot();
125
+ });
126
+
127
+ it('displays Legend component based on displayLegend prop', () => {
128
+ initialize({ displayLegend: true });
129
+ cy.findByTestId('Legend').should('be.visible');
130
+
131
+ initialize({ displayLegend: false });
132
+ cy.findByTestId('Legend').should('not.exist');
133
+
134
+ cy.makeSnapshot();
135
+ });
136
+
137
+ it('displays the title when the title is giving', () => {
138
+ initialize({ title: 'host' });
139
+ cy.findByTestId('Title').should('be.visible');
140
+
141
+ initialize({});
142
+ cy.findByTestId('Title').should('not.exist');
143
+
144
+ cy.makeSnapshot();
145
+ });
146
+
147
+ it('displays a message "No Data Available" when all values are null', () => {
148
+ initialize({ data: dataWithNullValues, title: 'host' });
149
+
150
+ cy.contains('No Data Available');
151
+
152
+ cy.makeSnapshot();
153
+ });
154
+ });
@@ -0,0 +1,123 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import { BarType } from './models';
4
+
5
+ import { BarStack } from '.';
6
+
7
+ const data = [
8
+ { color: '#88B922', label: 'Ok', value: 148 },
9
+ { color: '#999999', label: 'Unknown', value: 63 },
10
+ { color: '#F7931A', label: 'Warning', value: 16 },
11
+ { color: '#FF6666', label: 'Down', value: 13 }
12
+ ];
13
+
14
+ const dataWithBigNumbers = [
15
+ { color: '#88B922', label: 'Ok', value: 260000 },
16
+ { color: '#999999', label: 'Unknown', value: 1010900 },
17
+ { color: '#F7931A', label: 'Warning', value: 63114 },
18
+ { color: '#FF6666', label: 'Down', value: 122222 }
19
+ ];
20
+
21
+ const dataWithSmallNumber = [
22
+ { color: '#88B922', label: 'Ok', value: 148 },
23
+ { color: '#999999', label: 'Unknown', value: 42 },
24
+ { color: '#F7931A', label: 'Warning', value: 7 },
25
+ { color: '#FF6666', label: 'Down', value: 5 }
26
+ ];
27
+
28
+ const meta: Meta<typeof BarStack> = {
29
+ component: BarStack
30
+ };
31
+
32
+ export default meta;
33
+ type Story = StoryObj<typeof BarStack>;
34
+
35
+ const TooltipContent = ({ label, color, value }: BarType): JSX.Element => {
36
+ return (
37
+ <div style={{ color }}>
38
+ {label} : {value}
39
+ </div>
40
+ );
41
+ };
42
+
43
+ const Template = (args): JSX.Element => {
44
+ return (
45
+ <div style={{ height: '300px', width: '500px' }}>
46
+ <BarStack {...args} />
47
+ </div>
48
+ );
49
+ };
50
+
51
+ export const Vertical: Story = {
52
+ args: { data, title: 'hosts' },
53
+ render: Template
54
+ };
55
+
56
+ export const WithoutTitle: Story = {
57
+ args: { data },
58
+ render: Template
59
+ };
60
+
61
+ export const WithPencentage: Story = {
62
+ args: { data, title: 'hosts', unit: 'percentage' },
63
+ render: Template
64
+ };
65
+
66
+ export const WithoutLegend: Story = {
67
+ args: { data, displayLegend: false, title: 'hosts' },
68
+
69
+ render: Template
70
+ };
71
+
72
+ export const withDisplayedValues: Story = {
73
+ args: { data, displayValues: true, title: 'hosts' },
74
+ render: Template
75
+ };
76
+
77
+ export const WithTooltip: Story = {
78
+ args: { TooltipContent, data, title: 'hosts' },
79
+ render: Template
80
+ };
81
+
82
+ export const WithBigNumbers: Story = {
83
+ args: {
84
+ TooltipContent,
85
+ data: dataWithBigNumbers,
86
+ displayValues: true,
87
+ title: 'hosts'
88
+ },
89
+ render: Template
90
+ };
91
+
92
+ export const WithSmallNumbers: Story = {
93
+ args: {
94
+ TooltipContent,
95
+ data: dataWithSmallNumber,
96
+ displayValues: true,
97
+ title: 'hosts'
98
+ },
99
+ render: Template
100
+ };
101
+
102
+ export const Horizontal: Story = {
103
+ args: {
104
+ TooltipContent,
105
+ data,
106
+ displayValues: true,
107
+ title: 'hosts',
108
+ variant: 'horizontal'
109
+ },
110
+ render: Template
111
+ };
112
+
113
+ export const HorizontalWithoutLegend: Story = {
114
+ args: {
115
+ TooltipContent,
116
+ data,
117
+ displayLegend: false,
118
+ displayValues: true,
119
+ title: 'hosts',
120
+ variant: 'horizontal'
121
+ },
122
+ render: Template
123
+ };
@@ -0,0 +1,36 @@
1
+ import { makeStyles } from 'tss-react/mui';
2
+
3
+ export const useBarStackStyles = makeStyles()((theme) => ({
4
+ barStackTooltip: {
5
+ backgroundColor: theme.palette.background.paper,
6
+ color: theme.palette.text.primary,
7
+ padding: 0,
8
+ position: 'relative'
9
+ },
10
+ container: {
11
+ alignItems: 'center',
12
+ display: 'flex',
13
+ gap: theme.spacing(1.5),
14
+ justifyContent: 'center'
15
+ },
16
+ svgContainer: {
17
+ alignItems: 'center',
18
+ backgroundColor: theme.palette.background.panelGroups,
19
+ borderRadius: theme.shape.borderRadius,
20
+ display: 'flex',
21
+ justifyContent: 'center'
22
+ },
23
+ svgWrapper: {
24
+ alignItems: 'center',
25
+ display: 'flex',
26
+ flexDirection: 'column',
27
+ gap: theme.spacing(1),
28
+ justifyContent: 'center'
29
+ },
30
+ title: {
31
+ fontSize: theme.typography.h6.fontSize,
32
+ fontWeight: theme.typography.fontWeightMedium,
33
+ margin: 0,
34
+ padding: 0
35
+ }
36
+ }));
@@ -0,0 +1,14 @@
1
+ import { ParentSize } from '../..';
2
+
3
+ import ResponsiveBarStack from './ResponsiveBarStack';
4
+ import { BarStackProps } from './models';
5
+
6
+ const Bar = (props: BarStackProps): JSX.Element => (
7
+ <ParentSize>
8
+ {({ width, height }) => (
9
+ <ResponsiveBarStack {...props} height={height} width={width} />
10
+ )}
11
+ </ParentSize>
12
+ );
13
+
14
+ export default Bar;
@@ -0,0 +1,208 @@
1
+ import { useRef } from 'react';
2
+
3
+ import { BarStack as BarStackVertical, BarStackHorizontal } from '@visx/shape';
4
+ import { Group } from '@visx/group';
5
+ import numeral from 'numeral';
6
+ import { Text } from '@visx/text';
7
+ import { useTranslation } from 'react-i18next';
8
+
9
+ import { Typography } from '@mui/material';
10
+
11
+ import { Tooltip } from '../../components';
12
+ import { LegendProps } from '../Legend/models';
13
+ import { Legend as LegendComponent } from '../Legend';
14
+ import { getValueByUnit } from '../common/utils';
15
+ import { labelNoDataFound } from '../translatedLabels';
16
+
17
+ import { BarStackProps } from './models';
18
+ import { useBarStackStyles } from './BarStack.styles';
19
+ import useResponsiveBarStack from './useResponsiveBarStack';
20
+
21
+ const DefaultLengd = ({ scale, direction }: LegendProps): JSX.Element => (
22
+ <LegendComponent direction={direction} scale={scale} />
23
+ );
24
+
25
+ const BarStack = ({
26
+ title,
27
+ data,
28
+ width,
29
+ height,
30
+ size = 72,
31
+ onSingleBarClick,
32
+ displayLegend = true,
33
+ TooltipContent,
34
+ Legend = DefaultLengd,
35
+ unit = 'number',
36
+ displayValues,
37
+ variant = 'vertical',
38
+ legendDirection = 'column'
39
+ }: BarStackProps & { height: number; width: number }): JSX.Element => {
40
+ const { t } = useTranslation();
41
+ const { classes } = useBarStackStyles();
42
+
43
+ const titleRef = useRef(null);
44
+ const legendRef = useRef(null);
45
+
46
+ const {
47
+ barSize,
48
+ colorScale,
49
+ input,
50
+ keys,
51
+ legendScale,
52
+ total,
53
+ xScale,
54
+ yScale,
55
+ svgWrapperWidth,
56
+ svgContainerSize,
57
+ isVerticalBar,
58
+ areAllValuesNull
59
+ } = useResponsiveBarStack({
60
+ data,
61
+ height,
62
+ legendRef,
63
+ size,
64
+ titleRef,
65
+ unit,
66
+ variant,
67
+ width
68
+ });
69
+
70
+ const BarStackComponent = isVerticalBar
71
+ ? BarStackVertical
72
+ : BarStackHorizontal;
73
+
74
+ if (areAllValuesNull) {
75
+ return (
76
+ <div className={classes.container} style={{ height, width }}>
77
+ <Typography variant="h3">{t(labelNoDataFound)}</Typography>
78
+ </div>
79
+ );
80
+ }
81
+
82
+ return (
83
+ <div className={classes.container} style={{ height, width }}>
84
+ <div
85
+ className={classes.svgWrapper}
86
+ style={{
87
+ height,
88
+ width: svgWrapperWidth
89
+ }}
90
+ >
91
+ {title && (
92
+ <div className={classes.title} data-testid="Title" ref={titleRef}>
93
+ {`${numeral(total).format('0a').toUpperCase()} `} {title}
94
+ </div>
95
+ )}
96
+ <div
97
+ className={classes.svgContainer}
98
+ data-testid="barStack"
99
+ style={svgContainerSize}
100
+ >
101
+ <svg
102
+ data-variant={variant}
103
+ height={barSize.height}
104
+ width={barSize.width}
105
+ >
106
+ <Group>
107
+ <BarStackComponent
108
+ color={colorScale}
109
+ data={[input]}
110
+ keys={keys}
111
+ {...(isVerticalBar
112
+ ? { x: () => undefined }
113
+ : { y: () => undefined })}
114
+ xScale={xScale}
115
+ yScale={yScale}
116
+ >
117
+ {(barStacks) =>
118
+ barStacks.map((barStack) =>
119
+ barStack.bars.map((bar) => {
120
+ const onClick = (): void => {
121
+ onSingleBarClick?.(bar);
122
+ };
123
+
124
+ return (
125
+ <Tooltip
126
+ hasCaret
127
+ classes={{
128
+ tooltip: classes.barStackTooltip
129
+ }}
130
+ followCursor={false}
131
+ key={`bar-stack-${barStack.index}-${bar.index}`}
132
+ label={
133
+ TooltipContent && (
134
+ <TooltipContent
135
+ color={bar.color}
136
+ label={bar.key}
137
+ title={title}
138
+ total={total}
139
+ value={barStack.bars[0].bar.data[barStack.key]}
140
+ />
141
+ )
142
+ }
143
+ position={
144
+ isVerticalBar ? 'right-start' : 'bottom-start'
145
+ }
146
+ >
147
+ <g data-testid={bar.key}>
148
+ <rect
149
+ fill={bar.color}
150
+ height={
151
+ isVerticalBar ? bar.height - 1 : bar.height
152
+ }
153
+ key={`bar-stack-${barStack.index}-${bar.index}`}
154
+ ry={5}
155
+ width={isVerticalBar ? bar.width : bar.width - 1}
156
+ x={bar.x}
157
+ y={bar.y}
158
+ onClick={onClick}
159
+ />
160
+ {displayValues &&
161
+ bar.height > 10 &&
162
+ bar.width > 10 && (
163
+ <Text
164
+ cursor="pointer"
165
+ data-testid="value"
166
+ fill="#000"
167
+ fontSize={12}
168
+ textAnchor="middle"
169
+ verticalAnchor="middle"
170
+ x={bar.x + bar.width / 2}
171
+ y={bar.y + bar.height / 2}
172
+ >
173
+ {getValueByUnit({
174
+ total,
175
+ unit,
176
+ value:
177
+ barStack.bars[0].bar.data[barStack.key]
178
+ })}
179
+ </Text>
180
+ )}
181
+ </g>
182
+ </Tooltip>
183
+ );
184
+ })
185
+ )
186
+ }
187
+ </BarStackComponent>
188
+ </Group>
189
+ </svg>
190
+ </div>
191
+ </div>
192
+ {displayLegend && (
193
+ <div data-testid="Legend" ref={legendRef}>
194
+ <Legend
195
+ data={data}
196
+ direction={legendDirection}
197
+ scale={legendScale}
198
+ title={title}
199
+ total={total}
200
+ unit={unit}
201
+ />
202
+ </div>
203
+ )}
204
+ </div>
205
+ );
206
+ };
207
+
208
+ export default BarStack;
@@ -0,0 +1 @@
1
+ export { default as BarStack } from './BarStack';
@@ -0,0 +1,19 @@
1
+ export interface BarType {
2
+ color: string;
3
+ label: string;
4
+ value: number;
5
+ }
6
+
7
+ export type BarStackProps = {
8
+ Legend?: ({ scale, data, title, total, unit, direction }) => JSX.Element;
9
+ TooltipContent?: (barData) => JSX.Element | boolean | null;
10
+ data: Array<BarType>;
11
+ displayLegend?: boolean;
12
+ displayValues?: boolean;
13
+ legendDirection?: 'row' | 'column';
14
+ onSingleBarClick?: (barData) => void;
15
+ size?: number;
16
+ title?: string;
17
+ unit?: 'percentage' | 'number';
18
+ variant?: 'vertical' | 'horizontal';
19
+ };