The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx

159 lines
5.0 KiB

Alerting: Enhance Ruler and Prometheus group synchronization (#99012) * Add group actions menu * Refactor modals to accept raw ruler group * Use prometheus and ruler responses to dispaly GMA rules in the hierarchical view * Add groups loader component for data source managed rules * Improve rules matching algorithm for the search results * Use plus and minus icons for reconciliation state * loading spinner WIP for operations / transactions * update comment * Use ruler rules order when displaying a group, change rurler preload behaviour * Add ruler-based ordering for GMA rules * Refactor ruler API mocking * Refactor rule components to accept ruler only rules * Add tests for GrafanaGroupLoader * Add tests for vanilla prom groups * Unify data matching code, add tests for DS groups loader * Fix errors after rebasing * Improve handling of ruler group absence * Fix cache key * Add group action buttons for the new group pages * Add new rule action buttons to the new list page * Address PR feeback, component renaming, missing translations * Unify groups and rules links and actions * Fix new rule button * Add rule list action buttons tests * Fix lint errors * Add redirect to rule details page on save * Update FilterView tests * Fix imports and remove unused code * Improve type definitions, add pooling to Prom hooks, add inline docs * Remove unused code of group modals * Update translations * Disable cache population for filter-view generators * Add consistency check Alert to the RuleViewer when V2 list is enabled * Disable UI errors in prom generator * Improve missing datasouce handling * Add missing translations * Improve group loader tests, remove unused code * Enhance Prometheus API query to include notification options * Improve error handling, remove consistency check for vanilla prom data sources * Address PR feedback, add new version of the useHasRuler hook --------- Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
2 months ago
import { useMemo } from 'react';
import { Alert } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { GrafanaRuleGroupIdentifier } from 'app/types/unified-alerting';
import { GrafanaPromRuleDTO, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto';
import { alertRuleApi } from '../api/alertRuleApi';
import { prometheusApi } from '../api/prometheusApi';
import { RULE_LIST_POLL_INTERVAL_MS } from '../utils/constants';
import { GrafanaRulesSource } from '../utils/datasource';
import { GrafanaRuleListItem } from './GrafanaRuleLoader';
import { RuleOperationListItem } from './components/AlertRuleListItem';
import { AlertRuleListItemSkeleton } from './components/AlertRuleListItemLoader';
import { RuleOperation } from './components/RuleListIcon';
const { useGetGrafanaRulerGroupQuery } = alertRuleApi;
const { useGetGrafanaGroupsQuery } = prometheusApi;
export interface GrafanaGroupLoaderProps {
groupIdentifier: GrafanaRuleGroupIdentifier;
namespaceName: string;
/**
* Used to display the same number of skeletons as there are rules
* The number of rules is typically known from paginated Prometheus response
* Ruler response might contain different number of rules, but in most cases what we get from Prometheus is fine
*/
expectedRulesCount?: number;
}
/**
* Loads an evaluation group from Prometheus and Ruler endpoints.
* Displays a loading skeleton while the data is being fetched.
* Polls the Prometheus endpoint every 20 seconds to refresh the data.
*/
export function GrafanaGroupLoader({
groupIdentifier,
namespaceName,
expectedRulesCount = 3, // 3 is a random number. Usually we get the number of rules from Prometheus response
}: GrafanaGroupLoaderProps) {
const { data: promResponse, isLoading: isPromResponseLoading } = useGetGrafanaGroupsQuery(
{
folderUid: groupIdentifier.namespace.uid,
groupName: groupIdentifier.groupName,
},
{ pollingInterval: RULE_LIST_POLL_INTERVAL_MS }
);
const { data: rulerResponse, isLoading: isRulerGroupLoading } = useGetGrafanaRulerGroupQuery({
folderUid: groupIdentifier.namespace.uid,
groupName: groupIdentifier.groupName,
});
const { matches, promOnlyRules } = useMemo(() => {
const promRules = promResponse?.data.groups.at(0)?.rules ?? [];
const rulerRules = rulerResponse?.rules ?? [];
return matchRules(promRules, rulerRules);
}, [promResponse, rulerResponse]);
const isLoading = isPromResponseLoading || isRulerGroupLoading;
if (isLoading) {
return (
<>
{Array.from({ length: expectedRulesCount }).map((_, index) => (
<AlertRuleListItemSkeleton key={index} />
))}
</>
);
}
if (!rulerResponse || !promResponse) {
return (
<Alert
title={t(
'alerting.group-loader.group-load-failed',
'Failed to load rules from group {{ groupName }} in {{ namespaceName }}',
{ groupName: groupIdentifier.groupName, namespaceName }
)}
severity="error"
/>
);
}
return (
<>
{rulerResponse.rules.map((rulerRule) => {
const promRule = matches.get(rulerRule);
if (!promRule) {
return (
<GrafanaRuleListItem
key={rulerRule.grafana_alert.uid}
rule={promRule}
rulerRule={rulerRule}
groupIdentifier={groupIdentifier}
namespaceName={namespaceName}
operation={RuleOperation.Creating}
/>
);
}
return (
<GrafanaRuleListItem
key={promRule.uid}
rule={promRule}
rulerRule={rulerRule}
groupIdentifier={groupIdentifier}
namespaceName={namespaceName}
/>
);
})}
{promOnlyRules.map((rule) => (
<RuleOperationListItem
key={rule.uid}
name={rule.name}
namespace={namespaceName}
group={groupIdentifier.groupName}
rulesSource={GrafanaRulesSource}
application="grafana"
operation={RuleOperation.Deleting}
/>
))}
</>
);
}
interface MatchingResult {
matches: Map<RulerGrafanaRuleDTO, GrafanaPromRuleDTO>;
/**
* Rules that were already removed from the Ruler but the changes has not been yet propagated to Prometheus
*/
promOnlyRules: GrafanaPromRuleDTO[];
}
export function matchRules(
promRules: GrafanaPromRuleDTO[],
rulerRules: RulerGrafanaRuleDTO[]
): Readonly<MatchingResult> {
const promRulesMap = new Map(promRules.map((rule) => [rule.uid, rule]));
const matchingResult = rulerRules.reduce<MatchingResult>(
(acc, rulerRule) => {
const { matches } = acc;
const promRule = promRulesMap.get(rulerRule.grafana_alert.uid);
if (promRule) {
matches.set(rulerRule, promRule);
promRulesMap.delete(rulerRule.grafana_alert.uid);
}
return acc;
},
{ matches: new Map(), promOnlyRules: [] }
);
matchingResult.promOnlyRules.push(...promRulesMap.values());
return matchingResult;
}