2024-03-07 04:16:54 -08:00
|
|
|
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";
|
2024-03-07 07:58:51 -08:00
|
|
|
import classes from "./DataTable.module.css";
|
2024-04-03 05:45:35 -07:00
|
|
|
import dayjs from "dayjs";
|
|
|
|
import timezone from "dayjs/plugin/timezone";
|
|
|
|
import { useAppSelector } from "../../state/hooks";
|
|
|
|
import { formatTimestamp } from "../../lib/formatTime";
|
|
|
|
dayjs.extend(timezone);
|
2024-03-07 04:16:54 -08:00
|
|
|
|
|
|
|
const maxFormattableSeries = 1000;
|
|
|
|
const maxDisplayableSeries = 10000;
|
|
|
|
|
|
|
|
const limitSeries = <S extends InstantSample | RangeSamples>(
|
|
|
|
series: S[]
|
|
|
|
): S[] => {
|
|
|
|
if (series.length > maxDisplayableSeries) {
|
|
|
|
return series.slice(0, maxDisplayableSeries);
|
|
|
|
}
|
|
|
|
return series;
|
|
|
|
};
|
|
|
|
|
2024-03-08 08:43:38 -08:00
|
|
|
export interface DataTableProps {
|
2024-03-07 04:16:54 -08:00
|
|
|
expr: string;
|
|
|
|
evalTime: number | null;
|
|
|
|
retriggerIdx: number;
|
|
|
|
}
|
|
|
|
|
2024-03-08 08:43:38 -08:00
|
|
|
const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
2024-03-07 04:16:54 -08:00
|
|
|
const { data, error, isFetching, isLoading, refetch } =
|
|
|
|
useAPIQuery<InstantQueryResult>({
|
|
|
|
key: useId(),
|
|
|
|
path: "/query",
|
|
|
|
params: {
|
|
|
|
query: expr,
|
|
|
|
time: `${(evalTime !== null ? evalTime : Date.now()) / 1000}`,
|
|
|
|
},
|
|
|
|
enabled: expr !== "",
|
|
|
|
});
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
expr !== "" && refetch();
|
|
|
|
}, [retriggerIdx, refetch, expr, evalTime]);
|
|
|
|
|
2024-04-03 05:45:35 -07:00
|
|
|
const useLocalTime = useAppSelector((state) => state.settings.useLocalTime);
|
|
|
|
|
2024-03-07 04:16:54 -08:00
|
|
|
// 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;
|
|
|
|
|
|
|
|
if (result.length === 0) {
|
|
|
|
return (
|
2024-03-08 08:43:38 -08:00
|
|
|
<Alert title="Empty query result" icon={<IconInfoCircle size={14} />}>
|
2024-03-07 04:16:54 -08:00
|
|
|
This query returned no data.
|
|
|
|
</Alert>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const doFormat = result.length <= maxFormattableSeries;
|
|
|
|
|
|
|
|
return (
|
2024-03-07 07:58:51 -08:00
|
|
|
<Box pos="relative" className={classes.tableWrapper}>
|
2024-03-07 04:16:54 -08:00
|
|
|
<LoadingOverlay
|
|
|
|
visible={isFetching}
|
|
|
|
zIndex={1000}
|
|
|
|
overlayProps={{ radius: "sm", blur: 1 }}
|
|
|
|
loaderProps={{
|
|
|
|
children: <Skeleton m={0} w="100%" h="100%" />,
|
|
|
|
}}
|
|
|
|
styles={{ loader: { width: "100%", height: "100%" } }}
|
|
|
|
/>
|
2024-03-07 08:44:45 -08:00
|
|
|
<Table highlightOnHover fz="xs">
|
2024-03-07 04:16:54 -08:00
|
|
|
<Table.Tbody>
|
|
|
|
{resultType === "vector" ? (
|
|
|
|
limitSeries<InstantSample>(result).map((s, idx) => (
|
|
|
|
<Table.Tr key={idx}>
|
|
|
|
<Table.Td>
|
|
|
|
<SeriesName labels={s.metric} format={doFormat} />
|
|
|
|
</Table.Td>
|
2024-03-07 07:58:51 -08:00
|
|
|
<Table.Td className={classes.numberCell}>
|
2024-03-07 04:16:54 -08:00
|
|
|
{s.value && s.value[1]}
|
|
|
|
{s.histogram && "TODO HISTOGRAM DISPLAY"}
|
|
|
|
</Table.Td>
|
|
|
|
</Table.Tr>
|
|
|
|
))
|
|
|
|
) : resultType === "matrix" ? (
|
|
|
|
limitSeries<RangeSamples>(result).map((s, idx) => (
|
|
|
|
<Table.Tr key={idx}>
|
|
|
|
<Table.Td>
|
|
|
|
<SeriesName labels={s.metric} format={doFormat} />
|
|
|
|
</Table.Td>
|
2024-03-07 07:58:51 -08:00
|
|
|
<Table.Td className={classes.numberCell}>
|
2024-03-07 04:16:54 -08:00
|
|
|
{s.values &&
|
|
|
|
s.values.map((v, idx) => (
|
|
|
|
<div key={idx}>
|
2024-04-03 05:45:35 -07:00
|
|
|
{v[1]} @{" "}
|
|
|
|
{
|
|
|
|
<span title={formatTimestamp(v[0], useLocalTime)}>
|
|
|
|
{v[0]}
|
|
|
|
</span>
|
|
|
|
}
|
2024-03-07 04:16:54 -08:00
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</Table.Td>
|
|
|
|
</Table.Tr>
|
|
|
|
))
|
|
|
|
) : resultType === "scalar" ? (
|
|
|
|
<Table.Tr>
|
|
|
|
<Table.Td>Scalar value</Table.Td>
|
2024-03-07 07:58:51 -08:00
|
|
|
<Table.Td className={classes.numberCell}>{result[1]}</Table.Td>
|
2024-03-07 04:16:54 -08:00
|
|
|
</Table.Tr>
|
|
|
|
) : resultType === "string" ? (
|
|
|
|
<Table.Tr>
|
|
|
|
<Table.Td>String value</Table.Td>
|
|
|
|
<Table.Td>{result[1]}</Table.Td>
|
|
|
|
</Table.Tr>
|
|
|
|
) : (
|
|
|
|
<Alert
|
|
|
|
color="red"
|
|
|
|
title="Invalid query response"
|
|
|
|
icon={<IconAlertTriangle size={14} />}
|
|
|
|
>
|
|
|
|
Invalid result value type
|
|
|
|
</Alert>
|
|
|
|
)}
|
|
|
|
</Table.Tbody>
|
|
|
|
</Table>
|
|
|
|
</Box>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default DataTable;
|