summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/post.go45
-rw-r--r--api/status.go4
-rw-r--r--i18n/en.json10
-rw-r--r--store/sql_status_store.go22
-rw-r--r--store/sql_status_store_test.go11
-rw-r--r--store/store.go1
-rw-r--r--webapp/components/suggestion/at_mention_provider.jsx12
-rw-r--r--webapp/i18n/en.json1
-rw-r--r--webapp/utils/constants.jsx2
-rw-r--r--webapp/utils/text_formatting.jsx1
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();