import { FC, useEffect, useId, useLayoutEffect, useState } from "react"; import { Alert, Skeleton, Box, LoadingOverlay } from "@mantine/core"; import { IconAlertTriangle, IconInfoCircle } from "@tabler/icons-react"; import { InstantQueryResult, RangeSamples, } from "../../api/responseTypes/query"; import { useAPIQuery } from "../../api/api"; import classes from "./Graph.module.css"; import { GraphDisplayMode } from "../../state/queryPageSlice"; import { formatSeries } from "../../lib/formatSeries"; import uPlot, { Series } from "uplot"; import UplotReact from "uplot-react"; import "uplot/dist/uPlot.min.css"; import "./uplot.css"; import { useElementSize } from "@mantine/hooks"; 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"; export interface GraphProps { expr: string; endTime: number | null; range: number; resolution: number | null; showExemplars: boolean; displayMode: GraphDisplayMode; retriggerIdx: number; onSelectRange: (start: number, end: number) => void; } const Graph: FC = ({ expr, endTime, range, resolution, showExemplars, displayMode, retriggerIdx, onSelectRange, }) => { 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); const { data, error, isFetching, isLoading, refetch } = useAPIQuery({ key: useId(), path: "/query_range", params: { query: expr, step: effectiveResolution.toString(), start: startTime.toString(), end: effectiveEndTime.toString(), }, enabled: expr !== "", }); // 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({ startTime: startTime, endTime: effectiveEndTime, resolution: effectiveResolution, }); useEffect(() => { setDisplayedChartRange({ startTime: startTime, endTime: effectiveEndTime, resolution: effectiveResolution, }); }, [data]); useEffect(() => { expr !== "" && refetch(); }, [retriggerIdx, refetch, expr, endTime, range, resolution]); useEffect(() => { if (data !== undefined && rerender) { setRerender(false); } }, [data, rerender, setRerender]); // 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 ( {Array.from(Array(5), (_, i) => ( ))} ); } if (error) { return ( } > {error.message} ); } if (data === undefined) { return No data queried yet; } const { result, resultType } = data.data; if (resultType !== "matrix") { return ( } > This query returned a result of type "{resultType}", but a matrix was expected. ); } if (result.length === 0) { return ( }> This query returned no data. ); } return ( , // }} // styles={{ loader: { width: "100%", height: "100%" } }} /> ); }; export default Graph;