Skip to content

Commit

Permalink
fix(alert): send alert groups as a notification (#165)
Browse files Browse the repository at this point in the history
* fix(alert): send alert groups as a notification

* fix: e2e test
  • Loading branch information
mabdh authored Jan 16, 2023
1 parent 60bc6aa commit 54a02a3
Show file tree
Hide file tree
Showing 24 changed files with 216 additions and 129 deletions.
2 changes: 1 addition & 1 deletion core/alert/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

//go:generate mockery --name=Repository -r --case underscore --with-expecter --structname AlertRepository --filename alert_repository.go --output=./mocks
type Repository interface {
Create(context.Context, *Alert) error
Create(context.Context, Alert) (Alert, error)
List(context.Context, Filter) ([]Alert, error)
}

Expand Down
27 changes: 17 additions & 10 deletions core/alert/mocks/alert_repository.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions core/alert/mocks/alert_transformer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion core/alert/provider_plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import (

//go:generate mockery --name=AlertTransformer -r --case underscore --with-expecter --structname AlertTransformer --filename alert_transformer.go --output=./mocks
type AlertTransformer interface {
TransformToAlerts(ctx context.Context, providerID uint64, namespaceID uint64, body map[string]interface{}) ([]*Alert, int, error)
TransformToAlerts(ctx context.Context, providerID uint64, namespaceID uint64, body map[string]interface{}) ([]Alert, int, error)
}
7 changes: 5 additions & 2 deletions core/alert/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func NewService(repository Repository, registry map[string]AlertTransformer) *Se
return &Service{repository, registry}
}

func (s *Service) CreateAlerts(ctx context.Context, providerType string, providerID uint64, namespaceID uint64, body map[string]interface{}) ([]*Alert, int, error) {
func (s *Service) CreateAlerts(ctx context.Context, providerType string, providerID uint64, namespaceID uint64, body map[string]interface{}) ([]Alert, int, error) {
pluginService, err := s.getProviderPluginService(providerType)
if err != nil {
return nil, 0, err
Expand All @@ -30,13 +30,16 @@ func (s *Service) CreateAlerts(ctx context.Context, providerType string, provide
}

for _, alrt := range alerts {
if err := s.repository.Create(ctx, alrt); err != nil {
createdAlert, err := s.repository.Create(ctx, alrt)
if err != nil {
if errors.Is(err, ErrRelation) {
return nil, 0, errors.ErrNotFound.WithMsgf(err.Error())
}
return nil, 0, err
}
alrt.ID = createdAlert.ID
}

return alerts, firingLen, nil
}

Expand Down
17 changes: 9 additions & 8 deletions core/alert/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func TestService_Create(t *testing.T) {
name string
setup func(*mocks.AlertRepository, *mocks.AlertTransformer)
alertsToBeCreated map[string]interface{}
expectedAlerts []*alert.Alert
expectedAlerts []alert.Alert
expectedFiringLen int
wantErr bool
}{
Expand All @@ -124,39 +124,40 @@ func TestService_Create(t *testing.T) {
{
name: "should call repository Create method with proper arguments",
setup: func(ar *mocks.AlertRepository, at *mocks.AlertTransformer) {
at.EXPECT().TransformToAlerts(mock.AnythingOfType("*context.emptyCtx"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), mock.AnythingOfType("map[string]interface {}")).Return([]*alert.Alert{
at.EXPECT().TransformToAlerts(mock.AnythingOfType("*context.emptyCtx"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), mock.AnythingOfType("map[string]interface {}")).Return([]alert.Alert{
{ID: 1, ProviderID: 1, ResourceName: "foo", Severity: "CRITICAL", MetricName: "lag", MetricValue: "20",
Rule: "lagHigh", TriggeredAt: timenow},
}, 1, nil)
ar.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), mock.AnythingOfType("*alert.Alert")).Return(nil)
ar.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), mock.AnythingOfType("alert.Alert")).Return(alert.Alert{ID: 1, ProviderID: 1, ResourceName: "foo", Severity: "CRITICAL", MetricName: "lag", MetricValue: "20",
Rule: "lagHigh", TriggeredAt: timenow}, nil)
},
alertsToBeCreated: alertsToBeCreated,
expectedFiringLen: 1,
expectedAlerts: []*alert.Alert{
expectedAlerts: []alert.Alert{
{ID: 1, ProviderID: 1, ResourceName: "foo", Severity: "CRITICAL", MetricName: "lag", MetricValue: "20",
Rule: "lagHigh", TriggeredAt: timenow},
},
},
{
name: "should return error not found if repository return err relation",
setup: func(ar *mocks.AlertRepository, at *mocks.AlertTransformer) {
at.EXPECT().TransformToAlerts(mock.AnythingOfType("*context.emptyCtx"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), mock.AnythingOfType("map[string]interface {}")).Return([]*alert.Alert{
at.EXPECT().TransformToAlerts(mock.AnythingOfType("*context.emptyCtx"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), mock.AnythingOfType("map[string]interface {}")).Return([]alert.Alert{
{ID: 1, ProviderID: 1, ResourceName: "foo", Severity: "CRITICAL", MetricName: "lag", MetricValue: "20",
Rule: "lagHigh", TriggeredAt: timenow},
}, 1, nil)
ar.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), mock.Anything).Return(alert.ErrRelation)
ar.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), mock.Anything).Return(alert.Alert{}, alert.ErrRelation)
},
alertsToBeCreated: alertsToBeCreated,
wantErr: true,
},
{
name: "should handle errors from repository",
setup: func(ar *mocks.AlertRepository, at *mocks.AlertTransformer) {
at.EXPECT().TransformToAlerts(mock.AnythingOfType("*context.emptyCtx"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), mock.AnythingOfType("map[string]interface {}")).Return([]*alert.Alert{
at.EXPECT().TransformToAlerts(mock.AnythingOfType("*context.emptyCtx"), mock.AnythingOfType("uint64"), mock.AnythingOfType("uint64"), mock.AnythingOfType("map[string]interface {}")).Return([]alert.Alert{
{ID: 1, ProviderID: 1, ResourceName: "foo", Severity: "CRITICAL", MetricName: "lag", MetricValue: "20",
Rule: "lagHigh", TriggeredAt: timenow},
}, 1, nil)
ar.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), mock.Anything).Return(errors.New("random error"))
ar.EXPECT().Create(mock.AnythingOfType("*context.emptyCtx"), mock.Anything).Return(alert.Alert{}, errors.New("random error"))
},
alertsToBeCreated: alertsToBeCreated,
wantErr: true,
Expand Down
4 changes: 2 additions & 2 deletions core/notification/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,12 @@ func (ns *Service) DispatchToSubscribers(ctx context.Context, namespaceID uint64
if templateBody != "" {
renderedDetailString, err := template.RenderBody(templateBody, n)
if err != nil {
return errors.ErrInvalid.WithMsgf("failed to render template: %s", err.Error())
return errors.ErrInvalid.WithMsgf("failed to render template receiver %s: %s", rcv.Type, err.Error())
}

var messageDetails map[string]interface{}
if err := yaml.Unmarshal([]byte(renderedDetailString), &messageDetails); err != nil {
return errors.ErrInvalid.WithMsgf("failed to unmarshal rendered template: %s", err.Error())
return errors.ErrInvalid.WithMsgf("failed to unmarshal rendered template receiver %s: %s, rendered template: %v", rcv.Type, err.Error(), renderedDetailString)
}
message.Details = messageDetails
}
Expand Down
61 changes: 36 additions & 25 deletions core/template/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,43 @@ import (
"strings"
texttemplate "text/template"

"github.com/Masterminds/sprig/v3"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)

var defaultFuncMap = texttemplate.FuncMap{
"toUpper": strings.ToUpper,
"toLower": strings.ToLower,
"title": cases.Title(language.AmericanEnglish).String,
// join is equal to strings.Join but inverts the argument order
// for easier pipelining in templates.
"join": func(sep string, s []string) string {
return strings.Join(s, sep)
},
"joinStringValues": func(sep string, ms map[string]string) string {
var joinedString []string
for _, v := range ms {
joinedString = append(joinedString, v)
}
return strings.Join(joinedString, sep)
},
"match": regexp.MatchString,
"reReplaceAll": func(pattern, repl, text string) string {
re := regexp.MustCompile(pattern)
return re.ReplaceAllString(text, repl)
},
"stringSlice": func(s ...string) []string {
return s
},
}
var defaultFuncMap = func() texttemplate.FuncMap {
f := sprig.TxtFuncMap()

extra := texttemplate.FuncMap{
"toUpper": strings.ToUpper,
"toLower": strings.ToLower,
"title": cases.Title(language.AmericanEnglish).String,
// join is equal to strings.Join but inverts the argument order
// for easier pipelining in templates.
"join": func(sep string, s []string) string {
return strings.Join(s, sep)
},
"joinStringValues": func(sep string, ms map[string]string) string {
var joinedString []string
for _, v := range ms {
joinedString = append(joinedString, v)
}
return strings.Join(joinedString, sep)
},
"match": regexp.MatchString,
"reReplaceAll": func(pattern, repl, text string) string {
re := regexp.MustCompile(pattern)
return re.ReplaceAllString(text, repl)
},
"stringSlice": func(s ...string) []string {
return s
},
}

for k, v := range extra {
f[k] = v
}

return f
}()
13 changes: 11 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
contrib.go.opencensus.io/exporter/prometheus v0.4.2
contrib.go.opencensus.io/integrations/ocsql v0.1.7
github.com/MakeNowJust/heredoc v1.0.0
github.com/Masterminds/sprig/v3 v3.2.3
github.com/Masterminds/squirrel v1.5.3
github.com/envoyproxy/protoc-gen-validate v0.6.7
github.com/go-openapi/runtime v0.19.29
Expand Down Expand Up @@ -41,8 +42,16 @@ require (
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.0 // indirect
)

require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
Expand Down Expand Up @@ -117,7 +126,6 @@ require (
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/microcosm-cc/bluemonday v1.0.17 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.9.0 // indirect
Expand All @@ -144,6 +152,7 @@ require (
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/schollz/progressbar/v3 v3.9.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
Expand All @@ -164,7 +173,7 @@ require (
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/goleak v1.1.12 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/crypto v0.3.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/oauth2 v0.2.0 // indirect
golang.org/x/sync v0.1.0 // indirect
Expand Down
Loading

0 comments on commit 54a02a3

Please sign in to comment.