Skip to content

Commit

Permalink
feat: Add templates to trigger description #484
Browse files Browse the repository at this point in the history
* Added templates to trigger description
* Added presentation of trigger description in non-editing for api

Closed #484
  • Loading branch information
SantFlamel authored and Nikolay committed Feb 27, 2020
1 parent e411f20 commit 1da9355
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 1 deletion.
30 changes: 29 additions & 1 deletion api/handler/trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/go-chi/chi"
"github.com/go-chi/render"
"github.com/moira-alert/moira"
"github.com/moira-alert/moira/metric_source/local"
"github.com/moira-alert/moira/metric_source/remote"

Expand All @@ -20,7 +21,7 @@ import (
func trigger(router chi.Router) {
router.Use(middleware.TriggerContext)
router.Put("/", updateTrigger)
router.Get("/", getTrigger)
router.With(middleware.TriggerContext, middleware.Populate(false)).Get("/", getTrigger)
router.Delete("/", removeTrigger)
router.Get("/state", getTriggerState)
router.Route("/throttling", func(router chi.Router) {
Expand All @@ -35,6 +36,7 @@ func trigger(router chi.Router) {
func updateTrigger(writer http.ResponseWriter, request *http.Request) {
triggerID := middleware.GetTriggerID(request)
trigger := &dto.Trigger{}

if err := render.Bind(request, trigger); err != nil {
switch err := err.(type) {
case local.ErrParseExpr, local.ErrEvalExpr, local.ErrUnknownFunction:
Expand All @@ -50,9 +52,19 @@ func updateTrigger(writer http.ResponseWriter, request *http.Request) {
default:
render.Render(writer, request, api.ErrorInternalServer(err))
}

return
}

if trigger.Desc != nil {
triggerData := moira.TriggerData{Desc: *trigger.Desc, Name: trigger.Name}
if _, err := triggerData.GetPopulatedDescription(moira.NotificationEvents{}); err != nil {
render.Render(writer, request, api.ErrorRender(
fmt.Errorf("You have an error in your Go template: %v", err)))
return
}
}

timeSeriesNames := middleware.GetTimeSeriesNames(request)
response, err := controller.UpdateTrigger(database, &trigger.TriggerModel, triggerID, timeSeriesNames)
if err != nil {
Expand All @@ -79,11 +91,27 @@ func getTrigger(writer http.ResponseWriter, request *http.Request) {
if triggerID == "testlog" {
panic("Test for multi line logs")
}

trigger, err := controller.GetTrigger(database, triggerID)
if err != nil {
render.Render(writer, request, err)
return
}

if needToPopulate := middleware.GetPopulated(request); needToPopulate && trigger.Desc != nil {
triggerData := moira.TriggerData{Desc: *trigger.Desc, Name: trigger.Name}

eventsList, err := controller.GetTriggerEvents(database, triggerID, 0, 3)
if err != nil {
render.Render(writer, request, err)
}

desc, errPopulate := triggerData.GetPopulatedDescription(eventsList.List)
if errPopulate == nil {
*trigger.Desc = desc
}
}

if err := render.Render(writer, request, trigger); err != nil {
render.Render(writer, request, api.ErrorRender(err))
}
Expand Down
15 changes: 15 additions & 0 deletions api/middleware/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,21 @@ func Paginate(defaultPage, defaultSize int64) func(next http.Handler) http.Handl
}
}

// Populate gets bool value populate from URI query and set it to request context. If query has not values sets given values
func Populate(defaultPopulated bool) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
populate, err := strconv.ParseBool(request.URL.Query().Get("populated"))
if err != nil {
populate = defaultPopulated
}

ctxTemplate := context.WithValue(request.Context(), populateKey, populate)
next.ServeHTTP(writer, request.WithContext(ctxTemplate))
})
}
}

// DateRange gets from and to values from URI query and set it to request context. If query has not values sets given values
func DateRange(defaultFrom, defaultTo string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
Expand Down
6 changes: 6 additions & 0 deletions api/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var (
databaseKey ContextKey = "database"
searcherKey ContextKey = "searcher"
triggerIDKey ContextKey = "triggerID"
populateKey ContextKey = "populated"
contactIDKey ContextKey = "contactID"
tagKey ContextKey = "tag"
subscriptionIDKey ContextKey = "subscriptionID"
Expand Down Expand Up @@ -46,6 +47,11 @@ func GetTriggerID(request *http.Request) string {
return request.Context().Value(triggerIDKey).(string)
}

// GetPopulated get populate bool from request context, which was sets in TriggerContext middleware
func GetPopulated(request *http.Request) bool {
return request.Context().Value(populateKey).(bool)
}

// GetTag gets tag string from request context, which was sets in TagContext middleware
func GetTag(request *http.Request) string {
return request.Context().Value(tagKey).(string)
Expand Down
45 changes: 45 additions & 0 deletions datatypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package moira
import (
"bytes"
"fmt"
"html/template"
"math"
"strconv"
"strings"
Expand Down Expand Up @@ -103,6 +104,23 @@ func (event *NotificationEvent) CreateMessage(location *time.Location) string {
// NotificationEvents represents slice of NotificationEvent
type NotificationEvents []NotificationEvent

type templateData struct {
Trigger templateTrigger
Events []templateEvent
}

type templateEvent struct {
Metric string
MetricElements []string
Timestamp int64
Value *float64
State State
}

type templateTrigger struct {
Name string `json:"name"`
}

// TriggerData represents trigger object
type TriggerData struct {
ID string `json:"id"`
Expand All @@ -115,6 +133,33 @@ type TriggerData struct {
Tags []string `json:"__notifier_trigger_tags"`
}

func (trigger TriggerData) GetPopulatedDescription(events NotificationEvents) (string, error) {
buffer := new(bytes.Buffer)
templateEvents := make([]templateEvent, 0, len(events))

for _, data := range events {
event := templateEvent{
Metric: data.Metric,
MetricElements: strings.Split(data.Metric, "."),
Timestamp: data.Timestamp,
State: data.State,
Value: data.Value,
}

templateEvents = append(templateEvents, event)
}

triggerTemplate := template.Must(template.New("populate-description").Parse(trigger.Desc))
if err := triggerTemplate.Execute(buffer, templateData{
Trigger: templateTrigger{Name: trigger.Name},
Events: templateEvents,
}); err != nil {
return trigger.Desc, err
}

return buffer.String(), nil
}

// GetTriggerURI gets frontUri and returns triggerUrl, returns empty string on selfcheck and test notifications
func (trigger TriggerData) GetTriggerURI(frontURI string) string {
if trigger.ID != "" {
Expand Down
44 changes: 44 additions & 0 deletions datatypes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,50 @@ func TestTriggerData_GetTags(t *testing.T) {
})
}

func TestTriggerData_TemplateDescription(t *testing.T) {

Convey("Test templates", t, func() {
var trigger = TriggerData{Name: "TestName"}
trigger.Desc = "\n" +
"Trigger name: {{.Trigger.Name}}\n" +
"{{range $v := .Events }}\n" +
"Metric: {{$v.Metric}}\n" +
"MetricElements: {{$v.MetricElements}}\n" +
"Timestamp: {{$v.Timestamp}}\n" +
"Value: {{$v.Value}}\n" +
"State: {{$v.State}}\n" +
"{{end}}\n" +
"https://grafana.yourhost.com/some-dashboard{{ range $i, $v := .Events }}{{ if ne $i 0 }}&{{ else }}?{{ end }}var-host={{ $v.Metric }}{{ end }}\n"

var data = NotificationEvents{{Metric: "1"}, {Metric: "2"}}

Convey("Test nil data", func() {

expected, err := trigger.GetPopulatedDescription(nil)
So(err, ShouldBeNil)
So(`
Trigger name: TestName
https://grafana.yourhost.com/some-dashboard
`, ShouldResemble, expected)
})

Convey("Test data", func() {
expected, err := trigger.GetPopulatedDescription(data)
So(err, ShouldBeNil)
So("\nTrigger name: TestName\n\nMetric: 1\nMetricElements: [1]\nTimestamp: 0\nValue: <nil>\nState: \n\nMetric: 2\nMetricElements: [2]\nTimestamp: 0\nValue: <nil>\nState: \n\nhttps://grafana.yourhost.com/some-dashboard?var-host=1&var-host=2\n", ShouldResemble, expected)
})

Convey("Test description without templates", func() {
anotherText := "Another text"
trigger.Desc = anotherText
expected, err := trigger.GetPopulatedDescription(data)
So(err, ShouldBeNil)
So(anotherText, ShouldEqual, expected)
})
})
}

func TestScheduledNotification_GetKey(t *testing.T) {
Convey("Get key", t, func() {
notification := ScheduledNotification{
Expand Down
7 changes: 7 additions & 0 deletions notifier/notifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@ func (notifier *StandardNotifier) runSender(sender moira.Sender, ch chan Notific
notifier.logger.Errorf(buildErr)
}
}

desc, err := pkg.Trigger.GetPopulatedDescription(pkg.Events)
if err == nil {
notifier.logger.Errorf("Error populate description: %v", err)
pkg.Trigger.Desc = desc
}

err = sender.SendEvents(pkg.Events, pkg.Contact, pkg.Trigger, plot, pkg.Throttled)
if err == nil {
if metric, found := notifier.metrics.SendersOkMetrics.GetRegisteredMeter(pkg.Contact.Type); found {
Expand Down

0 comments on commit 1da9355

Please sign in to comment.