[PLT-840] Add option to outgoing webhooks to reply to the posted message as a comment (#7807)
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
e2b165cf3e
Коммит
fdba2d50fd
@@ -5,6 +5,7 @@ package api4
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -121,6 +122,7 @@ func testCreatePostWithOutgoingHook(
|
|||||||
hookContentType, expectedContentType, message, triggerWord string,
|
hookContentType, expectedContentType, message, triggerWord string,
|
||||||
fileIds []string,
|
fileIds []string,
|
||||||
triggerWhen int,
|
triggerWhen int,
|
||||||
|
commentPostType bool,
|
||||||
) {
|
) {
|
||||||
th := Setup().InitBasic().InitSystemAdmin()
|
th := Setup().InitBasic().InitSystemAdmin()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
@@ -199,6 +201,7 @@ func testCreatePostWithOutgoingHook(
|
|||||||
}
|
}
|
||||||
|
|
||||||
expectedFormValues, _ := url.ParseQuery(expectedPayload.ToFormValues())
|
expectedFormValues, _ := url.ParseQuery(expectedPayload.ToFormValues())
|
||||||
|
|
||||||
if !reflect.DeepEqual(expectedFormValues, r.Form) {
|
if !reflect.DeepEqual(expectedFormValues, r.Form) {
|
||||||
t.Logf("Form values are: %q\n, should be: %q\n", r.Form, expectedFormValues)
|
t.Logf("Form values are: %q\n, should be: %q\n", r.Form, expectedFormValues)
|
||||||
success <- false
|
success <- false
|
||||||
@@ -206,6 +209,20 @@ func testCreatePostWithOutgoingHook(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
respPostType := "" //if is empty or post will do a normal post.
|
||||||
|
if commentPostType {
|
||||||
|
respPostType = model.OUTGOING_HOOK_RESPONSE_TYPE_COMMENT
|
||||||
|
}
|
||||||
|
|
||||||
|
outGoingHookResponse := &model.OutgoingWebhookResponse{
|
||||||
|
Text: model.NewString("some test text"),
|
||||||
|
Username: "TestCommandServer",
|
||||||
|
IconURL: "https://www.mattermost.org/wp-content/uploads/2016/04/icon.png",
|
||||||
|
Type: "custom_as",
|
||||||
|
ResponseType: respPostType,
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, outGoingHookResponse.ToJson())
|
||||||
success <- true
|
success <- true
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
@@ -250,29 +267,53 @@ func testCreatePostWithOutgoingHook(
|
|||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
t.Fatal("Timeout, test server did not send the webhook.")
|
t.Fatal("Timeout, test server did not send the webhook.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if commentPostType {
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
postList, resp := th.SystemAdminClient.GetPostThread(post.Id, "")
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
if postList.Order[0] != post.Id {
|
||||||
|
t.Fatal("wrong order")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := postList.Posts[post.Id]; !ok {
|
||||||
|
t.Fatal("should have had post")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(postList.Posts) != 2 {
|
||||||
|
t.Fatal("should have 2 posts")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreatePostWithOutgoingHook_form_urlencoded(t *testing.T) {
|
func TestCreatePostWithOutgoingHook_form_urlencoded(t *testing.T) {
|
||||||
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH)
|
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH, false)
|
||||||
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH)
|
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH, false)
|
||||||
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "", "", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH)
|
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "", "", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH, false)
|
||||||
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "", "", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH)
|
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "", "", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH, false)
|
||||||
|
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH, true)
|
||||||
|
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreatePostWithOutgoingHook_json(t *testing.T) {
|
func TestCreatePostWithOutgoingHook_json(t *testing.T) {
|
||||||
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerword lorem ipsum", "triggerword", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_EXACT_MATCH)
|
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerword lorem ipsum", "triggerword", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_EXACT_MATCH, false)
|
||||||
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_STARTS_WITH)
|
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_STARTS_WITH, false)
|
||||||
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerword lorem ipsum", "", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH)
|
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerword lorem ipsum", "", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH, false)
|
||||||
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH)
|
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH, false)
|
||||||
|
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerword lorem ipsum", "triggerword", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_EXACT_MATCH, true)
|
||||||
|
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hooks created before we added the ContentType field should be considered as
|
// hooks created before we added the ContentType field should be considered as
|
||||||
// application/x-www-form-urlencoded
|
// application/x-www-form-urlencoded
|
||||||
func TestCreatePostWithOutgoingHook_no_content_type(t *testing.T) {
|
func TestCreatePostWithOutgoingHook_no_content_type(t *testing.T) {
|
||||||
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH)
|
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH, false)
|
||||||
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH)
|
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH, false)
|
||||||
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_EXACT_MATCH)
|
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_EXACT_MATCH, false)
|
||||||
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_STARTS_WITH)
|
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_STARTS_WITH, false)
|
||||||
|
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH, true)
|
||||||
|
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_EXACT_MATCH, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreatePostPublic(t *testing.T) {
|
func TestCreatePostPublic(t *testing.T) {
|
||||||
|
|||||||
@@ -110,10 +110,15 @@ func (a *App) TriggerWebhook(payload *model.OutgoingWebhookPayload, hook *model.
|
|||||||
l4g.Error(utils.T("api.post.handle_webhook_events_and_forget.event_post.error"), err.Error())
|
l4g.Error(utils.T("api.post.handle_webhook_events_and_forget.event_post.error"), err.Error())
|
||||||
} else {
|
} else {
|
||||||
defer consumeAndClose(resp)
|
defer consumeAndClose(resp)
|
||||||
|
|
||||||
webhookResp := model.OutgoingWebhookResponseFromJson(resp.Body)
|
webhookResp := model.OutgoingWebhookResponseFromJson(resp.Body)
|
||||||
|
|
||||||
if webhookResp != nil && webhookResp.Text != nil {
|
if webhookResp != nil && webhookResp.Text != nil {
|
||||||
if _, err := a.CreateWebhookPost(hook.CreatorId, channel, *webhookResp.Text, webhookResp.Username, webhookResp.IconURL, webhookResp.Props, webhookResp.Type); err != nil {
|
postRootId := ""
|
||||||
|
if webhookResp.ResponseType == model.OUTGOING_HOOK_RESPONSE_TYPE_COMMENT {
|
||||||
|
postRootId = post.Id
|
||||||
|
}
|
||||||
|
if _, err := a.CreateWebhookPost(hook.CreatorId, channel, *webhookResp.Text, webhookResp.Username, webhookResp.IconURL, webhookResp.Props, webhookResp.Type, postRootId); err != nil {
|
||||||
l4g.Error(utils.T("api.post.handle_webhook_events_and_forget.create_post.error"), err)
|
l4g.Error(utils.T("api.post.handle_webhook_events_and_forget.create_post.error"), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,12 +208,12 @@ func SplitWebhookPost(post *model.Post) ([]*model.Post, *model.AppError) {
|
|||||||
return splits, nil
|
return splits, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) CreateWebhookPost(userId string, channel *model.Channel, text, overrideUsername, overrideIconUrl string, props model.StringInterface, postType string) (*model.Post, *model.AppError) {
|
func (a *App) CreateWebhookPost(userId string, channel *model.Channel, text, overrideUsername, overrideIconUrl string, props model.StringInterface, postType string, postRootId string) (*model.Post, *model.AppError) {
|
||||||
// parse links into Markdown format
|
// parse links into Markdown format
|
||||||
linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
|
linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
|
||||||
text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
|
text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
|
||||||
|
|
||||||
post := &model.Post{UserId: userId, ChannelId: channel.Id, Message: text, Type: postType}
|
post := &model.Post{UserId: userId, ChannelId: channel.Id, Message: text, Type: postType, RootId: postRootId}
|
||||||
post.AddProp("from_webhook", "true")
|
post.AddProp("from_webhook", "true")
|
||||||
|
|
||||||
if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) {
|
if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) {
|
||||||
@@ -600,7 +605,7 @@ func (a *App) HandleIncomingWebhook(hookId string, req *model.IncomingWebhookReq
|
|||||||
overrideUsername := req.Username
|
overrideUsername := req.Username
|
||||||
overrideIconUrl := req.IconURL
|
overrideIconUrl := req.IconURL
|
||||||
|
|
||||||
_, err := a.CreateWebhookPost(hook.UserId, channel, text, overrideUsername, overrideIconUrl, req.Props, webhookType)
|
_, err := a.CreateWebhookPost(hook.UserId, channel, text, overrideUsername, overrideIconUrl, req.Props, webhookType, "")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func TestCreateWebhookPost(t *testing.T) {
|
|||||||
Text: "text",
|
Text: "text",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, model.POST_SLACK_ATTACHMENT)
|
}, model.POST_SLACK_ATTACHMENT, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ func TestCreateWebhookPost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", nil, model.POST_SYSTEM_GENERIC)
|
_, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", nil, model.POST_SYSTEM_GENERIC, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("should have failed - bad post type")
|
t.Fatal("should have failed - bad post type")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,13 +46,16 @@ type OutgoingWebhookPayload struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OutgoingWebhookResponse struct {
|
type OutgoingWebhookResponse struct {
|
||||||
Text *string `json:"text"`
|
Text *string `json:"text"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
IconURL string `json:"icon_url"`
|
IconURL string `json:"icon_url"`
|
||||||
Props StringInterface `json:"props"`
|
Props StringInterface `json:"props"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
ResponseType string `json:"response_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const OUTGOING_HOOK_RESPONSE_TYPE_COMMENT = "comment"
|
||||||
|
|
||||||
func (o *OutgoingWebhookPayload) ToJSON() string {
|
func (o *OutgoingWebhookPayload) ToJSON() string {
|
||||||
b, err := json.Marshal(o)
|
b, err := json.Marshal(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user