@centreon/ui 25.10.2-dev-25-10-x.0 → 25.10.9
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 +1 -1
- package/src/Form/Inputs/Grid.tsx +3 -2
- package/src/Form/Section/navigateToSection.ts +6 -6
- package/src/Graph/BarChart/BarChart.cypress.spec.tsx +9 -0
- package/src/Graph/BarChart/BarChart.stories.tsx +42 -1
- package/src/Graph/BarChart/BarChart.tsx +1 -1
- package/src/Graph/BarChart/ResponsiveBarChart.tsx +2 -1
- package/src/Graph/Chart/Chart.cypress.spec.tsx +24 -5
- package/src/Graph/Chart/Chart.stories.tsx +34 -1
- package/src/Graph/Chart/Chart.tsx +3 -2
- package/src/Graph/Chart/Legend/index.tsx +26 -2
- package/src/Graph/Chart/models.ts +6 -1
- package/src/Graph/Chart/useChartData.ts +1 -1
- package/src/Graph/common/BaseChart/BaseChart.tsx +6 -1
- package/src/Graph/common/timeSeries/index.ts +32 -11
- package/src/Graph/common/utils.ts +10 -4
- package/src/Graph/mockedData/dataWithMissingPoint.json +74 -0
- package/src/InputField/Select/index.tsx +1 -2
- package/src/Module/index.tsx +8 -2
- package/src/ThemeProvider/base.css +49 -0
- package/src/ThemeProvider/index.tsx +18 -46
- package/src/ThemeProvider/tailwindcss.css +26 -0
- package/src/api/useGraphQuery/index.ts +7 -2
- package/src/components/Modal/Modal.styles.ts +1 -2
- package/src/components/Modal/ModalHeader.tsx +5 -1
package/package.json
CHANGED
package/src/Form/Inputs/Grid.tsx
CHANGED
|
@@ -21,8 +21,9 @@ const Grid = ({
|
|
|
21
21
|
<div
|
|
22
22
|
className={`${className} grid gap-3`}
|
|
23
23
|
style={{
|
|
24
|
-
gridTemplateColumns:
|
|
25
|
-
|
|
24
|
+
gridTemplateColumns: className
|
|
25
|
+
? grid?.gridTemplateColumns || undefined
|
|
26
|
+
: grid?.gridTemplateColumns ||
|
|
26
27
|
`repeat(${grid?.columns.length || 1}, 1fr)`,
|
|
27
28
|
alignItems: grid?.alignItems || 'flex-start'
|
|
28
29
|
}}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export const useNavigateToSection = () => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
return (sectionName: string) => {
|
|
3
|
+
const section = document.querySelector(
|
|
4
|
+
`[data-section-group-form-id="${sectionName}"]`
|
|
5
|
+
);
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
section?.scrollIntoView({ behavior: 'smooth' });
|
|
8
|
+
};
|
|
9
9
|
};
|
|
@@ -8,6 +8,7 @@ import dataLastWeek from '../mockedData/lastWeek.json';
|
|
|
8
8
|
import dataPingService from '../mockedData/pingService.json';
|
|
9
9
|
import dataPingServiceMixedStacked from '../mockedData/pingServiceMixedStacked.json';
|
|
10
10
|
import dataPingServiceStacked from '../mockedData/pingServiceStacked.json';
|
|
11
|
+
import dataMissingPoint from '../mockedData/dataWithMissingPoint.json';
|
|
11
12
|
|
|
12
13
|
import BarChart, { BarChartProps } from './BarChart';
|
|
13
14
|
|
|
@@ -312,4 +313,12 @@ describe('Bar chart', () => {
|
|
|
312
313
|
cy.contains('1 s').should('be.visible');
|
|
313
314
|
cy.contains('1%').should('be.visible');
|
|
314
315
|
});
|
|
316
|
+
|
|
317
|
+
it('displays the stacked bar chart correctly when a point is missing compare to the time serie', () => {
|
|
318
|
+
initialize({ data: dataMissingPoint });
|
|
319
|
+
|
|
320
|
+
cy.findByTestId('stacked-bar-2-0-139').should('be.visible');
|
|
321
|
+
|
|
322
|
+
cy.makeSnapshot();
|
|
323
|
+
});
|
|
315
324
|
});
|
|
@@ -6,6 +6,8 @@ import dataPingService from '../mockedData/pingService.json';
|
|
|
6
6
|
import dataPingServiceMixedStacked from '../mockedData/pingServiceMixedStacked.json';
|
|
7
7
|
import dataPingServiceStacked from '../mockedData/pingServiceStacked.json';
|
|
8
8
|
|
|
9
|
+
import { ClickAwayListener } from '@mui/material';
|
|
10
|
+
import { useState } from 'react';
|
|
9
11
|
import BarChart from './BarChart';
|
|
10
12
|
|
|
11
13
|
const meta: Meta<typeof BarChart> = {
|
|
@@ -259,4 +261,43 @@ export const mixedStackedMinMax: Story = {
|
|
|
259
261
|
max: 20
|
|
260
262
|
},
|
|
261
263
|
render: Template
|
|
262
|
-
};
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const LegendSecondaryClick = (args) => {
|
|
267
|
+
const [position, setPosition] = useState<Array<[number, number]> | null>(
|
|
268
|
+
null
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<>
|
|
273
|
+
<Template
|
|
274
|
+
{...args}
|
|
275
|
+
legend={{
|
|
276
|
+
secondaryClick: ({ position }) => setPosition(position)
|
|
277
|
+
}}
|
|
278
|
+
/>
|
|
279
|
+
{position && (
|
|
280
|
+
<ClickAwayListener onClickAway={() => setPosition(null)}>
|
|
281
|
+
<div
|
|
282
|
+
className="absolute py-1 px-2 rounded-sm bg-background-widget shadow-md"
|
|
283
|
+
style={{ left: position?.[0], top: position?.[1] }}
|
|
284
|
+
open={Boolean(position)}
|
|
285
|
+
onClose={() => setPosition(null)}
|
|
286
|
+
>
|
|
287
|
+
menu
|
|
288
|
+
</div>
|
|
289
|
+
</ClickAwayListener>
|
|
290
|
+
)}
|
|
291
|
+
</>
|
|
292
|
+
);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export const withLegendSecondaryClick: Story = {
|
|
296
|
+
args: defaultArgs,
|
|
297
|
+
render: (args) => (
|
|
298
|
+
<LegendSecondaryClick
|
|
299
|
+
{...args}
|
|
300
|
+
data={dataPingService as unknown as LineChartData}
|
|
301
|
+
/>
|
|
302
|
+
)
|
|
303
|
+
};
|
|
@@ -196,7 +196,8 @@ const ResponsiveBarChart = ({
|
|
|
196
196
|
displayLegend,
|
|
197
197
|
mode: legend?.mode,
|
|
198
198
|
placement: legend?.placement,
|
|
199
|
-
renderExtraComponent: legend?.renderExtraComponent
|
|
199
|
+
renderExtraComponent: legend?.renderExtraComponent,
|
|
200
|
+
secondaryClick: legend?.secondaryClick
|
|
200
201
|
}}
|
|
201
202
|
legendRef={legendRef}
|
|
202
203
|
limitLegend={limitLegend}
|
|
@@ -180,7 +180,7 @@ describe('Line chart', () => {
|
|
|
180
180
|
cy.contains('06/18/2023').should('be.visible');
|
|
181
181
|
|
|
182
182
|
cy.contains('0.4 s').should('be.visible');
|
|
183
|
-
|
|
183
|
+
cy.contains('75.64%').should('be.visible');
|
|
184
184
|
|
|
185
185
|
cy.makeSnapshot();
|
|
186
186
|
});
|
|
@@ -532,7 +532,7 @@ describe('Line chart', () => {
|
|
|
532
532
|
|
|
533
533
|
checkGraphWidth();
|
|
534
534
|
cy.contains(':00 AM').should('be.visible');
|
|
535
|
-
cy.get('circle[cx="
|
|
535
|
+
cy.get('circle[cx="248.33333333333334"]').should('be.visible');
|
|
536
536
|
cy.get('circle[cy="251.79089393069725"]').should('be.visible');
|
|
537
537
|
|
|
538
538
|
cy.makeSnapshot();
|
|
@@ -591,7 +591,7 @@ describe('Line chart', () => {
|
|
|
591
591
|
cy.get('path.visx-area-closed')
|
|
592
592
|
.should('have.attr', 'stroke-dasharray')
|
|
593
593
|
.and('equals', '5 4');
|
|
594
|
-
cy.get('circle[cx="33.
|
|
594
|
+
cy.get('circle[cx="33.11111111111111"]').should('be.visible');
|
|
595
595
|
|
|
596
596
|
cy.makeSnapshot();
|
|
597
597
|
});
|
|
@@ -746,9 +746,11 @@ describe('Lines and bars', () => {
|
|
|
746
746
|
|
|
747
747
|
checkGraphWidth();
|
|
748
748
|
|
|
749
|
-
cy.get(
|
|
749
|
+
cy.get(
|
|
750
|
+
'path[d="M7.501377410468319,350.5553648585503 h56.51239669421488 h1v1 v23.44463514144968 a1,1 0 0 1 -1,1 h-56.51239669421488 a1,1 0 0 1 -1,-1 v-23.44463514144968 v-1h1z"]'
|
|
750
751
|
).should('be.visible');
|
|
751
|
-
cy.get(
|
|
752
|
+
cy.get(
|
|
753
|
+
'path[d="M24.05509641873278,201.58170928199803 h23.404958677685954 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,17.553719008264462 v113.86621756002336 v17.553719008264462h-17.553719008264462 h-23.404958677685954 h-17.553719008264462v-17.553719008264462 v-113.86621756002336 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,-17.553719008264462z"]'
|
|
752
754
|
).should('be.visible');
|
|
753
755
|
|
|
754
756
|
cy.makeSnapshot();
|
|
@@ -814,4 +816,21 @@ describe('Lines and bars', () => {
|
|
|
814
816
|
|
|
815
817
|
cy.makeSnapshot();
|
|
816
818
|
});
|
|
819
|
+
|
|
820
|
+
it('calls the secondary function when a metric is clicked in the legend', () => {
|
|
821
|
+
const secondaryClick = cy.stub().as('secondaryClick');
|
|
822
|
+
initialize({
|
|
823
|
+
data: dataPingServiceLines,
|
|
824
|
+
legend: {
|
|
825
|
+
mode: 'grid',
|
|
826
|
+
placement: 'bottom',
|
|
827
|
+
secondaryClick
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
checkGraphWidth();
|
|
832
|
+
|
|
833
|
+
cy.contains('Packet Loss').rightclick();
|
|
834
|
+
cy.get('@secondaryClick').should('have.been.called');
|
|
835
|
+
});
|
|
817
836
|
});
|
|
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
4
|
|
|
5
|
-
import { Button } from '@mui/material';
|
|
5
|
+
import { Button, Menu } from '@mui/material';
|
|
6
6
|
import ButtonGroup from '@mui/material/ButtonGroup';
|
|
7
7
|
import Switch from '@mui/material/Switch';
|
|
8
8
|
import Tooltip from '@mui/material/Tooltip';
|
|
@@ -758,3 +758,36 @@ export const linesAndBarsMinMaxForUnit: Story = {
|
|
|
758
758
|
/>
|
|
759
759
|
)
|
|
760
760
|
};
|
|
761
|
+
|
|
762
|
+
const LegendSecondaryClick = (args) => {
|
|
763
|
+
const [anchor, setAnchor] = useState<EventTarget | null>(null);
|
|
764
|
+
|
|
765
|
+
return (
|
|
766
|
+
<>
|
|
767
|
+
<WrapperChart
|
|
768
|
+
{...args}
|
|
769
|
+
legend={{
|
|
770
|
+
secondaryClick: ({ element }) => setAnchor(element)
|
|
771
|
+
}}
|
|
772
|
+
/>
|
|
773
|
+
<Menu
|
|
774
|
+
anchorEl={anchor}
|
|
775
|
+
open={Boolean(anchor)}
|
|
776
|
+
onClose={() => setAnchor(null)}
|
|
777
|
+
>
|
|
778
|
+
menu
|
|
779
|
+
</Menu>
|
|
780
|
+
</>
|
|
781
|
+
);
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
export const withLegendSecondaryClick: Story = {
|
|
785
|
+
argTypes,
|
|
786
|
+
args: argumentsData,
|
|
787
|
+
render: (args) => (
|
|
788
|
+
<LegendSecondaryClick
|
|
789
|
+
{...args}
|
|
790
|
+
data={dataPingService as unknown as LineChartData}
|
|
791
|
+
/>
|
|
792
|
+
)
|
|
793
|
+
};
|
|
@@ -277,7 +277,8 @@ const Chart = ({
|
|
|
277
277
|
legendHeight: legend?.height,
|
|
278
278
|
mode: legend?.mode,
|
|
279
279
|
placement: legend?.placement,
|
|
280
|
-
renderExtraComponent: legend?.renderExtraComponent
|
|
280
|
+
renderExtraComponent: legend?.renderExtraComponent,
|
|
281
|
+
secondaryClick: legend?.secondaryClick
|
|
281
282
|
}}
|
|
282
283
|
legendRef={legendRef}
|
|
283
284
|
limitLegend={limitLegend}
|
|
@@ -394,4 +395,4 @@ const Chart = ({
|
|
|
394
395
|
);
|
|
395
396
|
};
|
|
396
397
|
|
|
397
|
-
export default Chart;
|
|
398
|
+
export default Chart;
|
|
@@ -26,6 +26,11 @@ interface Props extends Pick<LegendModel, 'placement' | 'mode'> {
|
|
|
26
26
|
setLinesGraph: Dispatch<SetStateAction<Array<Line> | null>>;
|
|
27
27
|
shouldDisplayLegendInCompactMode: boolean;
|
|
28
28
|
toggable?: boolean;
|
|
29
|
+
secondaryClick?: (props: {
|
|
30
|
+
element: EventTarget | null;
|
|
31
|
+
metricId: number | string;
|
|
32
|
+
position: [number, number];
|
|
33
|
+
}) => void;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
const MainLegend = ({
|
|
@@ -38,7 +43,8 @@ const MainLegend = ({
|
|
|
38
43
|
shouldDisplayLegendInCompactMode,
|
|
39
44
|
placement,
|
|
40
45
|
height,
|
|
41
|
-
mode
|
|
46
|
+
mode,
|
|
47
|
+
secondaryClick
|
|
42
48
|
}: Props): JSX.Element => {
|
|
43
49
|
const { classes, cx } = useStyles({
|
|
44
50
|
limitLegendRows: Boolean(limitLegend),
|
|
@@ -65,7 +71,24 @@ const MainLegend = ({
|
|
|
65
71
|
value
|
|
66
72
|
}) || 'N/A';
|
|
67
73
|
|
|
68
|
-
const
|
|
74
|
+
const contextMenuClick =
|
|
75
|
+
(metricId: number) =>
|
|
76
|
+
(event: MouseEvent): void => {
|
|
77
|
+
if (!secondaryClick) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
secondaryClick({
|
|
82
|
+
element: event.target,
|
|
83
|
+
metricId,
|
|
84
|
+
position: [event.pageX, event.pageY]
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const selectMetric = ({
|
|
89
|
+
event,
|
|
90
|
+
metric_id
|
|
91
|
+
}: { event: MouseEvent; metric_id: number }): void => {
|
|
69
92
|
if (!toggable) {
|
|
70
93
|
return;
|
|
71
94
|
}
|
|
@@ -127,6 +150,7 @@ const MainLegend = ({
|
|
|
127
150
|
onClick={(event): void => selectMetric({ event, metric_id })}
|
|
128
151
|
onMouseEnter={(): void => highlightLine(metric_id)}
|
|
129
152
|
onMouseLeave={(): void => clearHighlight()}
|
|
153
|
+
onContextMenu={contextMenuClick(metric_id)}
|
|
130
154
|
>
|
|
131
155
|
<LegendHeader
|
|
132
156
|
color={markerColor}
|
|
@@ -175,6 +175,11 @@ export interface LegendModel {
|
|
|
175
175
|
mode: 'grid' | 'list';
|
|
176
176
|
placement: 'bottom' | 'left' | 'right';
|
|
177
177
|
renderExtraComponent?: ReactNode;
|
|
178
|
+
secondaryClick?: (props: {
|
|
179
|
+
element: EventTarget | null;
|
|
180
|
+
metricId: number | string;
|
|
181
|
+
position: [number, number];
|
|
182
|
+
}) => void;
|
|
178
183
|
}
|
|
179
184
|
|
|
180
185
|
export interface GetDate {
|
|
@@ -193,4 +198,4 @@ export interface GraphTooltipData {
|
|
|
193
198
|
unit: string;
|
|
194
199
|
value: number;
|
|
195
200
|
}>;
|
|
196
|
-
}
|
|
201
|
+
}
|
|
@@ -19,7 +19,10 @@ interface Props {
|
|
|
19
19
|
header?: LineChartHeader;
|
|
20
20
|
height: number | null;
|
|
21
21
|
isHorizontal?: boolean;
|
|
22
|
-
legend: Pick<
|
|
22
|
+
legend: Pick<
|
|
23
|
+
LegendModel,
|
|
24
|
+
'renderExtraComponent' | 'placement' | 'mode' | 'secondaryClick'
|
|
25
|
+
> & {
|
|
23
26
|
displayLegend: boolean;
|
|
24
27
|
legendHeight?: number;
|
|
25
28
|
};
|
|
@@ -101,6 +104,7 @@ const BaseChart = ({
|
|
|
101
104
|
shouldDisplayLegendInCompactMode={
|
|
102
105
|
shouldDisplayLegendInCompactMode
|
|
103
106
|
}
|
|
107
|
+
secondaryClick={legend?.secondaryClick}
|
|
104
108
|
/>
|
|
105
109
|
</div>
|
|
106
110
|
)}
|
|
@@ -122,6 +126,7 @@ const BaseChart = ({
|
|
|
122
126
|
renderExtraComponent={legend.renderExtraComponent}
|
|
123
127
|
setLinesGraph={setLines}
|
|
124
128
|
shouldDisplayLegendInCompactMode={shouldDisplayLegendInCompactMode}
|
|
129
|
+
secondaryClick={legend?.secondaryClick}
|
|
125
130
|
/>
|
|
126
131
|
</div>
|
|
127
132
|
)}
|
|
@@ -72,7 +72,7 @@ const toTimeTickValue = (
|
|
|
72
72
|
const getMetricsForIndex = (): Omit<TimeValue, 'timeTick'> => {
|
|
73
73
|
const addMetricForTimeIndex = (acc, { metric_id, data }): TimeValue => ({
|
|
74
74
|
...acc,
|
|
75
|
-
[metric_id]: data[timeIndex]
|
|
75
|
+
[metric_id]: data[timeIndex] === undefined ? null : data[timeIndex]
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
return reduce(addMetricForTimeIndex, {} as TimeValue, metrics);
|
|
@@ -240,7 +240,10 @@ const getStackedMetricValues = ({
|
|
|
240
240
|
timeSeries
|
|
241
241
|
}: LinesTimeSeries): Array<number> => {
|
|
242
242
|
const getTimeSeriesValuesForMetric = (metric_id): Array<number> =>
|
|
243
|
-
map(
|
|
243
|
+
map(
|
|
244
|
+
(timeValue) => getValueForMetric(timeValue)(metric_id) || 0,
|
|
245
|
+
timeSeries
|
|
246
|
+
);
|
|
244
247
|
|
|
245
248
|
const metricsValues = pipe(
|
|
246
249
|
map(prop('metric_id')) as (metric) => Array<number>,
|
|
@@ -593,15 +596,15 @@ const getYScalePerUnit = ({
|
|
|
593
596
|
return scalePerUnit;
|
|
594
597
|
};
|
|
595
598
|
|
|
596
|
-
const formatTime = (value
|
|
597
|
-
return `${numeral(value).format('0.[00]a')}
|
|
599
|
+
const formatTime = ({ value, unit }): string => {
|
|
600
|
+
return `${numeral(value).format('0.[00]a')} ${unit}`;
|
|
598
601
|
};
|
|
599
602
|
|
|
600
603
|
const registerMsUnitToNumeral = (): null => {
|
|
601
604
|
try {
|
|
602
605
|
numeral.register('format', 'milliseconds', {
|
|
603
606
|
format: (value) => {
|
|
604
|
-
return formatTime(value);
|
|
607
|
+
return formatTime({ value, unit: 'ms' });
|
|
605
608
|
},
|
|
606
609
|
regexps: {
|
|
607
610
|
format: /(ms)/,
|
|
@@ -618,6 +621,27 @@ const registerMsUnitToNumeral = (): null => {
|
|
|
618
621
|
|
|
619
622
|
registerMsUnitToNumeral();
|
|
620
623
|
|
|
624
|
+
const registerSecondsUnitToNumeral = (): null => {
|
|
625
|
+
try {
|
|
626
|
+
numeral.register('format', 'seconds', {
|
|
627
|
+
format: (value) => {
|
|
628
|
+
return formatTime({ value, unit: 's' });
|
|
629
|
+
},
|
|
630
|
+
regexps: {
|
|
631
|
+
format: /(s)/,
|
|
632
|
+
unformat: /(s)/
|
|
633
|
+
},
|
|
634
|
+
unformat: () => ''
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
return null;
|
|
638
|
+
} catch (_) {
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
registerSecondsUnitToNumeral();
|
|
644
|
+
|
|
621
645
|
const getBase1024 = ({ unit, base }): boolean => {
|
|
622
646
|
const base2Units = [
|
|
623
647
|
'B',
|
|
@@ -647,6 +671,7 @@ const formatMetricValue = ({
|
|
|
647
671
|
|
|
648
672
|
const formatSuffix = cond([
|
|
649
673
|
[equals('ms'), always(' ms')],
|
|
674
|
+
[equals('s'), always(' s')],
|
|
650
675
|
[T, always(base1024 ? ' ib' : 'a')]
|
|
651
676
|
])(unit);
|
|
652
677
|
|
|
@@ -671,8 +696,6 @@ const formatMetricValueWithUnit = ({
|
|
|
671
696
|
return null;
|
|
672
697
|
}
|
|
673
698
|
|
|
674
|
-
const base1024 = getBase1024({ base, unit });
|
|
675
|
-
|
|
676
699
|
if (isRaw) {
|
|
677
700
|
const unitText = equals('%', unit) ? unit : ` ${unit}`;
|
|
678
701
|
|
|
@@ -685,9 +708,7 @@ const formatMetricValueWithUnit = ({
|
|
|
685
708
|
|
|
686
709
|
const formattedMetricValue = formatMetricValue({ base, unit, value });
|
|
687
710
|
|
|
688
|
-
return
|
|
689
|
-
? formattedMetricValue
|
|
690
|
-
: `${formattedMetricValue} ${unit}`;
|
|
711
|
+
return formattedMetricValue;
|
|
691
712
|
};
|
|
692
713
|
|
|
693
714
|
const bisectDate = bisector(identity).center;
|
|
@@ -766,4 +787,4 @@ export {
|
|
|
766
787
|
formatMetricValueWithUnit,
|
|
767
788
|
getYScaleUnit,
|
|
768
789
|
getYScalePerUnit
|
|
769
|
-
};
|
|
790
|
+
};
|
|
@@ -25,7 +25,7 @@ import { BarStyle } from '../BarChart/models';
|
|
|
25
25
|
import { margin } from '../Chart/common';
|
|
26
26
|
import { LineStyle } from '../Chart/models';
|
|
27
27
|
import { Threshold, Thresholds } from './models';
|
|
28
|
-
import {
|
|
28
|
+
import { formatMetricValueWithUnit } from './timeSeries';
|
|
29
29
|
import { Line, TimeValue } from './timeSeries/models';
|
|
30
30
|
|
|
31
31
|
interface GetColorFromDataAndThresholdsProps {
|
|
@@ -234,7 +234,7 @@ export const getFormattedAxisValues = ({
|
|
|
234
234
|
|
|
235
235
|
const formattedData = metricIds.map((metricId) =>
|
|
236
236
|
timeSeries.map((data) =>
|
|
237
|
-
|
|
237
|
+
formatMetricValueWithUnit({
|
|
238
238
|
value: data[metricId],
|
|
239
239
|
unit: axisUnit,
|
|
240
240
|
base
|
|
@@ -246,7 +246,7 @@ export const getFormattedAxisValues = ({
|
|
|
246
246
|
|
|
247
247
|
const formattedThresholdValues = equals(thresholdUnit, axisUnit)
|
|
248
248
|
? threshold.map(({ value }) =>
|
|
249
|
-
|
|
249
|
+
formatMetricValueWithUnit({
|
|
250
250
|
value,
|
|
251
251
|
unit: axisUnit,
|
|
252
252
|
base
|
|
@@ -273,5 +273,11 @@ export const computeGElementMarginLeft = ({
|
|
|
273
273
|
export const computPixelsToShiftMouse = (xScale): number => {
|
|
274
274
|
const domain = xScale.domain();
|
|
275
275
|
|
|
276
|
-
|
|
276
|
+
const hoursDiffInGraph = dayjs(domain[1]).diff(domain[0], 'h');
|
|
277
|
+
|
|
278
|
+
if (!hoursDiffInGraph) {
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return Math.round(8 / hoursDiffInGraph);
|
|
277
283
|
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"global": {},
|
|
3
|
+
"metrics": [
|
|
4
|
+
{
|
|
5
|
+
"metric": "count",
|
|
6
|
+
"metric_id": 1,
|
|
7
|
+
"legend": "count",
|
|
8
|
+
"displayAs": "bar",
|
|
9
|
+
"ds_data": {
|
|
10
|
+
"ds_stack": true,
|
|
11
|
+
"ds_transparency": 0,
|
|
12
|
+
"ds_filled": false,
|
|
13
|
+
"ds_color_area": "#4269d0",
|
|
14
|
+
"ds_color_line": "#4269d0"
|
|
15
|
+
},
|
|
16
|
+
"datasourceOptions": {
|
|
17
|
+
"field": null,
|
|
18
|
+
"group_by": null,
|
|
19
|
+
"op": "count-doc",
|
|
20
|
+
"query": "severity_text:\"Fatal\" OR severity_text:\"Error\"",
|
|
21
|
+
"period": 3600
|
|
22
|
+
},
|
|
23
|
+
"data": [
|
|
24
|
+
217, 270, 293, 300, 303, 295, 298, 283, 299, 299, 297, 285, 270, 248,
|
|
25
|
+
274, 292, 284, 47
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"metric": "count",
|
|
30
|
+
"metric_id": 2,
|
|
31
|
+
"legend": "count",
|
|
32
|
+
"displayAs": "bar",
|
|
33
|
+
"ds_data": {
|
|
34
|
+
"ds_stack": true,
|
|
35
|
+
"ds_transparency": 0,
|
|
36
|
+
"ds_filled": false,
|
|
37
|
+
"ds_color_area": "#efb118",
|
|
38
|
+
"ds_color_line": "#efb118"
|
|
39
|
+
},
|
|
40
|
+
"datasourceOptions": {
|
|
41
|
+
"field": null,
|
|
42
|
+
"group_by": null,
|
|
43
|
+
"op": "count-doc",
|
|
44
|
+
"query": "",
|
|
45
|
+
"period": 3600
|
|
46
|
+
},
|
|
47
|
+
"data": [
|
|
48
|
+
32, 825, 939, 922, 918, 939, 943, 947, 946, 909, 931, 939, 883, 907,
|
|
49
|
+
928, 904, 923, 893, 139
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"times": [
|
|
54
|
+
"2025-10-04T16:19:30.000Z",
|
|
55
|
+
"2025-10-04T16:20:00.000Z",
|
|
56
|
+
"2025-10-04T16:20:30.000Z",
|
|
57
|
+
"2025-10-04T16:21:00.000Z",
|
|
58
|
+
"2025-10-04T16:21:30.000Z",
|
|
59
|
+
"2025-10-04T16:22:00.000Z",
|
|
60
|
+
"2025-10-04T16:22:30.000Z",
|
|
61
|
+
"2025-10-04T16:23:00.000Z",
|
|
62
|
+
"2025-10-04T16:23:30.000Z",
|
|
63
|
+
"2025-10-04T16:24:00.000Z",
|
|
64
|
+
"2025-10-04T16:24:30.000Z",
|
|
65
|
+
"2025-10-04T16:25:00.000Z",
|
|
66
|
+
"2025-10-04T16:25:30.000Z",
|
|
67
|
+
"2025-10-04T16:26:00.000Z",
|
|
68
|
+
"2025-10-04T16:26:30.000Z",
|
|
69
|
+
"2025-10-04T16:27:00.000Z",
|
|
70
|
+
"2025-10-04T16:27:30.000Z",
|
|
71
|
+
"2025-10-04T16:28:00.000Z",
|
|
72
|
+
"2025-10-04T16:28:30.000Z"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
@@ -96,7 +96,6 @@ const SelectField = ({
|
|
|
96
96
|
fullWidth={fullWidth}
|
|
97
97
|
size="small"
|
|
98
98
|
{...formControlProps}
|
|
99
|
-
|
|
100
99
|
>
|
|
101
100
|
{label && <InputLabel>{label}</InputLabel>}
|
|
102
101
|
<Select
|
|
@@ -151,4 +150,4 @@ const SelectField = ({
|
|
|
151
150
|
);
|
|
152
151
|
};
|
|
153
152
|
|
|
154
|
-
export default SelectField;
|
|
153
|
+
export default SelectField;
|
package/src/Module/index.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { Provider as JotaiProvider, createStore } from 'jotai';
|
|
|
3
3
|
|
|
4
4
|
import { StylesProvider, createGenerateClassName } from '@mui/styles';
|
|
5
5
|
|
|
6
|
+
import { ThemeOptions } from '@mui/material';
|
|
6
7
|
import { QueryProvider, ThemeProvider } from '..';
|
|
7
8
|
import SnackbarProvider from '../Snackbar/SnackbarProvider';
|
|
8
9
|
|
|
@@ -12,6 +13,10 @@ export interface ModuleProps {
|
|
|
12
13
|
queryClient?: QueryClient;
|
|
13
14
|
seedName: string;
|
|
14
15
|
store: ReturnType<typeof createStore>;
|
|
16
|
+
overrideTheme?: {
|
|
17
|
+
light: Partial<ThemeOptions>;
|
|
18
|
+
dark: Partial<ThemeOptions>;
|
|
19
|
+
};
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
const Module = ({
|
|
@@ -19,7 +24,8 @@ const Module = ({
|
|
|
19
24
|
seedName,
|
|
20
25
|
maxSnackbars = 3,
|
|
21
26
|
store,
|
|
22
|
-
queryClient
|
|
27
|
+
queryClient,
|
|
28
|
+
overrideTheme
|
|
23
29
|
}: ModuleProps): JSX.Element => {
|
|
24
30
|
const generateClassName = createGenerateClassName({
|
|
25
31
|
seed: seedName
|
|
@@ -29,7 +35,7 @@ const Module = ({
|
|
|
29
35
|
<QueryProvider queryClient={queryClient}>
|
|
30
36
|
<JotaiProvider store={store}>
|
|
31
37
|
<StylesProvider generateClassName={generateClassName}>
|
|
32
|
-
<ThemeProvider>
|
|
38
|
+
<ThemeProvider overrideTheme={overrideTheme}>
|
|
33
39
|
<SnackbarProvider maxSnackbars={maxSnackbars}>
|
|
34
40
|
{children}
|
|
35
41
|
</SnackbarProvider>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
@layer base {
|
|
2
|
+
::-webkit-scrollbar {
|
|
3
|
+
height: var(--spacing-2);
|
|
4
|
+
width: var(--spacing-2);
|
|
5
|
+
background-color: transparent;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
::-webkit-scrollbar-thumb {
|
|
9
|
+
background-color: var(--color-text-disabled);
|
|
10
|
+
border-radius: var(--spacing-1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
::-webkit-scrollbar-thumb:hover {
|
|
14
|
+
background-color: var(--color-primary-main);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
* {
|
|
18
|
+
scrollbar-color: var(--color-text-disabled) var(--color-background-default);
|
|
19
|
+
scrollbar-width: thin;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
html {
|
|
23
|
+
margin: 0;
|
|
24
|
+
padding: 0;
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 100%;
|
|
27
|
+
text-rendering: optimizeLegibility;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
body {
|
|
31
|
+
background-color: var(--color-background-paper);
|
|
32
|
+
height: 100%;
|
|
33
|
+
padding: 0;
|
|
34
|
+
width: 100%;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#root {
|
|
38
|
+
background-color: var(--color-background-paper) !important;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@variant dark {
|
|
42
|
+
::-webkit-scrollbar-thumb {
|
|
43
|
+
background-color: var(--color-divider);
|
|
44
|
+
}
|
|
45
|
+
* {
|
|
46
|
+
scrollbar-color: var(--color-divider) var(--color-background-default);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { useAtomValue } from 'jotai';
|
|
2
|
-
import { equals } from 'ramda';
|
|
2
|
+
import { equals, mergeDeepRight } from 'ramda';
|
|
3
3
|
import { CSSInterpolation } from 'tss-react';
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
ButtonProps,
|
|
7
7
|
InputBaseProps,
|
|
8
8
|
ThemeProvider as MuiThemeProvider,
|
|
9
|
+
PaletteOptions,
|
|
9
10
|
StyledEngineProvider,
|
|
10
11
|
Theme,
|
|
11
12
|
createTheme
|
|
@@ -147,46 +148,6 @@ export const getTheme = (mode: ThemeMode): ThemeOptions => ({
|
|
|
147
148
|
},
|
|
148
149
|
MuiCssBaseline: {
|
|
149
150
|
styleOverrides: (theme) => `
|
|
150
|
-
::-webkit-scrollbar {
|
|
151
|
-
height: ${theme.spacing(1)};
|
|
152
|
-
width: ${theme.spacing(1)};
|
|
153
|
-
background-color: transparent;
|
|
154
|
-
}
|
|
155
|
-
::-webkit-scrollbar-thumb {
|
|
156
|
-
background-color: ${
|
|
157
|
-
equals(mode, 'dark')
|
|
158
|
-
? theme.palette.divider
|
|
159
|
-
: theme.palette.text.disabled
|
|
160
|
-
};
|
|
161
|
-
border-radius: ${theme.spacing(0.5)};
|
|
162
|
-
}
|
|
163
|
-
::-webkit-scrollbar-thumb:hover {
|
|
164
|
-
background-color: ${theme.palette.primary.main};
|
|
165
|
-
}
|
|
166
|
-
* {
|
|
167
|
-
scrollbar-color: ${
|
|
168
|
-
equals(mode, 'dark')
|
|
169
|
-
? theme.palette.divider
|
|
170
|
-
: theme.palette.text.disabled
|
|
171
|
-
} ${theme.palette.background.default};
|
|
172
|
-
scrollbar-width: thin;
|
|
173
|
-
}
|
|
174
|
-
html {
|
|
175
|
-
margin: 0;
|
|
176
|
-
padding: 0;
|
|
177
|
-
width: 100%;
|
|
178
|
-
height: 100%;
|
|
179
|
-
text-rendering: optimizeLegibility;
|
|
180
|
-
}
|
|
181
|
-
body {
|
|
182
|
-
background-color: ${theme.palette.background.paper};
|
|
183
|
-
height: 100%;
|
|
184
|
-
padding: 0;
|
|
185
|
-
width: 100%;
|
|
186
|
-
}
|
|
187
|
-
#root {
|
|
188
|
-
background-color: ${theme.palette.background.paper};
|
|
189
|
-
}
|
|
190
151
|
@font-face {
|
|
191
152
|
font-family: 'Roboto';
|
|
192
153
|
font-style: normal;
|
|
@@ -211,6 +172,9 @@ export const getTheme = (mode: ThemeMode): ThemeOptions => ({
|
|
|
211
172
|
font-weight: 700;
|
|
212
173
|
src: local('Roboto'), local('Roboto-Bold'), url(${RobotoBoldWoff2}) format('woff2');
|
|
213
174
|
}
|
|
175
|
+
body {
|
|
176
|
+
background-color: ${theme.palette.background.paper};
|
|
177
|
+
}
|
|
214
178
|
`
|
|
215
179
|
},
|
|
216
180
|
MuiInputBase: {
|
|
@@ -294,15 +258,23 @@ export const getTheme = (mode: ThemeMode): ThemeOptions => ({
|
|
|
294
258
|
|
|
295
259
|
interface Props {
|
|
296
260
|
children: ReactNode;
|
|
261
|
+
overrideTheme?: {
|
|
262
|
+
light: Partial<PaletteOptions>;
|
|
263
|
+
dark: Partial<PaletteOptions>;
|
|
264
|
+
};
|
|
297
265
|
}
|
|
298
266
|
|
|
299
|
-
const ThemeProvider = ({ children }: Props): JSX.Element => {
|
|
267
|
+
const ThemeProvider = ({ children, overrideTheme }: Props): JSX.Element => {
|
|
300
268
|
const { themeMode } = useAtomValue(userAtom);
|
|
301
269
|
|
|
302
|
-
const theme = useMemo(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
270
|
+
const theme = useMemo(() => {
|
|
271
|
+
const overrideThemeByMode = overrideTheme?.[themeMode || 'light'];
|
|
272
|
+
return createTheme(
|
|
273
|
+
mergeDeepRight(getTheme(themeMode || ThemeMode.light), {
|
|
274
|
+
palette: overrideThemeByMode || {}
|
|
275
|
+
})
|
|
276
|
+
);
|
|
277
|
+
}, [themeMode, overrideTheme]);
|
|
306
278
|
|
|
307
279
|
return (
|
|
308
280
|
<StyledEngineProvider injectFirst enableCssLayer>
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
|
+
@import "./base.css"; /* Base styles: scrollbar, html, body, #root */
|
|
2
3
|
|
|
3
4
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
4
5
|
|
|
5
6
|
@theme {
|
|
7
|
+
/* Typography */
|
|
6
8
|
--font-body: Roboto, Arial, serif;
|
|
7
9
|
|
|
10
|
+
/* Spacing system */
|
|
8
11
|
--spacing: 4px;
|
|
9
12
|
|
|
10
13
|
--spacing-0: calc(var(--spacing) * 0);
|
|
@@ -19,6 +22,11 @@
|
|
|
19
22
|
--spacing-9: calc(var(--spacing) * 9);
|
|
20
23
|
--spacing-10: calc(var(--spacing) * 10);
|
|
21
24
|
|
|
25
|
+
/* =============
|
|
26
|
+
COLOR SYSTEM
|
|
27
|
+
============= */
|
|
28
|
+
|
|
29
|
+
/* Action colors */
|
|
22
30
|
--color-action-acknowledged: #745f35;
|
|
23
31
|
--color-action-acknowledged-background: #dfd2b9;
|
|
24
32
|
--color-action-in-flapping: #064a3f;
|
|
@@ -35,6 +43,8 @@
|
|
|
35
43
|
--color-action-in-downtime-background: #e5d8f3;
|
|
36
44
|
--color-action-selected: rgba(102, 102, 102, 0.3);
|
|
37
45
|
--color-action-selected-opacity: 0.3;
|
|
46
|
+
|
|
47
|
+
/* Background colors */
|
|
38
48
|
--color-background-default: #f4f4f4;
|
|
39
49
|
--color-background-listing-header: #666666;
|
|
40
50
|
--color-background-panel: #ededed;
|
|
@@ -42,13 +52,19 @@
|
|
|
42
52
|
--color-background-paper: #ffffff;
|
|
43
53
|
--color-background-tooltip: #434e58;
|
|
44
54
|
--color-background-widget: #f8f8f8;
|
|
55
|
+
|
|
56
|
+
/* Chip colors */
|
|
45
57
|
--color-chip-error: #ff6666;
|
|
46
58
|
--color-chip-info: #1588d1;
|
|
47
59
|
--color-chip-neutral: #6da4e4;
|
|
48
60
|
--color-chip-success: #88b922;
|
|
49
61
|
--color-chip-warning: #fd9b27;
|
|
62
|
+
|
|
63
|
+
/* Utility colors */
|
|
50
64
|
--color-divider: #e3e3e3;
|
|
51
65
|
--color-error-main: #ff4a4a;
|
|
66
|
+
|
|
67
|
+
/* Header colors */
|
|
52
68
|
--color-header-page-action-background-active: #1975d10f;
|
|
53
69
|
--color-header-page-action-background-default: #ffffff00;
|
|
54
70
|
--color-header-page-action-color-active: #1976d2;
|
|
@@ -74,13 +90,19 @@
|
|
|
74
90
|
--color-header-menu-item-color-active: #3e85d5;
|
|
75
91
|
--color-header-menu-item-color-default: #1b2127;
|
|
76
92
|
--color-header-menu-item-color-hover: #101418;
|
|
93
|
+
|
|
94
|
+
/* State colors */
|
|
77
95
|
--color-pending-main: #1ebeb3;
|
|
96
|
+
|
|
97
|
+
/* Primary & Secondary colors */
|
|
78
98
|
--color-primary-main: #2e68aa;
|
|
79
99
|
--color-primary-light: #cde7fc;
|
|
80
100
|
--color-primary-dark: #255891;
|
|
81
101
|
--color-secondary-main: #c772d6;
|
|
82
102
|
--color-secondary-light: #e5a5f0;
|
|
83
103
|
--color-secondary-dark: #ac28c1;
|
|
104
|
+
|
|
105
|
+
/* Status colors */
|
|
84
106
|
--color-status-background-error: #ff6666;
|
|
85
107
|
--color-status-background-none: --alpha(#2e68aa / 10%);
|
|
86
108
|
--color-status-background-pending: #1ebeb3;
|
|
@@ -88,12 +110,16 @@
|
|
|
88
110
|
--color-stauts-background-unknown: #e3e3e3;
|
|
89
111
|
--color-status-background-warning: #fd9b27;
|
|
90
112
|
--color-success-main: #88b922;
|
|
113
|
+
|
|
114
|
+
/* Text colors */
|
|
91
115
|
--color-text-disabled: #999999;
|
|
92
116
|
--color-text-primary: #000000;
|
|
93
117
|
--color-text-secondary: #666666;
|
|
94
118
|
--color-warning-main: #fd9b27;
|
|
95
119
|
--color-warning-light: #fcc481;
|
|
96
120
|
--color-warning-dark: #fc7e00;
|
|
121
|
+
|
|
122
|
+
/* Base colors */
|
|
97
123
|
--color-white: #ffffff;
|
|
98
124
|
--color-black: #000000;
|
|
99
125
|
}
|
|
@@ -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 {
|
|
@@ -101,7 +102,8 @@ const useGraphQuery = ({
|
|
|
101
102
|
refreshInterval = false,
|
|
102
103
|
refreshCount,
|
|
103
104
|
bypassQueryParams = false,
|
|
104
|
-
prefix
|
|
105
|
+
prefix,
|
|
106
|
+
isEnabled = true
|
|
105
107
|
}: UseMetricsQueryProps): UseMetricsQueryState => {
|
|
106
108
|
const timePeriodToUse = equals(timePeriod?.timePeriodType, -1)
|
|
107
109
|
? {
|
|
@@ -158,7 +160,10 @@ const useGraphQuery = ({
|
|
|
158
160
|
refreshCount || 0
|
|
159
161
|
],
|
|
160
162
|
queryOptions: {
|
|
161
|
-
enabled:
|
|
163
|
+
enabled:
|
|
164
|
+
areResourcesFullfilled(resources) &&
|
|
165
|
+
!isEmpty(definedMetrics) &&
|
|
166
|
+
isEnabled,
|
|
162
167
|
refetchInterval: refreshInterval,
|
|
163
168
|
suspense: false
|
|
164
169
|
},
|
|
@@ -16,7 +16,11 @@ const ModalHeader = ({
|
|
|
16
16
|
}: ModalHeaderProps & DialogTitleProps): ReactElement => {
|
|
17
17
|
return (
|
|
18
18
|
<div className={modalHeader}>
|
|
19
|
-
<MuiDialogTitle
|
|
19
|
+
<MuiDialogTitle
|
|
20
|
+
className="p-0 font-bold text-2xl"
|
|
21
|
+
color="primary"
|
|
22
|
+
{...rest}
|
|
23
|
+
>
|
|
20
24
|
{children}
|
|
21
25
|
</MuiDialogTitle>
|
|
22
26
|
</div>
|