diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts
index 1cf37071e23..0af05583faa 100644
--- a/packages/grafana-data/src/types/config.ts
+++ b/packages/grafana-data/src/types/config.ts
@@ -72,6 +72,7 @@ export interface UnifiedAlertingConfig {
alertStateHistoryBackend?: string;
// will be undefined if implementation is not "multiple"
alertStateHistoryPrimary?: string;
+ grafanaRecordingRulesUrl?: string;
}
/** Supported OAuth services
diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts
index f92f4023bf0..421eb5b2826 100644
--- a/packages/grafana-runtime/src/config.ts
+++ b/packages/grafana-runtime/src/config.ts
@@ -152,6 +152,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
minInterval: '',
alertStateHistoryBackend: undefined,
alertStateHistoryPrimary: undefined,
+ grafanaRecordingRulesUrl: undefined,
};
applicationInsightsConnectionString?: string;
applicationInsightsEndpointUrl?: string;
diff --git a/pkg/api/dtos/frontend_settings.go b/pkg/api/dtos/frontend_settings.go
index c93e00cea0f..1d5e1aa1fff 100644
--- a/pkg/api/dtos/frontend_settings.go
+++ b/pkg/api/dtos/frontend_settings.go
@@ -95,6 +95,7 @@ type FrontendSettingsUnifiedAlertingDTO struct {
MinInterval string `json:"minInterval"`
AlertStateHistoryBackend string `json:"alertStateHistoryBackend,omitempty"`
AlertStateHistoryPrimary string `json:"alertStateHistoryPrimary,omitempty"`
+ GrafanaRecordingRulesUrl string `json:"grafanaRecordingRulesUrl,omitempty"`
}
// Enterprise-only
diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go
index 9bf8c35ef75..c511cae816e 100644
--- a/pkg/api/frontendsettings.go
+++ b/pkg/api/frontendsettings.go
@@ -329,6 +329,9 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
if hs.Cfg.UnifiedAlerting.Enabled != nil {
frontendSettings.UnifiedAlertingEnabled = *hs.Cfg.UnifiedAlerting.Enabled
}
+ if hs.Cfg.UnifiedAlerting.RecordingRules.Enabled {
+ frontendSettings.UnifiedAlerting.GrafanaRecordingRulesUrl = hs.Cfg.UnifiedAlerting.RecordingRules.URL
+ }
// It returns false if the provider is not enabled or the skip org role sync is false.
parseSkipOrgRoleSyncEnabled := func(info *social.OAuthInfo) bool {
diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx
index 3f71b0b9e95..4555f4a6063 100644
--- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx
+++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx
@@ -3,6 +3,7 @@ import { chain, isEmpty, truncate } from 'lodash';
import { useState } from 'react';
import { NavModelItem, UrlQueryValue } from '@grafana/data';
+import { config } from '@grafana/runtime';
import { Alert, LinkButton, Stack, TabContent, Text, TextLink, useStyles2 } from '@grafana/ui';
import { PageInfoItem } from 'app/core/components/Page/types';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
@@ -13,6 +14,7 @@ import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-
import { defaultPageNav } from '../../RuleViewer';
import { PluginOriginBadge } from '../../plugins/PluginOriginBadge';
+import { getAllDataSources } from '../../utils/config';
import { Annotation } from '../../utils/constants';
import { makeDashboardLink, makePanelLink } from '../../utils/misc';
import {
@@ -195,10 +197,41 @@ const createMetadata = (rule: CombinedRule): PageInfoItem[] => {
}
if (isGrafanaRecordingRule(rule.rulerRule)) {
const metric = rule.rulerRule?.grafana_alert.record?.metric ?? '';
- metadata.push({
- label: 'Metric name',
- value: {metric},
- });
+
+ const dSWithRecordingRulesUrl = getDataSourceForRecordingRules();
+
+ if (dSWithRecordingRulesUrl) {
+ // if we have a datasource with recording rules, we can link to explore with the datasource and metric in the query
+ const exploreLink = createRelativeUrl('/explore', {
+ left: JSON.stringify({
+ datasource: dSWithRecordingRulesUrl.uid,
+ queries: [
+ {
+ refId: 'A',
+ datasource: { type: dSWithRecordingRulesUrl.type, uid: dSWithRecordingRulesUrl.uid },
+ expr: `${metric}{}`,
+ },
+ ],
+ range: { from: 'now-1h', to: 'now' },
+ }),
+ });
+
+ metadata.push({
+ label: 'Metric name',
+ value: (
+
+ {metric}
+
+ ),
+ });
+ } else {
+ // if we don't have a datasource with recording rules, we can just show the metric name
+ // this can happen if the datasource with the url specified in the config is not added as a datasource in Grafana
+ metadata.push({
+ label: 'Metric name',
+ value: {metric},
+ });
+ }
}
if (interval) {
@@ -219,6 +252,12 @@ const createMetadata = (rule: CombinedRule): PageInfoItem[] => {
return metadata;
};
+function getDataSourceForRecordingRules() {
+ const urlForRecordingRules = config.unifiedAlerting.grafanaRecordingRulesUrl;
+ const allDs = getAllDataSources();
+ return allDs.find((ds) => ds.url === urlForRecordingRules);
+}
+
// TODO move somewhere else
export const createListFilterLink = (values: Array<[string, string]>) => {
const params = new URLSearchParams([['search', values.map(([key, value]) => `${key}:"${value}"`).join(' ')]]);