vmui: add support relative time (#2504)

* feat: add support relative time

* app/vmselect: `make vmui-update`

* docs/CHANGELOG.md: document the change

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Yury Molodov 2022-04-26 15:46:06 +03:00 committed by GitHub
parent 6be07e8c25
commit 9b4bff67e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 92 additions and 56 deletions

View File

@ -1,7 +1,7 @@
{
"files": {
"main.css": "./static/css/main.d8362c27.css",
"main.js": "./static/js/main.1754e6b5.js",
"main.js": "./static/js/main.3e17cf70.js",
"static/js/362.1a2113d4.chunk.js": "./static/js/362.1a2113d4.chunk.js",
"static/js/27.939f971b.chunk.js": "./static/js/27.939f971b.chunk.js",
"static/media/README.md": "./static/media/README.5e5724daf3ee333540a3.md",
@ -9,6 +9,6 @@
},
"entrypoints": [
"static/css/main.d8362c27.css",
"static/js/main.1754e6b5.js"
"static/js/main.3e17cf70.js"
]
}

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script defer="defer" src="./static/js/main.1754e6b5.js"></script><link href="./static/css/main.d8362c27.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script defer="defer" src="./static/js/main.3e17cf70.js"></script><link href="./static/css/main.d8362c27.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,46 +1,20 @@
import React, {FC} from "preact/compat";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import dayjs from "dayjs";
import {relativeTimeOptions} from "../../../../utils/time";
interface TimeDurationSelector {
setDuration: (str: string, from: Date) => void;
setDuration: ({duration, until, id}: {duration: string, until: Date, id: string}) => void;
}
interface DurationOption {
duration: string,
title?: string,
from?: () => Date,
}
const durationOptions: DurationOption[] = [
{duration: "5m", title: "Last 5 minutes"},
{duration: "15m", title: "Last 15 minutes"},
{duration: "30m", title: "Last 30 minutes"},
{duration: "1h", title: "Last 1 hour"},
{duration: "3h", title: "Last 3 hours"},
{duration: "6h", title: "Last 6 hours"},
{duration: "12h", title: "Last 12 hours"},
{duration: "24h", title: "Last 24 hours"},
{duration: "2d", title: "Last 2 days"},
{duration: "7d", title: "Last 7 days"},
{duration: "30d", title: "Last 30 days"},
{duration: "90d", title: "Last 90 days"},
{duration: "180d", title: "Last 180 days"},
{duration: "1y", title: "Last 1 year"},
{duration: "1d", from: () => dayjs().subtract(1, "day").endOf("day").toDate(), title: "Yesterday"},
{duration: "1d", from: () => dayjs().endOf("day").toDate(), title: "Today"},
];
const TimeDurationSelector: FC<TimeDurationSelector> = ({setDuration}) => {
// setDurationString("5m"))
return <List style={{maxHeight: "168px", overflow: "auto", paddingRight: "15px"}}>
{durationOptions.map(d =>
<ListItem key={d.duration} button onClick={() => setDuration(d.duration, d.from ? d.from() : new Date())}>
<ListItemText primary={d.title || d.duration}/>
</ListItem>)}
{relativeTimeOptions.map(({id, duration, until, title}) =>
<ListItemButton key={id} onClick={() => setDuration({duration, until: until(), id})}>
<ListItemText primary={title || duration}/>
</ListItemButton>)}
</List>;
};

View File

@ -41,7 +41,7 @@ export const TimeSelector: FC = () => {
const [until, setUntil] = useState<string>();
const [from, setFrom] = useState<string>();
const {time: {period: {end, start}}} = useAppState();
const {time: {period: {end, start}, relativeTime}} = useAppState();
const dispatch = useAppDispatch();
useEffect(() => {
@ -52,10 +52,9 @@ export const TimeSelector: FC = () => {
setFrom(formatDateForNativeInput(dateFromSeconds(start)));
}, [start]);
const setDuration = (dur: string, from: Date) => {
dispatch({type: "SET_UNTIL", payload: from});
const setDuration = ({duration, until, id}: {duration: string, until: Date, id: string}) => {
dispatch({type: "SET_RELATIVE_TIME", payload: {duration, until, id}});
setAnchorEl(null);
dispatch({type: "SET_DURATION", payload: dur});
};
const formatRange = useMemo(() => {
@ -80,7 +79,9 @@ export const TimeSelector: FC = () => {
}}
startIcon={<QueryBuilderIcon/>}
onClick={(e) => setAnchorEl(e.currentTarget)}>
{formatRange.start} - {formatRange.end}
{relativeTime
? relativeTime.replace(/_/g, " ")
: `${formatRange.start} - ${formatRange.end}`}
</Button>
</Tooltip>
<Popper

View File

@ -7,7 +7,8 @@ import {
getDateNowUTC,
getDurationFromPeriod,
getTimeperiodForDuration,
getDurationFromMilliseconds
getDurationFromMilliseconds,
getRelativeTime
} from "../../utils/time";
import {getFromStorage} from "../../utils/storage";
import {getDefaultServer} from "../../utils/default-server-url";
@ -17,11 +18,12 @@ import dayjs from "dayjs";
export interface TimeState {
duration: string;
period: TimeParams;
relativeTime?: string;
}
export interface QueryHistory {
index: number,
values: string[]
index: number;
values: string[];
}
export interface AppState {
@ -44,6 +46,7 @@ export type Action =
| { type: "SET_QUERY_HISTORY_BY_INDEX", payload: {value: QueryHistory, queryNumber: number} }
| { type: "SET_QUERY_HISTORY", payload: QueryHistory[] }
| { type: "SET_DURATION", payload: string }
| { type: "SET_RELATIVE_TIME", payload: {id: string, duration: string, until: Date} }
| { type: "SET_UNTIL", payload: Date }
| { type: "SET_FROM", payload: Date }
| { type: "SET_PERIOD", payload: TimePeriod }
@ -53,8 +56,9 @@ export type Action =
| { type: "TOGGLE_AUTOCOMPLETE"}
| { type: "NO_CACHE"}
const duration = getQueryStringValue("g0.range_input", "1h") as string;
const endInput = formatDateToLocal(getQueryStringValue("g0.end_input", getDateNowUTC()) as Date);
const {relativeDuration, relativeUntil, relativeTimeId} = getRelativeTime();
const duration = relativeDuration || getQueryStringValue("g0.range_input", "1h") as string;
const endInput = relativeUntil || formatDateToLocal(getQueryStringValue("g0.end_input", getDateNowUTC()) as Date);
const query = getQueryArray();
export const initialState: AppState = {
@ -64,7 +68,8 @@ export const initialState: AppState = {
queryHistory: query.map(q => ({index: 0, values: [q]})),
time: {
duration,
period: getTimeperiodForDuration(duration, new Date(endInput))
period: getTimeperiodForDuration(duration, new Date(endInput)),
relativeTime: relativeTimeId,
},
queryControls: {
autoRefresh: false,
@ -107,7 +112,17 @@ export function reducer(state: AppState, action: Action): AppState {
time: {
...state.time,
duration: action.payload,
period: getTimeperiodForDuration(action.payload, dateFromSeconds(state.time.period.end))
period: getTimeperiodForDuration(action.payload, dateFromSeconds(state.time.period.end)),
relativeTime: ""
}
};
case "SET_RELATIVE_TIME":
return {
...state,
time: {
...state.time,
period: getTimeperiodForDuration(action.payload.duration, new Date(action.payload.until)),
relativeTime: action.payload.id,
}
};
case "SET_UNTIL":
@ -115,7 +130,8 @@ export function reducer(state: AppState, action: Action): AppState {
...state,
time: {
...state.time,
period: getTimeperiodForDuration(state.time.duration, action.payload)
period: getTimeperiodForDuration(state.time.duration, action.payload),
relativeTime: ""
}
};
case "SET_FROM":
@ -130,7 +146,8 @@ export function reducer(state: AppState, action: Action): AppState {
time: {
...state.time,
duration: durationFrom,
period: getTimeperiodForDuration(durationFrom, dayjs(state.time.period.end*1000).toDate())
period: getTimeperiodForDuration(durationFrom, dayjs(state.time.period.end*1000).toDate()),
relativeTime: ""
}
};
case "SET_PERIOD":
@ -145,7 +162,8 @@ export function reducer(state: AppState, action: Action): AppState {
time: {
...state.time,
duration,
period: getTimeperiodForDuration(duration, action.payload.to)
period: getTimeperiodForDuration(duration, action.payload.to),
relativeTime: ""
}
};
case "TOGGLE_AUTOREFRESH":

View File

@ -54,3 +54,10 @@ export interface DashboardSettings {
filename: string;
rows: DashboardRow[];
}
export interface RelativeTimeOption {
id: string,
duration: string,
until: () => Date,
title: string,
}

View File

@ -5,6 +5,7 @@ const stateToUrlParams = {
"time.duration": "range_input",
"time.period.date": "end_input",
"time.period.step": "step_input",
"time.relativeTime": "relative_time",
"displayType": "tab"
};

View File

@ -1,7 +1,8 @@
import {TimeParams, TimePeriod} from "../types";
import {RelativeTimeOption, TimeParams, TimePeriod} from "../types";
import dayjs, {UnitTypeShort} from "dayjs";
import duration from "dayjs/plugin/duration";
import utc from "dayjs/plugin/utc";
import {getQueryStringValue} from "./query-string";
dayjs.extend(duration);
dayjs.extend(utc);
@ -105,5 +106,38 @@ export const checkDurationLimit = (dur: string): string => {
return dur;
};
export const dateFromSeconds = (epochTimeInSeconds: number): Date =>
new Date(epochTimeInSeconds * 1000);
export const dateFromSeconds = (epochTimeInSeconds: number): Date => new Date(epochTimeInSeconds * 1000);
export const relativeTimeOptions: RelativeTimeOption[] = [
{title: "Last 5 minutes", duration: "5m"},
{title: "Last 15 minutes", duration: "15m"},
{title: "Last 30 minutes", duration: "30m"},
{title: "Last 1 hour", duration: "1h"},
{title: "Last 3 hours", duration: "3h"},
{title: "Last 6 hours", duration: "6h"},
{title: "Last 12 hours", duration: "12h"},
{title: "Last 24 hours", duration: "24h"},
{title: "Last 2 days", duration: "2d"},
{title: "Last 7 days", duration: "7d"},
{title: "Last 30 days", duration: "30d"},
{title: "Last 90 days", duration: "90d"},
{title: "Last 180 days", duration: "180d"},
{title: "Last 1 year", duration: "1y"},
{title: "Yesterday", duration: "1d", until: () => dayjs().subtract(1, "day").endOf("day").toDate()},
{title: "Today", duration: "1d", until: () => dayjs().endOf("day").toDate()},
].map(o => ({
id: o.title.replace(/\s/g, "_").toLocaleLowerCase(),
until: o.until ? o.until : () => dayjs().toDate(),
...o
}));
export const getRelativeTime = (relativeTimeId?: string) => {
const id = relativeTimeId || getQueryStringValue("g0.relative_time", "") as string;
const target = relativeTimeOptions.find(d => d.id === id);
if (!target) return {};
return {
relativeTimeId: id,
relativeDuration: target.duration,
relativeUntil: target.until()
};
};

View File

@ -25,6 +25,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
* FEATURE: allow specifying TLS cipher suites for incoming https requests via `-tlsCipherSuites` command-line flag. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2404).
* FEATURE: allow specifying TLS cipher suites for mTLS connections between cluster components via `-cluster.tlsCipherSuites` command-line flag. See [these docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#mtls-protection).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): shown an empty graph on the selected time range when there is no data on it. Previously `No data to show` placeholder was shown instead of the graph in this case. This prevented from zooming and scrolling of such a graph.
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): show the selected `last N minutes/hours/days` in the top right corner. Previously the `start - end` duration was shown instead, which could be hard to interpret. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2402).
* FEATURE: expose `vm_indexdb_items_added_total` and `vm_indexdb_items_added_size_bytes_total` counters at `/metrics` page, which can be used for monitoring the rate for addition of new entries in `indexdb` (aka `inverted index`) alongside the total size in bytes for the added entries. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2471).
* FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): add `drop_common_labels()` function, which drops common `label="name"` pairs from the passed time series. See [these docs](https://docs.victoriametrics.com/MetricsQL.html#drop_common_labels).