diff options
author | Joram Wilander <jwawilander@gmail.com> | 2016-07-21 10:00:09 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-07-21 10:00:09 -0400 |
commit | bfa04c0ab0eca5d812ad64e5f51e95ec458cf0d3 (patch) | |
tree | 6a51f8d4d144a181192499f5fd60ef82700e9abb | |
parent | f0e9ec2dd127ffe34472c617f978173a8bf60b7c (diff) | |
download | chat-bfa04c0ab0eca5d812ad64e5f51e95ec458cf0d3.tar.gz chat-bfa04c0ab0eca5d812ad64e5f51e95ec458cf0d3.tar.bz2 chat-bfa04c0ab0eca5d812ad64e5f51e95ec458cf0d3.zip |
PLT-2408 Adds here mention for online users (#3619)
* Added @here mention that notifies online users
* Fixed existing race condition that would sometime cause clients to miss mention count changes
* Added missing localization strings
* Prevent @here from mentioning the user who posted it
-rw-r--r-- | api/post.go | 45 | ||||
-rw-r--r-- | api/status.go | 4 | ||||
-rw-r--r-- | i18n/en.json | 10 | ||||
-rw-r--r-- | store/sql_status_store.go | 22 | ||||
-rw-r--r-- | store/sql_status_store_test.go | 11 | ||||
-rw-r--r-- | store/store.go | 1 | ||||
-rw-r--r-- | webapp/components/suggestion/at_mention_provider.jsx | 12 | ||||
-rw-r--r-- | webapp/i18n/en.json | 1 | ||||
-rw-r--r-- | webapp/utils/constants.jsx | 2 | ||||
-rw-r--r-- | webapp/utils/text_formatting.jsx | 1 |
10 files changed, 96 insertions, 13 deletions
diff --git a/api/post.go b/api/post.go index 951ccb527..9bf6cff40 100644 --- a/api/post.go +++ b/api/post.go @@ -477,6 +477,8 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel * mentionedUserIds := make(map[string]bool) alwaysNotifyUserIds := []string{} + hereNotification := false + updateMentionChans := []store.StoreChannel{} if channel.Type == model.CHANNEL_DIRECT { @@ -537,6 +539,11 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel * splitMessage := strings.Fields(post.Message) var userIds []string for _, word := range splitMessage { + if word == "@here" { + hereNotification = true + continue + } + // Non-case-sensitive check for regular keys if ids, match := keywordMap[strings.ToLower(word)]; match { userIds = append(userIds, ids...) @@ -591,7 +598,7 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel * } for id := range mentionedUserIds { - go updateMentionCount(post.ChannelId, id) + updateMentionChans = append(updateMentionChans, Srv.Store.Channel().IncrementMentionCount(post.ChannelId, id)) } } @@ -624,6 +631,28 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel * } } + if hereNotification { + if result := <-Srv.Store.Status().GetOnline(); result.Err != nil { + l4g.Warn(utils.T("api.post.notification.here.warn"), result.Err) + return + } else { + statuses := result.Data.([]*model.Status) + for _, status := range statuses { + if status.UserId == post.UserId { + continue + } + + _, profileFound := profileMap[status.UserId] + _, alreadyAdded := mentionedUserIds[status.UserId] + + if status.Status == model.STATUS_ONLINE && profileFound && !alreadyAdded { + mentionedUsersList = append(mentionedUsersList, status.UserId) + updateMentionChans = append(updateMentionChans, Srv.Store.Channel().IncrementMentionCount(post.ChannelId, status.UserId)) + } + } + } + } + sendPushNotifications := false if *utils.Cfg.EmailSettings.SendPushNotifications { pushServer := *utils.Cfg.EmailSettings.PushNotificationServer @@ -671,6 +700,14 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel * message.Add("mentions", model.ArrayToJson(mentionedUsersList)) } + // Make sure all mention updates are complete to prevent race + // Probably better to batch these DB updates in the future + for _, uchan := range updateMentionChans { + if result := <-uchan; result.Err != nil { + l4g.Warn(utils.T("api.post.update_mention_count_and_forget.update_error"), post.Id, post.ChannelId, result.Err) + } + } + go Publish(message) } @@ -837,12 +874,6 @@ func sendPushNotification(post *model.Post, user *model.User, channel *model.Cha } } -func updateMentionCount(channelId, userId string) { - if result := <-Srv.Store.Channel().IncrementMentionCount(channelId, userId); result.Err != nil { - l4g.Error(utils.T("api.post.update_mention_count_and_forget.update_error"), userId, channelId, result.Err) - } -} - func checkForOutOfChannelMentions(c *Context, post *model.Post, channel *model.Channel, allProfiles map[string]*model.User, members []model.ChannelMember) { // don't check for out of channel mentions in direct channels if channel.Type == model.CHANNEL_DIRECT { diff --git a/api/status.go b/api/status.go index 88f024f4e..2a5a73c4a 100644 --- a/api/status.go +++ b/api/status.go @@ -55,14 +55,12 @@ func GetAllStatuses() (map[string]interface{}, *model.AppError) { func SetStatusOnline(userId string, sessionId string) { broadcast := false - saveStatus := false var status *model.Status var err *model.AppError if status, err = GetStatus(userId); err != nil { status = &model.Status{userId, model.STATUS_ONLINE, model.GetMillis()} broadcast = true - saveStatus = true } else { if status.Status != model.STATUS_ONLINE { broadcast = true @@ -76,7 +74,7 @@ func SetStatusOnline(userId string, sessionId string) { achan := Srv.Store.Session().UpdateLastActivityAt(sessionId, model.GetMillis()) var schan store.StoreChannel - if saveStatus { + if broadcast { schan = Srv.Store.Status().SaveOrUpdate(status) } else { schan = Srv.Store.Status().UpdateLastActivityAt(status.UserId, status.LastActivityAt) diff --git a/i18n/en.json b/i18n/en.json index e735f6dc0..426b1ec1b 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -928,6 +928,10 @@ "translation": "{{.Username}} was mentioned, but they did not receive a notification because they do not belong to this channel." }, { + "id": "api.post.notification.here.warn", + "translation": "Unable to send notification to online users with @here, err=%v" + }, + { "id": "api.post.create_post.bad_filename.error", "translation": "Bad filename discarded, filename=%v" }, @@ -1077,7 +1081,7 @@ }, { "id": "api.post.update_mention_count_and_forget.update_error", - "translation": "Failed to update mention count for user_id=%v on channel_id=%v err=%v" + "translation": "Failed to update mention count, post_id=%v channel_id=%v err=%v" }, { "id": "api.post.update_post.find.app_error", @@ -3904,6 +3908,10 @@ "translation": "Encountered an error retrieving all the online/away statuses" }, { + "id": "store.sql_status.get_online.app_error", + "translation": "Encountered an error retrieving all the online statuses" + }, + { "id": "store.sql_status.get_total_active_users_count.app_error", "translation": "We could not count the active users" }, diff --git a/store/sql_status_store.go b/store/sql_status_store.go index 235d12fa8..bb3c7c364 100644 --- a/store/sql_status_store.go +++ b/store/sql_status_store.go @@ -98,7 +98,27 @@ func (s SqlStatusStore) GetOnlineAway() StoreChannel { var statuses []*model.Status if _, err := s.GetReplica().Select(&statuses, "SELECT * FROM Status WHERE Status = :Online OR Status = :Away", map[string]interface{}{"Online": model.STATUS_ONLINE, "Away": model.STATUS_AWAY}); err != nil { - result.Err = model.NewLocAppError("SqlStatusStore.GetOnline", "store.sql_status.get_online_away.app_error", nil, err.Error()) + result.Err = model.NewLocAppError("SqlStatusStore.GetOnlineAway", "store.sql_status.get_online_away.app_error", nil, err.Error()) + } else { + result.Data = statuses + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + +func (s SqlStatusStore) GetOnline() StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + var statuses []*model.Status + if _, err := s.GetReplica().Select(&statuses, "SELECT * FROM Status WHERE Status = :Online", map[string]interface{}{"Online": model.STATUS_ONLINE}); err != nil { + result.Err = model.NewLocAppError("SqlStatusStore.GetOnline", "store.sql_status.get_online.app_error", nil, err.Error()) } else { result.Data = statuses } diff --git a/store/sql_status_store_test.go b/store/sql_status_store_test.go index e16dc14d0..139915bad 100644 --- a/store/sql_status_store_test.go +++ b/store/sql_status_store_test.go @@ -48,6 +48,17 @@ func TestSqlStatusStore(t *testing.T) { } } + if result := <-store.Status().GetOnline(); result.Err != nil { + t.Fatal(result.Err) + } else { + statuses := result.Data.([]*model.Status) + for _, status := range statuses { + if status.Status != model.STATUS_ONLINE { + t.Fatal("should not have returned offline statuses") + } + } + } + if err := (<-store.Status().ResetAll()).Err; err != nil { t.Fatal(err) } diff --git a/store/store.go b/store/store.go index c495ff927..8efec5e54 100644 --- a/store/store.go +++ b/store/store.go @@ -266,6 +266,7 @@ type StatusStore interface { SaveOrUpdate(status *model.Status) StoreChannel Get(userId string) StoreChannel GetOnlineAway() StoreChannel + GetOnline() StoreChannel ResetAll() StoreChannel GetTotalActiveUsersCount() StoreChannel UpdateLastActivityAt(userId string, lastActivityAt int64) StoreChannel diff --git a/webapp/components/suggestion/at_mention_provider.jsx b/webapp/components/suggestion/at_mention_provider.jsx index b33662420..b9127e8d3 100644 --- a/webapp/components/suggestion/at_mention_provider.jsx +++ b/webapp/components/suggestion/at_mention_provider.jsx @@ -43,6 +43,15 @@ class AtMentionSuggestion extends Suggestion { /> ); icon = <i className='mention__image fa fa-users fa-2x'/>; + } else if (user.username === 'here') { + username = 'here'; + description = ( + <FormattedMessage + id='suggestion.mention.here' + defaultMessage='Notifies everyone in the channel and online' + /> + ); + icon = <i className='mention__image fa fa-users fa-2x'/>; } else { username = user.username; @@ -126,6 +135,9 @@ export default class AtMentionProvider { if ('all'.startsWith(prefix)) { filtered.push({username: 'all'}); } + if ('here'.startsWith(prefix)) { + filtered.push({username: 'here'}); + } } filtered.sort((a, b) => { diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index aee906818..ab7406d45 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -1410,6 +1410,7 @@ "sso_signup.team_error": "Please enter a team name", "suggestion.mention.all": "Notifies everyone in the channel, use in {townsquare} to notify the whole team", "suggestion.mention.channel": "Notifies everyone in the channel", + "suggestion.mention.here": "Notifies everyone in the channel and online", "suggestion.search.private": "Private Groups", "suggestion.search.public": "Public Channels", "team_export_tab.download": "download", diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx index f0b3f30c9..207ec5811 100644 --- a/webapp/utils/constants.jsx +++ b/webapp/utils/constants.jsx @@ -217,7 +217,7 @@ export const Constants = { ONLINE: 'online' }, - SPECIAL_MENTIONS: ['all', 'channel'], + SPECIAL_MENTIONS: ['all', 'channel', 'here'], CHARACTER_LIMIT: 4000, IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'], AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac', 'ogg'], diff --git a/webapp/utils/text_formatting.jsx b/webapp/utils/text_formatting.jsx index 5ada7727f..b304fa75a 100644 --- a/webapp/utils/text_formatting.jsx +++ b/webapp/utils/text_formatting.jsx @@ -207,6 +207,7 @@ function highlightCurrentMentions(text, tokens) { let output = text; const mentionKeys = UserStore.getCurrentMentionKeys(); + mentionKeys.push('@here'); // look for any existing tokens which are self mentions and should be highlighted var newTokens = new Map(); |