@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.
Files changed (153) hide show
  1. package/package.json +3 -2
  2. package/public/mockServiceWorker.js +1 -1
  3. package/src/Button/Icon/index.tsx +3 -1
  4. package/src/Dashboard/Dashboard.styles.ts +3 -4
  5. package/src/Dashboard/DashboardLayout.stories.tsx +1 -1
  6. package/src/Dashboard/Grid.tsx +11 -17
  7. package/src/Dashboard/Layout.tsx +27 -56
  8. package/src/Dialog/UnsavedChanges/index.tsx +15 -13
  9. package/src/Dialog/UnsavedChanges/translatedLabels.ts +15 -13
  10. package/src/Form/Form.tsx +0 -1
  11. package/src/Form/Inputs/Autocomplete.tsx +1 -1
  12. package/src/Form/Inputs/ConnectedAutocomplete.tsx +5 -2
  13. package/src/Form/Inputs/Grid.tsx +7 -1
  14. package/src/Form/Inputs/Radio.tsx +1 -1
  15. package/src/Form/Inputs/Switch.tsx +1 -1
  16. package/src/Form/Inputs/Text.tsx +1 -1
  17. package/src/Form/Inputs/index.tsx +25 -24
  18. package/src/Form/Inputs/models.ts +2 -0
  19. package/src/Graph/BarChart/BarChart.cypress.spec.tsx +3 -3
  20. package/src/Graph/BarChart/BarChart.tsx +24 -31
  21. package/src/Graph/BarChart/BarGroup.tsx +32 -59
  22. package/src/Graph/BarChart/BarStack.tsx +64 -13
  23. package/src/Graph/BarChart/MemoizedGroup.tsx +123 -0
  24. package/src/Graph/BarChart/ResponsiveBarChart.tsx +21 -7
  25. package/src/Graph/BarStack/BarStack.cypress.spec.tsx +87 -9
  26. package/src/Graph/BarStack/BarStack.stories.tsx +13 -4
  27. package/src/Graph/BarStack/BarStack.styles.ts +57 -33
  28. package/src/Graph/BarStack/Graph.tsx +173 -0
  29. package/src/Graph/BarStack/GraphAndLegend.tsx +117 -0
  30. package/src/Graph/BarStack/ResponsiveBarStack.tsx +61 -168
  31. package/src/Graph/BarStack/constants.ts +5 -0
  32. package/src/Graph/BarStack/models.ts +0 -1
  33. package/src/Graph/BarStack/useGraphAndLegend.ts +84 -0
  34. package/src/Graph/BarStack/useResponsiveBarStack.ts +73 -97
  35. package/src/Graph/Chart/Chart.cypress.spec.tsx +14 -26
  36. package/src/Graph/Chart/Chart.stories.tsx +1 -1
  37. package/src/Graph/Chart/Chart.tsx +53 -37
  38. package/src/Graph/Chart/InteractiveComponents/AnchorPoint/GuidingLines.tsx +3 -3
  39. package/src/Graph/Chart/InteractiveComponents/AnchorPoint/useTickGraph.ts +19 -6
  40. package/src/Graph/Chart/Legend/Legend.styles.ts +25 -11
  41. package/src/Graph/Chart/Legend/index.tsx +6 -24
  42. package/src/Graph/Chart/index.tsx +34 -43
  43. package/src/Graph/Chart/models.ts +0 -1
  44. package/src/Graph/Chart/useChartData.ts +19 -1
  45. package/src/Graph/HeatMap/ResponsiveHeatMap.tsx +20 -2
  46. package/src/Graph/HeatMap/model.ts +6 -2
  47. package/src/Graph/Legend/Legend.styles.ts +10 -0
  48. package/src/Graph/Legend/Legend.tsx +6 -1
  49. package/src/Graph/SingleBar/ResponsiveSingleBar.tsx +9 -10
  50. package/src/Graph/SingleBar/ThresholdLine.tsx +6 -6
  51. package/src/Graph/Text/Text.styles.ts +2 -2
  52. package/src/Graph/Text/Text.tsx +23 -10
  53. package/src/Graph/Timeline/ResponsiveTimeline.tsx +152 -0
  54. package/src/Graph/Timeline/Timeline.cypress.spec.tsx +148 -0
  55. package/src/Graph/Timeline/Timeline.stories.tsx +91 -0
  56. package/src/Graph/Timeline/Timeline.tsx +28 -0
  57. package/src/Graph/Timeline/index.ts +1 -0
  58. package/src/Graph/Timeline/models.ts +20 -0
  59. package/src/Graph/Timeline/timeline.styles.ts +11 -0
  60. package/src/Graph/Timeline/translatedLabel.ts +6 -0
  61. package/src/Graph/Timeline/useTimeline.ts +90 -0
  62. package/src/Graph/Tree/Links.tsx +2 -2
  63. package/src/Graph/Tree/Tree.tsx +2 -2
  64. package/src/Graph/Tree/constants.ts +1 -1
  65. package/src/Graph/common/Axes/index.tsx +1 -1
  66. package/src/Graph/common/Axes/useAxisY.ts +8 -4
  67. package/src/Graph/common/BaseChart/BaseChart.tsx +3 -12
  68. package/src/Graph/common/BaseChart/ChartSvgWrapper.tsx +12 -4
  69. package/src/Graph/common/BaseChart/Header/index.tsx +3 -1
  70. package/src/Graph/common/BaseChart/useComputeBaseChartDimensions.ts +23 -11
  71. package/src/Graph/common/BaseChart/useComputeYAxisMaxCharacters.ts +92 -0
  72. package/src/Graph/common/models.ts +7 -8
  73. package/src/Graph/common/timeSeries/index.test.ts +1 -1
  74. package/src/Graph/common/timeSeries/index.ts +56 -29
  75. package/src/Graph/common/timeSeries/models.ts +2 -0
  76. package/src/Graph/common/utils.ts +51 -3
  77. package/src/Graph/index.ts +4 -1
  78. package/src/Graph/mockedData/lastDayWithNullValues.json +6 -6
  79. package/src/Graph/mockedData/pingServiceLinesBars.json +47 -47
  80. package/src/Icon/DowntimeIcon.tsx +8 -1
  81. package/src/Icon/FlappingIcon.tsx +22 -0
  82. package/src/Icon/index.ts +1 -0
  83. package/src/InputField/Select/Autocomplete/Connected/Multi/index.test.tsx +21 -1
  84. package/src/InputField/Select/Autocomplete/Connected/index.test.tsx +2 -2
  85. package/src/InputField/Select/Autocomplete/Connected/index.tsx +52 -15
  86. package/src/InputField/Select/Autocomplete/Multi/index.stories.tsx +19 -0
  87. package/src/InputField/Select/Autocomplete/Multi/index.tsx +8 -5
  88. package/src/InputField/Select/Autocomplete/index.tsx +79 -54
  89. package/src/InputField/Text/index.tsx +6 -4
  90. package/src/InputField/translatedLabels.ts +2 -0
  91. package/src/Listing/ActionBar/index.tsx +1 -1
  92. package/src/Listing/Listing.styles.ts +3 -3
  93. package/src/Listing/index.tsx +40 -37
  94. package/src/Listing/models.ts +0 -8
  95. package/src/Listing/useStyleTable.ts +58 -32
  96. package/src/MultiSelectEntries/index.tsx +2 -0
  97. package/src/PopoverMenu/index.tsx +2 -9
  98. package/src/SortableItems/index.tsx +0 -1
  99. package/src/ThemeProvider/index.tsx +1 -1
  100. package/src/ThemeProvider/palettes.ts +6 -0
  101. package/src/TimePeriods/CustomTimePeriod/PopoverCustomTimePeriod/PickersStartEndDate.tsx +2 -3
  102. package/src/TimePeriods/DateTimePickerInput.tsx +3 -1
  103. package/src/api/buildListingEndpoint/getSearchQueryParameterValue.ts +7 -1
  104. package/src/api/buildListingEndpoint/models.ts +1 -0
  105. package/src/api/customFetch.ts +4 -1
  106. package/src/api/models.ts +9 -0
  107. package/src/api/useGraphQuery/index.ts +117 -20
  108. package/src/api/useGraphQuery/models.ts +1 -0
  109. package/src/api/useMutationQuery/index.ts +1 -1
  110. package/src/components/DataTable/DataTable.styles.ts +1 -1
  111. package/src/components/DataTable/EmptyState/DataTableEmptyState.styles.ts +2 -1
  112. package/src/components/DataTable/EmptyState/DataTableEmptyState.tsx +4 -1
  113. package/src/components/DataTable/Item/DataTableItem.styles.ts +28 -2
  114. package/src/components/DataTable/Item/DataTableItem.tsx +19 -4
  115. package/src/components/Form/FormActions.tsx +21 -12
  116. package/src/components/Layout/AreaIndicator.tsx +1 -1
  117. package/src/components/Layout/PageLayout/PageLayout.styles.ts +2 -7
  118. package/src/components/Layout/PageLayout/PageLayoutBody.tsx +0 -1
  119. package/src/components/Zoom/Zoom.tsx +9 -2
  120. package/src/components/Zoom/ZoomContent.tsx +143 -136
  121. package/src/components/Zoom/models.ts +18 -15
  122. package/src/components/Zoom/useMinimap.ts +5 -8
  123. package/src/components/Zoom/useZoom.ts +3 -3
  124. package/src/index.ts +2 -0
  125. package/src/utils/index.ts +1 -0
  126. package/src/utils/useLocale/index.ts +9 -0
  127. package/src/utils/useLocale/useLocale.cypress.spec.tsx +38 -0
  128. package/src/utils/useLocaleDateTimeFormat/index.ts +4 -2
  129. package/src/utils/usePluralizedTranslation.ts +2 -3
  130. package/src/Graph/common/timeSeries/index.test.ts-E +0 -622
  131. package/src/components/CrudPage/Actions/Actions.styles.ts +0 -16
  132. package/src/components/CrudPage/Actions/Actions.tsx +0 -24
  133. package/src/components/CrudPage/Actions/AddButton.tsx +0 -23
  134. package/src/components/CrudPage/Actions/Filters.tsx +0 -25
  135. package/src/components/CrudPage/Actions/Search.tsx +0 -31
  136. package/src/components/CrudPage/Actions/useSearch.tsx +0 -24
  137. package/src/components/CrudPage/Columns/Actions.tsx +0 -88
  138. package/src/components/CrudPage/CrudPage.cypress.spec.tsx +0 -559
  139. package/src/components/CrudPage/CrudPage.stories.tsx +0 -278
  140. package/src/components/CrudPage/CrudPageRoot.tsx +0 -142
  141. package/src/components/CrudPage/DeleteModal.tsx +0 -77
  142. package/src/components/CrudPage/Form/AddModal.tsx +0 -35
  143. package/src/components/CrudPage/Form/Buttons.tsx +0 -98
  144. package/src/components/CrudPage/Form/UpdateModal.tsx +0 -60
  145. package/src/components/CrudPage/Listing.tsx +0 -63
  146. package/src/components/CrudPage/atoms.ts +0 -30
  147. package/src/components/CrudPage/hooks/useDeleteItem.ts +0 -53
  148. package/src/components/CrudPage/hooks/useGetItem.ts +0 -36
  149. package/src/components/CrudPage/hooks/useGetItems.ts +0 -67
  150. package/src/components/CrudPage/hooks/useListingQueryKey.ts +0 -31
  151. package/src/components/CrudPage/index.tsx +0 -7
  152. package/src/components/CrudPage/models.ts +0 -118
  153. package/src/components/CrudPage/utils.ts +0 -4
@@ -1,7 +1,7 @@
1
- import { useEffect } from 'react';
1
+ import { useEffect, useMemo } from 'react';
2
2
 
3
3
  import { useAtomValue, useSetAtom } from 'jotai';
4
- import { equals, isNil, pick, type } from 'ramda';
4
+ import { equals, isEmpty, isNil, pick, type, update } from 'ramda';
5
5
  import { CSSObject } from 'tss-react';
6
6
 
7
7
  import { Theme } from '@mui/material';
@@ -12,14 +12,11 @@ import { Column, TableStyleAtom as Style } from './models';
12
12
  import { tableStyleAtom, tableStyleDerivedAtom } from './tableAtoms';
13
13
 
14
14
  interface TableStyle {
15
- checkable?: boolean;
16
- currentVisibleColumns?: Array<Column>;
17
15
  listingVariant?: ListingVariant;
18
16
  }
19
17
 
20
18
  interface TableStyleState {
21
19
  dataStyle: Style;
22
- getGridTemplateColumn: string;
23
20
  }
24
21
 
25
22
  const isCompactMode = equals<ListingVariant | undefined>(
@@ -40,35 +37,11 @@ export const getTextStyleByViewMode = ({
40
37
  theme.typography[isCompactMode(listingVariant) ? 'body2' : 'body1']
41
38
  );
42
39
 
43
- const useStyleTable = ({
44
- checkable,
45
- currentVisibleColumns,
46
- listingVariant
47
- }: TableStyle): TableStyleState => {
40
+ const useStyleTable = ({ listingVariant }: TableStyle): TableStyleState => {
48
41
  const dataStyle = useAtomValue(tableStyleAtom);
49
42
 
50
43
  const updateStyleTable = useSetAtom(tableStyleDerivedAtom);
51
44
 
52
- const getGridTemplateColumn = (): string => {
53
- const checkbox = checkable ? 'fit-content(1rem) ' : ''; // SelectAction (checkbox) cell adjusts to content
54
-
55
- const columnTemplate = currentVisibleColumns
56
- ?.filter((column) => column)
57
- ?.map(({ width, shortLabel }) => {
58
- if (!isNil(shortLabel)) {
59
- return 'min-content';
60
- }
61
- if (isNil(width)) {
62
- return 'auto';
63
- }
64
-
65
- return equals(type(width), 'Number') ? `${width}px` : width;
66
- })
67
- .join(' ');
68
-
69
- return `${checkbox}${columnTemplate}`;
70
- };
71
-
72
45
  useEffect(() => {
73
46
  if (listingVariant) {
74
47
  updateStyleTable({ listingVariant });
@@ -76,9 +49,62 @@ const useStyleTable = ({
76
49
  }, [listingVariant]);
77
50
 
78
51
  return {
79
- dataStyle,
80
- getGridTemplateColumn: getGridTemplateColumn()
52
+ dataStyle
81
53
  };
82
54
  };
83
55
 
84
56
  export default useStyleTable;
57
+
58
+ interface UseColumnStyleProps {
59
+ checkable?: boolean;
60
+ currentVisibleColumns?: Array<Column>;
61
+ }
62
+
63
+ export const useColumnStyle = ({
64
+ checkable,
65
+ currentVisibleColumns
66
+ }: UseColumnStyleProps): string => {
67
+ const gridTemplateColumn = useMemo((): string => {
68
+ const checkbox = checkable ? 'fit-content(1rem) ' : ''; // SelectAction (checkbox) cell adjusts to content
69
+
70
+ const columnTemplate: Array<string> =
71
+ currentVisibleColumns
72
+ ?.filter((column) => column)
73
+ ?.map(({ width, shortLabel }) => {
74
+ if (!isNil(shortLabel)) {
75
+ return 'min-content';
76
+ }
77
+ if (isNil(width)) {
78
+ return 'auto';
79
+ }
80
+
81
+ return (
82
+ equals(type(width), 'Number') ? `${width}px` : width
83
+ ) as string;
84
+ }) || [];
85
+
86
+ const hasOnlyContainerResponsiveColumns =
87
+ !isEmpty(columnTemplate) &&
88
+ columnTemplate.every(
89
+ (width: string) =>
90
+ width.includes('auto') ||
91
+ width.includes('fr') ||
92
+ width.includes('%') ||
93
+ width.includes('px')
94
+ );
95
+
96
+ if (!hasOnlyContainerResponsiveColumns) {
97
+ const fixedColumnTemplate = update(
98
+ columnTemplate.length - 1,
99
+ 'auto',
100
+ columnTemplate
101
+ );
102
+
103
+ return `${checkbox}${fixedColumnTemplate.join(' ')}`;
104
+ }
105
+
106
+ return `${checkbox}${columnTemplate.join(' ')}`;
107
+ }, [checkable, currentVisibleColumns]);
108
+
109
+ return gridTemplateColumn;
110
+ };
@@ -134,6 +134,8 @@ const MultiSelectEntries = ({
134
134
  [classes.container]: true
135
135
  } as CxArg)}
136
136
  ref={hoverRef as Ref<HTMLDivElement>}
137
+ role="button"
138
+ tabIndex={0}
137
139
  onClick={onClick}
138
140
  onKeyDown={onClick}
139
141
  >
@@ -10,7 +10,6 @@ import {
10
10
  } from '@mui/material';
11
11
  import type { PopperProps } from '@mui/material/Popper';
12
12
 
13
- import { equals, type } from 'ramda';
14
13
  import { IconButton } from '..';
15
14
 
16
15
  const useStyles = makeStyles()((theme) => ({
@@ -30,9 +29,8 @@ interface PopoverData {
30
29
 
31
30
  interface Props {
32
31
  canOpen?: boolean;
33
- children: (props?) => JSX.Element | JSX.Element;
32
+ children: (props?) => JSX.Element;
34
33
  className?: string;
35
- tooltipClassName?: string;
36
34
  dataTestId?: string;
37
35
  getPopoverData?: (data: PopoverData) => void;
38
36
  icon: JSX.Element;
@@ -54,7 +52,6 @@ const PopoverMenu = ({
54
52
  className,
55
53
  dataTestId,
56
54
  getPopoverData,
57
- tooltipClassName,
58
55
  popperProps
59
56
  }: Props): JSX.Element => {
60
57
  const { classes, cx } = useStyles();
@@ -116,11 +113,7 @@ const PopoverMenu = ({
116
113
  onResizeCapture={(): undefined => undefined}
117
114
  {...popperProps}
118
115
  >
119
- <Paper className={tooltipClassName}>
120
- {equals(type(children), 'Function')
121
- ? children({ close })
122
- : children}
123
- </Paper>
116
+ <Paper>{children({ close })}</Paper>
124
117
  </Popper>
125
118
  </ClickAwayListener>
126
119
  )}
@@ -185,7 +185,6 @@ const SortableItems = <T extends { [propertyToFilterItemsOn]: string }>({
185
185
  >
186
186
  <SortableContext items={sortableItemsIds} strategy={sortingStrategy}>
187
187
  <RootComponent>
188
- {/* biome-ignore lint: */}
189
188
  <>
190
189
  {sortableItemsIds.map((sortableItemId, index) => {
191
190
  const item = getItemById(sortableItemId) as
@@ -261,7 +261,7 @@ export const getTheme = (mode: ThemeMode): ThemeOptions => ({
261
261
  {
262
262
  backgroundColor: theme.palette.background.default,
263
263
  border: 'none',
264
- borderRadius: `${theme.shape.borderRadius}px`,
264
+ borderRadius: 0,
265
265
  boxShadow: theme.shadows[3]
266
266
  }
267
267
  })
@@ -27,6 +27,8 @@ declare module '@mui/material/styles/createPalette' {
27
27
  acknowledgedBackground: string;
28
28
  inDowntime: string;
29
29
  inDowntimeBackground: string;
30
+ inFlapping: string;
31
+ inFlappingBackground: string;
30
32
  }
31
33
  }
32
34
 
@@ -159,6 +161,8 @@ export const lightPalette: PaletteOptions = {
159
161
  action: {
160
162
  acknowledged: '#745F35',
161
163
  acknowledgedBackground: '#DFD2B9',
164
+ inFlapping: '#064A3F',
165
+ inFlappingBackground: '#D8F3EF',
162
166
  activatedOpacity: 0.12,
163
167
  active: '#666666',
164
168
  disabled: '#999999',
@@ -299,6 +303,8 @@ export const darkPalette: PaletteOptions = {
299
303
  action: {
300
304
  acknowledged: '#DFD2B9',
301
305
  acknowledgedBackground: '#745F35',
306
+ inFlapping: '#D8F3EF',
307
+ inFlappingBackground: '#064A3F',
302
308
  activatedOpacity: 0.3,
303
309
  active: '#B5B5B5',
304
310
  disabled: '#999999',
@@ -10,8 +10,6 @@ import { Typography } from '@mui/material';
10
10
  import { LocalizationProvider } from '@mui/x-date-pickers';
11
11
  import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
12
12
 
13
- import { userAtom } from '@centreon/ui-context';
14
-
15
13
  import DateTimePickerInput from '../../DateTimePickerInput';
16
14
  import { isInvalidDate } from '../../helpers';
17
15
  import {
@@ -20,6 +18,7 @@ import {
20
18
  } from '../../models';
21
19
  import { errorTimePeriodAtom } from '../../timePeriodsAtoms';
22
20
 
21
+ import { useLocale } from '../../../utils';
23
22
  import ErrorText from './ErrorText';
24
23
  import {
25
24
  PickersStartEndDateDirection,
@@ -100,7 +99,7 @@ const PickersStartEndDate = ({
100
99
  }: PickersStartEndDateProps): JSX.Element => {
101
100
  const { classes } = useStyles();
102
101
 
103
- const { locale } = useAtomValue(userAtom);
102
+ const locale = useLocale();
104
103
  const error = useAtomValue(errorTimePeriodAtom);
105
104
  const isError = error || isInvalidDate({ endDate, startDate });
106
105
 
@@ -13,6 +13,7 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
13
13
 
14
14
  import { userAtom } from '@centreon/ui-context';
15
15
 
16
+ import { useLocale } from '../utils';
16
17
  import { CustomTimePeriodProperty } from './models';
17
18
 
18
19
  interface ChangeDateProps {
@@ -50,6 +51,7 @@ const DateTimePickerInput = ({
50
51
  '@media (min-width: 1024px) or (pointer: fine)';
51
52
 
52
53
  const user = useAtomValue(userAtom);
54
+ const localeToUse = useLocale();
53
55
 
54
56
  const isUTC = equals(timezone ?? user.timezone, 'UTC');
55
57
 
@@ -72,7 +74,7 @@ const DateTimePickerInput = ({
72
74
 
73
75
  return (
74
76
  <LocalizationProvider
75
- adapterLocale={(locale ?? user.locale).substring(0, 2)}
77
+ adapterLocale={(locale ?? localeToUse).substring(0, 2)}
76
78
  dateAdapter={AdapterDayjs}
77
79
  dateLibInstance={dayjs}
78
80
  >
@@ -93,8 +93,14 @@ const getConditionsSearchQueryParameterValue = (
93
93
  equals(listField, field)
94
94
  );
95
95
 
96
+ const globalOperator = filteredItems.every(({ operator }) =>
97
+ equals(operator, filteredItems[0].operator)
98
+ )
99
+ ? filteredItems[0].operator || '$or'
100
+ : '$or';
101
+
96
102
  return {
97
- $or: flatten(
103
+ [globalOperator]: flatten(
98
104
  filteredItems.map(({ value, values }) => {
99
105
  if (!isNil(value)) {
100
106
  return [
@@ -63,6 +63,7 @@ export interface ConditionsSearchParameter {
63
63
  field: string;
64
64
  value?: unknown;
65
65
  values?: ConditionValue;
66
+ operator?: '$and' | '$or';
66
67
  }
67
68
 
68
69
  type SearchPatterns = Array<{ [field: string]: { $rg: string } }>;
@@ -55,10 +55,13 @@ export const customFetch = <T>({
55
55
  ? `${baseEndpoint}${endpoint}`
56
56
  : endpoint;
57
57
 
58
+ const isFormData = payload instanceof FormData;
59
+
58
60
  const options = isMutation
59
61
  ? {
60
62
  ...defaultOptions,
61
- body: payload instanceof FormData ? payload : JSON.stringify(payload)
63
+ body: isFormData ? payload : JSON.stringify(payload),
64
+ headers: isFormData ? undefined : headers
62
65
  }
63
66
  : defaultOptions;
64
67
 
package/src/api/models.ts CHANGED
@@ -8,3 +8,12 @@ export interface Listing<TEntity> {
8
8
  meta: ListingMeta;
9
9
  result: Array<TEntity>;
10
10
  }
11
+
12
+ export interface ListingMap<TEntity> {
13
+ content: Array<TEntity>;
14
+ totalPages: number;
15
+ totalElements: number;
16
+ size: number;
17
+ number: number;
18
+ numberOfElements: number;
19
+ }
@@ -36,6 +36,7 @@ interface UseMetricsQueryProps {
36
36
  start?: string | null;
37
37
  timePeriodType: number;
38
38
  };
39
+ isEnabled?: boolean;
39
40
  }
40
41
 
41
42
  interface UseMetricsQueryState {
@@ -46,6 +47,12 @@ interface UseMetricsQueryState {
46
47
  start: string;
47
48
  }
48
49
 
50
+ interface FormatLegend {
51
+ host?: string | null;
52
+ service?: string | null;
53
+ metric: string;
54
+ }
55
+
49
56
  const getStartEndFromTimePeriod = (
50
57
  timePeriod: number
51
58
  ): { end: string; start: string } => {
@@ -74,7 +81,8 @@ export const resourceTypeQueryParameter = {
74
81
  [WidgetResourceType.hostGroup]: 'hostgroup.id',
75
82
  [WidgetResourceType.serviceCategory]: 'servicecategory.id',
76
83
  [WidgetResourceType.serviceGroup]: 'servicegroup.id',
77
- [WidgetResourceType.service]: 'service.name'
84
+ [WidgetResourceType.service]: 'service.name',
85
+ [WidgetResourceType.metaService]: 'metaservice.id'
78
86
  };
79
87
 
80
88
  const areResourcesFullfilled = (value: Array<Resource>): boolean =>
@@ -94,7 +102,8 @@ const useGraphQuery = ({
94
102
  refreshInterval = false,
95
103
  refreshCount,
96
104
  bypassQueryParams = false,
97
- prefix
105
+ prefix,
106
+ isEnabled = true
98
107
  }: UseMetricsQueryProps): UseMetricsQueryState => {
99
108
  const timePeriodToUse = equals(timePeriod?.timePeriodType, -1)
100
109
  ? {
@@ -108,9 +117,9 @@ const useGraphQuery = ({
108
117
  : getStartEndFromTimePeriod(timePeriodToUse as number);
109
118
 
110
119
  const definedMetrics = metrics.filter((metric) => metric);
111
- const formattedDefinedMetrics = definedMetrics.map((metric) =>
112
- encodeURIComponent(metric.name)
113
- );
120
+ const formattedDefinedMetrics = definedMetrics
121
+ .map((metric) => `metric_names[]=${encodeURIComponent(metric.name)}`)
122
+ .join('&');
114
123
 
115
124
  const prefixQuery = prefix ? [prefix] : [];
116
125
 
@@ -129,7 +138,9 @@ const useGraphQuery = ({
129
138
  parameters: {
130
139
  search: {
131
140
  lists: resources.map((resource) => ({
132
- field: resourceTypeQueryParameter[resource.resourceType],
141
+ field: equals(resource.resourceType, 'hostgroup')
142
+ ? resourceTypeQueryParameter[WidgetResourceType.hostGroup]
143
+ : resourceTypeQueryParameter[resource.resourceType],
133
144
  values: equals(resource.resourceType, 'service')
134
145
  ? pluck('name', resource.resources)
135
146
  : pluck('id', resource.resources)
@@ -138,9 +149,7 @@ const useGraphQuery = ({
138
149
  }
139
150
  });
140
151
 
141
- return `${endpoint}&start=${startAndEnd.start}&end=${
142
- startAndEnd.end
143
- }&metric_names=[${formattedDefinedMetrics.join(',')}]`;
152
+ return `${endpoint}&start=${startAndEnd.start}&end=${startAndEnd.end}&${formattedDefinedMetrics}`;
144
153
  },
145
154
  getQueryKey: () => [
146
155
  ...prefixQuery,
@@ -151,7 +160,10 @@ const useGraphQuery = ({
151
160
  refreshCount || 0
152
161
  ],
153
162
  queryOptions: {
154
- enabled: areResourcesFullfilled(resources) && !isEmpty(definedMetrics),
163
+ enabled:
164
+ areResourcesFullfilled(resources) &&
165
+ !isEmpty(definedMetrics) &&
166
+ isEnabled,
155
167
  refetchInterval: refreshInterval,
156
168
  suspense: false
157
169
  },
@@ -163,22 +175,107 @@ const useGraphQuery = ({
163
175
  data.current = graphData;
164
176
  }
165
177
 
178
+ const getCurrentMetrics = () => {
179
+ if (!data.current) {
180
+ return undefined;
181
+ }
182
+
183
+ return bypassMetricsExclusion
184
+ ? data.current.metrics
185
+ : data.current.metrics.filter(({ metric_id }) => {
186
+ return pipe(
187
+ pluck('excludedMetrics'),
188
+ flatten,
189
+ includes(metric_id),
190
+ not
191
+ )(metrics);
192
+ });
193
+ };
194
+
195
+ const formatLegend = ({
196
+ host = null,
197
+ service = null,
198
+ metric
199
+ }: FormatLegend) => {
200
+ if (!host && !service) {
201
+ return metric;
202
+ }
203
+
204
+ if (!host) {
205
+ return `${service}: ${metric}`;
206
+ }
207
+
208
+ if (!service) {
209
+ return `${host}: ${metric}`;
210
+ }
211
+
212
+ return `${host} ${service}: ${metric}`;
213
+ };
214
+
215
+ const getFormattedMetrics = () => {
216
+ const metrics = getCurrentMetrics();
217
+
218
+ if (equals(metrics?.length, 1)) {
219
+ return metrics?.map((line) => {
220
+ const formattedLegend = formatLegend({
221
+ host: line?.host_name,
222
+ service: line?.service_name,
223
+ metric: line?.metric
224
+ });
225
+
226
+ return { ...line, legend: formattedLegend };
227
+ });
228
+ }
229
+
230
+ return metrics?.map((line) => {
231
+ const areHostNameRedundant = metrics.every(({ host_name }) =>
232
+ equals(host_name, line.host_name)
233
+ );
234
+ const areServiceNameRedundant = metrics.every(({ service_name }) =>
235
+ equals(service_name, line.service_name)
236
+ );
237
+
238
+ if (areHostNameRedundant && areServiceNameRedundant) {
239
+ const formattedLegend = formatLegend({ metric: line.metric });
240
+
241
+ return { ...line, legend: formattedLegend };
242
+ }
243
+
244
+ if (areHostNameRedundant) {
245
+ const formattedLegend = formatLegend({
246
+ service: line.service_name,
247
+ metric: line.metric
248
+ });
249
+
250
+ return { ...line, legend: formattedLegend };
251
+ }
252
+
253
+ if (areServiceNameRedundant) {
254
+ const formattedLegend = formatLegend({
255
+ host: line.host_name,
256
+ metric: line.metric
257
+ });
258
+
259
+ return { ...line, legend: formattedLegend };
260
+ }
261
+
262
+ const formattedLegend = formatLegend({
263
+ host: line.host_name,
264
+ service: line.service_name,
265
+ metric: line.metric
266
+ });
267
+
268
+ return { ...line, legend: formattedLegend };
269
+ });
270
+ };
271
+
166
272
  const formattedGraphData = data.current
167
273
  ? {
168
274
  global: {
169
275
  base: data.current.base,
170
276
  title: ''
171
277
  },
172
- metrics: bypassMetricsExclusion
173
- ? data.current.metrics
174
- : data.current.metrics.filter(({ metric_id }) => {
175
- return pipe(
176
- pluck('excludedMetrics'),
177
- flatten,
178
- includes(metric_id),
179
- not
180
- )(metrics);
181
- }),
278
+ metrics: getFormattedMetrics(),
182
279
  times: data.current.times
183
280
  }
184
281
  : undefined;
@@ -10,6 +10,7 @@ export enum WidgetResourceType {
10
10
  hostCategory = 'host-category',
11
11
  hostGroup = 'host-group',
12
12
  service = 'service',
13
+ metaService = 'meta-service',
13
14
  serviceCategory = 'service-category',
14
15
  serviceGroup = 'service-group'
15
16
  }
@@ -100,7 +100,7 @@ const useMutationQuery = <T extends object, TMeta>({
100
100
  defaultFailureMessage,
101
101
  endpoint: getEndpoint(_meta as TMeta),
102
102
  headers: new Headers({
103
- 'Content-Type': 'application/x-www-form-urlencoded',
103
+ 'Content-Type': 'application/json',
104
104
  ...fetchHeaders
105
105
  }),
106
106
  isMutation: true,
@@ -8,7 +8,7 @@ const useStyles = makeStyles()((theme) => ({
8
8
  },
9
9
  display: 'grid',
10
10
  gridGap: theme.spacing(2.5),
11
- gridTemplateColumns: `repeat(auto-fill, ${theme.spacing(45)})`
11
+ gridTemplateColumns: `repeat(auto-fill, ${theme.spacing(53)})`
12
12
  },
13
13
  '&[data-variant="listing"]': {
14
14
  height: '100%'
@@ -21,7 +21,8 @@ const useStyles = makeStyles()((theme) => ({
21
21
  width: '100%'
22
22
  },
23
23
  description: {
24
- maxWidth: '65%'
24
+ maxWidth: '65%',
25
+ textAlign: 'center'
25
26
  }
26
27
  }));
27
28
 
@@ -19,12 +19,14 @@ type ListEmptyStateProps = {
19
19
  description?: string;
20
20
  };
21
21
  onCreate?: () => void;
22
+ buttonCreateTestId?: string;
22
23
  };
23
24
 
24
25
  const DataTableEmptyState = ({
25
26
  labels,
26
27
  onCreate,
27
- canCreate = true
28
+ canCreate = true,
29
+ buttonCreateTestId
28
30
  }: ListEmptyStateProps): ReactElement => {
29
31
  const { classes } = useStyles();
30
32
  const { t } = useTranslation();
@@ -47,6 +49,7 @@ const DataTableEmptyState = ({
47
49
  icon={<AddIcon />}
48
50
  iconVariant="start"
49
51
  onClick={() => onCreate?.()}
52
+ data-testid={buttonCreateTestId}
50
53
  >
51
54
  {t(labels.actions?.create || '')}
52
55
  </Button>
@@ -6,6 +6,16 @@ const useStyles = makeStyles()((theme) => ({
6
6
  flexDirection: 'row',
7
7
  justifyContent: 'space-between'
8
8
  },
9
+ cardActions: {
10
+ backgroundColor: theme.palette.background.paper,
11
+ bottom: 0,
12
+ position: 'absolute',
13
+ width: '100%'
14
+ },
15
+ cardContent: {
16
+ padding: theme.spacing(2),
17
+ zIndex: 1
18
+ },
9
19
  dataTableItem: {
10
20
  '& .MuiCardActionArea-root': {
11
21
  alignItems: 'flex-start',
@@ -22,16 +32,32 @@ const useStyles = makeStyles()((theme) => ({
22
32
  display: 'flex',
23
33
  justifyContent: 'space-between'
24
34
  },
35
+ '&:hover img[alt*="thumbnail"]': {
36
+ transform: 'scale(1.1)',
37
+ transformOrigin: 'center'
38
+ },
25
39
  borderRadius: theme.shape.borderRadius,
26
40
  display: 'flex',
27
41
  flexDirection: 'column',
28
- height: '186px',
42
+ height: '250px',
29
43
  justifyContent: 'space-between',
30
44
  p: {
31
45
  color: theme.palette.text.secondary,
32
46
  letterSpacing: '0',
33
47
  margin: '0'
34
- }
48
+ },
49
+ position: 'relative'
50
+ },
51
+ description: {
52
+ maxHeight: '42px',
53
+ overflow: 'hidden'
54
+ },
55
+ thumbnail: {
56
+ height: theme.spacing(10.25),
57
+ objectFit: 'cover',
58
+ objectPosition: 'top',
59
+ transition: 'transform 150ms ease-out',
60
+ width: '100%'
35
61
  }
36
62
  }));
37
63