Skip to content

Commit

Permalink
feat: add Google Chat thread support (argoproj#95)
Browse files Browse the repository at this point in the history
Support posting messages to a chat thread based on an arbitrary key.

See Chat API create message [query parameters](https://developers.google.com/chat/api/reference/rest/v1/spaces.messages/create#query-parameters).

Signed-off-by: Tais P. Hansen <[email protected]>

Signed-off-by: Tais P. Hansen <[email protected]>
Co-authored-by: pasha-codefresh <[email protected]>
  • Loading branch information
taisph and pasha-codefresh authored Nov 28, 2022
1 parent b605032 commit b36d196
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 4 deletions.
11 changes: 11 additions & 0 deletions docs/services/googlechat.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,14 @@ template.app-sync-succeeded: |
```
The card message can be written in JSON too.
## Chat Threads
It is possible send both simple text and card messages in a chat thread by specifying a unique key for the thread. The thread key can be defined as follows:
```yaml
template.app-sync-succeeded: |
message: The app {{ .app.metadata.name }} has succesfully synced!
googlechat:
threadKey: {{ .app.metadata.name }}
```
40 changes: 36 additions & 4 deletions pkg/services/googlechat.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
texttemplate "text/template"

"github.com/ghodss/yaml"
Expand All @@ -15,7 +16,8 @@ import (
)

type GoogleChatNotification struct {
Cards string `json:"cards"`
Cards string `json:"cards"`
ThreadKey string `json:"threadKey,omitempty"`
}

type googleChatMessage struct {
Expand Down Expand Up @@ -52,6 +54,12 @@ func (n *GoogleChatNotification) GetTemplater(name string, f texttemplate.FuncMa
if err != nil {
return nil, fmt.Errorf("error in '%s' googlechat.cards : %w", name, err)
}

threadKey, err := texttemplate.New(name).Funcs(f).Parse(n.ThreadKey)
if err != nil {
return nil, fmt.Errorf("error in '%s' googlechat.threadKey : %w", name, err)
}

return func(notification *Notification, vars map[string]interface{}) error {
if notification.GoogleChat == nil {
notification.GoogleChat = &GoogleChatNotification{}
Expand All @@ -63,6 +71,15 @@ func (n *GoogleChatNotification) GetTemplater(name string, f texttemplate.FuncMa
if val := cardsBuff.String(); val != "" {
notification.GoogleChat.Cards = val
}

var threadKeyBuff bytes.Buffer
if err := threadKey.Execute(&threadKeyBuff, vars); err != nil {
return err
}
if val := threadKeyBuff.String(); val != "" {
notification.GoogleChat.ThreadKey = val
}

return nil
}, nil
}
Expand Down Expand Up @@ -106,12 +123,22 @@ type googlechatClient struct {
url string
}

func (c *googlechatClient) sendMessage(message *googleChatMessage) (*webhookReturn, error) {
func (c *googlechatClient) sendMessage(message *googleChatMessage, threadKey string) (*webhookReturn, error) {
jsonMessage, err := json.Marshal(message)
if err != nil {
return nil, err
}
response, err := c.httpClient.Post(c.url, "application/json", bytes.NewReader(jsonMessage))

u, err := url.Parse(c.url)
if err != nil {
return nil, err
}
if threadKey != "" {
q := u.Query()
q.Add("threadKey", threadKey)
u.RawQuery = q.Encode()
}
response, err := c.httpClient.Post(u.String(), "application/json", bytes.NewReader(jsonMessage))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -143,7 +170,12 @@ func (s googleChatService) Send(notification Notification, dest Destination) err
return fmt.Errorf("cannot create message: %w", err)
}

body, err := client.sendMessage(message)
var threadKey string
if notification.GoogleChat != nil {
threadKey = notification.GoogleChat.ThreadKey
}

body, err := client.sendMessage(message, threadKey)
if err != nil {
return fmt.Errorf("cannot send message: %w", err)
}
Expand Down
74 changes: 74 additions & 0 deletions pkg/services/googlechat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,47 @@ func TestTextMessage_GoogleChat(t *testing.T) {
return
}

assert.Nil(t, notification.GoogleChat)

message, err := googleChatNotificationToMessage(notification)
if err != nil {
t.Error(err)
return
}

assert.NotNil(t, message)
assert.Equal(t, message.Text, "message value")
}

func TestTextMessageWithThreadKey_GoogleChat(t *testing.T) {
notificationTemplate := Notification{
Message: "message {{.value}}",
GoogleChat: &GoogleChatNotification{
ThreadKey: "{{.threadKey}}",
},
}

templater, err := notificationTemplate.GetTemplater("test", template.FuncMap{})
if err != nil {
t.Error(err)
return
}

notification := Notification{}

err = templater(&notification, map[string]interface{}{
"value": "value",
"threadKey": "testThreadKey",
})

if err != nil {
t.Error(err)
return
}

assert.NotNil(t, notification.GoogleChat)
assert.Equal(t, notification.GoogleChat.ThreadKey, "testThreadKey")

message, err := googleChatNotificationToMessage(notification)
if err != nil {
t.Error(err)
Expand Down Expand Up @@ -139,6 +180,12 @@ func TestSendMessage_NoError(t *testing.T) {
called := false
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
called = true

if err := req.ParseForm(); err != nil {
t.Fatal("error on parse form")
}
assert.False(t, req.Form.Has("threadKey"), "threadKey query param should not be set")

res.WriteHeader(http.StatusOK)
_, err := res.Write([]byte("{}"))
if err != nil {
Expand All @@ -155,3 +202,30 @@ func TestSendMessage_NoError(t *testing.T) {
assert.Nil(t, err)
assert.True(t, called)
}

func TestSendMessageWithThreadKey_NoError(t *testing.T) {
called := false
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
called = true

if err := req.ParseForm(); err != nil {
t.Fatal("error on parse form")
}
assert.Equal(t, "testThreadKey", req.Form.Get("threadKey"), "threadKey query param should be set")

res.WriteHeader(http.StatusOK)
_, err := res.Write([]byte("{}"))
if err != nil {
t.Fatal("error on write response body")
}
}))
defer func() { testServer.Close() }()

opts := GoogleChatOptions{WebhookUrls: map[string]string{"test": testServer.URL}}
service := NewGoogleChatService(opts).(*googleChatService)
notification := Notification{Message: "", GoogleChat: &GoogleChatNotification{ThreadKey: "testThreadKey"}}
destination := Destination{Recipient: "test"}
err := service.Send(notification, destination)
assert.Nil(t, err)
assert.True(t, called)
}

0 comments on commit b36d196

Please sign in to comment.