diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index d86fc7a3d50..74209b7ea38 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -44,7 +44,7 @@ const ( ) const ( - UIDLabel = "__alert_rule_uid__" + RuleUIDLabel = "__alert_rule_uid__" NamespaceUIDLabel = "__alert_rule_namespace_uid__" ) diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index 469aa7e5104..e0d3e2fcac2 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -89,7 +89,7 @@ func (ng *AlertNG) Init() error { Notifier: ng.Alertmanager, Metrics: ng.Metrics, } - ng.schedule = schedule.NewScheduler(schedCfg, ng.DataService) + ng.schedule = schedule.NewScheduler(schedCfg, ng.DataService, ng.Cfg.AppURL) api := api.API{ Cfg: ng.Cfg, diff --git a/pkg/services/ngalert/schedule/compat.go b/pkg/services/ngalert/schedule/compat.go index 850e4551a21..a885c13b3c1 100644 --- a/pkg/services/ngalert/schedule/compat.go +++ b/pkg/services/ngalert/schedule/compat.go @@ -1,31 +1,53 @@ package schedule import ( + "fmt" + "net/url" + "path" "time" "github.com/go-openapi/strfmt" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/prometheus/alertmanager/api/v2/models" + "github.com/grafana/grafana/pkg/infra/log" + ngModels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/state" ) -func FromAlertStateToPostableAlerts(firingStates []*state.State, stateManager *state.Manager) apimodels.PostableAlerts { +func FromAlertStateToPostableAlerts(logger log.Logger, firingStates []*state.State, stateManager *state.Manager, appURL string) apimodels.PostableAlerts { alerts := apimodels.PostableAlerts{PostableAlerts: make([]models.PostableAlert, 0, len(firingStates))} var sentAlerts []*state.State ts := time.Now() + + u, err := url.Parse(appURL) + if err != nil { + logger.Debug("failed to parse URL while joining URL", "url", appURL, "err", err.Error()) + u = nil + } + for _, alertState := range firingStates { if alertState.NeedsSending(stateManager.ResendDelay) { nL := alertState.Labels.Copy() if len(alertState.Results) > 0 { nL["__value__"] = alertState.Results[0].EvaluationString } + + genURL := appURL + if uid := nL[ngModels.RuleUIDLabel]; len(uid) > 0 && u != nil { + oldPath := u.Path + u.Path = path.Join(u.Path, fmt.Sprintf("/alerting/%s/edit", uid)) + genURL = u.String() + u.Path = oldPath + } + alerts.PostableAlerts = append(alerts.PostableAlerts, models.PostableAlert{ Annotations: alertState.Annotations, StartsAt: strfmt.DateTime(alertState.StartsAt), EndsAt: strfmt.DateTime(alertState.EndsAt), Alert: models.Alert{ - Labels: models.LabelSet(nL), + Labels: models.LabelSet(nL), + GeneratorURL: strfmt.URI(genURL), }, }) alertState.LastSentAt = ts diff --git a/pkg/services/ngalert/schedule/schedule.go b/pkg/services/ngalert/schedule/schedule.go index 1a07e51a7cc..4157550e016 100644 --- a/pkg/services/ngalert/schedule/schedule.go +++ b/pkg/services/ngalert/schedule/schedule.go @@ -88,7 +88,7 @@ func (sch *schedule) ruleRoutine(grafanaCtx context.Context, key models.AlertRul processedStates := stateManager.ProcessEvalResults(alertRule, results) sch.saveAlertStates(processedStates) - alerts := FromAlertStateToPostableAlerts(processedStates, stateManager) + alerts := FromAlertStateToPostableAlerts(sch.log, processedStates, stateManager, sch.appURL) sch.log.Debug("sending alerts to notifier", "count", len(alerts.PostableAlerts), "alerts", alerts.PostableAlerts) err = sch.sendAlerts(alerts) if err != nil { @@ -160,6 +160,8 @@ type schedule struct { dataService *tsdb.Service + appURL string + notifier Notifier metrics *metrics.Metrics } @@ -180,7 +182,7 @@ type SchedulerCfg struct { } // NewScheduler returns a new schedule. -func NewScheduler(cfg SchedulerCfg, dataService *tsdb.Service) *schedule { +func NewScheduler(cfg SchedulerCfg, dataService *tsdb.Service, appURL string) *schedule { ticker := alerting.NewTicker(cfg.C.Now(), time.Second*0, cfg.C, int64(cfg.BaseInterval.Seconds())) sch := schedule{ registry: alertRuleRegistry{alertRuleInfo: make(map[models.AlertRuleKey]alertRuleInfo)}, @@ -197,6 +199,7 @@ func NewScheduler(cfg SchedulerCfg, dataService *tsdb.Service) *schedule { dataService: dataService, notifier: cfg.Notifier, metrics: cfg.Metrics, + appURL: appURL, } return &sch } diff --git a/pkg/services/ngalert/schedule/schedule_test.go b/pkg/services/ngalert/schedule/schedule_test.go index 374435f4fb4..8f997f7a6b6 100644 --- a/pkg/services/ngalert/schedule/schedule_test.go +++ b/pkg/services/ngalert/schedule/schedule_test.go @@ -107,7 +107,7 @@ func TestWarmStateCache(t *testing.T) { InstanceStore: dbstore, Metrics: metrics.NewMetrics(prometheus.NewRegistry()), } - sched := schedule.NewScheduler(schedCfg, nil) + sched := schedule.NewScheduler(schedCfg, nil, "http://localhost") st := state.NewManager(schedCfg.Logger, nilMetrics) sched.WarmStateCache(st) @@ -153,7 +153,7 @@ func TestAlertingTicker(t *testing.T) { Logger: log.New("ngalert schedule test"), Metrics: metrics.NewMetrics(prometheus.NewRegistry()), } - sched := schedule.NewScheduler(schedCfg, nil) + sched := schedule.NewScheduler(schedCfg, nil, "http://localhost") ctx := context.Background() diff --git a/pkg/services/ngalert/state/cache.go b/pkg/services/ngalert/state/cache.go index f87ee0c8100..2057f043cf7 100644 --- a/pkg/services/ngalert/state/cache.go +++ b/pkg/services/ngalert/state/cache.go @@ -84,7 +84,7 @@ func (c *cache) getOrCreate(alertRule *ngModels.AlertRule, result eval.Result) * } func attachRuleLabels(m map[string]string, alertRule *ngModels.AlertRule) { - m[ngModels.UIDLabel] = alertRule.UID + m[ngModels.RuleUIDLabel] = alertRule.UID m[ngModels.NamespaceUIDLabel] = alertRule.NamespaceUID m[prometheusModel.AlertNameLabel] = alertRule.Title } diff --git a/pkg/tests/api/alerting/api_notification_channel_test.go b/pkg/tests/api/alerting/api_notification_channel_test.go index 44d20be02a3..43213ec9c48 100644 --- a/pkg/tests/api/alerting/api_notification_channel_test.go +++ b/pkg/tests/api/alerting/api_notification_channel_test.go @@ -1353,7 +1353,7 @@ var expNotifications = map[string][]string{ { "title": "[FIRING:1] SlackAlert2 ", "title_link": "http://localhost:3000/alerting/list", - "text": "**Firing**\n\nLabels:\n - alertname = SlackAlert2\nAnnotations:\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%%3DSlackAlert2\n", + "text": "**Firing**\n\nLabels:\n - alertname = SlackAlert2\nAnnotations:\nSource: http://localhost:3000/alerting/UID_SlackAlert2/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%%3DSlackAlert2\n", "fallback": "[FIRING:1] SlackAlert2 ", "footer": "Grafana v", "footer_icon": "https://grafana.com/assets/img/fav32.png", @@ -1386,7 +1386,7 @@ var expNotifications = map[string][]string{ "component": "Integration Test", "group": "testgroup", "custom_details": { - "firing": "\nLabels:\n - alertname = PagerdutyAlert\nAnnotations:\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%%3DPagerdutyAlert\n", + "firing": "\nLabels:\n - alertname = PagerdutyAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_PagerdutyAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%%3DPagerdutyAlert\n", "num_firing": "1", "num_resolved": "0", "resolved": "" @@ -1406,7 +1406,7 @@ var expNotifications = map[string][]string{ `{ "link": { "messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2F%2Flocalhost%3A3000%2Falerting%2Flist", - "text": "**Firing**\n\nLabels:\n - alertname = DingDingAlert\nAnnotations:\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DDingDingAlert\n", + "text": "**Firing**\n\nLabels:\n - alertname = DingDingAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_DingDingAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DDingDingAlert\n", "title": "[FIRING:1] DingDingAlert " }, "msgtype": "link" @@ -1431,7 +1431,7 @@ var expNotifications = map[string][]string{ ], "sections": [ { - "text": "**Firing**\n\nLabels:\n - alertname = TeamsAlert\nAnnotations:\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DTeamsAlert\n", + "text": "**Firing**\n\nLabels:\n - alertname = TeamsAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_TeamsAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DTeamsAlert\n", "title": "Details" } ], @@ -1453,7 +1453,7 @@ var expNotifications = map[string][]string{ "annotations": {}, "startsAt": "%s", "endsAt": "0001-01-01T00:00:00Z", - "generatorURL": "", + "generatorURL": "http://localhost:3000/alerting/UID_WebhookAlert/edit", "fingerprint": "7611eef9e67f6e50", "silenceURL": "http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%%3DWebhookAlert", "dashboardURL": "", @@ -1473,12 +1473,12 @@ var expNotifications = map[string][]string{ "truncatedAlerts": 0, "title": "[FIRING:1] WebhookAlert ", "state": "alerting", - "message": "**Firing**\n\nLabels:\n - alertname = WebhookAlert\nAnnotations:\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%%3DWebhookAlert\n" + "message": "**Firing**\n\nLabels:\n - alertname = WebhookAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_WebhookAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%%3DWebhookAlert\n" }`, }, "discord_recv/discord_test": { `{ - "content": "**Firing**\n\nLabels:\n - alertname = DiscordAlert\nAnnotations:\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DDiscordAlert\n", + "content": "**Firing**\n\nLabels:\n - alertname = DiscordAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_DiscordAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DDiscordAlert\n", "embeds": [ { "color": 14037554, @@ -1506,7 +1506,7 @@ var expNotifications = map[string][]string{ }, "name": "default" }, - "output": "**Firing**\n\nLabels:\n - alertname = SensuGoAlert\nAnnotations:\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%%3DSensuGoAlert\n", + "output": "**Firing**\n\nLabels:\n - alertname = SensuGoAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_SensuGoAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%%3DSensuGoAlert\n", "status": 2 }, "entity": { @@ -1519,10 +1519,10 @@ var expNotifications = map[string][]string{ }`, }, "pushover_recv/pushover_test": { - "--abcd\r\nContent-Disposition: form-data; name=\"user\"\r\n\r\nmysecretkey\r\n--abcd\r\nContent-Disposition: form-data; name=\"token\"\r\n\r\nmysecrettoken\r\n--abcd\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n--abcd\r\nContent-Disposition: form-data; name=\"sound\"\r\n\r\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n[FIRING:1] PushoverAlert \r\n--abcd\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\nhttp://localhost:3000/alerting/list\r\n--abcd\r\nContent-Disposition: form-data; name=\"url_title\"\r\n\r\nShow alert rule\r\n--abcd\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\n**Firing**\n\nLabels:\n - alertname = PushoverAlert\nAnnotations:\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DPushoverAlert\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"html\"\r\n\r\n1\r\n--abcd--\r\n", + "--abcd\r\nContent-Disposition: form-data; name=\"user\"\r\n\r\nmysecretkey\r\n--abcd\r\nContent-Disposition: form-data; name=\"token\"\r\n\r\nmysecrettoken\r\n--abcd\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n--abcd\r\nContent-Disposition: form-data; name=\"sound\"\r\n\r\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n[FIRING:1] PushoverAlert \r\n--abcd\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\nhttp://localhost:3000/alerting/list\r\n--abcd\r\nContent-Disposition: form-data; name=\"url_title\"\r\n\r\nShow alert rule\r\n--abcd\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\n**Firing**\n\nLabels:\n - alertname = PushoverAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_PushoverAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DPushoverAlert\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"html\"\r\n\r\n1\r\n--abcd--\r\n", }, "telegram_recv/bot6sh027hs034h": { - "--abcd\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\ntelegram_chat_id\r\n--abcd\r\nContent-Disposition: form-data; name=\"parse_mode\"\r\n\r\nhtml\r\n--abcd\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\n**Firing**\n\nLabels:\n - alertname = TelegramAlert\nAnnotations:\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DTelegramAlert\n\r\n--abcd--\r\n", + "--abcd\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\ntelegram_chat_id\r\n--abcd\r\nContent-Disposition: form-data; name=\"parse_mode\"\r\n\r\nhtml\r\n--abcd\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\n**Firing**\n\nLabels:\n - alertname = TelegramAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_TelegramAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DTelegramAlert\n\r\n--abcd--\r\n", }, "googlechat_recv/googlechat_test": { `{ @@ -1538,7 +1538,7 @@ var expNotifications = map[string][]string{ "widgets": [ { "textParagraph": { - "text": "**Firing**\n\nLabels:\n - alertname = GoogleChatAlert\nAnnotations:\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%%3DGoogleChatAlert\n" + "text": "**Firing**\n\nLabels:\n - alertname = GoogleChatAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_GoogleChatAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%%3DGoogleChatAlert\n" } }, { @@ -1576,7 +1576,7 @@ var expNotifications = map[string][]string{ "client": "Grafana", "client_url": "http://localhost:3000/alerting/list", "description": "[FIRING:1] KafkaAlert ", - "details": "**Firing**\n\nLabels:\n - alertname = KafkaAlert\nAnnotations:\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DKafkaAlert\n", + "details": "**Firing**\n\nLabels:\n - alertname = KafkaAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_KafkaAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DKafkaAlert\n", "incident_key": "35c0bdb1715f9162a20d7b2a01cb2e3a4c5b1dc663571701e3f67212b696332f" } } @@ -1584,10 +1584,10 @@ var expNotifications = map[string][]string{ }`, }, "line_recv/line_test": { - `message=%5BFIRING%3A1%5D+LineAlert+%0Ahttp%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A%0A%2A%2AFiring%2A%2A%0A%0ALabels%3A%0A+-+alertname+%3D+LineAlert%0AAnnotations%3A%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matchers%3Dalertname%253DLineAlert%0A`, + `message=%5BFIRING%3A1%5D+LineAlert+%0Ahttp%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A%0A%2A%2AFiring%2A%2A%0A%0ALabels%3A%0A+-+alertname+%3D+LineAlert%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2FUID_LineAlert%2Fedit%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matchers%3Dalertname%253DLineAlert%0A`, }, "threema_recv/threema_test": { - `from=%2A1234567&secret=myapisecret&text=%E2%9A%A0%EF%B8%8F+%5BFIRING%3A1%5D+ThreemaAlert+%0A%0A%2AMessage%3A%2A%0A%2A%2AFiring%2A%2A%0A%0ALabels%3A%0A+-+alertname+%3D+ThreemaAlert%0AAnnotations%3A%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matchers%3Dalertname%253DThreemaAlert%0A%0A%2AURL%3A%2A+http%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A&to=abcdefgh`, + `from=%2A1234567&secret=myapisecret&text=%E2%9A%A0%EF%B8%8F+%5BFIRING%3A1%5D+ThreemaAlert+%0A%0A%2AMessage%3A%2A%0A%2A%2AFiring%2A%2A%0A%0ALabels%3A%0A+-+alertname+%3D+ThreemaAlert%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2FUID_ThreemaAlert%2Fedit%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matchers%3Dalertname%253DThreemaAlert%0A%0A%2AURL%3A%2A+http%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A&to=abcdefgh`, }, "victorops_recv/victorops_test": { `{ @@ -1596,14 +1596,14 @@ var expNotifications = map[string][]string{ "entity_id": "633ae988fa7074bcb51f3d1c5fef2ba1c5c4ccb45b3ecbf681f7d507b078b1ae", "message_type": "CRITICAL", "monitoring_tool": "Grafana v", - "state_message": "**Firing**\n\nLabels:\n - alertname = VictorOpsAlert\nAnnotations:\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%%3DVictorOpsAlert\n", + "state_message": "**Firing**\n\nLabels:\n - alertname = VictorOpsAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_VictorOpsAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%%3DVictorOpsAlert\n", "timestamp": %s }`, }, "opsgenie_recv/opsgenie_test": { `{ "alias": "47e92f0f6ef9fe99f3954e0d6155f8d09c4b9a038d8c3105e82c0cee4c62956e", - "description": "[FIRING:1] OpsGenieAlert \nhttp://localhost:3000/alerting/list\n\n**Firing**\n\nLabels:\n - alertname = OpsGenieAlert\nAnnotations:\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DOpsGenieAlert\n", + "description": "[FIRING:1] OpsGenieAlert \nhttp://localhost:3000/alerting/list\n\n**Firing**\n\nLabels:\n - alertname = OpsGenieAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_OpsGenieAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DOpsGenieAlert\n", "details": { "url": "http://localhost:3000/alerting/list" }, @@ -1618,13 +1618,13 @@ var expNotifications = map[string][]string{ { "labels": { "__alert_rule_uid__": "UID_AlertmanagerAlert", - "__value__": "[ var='A' labels={} value=1 ]", + "__value__": "[ var='A' labels={} value=1 ]", "alertname": "AlertmanagerAlert" }, "annotations": {}, "startsAt": "%s", "endsAt": "0001-01-01T00:00:00Z", - "generatorURL": "", + "generatorURL": "http://localhost:3000/alerting/UID_AlertmanagerAlert/edit", "UpdatedAt": "%s", "Timeout": false }