diff --git a/dispatch/route.go b/dispatch/route.go index 4b3673c5..5ada178d 100644 --- a/dispatch/route.go +++ b/dispatch/route.go @@ -180,6 +180,29 @@ func (r *Route) Key() string { return b.String() } +// ID returns a unique identifier for the route. +func (r *Route) ID() string { + b := strings.Builder{} + + position := -1 + if r.parent != nil { + // Find the position in the same level leaf. + for i, cr := range r.parent.Routes { + if cr == r { + position = i + break + } + } + } + b.WriteString(r.Key()) + + if position > -1 { + b.WriteRune('/') + b.WriteString(fmt.Sprint(position)) + } + return b.String() +} + // Walk traverses the route tree in depth-first order. func (r *Route) Walk(visit func(*Route)) { visit(r) diff --git a/dispatch/route_test.go b/dispatch/route_test.go index e633ae5f..f89b1a64 100644 --- a/dispatch/route_test.go +++ b/dispatch/route_test.go @@ -853,3 +853,71 @@ routes: } } } + +func TestRouteID(t *testing.T) { + in := ` +receiver: 'notify-def' + +routes: +- matchers: ['{owner="team-A"}', '{level!="critical"}'] + receiver: 'notify-D' + group_by: [...] + continue: true +- matchers: ['{owner="team-A"}', '{level!="critical"}'] + receiver: 'notify-A' + routes: + - matchers: ['{env="testing"}', '{baz!~".*quux"}'] + receiver: 'notify-testing' + group_by: [...] + - match: + env: "production" + receiver: 'notify-productionA' + group_wait: 1m + continue: true + - matchers: [ env=~"produ.*", job=~".*"] + receiver: 'notify-productionB' + group_wait: 30s + group_interval: 5m + repeat_interval: 1h + group_by: ['job'] +- match_re: + owner: 'team-(B|C)' + group_by: ['foo', 'bar'] + group_wait: 2m + receiver: 'notify-BC' +- matchers: [group_by="role"] + group_by: ['role'] + routes: + - matchers: ['{env="testing"}'] + receiver: 'notify-testing' + routes: + - matchers: [wait="long"] + group_wait: 2m +` + + var ctree config.Route + if err := yaml.UnmarshalStrict([]byte(in), &ctree); err != nil { + t.Fatal(err) + } + tree := NewRoute(&ctree, nil) + + expected := []string{ + "{}", + "{}/{level!=\"critical\",owner=\"team-A\"}/0", + "{}/{level!=\"critical\",owner=\"team-A\"}/1", + "{}/{level!=\"critical\",owner=\"team-A\"}/{baz!~\".*quux\",env=\"testing\"}/0", + "{}/{level!=\"critical\",owner=\"team-A\"}/{env=\"production\"}/1", + "{}/{level!=\"critical\",owner=\"team-A\"}/{env=~\"produ.*\",job=~\".*\"}/2", + "{}/{owner=~\"^(?:team-(B|C))$\"}/2", + "{}/{group_by=\"role\"}/3", + "{}/{group_by=\"role\"}/{env=\"testing\"}/0", + "{}/{group_by=\"role\"}/{env=\"testing\"}/{wait=\"long\"}/0", + } + + var got []string + tree.Walk(func(r *Route) { + got = append(got, r.ID()) + }) + + require.ElementsMatch(t, got, expected) +}