Implement initial time check and fetching of metric names

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2024-03-08 15:15:49 +01:00
parent 33a753c2f8
commit d2f913ac1e
3 changed files with 93 additions and 6 deletions

View file

@ -108,12 +108,14 @@ export class HistoryCompleteStrategy implements CompleteStrategy {
interface ExpressionInputProps { interface ExpressionInputProps {
initialExpr: string; initialExpr: string;
metricNames: string[];
executeQuery: (expr: string) => void; executeQuery: (expr: string) => void;
removePanel: () => void; removePanel: () => void;
} }
const ExpressionInput: FC<ExpressionInputProps> = ({ const ExpressionInput: FC<ExpressionInputProps> = ({
initialExpr, initialExpr,
metricNames,
executeQuery, executeQuery,
removePanel, removePanel,
}) => { }) => {
@ -175,13 +177,13 @@ const ExpressionInput: FC<ExpressionInputProps> = ({
newCompleteStrategy({ newCompleteStrategy({
remote: { remote: {
url: pathPrefix, url: pathPrefix,
//cache: { initialMetricList: metricNames }, cache: { initialMetricList: metricNames },
}, },
}), }),
queryHistory queryHistory
), ),
}); });
}, []); // TODO: Make this depend on external settings changes, maybe use dynamic config compartment again. }, [metricNames, enableAutocomplete, enableLinter, queryHistory]); // TODO: Make this depend on external settings changes, maybe use dynamic config compartment again.
return ( return (
<Group align="flex-start" wrap="nowrap" gap="xs"> <Group align="flex-start" wrap="nowrap" gap="xs">

View file

@ -1,18 +1,101 @@
import { Button, Stack } from "@mantine/core"; import { Alert, Button, Notification, Stack, rem } from "@mantine/core";
import { IconPlus } from "@tabler/icons-react"; import {
IconAlertCircle,
IconAlertTriangle,
IconPlus,
IconX,
} from "@tabler/icons-react";
import { useAppDispatch, useAppSelector } from "../../state/hooks"; import { useAppDispatch, useAppSelector } from "../../state/hooks";
import { addPanel } from "../../state/queryPageSlice"; import { addPanel } from "../../state/queryPageSlice";
import Panel from "./QueryPanel"; import Panel from "./QueryPanel";
import { LabelValuesResult } from "../../api/responseTypes/labelValues";
import { useAPIQuery } from "../../api/api";
import { useEffect, useState } from "react";
import { InstantQueryResult } from "../../api/responseTypes/query";
import { humanizeDuration } from "../../lib/formatTime";
export default function QueryPage() { export default function QueryPage() {
const panels = useAppSelector((state) => state.queryPage.panels); const panels = useAppSelector((state) => state.queryPage.panels);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [timeDelta, setTimeDelta] = useState(0);
const { data: metricNamesResult, error: metricNamesError } =
useAPIQuery<LabelValuesResult>({
path: "/label/__name__/values",
});
const { data: timeResult, error: timeError } =
useAPIQuery<InstantQueryResult>({
path: "/query",
params: {
query: "time()",
},
});
useEffect(() => {
if (timeResult) {
if (timeResult.data.resultType !== "scalar") {
throw new Error("Unexpected result type from time query");
}
const browserTime = new Date().getTime() / 1000;
const serverTime = timeResult.data.result[0];
console.log(browserTime, serverTime, Math.abs(browserTime - serverTime));
setTimeDelta(Math.abs(browserTime - serverTime));
}
}, [timeResult]);
return ( return (
<> <>
<Stack gap="sm">
{metricNamesError && (
<Alert
icon={
<IconAlertTriangle style={{ width: rem(14), height: rem(14) }} />
}
color="red"
title="Error fetching metrics list"
withCloseButton
>
Unable to fetch list of metric names: {metricNamesError.message}
</Alert>
)}
{timeError && (
<Alert
icon={
<IconAlertTriangle style={{ width: rem(14), height: rem(14) }} />
}
color="red"
title="Error fetching server time"
withCloseButton
>
{timeError.message}
</Alert>
)}
{timeDelta > 30 && (
<Alert
title="Server time is out of sync"
color="red"
icon={
<IconAlertCircle style={{ width: rem(14), height: rem(14) }} />
}
onClose={() => setTimeDelta(0)}
>
Detected a time difference of{" "}
<strong>{humanizeDuration(timeDelta * 1000)}</strong> between your
browser and the server. You may see unexpected time-shifted query
results due to the time drift.
</Alert>
)}
</Stack>
<Stack gap="xl"> <Stack gap="xl">
{panels.map((p, idx) => ( {panels.map((p, idx) => (
<Panel key={p.id} idx={idx} /> <Panel
key={p.id}
idx={idx}
metricNames={metricNamesResult?.data || []}
/>
))} ))}
</Stack> </Stack>

View file

@ -30,12 +30,13 @@ import ExpressionInput from "./ExpressionInput";
export interface PanelProps { export interface PanelProps {
idx: number; idx: number;
metricNames: string[];
} }
// TODO: This is duplicated everywhere, unify it. // TODO: This is duplicated everywhere, unify it.
const iconStyle = { width: "0.9rem", height: "0.9rem" }; const iconStyle = { width: "0.9rem", height: "0.9rem" };
const QueryPanel: FC<PanelProps> = ({ idx }) => { const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
// Used to indicate to the selected display component that it should retrigger // Used to indicate to the selected display component that it should retrigger
// the query, even if the expression has not changed (e.g. when the user presses // the query, even if the expression has not changed (e.g. when the user presses
// the "Execute" button or hits <Enter> again). // the "Execute" button or hits <Enter> again).
@ -48,6 +49,7 @@ const QueryPanel: FC<PanelProps> = ({ idx }) => {
<Stack gap="lg" mt="sm"> <Stack gap="lg" mt="sm">
<ExpressionInput <ExpressionInput
initialExpr={panel.expr} initialExpr={panel.expr}
metricNames={metricNames}
executeQuery={(expr: string) => { executeQuery={(expr: string) => {
setRetriggerIdx((idx) => idx + 1); setRetriggerIdx((idx) => idx + 1);
dispatch(setExpr({ idx, expr })); dispatch(setExpr({ idx, expr }));