Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(alert): send alert groups as a notification #165

Merged
merged 2 commits into from
Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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