Support for interactive menus in message attachments (#9285)

Этот коммит содержится в:
Joram Wilander
2018-08-24 06:16:17 -04:00
коммит произвёл George Goldberg
родитель 9599f1a52f
Коммит 4de2e5206e
4 изменённых файлов: 89 добавлений и 8 удалений

Просмотреть файл

@@ -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
}

Просмотреть файл

@@ -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")

Просмотреть файл

@@ -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)
}

Просмотреть файл

@@ -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.