From 4de2e5206e23ee268b38f071ecd8fcbea0bbc80a Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Fri, 24 Aug 2018 06:16:17 -0400 Subject: Support for interactive menus in message attachments (#9285) --- api4/post.go | 7 ++++++- app/post.go | 8 +++++++- app/post_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++--- model/post.go | 28 +++++++++++++++++++++++++--- 4 files changed, 89 insertions(+), 8 deletions(-) diff --git a/api4/post.go b/api4/post.go index 2568ade0a..b4edc5124 100644 --- a/api4/post.go +++ b/api4/post.go @@ -513,7 +513,12 @@ func doPostAction(c *Context, w http.ResponseWriter, r *http.Request) { return } - if err := c.App.DoPostAction(c.Params.PostId, c.Params.ActionId, c.Session.UserId); err != nil { + actionRequest := model.DoPostActionRequestFromJson(r.Body) + if actionRequest == nil { + actionRequest = &model.DoPostActionRequest{} + } + + if err := c.App.DoPostAction(c.Params.PostId, c.Params.ActionId, c.Session.UserId, actionRequest.SelectedOption); err != nil { c.Err = err return } diff --git a/app/post.go b/app/post.go index 0eed87280..fb0ef3f78 100644 --- a/app/post.go +++ b/app/post.go @@ -852,7 +852,7 @@ func makeOpenGraphURLsAbsolute(og *opengraph.OpenGraph, requestURL string) { } } -func (a *App) DoPostAction(postId string, actionId string, userId string) *model.AppError { +func (a *App) DoPostAction(postId, actionId, userId, selectedOption string) *model.AppError { pchan := a.Srv.Store.Post().GetSingle(postId) var post *model.Post @@ -870,9 +870,15 @@ func (a *App) DoPostAction(postId string, actionId string, userId string) *model request := &model.PostActionIntegrationRequest{ UserId: userId, PostId: postId, + Type: action.Type, Context: action.Integration.Context, } + if action.Type == model.POST_ACTION_TYPE_SELECT { + request.DataSource = action.DataSource + request.Context["selected_option"] = selectedOption + } + req, _ := http.NewRequest("POST", action.Integration.URL, strings.NewReader(request.ToJson())) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") diff --git a/app/post_test.go b/app/post_test.go index 482197833..66a141172 100644 --- a/app/post_test.go +++ b/app/post_test.go @@ -134,6 +134,12 @@ func TestPostAction(t *testing.T) { err := json.NewDecoder(r.Body).Decode(&request) assert.NoError(t, err) assert.Equal(t, request.UserId, th.BasicUser.Id) + if request.Type == model.POST_ACTION_TYPE_SELECT { + assert.Equal(t, request.DataSource, "some_source") + assert.Equal(t, request.Context["selected_option"], "selected") + } else { + assert.Equal(t, request.DataSource, "") + } assert.Equal(t, "foo", request.Context["s"]) assert.EqualValues(t, 3, request.Context["n"]) fmt.Fprintf(w, `{"update": {"message": "updated"}, "ephemeral_text": "foo"}`) @@ -158,7 +164,9 @@ func TestPostAction(t *testing.T) { }, URL: ts.URL, }, - Name: "action", + Name: "action", + Type: "some_type", + DataSource: "some_source", }, }, }, @@ -175,11 +183,51 @@ func TestPostAction(t *testing.T) { require.NotEmpty(t, attachments[0].Actions) require.NotEmpty(t, attachments[0].Actions[0].Id) - err = th.App.DoPostAction(post.Id, "notavalidid", th.BasicUser.Id) + menuPost := 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{ + { + Text: "hello", + Actions: []*model.PostAction{ + { + Integration: &model.PostActionIntegration{ + Context: model.StringInterface{ + "s": "foo", + "n": 3, + }, + URL: ts.URL, + }, + Name: "action", + Type: model.POST_ACTION_TYPE_SELECT, + DataSource: "some_source", + }, + }, + }, + }, + }, + } + + post2, err := th.App.CreatePostAsUser(&menuPost) + require.Nil(t, err) + + attachments2, ok := post2.Props["attachments"].([]*model.SlackAttachment) + require.True(t, ok) + + require.NotEmpty(t, attachments2[0].Actions) + require.NotEmpty(t, attachments2[0].Actions[0].Id) + + err = th.App.DoPostAction(post.Id, "notavalidid", th.BasicUser.Id, "") require.NotNil(t, err) assert.Equal(t, http.StatusNotFound, err.StatusCode) - err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id) + err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "") + require.Nil(t, err) + + err = th.App.DoPostAction(post2.Id, attachments2[0].Actions[0].Id, th.BasicUser.Id, "selected") require.Nil(t, err) } diff --git a/model/post.go b/model/post.go index 635493c9d..82cc0a0e9 100644 --- a/model/post.go +++ b/model/post.go @@ -50,6 +50,8 @@ const ( PROPS_ADD_CHANNEL_MEMBER = "add_channel_member" POST_PROPS_ADDED_USER_ID = "addedUserId" POST_PROPS_DELETE_BY = "deleteBy" + POST_ACTION_TYPE_BUTTON = "button" + POST_ACTION_TYPE_SELECT = "select" ) type Post struct { @@ -108,21 +110,35 @@ type PostForIndexing struct { ParentCreateAt *int64 `json:"parent_create_at"` } +type DoPostActionRequest struct { + SelectedOption string `json:"selected_option"` +} + type PostAction struct { Id string `json:"id"` Name string `json:"name"` + Type string `json:"type"` + DataSource string `json:"data_source"` + Options []*PostActionOptions `json:"options"` Integration *PostActionIntegration `json:"integration,omitempty"` } +type PostActionOptions struct { + Text string `json:"text"` + Value string `json:"value"` +} + type PostActionIntegration struct { URL string `json:"url,omitempty"` Context StringInterface `json:"context,omitempty"` } type PostActionIntegrationRequest struct { - UserId string `json:"user_id"` - PostId string `json:"post_id"` - Context StringInterface `json:"context,omitempty"` + UserId string `json:"user_id"` + PostId string `json:"post_id"` + Type string `json:"type"` + DataSource string `json:"data_source"` + Context StringInterface `json:"context,omitempty"` } type PostActionIntegrationResponse struct { @@ -455,6 +471,12 @@ func (o *PostEphemeral) ToUnsanitizedJson() string { return string(b) } +func DoPostActionRequestFromJson(data io.Reader) *DoPostActionRequest { + var o *DoPostActionRequest + json.NewDecoder(data).Decode(&o) + return o +} + // RewriteImageURLs takes a message and returns a copy that has all of the image URLs replaced // according to the function f. For each image URL, f will be invoked, and the resulting markdown // will contain the URL returned by that invocation instead. -- cgit v1.2.3-1-g7c22