From ba06796b8c98291f9077cf5668c6088300502d60 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Tue, 16 Apr 2024 07:05:30 -0600 Subject: [PATCH] save what i need from old .Message --- .message_test.go | 157 ------------------------------------------- go.mod | 2 + go.sum | 4 ++ slack.go | 169 +++++++++++++++++++++++++++++++++++++++++++++-- slack_test.go | 156 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 327 insertions(+), 161 deletions(-) delete mode 100644 .message_test.go diff --git a/.message_test.go b/.message_test.go deleted file mode 100644 index dd7581b..0000000 --- a/.message_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package main - -import ( - "os" - "path" - "testing" -) - -func TestParseSlackTestdata(t *testing.T) { - t.Parallel() - cases := map[string]struct { - inCh string - slackMessage slackMessage - message Message - }{ - "human_thread_message_from_opsgenie_alert.json": { - slackMessage: slackMessage{ - TS: 1712930706, - Event: slackEvent{ - ID: "1712930706.598629", - Channel: "C06U1DDBBU4", - ParentID: "1712927439.728409", - Text: "I gotta do this", - Blocks: []slackBlock{{ - Elements: []slackElement{{ - Elements: []slackElement{{ - RichText: "I gotta do this", - }}, - }}, - }}, - Bot: slackBot{ - Name: "", - }, - Attachments: []slackAttachment{}, - }, - }, - message: Message{ - ID: "1712927439.728409/1712930706", - TS: 1712930706, - Source: "https://renderinc.slack.com/archives/C06U1DDBBU4/p1712927439728409", - Channel: "C06U1DDBBU4", - Thread: "1712927439.728409", - EventName: "", - Event: "", - Plaintext: "I gotta do this", - Asset: "", - }, - }, - "opsgenie_alert.json": { - slackMessage: slackMessage{ - TS: 1712927439, - Event: slackEvent{ - ID: "1712927439.728409", - Channel: "C06U1DDBBU4", - Bot: slackBot{ - Name: "Opsgenie for Alert Management", - }, - Attachments: []slackAttachment{{ - Color: "F4511E", - Title: "#11071: [Grafana]: Firing: Alertconfig Workflow Failed", - Text: "At least one alertconfig run has failed unexpectedly.\nDashboard: \nPanel: \nSource: ", - Fields: []slackField{ - {Value: "P3", Title: "Priority"}, - {Value: "alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb", Title: "Tags"}, - {Value: "Datastores Non-Critical", Title: "Routed Teams"}, - }, - Actions: []slackAction{{}, {}, {}}, - }}, - }, - }, - message: Message{ - ID: "1712927439.728409/1712927439", - TS: 1712927439, - Source: "https://renderinc.slack.com/archives/C06U1DDBBU4/p1712927439728409", - Channel: "C06U1DDBBU4", - Thread: "1712927439.728409", - EventName: "Alertconfig Workflow Failed", - Event: "11071", - Plaintext: "At least one alertconfig run has failed unexpectedly.\nDashboard: \nPanel: \nSource: ", - Asset: "", - }, - }, - "opsgenie_alert_resolved.json": { - slackMessage: slackMessage{ - TS: 1712916339, - Event: slackEvent{ - ID: "1712916339.000300", - Channel: "C06U1DDBBU4", - Bot: slackBot{ - Name: "Opsgenie for Alert Management", - }, - Attachments: []slackAttachment{{ - Color: "2ecc71", - Title: "#11069: [Grafana]: Firing: Alertconfig Workflow Failed", - Text: "At least one alertconfig run has failed unexpectedly.\nDashboard: \nPanel: \nSource: ", - Fields: []slackField{ - {Value: "P3", Title: "Priority"}, - {Value: "alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb", Title: "Tags"}, - {Value: "Datastores Non-Critical", Title: "Routed Teams"}, - }, - Actions: []slackAction{}, - }}, - }, - }, - message: Message{ - ID: "1712916339.000300/1712916339", - TS: 1712916339, - Source: "https://renderinc.slack.com/archives/C06U1DDBBU4/p1712916339000300", - Channel: "C06U1DDBBU4", - Thread: "1712916339.000300", - EventName: "Alertconfig Workflow Failed", - Event: "11069", - Plaintext: "At least one alertconfig run has failed unexpectedly.\nDashboard: \nPanel: \nSource: ", - Asset: "", - Resolved: true, - }, - }, - "reingested_alert.json": { - inCh: "C06U1DDBBU4", - message: Message{ - ID: "1712892637.037639/1712892637", - TS: 1712892637, - Source: "https://renderinc.slack.com/archives/C06U1DDBBU4/p1712892637037639", - Channel: "C06U1DDBBU4", - Thread: "1712892637.037639", - EventName: "Alertconfig Workflow Failed", - Event: "11061", - Plaintext: "At least one alertconfig run has failed unexpectedly.\nDashboard: \nPanel: \nSource: ", - Asset: "", - Resolved: true, - }, - }, - } - - for name, d := range cases { - want := d - t.Run(name, func(t *testing.T) { - b, err := os.ReadFile(path.Join("testdata", "slack_events", name)) - if err != nil { - t.Fatal(err) - } - - t.Run("ParseSlackFromChannel "+want.inCh, func(t *testing.T) { - got, err := ParseSlackFromChannel(b, renderAssetPattern, renderDatacenterPattern, renderEventNamePattern, want.inCh) - if err != nil { - t.Fatal(err) - } - if got != want.message { - t.Errorf("wanted \n\t%+v, got\n\t%+v", want.message, got) - } - if time := got.Time(); time.Unix() != int64(got.TS) { - t.Error("not unix time", got.TS, time) - } - }) - }) - } -} diff --git a/go.mod b/go.mod index 5b3a94d..6178823 100644 --- a/go.mod +++ b/go.mod @@ -16,9 +16,11 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pkoukk/tiktoken-go v0.1.6 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect golang.org/x/sys v0.16.0 // indirect + gotest.tools v2.2.0+incompatible // indirect modernc.org/libc v1.22.5 // indirect modernc.org/mathutil v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect diff --git a/go.sum b/go.sum index 79c08d4..49774e6 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/nikolaydubina/llama2.go v0.7.1 h1:ORmH1XbwFYGIOPHprkjtUPOEovlVXhnmnMjbMckaSyE= github.com/nikolaydubina/llama2.go v0.7.1/go.mod h1:ggXhXOaDnEAgSSkcYsomqx/RLjInxe5ZAbcJ+/Y2mTM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw= github.com/pkoukk/tiktoken-go v0.1.6/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -34,6 +36,8 @@ golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= diff --git a/slack.go b/slack.go index 028d1b7..dca13cf 100644 --- a/slack.go +++ b/slack.go @@ -2,12 +2,20 @@ package main import ( "context" + "encoding/json" "errors" "fmt" + "strconv" + "strings" + "time" "github.com/breel-render/spoc-bot-vr/model" ) +var ( + ErrIrrelevantMessage = errors.New("message isnt relevant to spoc bot vr") +) + type SlackToModel struct { pipeline Pipeline } @@ -36,14 +44,167 @@ func NewSlackToModelPipeline(ctx context.Context, cfg Config) (Pipeline, error) func newSlackToModelProcess(cfg Config) processFunc { return func(ctx context.Context, slack []byte) ([]byte, error) { - m, err := ParseSlack(slack, cfg.AssetPattern, cfg.DatacenterPattern, cfg.EventNamePattern) + slackMessage, err := parseSlack(slack) if err != nil { return nil, fmt.Errorf("failed to deserialize slack %w: %s", err, slack) } - return m.Serialize(), nil + return nil, errors.New("not impl") + _ = slackMessage + return json.Marshal(Models{}) } } -func ParseSlack([]byte, string, string, string) (interface{ Serialize() []byte }, error) { - return nil, errors.New("not impl") +type ( + parsedSlackMessage struct { + ID string + TS uint64 + Source string + Channel string + Thread string + EventName string + Event string + Plaintext string + Asset string + Resolved bool + Datacenter string + } + + slackMessage struct { + slackEvent + Type string + TS uint64 `json:"event_time"` + Event slackEvent + MessageTS string `json:"ts"` + } + + slackEvent struct { + ID string `json:"event_ts"` + Channel string + // rewrites + Nested *slackEvent `json:"message"` + PreviousMessage *slackEvent `json:"previous_message"` + // human + ParentID string `json:"thread_ts"` + Text string + Blocks []slackBlock + // bot + Bot slackBot `json:"bot_profile"` + Attachments []slackAttachment + } + + slackBlock struct { + Elements []slackElement + } + + slackElement struct { + Elements []slackElement + RichText string `json:"text"` + } + + slackBot struct { + Name string + } + + slackAttachment struct { + Color string + Title string + Text string + Fields []slackField + Actions []slackAction + } + + slackField struct { + Value string + Title string + } + + slackAction struct{} +) + +func parseSlack(b []byte) (parsedSlackMessage, error) { + s, err := _parseSlack(b) + if err != nil { + return parsedSlackMessage{}, err + } + + /* + if ch != "" { + s.Event.Channel = ch + } + */ + + if s.Event.Bot.Name != "" { + if len(s.Event.Attachments) == 0 { + return parsedSlackMessage{}, ErrIrrelevantMessage + } else if !strings.Contains(s.Event.Attachments[0].Title, ": Firing: ") { + return parsedSlackMessage{}, ErrIrrelevantMessage + } + var tagsField string + for _, field := range s.Event.Attachments[0].Fields { + if field.Title == "Tags" { + tagsField = field.Value + } + } + return parsedSlackMessage{ + ID: fmt.Sprintf("%s/%v", s.Event.ID, s.TS), + TS: s.TS, + Source: fmt.Sprintf(`https://renderinc.slack.com/archives/%s/p%s`, s.Event.Channel, strings.ReplaceAll(s.Event.ID, ".", "")), + Channel: s.Event.Channel, + Thread: s.Event.ID, + EventName: strings.Split(s.Event.Attachments[0].Title, ": Firing: ")[1], + Event: strings.TrimPrefix(strings.Split(s.Event.Attachments[0].Title, ":")[0], "#"), + Plaintext: s.Event.Attachments[0].Text, + Asset: s.Event.Attachments[0].Text, + Resolved: !strings.HasPrefix(s.Event.Attachments[0].Color, "F"), + Datacenter: tagsField, + }, nil + } + + if s.Event.ParentID == "" { + return parsedSlackMessage{}, ErrIrrelevantMessage + } + return parsedSlackMessage{ + ID: fmt.Sprintf("%s/%v", s.Event.ParentID, s.TS), + TS: s.TS, + Source: fmt.Sprintf(`https://renderinc.slack.com/archives/%s/p%s`, s.Event.Channel, strings.ReplaceAll(s.Event.ParentID, ".", "")), + Channel: s.Event.Channel, + Thread: s.Event.ParentID, + EventName: "", + Event: "", + Plaintext: s.Event.Text, + Asset: "", + Datacenter: "", + }, nil +} + +func _parseSlack(b []byte) (slackMessage, error) { + var result slackMessage + err := json.Unmarshal(b, &result) + switch result.Type { + case "message": + result.Event = result.slackEvent + result.TS, _ = strconv.ParseUint(strings.Split(result.MessageTS, ".")[0], 10, 64) + result.Event.ID = result.MessageTS + } + if result.Event.Nested != nil && !result.Event.Nested.Empty() { + result.Event.Blocks = result.Event.Nested.Blocks + result.Event.Bot = result.Event.Nested.Bot + result.Event.Attachments = result.Event.Nested.Attachments + result.Event.Nested = nil + } + if result.Event.PreviousMessage != nil { + if result.Event.PreviousMessage.ID != "" { + result.Event.ID = result.Event.PreviousMessage.ID + } + result.Event.PreviousMessage = nil + } + return result, err +} + +func (this slackEvent) Empty() bool { + return fmt.Sprintf("%+v", this) == fmt.Sprintf("%+v", slackEvent{}) +} + +func (this parsedSlackMessage) Time() time.Time { + return time.Unix(int64(this.TS), 0) } diff --git a/slack_test.go b/slack_test.go index eab83ed..a13d036 100644 --- a/slack_test.go +++ b/slack_test.go @@ -2,8 +2,12 @@ package main import ( "context" + "os" + "path" "testing" "time" + + "gotest.tools/assert" ) func TestSlackToModelPipeline(t *testing.T) { @@ -47,3 +51,155 @@ func TestSlackToModelPipeline(t *testing.T) { } */ } + +func TestParseSlackTestdata(t *testing.T) { + t.Parallel() + cases := map[string]struct { + slackMessage slackMessage + message parsedSlackMessage + }{ + "human_thread_message_from_opsgenie_alert.json": { + slackMessage: slackMessage{ + TS: 1712930706, + Event: slackEvent{ + ID: "1712930706.598629", + Channel: "C06U1DDBBU4", + ParentID: "1712927439.728409", + Text: "I gotta do this", + Blocks: []slackBlock{{ + Elements: []slackElement{{ + Elements: []slackElement{{ + RichText: "I gotta do this", + }}, + }}, + }}, + Bot: slackBot{ + Name: "", + }, + Attachments: []slackAttachment{}, + }, + }, + message: parsedSlackMessage{ + ID: "1712927439.728409/1712930706", + TS: 1712930706, + Source: "https://renderinc.slack.com/archives/C06U1DDBBU4/p1712927439728409", + Channel: "C06U1DDBBU4", + Thread: "1712927439.728409", + EventName: "", + Event: "", + Plaintext: "I gotta do this", + Asset: "", + }, + }, + "opsgenie_alert.json": { + slackMessage: slackMessage{ + TS: 1712927439, + Event: slackEvent{ + ID: "1712927439.728409", + Channel: "C06U1DDBBU4", + Bot: slackBot{ + Name: "Opsgenie for Alert Management", + }, + Attachments: []slackAttachment{{ + Color: "F4511E", + Title: "#11071: [Grafana]: Firing: Alertconfig Workflow Failed", + Text: "At least one alertconfig run has failed unexpectedly.\nDashboard: \nPanel: \nSource: ", + Fields: []slackField{ + {Value: "P3", Title: "Priority"}, + {Value: "alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb", Title: "Tags"}, + {Value: "Datastores Non-Critical", Title: "Routed Teams"}, + }, + Actions: []slackAction{{}, {}, {}}, + }}, + }, + }, + message: parsedSlackMessage{ + ID: "1712927439.728409/1712927439", + TS: 1712927439, + Source: "https://renderinc.slack.com/archives/C06U1DDBBU4/p1712927439728409", + Channel: "C06U1DDBBU4", + Thread: "1712927439.728409", + EventName: "Alertconfig Workflow Failed", + Event: "11071", + Plaintext: "At least one alertconfig run has failed unexpectedly.\nDashboard: \nPanel: \nSource: ", + Asset: "At least one alertconfig run has failed unexpectedly.\nDashboard: \nPanel: \nSource: ", + Datacenter: "alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb", + }, + }, + "opsgenie_alert_resolved.json": { + slackMessage: slackMessage{ + TS: 1712916339, + Event: slackEvent{ + ID: "1712916339.000300", + Channel: "C06U1DDBBU4", + Bot: slackBot{ + Name: "Opsgenie for Alert Management", + }, + Attachments: []slackAttachment{{ + Color: "2ecc71", + Title: "#11069: [Grafana]: Firing: Alertconfig Workflow Failed", + Text: "At least one alertconfig run has failed unexpectedly.\nDashboard: \nPanel: \nSource: ", + Fields: []slackField{ + {Value: "P3", Title: "Priority"}, + {Value: "alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb", Title: "Tags"}, + {Value: "Datastores Non-Critical", Title: "Routed Teams"}, + }, + Actions: []slackAction{}, + }}, + }, + }, + message: parsedSlackMessage{ + ID: "1712916339.000300/1712916339", + TS: 1712916339, + Source: "https://renderinc.slack.com/archives/C06U1DDBBU4/p1712916339000300", + Channel: "C06U1DDBBU4", + Thread: "1712916339.000300", + EventName: "Alertconfig Workflow Failed", + Event: "11069", + Plaintext: "At least one alertconfig run has failed unexpectedly.\nDashboard: \nPanel: \nSource: ", + Asset: "At least one alertconfig run has failed unexpectedly.\nDashboard: \nPanel: \nSource: ", + Resolved: true, + Datacenter: "alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb", + }, + }, + "reingested_alert.json": { + message: parsedSlackMessage{ + ID: "1712892637.037639/1712892637", + TS: 1712892637, + Source: "https://renderinc.slack.com/archives//p1712892637037639", + //Channel: "C06U1DDBBU4", + Thread: "1712892637.037639", + EventName: "Alertconfig Workflow Failed", + Event: "11061", + Plaintext: "At least one alertconfig run has failed unexpectedly.\nDashboard: \nPanel: \nSource: ", + Asset: "At least one alertconfig run has failed unexpectedly.\nDashboard: \nPanel: \nSource: ", + Resolved: true, + Datacenter: "alertname:Alertconfig Workflow Failed, grafana_folder:Datastores, rule_uid:a7639f7e-6950-41be-850a-b22119f74cbb", + }, + }, + } + + for name, d := range cases { + want := d + t.Run(name, func(t *testing.T) { + b, err := os.ReadFile(path.Join("testdata", "slack_events", name)) + if err != nil { + t.Fatal(err) + } + + t.Run("parseSlack", func(t *testing.T) { + got, err := parseSlack(b) + if err != nil { + t.Fatal(err) + } + if got != want.message { + assert.DeepEqual(t, want.message, got) + t.Errorf("wanted \n\t%+v, got\n\t%+v", want.message, got) + } + if time := got.Time(); time.Unix() != int64(got.TS) { + t.Error("not unix time", got.TS, time) + } + }) + }) + } +}