From 3c17d16cf633892ae080bd43f73f77c82b8d4fc9 Mon Sep 17 00:00:00 2001 From: Sonia Aguilar Date: Thu, 29 Aug 2024 10:50:25 +0200 Subject: [PATCH] link to the explore view from the metric name in the detail view --- packages/grafana-data/src/types/config.ts | 1 + packages/grafana-runtime/src/config.ts | 1 + pkg/api/dtos/frontend_settings.go | 1 + pkg/api/frontendsettings.go | 3 ++ .../components/rule-viewer/RuleViewer.tsx | 47 +++++++++++++++++-- 5 files changed, 49 insertions(+), 4 deletions(-) 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(' ')]]);