summaryrefslogtreecommitdiffstats
path: root/model
diff options
context:
space:
mode:
Diffstat (limited to 'model')
-rw-r--r--model/client4.go14
-rw-r--r--model/command_response.go23
-rw-r--r--model/command_response_test.go14
-rw-r--r--model/config.go18
-rw-r--r--model/incoming_webhook.go52
-rw-r--r--model/incoming_webhook_test.go14
-rw-r--r--model/job.go79
-rw-r--r--model/job_status.go59
-rw-r--r--model/license.go10
-rw-r--r--model/license_test.go8
-rw-r--r--model/outgoing_webhook.go63
-rw-r--r--model/outgoing_webhook_test.go2
-rw-r--r--model/post.go6
-rw-r--r--model/slack_attachment.go52
-rw-r--r--model/slack_attachment_test.go38
15 files changed, 298 insertions, 154 deletions
diff --git a/model/client4.go b/model/client4.go
index 9ecdf9a62..a19a17d3a 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -1809,7 +1809,7 @@ func (c *Client4) GetFileInfosForPost(postId string, etag string) ([]*FileInfo,
// GetPing will return ok if the running goRoutines are below the threshold and unhealthy for above.
func (c *Client4) GetPing() (string, *Response) {
- if r, err := c.DoApiGet(c.GetSystemRoute()+"/ping", ""); r.StatusCode == 500 {
+ if r, err := c.DoApiGet(c.GetSystemRoute()+"/ping", ""); r != nil && r.StatusCode == 500 {
defer r.Body.Close()
return "unhealthy", BuildErrorResponse(r, err)
} else if err != nil {
@@ -2788,22 +2788,22 @@ func (c *Client4) OpenGraph(url string) (map[string]string, *Response) {
// Jobs Section
-// GetJobStatus gets the status of a single job.
-func (c *Client4) GetJobStatus(id string) (*JobStatus, *Response) {
+// GetJob gets a single job.
+func (c *Client4) GetJob(id string) (*Job, *Response) {
if r, err := c.DoApiGet(c.GetJobsRoute()+fmt.Sprintf("/%v/status", id), ""); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
- return JobStatusFromJson(r.Body), BuildResponse(r)
+ return JobFromJson(r.Body), BuildResponse(r)
}
}
-// GetJobStatusesByType gets the status of all jobs of a given type, sorted with the job that most recently started first.
-func (c *Client4) GetJobStatusesByType(jobType string, page int, perPage int) ([]*JobStatus, *Response) {
+// GetJobsByType gets all jobs of a given type, sorted with the job that most recently started first.
+func (c *Client4) GetJobsByType(jobType string, page int, perPage int) ([]*Job, *Response) {
if r, err := c.DoApiGet(c.GetJobsRoute()+fmt.Sprintf("/type/%v/statuses?page=%v&per_page=%v", jobType, page, perPage), ""); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
- return JobStatusesFromJson(r.Body), BuildResponse(r)
+ return JobsFromJson(r.Body), BuildResponse(r)
}
}
diff --git a/model/command_response.go b/model/command_response.go
index 1b2e06cdf..e3253acc0 100644
--- a/model/command_response.go
+++ b/model/command_response.go
@@ -5,7 +5,6 @@ package model
import (
"encoding/json"
- "fmt"
"io"
)
@@ -15,12 +14,12 @@ const (
)
type CommandResponse struct {
- ResponseType string `json:"response_type"`
- Text string `json:"text"`
- Username string `json:"username"`
- IconURL string `json:"icon_url"`
- GotoLocation string `json:"goto_location"`
- Attachments []*SlackAttachment `json:"attachments"`
+ ResponseType string `json:"response_type"`
+ Text string `json:"text"`
+ Username string `json:"username"`
+ IconURL string `json:"icon_url"`
+ GotoLocation string `json:"goto_location"`
+ Attachments SlackAttachments `json:"attachments"`
}
func (o *CommandResponse) ToJson() string {
@@ -40,14 +39,8 @@ func CommandResponseFromJson(data io.Reader) *CommandResponse {
return nil
}
- // Ensure attachment fields are stored as strings
- for _, attachment := range o.Attachments {
- for _, field := range attachment.Fields {
- if field.Value != nil {
- field.Value = fmt.Sprintf("%v", field.Value)
- }
- }
- }
+ o.Text = ExpandAnnouncement(o.Text)
+ o.Attachments.Process()
return &o
}
diff --git a/model/command_response_test.go b/model/command_response_test.go
index b57a77608..df478ff2c 100644
--- a/model/command_response_test.go
+++ b/model/command_response_test.go
@@ -82,3 +82,17 @@ func TestCommandResponseFromJson(t *testing.T) {
t.Fatal("should've received correct second attachment value")
}
}
+
+func TestCommandResponseNullArrayItems(t *testing.T) {
+ payload := `{"attachments":[{"fields":[{"title":"foo","value":"bar","short":true}, null]}, null]}`
+ cr := CommandResponseFromJson(strings.NewReader(payload))
+ if cr == nil {
+ t.Fatal("CommandResponse should not be nil")
+ }
+ if len(cr.Attachments) != 1 {
+ t.Fatalf("expected one attachment")
+ }
+ if len(cr.Attachments[0].Fields) != 1 {
+ t.Fatalf("expected one field")
+ }
+}
diff --git a/model/config.go b/model/config.go
index 38d27d8bb..5add720d3 100644
--- a/model/config.go
+++ b/model/config.go
@@ -436,6 +436,11 @@ type DataRetentionSettings struct {
Enable *bool
}
+type JobSettings struct {
+ RunJobs *bool
+ RunScheduler *bool
+}
+
type Config struct {
ServiceSettings ServiceSettings
TeamSettings TeamSettings
@@ -462,6 +467,7 @@ type Config struct {
WebrtcSettings WebrtcSettings
ElasticSearchSettings ElasticSearchSettings
DataRetentionSettings DataRetentionSettings
+ JobSettings JobSettings
}
func (o *Config) ToJson() string {
@@ -1380,6 +1386,16 @@ func (o *Config) SetDefaults() {
*o.DataRetentionSettings.Enable = false
}
+ if o.JobSettings.RunJobs == nil {
+ o.JobSettings.RunJobs = new(bool)
+ *o.JobSettings.RunJobs = true
+ }
+
+ if o.JobSettings.RunScheduler == nil {
+ o.JobSettings.RunScheduler = new(bool)
+ *o.JobSettings.RunScheduler = true
+ }
+
o.defaultWebrtcSettings()
}
@@ -1646,8 +1662,6 @@ func (o *Config) Sanitize() {
o.SqlSettings.DataSourceSearchReplicas[i] = FAKE_SETTING
}
- *o.ElasticSearchSettings.ConnectionUrl = FAKE_SETTING
- *o.ElasticSearchSettings.Username = FAKE_SETTING
*o.ElasticSearchSettings.Password = FAKE_SETTING
}
diff --git a/model/incoming_webhook.go b/model/incoming_webhook.go
index 2235cb2c6..4ebc7e8b1 100644
--- a/model/incoming_webhook.go
+++ b/model/incoming_webhook.go
@@ -6,10 +6,8 @@ package model
import (
"bytes"
"encoding/json"
- "fmt"
"io"
"regexp"
- "strings"
)
const (
@@ -29,13 +27,13 @@ type IncomingWebhook struct {
}
type IncomingWebhookRequest struct {
- Text string `json:"text"`
- Username string `json:"username"`
- IconURL string `json:"icon_url"`
- ChannelName string `json:"channel"`
- Props StringInterface `json:"props"`
- Attachments []*SlackAttachment `json:"attachments"`
- Type string `json:"type"`
+ Text string `json:"text"`
+ Username string `json:"username"`
+ IconURL string `json:"icon_url"`
+ ChannelName string `json:"channel"`
+ Props StringInterface `json:"props"`
+ Attachments SlackAttachments `json:"attachments"`
+ Type string `json:"type"`
}
func (o *IncomingWebhook) ToJson() string {
@@ -193,39 +191,6 @@ func decodeIncomingWebhookRequest(by []byte) (*IncomingWebhookRequest, error) {
}
}
-// To mention @channel via a webhook in Slack, the message should contain
-// <!channel>, as explained at the bottom of this article:
-// https://get.slack.help/hc/en-us/articles/202009646-Making-announcements
-func expandAnnouncement(text string) string {
- c1 := "<!channel>"
- c2 := "@channel"
- if strings.Contains(text, c1) {
- return strings.Replace(text, c1, c2, -1)
- }
- return text
-}
-
-// Expand announcements in incoming webhooks from Slack. Those announcements
-// can be found in the text attribute, or in the pretext, text, title and value
-// attributes of the attachment structure. The Slack attachment structure is
-// documented here: https://api.slack.com/docs/attachments
-func expandAnnouncements(i *IncomingWebhookRequest) {
- i.Text = expandAnnouncement(i.Text)
-
- for _, attachment := range i.Attachments {
- attachment.Pretext = expandAnnouncement(attachment.Pretext)
- attachment.Text = expandAnnouncement(attachment.Text)
- attachment.Title = expandAnnouncement(attachment.Title)
-
- for _, field := range attachment.Fields {
- if field.Value != nil {
- // Ensure the value is set to a string if it is set
- field.Value = expandAnnouncement(fmt.Sprintf("%v", field.Value))
- }
- }
- }
-}
-
func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest {
buf := new(bytes.Buffer)
buf.ReadFrom(data)
@@ -241,7 +206,8 @@ func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest {
}
}
- expandAnnouncements(o)
+ o.Text = ExpandAnnouncement(o.Text)
+ o.Attachments.Process()
return o
}
diff --git a/model/incoming_webhook_test.go b/model/incoming_webhook_test.go
index f6baca988..36f8ed6e6 100644
--- a/model/incoming_webhook_test.go
+++ b/model/incoming_webhook_test.go
@@ -230,3 +230,17 @@ func TestIncomingWebhookRequestFromJson(t *testing.T) {
}
}
}
+
+func TestIncomingWebhookNullArrayItems(t *testing.T) {
+ payload := `{"attachments":[{"fields":[{"title":"foo","value":"bar","short":true}, null]}, null]}`
+ iwr := IncomingWebhookRequestFromJson(strings.NewReader(payload))
+ if iwr == nil {
+ t.Fatal("IncomingWebhookRequest should not be nil")
+ }
+ if len(iwr.Attachments) != 1 {
+ t.Fatalf("expected one attachment")
+ }
+ if len(iwr.Attachments[0].Fields) != 1 {
+ t.Fatalf("expected one field")
+ }
+}
diff --git a/model/job.go b/model/job.go
index d539b5bf9..b0567bf1a 100644
--- a/model/job.go
+++ b/model/job.go
@@ -3,7 +3,84 @@
package model
-type Job interface {
+import (
+ "encoding/json"
+ "io"
+)
+
+const (
+ JOB_TYPE_DATA_RETENTION = "data_retention"
+ JOB_TYPE_SEARCH_INDEXING = "search_indexing"
+
+ JOB_STATUS_PENDING = "pending"
+ JOB_STATUS_IN_PROGRESS = "in_progress"
+ JOB_STATUS_SUCCESS = "success"
+ JOB_STATUS_ERROR = "error"
+ JOB_STATUS_CANCEL_REQUESTED = "cancel_requested"
+ JOB_STATUS_CANCELED = "canceled"
+)
+
+type Job struct {
+ Id string `json:"id"`
+ Type string `json:"type"`
+ Priority int64 `json:"priority"`
+ CreateAt int64 `json:"create_at"`
+ StartAt int64 `json:"start_at"`
+ LastActivityAt int64 `json:"last_activity_at"`
+ Status string `json:"status"`
+ Progress int64 `json:"progress"`
+ Data map[string]interface{} `json:"data"`
+}
+
+func (js *Job) ToJson() string {
+ if b, err := json.Marshal(js); err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func JobFromJson(data io.Reader) *Job {
+ var status Job
+ if err := json.NewDecoder(data).Decode(&status); err == nil {
+ return &status
+ } else {
+ return nil
+ }
+}
+
+func JobsToJson(jobs []*Job) string {
+ if b, err := json.Marshal(jobs); err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func JobsFromJson(data io.Reader) []*Job {
+ var jobs []*Job
+ if err := json.NewDecoder(data).Decode(&jobs); err == nil {
+ return jobs
+ } else {
+ return nil
+ }
+}
+
+func (js *Job) DataToJson() string {
+ if b, err := json.Marshal(js.Data); err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+type Worker interface {
+ Run()
+ Stop()
+ JobChannel() chan<- Job
+}
+
+type Scheduler interface {
Run()
Stop()
}
diff --git a/model/job_status.go b/model/job_status.go
deleted file mode 100644
index cf490648f..000000000
--- a/model/job_status.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package model
-
-import (
- "encoding/json"
- "io"
-)
-
-const (
- JOB_TYPE_DATA_RETENTION = "data_retention"
- JOB_TYPE_SEARCH_INDEXING = "search_indexing"
-)
-
-type JobStatus struct {
- Id string `json:"id"`
- Type string `json:"type"`
- StartAt int64 `json:"start_at"`
- LastActivityAt int64 `json:"last_activity_at"`
- LastRunStartedAt int64 `json:"last_run_started_at"`
- LastRunCompletedAt int64 `json:"last_run_completed_at"`
- Status string `json:"status"`
- Data map[string]interface{} `json:"data"`
-}
-
-func (js *JobStatus) ToJson() string {
- if b, err := json.Marshal(js); err != nil {
- return ""
- } else {
- return string(b)
- }
-}
-
-func JobStatusFromJson(data io.Reader) *JobStatus {
- var status JobStatus
- if err := json.NewDecoder(data).Decode(&status); err == nil {
- return &status
- } else {
- return nil
- }
-}
-
-func JobStatusesToJson(statuses []*JobStatus) string {
- if b, err := json.Marshal(statuses); err != nil {
- return ""
- } else {
- return string(b)
- }
-}
-
-func JobStatusesFromJson(data io.Reader) []*JobStatus {
- var statuses []*JobStatus
- if err := json.NewDecoder(data).Decode(&statuses); err == nil {
- return statuses
- } else {
- return nil
- }
-}
diff --git a/model/license.go b/model/license.go
index 443d78282..8d53bd4cd 100644
--- a/model/license.go
+++ b/model/license.go
@@ -49,7 +49,7 @@ type Features struct {
MHPNS *bool `json:"mhpns"`
SAML *bool `json:"saml"`
PasswordRequirements *bool `json:"password_requirements"`
- ElasticSearch *bool `json:"elastic_search"`
+ Elasticsearch *bool `json:"elastic_search"`
Announcement *bool `json:"announcement"`
// after we enabled more features for webrtc we'll need to control them with this
FutureFeatures *bool `json:"future_features"`
@@ -68,7 +68,7 @@ func (f *Features) ToMap() map[string]interface{} {
"mhpns": *f.MHPNS,
"saml": *f.SAML,
"password": *f.PasswordRequirements,
- "elastic_search": *f.ElasticSearch,
+ "elastic_search": *f.Elasticsearch,
"future": *f.FutureFeatures,
}
}
@@ -139,9 +139,9 @@ func (f *Features) SetDefaults() {
*f.PasswordRequirements = *f.FutureFeatures
}
- if f.ElasticSearch == nil {
- f.ElasticSearch = new(bool)
- *f.ElasticSearch = *f.FutureFeatures
+ if f.Elasticsearch == nil {
+ f.Elasticsearch = new(bool)
+ *f.Elasticsearch = *f.FutureFeatures
}
if f.Announcement == nil {
diff --git a/model/license_test.go b/model/license_test.go
index 8b65d0700..952ab493e 100644
--- a/model/license_test.go
+++ b/model/license_test.go
@@ -45,7 +45,7 @@ func TestLicenseFeaturesSetDefaults(t *testing.T) {
CheckTrue(t, *f.MHPNS)
CheckTrue(t, *f.SAML)
CheckTrue(t, *f.PasswordRequirements)
- CheckTrue(t, *f.ElasticSearch)
+ CheckTrue(t, *f.Elasticsearch)
CheckTrue(t, *f.FutureFeatures)
f = Features{}
@@ -64,7 +64,7 @@ func TestLicenseFeaturesSetDefaults(t *testing.T) {
*f.MHPNS = true
*f.SAML = true
*f.PasswordRequirements = true
- *f.ElasticSearch = true
+ *f.Elasticsearch = true
f.SetDefaults()
@@ -80,7 +80,7 @@ func TestLicenseFeaturesSetDefaults(t *testing.T) {
CheckTrue(t, *f.MHPNS)
CheckTrue(t, *f.SAML)
CheckTrue(t, *f.PasswordRequirements)
- CheckTrue(t, *f.ElasticSearch)
+ CheckTrue(t, *f.Elasticsearch)
CheckFalse(t, *f.FutureFeatures)
}
@@ -161,7 +161,7 @@ func TestLicenseToFromJson(t *testing.T) {
CheckBool(t, *f1.MHPNS, *f.MHPNS)
CheckBool(t, *f1.SAML, *f.SAML)
CheckBool(t, *f1.PasswordRequirements, *f.PasswordRequirements)
- CheckBool(t, *f1.ElasticSearch, *f.ElasticSearch)
+ CheckBool(t, *f1.Elasticsearch, *f.Elasticsearch)
CheckBool(t, *f1.FutureFeatures, *f.FutureFeatures)
invalid := `{"asdf`
diff --git a/model/outgoing_webhook.go b/model/outgoing_webhook.go
index 3cfed9e74..59408c24e 100644
--- a/model/outgoing_webhook.go
+++ b/model/outgoing_webhook.go
@@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "net/http"
"net/url"
"strconv"
"strings"
@@ -41,6 +42,7 @@ type OutgoingWebhookPayload struct {
PostId string `json:"post_id"`
Text string `json:"text"`
TriggerWord string `json:"trigger_word"`
+ FileIds string `json:"file_ids"`
}
func (o *OutgoingWebhookPayload) ToJSON() string {
@@ -65,6 +67,7 @@ func (o *OutgoingWebhookPayload) ToFormValues() string {
v.Set("post_id", o.PostId)
v.Set("text", o.Text)
v.Set("trigger_word", o.TriggerWord)
+ v.Set("file_ids", o.FileIds)
return v.Encode()
}
@@ -112,69 +115,69 @@ func OutgoingWebhookListFromJson(data io.Reader) []*OutgoingWebhook {
func (o *OutgoingWebhook) IsValid() *AppError {
if len(o.Id) != 26 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.id.app_error", nil, "")
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Token) != 26 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.token.app_error", nil, "")
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.token.app_error", nil, "", http.StatusBadRequest)
}
if o.CreateAt == 0 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.create_at.app_error", nil, "id="+o.Id)
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if o.UpdateAt == 0 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.update_at.app_error", nil, "id="+o.Id)
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if len(o.CreatorId) != 26 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "")
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.ChannelId) != 0 && len(o.ChannelId) != 26 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "")
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.TeamId) != 26 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.team_id.app_error", nil, "")
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.team_id.app_error", nil, "", http.StatusBadRequest)
}
if len(fmt.Sprintf("%s", o.TriggerWords)) > 1024 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.words.app_error", nil, "")
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.words.app_error", nil, "", http.StatusBadRequest)
}
if len(o.TriggerWords) != 0 {
for _, triggerWord := range o.TriggerWords {
if len(triggerWord) == 0 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.trigger_words.app_error", nil, "")
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.trigger_words.app_error", nil, "", http.StatusBadRequest)
}
}
}
if len(o.CallbackURLs) == 0 || len(fmt.Sprintf("%s", o.CallbackURLs)) > 1024 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.callback.app_error", nil, "")
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.callback.app_error", nil, "", http.StatusBadRequest)
}
for _, callback := range o.CallbackURLs {
if !IsValidHttpUrl(callback) {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.url.app_error", nil, "")
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.url.app_error", nil, "", http.StatusBadRequest)
}
}
if len(o.DisplayName) > 64 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.display_name.app_error", nil, "")
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.display_name.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Description) > 128 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "")
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "", http.StatusBadRequest)
}
if len(o.ContentType) > 128 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "")
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest)
}
if o.TriggerWhen > 1 {
- return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "")
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest)
}
return nil
@@ -197,8 +200,8 @@ func (o *OutgoingWebhook) PreUpdate() {
o.UpdateAt = GetMillis()
}
-func (o *OutgoingWebhook) HasTriggerWord(word string) bool {
- if len(o.TriggerWords) == 0 || len(word) == 0 {
+func (o *OutgoingWebhook) TriggerWordExactMatch(word string) bool {
+ if len(word) == 0 {
return false
}
@@ -212,7 +215,7 @@ func (o *OutgoingWebhook) HasTriggerWord(word string) bool {
}
func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool {
- if len(o.TriggerWords) == 0 || len(word) == 0 {
+ if len(word) == 0 {
return false
}
@@ -224,3 +227,27 @@ func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool {
return false
}
+
+func (o *OutgoingWebhook) GetTriggerWord(word string, isExactMatch bool) (triggerWord string) {
+ if len(word) == 0 {
+ return
+ }
+
+ if isExactMatch {
+ for _, trigger := range o.TriggerWords {
+ if trigger == word {
+ triggerWord = trigger
+ break
+ }
+ }
+ } else {
+ for _, trigger := range o.TriggerWords {
+ if strings.HasPrefix(word, trigger) {
+ triggerWord = trigger
+ break
+ }
+ }
+ }
+
+ return triggerWord
+}
diff --git a/model/outgoing_webhook_test.go b/model/outgoing_webhook_test.go
index 725423cdf..431b1f6c1 100644
--- a/model/outgoing_webhook_test.go
+++ b/model/outgoing_webhook_test.go
@@ -136,6 +136,7 @@ func TestOutgoingWebhookPayloadToFormValues(t *testing.T) {
PostId: "PostId",
Text: "Text",
TriggerWord: "TriggerWord",
+ FileIds: "FileIds",
}
v := url.Values{}
v.Set("token", "Token")
@@ -149,6 +150,7 @@ func TestOutgoingWebhookPayloadToFormValues(t *testing.T) {
v.Set("post_id", "PostId")
v.Set("text", "Text")
v.Set("trigger_word", "TriggerWord")
+ v.Set("file_ids", "FileIds")
if got, want := p.ToFormValues(), v.Encode(); !reflect.DeepEqual(got, want) {
t.Fatalf("Got %+v, wanted %+v", got, want)
}
diff --git a/model/post.go b/model/post.go
index f5a398656..55e6f591d 100644
--- a/model/post.go
+++ b/model/post.go
@@ -62,6 +62,12 @@ type PostPatch struct {
HasReactions *bool `json:"has_reactions"`
}
+type PostForIndexing struct {
+ Post
+ TeamId string `json:"team_id"`
+ ParentCreateAt *int64 `json:"parent_create_at"`
+}
+
func (o *Post) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
diff --git a/model/slack_attachment.go b/model/slack_attachment.go
index 6fd0071b4..a3199c44c 100644
--- a/model/slack_attachment.go
+++ b/model/slack_attachment.go
@@ -3,6 +3,11 @@
package model
+import (
+ "fmt"
+ "strings"
+)
+
type SlackAttachment struct {
Id int64 `json:"id"`
Fallback string `json:"fallback"`
@@ -27,3 +32,50 @@ type SlackAttachmentField struct {
Value interface{} `json:"value"`
Short bool `json:"short"`
}
+
+type SlackAttachments []*SlackAttachment
+
+// To mention @channel via a webhook in Slack, the message should contain
+// <!channel>, as explained at the bottom of this article:
+// https://get.slack.help/hc/en-us/articles/202009646-Making-announcements
+func ExpandAnnouncement(text string) string {
+ c1 := "<!channel>"
+ c2 := "@channel"
+ if strings.Contains(text, c1) {
+ return strings.Replace(text, c1, c2, -1)
+ }
+ return text
+}
+
+// Expand announcements in incoming webhooks from Slack. Those announcements
+// can be found in the text attribute, or in the pretext, text, title and value
+// attributes of the attachment structure. The Slack attachment structure is
+// documented here: https://api.slack.com/docs/attachments
+func (a *SlackAttachments) Process() {
+ var nonNilAttachments []*SlackAttachment
+ for _, attachment := range *a {
+ if attachment == nil {
+ continue
+ }
+ nonNilAttachments = append(nonNilAttachments, attachment)
+
+ attachment.Pretext = ExpandAnnouncement(attachment.Pretext)
+ attachment.Text = ExpandAnnouncement(attachment.Text)
+ attachment.Title = ExpandAnnouncement(attachment.Title)
+
+ var nonNilFields []*SlackAttachmentField
+ for _, field := range attachment.Fields {
+ if field == nil {
+ continue
+ }
+ nonNilFields = append(nonNilFields, field)
+
+ if field.Value != nil {
+ // Ensure the value is set to a string if it is set
+ field.Value = ExpandAnnouncement(fmt.Sprintf("%v", field.Value))
+ }
+ }
+ attachment.Fields = nonNilFields
+ }
+ *a = nonNilAttachments
+}
diff --git a/model/slack_attachment_test.go b/model/slack_attachment_test.go
new file mode 100644
index 000000000..ff4df888f
--- /dev/null
+++ b/model/slack_attachment_test.go
@@ -0,0 +1,38 @@
+package model
+
+import (
+ "testing"
+)
+
+func TestExpandAnnouncement(t *testing.T) {
+ if ExpandAnnouncement("<!channel> foo <!channel>") != "@channel foo @channel" {
+ t.Fail()
+ }
+}
+
+func TestSlackAnnouncementProcess(t *testing.T) {
+ attachments := SlackAttachments{
+ {
+ Pretext: "<!channel> pretext",
+ Text: "<!channel> text",
+ Title: "<!channel> title",
+ Fields: []*SlackAttachmentField{
+ {
+ Title: "foo",
+ Value: "<!channel> bar",
+ Short: true,
+ }, nil,
+ },
+ }, nil,
+ }
+ attachments.Process()
+ if len(attachments) != 1 || len(attachments[0].Fields) != 1 {
+ t.Fail()
+ }
+ if attachments[0].Pretext != "@channel pretext" ||
+ attachments[0].Text != "@channel text" ||
+ attachments[0].Title != "@channel title" ||
+ attachments[0].Fields[0].Value != "@channel bar" {
+ t.Fail()
+ }
+}