From 908ed5555f8a3d37cd057035b2792d66c8b7838a Mon Sep 17 00:00:00 2001 From: Carlos Tadeu Panato Junior Date: Sat, 13 Oct 2018 12:35:57 +0200 Subject: [APIv4] add getChannelMembersTimezone (#9286) * add getChannelMembersTimezone * update per feedback review * add delimeter to error --- api4/channel.go | 22 +++++++++++++++- api4/channel_test.go | 49 +++++++++++++++++++++++++++++++++++ app/channel.go | 18 +++++++++++++ app/channel_test.go | 38 +++++++++++++++++++++++++++ i18n/en.json | 4 +++ model/client4.go | 11 ++++++++ model/user.go | 6 +---- model/utils.go | 23 ++++++++++++++++ store/sqlstore/channel_store.go | 23 +++++++++++++++- store/store.go | 1 + store/storetest/mocks/ChannelStore.go | 15 +++++++++++ 11 files changed, 203 insertions(+), 7 deletions(-) diff --git a/api4/channel.go b/api4/channel.go index 02a7c34b5..e3a5bf703 100644 --- a/api4/channel.go +++ b/api4/channel.go @@ -33,7 +33,7 @@ func (api *API) InitChannel() { api.BaseRoutes.Channel.Handle("", api.ApiSessionRequired(deleteChannel)).Methods("DELETE") api.BaseRoutes.Channel.Handle("/stats", api.ApiSessionRequired(getChannelStats)).Methods("GET") api.BaseRoutes.Channel.Handle("/pinned", api.ApiSessionRequired(getPinnedPosts)).Methods("GET") - + api.BaseRoutes.Channel.Handle("/timezones", api.ApiSessionRequired(getChannelMembersTimezones)).Methods("GET") api.BaseRoutes.ChannelForUser.Handle("/unread", api.ApiSessionRequired(getChannelUnread)).Methods("GET") api.BaseRoutes.ChannelByName.Handle("", api.ApiSessionRequired(getChannelByName)).Methods("GET") @@ -821,6 +821,26 @@ func getChannelMembers(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(members.ToJson())) } +func getChannelMembersTimezones(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireChannelId() + if c.Err != nil { + return + } + + if !c.App.SessionHasPermissionToChannel(c.Session, c.Params.ChannelId, model.PERMISSION_READ_CHANNEL) { + c.SetPermissionError(model.PERMISSION_READ_CHANNEL) + return + } + + membersTimezones, err := c.App.GetChannelMembersTimezones(c.Params.ChannelId) + if err != nil { + c.Err = err + return + } + + w.Write([]byte(model.ArrayToJson(membersTimezones))) +} + func getChannelMembersByIds(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireChannelId() if c.Err != nil { diff --git a/api4/channel_test.go b/api4/channel_test.go index 918b37c0f..d588c0c25 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -2335,3 +2335,52 @@ func TestUpdateChannelScheme(t *testing.T) { _, resp = th.SystemAdminClient.UpdateChannelScheme(channel.Id, channelScheme.Id) CheckUnauthorizedStatus(t, resp) } + +func TestGetChannelMembersTimezones(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + Client := th.Client + + user := th.BasicUser + user.Timezone["useAutomaticTimezone"] = "false" + user.Timezone["manualTimezone"] = "XOXO/BLABLA" + _, resp := Client.UpdateUser(user) + CheckNoError(t, resp) + + user2 := th.BasicUser2 + user2.Timezone["automaticTimezone"] = "NoWhere/Island" + _, resp = th.SystemAdminClient.UpdateUser(user2) + CheckNoError(t, resp) + + timezone, resp := Client.GetChannelMembersTimezones(th.BasicChannel.Id) + CheckNoError(t, resp) + if len(timezone) != 2 { + t.Fatal("should return 2 timezones") + } + + //both users have same timezone + user2.Timezone["automaticTimezone"] = "XOXO/BLABLA" + _, resp = th.SystemAdminClient.UpdateUser(user2) + CheckNoError(t, resp) + + timezone, resp = Client.GetChannelMembersTimezones(th.BasicChannel.Id) + CheckNoError(t, resp) + if len(timezone) != 1 { + t.Fatal("should return 1 timezone") + } + + //no timezone set should return empty + user2.Timezone["automaticTimezone"] = "" + _, resp = th.SystemAdminClient.UpdateUser(user2) + CheckNoError(t, resp) + + user.Timezone["manualTimezone"] = "" + _, resp = Client.UpdateUser(user) + + timezone, resp = Client.GetChannelMembersTimezones(th.BasicChannel.Id) + CheckNoError(t, resp) + if len(timezone) > 0 { + t.Fatal("should return 0 timezone") + } + +} diff --git a/app/channel.go b/app/channel.go index 93037cf05..dee856b94 100644 --- a/app/channel.go +++ b/app/channel.go @@ -1153,6 +1153,24 @@ func (a *App) GetChannelMembersPage(channelId string, page, perPage int) (*model return result.Data.(*model.ChannelMembers), nil } +func (a *App) GetChannelMembersTimezones(channelId string) ([]string, *model.AppError) { + result := <-a.Srv.Store.Channel().GetChannelMembersTimezones(channelId) + if result.Err != nil { + return nil, result.Err + } + membersTimezones := result.Data.([]map[string]string) + + var timezones []string + for _, membersTimezone := range membersTimezones { + if membersTimezone["automaticTimezone"] == "" && membersTimezone["manualTimezone"] == "" { + continue + } + timezones = append(timezones, model.GetPreferredTimezone(membersTimezone)) + } + + return model.RemoveDuplicateStrings(timezones), nil +} + func (a *App) GetChannelMembersByIds(channelId string, userIds []string) (*model.ChannelMembers, *model.AppError) { result := <-a.Srv.Store.Channel().GetMembersByIds(channelId, userIds) if result.Err != nil { diff --git a/app/channel_test.go b/app/channel_test.go index 4b09bbb78..b6f460741 100644 --- a/app/channel_test.go +++ b/app/channel_test.go @@ -4,6 +4,7 @@ package app import ( + "strings" "testing" "github.com/mattermost/mattermost-server/model" @@ -709,3 +710,40 @@ func TestRenameChannel(t *testing.T) { }) } } + +func TestGetChannelMembersTimezones(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + userRequestorId := "" + postRootId := "" + if _, err := th.App.AddChannelMember(th.BasicUser2.Id, th.BasicChannel, userRequestorId, postRootId, false); err != nil { + t.Fatal("Failed to add user to channel. Error: " + err.Message) + } + + user := th.BasicUser + user.Timezone["useAutomaticTimezone"] = "false" + user.Timezone["manualTimezone"] = "XOXO/BLABLA" + th.App.UpdateUser(user, false) + + user2 := th.BasicUser2 + user2.Timezone["automaticTimezone"] = "NoWhere/Island" + th.App.UpdateUser(user2, false) + + user3 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser, _ := th.App.CreateUser(&user3) + th.App.AddUserToChannel(ruser, th.BasicChannel) + + ruser.Timezone["automaticTimezone"] = "NoWhere/Island" + th.App.UpdateUser(ruser, false) + + user4 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser, _ = th.App.CreateUser(&user4) + th.App.AddUserToChannel(ruser, th.BasicChannel) + + timezones, err := th.App.GetChannelMembersTimezones(th.BasicChannel.Id) + if err != nil { + t.Fatal("Failed to get the timezones for a channel. Error: " + err.Error()) + } + assert.Equal(t, 2, len(timezones)) +} diff --git a/i18n/en.json b/i18n/en.json index 6bcfc603f..db325eee0 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -4986,6 +4986,10 @@ "id": "store.sql_channel.get_members.app_error", "translation": "Unable to get the channel members" }, + { + "id": "store.sql_channel.get_timezone.app_error", + "translation": "We couldn't get the channel members timezones" + }, { "id": "store.sql_channel.get_members_by_ids.app_error", "translation": "Unable to get the channel members" diff --git a/model/client4.go b/model/client4.go index 903687ece..6a613b6b3 100644 --- a/model/client4.go +++ b/model/client4.go @@ -1746,6 +1746,17 @@ func (c *Client4) GetChannelStats(channelId string, etag string) (*ChannelStats, } } +// GetChannelMembersTimezones gets a list of timezones for a channel. +func (c *Client4) GetChannelMembersTimezones(channelId string) ([]string, *Response) { + r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/timezones", "") + if err != nil { + return nil, BuildErrorResponse(r, err) + } + + defer closeBody(r) + return ArrayFromJson(r.Body), BuildResponse(r) +} + // GetPinnedPosts gets a list of pinned posts. func (c *Client4) GetPinnedPosts(channelId string, etag string) (*PostList, *Response) { if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/pinned", etag); err != nil { diff --git a/model/user.go b/model/user.go index 781bcae66..06ad9294f 100644 --- a/model/user.go +++ b/model/user.go @@ -499,11 +499,7 @@ func (u *User) IsSAMLUser() bool { } func (u *User) GetPreferredTimezone() string { - if u.Timezone["useAutomaticTimezone"] == "true" { - return u.Timezone["automaticTimezone"] - } - - return u.Timezone["manualTimezone"] + return GetPreferredTimezone(u.Timezone) } // UserFromJson will decode the input and return a User diff --git a/model/utils.go b/model/utils.go index 172b78242..849e529c1 100644 --- a/model/utils.go +++ b/model/utils.go @@ -564,3 +564,26 @@ func IsDomainName(s string) bool { return ok } + +func RemoveDuplicateStrings(in []string) []string { + out := []string{} + seen := make(map[string]bool, len(in)) + + for _, item := range in { + if !seen[item] { + out = append(out, item) + + seen[item] = true + } + } + + return out +} + +func GetPreferredTimezone(timezone StringMap) string { + if timezone["useAutomaticTimezone"] == "true" { + return timezone["automaticTimezone"] + } + + return timezone["manualTimezone"] +} diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index b1886c428..17ef6d4c9 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -1197,7 +1197,7 @@ func (s SqlChannelStore) GetMembers(channelId string, offset, limit int) store.S var dbMembers channelMemberWithSchemeRolesList _, err := s.GetReplica().Select(&dbMembers, CHANNEL_MEMBERS_WITH_SCHEME_SELECT_QUERY+"WHERE ChannelId = :ChannelId LIMIT :Limit OFFSET :Offset", map[string]interface{}{"ChannelId": channelId, "Limit": limit, "Offset": offset}) if err != nil { - result.Err = model.NewAppError("SqlChannelStore.GetMembers", "store.sql_channel.get_members.app_error", nil, "channel_id="+channelId+err.Error(), http.StatusInternalServerError) + result.Err = model.NewAppError("SqlChannelStore.GetMembers", "store.sql_channel.get_members.app_error", nil, "channel_id="+channelId+","+err.Error(), http.StatusInternalServerError) return } @@ -1205,6 +1205,27 @@ func (s SqlChannelStore) GetMembers(channelId string, offset, limit int) store.S }) } +func (s SqlChannelStore) GetChannelMembersTimezones(channelId string) store.StoreChannel { + return store.Do(func(result *store.StoreResult) { + var dbMembersTimezone []map[string]string + _, err := s.GetReplica().Select(&dbMembersTimezone, ` + SELECT + Users.Timezone + FROM + ChannelMembers + LEFT JOIN + Users ON ChannelMembers.UserId = Id + WHERE ChannelId = :ChannelId + `, map[string]interface{}{ + "ChannelId": channelId}) + if err != nil { + result.Err = model.NewAppError("SqlChannelStore.GetChannelMembersTimezones", "store.sql_channel.get_members.app_error", nil, "channel_id="+channelId+","+err.Error(), http.StatusInternalServerError) + return + } + result.Data = dbMembersTimezone + }) +} + func (s SqlChannelStore) GetMember(channelId string, userId string) store.StoreChannel { return store.Do(func(result *store.StoreResult) { var dbMember channelMemberWithSchemeRoles diff --git a/store/store.go b/store/store.go index e21917345..1e153b422 100644 --- a/store/store.go +++ b/store/store.go @@ -147,6 +147,7 @@ type ChannelStore interface { UpdateMember(member *model.ChannelMember) StoreChannel GetMembers(channelId string, offset, limit int) StoreChannel GetMember(channelId string, userId string) StoreChannel + GetChannelMembersTimezones(channelId string) StoreChannel GetAllChannelMembersForUser(userId string, allowFromCache bool, includeDeleted bool) StoreChannel InvalidateAllChannelMembersForUser(userId string) IsUserInChannelUseCache(userId string, channelId string) bool diff --git a/store/storetest/mocks/ChannelStore.go b/store/storetest/mocks/ChannelStore.go index 1c3a2a8f2..9d42d0b65 100644 --- a/store/storetest/mocks/ChannelStore.go +++ b/store/storetest/mocks/ChannelStore.go @@ -442,6 +442,21 @@ func (_m *ChannelStore) GetMember(channelId string, userId string) store.StoreCh return r0 } +func (_m *ChannelStore) GetChannelMembersTimezones(channelId string) store.StoreChannel { + ret := _m.Called(channelId) + + var r0 store.StoreChannel + if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok { + r0 = rf(channelId) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.StoreChannel) + } + } + + return r0 +} + // GetMemberCount provides a mock function with given fields: channelId, allowFromCache func (_m *ChannelStore) GetMemberCount(channelId string, allowFromCache bool) store.StoreChannel { ret := _m.Called(channelId, allowFromCache) -- cgit v1.2.3-1-g7c22