link to the explore view from the metric name in the detail view

This commit is contained in:
Sonia Aguilar
2024-08-29 10:50:25 +02:00
parent 5439d99d07
commit 3c17d16cf6
5 changed files with 49 additions and 4 deletions
@@ -72,6 +72,7 @@ export interface UnifiedAlertingConfig {
alertStateHistoryBackend?: string; alertStateHistoryBackend?: string;
// will be undefined if implementation is not "multiple" // will be undefined if implementation is not "multiple"
alertStateHistoryPrimary?: string; alertStateHistoryPrimary?: string;
grafanaRecordingRulesUrl?: string;
} }
/** Supported OAuth services /** Supported OAuth services
+1
View File
@@ -152,6 +152,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
minInterval: '', minInterval: '',
alertStateHistoryBackend: undefined, alertStateHistoryBackend: undefined,
alertStateHistoryPrimary: undefined, alertStateHistoryPrimary: undefined,
grafanaRecordingRulesUrl: undefined,
}; };
applicationInsightsConnectionString?: string; applicationInsightsConnectionString?: string;
applicationInsightsEndpointUrl?: string; applicationInsightsEndpointUrl?: string;
+1
View File
@@ -95,6 +95,7 @@ type FrontendSettingsUnifiedAlertingDTO struct {
MinInterval string `json:"minInterval"` MinInterval string `json:"minInterval"`
AlertStateHistoryBackend string `json:"alertStateHistoryBackend,omitempty"` AlertStateHistoryBackend string `json:"alertStateHistoryBackend,omitempty"`
AlertStateHistoryPrimary string `json:"alertStateHistoryPrimary,omitempty"` AlertStateHistoryPrimary string `json:"alertStateHistoryPrimary,omitempty"`
GrafanaRecordingRulesUrl string `json:"grafanaRecordingRulesUrl,omitempty"`
} }
// Enterprise-only // Enterprise-only
+3
View File
@@ -329,6 +329,9 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
if hs.Cfg.UnifiedAlerting.Enabled != nil { if hs.Cfg.UnifiedAlerting.Enabled != nil {
frontendSettings.UnifiedAlertingEnabled = *hs.Cfg.UnifiedAlerting.Enabled 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. // It returns false if the provider is not enabled or the skip org role sync is false.
parseSkipOrgRoleSyncEnabled := func(info *social.OAuthInfo) bool { parseSkipOrgRoleSyncEnabled := func(info *social.OAuthInfo) bool {
@@ -3,6 +3,7 @@ import { chain, isEmpty, truncate } from 'lodash';
import { useState } from 'react'; import { useState } from 'react';
import { NavModelItem, UrlQueryValue } from '@grafana/data'; import { NavModelItem, UrlQueryValue } from '@grafana/data';
import { config } from '@grafana/runtime';
import { Alert, LinkButton, Stack, TabContent, Text, TextLink, useStyles2 } from '@grafana/ui'; import { Alert, LinkButton, Stack, TabContent, Text, TextLink, useStyles2 } from '@grafana/ui';
import { PageInfoItem } from 'app/core/components/Page/types'; import { PageInfoItem } from 'app/core/components/Page/types';
import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { useQueryParams } from 'app/core/hooks/useQueryParams';
@@ -13,6 +14,7 @@ import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-
import { defaultPageNav } from '../../RuleViewer'; import { defaultPageNav } from '../../RuleViewer';
import { PluginOriginBadge } from '../../plugins/PluginOriginBadge'; import { PluginOriginBadge } from '../../plugins/PluginOriginBadge';
import { getAllDataSources } from '../../utils/config';
import { Annotation } from '../../utils/constants'; import { Annotation } from '../../utils/constants';
import { makeDashboardLink, makePanelLink } from '../../utils/misc'; import { makeDashboardLink, makePanelLink } from '../../utils/misc';
import { import {
@@ -195,10 +197,41 @@ const createMetadata = (rule: CombinedRule): PageInfoItem[] => {
} }
if (isGrafanaRecordingRule(rule.rulerRule)) { if (isGrafanaRecordingRule(rule.rulerRule)) {
const metric = rule.rulerRule?.grafana_alert.record?.metric ?? ''; const metric = rule.rulerRule?.grafana_alert.record?.metric ?? '';
metadata.push({
label: 'Metric name', const dSWithRecordingRulesUrl = getDataSourceForRecordingRules();
value: <Text color="primary">{metric}</Text>,
}); 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: (
<TextLink variant="bodySmall" href={exploreLink} external>
{metric}
</TextLink>
),
});
} 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: <Text color="primary">{metric}</Text>,
});
}
} }
if (interval) { if (interval) {
@@ -219,6 +252,12 @@ const createMetadata = (rule: CombinedRule): PageInfoItem[] => {
return metadata; return metadata;
}; };
function getDataSourceForRecordingRules() {
const urlForRecordingRules = config.unifiedAlerting.grafanaRecordingRulesUrl;
const allDs = getAllDataSources();
return allDs.find((ds) => ds.url === urlForRecordingRules);
}
// TODO move somewhere else // TODO move somewhere else
export const createListFilterLink = (values: Array<[string, string]>) => { export const createListFilterLink = (values: Array<[string, string]>) => {
const params = new URLSearchParams([['search', values.map(([key, value]) => `${key}:"${value}"`).join(' ')]]); const params = new URLSearchParams([['search', values.map(([key, value]) => `${key}:"${value}"`).join(' ')]]);