Shaheer Kochai a98c8db949
feat: add drag support to alert history horizontal graph (#5928)
* feat: add drag support to alert history horizontal graph

* chore: use startTimestamp and endTimestamp

* fix: fix the issue of alert history breaking on navigating back from selected time range
2024-10-03 11:09:18 +04:30

223 lines
5.2 KiB
TypeScript

import { Color } from '@signozhq/design-tokens';
import Uplot from 'components/Uplot';
import { QueryParams } from 'constants/query';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import heatmapPlugin from 'lib/uPlotLib/plugins/heatmapPlugin';
import timelinePlugin from 'lib/uPlotLib/plugins/timelinePlugin';
import { useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { UpdateTimeInterval } from 'store/actions';
import { AlertRuleTimelineGraphResponse } from 'types/api/alerts/def';
import uPlot, { AlignedData } from 'uplot';
import { ALERT_STATUS, TIMELINE_OPTIONS } from './constants';
type Props = { type: string; data: AlertRuleTimelineGraphResponse[] };
function HorizontalTimelineGraph({
width,
isDarkMode,
data,
}: {
width: number;
isDarkMode: boolean;
data: AlertRuleTimelineGraphResponse[];
}): JSX.Element {
const transformedData: AlignedData = useMemo(() => {
if (!data?.length) {
return [[], []];
}
// add an entry for the end time of the last entry to make sure the graph displays all the data
const timestamps = [
...data.map((item) => item.start / 1000),
data[data.length - 1].end / 1000, // end value of last entry
];
const states = [
...data.map((item) => ALERT_STATUS[item.state]),
ALERT_STATUS[data[data.length - 1].state], // Same state as the last entry
];
return [timestamps, states];
}, [data]);
const urlQuery = useUrlQuery();
const dispatch = useDispatch();
const options: uPlot.Options = useMemo(
() => ({
width,
height: 85,
axes: [
{
gap: 10,
stroke: isDarkMode ? Color.BG_VANILLA_400 : Color.BG_INK_400,
},
{ show: false },
],
legend: {
show: false,
},
padding: [null, 0, null, 0],
series: [
{
label: 'Time',
},
{
label: 'States',
},
],
hooks: {
setSelect: [
(self): void => {
const selection = self.select;
if (selection) {
const startTime = self.posToVal(selection.left, 'x');
const endTime = self.posToVal(selection.left + selection.width, 'x');
const diff = endTime - startTime;
if (diff > 0) {
if (urlQuery.has(QueryParams.relativeTime)) {
urlQuery.delete(QueryParams.relativeTime);
}
const startTimestamp = Math.floor(startTime * 1000);
const endTimestamp = Math.floor(endTime * 1000);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
history.push({
search: urlQuery.toString(),
});
}
}
},
],
},
plugins:
transformedData?.length > 1
? [
timelinePlugin({
count: transformedData.length - 1,
...TIMELINE_OPTIONS,
}),
]
: [],
}),
[width, isDarkMode, transformedData.length, urlQuery, dispatch],
);
return <Uplot data={transformedData} options={options} />;
}
const transformVerticalTimelineGraph = (data: any[]): any => [
data.map((item: { timestamp: any }) => item.timestamp),
Array(data.length).fill(0),
Array(data.length).fill(10),
Array(data.length).fill([0, 1, 2, 3, 4, 5]),
data.map((item: { value: number }) => {
const count = Math.floor(item.value / 10);
return [...Array(count).fill(1), 2];
}),
];
const datatest: any[] = [];
const now = Math.floor(Date.now() / 1000); // current timestamp in seconds
const oneDay = 24 * 60 * 60; // one day in seconds
for (let i = 0; i < 90; i++) {
const timestamp = now - i * oneDay;
const startOfDay = timestamp - (timestamp % oneDay);
datatest.push({
timestamp: startOfDay,
value: Math.floor(Math.random() * 30) + 1,
});
}
function VerticalTimelineGraph({
isDarkMode,
width,
}: {
width: number;
isDarkMode: boolean;
}): JSX.Element {
const transformedData = useMemo(
() => transformVerticalTimelineGraph(datatest),
[],
);
const options: uPlot.Options = useMemo(
() => ({
width,
height: 90,
plugins: [heatmapPlugin()],
cursor: { show: false },
legend: {
show: false,
},
axes: [
{
gap: 10,
stroke: isDarkMode ? Color.BG_VANILLA_400 : Color.BG_INK_400,
},
{ show: false },
],
series: [
{},
{
paths: (): null => null,
points: { show: false },
},
{
paths: (): null => null,
points: { show: false },
},
],
}),
[isDarkMode, width],
);
return <Uplot data={transformedData} options={options} />;
}
function Graph({ type, data }: Props): JSX.Element | null {
const graphRef = useRef<HTMLDivElement>(null);
const isDarkMode = useIsDarkMode();
const containerDimensions = useResizeObserver(graphRef);
if (type === 'horizontal') {
return (
<div ref={graphRef}>
<HorizontalTimelineGraph
isDarkMode={isDarkMode}
width={containerDimensions.width}
data={data}
/>
</div>
);
}
return (
<div ref={graphRef}>
<VerticalTimelineGraph
isDarkMode={isDarkMode}
width={containerDimensions.width}
/>
</div>
);
}
export default Graph;