mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-17 08:36:55 +03:00
app/vmui: add alert rule links to top queries table
When vmalert is enabled, fetch all alert rules on load and match their queries against entries in the Top Queries table. If a query matches an alert rule, display an "alert?" link in the time range column that navigates directly to the first matching alert rule. Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9493 Supersedes https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10787
This commit is contained in:
@@ -10,7 +10,7 @@ import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
|
||||
export interface TopQueryColumn {
|
||||
title?: string;
|
||||
tooltip?: string;
|
||||
tooltip?: ReactNode;
|
||||
key: keyof TopQuery;
|
||||
sortBy?: keyof TopQuery;
|
||||
format?: (row: TopQuery) => ReactNode;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useEffect, useMemo, useState } from "preact/compat";
|
||||
import { useAppState } from "../../../state/common/StateContext";
|
||||
import { Group } from "../../../types";
|
||||
|
||||
const getAllRulesUrl = (server: string): string =>
|
||||
`${server}/vmalert/api/v1/rules?datasource_type=prometheus&group_limit=1000`;
|
||||
|
||||
type AlertRuleRef = { group_id: string; rule_id: string };
|
||||
|
||||
export const useFetchAlertQueries = (): Map<string, AlertRuleRef> => {
|
||||
const { serverUrl, appConfig } = useAppState();
|
||||
const [alertQueries, setAlertQueries] = useState<Map<string, AlertRuleRef>>(new Map());
|
||||
|
||||
const isEnabled = appConfig?.vmalert?.enabled ?? false;
|
||||
|
||||
const fetchUrl = useMemo(
|
||||
() => (isEnabled ? getAllRulesUrl(serverUrl) : null),
|
||||
[serverUrl, isEnabled],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!fetchUrl) return;
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await fetch(fetchUrl);
|
||||
if (!response.ok) return;
|
||||
const resp = await response.json();
|
||||
const groups = (resp?.data?.groups || []) as Group[];
|
||||
const queries = new Map<string, AlertRuleRef>();
|
||||
for (const group of groups) {
|
||||
for (const rule of group.rules) {
|
||||
if (rule.query && !queries.has(rule.query)) {
|
||||
queries.set(rule.query, { group_id: group.id, rule_id: rule.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
setAlertQueries(queries);
|
||||
} catch (e) {
|
||||
// silently ignore fetch errors
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, [fetchUrl]);
|
||||
|
||||
return alertQueries;
|
||||
};
|
||||
@@ -1,13 +1,18 @@
|
||||
import { useMemo } from "react";
|
||||
import { useMemo, Fragment } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { TopQueryColumn } from "../TopQueryPanel/TopQueryPanel";
|
||||
import { humanizeSeconds } from "../../../utils/time";
|
||||
import { formatBytes } from "../../../utils/bytes";
|
||||
import router from "../../../router";
|
||||
|
||||
type AlertRuleRef = { group_id: string; rule_id: string };
|
||||
|
||||
type UseTopQueriesColumns = {
|
||||
maxLifetime: string;
|
||||
alertQueries?: Map<string, AlertRuleRef>;
|
||||
};
|
||||
|
||||
export const useTopQueriesColumns = ({ maxLifetime }: UseTopQueriesColumns) => {
|
||||
export const useTopQueriesColumns = ({ maxLifetime, alertQueries }: UseTopQueriesColumns) => {
|
||||
return useMemo(() => {
|
||||
const queryCol: TopQueryColumn = {
|
||||
key: "query"
|
||||
@@ -17,7 +22,29 @@ export const useTopQueriesColumns = ({ maxLifetime }: UseTopQueriesColumns) => {
|
||||
key: "timeRange",
|
||||
sortBy: "timeRangeSeconds",
|
||||
title: "range",
|
||||
tooltip: "The time range between start and end of the query request. 'instant' means the query was executed at a single point in time without a time range"
|
||||
tooltip: (
|
||||
<Fragment>
|
||||
The time range between start and end of the query request.<br/>
|
||||
<br/>
|
||||
'instant' means the query was executed at a single point in time without a time range.<br/>
|
||||
<br/>
|
||||
'alert?' means the query matches an alert rule — the link goes to the first matching alert.
|
||||
</Fragment>
|
||||
),
|
||||
format: (row) => {
|
||||
const ref = alertQueries?.get(row.query);
|
||||
if (ref) {
|
||||
return (
|
||||
<Link
|
||||
to={`${router.rules}?group_id=${ref.group_id}&rule_id=${ref.rule_id}`}
|
||||
className="vm-link vm-link_colored"
|
||||
>
|
||||
alert?
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return row.timeRange;
|
||||
},
|
||||
};
|
||||
|
||||
const countCol: TopQueryColumn = {
|
||||
@@ -73,5 +100,5 @@ export const useTopQueriesColumns = ({ maxLifetime }: UseTopQueriesColumns) => {
|
||||
topByCount,
|
||||
topByAvgMemoryUsage,
|
||||
};
|
||||
}, [maxLifetime]);
|
||||
}, [maxLifetime, alertQueries]);
|
||||
};
|
||||
@@ -16,6 +16,7 @@ import useDeviceDetect from "../../hooks/useDeviceDetect";
|
||||
import classNames from "classnames";
|
||||
import useStateSearchParams from "../../hooks/useStateSearchParams";
|
||||
import { useTopQueriesColumns } from "./hooks/useTopQueriesColumns";
|
||||
import { useFetchAlertQueries } from "./hooks/useFetchAlertQueries";
|
||||
|
||||
const exampleDuration = "30ms, 15s, 3d4h, 1y2w";
|
||||
|
||||
@@ -24,7 +25,8 @@ const TopQueries: FC = () => {
|
||||
|
||||
const [topN, setTopN] = useStateSearchParams(10, "topN");
|
||||
const [maxLifetime, setMaxLifetime] = useStateSearchParams("10m", "maxLifetime");
|
||||
const columns = useTopQueriesColumns({ maxLifetime });
|
||||
const alertQueries = useFetchAlertQueries();
|
||||
const columns = useTopQueriesColumns({ maxLifetime, alertQueries });
|
||||
|
||||
const { data, error, loading, fetch } = useFetchTopQueries({ topN, maxLifetime });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user