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/DataSourceGroupLoader.tsx

227 lines
6.9 KiB

import { skipToken } from '@reduxjs/toolkit/query';
import { useMemo } from 'react';
import { t } from '@grafana/i18n';
import { isFetchError } from '@grafana/runtime';
import { Alert } from '@grafana/ui';
import { DataSourceRuleGroupIdentifier } from 'app/types/unified-alerting';
import {
PromRuleDTO,
PromRuleGroupDTO,
RulerCloudRuleDTO,
RulerRuleGroupDTO,
RulesSourceApplication,
} from 'app/types/unified-alerting-dto';
import { alertRuleApi } from '../api/alertRuleApi';
import { featureDiscoveryApi } from '../api/featureDiscoveryApi';
import { prometheusApi } from '../api/prometheusApi';
import { RULE_LIST_POLL_INTERVAL_MS } from '../utils/constants';
import { hashRule } from '../utils/rule-id';
import { getRuleName, isCloudRulerGroup } from '../utils/rules';
import { DataSourceRuleListItem } from './DataSourceRuleListItem';
import { RuleOperationListItem } from './components/AlertRuleListItem';
import { AlertRuleListItemSkeleton } from './components/AlertRuleListItemLoader';
import { RuleActionsButtons } from './components/RuleActionsButtons.V2';
import { RuleOperation } from './components/RuleListIcon';
import { matchRulesGroup } from './ruleMatching';
const { useDiscoverDsFeaturesQuery } = featureDiscoveryApi;
const { useGetGroupsQuery } = prometheusApi;
const { useGetRuleGroupForNamespaceQuery } = alertRuleApi;
export interface DataSourceGroupLoaderProps {
groupIdentifier: DataSourceRuleGroupIdentifier;
/**
* 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 DataSourceGroupLoader({ groupIdentifier, expectedRulesCount = 3 }: DataSourceGroupLoaderProps) {
const { namespace, groupName } = groupIdentifier;
const namespaceName = namespace.name;
const {
data: promResponse,
isLoading: isPromResponseLoading,
isError: isPromResponseError,
} = useGetGroupsQuery(
{
ruleSource: { uid: groupIdentifier.rulesSource.uid },
namespace: namespaceName,
groupName: groupName,
},
{ pollingInterval: RULE_LIST_POLL_INTERVAL_MS }
);
const {
data: dsFeatures,
isLoading: isDsFeaturesLoading,
isError: isDsFeaturesError,
} = useDiscoverDsFeaturesQuery({
uid: groupIdentifier.rulesSource.uid,
});
const {
data: rulerGroup,
error: rulerGroupError,
isFetching: isRulerGroupFetching,
isError: isRulerGroupError,
} = useGetRuleGroupForNamespaceQuery(
dsFeatures?.rulerConfig
? {
rulerConfig: dsFeatures?.rulerConfig,
namespace: namespaceName,
group: groupName,
}
: skipToken
);
const isLoading = isPromResponseLoading || isDsFeaturesLoading || isRulerGroupFetching;
if (isLoading) {
return (
<>
{Array.from({ length: expectedRulesCount }).map((_, index) => (
<AlertRuleListItemSkeleton key={index} />
))}
</>
);
}
const isError = isPromResponseError || isDsFeaturesError || isRulerGroupError;
if (isError) {
if (isFetchError(rulerGroupError) && rulerGroupError.status === 404) {
return (
<Alert severity="warning" title={t('alerting.ds-group-loader.group-deleting', 'The group is being deleted')} />
);
}
return (
<Alert
title={t(
'alerting.ds-group-loader.group-load-failed',
'Failed to load rules from group {{ groupName }} in {{ namespaceName }}',
{ groupName, namespaceName }
)}
severity="error"
/>
);
}
// There should be always only one group in the response but some Prometheus-compatible data sources
// implement different filter parameters
const promGroup = promResponse?.data.groups.find((g) => g.file === namespaceName && g.name === groupName);
if (dsFeatures?.rulerConfig && rulerGroup && isCloudRulerGroup(rulerGroup) && promGroup) {
return (
<RulerBasedGroupRules
groupIdentifier={groupIdentifier}
promGroup={promGroup}
rulerGroup={rulerGroup}
application={dsFeatures.application}
/>
);
}
// Data source without ruler
if (promGroup) {
return (
<>
{promGroup.rules.map((rule) => (
<DataSourceRuleListItem
key={hashRule(rule)}
rule={rule}
groupIdentifier={groupIdentifier}
application={dsFeatures?.application}
showLocation={false}
/>
))}
</>
);
}
// This should never happen
return (
<Alert
title={t(
'alerting.ds-group-loader.group-load-failed',
'Failed to load rules from group {{ groupName }} in {{ namespaceName }}',
{ groupName, namespaceName }
)}
severity="warning"
/>
);
}
interface RulerBasedGroupRulesProps {
groupIdentifier: DataSourceRuleGroupIdentifier;
promGroup: PromRuleGroupDTO<PromRuleDTO>;
rulerGroup: RulerRuleGroupDTO<RulerCloudRuleDTO>;
application: RulesSourceApplication;
}
export function RulerBasedGroupRules({
groupIdentifier,
application,
promGroup,
rulerGroup,
}: RulerBasedGroupRulesProps) {
const { namespace, groupName } = groupIdentifier;
const { matches, promOnlyRules } = useMemo(() => {
return matchRulesGroup(rulerGroup, promGroup);
}, [promGroup, rulerGroup]);
return (
<>
{rulerGroup.rules.map((rulerRule) => {
const promRule = matches.get(rulerRule);
return promRule ? (
<DataSourceRuleListItem
key={hashRule(promRule)}
rule={promRule}
rulerRule={rulerRule}
groupIdentifier={groupIdentifier}
application={application}
actions={
<RuleActionsButtons rule={rulerRule} promRule={promRule} groupIdentifier={groupIdentifier} compact />
}
showLocation={false}
/>
) : (
<RuleOperationListItem
key={getRuleName(rulerRule)}
name={getRuleName(rulerRule)}
namespace={namespace.name}
group={groupName}
rulesSource={groupIdentifier.rulesSource}
application={application}
operation={RuleOperation.Creating}
showLocation={false}
/>
);
})}
{promOnlyRules.map((rule) => (
<RuleOperationListItem
key={rule.name}
name={rule.name}
namespace={namespace.name}
group={groupName}
rulesSource={groupIdentifier.rulesSource}
application={application}
operation={RuleOperation.Deleting}
showLocation={false}
/>
))}
</>
);
}