Skip to content
Open
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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ The returned event payload is the same as the input.
}
```


### GetReactionMessageInfo
This command would retrieve the message text of reaction sent by a user.

```
{ "count": 50 , //default value is 100 mandatory
"message": "" ,
"threadTimestamp":"...",
"reactionUser":"...", // mandatory list of reactions for a user
"channelId": "...",
"threadTimestamp":"..."
}
```



## Events

### ReceivedMessage
Expand Down
37 changes: 36 additions & 1 deletion client/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type client interface {
SendMessage(message *slack.OutgoingMessage)
PostMessage(channel string, opts ...slack.MsgOption) (string, string, error)
GetConversations(params *slack.GetConversationsParameters) (channels []slack.Channel, nextCursor string, err error)
ListReactions(params slack.ListReactionsParameters) ([]slack.ReactedItem, *slack.Paging, error)
}

// our slack implementation makes consistent use of channel id
Expand All @@ -41,6 +42,7 @@ type Slack interface {
// GetConversations is a heavy call used to fetch data about all channels in a workspace
// intended to be cached, not called each time this is needed
GetConversations() ([]types.Conversation, error)
GetReactionMessageText(count int, user string, channelId, threadTimestamp string) (text string, err error)
}

type slackClient struct {
Expand Down Expand Up @@ -69,7 +71,7 @@ func NewSlack(token string) Slack {

const (
getConversationsLimit = 1000 // max 1000
excludeArchived = true
excludeArchived = true
)

func (sl *slackClient) GetConversations() ([]types.Conversation, error) {
Expand Down Expand Up @@ -208,3 +210,36 @@ func newUser(u *slack.User) user {
LastName: u.Profile.LastName,
}
}

func (sl *slackClient) GetReactionMessageText(count int, user string, channelId, threadTimestamp string) (text string, err error) {
params := slack.ListReactionsParameters{
Count: count,
User: user,
}
log.Debug().Msgf("Count = %v user = %s channel id= %v threadTimestamp= %v", count, user, channelId, threadTimestamp)

reaction, _, err := sl.client.ListReactions(params)
if err != nil {
log.Debug().Msgf("Error = %v", err)
return "", err
}

for i := range reaction {
if reaction[i].Type == "message" {
if reaction[i].Channel == channelId {
if reaction[i].Message.Timestamp == threadTimestamp {
log.Debug().Msgf("reaction match found added: Value of Type = %v , channel = %v Msg Timestamp = %v , Text = %v",
reaction[i].Type, reaction[i].Channel, reaction[i].Message.ThreadTimestamp,
reaction[i].Message.Text)
return reaction[i].Message.Text, nil
} else {
log.Debug().Msgf("reaction match not found with provided Timestamp")
}
} else {
log.Debug().Msgf("reaction match not found with provided Channel")
}
}
}
err = fmt.Errorf("message info not found for input timestamp and channel")
return "", err
}
69 changes: 68 additions & 1 deletion client/slack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,64 @@ func TestIncomingMessages(t *testing.T) {
}
}


func TestGetReactionMessageText(t *testing.T) {

// given
Before(t)

SlackMockClient.ListReactionsFunc = func(params slack.ListReactionsParameters) ([]slack.ReactedItem, *slack.Paging, error) {

u := slack.Item{
Type: "message",
Channel: "C030JSNMMT6",
Message: &slack.Message{
Msg: slack.Msg{
ClientMsgID: "6cff3493-f89d-4230-b89c-d2442c88983f",
Type: "message",
Channel: "C030JSNMMT6",
User: "U01NAB6ERFB",
Text: "test tickets",
Timestamp: "1652252021.476309",
ThreadTimestamp: "1652252021.476309",
IsStarred: false,
},
},
}
r := slack.ItemReaction{
Name: "create-a-ticket",
Count: 1,
Users: []string{"U01NAB6ERFB"},
}

slackReactedItem := slack.ReactedItem{
u,
[]slack.ItemReaction{r},
}

return []slack.ReactedItem{slackReactedItem}, nil, nil
}
v, err := SlackImpl.GetReactionMessageText(50, "U01NAB6ERFB", "C030JSNMMT6", "1652252021.476309")
assert.Equal(t, "test tickets", v)
assert.Equal(t, nil, err)

}

func TestGetReactionMessageTextError(t *testing.T) {

// given
Before(t)

SlackMockClient.ListReactionsFunc = func(params slack.ListReactionsParameters) ([]slack.ReactedItem, *slack.Paging, error) {

err := fmt.Errorf("some unknown error occurred")
return []slack.ReactedItem{}, nil, err
}
v, _ := SlackImpl.GetReactionMessageText(50, "U01NAB6ERFB", "C030JSNMMT6", "1652252021.476309")
assert.Equal(t, "", v)

}

// --- helpers ---

// this simulates messages coming from slack
Expand Down Expand Up @@ -213,7 +271,12 @@ type MockClient struct {
// map stores all the sent messages by channelId (key is channelId)
OutgoingMessages map[string][]*slack.OutgoingMessage
// Slice of rich messages
PostMessageFunc func(channel string, opts ...slack.MsgOption) (string, string, error)
PostMessageFunc func(channel string, opts ...slack.MsgOption) (string, string, error)
ListReactionsFunc func(params slack.ListReactionsParameters) ([]slack.ReactedItem, *slack.Paging, error)
}

func (m *MockClient) ListReactions(params slack.ListReactionsParameters) ([]slack.ReactedItem, *slack.Paging, error) {
return m.ListReactionsFunc(params)
}

func NewMockClient(t *testing.T) *MockClient {
Expand All @@ -224,6 +287,10 @@ func NewMockClient(t *testing.T) *MockClient {
m.PostMessageFunc = func(channel string, params ...slack.MsgOption) (string, string, error) {
return "", "", nil
}
m.ListReactionsFunc = func(params slack.ListReactionsParameters) ([]slack.ReactedItem, *slack.Paging, error) {
return m.ListReactionsFunc(params)
}

return m
}

Expand Down
87 changes: 87 additions & 0 deletions command/reaction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
Copyright (C) 2018 Expedia Group.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package command

import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

var ReactionMockSlack *MockSlack

func TestGetReactionListCommandIsPopulated(t *testing.T) {

command := GetReactionMessageInfo(ReactionMockSlack)

assert.Equal(t, "GetReactionMessageInfo", command.Name)
require.Equal(t, 2, len(command.OutputEvents))
assert.Equal(t, "GetReactionMessageInfoSuccess", command.OutputEvents[0].Name)
assert.Equal(t, "GetReactionMessageInfoFailed", command.OutputEvents[1].Name)
}

func TestGetReactionListReturnsGetReactionListSuccess(t *testing.T) {

BeforeMessage()

handler := GetReactionMessageInfo(ReactionMockSlack).Handler
event := handler([]byte(`{"count": 50 ,
"message": "" ,
"threadTimestamp":"1645441176.871569",
"reactionUser":"UXXXXXX",
"channelId": "CXXXXXXX",
"threadTimestamp":"1645441176.871569"
}`))
output := event.Payload.(GetReactionMessageInfoOutput)
assert.Equal(t, "GetReactionMessageInfoSuccess", event.EventDef.Name)
assert.Equal(t, "", output.Message)
assert.Equal(t, "CXXXXXXX", output.ChannelId)
}

func TestGetReactionListReturnsGetReactionMessageInfoFailedMissingTimestamp(t *testing.T) {

BeforeMessage()

handler := GetReactionMessageInfo(ReactionMockSlack).Handler
event := handler([]byte(`{"count": 50 ,
"message": "" ,
"reactionUser":"UXXXXXX",
"channelId": "CXXXXXXX",
"threadTimestamp":""
}`))
output := event.Payload.(GetReactionMessageInfoFailed)
output = output
assert.Equal(t, "GetReactionMessageInfoFailed", event.EventDef.Name)
assert.Equal(t, "missing Message Timestamp field", output.Error)
}

func TestGetReactionListReturnsGetReactionMessageInfoFailedMissingReactionUser(t *testing.T) {

BeforeMessage()

handler := GetReactionMessageInfo(ReactionMockSlack).Handler
event := handler([]byte(`{"count": 50 ,
"message": "" ,
"reactionUser":"",
"channelId": "CXXXXXXX",
"threadTimestamp":"213.4445"
}`))
output := event.Payload.(GetReactionMessageInfoFailed)
output = output
assert.Equal(t, "GetReactionMessageInfoFailed", event.EventDef.Name)
assert.Equal(t, "missing user id field", output.Error)
}
91 changes: 91 additions & 0 deletions command/reactions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package command

import (
"encoding/json"
"fmt"
"github.com/ExpediaGroup/flyte-client/flyte"
"github.com/ExpediaGroup/flyte-slack/client"
"strings"
)

var (
getReactionMessageInfoEventDef = flyte.EventDef{Name: "GetReactionMessageInfoSuccess"}
getReactionMessageInfoFailedEventDef = flyte.EventDef{Name: "GetReactionMessageInfoFailed"}
)

type GetReactionMessageInfoInput struct {
Count int `json:"count"`
Message string `json:"message"`
ThreadTimestamp string `json:"threadTimestamp"`
User string `json:"reactionUser"`
ChannelId string `json:"channelId"`
ItemUser string `json:"itemUser"`
}

type GetReactionMessageInfoOutput struct {
GetReactionMessageInfoInput
}

type GetReactionMessageInfoFailed struct {
GetReactionMessageInfoOutput
Error string `json:"error"`
}

func GetReactionMessageInfo(slack client.Slack) flyte.Command {

return flyte.Command{
Name: "GetReactionMessageInfo",
OutputEvents: []flyte.EventDef{getReactionMessageInfoEventDef, getReactionMessageInfoFailedEventDef},
Handler: getReactionMessageInfoHandler(slack),
}
}

func getReactionMessageInfoHandler(slack client.Slack) func(json.RawMessage) flyte.Event {

return func(rawInput json.RawMessage) flyte.Event {
input := GetReactionMessageInfoInput{}
if err := json.Unmarshal(rawInput, &input); err != nil {
return flyte.NewFatalEvent(fmt.Sprintf("input is not valid: %v", err))
}

errorMessages := []string{}
if input.ThreadTimestamp == "" {
errorMessages = append(errorMessages, "missing Message Timestamp field")
}
if input.ChannelId == "" {
errorMessages = append(errorMessages, "missing channel id field")
}
if input.User == "" {
errorMessages = append(errorMessages, "missing user id field")
}
if len(errorMessages) != 0 {
return getReactionMessageFailedEvent(input.Message, input.ChannelId, strings.Join(errorMessages, ", "))
}

issueSummary, err := slack.GetReactionMessageText(input.Count, input.User, input.ChannelId, input.ThreadTimestamp)
if err != nil {
resp := fmt.Errorf("Got the error = [%s] while listing the reaction for user %s Channel= %s ", err, input.User, input.ChannelId)
errorMessages = append(errorMessages, fmt.Sprintf("input is not valid: %v", resp))
return getReactionMessageFailedEvent(input.Message, input.ChannelId, strings.Join(errorMessages, ", "))
}

return getReactionMessageSuccessInfoEvent(issueSummary, input.ChannelId)
}
}

func getReactionMessageSuccessInfoEvent(message, channelId string) flyte.Event {

return flyte.Event{
EventDef: getReactionMessageInfoEventDef,
Payload: GetReactionMessageInfoOutput{GetReactionMessageInfoInput: GetReactionMessageInfoInput{Message: message, ChannelId: channelId}},
}
}

func getReactionMessageFailedEvent(message, channelId string, err string) flyte.Event {

output := GetReactionMessageInfoOutput{GetReactionMessageInfoInput{Message: message, ChannelId: channelId}}
return flyte.Event{
EventDef: getReactionMessageInfoFailedEventDef,
Payload: GetReactionMessageInfoFailed{GetReactionMessageInfoOutput: output, Error: err},
}
}
10 changes: 10 additions & 0 deletions command/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,13 @@ func (m *MockSlack) IncomingMessages() <-chan flyte.Event {
func (m *MockSlack) GetConversations() ([]types.Conversation, error) {
return []types.Conversation(nil), nil
}

func (m *MockSlack) GetReactionMessageText(count int, user string, channelId, threadTimestamp string) (text string, err error) {
count = 50
channelId = "CXXXXX"
threadTimestamp = "765523.455667"
if user == "UXXXXX" {
return "This is sample test reaction message for user", nil
}
return "", nil
}
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func packDef(slack client.Slack, cache cache.Cache) flyte.PackDef {
command.SendMessage(slack),
command.SendRichMessage(slack),
command.GetChannelInfo(slack, cache),
command.GetReactionMessageInfo(slack),
},
EventDefs: []flyte.EventDef{
{Name: "ReceivedMessage"},
Expand Down