Alerting: Template preview enhancements (#103817)
API Changes:
- Fixes validation in template CRUD API to be closer to how the running
alertmanager will use the template. Should remove some incorrect
validation errors.
- Adds some missing default placeholder labels to receiver testing that
are used during template testing but missing during receiver testing
Template Preview:
- Replaced basic preview with a readonly CodeEditor for better whitespace
and alignment clarity (also adds support for future syntax highlighting
in template previews for upcoming webhook payload templates)
Template Selector (Receiver Form):
- Refactored to use same components as Template editor for preview.
- Fixed preview to work with multi-definition templates
- Fixed copy to correctly copy the template contents instead of
{{ template "<name>" . }}.
Template Editor:
- Fixed detection of when to display functions vs snippets in multi-line
expressions
This commit is contained in:
@@ -2,10 +2,8 @@ package definitions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
tmplhtml "html/template"
|
||||
"regexp"
|
||||
"strings"
|
||||
tmpltext "text/template"
|
||||
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -35,17 +33,12 @@ func (t *NotificationTemplate) Validate() error {
|
||||
t.Template = content
|
||||
|
||||
// Validate template contents. We try to stick as close to what will actually happen when the templates are parsed
|
||||
// by the alertmanager as possible. That means parsing with both the text and html parsers and making sure we set
|
||||
// the template name and options.
|
||||
ttext := tmpltext.New(t.Name).Option("missingkey=zero")
|
||||
ttext.Funcs(tmpltext.FuncMap(template.DefaultFuncs))
|
||||
if _, err := ttext.Parse(t.Template); err != nil {
|
||||
return fmt.Errorf("invalid template: %w", err)
|
||||
// by the alertmanager as possible.
|
||||
tmpl, err := template.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create template: %w", err)
|
||||
}
|
||||
|
||||
thtml := tmplhtml.New(t.Name).Option("missingkey=zero")
|
||||
thtml.Funcs(tmplhtml.FuncMap(template.DefaultFuncs))
|
||||
if _, err := thtml.Parse(t.Template); err != nil {
|
||||
if err := tmpl.Parse(strings.NewReader(t.Template)); err != nil {
|
||||
return fmt.Errorf("invalid template: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -442,7 +442,6 @@ func TestValidateNotificationTemplates(t *testing.T) {
|
||||
expError: errors.New("invalid template: template: Different name than definition:1: template: multiple definition of template \"Alert Instance Template\""),
|
||||
},
|
||||
{
|
||||
// This is fine as long as the template name is different from the definition, it just ignores the extra text.
|
||||
name: "Extra text outside definition block - different template name and definition",
|
||||
template: NotificationTemplate{
|
||||
Name: "Different name than definition",
|
||||
@@ -452,16 +451,16 @@ func TestValidateNotificationTemplates(t *testing.T) {
|
||||
expContent: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}[what is this?]`,
|
||||
expError: nil,
|
||||
},
|
||||
// This test used to error because our template code parsed the template with the filename as template name.
|
||||
// However, we have since moved away from this. We keep this test to ensure we don't regress.
|
||||
{
|
||||
// This is NOT fine as the template name is the same as the definition.
|
||||
// GO template parser will treat it as if it's wrapped in {{ define "Alert Instance Template" }}, thus creating a duplicate definition.
|
||||
name: "Extra text outside definition block - same template name and definition",
|
||||
template: NotificationTemplate{
|
||||
Name: "Alert Instance Template",
|
||||
Template: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}[what is this?]`,
|
||||
Provenance: "test",
|
||||
},
|
||||
expError: errors.New("invalid template: template: Alert Instance Template:1: template: multiple definition of template \"Alert Instance Template\""),
|
||||
expContent: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}[what is this?]`,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ type TestTemplatesResults = alertingNotify.TestTemplatesResults
|
||||
|
||||
var (
|
||||
DefaultLabels = map[string]string{
|
||||
prometheusModel.AlertNameLabel: `alert title`,
|
||||
alertingModels.FolderTitleLabel: `folder title`,
|
||||
prometheusModel.AlertNameLabel: `TestAlert`,
|
||||
alertingModels.FolderTitleLabel: `Test Folder`,
|
||||
}
|
||||
DefaultAnnotations = map[string]string{
|
||||
alertingModels.ValuesAnnotation: `{"B":22,"C":1}`,
|
||||
|
||||
@@ -84,7 +84,7 @@ CommonAnnotations: {{ range .CommonAnnotations.SortedPairs }}{{ .Name }}={{ .Val
|
||||
expected: TestTemplatesResults{
|
||||
Results: []alertingNotify.TestTemplatesResult{{
|
||||
Name: "slack.title",
|
||||
Text: "\nReceiver: TestReceiver\nStatus: firing\nExternalURL: http://localhost:9093\nAlerts: 1\nFiring Alerts: 1\nResolved Alerts: 0\nGroupLabels: group_label=group_label_value \nCommonLabels: alertname=alert1 grafana_folder=folder title lbl1=val1 \nCommonAnnotations: ann1=annv1 \n",
|
||||
Text: "\nReceiver: TestReceiver\nStatus: firing\nExternalURL: http://localhost:9093\nAlerts: 1\nFiring Alerts: 1\nResolved Alerts: 0\nGroupLabels: group_label=group_label_value \nCommonLabels: alertname=alert1 grafana_folder=Test Folder lbl1=val1 \nCommonAnnotations: ann1=annv1 \n",
|
||||
Scope: alertingNotify.TemplateScope(apimodels.RootScope),
|
||||
}},
|
||||
Errors: nil,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
alertingNotify "github.com/grafana/alerting/notify"
|
||||
v2 "github.com/prometheus/alertmanager/api/v2"
|
||||
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
)
|
||||
@@ -30,13 +31,17 @@ func (am *alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei
|
||||
},
|
||||
})
|
||||
}
|
||||
var alert *alertingNotify.TestReceiversConfigAlertParams
|
||||
a := &alertingNotify.PostableAlert{}
|
||||
if c.Alert != nil {
|
||||
alert = &alertingNotify.TestReceiversConfigAlertParams{Annotations: c.Alert.Annotations, Labels: c.Alert.Labels}
|
||||
a.Annotations = v2.ModelLabelSetToAPILabelSet(c.Alert.Annotations)
|
||||
a.Labels = v2.ModelLabelSetToAPILabelSet(c.Alert.Labels)
|
||||
}
|
||||
|
||||
AddDefaultLabelsAndAnnotations(a)
|
||||
return am.Base.TestReceivers(ctx, alertingNotify.TestReceiversConfigBodyParams{
|
||||
Alert: alert,
|
||||
Alert: &alertingNotify.TestReceiversConfigAlertParams{
|
||||
Annotations: v2.APILabelSetToModelLabelSet(a.Annotations),
|
||||
Labels: v2.APILabelSetToModelLabelSet(a.Labels),
|
||||
},
|
||||
Receivers: receivers,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user