summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/post.go14
-rw-r--r--model/incoming_webhook.go11
-rw-r--r--model/post.go46
-rw-r--r--model/utils.go20
-rw-r--r--store/sql_post_store.go11
-rw-r--r--store/sql_store.go35
-rw-r--r--web/react/components/post_body.jsx15
-rw-r--r--web/react/components/post_body_additional_content.jsx56
-rw-r--r--web/sass-files/sass/partials/_post.scss4
-rw-r--r--web/web.go11
10 files changed, 182 insertions, 41 deletions
diff --git a/api/post.go b/api/post.go
index b52db8752..3892d4ee8 100644
--- a/api/post.go
+++ b/api/post.go
@@ -147,7 +147,7 @@ func CreatePost(c *Context, post *model.Post, triggerWebhooks bool) (*model.Post
return rpost, nil
}
-func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIconUrl string) (*model.Post, *model.AppError) {
+func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIconUrl string, props model.StringInterface, postType string) (*model.Post, *model.AppError) {
// parse links into Markdown format
linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
@@ -155,7 +155,7 @@ func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIc
linkRegex := regexp.MustCompile(`<\s*(\S*)\s*>`)
text = linkRegex.ReplaceAllString(text, "${1}")
- post := &model.Post{UserId: c.Session.UserId, ChannelId: channelId, Message: text}
+ post := &model.Post{UserId: c.Session.UserId, ChannelId: channelId, Message: text, Type: postType}
post.AddProp("from_webhook", "true")
if utils.Cfg.ServiceSettings.EnablePostUsernameOverride {
@@ -174,6 +174,14 @@ func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIc
}
}
+ if len(props) > 0 {
+ for key, val := range props {
+ if key != "override_icon_url" && key != "override_username" && key != "from_webhook" {
+ post.AddProp(key, val)
+ }
+ }
+ }
+
if _, err := CreatePost(c, post, false); err != nil {
return nil, model.NewAppError("CreateWebhookPost", "Error creating post", "err="+err.Message)
}
@@ -286,7 +294,7 @@ func handleWebhookEventsAndForget(c *Context, post *model.Post, team *model.Team
newContext := &Context{mockSession, model.NewId(), "", c.Path, nil, c.teamURLValid, c.teamURL, c.siteURL, 0}
if text, ok := respProps["text"]; ok {
- if _, err := CreateWebhookPost(newContext, post.ChannelId, text, respProps["username"], respProps["icon_url"]); err != nil {
+ if _, err := CreateWebhookPost(newContext, post.ChannelId, text, respProps["username"], respProps["icon_url"], post.Props, post.Type); err != nil {
l4g.Error("Failed to create response post, err=%v", err)
}
}
diff --git a/model/incoming_webhook.go b/model/incoming_webhook.go
index be1984244..8ead0da9f 100644
--- a/model/incoming_webhook.go
+++ b/model/incoming_webhook.go
@@ -24,10 +24,13 @@ type IncomingWebhook struct {
}
type IncomingWebhookRequest struct {
- Text string `json:"text"`
- Username string `json:"username"`
- IconURL string `json:"icon_url"`
- ChannelName string `json:"channel"`
+ Text string `json:"text"`
+ Username string `json:"username"`
+ IconURL string `json:"icon_url"`
+ ChannelName string `json:"channel"`
+ Props StringInterface `json:"props"`
+ Attachments interface{} `json:"attachments"`
+ Type string `json:"type"`
}
func (o *IncomingWebhook) ToJson() string {
diff --git a/model/post.go b/model/post.go
index e0074b348..248d40321 100644
--- a/model/post.go
+++ b/model/post.go
@@ -10,27 +10,28 @@ import (
)
const (
- POST_DEFAULT = ""
- POST_JOIN_LEAVE = "join_leave"
+ POST_DEFAULT = ""
+ POST_SLACK_ATTACHMENT = "slack_attachment"
+ POST_JOIN_LEAVE = "join_leave"
)
type Post struct {
- Id string `json:"id"`
- CreateAt int64 `json:"create_at"`
- UpdateAt int64 `json:"update_at"`
- DeleteAt int64 `json:"delete_at"`
- UserId string `json:"user_id"`
- ChannelId string `json:"channel_id"`
- RootId string `json:"root_id"`
- ParentId string `json:"parent_id"`
- OriginalId string `json:"original_id"`
- Message string `json:"message"`
- ImgCount int64 `json:"img_count"`
- Type string `json:"type"`
- Props StringMap `json:"props"`
- Hashtags string `json:"hashtags"`
- Filenames StringArray `json:"filenames"`
- PendingPostId string `json:"pending_post_id" db:"-"`
+ Id string `json:"id"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ DeleteAt int64 `json:"delete_at"`
+ UserId string `json:"user_id"`
+ ChannelId string `json:"channel_id"`
+ RootId string `json:"root_id"`
+ ParentId string `json:"parent_id"`
+ OriginalId string `json:"original_id"`
+ Message string `json:"message"`
+ ImgCount int64 `json:"img_count"`
+ Type string `json:"type"`
+ Props StringInterface `json:"props"`
+ Hashtags string `json:"hashtags"`
+ Filenames StringArray `json:"filenames"`
+ PendingPostId string `json:"pending_post_id" db:"-"`
}
func (o *Post) ToJson() string {
@@ -103,7 +104,8 @@ func (o *Post) IsValid() *AppError {
return NewAppError("Post.IsValid", "Invalid hashtags", "id="+o.Id)
}
- if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE) {
+ // should be removed once more message types are supported
+ if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE || o.Type == POST_SLACK_ATTACHMENT) {
return NewAppError("Post.IsValid", "Invalid type", "id="+o.Type)
}
@@ -128,7 +130,7 @@ func (o *Post) PreSave() {
o.UpdateAt = o.CreateAt
if o.Props == nil {
- o.Props = make(map[string]string)
+ o.Props = make(map[string]interface{})
}
if o.Filenames == nil {
@@ -138,14 +140,14 @@ func (o *Post) PreSave() {
func (o *Post) MakeNonNil() {
if o.Props == nil {
- o.Props = make(map[string]string)
+ o.Props = make(map[string]interface{})
}
if o.Filenames == nil {
o.Filenames = []string{}
}
}
-func (o *Post) AddProp(key string, value string) {
+func (o *Post) AddProp(key string, value interface{}) {
o.MakeNonNil()
diff --git a/model/utils.go b/model/utils.go
index 681ade870..1e71836c1 100644
--- a/model/utils.go
+++ b/model/utils.go
@@ -17,6 +17,7 @@ import (
"time"
)
+type StringInterface map[string]interface{}
type StringMap map[string]string
type StringArray []string
type EncryptStringMap map[string]string
@@ -125,6 +126,25 @@ func ArrayFromJson(data io.Reader) []string {
}
}
+func StringInterfaceToJson(objmap map[string]interface{}) string {
+ if b, err := json.Marshal(objmap); err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func StringInterfaceFromJson(data io.Reader) map[string]interface{} {
+ decoder := json.NewDecoder(data)
+
+ var objmap map[string]interface{}
+ if err := decoder.Decode(&objmap); err != nil {
+ return make(map[string]interface{})
+ } else {
+ return objmap
+ }
+}
+
func IsLower(s string) bool {
if strings.ToLower(s) == s {
return true
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index fdae20f60..61cd109a1 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -9,8 +9,10 @@ import (
"strconv"
"strings"
+ l4g "code.google.com/p/log4go"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
+ "time"
)
type SqlPostStore struct {
@@ -38,6 +40,15 @@ func NewSqlPostStore(sqlStore *SqlStore) PostStore {
}
func (s SqlPostStore) UpgradeSchemaIfNeeded() {
+ col := s.GetColumnInformation("Posts", "Props")
+ if col.Type != "text" {
+ _, err := s.GetMaster().Exec("ALTER TABLE Posts MODIFY COLUMN Props TEXT")
+ if err != nil {
+ l4g.Critical("Failed to alter column Posts.Props to TEXT: " + err.Error())
+ time.Sleep(time.Second)
+ panic("Failed to alter column Posts.Props to TEXT: " + err.Error())
+ }
+ }
}
func (s SqlPostStore) CreateIndexesIfNotExists() {
diff --git a/store/sql_store.go b/store/sql_store.go
index e5c540e06..d2cca21d0 100644
--- a/store/sql_store.go
+++ b/store/sql_store.go
@@ -50,6 +50,15 @@ type SqlStore struct {
preference PreferenceStore
}
+type Column struct {
+ Field string
+ Type string
+ Null string
+ Key interface{}
+ Default interface{}
+ Extra interface{}
+}
+
func NewSqlStore() Store {
sqlStore := &SqlStore{}
@@ -455,6 +464,20 @@ func IsUniqueConstraintError(err string, mysql string, postgres string) bool {
return unique && field
}
+func (ss SqlStore) GetColumnInformation(tableName, columnName string) Column {
+ var col Column
+ err := ss.GetMaster().SelectOne(&col, "SHOW COLUMNS FROM "+tableName+" WHERE Field = :Columnname", map[string]interface{}{
+ "Columnname": columnName,
+ })
+ if err != nil {
+ l4g.Critical("Failed to get information for column %s from table %s: %v", tableName, columnName, err.Error())
+ time.Sleep(time.Second)
+ panic("Failed to get information for column " + tableName + " from table " + columnName + ": " + err.Error())
+ }
+
+ return col
+}
+
func (ss SqlStore) GetMaster() *gorp.DbMap {
return ss.master
}
@@ -529,6 +552,8 @@ func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {
return model.ArrayToJson(t), nil
case model.EncryptStringMap:
return encrypt([]byte(utils.Cfg.SqlSettings.AtRestEncryptKey), model.MapToJson(t))
+ case model.StringInterface:
+ return model.StringInterfaceToJson(t), nil
}
return val, nil
@@ -572,6 +597,16 @@ func (me mattermConverter) FromDb(target interface{}) (gorp.CustomScanner, bool)
return json.Unmarshal(b, target)
}
return gorp.CustomScanner{new(string), target, binder}, true
+ case *model.StringInterface:
+ binder := func(holder, target interface{}) error {
+ s, ok := holder.(*string)
+ if !ok {
+ return errors.New("FromDb: Unable to convert StringInterface to *string")
+ }
+ b := []byte(*s)
+ return json.Unmarshal(b, target)
+ }
+ return gorp.CustomScanner{new(string), target, binder}, true
}
return gorp.CustomScanner{}, false
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 4da13dace..5a157b792 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -7,7 +7,7 @@ const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx');
const TextFormatting = require('../utils/text_formatting.jsx');
const twemoji = require('twemoji');
-const PostAttachmentList = require('./post_attachment_list.jsx');
+const PostBodyAdditionalContent = require('./post_body_additional_content.jsx');
export default class PostBody extends React.Component {
constructor(props) {
@@ -317,15 +317,6 @@ export default class PostBody extends React.Component {
);
}
- let postAttachments = '';
- if (post.attachments && post.attachments.length) {
- postAttachments = (
- <PostAttachmentList
- attachments={post.attachments}
- />
- );
- }
-
return (
<div className='post-body'>
{comment}
@@ -341,7 +332,9 @@ export default class PostBody extends React.Component {
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.state.message)}}
/>
</div>
- {postAttachments}
+ <PostBodyAdditionalContent
+ post={post}
+ />
{fileAttachmentHolder}
{embed}
</div>
diff --git a/web/react/components/post_body_additional_content.jsx b/web/react/components/post_body_additional_content.jsx
new file mode 100644
index 000000000..8189ba2d3
--- /dev/null
+++ b/web/react/components/post_body_additional_content.jsx
@@ -0,0 +1,56 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const PostAttachmentList = require('./post_attachment_list.jsx');
+
+export default class PostBodyAdditionalContent extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getSlackAttachment = this.getSlackAttachment.bind(this);
+ this.getComponent = this.getComponent.bind(this);
+ }
+
+ componentWillMount() {
+ this.setState({type: this.props.post.type, shouldRender: Boolean(this.props.post.type)});
+ }
+
+ getSlackAttachment() {
+ const attachments = this.props.post.props && this.props.post.props.attachments || [];
+ return (
+ <PostAttachmentList
+ key={'post_body_additional_content' + this.props.post.id}
+ attachments={attachments}
+ />
+ );
+ }
+
+ getComponent() {
+ switch (this.state.type) {
+ case 'slack_attachment':
+ return this.getSlackAttachment();
+ }
+ }
+
+ render() {
+ let content = [];
+
+ if (this.state.shouldRender) {
+ const component = this.getComponent();
+
+ if (component) {
+ content = component;
+ }
+ }
+
+ return (
+ <div>
+ {content}
+ </div>
+ );
+ }
+}
+
+PostBodyAdditionalContent.propTypes = {
+ post: React.PropTypes.object.isRequired
+}; \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index db03a1578..b57c51242 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -668,6 +668,7 @@ body.ios {
}
.attachment__image {
max-width: 100%;
+ margin-bottom: 1em;
}
.attachment__thumb-container {
width: 20%;
@@ -682,5 +683,8 @@ body.ios {
.attachment___field-caption {
font-weight: 700;
}
+ .attachment___field p {
+ margin: 0;
+ }
}
} \ No newline at end of file
diff --git a/web/web.go b/web/web.go
index 51f6664b6..bd0154542 100644
--- a/web/web.go
+++ b/web/web.go
@@ -990,6 +990,15 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
channelName := parsedRequest.ChannelName
+ webhookType := parsedRequest.Type
+
+ if parsedRequest.Attachments != nil {
+ if len(parsedRequest.Props) == 0 {
+ parsedRequest.Props = make(model.StringInterface)
+ }
+ parsedRequest.Props["attachments"] = parsedRequest.Attachments
+ webhookType = model.POST_SLACK_ATTACHMENT
+ }
var hook *model.IncomingWebhook
if result := <-hchan; result.Err != nil {
@@ -1039,7 +1048,7 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
return
}
- if _, err := api.CreateWebhookPost(c, channel.Id, text, overrideUsername, overrideIconUrl); err != nil {
+ if _, err := api.CreateWebhookPost(c, channel.Id, text, overrideUsername, overrideIconUrl, parsedRequest.Props, webhookType); err != nil {
c.Err = err
return
}