@centreon/ui 24.10.12 → 24.10.13
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 +3 -2
- package/public/mockServiceWorker.js +1 -1
- package/src/Button/Icon/index.tsx +3 -1
- package/src/Dashboard/Dashboard.styles.ts +3 -4
- package/src/Dashboard/DashboardLayout.stories.tsx +1 -1
- package/src/Dashboard/Grid.tsx +11 -17
- package/src/Dashboard/Layout.tsx +27 -56
- package/src/Dialog/UnsavedChanges/index.tsx +15 -13
- package/src/Dialog/UnsavedChanges/translatedLabels.ts +15 -13
- package/src/Form/Form.tsx +0 -1
- package/src/Form/Inputs/Autocomplete.tsx +1 -1
- package/src/Form/Inputs/ConnectedAutocomplete.tsx +5 -2
- package/src/Form/Inputs/Grid.tsx +7 -1
- package/src/Form/Inputs/Radio.tsx +1 -1
- package/src/Form/Inputs/Switch.tsx +1 -1
- package/src/Form/Inputs/Text.tsx +1 -1
- package/src/Form/Inputs/index.tsx +25 -24
- package/src/Form/Inputs/models.ts +2 -0
- package/src/Graph/BarChart/BarChart.cypress.spec.tsx +3 -3
- package/src/Graph/BarChart/BarChart.tsx +24 -31
- package/src/Graph/BarChart/BarGroup.tsx +32 -59
- package/src/Graph/BarChart/BarStack.tsx +64 -13
- package/src/Graph/BarChart/MemoizedGroup.tsx +123 -0
- package/src/Graph/BarChart/ResponsiveBarChart.tsx +21 -7
- package/src/Graph/BarStack/BarStack.cypress.spec.tsx +87 -9
- package/src/Graph/BarStack/BarStack.stories.tsx +13 -4
- package/src/Graph/BarStack/BarStack.styles.ts +57 -33
- package/src/Graph/BarStack/Graph.tsx +173 -0
- package/src/Graph/BarStack/GraphAndLegend.tsx +117 -0
- package/src/Graph/BarStack/ResponsiveBarStack.tsx +61 -168
- package/src/Graph/BarStack/constants.ts +5 -0
- package/src/Graph/BarStack/models.ts +0 -1
- package/src/Graph/BarStack/useGraphAndLegend.ts +84 -0
- package/src/Graph/BarStack/useResponsiveBarStack.ts +73 -97
- package/src/Graph/Chart/Chart.cypress.spec.tsx +14 -26
- package/src/Graph/Chart/Chart.stories.tsx +1 -1
- package/src/Graph/Chart/Chart.tsx +53 -37
- package/src/Graph/Chart/InteractiveComponents/AnchorPoint/GuidingLines.tsx +3 -3
- package/src/Graph/Chart/InteractiveComponents/AnchorPoint/useTickGraph.ts +19 -6
- package/src/Graph/Chart/Legend/Legend.styles.ts +25 -11
- package/src/Graph/Chart/Legend/index.tsx +6 -24
- package/src/Graph/Chart/index.tsx +34 -43
- package/src/Graph/Chart/models.ts +0 -1
- package/src/Graph/Chart/useChartData.ts +19 -1
- package/src/Graph/HeatMap/ResponsiveHeatMap.tsx +20 -2
- package/src/Graph/HeatMap/model.ts +6 -2
- package/src/Graph/Legend/Legend.styles.ts +10 -0
- package/src/Graph/Legend/Legend.tsx +6 -1
- package/src/Graph/SingleBar/ResponsiveSingleBar.tsx +9 -10
- package/src/Graph/SingleBar/ThresholdLine.tsx +6 -6
- package/src/Graph/Text/Text.styles.ts +2 -2
- package/src/Graph/Text/Text.tsx +23 -10
- package/src/Graph/Timeline/ResponsiveTimeline.tsx +152 -0
- package/src/Graph/Timeline/Timeline.cypress.spec.tsx +148 -0
- package/src/Graph/Timeline/Timeline.stories.tsx +91 -0
- package/src/Graph/Timeline/Timeline.tsx +28 -0
- package/src/Graph/Timeline/index.ts +1 -0
- package/src/Graph/Timeline/models.ts +20 -0
- package/src/Graph/Timeline/timeline.styles.ts +11 -0
- package/src/Graph/Timeline/translatedLabel.ts +6 -0
- package/src/Graph/Timeline/useTimeline.ts +90 -0
- package/src/Graph/Tree/Links.tsx +2 -2
- package/src/Graph/Tree/Tree.tsx +2 -2
- package/src/Graph/Tree/constants.ts +1 -1
- package/src/Graph/common/Axes/index.tsx +1 -1
- package/src/Graph/common/Axes/useAxisY.ts +8 -4
- package/src/Graph/common/BaseChart/BaseChart.tsx +3 -12
- package/src/Graph/common/BaseChart/ChartSvgWrapper.tsx +12 -4
- package/src/Graph/common/BaseChart/Header/index.tsx +3 -1
- package/src/Graph/common/BaseChart/useComputeBaseChartDimensions.ts +23 -11
- package/src/Graph/common/BaseChart/useComputeYAxisMaxCharacters.ts +92 -0
- package/src/Graph/common/models.ts +7 -8
- package/src/Graph/common/timeSeries/index.test.ts +1 -1
- package/src/Graph/common/timeSeries/index.ts +56 -29
- package/src/Graph/common/timeSeries/models.ts +2 -0
- package/src/Graph/common/utils.ts +51 -3
- package/src/Graph/index.ts +4 -1
- package/src/Graph/mockedData/lastDayWithNullValues.json +6 -6
- package/src/Graph/mockedData/pingServiceLinesBars.json +47 -47
- package/src/Icon/DowntimeIcon.tsx +8 -1
- package/src/Icon/FlappingIcon.tsx +22 -0
- package/src/Icon/index.ts +1 -0
- package/src/InputField/Select/Autocomplete/Connected/Multi/index.test.tsx +21 -1
- package/src/InputField/Select/Autocomplete/Connected/index.test.tsx +2 -2
- package/src/InputField/Select/Autocomplete/Connected/index.tsx +52 -15
- package/src/InputField/Select/Autocomplete/Multi/index.stories.tsx +19 -0
- package/src/InputField/Select/Autocomplete/Multi/index.tsx +8 -5
- package/src/InputField/Select/Autocomplete/index.tsx +79 -54
- package/src/InputField/Text/index.tsx +6 -4
- package/src/InputField/translatedLabels.ts +2 -0
- package/src/Listing/ActionBar/index.tsx +1 -1
- package/src/Listing/Listing.styles.ts +3 -3
- package/src/Listing/index.tsx +40 -37
- package/src/Listing/models.ts +0 -8
- package/src/Listing/useStyleTable.ts +58 -32
- package/src/MultiSelectEntries/index.tsx +2 -0
- package/src/PopoverMenu/index.tsx +2 -9
- package/src/SortableItems/index.tsx +0 -1
- package/src/ThemeProvider/index.tsx +1 -1
- package/src/ThemeProvider/palettes.ts +6 -0
- package/src/TimePeriods/CustomTimePeriod/PopoverCustomTimePeriod/PickersStartEndDate.tsx +2 -3
- package/src/TimePeriods/DateTimePickerInput.tsx +3 -1
- package/src/api/buildListingEndpoint/getSearchQueryParameterValue.ts +7 -1
- package/src/api/buildListingEndpoint/models.ts +1 -0
- package/src/api/customFetch.ts +4 -1
- package/src/api/models.ts +9 -0
- package/src/api/useGraphQuery/index.ts +117 -20
- package/src/api/useGraphQuery/models.ts +1 -0
- package/src/api/useMutationQuery/index.ts +1 -1
- package/src/components/DataTable/DataTable.styles.ts +1 -1
- package/src/components/DataTable/EmptyState/DataTableEmptyState.styles.ts +2 -1
- package/src/components/DataTable/EmptyState/DataTableEmptyState.tsx +4 -1
- package/src/components/DataTable/Item/DataTableItem.styles.ts +28 -2
- package/src/components/DataTable/Item/DataTableItem.tsx +19 -4
- package/src/components/Form/FormActions.tsx +21 -12
- package/src/components/Layout/AreaIndicator.tsx +1 -1
- package/src/components/Layout/PageLayout/PageLayout.styles.ts +2 -7
- package/src/components/Layout/PageLayout/PageLayoutBody.tsx +0 -1
- package/src/components/Zoom/Zoom.tsx +9 -2
- package/src/components/Zoom/ZoomContent.tsx +143 -136
- package/src/components/Zoom/models.ts +18 -15
- package/src/components/Zoom/useMinimap.ts +5 -8
- package/src/components/Zoom/useZoom.ts +3 -3
- package/src/index.ts +2 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/useLocale/index.ts +9 -0
- package/src/utils/useLocale/useLocale.cypress.spec.tsx +38 -0
- package/src/utils/useLocaleDateTimeFormat/index.ts +4 -2
- package/src/utils/usePluralizedTranslation.ts +2 -3
- package/src/Graph/common/timeSeries/index.test.ts-E +0 -622
- package/src/components/CrudPage/Actions/Actions.styles.ts +0 -16
- package/src/components/CrudPage/Actions/Actions.tsx +0 -24
- package/src/components/CrudPage/Actions/AddButton.tsx +0 -23
- package/src/components/CrudPage/Actions/Filters.tsx +0 -25
- package/src/components/CrudPage/Actions/Search.tsx +0 -31
- package/src/components/CrudPage/Actions/useSearch.tsx +0 -24
- package/src/components/CrudPage/Columns/Actions.tsx +0 -88
- package/src/components/CrudPage/CrudPage.cypress.spec.tsx +0 -559
- package/src/components/CrudPage/CrudPage.stories.tsx +0 -278
- package/src/components/CrudPage/CrudPageRoot.tsx +0 -142
- package/src/components/CrudPage/DeleteModal.tsx +0 -77
- package/src/components/CrudPage/Form/AddModal.tsx +0 -35
- package/src/components/CrudPage/Form/Buttons.tsx +0 -98
- package/src/components/CrudPage/Form/UpdateModal.tsx +0 -60
- package/src/components/CrudPage/Listing.tsx +0 -63
- package/src/components/CrudPage/atoms.ts +0 -30
- package/src/components/CrudPage/hooks/useDeleteItem.ts +0 -53
- package/src/components/CrudPage/hooks/useGetItem.ts +0 -36
- package/src/components/CrudPage/hooks/useGetItems.ts +0 -67
- package/src/components/CrudPage/hooks/useListingQueryKey.ts +0 -31
- package/src/components/CrudPage/index.tsx +0 -7
- package/src/components/CrudPage/models.ts +0 -118
- package/src/components/CrudPage/utils.ts +0 -4
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
compose,
|
|
5
5
|
flatten,
|
|
6
6
|
groupBy,
|
|
7
|
+
isEmpty,
|
|
7
8
|
isNil,
|
|
8
9
|
lensPath,
|
|
9
10
|
pipe,
|
|
@@ -29,6 +30,8 @@ interface Props {
|
|
|
29
30
|
start?: string;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
const getBoolean = (value) => Boolean(Number(value));
|
|
34
|
+
|
|
32
35
|
const useGraphData = ({ data, end, start }: Props): GraphDataResult => {
|
|
33
36
|
const adjustedDataRef = useRef<Data>();
|
|
34
37
|
|
|
@@ -36,13 +39,27 @@ const useGraphData = ({ data, end, start }: Props): GraphDataResult => {
|
|
|
36
39
|
if (isNil(data)) {
|
|
37
40
|
return data;
|
|
38
41
|
}
|
|
42
|
+
|
|
43
|
+
if (isEmpty(data.metrics) || isEmpty(data.times)) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
const metricsGroupedByColor = groupBy(
|
|
40
48
|
(metric) => metric.ds_data.ds_color_line
|
|
41
49
|
)(data?.metrics || []);
|
|
42
50
|
|
|
43
51
|
const newMetrics = Object.entries(metricsGroupedByColor).map(
|
|
44
52
|
([color, value]) => {
|
|
45
|
-
|
|
53
|
+
const adjustedValue = value?.map((item) => ({
|
|
54
|
+
...item,
|
|
55
|
+
ds_data: {
|
|
56
|
+
...item?.ds_data,
|
|
57
|
+
ds_invert: getBoolean(item?.ds_data?.ds_invert),
|
|
58
|
+
ds_filled: getBoolean(item?.ds_data?.ds_filled)
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
return adjustedValue?.map((metric, index) =>
|
|
46
63
|
set(
|
|
47
64
|
lensPath(['ds_data', 'ds_color_line']),
|
|
48
65
|
emphasizeCurveColor({ color, index }),
|
|
@@ -70,6 +87,7 @@ const useGraphData = ({ data, end, start }: Props): GraphDataResult => {
|
|
|
70
87
|
const { title } = dataWithAdjustedMetricsColor.global;
|
|
71
88
|
|
|
72
89
|
const newLineData = adjustGraphData(dataWithAdjustedMetricsColor).lines;
|
|
90
|
+
|
|
73
91
|
const sortedLines = sortBy(compose(toLower, prop('name')), newLineData);
|
|
74
92
|
|
|
75
93
|
adjustedDataRef.current = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
1
|
+
import { useMemo, useRef } from 'react';
|
|
2
2
|
|
|
3
3
|
import { scaleLinear } from '@visx/scale';
|
|
4
4
|
import { T, equals, gt, lt } from 'ramda';
|
|
@@ -13,6 +13,7 @@ import { HeatMapProps } from './model';
|
|
|
13
13
|
const gap = 8;
|
|
14
14
|
const maxTileSize = 120;
|
|
15
15
|
const smallestTileSize = 44;
|
|
16
|
+
const toleratedRangeWidth = 10;
|
|
16
17
|
|
|
17
18
|
const ResponsiveHeatMap = <TData,>({
|
|
18
19
|
width,
|
|
@@ -28,6 +29,7 @@ const ResponsiveHeatMap = <TData,>({
|
|
|
28
29
|
width: number;
|
|
29
30
|
}): JSX.Element | null => {
|
|
30
31
|
const { classes, cx } = useHeatMapStyles();
|
|
32
|
+
const previousTileSize = useRef(0);
|
|
31
33
|
|
|
32
34
|
const tileSize = useMemo(() => {
|
|
33
35
|
const scaleWidth = scaleLinear({
|
|
@@ -44,6 +46,13 @@ const ResponsiveHeatMap = <TData,>({
|
|
|
44
46
|
const theoricalTotalTilesWidth =
|
|
45
47
|
tilesLength * tileWidth + (tilesLength - 1) * gap;
|
|
46
48
|
|
|
49
|
+
const canUpdateTileSize =
|
|
50
|
+
Math.abs(tileWidth - previousTileSize.current) > toleratedRangeWidth;
|
|
51
|
+
|
|
52
|
+
if (!canUpdateTileSize) {
|
|
53
|
+
return previousTileSize.current;
|
|
54
|
+
}
|
|
55
|
+
|
|
47
56
|
if (
|
|
48
57
|
(lt(height, maxTileSize) ||
|
|
49
58
|
(lt(width, 680) && gt(maxTotalTilesWidth, width))) &&
|
|
@@ -60,6 +69,8 @@ const ResponsiveHeatMap = <TData,>({
|
|
|
60
69
|
}, [width, tiles, height]);
|
|
61
70
|
|
|
62
71
|
const isSmallestSize = equals(tileSize, smallestTileSize);
|
|
72
|
+
const isMediumSize = !isSmallestSize && lt(tileSize, 90);
|
|
73
|
+
previousTileSize.current = tileSize;
|
|
63
74
|
|
|
64
75
|
if (equals(width, 0)) {
|
|
65
76
|
return null;
|
|
@@ -100,7 +111,14 @@ const ResponsiveHeatMap = <TData,>({
|
|
|
100
111
|
position="right-start"
|
|
101
112
|
>
|
|
102
113
|
<div className={classes.heatMapTileContent}>
|
|
103
|
-
{children({
|
|
114
|
+
{children({
|
|
115
|
+
backgroundColor,
|
|
116
|
+
data,
|
|
117
|
+
id,
|
|
118
|
+
isSmallestSize,
|
|
119
|
+
tileSize,
|
|
120
|
+
isMediumSize
|
|
121
|
+
})}
|
|
104
122
|
</div>
|
|
105
123
|
</Tooltip>
|
|
106
124
|
</Box>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactElement } from 'react';
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
2
|
|
|
3
3
|
interface Tile<TData> {
|
|
4
4
|
backgroundColor: string;
|
|
@@ -11,6 +11,8 @@ interface ChildrenProps<TData> {
|
|
|
11
11
|
data: TData;
|
|
12
12
|
id: string;
|
|
13
13
|
isSmallestSize: boolean;
|
|
14
|
+
isMediumSize?: boolean;
|
|
15
|
+
tileSize?: number;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export interface HeatMapProps<TData> {
|
|
@@ -19,7 +21,9 @@ export interface HeatMapProps<TData> {
|
|
|
19
21
|
backgroundColor,
|
|
20
22
|
id,
|
|
21
23
|
data,
|
|
22
|
-
isSmallestSize
|
|
24
|
+
isSmallestSize,
|
|
25
|
+
tileSize,
|
|
26
|
+
isMediumSize
|
|
23
27
|
}: ChildrenProps<TData>) => ReactElement | boolean | null;
|
|
24
28
|
displayTooltipCondition?: (data: TData) => boolean;
|
|
25
29
|
tileSizeFixed?: boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { makeStyles } from 'tss-react/mui';
|
|
2
|
+
|
|
3
|
+
export const useStyles = makeStyles()((theme) => ({
|
|
4
|
+
container: {
|
|
5
|
+
padding: theme.spacing(1),
|
|
6
|
+
border: `1px solid ${theme.palette.divider}`,
|
|
7
|
+
borderRadius: theme.shape.borderRadius,
|
|
8
|
+
width: 'fit-content'
|
|
9
|
+
}
|
|
10
|
+
}));
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { LegendOrdinal } from '@visx/legend';
|
|
2
2
|
import { scaleOrdinal } from '@visx/scale';
|
|
3
3
|
|
|
4
|
+
import { equals } from 'ramda';
|
|
5
|
+
import { useStyles } from './Legend.styles';
|
|
4
6
|
import { LegendProps } from './models';
|
|
5
7
|
|
|
6
8
|
const Legend = ({ scale, direction = 'column' }: LegendProps): JSX.Element => {
|
|
9
|
+
const { classes } = useStyles();
|
|
10
|
+
|
|
7
11
|
const legendScale = scaleOrdinal({
|
|
8
12
|
domain: scale.domain,
|
|
9
13
|
range: scale.range
|
|
@@ -12,8 +16,9 @@ const Legend = ({ scale, direction = 'column' }: LegendProps): JSX.Element => {
|
|
|
12
16
|
return (
|
|
13
17
|
<LegendOrdinal
|
|
14
18
|
direction={direction}
|
|
15
|
-
labelMargin="0 16px 0 0"
|
|
16
19
|
scale={legendScale}
|
|
20
|
+
labelMargin={equals(direction, 'row') ? '0 12px 0 0' : '0 0 0 0'}
|
|
21
|
+
className={classes.container}
|
|
17
22
|
/>
|
|
18
23
|
);
|
|
19
24
|
};
|
|
@@ -4,7 +4,7 @@ import { animated, useSpring } from '@react-spring/web';
|
|
|
4
4
|
import { scaleLinear } from '@visx/scale';
|
|
5
5
|
import { Bar } from '@visx/shape';
|
|
6
6
|
import { Group, Tooltip } from '@visx/visx';
|
|
7
|
-
import { equals, flatten, head,
|
|
7
|
+
import { clamp, equals, flatten, head, pluck } from 'ramda';
|
|
8
8
|
|
|
9
9
|
import { Box, alpha, useTheme } from '@mui/material';
|
|
10
10
|
|
|
@@ -40,8 +40,6 @@ const ResponsiveSingleBar = ({
|
|
|
40
40
|
const { classes } = useTooltipStyles();
|
|
41
41
|
const theme = useTheme();
|
|
42
42
|
|
|
43
|
-
const isSmallHeight = lt(height, 150);
|
|
44
|
-
|
|
45
43
|
const metric = getMetricWithLatestData(data) as Metric;
|
|
46
44
|
const latestMetricData = head(metric.data) as number;
|
|
47
45
|
const thresholdValues = thresholds.enabled
|
|
@@ -72,7 +70,7 @@ const ResponsiveSingleBar = ({
|
|
|
72
70
|
[latestMetricData, thresholds, theme]
|
|
73
71
|
);
|
|
74
72
|
|
|
75
|
-
const isSmall = equals(size, 'small')
|
|
73
|
+
const isSmall = equals(size, 'small');
|
|
76
74
|
|
|
77
75
|
const textStyle = isSmall ? theme.typography.h6 : theme.typography.h4;
|
|
78
76
|
|
|
@@ -118,14 +116,15 @@ const ResponsiveSingleBar = ({
|
|
|
118
116
|
|
|
119
117
|
const springStyle = useSpring({ width: metricBarWidth });
|
|
120
118
|
|
|
121
|
-
const barHeight = isSmallHeight ? barHeights.small : barHeights[size];
|
|
122
|
-
|
|
123
119
|
const barY = groupMargin + (isSmall ? 0 : 2 * margins.top);
|
|
124
120
|
|
|
125
|
-
const realBarHeight =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
const realBarHeight = isSmall
|
|
122
|
+
? barHeights.small
|
|
123
|
+
: clamp(
|
|
124
|
+
barHeights.small,
|
|
125
|
+
barHeights.medium,
|
|
126
|
+
height - textHeight - 2 * margins.top
|
|
127
|
+
);
|
|
129
128
|
|
|
130
129
|
return (
|
|
131
130
|
<div
|
|
@@ -63,15 +63,15 @@ export const ThresholdLine = ({
|
|
|
63
63
|
strokeDasharray="6, 6"
|
|
64
64
|
strokeWidth={2}
|
|
65
65
|
x1={scaledValue}
|
|
66
|
-
x2={scaledValue}
|
|
66
|
+
x2={scaledValue + 1}
|
|
67
67
|
y1={
|
|
68
68
|
isSmall
|
|
69
|
-
? groupMargin - lineMargin
|
|
69
|
+
? groupMargin - lineMargin
|
|
70
70
|
: groupMargin + lineMargin + margins.top
|
|
71
71
|
}
|
|
72
72
|
y2={
|
|
73
73
|
isSmall
|
|
74
|
-
? barHeight + groupMargin - lineMargin + margins.top -
|
|
74
|
+
? barHeight + groupMargin - lineMargin + margins.top - 6
|
|
75
75
|
: barHeight + groupMargin + lineMargin + 2 * margins.top
|
|
76
76
|
}
|
|
77
77
|
/>
|
|
@@ -80,15 +80,15 @@ export const ThresholdLine = ({
|
|
|
80
80
|
stroke="transparent"
|
|
81
81
|
strokeWidth={5}
|
|
82
82
|
x1={scaledValue}
|
|
83
|
-
x2={scaledValue}
|
|
83
|
+
x2={scaledValue + 1}
|
|
84
84
|
y1={
|
|
85
85
|
isSmall
|
|
86
|
-
? groupMargin - lineMargin
|
|
86
|
+
? groupMargin - lineMargin
|
|
87
87
|
: groupMargin + lineMargin + margins.top
|
|
88
88
|
}
|
|
89
89
|
y2={
|
|
90
90
|
isSmall
|
|
91
|
-
? barHeight + groupMargin - lineMargin + margins.top
|
|
91
|
+
? barHeight + groupMargin - lineMargin + margins.top - 6
|
|
92
92
|
: barHeight + groupMargin + lineMargin + 2 * margins.top
|
|
93
93
|
}
|
|
94
94
|
onMouseEnter={onMouseEnter}
|
|
@@ -11,13 +11,13 @@ export const useTextStyles = makeStyles()((theme) => ({
|
|
|
11
11
|
gap: theme.spacing(1),
|
|
12
12
|
justifyContent: 'center'
|
|
13
13
|
},
|
|
14
|
-
|
|
14
|
+
thresholdLabel: {
|
|
15
15
|
textAlign: 'center'
|
|
16
16
|
},
|
|
17
17
|
thresholds: {
|
|
18
18
|
display: 'flex',
|
|
19
19
|
flexDirection: 'row',
|
|
20
|
-
gap: theme.spacing(
|
|
20
|
+
gap: theme.spacing(1),
|
|
21
21
|
whiteSpace: 'nowrap',
|
|
22
22
|
width: '100%'
|
|
23
23
|
},
|
package/src/Graph/Text/Text.tsx
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
} from '../common/timeSeries';
|
|
11
11
|
import { getColorFromDataAndTresholds } from '../common/utils';
|
|
12
12
|
|
|
13
|
+
import { type ReactElement } from 'react';
|
|
14
|
+
import useResizeObserver from 'use-resize-observer';
|
|
13
15
|
import { useTextStyles } from './Text.styles';
|
|
14
16
|
|
|
15
17
|
export interface Props {
|
|
@@ -21,6 +23,8 @@ export interface Props {
|
|
|
21
23
|
warning: string;
|
|
22
24
|
};
|
|
23
25
|
thresholds: Thresholds;
|
|
26
|
+
prefThresholds?: number;
|
|
27
|
+
minThresholds?: string;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export const Text = ({
|
|
@@ -28,10 +32,13 @@ export const Text = ({
|
|
|
28
32
|
data,
|
|
29
33
|
displayAsRaw,
|
|
30
34
|
labels,
|
|
31
|
-
baseColor
|
|
32
|
-
|
|
35
|
+
baseColor,
|
|
36
|
+
prefThresholds = 14,
|
|
37
|
+
minThresholds
|
|
38
|
+
}: Props): ReactElement | null => {
|
|
33
39
|
const theme = useTheme();
|
|
34
40
|
const { classes, cx } = useTextStyles();
|
|
41
|
+
const { ref, width = 0 } = useResizeObserver();
|
|
35
42
|
|
|
36
43
|
if (isNil(data)) {
|
|
37
44
|
return null;
|
|
@@ -62,11 +69,15 @@ export const Text = ({
|
|
|
62
69
|
})
|
|
63
70
|
);
|
|
64
71
|
|
|
72
|
+
const canDisplayThresholdLabel = width > 150;
|
|
73
|
+
const warningLabel = canDisplayThresholdLabel ? `${labels.warning}: ` : '';
|
|
74
|
+
const criticalLabel = canDisplayThresholdLabel ? `${labels.critical}: ` : '';
|
|
75
|
+
|
|
65
76
|
return (
|
|
66
|
-
<div className={classes.graphText}>
|
|
77
|
+
<div className={classes.graphText} ref={ref}>
|
|
67
78
|
<FluidTypography
|
|
68
79
|
max="40px"
|
|
69
|
-
pref={
|
|
80
|
+
pref={14}
|
|
70
81
|
sx={{ color, fontWeight: 'bold', textAlign: 'center' }}
|
|
71
82
|
text={
|
|
72
83
|
formatMetricValueWithUnit({
|
|
@@ -80,18 +91,20 @@ export const Text = ({
|
|
|
80
91
|
{thresholds.enabled && (
|
|
81
92
|
<div className={classes.thresholds}>
|
|
82
93
|
<FluidTypography
|
|
83
|
-
containerClassName={cx(classes.
|
|
94
|
+
containerClassName={cx(classes.thresholdLabel, classes.warning)}
|
|
84
95
|
max="30px"
|
|
85
|
-
pref={
|
|
86
|
-
text={`${
|
|
96
|
+
pref={prefThresholds}
|
|
97
|
+
text={`${warningLabel}${warningThresholdLabels.join(' - ')}`}
|
|
87
98
|
variant="h5"
|
|
99
|
+
min={minThresholds}
|
|
88
100
|
/>
|
|
89
101
|
<FluidTypography
|
|
90
|
-
containerClassName={cx(classes.
|
|
102
|
+
containerClassName={cx(classes.thresholdLabel, classes.critical)}
|
|
91
103
|
max="30px"
|
|
92
|
-
pref={
|
|
93
|
-
text={`${
|
|
104
|
+
pref={prefThresholds}
|
|
105
|
+
text={`${criticalLabel}${criticalThresholdLabels.join(' - ')}`}
|
|
94
106
|
variant="h5"
|
|
107
|
+
min={minThresholds}
|
|
95
108
|
/>
|
|
96
109
|
</div>
|
|
97
110
|
)}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
dateTimeFormat,
|
|
3
|
+
getXAxisTickFormat,
|
|
4
|
+
useLocaleDateTimeFormat
|
|
5
|
+
} from '@centreon/ui';
|
|
6
|
+
|
|
7
|
+
import { Typography, useTheme } from '@mui/material';
|
|
8
|
+
|
|
9
|
+
import dayjs from 'dayjs';
|
|
10
|
+
import timezonePlugin from 'dayjs/plugin/timezone';
|
|
11
|
+
import utc from 'dayjs/plugin/utc';
|
|
12
|
+
|
|
13
|
+
import { userAtom } from '@centreon/ui-context';
|
|
14
|
+
import { Axis } from '@visx/visx';
|
|
15
|
+
|
|
16
|
+
import { scaleTime } from '@visx/scale';
|
|
17
|
+
import { BarRounded } from '@visx/shape';
|
|
18
|
+
import { useAtomValue } from 'jotai';
|
|
19
|
+
import { equals } from 'ramda';
|
|
20
|
+
import { useCallback } from 'react';
|
|
21
|
+
import { Tooltip } from '../../components';
|
|
22
|
+
import { margins } from '../common/margins';
|
|
23
|
+
import type { TimelineProps } from './models';
|
|
24
|
+
import { useStyles } from './timeline.styles';
|
|
25
|
+
import { useTimeline } from './useTimeline';
|
|
26
|
+
|
|
27
|
+
dayjs.extend(utc);
|
|
28
|
+
dayjs.extend(timezonePlugin);
|
|
29
|
+
|
|
30
|
+
interface Props extends TimelineProps {
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const axisPadding = 4;
|
|
36
|
+
|
|
37
|
+
const Timeline = ({
|
|
38
|
+
data,
|
|
39
|
+
startDate,
|
|
40
|
+
endDate,
|
|
41
|
+
width,
|
|
42
|
+
height,
|
|
43
|
+
TooltipContent,
|
|
44
|
+
tooltipClassName
|
|
45
|
+
}: Props) => {
|
|
46
|
+
const { classes, cx } = useStyles();
|
|
47
|
+
const { format } = useLocaleDateTimeFormat();
|
|
48
|
+
const { timezone } = useAtomValue(userAtom);
|
|
49
|
+
|
|
50
|
+
const theme = useTheme();
|
|
51
|
+
|
|
52
|
+
const xScale = scaleTime({
|
|
53
|
+
domain: [new Date(startDate), new Date(endDate)],
|
|
54
|
+
range: [margins.left, width - margins.right],
|
|
55
|
+
clamp: true
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const numTicks = Math.min(Math.ceil(width / 82), 12);
|
|
59
|
+
|
|
60
|
+
const { getTimeDifference } = useTimeline();
|
|
61
|
+
|
|
62
|
+
const getFormattedStart = useCallback(
|
|
63
|
+
(start) =>
|
|
64
|
+
format({
|
|
65
|
+
date: dayjs(start).tz(timezone).toDate(),
|
|
66
|
+
formatString: dateTimeFormat
|
|
67
|
+
}),
|
|
68
|
+
[dateTimeFormat, timezone]
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const getFormattedEnd = useCallback(
|
|
72
|
+
(end) =>
|
|
73
|
+
format({
|
|
74
|
+
date: dayjs(end).tz(timezone).toDate(),
|
|
75
|
+
formatString: dateTimeFormat
|
|
76
|
+
}),
|
|
77
|
+
[dateTimeFormat, timezone]
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<svg width={width} height={height + axisPadding}>
|
|
82
|
+
{data.map(({ start, end, color }, idx) => (
|
|
83
|
+
<Tooltip
|
|
84
|
+
hasCaret
|
|
85
|
+
classes={{
|
|
86
|
+
tooltip: cx(classes.tooltip, tooltipClassName)
|
|
87
|
+
}}
|
|
88
|
+
followCursor={false}
|
|
89
|
+
key={`rect-${start}--${end}`}
|
|
90
|
+
label={
|
|
91
|
+
TooltipContent ? (
|
|
92
|
+
<TooltipContent
|
|
93
|
+
start={getFormattedStart(start)}
|
|
94
|
+
end={getFormattedEnd(end)}
|
|
95
|
+
color={color}
|
|
96
|
+
duration={getTimeDifference({
|
|
97
|
+
start: dayjs(start),
|
|
98
|
+
end: dayjs(end)
|
|
99
|
+
})}
|
|
100
|
+
/>
|
|
101
|
+
) : (
|
|
102
|
+
<div style={{ color }}>
|
|
103
|
+
<Typography variant="body2">
|
|
104
|
+
{getTimeDifference({ start: dayjs(start), end: dayjs(end) })}
|
|
105
|
+
</Typography>
|
|
106
|
+
<Typography variant="body2">{`${format({ date: start, formatString: 'L LT' })} - ${format({ date: end, formatString: 'L LT' })}`}</Typography>
|
|
107
|
+
</div>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
position="top"
|
|
111
|
+
>
|
|
112
|
+
<g>
|
|
113
|
+
<BarRounded
|
|
114
|
+
x={xScale(dayjs(start).tz(timezone))}
|
|
115
|
+
y={0}
|
|
116
|
+
width={
|
|
117
|
+
xScale(dayjs(end).tz(timezone)) -
|
|
118
|
+
xScale(dayjs(start).tz(timezone))
|
|
119
|
+
}
|
|
120
|
+
height={height - margins.bottom}
|
|
121
|
+
fill={color}
|
|
122
|
+
left={equals(idx, 0)}
|
|
123
|
+
radius={4}
|
|
124
|
+
right={equals(idx, data.length - 1)}
|
|
125
|
+
/>
|
|
126
|
+
</g>
|
|
127
|
+
</Tooltip>
|
|
128
|
+
))}
|
|
129
|
+
|
|
130
|
+
<Axis.AxisBottom
|
|
131
|
+
top={height - margins.bottom + axisPadding}
|
|
132
|
+
scale={xScale}
|
|
133
|
+
numTicks={numTicks}
|
|
134
|
+
tickFormat={(value) =>
|
|
135
|
+
format({
|
|
136
|
+
date: new Date(value),
|
|
137
|
+
formatString: getXAxisTickFormat({ end: endDate, start: startDate })
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
stroke={theme.palette.text.primary}
|
|
141
|
+
tickStroke={theme.palette.text.primary}
|
|
142
|
+
tickLabelProps={() => ({
|
|
143
|
+
fill: theme.palette.text.primary,
|
|
144
|
+
fontSize: theme.typography.caption.fontSize,
|
|
145
|
+
textAnchor: 'middle'
|
|
146
|
+
})}
|
|
147
|
+
/>
|
|
148
|
+
</svg>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export default Timeline;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { userAtom } from '@centreon/ui-context';
|
|
2
|
+
import { Provider, createStore } from 'jotai';
|
|
3
|
+
import Timeline from './Timeline';
|
|
4
|
+
import { Tooltip } from './models';
|
|
5
|
+
|
|
6
|
+
const data = [
|
|
7
|
+
{
|
|
8
|
+
start: '2024-09-09T10:57:42+02:00',
|
|
9
|
+
end: '2024-09-09T11:15:00+02:00',
|
|
10
|
+
color: 'green'
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
start: '2024-09-09T11:15:00+02:00',
|
|
14
|
+
end: '2024-09-09T11:30:00+02:00',
|
|
15
|
+
color: 'red'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
start: '2024-09-09T11:30:00+02:00',
|
|
19
|
+
end: '2024-09-09T11:45:00+02:00',
|
|
20
|
+
color: 'gray'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
start: '2024-09-09T11:45:00+02:00',
|
|
24
|
+
end: '2024-09-09T12:00:00+02:00',
|
|
25
|
+
color: 'green'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
start: '2024-09-09T12:00:00+02:00',
|
|
29
|
+
end: '2024-09-09T12:20:00+02:00',
|
|
30
|
+
color: 'red'
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
start: '2024-09-09T12:20:00+02:00',
|
|
34
|
+
end: '2024-09-09T12:40:00+02:00',
|
|
35
|
+
color: 'gray'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
start: '2024-09-09T12:40:00+02:00',
|
|
39
|
+
end: '2024-09-09T12:57:42+02:00',
|
|
40
|
+
color: 'green'
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const startDate = '2024-09-09T10:57:42+02:00';
|
|
45
|
+
const endDate = '2024-09-09T12:57:42+02:00';
|
|
46
|
+
|
|
47
|
+
const TooltipContent = ({ start, end, color, duration }: Tooltip) => (
|
|
48
|
+
<div
|
|
49
|
+
data-testid="tooltip-content"
|
|
50
|
+
style={{
|
|
51
|
+
display: 'flex',
|
|
52
|
+
flexDirection: 'column',
|
|
53
|
+
justifyContent: 'center',
|
|
54
|
+
alignItems: 'center',
|
|
55
|
+
gap: '10px',
|
|
56
|
+
padding: '5px'
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<span>{start}</span>
|
|
60
|
+
<span>{end}</span>
|
|
61
|
+
<span>{color}</span>
|
|
62
|
+
<span>{duration}</span>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const store = createStore();
|
|
67
|
+
store.set(userAtom, { timezone: 'Europe/Paris', locale: 'en' });
|
|
68
|
+
|
|
69
|
+
const initialize = (displayDefaultTooltip = true): void => {
|
|
70
|
+
cy.mount({
|
|
71
|
+
Component: (
|
|
72
|
+
<Provider store={store}>
|
|
73
|
+
<div
|
|
74
|
+
style={{
|
|
75
|
+
height: '100px',
|
|
76
|
+
width: '70%'
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
<Timeline
|
|
80
|
+
data={data}
|
|
81
|
+
startDate={startDate}
|
|
82
|
+
endDate={endDate}
|
|
83
|
+
TooltipContent={displayDefaultTooltip ? undefined : TooltipContent}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
</Provider>
|
|
87
|
+
)
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
describe('Timeline', () => {
|
|
92
|
+
it('checks that the correct number of bars are rendered', () => {
|
|
93
|
+
initialize();
|
|
94
|
+
|
|
95
|
+
cy.get('path').should('have.length', data.length);
|
|
96
|
+
|
|
97
|
+
cy.makeSnapshot();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('checks that each bar has the correct color', () => {
|
|
101
|
+
initialize();
|
|
102
|
+
|
|
103
|
+
data.forEach(({ color }, index) => {
|
|
104
|
+
cy.get('path').eq(index).should('have.attr', 'fill', color);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('displays tooltip with correct information when hovered over a bar', () => {
|
|
109
|
+
initialize(false);
|
|
110
|
+
|
|
111
|
+
cy.get('path').first().trigger('mouseover');
|
|
112
|
+
|
|
113
|
+
cy.get('[data-testid="tooltip-content"]').within(() => {
|
|
114
|
+
cy.contains('09/09/2024 10:57 AM').should('be.visible');
|
|
115
|
+
cy.contains('09/09/2024 11:15 AM').should('be.visible');
|
|
116
|
+
cy.contains('green').should('be.visible');
|
|
117
|
+
cy.contains('17 minutes').should('be.visible');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
cy.makeSnapshot();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('displays the default tooltip with correct information when hovered over a bar', () => {
|
|
124
|
+
initialize();
|
|
125
|
+
|
|
126
|
+
cy.get('path').first().trigger('mouseover');
|
|
127
|
+
|
|
128
|
+
cy.get('[role="tooltip"]').within(() => {
|
|
129
|
+
cy.contains('09/09/2024 10:57 AM')
|
|
130
|
+
.should('be.visible')
|
|
131
|
+
.and('have.css', 'color', 'rgb(0, 128, 0)');
|
|
132
|
+
cy.contains('09/09/2024 11:15 AM')
|
|
133
|
+
.should('be.visible')
|
|
134
|
+
.and('have.css', 'color', 'rgb(0, 128, 0)');
|
|
135
|
+
cy.contains('17 minutes')
|
|
136
|
+
.should('be.visible')
|
|
137
|
+
.and('have.css', 'color', 'rgb(0, 128, 0)');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
cy.makeSnapshot();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('displays correct tick labels on the x-axis', () => {
|
|
144
|
+
initialize();
|
|
145
|
+
|
|
146
|
+
cy.get('.visx-axis-bottom .visx-axis-tick').first().contains('11:00');
|
|
147
|
+
});
|
|
148
|
+
});
|