Alerting: Limit instances on alert detail view unless in instances tab (#89368)

This commit is contained in:
Gilles De Mey 2024-06-25 13:19:43 +02:00 committed by GitHub
parent b59ebf85bc
commit 87b8da1719
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 47 additions and 14 deletions

View File

@ -8,7 +8,7 @@ import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
import { AlertRuleProvider } from './components/rule-viewer/RuleContext';
import DetailView from './components/rule-viewer/RuleViewer';
import DetailView, { ActiveTab, useActiveTab } from './components/rule-viewer/RuleViewer';
import { useCombinedRule } from './hooks/useCombinedRule';
import { stringifyErrorLike } from './utils/misc';
import { getRuleIdFromPathname, parse as parseRuleId } from './utils/rule-id';
@ -21,6 +21,13 @@ type RuleViewerProps = GrafanaRouteComponentProps<{
const RuleViewer = (props: RuleViewerProps): JSX.Element => {
const id = getRuleIdFromPathname(props.match.params);
const [activeTab] = useActiveTab();
const instancesTab = activeTab === ActiveTab.Instances;
// We will fetch no instances by default to speed up loading times and reduce memory footprint _unless_ we are visiting
// the "instances" tab. This optimization is only available for the Grafana-managed ruler.
const limitAlerts = instancesTab ? undefined : 0; // "0" means "do not include alert rule instances in the response"
// we convert the stringified ID to a rule identifier object which contains additional
// type and source information
const identifier = React.useMemo(() => {
@ -32,7 +39,7 @@ const RuleViewer = (props: RuleViewerProps): JSX.Element => {
}, [id]);
// we then fetch the rule from the correct API endpoint(s)
const { loading, error, result: rule } = useCombinedRule({ ruleIdentifier: identifier });
const { loading, error, result: rule } = useCombinedRule({ ruleIdentifier: identifier, limitAlerts });
if (error) {
return (

View File

@ -44,6 +44,13 @@ export interface Datasource {
export const PREVIEW_URL = '/api/v1/rule/test/grafana';
export const PROM_RULES_URL = 'api/prometheus/grafana/api/v1/rules';
export enum PrometheusAPIFilters {
RuleGroup = 'rule_group',
Namespace = 'file',
FolderUID = 'folder_uid',
LimitAlerts = 'limit_alerts',
}
export interface Data {
refId: string;
relativeTimeRange: RelativeTimeRange;
@ -137,12 +144,12 @@ export const alertRuleApi = alertingApi.injectEndpoints({
// if we're fetching for Grafana managed rules, we should add a limit to the number of alert instances
// we do this because the response is large otherwise and we don't show all of them in the UI anyway.
if (limitAlerts) {
searchParams.set('limit_alerts', String(limitAlerts));
searchParams.set(PrometheusAPIFilters.LimitAlerts, String(limitAlerts));
}
if (identifier && (isPrometheusRuleIdentifier(identifier) || isCloudRuleIdentifier(identifier))) {
searchParams.set('file', identifier.namespace);
searchParams.set('rule_group', identifier.groupName);
searchParams.set(PrometheusAPIFilters.Namespace, identifier.namespace);
searchParams.set(PrometheusAPIFilters.RuleGroup, identifier.groupName);
}
const params = prepareRulesFilterQueryParams(searchParams, filter);
@ -163,9 +170,10 @@ export const alertRuleApi = alertingApi.injectEndpoints({
ruleName?: string;
dashboardUid?: string;
panelId?: number;
limitAlerts?: number;
}
>({
query: ({ ruleSourceName, namespace, groupName, ruleName, dashboardUid, panelId }) => {
query: ({ ruleSourceName, namespace, groupName, ruleName, dashboardUid, panelId, limitAlerts }) => {
const queryParams: Record<string, string | undefined> = {
rule_group: groupName,
rule_name: ruleName,
@ -175,12 +183,16 @@ export const alertRuleApi = alertingApi.injectEndpoints({
if (namespace) {
if (isGrafanaRulesSource(ruleSourceName)) {
set(queryParams, 'folder_uid', namespace);
set(queryParams, PrometheusAPIFilters.FolderUID, namespace);
} else {
set(queryParams, 'file', namespace);
set(queryParams, PrometheusAPIFilters.Namespace, namespace);
}
}
if (limitAlerts !== undefined) {
set(queryParams, PrometheusAPIFilters.LimitAlerts, String(limitAlerts));
}
return {
url: `api/prometheus/${getDatasourceAPIUid(ruleSourceName)}/api/v1/rules`,
params: queryParams,

View File

@ -1,5 +1,5 @@
import { css } from '@emotion/css';
import { isEmpty, truncate } from 'lodash';
import { chain, isEmpty, truncate } from 'lodash';
import React, { useState } from 'react';
import { NavModelItem, UrlQueryValue } from '@grafana/data';
@ -8,7 +8,7 @@ import { PageInfoItem } from 'app/core/components/Page/types';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import InfoPausedRule from 'app/features/alerting/unified/components/InfoPausedRule';
import { RuleActionsButtons } from 'app/features/alerting/unified/components/rules/RuleActionsButtons';
import { CombinedRule, RuleHealth, RuleIdentifier } from 'app/types/unified-alerting';
import { AlertInstanceTotalState, CombinedRule, RuleHealth, RuleIdentifier } from 'app/types/unified-alerting';
import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto';
import { defaultPageNav } from '../../RuleViewer';
@ -42,7 +42,7 @@ import { InstancesList } from './tabs/Instances';
import { QueryResults } from './tabs/Query';
import { Routing } from './tabs/Routing';
enum ActiveTab {
export enum ActiveTab {
Query = 'query',
Instances = 'instances',
History = 'history',
@ -251,7 +251,7 @@ export const Title = ({ name, paused = false, state, health, ruleType, ruleOrigi
export const isErrorHealth = (health?: RuleHealth) => health === 'error' || health === 'err';
function useActiveTab(): [ActiveTab, (tab: ActiveTab) => void] {
export function useActiveTab(): [ActiveTab, (tab: ActiveTab) => void] {
const [queryParams, setQueryParams] = useQueryParams();
const tabFromQuery = queryParams['tab'];
@ -277,7 +277,7 @@ function usePageNav(rule: CombinedRule) {
const summary = annotations[Annotation.summary];
const isAlertType = isAlertingRule(promRule);
const numberOfInstance = isAlertType ? (promRule.alerts ?? []).length : undefined;
const numberOfInstance = isAlertType ? calculateTotalInstances(rule.instanceTotals) : undefined;
const namespaceName = decodeGrafanaNamespace(rule.namespace).name;
const groupName = rule.group.name;
@ -343,6 +343,14 @@ function usePageNav(rule: CombinedRule) {
};
}
const calculateTotalInstances = (stats: CombinedRule['instanceTotals']) => {
return chain(stats)
.pick([AlertInstanceTotalState.Alerting, AlertInstanceTotalState.Pending, AlertInstanceTotalState.Normal])
.values()
.sum()
.value();
};
const getStyles = () => ({
title: css({
display: 'flex',

View File

@ -171,10 +171,15 @@ interface RequestState<T> {
error?: unknown;
}
interface Props {
ruleIdentifier: RuleIdentifier;
limitAlerts?: number;
}
// Many places still use the old way of fetching code so synchronizing cache expiration is difficult
// Hence, this hook fetches a fresh version of a rule most of the time
// Due to enabled filtering for Prometheus and Ruler rules it shouldn't be a problem
export function useCombinedRule({ ruleIdentifier }: { ruleIdentifier: RuleIdentifier }): RequestState<CombinedRule> {
export function useCombinedRule({ ruleIdentifier, limitAlerts }: Props): RequestState<CombinedRule> {
const { ruleSourceName } = ruleIdentifier;
const ruleSource = getRulesSourceFromIdentifier(ruleIdentifier);
@ -195,6 +200,7 @@ export function useCombinedRule({ ruleIdentifier }: { ruleIdentifier: RuleIdenti
namespace: ruleLocation?.namespace,
groupName: ruleLocation?.group,
ruleName: ruleLocation?.ruleName,
limitAlerts,
},
{
skip: !ruleLocation || isLoadingRuleLocation,