Skip to content

Commit

Permalink
A lot of work around uPlot and resolution picking
Browse files Browse the repository at this point in the history
Signed-off-by: Julius Volz <[email protected]>
  • Loading branch information
juliusv committed Jul 21, 2024
1 parent 1c91b82 commit 8ef66c4
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 108 deletions.
2 changes: 2 additions & 0 deletions web/ui/mantine-ui/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ module.exports = {
'warn',
{ allowConstantExport: true },
],
'no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }],
'@typescript-eslint/no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }],
},
}
10 changes: 4 additions & 6 deletions web/ui/mantine-ui/src/pages/query/ExpressionInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
PromQLExtension,
newCompleteStrategy,
} from "@prometheus-io/codemirror-promql";
import { FC, useEffect, useState } from "react";
import { FC, useEffect, useMemo, useState } from "react";
import CodeMirror, {
EditorState,
EditorView,
Expand Down Expand Up @@ -107,10 +107,6 @@ export class HistoryCompleteStrategy implements CompleteStrategy {
}
}

// This is just a placeholder until query history is implemented, so disable the linter warning.
// eslint-disable-next-line react-hooks/exhaustive-deps
const queryHistory = [] as string[];

interface ExpressionInputProps {
initialExpr: string;
metricNames: string[];
Expand Down Expand Up @@ -163,13 +159,15 @@ const ExpressionInput: FC<ExpressionInputProps> = ({
if (formatResult) {
setExpr(formatResult.data);
notifications.show({
color: "green",
title: "Expression formatted",
message: "Expression formatted successfully!",
});
}
}, [formatResult, formatError]);

// This is just a placeholder until query history is implemented, so disable the linter warning.
const queryHistory = useMemo<string[]>(() => [], []);

// (Re)initialize editor based on settings / setting changes.
useEffect(() => {
// Build the dynamic part of the config.
Expand Down
30 changes: 13 additions & 17 deletions web/ui/mantine-ui/src/pages/query/Graph.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
import { FC, useEffect, useId, useLayoutEffect, useState } from "react";
import { FC, useEffect, useId, 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 { InstantQueryResult } 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 {
GraphDisplayMode,
GraphResolution,
getEffectiveResolution,
} from "../../state/queryPageSlice";
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";
import UPlotChart, { UPlotChartRange } from "./UPlotChart";

export interface GraphProps {
expr: string;
endTime: number | null;
range: number;
resolution: number | null;
resolution: GraphResolution;
showExemplars: boolean;
displayMode: GraphDisplayMode;
retriggerIdx: number;
Expand All @@ -45,8 +40,7 @@ const Graph: FC<GraphProps> = ({

const effectiveEndTime = (endTime !== null ? endTime : Date.now()) / 1000;
const startTime = effectiveEndTime - range / 1000;
const effectiveResolution =
resolution || Math.max(Math.floor(range / 250000), 1);
const effectiveResolution = getEffectiveResolution(resolution, range) / 1000;

const { data, error, isFetching, isLoading, refetch } =
useAPIQuery<InstantQueryResult>({
Expand All @@ -62,7 +56,7 @@ const Graph: FC<GraphProps> = ({
});

// 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
// the chart will keep displaying the old range while a query for a new range
// is still in progress.
const [displayedChartRange, setDisplayedChartRange] =
useState<UPlotChartRange>({
Expand All @@ -77,6 +71,8 @@ const Graph: FC<GraphProps> = ({
endTime: effectiveEndTime,
resolution: effectiveResolution,
});
// We actually want to update the displayed range only once the new data is there.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);

useEffect(() => {
Expand Down
137 changes: 111 additions & 26 deletions web/ui/mantine-ui/src/pages/query/QueryPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import {
Center,
Space,
Box,
Input,
SegmentedControl,
Stack,
Select,
TextInput,
} from "@mantine/core";
import {
IconChartAreaFilled,
Expand All @@ -20,6 +20,8 @@ import { FC, useState } from "react";
import { useAppDispatch, useAppSelector } from "../../state/hooks";
import {
GraphDisplayMode,
GraphResolution,
getEffectiveResolution,
removePanel,
setExpr,
setVisualizer,
Expand All @@ -29,6 +31,10 @@ import TimeInput from "./TimeInput";
import RangeInput from "./RangeInput";
import ExpressionInput from "./ExpressionInput";
import Graph from "./Graph";
import {
formatPrometheusDuration,
parsePrometheusDuration,
} from "../../lib/formatTime";

export interface PanelProps {
idx: number;
Expand All @@ -45,8 +51,40 @@ const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
const [retriggerIdx, setRetriggerIdx] = useState<number>(0);

const panel = useAppSelector((state) => state.queryPage.panels[idx]);
const resolution = panel.visualizer.resolution;
const dispatch = useAppDispatch();

const [customResolutionInput, setCustomResolutionInput] = useState<string>(
formatPrometheusDuration(
getEffectiveResolution(resolution, panel.visualizer.range)
)
);

const setResolution = (res: GraphResolution) => {
dispatch(
setVisualizer({
idx,
visualizer: {
...panel.visualizer,
resolution: res,
},
})
);
};

const onChangeCustomResolutionInput = (resText: string): void => {
const newResolution = parsePrometheusDuration(resText);
if (newResolution === null) {
setCustomResolutionInput(
formatPrometheusDuration(
getEffectiveResolution(resolution, panel.visualizer.range)
)
);
} else {
setResolution({ type: "custom", value: newResolution });
}
};

return (
<Stack gap="lg">
<ExpressionInput
Expand Down Expand Up @@ -124,52 +162,99 @@ const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
/>

<Select
title="Resolution"
placeholder="Resolution"
maxDropdownHeight={500}
data={[
{
group: "Automatic resolution",
items: [
{ label: "Low", value: "low" },
{ label: "Medium", value: "medium" },
{ label: "High", value: "high" },
{ label: "Low res.", value: "low" },
{ label: "Medium res.", value: "medium" },
{ label: "High res.", value: "high" },
],
},
{
group: "Fixed resolution",
items: [
{ label: "10s", value: "10" },
{ label: "30s", value: "30" },
{ label: "1m", value: "60" },
{ label: "5m", value: "300" },
{ label: "15m", value: "900" },
{ label: "1h", value: "3600" },
{ label: "10s", value: "10000" },
{ label: "30s", value: "30000" },
{ label: "1m", value: "60000" },
{ label: "5m", value: "300000" },
{ label: "15m", value: "900000" },
{ label: "1h", value: "3600000" },
],
},
{
group: "Custom resolution",
items: [{ label: "Enter value", value: "custom" }],
items: [{ label: "Enter value...", value: "custom" }],
},
]}
w={160}
// value={value ? value.value : null}
value={
resolution.type === "auto"
? resolution.density
: resolution.type === "fixed"
? resolution.value.toString()
: "custom"
}
onChange={(_value, option) => {
dispatch(
setVisualizer({
idx,
visualizer: {
...panel.visualizer,
resolution: option
? option.value
? parseInt(option.value)
: null
: null,
},
})
);
if (["low", "medium", "high"].includes(option.value)) {
setResolution({
type: "auto",
density: option.value as "low" | "medium" | "high",
});
return;
}

if (option.value === "custom") {
// Start the custom resolution at the current effective resolution.
const effectiveResolution = getEffectiveResolution(
resolution,
panel.visualizer.range
);
setResolution({
type: "custom",
value: effectiveResolution,
});
setCustomResolutionInput(
formatPrometheusDuration(effectiveResolution)
);
return;
}

const value = parseInt(option.value);
if (!isNaN(value)) {
setResolution({
type: "fixed",
value,
});
} else {
throw new Error("Invalid resolution value");
}
}}
clearable
/>

{resolution.type === "custom" && (
<TextInput
placeholder="Resolution"
value={customResolutionInput}
onChange={(event) =>
setCustomResolutionInput(event.currentTarget.value)
}
onBlur={() =>
onChangeCustomResolutionInput(customResolutionInput)
}
onKeyDown={(event) =>
event.key === "Enter" &&
onChangeCustomResolutionInput(customResolutionInput)
}
aria-label="Range"
style={{
width: `calc(44px + ${customResolutionInput.length + 3}ch)`,
}}
/>
)}
</Group>

<SegmentedControl
Expand Down
1 change: 1 addition & 0 deletions web/ui/mantine-ui/src/pages/query/RangeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const RangeInput: FC<RangeInputProps> = ({ range, onChangeRange }) => {
return (
<Group gap={5}>
<Input
title="Range"
value={rangeInput}
onChange={(event) => setRangeInput(event.currentTarget.value)}
onBlur={() => onChangeRangeInput(rangeInput)}
Expand Down
1 change: 1 addition & 0 deletions web/ui/mantine-ui/src/pages/query/TimeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const TimeInput: FC<TimeInputProps> = ({
<Group gap={5}>
<DatesProvider settings={{ timezone: useLocalTime ? undefined : "UTC" }}>
<DateTimePicker
title="End time"
w={230}
valueFormat="YYYY-MM-DD HH:mm:ss"
withSeconds
Expand Down
Loading

0 comments on commit 8ef66c4

Please sign in to comment.