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
This commit is contained in:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user