Alerting: Add target datasource dropdown for Grafana recording rules (#101805)

* Update types with optional recording rules data source UID

* Show target data source for recording rules

* Add i18n

* Fix types

* Show datasource on recording rule page

* Fix loading of initial value for target datasource

* Update translations

* Update rule-form.test.ts.snap

* Update names and remove fragment
pull/101885/head
Tom Ratcliffe 2 months ago committed by GitHub
parent e7e0b689c2
commit da8966821f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 41
      public/app/features/alerting/unified/components/rule-editor/AlertRuleNameInput.tsx
  2. 29
      public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx
  3. 2
      public/app/features/alerting/unified/types/rule-form.ts
  4. 1
      public/app/features/alerting/unified/utils/__snapshots__/rule-form.test.ts.snap
  5. 3
      public/app/features/alerting/unified/utils/rule-form.ts
  6. 1
      public/app/types/unified-alerting-dto.ts
  7. 7
      public/locales/en-US/grafana.json

@ -1,7 +1,11 @@
import { useFormContext } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import { DataSourceInstanceSettings } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { config } from '@grafana/runtime';
import { Field, Input, Stack, Text } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
import { RuleFormType, RuleFormValues } from '../../types/rule-form';
import { isCloudRecordingRuleByType, isGrafanaRecordingRuleByType, isRecordingRuleByType } from '../../utils/rules';
@ -21,9 +25,11 @@ const recordingRuleNameValidationPattern = (type: RuleFormType) => ({
*/
export const AlertRuleNameAndMetric = () => {
const {
control,
register,
watch,
formState: { errors },
setValue,
} = useFormContext<RuleFormValues>();
const ruleFormType = watch('type');
@ -76,6 +82,39 @@ export const AlertRuleNameAndMetric = () => {
/>
</Field>
)}
{isGrafanaRecordingRule && config.featureToggles.grafanaManagedRecordingRulesDatasources && (
<Field
id="target-data-source"
label={t('alerting.recording-rules.label-target-data-source', 'Target data source')}
description={t(
'alerting.recording-rules.description-target-data-source',
'The Prometheus data source to store the recording rule in'
)}
error={errors.targetDatasourceUid?.message}
invalid={!!errors.targetDatasourceUid?.message}
>
<Controller
render={({ field: { onChange, ref, ...field } }) => (
<DataSourcePicker
{...field}
current={field.value}
noDefault
// Filter with `filter` prop instead of `type` prop to avoid showing the `-- Grafana --` data source
filter={(ds: DataSourceInstanceSettings) => ds.type === 'prometheus'}
onChange={(ds: DataSourceInstanceSettings) => {
setValue('targetDatasourceUid', ds.uid);
}}
/>
)}
name="targetDatasourceUid"
control={control}
rules={{
required: { value: true, message: 'Please select a data source' },
}}
/>
</Field>
)}
</Stack>
</RuleEditorSection>
);

@ -2,8 +2,10 @@ import { css } from '@emotion/css';
import { formatDistanceToNowStrict } from 'date-fns';
import { GrafanaTheme2, dateTimeFormat, dateTimeFormatTimeAgo } from '@grafana/data';
import { Icon, Stack, Text, TextLink, useStyles2 } from '@grafana/ui';
import { config } from '@grafana/runtime';
import { Icon, Link, Stack, Text, TextLink, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { useDatasource } from 'app/features/datasources/hooks';
import { CombinedRule } from 'app/types/unified-alerting';
import { usePendingPeriod } from '../../../hooks/rules/usePendingPeriod';
@ -53,6 +55,17 @@ export const Details = ({ rule }: DetailsProps) => {
determinedRuleType = RuleType.CloudRecordingRule;
}
const targetDatasourceUid = rulerRuleType.grafana.recordingRule(rule.rulerRule)
? rule.rulerRule.grafana_alert.record?.target_datasource_uid
: null;
const datasource = useDatasource(targetDatasourceUid);
const showTargetDatasource =
config.featureToggles.grafanaManagedRecordingRulesDatasources &&
targetDatasourceUid &&
targetDatasourceUid !== 'grafana';
const evaluationDuration = rule.promRule?.evaluationTime;
const evaluationTimestamp = rule.promRule?.lastEvaluation;
@ -101,6 +114,20 @@ export const Details = ({ rule }: DetailsProps) => {
)}
</>
)}
{showTargetDatasource && (
<DetailText
id="target-datasource-uid"
label={t('alerting.alert.target-datasource-uid', 'Target data source')}
value={
<Link href={`/connections/datasources/edit/${datasource?.uid}`}>
<Stack direction="row" gap={1}>
<img style={{ width: '16px' }} src={datasource?.meta.info.logos.small} alt="datasource logo" />
{datasource?.name}
</Stack>
</Link>
}
/>
)}
</DetailGroup>
<DetailGroup title={t('alerting.alert.evaluation', 'Evaluation')}>

@ -54,7 +54,7 @@ export interface RuleFormValues {
contactPoints?: AlertManagerManualRouting;
editorSettings?: SimplifiedEditor;
metric?: string;
targetDatasourceUid?: string;
// cortex / loki rules
namespace: string;
forTime: number;

@ -28,6 +28,7 @@ exports[`formValuesToRulerGrafanaRuleDTO should correctly convert rule form valu
"record": {
"from": "A",
"metric": "",
"target_datasource_uid": undefined,
},
"title": "",
},

@ -148,6 +148,7 @@ export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): Postabl
manualRouting,
type,
metric,
targetDatasourceUid,
} = values;
if (!condition) {
throw new Error('You cannot create an alert rule without specifying the alert condition');
@ -196,6 +197,7 @@ export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): Postabl
record: {
metric: metric ?? name,
from: condition,
target_datasource_uid: targetDatasourceUid,
},
},
annotations,
@ -284,6 +286,7 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF
folder: { title: namespace, uid: ga.namespace_uid },
isPaused: ga.is_paused,
metric: ga.record?.metric,
targetDatasourceUid: ga.record?.target_datasource_uid,
};
} else if (rulerRuleType.grafana.rule(rule)) {
// grafana alerting rule

@ -262,6 +262,7 @@ export interface PostableGrafanaRuleDefinition {
record?: {
metric: string;
from: string;
target_datasource_uid?: string;
};
intervalSeconds?: number;
}

@ -184,7 +184,8 @@
"rule-identifier": "Rule identifier",
"rule-type": "Rule type",
"state-error-timeout": "Alert state if execution error or timeout",
"state-no-data": "Alert state if no data or all values are null"
"state-no-data": "Alert state if no data or all values are null",
"target-datasource-uid": "Target data source"
},
"alert-recording-rule-form": {
"evaluation-behaviour": {
@ -504,6 +505,10 @@
"preview": "Preview",
"previewCondition": "Preview alert rule condition"
},
"recording-rules": {
"description-target-data-source": "The Prometheus data source to store the recording rule in",
"label-target-data-source": "Target data source"
},
"rule-form": {
"evaluation": {
"evaluation-group-and-interval": "Evaluation group and interval",

Loading…
Cancel
Save