From 213a072b38d29d3c3ec8e150584685b1144a7d6a Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 29 Aug 2017 16:14:59 -0500 Subject: PLT-6403: Interactive messages (#7274) * wip * finish first pass * requested changes * add DoPostAction to Client4 --- app/command.go | 2 +- app/notification.go | 4 --- app/post.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++++- app/post_test.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 138 insertions(+), 7 deletions(-) (limited to 'app') diff --git a/app/command.go b/app/command.go index b9d6748fb..a2e63a3d4 100644 --- a/app/command.go +++ b/app/command.go @@ -53,7 +53,7 @@ func CreateCommandPost(post *model.Post, teamId string, response *model.CommandR } post.ParentId = "" - SendEphemeralPost(teamId, post.UserId, post) + SendEphemeralPost(post.UserId, post) } return post, nil diff --git a/app/notification.go b/app/notification.go index 258606ad4..af7589934 100644 --- a/app/notification.go +++ b/app/notification.go @@ -182,7 +182,6 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe if hereNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel { hereNotification = false SendEphemeralPost( - team.Id, post.UserId, &model.Post{ ChannelId: post.ChannelId, @@ -195,7 +194,6 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe // If the channel has more than 1K users then @channel is disabled if channelNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel { SendEphemeralPost( - team.Id, post.UserId, &model.Post{ ChannelId: post.ChannelId, @@ -208,7 +206,6 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe // If the channel has more than 1K users then @all is disabled if allNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel { SendEphemeralPost( - team.Id, post.UserId, &model.Post{ ChannelId: post.ChannelId, @@ -734,7 +731,6 @@ func sendOutOfChannelMentions(sender *model.User, post *model.Post, teamId strin } SendEphemeralPost( - teamId, post.UserId, &model.Post{ ChannelId: post.ChannelId, diff --git a/app/post.go b/app/post.go index 5b83ab7a2..c852a90d2 100644 --- a/app/post.go +++ b/app/post.go @@ -4,8 +4,11 @@ package app import ( + "encoding/json" + "fmt" "net/http" "regexp" + "strings" l4g "github.com/alecthomas/log4go" "github.com/dyatlov/go-opengraph/opengraph" @@ -210,7 +213,7 @@ func parseSlackLinksToMarkdown(text string) string { return linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})") } -func SendEphemeralPost(teamId, userId string, post *model.Post) *model.Post { +func SendEphemeralPost(userId string, post *model.Post) *model.Post { post.Type = model.POST_EPHEMERAL // fill in fields which haven't been specified which have sensible defaults @@ -638,3 +641,65 @@ func GetOpenGraphMetadata(url string) *opengraph.OpenGraph { return og } + +func DoPostAction(postId string, actionId string, userId string) *model.AppError { + pchan := Srv.Store.Post().GetSingle(postId) + + var post *model.Post + if result := <-pchan; result.Err != nil { + return result.Err + } else { + post = result.Data.(*model.Post) + } + + action := post.GetAction(actionId) + if action == nil || action.Integration == nil { + return model.NewAppError("DoPostAction", "api.post.do_action.action_id.app_error", nil, fmt.Sprintf("action=%v", action), http.StatusNotFound) + } + + request := &model.PostActionIntegrationRequest{ + UserId: userId, + Context: action.Integration.Context, + } + + req, _ := http.NewRequest("POST", action.Integration.URL, strings.NewReader(request.ToJson())) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + resp, err := utils.HttpClient(false).Do(req) + if err != nil { + return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, fmt.Sprintf("status=%v", resp.StatusCode), http.StatusBadRequest) + } + + var response model.PostActionIntegrationResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest) + } + + if response.Update != nil { + response.Update.Id = postId + response.Update.AddProp("from_webhook", "true") + if _, err := UpdatePost(response.Update, false); err != nil { + return err + } + } + + if response.EphemeralText != "" { + ephemeralPost := &model.Post{} + ephemeralPost.Message = parseSlackLinksToMarkdown(response.EphemeralText) + ephemeralPost.ChannelId = post.ChannelId + ephemeralPost.RootId = post.RootId + if ephemeralPost.RootId == "" { + ephemeralPost.RootId = post.Id + } + ephemeralPost.UserId = userId + ephemeralPost.AddProp("from_webhook", "true") + SendEphemeralPost(userId, ephemeralPost) + } + + return nil +} diff --git a/app/post_test.go b/app/post_test.go index 416fbfc9e..ab8e27021 100644 --- a/app/post_test.go +++ b/app/post_test.go @@ -4,11 +4,18 @@ package app import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" "testing" "time" - "fmt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" ) func TestUpdatePostEditAt(t *testing.T) { @@ -68,3 +75,66 @@ func TestPostReplyToPostWhereRootPosterLeftChannel(t *testing.T) { t.Fatal(err) } } + +func TestPostAction(t *testing.T) { + th := Setup().InitBasic() + + allowedInternalConnections := *utils.Cfg.ServiceSettings.AllowedUntrustedInternalConnections + defer func() { + utils.Cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections + }() + *utils.Cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1" + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var request model.PostActionIntegrationRequest + err := json.NewDecoder(r.Body).Decode(&request) + assert.NoError(t, err) + assert.Equal(t, request.UserId, th.BasicUser.Id) + assert.Equal(t, "foo", request.Context["s"]) + assert.EqualValues(t, 3, request.Context["n"]) + fmt.Fprintf(w, `{"update": {"message": "updated"}, "ephemeral_text": "foo"}`) + })) + defer ts.Close() + + interactivePost := model.Post{ + Message: "Interactive post", + ChannelId: th.BasicChannel.Id, + PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()), + UserId: th.BasicUser.Id, + Props: model.StringInterface{ + "attachments": []*model.SlackAttachment{ + &model.SlackAttachment{ + Text: "hello", + Actions: []*model.PostAction{ + &model.PostAction{ + Integration: &model.PostActionIntegration{ + Context: model.StringInterface{ + "s": "foo", + "n": 3, + }, + URL: ts.URL, + }, + Name: "action", + }, + }, + }, + }, + }, + } + + post, err := CreatePostAsUser(&interactivePost) + require.Nil(t, err) + + attachments, ok := post.Props["attachments"].([]*model.SlackAttachment) + require.True(t, ok) + + require.NotEmpty(t, attachments[0].Actions) + require.NotEmpty(t, attachments[0].Actions[0].Id) + + err = DoPostAction(post.Id, "notavalidid", th.BasicUser.Id) + require.NotNil(t, err) + assert.Equal(t, http.StatusNotFound, err.StatusCode) + + err = DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id) + require.Nil(t, err) +} -- cgit v1.2.3-1-g7c22