diff --git a/web/ui/mantine-ui/src/pages/query/Graph.tsx b/web/ui/mantine-ui/src/pages/query/Graph.tsx new file mode 100644 index 0000000000..caca6de15c --- /dev/null +++ b/web/ui/mantine-ui/src/pages/query/Graph.tsx @@ -0,0 +1,172 @@ +import { FC, useEffect, useId } from "react"; +import { Table, Alert, Skeleton, Box, LoadingOverlay } from "@mantine/core"; +import { IconAlertTriangle, IconInfoCircle } from "@tabler/icons-react"; +import { + InstantQueryResult, + InstantSample, + RangeSamples, +} from "../../api/responseTypes/query"; +import SeriesName from "./SeriesName"; +import { useAPIQuery } from "../../api/api"; +import classes from "./DataTable.module.css"; +import { GraphDisplayMode } from "../../state/queryPageSlice"; + +const maxFormattableSeries = 1000; +const maxDisplayableSeries = 10000; + +const limitSeries = ( + series: S[] +): S[] => { + if (series.length > maxDisplayableSeries) { + return series.slice(0, maxDisplayableSeries); + } + return series; +}; + +export interface GraphProps { + expr: string; + endTime: number | null; + range: number; + resolution: number | null; + showExemplars: boolean; + displayMode: GraphDisplayMode; + retriggerIdx: number; +} + +const Graph: FC = ({ + expr, + endTime, + range, + resolution, + showExemplars, + displayMode, + retriggerIdx, +}) => { + const realEndTime = (endTime !== null ? endTime : Date.now()) / 1000; + const { data, error, isFetching, isLoading, refetch } = + useAPIQuery({ + key: useId(), + path: "/query_range", + params: { + query: expr, + step: ( + resolution || Math.max(Math.floor(range / 250000), 1) + ).toString(), + start: (realEndTime - range / 1000).toString(), + end: realEndTime.toString(), + }, + enabled: expr !== "", + }); + + useEffect(() => { + expr !== "" && refetch(); + }, [retriggerIdx, refetch, expr, endTime, range, resolution]); + + // 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 (result.length === 0) { + return ( + }> + This query returned no data. + + ); + } + + const doFormat = result.length <= maxFormattableSeries; + + return ( + + , + }} + styles={{ loader: { width: "100%", height: "100%" } }} + /> + + + {resultType === "vector" ? ( + limitSeries(result).map((s, idx) => ( + + + + + + {s.value && s.value[1]} + {s.histogram && "TODO HISTOGRAM DISPLAY"} + + + )) + ) : resultType === "matrix" ? ( + limitSeries(result).map((s, idx) => ( + + + + + + {s.values && + s.values.map((v, idx) => ( +
+ {v[1]} @ {v[0]} +
+ ))} +
+
+ )) + ) : resultType === "scalar" ? ( + + Scalar value + {result[1]} + + ) : resultType === "string" ? ( + + String value + {result[1]} + + ) : ( + } + > + Invalid result value type + + )} +
+
+
+ ); +}; + +export default Graph; diff --git a/web/ui/mantine-ui/src/pages/query/QueryPanel.tsx b/web/ui/mantine-ui/src/pages/query/QueryPanel.tsx index dc873a4b7d..67467648ac 100644 --- a/web/ui/mantine-ui/src/pages/query/QueryPanel.tsx +++ b/web/ui/mantine-ui/src/pages/query/QueryPanel.tsx @@ -27,6 +27,7 @@ import DataTable from "./DataTable"; import TimeInput from "./TimeInput"; import RangeInput from "./RangeInput"; import ExpressionInput from "./ExpressionInput"; +import Graph from "./Graph"; export interface PanelProps { idx: number; @@ -107,7 +108,6 @@ const QueryPanel: FC = ({ idx, metricNames }) => { ) } /> - = ({ idx, metricNames }) => { /> */} -
- GRAPH PLACEHOLDER -
+ {/* Link button to remove this panel. */}