Alerting: Refactor and clean up rule type guard functions (#101639)

pull/101821/head
Gilles De Mey 4 months ago committed by GitHub
parent 3a3f781dcf
commit da8f26a07c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      public/app/features/alerting/unified/api/alertRuleApi.ts
  2. 4
      public/app/features/alerting/unified/components/MenuItemPauseRule.tsx
  3. 6
      public/app/features/alerting/unified/components/export/GrafanaModifyExport.tsx
  4. 6
      public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx
  5. 6
      public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx
  6. 12
      public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx
  7. 4
      public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx
  8. 6
      public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx
  9. 17
      public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx
  10. 30
      public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx
  11. 42
      public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx
  12. 8
      public/app/features/alerting/unified/components/rule-viewer/tabs/Query.tsx
  13. 6
      public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx
  14. 26
      public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.tsx
  15. 6
      public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx
  16. 4
      public/app/features/alerting/unified/components/rules/RuleConfigStatus.tsx
  17. 21
      public/app/features/alerting/unified/components/rules/RuleDetails.tsx
  18. 6
      public/app/features/alerting/unified/components/rules/RuleDetailsButtons.tsx
  19. 4
      public/app/features/alerting/unified/components/rules/RuleDetailsDataSources.tsx
  20. 8
      public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx
  21. 8
      public/app/features/alerting/unified/components/rules/RuleListStateView.tsx
  22. 11
      public/app/features/alerting/unified/components/rules/RuleState.tsx
  23. 7
      public/app/features/alerting/unified/components/rules/RulesGroup.tsx
  24. 8
      public/app/features/alerting/unified/components/rules/RulesTable.tsx
  25. 4
      public/app/features/alerting/unified/hooks/ruleGroup/useUpsertRuleFromRuleGroup.ts
  26. 17
      public/app/features/alerting/unified/hooks/useAbilities.ts
  27. 50
      public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts
  28. 16
      public/app/features/alerting/unified/hooks/useFilteredRules.ts
  29. 6
      public/app/features/alerting/unified/hooks/useIsRuleEditable.ts
  30. 13
      public/app/features/alerting/unified/reducers/ruler/ruleGroups.ts
  31. 10
      public/app/features/alerting/unified/rule-editor/CloneRuleEditor.tsx
  32. 6
      public/app/features/alerting/unified/rule-list/DataSourceRuleLoader.tsx
  33. 10
      public/app/features/alerting/unified/rule-list/StateView.tsx
  34. 6
      public/app/features/alerting/unified/rule-list/components/RuleActionsButtons.V2.tsx
  35. 5
      public/app/features/alerting/unified/rule-list/components/RuleGroup.tsx
  36. 4
      public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts
  37. 4
      public/app/features/alerting/unified/utils/groupIdentifier.ts
  38. 4
      public/app/features/alerting/unified/utils/query.ts
  39. 17
      public/app/features/alerting/unified/utils/rule-form.ts
  40. 24
      public/app/features/alerting/unified/utils/rule-id.ts
  41. 99
      public/app/features/alerting/unified/utils/rules.ts
  42. 4
      public/app/features/dashboard-scene/scene/AlertStatesDataLayer.ts
  43. 4
      public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.ts
  44. 6
      public/app/plugins/panel/alertlist/unified-alerting/UngroupedView.tsx
  45. 6
      public/app/types/unified-alerting-dto.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}` },

@ -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'];

@ -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 (
<Alert
@ -69,7 +69,7 @@ function RuleModifyExport({ ruleIdentifier }: { ruleIdentifier: RuleIdentifier }
);
}
if (rulerRule && isGrafanaRulerRule(rulerRule.rule)) {
if (rulerRule && rulerRuleType.grafana.rule(rulerRule.rule)) {
return (
<ModifyExportRuleForm
ruleForm={formValuesFromExistingRule(rulerRule)}

@ -31,7 +31,7 @@ import {
isGrafanaAlertingRuleByType,
isGrafanaManagedRuleByType,
isGrafanaRecordingRuleByType,
isGrafanaRulerRule,
rulerRuleType,
} from '../../utils/rules';
import { parsePrometheusDuration } from '../../utils/time';
import { CollapseToggle } from '../CollapseToggle';
@ -82,7 +82,9 @@ const namespaceToGroupOptions = (rulerNamespace: RulerRulesConfigDTO, enableProv
};
const isProvisionedGroup = (group: RulerRuleGroupDTO) => {
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<string>, b: SelectableValue<string>) => {

@ -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<RuleFormValues> {
if (isAlertingRulerRule(rulerRule)) {
if (rulerRuleType.dataSource.alertingRule(rulerRule)) {
return alertingRulerRuleToRuleForm(rulerRule);
} else if (isRecordingRulerRule(rulerRule)) {
} else if (rulerRuleType.dataSource.recordingRule(rulerRule)) {
return recordingRulerRuleToRuleForm(rulerRule);
}

@ -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) =>
</Stack>
);
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);
}

@ -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 {

@ -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' && (
<MenuItemPauseRule rule={rulerRule} groupIdentifier={groupIdentifier} onPauseChange={onPauseChange} />
)}
{canSilence && <Menu.Item label="Silence notifications" icon="bell-slash" onClick={handleSilence} />}

@ -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);

@ -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 = () => {
<TabContent>
{activeTab === ActiveTab.Query && <QueryResults rule={rule} />}
{activeTab === ActiveTab.Instances && <InstancesList rule={rule} />}
{activeTab === ActiveTab.History && isGrafanaRulerRule(rule.rulerRule) && <History rule={rule.rulerRule} />}
{activeTab === ActiveTab.History && rulerRuleType.grafana.rule(rule.rulerRule) && (
<History rule={rule.rulerRule} />
)}
{activeTab === ActiveTab.Routing && <Routing />}
{activeTab === ActiveTab.Details && <Details rule={rule} />}
{activeTab === ActiveTab.VersionHistory && isGrafanaRulerRule(rule.rulerRule) && (
{activeTab === ActiveTab.VersionHistory && rulerRuleType.grafana.rule(rule.rulerRule) && (
<AlertVersionHistory rule={rule.rulerRule} />
)}
</TabContent>
@ -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,

@ -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 = (
<Stack>
<Text color="warning">
@ -82,8 +76,8 @@ export const Details = ({ rule }: DetailsProps) => {
return (
<div className={styles.metadata}>
<DetailGroup title={t('alerting.alert.rule', 'Rule')}>
<DetailText id="rule-type" label={t('alerting.alert.rule-type', 'Rule type')} value={ruleType} />
{isGrafanaRulerRule(rule.rulerRule) && (
<DetailText id="rule-type" label={t('alerting.alert.rule-type', 'Rule type')} value={determinedRuleType} />
{rulerRuleType.grafana.rule(rule.rulerRule) && (
<>
<DetailText
id="rule-type"
@ -145,7 +139,7 @@ export const Details = ({ rule }: DetailsProps) => {
)}
</DetailGroup>
{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 && (

@ -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 && (
<GrafanaRuleQueryViewer
rule={rule}
condition={rule.rulerRule.grafana_alert.condition}
@ -55,7 +55,7 @@ const QueryResults = ({ rule }: Props) => {
/>
)}
{!isGrafanaRulerRule(rule.rulerRule) &&
{!rulerRuleType.grafana.rule(rule.rulerRule) &&
!isFederatedRule &&
queryPreviewData &&
Object.keys(queryPreviewData).length > 0 && (

@ -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 (

@ -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) && <div className={styles.listItemName}>{rule.grafana_alert.title}</div>}
{isRecordingRulerRule(rule) && (
<>
<div className={styles.listItemName}>{rule.record}</div>
<Badge text="Recording" color="purple" />
</>
)}
{isAlertingRulerRule(rule) && <div className={styles.listItemName}>{rule.alert}</div>}
<div className={styles.listItemName}>
{getRuleName(rule)}
{rulerRuleType.any.recordingRule(rule) && (
<>
{' '}
<Badge text="Recording" color="purple" />
</>
)}
</div>
{rulerRuleType.dataSource.alertingRule(rule) && <div className={styles.listItemName}>{rule.alert}</div>}
<Icon name="draggabledots" />
</div>
);

@ -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 && (
<SilenceGrafanaRuleDrawer rulerRule={rule.rulerRule} onClose={() => setShowSilenceDrawer(false)} />
)}
{redirectToClone?.identifier && (

@ -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;

@ -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) => {
<RuleDetailsDataSources rulesSource={rulesSource} rule={rule} />
</div>
</div>
{!(
isRecordingRulerRule(rule.rulerRule) ||
isRecordingRule(rule.promRule) ||
isGrafanaRecordingRule(rule.rulerRule)
) && (
<DetailsField label="Instances" horizontal={true}>
<RuleDetailsMatchingInstances rule={rule} itemsDisplayLimit={INSTANCES_DISPLAY_LIMIT} />
</DetailsField>
)}
{rulerRuleType.any.alertingRule(rule.rulerRule) ||
(prometheusRuleType.alertingRule(rule.promRule) && (
<DetailsField label="Instances" horizontal={true}>
<RuleDetailsMatchingInstances rule={rule} itemsDisplayLimit={INSTANCES_DISPLAY_LIMIT} />
</DetailsField>
))}
</div>
);
};
@ -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);

@ -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(
<Fragment key="history">
<Button
size="sm"
icon="history"
onClick={() => isGrafanaRulerRule(rule.rulerRule) && showStateHistoryModal(rule.rulerRule)}
onClick={() => rulerRuleType.grafana.rule(rule.rulerRule) && showStateHistoryModal(rule.rulerRule)}
>
Show state history
</Button>

@ -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<Record<string, { name: string; icon?: string }>>((dataSources, query) => {
const ds = getDataSourceSrv().getInstanceSettings(query.datasourceUid);

@ -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;
}

@ -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;

@ -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 (
<Tooltip content={'Recording rule evaluation is currently paused'} placement="top">
<StateTag state="warning">
@ -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) =>
<Trans i18nKey="alerting.rule-state.creating">Creating</Trans>
</Stack>
);
} else if (promRule && isAlertingRule(promRule)) {
} else if (prometheusRuleType.alertingRule(promRule)) {
return (
<Stack gap={1}>
<AlertStateTag state={promRule.state} isPaused={isPaused} />
{!isPaused && forTime}
</Stack>
);
} else if (promRule && isRecordingRule(promRule)) {
} else if (promRule && prometheusRuleType.recordingRule(promRule)) {
return <RecordingRuleState />;
}
return <>n/a</>;

@ -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

@ -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 <PluginOriginBadge pluginId={originMeta.pluginId} />;
}
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 };
}

@ -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;

@ -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<AlertRul
const canSilence = useCanSilence(rule.rulerRule);
const abilities = useMemo<Abilities<AlertRuleAction>>(() => {
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<Abilities<AlertRuleAction>>(() => {
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);

@ -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<RuleGroup, 'rules' | 'totals'>)
};
}
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;
}

@ -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;

@ -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.`

@ -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;

@ -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<RulerRuleDTO>) {
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

@ -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 (
<AlertRuleListItem
name={rule.name}
@ -96,7 +96,7 @@ export const DataSourceRuleLoader = memo(function DataSourceRuleLoader({
);
}
if (isRecordingRule(rule)) {
if (prometheusRuleType.recordingRule(rule)) {
return (
<RecordingRuleListItem
name={rule.name}

@ -13,7 +13,7 @@ import { ListSection } from '../rule-list/components/ListSection';
import { groupIdentifier } from '../utils/groupIdentifier';
import { createViewLink } from '../utils/misc';
import { hashRule } from '../utils/rule-id';
import { getRulePluginOrigin, isAlertingRule, isGrafanaRulerRule } from '../utils/rules';
import { getRulePluginOrigin, prometheusRuleType, rulerRuleType } from '../utils/rules';
import { AlertRuleListItem } from './components/AlertRuleListItem';
import { RuleActionsButtons } from './components/RuleActionsButtons.V2';
@ -41,7 +41,7 @@ export const StateView = ({ 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);
}
})
@ -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) {

@ -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 && (
<SilenceGrafanaRuleDrawer rulerRule={rule} onClose={() => setShowSilenceDrawer(false)} />
)}
{redirectToClone?.identifier && (

@ -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;

@ -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) {

@ -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,

@ -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);
}

@ -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 {

@ -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');

@ -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;
function isGrafanaRulerRule(rule?: RulerRuleDTO | PostableRuleDTO): rule is RulerGrafanaRuleDTO {
return typeof rule === 'object' && 'grafana_alert' in rule;
}
export function isAlertingRulerRule(rule?: RulerRuleDTO): rule is RulerAlertingRuleDTO {
return typeof rule === 'object' && 'alert' in rule;
function isGrafanaAlertingRule(rule?: RulerRuleDTO): rule is RulerGrafanaRuleDTO<GrafanaAlertingRuleDefinition> {
return isGrafanaRulerRule(rule) && !isGrafanaRecordingRule(rule);
}
export function isRecordingRulerRule(rule?: RulerRuleDTO): rule is RulerRecordingRuleDTO {
return typeof rule === 'object' && 'record' in rule;
function isGrafanaRecordingRule(rule?: RulerRuleDTO): rule is RulerGrafanaRuleDTO<GrafanaRecordingRuleDefinition> {
return isGrafanaRulerRule(rule) && 'record' in rule.grafana_alert;
}
export function isGrafanaOrDataSourceRecordingRule(rule?: RulerRuleDTO) {
return (
(typeof rule === 'object' && isRecordingRulerRule(rule)) ||
(isGrafanaRulerRule(rule) && 'record' in rule.grafana_alert)
);
export function isPausedRule(rule: RulerGrafanaRuleDTO) {
return Boolean(rule.grafana_alert.is_paused);
}
export function isGrafanaRecordingRule(rule?: RulerRuleDTO): rule is RulerGrafanaRuleDTO {
return typeof rule === 'object' && isGrafanaOrDataSourceRecordingRule(rule) && isGrafanaRulerRule(rule);
/* Data source managed rules */
function isAlertingRulerRule(rule?: RulerRuleDTO): rule is RulerAlertingRuleDTO {
return typeof rule === 'object' && 'alert' in rule;
}
export function isGrafanaAlertingRule(rule?: RulerRuleDTO): rule is RulerGrafanaRuleDTO {
return typeof rule === 'object' && isGrafanaRulerRule(rule) && !isGrafanaOrDataSourceRecordingRule(rule);
function isCloudRulerRule(rule?: RulerRuleDTO | PostableRuleDTO): rule is RulerCloudRuleDTO {
return typeof rule === 'object' && !isGrafanaRulerRule(rule);
}
export function isGrafanaRulerRule(rule?: RulerRuleDTO | PostableRuleDTO): rule is RulerGrafanaRuleDTO {
return typeof rule === 'object' && 'grafana_alert' in rule;
function isCloudRecordingRulerRule(rule?: RulerRuleDTO): rule is RulerRecordingRuleDTO {
return typeof rule === 'object' && 'record' in rule;
}
export function isCloudRulerRule(rule?: RulerRuleDTO | PostableRuleDTO): rule is RulerCloudRuleDTO {
return typeof rule === 'object' && !isGrafanaRulerRule(rule);
/* Prometheus rules */
function isAlertingRule(rule?: Rule): rule is AlertingRule {
return typeof rule === 'object' && rule.type === PromRuleType.Alerting;
}
export function isGrafanaRulerRulePaused(rule: RulerGrafanaRuleDTO) {
return rule && isGrafanaRulerRule(rule) && Boolean(rule.grafana_alert.is_paused);
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;
}

@ -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<number, AlertStateInfo> = {};
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);

@ -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<number, AlertStateInfo> = {};
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);

@ -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
<ol className={styles.alertRuleList}>
{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);

@ -278,6 +278,12 @@ export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition {
version?: number;
}
// types for Grafana-managed recording and alerting rules
export type GrafanaAlertingRuleDefinition = Omit<GrafanaRuleDefinition, 'record'>;
export type GrafanaRecordingRuleDefinition = GrafanaRuleDefinition & {
record: GrafanaRuleDefinition['record'];
};
export interface RulerGrafanaRuleDTO<T = GrafanaRuleDefinition> {
grafana_alert: T;
for?: string;

Loading…
Cancel
Save