From 43a8013cb5d2f23e24adc924194df5a8d00465bf Mon Sep 17 00:00:00 2001 From: Matthias Loibl Date: Sun, 19 Feb 2023 18:43:02 +0100 Subject: [PATCH] Use text for relative timestamps and numbers for absolute Using text like `now` and `now-12h` will not spam the browser's history like reported in #590. However, selecting a specific time range with the cursor in the graphs will now still select the very specific timestamps and store them in the URL. --- ui/src/components/graphs/DurationGraph.tsx | 6 +++ ui/src/components/graphs/ErrorBudgetGraph.tsx | 6 +++ ui/src/components/graphs/ErrorsGraph.tsx | 6 +++ ui/src/components/graphs/RequestsGraph.tsx | 6 +++ ui/src/components/graphs/selectTimeRange.tsx | 10 ++++ ui/src/pages/Detail.tsx | 46 ++++++++++++++++--- 6 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 ui/src/components/graphs/selectTimeRange.tsx diff --git a/ui/src/components/graphs/DurationGraph.tsx b/ui/src/components/graphs/DurationGraph.tsx index 0d04ac76..41a4012a 100644 --- a/ui/src/components/graphs/DurationGraph.tsx +++ b/ui/src/components/graphs/DurationGraph.tsx @@ -15,6 +15,7 @@ import { Series, Timeseries, } from '../../proto/objectives/v1alpha1/objectives_pb' +import {selectTimeRange} from './selectTimeRange' interface DurationGraphProps { client: PromiseClient @@ -23,6 +24,7 @@ interface DurationGraphProps { from: number to: number uPlotCursor: uPlot.Cursor + updateTimeRange: (min: number, max: number, absolute: boolean) => void target: number latency: number | undefined } @@ -34,6 +36,7 @@ const DurationGraph = ({ from, to, uPlotCursor, + updateTimeRange, target, latency, }: DurationGraphProps): JSX.Element => { @@ -189,6 +192,9 @@ const DurationGraph = ({ v.map((v: number) => formatDuration(v * 1000)), }, ], + hooks: { + setSelect: [selectTimeRange(updateTimeRange)], + }, }} data={durations} /> diff --git a/ui/src/components/graphs/ErrorBudgetGraph.tsx b/ui/src/components/graphs/ErrorBudgetGraph.tsx index 6f4c0c6d..676de46f 100644 --- a/ui/src/components/graphs/ErrorBudgetGraph.tsx +++ b/ui/src/components/graphs/ErrorBudgetGraph.tsx @@ -12,6 +12,7 @@ import {PromiseClient} from '@bufbuild/connect-web' import {ObjectiveService} from '../../proto/objectives/v1alpha1/objectives_connectweb' import {GraphErrorBudgetResponse} from '../../proto/objectives/v1alpha1/objectives_pb' import {Timestamp} from '@bufbuild/protobuf' +import {selectTimeRange} from './selectTimeRange' interface ErrorBudgetGraphProps { client: PromiseClient @@ -20,6 +21,7 @@ interface ErrorBudgetGraphProps { from: number to: number uPlotCursor: uPlot.Cursor + updateTimeRange: (min: number, max: number, absolute: boolean) => void } const ErrorBudgetGraph = ({ @@ -29,6 +31,7 @@ const ErrorBudgetGraph = ({ from, to, uPlotCursor, + updateTimeRange, }: ErrorBudgetGraphProps): JSX.Element => { const targetRef = useRef() as React.MutableRefObject @@ -200,6 +203,9 @@ const ErrorBudgetGraph = ({ values: (uplot: uPlot, v: number[]) => v.map((v: number) => `${v.toFixed(2)}%`), }, ], + hooks: { + setSelect: [selectTimeRange(updateTimeRange)], + }, }} data={samples} /> diff --git a/ui/src/components/graphs/ErrorsGraph.tsx b/ui/src/components/graphs/ErrorsGraph.tsx index f26f3806..cf4f8361 100644 --- a/ui/src/components/graphs/ErrorsGraph.tsx +++ b/ui/src/components/graphs/ErrorsGraph.tsx @@ -11,6 +11,7 @@ import {PromiseClient} from '@bufbuild/connect-web' import {ObjectiveService} from '../../proto/objectives/v1alpha1/objectives_connectweb' import {Timestamp} from '@bufbuild/protobuf' import {GraphErrorsResponse, Series} from '../../proto/objectives/v1alpha1/objectives_pb' +import {selectTimeRange} from './selectTimeRange' interface ErrorsGraphProps { client: PromiseClient @@ -20,6 +21,7 @@ interface ErrorsGraphProps { from: number to: number uPlotCursor: uPlot.Cursor + updateTimeRange: (min: number, max: number, absolute: boolean) => void } const ErrorsGraph = ({ @@ -30,6 +32,7 @@ const ErrorsGraph = ({ from, to, uPlotCursor, + updateTimeRange, }: ErrorsGraphProps): JSX.Element => { const targetRef = useRef() as React.MutableRefObject @@ -157,6 +160,9 @@ const ErrorsGraph = ({ values: (uplot: uPlot, v: number[]) => v.map((v: number) => `${v}%`), }, ], + hooks: { + setSelect: [selectTimeRange(updateTimeRange)], + }, }} data={errors} /> diff --git a/ui/src/components/graphs/RequestsGraph.tsx b/ui/src/components/graphs/RequestsGraph.tsx index 2ed15a3a..f708f990 100644 --- a/ui/src/components/graphs/RequestsGraph.tsx +++ b/ui/src/components/graphs/RequestsGraph.tsx @@ -11,6 +11,7 @@ import {PromiseClient} from '@bufbuild/connect-web' import {ObjectiveService} from '../../proto/objectives/v1alpha1/objectives_connectweb' import {GraphRateResponse, Series} from '../../proto/objectives/v1alpha1/objectives_pb' import {Timestamp} from '@bufbuild/protobuf' +import {selectTimeRange} from './selectTimeRange' interface RequestsGraphProps { client: PromiseClient @@ -19,6 +20,7 @@ interface RequestsGraphProps { from: number to: number uPlotCursor: uPlot.Cursor + updateTimeRange: (min: number, max: number, absolute: boolean) => void } const RequestsGraph = ({ @@ -28,6 +30,7 @@ const RequestsGraph = ({ from, to, uPlotCursor, + updateTimeRange, }: RequestsGraphProps): JSX.Element => { const targetRef = useRef() as React.MutableRefObject @@ -152,6 +155,9 @@ const RequestsGraph = ({ }, }, }, + hooks: { + setSelect: [selectTimeRange(updateTimeRange)], + }, }} data={requests} /> diff --git a/ui/src/components/graphs/selectTimeRange.tsx b/ui/src/components/graphs/selectTimeRange.tsx new file mode 100644 index 00000000..7f222469 --- /dev/null +++ b/ui/src/components/graphs/selectTimeRange.tsx @@ -0,0 +1,10 @@ +import uPlot from 'uplot' + +export const selectTimeRange = + (updateTimeRange: (min: number, max: number, absolute: boolean) => void) => (u: uPlot) => { + if (u.select.width > 0) { + const min = u.posToVal(u.select.left, 'x') + const max = u.posToVal(u.select.left + u.select.width, 'x') + updateTimeRange(Math.floor(min * 1000), Math.floor(max * 1000), true) + } + } diff --git a/ui/src/pages/Detail.tsx b/ui/src/pages/Detail.tsx index 00f75a4e..3bc49151 100644 --- a/ui/src/pages/Detail.tsx +++ b/ui/src/pages/Detail.tsx @@ -17,6 +17,7 @@ import { hasObjectiveType, latencyTarget, ObjectiveType, + parseDuration, renderLatencyTarget, } from '../App' import Navbar from '../components/Navbar' @@ -70,11 +71,26 @@ const Detail = () => { const name: string = labels[MetricName] + let to: number = Date.now() const toQuery = query.get('to') - const to = toQuery != null ? parseInt(toQuery) : Date.now() + if (toQuery !== null) { + if (!toQuery.includes('now')) { + to = parseInt(toQuery) + } + } + let from: number = to - 60 * 60 * 1000 const fromQuery = query.get('from') - const from = fromQuery != null ? parseInt(fromQuery) : to - 3600 * 1000 + if (fromQuery !== null) { + if (fromQuery.includes('now')) { + const duration = parseDuration(fromQuery.substring(4)) // omit first 4 chars: `now-` + if (duration !== null) { + from = to - duration + } + } else { + from = parseInt(fromQuery) + } + } document.title = `${name} - Pyrra` @@ -159,12 +175,26 @@ const Detail = () => { }, [getObjective, getObjectiveStatus]) const updateTimeRange = useCallback( - (from: number, to: number) => { - navigate(`/objectives?expr=${expr}&grouping=${groupingExpr ?? ''}&from=${from}&to=${to}`) + (from: number, to: number, absolute: boolean) => { + let fromStr = from.toString() + let toStr = to.toString() + if (!absolute) { + fromStr = `now-${formatDuration(to - from)}` + toStr = 'now' + } + navigate( + `/objectives?expr=${expr}&grouping=${groupingExpr ?? ''}&from=${fromStr}&to=${toStr}`, + ) }, [navigate, expr, groupingExpr], ) + const updateTimeRangeSelect = (min: number, max: number, absolute: boolean) => { + // when selecting time ranges with the mouse we want to disable the auto refresh + setAutoReload(false) + updateTimeRange(min, max, absolute) + } + const duration = to - from const interval = intervalFromDuration(duration) @@ -173,7 +203,7 @@ const Detail = () => { const id = setInterval(() => { const newTo = Date.now() const newFrom = newTo - duration - updateTimeRange(newFrom, newTo) + updateTimeRange(newFrom, newTo, false) }, interval) return () => { @@ -185,7 +215,7 @@ const Detail = () => { const handleTimeRangeClick = (t: number) => () => { const to = Date.now() const from = to - t - updateTimeRange(from, to) + updateTimeRange(from, to, false) } if (objectiveError !== '') { @@ -450,6 +480,7 @@ const Detail = () => { from={from} to={to} uPlotCursor={uPlotCursor} + updateTimeRange={updateTimeRangeSelect} /> @@ -465,6 +496,7 @@ const Detail = () => { from={from} to={to} uPlotCursor={uPlotCursor} + updateTimeRange={updateTimeRangeSelect} /> { from={from} to={to} uPlotCursor={uPlotCursor} + updateTimeRange={updateTimeRangeSelect} /> {objectiveType === ObjectiveType.Latency ? ( @@ -490,6 +523,7 @@ const Detail = () => { from={from} to={to} uPlotCursor={uPlotCursor} + updateTimeRange={updateTimeRangeSelect} target={objective.target} latency={latencyTarget(objective)} />