2024-07-21 09:26:11 -07:00
|
|
|
import { FC, useEffect, useId, useLayoutEffect, useState } from "react";
|
2024-07-17 12:09:30 -07:00
|
|
|
import { Alert, Skeleton, Box, LoadingOverlay } from "@mantine/core";
|
2024-03-08 08:44:21 -08:00
|
|
|
import { IconAlertTriangle, IconInfoCircle } from "@tabler/icons-react";
|
2024-07-21 09:26:11 -07:00
|
|
|
import {
|
|
|
|
InstantQueryResult,
|
|
|
|
RangeSamples,
|
|
|
|
} from "../../api/responseTypes/query";
|
2024-03-08 08:44:21 -08:00
|
|
|
import { useAPIQuery } from "../../api/api";
|
2024-07-19 03:13:14 -07:00
|
|
|
import classes from "./Graph.module.css";
|
2024-03-08 08:44:21 -08:00
|
|
|
import { GraphDisplayMode } from "../../state/queryPageSlice";
|
2024-07-17 12:09:30 -07:00
|
|
|
import { formatSeries } from "../../lib/formatSeries";
|
2024-07-21 09:26:11 -07:00
|
|
|
import uPlot, { Series } from "uplot";
|
2024-07-19 03:13:14 -07:00
|
|
|
import UplotReact from "uplot-react";
|
|
|
|
import "uplot/dist/uPlot.min.css";
|
|
|
|
import "./uplot.css";
|
|
|
|
import { useElementSize } from "@mantine/hooks";
|
2024-07-21 09:26:11 -07:00
|
|
|
import { formatTimestamp } from "../../lib/formatTime";
|
|
|
|
import { computePosition, shift, flip, offset, Axis } from "@floating-ui/dom";
|
|
|
|
import { colorPool } from "./ColorPool";
|
|
|
|
import UPlotChart, { UPlotChartProps, UPlotChartRange } from "./UPlotChart";
|
2024-03-08 08:44:21 -08:00
|
|
|
|
|
|
|
export interface GraphProps {
|
|
|
|
expr: string;
|
|
|
|
endTime: number | null;
|
|
|
|
range: number;
|
|
|
|
resolution: number | null;
|
|
|
|
showExemplars: boolean;
|
|
|
|
displayMode: GraphDisplayMode;
|
|
|
|
retriggerIdx: number;
|
2024-07-21 09:26:11 -07:00
|
|
|
onSelectRange: (start: number, end: number) => void;
|
2024-03-08 08:44:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
const Graph: FC<GraphProps> = ({
|
|
|
|
expr,
|
|
|
|
endTime,
|
|
|
|
range,
|
|
|
|
resolution,
|
2024-07-21 09:26:11 -07:00
|
|
|
showExemplars,
|
2024-07-17 12:09:30 -07:00
|
|
|
displayMode,
|
2024-03-08 08:44:21 -08:00
|
|
|
retriggerIdx,
|
2024-07-21 09:26:11 -07:00
|
|
|
onSelectRange,
|
2024-03-08 08:44:21 -08:00
|
|
|
}) => {
|
2024-07-21 09:26:11 -07:00
|
|
|
const { ref, width } = useElementSize();
|
|
|
|
const [rerender, setRerender] = useState(true);
|
|
|
|
|
|
|
|
const effectiveEndTime = (endTime !== null ? endTime : Date.now()) / 1000;
|
|
|
|
const startTime = effectiveEndTime - range / 1000;
|
|
|
|
const effectiveResolution =
|
|
|
|
resolution || Math.max(Math.floor(range / 250000), 1);
|
2024-07-19 03:13:14 -07:00
|
|
|
|
2024-03-08 08:44:21 -08:00
|
|
|
const { data, error, isFetching, isLoading, refetch } =
|
|
|
|
useAPIQuery<InstantQueryResult>({
|
|
|
|
key: useId(),
|
|
|
|
path: "/query_range",
|
|
|
|
params: {
|
|
|
|
query: expr,
|
2024-07-21 09:26:11 -07:00
|
|
|
step: effectiveResolution.toString(),
|
|
|
|
start: startTime.toString(),
|
|
|
|
end: effectiveEndTime.toString(),
|
2024-03-08 08:44:21 -08:00
|
|
|
},
|
|
|
|
enabled: expr !== "",
|
|
|
|
});
|
|
|
|
|
2024-07-21 09:26:11 -07:00
|
|
|
// Keep the displayed chart range separate from the actual query range, so that
|
|
|
|
// the chart will keep displaying the old range while a new query for a different range
|
|
|
|
// is still in progress.
|
|
|
|
const [displayedChartRange, setDisplayedChartRange] =
|
|
|
|
useState<UPlotChartRange>({
|
|
|
|
startTime: startTime,
|
|
|
|
endTime: effectiveEndTime,
|
|
|
|
resolution: effectiveResolution,
|
|
|
|
});
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
setDisplayedChartRange({
|
|
|
|
startTime: startTime,
|
|
|
|
endTime: effectiveEndTime,
|
|
|
|
resolution: effectiveResolution,
|
|
|
|
});
|
|
|
|
}, [data]);
|
|
|
|
|
2024-03-08 08:44:21 -08:00
|
|
|
useEffect(() => {
|
|
|
|
expr !== "" && refetch();
|
|
|
|
}, [retriggerIdx, refetch, expr, endTime, range, resolution]);
|
|
|
|
|
2024-07-21 09:26:11 -07:00
|
|
|
useEffect(() => {
|
|
|
|
if (data !== undefined && rerender) {
|
|
|
|
setRerender(false);
|
|
|
|
}
|
|
|
|
}, [data, rerender, setRerender]);
|
|
|
|
|
2024-03-08 08:44:21 -08:00
|
|
|
// TODO: Share all the loading/error/empty data notices with the DataTable.
|
|
|
|
|
|
|
|
// Show a skeleton only on the first load, not on subsequent ones.
|
|
|
|
if (isLoading) {
|
|
|
|
return (
|
|
|
|
<Box>
|
|
|
|
{Array.from(Array(5), (_, i) => (
|
|
|
|
<Skeleton key={i} height={30} mb={15} />
|
|
|
|
))}
|
|
|
|
</Box>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
return (
|
|
|
|
<Alert
|
|
|
|
color="red"
|
|
|
|
title="Error executing query"
|
|
|
|
icon={<IconAlertTriangle size={14} />}
|
|
|
|
>
|
|
|
|
{error.message}
|
|
|
|
</Alert>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data === undefined) {
|
|
|
|
return <Alert variant="transparent">No data queried yet</Alert>;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { result, resultType } = data.data;
|
|
|
|
|
2024-07-17 12:09:30 -07:00
|
|
|
if (resultType !== "matrix") {
|
|
|
|
return (
|
|
|
|
<Alert
|
|
|
|
title="Invalid query result"
|
|
|
|
icon={<IconAlertTriangle size={14} />}
|
|
|
|
>
|
|
|
|
This query returned a result of type "{resultType}", but a matrix was
|
|
|
|
expected.
|
|
|
|
</Alert>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-03-08 08:44:21 -08:00
|
|
|
if (result.length === 0) {
|
|
|
|
return (
|
|
|
|
<Alert title="Empty query result" icon={<IconInfoCircle size={14} />}>
|
|
|
|
This query returned no data.
|
|
|
|
</Alert>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2024-07-19 03:13:14 -07:00
|
|
|
<Box pos="relative" ref={ref} className={classes.chartWrapper}>
|
2024-03-08 08:44:21 -08:00
|
|
|
<LoadingOverlay
|
|
|
|
visible={isFetching}
|
|
|
|
zIndex={1000}
|
2024-07-21 09:26:11 -07:00
|
|
|
h={570}
|
|
|
|
overlayProps={{ radius: "sm", blur: 0.5 }}
|
|
|
|
loaderProps={{ type: "dots", color: "gray" }}
|
|
|
|
// loaderProps={{
|
|
|
|
// children: <Skeleton m={0} w="100%" h="100%" />,
|
|
|
|
// }}
|
|
|
|
// styles={{ loader: { width: "100%", height: "100%" } }}
|
2024-03-08 08:44:21 -08:00
|
|
|
/>
|
2024-07-21 09:26:11 -07:00
|
|
|
<UPlotChart
|
|
|
|
data={data.data.result}
|
|
|
|
range={displayedChartRange}
|
|
|
|
width={width}
|
|
|
|
showExemplars={showExemplars}
|
|
|
|
displayMode={displayMode}
|
|
|
|
onSelectRange={onSelectRange}
|
2024-07-17 12:09:30 -07:00
|
|
|
/>
|
2024-03-08 08:44:21 -08:00
|
|
|
</Box>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default Graph;
|