Skip to content

Commit

Permalink
feat: trigger description to telegram alerts (#1075)
Browse files Browse the repository at this point in the history
  • Loading branch information
AleksandrMatsko authored Sep 5, 2024
1 parent 78b2293 commit a8c5b00
Show file tree
Hide file tree
Showing 11 changed files with 627 additions and 57 deletions.
1 change: 1 addition & 0 deletions senders/mattermost/sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca
location,
uriFormatter,
descriptionFormatter,
msgformat.DefaultDescriptionCutter,
boldFormatter,
eventStringFormatter,
codeBlockStart,
Expand Down
37 changes: 22 additions & 15 deletions senders/msgformat/highlighter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"time"
"unicode/utf8"

"github.com/moira-alert/moira"
"github.com/moira-alert/moira/senders"
Expand All @@ -23,41 +24,47 @@ type BoldFormatter func(str string) string
// EventStringFormatter formats single event string.
type EventStringFormatter func(event moira.NotificationEvent, location *time.Location) string

// HighlightSyntaxFormatter formats message by using functions, emojis and some other highlight patterns.
type HighlightSyntaxFormatter struct {
// DescriptionCutter cuts the given description to fit max size.
type DescriptionCutter func(desc string, maxSize int) string

// highlightSyntaxFormatter formats message by using functions, emojis and some other highlight patterns.
type highlightSyntaxFormatter struct {
// emojiGetter used in titles for better description.
emojiGetter emoji_provider.StateEmojiGetter
frontURI string
location *time.Location
useEmoji bool
uriFormatter UriFormatter
descriptionFormatter DescriptionFormatter
descriptionCutter DescriptionCutter
boldFormatter BoldFormatter
eventsStringFormatter EventStringFormatter
codeBlockStart string
codeBlockEnd string
}

// NewHighlightSyntaxFormatter creates new HighlightSyntaxFormatter with given arguments.
// NewHighlightSyntaxFormatter creates new highlightSyntaxFormatter with given arguments.
func NewHighlightSyntaxFormatter(
emojiGetter emoji_provider.StateEmojiGetter,
useEmoji bool,
frontURI string,
location *time.Location,
uriFormatter UriFormatter,
descriptionFormatter DescriptionFormatter,
descriptionCutter DescriptionCutter,
boldFormatter BoldFormatter,
eventsStringFormatter EventStringFormatter,
codeBlockStart string,
codeBlockEnd string,
) MessageFormatter {
return &HighlightSyntaxFormatter{
return &highlightSyntaxFormatter{
emojiGetter: emojiGetter,
frontURI: frontURI,
location: location,
useEmoji: useEmoji,
uriFormatter: uriFormatter,
descriptionFormatter: descriptionFormatter,
descriptionCutter: descriptionCutter,
boldFormatter: boldFormatter,
eventsStringFormatter: eventsStringFormatter,
codeBlockStart: codeBlockStart,
Expand All @@ -66,25 +73,25 @@ func NewHighlightSyntaxFormatter(
}

// Format formats message using given params and formatter functions.
func (formatter *HighlightSyntaxFormatter) Format(params MessageFormatterParams) string {
func (formatter *highlightSyntaxFormatter) Format(params MessageFormatterParams) string {
var message strings.Builder
state := params.Events.GetCurrentState(params.Throttled)
emoji := formatter.emojiGetter.GetStateEmoji(state)

title := formatter.buildTitle(params.Events, params.Trigger, emoji, params.Throttled)
titleLen := len([]rune(title))
titleLen := utf8.RuneCountInString(title)

desc := formatter.descriptionFormatter(params.Trigger)
descLen := len([]rune(desc))
descLen := utf8.RuneCountInString(desc)

eventsString := formatter.buildEventsString(params.Events, -1, params.Throttled)
eventsStringLen := len([]rune(eventsString))
eventsStringLen := utf8.RuneCountInString(eventsString)

charsLeftAfterTitle := params.MessageMaxChars - titleLen

descNewLen, eventsNewLen := senders.CalculateMessagePartsLength(charsLeftAfterTitle, descLen, eventsStringLen)
if descLen != descNewLen {
desc = desc[:descNewLen] + "...\n"
desc = formatter.descriptionCutter(desc, descNewLen)
}
if eventsNewLen != eventsStringLen {
eventsString = formatter.buildEventsString(params.Events, eventsNewLen, params.Throttled)
Expand All @@ -96,7 +103,7 @@ func (formatter *HighlightSyntaxFormatter) Format(params MessageFormatterParams)
return message.String()
}

func (formatter *HighlightSyntaxFormatter) buildTitle(events moira.NotificationEvents, trigger moira.TriggerData, emoji string, throttled bool) string {
func (formatter *highlightSyntaxFormatter) buildTitle(events moira.NotificationEvents, trigger moira.TriggerData, emoji string, throttled bool) string {
state := events.GetCurrentState(throttled)
title := ""
if formatter.useEmoji {
Expand All @@ -122,11 +129,11 @@ func (formatter *HighlightSyntaxFormatter) buildTitle(events moira.NotificationE

// buildEventsString builds the string from moira events and limits it to charsForEvents.
// if charsForEvents is negative buildEventsString does not limit the events string.
func (formatter *HighlightSyntaxFormatter) buildEventsString(events moira.NotificationEvents, charsForEvents int, throttled bool) string {
func (formatter *highlightSyntaxFormatter) buildEventsString(events moira.NotificationEvents, charsForEvents int, throttled bool) string {
charsForThrottleMsg := 0
throttleMsg := fmt.Sprintf("\nPlease, %s to generate less events.", formatter.boldFormatter(changeRecommendation))
throttleMsg := fmt.Sprintf("\nPlease, %s to generate less events.", formatter.boldFormatter(ChangeTriggerRecommendation))
if throttled {
charsForThrottleMsg = len([]rune(throttleMsg))
charsForThrottleMsg = utf8.RuneCountInString(throttleMsg)
}
charsLeftForEvents := charsForEvents - charsForThrottleMsg

Expand All @@ -143,8 +150,8 @@ func (formatter *HighlightSyntaxFormatter) buildEventsString(events moira.Notifi
}

tailString = fmt.Sprintf("\n...and %d more events.", len(events)-eventsPrinted)
tailStringLen := len([]rune(formatter.codeBlockEnd)) + len("\n") + len([]rune(tailString))
if !(charsForEvents < 0) && (len([]rune(eventsString))+len([]rune(line)) > charsLeftForEvents-tailStringLen) {
tailStringLen := utf8.RuneCountInString(formatter.codeBlockEnd) + len("\n") + utf8.RuneCountInString(tailString)
if !(charsForEvents < 0) && (utf8.RuneCountInString(eventsString)+utf8.RuneCountInString(line) > charsLeftForEvents-tailStringLen) {
eventsLenLimitReached = true
break
}
Expand Down
3 changes: 2 additions & 1 deletion senders/msgformat/highlighter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func TestFormat(t *testing.T) {
location,
testUriFormatter,
testDescriptionFormatter,
DefaultDescriptionCutter,
testBoldFormatter,
testEventStringFormatter,
"```",
Expand Down Expand Up @@ -135,7 +136,7 @@ func TestFormat(t *testing.T) {
Convey("Long description and many events. both desc and events > msgLimit/2", func() {
actual := formatter.Format(getParams(longEvents, moira.TriggerData{Desc: longDesc}, false))
expected := "**NODATA**\n" +
strings.Repeat("a", 1984) + "...\n" +
strings.Repeat("a", 1980) + "...\n" +
"```\n" +
strings.Repeat("02:40 (GMT+00:00): Metric = 123 (OK to NODATA)\n", 40) +
"02:40 (GMT+00:00): Metric = 123 (OK to NODATA)\n```\n" +
Expand Down
11 changes: 9 additions & 2 deletions senders/msgformat/msgformat.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Package msgformat provides MessageFormatter interface which may be used for formatting messages.
// Also, it contains some realizations such as HighlightSyntaxFormatter.
// Also, it contains some realizations such as highlightSyntaxFormatter.
package msgformat

import (
"github.com/moira-alert/moira"
)

const changeRecommendation = "fix your system or tune this trigger"
const ChangeTriggerRecommendation = "fix your system or tune this trigger"

// MessageFormatter is used for formatting messages to send via telegram, mattermost, etc.
type MessageFormatter interface {
Expand All @@ -21,3 +21,10 @@ type MessageFormatterParams struct {
MessageMaxChars int
Throttled bool
}

// DefaultDescriptionCutter cuts description, so len(newDesc) <= maxSize. Ensure that len(desc) >= maxSize and
// maxSize >= len("...\n").
func DefaultDescriptionCutter(desc string, maxSize int) string {
suffix := "...\n"
return desc[:maxSize-len(suffix)] + suffix
}
1 change: 1 addition & 0 deletions senders/slack/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca
location,
uriFormatter,
descriptionFormatter,
msgformat.DefaultDescriptionCutter,
boldFormatter,
eventStringFormatter,
codeBlockStart,
Expand Down
Loading

0 comments on commit a8c5b00

Please sign in to comment.