From 3a91d4e5e419a43ff19a0736ce697f8d611d36e3 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Thu, 2 Mar 2017 17:48:56 -0500 Subject: PLT-3077 Add group messaging (#5489) * Implement server changes for group messaging * Majority of client-side implementation * Some server updates * Added new React multiselect component * Fix style issues * Add custom renderer for options * Fix model test * Update ENTER functionality for multiselect control * Remove buttons from multiselect UI control * Updating group messaging UI (#5524) * Move filter controls up a component level * Scroll with arrow keys * Updating mobile layout for multiselect (#5534) * Fix race condition when backspacing quickly * Hidden or new GMs show up for regular messages * Add overriding of number remaining text * Add UI filtering for team if config setting set * Add icon to channel switcher and class prop to status icon * Minor updates per feedback * Improving group messaging UI (#5563) * UX changes per feedback * Update email for group messages * UI fixes for group messaging (#5587) * Fix missing localization string * Add maximum users message when adding members to GM * Fix input clearing on Android * Updating group messaging UI (#5603) * Updating UI for group messaging (#5604) --- app/channel.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++++-- app/email_batching.go | 2 ++ app/notification.go | 38 ++++++++++++++++++++++++--------- app/team.go | 2 +- 4 files changed, 87 insertions(+), 13 deletions(-) (limited to 'app') diff --git a/app/channel.go b/app/channel.go index 533a2f0bb..f037e64c3 100644 --- a/app/channel.go +++ b/app/channel.go @@ -77,7 +77,7 @@ func JoinDefaultChannels(teamId string, user *model.User, channelRole string) *m } func CreateChannelWithUser(channel *model.Channel, userId string) (*model.Channel, *model.AppError) { - if channel.Type == model.CHANNEL_DIRECT { + if channel.IsGroupOrDirect() { return nil, model.NewAppError("CreateChannelWithUser", "api.channel.create_channel.direct_channel.app_error", nil, "", http.StatusBadRequest) } @@ -197,6 +197,60 @@ func WaitForChannelMembership(channelId string, userId string) { } } +func CreateGroupChannel(userIds []string) (*model.Channel, *model.AppError) { + if len(userIds) > model.CHANNEL_GROUP_MAX_USERS || len(userIds) < model.CHANNEL_GROUP_MIN_USERS { + return nil, model.NewAppError("CreateGroupChannel", "api.channel.create_group.bad_size.app_error", nil, "", http.StatusBadRequest) + } + + var users []*model.User + if result := <-Srv.Store.User().GetProfileByIds(userIds, true); result.Err != nil { + return nil, result.Err + } else { + users = result.Data.([]*model.User) + } + + if len(users) != len(userIds) { + return nil, model.NewAppError("CreateGroupChannel", "api.channel.create_group.bad_user.app_error", nil, "user_ids="+model.ArrayToJson(userIds), http.StatusBadRequest) + } + + group := &model.Channel{ + Name: model.GetGroupNameFromUserIds(userIds), + DisplayName: model.GetGroupDisplayNameFromUsers(users, true), + Type: model.CHANNEL_GROUP, + } + + if result := <-Srv.Store.Channel().Save(group); result.Err != nil { + if result.Err.Id == store.CHANNEL_EXISTS_ERROR { + return result.Data.(*model.Channel), nil + } else { + return nil, result.Err + } + } else { + channel := result.Data.(*model.Channel) + + for _, user := range users { + cm := &model.ChannelMember{ + UserId: user.Id, + ChannelId: group.Id, + NotifyProps: model.GetDefaultChannelNotifyProps(), + Roles: model.ROLE_CHANNEL_USER.Id, + } + + if result := <-Srv.Store.Channel().SaveMember(cm); result.Err != nil { + return nil, result.Err + } + + InvalidateCacheForUser(user.Id) + } + + message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_GROUP_ADDED, "", group.Id, "", nil) + message.Add("teammate_ids", model.ArrayToJson(userIds)) + Publish(message) + + return channel, nil + } +} + func UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) { if result := <-Srv.Store.Channel().Update(channel); result.Err != nil { return nil, result.Err @@ -702,7 +756,7 @@ func LeaveChannel(channelId string, userId string) *model.AppError { user := uresult.Data.(*model.User) membersCount := ccmresult.Data.(int64) - if channel.Type == model.CHANNEL_DIRECT { + if channel.IsGroupOrDirect() { err := model.NewLocAppError("LeaveChannel", "api.channel.leave.direct.app_error", nil, "") err.StatusCode = http.StatusBadRequest return err diff --git a/app/email_batching.go b/app/email_batching.go index fc2fb1cea..055656f30 100644 --- a/app/email_batching.go +++ b/app/email_batching.go @@ -244,6 +244,8 @@ func renderBatchedPost(template *utils.HTMLTemplate, post *model.Post, teamName return "" } else if channel := result.Data.(*model.Channel); channel.Type == model.CHANNEL_DIRECT { template.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.direct_message") + } else if channel.Type == model.CHANNEL_GROUP { + template.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.group_message") } else { template.Props["ChannelName"] = channel.DisplayName } diff --git a/app/notification.go b/app/notification.go index e0c5d3d5b..4869560da 100644 --- a/app/notification.go +++ b/app/notification.go @@ -118,6 +118,7 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe } senderName := make(map[string]string) + channelName := make(map[string]string) for _, id := range mentionedUsersList { senderName[id] = "" if post.IsSystemMessage() { @@ -135,6 +136,19 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe } } } + + if channel.Type == model.CHANNEL_GROUP { + userList := []*model.User{} + for _, u := range profileMap { + if u.Id != sender.Id && u.Id != id { + userList = append(userList, u) + } + } + userList = append(userList, sender) + channelName[id] = model.GetGroupDisplayNameFromUsers(userList, false) + } else { + channelName[id] = channel.DisplayName + } } var senderUsername string @@ -259,7 +273,7 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe } if ShouldSendPushNotification(profileMap[id], channelMemberNotifyPropsMap[id], true, status, post) { - sendPushNotification(post, profileMap[id], channel, senderName[id], true) + sendPushNotification(post, profileMap[id], channel, senderName[id], channelName[id], true) } } @@ -272,7 +286,7 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe } if ShouldSendPushNotification(profileMap[id], channelMemberNotifyPropsMap[id], false, status, post) { - sendPushNotification(post, profileMap[id], channel, senderName[id], false) + sendPushNotification(post, profileMap[id], channel, senderName[id], channelName[id], false) } } } @@ -313,8 +327,8 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe } func sendNotificationEmail(post *model.Post, user *model.User, channel *model.Channel, team *model.Team, senderName string, sender *model.User) *model.AppError { - if channel.Type == model.CHANNEL_DIRECT && channel.TeamId != team.Id { - // this message is a cross-team DM so it we need to find a team that the recipient is on to use in the link + if channel.IsGroupOrDirect() && channel.TeamId != team.Id { + // this message is a cross-team DM/GM so we need to find a team that the recipient is on to use in the link if result := <-Srv.Store.Team().GetTeamsByUserId(user.Id); result.Err != nil { return result.Err } else { @@ -381,6 +395,14 @@ func sendNotificationEmail(post *model.Post, user *model.User, channel *model.Ch mailTemplate = "api.templates.post_subject_in_direct_message" mailParameters = map[string]interface{}{"SubjectText": subjectText, "TeamDisplayName": team.DisplayName, "SenderDisplayName": senderDisplayName, "Month": month, "Day": day, "Year": year} + } else if channel.Type == model.CHANNEL_GROUP { + bodyText = userLocale("api.post.send_notifications_and_forget.mention_body") + + senderDisplayName := senderName + + mailTemplate = "api.templates.post_subject_in_group_message" + mailParameters = map[string]interface{}{"SenderDisplayName": senderDisplayName, "Month": month, "Day": day, "Year": year} + channelName = userLocale("api.templates.channel_name.group") } else { bodyText = userLocale("api.post.send_notifications_and_forget.mention_body") subjectText = userLocale("api.post.send_notifications_and_forget.mention_subject") @@ -456,18 +478,14 @@ func GetMessageForNotification(post *model.Post, translateFunc i18n.TranslateFun } } -func sendPushNotification(post *model.Post, user *model.User, channel *model.Channel, senderName string, wasMentioned bool) *model.AppError { +func sendPushNotification(post *model.Post, user *model.User, channel *model.Channel, senderName, channelName string, wasMentioned bool) *model.AppError { sessions, err := getMobileAppSessions(user.Id) if err != nil { return err } - var channelName string - if channel.Type == model.CHANNEL_DIRECT { channelName = senderName - } else { - channelName = channel.DisplayName } userLocale := utils.GetUserTranslations(user.Locale) @@ -495,7 +513,7 @@ func sendPushNotification(post *model.Post, user *model.User, channel *model.Cha if channel.Type == model.CHANNEL_DIRECT { msg.Category = model.CATEGORY_DM msg.Message = senderName + userLocale("api.post.send_notifications_and_forget.push_message") - } else if wasMentioned { + } else if wasMentioned || channel.Type == model.CHANNEL_GROUP { msg.Message = senderName + userLocale("api.post.send_notifications_and_forget.push_mention") + channelName } else { msg.Message = senderName + userLocale("api.post.send_notifications_and_forget.push_non_mention") + channelName diff --git a/app/team.go b/app/team.go index b1e0f1362..60a2f4220 100644 --- a/app/team.go +++ b/app/team.go @@ -438,7 +438,7 @@ func LeaveTeam(team *model.Team, user *model.User) *model.AppError { } for _, channel := range *channelList { - if channel.Type != model.CHANNEL_DIRECT { + if !channel.IsGroupOrDirect() { InvalidateCacheForChannelMembers(channel.Id) if result := <-Srv.Store.Channel().RemoveMember(channel.Id, user.Id); result.Err != nil { return result.Err -- cgit v1.2.3-1-g7c22