diff --git a/public/app/features/alerting/unified/api/alertRuleApi.ts b/public/app/features/alerting/unified/api/alertRuleApi.ts index 9c792b5cbc9..080ea10fe2f 100644 --- a/public/app/features/alerting/unified/api/alertRuleApi.ts +++ b/public/app/features/alerting/unified/api/alertRuleApi.ts @@ -25,7 +25,7 @@ import { ExportFormats } from '../components/export/providers'; import { Folder } from '../types/rule-form'; import { GRAFANA_RULES_SOURCE_NAME, getDatasourceAPIUid, isGrafanaRulesSource } from '../utils/datasource'; import { arrayKeyValuesToObject } from '../utils/labels'; -import { isCloudRuleIdentifier, isGrafanaRulerRule, isPrometheusRuleIdentifier } from '../utils/rules'; +import { isCloudRuleIdentifier, isPrometheusRuleIdentifier, rulerRuleType } from '../utils/rules'; import { WithNotificationOptions, alertingApi } from './alertingApi'; import { GRAFANA_RULER_CONFIG } from './featureDiscoveryApi'; @@ -330,7 +330,7 @@ export const alertRuleApi = alertingApi.injectEndpoints({ }; }, invalidatesTags: (result, _error, { namespace, payload, rulerConfig }) => { - const grafanaRulerRules = payload.rules.filter((rule) => isGrafanaRulerRule(rule)); + const grafanaRulerRules = payload.rules.filter(rulerRuleType.grafana.rule); return [ { type: 'RuleNamespace', id: `${rulerConfig.dataSourceUid}/${namespace}` }, diff --git a/public/app/features/alerting/unified/components/MenuItemPauseRule.tsx b/public/app/features/alerting/unified/components/MenuItemPauseRule.tsx index c2c800c08e2..0424566f79d 100644 --- a/public/app/features/alerting/unified/components/MenuItemPauseRule.tsx +++ b/public/app/features/alerting/unified/components/MenuItemPauseRule.tsx @@ -6,7 +6,7 @@ import { RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto'; import { usePauseRuleInGroup } from '../hooks/ruleGroup/usePauseAlertRule'; import { isLoading } from '../hooks/useAsync'; import { stringifyErrorLike } from '../utils/misc'; -import { isGrafanaRulerRulePaused } from '../utils/rules'; +import { isPausedRule } from '../utils/rules'; interface Props { rule: RulerGrafanaRuleDTO; @@ -25,7 +25,7 @@ const MenuItemPauseRule = ({ rule, groupIdentifier, onPauseChange }: Props) => { const notifyApp = useAppNotification(); const [pauseRule, updateState] = usePauseRuleInGroup(); - const [icon, title] = isGrafanaRulerRulePaused(rule) + const [icon, title] = isPausedRule(rule) ? ['play' as const, 'Resume evaluation'] : ['pause' as const, 'Pause evaluation']; diff --git a/public/app/features/alerting/unified/components/export/GrafanaModifyExport.tsx b/public/app/features/alerting/unified/components/export/GrafanaModifyExport.tsx index 1c4241fe519..d9840cd37ec 100644 --- a/public/app/features/alerting/unified/components/export/GrafanaModifyExport.tsx +++ b/public/app/features/alerting/unified/components/export/GrafanaModifyExport.tsx @@ -9,7 +9,7 @@ import { useRuleWithLocation } from '../../hooks/useCombinedRule'; import { formValuesFromExistingRule } from '../../rule-editor/formDefaults'; import { stringifyErrorLike } from '../../utils/misc'; import * as ruleId from '../../utils/rule-id'; -import { isGrafanaRulerRule } from '../../utils/rules'; +import { rulerRuleType } from '../../utils/rules'; import { createRelativeUrl } from '../../utils/url'; import { withPageErrorBoundary } from '../../withPageErrorBoundary'; import { AlertingPageWrapper } from '../AlertingPageWrapper'; @@ -58,7 +58,7 @@ function RuleModifyExport({ ruleIdentifier }: { ruleIdentifier: RuleIdentifier } ); } - if (rulerRule && !isGrafanaRulerRule(rulerRule.rule)) { + if (rulerRule && !rulerRuleType.grafana.rule(rulerRule.rule)) { // alert rule exists but is not a grafana-managed rule return ( { - return group.rules.some((rule) => isGrafanaRulerRule(rule) && Boolean(rule.grafana_alert.provenance) === true); + return group.rules.some( + (rule) => rulerRuleType.grafana.rule(rule) && Boolean(rule.grafana_alert.provenance) === true + ); }; const sortByLabel = (a: SelectableValue, b: SelectableValue) => { diff --git a/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx b/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx index d862994827b..900346c070f 100644 --- a/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx @@ -14,7 +14,7 @@ import { formValuesToRulerRuleDTO, recordingRulerRuleToRuleForm, } from '../../utils/rule-form'; -import { isAlertingRulerRule, isRecordingRulerRule } from '../../utils/rules'; +import { rulerRuleType } from '../../utils/rules'; interface Props { onClose: () => void; @@ -145,9 +145,9 @@ function YamlContentInfo() { } function rulerRuleToRuleFormValues(rulerRule: RulerRuleDTO): Partial { - if (isAlertingRulerRule(rulerRule)) { + if (rulerRuleType.dataSource.alertingRule(rulerRule)) { return alertingRulerRuleToRuleForm(rulerRule); - } else if (isRecordingRulerRule(rulerRule)) { + } else if (rulerRuleType.dataSource.recordingRule(rulerRule)) { return recordingRulerRuleToRuleForm(rulerRule); } diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index e5d28d4d478..b99fcac732a 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -16,11 +16,10 @@ import { getRuleGroupLocationFromRuleWithLocation, isCloudAlertingRuleByType, isCloudRecordingRuleByType, - isCloudRulerRule, isGrafanaManagedRuleByType, - isGrafanaRulerRule, - isGrafanaRulerRulePaused, + isPausedRule, isRecordingRuleByType, + rulerRuleType, } from 'app/features/alerting/unified/utils/rules'; import { isExpressionQuery } from 'app/features/expressions/guards'; import { RuleGroupIdentifier, RuleIdentifier, RuleWithLocation } from 'app/types/unified-alerting'; @@ -209,7 +208,7 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => // Cloud Ruler rules identifier changes on update due to containing rule name and hash components // After successful update we need to update the URL to avoid displaying 404 errors - if (isCloudRulerRule(ruleDefinition)) { + if (rulerRuleType.dataSource.rule(ruleDefinition)) { const updatedRuleIdentifier = fromRulerRule(dataSourceName, namespaceName, groupName, ruleDefinition); locationService.replace(`/alerting/${encodeURIComponent(stringifyIdentifier(updatedRuleIdentifier))}/edit`); } @@ -295,7 +294,8 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => ); - const isPaused = existing && isGrafanaRulerRule(existing.rule) && isGrafanaRulerRulePaused(existing.rule); + const isPaused = rulerRuleType.grafana.alertingRule(existing?.rule) && isPausedRule(existing?.rule); + if (!type) { return null; } @@ -369,7 +369,7 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => function getReturnToUrl(groupId: RuleGroupIdentifier, rule: RulerRuleDTO | PostableRuleGrafanaRuleDTO) { const { dataSourceName, namespaceName, groupName } = groupId; - if (prometheusRulesPrimary && isCloudRulerRule(rule)) { + if (prometheusRulesPrimary && rulerRuleType.dataSource.rule(rule)) { const ruleIdentifier = fromRulerRule(dataSourceName, namespaceName, groupName, rule); return createViewLinkFromIdentifier(ruleIdentifier); } diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx index 71c049e661b..f6d18278089 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx @@ -20,7 +20,7 @@ import { DEFAULT_GROUP_EVALUATION_INTERVAL, getDefaultFormValues } from '../../. import { RuleFormType, RuleFormValues } from '../../../types/rule-form'; import { GRAFANA_RULES_SOURCE_NAME } from '../../../utils/datasource'; import { formValuesToRulerGrafanaRuleDTO, getDefaultQueries } from '../../../utils/rule-form'; -import { isGrafanaRulerRule } from '../../../utils/rules'; +import { rulerRuleType } from '../../../utils/rules'; import { FileExportPreview } from '../../export/FileExportPreview'; import { GrafanaExportDrawer } from '../../export/GrafanaExportDrawer'; import { ExportFormats, HclExportProvider, allGrafanaExportProviders } from '../../export/providers'; @@ -150,7 +150,7 @@ export const getPayloadToExport = ( // we have to update the rule in the group in the same position if it exists, otherwise we have to add it at the end let alreadyExistsInGroup = false; const updatedRules = existingGroup.rules.map((rule: RulerRuleDTO) => { - if (isGrafanaRulerRule(rule) && rule.grafana_alert.uid === ruleUid) { + if (rulerRuleType.grafana.rule(rule) && rule.grafana_alert.uid === ruleUid) { alreadyExistsInGroup = true; return updatedRule; } else { diff --git a/public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx b/public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx index d8d7641b2ce..f7a1237ae9f 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx @@ -10,7 +10,7 @@ import { PromAlertingRuleState, RulerRuleDTO } from 'app/types/unified-alerting- import { AlertRuleAction, useRulerRuleAbility } from '../../hooks/useAbilities'; import { createShareLink, isLocalDevEnv, isOpenSourceEdition } from '../../utils/misc'; import * as ruleId from '../../utils/rule-id'; -import { isAlertingRule, isGrafanaRulerRule } from '../../utils/rules'; +import { prometheusRuleType, rulerRuleType } from '../../utils/rules'; import { createRelativeUrl } from '../../utils/url'; import { DeclareIncidentMenuItem } from '../bridges/DeclareIncidentButton'; @@ -76,7 +76,7 @@ const AlertRuleMenu = ({ // @TODO Migrate "declare incident button" to plugin links extensions const shouldShowDeclareIncidentButton = (!isOpenSourceEdition() || isLocalDevEnv()) && - isAlertingRule(promRule) && + prometheusRuleType.alertingRule(promRule) && promRule.state === PromAlertingRuleState.Firing; const shareUrl = createShareLink(identifier); @@ -86,7 +86,7 @@ const AlertRuleMenu = ({ const menuItems = ( <> - {canPause && isGrafanaRulerRule(rulerRule) && groupIdentifier.groupOrigin === 'grafana' && ( + {canPause && rulerRuleType.grafana.rule(rulerRule) && groupIdentifier.groupOrigin === 'grafana' && ( )} {canSilence && } diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx index 802be0d3e10..8653c39f707 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx @@ -343,21 +343,30 @@ describe('RuleViewer', () => { const mockRule = getVanillaPromRule({ name: 'prom test alert', namespace: mockCombinedCloudRuleNamespace({ name: 'prometheus' }, prometheus.name), - annotations: { [Annotation.summary]: 'prom summary', [Annotation.runbookURL]: 'https://runbook.example.com' }, + annotations: { + [Annotation.summary]: 'prom summary', + [Annotation.runbookURL]: 'https://runbook.example.com', + }, promRule: { - ...mockPromAlertingRule(), + ...mockPromAlertingRule({ + annotations: { + [Annotation.summary]: 'prom summary', + [Annotation.runbookURL]: 'https://runbook.example.com', + }, + }), duration: 900, // 15 minutes }, }); const mockRuleIdentifier = ruleId.fromCombinedRule(prometheus.name, mockRule); - it('should render pending period for vanilla Prometheus alert rule', async () => { + it('should render metadata for vanilla Prometheus alert rule', async () => { renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.Details); expect(screen.getByText('prom test alert')).toBeInTheDocument(); - // One summary is rendered by the Title component, and the other by the DetailsTab component + // Both summary and runbook are rendered by the Title component, and the DetailsTab component + expect(ELEMENTS.metadata.summary(mockRule.annotations[Annotation.runbookURL]).getAll()).toHaveLength(2); expect(ELEMENTS.metadata.summary(mockRule.annotations[Annotation.summary]).getAll()).toHaveLength(2); expect(ELEMENTS.details.pendingPeriod.get()).toHaveTextContent(/15m/i); diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx index e714277c490..d661d942077 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx @@ -23,12 +23,10 @@ import { makeDashboardLink, makePanelLink, stringifyErrorLike } from '../../util import { RulePluginOrigin, getRulePluginOrigin, - isAlertingRule, isFederatedRuleGroup, - isGrafanaRecordingRule, - isGrafanaRulerRule, - isGrafanaRulerRulePaused, - isRecordingRule, + isPausedRule, + prometheusRuleType, + rulerRuleType, } from '../../utils/rules'; import { createRelativeUrl } from '../../utils/url'; import { AlertLabels } from '../AlertLabels'; @@ -71,11 +69,11 @@ const RuleViewer = () => { const { annotations, promRule, rulerRule } = rule; const hasError = isErrorHealth(promRule?.health); - const isAlertType = isAlertingRule(promRule); + const isAlertType = prometheusRuleType.alertingRule(promRule); const isFederatedRule = isFederatedRuleGroup(rule.group); - const isProvisioned = isGrafanaRulerRule(rulerRule) && Boolean(rulerRule.grafana_alert.provenance); - const isPaused = isGrafanaRulerRule(rulerRule) && isGrafanaRulerRulePaused(rulerRule); + const isProvisioned = rulerRuleType.grafana.rule(rulerRule) && Boolean(rulerRule.grafana_alert.provenance); + const isPaused = rulerRuleType.grafana.alertingRule(rulerRule) && isPausedRule(rulerRule); const showError = hasError && !isPaused; const ruleOrigin = rulerRule ? getRulePluginOrigin(rulerRule) : getRulePluginOrigin(promRule); @@ -126,10 +124,12 @@ const RuleViewer = () => { {activeTab === ActiveTab.Query && } {activeTab === ActiveTab.Instances && } - {activeTab === ActiveTab.History && isGrafanaRulerRule(rule.rulerRule) && } + {activeTab === ActiveTab.History && rulerRuleType.grafana.rule(rule.rulerRule) && ( + + )} {activeTab === ActiveTab.Routing && } {activeTab === ActiveTab.Details &&
} - {activeTab === ActiveTab.VersionHistory && isGrafanaRulerRule(rule.rulerRule) && ( + {activeTab === ActiveTab.VersionHistory && rulerRuleType.grafana.rule(rule.rulerRule) && ( )} @@ -206,7 +206,7 @@ const createMetadata = (rule: CombinedRule): PageInfoItem[] => { ), }); } - if (isGrafanaRecordingRule(rule.rulerRule)) { + if (rulerRuleType.grafana.recordingRule(rule.rulerRule)) { const metric = rule.rulerRule?.grafana_alert.record?.metric ?? ''; metadata.push({ label: 'Metric name', @@ -336,15 +336,15 @@ function usePageNav(rule: CombinedRule) { const { annotations, promRule, rulerRule } = rule; const summary = annotations[Annotation.summary]; - const isAlertType = isAlertingRule(promRule); + const isAlertType = prometheusRuleType.alertingRule(promRule); const numberOfInstance = isAlertType ? calculateTotalInstances(rule.instanceTotals) : undefined; const namespaceName = decodeGrafanaNamespace(rule.namespace).name; const groupName = rule.group.name; - const isGrafanaAlertRule = isGrafanaRulerRule(rulerRule) && isAlertType; - const grafanaRecordingRule = isGrafanaRecordingRule(rulerRule); - const isRecordingRuleType = isRecordingRule(promRule); + const isGrafanaAlertRule = rulerRuleType.grafana.rule(rulerRule) && isAlertType; + const grafanaRecordingRule = rulerRuleType.grafana.recordingRule(rulerRule); + const isRecordingRuleType = prometheusRuleType.recordingRule(promRule); const pageNav: NavModelItem = { ...defaultPageNav, diff --git a/public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx b/public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx index 218e6cf0420..8adb1023776 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx @@ -7,13 +7,7 @@ import { Trans, t } from 'app/core/internationalization'; import { CombinedRule } from 'app/types/unified-alerting'; import { usePendingPeriod } from '../../../hooks/rules/usePendingPeriod'; -import { - getAnnotations, - isGrafanaAlertingRule, - isGrafanaRecordingRule, - isGrafanaRulerRule, - isRecordingRulerRule, -} from '../../../utils/rules'; +import { getAnnotations, isPausedRule, prometheusRuleType, rulerRuleType } from '../../../utils/rules'; import { isNullDate } from '../../../utils/time'; import { Tokenize } from '../../Tokenize'; import { DetailText } from '../../common/DetailText'; @@ -25,6 +19,7 @@ enum RuleType { GrafanaManagedRecordingRule = 'Grafana-managed recording rule', CloudAlertRule = 'Cloud alert rule', CloudRecordingRule = 'Cloud recording rule', + Unknown = 'Unknown', } const DetailGroup = ({ title, children }: { title: string; children: React.ReactNode }) => { @@ -45,30 +40,29 @@ interface DetailsProps { export const Details = ({ rule }: DetailsProps) => { const styles = useStyles2(getStyles); - let ruleType: RuleType; - const pendingPeriod = usePendingPeriod(rule); - if (isGrafanaRulerRule(rule.rulerRule)) { - ruleType = isGrafanaRecordingRule(rule.rulerRule) - ? RuleType.GrafanaManagedRecordingRule - : RuleType.GrafanaManagedAlertRule; - } else if (isRecordingRulerRule(rule.rulerRule)) { - ruleType = RuleType.CloudRecordingRule; - } else { - // probably not the greatest assumption - ruleType = RuleType.CloudAlertRule; + let determinedRuleType: RuleType = RuleType.Unknown; + if (rulerRuleType.grafana.alertingRule(rule.rulerRule)) { + determinedRuleType = RuleType.GrafanaManagedAlertRule; + } else if (rulerRuleType.grafana.recordingRule(rule.rulerRule)) { + determinedRuleType = RuleType.GrafanaManagedRecordingRule; + } else if (rulerRuleType.dataSource.alertingRule(rule.rulerRule)) { + determinedRuleType = RuleType.CloudAlertRule; + } else if (rulerRuleType.dataSource.recordingRule(rule.rulerRule)) { + determinedRuleType = RuleType.CloudRecordingRule; } const evaluationDuration = rule.promRule?.evaluationTime; const evaluationTimestamp = rule.promRule?.lastEvaluation; - const annotations = getAnnotations(rule); + const annotations = prometheusRuleType.alertingRule(rule.promRule) ? getAnnotations(rule.promRule) : undefined; const hasEvaluationDuration = Number.isFinite(evaluationDuration); - const updated = isGrafanaRulerRule(rule.rulerRule) ? rule.rulerRule.grafana_alert.updated : undefined; - const isPaused = isGrafanaAlertingRule(rule.rulerRule) && rule.rulerRule.grafana_alert.is_paused; + const updated = rulerRuleType.grafana.rule(rule.rulerRule) ? rule.rulerRule.grafana_alert.updated : undefined; + const isPaused = rulerRuleType.grafana.alertingRule(rule.rulerRule) && isPausedRule(rule.rulerRule); + const pausedIcon = ( @@ -82,8 +76,8 @@ export const Details = ({ rule }: DetailsProps) => { return (
- - {isGrafanaRulerRule(rule.rulerRule) && ( + + {rulerRuleType.grafana.rule(rule.rulerRule) && ( <> { )} - {isGrafanaRulerRule(rule.rulerRule) && + {rulerRuleType.grafana.rule(rule.rulerRule) && // grafana recording rules don't have these fields rule.rulerRule.grafana_alert.no_data_state && rule.rulerRule.grafana_alert.exec_err_state && ( diff --git a/public/app/features/alerting/unified/components/rule-viewer/tabs/Query.tsx b/public/app/features/alerting/unified/components/rule-viewer/tabs/Query.tsx index 7d954c53303..388dae34f94 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/tabs/Query.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/tabs/Query.tsx @@ -8,7 +8,7 @@ import { CombinedRule } from 'app/types/unified-alerting'; import { GrafanaRuleQueryViewer, QueryPreview } from '../../../GrafanaRuleQueryViewer'; import { useAlertQueriesStatus } from '../../../hooks/useAlertQueriesStatus'; import { alertRuleToQueries } from '../../../utils/query'; -import { isFederatedRuleGroup, isGrafanaRulerRule } from '../../../utils/rules'; +import { isFederatedRuleGroup, rulerRuleType } from '../../../utils/rules'; import { useAlertQueryRunner } from '../../rule-editor/query-and-alert-condition/useAlertQueryRunner'; interface Props { @@ -25,7 +25,7 @@ const QueryResults = ({ rule }: Props) => { const onRunQueries = useCallback(() => { if (queries.length > 0 && allDataSourcesAvailable) { let condition; - if (rule && isGrafanaRulerRule(rule.rulerRule)) { + if (rule && rulerRuleType.grafana.rule(rule.rulerRule)) { condition = rule.rulerRule.grafana_alert.condition; } runQueries(queries, condition ?? 'A'); @@ -46,7 +46,7 @@ const QueryResults = ({ rule }: Props) => { return ( <> - {isGrafanaRulerRule(rule.rulerRule) && !isFederatedRule && ( + {rulerRuleType.grafana.rule(rule.rulerRule) && !isFederatedRule && ( { /> )} - {!isGrafanaRulerRule(rule.rulerRule) && + {!rulerRuleType.grafana.rule(rule.rulerRule) && !isFederatedRule && queryPreviewData && Object.keys(queryPreviewData).length > 0 && ( diff --git a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx index 828920a7e52..ae15d299c37 100644 --- a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx +++ b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx @@ -35,7 +35,7 @@ import { fetchRulerRulesAction, rulesInSameGroupHaveInvalidFor } from '../../sta import { checkEvaluationIntervalGlobalLimit } from '../../utils/config'; import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; import { stringifyErrorLike } from '../../utils/misc'; -import { AlertInfo, getAlertInfo, isGrafanaOrDataSourceRecordingRule } from '../../utils/rules'; +import { AlertInfo, getAlertInfo, rulerRuleType } from '../../utils/rules'; import { formatPrometheusDuration, parsePrometheusDuration, safeParsePrometheusDuration } from '../../utils/time'; import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; import { EvaluationIntervalLimitExceeded } from '../InvalidIntervalWarning'; @@ -310,9 +310,7 @@ export function EditRuleGroupModalForm(props: ModalFormProps): React.ReactElemen notifyApp.error('There are errors in the form. Correct the errors and retry.'); }; - const rulesWithoutRecordingRules = compact( - ruleGroup?.rules.filter((rule) => !isGrafanaOrDataSourceRecordingRule(rule)) - ); + const rulesWithoutRecordingRules = compact(ruleGroup?.rules.filter((rule) => !rulerRuleType.any.recordingRule(rule))); const hasSomeNoRecordingRules = rulesWithoutRecordingRules.length > 0; return ( diff --git a/public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.tsx b/public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.tsx index 5318b4a6f37..779c68197e4 100644 --- a/public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.tsx +++ b/public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.tsx @@ -10,7 +10,6 @@ import { import cx from 'classnames'; import { produce } from 'immer'; import { useCallback, useEffect, useState } from 'react'; -import * as React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { Badge, Button, Icon, Modal, Tooltip, useStyles2 } from '@grafana/ui'; @@ -31,12 +30,7 @@ import { SwapOperation, swapItems } from '../../reducers/ruler/ruleGroups'; import { fetchRulerRulesAction } from '../../state/actions'; import { isCloudRulesSource } from '../../utils/datasource'; import { hashRulerRule } from '../../utils/rule-id'; -import { - isAlertingRulerRule, - isGrafanaRulerRule, - isRecordingRulerRule, - rulesSourceToDataSourceName, -} from '../../utils/rules'; +import { getRuleName, rulerRuleType, rulesSourceToDataSourceName } from '../../utils/rules'; interface ModalProps { namespace: CombinedRuleNamespace; @@ -189,14 +183,16 @@ const ListItem = ({ provided, rule, isClone = false, isDragging = false }: ListI {...provided.draggableProps} {...provided.dragHandleProps} > - {isGrafanaRulerRule(rule) &&
{rule.grafana_alert.title}
} - {isRecordingRulerRule(rule) && ( - <> -
{rule.record}
- - - )} - {isAlertingRulerRule(rule) &&
{rule.alert}
} +
+ {getRuleName(rule)} + {rulerRuleType.any.recordingRule(rule) && ( + <> + {' '} + + + )} +
+ {rulerRuleType.dataSource.alertingRule(rule) &&
{rule.alert}
}
); diff --git a/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx b/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx index 9f7d4710520..8ff803ac421 100644 --- a/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx @@ -16,7 +16,7 @@ import { GRAFANA_RULES_SOURCE_NAME, getRulesSourceName } from '../../utils/datas import { groupIdentifier } from '../../utils/groupIdentifier'; import { createViewLink } from '../../utils/misc'; import * as ruleId from '../../utils/rule-id'; -import { isGrafanaAlertingRule, isGrafanaRulerRule } from '../../utils/rules'; +import { rulerRuleType } from '../../utils/rules'; import { createRelativeUrl } from '../../utils/url'; import { RedirectToCloneRule } from './CloneRule'; @@ -52,7 +52,7 @@ export const RuleActionsButtons = ({ compact, showViewButton, rule, rulesSource const { namespace, group, rulerRule } = rule; const { hasActiveFilters } = useRulesFilter(); - const isProvisioned = isGrafanaRulerRule(rule.rulerRule) && Boolean(rule.rulerRule.grafana_alert.provenance); + const isProvisioned = rulerRuleType.grafana.rule(rule.rulerRule) && Boolean(rule.rulerRule.grafana_alert.provenance); const [editRuleSupported, editRuleAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Update); @@ -125,7 +125,7 @@ export const RuleActionsButtons = ({ compact, showViewButton, rule, rulesSource buttonSize={buttonSize} /> {deleteModal} - {isGrafanaAlertingRule(rule.rulerRule) && showSilenceDrawer && ( + {rulerRuleType.grafana.alertingRule(rule.rulerRule) && showSilenceDrawer && ( setShowSilenceDrawer(false)} /> )} {redirectToClone?.identifier && ( diff --git a/public/app/features/alerting/unified/components/rules/RuleConfigStatus.tsx b/public/app/features/alerting/unified/components/rules/RuleConfigStatus.tsx index 2309f946e78..c0ed270d66f 100644 --- a/public/app/features/alerting/unified/components/rules/RuleConfigStatus.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleConfigStatus.tsx @@ -7,7 +7,7 @@ import { Icon, Tooltip, useStyles2 } from '@grafana/ui/src'; import { CombinedRule } from '../../../../../types/unified-alerting'; import { checkEvaluationIntervalGlobalLimit } from '../../utils/config'; -import { isGrafanaRulerRule } from '../../utils/rules'; +import { rulerRuleType } from '../../utils/rules'; interface RuleConfigStatusProps { rule: CombinedRule; @@ -15,7 +15,7 @@ interface RuleConfigStatusProps { export function RuleConfigStatus({ rule }: RuleConfigStatusProps) { const styles = useStyles2(getStyles); - const isGrafanaManagedRule = isGrafanaRulerRule(rule.rulerRule); + const isGrafanaManagedRule = rulerRuleType.grafana.rule(rule.rulerRule); const exceedsLimit = useMemo(() => { return isGrafanaManagedRule ? checkEvaluationIntervalGlobalLimit(rule.group.interval).exceedsLimit : false; diff --git a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx index ab593b12b1c..2e5394fc02f 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx @@ -7,7 +7,7 @@ import { CombinedRule } from 'app/types/unified-alerting'; import { usePendingPeriod } from '../../hooks/rules/usePendingPeriod'; import { useCleanAnnotations } from '../../utils/annotations'; -import { isGrafanaRecordingRule, isRecordingRule, isRecordingRulerRule } from '../../utils/rules'; +import { prometheusRuleType, rulerRuleType } from '../../utils/rules'; import { isNullDate } from '../../utils/time'; import { AlertLabels } from '../AlertLabels'; import { DetailsField } from '../DetailsField'; @@ -53,15 +53,12 @@ export const RuleDetails = ({ rule }: Props) => { - {!( - isRecordingRulerRule(rule.rulerRule) || - isRecordingRule(rule.promRule) || - isGrafanaRecordingRule(rule.rulerRule) - ) && ( - - - - )} + {rulerRuleType.any.alertingRule(rule.rulerRule) || + (prometheusRuleType.alertingRule(rule.promRule) && ( + + + + ))} ); }; @@ -74,7 +71,9 @@ const EvaluationBehaviorSummary = ({ rule }: EvaluationBehaviorSummaryProps) => const every = rule.group.interval; const lastEvaluation = rule.promRule?.lastEvaluation; const lastEvaluationDuration = rule.promRule?.evaluationTime; - const metric = isGrafanaRecordingRule(rule.rulerRule) ? rule.rulerRule?.grafana_alert.record?.metric : undefined; + const metric = rulerRuleType.grafana.recordingRule(rule.rulerRule) + ? rule.rulerRule?.grafana_alert.record?.metric + : undefined; const pendingPeriod = usePendingPeriod(rule); diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsButtons.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsButtons.tsx index 079a26dcf48..6f19a0ab8a6 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsButtons.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsButtons.tsx @@ -10,7 +10,7 @@ import { useStateHistoryModal } from '../../hooks/useStateHistoryModal'; import { Annotation } from '../../utils/constants'; import { isCloudRulesSource } from '../../utils/datasource'; import { createExploreLink } from '../../utils/misc'; -import { isFederatedRuleGroup, isGrafanaAlertingRule, isGrafanaRulerRule } from '../../utils/rules'; +import { isFederatedRuleGroup, rulerRuleType } from '../../utils/rules'; interface Props { rule: CombinedRule; @@ -103,13 +103,13 @@ const RuleDetailsButtons = ({ rule, rulesSource }: Props) => { } } - if (isGrafanaAlertingRule(rule.rulerRule)) { + if (rulerRuleType.grafana.alertingRule(rule.rulerRule)) { buttons.push( diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsDataSources.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsDataSources.tsx index 9f6ea6c1dcc..9f18a713b18 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsDataSources.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsDataSources.tsx @@ -8,7 +8,7 @@ import { ExpressionDatasourceUID } from 'app/features/expressions/types'; import { CombinedRule, RulesSource } from 'app/types/unified-alerting'; import { isCloudRulesSource } from '../../utils/datasource'; -import { isGrafanaRulerRule } from '../../utils/rules'; +import { rulerRuleType } from '../../utils/rules'; import { DetailsField } from '../DetailsField'; type Props = { @@ -25,7 +25,7 @@ export function RuleDetailsDataSources(props: Props): JSX.Element | null { return [{ name: rulesSource.name, icon: rulesSource.meta.info.logos.small }]; } - if (isGrafanaRulerRule(rule.rulerRule)) { + if (rulerRuleType.grafana.rule(rule.rulerRule)) { const { data } = rule.rulerRule.grafana_alert; const unique = data.reduce>((dataSources, query) => { const ds = getDataSourceSrv().getInstanceSettings(query.datasourceUid); diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx index c9491eda390..c45c3cdee34 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx @@ -1,7 +1,7 @@ import { css, cx } from '@emotion/css'; import { countBy, sum } from 'lodash'; -import { useMemo, useState } from 'react'; import * as React from 'react'; +import { useMemo, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { LinkButton, useStyles2 } from '@grafana/ui'; @@ -18,7 +18,7 @@ import { mapStateWithReasonToBaseState } from 'app/types/unified-alerting-dto'; import { GRAFANA_RULES_SOURCE_NAME, isGrafanaRulesSource } from '../../utils/datasource'; import { parsePromQLStyleMatcherLooseSafe } from '../../utils/matchers'; -import { isAlertingRule } from '../../utils/rules'; +import { prometheusRuleType } from '../../utils/rules'; import { AlertInstancesTable } from './AlertInstancesTable'; import { getComponentsFromStats } from './RuleStats'; @@ -73,13 +73,13 @@ export function RuleDetailsMatchingInstances(props: Props) { const alerts = useMemo( (): Alert[] => - isAlertingRule(promRule) && promRule.alerts?.length + prometheusRuleType.alertingRule(promRule) && promRule.alerts?.length ? filterAlerts(queryString, alertState, sortAlerts(SortOrder.Importance, promRule.alerts)) : [], [promRule, alertState, queryString] ); - if (!isAlertingRule(promRule)) { + if (!prometheusRuleType.alertingRule(promRule)) { return null; } diff --git a/public/app/features/alerting/unified/components/rules/RuleListStateView.tsx b/public/app/features/alerting/unified/components/rules/RuleListStateView.tsx index 0bd026d6d43..3295e2950e6 100644 --- a/public/app/features/alerting/unified/components/rules/RuleListStateView.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleListStateView.tsx @@ -14,7 +14,7 @@ import { GRAFANA_RULES_SOURCE_NAME, getRulesDataSources } from '../../utils/data import { createViewLink } from '../../utils/misc'; import { isAsyncRequestStatePending } from '../../utils/redux'; import { hashRule } from '../../utils/rule-id'; -import { getRulePluginOrigin, isAlertingRule, isProvisionedRule } from '../../utils/rules'; +import { getRulePluginOrigin, isProvisionedRule, prometheusRuleType } from '../../utils/rules'; import { calculateTotalInstances } from '../rule-viewer/RuleViewer'; import { RuleActionsButtons } from './RuleActionsButtons'; @@ -43,7 +43,7 @@ export const RuleListStateView = ({ namespaces }: Props) => { // We might hit edge cases where there type = alerting, but there is no state. // In this case, we shouldn't try to group these alerts in the state view // Even though we handle this at the API layer, this is a last catch point for any edge cases - if (rule.promRule && isAlertingRule(rule.promRule) && rule.promRule.state) { + if (prometheusRuleType.alertingRule(rule.promRule) && rule.promRule.state) { result.get(rule.promRule.state)?.push(rule); } }) @@ -103,7 +103,9 @@ const RulesByState = ({ state, rules }: { state: PromAlertingRuleState; rules: C const { rulerRule, promRule } = rule; const isProvisioned = rulerRule ? isProvisionedRule(rulerRule) : false; - const instancesCount = isAlertingRule(rule.promRule) ? calculateTotalInstances(rule.instanceTotals) : undefined; + const instancesCount = prometheusRuleType.alertingRule(rule.promRule) + ? calculateTotalInstances(rule.instanceTotals) + : undefined; if (!promRule) { return null; diff --git a/public/app/features/alerting/unified/components/rules/RuleState.tsx b/public/app/features/alerting/unified/components/rules/RuleState.tsx index 48912322e54..78637dac2e1 100644 --- a/public/app/features/alerting/unified/components/rules/RuleState.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleState.tsx @@ -7,7 +7,7 @@ import { Trans } from 'app/core/internationalization'; import { CombinedRule } from 'app/types/unified-alerting'; import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; -import { getFirstActiveAt, isAlertingRule, isGrafanaRecordingRule, isRecordingRule } from '../../utils/rules'; +import { getFirstActiveAt, prometheusRuleType, rulerRuleType } from '../../utils/rules'; import { StateTag } from '../StateTag'; import { AlertStateTag } from './AlertStateTag'; @@ -24,7 +24,7 @@ export const RuleState = ({ rule, isDeleting, isCreating, isPaused }: Props) => const { promRule, rulerRule } = rule; // return how long the rule has been in its firing state, if any const RecordingRuleState = () => { - if (isPaused && isGrafanaRecordingRule(rulerRule)) { + if (isPaused && rulerRuleType.grafana.recordingRule(rulerRule)) { return ( @@ -39,8 +39,7 @@ export const RuleState = ({ rule, isDeleting, isCreating, isPaused }: Props) => }; const forTime = useMemo(() => { if ( - promRule && - isAlertingRule(promRule) && + prometheusRuleType.alertingRule(promRule) && promRule.alerts?.length && promRule.state !== PromAlertingRuleState.Inactive ) { @@ -80,14 +79,14 @@ export const RuleState = ({ rule, isDeleting, isCreating, isPaused }: Props) => Creating
); - } else if (promRule && isAlertingRule(promRule)) { + } else if (prometheusRuleType.alertingRule(promRule)) { return ( {!isPaused && forTime} ); - } else if (promRule && isRecordingRule(promRule)) { + } else if (promRule && prometheusRuleType.recordingRule(promRule)) { return ; } return <>n/a; diff --git a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx index ccf725b5f99..7cde4ee36c4 100644 --- a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx @@ -15,7 +15,7 @@ import { useHasRuler } from '../../hooks/useHasRuler'; import { useRulesAccess } from '../../utils/accessControlHooks'; import { GRAFANA_RULES_SOURCE_NAME, getRulesSourceName, isCloudRulesSource } from '../../utils/datasource'; import { makeFolderLink, makeFolderSettingsLink } from '../../utils/misc'; -import { isFederatedRuleGroup, isGrafanaRulerRule } from '../../utils/rules'; +import { isFederatedRuleGroup, rulerRuleType } from '../../utils/rules'; import { CollapseToggle } from '../CollapseToggle'; import { RuleLocation } from '../RuleLocation'; import { GrafanaRuleFolderExporter } from '../export/GrafanaRuleFolderExporter'; @@ -63,7 +63,8 @@ export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }: const { currentData: dsFeatures } = useDiscoverDsFeaturesQuery({ rulesSourceName }); const rulerRule = group.rules[0]?.rulerRule; - const folderUID = (rulerRule && isGrafanaRulerRule(rulerRule) && rulerRule.grafana_alert.namespace_uid) || undefined; + const folderUID = + (rulerRule && rulerRuleType.grafana.rule(rulerRule) && rulerRule.grafana_alert.namespace_uid) || undefined; const { folder } = useFolder(folderUID); // group "is deleting" if rules source has ruler, but this group has no rules that are in ruler @@ -72,7 +73,7 @@ export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }: // check if group has provisioned items const isProvisioned = group.rules.some((rule) => { - return isGrafanaRulerRule(rule.rulerRule) && rule.rulerRule.grafana_alert.provenance; + return rulerRuleType.grafana.rule(rule.rulerRule) && rule.rulerRule.grafana_alert.provenance; }); // check what view mode we are in diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.tsx index 1f7c6c44b11..b5a0aeabc62 100644 --- a/public/app/features/alerting/unified/components/rules/RulesTable.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesTable.tsx @@ -19,7 +19,7 @@ import { PluginOriginBadge } from '../../plugins/PluginOriginBadge'; import { calculateNextEvaluationEstimate } from '../../rule-list/components/util'; import { Annotation } from '../../utils/constants'; import { GRAFANA_RULES_SOURCE_NAME, getRulesSourceName } from '../../utils/datasource'; -import { getRulePluginOrigin, isGrafanaRulerRule, isGrafanaRulerRulePaused } from '../../utils/rules'; +import { getRulePluginOrigin, isPausedRule, rulerRuleType } from '../../utils/rules'; import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; import { DynamicTableWithGuidelines } from '../DynamicTableWithGuidelines'; import { ProvisioningBadge } from '../Provisioning'; @@ -219,7 +219,7 @@ function useColumns( return ; } - const isGrafanaManagedRule = isGrafanaRulerRule(rulerRule); + const isGrafanaManagedRule = rulerRuleType.grafana.rule(rulerRule); if (!isGrafanaManagedRule) { return null; } @@ -347,13 +347,13 @@ function useRuleStatus(rule: CombinedRule) { // If prometheusRulesPrimary is enabled, we don't fetch rules from the Ruler API (except for Grafana managed rules) // so there is no way to detect statuses - if (prometheusRulesPrimary && !isGrafanaRulerRule(rulerRule)) { + if (prometheusRulesPrimary && !rulerRuleType.grafana.rule(rulerRule)) { return { isDeleting: false, isCreating: false, isPaused: false }; } const isDeleting = Boolean(hasRuler && rulerRulesLoaded && promRule && !rulerRule); const isCreating = Boolean(hasRuler && rulerRulesLoaded && rulerRule && !promRule); - const isPaused = isGrafanaRulerRule(rulerRule) && isGrafanaRulerRulePaused(rulerRule); + const isPaused = rulerRuleType.grafana.alertingRule(rulerRule) && isPausedRule(rulerRule); return { isDeleting, isCreating, isPaused }; } diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/useUpsertRuleFromRuleGroup.ts b/public/app/features/alerting/unified/hooks/ruleGroup/useUpsertRuleFromRuleGroup.ts index 7d739837955..d3a68a4c095 100644 --- a/public/app/features/alerting/unified/hooks/ruleGroup/useUpsertRuleFromRuleGroup.ts +++ b/public/app/features/alerting/unified/hooks/ruleGroup/useUpsertRuleFromRuleGroup.ts @@ -7,7 +7,7 @@ import { PostableRuleDTO } from 'app/types/unified-alerting-dto'; import { alertRuleApi } from '../../api/alertRuleApi'; import { addRuleAction, updateRuleAction } from '../../reducers/ruler/ruleGroups'; -import { isGrafanaRuleIdentifier, isGrafanaRulerRule } from '../../utils/rules'; +import { isGrafanaRuleIdentifier, rulerRuleType } from '../../utils/rules'; import { useAsync } from '../useAsync'; import { useDeleteRuleFromGroup } from './useDeleteRuleFromGroup'; @@ -132,7 +132,7 @@ function copyGrafanaUID(ruleIdentifier: EditableRuleIdentifier, ruleDefinition: // by copying over the rule UID the backend will perform an atomic move operation // so there is no need for us to manually remove it from the previous group return produce(ruleDefinition, (draft) => { - const isGrafanaManagedRuleDefinition = isGrafanaRulerRule(draft); + const isGrafanaManagedRuleDefinition = rulerRuleType.grafana.rule(draft); if (isGrafanaManagedRuleIdentifier && isGrafanaManagedRuleDefinition) { draft.grafana_alert.uid = ruleIdentifier.uid; diff --git a/public/app/features/alerting/unified/hooks/useAbilities.ts b/public/app/features/alerting/unified/hooks/useAbilities.ts index 8bace1f93e9..098913b2375 100644 --- a/public/app/features/alerting/unified/hooks/useAbilities.ts +++ b/public/app/features/alerting/unified/hooks/useAbilities.ts @@ -22,7 +22,7 @@ import { getInstancesPermissions, getNotificationsPermissions, getRulesPermissio import { getRulesSourceName } from '../utils/datasource'; import { getGroupOriginName } from '../utils/groupIdentifier'; import { isAdmin } from '../utils/misc'; -import { isFederatedRuleGroup, isGrafanaRecordingRule, isGrafanaRulerRule, isPluginProvidedRule } from '../utils/rules'; +import { isFederatedRuleGroup, isPluginProvidedRule, rulerRuleType } from '../utils/rules'; import { useIsRuleEditable } from './useIsRuleEditable'; @@ -206,9 +206,10 @@ export function useAllAlertRuleAbilities(rule: CombinedRule): Abilities>(() => { - const isProvisioned = isGrafanaRulerRule(rule.rulerRule) && Boolean(rule.rulerRule.grafana_alert.provenance); + const isProvisioned = + rulerRuleType.grafana.rule(rule.rulerRule) && Boolean(rule.rulerRule.grafana_alert.provenance); const isFederated = isFederatedRuleGroup(rule.group); - const isGrafanaManagedAlertRule = isGrafanaRulerRule(rule.rulerRule); + const isGrafanaManagedAlertRule = rulerRuleType.grafana.rule(rule.rulerRule); const isPluginProvided = isPluginProvidedRule(rule.rulerRule); // if a rule is either provisioned, federated or provided by a plugin rule, we don't allow it to be removed or edited @@ -252,10 +253,10 @@ export function useAllRulerRuleAbilities( const canSilence = useCanSilence(rule); const abilities = useMemo>(() => { - const isProvisioned = isGrafanaRulerRule(rule) && Boolean(rule.grafana_alert.provenance); + const isProvisioned = rulerRuleType.grafana.rule(rule) && Boolean(rule.grafana_alert.provenance); // const isFederated = isFederatedRuleGroup(); const isFederated = false; - const isGrafanaManagedAlertRule = isGrafanaRulerRule(rule); + const isGrafanaManagedAlertRule = rulerRuleType.grafana.rule(rule); const isPluginProvided = isPluginProvidedRule(rule); // if a rule is either provisioned, federated or provided by a plugin rule, we don't allow it to be removed or edited @@ -441,11 +442,11 @@ const { useGetGrafanaAlertingConfigurationStatusQuery } = alertmanagerApi; * 2. the admin has configured to only send instances to external AMs */ function useCanSilence(rule?: RulerRuleDTO): [boolean, boolean] { - const folderUID = isGrafanaRulerRule(rule) ? rule.grafana_alert.namespace_uid : undefined; + const folderUID = rulerRuleType.grafana.rule(rule) ? rule.grafana_alert.namespace_uid : undefined; const { loading: folderIsLoading, folder } = useFolder(folderUID); - const isGrafanaManagedRule = rule && isGrafanaRulerRule(rule); - const isGrafanaRecording = rule && isGrafanaRecordingRule(rule); + const isGrafanaManagedRule = rule && rulerRuleType.grafana.rule(rule); + const isGrafanaRecording = rulerRuleType.grafana.recordingRule(rule); const silenceSupported = useGrafanaRulesSilenceSupport(); const canSilenceInFolder = useCanSilenceInFolder(folderUID); diff --git a/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts index f28bc644ecc..6350144a583 100644 --- a/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts +++ b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts @@ -32,13 +32,7 @@ import { isGrafanaRulesSource, } from '../utils/datasource'; import { hashQuery } from '../utils/rule-id'; -import { - isAlertingRule, - isAlertingRulerRule, - isGrafanaRulerRule, - isRecordingRule, - isRecordingRulerRule, -} from '../utils/rules'; +import { getAnnotations, isPausedRule, prometheusRuleType, rulerRuleType } from '../utils/rules'; import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector'; @@ -98,7 +92,7 @@ export function useCombinedRuleNamespaces( // We need to set the namespace_uid for grafana rules as it's required to obtain the rule's groups // All rules from all groups have the same namespace_uid so we're taking the first one. - if (isGrafanaRulerRule(groups[0].rules[0])) { + if (rulerRuleType.grafana.rule(groups[0].rules[0])) { namespace.uid = groups[0].rules[0].grafana_alert.namespace_uid; } @@ -247,8 +241,10 @@ export function addRulerGroupsToCombinedNamespace( groups: RulerRuleGroupDTO[] = [] ): void { namespace.groups = groups.map((group) => { - const numRecordingRules = group.rules.filter((rule) => isRecordingRulerRule(rule)).length; - const numPaused = group.rules.filter((rule) => isGrafanaRulerRule(rule) && rule.grafana_alert.is_paused).length; + const numRecordingRules = group.rules.filter((rule) => rulerRuleType.any.recordingRule(rule)).length; + const numPaused = group.rules.filter((rule) => { + return rulerRuleType.grafana.alertingRule(rule) && isPausedRule(rule); + }).length; const combinedGroup: CombinedRuleGroup = { name: group.name, @@ -298,8 +294,10 @@ export function addPromGroupsToCombinedNamespace(namespace: CombinedRuleNamespac const existingRule = getExistingRuleInGroup(rule, combinedRulesByName, namespace.rulesSource); if (existingRule) { existingRule.promRule = rule; - existingRule.instanceTotals = isAlertingRule(rule) ? calculateRuleTotals(rule) : {}; - existingRule.filteredInstanceTotals = isAlertingRule(rule) ? calculateRuleFilteredTotals(rule) : {}; + existingRule.instanceTotals = prometheusRuleType.alertingRule(rule) ? calculateRuleTotals(rule) : {}; + existingRule.filteredInstanceTotals = prometheusRuleType.alertingRule(rule) + ? calculateRuleFilteredTotals(rule) + : {}; } else { combinedGroup!.rules.push(promRuleToCombinedRule(rule, namespace, combinedGroup!)); } @@ -344,9 +342,9 @@ export function calculateGroupTotals(group: Pick) }; } - const countsByState = countBy(group.rules, (rule) => isAlertingRule(rule) && rule.state); + const countsByState = countBy(group.rules, (rule) => prometheusRuleType.alertingRule(rule) && rule.state); const countsByHealth = countBy(group.rules, (rule) => rule.health); - const recordingCount = group.rules.filter((rule) => isRecordingRule(rule)).length; + const recordingCount = group.rules.filter((rule) => prometheusRuleType.recordingRule(rule)).length; return { alerting: countsByState[PromAlertingRuleState.Firing], @@ -382,12 +380,12 @@ function promRuleToCombinedRule(rule: Rule, namespace: CombinedRuleNamespace, gr name: rule.name, query: rule.query, labels: rule.labels || {}, - annotations: isAlertingRule(rule) ? rule.annotations || {} : {}, + annotations: prometheusRuleType.alertingRule(rule) ? getAnnotations(rule) : {}, promRule: rule, namespace: namespace, group, - instanceTotals: isAlertingRule(rule) ? calculateRuleTotals(rule) : {}, - filteredInstanceTotals: isAlertingRule(rule) ? calculateRuleFilteredTotals(rule) : {}, + instanceTotals: prometheusRuleType.alertingRule(rule) ? calculateRuleTotals(rule) : {}, + filteredInstanceTotals: prometheusRuleType.alertingRule(rule) ? calculateRuleFilteredTotals(rule) : {}, }; } @@ -396,7 +394,7 @@ function rulerRuleToCombinedRule( namespace: CombinedRuleNamespace, group: CombinedRuleGroup ): CombinedRule { - return isAlertingRulerRule(rule) + return rulerRuleType.dataSource.alertingRule(rule) ? { name: rule.alert, query: rule.expr, @@ -408,7 +406,7 @@ function rulerRuleToCombinedRule( instanceTotals: {}, filteredInstanceTotals: {}, } - : isRecordingRulerRule(rule) + : rulerRuleType.dataSource.recordingRule(rule) ? { name: rule.record, query: rule.expr, @@ -473,10 +471,18 @@ function getExistingRuleInGroup( } function isCombinedRuleEqualToPromRule(combinedRule: CombinedRule, rule: Rule, checkQuery = true): boolean { + const promRuleAnnotations = prometheusRuleType.alertingRule(rule) ? getAnnotations(rule) : {}; + const promRuleLabels = rule.labels ?? {}; + const promQuery = checkQuery ? hashQuery(rule.query) : ''; + + const combinedRuleAnnotations = combinedRule.annotations; + const combinedRuleLabels = combinedRule.labels; + const combinedRuleQuery = checkQuery ? hashQuery(combinedRule.query) : ''; + if (combinedRule.name === rule.name) { return isEqual( - [checkQuery ? hashQuery(combinedRule.query) : '', combinedRule.labels, combinedRule.annotations], - [checkQuery ? hashQuery(rule.query) : '', rule.labels || {}, isAlertingRule(rule) ? rule.annotations || {} : {}] + [combinedRuleQuery, combinedRuleLabels, combinedRuleAnnotations], + [promQuery, promRuleLabels, promRuleAnnotations] ); } return false; @@ -554,7 +560,7 @@ export function useCombinedRules( // We need to set the namespace_uid for grafana rules as it's required to obtain the rule's groups // All rules from all groups have the same namespace_uid so we're taking the first one. - if (isGrafanaRulerRule(groups[0].rules[0])) { + if (rulerRuleType.grafana.rule(groups[0].rules[0])) { namespace.uid = groups[0].rules[0].grafana_alert.namespace_uid; } diff --git a/public/app/features/alerting/unified/hooks/useFilteredRules.ts b/public/app/features/alerting/unified/hooks/useFilteredRules.ts index e4d720cc7cb..c8123de32c4 100644 --- a/public/app/features/alerting/unified/hooks/useFilteredRules.ts +++ b/public/app/features/alerting/unified/hooks/useFilteredRules.ts @@ -14,13 +14,7 @@ import { labelsMatchMatchers, matcherToMatcherField } from '../utils/alertmanage import { Annotation } from '../utils/constants'; import { isCloudRulesSource } from '../utils/datasource'; import { parseMatcher, parsePromQLStyleMatcherLoose } from '../utils/matchers'; -import { - getRuleHealth, - isAlertingRule, - isGrafanaRulerRule, - isPluginProvidedRule, - isPromRuleType, -} from '../utils/rules'; +import { getRuleHealth, isPluginProvidedRule, isPromRuleType, prometheusRuleType, rulerRuleType } from '../utils/rules'; import { calculateGroupTotals, calculateRuleFilteredTotals, calculateRuleTotals } from './useCombinedRuleNamespaces'; import { useURLSearchParams } from './useURLSearchParams'; @@ -106,7 +100,7 @@ export const useFilteredRules = (namespaces: CombinedRuleNamespace[], filterStat filteredRules.forEach((namespace) => { namespace.groups.forEach((group) => { group.rules.forEach((rule) => { - if (isAlertingRule(rule.promRule)) { + if (prometheusRuleType.alertingRule(rule.promRule)) { rule.instanceTotals = calculateRuleTotals(rule.promRule); rule.filteredInstanceTotals = calculateRuleFilteredTotals(rule.promRule); } @@ -225,7 +219,7 @@ const reduceGroups = (filterState: RulesFilter) => { if ('contactPoint' in matchesFilterFor) { const contactPoint = filterState.contactPoint; const hasContactPoint = - isGrafanaRulerRule(rule.rulerRule) && + rulerRuleType.grafana.rule(rule.rulerRule) && rule.rulerRule.grafana_alert.notification_settings?.receiver === contactPoint; if (hasContactPoint) { @@ -234,7 +228,7 @@ const reduceGroups = (filterState: RulesFilter) => { } if ('dataSourceNames' in matchesFilterFor) { - if (isGrafanaRulerRule(rule.rulerRule)) { + if (rulerRuleType.grafana.rule(rule.rulerRule)) { const doesNotQueryDs = isQueryingDataSource(rule.rulerRule, filterState); if (doesNotQueryDs) { @@ -274,7 +268,7 @@ const reduceGroups = (filterState: RulesFilter) => { if ('ruleState' in matchesFilterFor) { const promRule = rule.promRule; - const hasPromRuleDefinition = promRule && isAlertingRule(promRule); + const hasPromRuleDefinition = promRule && prometheusRuleType.alertingRule(promRule); const ruleStateMatches = hasPromRuleDefinition && promRule.state === filterState.ruleState; diff --git a/public/app/features/alerting/unified/hooks/useIsRuleEditable.ts b/public/app/features/alerting/unified/hooks/useIsRuleEditable.ts index 017e008c02c..59211520d47 100644 --- a/public/app/features/alerting/unified/hooks/useIsRuleEditable.ts +++ b/public/app/features/alerting/unified/hooks/useIsRuleEditable.ts @@ -3,7 +3,7 @@ import { RulerRuleDTO } from 'app/types/unified-alerting-dto'; import { featureDiscoveryApi } from '../api/featureDiscoveryApi'; import { getRulesPermissions } from '../utils/access-control'; -import { isGrafanaRulerRule } from '../utils/rules'; +import { rulerRuleType } from '../utils/rules'; import { useFolder } from './useFolder'; @@ -24,7 +24,7 @@ export function useIsRuleEditable(rulesSourceName: string, rule?: RulerRuleDTO): rulesSourceName, }); - const folderUID = rule && isGrafanaRulerRule(rule) ? rule.grafana_alert.namespace_uid : undefined; + const folderUID = rule && rulerRuleType.grafana.rule(rule) ? rule.grafana_alert.namespace_uid : undefined; const rulePermission = getRulesPermissions(rulesSourceName); const { folder, loading } = useFolder(folderUID); @@ -47,7 +47,7 @@ export function useIsRuleEditable(rulesSourceName: string, rule?: RulerRuleDTO): // Grafana rules can be edited if user can edit the folder they're in // When RBAC is disabled access to a folder is the only requirement for managing rules // When RBAC is enabled the appropriate alerting permissions need to be met - if (isGrafanaRulerRule(rule)) { + if (rulerRuleType.grafana.rule(rule)) { if (!folderUID) { throw new Error( `Rule ${rule.grafana_alert.title} does not have a folder uid, cannot determine if it is editable.` diff --git a/public/app/features/alerting/unified/reducers/ruler/ruleGroups.ts b/public/app/features/alerting/unified/reducers/ruler/ruleGroups.ts index 41d7c42c889..b8d91042b2c 100644 --- a/public/app/features/alerting/unified/reducers/ruler/ruleGroups.ts +++ b/public/app/features/alerting/unified/reducers/ruler/ruleGroups.ts @@ -6,12 +6,7 @@ import { PostableRuleDTO, PostableRulerRuleGroupDTO } from 'app/types/unified-al import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; import { hashRulerRule } from '../../utils/rule-id'; -import { - isCloudRuleIdentifier, - isCloudRulerRule, - isGrafanaRuleIdentifier, - isGrafanaRulerRule, -} from '../../utils/rules'; +import { isCloudRuleIdentifier, isGrafanaRuleIdentifier, rulerRuleType } from '../../utils/rules'; // rule-scoped actions export const addRuleAction = createAction<{ rule: PostableRuleDTO; groupName?: string; interval?: string }>( @@ -61,7 +56,7 @@ export const ruleGroupReducer = createReducer(initialState, (builder) => { const index = findRuleIndex(draft.rules, identifier); const matchingRule = draft.rules[index]; - if (isGrafanaRulerRule(matchingRule)) { + if (rulerRuleType.grafana.rule(matchingRule)) { matchingRule.grafana_alert.is_paused = pause; } else { throw new Error('Matching rule is not a Grafana-managed rule'); @@ -98,8 +93,8 @@ const ruleFinder = (identifier: RuleIdentifier) => { const dataSourceManagedIdentifier = isCloudRuleIdentifier(identifier); return (rule: PostableRuleDTO) => { - const isGrafanaManagedRule = isGrafanaRulerRule(rule); - const isDataSourceManagedRule = isCloudRulerRule(rule); + const isGrafanaManagedRule = rulerRuleType.grafana.rule(rule); + const isDataSourceManagedRule = rulerRuleType.dataSource.rule(rule); if (grafanaManagedIdentifier && isGrafanaManagedRule) { return rule.grafana_alert.uid === identifier.uid; diff --git a/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.tsx b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.tsx index e92ad4b46e6..c8da82711dc 100644 --- a/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.tsx +++ b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.tsx @@ -10,7 +10,7 @@ import { useRuleWithLocation } from '../hooks/useCombinedRule'; import { generateCopiedName } from '../utils/duplicate'; import { stringifyErrorLike } from '../utils/misc'; import { rulerRuleToFormValues } from '../utils/rule-form'; -import { getRuleName, isAlertingRulerRule, isGrafanaRulerRule, isRecordingRulerRule } from '../utils/rules'; +import { getRuleName, rulerRuleType } from '../utils/rules'; import { createRelativeUrl } from '../utils/url'; export function CloneRuleEditor({ sourceRuleId }: { sourceRuleId: RuleIdentifier }) { @@ -45,14 +45,14 @@ export function CloneRuleEditor({ sourceRuleId }: { sourceRuleId: RuleIdentifier } function changeRuleName(rule: RulerRuleDTO, newName: string) { - if (isGrafanaRulerRule(rule)) { + if (rulerRuleType.grafana.rule(rule)) { rule.grafana_alert.title = newName; } - if (isAlertingRulerRule(rule)) { + if (rulerRuleType.dataSource.alertingRule(rule)) { rule.alert = newName; } - if (isRecordingRulerRule(rule)) { + if (rulerRuleType.dataSource.recordingRule(rule)) { rule.record = newName; } } @@ -64,7 +64,7 @@ export function cloneRuleDefinition(rule: RuleWithLocation) { generateCopiedName(getRuleName(ruleClone.rule), ruleClone.group.rules.map(getRuleName)) ); - if (isGrafanaRulerRule(ruleClone.rule)) { + if (rulerRuleType.grafana.rule(ruleClone.rule)) { ruleClone.rule.grafana_alert.uid = ''; // Provisioned alert rules have provisioned alert group which cannot be used in UI diff --git a/public/app/features/alerting/unified/rule-list/DataSourceRuleLoader.tsx b/public/app/features/alerting/unified/rule-list/DataSourceRuleLoader.tsx index d041b42566d..95edc6db6f6 100644 --- a/public/app/features/alerting/unified/rule-list/DataSourceRuleLoader.tsx +++ b/public/app/features/alerting/unified/rule-list/DataSourceRuleLoader.tsx @@ -5,7 +5,7 @@ import { DataSourceRuleGroupIdentifier, Rule, RuleIdentifier } from 'app/types/u import { alertRuleApi } from '../api/alertRuleApi'; import { featureDiscoveryApi } from '../api/featureDiscoveryApi'; import { equal, fromRule, fromRulerRule, stringifyIdentifier } from '../utils/rule-id'; -import { getRulePluginOrigin, isAlertingRule, isRecordingRule } from '../utils/rules'; +import { getRulePluginOrigin, prometheusRuleType } from '../utils/rules'; import { createRelativeUrl } from '../utils/url'; import { AlertRuleListItem, RecordingRuleListItem, UnknownRuleListItem } from './components/AlertRuleListItem'; @@ -74,7 +74,7 @@ export const DataSourceRuleLoader = memo(function DataSourceRuleLoader({ return null; }, [groupIdentifier, isLoading, rule, rulerRule]); - if (isAlertingRule(rule)) { + if (prometheusRuleType.alertingRule(rule)) { return ( { // We might hit edge cases where there type = alerting, but there is no state. // In this case, we shouldn't try to group these alerts in the state view // Even though we handle this at the API layer, this is a last catch point for any edge cases - if (rule.promRule && isAlertingRule(rule.promRule) && rule.promRule.state) { + if (prometheusRuleType.alertingRule(rule.promRule) && rule.promRule.state) { result.get(rule.promRule.state)?.push(rule); } }) @@ -97,8 +97,10 @@ const RulesByState = ({ state, rules }: { state: PromAlertingRuleState; rules: C {pageItems.map((rule) => { const { rulerRule, promRule } = rule; - const isProvisioned = isGrafanaRulerRule(rulerRule) && Boolean(rulerRule.grafana_alert.provenance); - const instancesCount = isAlertingRule(rule.promRule) ? calculateTotalInstances(rule.instanceTotals) : undefined; + const isProvisioned = rulerRuleType.grafana.rule(rulerRule) && Boolean(rulerRule.grafana_alert.provenance); + const instancesCount = prometheusRuleType.alertingRule(rule.promRule) + ? calculateTotalInstances(rule.instanceTotals) + : undefined; const groupId = groupIdentifier.fromCombinedRule(rule); if (!promRule) { diff --git a/public/app/features/alerting/unified/rule-list/components/RuleActionsButtons.V2.tsx b/public/app/features/alerting/unified/rule-list/components/RuleActionsButtons.V2.tsx index 5c27ea48476..21199ce1523 100644 --- a/public/app/features/alerting/unified/rule-list/components/RuleActionsButtons.V2.tsx +++ b/public/app/features/alerting/unified/rule-list/components/RuleActionsButtons.V2.tsx @@ -11,7 +11,7 @@ import { RulerRuleDTO } from 'app/types/unified-alerting-dto'; import { AlertRuleAction, useRulerRuleAbility } from '../../hooks/useAbilities'; import * as ruleId from '../../utils/rule-id'; -import { isGrafanaAlertingRule, isGrafanaRulerRule } from '../../utils/rules'; +import { isProvisionedRule, rulerRuleType } from '../../utils/rules'; import { createRelativeUrl } from '../../utils/url'; interface Props { @@ -37,7 +37,7 @@ export function RuleActionsButtons({ compact, rule, promRule, groupIdentifier }: { identifier: RuleIdentifier; isProvisioned: boolean } | undefined >(undefined); - const isProvisioned = isGrafanaRulerRule(rule) && Boolean(rule.grafana_alert.provenance); + const isProvisioned = isProvisionedRule(rule); const [editRuleSupported, editRuleAllowed] = useRulerRuleAbility(rule, groupIdentifier, AlertRuleAction.Update); @@ -72,7 +72,7 @@ export function RuleActionsButtons({ compact, rule, promRule, groupIdentifier }: handleDuplicateRule={() => setRedirectToClone({ identifier, isProvisioned })} /> {deleteModal} - {isGrafanaAlertingRule(rule) && showSilenceDrawer && ( + {rulerRuleType.grafana.alertingRule(rule) && showSilenceDrawer && ( setShowSilenceDrawer(false)} /> )} {redirectToClone?.identifier && ( diff --git a/public/app/features/alerting/unified/rule-list/components/RuleGroup.tsx b/public/app/features/alerting/unified/rule-list/components/RuleGroup.tsx index 42c12a08c59..4020917fca5 100644 --- a/public/app/features/alerting/unified/rule-list/components/RuleGroup.tsx +++ b/public/app/features/alerting/unified/rule-list/components/RuleGroup.tsx @@ -7,7 +7,8 @@ import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; import { alertRuleApi } from '../../api/alertRuleApi'; import { usePagination } from '../../hooks/usePagination'; -import { isAlertingRule } from '../../utils/rules'; +import { Annotation } from '../../utils/constants'; +import { prometheusRuleType } from '../../utils/rules'; import { AlertRuleListItem } from './AlertRuleListItem'; import { EvaluationGroup } from './EvaluationGroup'; @@ -65,7 +66,7 @@ export const EvaluationGroupLoader = ({ state={PromAlertingRuleState.Inactive} name={rule.name} href={'/'} - summary={isAlertingRule(rule) ? rule.annotations?.summary : undefined} + summary={prometheusRuleType.alertingRule(rule) ? rule.annotations?.[Annotation.summary] : undefined} />; return null; diff --git a/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts b/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts index 6512ddcea18..01ec7fe7bcf 100644 --- a/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts +++ b/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts @@ -21,7 +21,7 @@ import { labelsMatchMatchers } from '../../utils/alertmanager'; import { Annotation } from '../../utils/constants'; import { getDatasourceAPIUid, getExternalRulesSources } from '../../utils/datasource'; import { parseMatcher } from '../../utils/matchers'; -import { isAlertingRule } from '../../utils/rules'; +import { prometheusRuleType } from '../../utils/rules'; import { useGrafanaGroupsGenerator, usePrometheusGroupsGenerator } from './prometheusGroupsGenerator'; @@ -168,7 +168,7 @@ function ruleFilter(rule: PromRuleDTO, filterState: RulesFilter) { } if (filterState.ruleState) { - if (!isAlertingRule(rule)) { + if (!prometheusRuleType.alertingRule(rule)) { return false; } if (rule.state !== filterState.ruleState) { diff --git a/public/app/features/alerting/unified/utils/groupIdentifier.ts b/public/app/features/alerting/unified/utils/groupIdentifier.ts index 61ae10d49ab..aa0701c7767 100644 --- a/public/app/features/alerting/unified/utils/groupIdentifier.ts +++ b/public/app/features/alerting/unified/utils/groupIdentifier.ts @@ -1,10 +1,10 @@ import { CombinedRule, RuleGroupIdentifier, RuleGroupIdentifierV2 } from 'app/types/unified-alerting'; import { GRAFANA_RULES_SOURCE_NAME, getDatasourceAPIUid, getRulesSourceName, isGrafanaRulesSource } from './datasource'; -import { isGrafanaRulerRule } from './rules'; +import { rulerRuleType } from './rules'; function fromCombinedRule(rule: CombinedRule): RuleGroupIdentifierV2 { - if (isGrafanaRulerRule(rule.rulerRule) && isGrafanaRulesSource(rule.namespace.rulesSource)) { + if (rulerRuleType.grafana.rule(rule.rulerRule) && isGrafanaRulesSource(rule.namespace.rulesSource)) { return { namespace: { uid: rule.rulerRule.grafana_alert.namespace_uid }, groupName: rule.group.name, diff --git a/public/app/features/alerting/unified/utils/query.ts b/public/app/features/alerting/unified/utils/query.ts index cebef12e2e8..dc40ca3534e 100644 --- a/public/app/features/alerting/unified/utils/query.ts +++ b/public/app/features/alerting/unified/utils/query.ts @@ -8,7 +8,7 @@ import { CombinedRule } from 'app/types/unified-alerting'; import { AlertQuery } from 'app/types/unified-alerting-dto'; import { isCloudRulesSource } from './datasource'; -import { isGrafanaRulerRule } from './rules'; +import { rulerRuleType } from './rules'; import { safeParsePrometheusDuration } from './time'; export function alertRuleToQueries(combinedRule: CombinedRule | undefined | null): AlertQuery[] { @@ -18,7 +18,7 @@ export function alertRuleToQueries(combinedRule: CombinedRule | undefined | null const { namespace, rulerRule } = combinedRule; const { rulesSource } = namespace; - if (isGrafanaRulerRule(rulerRule)) { + if (rulerRuleType.grafana.rule(rulerRule)) { const query = rulerRule.grafana_alert.data; return widenRelativeTimeRanges(query, rulerRule.for ?? '', combinedRule.group.interval); } diff --git a/public/app/features/alerting/unified/utils/rule-form.ts b/public/app/features/alerting/unified/utils/rule-form.ts index ecea384f9e7..d9e1130736e 100644 --- a/public/app/features/alerting/unified/utils/rule-form.ts +++ b/public/app/features/alerting/unified/utils/rule-form.ts @@ -58,14 +58,7 @@ import { isGrafanaRulesSource, } from './datasource'; import { arrayToRecord, recordToArray } from './misc'; -import { - isAlertingRulerRule, - isGrafanaAlertingRuleByType, - isGrafanaRecordingRule, - isGrafanaRecordingRuleByType, - isGrafanaRulerRule, - isRecordingRulerRule, -} from './rules'; +import { isGrafanaAlertingRuleByType, isGrafanaRecordingRuleByType, rulerRuleType } from './rules'; import { parseInterval } from './time'; export type PromOrLokiQuery = PromQuery | LokiQuery; @@ -275,7 +268,7 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF const defaultFormValues = getDefaultFormValues(); if (isGrafanaRulesSource(ruleSourceName)) { // GRAFANA-MANAGED RULES - if (isGrafanaRecordingRule(rule)) { + if (rulerRuleType.grafana.recordingRule(rule)) { // grafana recording rule const ga = rule.grafana_alert; return { @@ -292,7 +285,7 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF isPaused: ga.is_paused, metric: ga.record?.metric, }; - } else if (isGrafanaRulerRule(rule)) { + } else if (rulerRuleType.grafana.rule(rule)) { // grafana alerting rule const ga = rule.grafana_alert; const routingSettings: AlertManagerManualRouting | undefined = getContactPointsFromDTO(ga); @@ -326,7 +319,7 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF } } else { // DATASOURCE-MANAGED RULES - if (isAlertingRulerRule(rule)) { + if (rulerRuleType.dataSource.alertingRule(rule)) { const datasourceUid = getDataSourceSrv().getInstanceSettings(ruleSourceName)?.uid ?? ''; const defaultQuery = { @@ -354,7 +347,7 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF namespace, group: group.name, }; - } else if (isRecordingRulerRule(rule)) { + } else if (rulerRuleType.dataSource.recordingRule(rule)) { const recordingRuleValues = recordingRulerRuleToRuleForm(rule); return { diff --git a/public/app/features/alerting/unified/utils/rule-id.ts b/public/app/features/alerting/unified/utils/rule-id.ts index 747cf852db2..cfe1c288543 100644 --- a/public/app/features/alerting/unified/utils/rule-id.ts +++ b/public/app/features/alerting/unified/utils/rule-id.ts @@ -18,14 +18,12 @@ import { shouldUsePrometheusRulesPrimary } from '../featureToggles'; import { GRAFANA_RULES_SOURCE_NAME } from './datasource'; import { - isAlertingRule, - isAlertingRulerRule, + getRuleName, isCloudRuleIdentifier, isGrafanaRuleIdentifier, - isGrafanaRulerRule, isPrometheusRuleIdentifier, - isRecordingRule, - isRecordingRulerRule, + prometheusRuleType, + rulerRuleType, } from './rules'; export function fromRulerRule( @@ -34,14 +32,14 @@ export function fromRulerRule( groupName: string, rule: RulerRuleDTO ): EditableRuleIdentifier { - if (isGrafanaRulerRule(rule)) { + if (rulerRuleType.grafana.rule(rule)) { return { uid: rule.grafana_alert.uid!, ruleSourceName: 'grafana' }; } return { ruleSourceName, namespace, groupName, - ruleName: isAlertingRulerRule(rule) ? rule.alert : rule.record, + ruleName: getRuleName(rule), rulerRuleHash: hashRulerRule(rule), } satisfies CloudRuleIdentifier; } @@ -51,7 +49,7 @@ export function fromRulerRuleAndGroupIdentifierV2( rule: RulerRuleDTO ): EditableRuleIdentifier { if (ruleGroup.groupOrigin === 'grafana') { - if (isGrafanaRulerRule(rule)) { + if (rulerRuleType.grafana.rule(rule)) { return { uid: rule.grafana_alert.uid, ruleSourceName: 'grafana' }; } logError(new Error('Rule is not a Grafana Ruler rule')); @@ -260,7 +258,7 @@ export function hash(value: string): number { // this is used to identify rules, mimir / loki rules do not have a unique identifier export function hashRulerRule(rule: RulerRuleDTO): string { - if (isGrafanaRulerRule(rule)) { + if (rulerRuleType.grafana.rule(rule)) { return rule.grafana_alert.uid; } @@ -276,10 +274,10 @@ function getRulerRuleFingerprint(rule: RulerCloudRuleDTO) { const queryHash = prometheusRulesPrimary ? '' : hashQuery(rule.expr); const labelsHash = hashLabelsOrAnnotations(rule.labels); - if (isRecordingRulerRule(rule)) { + if (rulerRuleType.dataSource.recordingRule(rule)) { return [rule.record, PromRuleType.Recording, queryHash, labelsHash]; } - if (isAlertingRulerRule(rule)) { + if (rulerRuleType.dataSource.alertingRule(rule)) { return [rule.alert, PromRuleType.Alerting, queryHash, hashLabelsOrAnnotations(rule.annotations), labelsHash]; } throw new Error('Only recording and alerting ruler rules can be hashed'); @@ -296,10 +294,10 @@ function getPromRuleFingerprint(rule: Rule) { const queryHash = prometheusRulesPrimary ? '' : hashQuery(rule.query); const labelsHash = hashLabelsOrAnnotations(rule.labels); - if (isRecordingRule(rule)) { + if (prometheusRuleType.recordingRule(rule)) { return [rule.name, PromRuleType.Recording, queryHash, labelsHash]; } - if (isAlertingRule(rule)) { + if (prometheusRuleType.alertingRule(rule)) { return [rule.name, PromRuleType.Alerting, queryHash, hashLabelsOrAnnotations(rule.annotations), labelsHash]; } throw new Error('Only recording and alerting rules can be hashed'); diff --git a/public/app/features/alerting/unified/utils/rules.ts b/public/app/features/alerting/unified/utils/rules.ts index ad6ffc15a3f..001ecf36d71 100644 --- a/public/app/features/alerting/unified/utils/rules.ts +++ b/public/app/features/alerting/unified/utils/rules.ts @@ -25,6 +25,8 @@ import { Annotations, GrafanaAlertState, GrafanaAlertStateWithReason, + GrafanaAlertingRuleDefinition, + GrafanaRecordingRuleDefinition, PostableRuleDTO, PromAlertingRuleState, PromRuleType, @@ -47,49 +49,71 @@ import { GRAFANA_ORIGIN_LABEL } from './labels'; import { AsyncRequestState } from './redux'; import { formatPrometheusDuration, safeParsePrometheusDuration } from './time'; -export function isAlertingRule(rule: Rule | undefined): rule is AlertingRule { - return typeof rule === 'object' && rule.type === PromRuleType.Alerting; -} +/* Grafana managed rules */ -export function isRecordingRule(rule: Rule | undefined): rule is RecordingRule { - return typeof rule === 'object' && rule.type === PromRuleType.Recording; -} - -export function isAlertingRulerRule(rule?: RulerRuleDTO): rule is RulerAlertingRuleDTO { - return typeof rule === 'object' && 'alert' in rule; -} - -export function isRecordingRulerRule(rule?: RulerRuleDTO): rule is RulerRecordingRuleDTO { - return typeof rule === 'object' && 'record' in rule; -} - -export function isGrafanaOrDataSourceRecordingRule(rule?: RulerRuleDTO) { - return ( - (typeof rule === 'object' && isRecordingRulerRule(rule)) || - (isGrafanaRulerRule(rule) && 'record' in rule.grafana_alert) - ); -} - -export function isGrafanaRecordingRule(rule?: RulerRuleDTO): rule is RulerGrafanaRuleDTO { - return typeof rule === 'object' && isGrafanaOrDataSourceRecordingRule(rule) && isGrafanaRulerRule(rule); -} - -export function isGrafanaAlertingRule(rule?: RulerRuleDTO): rule is RulerGrafanaRuleDTO { - return typeof rule === 'object' && isGrafanaRulerRule(rule) && !isGrafanaOrDataSourceRecordingRule(rule); -} - -export function isGrafanaRulerRule(rule?: RulerRuleDTO | PostableRuleDTO): rule is RulerGrafanaRuleDTO { +function isGrafanaRulerRule(rule?: RulerRuleDTO | PostableRuleDTO): rule is RulerGrafanaRuleDTO { return typeof rule === 'object' && 'grafana_alert' in rule; } -export function isCloudRulerRule(rule?: RulerRuleDTO | PostableRuleDTO): rule is RulerCloudRuleDTO { +function isGrafanaAlertingRule(rule?: RulerRuleDTO): rule is RulerGrafanaRuleDTO { + return isGrafanaRulerRule(rule) && !isGrafanaRecordingRule(rule); +} + +function isGrafanaRecordingRule(rule?: RulerRuleDTO): rule is RulerGrafanaRuleDTO { + return isGrafanaRulerRule(rule) && 'record' in rule.grafana_alert; +} + +export function isPausedRule(rule: RulerGrafanaRuleDTO) { + return Boolean(rule.grafana_alert.is_paused); +} + +/* Data source managed rules */ + +function isAlertingRulerRule(rule?: RulerRuleDTO): rule is RulerAlertingRuleDTO { + return typeof rule === 'object' && 'alert' in rule; +} + +function isCloudRulerRule(rule?: RulerRuleDTO | PostableRuleDTO): rule is RulerCloudRuleDTO { return typeof rule === 'object' && !isGrafanaRulerRule(rule); } -export function isGrafanaRulerRulePaused(rule: RulerGrafanaRuleDTO) { - return rule && isGrafanaRulerRule(rule) && Boolean(rule.grafana_alert.is_paused); +function isCloudRecordingRulerRule(rule?: RulerRuleDTO): rule is RulerRecordingRuleDTO { + return typeof rule === 'object' && 'record' in rule; } +/* Prometheus rules */ + +function isAlertingRule(rule?: Rule): rule is AlertingRule { + return typeof rule === 'object' && rule.type === PromRuleType.Alerting; +} + +function isRecordingRule(rule?: Rule): rule is RecordingRule { + return typeof rule === 'object' && rule.type === PromRuleType.Recording; +} + +export const rulerRuleType = { + grafana: { + rule: isGrafanaRulerRule, + alertingRule: isGrafanaAlertingRule, + recordingRule: isGrafanaRecordingRule, + }, + dataSource: { + rule: isCloudRulerRule, + alertingRule: isAlertingRulerRule, + recordingRule: isCloudRecordingRulerRule, + }, + any: { + recordingRule: (rule?: RulerRuleDTO) => isCloudRecordingRulerRule(rule) || isGrafanaRecordingRule(rule), + alertingRule: (rule?: RulerRuleDTO) => isAlertingRulerRule(rule) || isGrafanaAlertingRule(rule), + }, +}; + +export const prometheusRuleType = { + rule: (rule?: Rule) => isAlertingRule(rule) || isRecordingRule(rule), + alertingRule: isAlertingRule, + recordingRule: isRecordingRule, +}; + export function alertInstanceKey(alert: Alert): string { return JSON.stringify(alert.labels); } @@ -139,11 +163,7 @@ export function getRuleHealth(health: string): RuleHealth | undefined { } export function getPendingPeriod(rule: CombinedRule): string | undefined { - if ( - isRecordingRulerRule(rule.rulerRule) || - isRecordingRule(rule.promRule) || - isGrafanaRecordingRule(rule.rulerRule) - ) { + if (rulerRuleType.any.recordingRule(rule.rulerRule)) { return undefined; } @@ -162,16 +182,10 @@ export function getPendingPeriod(rule: CombinedRule): string | undefined { return undefined; } -export function getAnnotations(rule: CombinedRule): Annotations | undefined { - if ( - isRecordingRulerRule(rule.rulerRule) || - isRecordingRule(rule.promRule) || - isGrafanaRecordingRule(rule.rulerRule) - ) { - return undefined; - } - return rule.annotations ?? []; +export function getAnnotations(rule?: AlertingRule): Annotations { + return rule?.annotations ?? {}; } + export interface RulePluginOrigin { pluginId: string; } @@ -300,15 +314,16 @@ export function isFederatedRuleGroup(group: CombinedRuleGroup) { return Array.isArray(group.source_tenants); } -export function getRuleName(rule: RulerRuleDTO) { - if (isGrafanaRulerRule(rule)) { +export function getRuleName(rule: RulerRuleDTO): string { + if (rulerRuleType.grafana.rule(rule)) { return rule.grafana_alert.title; } - if (isAlertingRulerRule(rule)) { + + if (rulerRuleType.dataSource.alertingRule(rule)) { return rule.alert; } - if (isRecordingRulerRule(rule)) { + if (rulerRuleType.dataSource.recordingRule(rule)) { return rule.record; } diff --git a/public/app/features/dashboard-scene/scene/AlertStatesDataLayer.ts b/public/app/features/dashboard-scene/scene/AlertStatesDataLayer.ts index 54f0a7264f8..643ea0ac56f 100644 --- a/public/app/features/dashboard-scene/scene/AlertStatesDataLayer.ts +++ b/public/app/features/dashboard-scene/scene/AlertStatesDataLayer.ts @@ -17,7 +17,7 @@ import { alertRuleApi } from 'app/features/alerting/unified/api/alertRuleApi'; import { ungroupRulesByFileName } from 'app/features/alerting/unified/api/prometheus'; import { Annotation } from 'app/features/alerting/unified/utils/constants'; import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; -import { isAlertingRule } from 'app/features/alerting/unified/utils/rules'; +import { prometheusRuleType } from 'app/features/alerting/unified/utils/rules'; import { dispatch } from 'app/store/store'; import { AccessControlAction } from 'app/types'; import { RuleNamespace } from 'app/types/unified-alerting'; @@ -96,7 +96,7 @@ export class AlertStatesDataLayer const panelIdToAlertState: Record = {}; groups.forEach((group) => group.rules.forEach((rule) => { - if (isAlertingRule(rule) && rule.annotations && rule.annotations[Annotation.panelID]) { + if (prometheusRuleType.alertingRule(rule) && rule.annotations?.[Annotation.panelID]) { this.hasAlertRules = true; const panelId = Number(rule.annotations[Annotation.panelID]); const state = promAlertStateToAlertState(rule.state); diff --git a/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.ts b/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.ts index 8e352896e54..18c001c983c 100644 --- a/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.ts +++ b/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.ts @@ -8,7 +8,7 @@ import { alertRuleApi } from 'app/features/alerting/unified/api/alertRuleApi'; import { ungroupRulesByFileName } from 'app/features/alerting/unified/api/prometheus'; import { Annotation } from 'app/features/alerting/unified/utils/constants'; import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; -import { isAlertingRule } from 'app/features/alerting/unified/utils/rules'; +import { prometheusRuleType } from 'app/features/alerting/unified/utils/rules'; import { promAlertStateToAlertState } from 'app/features/dashboard-scene/scene/AlertStatesDataLayer'; import { dispatch } from 'app/store/store'; import { AccessControlAction } from 'app/types'; @@ -82,7 +82,7 @@ export class UnifiedAlertStatesWorker implements DashboardQueryRunnerWorker { const panelIdToAlertState: Record = {}; groups.forEach((group) => group.rules.forEach((rule) => { - if (isAlertingRule(rule) && rule.annotations && rule.annotations[Annotation.panelID]) { + if (prometheusRuleType.alertingRule(rule) && rule.annotations && rule.annotations[Annotation.panelID]) { this.hasAlertRules[dashboard.uid] = true; const panelId = Number(rule.annotations[Annotation.panelID]); const state = promAlertStateToAlertState(rule.state); diff --git a/public/app/plugins/panel/alertlist/unified-alerting/UngroupedView.tsx b/public/app/plugins/panel/alertlist/unified-alerting/UngroupedView.tsx index 6b43f91b632..35a58263e22 100644 --- a/public/app/plugins/panel/alertlist/unified-alerting/UngroupedView.tsx +++ b/public/app/plugins/panel/alertlist/unified-alerting/UngroupedView.tsx @@ -10,13 +10,13 @@ import { alertStateToReadable, alertStateToState, getFirstActiveAt, - isAlertingRule, + prometheusRuleType, } from 'app/features/alerting/unified/utils/rules'; import { createRelativeUrl } from 'app/features/alerting/unified/utils/url'; import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; import { GRAFANA_RULES_SOURCE_NAME } from '../../../../features/alerting/unified/utils/datasource'; -import { AlertInstanceTotalState, AlertingRule, CombinedRuleWithLocation } from '../../../../types/unified-alerting'; +import { AlertInstanceTotalState, CombinedRuleWithLocation } from '../../../../types/unified-alerting'; import { AlertInstances } from '../AlertInstances'; import { getStyles } from '../UnifiedAlertList'; import { UnifiedAlertListOptions } from '../types'; @@ -47,7 +47,7 @@ const UngroupedModeView = ({ rules, options, handleInstancesLimit, limitInstance
    {rulesToDisplay.map((ruleWithLocation, index) => { const { namespaceName, groupName, dataSourceName } = ruleWithLocation; - const alertingRule: AlertingRule | undefined = isAlertingRule(ruleWithLocation.promRule) + const alertingRule = prometheusRuleType.alertingRule(ruleWithLocation.promRule) ? ruleWithLocation.promRule : undefined; const firstActiveAt = getFirstActiveAt(alertingRule); diff --git a/public/app/types/unified-alerting-dto.ts b/public/app/types/unified-alerting-dto.ts index 85f06bf9722..bf112911756 100644 --- a/public/app/types/unified-alerting-dto.ts +++ b/public/app/types/unified-alerting-dto.ts @@ -278,6 +278,12 @@ export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition { version?: number; } +// types for Grafana-managed recording and alerting rules +export type GrafanaAlertingRuleDefinition = Omit; +export type GrafanaRecordingRuleDefinition = GrafanaRuleDefinition & { + record: GrafanaRuleDefinition['record']; +}; + export interface RulerGrafanaRuleDTO { grafana_alert: T; for?: string;