grafana/pkg/services/ngalert/state/template/funcs.go
George Robinson ed7d29f2b9
Alerting: Migrate old alerting templates to Go templates (#62911)
* Migrate old alerting templates to use $labels

* Fix imports

* Add test coverage and separate rewriting to Go templates

* Fix lint

* Check for additional closing braces

* Add logging of invalid message templates

* Fix tests

* Small fixes

* Update comments

* Panic on empty token

* Use logtest.Fake

* Fix lint

* Allow for spaces in variable names by not tokenizing spaces

* Add template function to deduplicate Labels in a Value map

* Fix behavior of mapLookupString

* Reference deduplicated labels in migrated message template

* Fix behavior of deduplicateLabelsFunc

* Don't create variable for parent logger

* Add more tests for deduplicateLabelsFunc

* Remove unused function

* Apply suggestions from code review

Co-authored by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>

* Give label val merge function better name

* Extract template migration and escape literal tokens

* Consolidate + simplify template migration

---------

Co-authored-by: William Wernert <william.wernert@grafana.com>
2023-10-02 11:25:33 -04:00

135 lines
3.3 KiB
Go

package template
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"strings"
"text/template"
"golang.org/x/exp/slices"
)
type query struct {
Datasource string `json:"datasource"`
Expr string `json:"expr"`
}
const (
FilterLabelFuncName = "filterLabels"
FilterLabelReFuncName = "filterLabelsRe"
GraphLinkFuncName = "graphLink"
RemoveLabelsFuncName = "removeLabels"
RemoveLabelsReFuncName = "removeLabelsRe"
TableLinkFuncName = "tableLink"
MergeLabelValuesFuncName = "mergeLabelValues"
)
var (
defaultFuncs = template.FuncMap{
FilterLabelFuncName: filterLabelsFunc,
FilterLabelReFuncName: filterLabelsReFunc,
GraphLinkFuncName: graphLinkFunc,
RemoveLabelsFuncName: removeLabelsFunc,
RemoveLabelsReFuncName: removeLabelsReFunc,
TableLinkFuncName: tableLinkFunc,
MergeLabelValuesFuncName: mergeLabelValuesFunc,
}
)
// filterLabelsFunc removes all labels that do not match the string.
func filterLabelsFunc(m Labels, match string) Labels {
res := make(Labels)
for k, v := range m {
if k == match {
res[k] = v
}
}
return res
}
// filterLabelsReFunc removes all labels that do not match the regex.
func filterLabelsReFunc(m Labels, pattern string) Labels {
re := regexp.MustCompile(pattern)
res := make(Labels)
for k, v := range m {
if re.MatchString(k) {
res[k] = v
}
}
return res
}
func graphLinkFunc(data string) string {
var q query
if err := json.Unmarshal([]byte(data), &q); err != nil {
return ""
}
datasource := url.QueryEscape(q.Datasource)
expr := url.QueryEscape(q.Expr)
return fmt.Sprintf(`/explore?left={"datasource":%[1]q,"queries":[{"datasource":%[1]q,"expr":%q,"instant":false,"range":true,"refId":"A"}],"range":{"from":"now-1h","to":"now"}}`, datasource, expr)
}
// removeLabelsFunc removes all labels that match the string.
func removeLabelsFunc(m Labels, match string) Labels {
res := make(Labels)
for k, v := range m {
if k != match {
res[k] = v
}
}
return res
}
// removeLabelsReFunc removes all labels that match the regex.
func removeLabelsReFunc(m Labels, pattern string) Labels {
re := regexp.MustCompile(pattern)
res := make(Labels)
for k, v := range m {
if !re.MatchString(k) {
res[k] = v
}
}
return res
}
func tableLinkFunc(data string) string {
var q query
if err := json.Unmarshal([]byte(data), &q); err != nil {
return ""
}
datasource := url.QueryEscape(q.Datasource)
expr := url.QueryEscape(q.Expr)
return fmt.Sprintf(`/explore?left={"datasource":%[1]q,"queries":[{"datasource":%[1]q,"expr":%q,"instant":true,"range":false,"refId":"A"}],"range":{"from":"now-1h","to":"now"}}`, datasource, expr)
}
// mergeLabelValuesFunc returns a map of label keys to deduplicated and comma separated values.
func mergeLabelValuesFunc(values map[string]Value) Labels {
type uniqueLabelVals map[string]struct{}
labels := make(map[string]uniqueLabelVals)
for _, value := range values {
for k, v := range value.Labels {
var ul uniqueLabelVals
var ok bool
if ul, ok = labels[k]; !ok {
ul = uniqueLabelVals{}
labels[k] = ul
}
ul[v] = struct{}{}
}
}
res := make(Labels)
for label, vals := range labels {
keys := make([]string, 0, len(vals))
for val := range vals {
keys = append(keys, val)
}
slices.Sort(keys)
res[label] = strings.Join(keys, ", ")
}
return res
}