Refactor to hit database less often.

Этот коммит содержится в:
JoramWilander
2015-10-16 11:17:24 -04:00
родитель 9de8bc4727
Коммит f24ea30a75
7 изменённых файлов: 89 добавлений и 161 удалений

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

@@ -137,55 +137,23 @@ func CreatePost(c *Context, post *model.Post, triggerWebhooks bool) (*model.Post
} else { } else {
rpost = result.Data.(*model.Post) rpost = result.Data.(*model.Post)
fireAndForgetNotifications(rpost, c.Session.TeamId, c.GetSiteURL()) handlePostEventsAndForget(c, rpost, triggerWebhooks)
if triggerWebhooks {
fireAndForgetWebhookEvent(c, rpost)
}
} }
return rpost, nil return rpost, nil
} }
func fireAndForgetWebhookEvent(c *Context, post *model.Post) { func handlePostEventsAndForget(c *Context, post *model.Post, triggerWebhooks bool) {
go func() { go func() {
tchan := Srv.Store.Team().Get(c.Session.TeamId)
chchan := Srv.Store.Webhook().GetOutgoingByChannel(post.ChannelId)
firstWord := strings.Split(post.Message, " ")[0]
var thchan store.StoreChannel
if len(firstWord) != 0 {
thchan = Srv.Store.Webhook().GetOutgoingByTriggerWord(c.Session.TeamId, post.ChannelId, firstWord)
}
hooks := []*model.OutgoingWebhook{}
if result := <-chchan; result.Err != nil {
l4g.Error("Encountered error getting webhook by channel, err=%v", result.Err)
return
} else {
hooks = append(hooks, result.Data.([]*model.OutgoingWebhook)...)
}
if thchan != nil {
if result := <-thchan; result.Err != nil {
l4g.Error("Encountered error getting webhook by trigger word, err=%v", result.Err)
return
} else {
hooks = append(hooks, result.Data.([]*model.OutgoingWebhook)...)
}
}
cchan := Srv.Store.Channel().Get(post.ChannelId) cchan := Srv.Store.Channel().Get(post.ChannelId)
uchan := Srv.Store.User().Get(post.UserId) uchan := Srv.Store.User().Get(post.UserId)
tchan := Srv.Store.Team().Get(c.Session.TeamId)
var team *model.Team var team *model.Team
if result := <-tchan; result.Err != nil { if result := <-tchan; result.Err != nil {
l4g.Error("Encountered error getting team, team_id=%s, err=%v", c.Session.TeamId, result.Err) l4g.Error("Encountered error getting team, team_id=%s, err=%v", c.Session.TeamId, result.Err)
return
} else { } else {
team = result.Data.(*model.Team) team = result.Data.(*model.Team)
} }
@@ -193,43 +161,76 @@ func fireAndForgetWebhookEvent(c *Context, post *model.Post) {
var channel *model.Channel var channel *model.Channel
if result := <-cchan; result.Err != nil { if result := <-cchan; result.Err != nil {
l4g.Error("Encountered error getting channel, channel_id=%s, err=%v", post.ChannelId, result.Err) l4g.Error("Encountered error getting channel, channel_id=%s, err=%v", post.ChannelId, result.Err)
return
} else { } else {
channel = result.Data.(*model.Channel) channel = result.Data.(*model.Channel)
} }
fireAndForgetNotifications(c, post, team, channel)
var user *model.User var user *model.User
if result := <-uchan; result.Err != nil { if result := <-uchan; result.Err != nil {
l4g.Error("Encountered error getting user, user_id=%s, err=%v", post.UserId, result.Err) l4g.Error("Encountered error getting user, user_id=%s, err=%v", post.UserId, result.Err)
return
} else { } else {
user = result.Data.(*model.User) user = result.Data.(*model.User)
} }
if triggerWebhooks {
handleWebhookEventsAndForget(c, post, team, channel, user)
}
}()
}
func handleWebhookEventsAndForget(c *Context, post *model.Post, team *model.Team, channel *model.Channel, user *model.User) {
go func() {
hchan := Srv.Store.Webhook().GetOutgoingByTeam(c.Session.TeamId)
hooks := []*model.OutgoingWebhook{}
if result := <-hchan; result.Err != nil {
l4g.Error("Encountered error getting webhooks by team, err=%v", result.Err)
return
} else {
hooks = result.Data.([]*model.OutgoingWebhook)
}
if len(hooks) == 0 {
return
}
firstWord := strings.Split(post.Message, " ")[0]
relevantHooks := []*model.OutgoingWebhook{}
for _, hook := range hooks { for _, hook := range hooks {
if hook.ChannelId == post.ChannelId {
if len(hook.TriggerWords) == 0 || hook.HasTriggerWord(firstWord) {
relevantHooks = append(relevantHooks, hook)
}
} else if len(hook.ChannelId) == 0 && hook.HasTriggerWord(firstWord) {
relevantHooks = append(relevantHooks, hook)
}
}
for _, hook := range relevantHooks {
go func() { go func() {
p := url.Values{} p := url.Values{}
p.Set("token", hook.Token) p.Set("token", hook.Token)
p.Set("team_id", hook.TeamId)
if team != nil { p.Set("team_id", hook.TeamId)
p.Set("team_domain", team.Name) p.Set("team_domain", team.Name)
}
p.Set("channel_id", post.ChannelId) p.Set("channel_id", post.ChannelId)
if channel != nil { p.Set("channel_name", channel.Name)
p.Set("channel_name", channel.Name)
}
p.Set("timestamp", strconv.FormatInt(post.CreateAt/1000, 10)) p.Set("timestamp", strconv.FormatInt(post.CreateAt/1000, 10))
p.Set("user_id", post.UserId) p.Set("user_id", post.UserId)
if user != nil { p.Set("user_name", user.Username)
p.Set("user_name", user.Username)
}
p.Set("text", post.Message) p.Set("text", post.Message)
if len(hook.TriggerWords) > 0 { p.Set("trigger_word", firstWord)
p.Set("trigger_word", firstWord)
}
client := &http.Client{} client := &http.Client{}
@@ -260,38 +261,29 @@ func fireAndForgetWebhookEvent(c *Context, post *model.Post) {
} }
func fireAndForgetNotifications(post *model.Post, teamId, siteURL string) { func fireAndForgetNotifications(c *Context, post *model.Post, team *model.Team, channel *model.Channel) {
go func() { go func() {
// Get a list of user names (to be used as keywords) and ids for the given team // Get a list of user names (to be used as keywords) and ids for the given team
uchan := Srv.Store.User().GetProfiles(teamId) uchan := Srv.Store.User().GetProfiles(c.Session.TeamId)
echan := Srv.Store.Channel().GetMembers(post.ChannelId) echan := Srv.Store.Channel().GetMembers(post.ChannelId)
cchan := Srv.Store.Channel().Get(post.ChannelId)
tchan := Srv.Store.Team().Get(teamId)
var channel *model.Channel
var channelName string var channelName string
var bodyText string var bodyText string
var subjectText string var subjectText string
if result := <-cchan; result.Err != nil { if channel.Type == model.CHANNEL_DIRECT {
l4g.Error("Failed to retrieve channel channel_id=%v, err=%v", post.ChannelId, result.Err) bodyText = "You have one new message."
return subjectText = "New Direct Message"
} else { } else {
channel = result.Data.(*model.Channel) bodyText = "You have one new mention."
if channel.Type == model.CHANNEL_DIRECT { subjectText = "New Mention"
bodyText = "You have one new message." channelName = channel.DisplayName
subjectText = "New Direct Message"
} else {
bodyText = "You have one new mention."
subjectText = "New Mention"
channelName = channel.DisplayName
}
} }
var mentionedUsers []string var mentionedUsers []string
if result := <-uchan; result.Err != nil { if result := <-uchan; result.Err != nil {
l4g.Error("Failed to retrieve user profiles team_id=%v, err=%v", teamId, result.Err) l4g.Error("Failed to retrieve user profiles team_id=%v, err=%v", c.Session.TeamId, result.Err)
return return
} else { } else {
profileMap := result.Data.(map[string]*model.User) profileMap := result.Data.(map[string]*model.User)
@@ -414,23 +406,15 @@ func fireAndForgetNotifications(post *model.Post, teamId, siteURL string) {
mentionedUsers = append(mentionedUsers, k) mentionedUsers = append(mentionedUsers, k)
} }
var teamDisplayName string teamURL := c.GetSiteURL() + "/" + team.Name
var teamURL string
if result := <-tchan; result.Err != nil {
l4g.Error("Failed to retrieve team team_id=%v, err=%v", teamId, result.Err)
return
} else {
teamDisplayName = result.Data.(*model.Team).DisplayName
teamURL = siteURL + "/" + result.Data.(*model.Team).Name
}
// Build and send the emails // Build and send the emails
location, _ := time.LoadLocation("UTC") location, _ := time.LoadLocation("UTC")
tm := time.Unix(post.CreateAt/1000, 0).In(location) tm := time.Unix(post.CreateAt/1000, 0).In(location)
subjectPage := NewServerTemplatePage("post_subject") subjectPage := NewServerTemplatePage("post_subject")
subjectPage.Props["SiteURL"] = siteURL subjectPage.Props["SiteURL"] = c.GetSiteURL()
subjectPage.Props["TeamDisplayName"] = teamDisplayName subjectPage.Props["TeamDisplayName"] = team.DisplayName
subjectPage.Props["SubjectText"] = subjectText subjectPage.Props["SubjectText"] = subjectText
subjectPage.Props["Month"] = tm.Month().String()[:3] subjectPage.Props["Month"] = tm.Month().String()[:3]
subjectPage.Props["Day"] = fmt.Sprintf("%d", tm.Day()) subjectPage.Props["Day"] = fmt.Sprintf("%d", tm.Day())
@@ -448,9 +432,9 @@ func fireAndForgetNotifications(post *model.Post, teamId, siteURL string) {
} }
bodyPage := NewServerTemplatePage("post_body") bodyPage := NewServerTemplatePage("post_body")
bodyPage.Props["SiteURL"] = siteURL bodyPage.Props["SiteURL"] = c.GetSiteURL()
bodyPage.Props["Nickname"] = profileMap[id].FirstName bodyPage.Props["Nickname"] = profileMap[id].FirstName
bodyPage.Props["TeamDisplayName"] = teamDisplayName bodyPage.Props["TeamDisplayName"] = team.DisplayName
bodyPage.Props["ChannelName"] = channelName bodyPage.Props["ChannelName"] = channelName
bodyPage.Props["BodyText"] = bodyText bodyPage.Props["BodyText"] = bodyText
bodyPage.Props["SenderName"] = senderName bodyPage.Props["SenderName"] = senderName
@@ -517,7 +501,7 @@ func fireAndForgetNotifications(post *model.Post, teamId, siteURL string) {
} }
} }
message := model.NewMessage(teamId, post.ChannelId, post.UserId, model.ACTION_POSTED) message := model.NewMessage(c.Session.TeamId, post.ChannelId, post.UserId, model.ACTION_POSTED)
message.Add("post", post.ToJson()) message.Add("post", post.ToJson())
if len(post.Filenames) != 0 { if len(post.Filenames) != 0 {

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

@@ -11,7 +11,6 @@
"EnablePostIconOverride": false, "EnablePostIconOverride": false,
"EnableTesting": false, "EnableTesting": false,
"EnableSecurityFixAlert": true "EnableSecurityFixAlert": true
"EnableTesting": false
}, },
"TeamSettings": { "TeamSettings": {
"SiteName": "Mattermost", "SiteName": "Mattermost",
@@ -91,4 +90,4 @@
"TokenEndpoint": "", "TokenEndpoint": "",
"UserApiEndpoint": "" "UserApiEndpoint": ""
} }
} }

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

@@ -119,3 +119,17 @@ func (o *OutgoingWebhook) PreSave() {
func (o *OutgoingWebhook) PreUpdate() { func (o *OutgoingWebhook) PreUpdate() {
o.UpdateAt = GetMillis() o.UpdateAt = GetMillis()
} }
func (o *OutgoingWebhook) HasTriggerWord(word string) bool {
if len(o.TriggerWords) == 0 || len(word) == 0 {
return false
}
for _, trigger := range o.TriggerWords {
if trigger == word {
return true
}
}
return false
}

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

@@ -32,7 +32,6 @@ import (
const ( const (
INDEX_TYPE_FULL_TEXT = "full_text" INDEX_TYPE_FULL_TEXT = "full_text"
INDEX_TYPE_PATTERN = "pattern"
INDEX_TYPE_DEFAULT = "default" INDEX_TYPE_DEFAULT = "default"
) )
@@ -376,10 +375,6 @@ func (ss SqlStore) CreateFullTextIndexIfNotExists(indexName string, tableName st
ss.createIndexIfNotExists(indexName, tableName, columnName, INDEX_TYPE_FULL_TEXT) ss.createIndexIfNotExists(indexName, tableName, columnName, INDEX_TYPE_FULL_TEXT)
} }
func (ss SqlStore) CreatePatternIndexIfNotExists(indexName string, tableName string, columnName string) {
ss.createIndexIfNotExists(indexName, tableName, columnName, INDEX_TYPE_PATTERN)
}
func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, columnName string, indexType string) { func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, columnName string, indexType string) {
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES { if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
@@ -392,8 +387,6 @@ func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, co
query := "" query := ""
if indexType == INDEX_TYPE_FULL_TEXT { if indexType == INDEX_TYPE_FULL_TEXT {
query = "CREATE INDEX " + indexName + " ON " + tableName + " USING gin(to_tsvector('english', " + columnName + "))" query = "CREATE INDEX " + indexName + " ON " + tableName + " USING gin(to_tsvector('english', " + columnName + "))"
} else if indexType == INDEX_TYPE_PATTERN {
query = "CREATE INDEX " + indexName + " ON " + tableName + " (" + columnName + " text_pattern_ops)"
} else { } else {
query = "CREATE INDEX " + indexName + " ON " + tableName + " (" + columnName + ")" query = "CREATE INDEX " + indexName + " ON " + tableName + " (" + columnName + ")"
} }
@@ -418,7 +411,7 @@ func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, co
} }
fullTextIndex := "" fullTextIndex := ""
if indexType == INDEX_TYPE_FULL_TEXT || indexType == INDEX_TYPE_PATTERN { if indexType == INDEX_TYPE_FULL_TEXT {
fullTextIndex = " FULLTEXT " fullTextIndex = " FULLTEXT "
} }

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

@@ -5,7 +5,6 @@ package store
import ( import (
"github.com/mattermost/platform/model" "github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
) )
type SqlWebhookStore struct { type SqlWebhookStore struct {
@@ -42,8 +41,6 @@ func (s SqlWebhookStore) CreateIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_incoming_webhook_user_id", "IncomingWebhooks", "UserId") s.CreateIndexIfNotExists("idx_incoming_webhook_user_id", "IncomingWebhooks", "UserId")
s.CreateIndexIfNotExists("idx_incoming_webhook_team_id", "IncomingWebhooks", "TeamId") s.CreateIndexIfNotExists("idx_incoming_webhook_team_id", "IncomingWebhooks", "TeamId")
s.CreateIndexIfNotExists("idx_outgoing_webhook_channel_id", "OutgoingWebhooks", "ChannelId") s.CreateIndexIfNotExists("idx_outgoing_webhook_channel_id", "OutgoingWebhooks", "ChannelId")
s.CreatePatternIndexIfNotExists("idx_outgoing_webhook_trigger_txt", "OutgoingWebhooks", "TriggerWords")
} }
func (s SqlWebhookStore) SaveIncoming(webhook *model.IncomingWebhook) StoreChannel { func (s SqlWebhookStore) SaveIncoming(webhook *model.IncomingWebhook) StoreChannel {
@@ -237,7 +234,7 @@ func (s SqlWebhookStore) GetOutgoingByChannel(channelId string) StoreChannel {
return storeChannel return storeChannel
} }
func (s SqlWebhookStore) GetOutgoingByTriggerWord(teamId, channelId, triggerWord string) StoreChannel { func (s SqlWebhookStore) GetOutgoingByTeam(teamId string) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)
go func() { go func() {
@@ -245,50 +242,8 @@ func (s SqlWebhookStore) GetOutgoingByTriggerWord(teamId, channelId, triggerWord
var webhooks []*model.OutgoingWebhook var webhooks []*model.OutgoingWebhook
var err error if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM OutgoingWebhooks WHERE TeamId = :TeamId AND DeleteAt = 0", map[string]interface{}{"TeamId": teamId}); err != nil {
result.Err = model.NewAppError("SqlWebhookStore.GetOutgoingByTeam", "We couldn't get the webhooks", "teamId="+teamId+", err="+err.Error())
if utils.Cfg.SqlSettings.DriverName == "postgres" {
searchQuery := `SELECT
*
FROM
OutgoingWebhooks
WHERE
DeleteAt = 0
AND TeamId = $1
AND TriggerWords LIKE '%' || $2 || '%'`
if len(channelId) != 0 {
searchQuery += " AND (ChannelId = $3 OR ChannelId = '')"
_, err = s.GetReplica().Select(&webhooks, searchQuery, teamId, triggerWord, channelId)
} else {
searchQuery += " AND ChannelId = ''"
_, err = s.GetReplica().Select(&webhooks, searchQuery, teamId, triggerWord)
}
} else if utils.Cfg.SqlSettings.DriverName == "mysql" {
searchQuery := `SELECT
*
FROM
OutgoingWebhooks
WHERE
DeleteAt = 0
AND TeamId = ?
AND MATCH (TriggerWords) AGAINST (? IN BOOLEAN MODE)`
triggerWord = "+" + triggerWord
if len(channelId) != 0 {
searchQuery += " AND (ChannelId = ? OR ChannelId = '')"
_, err = s.GetReplica().Select(&webhooks, searchQuery, teamId, triggerWord, channelId)
} else {
searchQuery += " AND ChannelId = ''"
_, err = s.GetReplica().Select(&webhooks, searchQuery, teamId, triggerWord)
}
}
if err != nil {
result.Err = model.NewAppError("SqlPostStore.GetOutgoingByTriggerWord", "We encounted an error while getting the outgoing webhooks by trigger word", "teamId="+teamId+", channelId="+channelId+", triggerWord="+triggerWord+", err="+err.Error())
} }
result.Data = webhooks result.Data = webhooks

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

@@ -201,27 +201,18 @@ func TestWebhookStoreGetOutgoingByCreator(t *testing.T) {
} }
} }
func TestWebhookStoreGetOutgoingByTriggerWord(t *testing.T) { func TestWebhookStoreGetOutgoingByTeam(t *testing.T) {
Setup() Setup()
o1 := &model.OutgoingWebhook{} o1 := &model.OutgoingWebhook{}
o1.ChannelId = model.NewId()
o1.CreatorId = model.NewId() o1.CreatorId = model.NewId()
o1.TeamId = model.NewId() o1.TeamId = model.NewId()
o1.TriggerWords = []string{"trigger"}
o1.CallbackURLs = []string{"http://nowhere.com/"} o1.CallbackURLs = []string{"http://nowhere.com/"}
o1 = (<-store.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook) o1 = (<-store.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook)
o2 := &model.OutgoingWebhook{} if r1 := <-store.Webhook().GetOutgoingByTeam(o1.TeamId); r1.Err != nil {
o2.CreatorId = model.NewId()
o2.TeamId = o1.TeamId
o2.ChannelId = model.NewId()
o2.TriggerWords = []string{"trigger"}
o2.CallbackURLs = []string{"http://nowhere.com/"}
o2 = (<-store.Webhook().SaveOutgoing(o2)).Data.(*model.OutgoingWebhook)
if r1 := <-store.Webhook().GetOutgoingByTriggerWord(o1.TeamId, "", "trigger"); r1.Err != nil {
t.Fatal(r1.Err) t.Fatal(r1.Err)
} else { } else {
if r1.Data.([]*model.OutgoingWebhook)[0].CreateAt != o1.CreateAt { if r1.Data.([]*model.OutgoingWebhook)[0].CreateAt != o1.CreateAt {
@@ -229,15 +220,7 @@ func TestWebhookStoreGetOutgoingByTriggerWord(t *testing.T) {
} }
} }
if r1 := <-store.Webhook().GetOutgoingByTriggerWord(o2.TeamId, o2.ChannelId, "trigger"); r1.Err != nil { if result := <-store.Webhook().GetOutgoingByTeam("123"); result.Err != nil {
t.Fatal(r1.Err)
} else {
if len(r1.Data.([]*model.OutgoingWebhook)) != 2 {
t.Fatal("wrong number of webhooks returned")
}
}
if result := <-store.Webhook().GetOutgoingByTriggerWord(o1.TeamId, "", "blargh"); result.Err != nil {
t.Fatal(result.Err) t.Fatal(result.Err)
} else { } else {
if len(result.Data.([]*model.OutgoingWebhook)) != 0 { if len(result.Data.([]*model.OutgoingWebhook)) != 0 {

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

@@ -154,7 +154,7 @@ type WebhookStore interface {
GetOutgoing(id string) StoreChannel GetOutgoing(id string) StoreChannel
GetOutgoingByCreator(userId string) StoreChannel GetOutgoingByCreator(userId string) StoreChannel
GetOutgoingByChannel(channelId string) StoreChannel GetOutgoingByChannel(channelId string) StoreChannel
GetOutgoingByTriggerWord(teamId, channelId, triggerWord string) StoreChannel GetOutgoingByTeam(teamId string) StoreChannel
DeleteOutgoing(webhookId string, time int64) StoreChannel DeleteOutgoing(webhookId string, time int64) StoreChannel
UpdateOutgoing(hook *model.OutgoingWebhook) StoreChannel UpdateOutgoing(hook *model.OutgoingWebhook) StoreChannel
} }